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