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
21namespace rcd = net::registry_controlled_domains;
22
23namespace extensions {
24
25namespace externally_connectable_errors {
26const char kErrorInvalidMatchPattern[] = "Invalid match pattern '*'";
27const char kErrorInvalidId[] = "Invalid ID '*'";
28const char kErrorNothingSpecified[] =
29    "'externally_connectable' specifies neither 'matches' nor 'ids'; "
30    "nothing will be able to connect";
31const char kErrorTopLevelDomainsNotAllowed[] =
32    "\"*\" is an effective top level domain for which wildcard subdomains such "
33    "as \"*\" are not allowed";
34const char kErrorWildcardHostsNotAllowed[] =
35    "Wildcard domain patterns such as \"*\" are not allowed";
36}  // namespace externally_connectable_errors
37
38namespace keys = extensions::manifest_keys;
39namespace errors = externally_connectable_errors;
40using core_api::extensions_manifest_types::ExternallyConnectable;
41
42namespace {
43
44const char kAllIds[] = "*";
45
46template <typename T>
47std::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
55ExternallyConnectableHandler::ExternallyConnectableHandler() {
56}
57
58ExternallyConnectableHandler::~ExternallyConnectableHandler() {
59}
60
61bool 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
84const std::vector<std::string> ExternallyConnectableHandler::Keys() const {
85  return SingleKey(keys::kExternallyConnectable);
86}
87
88// static
89ExternallyConnectableInfo* ExternallyConnectableInfo::Get(
90    const Extension* extension) {
91  return static_cast<ExternallyConnectableInfo*>(
92      extension->GetManifestData(keys::kExternallyConnectable));
93}
94
95// static
96scoped_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
206ExternallyConnectableInfo::~ExternallyConnectableInfo() {
207}
208
209ExternallyConnectableInfo::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
220bool 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