| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/renderer/manifest/manifest_parser.h" |
| |
| #include "base/json/json_reader.h" |
| #include "base/strings/nullable_string16.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_split.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/values.h" |
| #include "content/public/common/manifest.h" |
| #include "content/renderer/manifest/manifest_uma_util.h" |
| #include "ui/gfx/geometry/size.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| enum TrimType { |
| Trim, |
| NoTrim |
| }; |
| |
| base::NullableString16 ParseString(const base::DictionaryValue& dictionary, |
| const std::string& key, |
| TrimType trim) { |
| if (!dictionary.HasKey(key)) |
| return base::NullableString16(); |
| |
| base::string16 value; |
| if (!dictionary.GetString(key, &value)) { |
| // TODO(mlamouri): provide a custom message to the developer console about |
| // the property being incorrectly set. |
| return base::NullableString16(); |
| } |
| |
| if (trim == Trim) |
| base::TrimWhitespace(value, base::TRIM_ALL, &value); |
| return base::NullableString16(value, false); |
| } |
| |
| // Helper function to parse URLs present on a given |dictionary| in a given |
| // field identified by its |key|. The URL is first parsed as a string then |
| // resolved using |base_url|. |
| // Returns a GURL. If the parsing failed, the GURL will not be valid. |
| GURL ParseURL(const base::DictionaryValue& dictionary, |
| const std::string& key, |
| const GURL& base_url) { |
| base::NullableString16 url_str = ParseString(dictionary, key, NoTrim); |
| if (url_str.is_null()) |
| return GURL(); |
| |
| return base_url.Resolve(url_str.string()); |
| } |
| |
| // Parses the 'name' field of the manifest, as defined in: |
| // http://w3c.github.io/manifest/#dfn-steps-for-processing-the-name-member |
| // Returns the parsed string if any, a null string if the parsing failed. |
| base::NullableString16 ParseName(const base::DictionaryValue& dictionary) { |
| return ParseString(dictionary, "name", Trim); |
| } |
| |
| // Parses the 'short_name' field of the manifest, as defined in: |
| // http://w3c.github.io/manifest/#dfn-steps-for-processing-the-short-name-member |
| // Returns the parsed string if any, a null string if the parsing failed. |
| base::NullableString16 ParseShortName( |
| const base::DictionaryValue& dictionary) { |
| return ParseString(dictionary, "short_name", Trim); |
| } |
| |
| // Parses the 'start_url' field of the manifest, as defined in: |
| // http://w3c.github.io/manifest/#dfn-steps-for-processing-the-start_url-member |
| // Returns the parsed GURL if any, an empty GURL if the parsing failed. |
| GURL ParseStartURL(const base::DictionaryValue& dictionary, |
| const GURL& manifest_url, |
| const GURL& document_url) { |
| GURL start_url = ParseURL(dictionary, "start_url", manifest_url); |
| if (!start_url.is_valid()) |
| return GURL(); |
| |
| if (start_url.GetOrigin() != document_url.GetOrigin()) { |
| // TODO(mlamouri): provide a custom message to the developer console. |
| return GURL(); |
| } |
| |
| return start_url; |
| } |
| |
| // Parses the 'display' field of the manifest, as defined in: |
| // http://w3c.github.io/manifest/#dfn-steps-for-processing-the-display-member |
| // Returns the parsed DisplayMode if any, DISPLAY_MODE_UNSPECIFIED if the |
| // parsing failed. |
| Manifest::DisplayMode ParseDisplay(const base::DictionaryValue& dictionary) { |
| base::NullableString16 display = ParseString(dictionary, "display", Trim); |
| if (display.is_null()) |
| return Manifest::DISPLAY_MODE_UNSPECIFIED; |
| |
| if (LowerCaseEqualsASCII(display.string(), "fullscreen")) |
| return Manifest::DISPLAY_MODE_FULLSCREEN; |
| else if (LowerCaseEqualsASCII(display.string(), "standalone")) |
| return Manifest::DISPLAY_MODE_STANDALONE; |
| else if (LowerCaseEqualsASCII(display.string(), "minimal-ui")) |
| return Manifest::DISPLAY_MODE_MINIMAL_UI; |
| else if (LowerCaseEqualsASCII(display.string(), "browser")) |
| return Manifest::DISPLAY_MODE_BROWSER; |
| else |
| return Manifest::DISPLAY_MODE_UNSPECIFIED; |
| } |
| |
| // Parses the 'orientation' field of the manifest, as defined in: |
| // http://w3c.github.io/manifest/#dfn-steps-for-processing-the-orientation-member |
| // Returns the parsed WebScreenOrientationLockType if any, |
| // WebScreenOrientationLockDefault if the parsing failed. |
| blink::WebScreenOrientationLockType ParseOrientation( |
| const base::DictionaryValue& dictionary) { |
| base::NullableString16 orientation = |
| ParseString(dictionary, "orientation", Trim); |
| |
| if (orientation.is_null()) |
| return blink::WebScreenOrientationLockDefault; |
| |
| if (LowerCaseEqualsASCII(orientation.string(), "any")) |
| return blink::WebScreenOrientationLockAny; |
| else if (LowerCaseEqualsASCII(orientation.string(), "natural")) |
| return blink::WebScreenOrientationLockNatural; |
| else if (LowerCaseEqualsASCII(orientation.string(), "landscape")) |
| return blink::WebScreenOrientationLockLandscape; |
| else if (LowerCaseEqualsASCII(orientation.string(), "landscape-primary")) |
| return blink::WebScreenOrientationLockLandscapePrimary; |
| else if (LowerCaseEqualsASCII(orientation.string(), "landscape-secondary")) |
| return blink::WebScreenOrientationLockLandscapeSecondary; |
| else if (LowerCaseEqualsASCII(orientation.string(), "portrait")) |
| return blink::WebScreenOrientationLockPortrait; |
| else if (LowerCaseEqualsASCII(orientation.string(), "portrait-primary")) |
| return blink::WebScreenOrientationLockPortraitPrimary; |
| else if (LowerCaseEqualsASCII(orientation.string(), "portrait-secondary")) |
| return blink::WebScreenOrientationLockPortraitSecondary; |
| else |
| return blink::WebScreenOrientationLockDefault; |
| } |
| |
| // Parses the 'src' field of an icon, as defined in: |
| // http://w3c.github.io/manifest/#dfn-steps-for-processing-the-src-member-of-an-icon |
| // Returns the parsed GURL if any, an empty GURL if the parsing failed. |
| GURL ParseIconSrc(const base::DictionaryValue& icon, |
| const GURL& manifest_url) { |
| return ParseURL(icon, "src", manifest_url); |
| } |
| |
| // Parses the 'type' field of an icon, as defined in: |
| // http://w3c.github.io/manifest/#dfn-steps-for-processing-the-type-member-of-an-icon |
| // Returns the parsed string if any, a null string if the parsing failed. |
| base::NullableString16 ParseIconType(const base::DictionaryValue& icon) { |
| return ParseString(icon, "type", Trim); |
| } |
| |
| // Parses the 'density' field of an icon, as defined in: |
| // http://w3c.github.io/manifest/#dfn-steps-for-processing-a-density-member-of-an-icon |
| // Returns the parsed double if any, Manifest::Icon::kDefaultDensity if the |
| // parsing failed. |
| double ParseIconDensity(const base::DictionaryValue& icon) { |
| double density; |
| if (!icon.GetDouble("density", &density) || density <= 0) |
| return Manifest::Icon::kDefaultDensity; |
| return density; |
| } |
| |
| // Helper function that returns whether the given |str| is a valid width or |
| // height value for an icon sizes per: |
| // https://html.spec.whatwg.org/multipage/semantics.html#attr-link-sizes |
| bool IsValidIconWidthOrHeight(const std::string& str) { |
| if (str.empty() || str[0] == '0') |
| return false; |
| for (size_t i = 0; i < str.size(); ++i) |
| if (!IsAsciiDigit(str[i])) |
| return false; |
| return true; |
| } |
| |
| // Parses the 'sizes' attribute of an icon as described in the HTML spec: |
| // https://html.spec.whatwg.org/multipage/semantics.html#attr-link-sizes |
| // Return a vector of gfx::Size that contains the valid sizes found. "Any" is |
| // represented by gfx::Size(0, 0). |
| // TODO(mlamouri): this is implemented as a separate function because it should |
| // be refactored with the other icon sizes parsing implementations, see |
| // http://crbug.com/416477 |
| std::vector<gfx::Size> ParseIconSizesHTML(const base::string16& sizes_str16) { |
| if (!base::IsStringASCII(sizes_str16)) |
| return std::vector<gfx::Size>(); |
| |
| std::vector<gfx::Size> sizes; |
| std::string sizes_str = |
| base::StringToLowerASCII(base::UTF16ToUTF8(sizes_str16)); |
| std::vector<std::string> sizes_str_list; |
| base::SplitStringAlongWhitespace(sizes_str, &sizes_str_list); |
| |
| for (size_t i = 0; i < sizes_str_list.size(); ++i) { |
| std::string& size_str = sizes_str_list[i]; |
| if (size_str == "any") { |
| sizes.push_back(gfx::Size(0, 0)); |
| continue; |
| } |
| |
| // It is expected that [0] => width and [1] => height after the split. |
| std::vector<std::string> size_list; |
| base::SplitStringDontTrim(size_str, L'x', &size_list); |
| if (size_list.size() != 2) |
| continue; |
| if (!IsValidIconWidthOrHeight(size_list[0]) || |
| !IsValidIconWidthOrHeight(size_list[1])) { |
| continue; |
| } |
| |
| int width, height; |
| if (!base::StringToInt(size_list[0], &width) || |
| !base::StringToInt(size_list[1], &height)) { |
| continue; |
| } |
| |
| sizes.push_back(gfx::Size(width, height)); |
| } |
| |
| return sizes; |
| } |
| |
| // Parses the 'sizes' field of an icon, as defined in: |
| // http://w3c.github.io/manifest/#dfn-steps-for-processing-a-sizes-member-of-an-icon |
| // Returns a vector of gfx::Size with the successfully parsed sizes, if any. An |
| // empty vector if the field was not present or empty. "Any" is represented by |
| // gfx::Size(0, 0). |
| std::vector<gfx::Size> ParseIconSizes(const base::DictionaryValue& icon) { |
| base::NullableString16 sizes_str = ParseString(icon, "sizes", NoTrim); |
| |
| return sizes_str.is_null() ? std::vector<gfx::Size>() |
| : ParseIconSizesHTML(sizes_str.string()); |
| } |
| |
| // Parses the 'icons' field of a Manifest, as defined in: |
| // http://w3c.github.io/manifest/#dfn-steps-for-processing-the-icons-member |
| // Returns a vector of Manifest::Icon with the successfully parsed icons, if |
| // any. An empty vector if the field was not present or empty. |
| std::vector<Manifest::Icon> ParseIcons(const base::DictionaryValue& dictionary, |
| const GURL& manifest_url) { |
| std::vector<Manifest::Icon> icons; |
| if (!dictionary.HasKey("icons")) |
| return icons; |
| |
| const base::ListValue* icons_list = 0; |
| if (!dictionary.GetList("icons", &icons_list)) { |
| // TODO(mlamouri): provide a custom message to the developer console about |
| // the property being incorrectly set. |
| return icons; |
| } |
| |
| for (size_t i = 0; i < icons_list->GetSize(); ++i) { |
| const base::DictionaryValue* icon_dictionary = 0; |
| if (!icons_list->GetDictionary(i, &icon_dictionary)) |
| continue; |
| |
| Manifest::Icon icon; |
| icon.src = ParseIconSrc(*icon_dictionary, manifest_url); |
| // An icon MUST have a valid src. If it does not, it MUST be ignored. |
| if (!icon.src.is_valid()) |
| continue; |
| icon.type = ParseIconType(*icon_dictionary); |
| icon.density = ParseIconDensity(*icon_dictionary); |
| icon.sizes = ParseIconSizes(*icon_dictionary); |
| |
| icons.push_back(icon); |
| } |
| |
| return icons; |
| } |
| |
| // Parses the 'gcm_sender_id' field of the manifest. |
| // This is a proprietary extension of the Web Manifest specification. |
| // Returns the parsed string if any, a null string if the parsing failed. |
| base::NullableString16 ParseGCMSenderID( |
| const base::DictionaryValue& dictionary) { |
| return ParseString(dictionary, "gcm_sender_id", Trim); |
| } |
| |
| } // anonymous namespace |
| |
| Manifest ManifestParser::Parse(const base::StringPiece& json, |
| const GURL& manifest_url, |
| const GURL& document_url) { |
| scoped_ptr<base::Value> value(base::JSONReader::Read(json)); |
| if (!value) { |
| // TODO(mlamouri): get the JSON parsing error and report it to the developer |
| // console. |
| ManifestUmaUtil::ParseFailed(); |
| return Manifest(); |
| } |
| |
| if (value->GetType() != base::Value::TYPE_DICTIONARY) { |
| // TODO(mlamouri): provide a custom message to the developer console. |
| ManifestUmaUtil::ParseFailed(); |
| return Manifest(); |
| } |
| |
| base::DictionaryValue* dictionary = 0; |
| value->GetAsDictionary(&dictionary); |
| if (!dictionary) { |
| // TODO(mlamouri): provide a custom message to the developer console. |
| ManifestUmaUtil::ParseFailed(); |
| return Manifest(); |
| } |
| |
| Manifest manifest; |
| |
| manifest.name = ParseName(*dictionary); |
| manifest.short_name = ParseShortName(*dictionary); |
| manifest.start_url = ParseStartURL(*dictionary, manifest_url, document_url); |
| manifest.display = ParseDisplay(*dictionary); |
| manifest.orientation = ParseOrientation(*dictionary); |
| manifest.icons = ParseIcons(*dictionary, manifest_url); |
| manifest.gcm_sender_id = ParseGCMSenderID(*dictionary); |
| |
| ManifestUmaUtil::ParseSucceeded(manifest); |
| |
| return manifest; |
| } |
| |
| } // namespace content |