| // 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 |