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

Side by Side Diff: net/cert/cert_net_fetcher.cc

Issue 908863004: Initial implementation for CertNetFetcher. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: add a missing header Created 5 years, 10 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/cert_net_fetcher.h"
6
7 #include "base/logging.h"
8 #include "base/stl_util.h"
9 #include "base/timer/timer.h"
10 #include "net/base/load_flags.h"
11 #include "net/url_request/redirect_info.h"
12 #include "net/url_request/url_request_context.h"
13
14 // TODO(eroman): Add support for POST parameters.
15 // TODO(eroman): Add controls for bypassing the cache.
16 // TODO(eroman): Add a maximum number of in-flight jobs/requests.
17
18 namespace net {
19
20 namespace {
21
22 // The error returned when the response body exceeded the size limit.
23 const int kNetErrorResponseTooLarge = ERR_FILE_TOO_BIG;
24
25 // The error returned when the URL could not be fetched because it was not an
26 // allowed scheme (http).
27 const int kNetErrorNotHttpUrl = ERR_DISALLOWED_URL_SCHEME;
28
29 // The error returned when the URL fetch did not complete in time.
30 const int kNetErrorTimedOut = ERR_TIMED_OUT;
31
32 // The error returned when the response was HTTP however it did not have a
33 // status of 200/OK.
34 // TODO(eroman): Use a more specific error code.
35 const int kNetErrorNot200HttpResponse = ERR_FAILED;
36
37 // The size of the buffer used for reading the response body of the URLRequest.
38 const int kReadBufferSizeInBytes = 4096;
39
40 // The maximum size in bytes for the response body when fetching a CRL.
41 const int kMaxResponseSizeInBytesForCrl = 5 * 1024 * 1024;
42
43 // The maximum size in bytes for the response body when fetching an AIA URL
44 // (caIssuers/OCSP).
45 const int kMaxResponseSizeInBytesForAia = 64 * 1024;
46
47 // The default timeout in seconds for fetch requests.
48 const int kTimeoutSeconds = 15;
49
50 // Policy for which URLs are allowed to be fetched. This is called both for the
51 // initial URL and for each redirect. Returns OK on success or a net error
52 // code on failure.
53 int CanFetchUrl(const GURL& url) {
54 if (!url.SchemeIs("http"))
55 return kNetErrorNotHttpUrl;
56 return OK;
57 }
58
59 } // namespace
60
61 // CertNetFetcher::Request tracks an outstanding call to Fetch().
62 struct CertNetFetcher::Request {
63 Request(FetchCallback callback, Job* job) : callback(callback), job(job) {}
64
65 // The callback to invoke when the request has completed.
66 FetchCallback callback;
67
68 // A non-owned pointer to the job that is executing the request (and in effect
69 // owns |this|).
70 Job* job;
71
72 DISALLOW_COPY_AND_ASSIGN(Request);
73 };
74
75 CertNetFetcher::RequestParams::RequestParams(const GURL& url, RequestType type)
76 : url(url),
77 http_method(HTTP_METHOD_GET),
78 timeout(base::TimeDelta::FromSeconds(kTimeoutSeconds)) {
79 // Use the request type to determine the maximum allowed response size.
80 switch (type) {
81 case REQUEST_TYPE_CRL:
82 max_response_size_in_bytes = kMaxResponseSizeInBytesForCrl;
83 break;
84 case REQUEST_TYPE_CA_ISSUERS:
85 case REQUEST_TYPE_OCSP:
86 max_response_size_in_bytes = kMaxResponseSizeInBytesForAia;
87 break;
88 }
89 }
90
91 bool CertNetFetcher::RequestParams::operator<(
92 const RequestParams& other) const {
93 if (url != other.url)
94 return url < other.url;
95 if (http_method != other.http_method)
96 return http_method < other.http_method;
97 if (max_response_size_in_bytes != other.max_response_size_in_bytes)
98 return max_response_size_in_bytes < other.max_response_size_in_bytes;
99 if (timeout != other.timeout)
100 return timeout < other.timeout;
101 return false;
102 }
103
104 // CertNetFetcher::Job tracks an outstanding URLRequest as well as all of the
105 // pending requests for it.
106 class CertNetFetcher::Job : public base::RefCounted<CertNetFetcher::Job>,
107 public URLRequest::Delegate {
108 public:
109 Job(scoped_ptr<RequestParams> request_params, CertNetFetcher* parent);
110
111 // Cancels the job and all requests attached to it. No callbacks will be
112 // invoked following cancellation.
113 void Cancel();
114
115 const RequestParams& request_params() const { return *request_params_.get(); }
116
117 // Attaches a request to the job. When the job completes it will invoke
118 // |callback|.
119 RequestId AddRequest(FetchCallback callback);
120
121 // Removes |request| from the job and deletes it.
122 void CancelRequest(RequestId request);
123
124 // Creates and starts a URLRequest for the job. After the request has
125 // completed, OnJobCompleted() will be invoked and all the registered requests
126 // notified of completion.
127 void StartURLRequest(URLRequestContext* context);
128
129 private:
130 // The pointers in RequestList are owned by the Job.
131 typedef std::vector<Request*> RequestList;
132 friend class base::RefCounted<Job>;
133
134 ~Job() override;
135
136 // Implementation of URLRequest::Delegate
137 void OnReceivedRedirect(URLRequest* request,
138 const RedirectInfo& redirect_info,
139 bool* defer_redirect) override;
140 void OnResponseStarted(URLRequest* request) override;
141 void OnReadCompleted(URLRequest* request, int bytes_read) override;
142
143 // Clears the URLRequest and timer. Helper for doing work common to
144 // cancellation and job completion.
145 void Stop();
146
147 // Reads as much data as available from the |request|.
148 void ReadBody(URLRequest* request);
149
150 // Helper to copy the partial bytes read from the read IOBuffer to an
151 // aggregated buffer.
152 bool ConsumeBytesRead(URLRequest* request, int num_bytes);
153
154 // Called once the job has exceeded its deadline.
155 void OnTimeout();
156
157 // Called when the URLRequest has completed (either success or failure).
158 void OnUrlRequestCompleted(URLRequest* request);
159
160 // Called when the Job has completed. The job may finish in response to a
161 // timeout, an invalid URL, or the URLRequest completing. By the time this
162 // method is called, the response variables have been assigned
163 // (result_net_error_code_ et al).
164 void OnJobCompleted();
165
166 // The requests attached to this job.
167 RequestList requests_;
168
169 // The URLRequest input parameters.
170 scoped_ptr<RequestParams> request_params_;
171
172 // The URLRequest response information.
173 std::vector<uint8_t> response_body_;
174 int result_net_error_;
175
176 scoped_ptr<URLRequest> url_request_;
177 scoped_refptr<IOBuffer> read_buffer_;
178
179 base::OneShotTimer<Job> timer_;
180
181 // Non-owned pointer to the CertNetFetcher that created this job.
182 CertNetFetcher* parent_;
183 };
184
185 CertNetFetcher::Job::Job(scoped_ptr<RequestParams> request_params,
186 CertNetFetcher* parent)
187 : request_params_(request_params.Pass()),
188 result_net_error_(ERR_IO_PENDING),
189 parent_(parent) {
190 }
191
192 void CertNetFetcher::Job::Cancel() {
193 parent_ = NULL;
194 STLDeleteElements(&requests_);
195 Stop();
196 }
197
198 CertNetFetcher::RequestId CertNetFetcher::Job::AddRequest(
199 FetchCallback callback) {
200 requests_.push_back(new Request(callback, this));
201 return requests_.back();
202 }
203
204 void CertNetFetcher::Job::CancelRequest(RequestId request) {
205 // This scope ensures that |it| is destroyed before RemoveJob() is called,
206 // since that may delete |this|.
207 {
208 RequestList::iterator it =
209 std::find(requests_.begin(), requests_.end(), request);
210 DCHECK(it != requests_.end());
211 requests_.erase(it);
212 delete request;
213 }
214
215 // If there are no longer any requests attached to the job, cancel and delete
216 // the job.
217 if (requests_.empty() && parent_) {
218 CertNetFetcher* parent = parent_;
219 Cancel();
220 parent->RemoveJob(this);
221 }
222 }
223
224 void CertNetFetcher::Job::StartURLRequest(URLRequestContext* context) {
225 int error = CanFetchUrl(request_params_->url);
226 if (error != OK) {
227 result_net_error_ = error;
228 // The CertNetFetcher's API contract is that requests always complete
229 // asynchronously. This situation is the only one which requires an
230 // explicit PostTask().
231 base::MessageLoop::current()->PostTask(
232 FROM_HERE, base::Bind(&Job::OnJobCompleted, this));
233 return;
234 }
235
236 // Start the URLRequest.
237 read_buffer_ = new IOBuffer(kReadBufferSizeInBytes);
238 url_request_ = context->CreateRequest(request_params_->url, DEFAULT_PRIORITY,
239 this, NULL);
240 if (request_params_->http_method == HTTP_METHOD_POST)
241 url_request_->set_method("POST");
242 url_request_->SetLoadFlags(LOAD_DO_NOT_SAVE_COOKIES |
243 LOAD_DO_NOT_SEND_COOKIES);
244 url_request_->Start();
245
246 // Start a timer to limit how long the job runs for.
247 if (request_params_->timeout > base::TimeDelta())
248 timer_.Start(FROM_HERE, request_params_->timeout, this, &Job::OnTimeout);
249 }
250
251 CertNetFetcher::Job::~Job() {
252 DCHECK(requests_.empty());
253 }
254
255 void CertNetFetcher::Job::OnReceivedRedirect(URLRequest* request,
256 const RedirectInfo& redirect_info,
257 bool* defer_redirect) {
258 DCHECK_EQ(url_request_.get(), request);
259
260 // Ensure that the new URL matches the policy.
261 int error = CanFetchUrl(redirect_info.new_url);
262 if (error != OK) {
263 request->CancelWithError(error);
264 OnUrlRequestCompleted(request);
265 return;
266 }
267 }
268
269 void CertNetFetcher::Job::OnResponseStarted(URLRequest* request) {
270 DCHECK_EQ(url_request_.get(), request);
271
272 if (!request->status().is_success()) {
273 OnUrlRequestCompleted(request);
274 return;
275 }
276
277 // In practice all URLs fetched are HTTPS, but check anyway as defensive
mattm 2015/02/10 05:22:14 HTTP?
eroman 2015/02/11 03:51:52 Done. Indeed, I managed to write the opposite of w
278 // measure in case the policy is ever changed.
279 DCHECK(request->url().SchemeIsHTTPOrHTTPS());
280 int http_response_code = request->GetResponseCode();
281 if (http_response_code != 200 && request->url().SchemeIsHTTPOrHTTPS()) {
282 request->CancelWithError(kNetErrorNot200HttpResponse);
283 OnUrlRequestCompleted(request);
284 return;
285 }
286
287 ReadBody(request);
288 }
289
290 void CertNetFetcher::Job::OnReadCompleted(URLRequest* request, int bytes_read) {
291 DCHECK_EQ(url_request_.get(), request);
292
293 // Keep reading the response body.
294 if (ConsumeBytesRead(request, bytes_read))
295 ReadBody(request);
296 }
297
298 void CertNetFetcher::Job::Stop() {
299 timer_.Stop();
300 url_request_.reset();
301 }
302
303 void CertNetFetcher::Job::ReadBody(URLRequest* request) {
304 // Read as many bytes as are available synchronously.
305 int num_bytes;
306 while (
307 request->Read(read_buffer_.get(), kReadBufferSizeInBytes, &num_bytes)) {
308 if (!ConsumeBytesRead(request, num_bytes))
309 return;
310 }
311
312 // Check whether the read failed synchronously.
313 if (!request->status().is_io_pending())
314 OnUrlRequestCompleted(request);
315 return;
316 }
317
318 bool CertNetFetcher::Job::ConsumeBytesRead(URLRequest* request, int num_bytes) {
319 if (num_bytes <= 0) {
320 // Error while reading, or EOF.
321 OnUrlRequestCompleted(request);
322 return false;
323 }
324
325 // Enforce maximum size bound.
326 if (num_bytes + response_body_.size() >
327 request_params_->max_response_size_in_bytes) {
328 request->CancelWithError(kNetErrorResponseTooLarge);
329 OnUrlRequestCompleted(request);
330 return false;
331 }
332
333 // Append the data to |response_body_|.
334 response_body_.insert(response_body_.end(), read_buffer_->data(),
335 read_buffer_->data() + num_bytes);
336 return true;
337 }
338
339 void CertNetFetcher::Job::OnTimeout() {
340 result_net_error_ = kNetErrorTimedOut;
341 url_request_->CancelWithError(result_net_error_);
342 OnJobCompleted();
343 }
344
345 void CertNetFetcher::Job::OnUrlRequestCompleted(URLRequest* request) {
346 DCHECK_EQ(request, url_request_.get());
347
348 if (request->status().is_success())
349 result_net_error_ = OK;
350 else
351 result_net_error_ = request->status().error();
352
353 OnJobCompleted();
354 }
355
356 void CertNetFetcher::Job::OnJobCompleted() {
357 // Stop the timer and clear the URLRequest.
358 Stop();
359
360 // Check if the job was cancelled. This can happen when running the posted
361 // task for OnJobCompleted() as it is not aborted on cancellation.
362 if (!parent_)
363 return;
364
365 // Invoking the callbacks is subtle as it may cause re-entrancy:
366 // * The parent CertNetFetcher may be deleted (in which case this job is
367 // kept alive by |keep_alive|.
368 // * Requests in this job may be cancelled (which is why |requests_| is not
369 // iterated though.
370 // * New requests may be attached to this job (which is why |requests_| is
371 // not iterated through.
372
373 scoped_refptr<Job> keep_alive(this);
374
375 while (!requests_.empty()) {
376 scoped_ptr<Request> request(requests_.front());
377 requests_.erase(requests_.begin());
378 request->callback.Run(result_net_error_, response_body_);
379 }
380
381 if (parent_)
382 parent_->RemoveJob(this);
383 }
384
385 bool CertNetFetcher::JobComparator::operator()(
386 const scoped_refptr<Job>& job1,
387 const scoped_refptr<Job>& job2) const {
388 return job1->request_params() < job2->request_params();
389 }
390
391 CertNetFetcher::CertNetFetcher(URLRequestContext* context) : context_(context) {
392 }
393
394 CertNetFetcher::~CertNetFetcher() {
395 // Each job is explicitly cancelled since destroying |jobs_| might not
396 // release the final reference to an in-progress job.
397 for (JobSet::iterator it = jobs_.begin(); it != jobs_.end(); ++it) {
398 (*it)->Cancel();
399 }
400 }
401
402 CertNetFetcher::RequestId CertNetFetcher::Fetch(
403 scoped_ptr<RequestParams> request_params,
404 FetchCallback callback) {
405 // If there is an in-progress job that matches the request parameters use it.
406 // Otherwise start a new job.
407 scoped_refptr<Job> job = FindJob(*request_params.get());
408
409 if (!job) {
410 job = new Job(request_params.Pass(), this);
411 jobs_.insert(job);
412 job->StartURLRequest(context_);
413 }
414
415 return job->AddRequest(callback);
416 }
417
418 void CertNetFetcher::CancelRequest(RequestId request) {
419 request->job->CancelRequest(request);
420 }
421
422 struct CertNetFetcher::JobToRequestParamsComparator {
423 bool operator()(const scoped_refptr<Job>& job,
424 const CertNetFetcher::RequestParams& value) const {
425 return job->request_params() < value;
426 }
427 };
428
429 CertNetFetcher::Job* CertNetFetcher::FindJob(const RequestParams& params) {
430 // The JobSet is kept in sorted order so items can be find using binary
431 // search.
432 JobSet::iterator it = std::lower_bound(jobs_.begin(), jobs_.end(), params,
433 JobToRequestParamsComparator());
434 if (it != jobs_.end() && !(params < (*it)->request_params()))
435 return it->get();
436 return NULL;
437 }
438
439 void CertNetFetcher::RemoveJob(Job* job) {
440 DCHECK(jobs_.find(job) != jobs_.end());
441 jobs_.erase(job);
442 }
443
444 } // namespace net
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698