blob: abf38f954389e2ba367b051d471269dcbe844ae9 [file] [log] [blame]
// Copyright 2021 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/services/auction_worklet/auction_downloader.h"
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/run_loop.h"
#include "base/test/task_environment.h"
#include "content/services/auction_worklet/worklet_test_util.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_util.h"
#include "services/network/public/cpp/url_loader_completion_status.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "services/network/test/test_url_loader_factory.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "url/gurl.h"
namespace auction_worklet {
namespace {
const char kAsciiResponseBody[] = "ASCII response body.";
const char kUtf8ResponseBody[] = "\xc3\x9f\xc3\x9e";
const char kNonUtf8ResponseBody[] = "\xc3";
const char kAsciiCharset[] = "us-ascii";
const char kUtf8Charset[] = "utf-8";
class AuctionDownloaderTest : public testing::Test {
public:
AuctionDownloaderTest() = default;
~AuctionDownloaderTest() override = default;
std::unique_ptr<std::string> RunRequest() {
DCHECK(!run_loop_);
AuctionDownloader downloader(
&url_loader_factory_, url_, mime_type_,
base::BindOnce(&AuctionDownloaderTest::DownloadCompleteCallback,
base::Unretained(this)));
// Populate `run_loop_` after starting the download, since API guarantees
// callback will not be invoked synchronously.
run_loop_ = std::make_unique<base::RunLoop>();
run_loop_->Run();
run_loop_.reset();
return std::move(body_);
}
// Helper to avoid checking has_value all over the place.
std::string last_error_msg() const {
return error_.value_or("Not an error.");
}
protected:
void DownloadCompleteCallback(std::unique_ptr<std::string> body,
scoped_refptr<net::HttpResponseHeaders> headers,
absl::optional<std::string> error) {
DCHECK(!body_);
DCHECK(run_loop_);
body_ = std::move(body);
headers_ = std::move(headers);
error_ = std::move(error);
EXPECT_EQ(error_.has_value(), !body_);
run_loop_->Quit();
}
base::test::TaskEnvironment task_environment_;
const GURL url_ = GURL("https://url.test/script.js");
AuctionDownloader::MimeType mime_type_ =
AuctionDownloader::MimeType::kJavascript;
std::unique_ptr<base::RunLoop> run_loop_;
std::unique_ptr<std::string> body_;
scoped_refptr<net::HttpResponseHeaders> headers_;
absl::optional<std::string> error_;
network::TestURLLoaderFactory url_loader_factory_;
};
TEST_F(AuctionDownloaderTest, NetworkError) {
network::URLLoaderCompletionStatus status;
status.error_code = net::ERR_FAILED;
url_loader_factory_.AddResponse(url_, nullptr /* head */, kAsciiResponseBody,
status);
EXPECT_FALSE(RunRequest());
EXPECT_EQ(
"Failed to load https://url.test/script.js error = net::ERR_FAILED.",
last_error_msg());
}
// HTTP 404 responses are trested as failures.
TEST_F(AuctionDownloaderTest, HttpError) {
// This is an unlikely response for an error case, but should fail if it ever
// happens.
AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, kUtf8Charset,
kAsciiResponseBody, kAllowFledgeHeader, net::HTTP_NOT_FOUND);
EXPECT_FALSE(RunRequest());
EXPECT_EQ(
"Failed to load https://url.test/script.js HTTP status = 404 Not Found.",
last_error_msg());
}
TEST_F(AuctionDownloaderTest, Timeout) {
AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, kUtf8Charset,
kAsciiResponseBody, kAllowFledgeHeader,
net::HTTP_REQUEST_TIMEOUT);
EXPECT_FALSE(RunRequest());
EXPECT_EQ(
"Failed to load https://url.test/script.js HTTP status = 408 Request "
"Timeout.",
last_error_msg());
}
TEST_F(AuctionDownloaderTest, AllowFledge) {
AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, kUtf8Charset,
kAsciiResponseBody, "X-Allow-FLEDGE: true");
EXPECT_TRUE(RunRequest());
AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, kUtf8Charset,
kAsciiResponseBody, "x-aLLow-fLeDgE: true");
EXPECT_TRUE(RunRequest());
AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, kUtf8Charset,
kAsciiResponseBody, "X-Allow-FLEDGE: false");
EXPECT_FALSE(RunRequest());
EXPECT_EQ(
"Rejecting load of https://url.test/script.js due to lack of "
"X-Allow-FLEDGE: true.",
last_error_msg());
AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, kUtf8Charset,
kAsciiResponseBody, "X-Allow-FLEDGE: sometimes");
EXPECT_FALSE(RunRequest());
EXPECT_EQ(
"Rejecting load of https://url.test/script.js due to lack of "
"X-Allow-FLEDGE: true.",
last_error_msg());
AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, kUtf8Charset,
kAsciiResponseBody, "X-Allow-FLEDGE: ");
EXPECT_FALSE(RunRequest());
EXPECT_EQ(
"Rejecting load of https://url.test/script.js due to lack of "
"X-Allow-FLEDGE: true.",
last_error_msg());
AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, kUtf8Charset,
kAsciiResponseBody, "X-Allow-Hats: true");
EXPECT_FALSE(RunRequest());
EXPECT_EQ(
"Rejecting load of https://url.test/script.js due to lack of "
"X-Allow-FLEDGE: true.",
last_error_msg());
AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, kUtf8Charset,
kAsciiResponseBody, "");
EXPECT_FALSE(RunRequest());
EXPECT_EQ(
"Rejecting load of https://url.test/script.js due to lack of "
"X-Allow-FLEDGE: true.",
last_error_msg());
AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, kUtf8Charset,
kAsciiResponseBody, absl::nullopt);
EXPECT_FALSE(RunRequest());
EXPECT_EQ(
"Rejecting load of https://url.test/script.js due to lack of "
"X-Allow-FLEDGE: true.",
last_error_msg());
}
TEST_F(AuctionDownloaderTest, PassesHeaders) {
std::string allow_fledge_string;
std::string data_version_string;
AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, kUtf8Charset,
kAsciiResponseBody, "X-Allow-FLEDGE: true");
EXPECT_TRUE(RunRequest()) << last_error_msg();
EXPECT_TRUE(
headers_->GetNormalizedHeader("X-Allow-FLEDGE", &allow_fledge_string));
EXPECT_EQ("true", allow_fledge_string);
EXPECT_FALSE(
headers_->GetNormalizedHeader("Data-Version", &data_version_string));
mime_type_ = AuctionDownloader::MimeType::kJson;
AddVersionedJsonResponse(&url_loader_factory_, url_, kAsciiResponseBody, 10u);
EXPECT_TRUE(RunRequest()) << last_error_msg();
EXPECT_TRUE(
headers_->GetNormalizedHeader("X-Allow-FLEDGE", &allow_fledge_string));
EXPECT_EQ("true", allow_fledge_string);
EXPECT_TRUE(
headers_->GetNormalizedHeader("Data-Version", &data_version_string));
EXPECT_EQ("10", data_version_string);
AddVersionedJsonResponse(&url_loader_factory_, url_, kAsciiResponseBody, 5u);
EXPECT_TRUE(RunRequest()) << last_error_msg();
EXPECT_TRUE(
headers_->GetNormalizedHeader("X-Allow-FLEDGE", &allow_fledge_string));
EXPECT_EQ("true", allow_fledge_string);
EXPECT_TRUE(
headers_->GetNormalizedHeader("Data-Version", &data_version_string));
EXPECT_EQ("5", data_version_string);
AddJsonResponse(&url_loader_factory_, url_, kAsciiResponseBody);
EXPECT_TRUE(RunRequest()) << last_error_msg();
EXPECT_TRUE(
headers_->GetNormalizedHeader("X-Allow-FLEDGE", &allow_fledge_string));
EXPECT_EQ("true", allow_fledge_string);
EXPECT_FALSE(
headers_->GetNormalizedHeader("Data-Version", &data_version_string));
}
// Redirect responses are treated as failures.
TEST_F(AuctionDownloaderTest, Redirect) {
// None of these fields actually matter for this test, but a bit strange for
// them not to be populated.
net::RedirectInfo redirect_info;
redirect_info.status_code = net::HTTP_MOVED_PERMANENTLY;
redirect_info.new_url = url_;
redirect_info.new_method = "GET";
network::TestURLLoaderFactory::Redirects redirects;
redirects.push_back(
std::make_pair(redirect_info, network::mojom::URLResponseHead::New()));
AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, kUtf8Charset,
kAsciiResponseBody, kAllowFledgeHeader, net::HTTP_OK,
std::move(redirects));
EXPECT_FALSE(RunRequest());
EXPECT_EQ("Unexpected redirect on https://url.test/script.js.",
last_error_msg());
}
TEST_F(AuctionDownloaderTest, Success) {
AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, kUtf8Charset,
kAsciiResponseBody);
std::unique_ptr<std::string> body = RunRequest();
ASSERT_TRUE(body);
EXPECT_EQ(kAsciiResponseBody, *body);
}
// Test `AuctionDownloader::MimeType` values work as expected.
TEST_F(AuctionDownloaderTest, MimeType) {
// Javascript request, JSON response type.
AddResponse(&url_loader_factory_, url_, kJsonMimeType, kUtf8Charset,
kAsciiResponseBody);
EXPECT_FALSE(RunRequest());
EXPECT_EQ(
"Rejecting load of https://url.test/script.js due to unexpected MIME "
"type.",
last_error_msg());
// Javascript request, no response type.
AddResponse(&url_loader_factory_, url_, absl::nullopt, kUtf8Charset,
kAsciiResponseBody);
EXPECT_FALSE(RunRequest());
EXPECT_EQ(
"Rejecting load of https://url.test/script.js due to unexpected MIME "
"type.",
last_error_msg());
// Javascript request, empty response type.
AddResponse(&url_loader_factory_, url_, "", kUtf8Charset, kAsciiResponseBody);
EXPECT_FALSE(RunRequest());
EXPECT_EQ(
"Rejecting load of https://url.test/script.js due to unexpected MIME "
"type.",
last_error_msg());
// Javascript request, unknown response type.
AddResponse(&url_loader_factory_, url_, "blobfish", kUtf8Charset,
kAsciiResponseBody);
EXPECT_FALSE(RunRequest());
EXPECT_EQ(
"Rejecting load of https://url.test/script.js due to unexpected MIME "
"type.",
last_error_msg());
// JSON request, Javascript response type.
mime_type_ = AuctionDownloader::MimeType::kJson;
AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, kUtf8Charset,
kAsciiResponseBody);
EXPECT_FALSE(RunRequest());
EXPECT_EQ(
"Rejecting load of https://url.test/script.js due to unexpected MIME "
"type.",
last_error_msg());
// JSON request, no response type.
AddResponse(&url_loader_factory_, url_, absl::nullopt, kUtf8Charset,
kAsciiResponseBody);
EXPECT_FALSE(RunRequest());
EXPECT_EQ(
"Rejecting load of https://url.test/script.js due to unexpected MIME "
"type.",
last_error_msg());
// JSON request, empty response type.
AddResponse(&url_loader_factory_, url_, "", kUtf8Charset, kAsciiResponseBody);
EXPECT_FALSE(RunRequest());
EXPECT_EQ(
"Rejecting load of https://url.test/script.js due to unexpected MIME "
"type.",
last_error_msg());
// JSON request, unknown response type.
AddResponse(&url_loader_factory_, url_, "blobfish", kUtf8Charset,
kAsciiResponseBody);
EXPECT_FALSE(RunRequest());
EXPECT_EQ(
"Rejecting load of https://url.test/script.js due to unexpected MIME "
"type.",
last_error_msg());
// JSON request, JSON response type.
mime_type_ = AuctionDownloader::MimeType::kJson;
AddResponse(&url_loader_factory_, url_, kJsonMimeType, kUtf8Charset,
kAsciiResponseBody);
std::unique_ptr<std::string> body = RunRequest();
ASSERT_TRUE(body);
EXPECT_EQ(kAsciiResponseBody, *body);
}
TEST_F(AuctionDownloaderTest, MimeTypeWasm) {
mime_type_ = AuctionDownloader::MimeType::kWebAssembly;
// WASM request, Javascript response type.
AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, kUtf8Charset,
kAsciiResponseBody);
EXPECT_FALSE(RunRequest());
EXPECT_EQ(
"Rejecting load of https://url.test/script.js due to unexpected MIME "
"type.",
last_error_msg());
// WASM request, no response type.
AddResponse(&url_loader_factory_, url_, /*mime_type=*/absl::nullopt,
/*charset=*/absl::nullopt, kAsciiResponseBody);
EXPECT_FALSE(RunRequest());
EXPECT_EQ(
"Rejecting load of https://url.test/script.js due to unexpected MIME "
"type.",
last_error_msg());
// WASM request, JSON response type.
AddResponse(&url_loader_factory_, url_, kJsonMimeType, kUtf8Charset,
kAsciiResponseBody);
EXPECT_FALSE(RunRequest());
EXPECT_EQ(
"Rejecting load of https://url.test/script.js due to unexpected MIME "
"type.",
last_error_msg());
// WASM request, WASM response type.
AddResponse(&url_loader_factory_, url_, kWasmMimeType,
/*charset=*/absl::nullopt, kNonUtf8ResponseBody);
std::unique_ptr<std::string> body = RunRequest();
ASSERT_TRUE(body);
EXPECT_EQ(kNonUtf8ResponseBody, *body);
// Mimetypes are case insensitive.
AddResponse(&url_loader_factory_, url_, "Application/WasM",
/*charset=*/absl::nullopt, kNonUtf8ResponseBody);
body = RunRequest();
ASSERT_TRUE(body);
EXPECT_EQ(kNonUtf8ResponseBody, *body);
// No charset should be used (it's a binary format, after all).
AddResponse(&url_loader_factory_, url_, kWasmMimeType,
/*charset=*/kUtf8Charset, kUtf8ResponseBody);
body = RunRequest();
EXPECT_FALSE(RunRequest());
EXPECT_EQ(
"Rejecting load of https://url.test/script.js due to unexpected MIME "
"type.",
last_error_msg());
// Even an empty parameter list is to be rejected.
AddResponse(&url_loader_factory_, url_, "application/wasm;",
/*charset=*/absl::nullopt, kNonUtf8ResponseBody);
EXPECT_FALSE(RunRequest());
EXPECT_EQ(
"Rejecting load of https://url.test/script.js due to unexpected MIME "
"type.",
last_error_msg());
}
// Test all Javscript and JSON MIME type strings.
TEST_F(AuctionDownloaderTest, MimeTypeVariants) {
// All supported Javscript MIME types, copied from blink's mime_util.cc.
const char* kJavascriptMimeTypes[] = {
"application/ecmascript",
"application/javascript",
"application/x-ecmascript",
"application/x-javascript",
"text/ecmascript",
"text/javascript",
"text/javascript1.0",
"text/javascript1.1",
"text/javascript1.2",
"text/javascript1.3",
"text/javascript1.4",
"text/javascript1.5",
"text/jscript",
"text/livescript",
"text/x-ecmascript",
"text/x-javascript",
};
// Some MIME types (there are some wild cards in the matcher).
const char* kJsonMimeTypes[] = {
"application/json", "text/json",
"application/goat+json", "application/javascript+json",
"application/+json",
};
for (const char* javascript_type : kJavascriptMimeTypes) {
mime_type_ = AuctionDownloader::MimeType::kJavascript;
AddResponse(&url_loader_factory_, url_, javascript_type, kUtf8Charset,
kAsciiResponseBody);
std::unique_ptr<std::string> body = RunRequest();
ASSERT_TRUE(body);
EXPECT_EQ(kAsciiResponseBody, *body);
mime_type_ = AuctionDownloader::MimeType::kJson;
AddResponse(&url_loader_factory_, url_, javascript_type, kUtf8Charset,
kAsciiResponseBody);
EXPECT_FALSE(RunRequest());
EXPECT_EQ(
"Rejecting load of https://url.test/script.js due to unexpected MIME "
"type.",
last_error_msg());
}
for (const char* json_type : kJsonMimeTypes) {
mime_type_ = AuctionDownloader::MimeType::kJavascript;
AddResponse(&url_loader_factory_, url_, json_type, kUtf8Charset,
kAsciiResponseBody);
EXPECT_FALSE(RunRequest());
EXPECT_EQ(
"Rejecting load of https://url.test/script.js due to unexpected MIME "
"type.",
last_error_msg());
mime_type_ = AuctionDownloader::MimeType::kJson;
AddResponse(&url_loader_factory_, url_, json_type, kUtf8Charset,
kAsciiResponseBody);
std::unique_ptr<std::string> body = RunRequest();
ASSERT_TRUE(body);
EXPECT_EQ(kAsciiResponseBody, *body);
}
}
TEST_F(AuctionDownloaderTest, Charset) {
mime_type_ = AuctionDownloader::MimeType::kJson;
// Unknown/unsupported charsets should result in failure.
AddResponse(&url_loader_factory_, url_, kJsonMimeType, "fred",
kAsciiResponseBody);
EXPECT_FALSE(RunRequest());
EXPECT_EQ(
"Rejecting load of https://url.test/script.js due to unexpected charset.",
last_error_msg());
AddResponse(&url_loader_factory_, url_, kJsonMimeType, "iso-8859-1",
kAsciiResponseBody);
EXPECT_FALSE(RunRequest());
EXPECT_EQ(
"Rejecting load of https://url.test/script.js due to unexpected charset.",
last_error_msg());
// ASCII charset should restrict response bodies to ASCII characters.
mime_type_ = AuctionDownloader::MimeType::kJavascript;
AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, kAsciiCharset,
kUtf8ResponseBody);
EXPECT_FALSE(RunRequest());
EXPECT_EQ(
"Rejecting load of https://url.test/script.js due to unexpected charset.",
last_error_msg());
AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, kAsciiCharset,
kAsciiResponseBody);
std::unique_ptr<std::string> body = RunRequest();
ASSERT_TRUE(body);
EXPECT_EQ(kAsciiResponseBody, *body);
AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, kAsciiCharset,
kUtf8ResponseBody);
EXPECT_FALSE(RunRequest());
EXPECT_EQ(
"Rejecting load of https://url.test/script.js due to unexpected charset.",
last_error_msg());
AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, kAsciiCharset,
kNonUtf8ResponseBody);
EXPECT_FALSE(RunRequest());
EXPECT_EQ(
"Rejecting load of https://url.test/script.js due to unexpected charset.",
last_error_msg());
// UTF-8 charset should restrict response bodies to valid UTF-8 characters.
AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, kUtf8Charset,
kAsciiResponseBody);
body = RunRequest();
ASSERT_TRUE(body);
EXPECT_EQ(kAsciiResponseBody, *body);
AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, kUtf8Charset,
kUtf8ResponseBody);
body = RunRequest();
ASSERT_TRUE(body);
EXPECT_EQ(kUtf8ResponseBody, *body);
AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, kUtf8Charset,
kNonUtf8ResponseBody);
EXPECT_FALSE(RunRequest());
EXPECT_EQ(
"Rejecting load of https://url.test/script.js due to unexpected charset.",
last_error_msg());
// Null charset should act like UTF-8.
AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, absl::nullopt,
kAsciiResponseBody);
body = RunRequest();
ASSERT_TRUE(body);
EXPECT_EQ(kAsciiResponseBody, *body);
AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, absl::nullopt,
kUtf8ResponseBody);
body = RunRequest();
ASSERT_TRUE(body);
EXPECT_EQ(kUtf8ResponseBody, *body);
AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, absl::nullopt,
kNonUtf8ResponseBody);
EXPECT_FALSE(RunRequest());
EXPECT_EQ(
"Rejecting load of https://url.test/script.js due to unexpected charset.",
last_error_msg());
// Empty charset should act like UTF-8.
AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, "",
kAsciiResponseBody);
body = RunRequest();
ASSERT_TRUE(body);
EXPECT_EQ(kAsciiResponseBody, *body);
AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, "",
kUtf8ResponseBody);
body = RunRequest();
ASSERT_TRUE(body);
EXPECT_EQ(kUtf8ResponseBody, *body);
AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, "",
kNonUtf8ResponseBody);
EXPECT_FALSE(RunRequest());
EXPECT_EQ(
"Rejecting load of https://url.test/script.js due to unexpected charset.",
last_error_msg());
}
} // namespace
} // namespace auction_worklet