blob: a2e00d5b3bc9f77628027445aa42eb0547233bf9 [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/policy/cloud/external_policy_data_fetcher.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/sequenced_task_runner.h"
#include "base/stl_util.h"
#include "net/base/load_flags.h"
#include "net/base/net_errors.h"
#include "net/url_request/url_fetcher.h"
#include "net/url_request/url_request_context_getter.h"
#include "net/url_request/url_request_status.h"
namespace policy {
namespace {
// Helper that forwards the result of a fetch job from the thread that the
// ExternalPolicyDataFetcherBackend runs on to the thread that the
// ExternalPolicyDataFetcher which started the job runs on.
void ForwardJobFinished(
scoped_refptr<base::SequencedTaskRunner> task_runner,
const ExternalPolicyDataFetcherBackend::FetchCallback& callback,
ExternalPolicyDataFetcher::Job* job,
ExternalPolicyDataFetcher::Result result,
scoped_ptr<std::string> data) {
task_runner->PostTask(FROM_HERE,
base::Bind(callback, job, result, base::Passed(&data)));
}
// Helper that forwards a job cancelation confirmation from the thread that the
// ExternalPolicyDataFetcherBackend runs on to the thread that the
// ExternalPolicyDataFetcher which canceled the job runs on.
void ForwardJobCanceled(
scoped_refptr<base::SequencedTaskRunner> task_runner,
const base::Closure& callback) {
task_runner->PostTask(FROM_HERE, callback);
}
// Helper invoked when a job cancelation confirmation has been forwarded to the
// thread which canceled the job. The helper itself does nothing. It exists so
// that the |job| can be passed as base::Owned(), allowing it to be deleted on
// the correct thread and after any pending callbacks for the |job| have been
// processed.
void DoNothing(ExternalPolicyDataFetcher::Job* job) {
}
} // namespace
struct ExternalPolicyDataFetcher::Job {
Job(const GURL& url,
int64 max_size,
const ExternalPolicyDataFetcherBackend::FetchCallback& callback);
const GURL url;
const int64 max_size;
const ExternalPolicyDataFetcherBackend::FetchCallback callback;
private:
DISALLOW_COPY_AND_ASSIGN(Job);
};
ExternalPolicyDataFetcher::Job::Job(
const GURL& url,
int64 max_size,
const ExternalPolicyDataFetcherBackend::FetchCallback& callback)
: url(url),
max_size(max_size),
callback(callback) {
}
ExternalPolicyDataFetcher::ExternalPolicyDataFetcher(
scoped_refptr<base::SequencedTaskRunner> task_runner,
scoped_refptr<base::SequencedTaskRunner> io_task_runner,
const base::WeakPtr<ExternalPolicyDataFetcherBackend>& backend)
: task_runner_(task_runner),
io_task_runner_(io_task_runner),
backend_(backend),
weak_factory_(this) {
}
ExternalPolicyDataFetcher::~ExternalPolicyDataFetcher() {
DCHECK(task_runner_->RunsTasksOnCurrentThread());
for (JobSet::iterator it = jobs_.begin(); it != jobs_.end(); ++it)
CancelJob(*it);
}
ExternalPolicyDataFetcher::Job* ExternalPolicyDataFetcher::StartJob(
const GURL& url,
int64 max_size,
const FetchCallback& callback) {
DCHECK(task_runner_->RunsTasksOnCurrentThread());
Job* job = new Job(
url, max_size,
base::Bind(&ForwardJobFinished,
task_runner_,
base::Bind(&ExternalPolicyDataFetcher::OnJobFinished,
weak_factory_.GetWeakPtr(),
callback)));
jobs_.insert(job);
io_task_runner_->PostTask(
FROM_HERE,
base::Bind(&ExternalPolicyDataFetcherBackend::StartJob, backend_, job));
return job;
}
void ExternalPolicyDataFetcher::CancelJob(Job* job) {
DCHECK(task_runner_->RunsTasksOnCurrentThread());
DCHECK(jobs_.find(job) != jobs_.end());
jobs_.erase(job);
// Post a task that will cancel the |job| in the |backend_|. The |job| is
// removed from |jobs_| immediately to indicate that it has been canceled but
// is not actually deleted until the cancelation has reached the |backend_|
// and a confirmation has been posted back. This ensures that no new job can
// be allocated at the same address while an OnJobFinished() callback may
// still be pending for the canceled |job|.
io_task_runner_->PostTask(
FROM_HERE,
base::Bind(&ExternalPolicyDataFetcherBackend::CancelJob,
backend_,
job,
base::Bind(&ForwardJobCanceled,
task_runner_,
base::Bind(&DoNothing, base::Owned(job)))));
}
void ExternalPolicyDataFetcher::OnJobFinished(const FetchCallback& callback,
Job* job,
Result result,
scoped_ptr<std::string> data) {
DCHECK(task_runner_->RunsTasksOnCurrentThread());
JobSet::iterator it = jobs_.find(job);
if (it == jobs_.end()) {
// The |job| has been canceled and removed from |jobs_| already. This can
// happen because the |backend_| runs on a different thread and a |job| may
// finish before the cancellation has reached that thread.
return;
}
callback.Run(result, data.Pass());
jobs_.erase(it);
delete job;
}
ExternalPolicyDataFetcherBackend::ExternalPolicyDataFetcherBackend(
scoped_refptr<base::SequencedTaskRunner> io_task_runner,
scoped_refptr<net::URLRequestContextGetter> request_context)
: io_task_runner_(io_task_runner),
request_context_(request_context),
last_fetch_id_(-1),
weak_factory_(this) {
}
ExternalPolicyDataFetcherBackend::~ExternalPolicyDataFetcherBackend() {
DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
STLDeleteContainerPairFirstPointers(job_map_.begin(), job_map_.end());
}
scoped_ptr<ExternalPolicyDataFetcher>
ExternalPolicyDataFetcherBackend::CreateFrontend(
scoped_refptr<base::SequencedTaskRunner> task_runner) {
return make_scoped_ptr(new ExternalPolicyDataFetcher(
task_runner, io_task_runner_, weak_factory_.GetWeakPtr()));
}
void ExternalPolicyDataFetcherBackend::StartJob(
ExternalPolicyDataFetcher::Job* job) {
DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
net::URLFetcher* fetcher = net::URLFetcher::Create(
++last_fetch_id_, job->url, net::URLFetcher::GET, this);
fetcher->SetRequestContext(request_context_.get());
fetcher->SetLoadFlags(net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE |
net::LOAD_DO_NOT_SAVE_COOKIES | net::LOAD_IS_DOWNLOAD |
net::LOAD_DO_NOT_SEND_COOKIES |
net::LOAD_DO_NOT_SEND_AUTH_DATA);
fetcher->SetAutomaticallyRetryOnNetworkChanges(3);
fetcher->Start();
job_map_[fetcher] = job;
}
void ExternalPolicyDataFetcherBackend::CancelJob(
ExternalPolicyDataFetcher::Job* job,
const base::Closure& callback) {
DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
for (JobMap::iterator it = job_map_.begin(); it != job_map_.end(); ) {
if (it->second == job) {
delete it->first;
job_map_.erase(it++);
} else {
++it;
}
}
callback.Run();
}
void ExternalPolicyDataFetcherBackend::OnURLFetchComplete(
const net::URLFetcher* source) {
DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
JobMap::iterator it = job_map_.find(const_cast<net::URLFetcher*>(source));
if (it == job_map_.end()) {
NOTREACHED();
return;
}
ExternalPolicyDataFetcher::Result result = ExternalPolicyDataFetcher::SUCCESS;
scoped_ptr<std::string> data;
const net::URLRequestStatus status = it->first->GetStatus();
if (status.error() == net::ERR_CONNECTION_RESET ||
status.error() == net::ERR_TEMPORARILY_THROTTLED) {
// The connection was interrupted.
result = ExternalPolicyDataFetcher::CONNECTION_INTERRUPTED;
} else if (status.status() != net::URLRequestStatus::SUCCESS) {
// Another network error occurred.
result = ExternalPolicyDataFetcher::NETWORK_ERROR;
} else if (source->GetResponseCode() >= 500) {
// Problem at the server.
result = ExternalPolicyDataFetcher::SERVER_ERROR;
} else if (source->GetResponseCode() >= 400) {
// Client error.
result = ExternalPolicyDataFetcher::CLIENT_ERROR;
} else if (source->GetResponseCode() != 200) {
// Any other type of HTTP failure.
result = ExternalPolicyDataFetcher::HTTP_ERROR;
} else {
data.reset(new std::string);
source->GetResponseAsString(data.get());
if (static_cast<int64>(data->size()) > it->second->max_size) {
// Received |data| exceeds maximum allowed size.
data.reset();
result = ExternalPolicyDataFetcher::MAX_SIZE_EXCEEDED;
}
}
ExternalPolicyDataFetcher::Job* job = it->second;
delete it->first;
job_map_.erase(it);
job->callback.Run(job, result, data.Pass());
}
void ExternalPolicyDataFetcherBackend::OnURLFetchDownloadProgress(
const net::URLFetcher* source,
int64 current,
int64 total) {
DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
JobMap::iterator it = job_map_.find(const_cast<net::URLFetcher*>(source));
DCHECK(it != job_map_.end());
if (it == job_map_.end())
return;
// Reject the data if it exceeds the size limit. The content length is in
// |total|, and it may be -1 when not known.
if (current > it->second->max_size || total > it->second->max_size) {
ExternalPolicyDataFetcher::Job* job = it->second;
delete it->first;
job_map_.erase(it);
job->callback.Run(job,
ExternalPolicyDataFetcher::MAX_SIZE_EXCEEDED,
scoped_ptr<std::string>());
}
}
} // namespace policy