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_
.gcm_sender_id
= ParseGCMSenderID(*dictionary
);
131 manifest_
.gcm_user_visible_only
= ParseGCMUserVisibleOnly(*dictionary
);
133 ManifestUmaUtil::ParseSucceeded(manifest_
);
136 const Manifest
& ManifestParser::manifest() const {
140 const std::vector
<std::string
>& ManifestParser::errors() const {
144 bool ManifestParser::failed() const {
148 bool ManifestParser::ParseBoolean(const base::DictionaryValue
& dictionary
,
149 const std::string
& key
,
150 bool default_value
) {
151 if (!dictionary
.HasKey(key
))
152 return default_value
;
155 if (!dictionary
.GetBoolean(key
, &value
)) {
156 errors_
.push_back(GetErrorPrefix() +
157 "property '" + key
+ "' ignored, type boolean expected.");
158 return default_value
;
164 base::NullableString16
ManifestParser::ParseString(
165 const base::DictionaryValue
& dictionary
,
166 const std::string
& key
,
168 if (!dictionary
.HasKey(key
))
169 return base::NullableString16();
171 base::string16 value
;
172 if (!dictionary
.GetString(key
, &value
)) {
173 errors_
.push_back(GetErrorPrefix() +
174 "property '" + key
+ "' ignored, type string expected.");
175 return base::NullableString16();
179 base::TrimWhitespace(value
, base::TRIM_ALL
, &value
);
180 return base::NullableString16(value
, false);
183 GURL
ManifestParser::ParseURL(const base::DictionaryValue
& dictionary
,
184 const std::string
& key
,
185 const GURL
& base_url
) {
186 base::NullableString16 url_str
= ParseString(dictionary
, key
, NoTrim
);
187 if (url_str
.is_null())
190 return base_url
.Resolve(url_str
.string());
193 base::NullableString16
ManifestParser::ParseName(
194 const base::DictionaryValue
& dictionary
) {
195 return ParseString(dictionary
, "name", Trim
);
198 base::NullableString16
ManifestParser::ParseShortName(
199 const base::DictionaryValue
& dictionary
) {
200 return ParseString(dictionary
, "short_name", Trim
);
203 GURL
ManifestParser::ParseStartURL(const base::DictionaryValue
& dictionary
) {
204 GURL start_url
= ParseURL(dictionary
, "start_url", manifest_url_
);
205 if (!start_url
.is_valid())
208 if (start_url
.GetOrigin() != document_url_
.GetOrigin()) {
209 errors_
.push_back(GetErrorPrefix() + "property 'start_url' ignored, should "
210 "be same origin as document.");
217 Manifest::DisplayMode
ManifestParser::ParseDisplay(
218 const base::DictionaryValue
& dictionary
) {
219 base::NullableString16 display
= ParseString(dictionary
, "display", Trim
);
220 if (display
.is_null())
221 return Manifest::DISPLAY_MODE_UNSPECIFIED
;
223 if (LowerCaseEqualsASCII(display
.string(), "fullscreen"))
224 return Manifest::DISPLAY_MODE_FULLSCREEN
;
225 else if (LowerCaseEqualsASCII(display
.string(), "standalone"))
226 return Manifest::DISPLAY_MODE_STANDALONE
;
227 else if (LowerCaseEqualsASCII(display
.string(), "minimal-ui"))
228 return Manifest::DISPLAY_MODE_MINIMAL_UI
;
229 else if (LowerCaseEqualsASCII(display
.string(), "browser"))
230 return Manifest::DISPLAY_MODE_BROWSER
;
232 errors_
.push_back(GetErrorPrefix() + "unknown 'display' value ignored.");
233 return Manifest::DISPLAY_MODE_UNSPECIFIED
;
237 blink::WebScreenOrientationLockType
ManifestParser::ParseOrientation(
238 const base::DictionaryValue
& dictionary
) {
239 base::NullableString16 orientation
=
240 ParseString(dictionary
, "orientation", Trim
);
242 if (orientation
.is_null())
243 return blink::WebScreenOrientationLockDefault
;
245 if (LowerCaseEqualsASCII(orientation
.string(), "any"))
246 return blink::WebScreenOrientationLockAny
;
247 else if (LowerCaseEqualsASCII(orientation
.string(), "natural"))
248 return blink::WebScreenOrientationLockNatural
;
249 else if (LowerCaseEqualsASCII(orientation
.string(), "landscape"))
250 return blink::WebScreenOrientationLockLandscape
;
251 else if (LowerCaseEqualsASCII(orientation
.string(), "landscape-primary"))
252 return blink::WebScreenOrientationLockLandscapePrimary
;
253 else if (LowerCaseEqualsASCII(orientation
.string(), "landscape-secondary"))
254 return blink::WebScreenOrientationLockLandscapeSecondary
;
255 else if (LowerCaseEqualsASCII(orientation
.string(), "portrait"))
256 return blink::WebScreenOrientationLockPortrait
;
257 else if (LowerCaseEqualsASCII(orientation
.string(), "portrait-primary"))
258 return blink::WebScreenOrientationLockPortraitPrimary
;
259 else if (LowerCaseEqualsASCII(orientation
.string(), "portrait-secondary"))
260 return blink::WebScreenOrientationLockPortraitSecondary
;
262 errors_
.push_back(GetErrorPrefix() +
263 "unknown 'orientation' value ignored.");
264 return blink::WebScreenOrientationLockDefault
;
268 GURL
ManifestParser::ParseIconSrc(const base::DictionaryValue
& icon
) {
269 return ParseURL(icon
, "src", manifest_url_
);
272 base::NullableString16
ManifestParser::ParseIconType(
273 const base::DictionaryValue
& icon
) {
274 return ParseString(icon
, "type", Trim
);
277 double ManifestParser::ParseIconDensity(const base::DictionaryValue
& icon
) {
279 if (!icon
.HasKey("density"))
280 return Manifest::Icon::kDefaultDensity
;
282 if (!icon
.GetDouble("density", &density
) || density
<= 0) {
283 errors_
.push_back(GetErrorPrefix() +
284 "icon 'density' ignored, must be float greater than 0.");
285 return Manifest::Icon::kDefaultDensity
;
290 std::vector
<gfx::Size
> ManifestParser::ParseIconSizes(
291 const base::DictionaryValue
& icon
) {
292 base::NullableString16 sizes_str
= ParseString(icon
, "sizes", NoTrim
);
294 if (sizes_str
.is_null())
295 return std::vector
<gfx::Size
>();
297 std::vector
<gfx::Size
> sizes
= ParseIconSizesHTML(sizes_str
.string());
299 errors_
.push_back(GetErrorPrefix() + "found icon with no valid size.");
304 std::vector
<Manifest::Icon
> ManifestParser::ParseIcons(
305 const base::DictionaryValue
& dictionary
) {
306 std::vector
<Manifest::Icon
> icons
;
307 if (!dictionary
.HasKey("icons"))
310 const base::ListValue
* icons_list
= nullptr;
311 if (!dictionary
.GetList("icons", &icons_list
)) {
312 errors_
.push_back(GetErrorPrefix() +
313 "property 'icons' ignored, type array expected.");
317 for (size_t i
= 0; i
< icons_list
->GetSize(); ++i
) {
318 const base::DictionaryValue
* icon_dictionary
= nullptr;
319 if (!icons_list
->GetDictionary(i
, &icon_dictionary
))
323 icon
.src
= ParseIconSrc(*icon_dictionary
);
324 // An icon MUST have a valid src. If it does not, it MUST be ignored.
325 if (!icon
.src
.is_valid())
327 icon
.type
= ParseIconType(*icon_dictionary
);
328 icon
.density
= ParseIconDensity(*icon_dictionary
);
329 icon
.sizes
= ParseIconSizes(*icon_dictionary
);
331 icons
.push_back(icon
);
337 base::NullableString16
ManifestParser::ParseGCMSenderID(
338 const base::DictionaryValue
& dictionary
) {
339 return ParseString(dictionary
, "gcm_sender_id", Trim
);
342 bool ManifestParser::ParseGCMUserVisibleOnly(
343 const base::DictionaryValue
& dictionary
) {
344 return ParseBoolean(dictionary
, "gcm_user_visible_only", false);
347 } // namespace content