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

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: add missing comma 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 RequestImpl(Job* job, const FetchCallback& callback)
80 : callback_(callback), job_(job) {
81 DCHECK(!callback.is_null());
82 }
83
84 // Deletion cancels the outstanding request.
85 ~RequestImpl() override;
86
87 void OnJobCancelled(Job* job) {
88 DCHECK_EQ(job_, job);
89 job_ = nullptr;
90 callback_.Reset();
91 }
92
93 void OnJobCompleted(Job* job,
94 Error error,
95 const std::vector<uint8_t>& response_body) {
96 DCHECK_EQ(job_, job);
97 job_ = nullptr;
98 base::ResetAndReturn(&callback_).Run(error, response_body);
99 }
100
101 private:
102 // The callback to invoke when the request has completed.
103 FetchCallback callback_;
104
105 // A non-owned pointer to the job that is executing the request.
106 Job* job_;
107
108 private:
109 DISALLOW_COPY_AND_ASSIGN(RequestImpl);
110 };
111
112 struct CertNetFetcherImpl::RequestParams {
113 RequestParams();
114
115 bool operator<(const RequestParams& other) const;
116
117 GURL url;
118 HttpMethod http_method;
119 size_t max_response_bytes;
120
121 // If set to a value <= 0 then means "no timeout".
122 base::TimeDelta timeout;
123
124 // IMPORTANT: When adding fields to this structure, update operator<().
125
126 private:
127 DISALLOW_COPY_AND_ASSIGN(RequestParams);
128 };
129
130 CertNetFetcherImpl::RequestParams::RequestParams()
131 : http_method(HTTP_METHOD_GET), max_response_bytes(0) {
132 }
133
134 bool CertNetFetcherImpl::RequestParams::operator<(
135 const RequestParams& other) const {
136 if (url != other.url)
137 return url < other.url;
138 if (http_method != other.http_method)
139 return http_method < other.http_method;
140 if (max_response_bytes != other.max_response_bytes)
141 return max_response_bytes < other.max_response_bytes;
142 return timeout < other.timeout;
143 }
144
145 // CertNetFetcherImpl::Job tracks an outstanding URLRequest as well as all of
146 // the pending requests for it.
147 class CertNetFetcherImpl::Job : public URLRequest::Delegate {
148 public:
149 Job(scoped_ptr<RequestParams> request_params, CertNetFetcherImpl* parent);
150 ~Job() override;
151
152 // Cancels the job and all requests attached to it. No callbacks will be
153 // invoked following cancellation.
154 void Cancel();
155
156 const RequestParams& request_params() const { return *request_params_; }
157
158 // Create a request and attaches it to the job. When the job completes it will
159 // notify the request of completion through OnJobCompleted. Note that the Job
160 // does NOT own the request.
161 scoped_ptr<Request> CreateRequest(const FetchCallback& callback);
162
163 // Removes |request| from the job.
164 void DetachRequest(RequestImpl* request);
165
166 // Creates and starts a URLRequest for the job. After the request has
167 // completed, OnJobCompleted() will be invoked and all the registered requests
168 // notified of completion.
169 void StartURLRequest(URLRequestContext* context);
170
171 private:
172 // The pointers in RequestList are not owned by the Job.
173 using RequestList = std::deque<RequestImpl*>;
174
175 // Implementation of URLRequest::Delegate
176 void OnReceivedRedirect(URLRequest* request,
177 const RedirectInfo& redirect_info,
178 bool* defer_redirect) override;
179 void OnResponseStarted(URLRequest* request) override;
180 void OnReadCompleted(URLRequest* request, int bytes_read) override;
181
182 // Clears the URLRequest and timer. Helper for doing work common to
183 // cancellation and job completion.
184 void Stop();
185
186 // Reads as much data as available from |request|.
187 void ReadBody(URLRequest* request);
188
189 // Helper to copy the partial bytes read from the read IOBuffer to an
190 // aggregated buffer.
191 bool ConsumeBytesRead(URLRequest* request, int num_bytes);
192
193 // Called once the job has exceeded its deadline.
194 void OnTimeout();
195
196 // Called when the URLRequest has completed (either success or failure).
197 void OnUrlRequestCompleted(URLRequest* request);
198
199 // Called when the Job has completed. The job may finish in response to a
200 // timeout, an invalid URL, or the URLRequest completing. By the time this
201 // method is called, the response variables have been assigned
202 // (result_net_error_ and response_body_).
203 void OnJobCompleted();
204
205 // The requests attached to this job.
206 RequestList requests_;
207
208 // The input parameters for starting a URLRequest.
209 scoped_ptr<RequestParams> request_params_;
210
211 // The URLRequest response information.
212 std::vector<uint8_t> response_body_;
213 Error result_net_error_;
214
215 scoped_ptr<URLRequest> url_request_;
216 scoped_refptr<IOBuffer> read_buffer_;
217
218 // Used to timeout the job when the URLRequest takes too long. This timer is
219 // also used for notifying a failure to start the URLRequest.
220 base::OneShotTimer<Job> timer_;
221
222 // Non-owned pointer to the CertNetFetcherImpl that created this job.
223 CertNetFetcherImpl* parent_;
224
225 DISALLOW_COPY_AND_ASSIGN(Job);
226 };
227
228 CertNetFetcherImpl::RequestImpl::~RequestImpl() {
229 if (job_)
230 job_->DetachRequest(this);
231 }
232
233 CertNetFetcherImpl::Job::Job(scoped_ptr<RequestParams> request_params,
234 CertNetFetcherImpl* parent)
235 : request_params_(request_params.Pass()),
236 result_net_error_(ERR_IO_PENDING),
237 parent_(parent) {
238 }
239
240 CertNetFetcherImpl::Job::~Job() {
241 Cancel();
242 }
243
244 void CertNetFetcherImpl::Job::Cancel() {
245 parent_ = nullptr;
246
247 for (RequestImpl* request : requests_)
248 request->OnJobCancelled(this);
249 requests_.clear();
250
251 Stop();
252 }
253
254 scoped_ptr<CertNetFetcher::Request> CertNetFetcherImpl::Job::CreateRequest(
255 const FetchCallback& callback) {
256 scoped_ptr<RequestImpl> request(new RequestImpl(this, callback));
257 requests_.push_back(request.get());
258 return request.Pass();
259 }
260
261 void CertNetFetcherImpl::Job::DetachRequest(RequestImpl* request) {
262 scoped_ptr<Job> delete_this;
263
264 // TODO(eroman): If a lot of requests are cancelled this is not efficient.
265 RequestList::iterator it =
266 std::find(requests_.begin(), requests_.end(), request);
267 CHECK(it != requests_.end());
268 requests_.erase(it);
269
270 // If there are no longer any requests attached to the job then
271 // cancel and delete it.
272 if (requests_.empty())
273 delete_this = parent_->RemoveJob(this);
274 }
275
276 void CertNetFetcherImpl::Job::StartURLRequest(URLRequestContext* context) {
277 Error error = CanFetchUrl(request_params_->url);
278 if (error != OK) {
279 result_net_error_ = error;
280 // The CertNetFetcher's API contract is that requests always complete
281 // asynchronously. Use the timer class so the task is easily cancelled.
282 timer_.Start(FROM_HERE, base::TimeDelta(), this, &Job::OnJobCompleted);
283 return;
284 }
285
286 // Start the URLRequest.
287 read_buffer_ = new IOBuffer(kReadBufferSizeInBytes);
288 url_request_ =
289 context->CreateRequest(request_params_->url, DEFAULT_PRIORITY, this);
290 if (request_params_->http_method == HTTP_METHOD_POST)
291 url_request_->set_method("POST");
292 url_request_->SetLoadFlags(LOAD_DO_NOT_SAVE_COOKIES |
293 LOAD_DO_NOT_SEND_COOKIES);
294 url_request_->Start();
295
296 // Start a timer to limit how long the job runs for.
297 if (request_params_->timeout > base::TimeDelta())
298 timer_.Start(FROM_HERE, request_params_->timeout, this, &Job::OnTimeout);
299 }
300
301 void CertNetFetcherImpl::Job::OnReceivedRedirect(
302 URLRequest* request,
303 const RedirectInfo& redirect_info,
304 bool* defer_redirect) {
305 DCHECK_EQ(url_request_.get(), request);
306
307 // Ensure that the new URL matches the policy.
308 Error error = CanFetchUrl(redirect_info.new_url);
309 if (error != OK) {
310 request->CancelWithError(error);
311 OnUrlRequestCompleted(request);
312 return;
313 }
314 }
315
316 void CertNetFetcherImpl::Job::OnResponseStarted(URLRequest* request) {
317 DCHECK_EQ(url_request_.get(), request);
318
319 if (!request->status().is_success()) {
320 OnUrlRequestCompleted(request);
321 return;
322 }
323
324 if (request->GetResponseCode() != 200) {
325 // TODO(eroman): Use a more specific error code.
326 request->CancelWithError(ERR_FAILED);
327 OnUrlRequestCompleted(request);
328 return;
329 }
330
331 ReadBody(request);
332 }
333
334 void CertNetFetcherImpl::Job::OnReadCompleted(URLRequest* request,
335 int bytes_read) {
336 DCHECK_EQ(url_request_.get(), request);
337
338 // Keep reading the response body.
339 if (ConsumeBytesRead(request, bytes_read))
340 ReadBody(request);
341 }
342
343 void CertNetFetcherImpl::Job::Stop() {
344 timer_.Stop();
345 url_request_.reset();
346 }
347
348 void CertNetFetcherImpl::Job::ReadBody(URLRequest* request) {
349 // Read as many bytes as are available synchronously.
350 int num_bytes;
351 while (
352 request->Read(read_buffer_.get(), kReadBufferSizeInBytes, &num_bytes)) {
353 if (!ConsumeBytesRead(request, num_bytes))
354 return;
355 }
356
357 // Check whether the read failed synchronously.
358 if (!request->status().is_io_pending())
359 OnUrlRequestCompleted(request);
360 return;
361 }
362
363 bool CertNetFetcherImpl::Job::ConsumeBytesRead(URLRequest* request,
364 int num_bytes) {
365 if (num_bytes <= 0) {
366 // Error while reading, or EOF.
367 OnUrlRequestCompleted(request);
368 return false;
369 }
370
371 // Enforce maximum size bound.
372 if (num_bytes + response_body_.size() > request_params_->max_response_bytes) {
373 request->CancelWithError(ERR_FILE_TOO_BIG);
374 OnUrlRequestCompleted(request);
375 return false;
376 }
377
378 // Append the data to |response_body_|.
379 response_body_.reserve(num_bytes);
380 response_body_.insert(response_body_.end(), read_buffer_->data(),
381 read_buffer_->data() + num_bytes);
382 return true;
383 }
384
385 void CertNetFetcherImpl::Job::OnTimeout() {
386 result_net_error_ = ERR_TIMED_OUT;
387 url_request_->CancelWithError(result_net_error_);
388 OnJobCompleted();
389 }
390
391 void CertNetFetcherImpl::Job::OnUrlRequestCompleted(URLRequest* request) {
392 DCHECK_EQ(request, url_request_.get());
393
394 if (request->status().is_success())
395 result_net_error_ = OK;
396 else
397 result_net_error_ = static_cast<Error>(request->status().error());
398
399 OnJobCompleted();
400 }
401
402 void CertNetFetcherImpl::Job::OnJobCompleted() {
403 // Stop the timer and clear the URLRequest.
404 Stop();
405
406 // Invoking the callbacks is subtle as state may be mutated while iterating
407 // through the callbacks:
408 //
409 // * The parent CertNetFetcherImpl may be deleted
410 // * Requests in this job may be cancelled
411
412 scoped_ptr<Job> delete_this = parent_->RemoveJob(this);
413 parent_->SetCurrentlyCompletingJob(this);
414
415 while (!requests_.empty()) {
416 RequestImpl* request = requests_.front();
417 requests_.pop_front();
418 request->OnJobCompleted(this, result_net_error_, response_body_);
419 }
420
421 if (parent_)
422 parent_->ClearCurrentlyCompletingJob(this);
423 }
424
425 CertNetFetcherImpl::CertNetFetcherImpl(URLRequestContext* context)
426 : currently_completing_job_(nullptr), context_(context) {
427 }
428
429 CertNetFetcherImpl::~CertNetFetcherImpl() {
430 STLDeleteElements(&jobs_);
431
432 // The CertNetFetcherImpl was destroyed in a FetchCallback. Detach all
433 // remaining requests from the job so no further callbacks are called.
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 return job->CreateRequest(callback);
507 }
508
509 struct CertNetFetcherImpl::JobToRequestParamsComparator {
510 bool operator()(const Job* job,
511 const CertNetFetcherImpl::RequestParams& value) const {
512 return job->request_params() < value;
513 }
514 };
515
516 CertNetFetcherImpl::Job* CertNetFetcherImpl::FindJob(
517 const RequestParams& params) {
518 DCHECK(thread_checker_.CalledOnValidThread());
519
520 // The JobSet is kept in sorted order so items can be found using binary
521 // search.
522 JobSet::iterator it = std::lower_bound(jobs_.begin(), jobs_.end(), params,
523 JobToRequestParamsComparator());
524 if (it != jobs_.end() && !(params < (*it)->request_params()))
525 return *it;
526 return nullptr;
527 }
528
529 scoped_ptr<CertNetFetcherImpl::Job> CertNetFetcherImpl::RemoveJob(Job* job) {
530 DCHECK(thread_checker_.CalledOnValidThread());
531 bool erased_job = jobs_.erase(job) == 1;
532 DCHECK(erased_job);
533 return make_scoped_ptr(job);
534 }
535
536 void CertNetFetcherImpl::SetCurrentlyCompletingJob(Job* job) {
537 DCHECK(!currently_completing_job_);
538 DCHECK(job);
539 currently_completing_job_ = job;
540 }
541
542 void CertNetFetcherImpl::ClearCurrentlyCompletingJob(Job* job) {
543 DCHECK_EQ(currently_completing_job_, job);
544 currently_completing_job_ = nullptr;
545 }
546
547 } // 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