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"
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')
28 for (size_t i
= 0; i
< str
.size(); ++i
)
29 if (!IsAsciiDigit(str
[i
]))
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));
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)
63 if (!IsValidIconWidthOrHeight(size_list
[0]) ||
64 !IsValidIconWidthOrHeight(size_list
[1])) {
69 if (!base::StringToInt(size_list
[0], &width
) ||
70 !base::StringToInt(size_list
[1], &height
)) {
74 sizes
.push_back(gfx::Size(width
, height
));
80 const std::string
& GetErrorPrefix() {
81 CR_DEFINE_STATIC_LOCAL(std::string
, error_prefix
,
82 ("Manifest parsing error: "));
86 } // anonymous namespace
89 ManifestParser::ManifestParser(const base::StringPiece
& data
,
90 const GURL
& manifest_url
,
91 const GURL
& document_url
)
93 manifest_url_(manifest_url
),
94 document_url_(document_url
),
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
));
107 errors_
.push_back(GetErrorPrefix() + parse_error
);
108 ManifestUmaUtil::ParseFailed();
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();
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 {
141 const std::vector
<std::string
>& ManifestParser::errors() const {
145 bool ManifestParser::failed() const {
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
;
156 if (!dictionary
.GetBoolean(key
, &value
)) {
157 errors_
.push_back(GetErrorPrefix() +
158 "property '" + key
+ "' ignored, type boolean expected.");
159 return default_value
;
165 base::NullableString16
ManifestParser::ParseString(
166 const base::DictionaryValue
& dictionary
,
167 const std::string
& key
,
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();
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())
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())
209 if (start_url
.GetOrigin() != document_url_
.GetOrigin()) {
210 errors_
.push_back(GetErrorPrefix() + "property 'start_url' ignored, should "
211 "be same origin as document.");
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
;
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
;
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
) {
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
;
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());
300 errors_
.push_back(GetErrorPrefix() + "found icon with no valid size.");
305 std::vector
<Manifest::Icon
> ManifestParser::ParseIcons(
306 const base::DictionaryValue
& dictionary
) {
307 std::vector
<Manifest::Icon
> icons
;
308 if (!dictionary
.HasKey("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.");
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
))
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())
328 icon
.type
= ParseIconType(*icon_dictionary
);
329 icon
.density
= ParseIconDensity(*icon_dictionary
);
330 icon
.sizes
= ParseIconSizes(*icon_dictionary
);
332 icons
.push_back(icon
);
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"))
360 const base::ListValue
* applications_list
= nullptr;
361 if (!dictionary
.GetList("related_applications", &applications_list
)) {
364 "property 'related_applications' ignored, type array expected.");
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
))
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()) {
380 "'platform' is a required field, related application ignored.");
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
388 if (application
.url
.is_empty() && application
.id
.is_null()) {
391 "one of 'url' or 'id' is required, related application ignored.");
395 applications
.push_back(application
);
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