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