
This also deletes the out-param-based overload. Change-Id: I74b03b454bfaa99ea9b75227511801e50aa0f138 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5939082 Owners-Override: Rick Byers <rbyers@chromium.org> Commit-Queue: Chris Fredrickson <cfredric@chromium.org> Reviewed-by: Avi Drissman <avi@chromium.org> Reviewed-by: Maks Orlovich <morlovich@chromium.org> Code-Coverage: findit-for-me@appspot.gserviceaccount.com <findit-for-me@appspot.gserviceaccount.com> Reviewed-by: Rick Byers <rbyers@chromium.org> Auto-Submit: Chris Fredrickson <cfredric@chromium.org> Cr-Commit-Position: refs/heads/main@{#1370586}
1099 lines
44 KiB
C++
1099 lines
44 KiB
C++
// Copyright 2014 The Chromium Authors
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "extensions/browser/extension_protocols.h"
|
|
|
|
#include <stddef.h>
|
|
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <utility>
|
|
|
|
#include "base/command_line.h"
|
|
#include "base/files/file_util.h"
|
|
#include "base/path_service.h"
|
|
#include "base/run_loop.h"
|
|
#include "base/strings/string_number_conversions.h"
|
|
#include "base/strings/string_util.h"
|
|
#include "base/test/power_monitor_test.h"
|
|
#include "base/test/test_file_util.h"
|
|
#include "base/test/values_test_util.h"
|
|
#include "base/values.h"
|
|
#include "build/build_config.h"
|
|
#include "chrome/browser/extensions/chrome_content_verifier_delegate.h"
|
|
#include "chrome/browser/extensions/chrome_extensions_browser_client.h"
|
|
#include "chrome/browser/extensions/test_extension_system.h"
|
|
#include "chrome/common/chrome_paths.h"
|
|
#include "chrome/common/chrome_switches.h"
|
|
#include "chrome/test/base/testing_profile.h"
|
|
#include "components/crx_file/id_util.h"
|
|
#include "content/public/browser/render_process_host.h"
|
|
#include "content/public/test/browser_task_environment.h"
|
|
#include "content/public/test/test_renderer_host.h"
|
|
#include "content/public/test/test_utils.h"
|
|
#include "content/public/test/web_contents_tester.h"
|
|
#include "extensions/browser/content_verifier/content_verifier.h"
|
|
#include "extensions/browser/content_verifier/test_utils.h"
|
|
#include "extensions/browser/extension_prefs.h"
|
|
#include "extensions/browser/extension_registry.h"
|
|
#include "extensions/browser/extension_system.h"
|
|
#include "extensions/browser/unloaded_extension_reason.h"
|
|
#include "extensions/common/extension.h"
|
|
#include "extensions/common/extension_builder.h"
|
|
#include "extensions/common/extension_paths.h"
|
|
#include "extensions/common/file_util.h"
|
|
#include "extensions/test/test_extension_dir.h"
|
|
#include "mojo/public/cpp/bindings/pending_remote.h"
|
|
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
|
|
#include "services/network/public/cpp/resource_request.h"
|
|
#include "services/network/public/mojom/fetch_api.mojom.h"
|
|
#include "services/network/test/test_url_loader_client.h"
|
|
#include "testing/gmock/include/gmock/gmock.h"
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
|
#include "third_party/blink/public/common/features.h"
|
|
#include "third_party/blink/public/common/loader/referrer_utils.h"
|
|
|
|
using extensions::ExtensionRegistry;
|
|
using network::mojom::URLLoader;
|
|
using testing::_;
|
|
using testing::StrictMock;
|
|
|
|
namespace extensions {
|
|
namespace {
|
|
|
|
constexpr char kValidTrialToken1[] = "valid_token_1";
|
|
constexpr char kValidTrialToken2[] = "valid_token_2";
|
|
constexpr char kTrialTokensHeaderValue[] = "valid_token_1, valid_token_2";
|
|
|
|
base::FilePath GetTestPath(const std::string& name) {
|
|
base::FilePath path;
|
|
EXPECT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &path));
|
|
return path.AppendASCII("extensions").AppendASCII(name);
|
|
}
|
|
|
|
base::FilePath GetContentVerifierTestPath() {
|
|
base::FilePath path;
|
|
EXPECT_TRUE(base::PathService::Get(DIR_TEST_DATA, &path));
|
|
return path.AppendASCII("content_hash_fetcher")
|
|
.AppendASCII("different_sized_files");
|
|
}
|
|
|
|
scoped_refptr<const Extension> CreateTestExtension(const std::string& name,
|
|
bool incognito_split_mode,
|
|
int manifest_version) {
|
|
return ExtensionBuilder(name)
|
|
.SetManifestVersion(manifest_version)
|
|
.SetManifestKey("incognito", incognito_split_mode ? "split" : "spanning")
|
|
.SetPath(GetTestPath("response_headers"))
|
|
.SetLocation(mojom::ManifestLocation::kInternal)
|
|
.Build();
|
|
}
|
|
|
|
scoped_refptr<const Extension> CreateWebStoreExtension(int manifest_version) {
|
|
base::FilePath path;
|
|
EXPECT_TRUE(base::PathService::Get(chrome::DIR_RESOURCES, &path));
|
|
path = path.AppendASCII("web_store");
|
|
|
|
return ExtensionBuilder("WebStore")
|
|
.SetManifestVersion(manifest_version)
|
|
.SetManifestKey("icons",
|
|
base::Value::Dict().Set("16", "webstore_icon_16.png"))
|
|
.SetManifestKey(
|
|
"web_accessible_resources",
|
|
manifest_version == 3
|
|
? base::Value::List().Append(
|
|
base::Value::Dict()
|
|
.Set("resources",
|
|
base::Value::List().Append("webstore_icon_16.png"))
|
|
.Set("matches", base::Value::List().Append("*://*/*")))
|
|
: base::Value::List().Append("webstore_icon_16.png"))
|
|
.SetPath(path)
|
|
.SetLocation(mojom::ManifestLocation::kComponent)
|
|
.Build();
|
|
}
|
|
|
|
scoped_refptr<const Extension> CreateTestResponseHeaderExtension(
|
|
int manifest_version) {
|
|
if (manifest_version == 3) {
|
|
return ExtensionBuilder("An extension with web-accessible resources")
|
|
.SetManifestVersion(3)
|
|
.SetManifestKey(
|
|
"web_accessible_resources",
|
|
base::Value::List().Append(
|
|
base::Value::Dict()
|
|
.Set("resources", base::Value::List().Append("test.dat"))
|
|
.Set("matches", base::Value::List().Append("*://*/*"))))
|
|
.SetManifestKey("background", base::Value::Dict().Set("service_worker",
|
|
"background.js"))
|
|
.SetManifestKey("trial_tokens", base::Value::List()
|
|
.Append(kValidTrialToken1)
|
|
.Append(kValidTrialToken2))
|
|
.SetPath(GetTestPath("response_headers"))
|
|
.Build();
|
|
}
|
|
return ExtensionBuilder("An extension with web-accessible resources")
|
|
.SetManifestVersion(manifest_version)
|
|
.SetManifestKey("web_accessible_resources",
|
|
base::Value::List().Append("test.dat"))
|
|
.SetManifestKey(
|
|
"background",
|
|
base::Value::Dict().Set("scripts",
|
|
base::Value::List().Append("background.js")))
|
|
.SetPath(GetTestPath("response_headers"))
|
|
.Build();
|
|
}
|
|
|
|
scoped_refptr<const Extension> CreateTestModuleResponseHeaderExtension(
|
|
int manifest_version) {
|
|
return ExtensionBuilder("A module extension")
|
|
.SetManifestVersion(manifest_version)
|
|
.SetManifestKey("export", base::Value::Dict())
|
|
.SetPath(GetTestPath("response_headers"))
|
|
.Build();
|
|
}
|
|
|
|
scoped_refptr<const Extension> CreateTestModuleImporterResponseHeaderExtension(
|
|
int manifest_version,
|
|
const std::string& module_extension_id) {
|
|
if (manifest_version == 3) {
|
|
return ExtensionBuilder("A module importer extension")
|
|
.SetManifestVersion(3)
|
|
.SetManifestKey("import",
|
|
base::Value::List().Append(
|
|
base::Value::Dict().Set("id", module_extension_id)))
|
|
.SetManifestKey("trial_tokens", base::Value::List()
|
|
.Append(kValidTrialToken1)
|
|
.Append(kValidTrialToken2))
|
|
.SetPath(GetTestPath("response_headers"))
|
|
.Build();
|
|
}
|
|
return ExtensionBuilder("A module importer extension")
|
|
.SetManifestVersion(manifest_version)
|
|
.SetManifestKey("import",
|
|
base::Value::List().Append(
|
|
base::Value::Dict().Set("id", module_extension_id)))
|
|
.SetPath(GetTestPath("response_headers"))
|
|
.Build();
|
|
}
|
|
|
|
// Helper function to create a |ResourceRequest| for testing purposes.
|
|
network::ResourceRequest CreateResourceRequest(
|
|
const std::string& method,
|
|
network::mojom::RequestDestination destination,
|
|
const GURL& url) {
|
|
network::ResourceRequest request;
|
|
request.method = method;
|
|
request.url = url;
|
|
request.site_for_cookies =
|
|
net::SiteForCookies::FromUrl(url); // bypass third-party cookie blocking.
|
|
request.request_initiator =
|
|
url::Origin::Create(url); // ensure initiator set.
|
|
request.referrer_policy = blink::ReferrerUtils::GetDefaultNetReferrerPolicy();
|
|
request.destination = destination;
|
|
request.is_outermost_main_frame =
|
|
destination == network::mojom::RequestDestination::kDocument;
|
|
return request;
|
|
}
|
|
|
|
// The result of either a URLRequest of a URLLoader response (but not both)
|
|
// depending on the on test type.
|
|
class GetResult {
|
|
public:
|
|
GetResult(network::mojom::URLResponseHeadPtr response, int result)
|
|
: response_(std::move(response)), result_(result) {}
|
|
GetResult(GetResult&& other) : result_(other.result_) {}
|
|
|
|
GetResult(const GetResult&) = delete;
|
|
GetResult& operator=(const GetResult&) = delete;
|
|
|
|
~GetResult() = default;
|
|
|
|
std::string GetResponseHeaderByName(const std::string& name) const {
|
|
if (!response_ || !response_->headers) {
|
|
return std::string();
|
|
}
|
|
return response_->headers->GetNormalizedHeader(name).value_or(
|
|
std::string());
|
|
}
|
|
|
|
bool HasContentLengthHeader() {
|
|
std::string content_length =
|
|
GetResponseHeaderByName(net::HttpRequestHeaders::kContentLength);
|
|
|
|
int length_value = 0;
|
|
return !content_length.empty() &&
|
|
base::StringToInt(content_length, &length_value) && length_value > 0;
|
|
}
|
|
|
|
bool HeaderIsPresent(const std::string& name) {
|
|
return !GetResponseHeaderByName(name).empty();
|
|
}
|
|
|
|
int result() const { return result_; }
|
|
|
|
private:
|
|
network::mojom::URLResponseHeadPtr response_;
|
|
int result_;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
// This test lives in src/chrome instead of src/extensions because it tests
|
|
// functionality delegated back to Chrome via ChromeExtensionsBrowserClient.
|
|
// See chrome/browser/extensions/chrome_url_request_util.cc.
|
|
class ExtensionProtocolsTestBase : public testing::Test,
|
|
public testing::WithParamInterface<int> {
|
|
public:
|
|
explicit ExtensionProtocolsTestBase(bool force_incognito)
|
|
: task_environment_(content::BrowserTaskEnvironment::IO_MAINLOOP),
|
|
rvh_test_enabler_(new content::RenderViewHostTestEnabler()),
|
|
force_incognito_(force_incognito) {}
|
|
|
|
void SetUp() override {
|
|
testing::Test::SetUp();
|
|
testing_profile_ = TestingProfile::Builder().Build();
|
|
contents_ = CreateTestWebContents();
|
|
|
|
// Set up content verification.
|
|
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
|
|
command_line->AppendSwitchASCII(
|
|
switches::kExtensionContentVerification,
|
|
switches::kExtensionContentVerificationEnforce);
|
|
content_verifier_ = new ContentVerifier(
|
|
browser_context(),
|
|
std::make_unique<ChromeContentVerifierDelegate>(browser_context()));
|
|
content_verifier_->Start();
|
|
static_cast<TestExtensionSystem*>(ExtensionSystem::Get(browser_context()))
|
|
->set_content_verifier(content_verifier_.get());
|
|
loader_factory_.Bind(
|
|
CreateExtensionNavigationURLLoaderFactory(browser_context(), false));
|
|
}
|
|
|
|
void TearDown() override {
|
|
loader_factory_.reset();
|
|
content_verifier_->Shutdown();
|
|
// Shut down the PowerMonitor if initialized.
|
|
base::PowerMonitor::GetInstance()->ShutdownForTesting();
|
|
}
|
|
|
|
GetResult RequestOrLoad(const GURL& url,
|
|
network::mojom::RequestDestination destination) {
|
|
return LoadURL(url, destination);
|
|
}
|
|
|
|
void AddExtension(const scoped_refptr<const Extension>& extension,
|
|
bool incognito_enabled,
|
|
bool notifications_disabled) {
|
|
EXPECT_TRUE(extension_registry()->AddEnabled(extension));
|
|
extension_registry()->TriggerOnLoaded(extension.get());
|
|
ExtensionPrefs::Get(browser_context())
|
|
->SetIsIncognitoEnabled(extension->id(), incognito_enabled);
|
|
}
|
|
|
|
void RemoveExtension(const scoped_refptr<const Extension>& extension,
|
|
const UnloadedExtensionReason reason) {
|
|
EXPECT_TRUE(extension_registry()->RemoveEnabled(extension->id()));
|
|
extension_registry()->TriggerOnUnloaded(extension.get(), reason);
|
|
if (reason == UnloadedExtensionReason::DISABLE)
|
|
EXPECT_TRUE(extension_registry()->AddDisabled(extension));
|
|
}
|
|
|
|
// Helper method to create a URL request/loader, call RequestOrLoad on it, and
|
|
// return the result. If |extension| hasn't already been added to
|
|
// extension_registry(), this will add it.
|
|
GetResult DoRequestOrLoad(const scoped_refptr<Extension> extension,
|
|
const std::string& relative_path) {
|
|
if (!extension_registry()->enabled_extensions().Contains(extension->id())) {
|
|
AddExtension(extension.get(),
|
|
/*incognito_enabled=*/false,
|
|
/*notifications_disabled=*/false);
|
|
}
|
|
return RequestOrLoad(extension->GetResourceURL(relative_path),
|
|
network::mojom::RequestDestination::kDocument);
|
|
}
|
|
|
|
ExtensionRegistry* extension_registry() {
|
|
return ExtensionRegistry::Get(browser_context());
|
|
}
|
|
|
|
content::BrowserContext* browser_context() {
|
|
return force_incognito_ ? testing_profile_->GetPrimaryOTRProfile(
|
|
/*create_if_needed=*/true)
|
|
: testing_profile_.get();
|
|
}
|
|
|
|
void EnableSimulationOfSystemSuspendForRequests() {
|
|
power_monitor_source_.emplace();
|
|
}
|
|
|
|
protected:
|
|
scoped_refptr<ContentVerifier> content_verifier_;
|
|
|
|
private:
|
|
GetResult LoadURL(const GURL& url,
|
|
network::mojom::RequestDestination destination) {
|
|
constexpr int32_t kRequestId = 28;
|
|
|
|
mojo::PendingRemote<network::mojom::URLLoader> loader;
|
|
network::TestURLLoaderClient client;
|
|
loader_factory_->CreateLoaderAndStart(
|
|
loader.InitWithNewPipeAndPassReceiver(), kRequestId,
|
|
network::mojom::kURLLoadOptionNone,
|
|
CreateResourceRequest("GET", destination, url), client.CreateRemote(),
|
|
net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS));
|
|
|
|
// If `power_monitor_source_` is set, simulates power suspend and resume
|
|
// notifications. These notifications are posted tasks that will be executed
|
|
// by `client.RunUntilComplete()`.
|
|
if (power_monitor_source_) {
|
|
power_monitor_source_->Suspend();
|
|
power_monitor_source_->Resume();
|
|
}
|
|
|
|
client.RunUntilComplete();
|
|
return GetResult(client.response_head().Clone(),
|
|
client.completion_status().error_code);
|
|
}
|
|
|
|
std::unique_ptr<content::WebContents> CreateTestWebContents() {
|
|
auto site_instance = content::SiteInstance::Create(browser_context());
|
|
return content::WebContentsTester::CreateTestWebContents(
|
|
browser_context(), std::move(site_instance));
|
|
}
|
|
|
|
content::BrowserTaskEnvironment task_environment_;
|
|
std::unique_ptr<content::RenderViewHostTestEnabler> rvh_test_enabler_;
|
|
mojo::Remote<network::mojom::URLLoaderFactory> loader_factory_;
|
|
std::unique_ptr<TestingProfile> testing_profile_;
|
|
std::unique_ptr<content::WebContents> contents_;
|
|
const bool force_incognito_;
|
|
|
|
std::optional<base::test::ScopedPowerMonitorTestSource> power_monitor_source_;
|
|
};
|
|
|
|
class ExtensionProtocolsTest : public ExtensionProtocolsTestBase {
|
|
public:
|
|
ExtensionProtocolsTest()
|
|
: ExtensionProtocolsTestBase(false /*force_incognito*/) {}
|
|
};
|
|
|
|
class ExtensionProtocolsIncognitoTest : public ExtensionProtocolsTestBase {
|
|
public:
|
|
ExtensionProtocolsIncognitoTest()
|
|
: ExtensionProtocolsTestBase(true /*force_incognito*/) {}
|
|
};
|
|
|
|
// A specialization that will only run on MV3 extensions.
|
|
using ExtensionProtocolsMV3Test = ExtensionProtocolsTest;
|
|
|
|
INSTANTIATE_TEST_SUITE_P(MV2, ExtensionProtocolsTest, ::testing::Values(2));
|
|
INSTANTIATE_TEST_SUITE_P(MV3, ExtensionProtocolsTest, ::testing::Values(3));
|
|
INSTANTIATE_TEST_SUITE_P(MV2,
|
|
ExtensionProtocolsIncognitoTest,
|
|
::testing::Values(2));
|
|
INSTANTIATE_TEST_SUITE_P(MV3,
|
|
ExtensionProtocolsIncognitoTest,
|
|
::testing::Values(3));
|
|
INSTANTIATE_TEST_SUITE_P(MV3, ExtensionProtocolsMV3Test, ::testing::Values(3));
|
|
|
|
// Tests that making a chrome-extension request in an incognito context is
|
|
// only allowed under the right circumstances (if the extension is allowed
|
|
// in incognito, and it's either a non-main-frame request or a split-mode
|
|
// extension).
|
|
TEST_P(ExtensionProtocolsIncognitoTest, IncognitoRequest) {
|
|
struct TestCase {
|
|
// Inputs.
|
|
std::string name;
|
|
bool incognito_split_mode;
|
|
bool incognito_enabled;
|
|
|
|
// Expected result.
|
|
bool should_allow_main_frame_load;
|
|
} test_cases[] = {
|
|
{"spanning disabled", false, false, false},
|
|
{"split disabled", true, false, false},
|
|
{"spanning enabled", false, true, false},
|
|
{"split enabled", true, true, true},
|
|
};
|
|
|
|
for (const auto& test_case : test_cases) {
|
|
scoped_refptr<const Extension> extension = CreateTestExtension(
|
|
test_case.name, test_case.incognito_split_mode, GetParam());
|
|
AddExtension(extension, test_case.incognito_enabled, false);
|
|
|
|
// First test a main frame request.
|
|
// It doesn't matter that the resource doesn't exist. If the resource
|
|
// is blocked, we should see BLOCKED_BY_CLIENT. Otherwise, the request
|
|
// should just fail because the file doesn't exist.
|
|
auto get_result =
|
|
RequestOrLoad(extension->GetResourceURL("404.html"),
|
|
network::mojom::RequestDestination::kDocument);
|
|
|
|
if (test_case.should_allow_main_frame_load) {
|
|
EXPECT_EQ(net::ERR_FILE_NOT_FOUND, get_result.result()) << test_case.name;
|
|
} else {
|
|
EXPECT_EQ(net::ERR_BLOCKED_BY_CLIENT, get_result.result())
|
|
<< test_case.name;
|
|
}
|
|
// Subframe navigation requests are blocked in ExtensionNavigationThrottle
|
|
// which isn't added in this unit test. This is tested in an integration
|
|
// test in ExtensionResourceRequestPolicyTest.IframeNavigateToInaccessible.
|
|
RemoveExtension(extension, UnloadedExtensionReason::UNINSTALL);
|
|
}
|
|
}
|
|
|
|
// Tests getting a resource for a component extension works correctly, both when
|
|
// the extension is enabled and when it is disabled.
|
|
TEST_P(ExtensionProtocolsTest, ComponentResourceRequest) {
|
|
scoped_refptr<const Extension> extension =
|
|
CreateWebStoreExtension(GetParam());
|
|
AddExtension(extension, false, false);
|
|
|
|
// First test it with the extension enabled.
|
|
{
|
|
auto get_result =
|
|
RequestOrLoad(extension->GetResourceURL("webstore_icon_16.png"),
|
|
network::mojom::RequestDestination::kVideo);
|
|
EXPECT_EQ(net::OK, get_result.result());
|
|
EXPECT_TRUE(get_result.HasContentLengthHeader());
|
|
EXPECT_EQ("image/png", get_result.GetResponseHeaderByName(
|
|
net::HttpRequestHeaders::kContentType));
|
|
// TODO(crbug.com/333078381): remove "Content-Security-Policy" header from
|
|
// images.
|
|
EXPECT_TRUE(get_result.HeaderIsPresent("Content-Security-Policy"));
|
|
}
|
|
|
|
// And then test it with the extension disabled.
|
|
RemoveExtension(extension, UnloadedExtensionReason::DISABLE);
|
|
{
|
|
auto get_result =
|
|
RequestOrLoad(extension->GetResourceURL("webstore_icon_16.png"),
|
|
network::mojom::RequestDestination::kVideo);
|
|
EXPECT_EQ(net::OK, get_result.result());
|
|
EXPECT_TRUE(get_result.HasContentLengthHeader());
|
|
EXPECT_EQ("image/png", get_result.GetResponseHeaderByName(
|
|
net::HttpRequestHeaders::kContentType));
|
|
}
|
|
}
|
|
|
|
// Tests that a URL request for resource from an extension returns a few
|
|
// expected response headers.
|
|
TEST_P(ExtensionProtocolsTest, ResourceRequestResponseHeaders) {
|
|
scoped_refptr<const Extension> extension =
|
|
CreateTestResponseHeaderExtension(GetParam());
|
|
AddExtension(extension, false, false);
|
|
|
|
{
|
|
auto get_result = RequestOrLoad(extension->GetResourceURL("test.dat"),
|
|
network::mojom::RequestDestination::kVideo);
|
|
EXPECT_EQ(net::OK, get_result.result());
|
|
|
|
// Check that cache-related headers are set.
|
|
std::string etag = get_result.GetResponseHeaderByName("ETag");
|
|
EXPECT_TRUE(base::StartsWith(etag, "\"", base::CompareCase::SENSITIVE));
|
|
EXPECT_TRUE(base::EndsWith(etag, "\"", base::CompareCase::SENSITIVE));
|
|
|
|
EXPECT_EQ("no-cache", get_result.GetResponseHeaderByName("Cache-Control"));
|
|
|
|
// We set test.dat as web-accessible, so it should have CORS headers.
|
|
EXPECT_EQ(
|
|
"*", get_result.GetResponseHeaderByName("Access-Control-Allow-Origin"));
|
|
EXPECT_EQ("cross-origin", get_result.GetResponseHeaderByName(
|
|
"Cross-Origin-Resource-Policy"));
|
|
|
|
// Only background service worker script should be allowed to load as a
|
|
// service worker.
|
|
EXPECT_FALSE(get_result.HeaderIsPresent("Service-Worker-Allowed"));
|
|
|
|
// COEP header does not make sense in non-document responses.
|
|
EXPECT_FALSE(get_result.HeaderIsPresent("Cross-Origin-Embedder-Policy"));
|
|
|
|
// CSP header does not make sense in non-document responses
|
|
// TODO(crbug.com/333078381): remove "Content-Security-Policy" header from
|
|
// non-document responses and update this check.
|
|
EXPECT_TRUE(get_result.HeaderIsPresent("Content-Security-Policy"));
|
|
|
|
// COOP header does not make sense in non-document responses.
|
|
EXPECT_FALSE(get_result.HeaderIsPresent("Cross-Origin-Opener-Policy"));
|
|
|
|
// Origin Trials header does not make sense in video resource responses.
|
|
EXPECT_FALSE(get_result.HeaderIsPresent("Origin-Trial"));
|
|
}
|
|
}
|
|
|
|
// Tests that request for background script returns a few expected response
|
|
// headers.
|
|
TEST_P(ExtensionProtocolsTest, BackgroundScriptRequestResponseHeaders) {
|
|
const int manifest_version = GetParam();
|
|
scoped_refptr<const Extension> extension =
|
|
CreateTestResponseHeaderExtension(manifest_version);
|
|
AddExtension(extension, false, false);
|
|
|
|
{
|
|
auto get_result =
|
|
RequestOrLoad(extension->GetResourceURL("background.js"),
|
|
network::mojom::RequestDestination::kServiceWorker);
|
|
EXPECT_EQ(net::OK, get_result.result());
|
|
|
|
// Check that cache-related headers are set.
|
|
std::string etag = get_result.GetResponseHeaderByName("ETag");
|
|
EXPECT_TRUE(base::StartsWith(etag, "\"", base::CompareCase::SENSITIVE));
|
|
EXPECT_TRUE(base::EndsWith(etag, "\"", base::CompareCase::SENSITIVE));
|
|
|
|
EXPECT_EQ("no-cache", get_result.GetResponseHeaderByName("Cache-Control"));
|
|
|
|
// Background scripts are not web-accessible, so do not need CORS headers.
|
|
EXPECT_FALSE(get_result.HeaderIsPresent("Access-Control-Allow-Origin"));
|
|
EXPECT_FALSE(get_result.HeaderIsPresent("Cross-Origin-Resource-Policy"));
|
|
|
|
// Only background service worker script should be allowed to load as a
|
|
// service worker.
|
|
if (manifest_version == 3) {
|
|
EXPECT_EQ("/",
|
|
get_result.GetResponseHeaderByName("Service-Worker-Allowed"));
|
|
} else {
|
|
EXPECT_FALSE(get_result.HeaderIsPresent("Service-Worker-Allowed"));
|
|
}
|
|
|
|
// COEP header does not make sense in non-document responses.
|
|
EXPECT_FALSE(get_result.HeaderIsPresent("Cross-Origin-Embedder-Policy"));
|
|
|
|
// Even though CSP is currently not respected for service workers, it
|
|
// probably should be. We continue to send a CSP header for service worker
|
|
// scripts for when this changes.
|
|
// See also
|
|
// https://github.com/w3c/webappsec-csp/issues/336#issuecomment-1274730655
|
|
if (manifest_version == 3) {
|
|
EXPECT_EQ("script-src 'self';",
|
|
get_result.GetResponseHeaderByName("Content-Security-Policy"));
|
|
} else {
|
|
EXPECT_EQ(
|
|
"script-src 'self' blob: filesystem:; object-src 'self' blob: "
|
|
"filesystem:;",
|
|
get_result.GetResponseHeaderByName("Content-Security-Policy"));
|
|
}
|
|
|
|
// COOP header does not make sense in non-document responses.
|
|
EXPECT_FALSE(get_result.HeaderIsPresent("Cross-Origin-Opener-Policy"));
|
|
}
|
|
}
|
|
|
|
// Tests that request for background service worker returns Origin-Trial
|
|
// response header.
|
|
TEST_P(ExtensionProtocolsMV3Test, BackgroundScriptRequestResponseHeaders) {
|
|
EXPECT_EQ(3, GetParam());
|
|
scoped_refptr<const Extension> extension =
|
|
CreateTestResponseHeaderExtension(GetParam());
|
|
AddExtension(extension, false, false);
|
|
|
|
{
|
|
auto get_result =
|
|
RequestOrLoad(extension->GetResourceURL("background.js"),
|
|
network::mojom::RequestDestination::kServiceWorker);
|
|
EXPECT_EQ(net::OK, get_result.result());
|
|
|
|
// In MV3-style service workers origin trail tokens are served via service
|
|
// worker Origin-Trial header.
|
|
EXPECT_EQ(kTrialTokensHeaderValue,
|
|
get_result.GetResponseHeaderByName("Origin-Trial"));
|
|
}
|
|
}
|
|
|
|
// TODO(crbug.com/333078381): Add a test checking that:
|
|
// - when background.page or background.service_worker is specified requesting
|
|
// generated background page fails
|
|
// - when no background is specified, requesting generated background page fails
|
|
|
|
TEST_P(ExtensionProtocolsTest, BackgroundPageRequestResponseHeaders) {
|
|
const int manifest_version = GetParam();
|
|
scoped_refptr<const Extension> extension =
|
|
CreateTestResponseHeaderExtension(manifest_version);
|
|
AddExtension(extension, false, false);
|
|
|
|
{
|
|
auto get_result = RequestOrLoad(
|
|
extension->GetResourceURL(kGeneratedBackgroundPageFilename),
|
|
network::mojom::RequestDestination::kDocument);
|
|
EXPECT_EQ(net::OK, get_result.result());
|
|
|
|
// Check that cache-related headers are omitted
|
|
// TODO(crbug.com/333078381): consider adding these headers to generated
|
|
// pages.
|
|
EXPECT_FALSE(get_result.HeaderIsPresent("ETag"));
|
|
EXPECT_FALSE(get_result.HeaderIsPresent("Cache-Control"));
|
|
|
|
// Background pages are not web-accessible, so do not need CORS headers.
|
|
EXPECT_FALSE(get_result.HeaderIsPresent("Access-Control-Allow-Origin"));
|
|
EXPECT_FALSE(get_result.HeaderIsPresent("Cross-Origin-Resource-Policy"));
|
|
|
|
// Background page does not need to be loaded as a service worker.
|
|
EXPECT_FALSE(get_result.HeaderIsPresent("Service-Worker-Allowed"));
|
|
|
|
// Background page does not load cross-origin content so does not need COEP
|
|
// header.
|
|
EXPECT_FALSE(get_result.HeaderIsPresent("Cross-Origin-Embedder-Policy"));
|
|
|
|
if (manifest_version == 3) {
|
|
EXPECT_EQ("script-src 'self';",
|
|
get_result.GetResponseHeaderByName("Content-Security-Policy"));
|
|
} else {
|
|
EXPECT_EQ(
|
|
"script-src 'self' blob: filesystem:; object-src 'self' blob: "
|
|
"filesystem:;",
|
|
get_result.GetResponseHeaderByName("Content-Security-Policy"));
|
|
}
|
|
|
|
// COOP header does not make sense in non-document responses.
|
|
EXPECT_FALSE(get_result.HeaderIsPresent("Cross-Origin-Opener-Policy"));
|
|
}
|
|
}
|
|
|
|
// Tests that resources from imported module extensions get appropriately
|
|
// loaded with proper headers or rejected
|
|
TEST_P(ExtensionProtocolsTest, ModuleRequestResponseHeaders) {
|
|
const int manifest_version = GetParam();
|
|
scoped_refptr<const Extension> module_extension =
|
|
CreateTestModuleResponseHeaderExtension(manifest_version);
|
|
scoped_refptr<const Extension> importer_extension =
|
|
CreateTestModuleImporterResponseHeaderExtension(manifest_version,
|
|
module_extension->id());
|
|
AddExtension(module_extension, false, false);
|
|
AddExtension(importer_extension, false, false);
|
|
|
|
// Not imported id will fail.
|
|
{
|
|
auto get_result =
|
|
RequestOrLoad(importer_extension->GetResourceURL(
|
|
"_modules/modaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/test.dat"),
|
|
network::mojom::RequestDestination::kDocument);
|
|
EXPECT_EQ(net::ERR_BLOCKED_BY_CLIENT, get_result.result());
|
|
}
|
|
{
|
|
auto get_result =
|
|
RequestOrLoad(importer_extension->GetResourceURL(
|
|
"_modules/modaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/test.dat"),
|
|
network::mojom::RequestDestination::kServiceWorker);
|
|
EXPECT_EQ(net::ERR_BLOCKED_BY_CLIENT, get_result.result());
|
|
}
|
|
|
|
// Imported resources get loaded with proper headers (inherited from
|
|
// importer).
|
|
{
|
|
auto get_result =
|
|
RequestOrLoad(importer_extension->GetResourceURL(
|
|
"_modules/" + module_extension->id() + "/test.dat"),
|
|
network::mojom::RequestDestination::kDocument);
|
|
EXPECT_EQ(net::OK, get_result.result());
|
|
|
|
// Check that cache-related headers are set.
|
|
std::string etag = get_result.GetResponseHeaderByName("ETag");
|
|
EXPECT_TRUE(base::StartsWith(etag, "\"", base::CompareCase::SENSITIVE));
|
|
EXPECT_TRUE(base::EndsWith(etag, "\"", base::CompareCase::SENSITIVE));
|
|
|
|
// Background pages are not web-accessible, so do not need CORS headers.
|
|
EXPECT_FALSE(get_result.HeaderIsPresent("Access-Control-Allow-Origin"));
|
|
EXPECT_FALSE(get_result.HeaderIsPresent("Cross-Origin-Resource-Policy"));
|
|
|
|
// Background page does not need to be loaded as a service worker.
|
|
EXPECT_FALSE(get_result.HeaderIsPresent("Service-Worker-Allowed"));
|
|
|
|
// Background page does not load cross-origin content so does not need COEP
|
|
// header.
|
|
EXPECT_FALSE(get_result.HeaderIsPresent("Cross-Origin-Embedder-Policy"));
|
|
|
|
if (manifest_version == 3) {
|
|
EXPECT_EQ("script-src 'self';",
|
|
get_result.GetResponseHeaderByName("Content-Security-Policy"));
|
|
} else {
|
|
EXPECT_EQ(
|
|
"script-src 'self' blob: filesystem:; object-src 'self' blob: "
|
|
"filesystem:;",
|
|
get_result.GetResponseHeaderByName("Content-Security-Policy"));
|
|
}
|
|
|
|
// COOP header does not make sense in non-document responses.
|
|
EXPECT_FALSE(get_result.HeaderIsPresent("Cross-Origin-Opener-Policy"));
|
|
}
|
|
}
|
|
|
|
// Tests that request for background service worker returns Origin-Trial
|
|
// response header.
|
|
TEST_P(ExtensionProtocolsMV3Test, ModuleRequestResponseHeaders) {
|
|
EXPECT_EQ(3, GetParam());
|
|
const int manifest_version = GetParam();
|
|
scoped_refptr<const Extension> module_extension =
|
|
CreateTestModuleResponseHeaderExtension(manifest_version);
|
|
scoped_refptr<const Extension> importer_extension =
|
|
CreateTestModuleImporterResponseHeaderExtension(manifest_version,
|
|
module_extension->id());
|
|
AddExtension(module_extension, false, false);
|
|
AddExtension(importer_extension, false, false);
|
|
|
|
// Imported resources get loaded with proper headers (inherited from
|
|
// importer).
|
|
{
|
|
auto get_result =
|
|
RequestOrLoad(importer_extension->GetResourceURL(
|
|
"_modules/" + module_extension->id() + "/test.dat"),
|
|
network::mojom::RequestDestination::kDocument);
|
|
EXPECT_EQ(net::OK, get_result.result());
|
|
|
|
// Origin-Trial header should contain trials inherited from importer.
|
|
EXPECT_EQ(kTrialTokensHeaderValue,
|
|
get_result.GetResponseHeaderByName("Origin-Trial"));
|
|
}
|
|
}
|
|
|
|
TEST_P(ExtensionProtocolsTest, InvalidBackgroundScriptRequest) {
|
|
const int manifest_version = GetParam();
|
|
scoped_refptr<const Extension> extension =
|
|
CreateTestResponseHeaderExtension(manifest_version);
|
|
AddExtension(extension, false, false);
|
|
|
|
// Requesting script from background key with invalid destination is
|
|
// forbidden.
|
|
std::vector<network::mojom::RequestDestination> destinations = {
|
|
// TODO(crbug.com/333078381): carefully consider which other
|
|
// request destinations should be allowed or blocked and update
|
|
// this test
|
|
network::mojom::RequestDestination::kJson,
|
|
network::mojom::RequestDestination::kStyle,
|
|
network::mojom::RequestDestination::kVideo,
|
|
};
|
|
if (!base::FeatureList::IsEnabled(blink::features::kPlzDedicatedWorker)) {
|
|
destinations.push_back(network::mojom::RequestDestination::kWorker);
|
|
}
|
|
for (network::mojom::RequestDestination destination : destinations) {
|
|
auto get_result =
|
|
RequestOrLoad(extension->GetResourceURL("background.js"), destination);
|
|
EXPECT_EQ(net::ERR_BLOCKED_BY_CLIENT, get_result.result()) << destination;
|
|
}
|
|
}
|
|
|
|
// Tests that a URL request for main frame or subframe from an extension
|
|
// succeeds, but subresources fail. See http://crbug.com/312269.
|
|
TEST_P(ExtensionProtocolsTest, AllowFrameRequests) {
|
|
scoped_refptr<const Extension> extension =
|
|
CreateTestExtension("foo", false, GetParam());
|
|
AddExtension(extension, false, false);
|
|
|
|
// All MAIN_FRAME requests should succeed. SUB_FRAME requests that are not
|
|
// explicitly listed in web_accessible_resources or same-origin to the parent
|
|
// should not succeed.
|
|
{
|
|
auto get_result =
|
|
RequestOrLoad(extension->GetResourceURL("test.dat"),
|
|
network::mojom::RequestDestination::kDocument);
|
|
EXPECT_EQ(net::OK, get_result.result());
|
|
}
|
|
|
|
// Subframe navigation requests are blocked in ExtensionNavigationThrottle
|
|
// which isn't added in this unit test. This is tested in an integration test
|
|
// in ExtensionResourceRequestPolicyTest.IframeNavigateToInaccessible.
|
|
|
|
// And subresource types, such as media, should fail.
|
|
{
|
|
auto get_result = RequestOrLoad(extension->GetResourceURL("test.dat"),
|
|
network::mojom::RequestDestination::kVideo);
|
|
EXPECT_EQ(net::ERR_BLOCKED_BY_CLIENT, get_result.result());
|
|
}
|
|
}
|
|
|
|
// Make sure requests for paths ending with a separator aren't allowed. See
|
|
// https://crbug.com/356878412.
|
|
TEST_P(ExtensionProtocolsTest, PathsWithTrailingSeparatorsAreNotAllowed) {
|
|
base::FilePath extension_dir = GetTestPath("simple_with_file");
|
|
std::string error;
|
|
scoped_refptr<Extension> extension = file_util::LoadExtension(
|
|
extension_dir, mojom::ManifestLocation::kInternal, Extension::NO_FLAGS,
|
|
&error);
|
|
ASSERT_NE(extension.get(), nullptr) << "error: " << error;
|
|
|
|
// Loading "/file.html" should succeed.
|
|
EXPECT_EQ(net::OK, DoRequestOrLoad(extension, "file.html").result());
|
|
|
|
// Loading "/file.html/" should fail.
|
|
base::FilePath relative_path =
|
|
base::FilePath(FILE_PATH_LITERAL("file.html")).AsEndingWithSeparator();
|
|
EXPECT_EQ(net::ERR_FILE_NOT_FOUND,
|
|
DoRequestOrLoad(extension, relative_path.AsUTF8Unsafe()).result());
|
|
}
|
|
|
|
// Make sure directories with an index.html file aren't serving the file, i.e.
|
|
// index.html doesn't get any special treatment.
|
|
TEST_P(ExtensionProtocolsTest, DirectoryWithIndexHtml) {
|
|
base::FilePath extension_dir = GetTestPath("simple_with_index_html");
|
|
std::string error;
|
|
scoped_refptr<Extension> extension = file_util::LoadExtension(
|
|
extension_dir, mojom::ManifestLocation::kInternal, Extension::NO_FLAGS,
|
|
&error);
|
|
ASSERT_NE(extension.get(), nullptr) << "error: " << error;
|
|
|
|
// Loading "/test_dir" should fail.
|
|
base::FilePath relative_path(FILE_PATH_LITERAL("test_dir"));
|
|
EXPECT_EQ(net::ERR_FILE_NOT_FOUND,
|
|
DoRequestOrLoad(extension, relative_path.AsUTF8Unsafe()).result());
|
|
|
|
// Loading "/test_dir/" should fail.
|
|
relative_path = relative_path.AsEndingWithSeparator();
|
|
EXPECT_EQ(net::ERR_FILE_NOT_FOUND,
|
|
DoRequestOrLoad(extension, relative_path.AsUTF8Unsafe()).result());
|
|
|
|
// Loading "/test_dir/index.html" explicitly should succeed.
|
|
relative_path = relative_path.AppendASCII("index.html");
|
|
EXPECT_EQ(net::OK,
|
|
DoRequestOrLoad(extension, relative_path.AsUTF8Unsafe()).result());
|
|
}
|
|
|
|
TEST_P(ExtensionProtocolsTest, MetadataFolder) {
|
|
base::FilePath extension_dir = GetTestPath("metadata_folder");
|
|
std::string error;
|
|
scoped_refptr<Extension> extension = file_util::LoadExtension(
|
|
extension_dir, mojom::ManifestLocation::kInternal, Extension::NO_FLAGS,
|
|
&error);
|
|
ASSERT_NE(extension.get(), nullptr) << "error: " << error;
|
|
|
|
// Loading "/test.html" should succeed.
|
|
EXPECT_EQ(net::OK, DoRequestOrLoad(extension, "test.html").result());
|
|
|
|
// Loading "/_metadata/verified_contents.json" should fail.
|
|
base::FilePath relative_path =
|
|
base::FilePath(kMetadataFolder).Append(kVerifiedContentsFilename);
|
|
EXPECT_TRUE(base::PathExists(extension_dir.Append(relative_path)));
|
|
EXPECT_NE(net::OK,
|
|
DoRequestOrLoad(extension, relative_path.AsUTF8Unsafe()).result());
|
|
|
|
// Loading "/_metadata/a.txt" should also fail.
|
|
relative_path = base::FilePath(kMetadataFolder).AppendASCII("a.txt");
|
|
EXPECT_TRUE(base::PathExists(extension_dir.Append(relative_path)));
|
|
EXPECT_NE(net::OK,
|
|
DoRequestOrLoad(extension, relative_path.AsUTF8Unsafe()).result());
|
|
}
|
|
|
|
// Tests that unreadable files and deleted files correctly go through
|
|
// ContentVerifyJob.
|
|
TEST_P(ExtensionProtocolsTest, VerificationSeenForFileAccessErrors) {
|
|
// Unzip extension containing verification hashes to a temporary directory.
|
|
base::ScopedTempDir temp_dir;
|
|
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
|
|
base::FilePath unzipped_path = temp_dir.GetPath();
|
|
scoped_refptr<Extension> extension =
|
|
content_verifier_test_utils::UnzipToDirAndLoadExtension(
|
|
GetContentVerifierTestPath().AppendASCII("source.zip"),
|
|
unzipped_path);
|
|
ASSERT_TRUE(extension.get());
|
|
ExtensionId extension_id = extension->id();
|
|
|
|
const std::string kJs("1024.js");
|
|
base::FilePath kRelativePath(FILE_PATH_LITERAL("1024.js"));
|
|
|
|
// Valid and readable 1024.js.
|
|
{
|
|
TestContentVerifySingleJobObserver observer(extension_id, kRelativePath);
|
|
EXPECT_EQ(net::OK, DoRequestOrLoad(extension, kJs).result());
|
|
EXPECT_EQ(ContentVerifyJob::NONE, observer.WaitForJobFinished());
|
|
}
|
|
|
|
// chmod -r 1024.js.
|
|
{
|
|
TestContentVerifySingleJobObserver observer(extension_id, kRelativePath);
|
|
base::FilePath file_path = unzipped_path.AppendASCII(kJs);
|
|
ASSERT_TRUE(base::MakeFileUnreadable(file_path));
|
|
EXPECT_EQ(net::ERR_ACCESS_DENIED, DoRequestOrLoad(extension, kJs).result());
|
|
EXPECT_EQ(ContentVerifyJob::HASH_MISMATCH, observer.WaitForJobFinished());
|
|
// NOTE: In production, hash mismatch would have disabled |extension|, but
|
|
// since UnzipToDirAndLoadExtension() doesn't add the extension to
|
|
// ExtensionRegistry, ChromeContentVerifierDelegate won't disable it.
|
|
// TODO(lazyboy): We may want to update this to more closely reflect the
|
|
// real flow.
|
|
}
|
|
|
|
// Delete 1024.js.
|
|
{
|
|
TestContentVerifySingleJobObserver observer(extension_id, kRelativePath);
|
|
base::FilePath file_path = unzipped_path.AppendASCII(kJs);
|
|
ASSERT_TRUE(base::DieFileDie(file_path, false));
|
|
EXPECT_EQ(net::ERR_FILE_NOT_FOUND,
|
|
DoRequestOrLoad(extension, kJs).result());
|
|
EXPECT_EQ(ContentVerifyJob::HASH_MISMATCH, observer.WaitForJobFinished());
|
|
}
|
|
}
|
|
|
|
// Tests that zero byte files correctly go through ContentVerifyJob.
|
|
TEST_P(ExtensionProtocolsTest, VerificationSeenForZeroByteFile) {
|
|
const std::string kEmptyJs("empty.js");
|
|
base::ScopedTempDir temp_dir;
|
|
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
|
|
base::FilePath unzipped_path = temp_dir.GetPath();
|
|
|
|
scoped_refptr<Extension> extension =
|
|
content_verifier_test_utils::UnzipToDirAndLoadExtension(
|
|
GetContentVerifierTestPath().AppendASCII("source.zip"),
|
|
unzipped_path);
|
|
ASSERT_TRUE(extension.get());
|
|
|
|
base::FilePath kRelativePath(FILE_PATH_LITERAL("empty.js"));
|
|
ExtensionId extension_id = extension->id();
|
|
|
|
// Sanity check empty.js.
|
|
base::FilePath file_path = unzipped_path.AppendASCII(kEmptyJs);
|
|
std::optional<int64_t> foo_file_size = base::GetFileSize(file_path);
|
|
ASSERT_TRUE(foo_file_size.has_value());
|
|
ASSERT_EQ(0, foo_file_size.value());
|
|
|
|
// Request empty.js.
|
|
{
|
|
TestContentVerifySingleJobObserver observer(extension_id, kRelativePath);
|
|
EXPECT_EQ(net::OK, DoRequestOrLoad(extension, kEmptyJs).result());
|
|
EXPECT_EQ(ContentVerifyJob::NONE, observer.WaitForJobFinished());
|
|
}
|
|
|
|
// chmod -r empty.js.
|
|
// Unreadable empty file results in hash mismatch.
|
|
{
|
|
TestContentVerifySingleJobObserver observer(extension_id, kRelativePath);
|
|
ASSERT_TRUE(base::MakeFileUnreadable(file_path));
|
|
EXPECT_EQ(net::ERR_ACCESS_DENIED,
|
|
DoRequestOrLoad(extension, kEmptyJs).result());
|
|
EXPECT_EQ(ContentVerifyJob::HASH_MISMATCH, observer.WaitForJobFinished());
|
|
}
|
|
|
|
// rm empty.js.
|
|
// Deleted empty file results in hash mismatch.
|
|
{
|
|
TestContentVerifySingleJobObserver observer(extension_id, kRelativePath);
|
|
ASSERT_TRUE(base::DieFileDie(file_path, false));
|
|
EXPECT_EQ(net::ERR_FILE_NOT_FOUND,
|
|
DoRequestOrLoad(extension, kEmptyJs).result());
|
|
EXPECT_EQ(ContentVerifyJob::HASH_MISMATCH, observer.WaitForJobFinished());
|
|
}
|
|
}
|
|
|
|
TEST_P(ExtensionProtocolsTest, VerifyScriptListedAsIcon) {
|
|
const std::string kBackgroundJs("background.js");
|
|
base::ScopedTempDir temp_dir;
|
|
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
|
|
base::FilePath unzipped_path = temp_dir.GetPath();
|
|
|
|
base::FilePath path;
|
|
EXPECT_TRUE(base::PathService::Get(DIR_TEST_DATA, &path));
|
|
|
|
scoped_refptr<Extension> extension =
|
|
content_verifier_test_utils::UnzipToDirAndLoadExtension(
|
|
path.AppendASCII("content_hash_fetcher")
|
|
.AppendASCII("manifest_mislabeled_script")
|
|
.AppendASCII("source.zip"),
|
|
unzipped_path);
|
|
ASSERT_TRUE(extension.get());
|
|
|
|
base::FilePath kRelativePath(FILE_PATH_LITERAL("background.js"));
|
|
ExtensionId extension_id = extension->id();
|
|
|
|
// Request background.js.
|
|
{
|
|
TestContentVerifySingleJobObserver observer(extension_id, kRelativePath);
|
|
EXPECT_EQ(net::OK, DoRequestOrLoad(extension, kBackgroundJs).result());
|
|
EXPECT_EQ(ContentVerifyJob::NONE, observer.WaitForJobFinished());
|
|
}
|
|
|
|
// Modify background.js and request it.
|
|
{
|
|
base::FilePath file_path = unzipped_path.AppendASCII("background.js");
|
|
const std::string content = "new content";
|
|
EXPECT_TRUE(base::WriteFile(file_path, content));
|
|
|
|
TestContentVerifySingleJobObserver observer(extension_id, kRelativePath);
|
|
EXPECT_EQ(net::OK, DoRequestOrLoad(extension, kBackgroundJs).result());
|
|
EXPECT_EQ(ContentVerifyJob::HASH_MISMATCH, observer.WaitForJobFinished());
|
|
}
|
|
}
|
|
|
|
// Tests that mime types are properly set for returned extension resources.
|
|
TEST_P(ExtensionProtocolsTest, MimeTypesForKnownFiles) {
|
|
TestExtensionDir test_dir;
|
|
const int manifest_version = GetParam();
|
|
constexpr char kManifestV2[] = R"(
|
|
{
|
|
"name": "Test Ext",
|
|
"manifest_version": 2,
|
|
"version": "1",
|
|
"web_accessible_resources": ["*"]
|
|
})";
|
|
constexpr char kManifestV3[] = R"(
|
|
{
|
|
"name": "Test Ext",
|
|
"manifest_version": 3,
|
|
"version": "1",
|
|
"web_accessible_resources": [{
|
|
"resources": [ "*" ],
|
|
"matches": [ "*://*/*" ]
|
|
}]
|
|
})";
|
|
const char* kManifest = manifest_version == 3 ? kManifestV3 : kManifestV2;
|
|
test_dir.WriteManifest(kManifest);
|
|
base::Value::Dict manifest = base::test::ParseJsonDict(kManifest);
|
|
ASSERT_FALSE(manifest.empty());
|
|
|
|
test_dir.WriteFile(FILE_PATH_LITERAL("json_file.json"), "{}");
|
|
test_dir.WriteFile(FILE_PATH_LITERAL("js_file.js"), "function() {}");
|
|
|
|
base::FilePath unpacked_path = test_dir.UnpackedPath();
|
|
ASSERT_TRUE(base::PathExists(unpacked_path.AppendASCII("json_file.json")));
|
|
std::string error;
|
|
scoped_refptr<const Extension> extension =
|
|
ExtensionBuilder()
|
|
.SetManifest(std::move(manifest))
|
|
.SetPath(unpacked_path)
|
|
.SetLocation(mojom::ManifestLocation::kInternal)
|
|
.Build();
|
|
ASSERT_TRUE(extension);
|
|
|
|
AddExtension(extension.get(), false, false);
|
|
|
|
struct {
|
|
const char* file_name;
|
|
const char* expected_mime_type;
|
|
} test_cases[] = {
|
|
{"json_file.json", "application/json"},
|
|
{"js_file.js", "text/javascript"},
|
|
{"mem_file.mem", ""},
|
|
};
|
|
|
|
for (const auto& test_case : test_cases) {
|
|
SCOPED_TRACE(test_case.file_name);
|
|
EXPECT_EQ(
|
|
test_case.expected_mime_type,
|
|
RequestOrLoad(extension->GetResourceURL(test_case.file_name),
|
|
network::mojom::RequestDestination::kEmpty)
|
|
.GetResponseHeaderByName(net::HttpRequestHeaders::kContentType));
|
|
}
|
|
}
|
|
|
|
// Tests that requests for extension resources (including the generated
|
|
// background page) are not aborted on system suspend.
|
|
TEST_P(ExtensionProtocolsTest, ExtensionRequestsNotAborted) {
|
|
base::FilePath extension_dir =
|
|
GetTestPath("common").AppendASCII("background_script");
|
|
std::string error;
|
|
scoped_refptr<Extension> extension = file_util::LoadExtension(
|
|
extension_dir, mojom::ManifestLocation::kInternal, Extension::NO_FLAGS,
|
|
&error);
|
|
ASSERT_TRUE(extension.get()) << error;
|
|
|
|
EnableSimulationOfSystemSuspendForRequests();
|
|
|
|
// Request the generated background page. Ensure the request completes
|
|
// successfully.
|
|
EXPECT_EQ(net::OK,
|
|
DoRequestOrLoad(extension.get(), kGeneratedBackgroundPageFilename)
|
|
.result());
|
|
|
|
// Request the background.js file. Ensure the request completes successfully.
|
|
EXPECT_EQ(net::OK,
|
|
DoRequestOrLoad(extension.get(), "background.js").result());
|
|
}
|
|
|
|
} // namespace extensions
|