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/content_settings_pattern_parser.h"
6
7#include "base/strings/string_util.h"
8#include "chrome/common/url_constants.h"
9#include "extensions/common/constants.h"
10#include "net/base/net_util.h"
11#include "url/gurl.h"
12#include "url/url_canon.h"
13
14namespace {
15
16const char* kUrlPathSeparator = "/";
17const char* kUrlPortSeparator = ":";
18
19class Component {
20 public:
21  Component() : start(0), len(0) {}
22  Component(size_t s, size_t l) : start(s), len(l) {}
23
24  bool IsNonEmpty() {
25    return len > 0;
26  }
27
28  size_t start;
29  size_t len;
30};
31
32}  // namespace
33
34namespace content_settings {
35
36const char* PatternParser::kDomainWildcard = "[*.]";
37
38const size_t PatternParser::kDomainWildcardLength = 4;
39
40const char* PatternParser::kSchemeWildcard = "*";
41
42const char* PatternParser::kHostWildcard = "*";
43
44const char* PatternParser::kPortWildcard = "*";
45
46const char* PatternParser::kPathWildcard = "*";
47
48// static
49void PatternParser::Parse(const std::string& pattern_spec,
50                          ContentSettingsPattern::BuilderInterface* builder) {
51  if (pattern_spec == "*") {
52    builder->WithSchemeWildcard();
53    builder->WithDomainWildcard();
54    builder->WithPortWildcard();
55    return;
56  }
57
58  // Initialize components for the individual patterns parts to empty
59  // sub-strings.
60  Component scheme_component;
61  Component host_component;
62  Component port_component;
63  Component path_component;
64
65  size_t start = 0;
66  size_t current_pos = 0;
67
68  if (pattern_spec.empty())
69    return;
70
71  // Test if a scheme pattern is in the spec.
72  current_pos = pattern_spec.find(
73      std::string(content::kStandardSchemeSeparator), start);
74  if (current_pos != std::string::npos) {
75    scheme_component = Component(start, current_pos);
76    start = current_pos + strlen(content::kStandardSchemeSeparator);
77    current_pos = start;
78  } else {
79    current_pos = start;
80  }
81
82  if (start >= pattern_spec.size())
83    return;  // Bad pattern spec.
84
85  // Jump to the end of domain wildcards or an IPv6 addresses. IPv6 addresses
86  // contain ':'. So first move to the end of an IPv6 address befor searching
87  // for the ':' that separates the port form the host.
88  if (pattern_spec[current_pos] == '[')
89    current_pos = pattern_spec.find("]", start);
90
91  if (current_pos == std::string::npos)
92    return;  // Bad pattern spec.
93
94  current_pos = pattern_spec.find(std::string(kUrlPortSeparator), current_pos);
95  if (current_pos == std::string::npos) {
96    // No port spec found
97    current_pos = pattern_spec.find(std::string(kUrlPathSeparator), start);
98    if (current_pos == std::string::npos) {
99      current_pos = pattern_spec.size();
100      host_component = Component(start, current_pos - start);
101    } else {
102      // Pattern has a path spec.
103      host_component = Component(start, current_pos - start);
104    }
105    start = current_pos;
106  } else {
107    // Port spec found.
108    host_component = Component(start, current_pos - start);
109    start = current_pos + 1;
110    if (start < pattern_spec.size()) {
111      current_pos = pattern_spec.find(std::string(kUrlPathSeparator), start);
112      if (current_pos == std::string::npos) {
113        current_pos = pattern_spec.size();
114      }
115      port_component = Component(start, current_pos - start);
116      start = current_pos;
117    }
118  }
119
120  current_pos = pattern_spec.size();
121  if (start < current_pos) {
122    // Pattern has a path spec.
123    path_component = Component(start, current_pos - start);
124  }
125
126  // Set pattern parts.
127  std::string scheme;
128  if (scheme_component.IsNonEmpty()) {
129    scheme = pattern_spec.substr(scheme_component.start, scheme_component.len);
130    if (scheme == kSchemeWildcard) {
131      builder->WithSchemeWildcard();
132    } else {
133      builder->WithScheme(scheme);
134    }
135  } else {
136    builder->WithSchemeWildcard();
137  }
138
139  if (host_component.IsNonEmpty()) {
140    std::string host = pattern_spec.substr(host_component.start,
141                                           host_component.len);
142    if (host == kHostWildcard) {
143      builder->WithDomainWildcard();
144    } else if (StartsWithASCII(host, kDomainWildcard, true)) {
145      host = host.substr(kDomainWildcardLength);
146      builder->WithDomainWildcard();
147      builder->WithHost(host);
148    } else {
149      // If the host contains a wildcard symbol then it is invalid.
150      if (host.find(kHostWildcard) != std::string::npos) {
151        builder->Invalid();
152        return;
153      }
154      builder->WithHost(host);
155    }
156  }
157
158  if (port_component.IsNonEmpty()) {
159    const std::string port = pattern_spec.substr(port_component.start,
160                                                 port_component.len);
161    if (port == kPortWildcard) {
162      builder->WithPortWildcard();
163    } else {
164      // Check if the port string represents a valid port.
165      for (size_t i = 0; i < port.size(); ++i) {
166        if (!IsAsciiDigit(port[i])) {
167          builder->Invalid();
168          return;
169        }
170      }
171      // TODO(markusheintz): Check port range.
172      builder->WithPort(port);
173    }
174  } else {
175    if (scheme != std::string(extensions::kExtensionScheme) &&
176        scheme != std::string(chrome::kFileScheme))
177      builder->WithPortWildcard();
178  }
179
180  if (path_component.IsNonEmpty()) {
181    const std::string path = pattern_spec.substr(path_component.start,
182                                                 path_component.len);
183    if (path.substr(1) == kPathWildcard)
184      builder->WithPathWildcard();
185    else
186      builder->WithPath(path);
187  }
188}
189
190// static
191std::string PatternParser::ToString(
192    const ContentSettingsPattern::PatternParts& parts) {
193  // Return the most compact form to support legacy code and legacy pattern
194  // strings.
195  if (parts.is_scheme_wildcard &&
196      parts.has_domain_wildcard &&
197      parts.host.empty() &&
198      parts.is_port_wildcard)
199    return "*";
200
201  std::string str;
202  if (!parts.is_scheme_wildcard)
203    str += parts.scheme + content::kStandardSchemeSeparator;
204
205  if (parts.scheme == chrome::kFileScheme) {
206    if (parts.is_path_wildcard)
207      return str + kUrlPathSeparator + kPathWildcard;
208    else
209      return str + parts.path;
210  }
211
212  if (parts.has_domain_wildcard) {
213    if (parts.host.empty())
214      str += kHostWildcard;
215    else
216      str += kDomainWildcard;
217  }
218  str += parts.host;
219
220  if (parts.scheme == std::string(extensions::kExtensionScheme)) {
221    str += parts.path.empty() ? std::string(kUrlPathSeparator) : parts.path;
222    return str;
223  }
224
225  if (!parts.is_port_wildcard) {
226    str += std::string(kUrlPortSeparator) + parts.port;
227  }
228
229  return str;
230}
231
232}  // namespace content_settings
233