Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(316)

Side by Side Diff: net/cert_net/cert_net_fetcher_impl.cc

Issue 908863004: Initial implementation for CertNetFetcher. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: reset the callback on completion Created 5 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « net/cert_net/cert_net_fetcher_impl.h ('k') | net/cert_net/cert_net_fetcher_impl_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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
OLDNEW
« no previous file with comments | « net/cert_net/cert_net_fetcher_impl.h ('k') | net/cert_net/cert_net_fetcher_impl_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698