Fix a couple bugs in the settings_page_header wrt breadcrumbs.
[chromium-blink-merge.git] / content / renderer / manifest / manifest_parser.cc
blob02a5c9c5bedf461464ffc68ee4fba7c45ca20170
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 "ui/gfx/geometry/size.h"
18 namespace content {
20 namespace {
22 // Helper function that returns whether the given |str| is a valid width or
23 // height value for an icon sizes per:
24 // https://html.spec.whatwg.org/multipage/semantics.html#attr-link-sizes
25 bool IsValidIconWidthOrHeight(const std::string& str) {
26 if (str.empty() || str[0] == '0')
27 return false;
28 for (size_t i = 0; i < str.size(); ++i)
29 if (!IsAsciiDigit(str[i]))
30 return false;
31 return true;
34 // Parses the 'sizes' attribute of an icon as described in the HTML spec:
35 // https://html.spec.whatwg.org/multipage/semantics.html#attr-link-sizes
36 // Return a vector of gfx::Size that contains the valid sizes found. "Any" is
37 // represented by gfx::Size(0, 0).
38 // TODO(mlamouri): this is implemented as a separate function because it should
39 // be refactored with the other icon sizes parsing implementations, see
40 // http://crbug.com/416477
41 std::vector<gfx::Size> ParseIconSizesHTML(const base::string16& sizes_str16) {
42 if (!base::IsStringASCII(sizes_str16))
43 return std::vector<gfx::Size>();
45 std::vector<gfx::Size> sizes;
46 std::string sizes_str =
47 base::StringToLowerASCII(base::UTF16ToUTF8(sizes_str16));
48 std::vector<std::string> sizes_str_list;
49 base::SplitStringAlongWhitespace(sizes_str, &sizes_str_list);
51 for (size_t i = 0; i < sizes_str_list.size(); ++i) {
52 std::string& size_str = sizes_str_list[i];
53 if (size_str == "any") {
54 sizes.push_back(gfx::Size(0, 0));
55 continue;
58 // It is expected that [0] => width and [1] => height after the split.
59 std::vector<std::string> size_list;
60 base::SplitStringDontTrim(size_str, L'x', &size_list);
61 if (size_list.size() != 2)
62 continue;
63 if (!IsValidIconWidthOrHeight(size_list[0]) ||
64 !IsValidIconWidthOrHeight(size_list[1])) {
65 continue;
68 int width, height;
69 if (!base::StringToInt(size_list[0], &width) ||
70 !base::StringToInt(size_list[1], &height)) {
71 continue;
74 sizes.push_back(gfx::Size(width, height));
77 return sizes;
80 const std::string& GetErrorPrefix() {
81 CR_DEFINE_STATIC_LOCAL(std::string, error_prefix,
82 ("Manifest parsing error: "));
83 return error_prefix;
86 } // anonymous namespace
89 ManifestParser::ManifestParser(const base::StringPiece& data,
90 const GURL& manifest_url,
91 const GURL& document_url)
92 : data_(data),
93 manifest_url_(manifest_url),
94 document_url_(document_url),
95 failed_(false) {
98 ManifestParser::~ManifestParser() {
101 void ManifestParser::Parse() {
102 std::string parse_error;
103 scoped_ptr<base::Value> value(base::JSONReader::DeprecatedReadAndReturnError(
104 data_, base::JSON_PARSE_RFC, nullptr, &parse_error));
106 if (!value) {
107 errors_.push_back(GetErrorPrefix() + parse_error);
108 ManifestUmaUtil::ParseFailed();
109 failed_ = true;
110 return;
113 base::DictionaryValue* dictionary = nullptr;
114 if (!value->GetAsDictionary(&dictionary)) {
115 errors_.push_back(GetErrorPrefix() +
116 "root element must be a valid JSON object.");
117 ManifestUmaUtil::ParseFailed();
118 failed_ = true;
119 return;
121 DCHECK(dictionary);
123 manifest_.name = ParseName(*dictionary);
124 manifest_.short_name = ParseShortName(*dictionary);
125 manifest_.start_url = ParseStartURL(*dictionary);
126 manifest_.display = ParseDisplay(*dictionary);
127 manifest_.orientation = ParseOrientation(*dictionary);
128 manifest_.icons = ParseIcons(*dictionary);
129 manifest_.related_applications = ParseRelatedApplications(*dictionary);
130 manifest_.prefer_related_applications =
131 ParsePreferRelatedApplications(*dictionary);
132 manifest_.gcm_sender_id = ParseGCMSenderID(*dictionary);
134 ManifestUmaUtil::ParseSucceeded(manifest_);
137 const Manifest& ManifestParser::manifest() const {
138 return manifest_;
141 const std::vector<std::string>& ManifestParser::errors() const {
142 return errors_;
145 bool ManifestParser::failed() const {
146 return failed_;
149 bool ManifestParser::ParseBoolean(const base::DictionaryValue& dictionary,
150 const std::string& key,
151 bool default_value) {
152 if (!dictionary.HasKey(key))
153 return default_value;
155 bool value;
156 if (!dictionary.GetBoolean(key, &value)) {
157 errors_.push_back(GetErrorPrefix() +
158 "property '" + key + "' ignored, type boolean expected.");
159 return default_value;
162 return value;
165 base::NullableString16 ManifestParser::ParseString(
166 const base::DictionaryValue& dictionary,
167 const std::string& key,
168 TrimType trim) {
169 if (!dictionary.HasKey(key))
170 return base::NullableString16();
172 base::string16 value;
173 if (!dictionary.GetString(key, &value)) {
174 errors_.push_back(GetErrorPrefix() +
175 "property '" + key + "' ignored, type string expected.");
176 return base::NullableString16();
179 if (trim == Trim)
180 base::TrimWhitespace(value, base::TRIM_ALL, &value);
181 return base::NullableString16(value, false);
184 GURL ManifestParser::ParseURL(const base::DictionaryValue& dictionary,
185 const std::string& key,
186 const GURL& base_url) {
187 base::NullableString16 url_str = ParseString(dictionary, key, NoTrim);
188 if (url_str.is_null())
189 return GURL();
191 return base_url.Resolve(url_str.string());
194 base::NullableString16 ManifestParser::ParseName(
195 const base::DictionaryValue& dictionary) {
196 return ParseString(dictionary, "name", Trim);
199 base::NullableString16 ManifestParser::ParseShortName(
200 const base::DictionaryValue& dictionary) {
201 return ParseString(dictionary, "short_name", Trim);
204 GURL ManifestParser::ParseStartURL(const base::DictionaryValue& dictionary) {
205 GURL start_url = ParseURL(dictionary, "start_url", manifest_url_);
206 if (!start_url.is_valid())
207 return GURL();
209 if (start_url.GetOrigin() != document_url_.GetOrigin()) {
210 errors_.push_back(GetErrorPrefix() + "property 'start_url' ignored, should "
211 "be same origin as document.");
212 return GURL();
215 return start_url;
218 Manifest::DisplayMode ManifestParser::ParseDisplay(
219 const base::DictionaryValue& dictionary) {
220 base::NullableString16 display = ParseString(dictionary, "display", Trim);
221 if (display.is_null())
222 return Manifest::DISPLAY_MODE_UNSPECIFIED;
224 if (LowerCaseEqualsASCII(display.string(), "fullscreen"))
225 return Manifest::DISPLAY_MODE_FULLSCREEN;
226 else if (LowerCaseEqualsASCII(display.string(), "standalone"))
227 return Manifest::DISPLAY_MODE_STANDALONE;
228 else if (LowerCaseEqualsASCII(display.string(), "minimal-ui"))
229 return Manifest::DISPLAY_MODE_MINIMAL_UI;
230 else if (LowerCaseEqualsASCII(display.string(), "browser"))
231 return Manifest::DISPLAY_MODE_BROWSER;
232 else {
233 errors_.push_back(GetErrorPrefix() + "unknown 'display' value ignored.");
234 return Manifest::DISPLAY_MODE_UNSPECIFIED;
238 blink::WebScreenOrientationLockType ManifestParser::ParseOrientation(
239 const base::DictionaryValue& dictionary) {
240 base::NullableString16 orientation =
241 ParseString(dictionary, "orientation", Trim);
243 if (orientation.is_null())
244 return blink::WebScreenOrientationLockDefault;
246 if (LowerCaseEqualsASCII(orientation.string(), "any"))
247 return blink::WebScreenOrientationLockAny;
248 else if (LowerCaseEqualsASCII(orientation.string(), "natural"))
249 return blink::WebScreenOrientationLockNatural;
250 else if (LowerCaseEqualsASCII(orientation.string(), "landscape"))
251 return blink::WebScreenOrientationLockLandscape;
252 else if (LowerCaseEqualsASCII(orientation.string(), "landscape-primary"))
253 return blink::WebScreenOrientationLockLandscapePrimary;
254 else if (LowerCaseEqualsASCII(orientation.string(), "landscape-secondary"))
255 return blink::WebScreenOrientationLockLandscapeSecondary;
256 else if (LowerCaseEqualsASCII(orientation.string(), "portrait"))
257 return blink::WebScreenOrientationLockPortrait;
258 else if (LowerCaseEqualsASCII(orientation.string(), "portrait-primary"))
259 return blink::WebScreenOrientationLockPortraitPrimary;
260 else if (LowerCaseEqualsASCII(orientation.string(), "portrait-secondary"))
261 return blink::WebScreenOrientationLockPortraitSecondary;
262 else {
263 errors_.push_back(GetErrorPrefix() +
264 "unknown 'orientation' value ignored.");
265 return blink::WebScreenOrientationLockDefault;
269 GURL ManifestParser::ParseIconSrc(const base::DictionaryValue& icon) {
270 return ParseURL(icon, "src", manifest_url_);
273 base::NullableString16 ManifestParser::ParseIconType(
274 const base::DictionaryValue& icon) {
275 return ParseString(icon, "type", Trim);
278 double ManifestParser::ParseIconDensity(const base::DictionaryValue& icon) {
279 double density;
280 if (!icon.HasKey("density"))
281 return Manifest::Icon::kDefaultDensity;
283 if (!icon.GetDouble("density", &density) || density <= 0) {
284 errors_.push_back(GetErrorPrefix() +
285 "icon 'density' ignored, must be float greater than 0.");
286 return Manifest::Icon::kDefaultDensity;
288 return density;
291 std::vector<gfx::Size> ManifestParser::ParseIconSizes(
292 const base::DictionaryValue& icon) {
293 base::NullableString16 sizes_str = ParseString(icon, "sizes", NoTrim);
295 if (sizes_str.is_null())
296 return std::vector<gfx::Size>();
298 std::vector<gfx::Size> sizes = ParseIconSizesHTML(sizes_str.string());
299 if (sizes.empty()) {
300 errors_.push_back(GetErrorPrefix() + "found icon with no valid size.");
302 return sizes;
305 std::vector<Manifest::Icon> ManifestParser::ParseIcons(
306 const base::DictionaryValue& dictionary) {
307 std::vector<Manifest::Icon> icons;
308 if (!dictionary.HasKey("icons"))
309 return icons;
311 const base::ListValue* icons_list = nullptr;
312 if (!dictionary.GetList("icons", &icons_list)) {
313 errors_.push_back(GetErrorPrefix() +
314 "property 'icons' ignored, type array expected.");
315 return icons;
318 for (size_t i = 0; i < icons_list->GetSize(); ++i) {
319 const base::DictionaryValue* icon_dictionary = nullptr;
320 if (!icons_list->GetDictionary(i, &icon_dictionary))
321 continue;
323 Manifest::Icon icon;
324 icon.src = ParseIconSrc(*icon_dictionary);
325 // An icon MUST have a valid src. If it does not, it MUST be ignored.
326 if (!icon.src.is_valid())
327 continue;
328 icon.type = ParseIconType(*icon_dictionary);
329 icon.density = ParseIconDensity(*icon_dictionary);
330 icon.sizes = ParseIconSizes(*icon_dictionary);
332 icons.push_back(icon);
335 return icons;
338 base::NullableString16 ManifestParser::ParseRelatedApplicationPlatform(
339 const base::DictionaryValue& application) {
340 return ParseString(application, "platform", Trim);
343 GURL ManifestParser::ParseRelatedApplicationURL(
344 const base::DictionaryValue& application) {
345 return ParseURL(application, "url", manifest_url_);
348 base::NullableString16 ManifestParser::ParseRelatedApplicationId(
349 const base::DictionaryValue& application) {
350 return ParseString(application, "id", Trim);
353 std::vector<Manifest::RelatedApplication>
354 ManifestParser::ParseRelatedApplications(
355 const base::DictionaryValue& dictionary) {
356 std::vector<Manifest::RelatedApplication> applications;
357 if (!dictionary.HasKey("related_applications"))
358 return applications;
360 const base::ListValue* applications_list = nullptr;
361 if (!dictionary.GetList("related_applications", &applications_list)) {
362 errors_.push_back(
363 GetErrorPrefix() +
364 "property 'related_applications' ignored, type array expected.");
365 return applications;
368 for (size_t i = 0; i < applications_list->GetSize(); ++i) {
369 const base::DictionaryValue* application_dictionary = nullptr;
370 if (!applications_list->GetDictionary(i, &application_dictionary))
371 continue;
373 Manifest::RelatedApplication application;
374 application.platform =
375 ParseRelatedApplicationPlatform(*application_dictionary);
376 // "If platform is undefined, move onto the next item if any are left."
377 if (application.platform.is_null()) {
378 errors_.push_back(
379 GetErrorPrefix() +
380 "'platform' is a required field, related application ignored.");
381 continue;
384 application.id = ParseRelatedApplicationId(*application_dictionary);
385 application.url = ParseRelatedApplicationURL(*application_dictionary);
386 // "If both id and url are undefined, move onto the next item if any are
387 // left."
388 if (application.url.is_empty() && application.id.is_null()) {
389 errors_.push_back(
390 GetErrorPrefix() +
391 "one of 'url' or 'id' is required, related application ignored.");
392 continue;
395 applications.push_back(application);
398 return applications;
401 bool ManifestParser::ParsePreferRelatedApplications(
402 const base::DictionaryValue& dictionary) {
403 return ParseBoolean(dictionary, "prefer_related_applications", false);
406 base::NullableString16 ManifestParser::ParseGCMSenderID(
407 const base::DictionaryValue& dictionary) {
408 return ParseString(dictionary, "gcm_sender_id", Trim);
411 } // namespace content