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

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