blob: 7e7dd890a7aa3898953716199bb1702daa246e1f [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/web_package/mock_signed_exchange_handler.h"
#include <memory>
#include <string_view>
#include <utility>
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/task/sequenced_task_runner.h"
#include "content/browser/web_package/prefetched_signed_exchange_cache_entry.h"
#include "content/browser/web_package/signed_exchange_cert_fetcher_factory.h"
#include "net/filter/source_stream.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_util.h"
namespace content {
namespace {
// A mostly pass-through wrapper around another `net::SourceStream`, except that
// the wrapper strips an initial sequence of bytes (called the `prefix_to_strip`
// in the constructor).
//
// The wrapper is used to recover the inner resource from the body of a mocked
// SXG payload, because the mocked SXG payload is prefixed with `kMockSxgPrefix`
// so that it doesn't sniff as HTML when processed by ORB. SXG in general does
// not look like HTML (because of CBOR/binary encoding [1]), so this kind of
// prefixing is somewhat desirable in general, even though real SXGs don't
// really begin with `kMockSxgPrefix` bytes. This might remain desirable even
// once SXG prefetches switch from `no-cors` to `cors` mode (see
// https://crbug.com/1316660).
//
// [1] https://web.dev/signed-exchanges/#the-sxg-format
class PrefixStrippingSourceStream : public net::SourceStream {
public:
PrefixStrippingSourceStream(std::string_view prefix_to_strip,
std::unique_ptr<net::SourceStream> stream_to_wrap)
: net::SourceStream(stream_to_wrap->type()),
remaining_prefix_to_strip_(prefix_to_strip),
wrapped_stream_(std::move(stream_to_wrap)),
weak_factory_(this) {
DCHECK(wrapped_stream_);
}
~PrefixStrippingSourceStream() override = default;
int Read(net::IOBuffer* dest_buffer,
int buffer_size,
net::CompletionOnceCallback callback) override {
DCHECK(dest_buffer);
DCHECK_GT(buffer_size, 0);
DCHECK(callback);
// `callback` might sometimes need to survive more than 1 wrapped `Read`.
// To make this easier to handle, `callback` is put into a ref-counted
// PendingRead struct. (Performance implications of an extra heap
// allocation are ignored for this test-only code.)
auto pending_read = base::MakeRefCounted<PendingRead>();
pending_read->dest_buffer = dest_buffer;
pending_read->buffer_size = buffer_size;
pending_read->callback = std::move(callback);
return Read(pending_read);
}
std::string Description() const override {
return wrapped_stream_->Description();
}
bool MayHaveMoreBytes() const override {
return wrapped_stream_->MayHaveMoreBytes();
}
private:
// Stores arguments of a call to the public `PrefixStrippingSourceStream`'s
// `Read` method (during duration of 1 or more sync-or-async calls to the
// `wrapped_stream_`'s `Read` method, while we are waiting to read and skip
// the initial `prefix_to_strip` bytes).
struct PendingRead : public base::RefCountedThreadSafe<PendingRead> {
scoped_refptr<net::IOBuffer> dest_buffer;
int buffer_size;
net::CompletionOnceCallback callback;
private:
friend class base::RefCountedThreadSafe<PendingRead>;
virtual ~PendingRead() = default;
};
int Read(const scoped_refptr<PendingRead>& pending_read) {
DCHECK(pending_read);
if (remaining_prefix_to_strip_.empty()) {
return wrapped_stream_->Read(pending_read->dest_buffer.get(),
pending_read->buffer_size,
std::move(pending_read->callback));
}
int number_of_bytes_read = wrapped_stream_->Read(
pending_read->dest_buffer.get(), pending_read->buffer_size,
base::BindOnce(&PrefixStrippingSourceStream::OnWrappedReadCompleted,
weak_factory_.GetWeakPtr(), pending_read));
int number_of_post_prefix_bytes_read =
ConsumeWrappedRead(pending_read, number_of_bytes_read);
return number_of_post_prefix_bytes_read;
}
void OnWrappedReadCompleted(const scoped_refptr<PendingRead>& pending_read,
int number_of_bytes_read) {
DCHECK(pending_read);
DCHECK_NE(number_of_bytes_read, net::ERR_IO_PENDING);
int number_of_post_prefix_bytes_read =
ConsumeWrappedRead(pending_read, number_of_bytes_read);
if (number_of_post_prefix_bytes_read != net::ERR_IO_PENDING) {
std::move(pending_read->callback).Run(number_of_post_prefix_bytes_read);
}
}
// Processes the results of a sync or async call to the `wrapped_stream_`'s
// Read method:
// 1. Consumes bytes read from the `wrapped_stream_`, discarding them as long
// as they match the `remaining_prefix_to_strip_`.
// 2. If needed, starts more `Read`s from the `wrapped_stream_` and only
// returns a non-negative integer after post-prefix bytes are available.
int ConsumeWrappedRead(const scoped_refptr<PendingRead>& pending_read,
int number_of_bytes_read) {
DCHECK(pending_read);
// Propagate errors (and ERR_IO_PENDING indicator of async results).
// Only consider successful reads below.
if (number_of_bytes_read < 0) {
return number_of_bytes_read;
}
// Strip `remaining_prefix_to_strip_` bytes from `bytes_read`.
std::string_view bytes_read(pending_read->dest_buffer->data(),
number_of_bytes_read);
int maybe_consumed_bytes =
std::min(bytes_read.size(), remaining_prefix_to_strip_.size());
CHECK_EQ(remaining_prefix_to_strip_.substr(0, maybe_consumed_bytes),
bytes_read.substr(0, maybe_consumed_bytes))
<< "Unexpectedly mismatched prefix - can't continue (because some "
<< "of the wrapped bytes may have been already discarded earlier)";
remaining_prefix_to_strip_.remove_prefix(maybe_consumed_bytes);
bytes_read.remove_prefix(maybe_consumed_bytes);
// If ready, then return the number of real, post-prefix bytes read.
//
// Care is taken to avoid incorrectly reporting an EOF (zero bytes read)
// right after stripping the prefix, but before reading all the data from
// the `wrapped_stream_`.
bool maybe_incorrect_eof = bytes_read.empty() && MayHaveMoreBytes();
if (remaining_prefix_to_strip_.empty() && !maybe_incorrect_eof) {
// Source and destination may overlap - need to use `memmove`.
memmove(pending_read->dest_buffer->data(), bytes_read.data(),
bytes_read.size());
return bytes_read.size();
}
// Still have `remaining_prefix_to_strip_` - need to `Read` more bytes from
// the `wrapped_stream_`.
return Read(pending_read);
}
std::string_view remaining_prefix_to_strip_;
const std::unique_ptr<net::SourceStream> wrapped_stream_;
base::WeakPtrFactory<PrefixStrippingSourceStream> weak_factory_;
};
} // namespace
MockSignedExchangeHandlerParams::MockSignedExchangeHandlerParams(
const GURL& outer_url,
SignedExchangeLoadResult result,
net::Error error,
const GURL& inner_url,
const std::string& mime_type,
std::vector<std::pair<std::string, std::string>> response_headers,
const net::SHA256HashValue& header_integrity,
const base::Time& signature_expire_time)
: outer_url(outer_url),
result(result),
error(error),
inner_url(inner_url),
mime_type(mime_type),
response_headers(std::move(response_headers)),
header_integrity(header_integrity),
signature_expire_time(signature_expire_time.is_null()
? base::Time::Now() + base::Days(1)
: signature_expire_time) {}
MockSignedExchangeHandlerParams::MockSignedExchangeHandlerParams(
const MockSignedExchangeHandlerParams& other) = default;
MockSignedExchangeHandlerParams::~MockSignedExchangeHandlerParams() = default;
MockSignedExchangeHandler::MockSignedExchangeHandler(
const MockSignedExchangeHandlerParams& params,
std::unique_ptr<net::SourceStream> body,
ExchangeHeadersCallback headers_callback)
: header_integrity_(params.header_integrity),
signature_expire_time_(params.signature_expire_time),
cert_url_(params.outer_url.Resolve("mock_cert")),
cert_server_ip_address_(net::IPAddress::IPv4Localhost()) {
auto head = network::mojom::URLResponseHead::New();
if (params.error == net::OK) {
head->headers =
base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 200 OK");
head->mime_type = params.mime_type;
head->headers->SetHeader("Content-type", params.mime_type);
for (const auto& header : params.response_headers)
head->headers->AddHeader(header.first, header.second);
head->is_signed_exchange_inner_response = true;
head->content_length = head->headers->GetContentLength();
}
body = std::make_unique<PrefixStrippingSourceStream>(kMockSxgPrefix,
std::move(body));
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(std::move(headers_callback), params.result, params.error,
params.inner_url, std::move(head), std::move(body)));
}
bool MockSignedExchangeHandler::GetSignedExchangeInfoForPrefetchCache(
PrefetchedSignedExchangeCacheEntry& entry) const {
entry.SetHeaderIntegrity(
std::make_unique<net::SHA256HashValue>(header_integrity_));
entry.SetSignatureExpireTime(signature_expire_time_);
entry.SetCertUrl(cert_url_);
entry.SetCertServerIPAddress(cert_server_ip_address_);
return true;
}
MockSignedExchangeHandler::~MockSignedExchangeHandler() {}
MockSignedExchangeHandlerFactory::MockSignedExchangeHandlerFactory(
std::vector<MockSignedExchangeHandlerParams> params_list)
: params_list_(std::move(params_list)) {}
MockSignedExchangeHandlerFactory::~MockSignedExchangeHandlerFactory() = default;
std::unique_ptr<SignedExchangeHandler> MockSignedExchangeHandlerFactory::Create(
const GURL& outer_url,
std::unique_ptr<net::SourceStream> body,
ExchangeHeadersCallback headers_callback,
std::unique_ptr<SignedExchangeCertFetcherFactory> cert_fetcher_factory) {
for (const auto& params : params_list_) {
if (params.outer_url == outer_url) {
return std::make_unique<MockSignedExchangeHandler>(
params, std::move(body), std::move(headers_callback));
}
}
NOTREACHED();
}
} // namespace content