1 // Copyright 2014 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "extensions/common/manifest_handlers/externally_connectable.h" 6 7 #include <algorithm> 8 9 #include "base/stl_util.h" 10 #include "base/strings/utf_string_conversions.h" 11 #include "components/crx_file/id_util.h" 12 #include "extensions/common/api/extensions_manifest_types.h" 13 #include "extensions/common/error_utils.h" 14 #include "extensions/common/manifest_constants.h" 15 #include "extensions/common/manifest_handlers/permissions_parser.h" 16 #include "extensions/common/permissions/api_permission_set.h" 17 #include "extensions/common/url_pattern.h" 18 #include "net/base/registry_controlled_domains/registry_controlled_domain.h" 19 #include "url/gurl.h" 20 21 namespace rcd = net::registry_controlled_domains; 22 23 namespace extensions { 24 25 namespace externally_connectable_errors { 26 const char kErrorInvalidMatchPattern[] = "Invalid match pattern '*'"; 27 const char kErrorInvalidId[] = "Invalid ID '*'"; 28 const char kErrorNothingSpecified[] = 29 "'externally_connectable' specifies neither 'matches' nor 'ids'; " 30 "nothing will be able to connect"; 31 const char kErrorTopLevelDomainsNotAllowed[] = 32 "\"*\" is an effective top level domain for which wildcard subdomains such " 33 "as \"*\" are not allowed"; 34 const char kErrorWildcardHostsNotAllowed[] = 35 "Wildcard domain patterns such as \"*\" are not allowed"; 36 } // namespace externally_connectable_errors 37 38 namespace keys = extensions::manifest_keys; 39 namespace errors = externally_connectable_errors; 40 using core_api::extensions_manifest_types::ExternallyConnectable; 41 42 namespace { 43 44 const char kAllIds[] = "*"; 45 46 template <typename T> 47 std::vector<T> Sorted(const std::vector<T>& in) { 48 std::vector<T> out = in; 49 std::sort(out.begin(), out.end()); 50 return out; 51 } 52 53 } // namespace 54 55 ExternallyConnectableHandler::ExternallyConnectableHandler() { 56 } 57 58 ExternallyConnectableHandler::~ExternallyConnectableHandler() { 59 } 60 61 bool ExternallyConnectableHandler::Parse(Extension* extension, 62 base::string16* error) { 63 const base::Value* externally_connectable = NULL; 64 CHECK(extension->manifest()->Get(keys::kExternallyConnectable, 65 &externally_connectable)); 66 bool allow_all_urls = PermissionsParser::HasAPIPermission( 67 extension, APIPermission::kExternallyConnectableAllUrls); 68 69 std::vector<InstallWarning> install_warnings; 70 scoped_ptr<ExternallyConnectableInfo> info = 71 ExternallyConnectableInfo::FromValue( 72 *externally_connectable, allow_all_urls, &install_warnings, error); 73 if (!info) 74 return false; 75 if (!info->matches.is_empty()) { 76 PermissionsParser::AddAPIPermission(extension, 77 APIPermission::kWebConnectable); 78 } 79 extension->AddInstallWarnings(install_warnings); 80 extension->SetManifestData(keys::kExternallyConnectable, info.release()); 81 return true; 82 } 83 84 const std::vector<std::string> ExternallyConnectableHandler::Keys() const { 85 return SingleKey(keys::kExternallyConnectable); 86 } 87 88 // static 89 ExternallyConnectableInfo* ExternallyConnectableInfo::Get( 90 const Extension* extension) { 91 return static_cast<ExternallyConnectableInfo*>( 92 extension->GetManifestData(keys::kExternallyConnectable)); 93 } 94 95 // static 96 scoped_ptr<ExternallyConnectableInfo> ExternallyConnectableInfo::FromValue( 97 const base::Value& value, 98 bool allow_all_urls, 99 std::vector<InstallWarning>* install_warnings, 100 base::string16* error) { 101 scoped_ptr<ExternallyConnectable> externally_connectable = 102 ExternallyConnectable::FromValue(value, error); 103 if (!externally_connectable) 104 return scoped_ptr<ExternallyConnectableInfo>(); 105 106 URLPatternSet matches; 107 108 if (externally_connectable->matches) { 109 for (std::vector<std::string>::iterator it = 110 externally_connectable->matches->begin(); 111 it != externally_connectable->matches->end(); 112 ++it) { 113 // Safe to use SCHEME_ALL here; externally_connectable gives a page -> 114 // extension communication path, not the other way. 115 URLPattern pattern(URLPattern::SCHEME_ALL); 116 if (pattern.Parse(*it) != URLPattern::PARSE_SUCCESS) { 117 *error = ErrorUtils::FormatErrorMessageUTF16( 118 errors::kErrorInvalidMatchPattern, *it); 119 return scoped_ptr<ExternallyConnectableInfo>(); 120 } 121 122 if (allow_all_urls && pattern.match_all_urls()) { 123 matches.AddPattern(pattern); 124 continue; 125 } 126 127 // Wildcard hosts are not allowed. 128 if (pattern.host().empty()) { 129 // Warning not error for forwards compatibility. 130 install_warnings->push_back( 131 InstallWarning(ErrorUtils::FormatErrorMessage( 132 errors::kErrorWildcardHostsNotAllowed, *it), 133 keys::kExternallyConnectable, 134 *it)); 135 continue; 136 } 137 138 // Wildcards on subdomains of a TLD are not allowed. 139 size_t registry_length = rcd::GetRegistryLength( 140 pattern.host(), 141 // This means that things that look like TLDs - the foobar in 142 // http://google.foobar - count as TLDs. 143 rcd::INCLUDE_UNKNOWN_REGISTRIES, 144 // This means that effective TLDs like appspot.com count as TLDs; 145 // codereview.appspot.com and evil.appspot.com are different. 146 rcd::INCLUDE_PRIVATE_REGISTRIES); 147 148 if (registry_length == std::string::npos) { 149 // The URL parsing combined with host().empty() should have caught this. 150 NOTREACHED() << *it; 151 *error = ErrorUtils::FormatErrorMessageUTF16( 152 errors::kErrorInvalidMatchPattern, *it); 153 return scoped_ptr<ExternallyConnectableInfo>(); 154 } 155 156 // Broad match patterns like "*.com", "*.co.uk", and even "*.appspot.com" 157 // are not allowed. However just "appspot.com" is ok. 158 if (registry_length == 0 && pattern.match_subdomains()) { 159 // Warning not error for forwards compatibility. 160 install_warnings->push_back( 161 InstallWarning(ErrorUtils::FormatErrorMessage( 162 errors::kErrorTopLevelDomainsNotAllowed, 163 pattern.host().c_str(), 164 *it), 165 keys::kExternallyConnectable, 166 *it)); 167 continue; 168 } 169 170 matches.AddPattern(pattern); 171 } 172 } 173 174 std::vector<std::string> ids; 175 bool all_ids = false; 176 177 if (externally_connectable->ids) { 178 for (std::vector<std::string>::iterator it = 179 externally_connectable->ids->begin(); 180 it != externally_connectable->ids->end(); 181 ++it) { 182 if (*it == kAllIds) { 183 all_ids = true; 184 } else if (crx_file::id_util::IdIsValid(*it)) { 185 ids.push_back(*it); 186 } else { 187 *error = 188 ErrorUtils::FormatErrorMessageUTF16(errors::kErrorInvalidId, *it); 189 return scoped_ptr<ExternallyConnectableInfo>(); 190 } 191 } 192 } 193 194 if (!externally_connectable->matches && !externally_connectable->ids) { 195 install_warnings->push_back(InstallWarning(errors::kErrorNothingSpecified, 196 keys::kExternallyConnectable)); 197 } 198 199 bool accepts_tls_channel_id = 200 externally_connectable->accepts_tls_channel_id.get() && 201 *externally_connectable->accepts_tls_channel_id; 202 return make_scoped_ptr(new ExternallyConnectableInfo( 203 matches, ids, all_ids, accepts_tls_channel_id)); 204 } 205 206 ExternallyConnectableInfo::~ExternallyConnectableInfo() { 207 } 208 209 ExternallyConnectableInfo::ExternallyConnectableInfo( 210 const URLPatternSet& matches, 211 const std::vector<std::string>& ids, 212 bool all_ids, 213 bool accepts_tls_channel_id) 214 : matches(matches), 215 ids(Sorted(ids)), 216 all_ids(all_ids), 217 accepts_tls_channel_id(accepts_tls_channel_id) { 218 } 219 220 bool ExternallyConnectableInfo::IdCanConnect(const std::string& id) { 221 if (all_ids) 222 return true; 223 DCHECK(base::STLIsSorted(ids)); 224 return std::binary_search(ids.begin(), ids.end(), id); 225 } 226 227 } // namespace extensions 228