OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | |
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/cert_net/cert_net_fetcher_impl.h" | |
6 | |
7 #include "base/logging.h" | |
8 #include "base/numerics/safe_math.h" | |
9 #include "base/stl_util.h" | |
10 #include "base/timer/timer.h" | |
11 #include "net/base/load_flags.h" | |
12 #include "net/url_request/redirect_info.h" | |
13 #include "net/url_request/url_request_context.h" | |
14 | |
15 // TODO(eroman): Add support for POST parameters. | |
16 // TODO(eroman): Add controls for bypassing the cache. | |
17 // TODO(eroman): Add a maximum number of in-flight jobs/requests. | |
18 | |
19 namespace net { | |
20 | |
21 namespace { | |
22 | |
23 // The error returned when the response body exceeded the size limit. | |
24 const Error kNetErrorResponseTooLarge = ERR_FILE_TOO_BIG; | |
25 | |
26 // The error returned when the URL could not be fetched because it was not an | |
27 // allowed scheme (http). | |
28 const Error kNetErrorNotHttpUrl = ERR_DISALLOWED_URL_SCHEME; | |
29 | |
30 // The error returned when the URL fetch did not complete in time. | |
31 const Error kNetErrorTimedOut = ERR_TIMED_OUT; | |
32 | |
33 // The error returned when the response was HTTP however it did not have a | |
34 // status of 200/OK. | |
35 // TODO(eroman): Use a more specific error code. | |
36 const Error kNetErrorNot200HttpResponse = ERR_FAILED; | |
davidben
2015/03/27 23:34:32
Just using ERR_TIMED_OUT and the like where you us
eroman
2015/04/04 21:57:15
Done.
| |
37 | |
38 // The size of the buffer used for reading the response body of the URLRequest. | |
39 const int kReadBufferSizeInBytes = 4096; | |
40 | |
41 // The maximum size in bytes for the response body when fetching a CRL. | |
42 const int kMaxResponseSizeInBytesForCrl = 5 * 1024 * 1024; | |
43 | |
44 // The maximum size in bytes for the response body when fetching an AIA URL | |
45 // (caIssuers/OCSP). | |
46 const int kMaxResponseSizeInBytesForAia = 64 * 1024; | |
47 | |
48 // The default timeout in seconds for fetch requests. | |
49 const int kTimeoutSeconds = 15; | |
50 | |
51 // Policy for which URLs are allowed to be fetched. This is called both for the | |
52 // initial URL and for each redirect. Returns OK on success or a net error | |
53 // code on failure. | |
54 Error CanFetchUrl(const GURL& url) { | |
55 if (!url.SchemeIs("http")) | |
56 return kNetErrorNotHttpUrl; | |
57 return OK; | |
58 } | |
59 | |
60 base::TimeDelta GetTimeout(int timeout_milliseconds) { | |
61 if (timeout_milliseconds == CertNetFetcher::DEFAULT) | |
62 return base::TimeDelta::FromSeconds(kTimeoutSeconds); | |
63 return base::TimeDelta::FromMilliseconds(timeout_milliseconds); | |
64 } | |
65 | |
66 size_t GetMaxResponseBytes(int max_response_bytes, | |
67 size_t default_max_response_bytes) { | |
68 if (max_response_bytes == CertNetFetcher::DEFAULT) | |
69 return default_max_response_bytes; | |
70 | |
71 // Ensure that the specified limit is not negative, and cannot result in an | |
72 // overflow while reading. | |
73 base::CheckedNumeric<size_t> check(max_response_bytes); | |
74 check += kReadBufferSizeInBytes; | |
75 DCHECK(check.IsValid()); | |
76 | |
77 return max_response_bytes; | |
78 } | |
79 | |
80 enum HttpMethod { | |
81 HTTP_METHOD_GET, | |
82 HTTP_METHOD_POST, | |
83 }; | |
84 | |
85 } // namespace | |
86 | |
87 // CertNetFetcherImpl::Request tracks an outstanding call to Fetch(). | |
88 struct CertNetFetcherImpl::Request { | |
89 Request(FetchCallback callback, Job* job) : callback(callback), job(job) {} | |
90 | |
91 // The callback to invoke when the request has completed. | |
92 FetchCallback callback; | |
93 | |
94 // A non-owned pointer to the job that is executing the request (and in effect | |
95 // owns |this|). | |
96 Job* job; | |
97 | |
98 private: | |
99 DISALLOW_COPY_AND_ASSIGN(Request); | |
100 }; | |
101 | |
102 struct CertNetFetcherImpl::RequestParams { | |
103 RequestParams(); | |
104 | |
105 bool operator<(const RequestParams& other) const; | |
106 | |
107 GURL url; | |
108 HttpMethod http_method; | |
109 size_t max_response_bytes; | |
110 | |
111 // If set to a value <= 0 then means "no timeout". | |
112 base::TimeDelta timeout; | |
113 | |
114 // IMPORTANT: When adding fields to this structure, update operator<(). | |
115 | |
116 private: | |
117 DISALLOW_COPY_AND_ASSIGN(RequestParams); | |
118 }; | |
119 | |
120 CertNetFetcherImpl::RequestParams::RequestParams() | |
121 : http_method(HTTP_METHOD_GET), max_response_bytes(0) { | |
122 } | |
123 | |
124 bool CertNetFetcherImpl::RequestParams::operator<( | |
125 const RequestParams& other) const { | |
126 if (url != other.url) | |
127 return url < other.url; | |
128 if (http_method != other.http_method) | |
129 return http_method < other.http_method; | |
130 if (max_response_bytes != other.max_response_bytes) | |
131 return max_response_bytes < other.max_response_bytes; | |
132 return timeout < other.timeout; | |
davidben
2015/03/27 23:34:32
Aside: Comparing timeouts here is kinda weird. Tho
eroman
2015/04/04 21:57:15
Yeah agreed. There are some different strategies f
| |
133 } | |
134 | |
135 // CertNetFetcherImpl::Job tracks an outstanding URLRequest as well as all of | |
136 // the pending requests for it. | |
137 class CertNetFetcherImpl::Job : public URLRequest::Delegate { | |
138 public: | |
139 Job(scoped_ptr<RequestParams> request_params, CertNetFetcherImpl* parent); | |
140 ~Job() override; | |
141 | |
142 // Cancels the job and all requests attached to it. No callbacks will be | |
143 // invoked following cancellation. | |
144 void Cancel(); | |
145 | |
146 const RequestParams& request_params() const { return *request_params_; } | |
147 | |
148 // Attaches a request to the job. When the job completes it will invoke | |
149 // |callback|. | |
150 RequestId AddRequest(const FetchCallback& callback); | |
151 | |
152 // Removes |request| from the job and deletes it. | |
153 void CancelRequest(Request* request); | |
154 | |
155 // Creates and starts a URLRequest for the job. After the request has | |
156 // completed, OnJobCompleted() will be invoked and all the registered requests | |
157 // notified of completion. | |
158 void StartURLRequest(URLRequestContext* context); | |
159 | |
160 private: | |
161 // The pointers in RequestList are owned by the Job. | |
162 using RequestList = std::vector<Request*>; | |
davidben
2015/03/27 23:34:31
This might change if you make the consumer own the
eroman
2015/04/04 21:57:15
No longer applicable, as the request is no longer
| |
163 | |
164 // Implementation of URLRequest::Delegate | |
165 void OnReceivedRedirect(URLRequest* request, | |
166 const RedirectInfo& redirect_info, | |
167 bool* defer_redirect) override; | |
168 void OnResponseStarted(URLRequest* request) override; | |
169 void OnReadCompleted(URLRequest* request, int bytes_read) override; | |
170 | |
171 // Clears the URLRequest and timer. Helper for doing work common to | |
172 // cancellation and job completion. | |
173 void Stop(); | |
174 | |
175 // Reads as much data as available from the |request|. | |
davidben
2015/03/27 23:34:32
Nit: from the -> from
eroman
2015/04/04 21:57:15
Done.
| |
176 void ReadBody(URLRequest* request); | |
177 | |
178 // Helper to copy the partial bytes read from the read IOBuffer to an | |
179 // aggregated buffer. | |
180 bool ConsumeBytesRead(URLRequest* request, int num_bytes); | |
181 | |
182 // Called once the job has exceeded its deadline. | |
183 void OnTimeout(); | |
184 | |
185 // Called when the URLRequest has completed (either success or failure). | |
186 void OnUrlRequestCompleted(URLRequest* request); | |
187 | |
188 // Called when the Job has completed. The job may finish in response to a | |
189 // timeout, an invalid URL, or the URLRequest completing. By the time this | |
190 // method is called, the response variables have been assigned | |
191 // (result_net_error_code_ et al). | |
davidben
2015/03/31 20:52:23
Nit: result_net_error_code_ -> result_net_error_
eroman
2015/04/04 21:57:15
Done.
| |
192 void OnJobCompleted(); | |
193 | |
194 // The requests attached to this job. | |
195 RequestList requests_; | |
196 | |
197 // The input parameters for starting a URLRequest. | |
198 scoped_ptr<RequestParams> request_params_; | |
199 | |
200 // The URLRequest response information. | |
201 std::vector<uint8_t> response_body_; | |
202 Error result_net_error_; | |
203 | |
204 scoped_ptr<URLRequest> url_request_; | |
205 scoped_refptr<IOBuffer> read_buffer_; | |
206 | |
207 // Used to timeout the job when the URLRequest takes too long. This timer is | |
208 // also used for notifying a failure to start the URLRequest. | |
209 base::OneShotTimer<Job> timer_; | |
210 | |
211 // Non-owned pointer to the CertNetFetcherImpl that created this job. | |
212 CertNetFetcherImpl* parent_; | |
213 | |
214 DISALLOW_COPY_AND_ASSIGN(Job); | |
215 }; | |
216 | |
217 CertNetFetcherImpl::Job::Job(scoped_ptr<RequestParams> request_params, | |
218 CertNetFetcherImpl* parent) | |
219 : request_params_(request_params.Pass()), | |
220 result_net_error_(ERR_IO_PENDING), | |
221 parent_(parent) { | |
222 } | |
223 | |
224 CertNetFetcherImpl::Job::~Job() { | |
225 Cancel(); | |
226 } | |
227 | |
228 void CertNetFetcherImpl::Job::Cancel() { | |
229 parent_ = nullptr; | |
230 STLDeleteElements(&requests_); | |
231 Stop(); | |
232 } | |
233 | |
234 CertNetFetcher::RequestId CertNetFetcherImpl::Job::AddRequest( | |
235 const FetchCallback& callback) { | |
236 requests_.push_back(new Request(callback, this)); | |
237 return requests_.back(); | |
238 } | |
239 | |
240 void CertNetFetcherImpl::Job::CancelRequest(Request* request) { | |
241 scoped_ptr<Job> delete_this; | |
242 | |
243 RequestList::iterator it = | |
244 std::find(requests_.begin(), requests_.end(), request); | |
245 CHECK(it != requests_.end()); | |
246 requests_.erase(it); | |
247 delete request; | |
248 | |
249 // If there are no longer any requests attached to the job then | |
250 // cancel and delete it. | |
251 if (requests_.empty() && parent_) | |
252 delete_this = parent_->RemoveJob(this); | |
davidben
2015/03/27 23:34:31
Why not just "parent_->RemoveJob(this)"? Or moving
eroman
2015/04/04 21:57:14
I wanted to ensure that deletion of |this| was the
| |
253 } | |
254 | |
255 void CertNetFetcherImpl::Job::StartURLRequest(URLRequestContext* context) { | |
256 Error error = CanFetchUrl(request_params_->url); | |
257 if (error != OK) { | |
258 result_net_error_ = error; | |
259 // The CertNetFetcher's API contract is that requests always complete | |
260 // asynchronously. Use the timer class so the task is easily cancelled. | |
261 timer_.Start(FROM_HERE, base::TimeDelta(), this, &Job::OnJobCompleted); | |
262 return; | |
263 } | |
264 | |
265 // Start the URLRequest. | |
266 read_buffer_ = new IOBuffer(kReadBufferSizeInBytes); | |
267 url_request_ = | |
268 context->CreateRequest(request_params_->url, DEFAULT_PRIORITY, this); | |
269 if (request_params_->http_method == HTTP_METHOD_POST) | |
270 url_request_->set_method("POST"); | |
271 url_request_->SetLoadFlags(LOAD_DO_NOT_SAVE_COOKIES | | |
272 LOAD_DO_NOT_SEND_COOKIES); | |
273 url_request_->Start(); | |
274 | |
275 // Start a timer to limit how long the job runs for. | |
276 if (request_params_->timeout > base::TimeDelta()) | |
277 timer_.Start(FROM_HERE, request_params_->timeout, this, &Job::OnTimeout); | |
278 } | |
279 | |
280 void CertNetFetcherImpl::Job::OnReceivedRedirect( | |
281 URLRequest* request, | |
282 const RedirectInfo& redirect_info, | |
283 bool* defer_redirect) { | |
284 DCHECK_EQ(url_request_.get(), request); | |
285 | |
286 // Ensure that the new URL matches the policy. | |
287 Error error = CanFetchUrl(redirect_info.new_url); | |
288 if (error != OK) { | |
289 request->CancelWithError(error); | |
290 OnUrlRequestCompleted(request); | |
davidben
2015/03/27 23:34:31
This does end up deleting |request| while it's on
eroman
2015/04/04 21:57:14
Acknowledged.
| |
291 return; | |
292 } | |
293 } | |
294 | |
295 void CertNetFetcherImpl::Job::OnResponseStarted(URLRequest* request) { | |
296 DCHECK_EQ(url_request_.get(), request); | |
297 | |
298 if (!request->status().is_success()) { | |
299 OnUrlRequestCompleted(request); | |
300 return; | |
301 } | |
302 | |
303 // In practice all URLs fetched are HTTP, but check anyway as defensive | |
304 // measure in case the policy is ever changed. | |
davidben
2015/03/31 20:52:23
I don't understand what this comment is referring
eroman
2015/04/04 21:57:15
Fixed. This comment was referring to an older vers
| |
305 if (request->GetResponseCode() != 200) { | |
306 request->CancelWithError(kNetErrorNot200HttpResponse); | |
307 OnUrlRequestCompleted(request); | |
308 return; | |
309 } | |
310 | |
311 ReadBody(request); | |
312 } | |
313 | |
314 void CertNetFetcherImpl::Job::OnReadCompleted(URLRequest* request, | |
315 int bytes_read) { | |
316 DCHECK_EQ(url_request_.get(), request); | |
317 | |
318 // Keep reading the response body. | |
319 if (ConsumeBytesRead(request, bytes_read)) | |
320 ReadBody(request); | |
321 } | |
322 | |
323 void CertNetFetcherImpl::Job::Stop() { | |
324 timer_.Stop(); | |
325 url_request_.reset(); | |
326 } | |
327 | |
328 void CertNetFetcherImpl::Job::ReadBody(URLRequest* request) { | |
329 // Read as many bytes as are available synchronously. | |
330 int num_bytes; | |
331 while ( | |
332 request->Read(read_buffer_.get(), kReadBufferSizeInBytes, &num_bytes)) { | |
333 if (!ConsumeBytesRead(request, num_bytes)) | |
334 return; | |
335 } | |
336 | |
337 // Check whether the read failed synchronously. | |
338 if (!request->status().is_io_pending()) | |
339 OnUrlRequestCompleted(request); | |
340 return; | |
341 } | |
342 | |
343 bool CertNetFetcherImpl::Job::ConsumeBytesRead(URLRequest* request, | |
344 int num_bytes) { | |
345 if (num_bytes <= 0) { | |
346 // Error while reading, or EOF. | |
347 OnUrlRequestCompleted(request); | |
348 return false; | |
349 } | |
350 | |
351 // Enforce maximum size bound. | |
352 if (num_bytes + response_body_.size() > request_params_->max_response_bytes) { | |
353 request->CancelWithError(kNetErrorResponseTooLarge); | |
354 OnUrlRequestCompleted(request); | |
355 return false; | |
356 } | |
357 | |
358 // Append the data to |response_body_|. | |
359 response_body_.reserve(response_body_.size() + num_bytes); | |
davidben
2015/03/31 20:52:23
Does the reserve call do anything? I would have ho
eroman
2015/04/04 21:57:15
Agreed I don't think it is necessary. However I ad
davidben
2015/04/07 19:22:25
Oh, hrmf. Sorry, I should have checked one more ST
| |
360 response_body_.insert(response_body_.end(), read_buffer_->data(), | |
361 read_buffer_->data() + num_bytes); | |
362 return true; | |
363 } | |
364 | |
365 void CertNetFetcherImpl::Job::OnTimeout() { | |
366 result_net_error_ = kNetErrorTimedOut; | |
367 url_request_->CancelWithError(result_net_error_); | |
368 OnJobCompleted(); | |
369 } | |
370 | |
371 void CertNetFetcherImpl::Job::OnUrlRequestCompleted(URLRequest* request) { | |
372 DCHECK_EQ(request, url_request_.get()); | |
373 | |
374 if (request->status().is_success()) | |
375 result_net_error_ = OK; | |
376 else | |
377 result_net_error_ = static_cast<Error>(request->status().error()); | |
378 | |
379 OnJobCompleted(); | |
380 } | |
381 | |
382 void CertNetFetcherImpl::Job::OnJobCompleted() { | |
383 // Stop the timer and clear the URLRequest. | |
384 Stop(); | |
385 | |
386 // Invoking the callbacks is subtle as state may be mutated while iterating | |
387 // through the callbacks: | |
388 // | |
389 // * The parent CertNetFetcherImpl may be deleted | |
390 // * Requests in this job may be cancelled | |
391 | |
392 scoped_ptr<Job> delete_this = parent_->RemoveJob(this); | |
393 parent_->SetCurrentlyCompletingJob(this); | |
davidben
2015/03/27 23:34:31
DCHECK somewhere that there isn't another currentl
eroman
2015/04/04 21:57:14
Done. I added a new method ClearCurrentlyCompletin
| |
394 | |
395 while (!requests_.empty()) { | |
396 scoped_ptr<Request> request(requests_.front()); | |
397 requests_.erase(requests_.begin()); | |
398 request->callback.Run(result_net_error_, response_body_); | |
399 } | |
400 | |
401 if (parent_) | |
402 parent_->SetCurrentlyCompletingJob(nullptr); | |
403 } | |
404 | |
405 CertNetFetcherImpl::CertNetFetcherImpl(URLRequestContext* context) | |
406 : currently_completing_job_(nullptr), context_(context) { | |
407 } | |
408 | |
409 CertNetFetcherImpl::~CertNetFetcherImpl() { | |
410 STLDeleteElements(&jobs_); | |
411 if (currently_completing_job_) | |
412 currently_completing_job_->Cancel(); | |
413 } | |
414 | |
415 void CertNetFetcherImpl::CancelRequest(RequestId request_id) { | |
416 DCHECK(thread_checker_.CalledOnValidThread()); | |
417 | |
418 Request* request = reinterpret_cast<Request*>(request_id); | |
419 request->job->CancelRequest(request); | |
420 } | |
421 | |
422 CertNetFetcher::RequestId CertNetFetcherImpl::FetchCaIssuers( | |
423 const GURL& url, | |
424 int timeout_milliseconds, | |
425 int max_response_bytes, | |
426 const FetchCallback& callback) { | |
427 scoped_ptr<RequestParams> request_params(new RequestParams); | |
428 | |
429 request_params->url = url; | |
430 request_params->http_method = HTTP_METHOD_GET; | |
431 request_params->timeout = GetTimeout(timeout_milliseconds); | |
432 request_params->max_response_bytes = | |
433 GetMaxResponseBytes(max_response_bytes, kMaxResponseSizeInBytesForAia); | |
434 | |
435 return Fetch(request_params.Pass(), callback); | |
436 } | |
437 | |
438 CertNetFetcher::RequestId CertNetFetcherImpl::FetchCrl( | |
439 const GURL& url, | |
440 int timeout_milliseconds, | |
441 int max_response_bytes, | |
442 const FetchCallback& callback) { | |
443 scoped_ptr<RequestParams> request_params(new RequestParams); | |
444 | |
445 request_params->url = url; | |
446 request_params->http_method = HTTP_METHOD_GET; | |
447 request_params->timeout = GetTimeout(timeout_milliseconds); | |
448 request_params->max_response_bytes = | |
449 GetMaxResponseBytes(max_response_bytes, kMaxResponseSizeInBytesForCrl); | |
450 | |
451 return Fetch(request_params.Pass(), callback); | |
452 } | |
453 | |
454 CertNetFetcher::RequestId CertNetFetcherImpl::FetchOcsp( | |
455 const GURL& url, | |
456 int timeout_milliseconds, | |
457 int max_response_bytes, | |
458 const FetchCallback& callback) { | |
459 scoped_ptr<RequestParams> request_params(new RequestParams); | |
460 | |
461 request_params->url = url; | |
462 request_params->http_method = HTTP_METHOD_GET; | |
463 request_params->timeout = GetTimeout(timeout_milliseconds); | |
464 request_params->max_response_bytes = | |
465 GetMaxResponseBytes(max_response_bytes, kMaxResponseSizeInBytesForAia); | |
466 | |
467 return Fetch(request_params.Pass(), callback); | |
468 } | |
469 | |
470 bool CertNetFetcherImpl::JobComparator::operator()(const Job* job1, | |
471 const Job* job2) const { | |
472 return job1->request_params() < job2->request_params(); | |
473 } | |
474 | |
475 CertNetFetcher::RequestId CertNetFetcherImpl::Fetch( | |
476 scoped_ptr<RequestParams> request_params, | |
477 const FetchCallback& callback) { | |
478 DCHECK(thread_checker_.CalledOnValidThread()); | |
479 | |
480 // If there is an in-progress job that matches the request parameters use it. | |
481 // Otherwise start a new job. | |
482 Job* job = FindJob(*request_params); | |
483 | |
484 if (!job) { | |
485 job = new Job(request_params.Pass(), this); | |
486 jobs_.insert(job); | |
487 job->StartURLRequest(context_); | |
488 } | |
489 | |
490 return job->AddRequest(callback); | |
491 } | |
492 | |
493 struct CertNetFetcherImpl::JobToRequestParamsComparator { | |
494 bool operator()(const Job* job, | |
495 const CertNetFetcherImpl::RequestParams& value) const { | |
496 return job->request_params() < value; | |
497 } | |
498 }; | |
499 | |
500 CertNetFetcherImpl::Job* CertNetFetcherImpl::FindJob( | |
501 const RequestParams& params) { | |
502 DCHECK(thread_checker_.CalledOnValidThread()); | |
503 | |
504 // The JobSet is kept in sorted order so items can be found using binary | |
505 // search. | |
506 JobSet::iterator it = std::lower_bound(jobs_.begin(), jobs_.end(), params, | |
507 JobToRequestParamsComparator()); | |
508 if (it != jobs_.end() && !(params < (*it)->request_params())) | |
509 return *it; | |
510 return nullptr; | |
511 } | |
512 | |
513 scoped_ptr<CertNetFetcherImpl::Job> CertNetFetcherImpl::RemoveJob(Job* job) { | |
514 DCHECK(thread_checker_.CalledOnValidThread()); | |
515 bool erased_job = jobs_.erase(job) == 1; | |
516 DCHECK(erased_job); | |
517 return scoped_ptr<Job>(job); | |
davidben
2015/03/31 20:52:23
Optional: make_scoped_ptr might be a little less r
eroman
2015/04/04 21:57:15
Done.
| |
518 } | |
519 | |
520 void CertNetFetcherImpl::SetCurrentlyCompletingJob(Job* job) { | |
521 currently_completing_job_ = job; | |
522 } | |
523 | |
524 } // namespace net | |
OLD | NEW |