1 // Copyright 2023 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "net/shared_dictionary/shared_dictionary_network_transaction.h"
6
7 #include <optional>
8 #include <string>
9 #include <string_view>
10
11 #include "base/base64.h"
12 #include "base/feature_list.h"
13 #include "base/functional/bind.h"
14 #include "base/functional/callback_helpers.h"
15 #include "base/metrics/histogram_functions.h"
16 #include "base/metrics/histogram_macros.h"
17 #include "base/notreached.h"
18 #include "base/strings/strcat.h"
19 #include "base/strings/string_number_conversions.h"
20 #include "base/strings/string_util.h"
21 #include "base/types/expected.h"
22 #include "net/base/completion_once_callback.h"
23 #include "net/base/features.h"
24 #include "net/base/hash_value.h"
25 #include "net/base/io_buffer.h"
26 #include "net/base/load_flags.h"
27 #include "net/base/net_errors.h"
28 #include "net/base/transport_info.h"
29 #include "net/base/url_util.h"
30 #include "net/cert/x509_certificate.h"
31 #include "net/filter/brotli_source_stream.h"
32 #include "net/filter/filter_source_stream.h"
33 #include "net/filter/source_stream.h"
34 #include "net/filter/zstd_source_stream.h"
35 #include "net/http/http_request_info.h"
36 #include "net/http/structured_headers.h"
37 #include "net/shared_dictionary/shared_dictionary_constants.h"
38 #include "net/shared_dictionary/shared_dictionary_header_checker_source_stream.h"
39 #include "net/shared_dictionary/shared_dictionary_isolation_key.h"
40 #include "net/ssl/ssl_private_key.h"
41
42 namespace net {
43
44 namespace {
45
46 // Convert the interface from HttpTransaction to SourceStream.
47 class ProxyingSourceStream : public SourceStream {
48 public:
ProxyingSourceStream(HttpTransaction * transaction)49 explicit ProxyingSourceStream(HttpTransaction* transaction)
50 : SourceStream(SourceStream::TYPE_NONE), transaction_(transaction) {}
51
52 ProxyingSourceStream(const ProxyingSourceStream&) = delete;
53 ProxyingSourceStream& operator=(const ProxyingSourceStream&) = delete;
54
55 ~ProxyingSourceStream() override = default;
56
57 // SourceStream implementation:
Read(IOBuffer * dest_buffer,int buffer_size,CompletionOnceCallback callback)58 int Read(IOBuffer* dest_buffer,
59 int buffer_size,
60 CompletionOnceCallback callback) override {
61 DCHECK(transaction_);
62 return transaction_->Read(dest_buffer, buffer_size, std::move(callback));
63 }
64
Description() const65 std::string Description() const override { return std::string(); }
66
MayHaveMoreBytes() const67 bool MayHaveMoreBytes() const override { return true; }
68
69 private:
70 const raw_ptr<HttpTransaction> transaction_;
71 };
72
AddAcceptEncoding(HttpRequestHeaders * request_headers,std::string_view encoding_header)73 void AddAcceptEncoding(HttpRequestHeaders* request_headers,
74 std::string_view encoding_header) {
75 std::optional<std::string> accept_encoding =
76 request_headers->GetHeader(HttpRequestHeaders::kAcceptEncoding);
77 request_headers->SetHeader(
78 HttpRequestHeaders::kAcceptEncoding,
79 accept_encoding ? base::StrCat({*accept_encoding, ", ", encoding_header})
80 : std::string(encoding_header));
81 }
82
83 } // namespace
84
PendingReadTask(IOBuffer * buf,int buf_len,CompletionOnceCallback callback)85 SharedDictionaryNetworkTransaction::PendingReadTask::PendingReadTask(
86 IOBuffer* buf,
87 int buf_len,
88 CompletionOnceCallback callback)
89 : buf(buf), buf_len(buf_len), callback(std::move(callback)) {}
90
91 SharedDictionaryNetworkTransaction::PendingReadTask::~PendingReadTask() =
92 default;
93
SharedDictionaryNetworkTransaction(std::unique_ptr<HttpTransaction> network_transaction,bool enable_shared_zstd)94 SharedDictionaryNetworkTransaction::SharedDictionaryNetworkTransaction(
95 std::unique_ptr<HttpTransaction> network_transaction,
96 bool enable_shared_zstd)
97 : enable_shared_zstd_(enable_shared_zstd),
98 network_transaction_(std::move(network_transaction)) {
99 network_transaction_->SetConnectedCallback(
100 base::BindRepeating(&SharedDictionaryNetworkTransaction::OnConnected,
101 base::Unretained(this)));
102 }
103
104 SharedDictionaryNetworkTransaction::~SharedDictionaryNetworkTransaction() =
105 default;
106
Start(const HttpRequestInfo * request,CompletionOnceCallback callback,const NetLogWithSource & net_log)107 int SharedDictionaryNetworkTransaction::Start(const HttpRequestInfo* request,
108 CompletionOnceCallback callback,
109 const NetLogWithSource& net_log) {
110 if (!(request->load_flags & LOAD_CAN_USE_SHARED_DICTIONARY) ||
111 !request->dictionary_getter) {
112 return network_transaction_->Start(request, std::move(callback), net_log);
113 }
114 std::optional<SharedDictionaryIsolationKey> isolation_key =
115 SharedDictionaryIsolationKey::MaybeCreate(request->network_isolation_key,
116 request->frame_origin);
117 shared_dictionary_getter_ = base::BindRepeating(request->dictionary_getter,
118 isolation_key, request->url);
119
120 // Safe to bind unretained `this` because the callback is owned by
121 // `network_transaction_` which is owned by `this`.
122 network_transaction_->SetModifyRequestHeadersCallback(base::BindRepeating(
123 &SharedDictionaryNetworkTransaction::ModifyRequestHeaders,
124 base::Unretained(this), request->url));
125 return network_transaction_->Start(
126 request,
127 base::BindOnce(&SharedDictionaryNetworkTransaction::OnStartCompleted,
128 base::Unretained(this), std::move(callback)),
129 net_log);
130 }
131
132 SharedDictionaryNetworkTransaction::SharedDictionaryEncodingType
ParseSharedDictionaryEncodingType(const HttpResponseHeaders & headers)133 SharedDictionaryNetworkTransaction::ParseSharedDictionaryEncodingType(
134 const HttpResponseHeaders& headers) {
135 std::optional<std::string> content_encoding =
136 headers.GetNormalizedHeader("Content-Encoding");
137 if (!content_encoding) {
138 return SharedDictionaryEncodingType::kNotUsed;
139 } else if (content_encoding ==
140 shared_dictionary::kSharedBrotliContentEncodingName) {
141 return SharedDictionaryEncodingType::kSharedBrotli;
142 } else if (enable_shared_zstd_ &&
143 content_encoding ==
144 shared_dictionary::kSharedZstdContentEncodingName) {
145 return SharedDictionaryEncodingType::kSharedZstd;
146 }
147 return SharedDictionaryEncodingType::kNotUsed;
148 }
149
OnStartCompleted(CompletionOnceCallback callback,int result)150 void SharedDictionaryNetworkTransaction::OnStartCompleted(
151 CompletionOnceCallback callback,
152 int result) {
153 if (shared_dictionary_) {
154 base::UmaHistogramSparse(
155 base::StrCat({"Net.SharedDictionaryTransaction.NetResultWithDict.",
156 cert_is_issued_by_known_root_
157 ? "KnownRootCert"
158 : "UnknownRootCertOrNoCert"}),
159 -result);
160 }
161
162 if (result != OK || !shared_dictionary_) {
163 std::move(callback).Run(result);
164 return;
165 }
166
167 shared_dictionary_encoding_type_ = ParseSharedDictionaryEncodingType(
168 *network_transaction_->GetResponseInfo()->headers);
169 if (shared_dictionary_encoding_type_ ==
170 SharedDictionaryEncodingType::kNotUsed) {
171 std::move(callback).Run(result);
172 return;
173 }
174
175 shared_dictionary_used_response_info_ = std::make_unique<HttpResponseInfo>(
176 *network_transaction_->GetResponseInfo());
177 shared_dictionary_used_response_info_->did_use_shared_dictionary = true;
178 std::move(callback).Run(result);
179 }
180
ModifyRequestHeaders(const GURL & request_url,HttpRequestHeaders * request_headers)181 void SharedDictionaryNetworkTransaction::ModifyRequestHeaders(
182 const GURL& request_url,
183 HttpRequestHeaders* request_headers) {
184 // `shared_dictionary_` may have been already set if this transaction was
185 // restarted
186 if (!shared_dictionary_) {
187 shared_dictionary_ = shared_dictionary_getter_.Run();
188 }
189 if (!shared_dictionary_) {
190 return;
191 }
192
193 if (!IsLocalhost(request_url)) {
194 if (!base::FeatureList::IsEnabled(
195 features::kCompressionDictionaryTransportOverHttp1) &&
196 negotiated_protocol_ != kProtoHTTP2 &&
197 negotiated_protocol_ != kProtoQUIC) {
198 shared_dictionary_.reset();
199 return;
200 }
201 if (!base::FeatureList::IsEnabled(
202 features::kCompressionDictionaryTransportOverHttp2) &&
203 negotiated_protocol_ == kProtoHTTP2) {
204 shared_dictionary_.reset();
205 return;
206 }
207 }
208 if (base::FeatureList::IsEnabled(
209 features::kCompressionDictionaryTransportRequireKnownRootCert) &&
210 !cert_is_issued_by_known_root_ && !IsLocalhost(request_url)) {
211 shared_dictionary_.reset();
212 return;
213 }
214
215 // `is_shared_dictionary_read_allowed_callback_` triggers a notification of
216 // the shared dictionary usage to the browser process. So we need to call
217 // `is_shared_dictionary_read_allowed_callback_` after checking the result
218 // of `GetDictionarySync()`.
219 CHECK(is_shared_dictionary_read_allowed_callback_);
220 if (!is_shared_dictionary_read_allowed_callback_.Run()) {
221 shared_dictionary_.reset();
222 return;
223 }
224 dictionary_hash_base64_ = base::StrCat(
225 {":", base::Base64Encode(shared_dictionary_->hash().data), ":"});
226 request_headers->SetHeader(shared_dictionary::kAvailableDictionaryHeaderName,
227 dictionary_hash_base64_);
228 if (enable_shared_zstd_) {
229 AddAcceptEncoding(
230 request_headers,
231 base::StrCat({shared_dictionary::kSharedBrotliContentEncodingName, ", ",
232 shared_dictionary::kSharedZstdContentEncodingName}));
233 } else {
234 AddAcceptEncoding(request_headers,
235 shared_dictionary::kSharedBrotliContentEncodingName);
236 }
237
238 if (!shared_dictionary_->id().empty()) {
239 std::optional<std::string> serialized_id =
240 structured_headers::SerializeItem(shared_dictionary_->id());
241 if (serialized_id) {
242 request_headers->SetHeader("Dictionary-ID", *serialized_id);
243 }
244 }
245
246 if (dictionary_status_ == DictionaryStatus::kNoDictionary) {
247 dictionary_status_ = DictionaryStatus::kReading;
248 auto split_callback = base::SplitOnceCallback(base::BindOnce(
249 [](base::WeakPtr<SharedDictionaryNetworkTransaction> self,
250 base::Time read_start_time, int result) {
251 if (!self) {
252 bool succeeded = result == OK;
253 base::UmaHistogramTimes(
254 base::StrCat({"Net.SharedDictionaryTransaction."
255 "AbortedWhileReadingDictionary.",
256 succeeded ? "Success" : "Failure"}),
257 base::Time::Now() - read_start_time);
258 return;
259 }
260 self->OnReadSharedDictionary(read_start_time, result);
261 },
262 weak_factory_.GetWeakPtr(), /*read_start_time=*/base::Time::Now()));
263
264 int read_result =
265 shared_dictionary_->ReadAll(std::move(split_callback.first));
266 if (read_result != ERR_IO_PENDING) {
267 std::move(split_callback.second).Run(read_result);
268 }
269 }
270 }
271
OnReadSharedDictionary(base::Time read_start_time,int result)272 void SharedDictionaryNetworkTransaction::OnReadSharedDictionary(
273 base::Time read_start_time,
274 int result) {
275 bool succeeded = result == OK;
276 base::UmaHistogramTimes(
277 base::StrCat({"Net.SharedDictionaryTransaction.DictionaryReadLatency.",
278 succeeded ? "Success" : "Failure"}),
279 base::Time::Now() - read_start_time);
280 if (!succeeded) {
281 dictionary_status_ = DictionaryStatus::kFailed;
282 } else {
283 dictionary_status_ = DictionaryStatus::kFinished;
284 CHECK(shared_dictionary_->data());
285 }
286 if (pending_read_task_) {
287 auto task = std::move(pending_read_task_);
288 auto split_callback = base::SplitOnceCallback(std::move(task->callback));
289 int ret =
290 Read(task->buf.get(), task->buf_len, std::move(split_callback.first));
291 if (ret != ERR_IO_PENDING) {
292 std::move(split_callback.second).Run(ret);
293 }
294 }
295 }
296
RestartIgnoringLastError(CompletionOnceCallback callback)297 int SharedDictionaryNetworkTransaction::RestartIgnoringLastError(
298 CompletionOnceCallback callback) {
299 shared_dictionary_used_response_info_.reset();
300 return network_transaction_->RestartIgnoringLastError(
301 base::BindOnce(&SharedDictionaryNetworkTransaction::OnStartCompleted,
302 base::Unretained(this), std::move(callback)));
303 }
304
RestartWithCertificate(scoped_refptr<X509Certificate> client_cert,scoped_refptr<SSLPrivateKey> client_private_key,CompletionOnceCallback callback)305 int SharedDictionaryNetworkTransaction::RestartWithCertificate(
306 scoped_refptr<X509Certificate> client_cert,
307 scoped_refptr<SSLPrivateKey> client_private_key,
308 CompletionOnceCallback callback) {
309 shared_dictionary_used_response_info_.reset();
310 return network_transaction_->RestartWithCertificate(
311 std::move(client_cert), std::move(client_private_key),
312 base::BindOnce(&SharedDictionaryNetworkTransaction::OnStartCompleted,
313 base::Unretained(this), std::move(callback)));
314 }
315
RestartWithAuth(const AuthCredentials & credentials,CompletionOnceCallback callback)316 int SharedDictionaryNetworkTransaction::RestartWithAuth(
317 const AuthCredentials& credentials,
318 CompletionOnceCallback callback) {
319 shared_dictionary_used_response_info_.reset();
320 return network_transaction_->RestartWithAuth(
321 credentials,
322 base::BindOnce(&SharedDictionaryNetworkTransaction::OnStartCompleted,
323 base::Unretained(this), std::move(callback)));
324 }
325
IsReadyToRestartForAuth()326 bool SharedDictionaryNetworkTransaction::IsReadyToRestartForAuth() {
327 return network_transaction_->IsReadyToRestartForAuth();
328 }
329
Read(IOBuffer * buf,int buf_len,CompletionOnceCallback callback)330 int SharedDictionaryNetworkTransaction::Read(IOBuffer* buf,
331 int buf_len,
332 CompletionOnceCallback callback) {
333 if (!shared_dictionary_used_response_info_) {
334 return network_transaction_->Read(buf, buf_len, std::move(callback));
335 }
336
337 switch (dictionary_status_) {
338 case DictionaryStatus::kNoDictionary:
339 NOTREACHED();
340 case DictionaryStatus::kReading:
341 CHECK(!pending_read_task_);
342 pending_read_task_ =
343 std::make_unique<PendingReadTask>(buf, buf_len, std::move(callback));
344 return ERR_IO_PENDING;
345 case DictionaryStatus::kFinished:
346 if (!shared_compression_stream_) {
347 // Wrap the source `network_transaction_` with a
348 // SharedDictionaryHeaderCheckerSourceStream to check the header
349 // of Dictionary-Compressed stream.
350 std::unique_ptr<SourceStream> header_checker_source_stream =
351 std::make_unique<SharedDictionaryHeaderCheckerSourceStream>(
352 std::make_unique<ProxyingSourceStream>(
353 network_transaction_.get()),
354 shared_dictionary_encoding_type_ ==
355 SharedDictionaryEncodingType::kSharedBrotli
356 ? SharedDictionaryHeaderCheckerSourceStream::Type::
357 kDictionaryCompressedBrotli
358 : SharedDictionaryHeaderCheckerSourceStream::Type::
359 kDictionaryCompressedZstd,
360 shared_dictionary_->hash());
361 if (shared_dictionary_encoding_type_ ==
362 SharedDictionaryEncodingType::kSharedBrotli) {
363 SCOPED_UMA_HISTOGRAM_TIMER_MICROS(
364 "Network.SharedDictionary."
365 "CreateBrotliSourceStreamWithDictionary");
366 shared_compression_stream_ = CreateBrotliSourceStreamWithDictionary(
367 std::move(header_checker_source_stream),
368 shared_dictionary_->data(), shared_dictionary_->size());
369 } else if (shared_dictionary_encoding_type_ ==
370 SharedDictionaryEncodingType::kSharedZstd) {
371 SCOPED_UMA_HISTOGRAM_TIMER_MICROS(
372 "Network.SharedDictionary.CreateZstdSourceStreamWithDictionary");
373 shared_compression_stream_ = CreateZstdSourceStreamWithDictionary(
374 std::move(header_checker_source_stream),
375 shared_dictionary_->data(), shared_dictionary_->size());
376 }
377
378 UMA_HISTOGRAM_ENUMERATION("Network.SharedDictionary.EncodingType",
379 shared_dictionary_encoding_type_);
380 }
381 // When NET_DISABLE_BROTLI or NET_DISABLE_ZSTD is set,
382 // `shared_compression_stream_` can be null.
383 if (!shared_compression_stream_) {
384 return ERR_CONTENT_DECODING_FAILED;
385 }
386 return shared_compression_stream_->Read(buf, buf_len,
387 std::move(callback));
388 case DictionaryStatus::kFailed:
389 return ERR_DICTIONARY_LOAD_FAILED;
390 }
391 }
392
StopCaching()393 void SharedDictionaryNetworkTransaction::StopCaching() {
394 network_transaction_->StopCaching();
395 }
396
GetTotalReceivedBytes() const397 int64_t SharedDictionaryNetworkTransaction::GetTotalReceivedBytes() const {
398 return network_transaction_->GetTotalReceivedBytes();
399 }
400
GetTotalSentBytes() const401 int64_t SharedDictionaryNetworkTransaction::GetTotalSentBytes() const {
402 return network_transaction_->GetTotalSentBytes();
403 }
404
GetReceivedBodyBytes() const405 int64_t SharedDictionaryNetworkTransaction::GetReceivedBodyBytes() const {
406 return network_transaction_->GetReceivedBodyBytes();
407 }
408
DoneReading()409 void SharedDictionaryNetworkTransaction::DoneReading() {
410 network_transaction_->DoneReading();
411 }
412
GetResponseInfo() const413 const HttpResponseInfo* SharedDictionaryNetworkTransaction::GetResponseInfo()
414 const {
415 if (shared_dictionary_used_response_info_) {
416 return shared_dictionary_used_response_info_.get();
417 }
418 return network_transaction_->GetResponseInfo();
419 }
420
GetLoadState() const421 LoadState SharedDictionaryNetworkTransaction::GetLoadState() const {
422 return network_transaction_->GetLoadState();
423 }
424
SetQuicServerInfo(QuicServerInfo * quic_server_info)425 void SharedDictionaryNetworkTransaction::SetQuicServerInfo(
426 QuicServerInfo* quic_server_info) {
427 network_transaction_->SetQuicServerInfo(quic_server_info);
428 }
429
GetLoadTimingInfo(LoadTimingInfo * load_timing_info) const430 bool SharedDictionaryNetworkTransaction::GetLoadTimingInfo(
431 LoadTimingInfo* load_timing_info) const {
432 return network_transaction_->GetLoadTimingInfo(load_timing_info);
433 }
434
GetRemoteEndpoint(IPEndPoint * endpoint) const435 bool SharedDictionaryNetworkTransaction::GetRemoteEndpoint(
436 IPEndPoint* endpoint) const {
437 return network_transaction_->GetRemoteEndpoint(endpoint);
438 }
439
PopulateNetErrorDetails(NetErrorDetails * details) const440 void SharedDictionaryNetworkTransaction::PopulateNetErrorDetails(
441 NetErrorDetails* details) const {
442 return network_transaction_->PopulateNetErrorDetails(details);
443 }
444
SetPriority(RequestPriority priority)445 void SharedDictionaryNetworkTransaction::SetPriority(RequestPriority priority) {
446 network_transaction_->SetPriority(priority);
447 }
448
449 void SharedDictionaryNetworkTransaction::
SetWebSocketHandshakeStreamCreateHelper(WebSocketHandshakeStreamBase::CreateHelper * create_helper)450 SetWebSocketHandshakeStreamCreateHelper(
451 WebSocketHandshakeStreamBase::CreateHelper* create_helper) {
452 network_transaction_->SetWebSocketHandshakeStreamCreateHelper(create_helper);
453 }
454
SetBeforeNetworkStartCallback(BeforeNetworkStartCallback callback)455 void SharedDictionaryNetworkTransaction::SetBeforeNetworkStartCallback(
456 BeforeNetworkStartCallback callback) {
457 network_transaction_->SetBeforeNetworkStartCallback(std::move(callback));
458 }
459
SetRequestHeadersCallback(RequestHeadersCallback callback)460 void SharedDictionaryNetworkTransaction::SetRequestHeadersCallback(
461 RequestHeadersCallback callback) {
462 network_transaction_->SetRequestHeadersCallback(std::move(callback));
463 }
464
SetResponseHeadersCallback(ResponseHeadersCallback callback)465 void SharedDictionaryNetworkTransaction::SetResponseHeadersCallback(
466 ResponseHeadersCallback callback) {
467 network_transaction_->SetResponseHeadersCallback(std::move(callback));
468 }
469
SetEarlyResponseHeadersCallback(ResponseHeadersCallback callback)470 void SharedDictionaryNetworkTransaction::SetEarlyResponseHeadersCallback(
471 ResponseHeadersCallback callback) {
472 network_transaction_->SetEarlyResponseHeadersCallback(std::move(callback));
473 }
474
SetConnectedCallback(const ConnectedCallback & callback)475 void SharedDictionaryNetworkTransaction::SetConnectedCallback(
476 const ConnectedCallback& callback) {
477 connected_callback_ = callback;
478 }
479
ResumeNetworkStart()480 int SharedDictionaryNetworkTransaction::ResumeNetworkStart() {
481 return network_transaction_->ResumeNetworkStart();
482 }
483
SetModifyRequestHeadersCallback(base::RepeatingCallback<void (HttpRequestHeaders *)> callback)484 void SharedDictionaryNetworkTransaction::SetModifyRequestHeadersCallback(
485 base::RepeatingCallback<void(HttpRequestHeaders*)> callback) {
486 // This method should not be called for this class.
487 NOTREACHED();
488 }
489
490 void SharedDictionaryNetworkTransaction::
SetIsSharedDictionaryReadAllowedCallback(base::RepeatingCallback<bool ()> callback)491 SetIsSharedDictionaryReadAllowedCallback(
492 base::RepeatingCallback<bool()> callback) {
493 is_shared_dictionary_read_allowed_callback_ = std::move(callback);
494 }
495
GetConnectionAttempts() const496 ConnectionAttempts SharedDictionaryNetworkTransaction::GetConnectionAttempts()
497 const {
498 return network_transaction_->GetConnectionAttempts();
499 }
500
CloseConnectionOnDestruction()501 void SharedDictionaryNetworkTransaction::CloseConnectionOnDestruction() {
502 network_transaction_->CloseConnectionOnDestruction();
503 }
504
IsMdlMatchForMetrics() const505 bool SharedDictionaryNetworkTransaction::IsMdlMatchForMetrics() const {
506 return network_transaction_->IsMdlMatchForMetrics();
507 }
508
OnConnected(const TransportInfo & info,CompletionOnceCallback callback)509 int SharedDictionaryNetworkTransaction::OnConnected(
510 const TransportInfo& info,
511 CompletionOnceCallback callback) {
512 cert_is_issued_by_known_root_ = info.cert_is_issued_by_known_root;
513 negotiated_protocol_ = info.negotiated_protocol;
514
515 if (connected_callback_) {
516 return connected_callback_.Run(info, std::move(callback));
517 }
518 return OK;
519 }
520
521 } // namespace net
522