// 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 "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; } } // 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. return Manifest(); } if (value->GetType() != base::Value::TYPE_DICTIONARY) { // TODO(mlamouri): provide a custom message to the developer console. return Manifest(); } base::DictionaryValue* dictionary = 0; value->GetAsDictionary(&dictionary); if (!dictionary) { // TODO(mlamouri): provide a custom message to the developer console. 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); return manifest; } } // namespace content