blob: ac79e41f5705675e0955507977cad89c31cd5bba [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/safe_browsing/two_phase_uploader.h"
#include "base/basictypes.h"
#include "base/bind.h"
#include "base/task_runner.h"
#include "net/base/net_errors.h"
#include "net/http/http_response_headers.h"
#include "net/url_request/url_fetcher.h"
#include "net/url_request/url_fetcher_delegate.h"
#include "net/url_request/url_request_status.h"
namespace {
// Header sent on initial request to start the two phase upload process.
const char* kStartHeader = "x-goog-resumable: start";
// Header returned on initial response with URL to use for the second phase.
const char* kLocationHeader = "Location";
const char* kUploadContentType = "application/octet-stream";
class TwoPhaseUploaderImpl : public net::URLFetcherDelegate,
public TwoPhaseUploader {
public:
TwoPhaseUploaderImpl(net::URLRequestContextGetter* url_request_context_getter,
base::TaskRunner* file_task_runner,
const GURL& base_url,
const std::string& metadata,
const base::FilePath& file_path,
const ProgressCallback& progress_callback,
const FinishCallback& finish_callback);
~TwoPhaseUploaderImpl() override;
// Begins the upload process.
void Start() override;
// net::URLFetcherDelegate implementation:
void OnURLFetchComplete(const net::URLFetcher* source) override;
void OnURLFetchUploadProgress(const net::URLFetcher* source,
int64 current,
int64 total) override;
private:
void UploadMetadata();
void UploadFile();
void Finish(int net_error, int response_code, const std::string& response);
State state_;
scoped_refptr<net::URLRequestContextGetter> url_request_context_getter_;
scoped_refptr<base::TaskRunner> file_task_runner_;
GURL base_url_;
GURL upload_url_;
std::string metadata_;
const base::FilePath file_path_;
ProgressCallback progress_callback_;
FinishCallback finish_callback_;
scoped_ptr<net::URLFetcher> url_fetcher_;
DISALLOW_COPY_AND_ASSIGN(TwoPhaseUploaderImpl);
};
TwoPhaseUploaderImpl::TwoPhaseUploaderImpl(
net::URLRequestContextGetter* url_request_context_getter,
base::TaskRunner* file_task_runner,
const GURL& base_url,
const std::string& metadata,
const base::FilePath& file_path,
const ProgressCallback& progress_callback,
const FinishCallback& finish_callback)
: state_(STATE_NONE),
url_request_context_getter_(url_request_context_getter),
file_task_runner_(file_task_runner),
base_url_(base_url),
metadata_(metadata),
file_path_(file_path),
progress_callback_(progress_callback),
finish_callback_(finish_callback) {
}
TwoPhaseUploaderImpl::~TwoPhaseUploaderImpl() {
DCHECK(CalledOnValidThread());
}
void TwoPhaseUploaderImpl::Start() {
DCHECK(CalledOnValidThread());
DCHECK_EQ(STATE_NONE, state_);
UploadMetadata();
}
void TwoPhaseUploaderImpl::OnURLFetchComplete(const net::URLFetcher* source) {
DCHECK(CalledOnValidThread());
net::URLRequestStatus status = source->GetStatus();
int response_code = source->GetResponseCode();
DVLOG(1) << __FUNCTION__ << " " << source->GetURL().spec()
<< " " << status.status() << " " << response_code;
if (!status.is_success()) {
LOG(ERROR) << "URLFetcher failed, status=" << status.status()
<< " err=" << status.error();
Finish(status.error(), response_code, std::string());
return;
}
std::string response;
source->GetResponseAsString(&response);
switch (state_) {
case UPLOAD_METADATA:
{
if (response_code != 201) {
LOG(ERROR) << "Invalid response to initial request: "
<< response_code;
Finish(net::OK, response_code, response);
return;
}
std::string location;
if (!source->GetResponseHeaders()->EnumerateHeader(
NULL, kLocationHeader, &location)) {
LOG(ERROR) << "no location header";
Finish(net::OK, response_code, std::string());
return;
}
DVLOG(1) << "upload location: " << location;
upload_url_ = GURL(location);
UploadFile();
break;
}
case UPLOAD_FILE:
if (response_code != 200) {
LOG(ERROR) << "Invalid response to upload request: "
<< response_code;
} else {
state_ = STATE_SUCCESS;
}
Finish(net::OK, response_code, response);
return;
default:
NOTREACHED();
};
}
void TwoPhaseUploaderImpl::OnURLFetchUploadProgress(
const net::URLFetcher* source,
int64 current,
int64 total) {
DCHECK(CalledOnValidThread());
DVLOG(3) << __FUNCTION__ << " " << source->GetURL().spec()
<< " " << current << "/" << total;
if (state_ == UPLOAD_FILE && !progress_callback_.is_null())
progress_callback_.Run(current, total);
}
void TwoPhaseUploaderImpl::UploadMetadata() {
DCHECK(CalledOnValidThread());
state_ = UPLOAD_METADATA;
url_fetcher_.reset(net::URLFetcher::Create(base_url_, net::URLFetcher::POST,
this));
url_fetcher_->SetRequestContext(url_request_context_getter_.get());
url_fetcher_->SetExtraRequestHeaders(kStartHeader);
url_fetcher_->SetUploadData(kUploadContentType, metadata_);
url_fetcher_->Start();
}
void TwoPhaseUploaderImpl::UploadFile() {
DCHECK(CalledOnValidThread());
state_ = UPLOAD_FILE;
url_fetcher_.reset(net::URLFetcher::Create(upload_url_, net::URLFetcher::PUT,
this));
url_fetcher_->SetRequestContext(url_request_context_getter_.get());
url_fetcher_->SetUploadFilePath(
kUploadContentType, file_path_, 0, kuint64max, file_task_runner_);
url_fetcher_->Start();
}
void TwoPhaseUploaderImpl::Finish(int net_error,
int response_code,
const std::string& response) {
DCHECK(CalledOnValidThread());
finish_callback_.Run(state_, net_error, response_code, response);
}
} // namespace
// static
TwoPhaseUploaderFactory* TwoPhaseUploader::factory_ = NULL;
// static
TwoPhaseUploader* TwoPhaseUploader::Create(
net::URLRequestContextGetter* url_request_context_getter,
base::TaskRunner* file_task_runner,
const GURL& base_url,
const std::string& metadata,
const base::FilePath& file_path,
const ProgressCallback& progress_callback,
const FinishCallback& finish_callback) {
if (!TwoPhaseUploader::factory_)
return new TwoPhaseUploaderImpl(
url_request_context_getter, file_task_runner, base_url, metadata,
file_path, progress_callback, finish_callback);
return TwoPhaseUploader::factory_->CreateTwoPhaseUploader(
url_request_context_getter, file_task_runner, base_url, metadata,
file_path, progress_callback, finish_callback);
}