1// Copyright (c) 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 "chrome/browser/policy/cloud/external_policy_data_updater.h"
6
7#include "base/bind.h"
8#include "base/callback.h"
9#include "base/compiler_specific.h"
10#include "base/location.h"
11#include "base/logging.h"
12#include "base/memory/scoped_ptr.h"
13#include "base/sequenced_task_runner.h"
14#include "base/sha1.h"
15#include "base/stl_util.h"
16#include "net/base/backoff_entry.h"
17#include "net/base/load_flags.h"
18#include "net/base/net_errors.h"
19#include "net/url_request/url_fetcher.h"
20#include "net/url_request/url_fetcher_delegate.h"
21#include "net/url_request/url_request_context_getter.h"
22#include "net/url_request/url_request_status.h"
23#include "url/gurl.h"
24
25namespace policy {
26
27namespace {
28
29// Policies for exponential backoff of failed requests. There are 3 policies for
30// different classes of errors.
31
32// For temporary errors (HTTP 500, RST, etc).
33const net::BackoffEntry::Policy kRetrySoonPolicy = {
34  // Number of initial errors to ignore before starting to back off.
35  0,
36
37  // Initial delay in ms: 60 seconds.
38  1000 * 60,
39
40  // Factor by which the waiting time is multiplied.
41  2,
42
43  // Fuzzing percentage; this spreads delays randomly between 80% and 100%
44  // of the calculated time.
45  0.20,
46
47  // Maximum delay in ms: 12 hours.
48  1000 * 60 * 60 * 12,
49
50  // When to discard an entry: never.
51  -1,
52
53  // |always_use_initial_delay|; false means that the initial delay is
54  // applied after the first error, and starts backing off from there.
55  false,
56};
57
58// For other errors (request failed, server errors).
59const net::BackoffEntry::Policy kRetryLaterPolicy = {
60  // Number of initial errors to ignore before starting to back off.
61  0,
62
63  // Initial delay in ms: 1 hour.
64  1000 * 60 * 60,
65
66  // Factor by which the waiting time is multiplied.
67  2,
68
69  // Fuzzing percentage; this spreads delays randomly between 80% and 100%
70  // of the calculated time.
71  0.20,
72
73  // Maximum delay in ms: 12 hours.
74  1000 * 60 * 60 * 12,
75
76  // When to discard an entry: never.
77  -1,
78
79  // |always_use_initial_delay|; false means that the initial delay is
80  // applied after the first error, and starts backing off from there.
81  false,
82};
83
84// When the data fails validation (maybe because the policy URL and the data
85// served at that URL are out of sync). This essentially retries every 12 hours,
86// with some random jitter.
87const net::BackoffEntry::Policy kRetryMuchLaterPolicy = {
88  // Number of initial errors to ignore before starting to back off.
89  0,
90
91  // Initial delay in ms: 12 hours.
92  1000 * 60 * 60 * 12,
93
94  // Factor by which the waiting time is multiplied.
95  2,
96
97  // Fuzzing percentage; this spreads delays randomly between 80% and 100%
98  // of the calculated time.
99  0.20,
100
101  // Maximum delay in ms: 12 hours.
102  1000 * 60 * 60 * 12,
103
104  // When to discard an entry: never.
105  -1,
106
107  // |always_use_initial_delay|; false means that the initial delay is
108  // applied after the first error, and starts backing off from there.
109  false,
110};
111
112// Maximum number of retries for requests that aren't likely to get a
113// different response (e.g. HTTP 4xx replies).
114const int kMaxLimitedRetries = 3;
115
116}  // namespace
117
118class ExternalPolicyDataUpdater::FetchJob
119    : public base::SupportsWeakPtr<FetchJob>,
120      public net::URLFetcherDelegate {
121 public:
122  FetchJob(ExternalPolicyDataUpdater* updater,
123           int id,
124           const std::string& key,
125           const ExternalPolicyDataUpdater::Request& request,
126           const ExternalPolicyDataUpdater::FetchSuccessCallback& callback);
127  virtual ~FetchJob();
128
129  const std::string& key() const;
130  const ExternalPolicyDataUpdater::Request& request() const;
131
132  void Start();
133
134  // URLFetcherDelegate implementation:
135  virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE;
136  virtual void OnURLFetchDownloadProgress(const net::URLFetcher* source,
137                                          int64 current,
138                                          int64 total) OVERRIDE;
139
140 private:
141  void OnFailed(net::BackoffEntry* backoff_entry);
142  void Reschedule();
143
144  // Always valid as long as |this| is alive.
145  ExternalPolicyDataUpdater* updater_;
146
147  const int id_;
148  const std::string key_;
149  const ExternalPolicyDataUpdater::Request request_;
150  ExternalPolicyDataUpdater::FetchSuccessCallback callback_;
151
152  // If |fetcher_| exists, the job is currently running and must call back to
153  // the |updater_|'s OnJobSucceeded() or OnJobFailed() method eventually.
154  scoped_ptr<net::URLFetcher> fetcher_;
155
156  // Some errors should trigger a limited number of retries, even with backoff.
157  // This counts down the number of such retries to stop retrying once the limit
158  // is reached.
159  int limited_retries_remaining_;
160
161  // Various delays to retry a failed download, depending on the failure reason.
162  net::BackoffEntry retry_soon_entry_;
163  net::BackoffEntry retry_later_entry_;
164  net::BackoffEntry retry_much_later_entry_;
165
166  DISALLOW_COPY_AND_ASSIGN(FetchJob);
167};
168
169ExternalPolicyDataUpdater::Request::Request() {
170}
171
172ExternalPolicyDataUpdater::Request::Request(const std::string& url,
173                                            const std::string& hash,
174                                            int64 max_size)
175    : url(url), hash(hash), max_size(max_size) {
176}
177
178bool ExternalPolicyDataUpdater::Request::operator==(
179    const Request& other) const {
180  return url == other.url && hash == other.hash && max_size == other.max_size;
181}
182
183ExternalPolicyDataUpdater::FetchJob::FetchJob(
184    ExternalPolicyDataUpdater* updater,
185    int id,
186    const std::string& key,
187    const ExternalPolicyDataUpdater::Request& request,
188    const ExternalPolicyDataUpdater::FetchSuccessCallback& callback)
189    : updater_(updater),
190      id_(id),
191      key_(key),
192      request_(request),
193      callback_(callback),
194      limited_retries_remaining_(kMaxLimitedRetries),
195      retry_soon_entry_(&kRetrySoonPolicy),
196      retry_later_entry_(&kRetryLaterPolicy),
197      retry_much_later_entry_(&kRetryMuchLaterPolicy) {
198}
199
200ExternalPolicyDataUpdater::FetchJob::~FetchJob() {
201  if (fetcher_) {
202    fetcher_.reset();
203    // This job is currently running. Inform the updater that it was canceled.
204    updater_->OnJobFailed(this);
205  }
206}
207
208const std::string& ExternalPolicyDataUpdater::FetchJob::key() const {
209  return key_;
210}
211
212const ExternalPolicyDataUpdater::Request&
213    ExternalPolicyDataUpdater::FetchJob::request() const {
214  return request_;
215}
216
217void ExternalPolicyDataUpdater::FetchJob::Start() {
218  fetcher_.reset(net::URLFetcher::Create(id_, GURL(request_.url),
219                                         net::URLFetcher::GET, this));
220  fetcher_->SetRequestContext(updater_->request_context_.get());
221  fetcher_->SetLoadFlags(net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE |
222                         net::LOAD_DO_NOT_SAVE_COOKIES | net::LOAD_IS_DOWNLOAD |
223                         net::LOAD_DO_NOT_SEND_COOKIES |
224                         net::LOAD_DO_NOT_SEND_AUTH_DATA);
225  fetcher_->SetAutomaticallyRetryOnNetworkChanges(3);
226  fetcher_->Start();
227}
228
229void ExternalPolicyDataUpdater::FetchJob::OnURLFetchComplete(
230    const net::URLFetcher* source) {
231  DCHECK_EQ(fetcher_.get(), source);
232
233  const net::URLRequestStatus status = source->GetStatus();
234
235  // The connection was interrupted. Try again soon.
236  if (status.error() == net::ERR_CONNECTION_RESET ||
237      status.error() == net::ERR_TEMPORARILY_THROTTLED) {
238    OnFailed(&retry_soon_entry_);
239    return;
240  }
241
242  // Another network error occurred. Try again later.
243  if (status.status() != net::URLRequestStatus::SUCCESS) {
244    OnFailed(&retry_later_entry_);
245    return;
246  }
247
248  // Problem at the server. Try again soon.
249  if (source->GetResponseCode() >= 500) {
250    OnFailed(&retry_soon_entry_);
251    return;
252  }
253
254  // Client error. This is unlikely to go away. Try again later, and give up
255  // retrying after 3 attempts.
256  if (source->GetResponseCode() >= 400) {
257    OnFailed(limited_retries_remaining_ ? &retry_later_entry_ : NULL);
258    if (limited_retries_remaining_)
259      --limited_retries_remaining_;
260    return;
261  }
262
263  // Any other type of HTTP failure. Try again later.
264  if (source->GetResponseCode() != 200) {
265    OnFailed(&retry_later_entry_);
266    return;
267  }
268
269  std::string data;
270  if (!source->GetResponseAsString(&data) ||
271      static_cast<int64>(data.size()) > request_.max_size ||
272      base::SHA1HashString(data) != request_.hash) {
273    // Failed to retrieve |data|, its size exceeds the limit or its hash does
274    // not match the expected value. This may be because the data being served
275    // is stale. Try again much later.
276    OnFailed(&retry_much_later_entry_);
277    return;
278  }
279
280  // If the callback rejects the data, try again much later.
281  if (!callback_.Run(data)) {
282    OnFailed(&retry_much_later_entry_);
283    return;
284  }
285
286  // Signal success.
287  fetcher_.reset();
288  updater_->OnJobSucceeded(this);
289}
290
291void ExternalPolicyDataUpdater::FetchJob::OnURLFetchDownloadProgress(
292    const net::URLFetcher* source,
293    int64 current,
294    int64 total) {
295  DCHECK_EQ(fetcher_.get(), source);
296  // Reject the data if it exceeds the size limit. The content length is in
297  // |total|, and it may be -1 when not known.
298  if (current > request_.max_size || total > request_.max_size)
299    OnFailed(&retry_much_later_entry_);
300}
301
302void ExternalPolicyDataUpdater::FetchJob::OnFailed(net::BackoffEntry* entry) {
303  fetcher_.reset();
304
305  if (entry) {
306    entry->InformOfRequest(false);
307
308    // This function may have been invoked because the job was obsoleted and is
309    // in the process of being deleted. If this is the case, the WeakPtr will
310    // become invalid and the delayed task will never run.
311    updater_->task_runner_->PostDelayedTask(
312        FROM_HERE,
313        base::Bind(&FetchJob::Reschedule, AsWeakPtr()),
314        entry->GetTimeUntilRelease());
315  }
316
317  updater_->OnJobFailed(this);
318}
319
320void ExternalPolicyDataUpdater::FetchJob::Reschedule() {
321  updater_->ScheduleJob(this);
322}
323
324ExternalPolicyDataUpdater::ExternalPolicyDataUpdater(
325    scoped_refptr<base::SequencedTaskRunner> task_runner,
326    scoped_refptr<net::URLRequestContextGetter> request_context,
327    size_t max_parallel_fetches)
328    : task_runner_(task_runner),
329      request_context_(request_context),
330      max_parallel_jobs_(max_parallel_fetches),
331      running_jobs_(0),
332      next_job_id_(0),
333      shutting_down_(false) {
334  DCHECK(task_runner_->RunsTasksOnCurrentThread());
335}
336
337ExternalPolicyDataUpdater::~ExternalPolicyDataUpdater() {
338  DCHECK(CalledOnValidThread());
339  shutting_down_ = true;
340  STLDeleteValues(&job_map_);
341}
342
343void ExternalPolicyDataUpdater::FetchExternalData(
344    const std::string key,
345    const Request& request,
346    const FetchSuccessCallback& callback) {
347  DCHECK(CalledOnValidThread());
348
349  // Check whether a job exists for this |key| already.
350  FetchJob* job = job_map_[key];
351  if (job) {
352    // If the current |job| is handling the given |request| already, nothing
353    // needs to be done.
354    if (job->request() == request)
355      return;
356
357    // Otherwise, the current |job| is obsolete. If the |job| is on the queue,
358    // its WeakPtr will be invalidated and skipped by StartNextJobs(). If |job|
359    // is currently running, it will call OnJobFailed() immediately.
360    delete job;
361    job_map_.erase(key);
362  }
363
364  // Start a new job to handle |request|.
365  job = new FetchJob(this, next_job_id_++, key, request, callback);
366  job_map_[key] = job;
367  ScheduleJob(job);
368}
369
370void ExternalPolicyDataUpdater::CancelExternalDataFetch(
371    const std::string& key) {
372  DCHECK(CalledOnValidThread());
373
374  // If a |job| exists for this |key|, delete it. If the |job| is on the queue,
375  // its WeakPtr will be invalidated and skipped by StartNextJobs(). If |job| is
376  // currently running, it will call OnJobFailed() immediately.
377  std::map<std::string, FetchJob*>::iterator job = job_map_.find(key);
378  if (job != job_map_.end()) {
379    delete job->second;
380    job_map_.erase(job);
381  }
382}
383
384void ExternalPolicyDataUpdater::StartNextJobs() {
385  if (shutting_down_)
386    return;
387
388  while (running_jobs_ < max_parallel_jobs_ && !job_queue_.empty()) {
389    FetchJob* job = job_queue_.front().get();
390    job_queue_.pop();
391
392    // Some of the jobs may have been invalidated, and have to be skipped.
393    if (job) {
394      ++running_jobs_;
395      // A started job will always call OnJobSucceeded() or OnJobFailed().
396      job->Start();
397    }
398  }
399}
400
401void ExternalPolicyDataUpdater::ScheduleJob(FetchJob* job) {
402  DCHECK_EQ(job_map_[job->key()], job);
403
404  job_queue_.push(job->AsWeakPtr());
405
406  StartNextJobs();
407}
408
409void ExternalPolicyDataUpdater::OnJobSucceeded(FetchJob* job) {
410  DCHECK(running_jobs_);
411  DCHECK_EQ(job_map_[job->key()], job);
412
413  --running_jobs_;
414  job_map_.erase(job->key());
415  delete job;
416
417  StartNextJobs();
418}
419
420void ExternalPolicyDataUpdater::OnJobFailed(FetchJob* job) {
421  DCHECK(running_jobs_);
422  DCHECK_EQ(job_map_[job->key()], job);
423
424  --running_jobs_;
425
426  // The job is not deleted when it fails because a retry attempt may have been
427  // scheduled.
428  StartNextJobs();
429}
430
431}  // namespace policy
432