blob: 2de1cc107f2b0b94cbf9f11ad790d718758ef33e [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/extensions/api/declarative_webrequest/webrequest_action.h"
#include "base/files/file_path.h"
#include "base/json/json_file_value_serializer.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop/message_loop.h"
#include "base/path_service.h"
#include "base/test/values_test_util.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/browser/extensions/api/declarative_webrequest/request_stage.h"
#include "chrome/browser/extensions/api/declarative_webrequest/webrequest_condition.h"
#include "chrome/browser/extensions/api/declarative_webrequest/webrequest_constants.h"
#include "chrome/browser/extensions/api/web_request/web_request_api_helpers.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/common/extensions/extension_test_util.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "extensions/browser/info_map.h"
#include "extensions/common/extension.h"
#include "net/base/request_priority.h"
#include "net/http/http_response_headers.h"
#include "net/url_request/url_request_test_util.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using base::DictionaryValue;
using base::ListValue;
using extension_test_util::LoadManifestUnchecked;
using testing::HasSubstr;
namespace extensions {
namespace {
const char kUnknownActionType[] = "unknownType";
scoped_ptr<WebRequestActionSet> CreateSetOfActions(const char* json) {
scoped_ptr<Value> parsed_value(base::test::ParseJson(json));
const ListValue* parsed_list;
CHECK(parsed_value->GetAsList(&parsed_list));
WebRequestActionSet::AnyVector actions;
for (ListValue::const_iterator it = parsed_list->begin();
it != parsed_list->end();
++it) {
const DictionaryValue* dict;
CHECK((*it)->GetAsDictionary(&dict));
actions.push_back(linked_ptr<base::Value>(dict->DeepCopy()));
}
std::string error;
bool bad_message = false;
scoped_ptr<WebRequestActionSet> action_set(
WebRequestActionSet::Create(NULL, actions, &error, &bad_message));
EXPECT_EQ("", error);
EXPECT_FALSE(bad_message);
CHECK(action_set);
return action_set.Pass();
}
} // namespace
namespace keys = declarative_webrequest_constants;
class WebRequestActionWithThreadsTest : public testing::Test {
public:
WebRequestActionWithThreadsTest()
: thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP) {}
protected:
virtual void SetUp() OVERRIDE;
// Creates a URL request for URL |url_string|, and applies the actions from
// |action_set| as if they were triggered by the extension with
// |extension_id| during |stage|.
bool ActionWorksOnRequest(const char* url_string,
const std::string& extension_id,
const WebRequestActionSet* action_set,
RequestStage stage);
// Expects a JSON description of an |action| requiring <all_urls> host
// permission, and checks that only an extensions with full host permissions
// can execute that action at |stage|. Also checks that the action is not
// executable for http://clients1.google.com.
void CheckActionNeedsAllUrls(const char* action, RequestStage stage);
net::TestURLRequestContext context_;
// An extension with *.com host permissions and the DWR permission.
scoped_refptr<Extension> extension_;
// An extension with host permissions for all URLs and the DWR permission.
scoped_refptr<Extension> extension_all_urls_;
scoped_refptr<InfoMap> extension_info_map_;
private:
content::TestBrowserThreadBundle thread_bundle_;
};
void WebRequestActionWithThreadsTest::SetUp() {
testing::Test::SetUp();
std::string error;
extension_ = LoadManifestUnchecked("permissions",
"web_request_com_host_permissions.json",
Manifest::INVALID_LOCATION,
Extension::NO_FLAGS,
"ext_id_1",
&error);
ASSERT_TRUE(extension_.get()) << error;
extension_all_urls_ =
LoadManifestUnchecked("permissions",
"web_request_all_host_permissions.json",
Manifest::INVALID_LOCATION,
Extension::NO_FLAGS,
"ext_id_2",
&error);
ASSERT_TRUE(extension_all_urls_.get()) << error;
extension_info_map_ = new InfoMap;
ASSERT_TRUE(extension_info_map_.get());
extension_info_map_->AddExtension(
extension_.get(),
base::Time::Now(),
false /*incognito_enabled*/,
false /*notifications_disabled*/);
extension_info_map_->AddExtension(extension_all_urls_.get(),
base::Time::Now(),
false /*incognito_enabled*/,
false /*notifications_disabled*/);
}
bool WebRequestActionWithThreadsTest::ActionWorksOnRequest(
const char* url_string,
const std::string& extension_id,
const WebRequestActionSet* action_set,
RequestStage stage) {
net::TestURLRequest regular_request(
GURL(url_string), net::DEFAULT_PRIORITY, NULL, &context_);
std::list<LinkedPtrEventResponseDelta> deltas;
scoped_refptr<net::HttpResponseHeaders> headers(
new net::HttpResponseHeaders(""));
WebRequestData request_data(&regular_request, stage, headers.get());
std::set<std::string> ignored_tags;
WebRequestAction::ApplyInfo apply_info = { extension_info_map_.get(),
request_data,
false /*crosses_incognito*/,
&deltas, &ignored_tags };
action_set->Apply(extension_id, base::Time(), &apply_info);
return (1u == deltas.size() || 0u < ignored_tags.size());
}
void WebRequestActionWithThreadsTest::CheckActionNeedsAllUrls(
const char* action,
RequestStage stage) {
scoped_ptr<WebRequestActionSet> action_set(CreateSetOfActions(action));
// Although |extension_| has matching *.com host permission, |action|
// is intentionally forbidden -- in Declarative WR, host permission
// for less than all URLs are ignored (except in SendMessageToExtension).
EXPECT_FALSE(ActionWorksOnRequest(
"http://test.com", extension_->id(), action_set.get(), stage));
// With the "<all_urls>" host permission they are allowed.
EXPECT_TRUE(ActionWorksOnRequest(
"http://test.com", extension_all_urls_->id(), action_set.get(), stage));
// The protected URLs should not be touched at all.
EXPECT_FALSE(ActionWorksOnRequest(
"http://clients1.google.com", extension_->id(), action_set.get(), stage));
EXPECT_FALSE(ActionWorksOnRequest("http://clients1.google.com",
extension_all_urls_->id(),
action_set.get(),
stage));
}
TEST(WebRequestActionTest, CreateAction) {
std::string error;
bool bad_message = false;
scoped_refptr<const WebRequestAction> result;
// Test wrong data type passed.
error.clear();
base::ListValue empty_list;
result = WebRequestAction::Create(NULL, empty_list, &error, &bad_message);
EXPECT_TRUE(bad_message);
EXPECT_FALSE(result.get());
// Test missing instanceType element.
base::DictionaryValue input;
error.clear();
result = WebRequestAction::Create(NULL, input, &error, &bad_message);
EXPECT_TRUE(bad_message);
EXPECT_FALSE(result.get());
// Test wrong instanceType element.
input.SetString(keys::kInstanceTypeKey, kUnknownActionType);
error.clear();
result = WebRequestAction::Create(NULL, input, &error, &bad_message);
EXPECT_NE("", error);
EXPECT_FALSE(result.get());
// Test success
input.SetString(keys::kInstanceTypeKey, keys::kCancelRequestType);
error.clear();
result = WebRequestAction::Create(NULL, input, &error, &bad_message);
EXPECT_EQ("", error);
EXPECT_FALSE(bad_message);
ASSERT_TRUE(result.get());
EXPECT_EQ(WebRequestAction::ACTION_CANCEL_REQUEST, result->type());
}
TEST(WebRequestActionTest, CreateActionSet) {
std::string error;
bool bad_message = false;
scoped_ptr<WebRequestActionSet> result;
WebRequestActionSet::AnyVector input;
// Test empty input.
error.clear();
result = WebRequestActionSet::Create(NULL, input, &error, &bad_message);
EXPECT_TRUE(error.empty()) << error;
EXPECT_FALSE(bad_message);
ASSERT_TRUE(result.get());
EXPECT_TRUE(result->actions().empty());
EXPECT_EQ(std::numeric_limits<int>::min(), result->GetMinimumPriority());
DictionaryValue correct_action;
correct_action.SetString(keys::kInstanceTypeKey, keys::kIgnoreRulesType);
correct_action.SetInteger(keys::kLowerPriorityThanKey, 10);
DictionaryValue incorrect_action;
incorrect_action.SetString(keys::kInstanceTypeKey, kUnknownActionType);
// Test success.
input.push_back(linked_ptr<base::Value>(correct_action.DeepCopy()));
error.clear();
result = WebRequestActionSet::Create(NULL, input, &error, &bad_message);
EXPECT_TRUE(error.empty()) << error;
EXPECT_FALSE(bad_message);
ASSERT_TRUE(result.get());
ASSERT_EQ(1u, result->actions().size());
EXPECT_EQ(WebRequestAction::ACTION_IGNORE_RULES,
result->actions()[0]->type());
EXPECT_EQ(10, result->GetMinimumPriority());
// Test failure.
input.push_back(linked_ptr<base::Value>(incorrect_action.DeepCopy()));
error.clear();
result = WebRequestActionSet::Create(NULL, input, &error, &bad_message);
EXPECT_NE("", error);
EXPECT_FALSE(result.get());
}
// Test capture group syntax conversions of WebRequestRedirectByRegExAction
TEST(WebRequestActionTest, PerlToRe2Style) {
#define CallPerlToRe2Style WebRequestRedirectByRegExAction::PerlToRe2Style
// foo$1bar -> foo\1bar
EXPECT_EQ("foo\\1bar", CallPerlToRe2Style("foo$1bar"));
// foo\$1bar -> foo$1bar
EXPECT_EQ("foo$1bar", CallPerlToRe2Style("foo\\$1bar"));
// foo\\$1bar -> foo\\\1bar
EXPECT_EQ("foo\\\\\\1bar", CallPerlToRe2Style("foo\\\\$1bar"));
// foo\bar -> foobar
EXPECT_EQ("foobar", CallPerlToRe2Style("foo\\bar"));
// foo$bar -> foo$bar
EXPECT_EQ("foo$bar", CallPerlToRe2Style("foo$bar"));
#undef CallPerlToRe2Style
}
TEST_F(WebRequestActionWithThreadsTest, PermissionsToRedirect) {
const char kAction[] =
"[{"
" \"instanceType\": \"declarativeWebRequest.RedirectRequest\","
" \"redirectUrl\": \"http://www.foobar.com\""
"}]";
CheckActionNeedsAllUrls(kAction, ON_BEFORE_REQUEST);
}
TEST_F(WebRequestActionWithThreadsTest, PermissionsToRedirectByRegEx) {
const char kAction[] =
"[{"
" \"instanceType\": \"declarativeWebRequest.RedirectByRegEx\","
" \"from\": \".*\","
" \"to\": \"http://www.foobar.com\""
"}]";
CheckActionNeedsAllUrls(kAction, ON_BEFORE_REQUEST);
}
TEST_F(WebRequestActionWithThreadsTest, PermissionsToSetRequestHeader) {
const char kAction[] =
"[{"
" \"instanceType\": \"declarativeWebRequest.SetRequestHeader\","
" \"name\": \"testname\","
" \"value\": \"testvalue\""
"}]";
CheckActionNeedsAllUrls(kAction, ON_BEFORE_SEND_HEADERS);
}
TEST_F(WebRequestActionWithThreadsTest, PermissionsToRemoveRequestHeader) {
const char kAction[] =
"[{"
" \"instanceType\": \"declarativeWebRequest.RemoveRequestHeader\","
" \"name\": \"testname\""
"}]";
CheckActionNeedsAllUrls(kAction, ON_BEFORE_SEND_HEADERS);
}
TEST_F(WebRequestActionWithThreadsTest, PermissionsToAddResponseHeader) {
const char kAction[] =
"[{"
" \"instanceType\": \"declarativeWebRequest.AddResponseHeader\","
" \"name\": \"testname\","
" \"value\": \"testvalue\""
"}]";
CheckActionNeedsAllUrls(kAction, ON_HEADERS_RECEIVED);
}
TEST_F(WebRequestActionWithThreadsTest, PermissionsToRemoveResponseHeader) {
const char kAction[] =
"[{"
" \"instanceType\": \"declarativeWebRequest.RemoveResponseHeader\","
" \"name\": \"testname\""
"}]";
CheckActionNeedsAllUrls(kAction, ON_HEADERS_RECEIVED);
}
TEST_F(WebRequestActionWithThreadsTest, PermissionsToSendMessageToExtension) {
const char kAction[] =
"[{"
" \"instanceType\": \"declarativeWebRequest.SendMessageToExtension\","
" \"message\": \"testtext\""
"}]";
scoped_ptr<WebRequestActionSet> action_set(CreateSetOfActions(kAction));
// For sending messages, specific host permissions actually matter.
EXPECT_TRUE(ActionWorksOnRequest("http://test.com",
extension_->id(),
action_set.get(),
ON_BEFORE_REQUEST));
// With the "<all_urls>" host permission they are allowed.
EXPECT_TRUE(ActionWorksOnRequest("http://test.com",
extension_all_urls_->id(),
action_set.get(),
ON_BEFORE_REQUEST));
// The protected URLs should not be touched at all.
EXPECT_FALSE(ActionWorksOnRequest("http://clients1.google.com",
extension_->id(),
action_set.get(),
ON_BEFORE_REQUEST));
EXPECT_FALSE(ActionWorksOnRequest("http://clients1.google.com",
extension_all_urls_->id(),
action_set.get(),
ON_BEFORE_REQUEST));
}
TEST_F(WebRequestActionWithThreadsTest, PermissionsToAddRequestCookie) {
const char kAction[] =
"[{"
" \"instanceType\": \"declarativeWebRequest.AddRequestCookie\","
" \"cookie\": { \"name\": \"cookiename\", \"value\": \"cookievalue\" }"
"}]";
CheckActionNeedsAllUrls(kAction, ON_BEFORE_SEND_HEADERS);
}
TEST_F(WebRequestActionWithThreadsTest, PermissionsToAddResponseCookie) {
const char kAction[] =
"[{"
" \"instanceType\": \"declarativeWebRequest.AddResponseCookie\","
" \"cookie\": { \"name\": \"cookiename\", \"value\": \"cookievalue\" }"
"}]";
CheckActionNeedsAllUrls(kAction, ON_HEADERS_RECEIVED);
}
TEST_F(WebRequestActionWithThreadsTest, PermissionsToEditRequestCookie) {
const char kAction[] =
"[{"
" \"instanceType\": \"declarativeWebRequest.EditRequestCookie\","
" \"filter\": { \"name\": \"cookiename\", \"value\": \"cookievalue\" },"
" \"modification\": { \"name\": \"name2\", \"value\": \"value2\" }"
"}]";
CheckActionNeedsAllUrls(kAction, ON_BEFORE_SEND_HEADERS);
}
TEST_F(WebRequestActionWithThreadsTest, PermissionsToEditResponseCookie) {
const char kAction[] =
"[{"
" \"instanceType\": \"declarativeWebRequest.EditResponseCookie\","
" \"filter\": { \"name\": \"cookiename\", \"value\": \"cookievalue\" },"
" \"modification\": { \"name\": \"name2\", \"value\": \"value2\" }"
"}]";
CheckActionNeedsAllUrls(kAction, ON_HEADERS_RECEIVED);
}
TEST_F(WebRequestActionWithThreadsTest, PermissionsToRemoveRequestCookie) {
const char kAction[] =
"[{"
" \"instanceType\": \"declarativeWebRequest.RemoveRequestCookie\","
" \"filter\": { \"name\": \"cookiename\", \"value\": \"cookievalue\" }"
"}]";
CheckActionNeedsAllUrls(kAction, ON_BEFORE_SEND_HEADERS);
}
TEST_F(WebRequestActionWithThreadsTest, PermissionsToRemoveResponseCookie) {
const char kAction[] =
"[{"
" \"instanceType\": \"declarativeWebRequest.RemoveResponseCookie\","
" \"filter\": { \"name\": \"cookiename\", \"value\": \"cookievalue\" }"
"}]";
CheckActionNeedsAllUrls(kAction, ON_HEADERS_RECEIVED);
}
TEST_F(WebRequestActionWithThreadsTest, PermissionsToCancel) {
const char kAction[] =
"[{"
" \"instanceType\": \"declarativeWebRequest.CancelRequest\""
"}]";
scoped_ptr<WebRequestActionSet> action_set(CreateSetOfActions(kAction));
// Cancelling requests works without full host permissions.
EXPECT_TRUE(ActionWorksOnRequest("http://test.org",
extension_->id(),
action_set.get(),
ON_BEFORE_REQUEST));
}
TEST_F(WebRequestActionWithThreadsTest,
PermissionsToRedirectToTransparentImage) {
const char kAction[] =
"[{"
" \"instanceType\": \"declarativeWebRequest.RedirectToTransparentImage\""
"}]";
scoped_ptr<WebRequestActionSet> action_set(CreateSetOfActions(kAction));
// Redirecting to transparent images works without full host permissions.
EXPECT_TRUE(ActionWorksOnRequest("http://test.org",
extension_->id(),
action_set.get(),
ON_BEFORE_REQUEST));
}
TEST_F(WebRequestActionWithThreadsTest, PermissionsToRedirectToEmptyDocument) {
const char kAction[] =
"[{"
" \"instanceType\": \"declarativeWebRequest.RedirectToEmptyDocument\""
"}]";
scoped_ptr<WebRequestActionSet> action_set(CreateSetOfActions(kAction));
// Redirecting to the empty document works without full host permissions.
EXPECT_TRUE(ActionWorksOnRequest("http://test.org",
extension_->id(),
action_set.get(),
ON_BEFORE_REQUEST));
}
TEST_F(WebRequestActionWithThreadsTest, PermissionsToIgnore) {
const char kAction[] =
"[{"
" \"instanceType\": \"declarativeWebRequest.IgnoreRules\","
" \"lowerPriorityThan\": 123,"
" \"hasTag\": \"some_tag\""
"}]";
scoped_ptr<WebRequestActionSet> action_set(CreateSetOfActions(kAction));
// Ignoring rules works without full host permissions.
EXPECT_TRUE(ActionWorksOnRequest("http://test.org",
extension_->id(),
action_set.get(),
ON_BEFORE_REQUEST));
}
TEST(WebRequestActionTest, GetName) {
const char kActions[] =
"[{"
" \"instanceType\": \"declarativeWebRequest.RedirectRequest\","
" \"redirectUrl\": \"http://www.foobar.com\""
"},"
"{"
" \"instanceType\": \"declarativeWebRequest.RedirectByRegEx\","
" \"from\": \".*\","
" \"to\": \"http://www.foobar.com\""
"},"
"{"
" \"instanceType\": \"declarativeWebRequest.SetRequestHeader\","
" \"name\": \"testname\","
" \"value\": \"testvalue\""
"},"
"{"
" \"instanceType\": \"declarativeWebRequest.RemoveRequestHeader\","
" \"name\": \"testname\""
"},"
"{"
" \"instanceType\": \"declarativeWebRequest.AddResponseHeader\","
" \"name\": \"testname\","
" \"value\": \"testvalue\""
"},"
"{"
" \"instanceType\": \"declarativeWebRequest.RemoveResponseHeader\","
" \"name\": \"testname\""
"},"
"{"
" \"instanceType\": \"declarativeWebRequest.SendMessageToExtension\","
" \"message\": \"testtext\""
"},"
"{"
" \"instanceType\": \"declarativeWebRequest.AddRequestCookie\","
" \"cookie\": { \"name\": \"cookiename\", \"value\": \"cookievalue\" }"
"},"
"{"
" \"instanceType\": \"declarativeWebRequest.AddResponseCookie\","
" \"cookie\": { \"name\": \"cookiename\", \"value\": \"cookievalue\" }"
"},"
"{"
" \"instanceType\": \"declarativeWebRequest.EditRequestCookie\","
" \"filter\": { \"name\": \"cookiename\", \"value\": \"cookievalue\" },"
" \"modification\": { \"name\": \"name2\", \"value\": \"value2\" }"
"},"
"{"
" \"instanceType\": \"declarativeWebRequest.EditResponseCookie\","
" \"filter\": { \"name\": \"cookiename\", \"value\": \"cookievalue\" },"
" \"modification\": { \"name\": \"name2\", \"value\": \"value2\" }"
"},"
"{"
" \"instanceType\": \"declarativeWebRequest.RemoveRequestCookie\","
" \"filter\": { \"name\": \"cookiename\", \"value\": \"cookievalue\" }"
"},"
"{"
" \"instanceType\": \"declarativeWebRequest.RemoveResponseCookie\","
" \"filter\": { \"name\": \"cookiename\", \"value\": \"cookievalue\" }"
"},"
"{"
" \"instanceType\": \"declarativeWebRequest.CancelRequest\""
"},"
"{"
" \"instanceType\": \"declarativeWebRequest.RedirectToTransparentImage\""
"},"
"{"
" \"instanceType\": \"declarativeWebRequest.RedirectToEmptyDocument\""
"},"
"{"
" \"instanceType\": \"declarativeWebRequest.IgnoreRules\","
" \"lowerPriorityThan\": 123,"
" \"hasTag\": \"some_tag\""
"}]";
const char* kExpectedNames[] = {
"declarativeWebRequest.RedirectRequest",
"declarativeWebRequest.RedirectByRegEx",
"declarativeWebRequest.SetRequestHeader",
"declarativeWebRequest.RemoveRequestHeader",
"declarativeWebRequest.AddResponseHeader",
"declarativeWebRequest.RemoveResponseHeader",
"declarativeWebRequest.SendMessageToExtension",
"declarativeWebRequest.AddRequestCookie",
"declarativeWebRequest.AddResponseCookie",
"declarativeWebRequest.EditRequestCookie",
"declarativeWebRequest.EditResponseCookie",
"declarativeWebRequest.RemoveRequestCookie",
"declarativeWebRequest.RemoveResponseCookie",
"declarativeWebRequest.CancelRequest",
"declarativeWebRequest.RedirectToTransparentImage",
"declarativeWebRequest.RedirectToEmptyDocument",
"declarativeWebRequest.IgnoreRules",
};
scoped_ptr<WebRequestActionSet> action_set(CreateSetOfActions(kActions));
ASSERT_EQ(arraysize(kExpectedNames), action_set->actions().size());
size_t index = 0;
for (WebRequestActionSet::Actions::const_iterator it =
action_set->actions().begin();
it != action_set->actions().end();
++it) {
EXPECT_EQ(kExpectedNames[index], (*it)->GetName());
++index;
}
}
} // namespace extensions