Home | History | Annotate | Download | only in cloud
      1 // Copyright 2013 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 "components/policy/core/common/cloud/external_policy_data_fetcher.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/bind_helpers.h"
      9 #include "base/location.h"
     10 #include "base/logging.h"
     11 #include "base/sequenced_task_runner.h"
     12 #include "base/stl_util.h"
     13 #include "net/base/load_flags.h"
     14 #include "net/base/net_errors.h"
     15 #include "net/url_request/url_fetcher.h"
     16 #include "net/url_request/url_request_context_getter.h"
     17 #include "net/url_request/url_request_status.h"
     18 
     19 namespace policy {
     20 
     21 namespace {
     22 
     23 // Helper that forwards the result of a fetch job from the thread that the
     24 // ExternalPolicyDataFetcherBackend runs on to the thread that the
     25 // ExternalPolicyDataFetcher which started the job runs on.
     26 void ForwardJobFinished(
     27     scoped_refptr<base::SequencedTaskRunner> task_runner,
     28     const ExternalPolicyDataFetcherBackend::FetchCallback& callback,
     29     ExternalPolicyDataFetcher::Job* job,
     30     ExternalPolicyDataFetcher::Result result,
     31     scoped_ptr<std::string> data) {
     32   task_runner->PostTask(FROM_HERE,
     33                         base::Bind(callback, job, result, base::Passed(&data)));
     34 }
     35 
     36 // Helper that forwards a job cancelation confirmation from the thread that the
     37 // ExternalPolicyDataFetcherBackend runs on to the thread that the
     38 // ExternalPolicyDataFetcher which canceled the job runs on.
     39 void ForwardJobCanceled(
     40     scoped_refptr<base::SequencedTaskRunner> task_runner,
     41     const base::Closure& callback) {
     42   task_runner->PostTask(FROM_HERE, callback);
     43 }
     44 
     45 // Helper invoked when a job cancelation confirmation has been forwarded to the
     46 // thread which canceled the job. The helper itself does nothing. It exists so
     47 // that the |job| can be passed as base::Owned(), allowing it to be deleted on
     48 // the correct thread and after any pending callbacks for the |job| have been
     49 // processed.
     50 void DoNothing(ExternalPolicyDataFetcher::Job* job) {
     51 }
     52 
     53 }  // namespace
     54 
     55 struct ExternalPolicyDataFetcher::Job {
     56   Job(const GURL& url,
     57       int64 max_size,
     58       const ExternalPolicyDataFetcherBackend::FetchCallback& callback);
     59 
     60   const GURL url;
     61   const int64 max_size;
     62   const ExternalPolicyDataFetcherBackend::FetchCallback callback;
     63 
     64  private:
     65   DISALLOW_COPY_AND_ASSIGN(Job);
     66 };
     67 
     68 ExternalPolicyDataFetcher::Job::Job(
     69     const GURL& url,
     70     int64 max_size,
     71     const ExternalPolicyDataFetcherBackend::FetchCallback& callback)
     72     : url(url),
     73       max_size(max_size),
     74       callback(callback) {
     75 }
     76 
     77 ExternalPolicyDataFetcher::ExternalPolicyDataFetcher(
     78     scoped_refptr<base::SequencedTaskRunner> task_runner,
     79     scoped_refptr<base::SequencedTaskRunner> io_task_runner,
     80     const base::WeakPtr<ExternalPolicyDataFetcherBackend>& backend)
     81     : task_runner_(task_runner),
     82       io_task_runner_(io_task_runner),
     83       backend_(backend),
     84       weak_factory_(this) {
     85 }
     86 
     87 ExternalPolicyDataFetcher::~ExternalPolicyDataFetcher() {
     88   DCHECK(task_runner_->RunsTasksOnCurrentThread());
     89   for (JobSet::iterator it = jobs_.begin(); it != jobs_.end(); ++it)
     90     CancelJob(*it);
     91 }
     92 
     93 ExternalPolicyDataFetcher::Job* ExternalPolicyDataFetcher::StartJob(
     94     const GURL& url,
     95     int64 max_size,
     96     const FetchCallback& callback) {
     97   DCHECK(task_runner_->RunsTasksOnCurrentThread());
     98   Job* job = new Job(
     99       url, max_size,
    100       base::Bind(&ForwardJobFinished,
    101                  task_runner_,
    102                  base::Bind(&ExternalPolicyDataFetcher::OnJobFinished,
    103                             weak_factory_.GetWeakPtr(),
    104                             callback)));
    105   jobs_.insert(job);
    106   io_task_runner_->PostTask(
    107       FROM_HERE,
    108       base::Bind(&ExternalPolicyDataFetcherBackend::StartJob, backend_, job));
    109   return job;
    110 }
    111 
    112 void ExternalPolicyDataFetcher::CancelJob(Job* job) {
    113   DCHECK(task_runner_->RunsTasksOnCurrentThread());
    114   DCHECK(jobs_.find(job) != jobs_.end());
    115   jobs_.erase(job);
    116   // Post a task that will cancel the |job| in the |backend_|. The |job| is
    117   // removed from |jobs_| immediately to indicate that it has been canceled but
    118   // is not actually deleted until the cancelation has reached the |backend_|
    119   // and a confirmation has been posted back. This ensures that no new job can
    120   // be allocated at the same address while an OnJobFinished() callback may
    121   // still be pending for the canceled |job|.
    122   io_task_runner_->PostTask(
    123       FROM_HERE,
    124       base::Bind(&ExternalPolicyDataFetcherBackend::CancelJob,
    125                  backend_,
    126                  job,
    127                  base::Bind(&ForwardJobCanceled,
    128                             task_runner_,
    129                             base::Bind(&DoNothing, base::Owned(job)))));
    130 }
    131 
    132 void ExternalPolicyDataFetcher::OnJobFinished(const FetchCallback& callback,
    133                                               Job* job,
    134                                               Result result,
    135                                               scoped_ptr<std::string> data) {
    136   DCHECK(task_runner_->RunsTasksOnCurrentThread());
    137   JobSet::iterator it = jobs_.find(job);
    138   if (it == jobs_.end()) {
    139     // The |job| has been canceled and removed from |jobs_| already. This can
    140     // happen because the |backend_| runs on a different thread and a |job| may
    141     // finish before the cancellation has reached that thread.
    142     return;
    143   }
    144   callback.Run(result, data.Pass());
    145   jobs_.erase(it);
    146   delete job;
    147 }
    148 
    149 ExternalPolicyDataFetcherBackend::ExternalPolicyDataFetcherBackend(
    150     scoped_refptr<base::SequencedTaskRunner> io_task_runner,
    151     scoped_refptr<net::URLRequestContextGetter> request_context)
    152     : io_task_runner_(io_task_runner),
    153       request_context_(request_context),
    154       last_fetch_id_(-1),
    155       weak_factory_(this) {
    156 }
    157 
    158 ExternalPolicyDataFetcherBackend::~ExternalPolicyDataFetcherBackend() {
    159   DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
    160   STLDeleteContainerPairFirstPointers(job_map_.begin(), job_map_.end());
    161 }
    162 
    163 scoped_ptr<ExternalPolicyDataFetcher>
    164     ExternalPolicyDataFetcherBackend::CreateFrontend(
    165         scoped_refptr<base::SequencedTaskRunner> task_runner) {
    166   return make_scoped_ptr(new ExternalPolicyDataFetcher(
    167       task_runner, io_task_runner_, weak_factory_.GetWeakPtr()));
    168 }
    169 
    170 void ExternalPolicyDataFetcherBackend::StartJob(
    171     ExternalPolicyDataFetcher::Job* job) {
    172   DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
    173   net::URLFetcher* fetcher = net::URLFetcher::Create(
    174       ++last_fetch_id_, job->url, net::URLFetcher::GET, this);
    175   fetcher->SetRequestContext(request_context_.get());
    176   fetcher->SetLoadFlags(net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE |
    177                         net::LOAD_DO_NOT_SAVE_COOKIES | net::LOAD_IS_DOWNLOAD |
    178                         net::LOAD_DO_NOT_SEND_COOKIES |
    179                         net::LOAD_DO_NOT_SEND_AUTH_DATA);
    180   fetcher->SetAutomaticallyRetryOnNetworkChanges(3);
    181   fetcher->Start();
    182   job_map_[fetcher] = job;
    183 }
    184 
    185 void ExternalPolicyDataFetcherBackend::CancelJob(
    186     ExternalPolicyDataFetcher::Job* job,
    187     const base::Closure& callback) {
    188   DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
    189   for (JobMap::iterator it = job_map_.begin(); it != job_map_.end(); ) {
    190     if (it->second == job) {
    191       delete it->first;
    192       job_map_.erase(it++);
    193     } else {
    194       ++it;
    195     }
    196   }
    197   callback.Run();
    198 }
    199 
    200 void ExternalPolicyDataFetcherBackend::OnURLFetchComplete(
    201     const net::URLFetcher* source) {
    202   DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
    203   JobMap::iterator it = job_map_.find(const_cast<net::URLFetcher*>(source));
    204   if (it == job_map_.end()) {
    205     NOTREACHED();
    206     return;
    207   }
    208 
    209   ExternalPolicyDataFetcher::Result result = ExternalPolicyDataFetcher::SUCCESS;
    210   scoped_ptr<std::string> data;
    211 
    212   const net::URLRequestStatus status = it->first->GetStatus();
    213   if (status.error() == net::ERR_CONNECTION_RESET ||
    214       status.error() == net::ERR_TEMPORARILY_THROTTLED) {
    215     // The connection was interrupted.
    216     result = ExternalPolicyDataFetcher::CONNECTION_INTERRUPTED;
    217   } else if (status.status() != net::URLRequestStatus::SUCCESS) {
    218     // Another network error occurred.
    219     result = ExternalPolicyDataFetcher::NETWORK_ERROR;
    220   } else if (source->GetResponseCode() >= 500) {
    221     // Problem at the server.
    222     result = ExternalPolicyDataFetcher::SERVER_ERROR;
    223   } else if (source->GetResponseCode() >= 400) {
    224     // Client error.
    225     result = ExternalPolicyDataFetcher::CLIENT_ERROR;
    226   } else if (source->GetResponseCode() != 200) {
    227     // Any other type of HTTP failure.
    228     result = ExternalPolicyDataFetcher::HTTP_ERROR;
    229   } else {
    230     data.reset(new std::string);
    231     source->GetResponseAsString(data.get());
    232     if (static_cast<int64>(data->size()) > it->second->max_size) {
    233       // Received |data| exceeds maximum allowed size.
    234       data.reset();
    235       result = ExternalPolicyDataFetcher::MAX_SIZE_EXCEEDED;
    236     }
    237   }
    238 
    239   ExternalPolicyDataFetcher::Job* job = it->second;
    240   delete it->first;
    241   job_map_.erase(it);
    242   job->callback.Run(job, result, data.Pass());
    243 }
    244 
    245 void ExternalPolicyDataFetcherBackend::OnURLFetchDownloadProgress(
    246     const net::URLFetcher* source,
    247     int64 current,
    248     int64 total) {
    249   DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
    250   JobMap::iterator it = job_map_.find(const_cast<net::URLFetcher*>(source));
    251   DCHECK(it != job_map_.end());
    252   if (it == job_map_.end())
    253     return;
    254 
    255   // Reject the data if it exceeds the size limit. The content length is in
    256   // |total|, and it may be -1 when not known.
    257   if (current > it->second->max_size || total > it->second->max_size) {
    258     ExternalPolicyDataFetcher::Job* job = it->second;
    259     delete it->first;
    260     job_map_.erase(it);
    261     job->callback.Run(job,
    262                       ExternalPolicyDataFetcher::MAX_SIZE_EXCEEDED,
    263                       scoped_ptr<std::string>());
    264   }
    265 }
    266 
    267 }  // namespace policy
    268