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

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 more feedback Created 5 years, 9 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
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/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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698