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