blob: 3d40cd6ee07dd555ffe0831a5a8cc24af1e8b57c [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/download_protection/two_phase_uploader.h"
#include <stdint.h>
#include <limits>
#include "base/bind.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/task_runner.h"
#include "content/public/browser/browser_thread.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"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/simple_url_loader.h"
namespace {
// Header sent on initial request to start the two phase upload process.
const char kStartHeaderKey[] = "x-goog-resumable";
const char kStartHeaderValue[] = "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 TwoPhaseUploader {
public:
TwoPhaseUploaderImpl(
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
base::TaskRunner* file_task_runner,
const GURL& base_url,
const std::string& metadata,
const base::FilePath& file_path,
const FinishCallback& finish_callback,
const net::NetworkTrafficAnnotationTag& traffic_annotation);
~TwoPhaseUploaderImpl() override;
// Begins the upload process.
void Start() override;
void OnURLLoaderComplete(std::unique_ptr<std::string> response_body);
private:
void UploadMetadata();
void UploadFile();
void Finish(int net_error, int response_code, const std::string& response);
State state_;
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
scoped_refptr<base::TaskRunner> file_task_runner_;
GURL base_url_;
GURL upload_url_;
std::string metadata_;
const base::FilePath file_path_;
FinishCallback finish_callback_;
net::NetworkTrafficAnnotationTag traffic_annotation_;
std::unique_ptr<network::SimpleURLLoader> url_loader_;
DISALLOW_COPY_AND_ASSIGN(TwoPhaseUploaderImpl);
};
TwoPhaseUploaderImpl::TwoPhaseUploaderImpl(
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
base::TaskRunner* file_task_runner,
const GURL& base_url,
const std::string& metadata,
const base::FilePath& file_path,
const FinishCallback& finish_callback,
const net::NetworkTrafficAnnotationTag& traffic_annotation)
: state_(STATE_NONE),
url_loader_factory_(url_loader_factory),
file_task_runner_(file_task_runner),
base_url_(base_url),
metadata_(metadata),
file_path_(file_path),
finish_callback_(finish_callback),
traffic_annotation_(traffic_annotation) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
}
TwoPhaseUploaderImpl::~TwoPhaseUploaderImpl() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
}
void TwoPhaseUploaderImpl::Start() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK_EQ(STATE_NONE, state_);
UploadMetadata();
}
void TwoPhaseUploaderImpl::OnURLLoaderComplete(
std::unique_ptr<std::string> response_body) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
int response_code = 0;
if (url_loader_->ResponseInfo() && url_loader_->ResponseInfo()->headers)
response_code = url_loader_->ResponseInfo()->headers->response_code();
DVLOG(1) << __func__ << " " << url_loader_->GetFinalURL().spec() << " "
<< url_loader_->NetError() << " " << response_code;
if (url_loader_->NetError() != net::OK) {
LOG(ERROR) << "URLFetcher failed, err=" << url_loader_->NetError();
Finish(url_loader_->NetError(), response_code, std::string());
return;
}
switch (state_) {
case UPLOAD_METADATA: {
if (response_code != 201) {
LOG(ERROR) << "Invalid response to initial request: " << response_code;
Finish(net::OK, response_code, *response_body.get());
return;
}
std::string location;
if (!url_loader_->ResponseInfo() ||
!url_loader_->ResponseInfo()->headers ||
!url_loader_->ResponseInfo()->headers->EnumerateHeader(
nullptr, 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_body.get());
return;
default:
NOTREACHED();
}
}
void TwoPhaseUploaderImpl::UploadMetadata() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
state_ = UPLOAD_METADATA;
auto resource_request = std::make_unique<network::ResourceRequest>();
resource_request->url = base_url_;
resource_request->method = "POST";
resource_request->headers.SetHeader(kStartHeaderKey, kStartHeaderValue);
url_loader_ = network::SimpleURLLoader::Create(std::move(resource_request),
traffic_annotation_);
url_loader_->SetAllowHttpErrorResults(true);
url_loader_->AttachStringForUpload(metadata_, kUploadContentType);
url_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
url_loader_factory_.get(),
base::BindOnce(&TwoPhaseUploaderImpl::OnURLLoaderComplete,
base::Unretained(this)));
}
void TwoPhaseUploaderImpl::UploadFile() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
state_ = UPLOAD_FILE;
auto resource_request = std::make_unique<network::ResourceRequest>();
resource_request->url = upload_url_;
resource_request->method = "PUT";
url_loader_ = network::SimpleURLLoader::Create(std::move(resource_request),
traffic_annotation_);
url_loader_->SetAllowHttpErrorResults(true);
url_loader_->AttachFileForUpload(file_path_, kUploadContentType);
url_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
url_loader_factory_.get(),
base::BindOnce(&TwoPhaseUploaderImpl::OnURLLoaderComplete,
base::Unretained(this)));
}
void TwoPhaseUploaderImpl::Finish(int net_error,
int response_code,
const std::string& response) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
finish_callback_.Run(state_, net_error, response_code, response);
}
} // namespace
// static
TwoPhaseUploaderFactory* TwoPhaseUploader::factory_ = nullptr;
// static
std::unique_ptr<TwoPhaseUploader> TwoPhaseUploader::Create(
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
base::TaskRunner* file_task_runner,
const GURL& base_url,
const std::string& metadata,
const base::FilePath& file_path,
const FinishCallback& finish_callback,
const net::NetworkTrafficAnnotationTag& traffic_annotation) {
if (!factory_) {
return base::WrapUnique(new TwoPhaseUploaderImpl(
url_loader_factory, file_task_runner, base_url, metadata, file_path,
finish_callback, traffic_annotation));
}
return TwoPhaseUploader::factory_->CreateTwoPhaseUploader(
url_loader_factory, file_task_runner, base_url, metadata, file_path,
finish_callback, traffic_annotation);
}