Refactor WebsiteSettings to operate on a SecurityInfo
[chromium-blink-merge.git] / content / renderer / manifest / manifest_parser.cc
blob417188d58af4379bb264739796b2b91628c48a4c
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/renderer/manifest/manifest_parser.h"
7 #include "base/json/json_reader.h"
8 #include "base/strings/nullable_string16.h"
9 #include "base/strings/string_number_conversions.h"
10 #include "base/strings/string_split.h"
11 #include "base/strings/string_util.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "base/values.h"
14 #include "content/public/common/manifest.h"
15 #include "content/renderer/manifest/manifest_uma_util.h"
16 #include "third_party/WebKit/public/platform/WebColor.h"
17 #include "third_party/WebKit/public/platform/WebString.h"
18 #include "third_party/WebKit/public/web/WebCSSParser.h"
19 #include "ui/gfx/geometry/size.h"
21 namespace content {
23 namespace {
25 // Helper function that returns whether the given |str| is a valid width or
26 // height value for an icon sizes per:
27 // https://html.spec.whatwg.org/multipage/semantics.html#attr-link-sizes
28 bool IsValidIconWidthOrHeight(const std::string& str) {
29 if (str.empty() || str[0] == '0')
30 return false;
31 for (size_t i = 0; i < str.size(); ++i)
32 if (!base::IsAsciiDigit(str[i]))
33 return false;
34 return true;
37 // Parses the 'sizes' attribute of an icon as described in the HTML spec:
38 // https://html.spec.whatwg.org/multipage/semantics.html#attr-link-sizes
39 // Return a vector of gfx::Size that contains the valid sizes found. "Any" is
40 // represented by gfx::Size(0, 0).
41 // TODO(mlamouri): this is implemented as a separate function because it should
42 // be refactored with the other icon sizes parsing implementations, see
43 // http://crbug.com/416477
44 std::vector<gfx::Size> ParseIconSizesHTML(const base::string16& sizes_str16) {
45 if (!base::IsStringASCII(sizes_str16))
46 return std::vector<gfx::Size>();
48 std::vector<gfx::Size> sizes;
49 std::string sizes_str =
50 base::ToLowerASCII(base::UTF16ToUTF8(sizes_str16));
51 std::vector<std::string> sizes_str_list = base::SplitString(
52 sizes_str, base::kWhitespaceASCII, base::KEEP_WHITESPACE,
53 base::SPLIT_WANT_NONEMPTY);
55 for (size_t i = 0; i < sizes_str_list.size(); ++i) {
56 std::string& size_str = sizes_str_list[i];
57 if (size_str == "any") {
58 sizes.push_back(gfx::Size(0, 0));
59 continue;
62 // It is expected that [0] => width and [1] => height after the split.
63 std::vector<std::string> size_list = base::SplitString(
64 size_str, "x", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
65 if (size_list.size() != 2)
66 continue;
67 if (!IsValidIconWidthOrHeight(size_list[0]) ||
68 !IsValidIconWidthOrHeight(size_list[1])) {
69 continue;
72 int width, height;
73 if (!base::StringToInt(size_list[0], &width) ||
74 !base::StringToInt(size_list[1], &height)) {
75 continue;
78 sizes.push_back(gfx::Size(width, height));
81 return sizes;
84 const std::string& GetErrorPrefix() {
85 CR_DEFINE_STATIC_LOCAL(std::string, error_prefix,
86 ("Manifest parsing error: "));
87 return error_prefix;
90 } // anonymous namespace
93 ManifestParser::ManifestParser(const base::StringPiece& data,
94 const GURL& manifest_url,
95 const GURL& document_url)
96 : data_(data),
97 manifest_url_(manifest_url),
98 document_url_(document_url),
99 failed_(false) {
102 ManifestParser::~ManifestParser() {
105 void ManifestParser::Parse() {
106 std::string parse_error;
107 scoped_ptr<base::Value> value = base::JSONReader::ReadAndReturnError(
108 data_, base::JSON_PARSE_RFC, nullptr, &parse_error);
110 if (!value) {
111 errors_.push_back(GetErrorPrefix() + parse_error);
112 ManifestUmaUtil::ParseFailed();
113 failed_ = true;
114 return;
117 base::DictionaryValue* dictionary = nullptr;
118 if (!value->GetAsDictionary(&dictionary)) {
119 errors_.push_back(GetErrorPrefix() +
120 "root element must be a valid JSON object.");
121 ManifestUmaUtil::ParseFailed();
122 failed_ = true;
123 return;
125 DCHECK(dictionary);
127 manifest_.name = ParseName(*dictionary);
128 manifest_.short_name = ParseShortName(*dictionary);
129 manifest_.start_url = ParseStartURL(*dictionary);
130 manifest_.display = ParseDisplay(*dictionary);
131 manifest_.orientation = ParseOrientation(*dictionary);
132 manifest_.icons = ParseIcons(*dictionary);
133 manifest_.related_applications = ParseRelatedApplications(*dictionary);
134 manifest_.prefer_related_applications =
135 ParsePreferRelatedApplications(*dictionary);
136 manifest_.theme_color = ParseThemeColor(*dictionary);
137 manifest_.background_color = ParseBackgroundColor(*dictionary);
138 manifest_.gcm_sender_id = ParseGCMSenderID(*dictionary);
140 ManifestUmaUtil::ParseSucceeded(manifest_);
143 const Manifest& ManifestParser::manifest() const {
144 return manifest_;
147 const std::vector<std::string>& ManifestParser::errors() const {
148 return errors_;
151 bool ManifestParser::failed() const {
152 return failed_;
155 bool ManifestParser::ParseBoolean(const base::DictionaryValue& dictionary,
156 const std::string& key,
157 bool default_value) {
158 if (!dictionary.HasKey(key))
159 return default_value;
161 bool value;
162 if (!dictionary.GetBoolean(key, &value)) {
163 errors_.push_back(GetErrorPrefix() +
164 "property '" + key + "' ignored, type boolean expected.");
165 return default_value;
168 return value;
171 base::NullableString16 ManifestParser::ParseString(
172 const base::DictionaryValue& dictionary,
173 const std::string& key,
174 TrimType trim) {
175 if (!dictionary.HasKey(key))
176 return base::NullableString16();
178 base::string16 value;
179 if (!dictionary.GetString(key, &value)) {
180 errors_.push_back(GetErrorPrefix() +
181 "property '" + key + "' ignored, type string expected.");
182 return base::NullableString16();
185 if (trim == Trim)
186 base::TrimWhitespace(value, base::TRIM_ALL, &value);
187 return base::NullableString16(value, false);
190 int64_t ManifestParser::ParseColor(
191 const base::DictionaryValue& dictionary,
192 const std::string& key) {
193 base::NullableString16 parsed_color = ParseString(dictionary, key, Trim);
194 if (parsed_color.is_null())
195 return Manifest::kInvalidOrMissingColor;
197 blink::WebColor color;
198 if (!blink::WebCSSParser::parseColor(&color, parsed_color.string())) {
199 errors_.push_back(GetErrorPrefix() +
200 "property '" + key + "' ignored, '" +
201 base::UTF16ToUTF8(parsed_color.string()) +
202 "' is not a valid color.");
203 return Manifest::kInvalidOrMissingColor;
206 // We do this here because Java does not have an unsigned int32 type so colors
207 // with high alpha values will be negative. Instead of doing the conversion
208 // after we pass over to Java, we do it here as it is easier and clearer.
209 int32_t signed_color = reinterpret_cast<int32_t&>(color);
210 return static_cast<int64_t>(signed_color);
213 GURL ManifestParser::ParseURL(const base::DictionaryValue& dictionary,
214 const std::string& key,
215 const GURL& base_url) {
216 base::NullableString16 url_str = ParseString(dictionary, key, NoTrim);
217 if (url_str.is_null())
218 return GURL();
220 return base_url.Resolve(url_str.string());
223 base::NullableString16 ManifestParser::ParseName(
224 const base::DictionaryValue& dictionary) {
225 return ParseString(dictionary, "name", Trim);
228 base::NullableString16 ManifestParser::ParseShortName(
229 const base::DictionaryValue& dictionary) {
230 return ParseString(dictionary, "short_name", Trim);
233 GURL ManifestParser::ParseStartURL(const base::DictionaryValue& dictionary) {
234 GURL start_url = ParseURL(dictionary, "start_url", manifest_url_);
235 if (!start_url.is_valid())
236 return GURL();
238 if (start_url.GetOrigin() != document_url_.GetOrigin()) {
239 errors_.push_back(GetErrorPrefix() + "property 'start_url' ignored, should "
240 "be same origin as document.");
241 return GURL();
244 return start_url;
247 blink::WebDisplayMode ManifestParser::ParseDisplay(
248 const base::DictionaryValue& dictionary) {
249 base::NullableString16 display = ParseString(dictionary, "display", Trim);
250 if (display.is_null())
251 return blink::WebDisplayModeUndefined;
253 if (base::LowerCaseEqualsASCII(display.string(), "fullscreen"))
254 return blink::WebDisplayModeFullscreen;
255 else if (base::LowerCaseEqualsASCII(display.string(), "standalone"))
256 return blink::WebDisplayModeStandalone;
257 else if (base::LowerCaseEqualsASCII(display.string(), "minimal-ui"))
258 return blink::WebDisplayModeMinimalUi;
259 else if (base::LowerCaseEqualsASCII(display.string(), "browser"))
260 return blink::WebDisplayModeBrowser;
261 else {
262 errors_.push_back(GetErrorPrefix() + "unknown 'display' value ignored.");
263 return blink::WebDisplayModeUndefined;
267 blink::WebScreenOrientationLockType ManifestParser::ParseOrientation(
268 const base::DictionaryValue& dictionary) {
269 base::NullableString16 orientation =
270 ParseString(dictionary, "orientation", Trim);
272 if (orientation.is_null())
273 return blink::WebScreenOrientationLockDefault;
275 if (base::LowerCaseEqualsASCII(orientation.string(), "any"))
276 return blink::WebScreenOrientationLockAny;
277 else if (base::LowerCaseEqualsASCII(orientation.string(), "natural"))
278 return blink::WebScreenOrientationLockNatural;
279 else if (base::LowerCaseEqualsASCII(orientation.string(), "landscape"))
280 return blink::WebScreenOrientationLockLandscape;
281 else if (base::LowerCaseEqualsASCII(orientation.string(),
282 "landscape-primary"))
283 return blink::WebScreenOrientationLockLandscapePrimary;
284 else if (base::LowerCaseEqualsASCII(orientation.string(),
285 "landscape-secondary"))
286 return blink::WebScreenOrientationLockLandscapeSecondary;
287 else if (base::LowerCaseEqualsASCII(orientation.string(), "portrait"))
288 return blink::WebScreenOrientationLockPortrait;
289 else if (base::LowerCaseEqualsASCII(orientation.string(),
290 "portrait-primary"))
291 return blink::WebScreenOrientationLockPortraitPrimary;
292 else if (base::LowerCaseEqualsASCII(orientation.string(),
293 "portrait-secondary"))
294 return blink::WebScreenOrientationLockPortraitSecondary;
295 else {
296 errors_.push_back(GetErrorPrefix() +
297 "unknown 'orientation' value ignored.");
298 return blink::WebScreenOrientationLockDefault;
302 GURL ManifestParser::ParseIconSrc(const base::DictionaryValue& icon) {
303 return ParseURL(icon, "src", manifest_url_);
306 base::NullableString16 ManifestParser::ParseIconType(
307 const base::DictionaryValue& icon) {
308 return ParseString(icon, "type", Trim);
311 double ManifestParser::ParseIconDensity(const base::DictionaryValue& icon) {
312 double density;
313 if (!icon.HasKey("density"))
314 return Manifest::Icon::kDefaultDensity;
316 if (!icon.GetDouble("density", &density) || density <= 0) {
317 errors_.push_back(GetErrorPrefix() +
318 "icon 'density' ignored, must be float greater than 0.");
319 return Manifest::Icon::kDefaultDensity;
321 return density;
324 std::vector<gfx::Size> ManifestParser::ParseIconSizes(
325 const base::DictionaryValue& icon) {
326 base::NullableString16 sizes_str = ParseString(icon, "sizes", NoTrim);
328 if (sizes_str.is_null())
329 return std::vector<gfx::Size>();
331 std::vector<gfx::Size> sizes = ParseIconSizesHTML(sizes_str.string());
332 if (sizes.empty()) {
333 errors_.push_back(GetErrorPrefix() + "found icon with no valid size.");
335 return sizes;
338 std::vector<Manifest::Icon> ManifestParser::ParseIcons(
339 const base::DictionaryValue& dictionary) {
340 std::vector<Manifest::Icon> icons;
341 if (!dictionary.HasKey("icons"))
342 return icons;
344 const base::ListValue* icons_list = nullptr;
345 if (!dictionary.GetList("icons", &icons_list)) {
346 errors_.push_back(GetErrorPrefix() +
347 "property 'icons' ignored, type array expected.");
348 return icons;
351 for (size_t i = 0; i < icons_list->GetSize(); ++i) {
352 const base::DictionaryValue* icon_dictionary = nullptr;
353 if (!icons_list->GetDictionary(i, &icon_dictionary))
354 continue;
356 Manifest::Icon icon;
357 icon.src = ParseIconSrc(*icon_dictionary);
358 // An icon MUST have a valid src. If it does not, it MUST be ignored.
359 if (!icon.src.is_valid())
360 continue;
361 icon.type = ParseIconType(*icon_dictionary);
362 icon.density = ParseIconDensity(*icon_dictionary);
363 icon.sizes = ParseIconSizes(*icon_dictionary);
365 icons.push_back(icon);
368 return icons;
371 base::NullableString16 ManifestParser::ParseRelatedApplicationPlatform(
372 const base::DictionaryValue& application) {
373 return ParseString(application, "platform", Trim);
376 GURL ManifestParser::ParseRelatedApplicationURL(
377 const base::DictionaryValue& application) {
378 return ParseURL(application, "url", manifest_url_);
381 base::NullableString16 ManifestParser::ParseRelatedApplicationId(
382 const base::DictionaryValue& application) {
383 return ParseString(application, "id", Trim);
386 std::vector<Manifest::RelatedApplication>
387 ManifestParser::ParseRelatedApplications(
388 const base::DictionaryValue& dictionary) {
389 std::vector<Manifest::RelatedApplication> applications;
390 if (!dictionary.HasKey("related_applications"))
391 return applications;
393 const base::ListValue* applications_list = nullptr;
394 if (!dictionary.GetList("related_applications", &applications_list)) {
395 errors_.push_back(
396 GetErrorPrefix() +
397 "property 'related_applications' ignored, type array expected.");
398 return applications;
401 for (size_t i = 0; i < applications_list->GetSize(); ++i) {
402 const base::DictionaryValue* application_dictionary = nullptr;
403 if (!applications_list->GetDictionary(i, &application_dictionary))
404 continue;
406 Manifest::RelatedApplication application;
407 application.platform =
408 ParseRelatedApplicationPlatform(*application_dictionary);
409 // "If platform is undefined, move onto the next item if any are left."
410 if (application.platform.is_null()) {
411 errors_.push_back(
412 GetErrorPrefix() +
413 "'platform' is a required field, related application ignored.");
414 continue;
417 application.id = ParseRelatedApplicationId(*application_dictionary);
418 application.url = ParseRelatedApplicationURL(*application_dictionary);
419 // "If both id and url are undefined, move onto the next item if any are
420 // left."
421 if (application.url.is_empty() && application.id.is_null()) {
422 errors_.push_back(
423 GetErrorPrefix() +
424 "one of 'url' or 'id' is required, related application ignored.");
425 continue;
428 applications.push_back(application);
431 return applications;
434 bool ManifestParser::ParsePreferRelatedApplications(
435 const base::DictionaryValue& dictionary) {
436 return ParseBoolean(dictionary, "prefer_related_applications", false);
439 int64_t ManifestParser::ParseThemeColor(
440 const base::DictionaryValue& dictionary) {
441 return ParseColor(dictionary, "theme_color");
444 int64_t ManifestParser::ParseBackgroundColor(
445 const base::DictionaryValue& dictionary) {
446 return ParseColor(dictionary, "background_color");
449 base::NullableString16 ManifestParser::ParseGCMSenderID(
450 const base::DictionaryValue& dictionary) {
451 return ParseString(dictionary, "gcm_sender_id", Trim);
454 } // namespace content