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(
104 base::JSONReader::ReadAndReturnError(data_
, base::JSON_PARSE_RFC
,
105 nullptr, &parse_error
));
108 errors_
.push_back(GetErrorPrefix() + parse_error
);
109 ManifestUmaUtil::ParseFailed();
114 base::DictionaryValue
* dictionary
= nullptr;
115 if (!value
->GetAsDictionary(&dictionary
)) {
116 errors_
.push_back(GetErrorPrefix() +
117 "root element must be a valid JSON object.");
118 ManifestUmaUtil::ParseFailed();
124 manifest_
.name
= ParseName(*dictionary
);
125 manifest_
.short_name
= ParseShortName(*dictionary
);
126 manifest_
.start_url
= ParseStartURL(*dictionary
);
127 manifest_
.display
= ParseDisplay(*dictionary
);
128 manifest_
.orientation
= ParseOrientation(*dictionary
);
129 manifest_
.icons
= ParseIcons(*dictionary
);
130 manifest_
.related_applications
= ParseRelatedApplications(*dictionary
);
131 manifest_
.prefer_related_applications
=
132 ParsePreferRelatedApplications(*dictionary
);
133 manifest_
.gcm_sender_id
= ParseGCMSenderID(*dictionary
);
134 manifest_
.gcm_user_visible_only
= ParseGCMUserVisibleOnly(*dictionary
);
136 ManifestUmaUtil::ParseSucceeded(manifest_
);
139 const Manifest
& ManifestParser::manifest() const {
143 const std::vector
<std::string
>& ManifestParser::errors() const {
147 bool ManifestParser::failed() const {
151 bool ManifestParser::ParseBoolean(const base::DictionaryValue
& dictionary
,
152 const std::string
& key
,
153 bool default_value
) {
154 if (!dictionary
.HasKey(key
))
155 return default_value
;
158 if (!dictionary
.GetBoolean(key
, &value
)) {
159 errors_
.push_back(GetErrorPrefix() +
160 "property '" + key
+ "' ignored, type boolean expected.");
161 return default_value
;
167 base::NullableString16
ManifestParser::ParseString(
168 const base::DictionaryValue
& dictionary
,
169 const std::string
& key
,
171 if (!dictionary
.HasKey(key
))
172 return base::NullableString16();
174 base::string16 value
;
175 if (!dictionary
.GetString(key
, &value
)) {
176 errors_
.push_back(GetErrorPrefix() +
177 "property '" + key
+ "' ignored, type string expected.");
178 return base::NullableString16();
182 base::TrimWhitespace(value
, base::TRIM_ALL
, &value
);
183 return base::NullableString16(value
, false);
186 GURL
ManifestParser::ParseURL(const base::DictionaryValue
& dictionary
,
187 const std::string
& key
,
188 const GURL
& base_url
) {
189 base::NullableString16 url_str
= ParseString(dictionary
, key
, NoTrim
);
190 if (url_str
.is_null())
193 return base_url
.Resolve(url_str
.string());
196 base::NullableString16
ManifestParser::ParseName(
197 const base::DictionaryValue
& dictionary
) {
198 return ParseString(dictionary
, "name", Trim
);
201 base::NullableString16
ManifestParser::ParseShortName(
202 const base::DictionaryValue
& dictionary
) {
203 return ParseString(dictionary
, "short_name", Trim
);
206 GURL
ManifestParser::ParseStartURL(const base::DictionaryValue
& dictionary
) {
207 GURL start_url
= ParseURL(dictionary
, "start_url", manifest_url_
);
208 if (!start_url
.is_valid())
211 if (start_url
.GetOrigin() != document_url_
.GetOrigin()) {
212 errors_
.push_back(GetErrorPrefix() + "property 'start_url' ignored, should "
213 "be same origin as document.");
220 Manifest::DisplayMode
ManifestParser::ParseDisplay(
221 const base::DictionaryValue
& dictionary
) {
222 base::NullableString16 display
= ParseString(dictionary
, "display", Trim
);
223 if (display
.is_null())
224 return Manifest::DISPLAY_MODE_UNSPECIFIED
;
226 if (LowerCaseEqualsASCII(display
.string(), "fullscreen"))
227 return Manifest::DISPLAY_MODE_FULLSCREEN
;
228 else if (LowerCaseEqualsASCII(display
.string(), "standalone"))
229 return Manifest::DISPLAY_MODE_STANDALONE
;
230 else if (LowerCaseEqualsASCII(display
.string(), "minimal-ui"))
231 return Manifest::DISPLAY_MODE_MINIMAL_UI
;
232 else if (LowerCaseEqualsASCII(display
.string(), "browser"))
233 return Manifest::DISPLAY_MODE_BROWSER
;
235 errors_
.push_back(GetErrorPrefix() + "unknown 'display' value ignored.");
236 return Manifest::DISPLAY_MODE_UNSPECIFIED
;
240 blink::WebScreenOrientationLockType
ManifestParser::ParseOrientation(
241 const base::DictionaryValue
& dictionary
) {
242 base::NullableString16 orientation
=
243 ParseString(dictionary
, "orientation", Trim
);
245 if (orientation
.is_null())
246 return blink::WebScreenOrientationLockDefault
;
248 if (LowerCaseEqualsASCII(orientation
.string(), "any"))
249 return blink::WebScreenOrientationLockAny
;
250 else if (LowerCaseEqualsASCII(orientation
.string(), "natural"))
251 return blink::WebScreenOrientationLockNatural
;
252 else if (LowerCaseEqualsASCII(orientation
.string(), "landscape"))
253 return blink::WebScreenOrientationLockLandscape
;
254 else if (LowerCaseEqualsASCII(orientation
.string(), "landscape-primary"))
255 return blink::WebScreenOrientationLockLandscapePrimary
;
256 else if (LowerCaseEqualsASCII(orientation
.string(), "landscape-secondary"))
257 return blink::WebScreenOrientationLockLandscapeSecondary
;
258 else if (LowerCaseEqualsASCII(orientation
.string(), "portrait"))
259 return blink::WebScreenOrientationLockPortrait
;
260 else if (LowerCaseEqualsASCII(orientation
.string(), "portrait-primary"))
261 return blink::WebScreenOrientationLockPortraitPrimary
;
262 else if (LowerCaseEqualsASCII(orientation
.string(), "portrait-secondary"))
263 return blink::WebScreenOrientationLockPortraitSecondary
;
265 errors_
.push_back(GetErrorPrefix() +
266 "unknown 'orientation' value ignored.");
267 return blink::WebScreenOrientationLockDefault
;
271 GURL
ManifestParser::ParseIconSrc(const base::DictionaryValue
& icon
) {
272 return ParseURL(icon
, "src", manifest_url_
);
275 base::NullableString16
ManifestParser::ParseIconType(
276 const base::DictionaryValue
& icon
) {
277 return ParseString(icon
, "type", Trim
);
280 double ManifestParser::ParseIconDensity(const base::DictionaryValue
& icon
) {
282 if (!icon
.HasKey("density"))
283 return Manifest::Icon::kDefaultDensity
;
285 if (!icon
.GetDouble("density", &density
) || density
<= 0) {
286 errors_
.push_back(GetErrorPrefix() +
287 "icon 'density' ignored, must be float greater than 0.");
288 return Manifest::Icon::kDefaultDensity
;
293 std::vector
<gfx::Size
> ManifestParser::ParseIconSizes(
294 const base::DictionaryValue
& icon
) {
295 base::NullableString16 sizes_str
= ParseString(icon
, "sizes", NoTrim
);
297 if (sizes_str
.is_null())
298 return std::vector
<gfx::Size
>();
300 std::vector
<gfx::Size
> sizes
= ParseIconSizesHTML(sizes_str
.string());
302 errors_
.push_back(GetErrorPrefix() + "found icon with no valid size.");
307 std::vector
<Manifest::Icon
> ManifestParser::ParseIcons(
308 const base::DictionaryValue
& dictionary
) {
309 std::vector
<Manifest::Icon
> icons
;
310 if (!dictionary
.HasKey("icons"))
313 const base::ListValue
* icons_list
= nullptr;
314 if (!dictionary
.GetList("icons", &icons_list
)) {
315 errors_
.push_back(GetErrorPrefix() +
316 "property 'icons' ignored, type array expected.");
320 for (size_t i
= 0; i
< icons_list
->GetSize(); ++i
) {
321 const base::DictionaryValue
* icon_dictionary
= nullptr;
322 if (!icons_list
->GetDictionary(i
, &icon_dictionary
))
326 icon
.src
= ParseIconSrc(*icon_dictionary
);
327 // An icon MUST have a valid src. If it does not, it MUST be ignored.
328 if (!icon
.src
.is_valid())
330 icon
.type
= ParseIconType(*icon_dictionary
);
331 icon
.density
= ParseIconDensity(*icon_dictionary
);
332 icon
.sizes
= ParseIconSizes(*icon_dictionary
);
334 icons
.push_back(icon
);
340 base::NullableString16
ManifestParser::ParseRelatedApplicationPlatform(
341 const base::DictionaryValue
& application
) {
342 return ParseString(application
, "platform", Trim
);
345 GURL
ManifestParser::ParseRelatedApplicationURL(
346 const base::DictionaryValue
& application
) {
347 return ParseURL(application
, "url", manifest_url_
);
350 base::NullableString16
ManifestParser::ParseRelatedApplicationId(
351 const base::DictionaryValue
& application
) {
352 return ParseString(application
, "id", Trim
);
355 std::vector
<Manifest::RelatedApplication
>
356 ManifestParser::ParseRelatedApplications(
357 const base::DictionaryValue
& dictionary
) {
358 std::vector
<Manifest::RelatedApplication
> applications
;
359 if (!dictionary
.HasKey("related_applications"))
362 const base::ListValue
* applications_list
= nullptr;
363 if (!dictionary
.GetList("related_applications", &applications_list
)) {
366 "property 'related_applications' ignored, type array expected.");
370 for (size_t i
= 0; i
< applications_list
->GetSize(); ++i
) {
371 const base::DictionaryValue
* application_dictionary
= nullptr;
372 if (!applications_list
->GetDictionary(i
, &application_dictionary
))
375 Manifest::RelatedApplication application
;
376 application
.platform
=
377 ParseRelatedApplicationPlatform(*application_dictionary
);
378 // "If platform is undefined, move onto the next item if any are left."
379 if (application
.platform
.is_null()) {
382 "'platform' is a required field, related application ignored.");
386 application
.id
= ParseRelatedApplicationId(*application_dictionary
);
387 application
.url
= ParseRelatedApplicationURL(*application_dictionary
);
388 // "If both id and url are undefined, move onto the next item if any are
390 if (application
.url
.is_empty() && application
.id
.is_null()) {
393 "one of 'url' or 'id' is required, related application ignored.");
397 applications
.push_back(application
);
403 bool ManifestParser::ParsePreferRelatedApplications(
404 const base::DictionaryValue
& dictionary
) {
405 return ParseBoolean(dictionary
, "prefer_related_applications", false);
408 base::NullableString16
ManifestParser::ParseGCMSenderID(
409 const base::DictionaryValue
& dictionary
) {
410 return ParseString(dictionary
, "gcm_sender_id", Trim
);
413 bool ManifestParser::ParseGCMUserVisibleOnly(
414 const base::DictionaryValue
& dictionary
) {
415 return ParseBoolean(dictionary
, "gcm_user_visible_only", false);
418 } // namespace content