Add ICU message format support
[chromium-blink-merge.git] / content / renderer / manifest / manifest_parser.cc
blob6b021d2a7a30286f1cbd292e161a3c5e5f495f71
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::StringToLowerASCII(base::UTF16ToUTF8(sizes_str16));
51 std::vector<std::string> sizes_str_list;
52 base::SplitStringAlongWhitespace(sizes_str, &sizes_str_list);
54 for (size_t i = 0; i < sizes_str_list.size(); ++i) {
55 std::string& size_str = sizes_str_list[i];
56 if (size_str == "any") {
57 sizes.push_back(gfx::Size(0, 0));
58 continue;
61 // It is expected that [0] => width and [1] => height after the split.
62 std::vector<std::string> size_list;
63 base::SplitStringDontTrim(size_str, L'x', &size_list);
64 if (size_list.size() != 2)
65 continue;
66 if (!IsValidIconWidthOrHeight(size_list[0]) ||
67 !IsValidIconWidthOrHeight(size_list[1])) {
68 continue;
71 int width, height;
72 if (!base::StringToInt(size_list[0], &width) ||
73 !base::StringToInt(size_list[1], &height)) {
74 continue;
77 sizes.push_back(gfx::Size(width, height));
80 return sizes;
83 const std::string& GetErrorPrefix() {
84 CR_DEFINE_STATIC_LOCAL(std::string, error_prefix,
85 ("Manifest parsing error: "));
86 return error_prefix;
89 } // anonymous namespace
92 ManifestParser::ManifestParser(const base::StringPiece& data,
93 const GURL& manifest_url,
94 const GURL& document_url)
95 : data_(data),
96 manifest_url_(manifest_url),
97 document_url_(document_url),
98 failed_(false) {
101 ManifestParser::~ManifestParser() {
104 void ManifestParser::Parse() {
105 std::string parse_error;
106 scoped_ptr<base::Value> value(base::JSONReader::DeprecatedReadAndReturnError(
107 data_, base::JSON_PARSE_RFC, nullptr, &parse_error));
109 if (!value) {
110 errors_.push_back(GetErrorPrefix() + parse_error);
111 ManifestUmaUtil::ParseFailed();
112 failed_ = true;
113 return;
116 base::DictionaryValue* dictionary = nullptr;
117 if (!value->GetAsDictionary(&dictionary)) {
118 errors_.push_back(GetErrorPrefix() +
119 "root element must be a valid JSON object.");
120 ManifestUmaUtil::ParseFailed();
121 failed_ = true;
122 return;
124 DCHECK(dictionary);
126 manifest_.name = ParseName(*dictionary);
127 manifest_.short_name = ParseShortName(*dictionary);
128 manifest_.start_url = ParseStartURL(*dictionary);
129 manifest_.display = ParseDisplay(*dictionary);
130 manifest_.orientation = ParseOrientation(*dictionary);
131 manifest_.icons = ParseIcons(*dictionary);
132 manifest_.related_applications = ParseRelatedApplications(*dictionary);
133 manifest_.prefer_related_applications =
134 ParsePreferRelatedApplications(*dictionary);
135 manifest_.theme_color = ParseThemeColor(*dictionary);
136 manifest_.gcm_sender_id = ParseGCMSenderID(*dictionary);
138 ManifestUmaUtil::ParseSucceeded(manifest_);
141 const Manifest& ManifestParser::manifest() const {
142 return manifest_;
145 const std::vector<std::string>& ManifestParser::errors() const {
146 return errors_;
149 bool ManifestParser::failed() const {
150 return failed_;
153 bool ManifestParser::ParseBoolean(const base::DictionaryValue& dictionary,
154 const std::string& key,
155 bool default_value) {
156 if (!dictionary.HasKey(key))
157 return default_value;
159 bool value;
160 if (!dictionary.GetBoolean(key, &value)) {
161 errors_.push_back(GetErrorPrefix() +
162 "property '" + key + "' ignored, type boolean expected.");
163 return default_value;
166 return value;
169 base::NullableString16 ManifestParser::ParseString(
170 const base::DictionaryValue& dictionary,
171 const std::string& key,
172 TrimType trim) {
173 if (!dictionary.HasKey(key))
174 return base::NullableString16();
176 base::string16 value;
177 if (!dictionary.GetString(key, &value)) {
178 errors_.push_back(GetErrorPrefix() +
179 "property '" + key + "' ignored, type string expected.");
180 return base::NullableString16();
183 if (trim == Trim)
184 base::TrimWhitespace(value, base::TRIM_ALL, &value);
185 return base::NullableString16(value, false);
188 GURL ManifestParser::ParseURL(const base::DictionaryValue& dictionary,
189 const std::string& key,
190 const GURL& base_url) {
191 base::NullableString16 url_str = ParseString(dictionary, key, NoTrim);
192 if (url_str.is_null())
193 return GURL();
195 return base_url.Resolve(url_str.string());
198 base::NullableString16 ManifestParser::ParseName(
199 const base::DictionaryValue& dictionary) {
200 return ParseString(dictionary, "name", Trim);
203 base::NullableString16 ManifestParser::ParseShortName(
204 const base::DictionaryValue& dictionary) {
205 return ParseString(dictionary, "short_name", Trim);
208 GURL ManifestParser::ParseStartURL(const base::DictionaryValue& dictionary) {
209 GURL start_url = ParseURL(dictionary, "start_url", manifest_url_);
210 if (!start_url.is_valid())
211 return GURL();
213 if (start_url.GetOrigin() != document_url_.GetOrigin()) {
214 errors_.push_back(GetErrorPrefix() + "property 'start_url' ignored, should "
215 "be same origin as document.");
216 return GURL();
219 return start_url;
222 Manifest::DisplayMode ManifestParser::ParseDisplay(
223 const base::DictionaryValue& dictionary) {
224 base::NullableString16 display = ParseString(dictionary, "display", Trim);
225 if (display.is_null())
226 return Manifest::DISPLAY_MODE_UNSPECIFIED;
228 if (base::LowerCaseEqualsASCII(display.string(), "fullscreen"))
229 return Manifest::DISPLAY_MODE_FULLSCREEN;
230 else if (base::LowerCaseEqualsASCII(display.string(), "standalone"))
231 return Manifest::DISPLAY_MODE_STANDALONE;
232 else if (base::LowerCaseEqualsASCII(display.string(), "minimal-ui"))
233 return Manifest::DISPLAY_MODE_MINIMAL_UI;
234 else if (base::LowerCaseEqualsASCII(display.string(), "browser"))
235 return Manifest::DISPLAY_MODE_BROWSER;
236 else {
237 errors_.push_back(GetErrorPrefix() + "unknown 'display' value ignored.");
238 return Manifest::DISPLAY_MODE_UNSPECIFIED;
242 blink::WebScreenOrientationLockType ManifestParser::ParseOrientation(
243 const base::DictionaryValue& dictionary) {
244 base::NullableString16 orientation =
245 ParseString(dictionary, "orientation", Trim);
247 if (orientation.is_null())
248 return blink::WebScreenOrientationLockDefault;
250 if (base::LowerCaseEqualsASCII(orientation.string(), "any"))
251 return blink::WebScreenOrientationLockAny;
252 else if (base::LowerCaseEqualsASCII(orientation.string(), "natural"))
253 return blink::WebScreenOrientationLockNatural;
254 else if (base::LowerCaseEqualsASCII(orientation.string(), "landscape"))
255 return blink::WebScreenOrientationLockLandscape;
256 else if (base::LowerCaseEqualsASCII(orientation.string(),
257 "landscape-primary"))
258 return blink::WebScreenOrientationLockLandscapePrimary;
259 else if (base::LowerCaseEqualsASCII(orientation.string(),
260 "landscape-secondary"))
261 return blink::WebScreenOrientationLockLandscapeSecondary;
262 else if (base::LowerCaseEqualsASCII(orientation.string(), "portrait"))
263 return blink::WebScreenOrientationLockPortrait;
264 else if (base::LowerCaseEqualsASCII(orientation.string(),
265 "portrait-primary"))
266 return blink::WebScreenOrientationLockPortraitPrimary;
267 else if (base::LowerCaseEqualsASCII(orientation.string(),
268 "portrait-secondary"))
269 return blink::WebScreenOrientationLockPortraitSecondary;
270 else {
271 errors_.push_back(GetErrorPrefix() +
272 "unknown 'orientation' value ignored.");
273 return blink::WebScreenOrientationLockDefault;
277 GURL ManifestParser::ParseIconSrc(const base::DictionaryValue& icon) {
278 return ParseURL(icon, "src", manifest_url_);
281 base::NullableString16 ManifestParser::ParseIconType(
282 const base::DictionaryValue& icon) {
283 return ParseString(icon, "type", Trim);
286 double ManifestParser::ParseIconDensity(const base::DictionaryValue& icon) {
287 double density;
288 if (!icon.HasKey("density"))
289 return Manifest::Icon::kDefaultDensity;
291 if (!icon.GetDouble("density", &density) || density <= 0) {
292 errors_.push_back(GetErrorPrefix() +
293 "icon 'density' ignored, must be float greater than 0.");
294 return Manifest::Icon::kDefaultDensity;
296 return density;
299 std::vector<gfx::Size> ManifestParser::ParseIconSizes(
300 const base::DictionaryValue& icon) {
301 base::NullableString16 sizes_str = ParseString(icon, "sizes", NoTrim);
303 if (sizes_str.is_null())
304 return std::vector<gfx::Size>();
306 std::vector<gfx::Size> sizes = ParseIconSizesHTML(sizes_str.string());
307 if (sizes.empty()) {
308 errors_.push_back(GetErrorPrefix() + "found icon with no valid size.");
310 return sizes;
313 std::vector<Manifest::Icon> ManifestParser::ParseIcons(
314 const base::DictionaryValue& dictionary) {
315 std::vector<Manifest::Icon> icons;
316 if (!dictionary.HasKey("icons"))
317 return icons;
319 const base::ListValue* icons_list = nullptr;
320 if (!dictionary.GetList("icons", &icons_list)) {
321 errors_.push_back(GetErrorPrefix() +
322 "property 'icons' ignored, type array expected.");
323 return icons;
326 for (size_t i = 0; i < icons_list->GetSize(); ++i) {
327 const base::DictionaryValue* icon_dictionary = nullptr;
328 if (!icons_list->GetDictionary(i, &icon_dictionary))
329 continue;
331 Manifest::Icon icon;
332 icon.src = ParseIconSrc(*icon_dictionary);
333 // An icon MUST have a valid src. If it does not, it MUST be ignored.
334 if (!icon.src.is_valid())
335 continue;
336 icon.type = ParseIconType(*icon_dictionary);
337 icon.density = ParseIconDensity(*icon_dictionary);
338 icon.sizes = ParseIconSizes(*icon_dictionary);
340 icons.push_back(icon);
343 return icons;
346 base::NullableString16 ManifestParser::ParseRelatedApplicationPlatform(
347 const base::DictionaryValue& application) {
348 return ParseString(application, "platform", Trim);
351 GURL ManifestParser::ParseRelatedApplicationURL(
352 const base::DictionaryValue& application) {
353 return ParseURL(application, "url", manifest_url_);
356 base::NullableString16 ManifestParser::ParseRelatedApplicationId(
357 const base::DictionaryValue& application) {
358 return ParseString(application, "id", Trim);
361 std::vector<Manifest::RelatedApplication>
362 ManifestParser::ParseRelatedApplications(
363 const base::DictionaryValue& dictionary) {
364 std::vector<Manifest::RelatedApplication> applications;
365 if (!dictionary.HasKey("related_applications"))
366 return applications;
368 const base::ListValue* applications_list = nullptr;
369 if (!dictionary.GetList("related_applications", &applications_list)) {
370 errors_.push_back(
371 GetErrorPrefix() +
372 "property 'related_applications' ignored, type array expected.");
373 return applications;
376 for (size_t i = 0; i < applications_list->GetSize(); ++i) {
377 const base::DictionaryValue* application_dictionary = nullptr;
378 if (!applications_list->GetDictionary(i, &application_dictionary))
379 continue;
381 Manifest::RelatedApplication application;
382 application.platform =
383 ParseRelatedApplicationPlatform(*application_dictionary);
384 // "If platform is undefined, move onto the next item if any are left."
385 if (application.platform.is_null()) {
386 errors_.push_back(
387 GetErrorPrefix() +
388 "'platform' is a required field, related application ignored.");
389 continue;
392 application.id = ParseRelatedApplicationId(*application_dictionary);
393 application.url = ParseRelatedApplicationURL(*application_dictionary);
394 // "If both id and url are undefined, move onto the next item if any are
395 // left."
396 if (application.url.is_empty() && application.id.is_null()) {
397 errors_.push_back(
398 GetErrorPrefix() +
399 "one of 'url' or 'id' is required, related application ignored.");
400 continue;
403 applications.push_back(application);
406 return applications;
409 bool ManifestParser::ParsePreferRelatedApplications(
410 const base::DictionaryValue& dictionary) {
411 return ParseBoolean(dictionary, "prefer_related_applications", false);
414 int64_t ManifestParser::ParseThemeColor(
415 const base::DictionaryValue& dictionary) {
416 base::NullableString16 theme_color = ParseString(
417 dictionary, "theme_color", Trim);
418 if (theme_color.is_null())
419 return Manifest::kInvalidOrMissingThemeColor;
421 blink::WebColor color;
422 if (!blink::WebCSSParser::parseColor(&color, theme_color.string())) {
423 errors_.push_back(GetErrorPrefix() +
424 "property 'theme_color' ignored, '" +
425 base::UTF16ToUTF8(theme_color.string()) +
426 "' is not a valid color.");
427 return Manifest::kInvalidOrMissingThemeColor;
430 return static_cast<int64_t>(color);
433 base::NullableString16 ManifestParser::ParseGCMSenderID(
434 const base::DictionaryValue& dictionary) {
435 return ParseString(dictionary, "gcm_sender_id", Trim);
438 } // namespace content