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.
5 #include "extensions/browser/api/declarative_webrequest/webrequest_action.h"
7 #include "base/files/file_path.h"
8 #include "base/json/json_file_value_serializer.h"
9 #include "base/memory/ref_counted.h"
10 #include "base/memory/scoped_ptr.h"
11 #include "base/message_loop/message_loop.h"
12 #include "base/test/values_test_util.h"
13 #include "base/time/time.h"
14 #include "base/values.h"
15 #include "chrome/common/chrome_paths.h"
16 #include "chrome/common/extensions/extension_constants.h"
17 #include "chrome/common/extensions/extension_test_util.h"
18 #include "content/public/test/test_browser_thread_bundle.h"
19 #include "extensions/browser/api/declarative_webrequest/request_stage.h"
20 #include "extensions/browser/api/declarative_webrequest/webrequest_condition.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 "extensions/browser/info_map.h"
24 #include "extensions/common/extension.h"
25 #include "net/base/request_priority.h"
26 #include "net/http/http_response_headers.h"
27 #include "net/url_request/url_request.h"
28 #include "net/url_request/url_request_test_util.h"
29 #include "testing/gmock/include/gmock/gmock.h"
30 #include "testing/gtest/include/gtest/gtest.h"
32 using base::DictionaryValue
;
33 using base::ListValue
;
34 using extension_test_util::LoadManifestUnchecked
;
35 using testing::HasSubstr
;
37 namespace extensions
{
41 const char kUnknownActionType
[] = "unknownType";
43 scoped_ptr
<WebRequestActionSet
> CreateSetOfActions(const char* json
) {
44 scoped_ptr
<base::Value
> parsed_value(base::test::ParseJson(json
));
45 const base::ListValue
* parsed_list
;
46 CHECK(parsed_value
->GetAsList(&parsed_list
));
48 WebRequestActionSet::Values actions
;
49 for (base::ListValue::const_iterator it
= parsed_list
->begin();
50 it
!= parsed_list
->end();
52 const base::DictionaryValue
* dict
;
53 CHECK((*it
)->GetAsDictionary(&dict
));
54 actions
.push_back(linked_ptr
<base::Value
>(dict
->DeepCopy()));
58 bool bad_message
= false;
60 scoped_ptr
<WebRequestActionSet
> action_set(
61 WebRequestActionSet::Create(NULL
, NULL
, actions
, &error
, &bad_message
));
63 EXPECT_FALSE(bad_message
);
65 return action_set
.Pass();
70 namespace keys
= declarative_webrequest_constants
;
72 class WebRequestActionWithThreadsTest
: public testing::Test
{
74 WebRequestActionWithThreadsTest()
75 : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP
) {}
78 void SetUp() override
;
80 // Creates a URL request for URL |url_string|, and applies the actions from
81 // |action_set| as if they were triggered by the extension with
82 // |extension_id| during |stage|.
83 bool ActionWorksOnRequest(const char* url_string
,
84 const std::string
& extension_id
,
85 const WebRequestActionSet
* action_set
,
88 // Expects a JSON description of an |action| requiring <all_urls> host
89 // permission, and checks that only an extensions with full host permissions
90 // can execute that action at |stage|. Also checks that the action is not
91 // executable for http://clients1.google.com.
92 void CheckActionNeedsAllUrls(const char* action
, RequestStage stage
);
95 content::TestBrowserThreadBundle thread_bundle_
;
98 net::TestURLRequestContext context_
;
100 // An extension with *.com host permissions and the DWR permission.
101 scoped_refptr
<Extension
> extension_
;
102 // An extension with host permissions for all URLs and the DWR permission.
103 scoped_refptr
<Extension
> extension_all_urls_
;
104 scoped_refptr
<InfoMap
> extension_info_map_
;
107 void WebRequestActionWithThreadsTest::SetUp() {
108 testing::Test::SetUp();
111 extension_
= LoadManifestUnchecked("permissions",
112 "web_request_com_host_permissions.json",
113 Manifest::INVALID_LOCATION
,
117 ASSERT_TRUE(extension_
.get()) << error
;
118 extension_all_urls_
=
119 LoadManifestUnchecked("permissions",
120 "web_request_all_host_permissions.json",
121 Manifest::INVALID_LOCATION
,
125 ASSERT_TRUE(extension_all_urls_
.get()) << error
;
126 extension_info_map_
= new InfoMap
;
127 ASSERT_TRUE(extension_info_map_
.get());
128 extension_info_map_
->AddExtension(
131 false /*incognito_enabled*/,
132 false /*notifications_disabled*/);
133 extension_info_map_
->AddExtension(extension_all_urls_
.get(),
135 false /*incognito_enabled*/,
136 false /*notifications_disabled*/);
139 bool WebRequestActionWithThreadsTest::ActionWorksOnRequest(
140 const char* url_string
,
141 const std::string
& extension_id
,
142 const WebRequestActionSet
* action_set
,
143 RequestStage stage
) {
144 scoped_ptr
<net::URLRequest
> regular_request(context_
.CreateRequest(
145 GURL(url_string
), net::DEFAULT_PRIORITY
, NULL
));
146 std::list
<LinkedPtrEventResponseDelta
> deltas
;
147 scoped_refptr
<net::HttpResponseHeaders
> headers(
148 new net::HttpResponseHeaders(""));
149 WebRequestData
request_data(regular_request
.get(), stage
, headers
.get());
150 std::set
<std::string
> ignored_tags
;
151 WebRequestAction::ApplyInfo apply_info
= { extension_info_map_
.get(),
153 false /*crosses_incognito*/,
154 &deltas
, &ignored_tags
};
155 action_set
->Apply(extension_id
, base::Time(), &apply_info
);
156 return (1u == deltas
.size() || 0u < ignored_tags
.size());
159 void WebRequestActionWithThreadsTest::CheckActionNeedsAllUrls(
161 RequestStage stage
) {
162 scoped_ptr
<WebRequestActionSet
> action_set(CreateSetOfActions(action
));
164 // Although |extension_| has matching *.com host permission, |action|
165 // is intentionally forbidden -- in Declarative WR, host permission
166 // for less than all URLs are ignored (except in SendMessageToExtension).
167 EXPECT_FALSE(ActionWorksOnRequest(
168 "http://test.com", extension_
->id(), action_set
.get(), stage
));
169 // With the "<all_urls>" host permission they are allowed.
170 EXPECT_TRUE(ActionWorksOnRequest(
171 "http://test.com", extension_all_urls_
->id(), action_set
.get(), stage
));
173 // The protected URLs should not be touched at all.
174 EXPECT_FALSE(ActionWorksOnRequest(
175 "http://clients1.google.com", extension_
->id(), action_set
.get(), stage
));
176 EXPECT_FALSE(ActionWorksOnRequest("http://clients1.google.com",
177 extension_all_urls_
->id(),
182 TEST(WebRequestActionTest
, CreateAction
) {
184 bool bad_message
= false;
185 scoped_refptr
<const WebRequestAction
> result
;
187 // Test wrong data type passed.
189 base::ListValue empty_list
;
190 result
= WebRequestAction::Create(
191 NULL
, NULL
, empty_list
, &error
, &bad_message
);
192 EXPECT_TRUE(bad_message
);
193 EXPECT_FALSE(result
.get());
195 // Test missing instanceType element.
196 base::DictionaryValue input
;
198 result
= WebRequestAction::Create(NULL
, NULL
, input
, &error
, &bad_message
);
199 EXPECT_TRUE(bad_message
);
200 EXPECT_FALSE(result
.get());
202 // Test wrong instanceType element.
203 input
.SetString(keys::kInstanceTypeKey
, kUnknownActionType
);
205 result
= WebRequestAction::Create(NULL
, NULL
, input
, &error
, &bad_message
);
206 EXPECT_NE("", error
);
207 EXPECT_FALSE(result
.get());
210 input
.SetString(keys::kInstanceTypeKey
, keys::kCancelRequestType
);
212 result
= WebRequestAction::Create(NULL
, NULL
, input
, &error
, &bad_message
);
213 EXPECT_EQ("", error
);
214 EXPECT_FALSE(bad_message
);
215 ASSERT_TRUE(result
.get());
216 EXPECT_EQ(WebRequestAction::ACTION_CANCEL_REQUEST
, result
->type());
219 TEST(WebRequestActionTest
, CreateActionSet
) {
221 bool bad_message
= false;
222 scoped_ptr
<WebRequestActionSet
> result
;
224 WebRequestActionSet::Values input
;
228 result
= WebRequestActionSet::Create(NULL
, NULL
, input
, &error
, &bad_message
);
229 EXPECT_TRUE(error
.empty()) << error
;
230 EXPECT_FALSE(bad_message
);
231 ASSERT_TRUE(result
.get());
232 EXPECT_TRUE(result
->actions().empty());
233 EXPECT_EQ(std::numeric_limits
<int>::min(), result
->GetMinimumPriority());
235 base::DictionaryValue correct_action
;
236 correct_action
.SetString(keys::kInstanceTypeKey
, keys::kIgnoreRulesType
);
237 correct_action
.SetInteger(keys::kLowerPriorityThanKey
, 10);
238 base::DictionaryValue incorrect_action
;
239 incorrect_action
.SetString(keys::kInstanceTypeKey
, kUnknownActionType
);
242 input
.push_back(linked_ptr
<base::Value
>(correct_action
.DeepCopy()));
244 result
= WebRequestActionSet::Create(NULL
, NULL
, input
, &error
, &bad_message
);
245 EXPECT_TRUE(error
.empty()) << error
;
246 EXPECT_FALSE(bad_message
);
247 ASSERT_TRUE(result
.get());
248 ASSERT_EQ(1u, result
->actions().size());
249 EXPECT_EQ(WebRequestAction::ACTION_IGNORE_RULES
,
250 result
->actions()[0]->type());
251 EXPECT_EQ(10, result
->GetMinimumPriority());
254 input
.push_back(linked_ptr
<base::Value
>(incorrect_action
.DeepCopy()));
256 result
= WebRequestActionSet::Create(NULL
, NULL
, input
, &error
, &bad_message
);
257 EXPECT_NE("", error
);
258 EXPECT_FALSE(result
.get());
261 // Test capture group syntax conversions of WebRequestRedirectByRegExAction
262 TEST(WebRequestActionTest
, PerlToRe2Style
) {
263 #define CallPerlToRe2Style WebRequestRedirectByRegExAction::PerlToRe2Style
264 // foo$1bar -> foo\1bar
265 EXPECT_EQ("foo\\1bar", CallPerlToRe2Style("foo$1bar"));
266 // foo\$1bar -> foo$1bar
267 EXPECT_EQ("foo$1bar", CallPerlToRe2Style("foo\\$1bar"));
268 // foo\\$1bar -> foo\\\1bar
269 EXPECT_EQ("foo\\\\\\1bar", CallPerlToRe2Style("foo\\\\$1bar"));
271 EXPECT_EQ("foobar", CallPerlToRe2Style("foo\\bar"));
272 // foo$bar -> foo$bar
273 EXPECT_EQ("foo$bar", CallPerlToRe2Style("foo$bar"));
274 #undef CallPerlToRe2Style
277 TEST_F(WebRequestActionWithThreadsTest
, PermissionsToRedirect
) {
278 const char kAction
[] =
280 " \"instanceType\": \"declarativeWebRequest.RedirectRequest\","
281 " \"redirectUrl\": \"http://www.foobar.com\""
283 CheckActionNeedsAllUrls(kAction
, ON_BEFORE_REQUEST
);
284 CheckActionNeedsAllUrls(kAction
, ON_HEADERS_RECEIVED
);
287 TEST_F(WebRequestActionWithThreadsTest
, PermissionsToRedirectByRegEx
) {
288 const char kAction
[] =
290 " \"instanceType\": \"declarativeWebRequest.RedirectByRegEx\","
292 " \"to\": \"http://www.foobar.com\""
294 CheckActionNeedsAllUrls(kAction
, ON_BEFORE_REQUEST
);
297 TEST_F(WebRequestActionWithThreadsTest
, PermissionsToSetRequestHeader
) {
298 const char kAction
[] =
300 " \"instanceType\": \"declarativeWebRequest.SetRequestHeader\","
301 " \"name\": \"testname\","
302 " \"value\": \"testvalue\""
304 CheckActionNeedsAllUrls(kAction
, ON_BEFORE_SEND_HEADERS
);
307 TEST_F(WebRequestActionWithThreadsTest
, PermissionsToRemoveRequestHeader
) {
308 const char kAction
[] =
310 " \"instanceType\": \"declarativeWebRequest.RemoveRequestHeader\","
311 " \"name\": \"testname\""
313 CheckActionNeedsAllUrls(kAction
, ON_BEFORE_SEND_HEADERS
);
316 TEST_F(WebRequestActionWithThreadsTest
, PermissionsToAddResponseHeader
) {
317 const char kAction
[] =
319 " \"instanceType\": \"declarativeWebRequest.AddResponseHeader\","
320 " \"name\": \"testname\","
321 " \"value\": \"testvalue\""
323 CheckActionNeedsAllUrls(kAction
, ON_HEADERS_RECEIVED
);
326 TEST_F(WebRequestActionWithThreadsTest
, PermissionsToRemoveResponseHeader
) {
327 const char kAction
[] =
329 " \"instanceType\": \"declarativeWebRequest.RemoveResponseHeader\","
330 " \"name\": \"testname\""
332 CheckActionNeedsAllUrls(kAction
, ON_HEADERS_RECEIVED
);
335 TEST_F(WebRequestActionWithThreadsTest
, PermissionsToSendMessageToExtension
) {
336 const char kAction
[] =
338 " \"instanceType\": \"declarativeWebRequest.SendMessageToExtension\","
339 " \"message\": \"testtext\""
341 scoped_ptr
<WebRequestActionSet
> action_set(CreateSetOfActions(kAction
));
343 // For sending messages, specific host permissions actually matter.
344 EXPECT_TRUE(ActionWorksOnRequest("http://test.com",
348 // With the "<all_urls>" host permission they are allowed.
349 EXPECT_TRUE(ActionWorksOnRequest("http://test.com",
350 extension_all_urls_
->id(),
354 // The protected URLs should not be touched at all.
355 EXPECT_FALSE(ActionWorksOnRequest("http://clients1.google.com",
359 EXPECT_FALSE(ActionWorksOnRequest("http://clients1.google.com",
360 extension_all_urls_
->id(),
365 TEST_F(WebRequestActionWithThreadsTest
, PermissionsToAddRequestCookie
) {
366 const char kAction
[] =
368 " \"instanceType\": \"declarativeWebRequest.AddRequestCookie\","
369 " \"cookie\": { \"name\": \"cookiename\", \"value\": \"cookievalue\" }"
371 CheckActionNeedsAllUrls(kAction
, ON_BEFORE_SEND_HEADERS
);
374 TEST_F(WebRequestActionWithThreadsTest
, PermissionsToAddResponseCookie
) {
375 const char kAction
[] =
377 " \"instanceType\": \"declarativeWebRequest.AddResponseCookie\","
378 " \"cookie\": { \"name\": \"cookiename\", \"value\": \"cookievalue\" }"
380 CheckActionNeedsAllUrls(kAction
, ON_HEADERS_RECEIVED
);
383 TEST_F(WebRequestActionWithThreadsTest
, PermissionsToEditRequestCookie
) {
384 const char kAction
[] =
386 " \"instanceType\": \"declarativeWebRequest.EditRequestCookie\","
387 " \"filter\": { \"name\": \"cookiename\", \"value\": \"cookievalue\" },"
388 " \"modification\": { \"name\": \"name2\", \"value\": \"value2\" }"
390 CheckActionNeedsAllUrls(kAction
, ON_BEFORE_SEND_HEADERS
);
393 TEST_F(WebRequestActionWithThreadsTest
, PermissionsToEditResponseCookie
) {
394 const char kAction
[] =
396 " \"instanceType\": \"declarativeWebRequest.EditResponseCookie\","
397 " \"filter\": { \"name\": \"cookiename\", \"value\": \"cookievalue\" },"
398 " \"modification\": { \"name\": \"name2\", \"value\": \"value2\" }"
400 CheckActionNeedsAllUrls(kAction
, ON_HEADERS_RECEIVED
);
403 TEST_F(WebRequestActionWithThreadsTest
, PermissionsToRemoveRequestCookie
) {
404 const char kAction
[] =
406 " \"instanceType\": \"declarativeWebRequest.RemoveRequestCookie\","
407 " \"filter\": { \"name\": \"cookiename\", \"value\": \"cookievalue\" }"
409 CheckActionNeedsAllUrls(kAction
, ON_BEFORE_SEND_HEADERS
);
412 TEST_F(WebRequestActionWithThreadsTest
, PermissionsToRemoveResponseCookie
) {
413 const char kAction
[] =
415 " \"instanceType\": \"declarativeWebRequest.RemoveResponseCookie\","
416 " \"filter\": { \"name\": \"cookiename\", \"value\": \"cookievalue\" }"
418 CheckActionNeedsAllUrls(kAction
, ON_HEADERS_RECEIVED
);
421 TEST_F(WebRequestActionWithThreadsTest
, PermissionsToCancel
) {
422 const char kAction
[] =
424 " \"instanceType\": \"declarativeWebRequest.CancelRequest\""
426 scoped_ptr
<WebRequestActionSet
> action_set(CreateSetOfActions(kAction
));
428 // Cancelling requests works without full host permissions.
429 EXPECT_TRUE(ActionWorksOnRequest("http://test.org",
435 TEST_F(WebRequestActionWithThreadsTest
,
436 PermissionsToRedirectToTransparentImage
) {
437 const char kAction
[] =
439 " \"instanceType\": \"declarativeWebRequest.RedirectToTransparentImage\""
441 scoped_ptr
<WebRequestActionSet
> action_set(CreateSetOfActions(kAction
));
443 // Redirecting to transparent images works without full host permissions.
444 EXPECT_TRUE(ActionWorksOnRequest("http://test.org",
448 EXPECT_TRUE(ActionWorksOnRequest("http://test.org",
451 ON_HEADERS_RECEIVED
));
454 TEST_F(WebRequestActionWithThreadsTest
, PermissionsToRedirectToEmptyDocument
) {
455 const char kAction
[] =
457 " \"instanceType\": \"declarativeWebRequest.RedirectToEmptyDocument\""
459 scoped_ptr
<WebRequestActionSet
> action_set(CreateSetOfActions(kAction
));
461 // Redirecting to the empty document works without full host permissions.
462 EXPECT_TRUE(ActionWorksOnRequest("http://test.org",
466 EXPECT_TRUE(ActionWorksOnRequest("http://test.org",
469 ON_HEADERS_RECEIVED
));
472 TEST_F(WebRequestActionWithThreadsTest
, PermissionsToIgnore
) {
473 const char kAction
[] =
475 " \"instanceType\": \"declarativeWebRequest.IgnoreRules\","
476 " \"lowerPriorityThan\": 123,"
477 " \"hasTag\": \"some_tag\""
479 scoped_ptr
<WebRequestActionSet
> action_set(CreateSetOfActions(kAction
));
481 // Ignoring rules works without full host permissions.
482 EXPECT_TRUE(ActionWorksOnRequest("http://test.org",
488 TEST(WebRequestActionTest
, GetName
) {
489 const char kActions
[] =
491 " \"instanceType\": \"declarativeWebRequest.RedirectRequest\","
492 " \"redirectUrl\": \"http://www.foobar.com\""
495 " \"instanceType\": \"declarativeWebRequest.RedirectByRegEx\","
497 " \"to\": \"http://www.foobar.com\""
500 " \"instanceType\": \"declarativeWebRequest.SetRequestHeader\","
501 " \"name\": \"testname\","
502 " \"value\": \"testvalue\""
505 " \"instanceType\": \"declarativeWebRequest.RemoveRequestHeader\","
506 " \"name\": \"testname\""
509 " \"instanceType\": \"declarativeWebRequest.AddResponseHeader\","
510 " \"name\": \"testname\","
511 " \"value\": \"testvalue\""
514 " \"instanceType\": \"declarativeWebRequest.RemoveResponseHeader\","
515 " \"name\": \"testname\""
518 " \"instanceType\": \"declarativeWebRequest.SendMessageToExtension\","
519 " \"message\": \"testtext\""
522 " \"instanceType\": \"declarativeWebRequest.AddRequestCookie\","
523 " \"cookie\": { \"name\": \"cookiename\", \"value\": \"cookievalue\" }"
526 " \"instanceType\": \"declarativeWebRequest.AddResponseCookie\","
527 " \"cookie\": { \"name\": \"cookiename\", \"value\": \"cookievalue\" }"
530 " \"instanceType\": \"declarativeWebRequest.EditRequestCookie\","
531 " \"filter\": { \"name\": \"cookiename\", \"value\": \"cookievalue\" },"
532 " \"modification\": { \"name\": \"name2\", \"value\": \"value2\" }"
535 " \"instanceType\": \"declarativeWebRequest.EditResponseCookie\","
536 " \"filter\": { \"name\": \"cookiename\", \"value\": \"cookievalue\" },"
537 " \"modification\": { \"name\": \"name2\", \"value\": \"value2\" }"
540 " \"instanceType\": \"declarativeWebRequest.RemoveRequestCookie\","
541 " \"filter\": { \"name\": \"cookiename\", \"value\": \"cookievalue\" }"
544 " \"instanceType\": \"declarativeWebRequest.RemoveResponseCookie\","
545 " \"filter\": { \"name\": \"cookiename\", \"value\": \"cookievalue\" }"
548 " \"instanceType\": \"declarativeWebRequest.CancelRequest\""
551 " \"instanceType\": \"declarativeWebRequest.RedirectToTransparentImage\""
554 " \"instanceType\": \"declarativeWebRequest.RedirectToEmptyDocument\""
557 " \"instanceType\": \"declarativeWebRequest.IgnoreRules\","
558 " \"lowerPriorityThan\": 123,"
559 " \"hasTag\": \"some_tag\""
561 const char* const kExpectedNames
[] = {
562 "declarativeWebRequest.RedirectRequest",
563 "declarativeWebRequest.RedirectByRegEx",
564 "declarativeWebRequest.SetRequestHeader",
565 "declarativeWebRequest.RemoveRequestHeader",
566 "declarativeWebRequest.AddResponseHeader",
567 "declarativeWebRequest.RemoveResponseHeader",
568 "declarativeWebRequest.SendMessageToExtension",
569 "declarativeWebRequest.AddRequestCookie",
570 "declarativeWebRequest.AddResponseCookie",
571 "declarativeWebRequest.EditRequestCookie",
572 "declarativeWebRequest.EditResponseCookie",
573 "declarativeWebRequest.RemoveRequestCookie",
574 "declarativeWebRequest.RemoveResponseCookie",
575 "declarativeWebRequest.CancelRequest",
576 "declarativeWebRequest.RedirectToTransparentImage",
577 "declarativeWebRequest.RedirectToEmptyDocument",
578 "declarativeWebRequest.IgnoreRules",
580 scoped_ptr
<WebRequestActionSet
> action_set(CreateSetOfActions(kActions
));
581 ASSERT_EQ(arraysize(kExpectedNames
), action_set
->actions().size());
583 for (WebRequestActionSet::Actions::const_iterator it
=
584 action_set
->actions().begin();
585 it
!= action_set
->actions().end();
587 EXPECT_EQ(kExpectedNames
[index
], (*it
)->GetName());
592 } // namespace extensions