diff options
Diffstat (limited to 'chromium/chrome/renderer/extensions/extension_hooks_delegate_unittest.cc')
-rw-r--r-- | chromium/chrome/renderer/extensions/extension_hooks_delegate_unittest.cc | 266 |
1 files changed, 266 insertions, 0 deletions
diff --git a/chromium/chrome/renderer/extensions/extension_hooks_delegate_unittest.cc b/chromium/chrome/renderer/extensions/extension_hooks_delegate_unittest.cc new file mode 100644 index 00000000000..613dfb92d9e --- /dev/null +++ b/chromium/chrome/renderer/extensions/extension_hooks_delegate_unittest.cc @@ -0,0 +1,266 @@ +// Copyright 2017 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/renderer/extensions/extension_hooks_delegate.h" + +#include "base/strings/stringprintf.h" +#include "content/public/common/child_process_host.h" +#include "extensions/common/api/messaging/messaging_endpoint.h" +#include "extensions/common/extension_builder.h" +#include "extensions/common/extension_messages.h" +#include "extensions/common/value_builder.h" +#include "extensions/renderer/bindings/api_binding_test_util.h" +#include "extensions/renderer/message_target.h" +#include "extensions/renderer/messaging_util.h" +#include "extensions/renderer/native_extension_bindings_system.h" +#include "extensions/renderer/native_extension_bindings_system_test_base.h" +#include "extensions/renderer/native_renderer_messaging_service.h" +#include "extensions/renderer/runtime_hooks_delegate.h" +#include "extensions/renderer/script_context.h" +#include "extensions/renderer/script_context_set.h" +#include "extensions/renderer/send_message_tester.h" + +namespace extensions { + +class ExtensionHooksDelegateTest + : public NativeExtensionBindingsSystemUnittest { + public: + ExtensionHooksDelegateTest() {} + ~ExtensionHooksDelegateTest() override {} + + // NativeExtensionBindingsSystemUnittest: + void SetUp() override { + NativeExtensionBindingsSystemUnittest::SetUp(); + messaging_service_ = + std::make_unique<NativeRendererMessagingService>(bindings_system()); + + bindings_system() + ->api_system() + ->GetHooksForAPI("extension") + ->SetDelegate( + std::make_unique<ExtensionHooksDelegate>(messaging_service_.get())); + bindings_system()->api_system()->GetHooksForAPI("runtime")->SetDelegate( + std::make_unique<RuntimeHooksDelegate>(messaging_service_.get())); + + scoped_refptr<const Extension> mutable_extension = BuildExtension(); + RegisterExtension(mutable_extension); + extension_ = mutable_extension; + + v8::HandleScope handle_scope(isolate()); + v8::Local<v8::Context> context = MainContext(); + + script_context_ = CreateScriptContext(context, mutable_extension.get(), + Feature::BLESSED_EXTENSION_CONTEXT); + script_context_->set_url(extension_->url()); + bindings_system()->UpdateBindingsForContext(script_context_); + } + void TearDown() override { + script_context_ = nullptr; + extension_ = nullptr; + messaging_service_.reset(); + NativeExtensionBindingsSystemUnittest::TearDown(); + } + bool UseStrictIPCMessageSender() override { return true; } + + virtual scoped_refptr<const Extension> BuildExtension() { + return ExtensionBuilder("foo").Build(); + } + + NativeRendererMessagingService* messaging_service() { + return messaging_service_.get(); + } + ScriptContext* script_context() { return script_context_; } + const Extension* extension() { return extension_.get(); } + + private: + std::unique_ptr<NativeRendererMessagingService> messaging_service_; + + ScriptContext* script_context_ = nullptr; + scoped_refptr<const Extension> extension_; + + DISALLOW_COPY_AND_ASSIGN(ExtensionHooksDelegateTest); +}; + +// Test chrome.extension messaging methods. Many of these are just aliased to +// chrome.runtime counterparts, but some others (like sendRequest) are +// implemented as hooks. +TEST_F(ExtensionHooksDelegateTest, MessagingSanityChecks) { + v8::HandleScope handle_scope(isolate()); + + MessageTarget self_target = MessageTarget::ForExtension(extension()->id()); + SendMessageTester tester(ipc_message_sender(), script_context(), 0, + "extension"); + + const bool kExpectIncludeTlsChannelId = false; + tester.TestConnect("", "", self_target, kExpectIncludeTlsChannelId); + + constexpr char kStandardMessage[] = R"({"data":"hello"})"; + tester.TestSendMessage("{data: 'hello'}", kStandardMessage, self_target, + false, SendMessageTester::CLOSED); + tester.TestSendMessage("{data: 'hello'}, function() {}", kStandardMessage, + self_target, false, SendMessageTester::OPEN); + + tester.TestSendRequest("{data: 'hello'}", kStandardMessage, self_target, + SendMessageTester::CLOSED); + tester.TestSendRequest("{data: 'hello'}, function() {}", kStandardMessage, + self_target, SendMessageTester::OPEN); + + // Ambiguous argument case ('hi' could be an id or a message); we massage it + // into being the message because that's a required argument. + tester.TestSendRequest("'hi', function() {}", "\"hi\"", self_target, + SendMessageTester::OPEN); +} + +TEST_F(ExtensionHooksDelegateTest, SendRequestDisabled) { + // Construct an extension for which sendRequest is disabled (unpacked + // extension with an event page). + scoped_refptr<const Extension> extension = + ExtensionBuilder("foo") + .SetBackgroundContext(ExtensionBuilder::BackgroundContext::EVENT_PAGE) + .SetLocation(Manifest::UNPACKED) + .Build(); + RegisterExtension(extension); + + v8::HandleScope handle_scope(isolate()); + v8::Local<v8::Context> context = AddContext(); + ScriptContext* script_context = CreateScriptContext( + context, extension.get(), Feature::BLESSED_EXTENSION_CONTEXT); + script_context->set_url(extension->url()); + bindings_system()->UpdateBindingsForContext(script_context); + ASSERT_TRUE(messaging_util::IsSendRequestDisabled(script_context)); + + enum AccessBehavior { + THROWS, + DOESNT_THROW, + }; + + auto check_access = [context](const char* object, AccessBehavior behavior) { + SCOPED_TRACE(object); + constexpr char kExpectedError[] = + "Uncaught Error: extension.sendRequest, extension.onRequest, and " + "extension.onRequestExternal are deprecated. Please use " + "runtime.sendMessage, runtime.onMessage, and runtime.onMessageExternal " + "instead."; + v8::Local<v8::Function> function = FunctionFromString( + context, base::StringPrintf("(function() {%s;})", object)); + if (behavior == THROWS) + RunFunctionAndExpectError(function, context, 0, nullptr, kExpectedError); + else + RunFunction(function, context, 0, nullptr); + }; + + check_access("chrome.extension.sendRequest", THROWS); + check_access("chrome.extension.onRequest", THROWS); + check_access("chrome.extension.onRequestExternal", THROWS); + check_access("chrome.extension.sendMessage", DOESNT_THROW); + check_access("chrome.extension.onMessage", DOESNT_THROW); + check_access("chrome.extension.onMessageExternal", DOESNT_THROW); +} + +// Ensure that the extension.sendRequest() method doesn't close the channel if +// the listener does not reply and also does not return `true` (unlike the +// runtime.sendMessage() method, which will). +TEST_F(ExtensionHooksDelegateTest, SendRequestChannelLeftOpenToReplyAsync) { + v8::HandleScope handle_scope(isolate()); + v8::Local<v8::Context> context = MainContext(); + + constexpr char kRegisterListener[] = + "(function() {\n" + " chrome.extension.onRequest.addListener(\n" + " function(message, sender, reply) {});\n" + "})"; + v8::Local<v8::Function> add_listener = + FunctionFromString(context, kRegisterListener); + RunFunctionOnGlobal(add_listener, context, 0, nullptr); + + const std::string kChannel = "chrome.extension.sendRequest"; + base::UnguessableToken other_context_id = base::UnguessableToken::Create(); + const PortId port_id(other_context_id, 0, false); + + ExtensionMsg_TabConnectionInfo tab_connection_info; + tab_connection_info.frame_id = 0; + const int tab_id = 10; + GURL source_url("http://example.com"); + tab_connection_info.tab.Swap( + DictionaryBuilder().Set("tabId", tab_id).Build().get()); + ExtensionMsg_ExternalConnectionInfo external_connection_info; + external_connection_info.target_id = extension()->id(); + external_connection_info.source_endpoint = + MessagingEndpoint::ForExtension(extension()->id()); + external_connection_info.source_url = source_url; + external_connection_info.guest_process_id = + content::ChildProcessHost::kInvalidUniqueID; + external_connection_info.guest_render_frame_routing_id = 0; + + // Open a receiver for the message. + EXPECT_CALL(*ipc_message_sender(), + SendOpenMessagePort(MSG_ROUTING_NONE, port_id)); + messaging_service()->DispatchOnConnect(script_context_set(), port_id, + kChannel, tab_connection_info, + external_connection_info, nullptr); + ::testing::Mock::VerifyAndClearExpectations(ipc_message_sender()); + EXPECT_TRUE( + messaging_service()->HasPortForTesting(script_context(), port_id)); + + // Post the message to the receiver. Since the receiver doesn't respond, the + // channel should remain open. + messaging_service()->DeliverMessage(script_context_set(), port_id, + Message("\"message\"", false), nullptr); + ::testing::Mock::VerifyAndClearExpectations(ipc_message_sender()); + EXPECT_TRUE( + messaging_service()->HasPortForTesting(script_context(), port_id)); +} + +// Tests that overriding the runtime equivalents of chrome.extension methods +// with accessors that throw does not cause a crash on access. Regression test +// for https://crbug.com/949170. +TEST_F(ExtensionHooksDelegateTest, RuntimeAliasesCorrupted) { + v8::HandleScope handle_scope(isolate()); + v8::Local<v8::Context> context = MainContext(); + + // Set a trap on chrome.runtime.sendMessage. + constexpr char kMutateChromeRuntime[] = + R"((function() { + Object.defineProperty( + chrome.runtime, 'sendMessage', + { get() { throw new Error('haha'); } }); + }))"; + RunFunctionOnGlobal(FunctionFromString(context, kMutateChromeRuntime), + context, 0, nullptr); + + // Touch chrome.extension.sendMessage, which is aliased to the runtime + // version. Though an error is thrown, we shouldn't crash. + constexpr char kTouchExtensionSendMessage[] = + "(function() { chrome.extension.sendMessage; })"; + RunFunctionOnGlobal(FunctionFromString(context, kTouchExtensionSendMessage), + context, 0, nullptr); +} + +// Ensure that HandleGetURL allows extension URLs and doesn't allow arbitrary +// non-extension URLs. Very similar to RuntimeHooksDeligateTest that tests a +// similar function. +TEST_F(ExtensionHooksDelegateTest, GetURL) { + v8::HandleScope handle_scope(isolate()); + v8::Local<v8::Context> context = MainContext(); + + auto get_url = [this, context](const char* args, const GURL& expected_url) { + SCOPED_TRACE(base::StringPrintf("Args: `%s`", args)); + constexpr char kGetUrlTemplate[] = + "(function() { return chrome.extension.getURL(%s); })"; + v8::Local<v8::Function> get_url = + FunctionFromString(context, base::StringPrintf(kGetUrlTemplate, args)); + v8::Local<v8::Value> url = RunFunction(get_url, context, 0, nullptr); + ASSERT_FALSE(url.IsEmpty()); + ASSERT_TRUE(url->IsString()); + EXPECT_EQ(expected_url.spec(), gin::V8ToString(isolate(), url)); + }; + + get_url("''", extension()->url()); + get_url("'foo'", extension()->GetResourceURL("foo")); + get_url("'/foo'", extension()->GetResourceURL("foo")); + get_url("'https://www.google.com'", + GURL(extension()->url().spec() + "https://www.google.com")); +} + +} // namespace extensions |