blob: 8226d0b80960a9093275a0b332f1bb0e4b35b3ff [file] [log] [blame]
// Copyright 2016 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 "content/browser/service_worker/foreign_fetch_request_handler.h"
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "base/test/simple_test_tick_clock.h"
#include "content/browser/browser_thread_impl.h"
#include "content/browser/service_worker/embedded_worker_test_helper.h"
#include "content/browser/service_worker/service_worker_context_core.h"
#include "content/browser/service_worker/service_worker_registration.h"
#include "content/browser/service_worker/service_worker_test_utils.h"
#include "content/browser/service_worker/service_worker_version.h"
#include "content/common/service_worker/service_worker_utils.h"
#include "content/public/common/content_client.h"
#include "content/public/common/origin_trial_policy.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "content/test/test_content_browser_client.h"
#include "net/http/http_response_info.h"
#include "net/test/cert_test_util.h"
#include "net/test/test_data_directory.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_test_util.h"
#include "storage/browser/blob/blob_storage_context.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace content {
namespace {
// This is a sample public key for testing the API. The corresponding private
// key (use this to generate new samples for this test file) is:
//
// 0x83, 0x67, 0xf4, 0xcd, 0x2a, 0x1f, 0x0e, 0x04, 0x0d, 0x43, 0x13,
// 0x4c, 0x67, 0xc4, 0xf4, 0x28, 0xc9, 0x90, 0x15, 0x02, 0xe2, 0xba,
// 0xfd, 0xbb, 0xfa, 0xbc, 0x92, 0x76, 0x8a, 0x2c, 0x4b, 0xc7, 0x75,
// 0x10, 0xac, 0xf9, 0x3a, 0x1c, 0xb8, 0xa9, 0x28, 0x70, 0xd2, 0x9a,
// 0xd0, 0x0b, 0x59, 0xe1, 0xac, 0x2b, 0xb7, 0xd5, 0xca, 0x1f, 0x64,
// 0x90, 0x08, 0x8e, 0xa8, 0xe0, 0x56, 0x3a, 0x04, 0xd0
const uint8_t kTestPublicKey[] = {
0x75, 0x10, 0xac, 0xf9, 0x3a, 0x1c, 0xb8, 0xa9, 0x28, 0x70, 0xd2,
0x9a, 0xd0, 0x0b, 0x59, 0xe1, 0xac, 0x2b, 0xb7, 0xd5, 0xca, 0x1f,
0x64, 0x90, 0x08, 0x8e, 0xa8, 0xe0, 0x56, 0x3a, 0x04, 0xd0,
};
int kMockProviderId = 1;
const char* kValidUrl = "https://valid.example.com/foo/bar";
void EmptyCallback() {}
} // namespace
class ForeignFetchRequestHandlerTest : public testing::Test {
public:
ForeignFetchRequestHandlerTest()
: browser_thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP) {
SetContentClient(&test_content_client_);
SetBrowserClientForTesting(&test_content_browser_client_);
}
~ForeignFetchRequestHandlerTest() override {}
void SetUp() override {
const GURL kScope("https://valid.example.com/scope/");
const GURL kResource1("https://valid.example.com/scope/sw.js");
const int64_t kRegistrationId = 0;
const int64_t kVersionId = 0;
helper_.reset(new EmbeddedWorkerTestHelper(base::FilePath()));
// Create a registration for the worker which has foreign fetch event
// handler.
registration_ =
new ServiceWorkerRegistration(ServiceWorkerRegistrationOptions(kScope),
kRegistrationId, context()->AsWeakPtr());
version_ = new ServiceWorkerVersion(registration_.get(), kResource1,
kVersionId, context()->AsWeakPtr());
version_->set_foreign_fetch_scopes({kScope});
context()->storage()->LazyInitialize(base::Bind(&EmptyCallback));
base::RunLoop().RunUntilIdle();
// Persist the registration data.
std::vector<ServiceWorkerDatabase::ResourceRecord> records;
records.push_back(
ServiceWorkerDatabase::ResourceRecord(10, version_->script_url(), 100));
version_->script_cache_map()->SetResources(records);
version_->set_fetch_handler_existence(
ServiceWorkerVersion::FetchHandlerExistence::EXISTS);
version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
registration_->SetActiveVersion(version_);
context()->storage()->StoreRegistration(
registration_.get(), version_.get(),
base::Bind(&ServiceWorkerUtils::NoOpStatusCallback));
base::RunLoop().RunUntilIdle();
}
void TearDown() override {
version_ = nullptr;
registration_ = nullptr;
helper_.reset();
base::RunLoop().RunUntilIdle();
}
protected:
ServiceWorkerContextCore* context() const { return helper_->context(); }
ServiceWorkerContextWrapper* context_wrapper() const {
return helper_->context_wrapper();
}
ServiceWorkerProviderHost* provider_host() const {
return provider_host_.get();
}
bool CheckOriginTrialToken(const ServiceWorkerVersion* const version) const {
return ForeignFetchRequestHandler::CheckOriginTrialToken(version);
}
base::Optional<base::TimeDelta> timeout_for_request(
ForeignFetchRequestHandler* handler) {
return handler->timeout_;
}
ServiceWorkerVersion* version() const { return version_.get(); }
static std::unique_ptr<net::HttpResponseInfo> CreateTestHttpResponseInfo() {
std::unique_ptr<net::HttpResponseInfo> http_info(
base::MakeUnique<net::HttpResponseInfo>());
http_info->ssl_info.cert =
net::ImportCertFromFile(net::GetTestCertsDirectory(), "ok_cert.pem");
DCHECK(http_info->ssl_info.is_valid());
http_info->ssl_info.security_bits = 0x100;
// SSL3 TLS_DHE_RSA_WITH_AES_256_CBC_SHA
http_info->ssl_info.connection_status = 0x300039;
http_info->headers = make_scoped_refptr(new net::HttpResponseHeaders(""));
return http_info;
}
ForeignFetchRequestHandler* InitializeHandler(const std::string& url,
ResourceType resource_type,
const char* initiator) {
request_ = url_request_context_.CreateRequest(
GURL(url), net::DEFAULT_PRIORITY, &url_request_delegate_,
TRAFFIC_ANNOTATION_FOR_TESTS);
if (initiator)
request_->set_initiator(url::Origin(GURL(initiator)));
ForeignFetchRequestHandler::InitializeHandler(
request_.get(), context_wrapper(), &blob_storage_context_,
helper_->mock_render_process_id(), provider_host()->provider_id(),
ServiceWorkerMode::ALL, FETCH_REQUEST_MODE_CORS,
FETCH_CREDENTIALS_MODE_OMIT, FetchRedirectMode::FOLLOW_MODE,
resource_type, REQUEST_CONTEXT_TYPE_FETCH,
REQUEST_CONTEXT_FRAME_TYPE_NONE, nullptr,
true /* initiated_in_secure_context */);
return ForeignFetchRequestHandler::GetHandler(request_.get());
}
void CreateWindowTypeProviderHost() {
remote_endpoints_.emplace_back();
std::unique_ptr<ServiceWorkerProviderHost> host =
CreateProviderHostForWindow(
helper_->mock_render_process_id(), kMockProviderId,
true /* is_parent_frame_secure */, helper_->context()->AsWeakPtr(),
&remote_endpoints_.back());
EXPECT_FALSE(
context()->GetProviderHost(host->process_id(), host->provider_id()));
host->SetDocumentUrl(GURL("https://host/scope/"));
provider_host_ = host->AsWeakPtr();
context()->AddProviderHost(std::move(host));
}
void CreateServiceWorkerTypeProviderHost() {
remote_endpoints_.emplace_back();
std::unique_ptr<ServiceWorkerProviderHost> host =
CreateProviderHostForServiceWorkerContext(
helper_->mock_render_process_id(), kMockProviderId,
true /* is_parent_frame_secure */, helper_->context()->AsWeakPtr(),
&remote_endpoints_.back());
EXPECT_FALSE(
context()->GetProviderHost(host->process_id(), host->provider_id()));
provider_host_ = host->AsWeakPtr();
context()->AddProviderHost(std::move(host));
// Create another worker whose requests will be intercepted by the foreign
// fetch event handler.
scoped_refptr<ServiceWorkerRegistration> registration =
new ServiceWorkerRegistration(
ServiceWorkerRegistrationOptions(GURL("https://host/scope")), 1L,
context()->AsWeakPtr());
scoped_refptr<ServiceWorkerVersion> version = new ServiceWorkerVersion(
registration.get(), GURL("https://host/script.js"), 1L,
context()->AsWeakPtr());
std::vector<ServiceWorkerDatabase::ResourceRecord> records;
records.push_back(
ServiceWorkerDatabase::ResourceRecord(10, version->script_url(), 100));
version->script_cache_map()->SetResources(records);
version->set_fetch_handler_existence(
ServiceWorkerVersion::FetchHandlerExistence::EXISTS);
version->SetStatus(ServiceWorkerVersion::ACTIVATED);
registration->SetActiveVersion(version);
context()->storage()->StoreRegistration(
registration.get(), version.get(),
base::Bind(&ServiceWorkerUtils::NoOpStatusCallback));
base::RunLoop().RunUntilIdle();
provider_host_->running_hosted_version_ = version;
}
private:
class TestContentClient : public ContentClient {
public:
// ContentRendererClient methods
OriginTrialPolicy* GetOriginTrialPolicy() override {
return &origin_trial_policy_;
}
private:
class TestOriginTrialPolicy : public OriginTrialPolicy {
public:
base::StringPiece GetPublicKey() const override {
return base::StringPiece(reinterpret_cast<const char*>(kTestPublicKey),
arraysize(kTestPublicKey));
}
};
TestOriginTrialPolicy origin_trial_policy_;
};
scoped_refptr<ServiceWorkerRegistration> registration_;
scoped_refptr<ServiceWorkerVersion> version_;
TestContentClient test_content_client_;
TestContentBrowserClient test_content_browser_client_;
std::unique_ptr<EmbeddedWorkerTestHelper> helper_;
TestBrowserThreadBundle browser_thread_bundle_;
net::URLRequestContext url_request_context_;
net::TestDelegate url_request_delegate_;
base::WeakPtr<ServiceWorkerProviderHost> provider_host_;
storage::BlobStorageContext blob_storage_context_;
std::unique_ptr<net::URLRequest> request_;
std::vector<ServiceWorkerRemoteProviderEndpoint> remote_endpoints_;
DISALLOW_COPY_AND_ASSIGN(ForeignFetchRequestHandlerTest);
};
TEST_F(ForeignFetchRequestHandlerTest, CheckOriginTrialToken_NoToken) {
CreateWindowTypeProviderHost();
EXPECT_TRUE(CheckOriginTrialToken(version()));
std::unique_ptr<net::HttpResponseInfo> http_info(
CreateTestHttpResponseInfo());
version()->SetMainScriptHttpResponseInfo(*http_info);
EXPECT_FALSE(CheckOriginTrialToken(version()));
}
TEST_F(ForeignFetchRequestHandlerTest, CheckOriginTrialToken_ValidToken) {
CreateWindowTypeProviderHost();
EXPECT_TRUE(CheckOriginTrialToken(version()));
std::unique_ptr<net::HttpResponseInfo> http_info(
CreateTestHttpResponseInfo());
const std::string kOriginTrial("Origin-Trial: ");
// Token for ForeignFetch which expires 2033-05-18.
// generate_token.py valid.example.com ForeignFetch
// --expire-timestamp=2000000000
// TODO(horo): Generate this sample token during the build.
const std::string kFeatureToken(
"AsDmvl17hoVfq9G6OT0VEhX28Nnl0VnbGZJoG6XFzosIamNfxNJ28m40PRA3PtFv3BfOlRe1"
"5bLrEZhtICdDnwwAAABceyJvcmlnaW4iOiAiaHR0cHM6Ly92YWxpZC5leGFtcGxlLmNvbTo0"
"NDMiLCAiZmVhdHVyZSI6ICJGb3JlaWduRmV0Y2giLCAiZXhwaXJ5IjogMjAwMDAwMDAwMH0"
"=");
http_info->headers->AddHeader(kOriginTrial + kFeatureToken);
version()->SetMainScriptHttpResponseInfo(*http_info);
EXPECT_TRUE(CheckOriginTrialToken(version()));
}
TEST_F(ForeignFetchRequestHandlerTest, CheckOriginTrialToken_InvalidToken) {
CreateWindowTypeProviderHost();
EXPECT_TRUE(CheckOriginTrialToken(version()));
std::unique_ptr<net::HttpResponseInfo> http_info(
CreateTestHttpResponseInfo());
const std::string kOriginTrial("Origin-Trial: ");
// Token for FooBar which expires 2033-05-18.
// generate_token.py valid.example.com FooBar
// --expire-timestamp=2000000000
// TODO(horo): Generate this sample token during the build.
const std::string kFeatureToken(
"AqwtRpuoLdc6MKSFH8TbzoLFvLouL8VXTv6OJIqQvRtJBynRMbAbFwjUlcwMuj4pXUBbquBj"
"zj/w/d1H/ZsOcQIAAABWeyJvcmlnaW4iOiAiaHR0cHM6Ly92YWxpZC5leGFtcGxlLmNvbTo0"
"NDMiLCAiZmVhdHVyZSI6ICJGb29CYXIiLCAiZXhwaXJ5IjogMjAwMDAwMDAwMH0=");
http_info->headers->AddHeader(kOriginTrial + kFeatureToken);
version()->SetMainScriptHttpResponseInfo(*http_info);
EXPECT_FALSE(CheckOriginTrialToken(version()));
}
TEST_F(ForeignFetchRequestHandlerTest, CheckOriginTrialToken_ExpiredToken) {
CreateWindowTypeProviderHost();
EXPECT_TRUE(CheckOriginTrialToken(version()));
std::unique_ptr<net::HttpResponseInfo> http_info(
CreateTestHttpResponseInfo());
const std::string kOriginTrial("Origin-Trial: ");
// Token for ForeignFetch which expired 2001-09-09.
// generate_token.py valid.example.com ForeignFetch
// --expire-timestamp=1000000000
const std::string kFeatureToken(
"AgBgj4Zhwzn85LJw7rzh4ZFRFqp49+9Es2SrCwZdDcoqtqQEjbvui4SKLn6GqMpr4DynGfJh"
"tIy9dpOuK8PVTwkAAABceyJvcmlnaW4iOiAiaHR0cHM6Ly92YWxpZC5leGFtcGxlLmNvbTo0"
"NDMiLCAiZmVhdHVyZSI6ICJGb3JlaWduRmV0Y2giLCAiZXhwaXJ5IjogMTAwMDAwMDAwMH0"
"=");
http_info->headers->AddHeader(kOriginTrial + kFeatureToken);
version()->SetMainScriptHttpResponseInfo(*http_info);
EXPECT_FALSE(CheckOriginTrialToken(version()));
}
TEST_F(ForeignFetchRequestHandlerTest, InitializeHandler_Success) {
CreateWindowTypeProviderHost();
EXPECT_TRUE(InitializeHandler(kValidUrl, RESOURCE_TYPE_IMAGE,
nullptr /* initiator */));
}
TEST_F(ForeignFetchRequestHandlerTest, InitializeHandler_WrongResourceType) {
CreateWindowTypeProviderHost();
EXPECT_FALSE(InitializeHandler(kValidUrl, RESOURCE_TYPE_MAIN_FRAME,
nullptr /* initiator */));
EXPECT_FALSE(InitializeHandler(kValidUrl, RESOURCE_TYPE_SUB_FRAME,
nullptr /* initiator */));
EXPECT_FALSE(InitializeHandler(kValidUrl, RESOURCE_TYPE_WORKER,
nullptr /* initiator */));
EXPECT_FALSE(InitializeHandler(kValidUrl, RESOURCE_TYPE_SHARED_WORKER,
nullptr /* initiator */));
EXPECT_FALSE(InitializeHandler(kValidUrl, RESOURCE_TYPE_SERVICE_WORKER,
nullptr /* initiator */));
}
TEST_F(ForeignFetchRequestHandlerTest, InitializeHandler_SameOriginRequest) {
CreateWindowTypeProviderHost();
EXPECT_FALSE(InitializeHandler(kValidUrl, RESOURCE_TYPE_IMAGE,
kValidUrl /* initiator */));
}
TEST_F(ForeignFetchRequestHandlerTest, InitializeHandler_NoRegisteredHandlers) {
CreateWindowTypeProviderHost();
EXPECT_FALSE(InitializeHandler("https://invalid.example.com/foo",
RESOURCE_TYPE_IMAGE, nullptr /* initiator */));
}
TEST_F(ForeignFetchRequestHandlerTest,
InitializeHandler_TimeoutBehaviorForWindow) {
CreateWindowTypeProviderHost();
ForeignFetchRequestHandler* handler =
InitializeHandler("https://valid.example.com/foo", RESOURCE_TYPE_IMAGE,
nullptr /* initiator */);
ASSERT_TRUE(handler);
EXPECT_EQ(base::nullopt, timeout_for_request(handler));
}
TEST_F(ForeignFetchRequestHandlerTest,
InitializeHandler_TimeoutBehaviorForServiceWorker) {
CreateServiceWorkerTypeProviderHost();
ServiceWorkerVersion* version = provider_host()->running_hosted_version();
std::unique_ptr<net::HttpResponseInfo> http_info(
CreateTestHttpResponseInfo());
version->SetMainScriptHttpResponseInfo(*http_info);
// Set mock clock on version to check timeout behavior.
base::SimpleTestTickClock* tick_clock = new base::SimpleTestTickClock();
tick_clock->SetNowTicks(base::TimeTicks::Now());
version->SetTickClockForTesting(base::WrapUnique(tick_clock));
// Make sure worker has a non-zero timeout.
version->StartWorker(ServiceWorkerMetrics::EventType::UNKNOWN,
base::Bind(&ServiceWorkerUtils::NoOpStatusCallback));
base::RunLoop().RunUntilIdle();
version->StartRequestWithCustomTimeout(
ServiceWorkerMetrics::EventType::ACTIVATE,
base::Bind(&ServiceWorkerUtils::NoOpStatusCallback),
base::TimeDelta::FromSeconds(10), ServiceWorkerVersion::KILL_ON_TIMEOUT);
// Advance clock by a couple seconds.
tick_clock->Advance(base::TimeDelta::FromSeconds(4));
base::TimeDelta remaining_time = version->remaining_timeout();
EXPECT_EQ(base::TimeDelta::FromSeconds(6), remaining_time);
// Make sure new request only gets remaining timeout.
ForeignFetchRequestHandler* handler =
InitializeHandler("https://valid.example.com/foo", RESOURCE_TYPE_IMAGE,
nullptr /* initiator */);
ASSERT_TRUE(handler);
ASSERT_TRUE(timeout_for_request(handler).has_value());
EXPECT_EQ(remaining_time, timeout_for_request(handler).value());
}
} // namespace content