OLD | NEW |
(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 |
OLD | NEW |