blob: 98c8c10ed36e72bc4fdfbecfda635502867a59b4 [file] [log] [blame]
// Copyright (c) 2012 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 "content/renderer/fetchers/resource_fetcher_impl.h"
#include <stdint.h>
#include "base/logging.h"
#include "base/macros.h"
#include "base/single_thread_task_runner.h"
#include "content/public/common/referrer.h"
#include "content/public/renderer/render_frame.h"
#include "content/renderer/loader/resource_dispatcher.h"
#include "content/renderer/loader/web_url_request_util.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "net/base/net_errors.h"
#include "net/http/http_request_headers.h"
#include "net/url_request/url_request_context.h"
#include "services/network/public/cpp/resource_request_body.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/mojom/url_loader_factory.mojom.h"
#include "third_party/blink/public/platform/web_security_origin.h"
#include "third_party/blink/public/platform/web_string.h"
#include "third_party/blink/public/platform/web_url.h"
#include "third_party/blink/public/platform/web_url_response.h"
#include "third_party/blink/public/web/web_document.h"
#include "third_party/blink/public/web/web_local_frame.h"
namespace {
constexpr int32_t kRoutingId = 0;
const char kAccessControlAllowOriginHeader[] = "Access-Control-Allow-Origin";
} // namespace
namespace content {
// static
std::unique_ptr<ResourceFetcher> ResourceFetcher::Create(const GURL& url) {
// Can not use std::make_unique<> because the constructor is private.
return std::unique_ptr<ResourceFetcher>(new ResourceFetcherImpl(url));
}
// TODO(toyoshim): Internal implementation might be replaced with
// SimpleURLLoader, and content::ResourceFetcher could be a thin-wrapper
// class to use SimpleURLLoader with blink-friendly types.
class ResourceFetcherImpl::ClientImpl : public network::mojom::URLLoaderClient {
public:
ClientImpl(ResourceFetcherImpl* parent,
Callback callback,
size_t maximum_download_size,
scoped_refptr<base::SingleThreadTaskRunner> task_runner)
: parent_(parent),
client_binding_(this),
data_pipe_watcher_(FROM_HERE,
mojo::SimpleWatcher::ArmingPolicy::MANUAL,
std::move(task_runner)),
status_(Status::kNotStarted),
completed_(false),
maximum_download_size_(maximum_download_size),
callback_(std::move(callback)) {}
~ClientImpl() override {
callback_ = Callback();
Cancel();
}
void Start(const network::ResourceRequest& request,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
const net::NetworkTrafficAnnotationTag& annotation_tag,
scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
status_ = Status::kStarted;
response_.SetURL(request.url);
network::mojom::URLLoaderClientPtr client;
client_binding_.Bind(mojo::MakeRequest(&client), std::move(task_runner));
url_loader_factory->CreateLoaderAndStart(
mojo::MakeRequest(&loader_), kRoutingId,
ResourceDispatcher::MakeRequestID(), network::mojom::kURLLoadOptionNone,
request, std::move(client),
net::MutableNetworkTrafficAnnotationTag(annotation_tag));
}
void Cancel() {
ClearReceivedDataToFail();
completed_ = true;
Close();
}
bool IsActive() const {
return status_ == Status::kStarted || status_ == Status::kFetching ||
status_ == Status::kClosed;
}
private:
enum class Status {
kNotStarted, // Initial state.
kStarted, // Start() is called, but data pipe is not ready yet.
kFetching, // Fetching via data pipe.
kClosed, // Data pipe is already closed, but may not be completed yet.
kCompleted, // Final state.
};
void MayComplete() {
DCHECK(IsActive()) << "status: " << static_cast<int>(status_);
DCHECK_NE(Status::kCompleted, status_);
if (status_ == Status::kFetching || !completed_)
return;
status_ = Status::kCompleted;
loader_.reset();
parent_->OnLoadComplete();
if (callback_.is_null())
return;
std::move(callback_).Run(response_, data_);
}
void ClearReceivedDataToFail() {
response_ = blink::WebURLResponse();
data_.clear();
}
void ReadDataPipe() {
DCHECK_EQ(Status::kFetching, status_);
for (;;) {
const void* data;
uint32_t size;
MojoResult result =
data_pipe_->BeginReadData(&data, &size, MOJO_READ_DATA_FLAG_NONE);
if (result == MOJO_RESULT_SHOULD_WAIT) {
data_pipe_watcher_.ArmOrNotify();
return;
}
if (result == MOJO_RESULT_FAILED_PRECONDITION) {
// Complete to read the data pipe successfully.
Close();
return;
}
DCHECK_EQ(MOJO_RESULT_OK, result); // Only program errors can fire.
if (data_.size() + size > maximum_download_size_) {
data_pipe_->EndReadData(size);
Cancel();
return;
}
data_.append(static_cast<const char*>(data), size);
result = data_pipe_->EndReadData(size);
DCHECK_EQ(MOJO_RESULT_OK, result); // Only program errors can fire.
}
}
void Close() {
if (status_ == Status::kFetching) {
data_pipe_watcher_.Cancel();
data_pipe_.reset();
}
status_ = Status::kClosed;
MayComplete();
}
void OnDataPipeSignaled(MojoResult result,
const mojo::HandleSignalsState& state) {
ReadDataPipe();
}
// network::mojom::URLLoaderClient overrides:
void OnReceiveResponse(
const network::ResourceResponseHead& response_head) override {
DCHECK_EQ(Status::kStarted, status_);
// Existing callers need URL and HTTP status code. URL is already set in
// Start().
if (response_head.headers)
response_.SetHTTPStatusCode(response_head.headers->response_code());
}
void OnReceiveRedirect(
const net::RedirectInfo& redirect_info,
const network::ResourceResponseHead& response_head) override {
DCHECK_EQ(Status::kStarted, status_);
loader_->FollowRedirect(base::nullopt, base::nullopt);
response_.SetURL(redirect_info.new_url);
}
void OnUploadProgress(int64_t current_position,
int64_t total_size,
OnUploadProgressCallback ack_callback) override {}
void OnReceiveCachedMetadata(const std::vector<uint8_t>& data) override {}
void OnTransferSizeUpdated(int32_t transfer_size_diff) override {}
void OnStartLoadingResponseBody(
mojo::ScopedDataPipeConsumerHandle body) override {
DCHECK_EQ(Status::kStarted, status_);
status_ = Status::kFetching;
data_pipe_ = std::move(body);
data_pipe_watcher_.Watch(
data_pipe_.get(),
MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
MOJO_WATCH_CONDITION_SATISFIED,
base::BindRepeating(
&ResourceFetcherImpl::ClientImpl::OnDataPipeSignaled,
base::Unretained(this)));
ReadDataPipe();
}
void OnComplete(const network::URLLoaderCompletionStatus& status) override {
// When Cancel() sets |complete_|, OnComplete() may be called.
if (completed_)
return;
DCHECK(IsActive()) << "status: " << static_cast<int>(status_);
if (status.error_code != net::OK) {
ClearReceivedDataToFail();
Close();
}
completed_ = true;
MayComplete();
}
private:
ResourceFetcherImpl* parent_;
network::mojom::URLLoaderPtr loader_;
mojo::Binding<network::mojom::URLLoaderClient> client_binding_;
mojo::ScopedDataPipeConsumerHandle data_pipe_;
mojo::SimpleWatcher data_pipe_watcher_;
Status status_;
// A flag to represent if OnComplete() is already called. |data_pipe_| can be
// ready even after OnComplete() is called.
bool completed_;
// Maximum download size to be stored in |data_|.
const size_t maximum_download_size_;
// Received data to be passed to the |callback_|.
std::string data_;
// Response to be passed to the |callback_|.
blink::WebURLResponse response_;
// Callback when we're done.
Callback callback_;
DISALLOW_COPY_AND_ASSIGN(ClientImpl);
};
ResourceFetcherImpl::ResourceFetcherImpl(const GURL& url) {
DCHECK(url.is_valid());
request_.url = url;
}
ResourceFetcherImpl::~ResourceFetcherImpl() {
client_.reset();
}
void ResourceFetcherImpl::SetMethod(const std::string& method) {
DCHECK(!client_);
request_.method = method;
}
void ResourceFetcherImpl::SetBody(const std::string& body) {
DCHECK(!client_);
request_.request_body =
network::ResourceRequestBody::CreateFromBytes(body.data(), body.size());
}
void ResourceFetcherImpl::SetHeader(const std::string& header,
const std::string& value) {
DCHECK(!client_);
if (base::LowerCaseEqualsASCII(header, net::HttpRequestHeaders::kReferer)) {
request_.referrer = GURL(value);
DCHECK(request_.referrer.is_valid());
request_.referrer_policy = Referrer::GetDefaultReferrerPolicy();
} else {
request_.headers.SetHeader(header, value);
}
}
void ResourceFetcherImpl::Start(
blink::WebLocalFrame* frame,
blink::WebURLRequest::RequestContext request_context,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
const net::NetworkTrafficAnnotationTag& annotation_tag,
Callback callback,
size_t maximum_download_size) {
DCHECK(!client_);
DCHECK(frame);
DCHECK(url_loader_factory);
DCHECK(!frame->GetDocument().IsNull());
if (request_.method.empty())
request_.method = net::HttpRequestHeaders::kGetMethod;
if (request_.request_body) {
DCHECK(!base::LowerCaseEqualsASCII(request_.method,
net::HttpRequestHeaders::kGetMethod))
<< "GETs can't have bodies.";
}
request_.fetch_request_context_type = request_context;
request_.site_for_cookies = frame->GetDocument().SiteForCookies();
if (!frame->GetDocument().GetSecurityOrigin().IsNull()) {
request_.request_initiator =
static_cast<url::Origin>(frame->GetDocument().GetSecurityOrigin());
SetHeader(kAccessControlAllowOriginHeader,
blink::WebSecurityOrigin::CreateUnique().ToString().Ascii());
}
request_.resource_type = WebURLRequestContextToResourceType(request_context);
client_ = std::make_unique<ClientImpl>(
this, std::move(callback), maximum_download_size,
frame->GetTaskRunner(blink::TaskType::kNetworking));
// TODO(kinuko, toyoshim): This task runner should be given by the consumer
// of this class.
client_->Start(request_, std::move(url_loader_factory), annotation_tag,
frame->GetTaskRunner(blink::TaskType::kNetworking));
// No need to hold on to the request; reset it now.
request_ = network::ResourceRequest();
}
void ResourceFetcherImpl::SetTimeout(const base::TimeDelta& timeout) {
DCHECK(client_);
DCHECK(client_->IsActive());
DCHECK(!timeout_timer_.IsRunning());
timeout_timer_.Start(FROM_HERE, timeout, this,
&ResourceFetcherImpl::OnTimeout);
}
void ResourceFetcherImpl::OnLoadComplete() {
timeout_timer_.Stop();
}
void ResourceFetcherImpl::OnTimeout() {
DCHECK(client_);
DCHECK(client_->IsActive());
client_->Cancel();
}
} // namespace content