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 "extensions/browser/api/declarative_webrequest/webrequest_rules_registry.h"
6
7#include <string>
8#include <vector>
9
10#include "base/basictypes.h"
11#include "base/json/json_reader.h"
12#include "base/memory/linked_ptr.h"
13#include "base/memory/scoped_ptr.h"
14#include "base/message_loop/message_loop.h"
15#include "base/stl_util.h"
16#include "base/test/values_test_util.h"
17#include "base/values.h"
18#include "chrome/common/extensions/extension_test_util.h"
19#include "components/url_matcher/url_matcher_constants.h"
20#include "content/public/test/test_browser_thread.h"
21#include "extensions/browser/api/declarative_webrequest/webrequest_constants.h"
22#include "extensions/browser/api/web_request/web_request_api_helpers.h"
23#include "net/base/request_priority.h"
24#include "net/url_request/url_request.h"
25#include "net/url_request/url_request_test_util.h"
26#include "testing/gmock/include/gmock/gmock.h"
27#include "testing/gtest/include/gtest/gtest-message.h"
28#include "testing/gtest/include/gtest/gtest.h"
29
30using base::Value;
31using extension_test_util::LoadManifest;
32using extension_test_util::LoadManifestUnchecked;
33using testing::HasSubstr;
34using url_matcher::URLMatcher;
35
36namespace {
37const char kExtensionId[] = "ext1";
38const char kExtensionId2[] = "ext2";
39const char kRuleId1[] = "rule1";
40const char kRuleId2[] = "rule2";
41const char kRuleId3[] = "rule3";
42const char kRuleId4[] = "rule4";
43}  // namespace
44
45namespace extensions {
46
47namespace helpers = extension_web_request_api_helpers;
48namespace keys = declarative_webrequest_constants;
49namespace keys2 = url_matcher::url_matcher_constants;
50
51class TestWebRequestRulesRegistry : public WebRequestRulesRegistry {
52 public:
53  TestWebRequestRulesRegistry(
54      scoped_refptr<InfoMap> extension_info_map)
55      : WebRequestRulesRegistry(NULL /*profile*/,
56                                NULL /* cache_delegate */,
57                                WebViewKey(0, 0)),
58        num_clear_cache_calls_(0) {
59    SetExtensionInfoMapForTesting(extension_info_map);
60  }
61
62  // Returns how often the in-memory caches of the renderers were instructed
63  // to be cleared.
64  int num_clear_cache_calls() const { return num_clear_cache_calls_; }
65
66  // How many rules are there which have some conditions not triggered by URL
67  // matches.
68  size_t RulesWithoutTriggers() const {
69    return rules_with_untriggered_conditions_for_test().size();
70  }
71
72 protected:
73  virtual ~TestWebRequestRulesRegistry() {}
74
75  virtual void ClearCacheOnNavigation() OVERRIDE {
76    ++num_clear_cache_calls_;
77  }
78
79 private:
80  int num_clear_cache_calls_;
81};
82
83class WebRequestRulesRegistryTest : public testing::Test {
84 public:
85  WebRequestRulesRegistryTest()
86      : ui_(content::BrowserThread::UI, &message_loop_),
87        io_(content::BrowserThread::IO, &message_loop_) {}
88
89  virtual ~WebRequestRulesRegistryTest() {}
90
91  virtual void SetUp() OVERRIDE;
92
93  virtual void TearDown() OVERRIDE {
94    // Make sure that deletion traits of all registries are executed.
95    message_loop_.RunUntilIdle();
96  }
97
98  // Returns a rule that roughly matches http://*.example.com and
99  // https://www.example.com and cancels it
100  linked_ptr<RulesRegistry::Rule> CreateRule1() {
101    base::ListValue* scheme_http = new base::ListValue();
102    scheme_http->Append(new base::StringValue("http"));
103    base::DictionaryValue* http_condition_dict = new base::DictionaryValue();
104    http_condition_dict->Set(keys2::kSchemesKey, scheme_http);
105    http_condition_dict->SetString(keys2::kHostSuffixKey, "example.com");
106    base::DictionaryValue http_condition_url_filter;
107    http_condition_url_filter.Set(keys::kUrlKey, http_condition_dict);
108    http_condition_url_filter.SetString(keys::kInstanceTypeKey,
109                                        keys::kRequestMatcherType);
110
111    base::ListValue* scheme_https = new base::ListValue();
112    scheme_http->Append(new base::StringValue("https"));
113    base::DictionaryValue* https_condition_dict = new base::DictionaryValue();
114    https_condition_dict->Set(keys2::kSchemesKey, scheme_https);
115    https_condition_dict->SetString(keys2::kHostSuffixKey, "example.com");
116    https_condition_dict->SetString(keys2::kHostPrefixKey, "www");
117    base::DictionaryValue https_condition_url_filter;
118    https_condition_url_filter.Set(keys::kUrlKey, https_condition_dict);
119    https_condition_url_filter.SetString(keys::kInstanceTypeKey,
120                                         keys::kRequestMatcherType);
121
122    base::DictionaryValue action_dict;
123    action_dict.SetString(keys::kInstanceTypeKey, keys::kCancelRequestType);
124
125    linked_ptr<RulesRegistry::Rule> rule(new RulesRegistry::Rule);
126    rule->id.reset(new std::string(kRuleId1));
127    rule->priority.reset(new int(100));
128    rule->actions.push_back(linked_ptr<base::Value>(action_dict.DeepCopy()));
129    rule->conditions.push_back(
130        linked_ptr<base::Value>(http_condition_url_filter.DeepCopy()));
131    rule->conditions.push_back(
132        linked_ptr<base::Value>(https_condition_url_filter.DeepCopy()));
133    return rule;
134  }
135
136  // Returns a rule that matches anything and cancels it.
137  linked_ptr<RulesRegistry::Rule> CreateRule2() {
138    base::DictionaryValue condition_dict;
139    condition_dict.SetString(keys::kInstanceTypeKey, keys::kRequestMatcherType);
140
141    base::DictionaryValue action_dict;
142    action_dict.SetString(keys::kInstanceTypeKey, keys::kCancelRequestType);
143
144    linked_ptr<RulesRegistry::Rule> rule(new RulesRegistry::Rule);
145    rule->id.reset(new std::string(kRuleId2));
146    rule->priority.reset(new int(100));
147    rule->actions.push_back(linked_ptr<base::Value>(action_dict.DeepCopy()));
148    rule->conditions.push_back(
149        linked_ptr<base::Value>(condition_dict.DeepCopy()));
150    return rule;
151  }
152
153  linked_ptr<RulesRegistry::Rule> CreateRedirectRule(
154      const std::string& destination) {
155    base::DictionaryValue condition_dict;
156    condition_dict.SetString(keys::kInstanceTypeKey, keys::kRequestMatcherType);
157
158    base::DictionaryValue action_dict;
159    action_dict.SetString(keys::kInstanceTypeKey, keys::kRedirectRequestType);
160    action_dict.SetString(keys::kRedirectUrlKey, destination);
161
162    linked_ptr<RulesRegistry::Rule> rule(new RulesRegistry::Rule);
163    rule->id.reset(new std::string(kRuleId3));
164    rule->priority.reset(new int(100));
165    rule->actions.push_back(linked_ptr<base::Value>(action_dict.DeepCopy()));
166    rule->conditions.push_back(
167        linked_ptr<base::Value>(condition_dict.DeepCopy()));
168    return rule;
169  }
170
171  // Create a rule to ignore all other rules for a destination that
172  // contains index.html.
173  linked_ptr<RulesRegistry::Rule> CreateIgnoreRule() {
174    base::DictionaryValue condition_dict;
175    base::DictionaryValue* http_condition_dict = new base::DictionaryValue();
176    http_condition_dict->SetString(keys2::kPathContainsKey, "index.html");
177    condition_dict.SetString(keys::kInstanceTypeKey, keys::kRequestMatcherType);
178    condition_dict.Set(keys::kUrlKey, http_condition_dict);
179
180    base::DictionaryValue action_dict;
181    action_dict.SetString(keys::kInstanceTypeKey, keys::kIgnoreRulesType);
182    action_dict.SetInteger(keys::kLowerPriorityThanKey, 150);
183
184    linked_ptr<RulesRegistry::Rule> rule(new RulesRegistry::Rule);
185    rule->id.reset(new std::string(kRuleId4));
186    rule->priority.reset(new int(200));
187    rule->actions.push_back(linked_ptr<base::Value>(action_dict.DeepCopy()));
188    rule->conditions.push_back(
189        linked_ptr<base::Value>(condition_dict.DeepCopy()));
190    return rule;
191  }
192
193  // Create a condition with the attributes specified. An example value of
194  // |attributes| is: "\"resourceType\": [\"stylesheet\"], \n".
195  linked_ptr<base::Value> CreateCondition(const std::string& attributes) {
196    std::string json_description =
197        "{ \n"
198        "  \"instanceType\": \"declarativeWebRequest.RequestMatcher\", \n";
199    json_description += attributes;
200    json_description += "}";
201
202    return linked_ptr<base::Value>(
203        base::test::ParseJson(json_description).release());
204  }
205
206  // Create a rule with the ID |rule_id| and with conditions created from the
207  // |attributes| specified (one entry one condition). An example value of a
208  // string from |attributes| is: "\"resourceType\": [\"stylesheet\"], \n".
209  linked_ptr<RulesRegistry::Rule> CreateCancellingRule(
210      const char* rule_id,
211      const std::vector<const std::string*>& attributes) {
212    base::DictionaryValue action_dict;
213    action_dict.SetString(keys::kInstanceTypeKey, keys::kCancelRequestType);
214
215    linked_ptr<RulesRegistry::Rule> rule(new RulesRegistry::Rule);
216    rule->id.reset(new std::string(rule_id));
217    rule->priority.reset(new int(1));
218    rule->actions.push_back(linked_ptr<base::Value>(action_dict.DeepCopy()));
219    for (std::vector<const std::string*>::const_iterator it =
220             attributes.begin();
221         it != attributes.end(); ++it)
222      rule->conditions.push_back(CreateCondition(**it));
223    return rule;
224  }
225
226 protected:
227  base::MessageLoopForIO message_loop_;
228  content::TestBrowserThread ui_;
229  content::TestBrowserThread io_;
230  // Two extensions with host permissions for all URLs and the DWR permission.
231  // Installation times will be so that |extension_| is older than
232  // |extension2_|.
233  scoped_refptr<Extension> extension_;
234  scoped_refptr<Extension> extension2_;
235  scoped_refptr<InfoMap> extension_info_map_;
236};
237
238void WebRequestRulesRegistryTest::SetUp() {
239  testing::Test::SetUp();
240
241  std::string error;
242  extension_ = LoadManifestUnchecked("permissions",
243                                     "web_request_all_host_permissions.json",
244                                     Manifest::INVALID_LOCATION,
245                                     Extension::NO_FLAGS,
246                                     kExtensionId,
247                                     &error);
248  ASSERT_TRUE(extension_.get()) << error;
249  extension2_ = LoadManifestUnchecked("permissions",
250                                      "web_request_all_host_permissions.json",
251                                      Manifest::INVALID_LOCATION,
252                                      Extension::NO_FLAGS,
253                                      kExtensionId2,
254                                      &error);
255  ASSERT_TRUE(extension2_.get()) << error;
256  extension_info_map_ = new InfoMap;
257  ASSERT_TRUE(extension_info_map_.get());
258  extension_info_map_->AddExtension(extension_.get(),
259                                    base::Time() + base::TimeDelta::FromDays(1),
260                                    false /*incognito_enabled*/,
261                                    false /*notifications_disabled*/);
262  extension_info_map_->AddExtension(extension2_.get(),
263                                    base::Time() + base::TimeDelta::FromDays(2),
264                                    false /*incognito_enabled*/,
265                                    false /*notifications_disabled*/);
266}
267
268
269TEST_F(WebRequestRulesRegistryTest, AddRulesImpl) {
270  scoped_refptr<TestWebRequestRulesRegistry> registry(
271      new TestWebRequestRulesRegistry(extension_info_map_));
272  std::string error;
273
274  std::vector<linked_ptr<RulesRegistry::Rule> > rules;
275  rules.push_back(CreateRule1());
276  rules.push_back(CreateRule2());
277
278  error = registry->AddRules(kExtensionId, rules);
279  EXPECT_EQ("", error);
280  EXPECT_EQ(1, registry->num_clear_cache_calls());
281
282  std::set<const WebRequestRule*> matches;
283
284  GURL http_url("http://www.example.com");
285  net::TestURLRequestContext context;
286  scoped_ptr<net::URLRequest> http_request(context.CreateRequest(
287      http_url, net::DEFAULT_PRIORITY, NULL, NULL));
288  WebRequestData request_data(http_request.get(), ON_BEFORE_REQUEST);
289  matches = registry->GetMatches(request_data);
290  EXPECT_EQ(2u, matches.size());
291
292  std::set<WebRequestRule::GlobalRuleId> matches_ids;
293  for (std::set<const WebRequestRule*>::const_iterator it = matches.begin();
294       it != matches.end(); ++it)
295    matches_ids.insert((*it)->id());
296  EXPECT_TRUE(ContainsKey(matches_ids, std::make_pair(kExtensionId, kRuleId1)));
297  EXPECT_TRUE(ContainsKey(matches_ids, std::make_pair(kExtensionId, kRuleId2)));
298
299  GURL foobar_url("http://www.foobar.com");
300  scoped_ptr<net::URLRequest> foobar_request(context.CreateRequest(
301      foobar_url, net::DEFAULT_PRIORITY, NULL, NULL));
302  request_data.request = foobar_request.get();
303  matches = registry->GetMatches(request_data);
304  EXPECT_EQ(1u, matches.size());
305  WebRequestRule::GlobalRuleId expected_pair =
306      std::make_pair(kExtensionId, kRuleId2);
307  EXPECT_EQ(expected_pair, (*matches.begin())->id());
308}
309
310TEST_F(WebRequestRulesRegistryTest, RemoveRulesImpl) {
311  scoped_refptr<TestWebRequestRulesRegistry> registry(
312      new TestWebRequestRulesRegistry(extension_info_map_));
313  std::string error;
314
315  // Setup RulesRegistry to contain two rules.
316  std::vector<linked_ptr<RulesRegistry::Rule> > rules_to_add;
317  rules_to_add.push_back(CreateRule1());
318  rules_to_add.push_back(CreateRule2());
319  error = registry->AddRules(kExtensionId, rules_to_add);
320  EXPECT_EQ("", error);
321  EXPECT_EQ(1, registry->num_clear_cache_calls());
322
323  // Verify initial state.
324  std::vector<linked_ptr<RulesRegistry::Rule> > registered_rules;
325  registry->GetAllRules(kExtensionId, &registered_rules);
326  EXPECT_EQ(2u, registered_rules.size());
327  EXPECT_EQ(1u, registry->RulesWithoutTriggers());
328
329  // Remove first rule.
330  std::vector<std::string> rules_to_remove;
331  rules_to_remove.push_back(kRuleId1);
332  error = registry->RemoveRules(kExtensionId, rules_to_remove);
333  EXPECT_EQ("", error);
334  EXPECT_EQ(2, registry->num_clear_cache_calls());
335
336  // Verify that only one rule is left.
337  registered_rules.clear();
338  registry->GetAllRules(kExtensionId, &registered_rules);
339  EXPECT_EQ(1u, registered_rules.size());
340  EXPECT_EQ(1u, registry->RulesWithoutTriggers());
341
342  // Now rules_to_remove contains both rules, i.e. one that does not exist in
343  // the rules registry anymore. Effectively we only remove the second rule.
344  rules_to_remove.push_back(kRuleId2);
345  error = registry->RemoveRules(kExtensionId, rules_to_remove);
346  EXPECT_EQ("", error);
347  EXPECT_EQ(3, registry->num_clear_cache_calls());
348
349  // Verify that everything is gone.
350  registered_rules.clear();
351  registry->GetAllRules(kExtensionId, &registered_rules);
352  EXPECT_EQ(0u, registered_rules.size());
353  EXPECT_EQ(0u, registry->RulesWithoutTriggers());
354
355  EXPECT_TRUE(registry->IsEmpty());
356}
357
358TEST_F(WebRequestRulesRegistryTest, RemoveAllRulesImpl) {
359  scoped_refptr<TestWebRequestRulesRegistry> registry(
360      new TestWebRequestRulesRegistry(extension_info_map_));
361  std::string error;
362
363  // Setup RulesRegistry to contain two rules, one for each extension.
364  std::vector<linked_ptr<RulesRegistry::Rule> > rules_to_add(1);
365  rules_to_add[0] = CreateRule1();
366  error = registry->AddRules(kExtensionId, rules_to_add);
367  EXPECT_EQ("", error);
368  EXPECT_EQ(1, registry->num_clear_cache_calls());
369
370  rules_to_add[0] = CreateRule2();
371  error = registry->AddRules(kExtensionId2, rules_to_add);
372  EXPECT_EQ("", error);
373  EXPECT_EQ(2, registry->num_clear_cache_calls());
374
375  // Verify initial state.
376  std::vector<linked_ptr<RulesRegistry::Rule> > registered_rules;
377  registry->GetAllRules(kExtensionId, &registered_rules);
378  EXPECT_EQ(1u, registered_rules.size());
379  registered_rules.clear();
380  registry->GetAllRules(kExtensionId2, &registered_rules);
381  EXPECT_EQ(1u, registered_rules.size());
382
383  // Remove rule of first extension.
384  error = registry->RemoveAllRules(kExtensionId);
385  EXPECT_EQ("", error);
386  EXPECT_EQ(3, registry->num_clear_cache_calls());
387
388  // Verify that only the first rule is deleted.
389  registered_rules.clear();
390  registry->GetAllRules(kExtensionId, &registered_rules);
391  EXPECT_EQ(0u, registered_rules.size());
392  registered_rules.clear();
393  registry->GetAllRules(kExtensionId2, &registered_rules);
394  EXPECT_EQ(1u, registered_rules.size());
395
396  // Test removing rules if none exist.
397  error = registry->RemoveAllRules(kExtensionId);
398  EXPECT_EQ("", error);
399  EXPECT_EQ(4, registry->num_clear_cache_calls());
400
401  // Remove rule from second extension.
402  error = registry->RemoveAllRules(kExtensionId2);
403  EXPECT_EQ("", error);
404  EXPECT_EQ(5, registry->num_clear_cache_calls());
405
406  EXPECT_TRUE(registry->IsEmpty());
407}
408
409// Test precedences between extensions.
410TEST_F(WebRequestRulesRegistryTest, Precedences) {
411  scoped_refptr<WebRequestRulesRegistry> registry(
412      new TestWebRequestRulesRegistry(extension_info_map_));
413  std::string error;
414
415  std::vector<linked_ptr<RulesRegistry::Rule> > rules_to_add_1(1);
416  rules_to_add_1[0] = CreateRedirectRule("http://www.foo.com");
417  error = registry->AddRules(kExtensionId, rules_to_add_1);
418  EXPECT_EQ("", error);
419
420  std::vector<linked_ptr<RulesRegistry::Rule> > rules_to_add_2(1);
421  rules_to_add_2[0] = CreateRedirectRule("http://www.bar.com");
422  error = registry->AddRules(kExtensionId2, rules_to_add_2);
423  EXPECT_EQ("", error);
424
425  GURL url("http://www.google.com");
426  net::TestURLRequestContext context;
427  scoped_ptr<net::URLRequest> request(context.CreateRequest(
428      url, net::DEFAULT_PRIORITY, NULL, NULL));
429  WebRequestData request_data(request.get(), ON_BEFORE_REQUEST);
430  std::list<LinkedPtrEventResponseDelta> deltas =
431      registry->CreateDeltas(NULL, request_data, false);
432
433  // The second extension is installed later and will win for this reason
434  // in conflict resolution.
435  ASSERT_EQ(2u, deltas.size());
436  deltas.sort(&helpers::InDecreasingExtensionInstallationTimeOrder);
437
438  std::list<LinkedPtrEventResponseDelta>::iterator i = deltas.begin();
439  LinkedPtrEventResponseDelta winner = *i++;
440  LinkedPtrEventResponseDelta loser = *i;
441
442  EXPECT_EQ(kExtensionId2, winner->extension_id);
443  EXPECT_EQ(base::Time() + base::TimeDelta::FromDays(2),
444            winner->extension_install_time);
445  EXPECT_EQ(GURL("http://www.bar.com"), winner->new_url);
446
447  EXPECT_EQ(kExtensionId, loser->extension_id);
448  EXPECT_EQ(base::Time() + base::TimeDelta::FromDays(1),
449            loser->extension_install_time);
450  EXPECT_EQ(GURL("http://www.foo.com"), loser->new_url);
451}
452
453// Test priorities of rules within one extension.
454TEST_F(WebRequestRulesRegistryTest, Priorities) {
455  scoped_refptr<WebRequestRulesRegistry> registry(
456      new TestWebRequestRulesRegistry(extension_info_map_));
457  std::string error;
458
459  std::vector<linked_ptr<RulesRegistry::Rule> > rules_to_add_1(1);
460  rules_to_add_1[0] = CreateRedirectRule("http://www.foo.com");
461  error = registry->AddRules(kExtensionId, rules_to_add_1);
462  EXPECT_EQ("", error);
463
464  std::vector<linked_ptr<RulesRegistry::Rule> > rules_to_add_2(1);
465  rules_to_add_2[0] = CreateRedirectRule("http://www.bar.com");
466  error = registry->AddRules(kExtensionId2, rules_to_add_2);
467  EXPECT_EQ("", error);
468
469  std::vector<linked_ptr<RulesRegistry::Rule> > rules_to_add_3(1);
470  rules_to_add_3[0] = CreateIgnoreRule();
471  error = registry->AddRules(kExtensionId, rules_to_add_3);
472  EXPECT_EQ("", error);
473
474  GURL url("http://www.google.com/index.html");
475  net::TestURLRequestContext context;
476  scoped_ptr<net::URLRequest> request(context.CreateRequest(
477      url, net::DEFAULT_PRIORITY, NULL, NULL));
478  WebRequestData request_data(request.get(), ON_BEFORE_REQUEST);
479  std::list<LinkedPtrEventResponseDelta> deltas =
480      registry->CreateDeltas(NULL, request_data, false);
481
482  // The redirect by the first extension is ignored due to the ignore rule.
483  ASSERT_EQ(1u, deltas.size());
484  LinkedPtrEventResponseDelta effective_rule = *(deltas.begin());
485
486  EXPECT_EQ(kExtensionId2, effective_rule->extension_id);
487  EXPECT_EQ(base::Time() + base::TimeDelta::FromDays(2),
488            effective_rule->extension_install_time);
489  EXPECT_EQ(GURL("http://www.bar.com"), effective_rule->new_url);
490}
491
492// Test ignoring of rules by tag.
493TEST_F(WebRequestRulesRegistryTest, IgnoreRulesByTag) {
494  const char kRule1[] =
495      "{                                                                 \n"
496      "  \"id\": \"rule1\",                                              \n"
497      "  \"tags\": [\"non_matching_tag\", \"ignore_tag\"],               \n"
498      "  \"conditions\": [                                               \n"
499      "    {                                                             \n"
500      "      \"instanceType\": \"declarativeWebRequest.RequestMatcher\", \n"
501      "      \"url\": {\"hostSuffix\": \"foo.com\"}                      \n"
502      "    }                                                             \n"
503      "  ],                                                              \n"
504      "  \"actions\": [                                                  \n"
505      "    {                                                             \n"
506      "      \"instanceType\": \"declarativeWebRequest.RedirectRequest\",\n"
507      "      \"redirectUrl\": \"http://bar.com\"                         \n"
508      "    }                                                             \n"
509      "  ],                                                              \n"
510      "  \"priority\": 200                                               \n"
511      "}                                                                 ";
512
513  const char kRule2[] =
514      "{                                                                 \n"
515      "  \"id\": \"rule2\",                                              \n"
516      "  \"conditions\": [                                               \n"
517      "    {                                                             \n"
518      "      \"instanceType\": \"declarativeWebRequest.RequestMatcher\", \n"
519      "      \"url\": {\"pathPrefix\": \"/test\"}                        \n"
520      "    }                                                             \n"
521      "  ],                                                              \n"
522      "  \"actions\": [                                                  \n"
523      "    {                                                             \n"
524      "      \"instanceType\": \"declarativeWebRequest.IgnoreRules\",    \n"
525      "      \"hasTag\": \"ignore_tag\"                                  \n"
526      "    }                                                             \n"
527      "  ],                                                              \n"
528      "  \"priority\": 300                                               \n"
529      "}                                                                 ";
530
531  scoped_ptr<base::Value> value1(base::JSONReader::Read(kRule1));
532  ASSERT_TRUE(value1.get());
533  scoped_ptr<base::Value> value2(base::JSONReader::Read(kRule2));
534  ASSERT_TRUE(value2.get());
535
536  std::vector<linked_ptr<RulesRegistry::Rule> > rules;
537  rules.push_back(make_linked_ptr(new RulesRegistry::Rule));
538  rules.push_back(make_linked_ptr(new RulesRegistry::Rule));
539  ASSERT_TRUE(RulesRegistry::Rule::Populate(*value1, rules[0].get()));
540  ASSERT_TRUE(RulesRegistry::Rule::Populate(*value2, rules[1].get()));
541
542  scoped_refptr<WebRequestRulesRegistry> registry(
543      new TestWebRequestRulesRegistry(extension_info_map_));
544  std::string error = registry->AddRulesImpl(kExtensionId, rules);
545  EXPECT_EQ("", error);
546  EXPECT_FALSE(registry->IsEmpty());
547
548  GURL url("http://www.foo.com/test");
549  net::TestURLRequestContext context;
550  scoped_ptr<net::URLRequest> request(context.CreateRequest(
551      url, net::DEFAULT_PRIORITY, NULL, NULL));
552  WebRequestData request_data(request.get(), ON_BEFORE_REQUEST);
553  std::list<LinkedPtrEventResponseDelta> deltas =
554      registry->CreateDeltas(NULL, request_data, false);
555
556  // The redirect by the redirect rule is ignored due to the ignore rule.
557  std::set<const WebRequestRule*> matches = registry->GetMatches(request_data);
558  EXPECT_EQ(2u, matches.size());
559  ASSERT_EQ(0u, deltas.size());
560}
561
562// Test that rules failing IsFulfilled on their conditions are never returned by
563// GetMatches.
564TEST_F(WebRequestRulesRegistryTest, GetMatchesCheckFulfilled) {
565  scoped_refptr<TestWebRequestRulesRegistry> registry(
566      new TestWebRequestRulesRegistry(extension_info_map_));
567  const std::string kMatchingUrlAttribute(
568      "\"url\": { \"pathContains\": \"\" }, \n");
569  const std::string kNonMatchingNonUrlAttribute(
570      "\"resourceType\": [\"stylesheet\"], \n");
571  const std::string kBothAttributes(kMatchingUrlAttribute +
572                                    kNonMatchingNonUrlAttribute);
573  std::string error;
574  std::vector<const std::string*> attributes;
575  std::vector<linked_ptr<RulesRegistry::Rule> > rules;
576
577  // Rules 1 and 2 have one condition, neither of them should fire.
578  attributes.push_back(&kNonMatchingNonUrlAttribute);
579  rules.push_back(CreateCancellingRule(kRuleId1, attributes));
580
581  attributes.clear();
582  attributes.push_back(&kBothAttributes);
583  rules.push_back(CreateCancellingRule(kRuleId2, attributes));
584
585  // Rule 3 has two conditions, one with a matching URL attribute, and one
586  // with a non-matching non-URL attribute.
587  attributes.clear();
588  attributes.push_back(&kMatchingUrlAttribute);
589  attributes.push_back(&kNonMatchingNonUrlAttribute);
590  rules.push_back(CreateCancellingRule(kRuleId3, attributes));
591
592  error = registry->AddRules(kExtensionId, rules);
593  EXPECT_EQ("", error);
594  EXPECT_EQ(1, registry->num_clear_cache_calls());
595
596  std::set<const WebRequestRule*> matches;
597
598  GURL http_url("http://www.example.com");
599  net::TestURLRequestContext context;
600  scoped_ptr<net::URLRequest> http_request(context.CreateRequest(
601      http_url, net::DEFAULT_PRIORITY, NULL, NULL));
602  WebRequestData request_data(http_request.get(), ON_BEFORE_REQUEST);
603  matches = registry->GetMatches(request_data);
604  EXPECT_EQ(1u, matches.size());
605  WebRequestRule::GlobalRuleId expected_pair = std::make_pair(kExtensionId,
606                                                              kRuleId3);
607  EXPECT_EQ(expected_pair, (*matches.begin())->id());
608}
609
610// Test that the url and firstPartyForCookiesUrl attributes are evaluated
611// against corresponding URLs. Tested on requests where these URLs actually
612// differ.
613TEST_F(WebRequestRulesRegistryTest, GetMatchesDifferentUrls) {
614  scoped_refptr<TestWebRequestRulesRegistry> registry(
615      new TestWebRequestRulesRegistry(extension_info_map_));
616  const std::string kUrlAttribute(
617      "\"url\": { \"hostContains\": \"url\" }, \n");
618  const std::string kFirstPartyUrlAttribute(
619      "\"firstPartyForCookiesUrl\": { \"hostContains\": \"fpfc\" }, \n");
620  std::string error;
621  std::vector<const std::string*> attributes;
622  std::vector<linked_ptr<RulesRegistry::Rule> > rules;
623
624  // Rule 1 has one condition, with a url attribute
625  attributes.push_back(&kUrlAttribute);
626  rules.push_back(CreateCancellingRule(kRuleId1, attributes));
627
628  // Rule 2 has one condition, with a firstPartyForCookiesUrl attribute
629  attributes.clear();
630  attributes.push_back(&kFirstPartyUrlAttribute);
631  rules.push_back(CreateCancellingRule(kRuleId2, attributes));
632
633  error = registry->AddRules(kExtensionId, rules);
634  EXPECT_EQ("", error);
635  EXPECT_EQ(1, registry->num_clear_cache_calls());
636
637  std::set<const WebRequestRule*> matches;
638
639  const GURL urls[] = {
640    GURL("http://url.example.com"),  // matching
641    GURL("http://www.example.com")   // non-matching
642  };
643  const GURL firstPartyUrls[] = {
644    GURL("http://www.example.com"),  // non-matching
645    GURL("http://fpfc.example.com")  // matching
646  };
647  // Which rules should match in subsequent test iterations.
648  const char* matchingRuleIds[] = { kRuleId1, kRuleId2 };
649  COMPILE_ASSERT(arraysize(urls) == arraysize(firstPartyUrls),
650                 urls_and_firstPartyUrls_need_to_have_the_same_size);
651  COMPILE_ASSERT(arraysize(urls) == arraysize(matchingRuleIds),
652                 urls_and_matchingRuleIds_need_to_have_the_same_size);
653  net::TestURLRequestContext context;
654
655  for (size_t i = 0; i < arraysize(matchingRuleIds); ++i) {
656    // Construct the inputs.
657    scoped_ptr<net::URLRequest> http_request(context.CreateRequest(
658        urls[i], net::DEFAULT_PRIORITY, NULL, NULL));
659    WebRequestData request_data(http_request.get(), ON_BEFORE_REQUEST);
660    http_request->set_first_party_for_cookies(firstPartyUrls[i]);
661    // Now run both rules on the input.
662    matches = registry->GetMatches(request_data);
663    SCOPED_TRACE(testing::Message("i = ") << i << ", rule id = "
664                                          << matchingRuleIds[i]);
665    // Make sure that the right rule succeeded.
666    EXPECT_EQ(1u, matches.size());
667    EXPECT_EQ(WebRequestRule::GlobalRuleId(std::make_pair(kExtensionId,
668                                                          matchingRuleIds[i])),
669              (*matches.begin())->id());
670  }
671}
672
673TEST(WebRequestRulesRegistrySimpleTest, StageChecker) {
674  // The contentType condition can only be evaluated during ON_HEADERS_RECEIVED
675  // but the SetRequestHeader action can only be executed during
676  // ON_BEFORE_SEND_HEADERS.
677  // Therefore, this is an inconsistent rule that needs to be flagged.
678  const char kRule[] =
679      "{                                                                  \n"
680      "  \"id\": \"rule1\",                                               \n"
681      "  \"conditions\": [                                                \n"
682      "    {                                                              \n"
683      "      \"instanceType\": \"declarativeWebRequest.RequestMatcher\",  \n"
684      "      \"url\": {\"hostSuffix\": \"foo.com\"},                      \n"
685      "      \"contentType\": [\"image/jpeg\"]                            \n"
686      "    }                                                              \n"
687      "  ],                                                               \n"
688      "  \"actions\": [                                                   \n"
689      "    {                                                              \n"
690      "      \"instanceType\": \"declarativeWebRequest.SetRequestHeader\",\n"
691      "      \"name\": \"Content-Type\",                                  \n"
692      "      \"value\": \"text/plain\"                                    \n"
693      "    }                                                              \n"
694      "  ],                                                               \n"
695      "  \"priority\": 200                                                \n"
696      "}                                                                  ";
697
698  scoped_ptr<base::Value> value(base::JSONReader::Read(kRule));
699  ASSERT_TRUE(value);
700
701  RulesRegistry::Rule rule;
702  ASSERT_TRUE(RulesRegistry::Rule::Populate(*value, &rule));
703
704  std::string error;
705  URLMatcher matcher;
706  scoped_ptr<WebRequestConditionSet> conditions =
707      WebRequestConditionSet::Create(
708          NULL, matcher.condition_factory(), rule.conditions, &error);
709  ASSERT_TRUE(error.empty()) << error;
710  ASSERT_TRUE(conditions);
711
712  bool bad_message = false;
713  scoped_ptr<WebRequestActionSet> actions =
714      WebRequestActionSet::Create(
715          NULL, NULL, rule.actions, &error, &bad_message);
716  ASSERT_TRUE(error.empty()) << error;
717  ASSERT_FALSE(bad_message);
718  ASSERT_TRUE(actions);
719
720  EXPECT_FALSE(WebRequestRulesRegistry::StageChecker(
721      conditions.get(), actions.get(), &error));
722  EXPECT_THAT(error, HasSubstr("no time in the request life-cycle"));
723  EXPECT_THAT(error, HasSubstr(actions->actions().back()->GetName()));
724}
725
726TEST(WebRequestRulesRegistrySimpleTest, HostPermissionsChecker) {
727  const char kAction[] =  // This action requires all URLs host permission.
728      "{                                                             \n"
729      "  \"instanceType\": \"declarativeWebRequest.RedirectRequest\",\n"
730      "  \"redirectUrl\": \"http://bar.com\"                         \n"
731      "}                                                             ";
732  scoped_ptr<base::Value> action_value(base::JSONReader::Read(kAction));
733  ASSERT_TRUE(action_value);
734
735  WebRequestActionSet::AnyVector actions;
736  actions.push_back(linked_ptr<base::Value>(action_value.release()));
737  ASSERT_TRUE(actions.back().get());
738
739  std::string error;
740  bool bad_message = false;
741  scoped_ptr<WebRequestActionSet> action_set(
742      WebRequestActionSet::Create(NULL, NULL, actions, &error, &bad_message));
743  ASSERT_TRUE(error.empty()) << error;
744  ASSERT_FALSE(bad_message);
745  ASSERT_TRUE(action_set);
746
747  scoped_refptr<Extension> extension_no_url(
748      LoadManifest("permissions", "web_request_no_host.json"));
749  scoped_refptr<Extension> extension_some_urls(
750      LoadManifest("permissions", "web_request_com_host_permissions.json"));
751  scoped_refptr<Extension> extension_all_urls(
752      LoadManifest("permissions", "web_request_all_host_permissions.json"));
753
754  EXPECT_TRUE(WebRequestRulesRegistry::HostPermissionsChecker(
755      extension_all_urls.get(), action_set.get(), &error));
756  EXPECT_TRUE(error.empty()) << error;
757
758  EXPECT_FALSE(WebRequestRulesRegistry::HostPermissionsChecker(
759      extension_some_urls.get(), action_set.get(), &error));
760  EXPECT_THAT(error, HasSubstr("permission for all"));
761  EXPECT_THAT(error, HasSubstr(action_set->actions().back()->GetName()));
762
763  EXPECT_FALSE(WebRequestRulesRegistry::HostPermissionsChecker(
764      extension_no_url.get(), action_set.get(), &error));
765  EXPECT_THAT(error, HasSubstr("permission for all"));
766  EXPECT_THAT(error, HasSubstr(action_set->actions().back()->GetName()));
767}
768
769TEST_F(WebRequestRulesRegistryTest, CheckOriginAndPathRegEx) {
770  const char kRule[] =
771      "{                                                                 \n"
772      "  \"id\": \"rule1\",                                              \n"
773      "  \"conditions\": [                                               \n"
774      "    {                                                             \n"
775      "      \"instanceType\": \"declarativeWebRequest.RequestMatcher\", \n"
776      "      \"url\": {\"originAndPathMatches\": \"fo+.com\"}            \n"
777      "    }                                                             \n"
778      "  ],                                                              \n"
779      "  \"actions\": [                                                  \n"
780      "    {                                                             \n"
781      "      \"instanceType\": \"declarativeWebRequest.RedirectRequest\",\n"
782      "      \"redirectUrl\": \"http://bar.com\"                         \n"
783      "    }                                                             \n"
784      "  ],                                                              \n"
785      "  \"priority\": 200                                               \n"
786      "}                                                                 ";
787
788  scoped_ptr<base::Value> value(base::JSONReader::Read(kRule));
789  ASSERT_TRUE(value.get());
790
791  std::vector<linked_ptr<RulesRegistry::Rule> > rules;
792  rules.push_back(make_linked_ptr(new RulesRegistry::Rule));
793  ASSERT_TRUE(RulesRegistry::Rule::Populate(*value, rules.back().get()));
794
795  scoped_refptr<WebRequestRulesRegistry> registry(
796      new TestWebRequestRulesRegistry(extension_info_map_));
797
798  URLMatcher matcher;
799  std::string error = registry->AddRulesImpl(kExtensionId, rules);
800  EXPECT_EQ("", error);
801
802  net::TestURLRequestContext context;
803  std::list<LinkedPtrEventResponseDelta> deltas;
804
805  // No match because match is in the query parameter.
806  GURL url1("http://bar.com/index.html?foo.com");
807  scoped_ptr<net::URLRequest> request1(context.CreateRequest(
808      url1, net::DEFAULT_PRIORITY, NULL, NULL));
809  WebRequestData request_data1(request1.get(), ON_BEFORE_REQUEST);
810  deltas = registry->CreateDeltas(NULL, request_data1, false);
811  EXPECT_EQ(0u, deltas.size());
812
813  // This is a correct match.
814  GURL url2("http://foo.com/index.html");
815  scoped_ptr<net::URLRequest> request2(context.CreateRequest(
816      url2, net::DEFAULT_PRIORITY, NULL, NULL));
817  WebRequestData request_data2(request2.get(), ON_BEFORE_REQUEST);
818  deltas = registry->CreateDeltas(NULL, request_data2, false);
819  EXPECT_EQ(1u, deltas.size());
820}
821
822}  // namespace extensions
823