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 "components/url_matcher/url_matcher_factory.h"
6
7#include <algorithm>
8#include <cctype>
9
10#include "base/lazy_instance.h"
11#include "base/logging.h"
12#include "base/strings/stringprintf.h"
13#include "base/values.h"
14#include "components/url_matcher/url_matcher_constants.h"
15#include "components/url_matcher/url_matcher_helpers.h"
16#include "third_party/re2/re2/re2.h"
17
18namespace url_matcher {
19
20namespace helpers = url_matcher_helpers;
21namespace keys = url_matcher_constants;
22
23namespace {
24
25// Error messages:
26const char kInvalidPortRanges[] = "Invalid port ranges in UrlFilter.";
27const char kVectorOfStringsExpected[] =
28    "UrlFilter attribute '%s' expected a vector of strings as parameter.";
29const char kUnknownURLFilterAttribute[] =
30    "Unknown attribute '%s' in UrlFilter.";
31const char kAttributeExpectedString[] =
32    "UrlFilter attribute '%s' expected a string value.";
33const char kUnparseableRegexString[] =
34    "Could not parse regular expression '%s': %s";
35const char kLowerCaseExpected[] = "%s values need to be in lower case.";
36
37// Registry for all factory methods of URLMatcherConditionFactory
38// that allows translating string literals from the extension API into
39// the corresponding factory method to be called.
40class URLMatcherConditionFactoryMethods {
41 public:
42  URLMatcherConditionFactoryMethods() {
43    typedef URLMatcherConditionFactory F;
44    factory_methods_[keys::kHostContainsKey] = &F::CreateHostContainsCondition;
45    factory_methods_[keys::kHostEqualsKey] = &F::CreateHostEqualsCondition;
46    factory_methods_[keys::kHostPrefixKey] = &F::CreateHostPrefixCondition;
47    factory_methods_[keys::kHostSuffixKey] = &F::CreateHostSuffixCondition;
48    factory_methods_[keys::kOriginAndPathMatchesKey] =
49        &F::CreateOriginAndPathMatchesCondition;
50    factory_methods_[keys::kPathContainsKey] = &F::CreatePathContainsCondition;
51    factory_methods_[keys::kPathEqualsKey] = &F::CreatePathEqualsCondition;
52    factory_methods_[keys::kPathPrefixKey] = &F::CreatePathPrefixCondition;
53    factory_methods_[keys::kPathSuffixKey] = &F::CreatePathSuffixCondition;
54    factory_methods_[keys::kQueryContainsKey] =
55        &F::CreateQueryContainsCondition;
56    factory_methods_[keys::kQueryEqualsKey] = &F::CreateQueryEqualsCondition;
57    factory_methods_[keys::kQueryPrefixKey] = &F::CreateQueryPrefixCondition;
58    factory_methods_[keys::kQuerySuffixKey] = &F::CreateQuerySuffixCondition;
59    factory_methods_[keys::kURLContainsKey] = &F::CreateURLContainsCondition;
60    factory_methods_[keys::kURLEqualsKey] = &F::CreateURLEqualsCondition;
61    factory_methods_[keys::kURLPrefixKey] = &F::CreateURLPrefixCondition;
62    factory_methods_[keys::kURLSuffixKey] = &F::CreateURLSuffixCondition;
63    factory_methods_[keys::kURLMatchesKey] = &F::CreateURLMatchesCondition;
64  }
65
66  // Returns whether a factory method for the specified |pattern_type| (e.g.
67  // "host_suffix") is known.
68  bool Contains(const std::string& pattern_type) const {
69    return factory_methods_.find(pattern_type) != factory_methods_.end();
70  }
71
72  // Creates a URLMatcherCondition instance from |url_matcher_condition_factory|
73  // of the given |pattern_type| (e.g. "host_suffix") for the given
74  // |pattern_value| (e.g. "example.com").
75  // The |pattern_type| needs to be known to this class (see Contains()) or
76  // a CHECK is triggered.
77  URLMatcherCondition Call(
78      URLMatcherConditionFactory* url_matcher_condition_factory,
79      const std::string& pattern_type,
80      const std::string& pattern_value) const {
81    FactoryMethods::const_iterator i = factory_methods_.find(pattern_type);
82    CHECK(i != factory_methods_.end());
83    const FactoryMethod& method = i->second;
84    return (url_matcher_condition_factory->*method)(pattern_value);
85  }
86
87 private:
88  typedef URLMatcherCondition
89      (URLMatcherConditionFactory::* FactoryMethod)
90      (const std::string& prefix);
91  typedef std::map<std::string, FactoryMethod> FactoryMethods;
92
93  FactoryMethods factory_methods_;
94
95  DISALLOW_COPY_AND_ASSIGN(URLMatcherConditionFactoryMethods);
96};
97
98static base::LazyInstance<URLMatcherConditionFactoryMethods>
99    g_url_matcher_condition_factory_methods = LAZY_INSTANCE_INITIALIZER;
100
101}  // namespace
102
103// static
104scoped_refptr<URLMatcherConditionSet>
105URLMatcherFactory::CreateFromURLFilterDictionary(
106    URLMatcherConditionFactory* url_matcher_condition_factory,
107    const base::DictionaryValue* url_filter_dict,
108    URLMatcherConditionSet::ID id,
109    std::string* error) {
110  scoped_ptr<URLMatcherSchemeFilter> url_matcher_schema_filter;
111  scoped_ptr<URLMatcherPortFilter> url_matcher_port_filter;
112  URLMatcherConditionSet::Conditions url_matcher_conditions;
113
114  for (base::DictionaryValue::Iterator iter(*url_filter_dict);
115       !iter.IsAtEnd(); iter.Advance()) {
116    const std::string& condition_attribute_name = iter.key();
117    const Value& condition_attribute_value = iter.value();
118    if (IsURLMatcherConditionAttribute(condition_attribute_name)) {
119      // Handle {host, path, ...}{Prefix, Suffix, Contains, Equals}.
120      URLMatcherCondition url_matcher_condition =
121          CreateURLMatcherCondition(
122              url_matcher_condition_factory,
123              condition_attribute_name,
124              &condition_attribute_value,
125              error);
126      if (!error->empty())
127        return scoped_refptr<URLMatcherConditionSet>(NULL);
128      url_matcher_conditions.insert(url_matcher_condition);
129    } else if (condition_attribute_name == keys::kSchemesKey) {
130      // Handle scheme.
131      url_matcher_schema_filter = CreateURLMatcherScheme(
132          &condition_attribute_value, error);
133      if (!error->empty())
134        return scoped_refptr<URLMatcherConditionSet>(NULL);
135    } else if (condition_attribute_name == keys::kPortsKey) {
136      // Handle ports.
137      url_matcher_port_filter = CreateURLMatcherPorts(
138          &condition_attribute_value, error);
139      if (!error->empty())
140        return scoped_refptr<URLMatcherConditionSet>(NULL);
141    } else {
142      // Handle unknown attributes.
143      *error = base::StringPrintf(kUnknownURLFilterAttribute,
144                                  condition_attribute_name.c_str());
145      return scoped_refptr<URLMatcherConditionSet>(NULL);
146    }
147  }
148
149  // As the URL is the preliminary matching criterion that triggers the tests
150  // for the remaining condition attributes, we insert an empty URL match if
151  // no other url match conditions were specified. Such an empty URL is always
152  // matched.
153  if (url_matcher_conditions.empty()) {
154    url_matcher_conditions.insert(
155        url_matcher_condition_factory->CreateHostPrefixCondition(
156            std::string()));
157  }
158
159  scoped_refptr<URLMatcherConditionSet> url_matcher_condition_set(
160      new URLMatcherConditionSet(id, url_matcher_conditions,
161          url_matcher_schema_filter.Pass(), url_matcher_port_filter.Pass()));
162  return url_matcher_condition_set;
163}
164
165// static
166bool URLMatcherFactory::IsURLMatcherConditionAttribute(
167    const std::string& condition_attribute_name) {
168  return g_url_matcher_condition_factory_methods.Get().Contains(
169      condition_attribute_name);
170}
171
172namespace {
173
174// Returns true if some alphabetic characters in this string are upper case.
175bool ContainsUpperCase(const std::string& str) {
176  return std::find_if(str.begin(), str.end(), ::isupper) != str.end();
177}
178
179}  // namespace
180
181// static
182URLMatcherCondition URLMatcherFactory::CreateURLMatcherCondition(
183    URLMatcherConditionFactory* url_matcher_condition_factory,
184    const std::string& condition_attribute_name,
185    const base::Value* value,
186    std::string* error) {
187  std::string str_value;
188  if (!value->GetAsString(&str_value)) {
189    *error = base::StringPrintf(kAttributeExpectedString,
190                                condition_attribute_name.c_str());
191    return URLMatcherCondition();
192  }
193  if (condition_attribute_name == keys::kHostContainsKey ||
194      condition_attribute_name == keys::kHostPrefixKey ||
195      condition_attribute_name == keys::kHostSuffixKey ||
196      condition_attribute_name == keys::kHostEqualsKey) {
197    if (ContainsUpperCase(str_value)) {
198      *error = base::StringPrintf(kLowerCaseExpected, "Host");
199      return URLMatcherCondition();
200    }
201  }
202
203  // Test regular expressions for validity.
204  if (condition_attribute_name == keys::kURLMatchesKey ||
205      condition_attribute_name == keys::kOriginAndPathMatchesKey) {
206    re2::RE2 regex(str_value);
207    if (!regex.ok()) {
208      *error = base::StringPrintf(
209          kUnparseableRegexString, str_value.c_str(), regex.error().c_str());
210      return URLMatcherCondition();
211    }
212  }
213  return g_url_matcher_condition_factory_methods.Get().Call(
214      url_matcher_condition_factory, condition_attribute_name, str_value);
215}
216
217// static
218scoped_ptr<URLMatcherSchemeFilter> URLMatcherFactory::CreateURLMatcherScheme(
219    const base::Value* value,
220    std::string* error) {
221  std::vector<std::string> schemas;
222  if (!helpers::GetAsStringVector(value, &schemas)) {
223    *error = base::StringPrintf(kVectorOfStringsExpected, keys::kSchemesKey);
224    return scoped_ptr<URLMatcherSchemeFilter>();
225  }
226  for (std::vector<std::string>::const_iterator it = schemas.begin();
227       it != schemas.end(); ++it) {
228    if (ContainsUpperCase(*it)) {
229      *error = base::StringPrintf(kLowerCaseExpected, "Scheme");
230      return scoped_ptr<URLMatcherSchemeFilter>();
231    }
232  }
233  return scoped_ptr<URLMatcherSchemeFilter>(
234      new URLMatcherSchemeFilter(schemas));
235}
236
237// static
238scoped_ptr<URLMatcherPortFilter> URLMatcherFactory::CreateURLMatcherPorts(
239    const base::Value* value,
240    std::string* error) {
241  std::vector<URLMatcherPortFilter::Range> ranges;
242  const base::ListValue* value_list = NULL;
243  if (!value->GetAsList(&value_list)) {
244    *error = kInvalidPortRanges;
245    return scoped_ptr<URLMatcherPortFilter>();
246  }
247
248  for (ListValue::const_iterator i = value_list->begin();
249       i != value_list->end(); ++i) {
250    Value* entry = *i;
251    int port = 0;
252    base::ListValue* range = NULL;
253    if (entry->GetAsInteger(&port)) {
254      ranges.push_back(URLMatcherPortFilter::CreateRange(port));
255    } else if (entry->GetAsList(&range)) {
256      int from = 0, to = 0;
257      if (range->GetSize() != 2u ||
258          !range->GetInteger(0, &from) ||
259          !range->GetInteger(1, &to)) {
260        *error = kInvalidPortRanges;
261        return scoped_ptr<URLMatcherPortFilter>();
262      }
263      ranges.push_back(URLMatcherPortFilter::CreateRange(from, to));
264    } else {
265      *error = kInvalidPortRanges;
266      return scoped_ptr<URLMatcherPortFilter>();
267    }
268  }
269
270  return scoped_ptr<URLMatcherPortFilter>(new URLMatcherPortFilter(ranges));
271}
272
273}  // namespace url_matcher
274