1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
content / browser / web_package / mock_signed_exchange_handler.cc [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_IN_MIGRATION();
return nullptr;
}
} // namespace content