// 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 "extensions/common/manifest_handlers/externally_connectable.h"
#include <algorithm>
#include "base/stl_util.h"
#include "base/strings/utf_string_conversions.h"
#include "extensions/common/api/extensions_manifest_types.h"
#include "extensions/common/error_utils.h"
#include "extensions/common/manifest_constants.h"
#include "extensions/common/manifest_handlers/permissions_parser.h"
#include "extensions/common/permissions/api_permission_set.h"
#include "extensions/common/url_pattern.h"
#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
#include "url/gurl.h"
namespace rcd = net::registry_controlled_domains;
namespace extensions {
namespace externally_connectable_errors {
const char kErrorInvalidMatchPattern[] = "Invalid match pattern '*'";
const char kErrorInvalidId[] = "Invalid ID '*'";
const char kErrorNothingSpecified[] =
"'externally_connectable' specifies neither 'matches' nor 'ids'; "
"nothing will be able to connect";
const char kErrorTopLevelDomainsNotAllowed[] =
"\"*\" is an effective top level domain for which wildcard subdomains such "
"as \"*\" are not allowed";
const char kErrorWildcardHostsNotAllowed[] =
"Wildcard domain patterns such as \"*\" are not allowed";
} // namespace externally_connectable_errors
namespace keys = extensions::manifest_keys;
namespace errors = externally_connectable_errors;
using core_api::extensions_manifest_types::ExternallyConnectable;
namespace {
const char kAllIds[] = "*";
template <typename T>
std::vector<T> Sorted(const std::vector<T>& in) {
std::vector<T> out = in;
std::sort(out.begin(), out.end());
return out;
}
} // namespace
ExternallyConnectableHandler::ExternallyConnectableHandler() {
}
ExternallyConnectableHandler::~ExternallyConnectableHandler() {
}
bool ExternallyConnectableHandler::Parse(Extension* extension,
base::string16* error) {
const base::Value* externally_connectable = NULL;
CHECK(extension->manifest()->Get(keys::kExternallyConnectable,
&externally_connectable));
std::vector<InstallWarning> install_warnings;
scoped_ptr<ExternallyConnectableInfo> info =
ExternallyConnectableInfo::FromValue(*externally_connectable,
&install_warnings,
error);
if (!info)
return false;
if (!info->matches.is_empty()) {
PermissionsParser::AddAPIPermission(extension,
APIPermission::kWebConnectable);
}
extension->AddInstallWarnings(install_warnings);
extension->SetManifestData(keys::kExternallyConnectable, info.release());
return true;
}
const std::vector<std::string> ExternallyConnectableHandler::Keys() const {
return SingleKey(keys::kExternallyConnectable);
}
// static
ExternallyConnectableInfo* ExternallyConnectableInfo::Get(
const Extension* extension) {
return static_cast<ExternallyConnectableInfo*>(
extension->GetManifestData(keys::kExternallyConnectable));
}
// static
scoped_ptr<ExternallyConnectableInfo> ExternallyConnectableInfo::FromValue(
const base::Value& value,
std::vector<InstallWarning>* install_warnings,
base::string16* error) {
scoped_ptr<ExternallyConnectable> externally_connectable =
ExternallyConnectable::FromValue(value, error);
if (!externally_connectable)
return scoped_ptr<ExternallyConnectableInfo>();
URLPatternSet matches;
if (externally_connectable->matches) {
for (std::vector<std::string>::iterator it =
externally_connectable->matches->begin();
it != externally_connectable->matches->end();
++it) {
// Safe to use SCHEME_ALL here; externally_connectable gives a page ->
// extension communication path, not the other way.
URLPattern pattern(URLPattern::SCHEME_ALL);
if (pattern.Parse(*it) != URLPattern::PARSE_SUCCESS) {
*error = ErrorUtils::FormatErrorMessageUTF16(
errors::kErrorInvalidMatchPattern, *it);
return scoped_ptr<ExternallyConnectableInfo>();
}
// Wildcard hosts are not allowed.
if (pattern.host().empty()) {
// Warning not error for forwards compatibility.
install_warnings->push_back(
InstallWarning(ErrorUtils::FormatErrorMessage(
errors::kErrorWildcardHostsNotAllowed, *it),
keys::kExternallyConnectable,
*it));
continue;
}
// Wildcards on subdomains of a TLD are not allowed.
size_t registry_length = rcd::GetRegistryLength(
pattern.host(),
// This means that things that look like TLDs - the foobar in
// http://google.foobar - count as TLDs.
rcd::INCLUDE_UNKNOWN_REGISTRIES,
// This means that effective TLDs like appspot.com count as TLDs;
// codereview.appspot.com and evil.appspot.com are different.
rcd::INCLUDE_PRIVATE_REGISTRIES);
if (registry_length == std::string::npos) {
// The URL parsing combined with host().empty() should have caught this.
NOTREACHED() << *it;
*error = ErrorUtils::FormatErrorMessageUTF16(
errors::kErrorInvalidMatchPattern, *it);
return scoped_ptr<ExternallyConnectableInfo>();
}
// Broad match patterns like "*.com", "*.co.uk", and even "*.appspot.com"
// are not allowed. However just "appspot.com" is ok.
if (registry_length == 0 && pattern.match_subdomains()) {
// Warning not error for forwards compatibility.
install_warnings->push_back(
InstallWarning(ErrorUtils::FormatErrorMessage(
errors::kErrorTopLevelDomainsNotAllowed,
pattern.host().c_str(),
*it),
keys::kExternallyConnectable,
*it));
continue;
}
matches.AddPattern(pattern);
}
}
std::vector<std::string> ids;
bool all_ids = false;
if (externally_connectable->ids) {
for (std::vector<std::string>::iterator it =
externally_connectable->ids->begin();
it != externally_connectable->ids->end();
++it) {
if (*it == kAllIds) {
all_ids = true;
} else if (Extension::IdIsValid(*it)) {
ids.push_back(*it);
} else {
*error =
ErrorUtils::FormatErrorMessageUTF16(errors::kErrorInvalidId, *it);
return scoped_ptr<ExternallyConnectableInfo>();
}
}
}
if (!externally_connectable->matches && !externally_connectable->ids) {
install_warnings->push_back(InstallWarning(errors::kErrorNothingSpecified,
keys::kExternallyConnectable));
}
bool accepts_tls_channel_id =
externally_connectable->accepts_tls_channel_id.get() &&
*externally_connectable->accepts_tls_channel_id;
return make_scoped_ptr(new ExternallyConnectableInfo(
matches, ids, all_ids, accepts_tls_channel_id));
}
ExternallyConnectableInfo::~ExternallyConnectableInfo() {
}
ExternallyConnectableInfo::ExternallyConnectableInfo(
const URLPatternSet& matches,
const std::vector<std::string>& ids,
bool all_ids,
bool accepts_tls_channel_id)
: matches(matches),
ids(Sorted(ids)),
all_ids(all_ids),
accepts_tls_channel_id(accepts_tls_channel_id) {
}
bool ExternallyConnectableInfo::IdCanConnect(const std::string& id) {
if (all_ids)
return true;
DCHECK(base::STLIsSorted(ids));
return std::binary_search(ids.begin(), ids.end(), id);
}
} // namespace extensions