Update SplitString calls to new form
[chromium-blink-merge.git] / content / renderer / manifest / manifest_parser.cc
blob780cf91a32017abab3f617228292966d58195818
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 = 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));
59 continue;
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)
66 continue;
67 if (!IsValidIconWidthOrHeight(size_list[0]) ||
68 !IsValidIconWidthOrHeight(size_list[1])) {
69 continue;
72 int width, height;
73 if (!base::StringToInt(size_list[0], &width) ||
74 !base::StringToInt(size_list[1], &height)) {
75 continue;
78 sizes.push_back(gfx::Size(width, height));
81 return sizes;
84 const std::string& GetErrorPrefix() {
85 CR_DEFINE_STATIC_LOCAL(std::string, error_prefix,
86 ("Manifest parsing error: "));
87 return error_prefix;
90 } // anonymous namespace
93 ManifestParser::ManifestParser(const base::StringPiece& data,
94 const GURL& manifest_url,
95 const GURL& document_url)
96 : data_(data),
97 manifest_url_(manifest_url),
98 document_url_(document_url),
99 failed_(false) {
102 ManifestParser::~ManifestParser() {
105 void ManifestParser::Parse() {
106 std::string parse_error;
107 scoped_ptr<base::Value> value(base::JSONReader::DeprecatedReadAndReturnError(
108 data_, base::JSON_PARSE_RFC, nullptr, &parse_error));
110 if (!value) {
111 errors_.push_back(GetErrorPrefix() + parse_error);
112 ManifestUmaUtil::ParseFailed();
113 failed_ = true;
114 return;
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();
122 failed_ = true;
123 return;
125 DCHECK(dictionary);
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_.gcm_sender_id = ParseGCMSenderID(*dictionary);
139 ManifestUmaUtil::ParseSucceeded(manifest_);
142 const Manifest& ManifestParser::manifest() const {
143 return manifest_;
146 const std::vector<std::string>& ManifestParser::errors() const {
147 return errors_;
150 bool ManifestParser::failed() const {
151 return failed_;
154 bool ManifestParser::ParseBoolean(const base::DictionaryValue& dictionary,
155 const std::string& key,
156 bool default_value) {
157 if (!dictionary.HasKey(key))
158 return default_value;
160 bool value;
161 if (!dictionary.GetBoolean(key, &value)) {
162 errors_.push_back(GetErrorPrefix() +
163 "property '" + key + "' ignored, type boolean expected.");
164 return default_value;
167 return value;
170 base::NullableString16 ManifestParser::ParseString(
171 const base::DictionaryValue& dictionary,
172 const std::string& key,
173 TrimType trim) {
174 if (!dictionary.HasKey(key))
175 return base::NullableString16();
177 base::string16 value;
178 if (!dictionary.GetString(key, &value)) {
179 errors_.push_back(GetErrorPrefix() +
180 "property '" + key + "' ignored, type string expected.");
181 return base::NullableString16();
184 if (trim == Trim)
185 base::TrimWhitespace(value, base::TRIM_ALL, &value);
186 return base::NullableString16(value, false);
189 GURL ManifestParser::ParseURL(const base::DictionaryValue& dictionary,
190 const std::string& key,
191 const GURL& base_url) {
192 base::NullableString16 url_str = ParseString(dictionary, key, NoTrim);
193 if (url_str.is_null())
194 return GURL();
196 return base_url.Resolve(url_str.string());
199 base::NullableString16 ManifestParser::ParseName(
200 const base::DictionaryValue& dictionary) {
201 return ParseString(dictionary, "name", Trim);
204 base::NullableString16 ManifestParser::ParseShortName(
205 const base::DictionaryValue& dictionary) {
206 return ParseString(dictionary, "short_name", Trim);
209 GURL ManifestParser::ParseStartURL(const base::DictionaryValue& dictionary) {
210 GURL start_url = ParseURL(dictionary, "start_url", manifest_url_);
211 if (!start_url.is_valid())
212 return GURL();
214 if (start_url.GetOrigin() != document_url_.GetOrigin()) {
215 errors_.push_back(GetErrorPrefix() + "property 'start_url' ignored, should "
216 "be same origin as document.");
217 return GURL();
220 return start_url;
223 Manifest::DisplayMode ManifestParser::ParseDisplay(
224 const base::DictionaryValue& dictionary) {
225 base::NullableString16 display = ParseString(dictionary, "display", Trim);
226 if (display.is_null())
227 return Manifest::DISPLAY_MODE_UNSPECIFIED;
229 if (base::LowerCaseEqualsASCII(display.string(), "fullscreen"))
230 return Manifest::DISPLAY_MODE_FULLSCREEN;
231 else if (base::LowerCaseEqualsASCII(display.string(), "standalone"))
232 return Manifest::DISPLAY_MODE_STANDALONE;
233 else if (base::LowerCaseEqualsASCII(display.string(), "minimal-ui"))
234 return Manifest::DISPLAY_MODE_MINIMAL_UI;
235 else if (base::LowerCaseEqualsASCII(display.string(), "browser"))
236 return Manifest::DISPLAY_MODE_BROWSER;
237 else {
238 errors_.push_back(GetErrorPrefix() + "unknown 'display' value ignored.");
239 return Manifest::DISPLAY_MODE_UNSPECIFIED;
243 blink::WebScreenOrientationLockType ManifestParser::ParseOrientation(
244 const base::DictionaryValue& dictionary) {
245 base::NullableString16 orientation =
246 ParseString(dictionary, "orientation", Trim);
248 if (orientation.is_null())
249 return blink::WebScreenOrientationLockDefault;
251 if (base::LowerCaseEqualsASCII(orientation.string(), "any"))
252 return blink::WebScreenOrientationLockAny;
253 else if (base::LowerCaseEqualsASCII(orientation.string(), "natural"))
254 return blink::WebScreenOrientationLockNatural;
255 else if (base::LowerCaseEqualsASCII(orientation.string(), "landscape"))
256 return blink::WebScreenOrientationLockLandscape;
257 else if (base::LowerCaseEqualsASCII(orientation.string(),
258 "landscape-primary"))
259 return blink::WebScreenOrientationLockLandscapePrimary;
260 else if (base::LowerCaseEqualsASCII(orientation.string(),
261 "landscape-secondary"))
262 return blink::WebScreenOrientationLockLandscapeSecondary;
263 else if (base::LowerCaseEqualsASCII(orientation.string(), "portrait"))
264 return blink::WebScreenOrientationLockPortrait;
265 else if (base::LowerCaseEqualsASCII(orientation.string(),
266 "portrait-primary"))
267 return blink::WebScreenOrientationLockPortraitPrimary;
268 else if (base::LowerCaseEqualsASCII(orientation.string(),
269 "portrait-secondary"))
270 return blink::WebScreenOrientationLockPortraitSecondary;
271 else {
272 errors_.push_back(GetErrorPrefix() +
273 "unknown 'orientation' value ignored.");
274 return blink::WebScreenOrientationLockDefault;
278 GURL ManifestParser::ParseIconSrc(const base::DictionaryValue& icon) {
279 return ParseURL(icon, "src", manifest_url_);
282 base::NullableString16 ManifestParser::ParseIconType(
283 const base::DictionaryValue& icon) {
284 return ParseString(icon, "type", Trim);
287 double ManifestParser::ParseIconDensity(const base::DictionaryValue& icon) {
288 double density;
289 if (!icon.HasKey("density"))
290 return Manifest::Icon::kDefaultDensity;
292 if (!icon.GetDouble("density", &density) || density <= 0) {
293 errors_.push_back(GetErrorPrefix() +
294 "icon 'density' ignored, must be float greater than 0.");
295 return Manifest::Icon::kDefaultDensity;
297 return density;
300 std::vector<gfx::Size> ManifestParser::ParseIconSizes(
301 const base::DictionaryValue& icon) {
302 base::NullableString16 sizes_str = ParseString(icon, "sizes", NoTrim);
304 if (sizes_str.is_null())
305 return std::vector<gfx::Size>();
307 std::vector<gfx::Size> sizes = ParseIconSizesHTML(sizes_str.string());
308 if (sizes.empty()) {
309 errors_.push_back(GetErrorPrefix() + "found icon with no valid size.");
311 return sizes;
314 std::vector<Manifest::Icon> ManifestParser::ParseIcons(
315 const base::DictionaryValue& dictionary) {
316 std::vector<Manifest::Icon> icons;
317 if (!dictionary.HasKey("icons"))
318 return icons;
320 const base::ListValue* icons_list = nullptr;
321 if (!dictionary.GetList("icons", &icons_list)) {
322 errors_.push_back(GetErrorPrefix() +
323 "property 'icons' ignored, type array expected.");
324 return icons;
327 for (size_t i = 0; i < icons_list->GetSize(); ++i) {
328 const base::DictionaryValue* icon_dictionary = nullptr;
329 if (!icons_list->GetDictionary(i, &icon_dictionary))
330 continue;
332 Manifest::Icon icon;
333 icon.src = ParseIconSrc(*icon_dictionary);
334 // An icon MUST have a valid src. If it does not, it MUST be ignored.
335 if (!icon.src.is_valid())
336 continue;
337 icon.type = ParseIconType(*icon_dictionary);
338 icon.density = ParseIconDensity(*icon_dictionary);
339 icon.sizes = ParseIconSizes(*icon_dictionary);
341 icons.push_back(icon);
344 return icons;
347 base::NullableString16 ManifestParser::ParseRelatedApplicationPlatform(
348 const base::DictionaryValue& application) {
349 return ParseString(application, "platform", Trim);
352 GURL ManifestParser::ParseRelatedApplicationURL(
353 const base::DictionaryValue& application) {
354 return ParseURL(application, "url", manifest_url_);
357 base::NullableString16 ManifestParser::ParseRelatedApplicationId(
358 const base::DictionaryValue& application) {
359 return ParseString(application, "id", Trim);
362 std::vector<Manifest::RelatedApplication>
363 ManifestParser::ParseRelatedApplications(
364 const base::DictionaryValue& dictionary) {
365 std::vector<Manifest::RelatedApplication> applications;
366 if (!dictionary.HasKey("related_applications"))
367 return applications;
369 const base::ListValue* applications_list = nullptr;
370 if (!dictionary.GetList("related_applications", &applications_list)) {
371 errors_.push_back(
372 GetErrorPrefix() +
373 "property 'related_applications' ignored, type array expected.");
374 return applications;
377 for (size_t i = 0; i < applications_list->GetSize(); ++i) {
378 const base::DictionaryValue* application_dictionary = nullptr;
379 if (!applications_list->GetDictionary(i, &application_dictionary))
380 continue;
382 Manifest::RelatedApplication application;
383 application.platform =
384 ParseRelatedApplicationPlatform(*application_dictionary);
385 // "If platform is undefined, move onto the next item if any are left."
386 if (application.platform.is_null()) {
387 errors_.push_back(
388 GetErrorPrefix() +
389 "'platform' is a required field, related application ignored.");
390 continue;
393 application.id = ParseRelatedApplicationId(*application_dictionary);
394 application.url = ParseRelatedApplicationURL(*application_dictionary);
395 // "If both id and url are undefined, move onto the next item if any are
396 // left."
397 if (application.url.is_empty() && application.id.is_null()) {
398 errors_.push_back(
399 GetErrorPrefix() +
400 "one of 'url' or 'id' is required, related application ignored.");
401 continue;
404 applications.push_back(application);
407 return applications;
410 bool ManifestParser::ParsePreferRelatedApplications(
411 const base::DictionaryValue& dictionary) {
412 return ParseBoolean(dictionary, "prefer_related_applications", false);
415 int64_t ManifestParser::ParseThemeColor(
416 const base::DictionaryValue& dictionary) {
417 base::NullableString16 theme_color = ParseString(
418 dictionary, "theme_color", Trim);
419 if (theme_color.is_null())
420 return Manifest::kInvalidOrMissingThemeColor;
422 blink::WebColor color;
423 if (!blink::WebCSSParser::parseColor(&color, theme_color.string())) {
424 errors_.push_back(GetErrorPrefix() +
425 "property 'theme_color' ignored, '" +
426 base::UTF16ToUTF8(theme_color.string()) +
427 "' is not a valid color.");
428 return Manifest::kInvalidOrMissingThemeColor;
431 return static_cast<int64_t>(color);
434 base::NullableString16 ManifestParser::ParseGCMSenderID(
435 const base::DictionaryValue& dictionary) {
436 return ParseString(dictionary, "gcm_sender_id", Trim);
439 } // namespace content