xref: /aosp_15_r20/external/cronet/components/cronet/cronet_url_request.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2014 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 "components/cronet/cronet_url_request.h"
6 
7 #include <limits>
8 #include <utility>
9 
10 #include "base/functional/bind.h"
11 #include "base/location.h"
12 #include "base/logging.h"
13 #include "build/build_config.h"
14 #include "components/cronet/cronet_context.h"
15 #include "net/base/idempotency.h"
16 #include "net/base/io_buffer.h"
17 #include "net/base/load_flags.h"
18 #include "net/base/load_states.h"
19 #include "net/base/net_errors.h"
20 #include "net/base/proxy_chain.h"
21 #include "net/base/proxy_server.h"
22 #include "net/base/request_priority.h"
23 #include "net/base/upload_data_stream.h"
24 #include "net/cert/cert_status_flags.h"
25 #include "net/cert/x509_certificate.h"
26 #include "net/http/http_response_headers.h"
27 #include "net/http/http_status_code.h"
28 #include "net/http/http_util.h"
29 #include "net/ssl/ssl_info.h"
30 #include "net/ssl/ssl_private_key.h"
31 #include "net/third_party/quiche/src/quiche/quic/core/quic_packets.h"
32 #include "net/traffic_annotation/network_traffic_annotation.h"
33 #include "net/url_request/redirect_info.h"
34 #include "net/url_request/url_request_context.h"
35 
36 namespace cronet {
37 
38 namespace {
39 
40 // Returns the string representation of the HostPortPair of the proxy server
41 // that was used to fetch the response.
GetProxy(const net::HttpResponseInfo & info)42 std::string GetProxy(const net::HttpResponseInfo& info) {
43   if (!info.proxy_chain.IsValid() || info.proxy_chain.is_direct()) {
44     return net::HostPortPair().ToString();
45   }
46   CHECK(info.proxy_chain.is_single_proxy());
47   return info.proxy_chain.First().host_port_pair().ToString();
48 }
49 
CalculateLoadFlags(int load_flags,bool disable_cache,bool disable_connection_migration)50 int CalculateLoadFlags(int load_flags,
51                        bool disable_cache,
52                        bool disable_connection_migration) {
53   if (disable_cache)
54     load_flags |= net::LOAD_DISABLE_CACHE;
55   if (disable_connection_migration)
56     load_flags |= net::LOAD_DISABLE_CONNECTION_MIGRATION_TO_CELLULAR;
57   return load_flags;
58 }
59 
60 }  // namespace
61 
CronetURLRequest(CronetContext * context,std::unique_ptr<Callback> callback,const GURL & url,net::RequestPriority priority,bool disable_cache,bool disable_connection_migration,bool traffic_stats_tag_set,int32_t traffic_stats_tag,bool traffic_stats_uid_set,int32_t traffic_stats_uid,net::Idempotency idempotency,net::handles::NetworkHandle network)62 CronetURLRequest::CronetURLRequest(CronetContext* context,
63                                    std::unique_ptr<Callback> callback,
64                                    const GURL& url,
65                                    net::RequestPriority priority,
66                                    bool disable_cache,
67                                    bool disable_connection_migration,
68                                    bool traffic_stats_tag_set,
69                                    int32_t traffic_stats_tag,
70                                    bool traffic_stats_uid_set,
71                                    int32_t traffic_stats_uid,
72                                    net::Idempotency idempotency,
73                                    net::handles::NetworkHandle network)
74     : context_(context),
75       network_tasks_(std::move(callback),
76                      url,
77                      priority,
78                      CalculateLoadFlags(context->default_load_flags(),
79                                         disable_cache,
80                                         disable_connection_migration),
81                      traffic_stats_tag_set,
82                      traffic_stats_tag,
83                      traffic_stats_uid_set,
84                      traffic_stats_uid,
85                      idempotency,
86                      network),
87       initial_method_("GET"),
88       initial_request_headers_(std::make_unique<net::HttpRequestHeaders>()) {
89   DCHECK(!context_->IsOnNetworkThread());
90 }
91 
~CronetURLRequest()92 CronetURLRequest::~CronetURLRequest() {
93   DCHECK(context_->IsOnNetworkThread());
94 }
95 
SetHttpMethod(const std::string & method)96 bool CronetURLRequest::SetHttpMethod(const std::string& method) {
97   DCHECK(!context_->IsOnNetworkThread());
98   // Http method is a token, just as header name.
99   if (!net::HttpUtil::IsValidHeaderName(method))
100     return false;
101   initial_method_ = method;
102   return true;
103 }
104 
AddRequestHeader(const std::string & name,const std::string & value)105 bool CronetURLRequest::AddRequestHeader(const std::string& name,
106                                         const std::string& value) {
107   DCHECK(!context_->IsOnNetworkThread());
108   DCHECK(initial_request_headers_);
109   if (!net::HttpUtil::IsValidHeaderName(name) ||
110       !net::HttpUtil::IsValidHeaderValue(value)) {
111     return false;
112   }
113   initial_request_headers_->SetHeader(name, value);
114   return true;
115 }
116 
SetUpload(std::unique_ptr<net::UploadDataStream> upload)117 void CronetURLRequest::SetUpload(
118     std::unique_ptr<net::UploadDataStream> upload) {
119   DCHECK(!context_->IsOnNetworkThread());
120   DCHECK(!upload_);
121   upload_ = std::move(upload);
122 }
123 
Start()124 void CronetURLRequest::Start() {
125   DCHECK(!context_->IsOnNetworkThread());
126   context_->PostTaskToNetworkThread(
127       FROM_HERE,
128       base::BindOnce(&CronetURLRequest::NetworkTasks::Start,
129                      base::Unretained(&network_tasks_),
130                      base::Unretained(context_), initial_method_,
131                      std::move(initial_request_headers_), std::move(upload_)));
132 }
133 
GetStatus(OnStatusCallback callback) const134 void CronetURLRequest::GetStatus(OnStatusCallback callback) const {
135   context_->PostTaskToNetworkThread(
136       FROM_HERE,
137       base::BindOnce(&CronetURLRequest::NetworkTasks::GetStatus,
138                      base::Unretained(&network_tasks_), std::move(callback)));
139 }
140 
FollowDeferredRedirect()141 void CronetURLRequest::FollowDeferredRedirect() {
142   context_->PostTaskToNetworkThread(
143       FROM_HERE,
144       base::BindOnce(&CronetURLRequest::NetworkTasks::FollowDeferredRedirect,
145                      base::Unretained(&network_tasks_)));
146 }
147 
ReadData(net::IOBuffer * raw_read_buffer,int max_size)148 bool CronetURLRequest::ReadData(net::IOBuffer* raw_read_buffer, int max_size) {
149   // TODO(crbug.com/40847077): Change to DCHECK() or remove after bug
150   // is fixed.
151   CHECK(max_size == 0 || (raw_read_buffer && raw_read_buffer->data()));
152 
153   scoped_refptr<net::IOBuffer> read_buffer(raw_read_buffer);
154   context_->PostTaskToNetworkThread(
155       FROM_HERE,
156       base::BindOnce(&CronetURLRequest::NetworkTasks::ReadData,
157                      base::Unretained(&network_tasks_), read_buffer, max_size));
158   return true;
159 }
160 
Destroy(bool send_on_canceled)161 void CronetURLRequest::Destroy(bool send_on_canceled) {
162   // Destroy could be called from any thread, including network thread (if
163   // posting task to executor throws an exception), but is posted, so |this|
164   // is valid until calling task is complete. Destroy() must be called from
165   // within a synchronized block that guarantees no future posts to the
166   // network thread with the request pointer.
167   context_->PostTaskToNetworkThread(
168       FROM_HERE, base::BindOnce(&CronetURLRequest::NetworkTasks::Destroy,
169                                 base::Unretained(&network_tasks_),
170                                 base::Unretained(this), send_on_canceled));
171 }
172 
MaybeReportMetricsAndRunCallback(base::OnceClosure callback)173 void CronetURLRequest::MaybeReportMetricsAndRunCallback(
174     base::OnceClosure callback) {
175   context_->PostTaskToNetworkThread(
176       FROM_HERE,
177       base::BindOnce(
178           &CronetURLRequest::NetworkTasks::MaybeReportMetricsAndRunCallback,
179           base::Unretained(&network_tasks_), std::move(callback)));
180 }
181 
NetworkTasks(std::unique_ptr<Callback> callback,const GURL & url,net::RequestPriority priority,int load_flags,bool traffic_stats_tag_set,int32_t traffic_stats_tag,bool traffic_stats_uid_set,int32_t traffic_stats_uid,net::Idempotency idempotency,net::handles::NetworkHandle network)182 CronetURLRequest::NetworkTasks::NetworkTasks(
183     std::unique_ptr<Callback> callback,
184     const GURL& url,
185     net::RequestPriority priority,
186     int load_flags,
187     bool traffic_stats_tag_set,
188     int32_t traffic_stats_tag,
189     bool traffic_stats_uid_set,
190     int32_t traffic_stats_uid,
191     net::Idempotency idempotency,
192     net::handles::NetworkHandle network)
193     : callback_(std::move(callback)),
194       initial_url_(url),
195       initial_priority_(priority),
196       initial_load_flags_(load_flags),
197       received_byte_count_from_redirects_(0l),
198       error_reported_(false),
199       metrics_reported_(false),
200       traffic_stats_tag_set_(traffic_stats_tag_set),
201       traffic_stats_tag_(traffic_stats_tag),
202       traffic_stats_uid_set_(traffic_stats_uid_set),
203       traffic_stats_uid_(traffic_stats_uid),
204       idempotency_(idempotency),
205       network_(network) {
206   DETACH_FROM_THREAD(network_thread_checker_);
207 }
208 
~NetworkTasks()209 CronetURLRequest::NetworkTasks::~NetworkTasks() {
210   DCHECK_CALLED_ON_VALID_THREAD(network_thread_checker_);
211 }
212 
OnReceivedRedirect(net::URLRequest * request,const net::RedirectInfo & redirect_info,bool * defer_redirect)213 void CronetURLRequest::NetworkTasks::OnReceivedRedirect(
214     net::URLRequest* request,
215     const net::RedirectInfo& redirect_info,
216     bool* defer_redirect) {
217   DCHECK_CALLED_ON_VALID_THREAD(network_thread_checker_);
218   received_byte_count_from_redirects_ += request->GetTotalReceivedBytes();
219   callback_->OnReceivedRedirect(
220       redirect_info.new_url.spec(), redirect_info.status_code,
221       request->response_headers()->GetStatusText(), request->response_headers(),
222       request->response_info().was_cached,
223       request->response_info().alpn_negotiated_protocol,
224       GetProxy(request->response_info()), received_byte_count_from_redirects_);
225   *defer_redirect = true;
226 }
227 
OnCertificateRequested(net::URLRequest * request,net::SSLCertRequestInfo * cert_request_info)228 void CronetURLRequest::NetworkTasks::OnCertificateRequested(
229     net::URLRequest* request,
230     net::SSLCertRequestInfo* cert_request_info) {
231   DCHECK_CALLED_ON_VALID_THREAD(network_thread_checker_);
232   // Cronet does not support client certificates.
233   request->ContinueWithCertificate(nullptr, nullptr);
234 }
235 
OnSSLCertificateError(net::URLRequest * request,int net_error,const net::SSLInfo & ssl_info,bool fatal)236 void CronetURLRequest::NetworkTasks::OnSSLCertificateError(
237     net::URLRequest* request,
238     int net_error,
239     const net::SSLInfo& ssl_info,
240     bool fatal) {
241   DCHECK_CALLED_ON_VALID_THREAD(network_thread_checker_);
242   ReportError(request, net_error);
243   request->Cancel();
244 }
245 
OnResponseStarted(net::URLRequest * request,int net_error)246 void CronetURLRequest::NetworkTasks::OnResponseStarted(net::URLRequest* request,
247                                                        int net_error) {
248   DCHECK_NE(net::ERR_IO_PENDING, net_error);
249   DCHECK_CALLED_ON_VALID_THREAD(network_thread_checker_);
250 
251   if (net_error != net::OK) {
252     ReportError(request, net_error);
253     return;
254   }
255   callback_->OnResponseStarted(
256       request->GetResponseCode(), request->response_headers()->GetStatusText(),
257       request->response_headers(), request->response_info().was_cached,
258       request->response_info().alpn_negotiated_protocol,
259       GetProxy(request->response_info()),
260       received_byte_count_from_redirects_ + request->GetTotalReceivedBytes());
261 }
262 
OnReadCompleted(net::URLRequest * request,int bytes_read)263 void CronetURLRequest::NetworkTasks::OnReadCompleted(net::URLRequest* request,
264                                                      int bytes_read) {
265   DCHECK_CALLED_ON_VALID_THREAD(network_thread_checker_);
266 
267   if (bytes_read < 0) {
268     ReportError(request, bytes_read);
269     return;
270   }
271 
272   if (bytes_read == 0) {
273     DCHECK(!error_reported_);
274     MaybeReportMetrics();
275     callback_->OnSucceeded(received_byte_count_from_redirects_ +
276                            request->GetTotalReceivedBytes());
277   } else {
278     callback_->OnReadCompleted(
279         read_buffer_, bytes_read,
280         received_byte_count_from_redirects_ + request->GetTotalReceivedBytes());
281   }
282   // Free the read buffer.
283   read_buffer_ = nullptr;
284 }
285 
Start(CronetContext * context,const std::string & method,std::unique_ptr<net::HttpRequestHeaders> request_headers,std::unique_ptr<net::UploadDataStream> upload)286 void CronetURLRequest::NetworkTasks::Start(
287     CronetContext* context,
288     const std::string& method,
289     std::unique_ptr<net::HttpRequestHeaders> request_headers,
290     std::unique_ptr<net::UploadDataStream> upload) {
291   DCHECK(context->IsOnNetworkThread());
292   DCHECK_CALLED_ON_VALID_THREAD(network_thread_checker_);
293   VLOG(1) << "Starting chromium request: "
294           << initial_url_.possibly_invalid_spec().c_str()
295           << " priority: " << RequestPriorityToString(initial_priority_);
296   url_request_ = context->GetURLRequestContext(network_)->CreateRequest(
297       initial_url_, net::DEFAULT_PRIORITY, this, MISSING_TRAFFIC_ANNOTATION);
298   url_request_->SetLoadFlags(initial_load_flags_);
299   url_request_->set_method(method);
300   url_request_->SetExtraRequestHeaders(*request_headers);
301   url_request_->SetPriority(initial_priority_);
302   url_request_->SetIdempotency(idempotency_);
303   std::string referer;
304   if (request_headers->GetHeader(net::HttpRequestHeaders::kReferer, &referer)) {
305     url_request_->SetReferrer(referer);
306   }
307   if (upload)
308     url_request_->set_upload(std::move(upload));
309   if (traffic_stats_tag_set_ || traffic_stats_uid_set_) {
310 #if BUILDFLAG(IS_ANDROID)
311     url_request_->set_socket_tag(net::SocketTag(
312         traffic_stats_uid_set_ ? traffic_stats_uid_ : net::SocketTag::UNSET_UID,
313         traffic_stats_tag_set_ ? traffic_stats_tag_
314                                : net::SocketTag::UNSET_TAG));
315 #else
316     CHECK(false);
317 #endif
318   }
319   url_request_->Start();
320 }
321 
GetStatus(OnStatusCallback callback) const322 void CronetURLRequest::NetworkTasks::GetStatus(
323     OnStatusCallback callback) const {
324   DCHECK_CALLED_ON_VALID_THREAD(network_thread_checker_);
325   net::LoadState status = net::LOAD_STATE_IDLE;
326   // |url_request_| is initialized in StartOnNetworkThread, and it is
327   // never nulled. If it is null, it must be that StartOnNetworkThread
328   // has not been called, pretend that we are in LOAD_STATE_IDLE.
329   // See https://crbug.com/606872.
330   if (url_request_)
331     status = url_request_->GetLoadState().state;
332   std::move(callback).Run(status);
333 }
334 
FollowDeferredRedirect()335 void CronetURLRequest::NetworkTasks::FollowDeferredRedirect() {
336   DCHECK_CALLED_ON_VALID_THREAD(network_thread_checker_);
337   url_request_->FollowDeferredRedirect(
338       std::nullopt /* removed_request_headers */,
339       std::nullopt /* modified_request_headers */);
340 }
341 
ReadData(scoped_refptr<net::IOBuffer> read_buffer,int buffer_size)342 void CronetURLRequest::NetworkTasks::ReadData(
343     scoped_refptr<net::IOBuffer> read_buffer,
344     int buffer_size) {
345   DCHECK_CALLED_ON_VALID_THREAD(network_thread_checker_);
346   DCHECK(read_buffer);
347   DCHECK(!read_buffer_);
348 
349   read_buffer_ = read_buffer;
350 
351   int result = url_request_->Read(read_buffer_.get(), buffer_size);
352   // If IO is pending, wait for the URLRequest to call OnReadCompleted.
353   if (result == net::ERR_IO_PENDING)
354     return;
355 
356   OnReadCompleted(url_request_.get(), result);
357 }
358 
Destroy(CronetURLRequest * request,bool send_on_canceled)359 void CronetURLRequest::NetworkTasks::Destroy(CronetURLRequest* request,
360                                              bool send_on_canceled) {
361   DCHECK_CALLED_ON_VALID_THREAD(network_thread_checker_);
362   MaybeReportMetrics();
363   if (send_on_canceled)
364     callback_->OnCanceled();
365   callback_->OnDestroyed();
366   // Check if the URLRequestContext associated to `network_` has become eligible
367   // for destruction. To simplify MaybeDestroyURLRequestContext's logic: destroy
368   // the underlying URLRequest in advance, so that it has already deregistered
369   // from its URLRequestContext by the time MaybeDestroyURLRequestContext is
370   // called.
371   url_request_.reset();
372   request->context_->MaybeDestroyURLRequestContext(network_);
373   // Deleting owner request also deletes `this`.
374   delete request;
375 }
376 
ReportError(net::URLRequest * request,int net_error)377 void CronetURLRequest::NetworkTasks::ReportError(net::URLRequest* request,
378                                                  int net_error) {
379   DCHECK_CALLED_ON_VALID_THREAD(network_thread_checker_);
380   DCHECK_NE(net::ERR_IO_PENDING, net_error);
381   DCHECK_LT(net_error, 0);
382   DCHECK_EQ(request, url_request_.get());
383   // Error may have already been reported.
384   if (error_reported_)
385     return;
386   error_reported_ = true;
387   net::NetErrorDetails net_error_details;
388   url_request_->PopulateNetErrorDetails(&net_error_details);
389   VLOG(1) << "Error " << net::ErrorToString(net_error)
390           << " on chromium request: " << initial_url_.possibly_invalid_spec();
391   MaybeReportMetrics();
392   callback_->OnError(
393       net_error, net_error_details.quic_connection_error,
394       net::ErrorToString(net_error),
395       received_byte_count_from_redirects_ + request->GetTotalReceivedBytes());
396 }
397 
MaybeReportMetrics()398 void CronetURLRequest::NetworkTasks::MaybeReportMetrics() {
399   DCHECK_CALLED_ON_VALID_THREAD(network_thread_checker_);
400   // If there was an exception while starting the CronetUrlRequest, there won't
401   // be a native URLRequest. In this case, the caller gets the exception
402   // immediately, and the onFailed callback isn't called, so don't report
403   // metrics either.
404   if (metrics_reported_ || !url_request_) {
405     return;
406   }
407   metrics_reported_ = true;
408   net::LoadTimingInfo metrics;
409   url_request_->GetLoadTimingInfo(&metrics);
410   net::NetErrorDetails net_error_details;
411   url_request_->PopulateNetErrorDetails(&net_error_details);
412   callback_->OnMetricsCollected(
413       metrics.request_start_time, metrics.request_start,
414       metrics.connect_timing.domain_lookup_start,
415       metrics.connect_timing.domain_lookup_end,
416       metrics.connect_timing.connect_start, metrics.connect_timing.connect_end,
417       metrics.connect_timing.ssl_start, metrics.connect_timing.ssl_end,
418       metrics.send_start, metrics.send_end, metrics.push_start,
419       metrics.push_end, metrics.receive_headers_end, base::TimeTicks::Now(),
420       metrics.socket_reused, url_request_->GetTotalSentBytes(),
421       received_byte_count_from_redirects_ +
422           url_request_->GetTotalReceivedBytes(),
423       net_error_details.quic_connection_migration_attempted,
424       net_error_details.quic_connection_migration_successful);
425 }
426 
MaybeReportMetricsAndRunCallback(base::OnceClosure callback)427 void CronetURLRequest::NetworkTasks::MaybeReportMetricsAndRunCallback(
428     base::OnceClosure callback) {
429   DCHECK_CALLED_ON_VALID_THREAD(network_thread_checker_);
430   MaybeReportMetrics();
431   std::move(callback).Run();
432 }
433 
434 }  // namespace cronet
435