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"
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')
31 for (size_t i
= 0; i
< str
.size(); ++i
)
32 if (!base::IsAsciiDigit(str
[i
]))
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));
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)
67 if (!IsValidIconWidthOrHeight(size_list
[0]) ||
68 !IsValidIconWidthOrHeight(size_list
[1])) {
73 if (!base::StringToInt(size_list
[0], &width
) ||
74 !base::StringToInt(size_list
[1], &height
)) {
78 sizes
.push_back(gfx::Size(width
, height
));
84 const std::string
& GetErrorPrefix() {
85 CR_DEFINE_STATIC_LOCAL(std::string
, error_prefix
,
86 ("Manifest parsing error: "));
90 } // anonymous namespace
93 ManifestParser::ManifestParser(const base::StringPiece
& data
,
94 const GURL
& manifest_url
,
95 const GURL
& document_url
)
97 manifest_url_(manifest_url
),
98 document_url_(document_url
),
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
);
111 errors_
.push_back(GetErrorPrefix() + parse_error
);
112 ManifestUmaUtil::ParseFailed();
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();
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 {
147 const std::vector
<std::string
>& ManifestParser::errors() const {
151 bool ManifestParser::failed() const {
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
;
162 if (!dictionary
.GetBoolean(key
, &value
)) {
163 errors_
.push_back(GetErrorPrefix() +
164 "property '" + key
+ "' ignored, type boolean expected.");
165 return default_value
;
171 base::NullableString16
ManifestParser::ParseString(
172 const base::DictionaryValue
& dictionary
,
173 const std::string
& key
,
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();
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())
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())
238 if (start_url
.GetOrigin() != document_url_
.GetOrigin()) {
239 errors_
.push_back(GetErrorPrefix() + "property 'start_url' ignored, should "
240 "be same origin as document.");
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
;
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(),
291 return blink::WebScreenOrientationLockPortraitPrimary
;
292 else if (base::LowerCaseEqualsASCII(orientation
.string(),
293 "portrait-secondary"))
294 return blink::WebScreenOrientationLockPortraitSecondary
;
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
) {
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
;
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());
333 errors_
.push_back(GetErrorPrefix() + "found icon with no valid size.");
338 std::vector
<Manifest::Icon
> ManifestParser::ParseIcons(
339 const base::DictionaryValue
& dictionary
) {
340 std::vector
<Manifest::Icon
> icons
;
341 if (!dictionary
.HasKey("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.");
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
))
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())
361 icon
.type
= ParseIconType(*icon_dictionary
);
362 icon
.density
= ParseIconDensity(*icon_dictionary
);
363 icon
.sizes
= ParseIconSizes(*icon_dictionary
);
365 icons
.push_back(icon
);
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"))
393 const base::ListValue
* applications_list
= nullptr;
394 if (!dictionary
.GetList("related_applications", &applications_list
)) {
397 "property 'related_applications' ignored, type array expected.");
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
))
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()) {
413 "'platform' is a required field, related application ignored.");
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
421 if (application
.url
.is_empty() && application
.id
.is_null()) {
424 "one of 'url' or 'id' is required, related application ignored.");
428 applications
.push_back(application
);
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