blob: 924d2aa9668581ef09e52d8e38378dd45423f3b0 [file] [log] [blame]
// 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 {
// 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;
}
const std::string& GetErrorPrefix() {
CR_DEFINE_STATIC_LOCAL(std::string, error_prefix,
("Manifest parsing error: "));
return error_prefix;
}
} // anonymous namespace
ManifestParser::ManifestParser(const base::StringPiece& data,
const GURL& manifest_url,
const GURL& document_url)
: data_(data),
manifest_url_(manifest_url),
document_url_(document_url),
failed_(false) {
}
ManifestParser::~ManifestParser() {
}
void ManifestParser::Parse() {
std::string parse_error;
scoped_ptr<base::Value> value(base::JSONReader::DeprecatedReadAndReturnError(
data_, base::JSON_PARSE_RFC, nullptr, &parse_error));
if (!value) {
errors_.push_back(GetErrorPrefix() + parse_error);
ManifestUmaUtil::ParseFailed();
failed_ = true;
return;
}
base::DictionaryValue* dictionary = nullptr;
if (!value->GetAsDictionary(&dictionary)) {
errors_.push_back(GetErrorPrefix() +
"root element must be a valid JSON object.");
ManifestUmaUtil::ParseFailed();
failed_ = true;
return;
}
DCHECK(dictionary);
manifest_.name = ParseName(*dictionary);
manifest_.short_name = ParseShortName(*dictionary);
manifest_.start_url = ParseStartURL(*dictionary);
manifest_.display = ParseDisplay(*dictionary);
manifest_.orientation = ParseOrientation(*dictionary);
manifest_.icons = ParseIcons(*dictionary);
manifest_.related_applications = ParseRelatedApplications(*dictionary);
manifest_.prefer_related_applications =
ParsePreferRelatedApplications(*dictionary);
manifest_.gcm_sender_id = ParseGCMSenderID(*dictionary);
manifest_.gcm_user_visible_only = ParseGCMUserVisibleOnly(*dictionary);
ManifestUmaUtil::ParseSucceeded(manifest_);
}
const Manifest& ManifestParser::manifest() const {
return manifest_;
}
const std::vector<std::string>& ManifestParser::errors() const {
return errors_;
}
bool ManifestParser::failed() const {
return failed_;
}
bool ManifestParser::ParseBoolean(const base::DictionaryValue& dictionary,
const std::string& key,
bool default_value) {
if (!dictionary.HasKey(key))
return default_value;
bool value;
if (!dictionary.GetBoolean(key, &value)) {
errors_.push_back(GetErrorPrefix() +
"property '" + key + "' ignored, type boolean expected.");
return default_value;
}
return value;
}
base::NullableString16 ManifestParser::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)) {
errors_.push_back(GetErrorPrefix() +
"property '" + key + "' ignored, type string expected.");
return base::NullableString16();
}
if (trim == Trim)
base::TrimWhitespace(value, base::TRIM_ALL, &value);
return base::NullableString16(value, false);
}
GURL ManifestParser::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());
}
base::NullableString16 ManifestParser::ParseName(
const base::DictionaryValue& dictionary) {
return ParseString(dictionary, "name", Trim);
}
base::NullableString16 ManifestParser::ParseShortName(
const base::DictionaryValue& dictionary) {
return ParseString(dictionary, "short_name", Trim);
}
GURL ManifestParser::ParseStartURL(const base::DictionaryValue& dictionary) {
GURL start_url = ParseURL(dictionary, "start_url", manifest_url_);
if (!start_url.is_valid())
return GURL();
if (start_url.GetOrigin() != document_url_.GetOrigin()) {
errors_.push_back(GetErrorPrefix() + "property 'start_url' ignored, should "
"be same origin as document.");
return GURL();
}
return start_url;
}
Manifest::DisplayMode ManifestParser::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 {
errors_.push_back(GetErrorPrefix() + "unknown 'display' value ignored.");
return Manifest::DISPLAY_MODE_UNSPECIFIED;
}
}
blink::WebScreenOrientationLockType ManifestParser::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 {
errors_.push_back(GetErrorPrefix() +
"unknown 'orientation' value ignored.");
return blink::WebScreenOrientationLockDefault;
}
}
GURL ManifestParser::ParseIconSrc(const base::DictionaryValue& icon) {
return ParseURL(icon, "src", manifest_url_);
}
base::NullableString16 ManifestParser::ParseIconType(
const base::DictionaryValue& icon) {
return ParseString(icon, "type", Trim);
}
double ManifestParser::ParseIconDensity(const base::DictionaryValue& icon) {
double density;
if (!icon.HasKey("density"))
return Manifest::Icon::kDefaultDensity;
if (!icon.GetDouble("density", &density) || density <= 0) {
errors_.push_back(GetErrorPrefix() +
"icon 'density' ignored, must be float greater than 0.");
return Manifest::Icon::kDefaultDensity;
}
return density;
}
std::vector<gfx::Size> ManifestParser::ParseIconSizes(
const base::DictionaryValue& icon) {
base::NullableString16 sizes_str = ParseString(icon, "sizes", NoTrim);
if (sizes_str.is_null())
return std::vector<gfx::Size>();
std::vector<gfx::Size> sizes = ParseIconSizesHTML(sizes_str.string());
if (sizes.empty()) {
errors_.push_back(GetErrorPrefix() + "found icon with no valid size.");
}
return sizes;
}
std::vector<Manifest::Icon> ManifestParser::ParseIcons(
const base::DictionaryValue& dictionary) {
std::vector<Manifest::Icon> icons;
if (!dictionary.HasKey("icons"))
return icons;
const base::ListValue* icons_list = nullptr;
if (!dictionary.GetList("icons", &icons_list)) {
errors_.push_back(GetErrorPrefix() +
"property 'icons' ignored, type array expected.");
return icons;
}
for (size_t i = 0; i < icons_list->GetSize(); ++i) {
const base::DictionaryValue* icon_dictionary = nullptr;
if (!icons_list->GetDictionary(i, &icon_dictionary))
continue;
Manifest::Icon icon;
icon.src = ParseIconSrc(*icon_dictionary);
// 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;
}
base::NullableString16 ManifestParser::ParseRelatedApplicationPlatform(
const base::DictionaryValue& application) {
return ParseString(application, "platform", Trim);
}
GURL ManifestParser::ParseRelatedApplicationURL(
const base::DictionaryValue& application) {
return ParseURL(application, "url", manifest_url_);
}
base::NullableString16 ManifestParser::ParseRelatedApplicationId(
const base::DictionaryValue& application) {
return ParseString(application, "id", Trim);
}
std::vector<Manifest::RelatedApplication>
ManifestParser::ParseRelatedApplications(
const base::DictionaryValue& dictionary) {
std::vector<Manifest::RelatedApplication> applications;
if (!dictionary.HasKey("related_applications"))
return applications;
const base::ListValue* applications_list = nullptr;
if (!dictionary.GetList("related_applications", &applications_list)) {
errors_.push_back(
GetErrorPrefix() +
"property 'related_applications' ignored, type array expected.");
return applications;
}
for (size_t i = 0; i < applications_list->GetSize(); ++i) {
const base::DictionaryValue* application_dictionary = nullptr;
if (!applications_list->GetDictionary(i, &application_dictionary))
continue;
Manifest::RelatedApplication application;
application.platform =
ParseRelatedApplicationPlatform(*application_dictionary);
// "If platform is undefined, move onto the next item if any are left."
if (application.platform.is_null()) {
errors_.push_back(
GetErrorPrefix() +
"'platform' is a required field, related application ignored.");
continue;
}
application.id = ParseRelatedApplicationId(*application_dictionary);
application.url = ParseRelatedApplicationURL(*application_dictionary);
// "If both id and url are undefined, move onto the next item if any are
// left."
if (application.url.is_empty() && application.id.is_null()) {
errors_.push_back(
GetErrorPrefix() +
"one of 'url' or 'id' is required, related application ignored.");
continue;
}
applications.push_back(application);
}
return applications;
}
bool ManifestParser::ParsePreferRelatedApplications(
const base::DictionaryValue& dictionary) {
return ParseBoolean(dictionary, "prefer_related_applications", false);
}
base::NullableString16 ManifestParser::ParseGCMSenderID(
const base::DictionaryValue& dictionary) {
return ParseString(dictionary, "gcm_sender_id", Trim);
}
bool ManifestParser::ParseGCMUserVisibleOnly(
const base::DictionaryValue& dictionary) {
return ParseBoolean(dictionary, "gcm_user_visible_only", false);
}
} // namespace content