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::AnyVector 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
);
94 net::TestURLRequestContext context_
;
96 // An extension with *.com host permissions and the DWR permission.
97 scoped_refptr
<Extension
> extension_
;
98 // An extension with host permissions for all URLs and the DWR permission.
99 scoped_refptr
<Extension
> extension_all_urls_
;
100 scoped_refptr
<InfoMap
> extension_info_map_
;
103 content::TestBrowserThreadBundle thread_bundle_
;
106 void WebRequestActionWithThreadsTest::SetUp() {
107 testing::Test::SetUp();
110 extension_
= LoadManifestUnchecked("permissions",
111 "web_request_com_host_permissions.json",
112 Manifest::INVALID_LOCATION
,
116 ASSERT_TRUE(extension_
.get()) << error
;
117 extension_all_urls_
=
118 LoadManifestUnchecked("permissions",
119 "web_request_all_host_permissions.json",
120 Manifest::INVALID_LOCATION
,
124 ASSERT_TRUE(extension_all_urls_
.get()) << error
;
125 extension_info_map_
= new InfoMap
;
126 ASSERT_TRUE(extension_info_map_
.get());
127 extension_info_map_
->AddExtension(
130 false /*incognito_enabled*/,
131 false /*notifications_disabled*/);
132 extension_info_map_
->AddExtension(extension_all_urls_
.get(),
134 false /*incognito_enabled*/,
135 false /*notifications_disabled*/);
138 bool WebRequestActionWithThreadsTest::ActionWorksOnRequest(
139 const char* url_string
,
140 const std::string
& extension_id
,
141 const WebRequestActionSet
* action_set
,
142 RequestStage stage
) {
143 scoped_ptr
<net::URLRequest
> regular_request(context_
.CreateRequest(
144 GURL(url_string
), net::DEFAULT_PRIORITY
, NULL
));
145 std::list
<LinkedPtrEventResponseDelta
> deltas
;
146 scoped_refptr
<net::HttpResponseHeaders
> headers(
147 new net::HttpResponseHeaders(""));
148 WebRequestData
request_data(regular_request
.get(), stage
, headers
.get());
149 std::set
<std::string
> ignored_tags
;
150 WebRequestAction::ApplyInfo apply_info
= { extension_info_map_
.get(),
152 false /*crosses_incognito*/,
153 &deltas
, &ignored_tags
};
154 action_set
->Apply(extension_id
, base::Time(), &apply_info
);
155 return (1u == deltas
.size() || 0u < ignored_tags
.size());
158 void WebRequestActionWithThreadsTest::CheckActionNeedsAllUrls(
160 RequestStage stage
) {
161 scoped_ptr
<WebRequestActionSet
> action_set(CreateSetOfActions(action
));
163 // Although |extension_| has matching *.com host permission, |action|
164 // is intentionally forbidden -- in Declarative WR, host permission
165 // for less than all URLs are ignored (except in SendMessageToExtension).
166 EXPECT_FALSE(ActionWorksOnRequest(
167 "http://test.com", extension_
->id(), action_set
.get(), stage
));
168 // With the "<all_urls>" host permission they are allowed.
169 EXPECT_TRUE(ActionWorksOnRequest(
170 "http://test.com", extension_all_urls_
->id(), action_set
.get(), stage
));
172 // The protected URLs should not be touched at all.
173 EXPECT_FALSE(ActionWorksOnRequest(
174 "http://clients1.google.com", extension_
->id(), action_set
.get(), stage
));
175 EXPECT_FALSE(ActionWorksOnRequest("http://clients1.google.com",
176 extension_all_urls_
->id(),
181 TEST(WebRequestActionTest
, CreateAction
) {
183 bool bad_message
= false;
184 scoped_refptr
<const WebRequestAction
> result
;
186 // Test wrong data type passed.
188 base::ListValue empty_list
;
189 result
= WebRequestAction::Create(
190 NULL
, NULL
, empty_list
, &error
, &bad_message
);
191 EXPECT_TRUE(bad_message
);
192 EXPECT_FALSE(result
.get());
194 // Test missing instanceType element.
195 base::DictionaryValue input
;
197 result
= WebRequestAction::Create(NULL
, NULL
, input
, &error
, &bad_message
);
198 EXPECT_TRUE(bad_message
);
199 EXPECT_FALSE(result
.get());
201 // Test wrong instanceType element.
202 input
.SetString(keys::kInstanceTypeKey
, kUnknownActionType
);
204 result
= WebRequestAction::Create(NULL
, NULL
, input
, &error
, &bad_message
);
205 EXPECT_NE("", error
);
206 EXPECT_FALSE(result
.get());
209 input
.SetString(keys::kInstanceTypeKey
, keys::kCancelRequestType
);
211 result
= WebRequestAction::Create(NULL
, NULL
, input
, &error
, &bad_message
);
212 EXPECT_EQ("", error
);
213 EXPECT_FALSE(bad_message
);
214 ASSERT_TRUE(result
.get());
215 EXPECT_EQ(WebRequestAction::ACTION_CANCEL_REQUEST
, result
->type());
218 TEST(WebRequestActionTest
, CreateActionSet
) {
220 bool bad_message
= false;
221 scoped_ptr
<WebRequestActionSet
> result
;
223 WebRequestActionSet::AnyVector input
;
227 result
= WebRequestActionSet::Create(NULL
, NULL
, input
, &error
, &bad_message
);
228 EXPECT_TRUE(error
.empty()) << error
;
229 EXPECT_FALSE(bad_message
);
230 ASSERT_TRUE(result
.get());
231 EXPECT_TRUE(result
->actions().empty());
232 EXPECT_EQ(std::numeric_limits
<int>::min(), result
->GetMinimumPriority());
234 base::DictionaryValue correct_action
;
235 correct_action
.SetString(keys::kInstanceTypeKey
, keys::kIgnoreRulesType
);
236 correct_action
.SetInteger(keys::kLowerPriorityThanKey
, 10);
237 base::DictionaryValue incorrect_action
;
238 incorrect_action
.SetString(keys::kInstanceTypeKey
, kUnknownActionType
);
241 input
.push_back(linked_ptr
<base::Value
>(correct_action
.DeepCopy()));
243 result
= WebRequestActionSet::Create(NULL
, NULL
, input
, &error
, &bad_message
);
244 EXPECT_TRUE(error
.empty()) << error
;
245 EXPECT_FALSE(bad_message
);
246 ASSERT_TRUE(result
.get());
247 ASSERT_EQ(1u, result
->actions().size());
248 EXPECT_EQ(WebRequestAction::ACTION_IGNORE_RULES
,
249 result
->actions()[0]->type());
250 EXPECT_EQ(10, result
->GetMinimumPriority());
253 input
.push_back(linked_ptr
<base::Value
>(incorrect_action
.DeepCopy()));
255 result
= WebRequestActionSet::Create(NULL
, NULL
, input
, &error
, &bad_message
);
256 EXPECT_NE("", error
);
257 EXPECT_FALSE(result
.get());
260 // Test capture group syntax conversions of WebRequestRedirectByRegExAction
261 TEST(WebRequestActionTest
, PerlToRe2Style
) {
262 #define CallPerlToRe2Style WebRequestRedirectByRegExAction::PerlToRe2Style
263 // foo$1bar -> foo\1bar
264 EXPECT_EQ("foo\\1bar", CallPerlToRe2Style("foo$1bar"));
265 // foo\$1bar -> foo$1bar
266 EXPECT_EQ("foo$1bar", CallPerlToRe2Style("foo\\$1bar"));
267 // foo\\$1bar -> foo\\\1bar
268 EXPECT_EQ("foo\\\\\\1bar", CallPerlToRe2Style("foo\\\\$1bar"));
270 EXPECT_EQ("foobar", CallPerlToRe2Style("foo\\bar"));
271 // foo$bar -> foo$bar
272 EXPECT_EQ("foo$bar", CallPerlToRe2Style("foo$bar"));
273 #undef CallPerlToRe2Style
276 TEST_F(WebRequestActionWithThreadsTest
, PermissionsToRedirect
) {
277 const char kAction
[] =
279 " \"instanceType\": \"declarativeWebRequest.RedirectRequest\","
280 " \"redirectUrl\": \"http://www.foobar.com\""
282 CheckActionNeedsAllUrls(kAction
, ON_BEFORE_REQUEST
);
283 CheckActionNeedsAllUrls(kAction
, ON_HEADERS_RECEIVED
);
286 TEST_F(WebRequestActionWithThreadsTest
, PermissionsToRedirectByRegEx
) {
287 const char kAction
[] =
289 " \"instanceType\": \"declarativeWebRequest.RedirectByRegEx\","
291 " \"to\": \"http://www.foobar.com\""
293 CheckActionNeedsAllUrls(kAction
, ON_BEFORE_REQUEST
);
296 TEST_F(WebRequestActionWithThreadsTest
, PermissionsToSetRequestHeader
) {
297 const char kAction
[] =
299 " \"instanceType\": \"declarativeWebRequest.SetRequestHeader\","
300 " \"name\": \"testname\","
301 " \"value\": \"testvalue\""
303 CheckActionNeedsAllUrls(kAction
, ON_BEFORE_SEND_HEADERS
);
306 TEST_F(WebRequestActionWithThreadsTest
, PermissionsToRemoveRequestHeader
) {
307 const char kAction
[] =
309 " \"instanceType\": \"declarativeWebRequest.RemoveRequestHeader\","
310 " \"name\": \"testname\""
312 CheckActionNeedsAllUrls(kAction
, ON_BEFORE_SEND_HEADERS
);
315 TEST_F(WebRequestActionWithThreadsTest
, PermissionsToAddResponseHeader
) {
316 const char kAction
[] =
318 " \"instanceType\": \"declarativeWebRequest.AddResponseHeader\","
319 " \"name\": \"testname\","
320 " \"value\": \"testvalue\""
322 CheckActionNeedsAllUrls(kAction
, ON_HEADERS_RECEIVED
);
325 TEST_F(WebRequestActionWithThreadsTest
, PermissionsToRemoveResponseHeader
) {
326 const char kAction
[] =
328 " \"instanceType\": \"declarativeWebRequest.RemoveResponseHeader\","
329 " \"name\": \"testname\""
331 CheckActionNeedsAllUrls(kAction
, ON_HEADERS_RECEIVED
);
334 TEST_F(WebRequestActionWithThreadsTest
, PermissionsToSendMessageToExtension
) {
335 const char kAction
[] =
337 " \"instanceType\": \"declarativeWebRequest.SendMessageToExtension\","
338 " \"message\": \"testtext\""
340 scoped_ptr
<WebRequestActionSet
> action_set(CreateSetOfActions(kAction
));
342 // For sending messages, specific host permissions actually matter.
343 EXPECT_TRUE(ActionWorksOnRequest("http://test.com",
347 // With the "<all_urls>" host permission they are allowed.
348 EXPECT_TRUE(ActionWorksOnRequest("http://test.com",
349 extension_all_urls_
->id(),
353 // The protected URLs should not be touched at all.
354 EXPECT_FALSE(ActionWorksOnRequest("http://clients1.google.com",
358 EXPECT_FALSE(ActionWorksOnRequest("http://clients1.google.com",
359 extension_all_urls_
->id(),
364 TEST_F(WebRequestActionWithThreadsTest
, PermissionsToAddRequestCookie
) {
365 const char kAction
[] =
367 " \"instanceType\": \"declarativeWebRequest.AddRequestCookie\","
368 " \"cookie\": { \"name\": \"cookiename\", \"value\": \"cookievalue\" }"
370 CheckActionNeedsAllUrls(kAction
, ON_BEFORE_SEND_HEADERS
);
373 TEST_F(WebRequestActionWithThreadsTest
, PermissionsToAddResponseCookie
) {
374 const char kAction
[] =
376 " \"instanceType\": \"declarativeWebRequest.AddResponseCookie\","
377 " \"cookie\": { \"name\": \"cookiename\", \"value\": \"cookievalue\" }"
379 CheckActionNeedsAllUrls(kAction
, ON_HEADERS_RECEIVED
);
382 TEST_F(WebRequestActionWithThreadsTest
, PermissionsToEditRequestCookie
) {
383 const char kAction
[] =
385 " \"instanceType\": \"declarativeWebRequest.EditRequestCookie\","
386 " \"filter\": { \"name\": \"cookiename\", \"value\": \"cookievalue\" },"
387 " \"modification\": { \"name\": \"name2\", \"value\": \"value2\" }"
389 CheckActionNeedsAllUrls(kAction
, ON_BEFORE_SEND_HEADERS
);
392 TEST_F(WebRequestActionWithThreadsTest
, PermissionsToEditResponseCookie
) {
393 const char kAction
[] =
395 " \"instanceType\": \"declarativeWebRequest.EditResponseCookie\","
396 " \"filter\": { \"name\": \"cookiename\", \"value\": \"cookievalue\" },"
397 " \"modification\": { \"name\": \"name2\", \"value\": \"value2\" }"
399 CheckActionNeedsAllUrls(kAction
, ON_HEADERS_RECEIVED
);
402 TEST_F(WebRequestActionWithThreadsTest
, PermissionsToRemoveRequestCookie
) {
403 const char kAction
[] =
405 " \"instanceType\": \"declarativeWebRequest.RemoveRequestCookie\","
406 " \"filter\": { \"name\": \"cookiename\", \"value\": \"cookievalue\" }"
408 CheckActionNeedsAllUrls(kAction
, ON_BEFORE_SEND_HEADERS
);
411 TEST_F(WebRequestActionWithThreadsTest
, PermissionsToRemoveResponseCookie
) {
412 const char kAction
[] =
414 " \"instanceType\": \"declarativeWebRequest.RemoveResponseCookie\","
415 " \"filter\": { \"name\": \"cookiename\", \"value\": \"cookievalue\" }"
417 CheckActionNeedsAllUrls(kAction
, ON_HEADERS_RECEIVED
);
420 TEST_F(WebRequestActionWithThreadsTest
, PermissionsToCancel
) {
421 const char kAction
[] =
423 " \"instanceType\": \"declarativeWebRequest.CancelRequest\""
425 scoped_ptr
<WebRequestActionSet
> action_set(CreateSetOfActions(kAction
));
427 // Cancelling requests works without full host permissions.
428 EXPECT_TRUE(ActionWorksOnRequest("http://test.org",
434 TEST_F(WebRequestActionWithThreadsTest
,
435 PermissionsToRedirectToTransparentImage
) {
436 const char kAction
[] =
438 " \"instanceType\": \"declarativeWebRequest.RedirectToTransparentImage\""
440 scoped_ptr
<WebRequestActionSet
> action_set(CreateSetOfActions(kAction
));
442 // Redirecting to transparent images works without full host permissions.
443 EXPECT_TRUE(ActionWorksOnRequest("http://test.org",
447 EXPECT_TRUE(ActionWorksOnRequest("http://test.org",
450 ON_HEADERS_RECEIVED
));
453 TEST_F(WebRequestActionWithThreadsTest
, PermissionsToRedirectToEmptyDocument
) {
454 const char kAction
[] =
456 " \"instanceType\": \"declarativeWebRequest.RedirectToEmptyDocument\""
458 scoped_ptr
<WebRequestActionSet
> action_set(CreateSetOfActions(kAction
));
460 // Redirecting to the empty document works without full host permissions.
461 EXPECT_TRUE(ActionWorksOnRequest("http://test.org",
465 EXPECT_TRUE(ActionWorksOnRequest("http://test.org",
468 ON_HEADERS_RECEIVED
));
471 TEST_F(WebRequestActionWithThreadsTest
, PermissionsToIgnore
) {
472 const char kAction
[] =
474 " \"instanceType\": \"declarativeWebRequest.IgnoreRules\","
475 " \"lowerPriorityThan\": 123,"
476 " \"hasTag\": \"some_tag\""
478 scoped_ptr
<WebRequestActionSet
> action_set(CreateSetOfActions(kAction
));
480 // Ignoring rules works without full host permissions.
481 EXPECT_TRUE(ActionWorksOnRequest("http://test.org",
487 TEST(WebRequestActionTest
, GetName
) {
488 const char kActions
[] =
490 " \"instanceType\": \"declarativeWebRequest.RedirectRequest\","
491 " \"redirectUrl\": \"http://www.foobar.com\""
494 " \"instanceType\": \"declarativeWebRequest.RedirectByRegEx\","
496 " \"to\": \"http://www.foobar.com\""
499 " \"instanceType\": \"declarativeWebRequest.SetRequestHeader\","
500 " \"name\": \"testname\","
501 " \"value\": \"testvalue\""
504 " \"instanceType\": \"declarativeWebRequest.RemoveRequestHeader\","
505 " \"name\": \"testname\""
508 " \"instanceType\": \"declarativeWebRequest.AddResponseHeader\","
509 " \"name\": \"testname\","
510 " \"value\": \"testvalue\""
513 " \"instanceType\": \"declarativeWebRequest.RemoveResponseHeader\","
514 " \"name\": \"testname\""
517 " \"instanceType\": \"declarativeWebRequest.SendMessageToExtension\","
518 " \"message\": \"testtext\""
521 " \"instanceType\": \"declarativeWebRequest.AddRequestCookie\","
522 " \"cookie\": { \"name\": \"cookiename\", \"value\": \"cookievalue\" }"
525 " \"instanceType\": \"declarativeWebRequest.AddResponseCookie\","
526 " \"cookie\": { \"name\": \"cookiename\", \"value\": \"cookievalue\" }"
529 " \"instanceType\": \"declarativeWebRequest.EditRequestCookie\","
530 " \"filter\": { \"name\": \"cookiename\", \"value\": \"cookievalue\" },"
531 " \"modification\": { \"name\": \"name2\", \"value\": \"value2\" }"
534 " \"instanceType\": \"declarativeWebRequest.EditResponseCookie\","
535 " \"filter\": { \"name\": \"cookiename\", \"value\": \"cookievalue\" },"
536 " \"modification\": { \"name\": \"name2\", \"value\": \"value2\" }"
539 " \"instanceType\": \"declarativeWebRequest.RemoveRequestCookie\","
540 " \"filter\": { \"name\": \"cookiename\", \"value\": \"cookievalue\" }"
543 " \"instanceType\": \"declarativeWebRequest.RemoveResponseCookie\","
544 " \"filter\": { \"name\": \"cookiename\", \"value\": \"cookievalue\" }"
547 " \"instanceType\": \"declarativeWebRequest.CancelRequest\""
550 " \"instanceType\": \"declarativeWebRequest.RedirectToTransparentImage\""
553 " \"instanceType\": \"declarativeWebRequest.RedirectToEmptyDocument\""
556 " \"instanceType\": \"declarativeWebRequest.IgnoreRules\","
557 " \"lowerPriorityThan\": 123,"
558 " \"hasTag\": \"some_tag\""
560 const char* const kExpectedNames
[] = {
561 "declarativeWebRequest.RedirectRequest",
562 "declarativeWebRequest.RedirectByRegEx",
563 "declarativeWebRequest.SetRequestHeader",
564 "declarativeWebRequest.RemoveRequestHeader",
565 "declarativeWebRequest.AddResponseHeader",
566 "declarativeWebRequest.RemoveResponseHeader",
567 "declarativeWebRequest.SendMessageToExtension",
568 "declarativeWebRequest.AddRequestCookie",
569 "declarativeWebRequest.AddResponseCookie",
570 "declarativeWebRequest.EditRequestCookie",
571 "declarativeWebRequest.EditResponseCookie",
572 "declarativeWebRequest.RemoveRequestCookie",
573 "declarativeWebRequest.RemoveResponseCookie",
574 "declarativeWebRequest.CancelRequest",
575 "declarativeWebRequest.RedirectToTransparentImage",
576 "declarativeWebRequest.RedirectToEmptyDocument",
577 "declarativeWebRequest.IgnoreRules",
579 scoped_ptr
<WebRequestActionSet
> action_set(CreateSetOfActions(kActions
));
580 ASSERT_EQ(arraysize(kExpectedNames
), action_set
->actions().size());
582 for (WebRequestActionSet::Actions::const_iterator it
=
583 action_set
->actions().begin();
584 it
!= action_set
->actions().end();
586 EXPECT_EQ(kExpectedNames
[index
], (*it
)->GetName());
591 } // namespace extensions