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