1// Copyright (c) 2012 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/permissions/socket_permission_data.h"
6
7#include <cstdlib>
8#include <sstream>
9#include <vector>
10
11#include "base/logging.h"
12#include "base/memory/scoped_ptr.h"
13#include "base/strings/string_number_conversions.h"
14#include "base/strings/string_split.h"
15#include "base/strings/string_util.h"
16#include "chrome/common/extensions/permissions/api_permission.h"
17#include "chrome/common/extensions/permissions/socket_permission.h"
18#include "url/url_canon.h"
19
20namespace {
21
22using content::SocketPermissionRequest;
23using extensions::SocketPermissionData;
24
25const char kColon = ':';
26const char kDot = '.';
27const char kWildcard[] = "*";
28const char kInvalid[] = "invalid";
29const char kTCPConnect[] = "tcp-connect";
30const char kTCPListen[] = "tcp-listen";
31const char kUDPBind[] = "udp-bind";
32const char kUDPSendTo[] = "udp-send-to";
33const char kUDPMulticastMembership[] = "udp-multicast-membership";
34const char kResolveHost[] = "resolve-host";
35const char kResolveProxy[] = "resolve-proxy";
36const int kWildcardPortNumber = 0;
37const int kInvalidPort = -1;
38
39SocketPermissionRequest::OperationType StringToType(const std::string& s) {
40  if (s == kTCPConnect)
41    return SocketPermissionRequest::TCP_CONNECT;
42  if (s == kTCPListen)
43    return SocketPermissionRequest::TCP_LISTEN;
44  if (s == kUDPBind)
45    return SocketPermissionRequest::UDP_BIND;
46  if (s == kUDPSendTo)
47    return SocketPermissionRequest::UDP_SEND_TO;
48  if (s == kUDPMulticastMembership)
49    return SocketPermissionRequest::UDP_MULTICAST_MEMBERSHIP;
50  if (s == kResolveHost)
51    return SocketPermissionRequest::RESOLVE_HOST;
52  if (s == kResolveProxy)
53    return SocketPermissionRequest::RESOLVE_PROXY;
54  return SocketPermissionRequest::NONE;
55}
56
57const char* TypeToString(SocketPermissionRequest::OperationType type) {
58  switch (type) {
59    case SocketPermissionRequest::TCP_CONNECT:
60      return kTCPConnect;
61    case SocketPermissionRequest::TCP_LISTEN:
62      return kTCPListen;
63    case SocketPermissionRequest::UDP_BIND:
64      return kUDPBind;
65    case SocketPermissionRequest::UDP_SEND_TO:
66      return kUDPSendTo;
67    case SocketPermissionRequest::UDP_MULTICAST_MEMBERSHIP:
68      return kUDPMulticastMembership;
69    case SocketPermissionRequest::RESOLVE_HOST:
70      return kResolveHost;
71    case SocketPermissionRequest::RESOLVE_PROXY:
72      return kResolveProxy;
73    default:
74      return kInvalid;
75  }
76}
77
78bool StartsOrEndsWithWhitespace(const std::string& str) {
79  if (str.find_first_not_of(kWhitespaceASCII) != 0)
80    return true;
81  if (str.find_last_not_of(kWhitespaceASCII) != str.length() - 1)
82    return true;
83  return false;
84}
85
86}  // namespace
87
88namespace extensions {
89
90SocketPermissionData::SocketPermissionData()
91  : pattern_(SocketPermissionRequest::NONE, std::string(), kInvalidPort) {
92  Reset();
93}
94
95SocketPermissionData::~SocketPermissionData() {
96}
97
98bool SocketPermissionData::operator<(const SocketPermissionData& rhs) const {
99  if (pattern_.type < rhs.pattern_.type)
100    return true;
101  if (pattern_.type > rhs.pattern_.type)
102    return false;
103
104  if (pattern_.host < rhs.pattern_.host)
105    return true;
106  if (pattern_.host > rhs.pattern_.host)
107    return false;
108
109  if (match_subdomains_ < rhs.match_subdomains_)
110    return true;
111  if (match_subdomains_ > rhs.match_subdomains_)
112    return false;
113
114  if (pattern_.port < rhs.pattern_.port)
115    return true;
116  return false;
117}
118
119bool SocketPermissionData::operator==(const SocketPermissionData& rhs) const {
120  return (pattern_.type == rhs.pattern_.type) &&
121         (pattern_.host == rhs.pattern_.host) &&
122         (match_subdomains_ == rhs.match_subdomains_) &&
123         (pattern_.port == rhs.pattern_.port);
124}
125
126bool SocketPermissionData::Check(
127    const APIPermission::CheckParam* param) const {
128  if (!param)
129    return false;
130  const SocketPermission::CheckParam& specific_param =
131      *static_cast<const SocketPermission::CheckParam*>(param);
132  const SocketPermissionRequest &request = specific_param.request;
133
134  if (pattern_.type != request.type)
135    return false;
136
137  std::string lhost = StringToLowerASCII(request.host);
138  if (pattern_.host != lhost) {
139    if (!match_subdomains_)
140      return false;
141
142    if (!pattern_.host.empty()) {
143      // Do not wildcard part of IP address.
144      url_parse::Component component(0, lhost.length());
145      url_canon::RawCanonOutputT<char, 128> ignored_output;
146      url_canon::CanonHostInfo host_info;
147      url_canon::CanonicalizeIPAddress(lhost.c_str(), component,
148                                       &ignored_output, &host_info);
149      if (host_info.IsIPAddress())
150        return false;
151
152      // host should equal one or more chars + "." +  host_.
153      int i = lhost.length() - pattern_.host.length();
154      if (i < 2)
155        return false;
156
157      if (lhost.compare(i, pattern_.host.length(), pattern_.host) != 0)
158        return false;
159
160      if (lhost[i - 1] != kDot)
161        return false;
162    }
163  }
164
165  if (pattern_.port != request.port && pattern_.port != kWildcardPortNumber)
166    return false;
167
168  return true;
169}
170
171scoped_ptr<base::Value> SocketPermissionData::ToValue() const {
172  return scoped_ptr<base::Value>(new base::StringValue(GetAsString()));
173}
174
175bool SocketPermissionData::FromValue(const base::Value* value) {
176  std::string spec;
177  if (!value->GetAsString(&spec))
178    return false;
179
180  return Parse(spec);
181}
182
183SocketPermissionData::HostType SocketPermissionData::GetHostType() const {
184  return pattern_.host.empty() ? SocketPermissionData::ANY_HOST :
185         match_subdomains_     ? SocketPermissionData::HOSTS_IN_DOMAINS :
186                                 SocketPermissionData::SPECIFIC_HOSTS;
187}
188
189const std::string SocketPermissionData::GetHost() const {
190  return pattern_.host;
191}
192
193content::SocketPermissionRequest& SocketPermissionData::pattern() {
194  // Clear the spec because the caller could mutate |this|.
195  spec_.clear();
196  return pattern_;
197}
198
199bool& SocketPermissionData::match_subdomains() {
200  // Clear the spec because the caller could mutate |this|.
201  spec_.clear();
202  return match_subdomains_;
203}
204
205// TODO(ikarienator): Rewrite this method to support IPv6.
206bool SocketPermissionData::Parse(const std::string& permission) {
207  do {
208    pattern_.host.clear();
209    match_subdomains_ = true;
210    pattern_.port = kWildcardPortNumber;
211    spec_.clear();
212
213    std::vector<std::string> tokens;
214    base::SplitStringDontTrim(permission, kColon, &tokens);
215
216    if (tokens.empty() || tokens.size() > 3)
217      break;
218
219    pattern_.type = StringToType(tokens[0]);
220    if (pattern_.type == SocketPermissionRequest::NONE)
221      break;
222
223    if (tokens.size() == 1)
224      return true;
225
226    // Multicast membership, resolve proxy and resolve host permission strings
227    // do not carry an address.
228    if (pattern_.type == SocketPermissionRequest::UDP_MULTICAST_MEMBERSHIP ||
229        pattern_.type == SocketPermissionRequest::RESOLVE_PROXY ||
230        pattern_.type == SocketPermissionRequest::RESOLVE_HOST)
231      break;
232
233    pattern_.host = tokens[1];
234    if (!pattern_.host.empty()) {
235      if (StartsOrEndsWithWhitespace(pattern_.host))
236        break;
237      pattern_.host = StringToLowerASCII(pattern_.host);
238
239      // The first component can optionally be '*' to match all subdomains.
240      std::vector<std::string> host_components;
241      base::SplitString(pattern_.host, kDot, &host_components);
242      DCHECK(!host_components.empty());
243
244      if (host_components[0] == kWildcard || host_components[0].empty()) {
245        host_components.erase(host_components.begin(),
246                              host_components.begin() + 1);
247      } else {
248        match_subdomains_ = false;
249      }
250      pattern_.host = JoinString(host_components, kDot);
251    }
252
253    if (tokens.size() == 2 || tokens[2].empty() || tokens[2] == kWildcard)
254      return true;
255
256    if (StartsOrEndsWithWhitespace(tokens[2]))
257      break;
258
259    if (!base::StringToInt(tokens[2], &pattern_.port) ||
260        pattern_.port < 1 || pattern_.port > 65535)
261      break;
262    return true;
263  } while (false);
264
265  Reset();
266  return false;
267}
268
269const std::string& SocketPermissionData::GetAsString() const {
270  if (!spec_.empty())
271    return spec_;
272
273  spec_.reserve(64);
274  spec_.append(TypeToString(pattern_.type));
275
276  if (pattern_.type == SocketPermissionRequest::UDP_MULTICAST_MEMBERSHIP ||
277      pattern_.type == SocketPermissionRequest::RESOLVE_PROXY ||
278      pattern_.type == SocketPermissionRequest::RESOLVE_HOST)
279    return spec_;
280
281  if (match_subdomains_) {
282    spec_.append(1, kColon).append(kWildcard);
283    if (!pattern_.host.empty())
284      spec_.append(1, kDot).append(pattern_.host);
285  } else {
286     spec_.append(1, kColon).append(pattern_.host);
287  }
288
289  if (pattern_.port == kWildcardPortNumber)
290    spec_.append(1, kColon).append(kWildcard);
291  else
292    spec_.append(1, kColon).append(base::IntToString(pattern_.port));
293
294  return spec_;
295}
296
297void SocketPermissionData::Reset() {
298  pattern_.type = SocketPermissionRequest::NONE;
299  pattern_.host.clear();
300  match_subdomains_ = false;
301  pattern_.port = kInvalidPort;
302  spec_.clear();
303}
304
305}  // namespace extensions
306