blob: b3c94aa2e33b2add27d46b6036505ba40eafed3b [file] [log] [blame]
// Copyright 2017 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/public/common/simple_url_loader.h"
#include <stdint.h>
#include <algorithm>
#include <limits>
#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/sequence_checker.h"
#include "base/sequenced_task_runner.h"
#include "base/task_scheduler/post_task.h"
#include "base/task_scheduler/task_traits.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "mojo/public/cpp/bindings/binding_set.h"
#include "mojo/public/cpp/system/data_pipe.h"
#include "mojo/public/cpp/system/simple_watcher.h"
#include "net/base/net_errors.h"
#include "net/base/request_priority.h"
#include "net/http/http_request_headers.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "services/network/public/cpp/data_element.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/resource_response.h"
#include "services/network/public/interfaces/data_pipe_getter.mojom.h"
#include "services/network/public/interfaces/url_loader.mojom.h"
#include "services/network/public/interfaces/url_loader_factory.mojom.h"
namespace content {
const size_t SimpleURLLoader::kMaxBoundedStringDownloadSize = 1024 * 1024;
const size_t SimpleURLLoader::kMaxUploadStringSizeToCopy = 256 * 1024;
namespace {
// This file contains SimpleURLLoaderImpl, several BodyHandler implementations,
// BodyReader, and StringUploadDataPipeGetter.
//
// SimpleURLLoaderImpl implements URLLoaderClient and drives the URLLoader.
//
// Each SimpleURLLoaderImpl creates a BodyHandler when the request is started.
// The BodyHandler drives the body pipe, handles body data as needed (writes it
// to a string, file, etc), and handles passing that data to the consumer's
// callback.
//
// BodyReader is a utility class that BodyHandler implementations use to drive
// the BodyPipe. This isn't handled by the SimpleURLLoader as some BodyHandlers
// consume data off thread, so having it as a separate class allows the data
// pipe to be used off thread, reducing use of the main thread.
//
// StringUploadDataPipeGetter is a class to stream a string upload body to the
// network service, rather than to copy it all at once.
class StringUploadDataPipeGetter : public network::mojom::DataPipeGetter {
public:
explicit StringUploadDataPipeGetter(const std::string& upload_string)
: upload_string_(upload_string) {}
~StringUploadDataPipeGetter() override = default;
// Returns a DataPipeGetterPtr for a new upload attempt, closing all
// previously opened pipes.
network::mojom::DataPipeGetterPtr GetPtrForNewUpload() {
// If this is a retry, need to close all bindings, since only one consumer
// can read from the data pipe at a time.
binding_set_.CloseAllBindings();
// Not strictly needed, but seems best to close the old body pipe and stop
// any pending reads.
ResetBodyPipe();
network::mojom::DataPipeGetterPtr data_pipe_getter;
binding_set_.AddBinding(this, mojo::MakeRequest(&data_pipe_getter));
return data_pipe_getter;
}
private:
// DataPipeGetter implementation:
void Read(mojo::ScopedDataPipeProducerHandle pipe,
ReadCallback callback) override {
// Close any previous body pipe, to avoid confusion between the two, if the
// consumer wants to restart reading from the file.
ResetBodyPipe();
std::move(callback).Run(net::OK, upload_string_.length());
upload_body_pipe_ = std::move(pipe);
handle_watcher_ = std::make_unique<mojo::SimpleWatcher>(
FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::MANUAL);
handle_watcher_->Watch(
upload_body_pipe_.get(),
// Don't bother watching for close - rely on read pipes for errors.
MOJO_HANDLE_SIGNAL_WRITABLE, MOJO_WATCH_CONDITION_SATISFIED,
base::BindRepeating(&StringUploadDataPipeGetter::MojoReadyCallback,
base::Unretained(this)));
WriteData();
}
void Clone(network::mojom::DataPipeGetterRequest request) override {
binding_set_.AddBinding(this, std::move(request));
}
void MojoReadyCallback(MojoResult result,
const mojo::HandleSignalsState& state) {
WriteData();
}
void WriteData() {
DCHECK_LE(write_position_, upload_string_.length());
while (true) {
uint32_t write_size = static_cast<uint32_t>(
std::min(static_cast<size_t>(32 * 1024),
upload_string_.length() - write_position_));
if (write_size == 0) {
// Upload is done. Close the uplaod body pipe and wait for another call
// to Read().
ResetBodyPipe();
return;
}
int result =
upload_body_pipe_->WriteData(upload_string_.data() + write_position_,
&write_size, MOJO_WRITE_DATA_FLAG_NONE);
if (result == MOJO_RESULT_SHOULD_WAIT) {
handle_watcher_->ArmOrNotify();
return;
}
if (result != MOJO_RESULT_OK) {
// Ignore the pipe being closed - the upload may still be retried with
// another call to Read.
ResetBodyPipe();
return;
}
write_position_ += write_size;
DCHECK_LE(write_position_, upload_string_.length());
}
}
// Closes the body pipe, and resets the position the class is writing from.
// Should be called either when a new binding is created, or a new read
// through the file is started.
void ResetBodyPipe() {
handle_watcher_.reset();
upload_body_pipe_.reset();
write_position_ = 0;
}
mojo::BindingSet<network::mojom::DataPipeGetter> binding_set_;
mojo::ScopedDataPipeProducerHandle upload_body_pipe_;
// Must be below |write_pipe_|, so it's deleted first.
std::unique_ptr<mojo::SimpleWatcher> handle_watcher_;
size_t write_position_ = 0;
const std::string upload_string_;
DISALLOW_COPY_AND_ASSIGN(StringUploadDataPipeGetter);
};
class BodyHandler;
class SimpleURLLoaderImpl : public SimpleURLLoader,
public network::mojom::URLLoaderClient {
public:
SimpleURLLoaderImpl(
std::unique_ptr<network::ResourceRequest> resource_request,
const net::NetworkTrafficAnnotationTag& annotation_tag);
~SimpleURLLoaderImpl() override;
// SimpleURLLoader implementation.
void DownloadToString(network::mojom::URLLoaderFactory* url_loader_factory,
BodyAsStringCallback body_as_string_callback,
size_t max_body_size) override;
void DownloadToStringOfUnboundedSizeUntilCrashAndDie(
network::mojom::URLLoaderFactory* url_loader_factory,
BodyAsStringCallback body_as_string_callback) override;
void DownloadToFile(
network::mojom::URLLoaderFactory* url_loader_factory,
DownloadToFileCompleteCallback download_to_file_complete_callback,
const base::FilePath& file_path,
int64_t max_body_size) override;
void DownloadToTempFile(
network::mojom::URLLoaderFactory* url_loader_factory,
DownloadToFileCompleteCallback download_to_file_complete_callback,
int64_t max_body_size) override;
void SetOnRedirectCallback(
const OnRedirectCallback& on_redirect_callback) override;
void SetAllowPartialResults(bool allow_partial_results) override;
void SetAllowHttpErrorResults(bool allow_http_error_results) override;
void AttachStringForUpload(const std::string& upload_data,
const std::string& upload_content_type) override;
void AttachFileForUpload(const base::FilePath& upload_file_path,
const std::string& upload_content_type) override;
void SetRetryOptions(int max_retries, int retry_mode) override;
int NetError() const override;
const network::ResourceResponseHead* ResponseInfo() const override;
// Called by BodyHandler when the BodyHandler body handler is done. If |error|
// is not net::OK, some error occurred reading or consuming the body. If it is
// net::OK, the pipe was closed and all data received was successfully
// handled. This could indicate an error, concellation, or completion. To
// determine which case this is, the size will also be compared to the size
// reported in network::URLLoaderCompletionStatus(), if
// network::URLLoaderCompletionStatus indicates a success.
void OnBodyHandlerDone(net::Error error, int64_t received_body_size);
// Finished the request with the provided error code, after freeing Mojo
// resources. Closes any open pipes, so no URLLoader or BodyHandlers callbacks
// will be invoked after this is called.
void FinishWithResult(int net_error);
private:
// Per-request state values. This object is re-created for each retry.
// Separating out the values makes re-initializing them on retry simpler.
struct RequestState {
RequestState() = default;
~RequestState() = default;
bool request_completed = false;
// The expected total size of the body, taken from
// network::URLLoaderCompletionStatus.
int64_t expected_body_size = 0;
bool body_started = false;
bool body_completed = false;
// Final size of the body. Set once the body's Mojo pipe has been closed.
int64_t received_body_size = 0;
// Set to true when FinishWithResult() is called. Once that happens, the
// consumer is informed of completion, and both pipes are closed.
bool finished = false;
// Result of the request.
int net_error = net::ERR_IO_PENDING;
std::unique_ptr<network::ResourceResponseHead> response_info;
};
// Prepares internal state to start a request, and then calls StartRequest().
// Only used for the initial request (Not retries).
void Start(network::mojom::URLLoaderFactory* url_loader_factory);
// Starts a request. Used for both the initial request and retries, if any.
void StartRequest(network::mojom::URLLoaderFactory* url_loader_factory);
// Re-initializes state of |this| and |body_handler_| prior to retrying a
// request.
void Retry();
// network::mojom::URLLoaderClient implementation;
void OnReceiveResponse(
const network::ResourceResponseHead& response_head,
const base::Optional<net::SSLInfo>& ssl_info,
network::mojom::DownloadedTempFilePtr downloaded_file) override;
void OnReceiveRedirect(
const net::RedirectInfo& redirect_info,
const network::ResourceResponseHead& response_head) override;
void OnDataDownloaded(int64_t data_length, int64_t encoded_length) override;
void OnReceiveCachedMetadata(const std::vector<uint8_t>& data) override;
void OnTransferSizeUpdated(int32_t transfer_size_diff) override;
void OnUploadProgress(int64_t current_position,
int64_t total_size,
OnUploadProgressCallback ack_callback) override;
void OnStartLoadingResponseBody(
mojo::ScopedDataPipeConsumerHandle body) override;
void OnComplete(const network::URLLoaderCompletionStatus& status) override;
// Choose the TaskPriority based on |resource_request_|'s net priority.
// TODO(mmenke): Can something better be done here?
base::TaskPriority GetTaskPriority() const {
base::TaskPriority task_priority;
if (resource_request_->priority >= net::MEDIUM) {
task_priority = base::TaskPriority::USER_BLOCKING;
} else if (resource_request_->priority >= net::LOW) {
task_priority = base::TaskPriority::USER_VISIBLE;
} else {
task_priority = base::TaskPriority::BACKGROUND;
}
return task_priority;
}
// Bound to the URLLoaderClient message pipe (|client_binding_|) via
// set_connection_error_handler.
void OnConnectionError();
// Completes the request by calling FinishWithResult() if OnComplete() was
// called and either no body pipe was ever received, or the body pipe was
// closed.
void MaybeComplete();
OnRedirectCallback on_redirect_callback_;
bool allow_partial_results_ = false;
bool allow_http_error_results_ = false;
// Information related to retrying.
int remaining_retries_ = 0;
int retry_mode_ = RETRY_NEVER;
// The next values contain all the information required to restart the
// request.
// Populated in the constructor, and cleared once no longer needed, when no
// more retries are possible.
std::unique_ptr<network::ResourceRequest> resource_request_;
const net::NetworkTrafficAnnotationTag annotation_tag_;
// Cloned from the input URLLoaderFactory if it may be needed to follow
// redirects.
network::mojom::URLLoaderFactoryPtr url_loader_factory_ptr_;
std::unique_ptr<BodyHandler> body_handler_;
mojo::Binding<network::mojom::URLLoaderClient> client_binding_;
network::mojom::URLLoaderPtr url_loader_;
std::unique_ptr<StringUploadDataPipeGetter> string_upload_data_pipe_getter_;
// Per-request state. Always non-null, but re-created on redirect.
std::unique_ptr<RequestState> request_state_;
SEQUENCE_CHECKER(sequence_checker_);
base::WeakPtrFactory<SimpleURLLoaderImpl> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(SimpleURLLoaderImpl);
};
// Utility class to drive the pipe reading a response body. Can be created on
// one thread and then used to read data on another. A BodyReader may only be
// used once. If a request is retried, a new one must be created.
class BodyReader {
public:
class Delegate {
public:
Delegate() {}
// The specified amount of data was read from the pipe. The BodyReader must
// not be deleted during this callback. The Delegate should return net::OK
// to continue reading, or a value indicating an error if the pipe should be
// closed.
virtual net::Error OnDataRead(uint32_t length, const char* data) = 0;
// Called when the pipe is closed by the remote size, the size limit is
// reached, or OnDataRead returned an error. |error| is net::OK if the
// pipe was closed, or an error value otherwise. It is safe to delete the
// BodyReader during this callback.
virtual void OnDone(net::Error error, int64_t total_bytes) = 0;
protected:
virtual ~Delegate() {}
private:
DISALLOW_COPY_AND_ASSIGN(Delegate);
};
BodyReader(Delegate* delegate, int64_t max_body_size)
: delegate_(delegate), max_body_size_(max_body_size) {
DCHECK_GE(max_body_size_, 0);
}
// Makes the reader start reading from |body_data_pipe|. May only be called
// once. The reader will continuously to try to read from the pipe (without
// blocking the thread), calling OnDataRead as data is read, until one of the
// following happens:
// * The size limit is reached.
// * OnDataRead returns an error.
// * The BodyReader is deleted.
void Start(mojo::ScopedDataPipeConsumerHandle body_data_pipe) {
DCHECK(!body_data_pipe_.is_valid());
body_data_pipe_ = std::move(body_data_pipe);
handle_watcher_ = std::make_unique<mojo::SimpleWatcher>(
FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::MANUAL);
handle_watcher_->Watch(
body_data_pipe_.get(),
MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
MOJO_WATCH_CONDITION_SATISFIED,
base::BindRepeating(&BodyReader::MojoReadyCallback,
base::Unretained(this)));
ReadData();
}
private:
void MojoReadyCallback(MojoResult result,
const mojo::HandleSignalsState& state) {
ReadData();
}
// Reads as much data as possible from |body_data_pipe_|, copying it to
// |body_|. Arms |handle_watcher_| when data is not currently available.
void ReadData() {
while (true) {
const void* body_data;
uint32_t read_size;
MojoResult result = body_data_pipe_->BeginReadData(
&body_data, &read_size, MOJO_READ_DATA_FLAG_NONE);
if (result == MOJO_RESULT_SHOULD_WAIT) {
handle_watcher_->ArmOrNotify();
return;
}
// If the pipe was closed, unclear if it was an error or success. Notify
// the consumer of how much data was received.
if (result != MOJO_RESULT_OK) {
// The only error other than MOJO_RESULT_SHOULD_WAIT this should fail
// with is MOJO_RESULT_FAILED_PRECONDITION, in the case the pipe was
// closed.
DCHECK_EQ(MOJO_RESULT_FAILED_PRECONDITION, result);
ClosePipe();
delegate_->OnDone(net::OK, total_bytes_read_);
return;
}
// Check size against the limit.
uint32_t copy_size = read_size;
if (static_cast<int64_t>(copy_size) > max_body_size_ - total_bytes_read_)
copy_size = max_body_size_ - total_bytes_read_;
total_bytes_read_ += copy_size;
net::Error error =
delegate_->OnDataRead(copy_size, static_cast<const char*>(body_data));
body_data_pipe_->EndReadData(read_size);
// Handling reads asynchronously is currently not supported.
DCHECK_NE(error, net::ERR_IO_PENDING);
if (error == net::OK && copy_size < read_size)
error = net::ERR_INSUFFICIENT_RESOURCES;
if (error) {
ClosePipe();
delegate_->OnDone(error, total_bytes_read_);
return;
}
}
}
// Frees Mojo resources and prevents any more Mojo messages from arriving.
void ClosePipe() {
handle_watcher_.reset();
body_data_pipe_.reset();
}
mojo::ScopedDataPipeConsumerHandle body_data_pipe_;
std::unique_ptr<mojo::SimpleWatcher> handle_watcher_;
Delegate* const delegate_;
const int64_t max_body_size_;
int64_t total_bytes_read_ = 0;
DISALLOW_COPY_AND_ASSIGN(BodyReader);
};
// Class to drive the pipe for reading the body, handle the results of the body
// read as appropriate, and invoke the consumer's callback to notify it of
// request completion. Implementations typically use a BodyReader to to manage
// reads from the body data pipe.
class BodyHandler {
public:
// A raw pointer is safe, since |simple_url_loader| owns the BodyHandler.
explicit BodyHandler(SimpleURLLoaderImpl* simple_url_loader)
: simple_url_loader_(simple_url_loader) {}
virtual ~BodyHandler() {}
// Called by SimpleURLLoader with the data pipe received from the URLLoader.
// The BodyHandler is responsible for reading from it and monitoring it for
// closure. Should call SimpleURLLoaderImpl::OnBodyHandlerDone(), once either
// when the body pipe is closed or when an error occurs, like a write to a
// file fails.
virtual void OnStartLoadingResponseBody(
mojo::ScopedDataPipeConsumerHandle body_data_pipe) = 0;
// Called by SimpleURLLoader. Notifies the SimpleURLLoader's consumer that the
// request has completed, either successfully or with an error. May be invoked
// before OnStartLoadingResponseBody(), or before the BodyHandler has notified
// SimplerURLLoader of completion or an error. Once this is called, the
// BodyHandler must not invoke any of SimpleURLLoaderImpl's callbacks. If
// |destroy_results| is true, any received data should be destroyed instead of
// being sent to the consumer.
virtual void NotifyConsumerOfCompletion(bool destroy_results) = 0;
// Called before retrying a request. Only called either before receiving a
// body pipe, or after the body pipe has been closed, so there should be no
// pending callbacks when invoked. |retry_callback| should be invoked when
// the BodyHandler is ready for the request to be retried. Callback may be
// invoked synchronously.
virtual void PrepareToRetry(base::OnceClosure retry_callback) = 0;
protected:
SimpleURLLoaderImpl* simple_url_loader() { return simple_url_loader_; }
private:
SimpleURLLoaderImpl* const simple_url_loader_;
DISALLOW_COPY_AND_ASSIGN(BodyHandler);
};
// BodyHandler implementation for consuming the response as a string.
class SaveToStringBodyHandler : public BodyHandler,
public BodyReader::Delegate {
public:
SaveToStringBodyHandler(
SimpleURLLoaderImpl* simple_url_loader,
SimpleURLLoader::BodyAsStringCallback body_as_string_callback,
int64_t max_body_size)
: BodyHandler(simple_url_loader),
max_body_size_(max_body_size),
body_as_string_callback_(std::move(body_as_string_callback)) {}
~SaveToStringBodyHandler() override {}
// BodyHandler implementation:
void OnStartLoadingResponseBody(
mojo::ScopedDataPipeConsumerHandle body_data_pipe) override {
DCHECK(!body_);
DCHECK(!body_reader_);
body_ = std::make_unique<std::string>();
body_reader_ = std::make_unique<BodyReader>(this, max_body_size_);
body_reader_->Start(std::move(body_data_pipe));
}
void NotifyConsumerOfCompletion(bool destroy_results) override {
body_reader_.reset();
if (destroy_results)
body_.reset();
std::move(body_as_string_callback_).Run(std::move(body_));
}
void PrepareToRetry(base::OnceClosure retry_callback) override {
body_.reset();
body_reader_.reset();
std::move(retry_callback).Run();
}
private:
// BodyReader::Delegate implementation.
net::Error OnDataRead(uint32_t length, const char* data) override {
body_->append(data, length);
return net::OK;
}
void OnDone(net::Error error, int64_t total_bytes) override {
DCHECK_EQ(body_->size(), static_cast<size_t>(total_bytes));
simple_url_loader()->OnBodyHandlerDone(error, total_bytes);
}
const int64_t max_body_size_;
std::unique_ptr<std::string> body_;
SimpleURLLoader::BodyAsStringCallback body_as_string_callback_;
std::unique_ptr<BodyReader> body_reader_;
DISALLOW_COPY_AND_ASSIGN(SaveToStringBodyHandler);
};
// BodyHandler implementation for saving the response to a file
class SaveToFileBodyHandler : public BodyHandler {
public:
// |net_priority| is the priority from the ResourceRequest, and is used to
// determine the TaskPriority of the sequence used to read from the response
// body and write to the file. If |create_temp_file| is true, a temp file is
// created instead of using |path|.
SaveToFileBodyHandler(SimpleURLLoaderImpl* simple_url_loader,
SimpleURLLoader::DownloadToFileCompleteCallback
download_to_file_complete_callback,
const base::FilePath& path,
bool create_temp_file,
uint64_t max_body_size,
base::TaskPriority task_priority)
: BodyHandler(simple_url_loader),
download_to_file_complete_callback_(
std::move(download_to_file_complete_callback)),
weak_ptr_factory_(this) {
DCHECK(create_temp_file || !path.empty());
// Can only do this after initializing the WeakPtrFactory.
file_writer_ = std::make_unique<FileWriter>(path, create_temp_file,
max_body_size, task_priority);
}
~SaveToFileBodyHandler() override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (file_writer_) {
// |file_writer_| is only non-null at this point if any downloaded file
// wasn't passed to the consumer. Destroy any partially downloaded file.
file_writer_->DeleteFile(base::OnceClosure());
FileWriter::Destroy(std::move(file_writer_));
}
}
// BodyHandler implementation:
void OnStartLoadingResponseBody(
mojo::ScopedDataPipeConsumerHandle body_data_pipe) override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
file_writer_->StartWriting(std::move(body_data_pipe),
base::BindOnce(&SaveToFileBodyHandler::OnDone,
weak_ptr_factory_.GetWeakPtr()));
}
void NotifyConsumerOfCompletion(bool destroy_results) override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!download_to_file_complete_callback_.is_null());
if (destroy_results) {
// Prevent the FileWriter from calling OnDone().
weak_ptr_factory_.InvalidateWeakPtrs();
// To avoid any issues if the consumer tries to re-download a file to the
// same location, don't invoke the callback until any partially downloaded
// file has been destroyed.
file_writer_->DeleteFile(
base::BindOnce(&SaveToFileBodyHandler::InvokeCallbackAsynchronously,
weak_ptr_factory_.GetWeakPtr()));
FileWriter::Destroy(std::move(file_writer_));
return;
}
// Destroy the |file_writer_|, so the file won't be destroyed in |this|'s
// destructor.
FileWriter::Destroy(std::move(file_writer_));
std::move(download_to_file_complete_callback_).Run(path_);
}
void PrepareToRetry(base::OnceClosure retry_callback) override {
// |file_writer_| is only destroyed when notifying the consumer of
// completion and in the destructor. After either of those happens, a
// request should not be retried.
DCHECK(file_writer_);
// Delete file and wait for it to be destroyed, so if the retry fails
// before trying to create a new file, the consumer will still only be
// notified of completion after the file is destroyed.
file_writer_->DeleteFile(std::move(retry_callback));
}
private:
// Class to read from a mojo::ScopedDataPipeConsumerHandle and write the
// contents to a file. Does all reading and writing on a separate file
// SequencedTaskRunner. All public methods except the destructor are called on
// BodyHandler's TaskRunner. All private methods and the destructor are run
// on the file TaskRunner.
//
// FileWriter is owned by the SaveToFileBodyHandler and destroyed by a task
// moving its unique_ptr to the |file_writer_task_runner_|. As a result, tasks
// posted to |file_writer_task_runner_| can always use base::Unretained. Tasks
// posted the other way, however, require the SaveToFileBodyHandler to use
// WeakPtrs, since the SaveToFileBodyHandler can be destroyed at any time.
//
// When a request is retried, the FileWriter deletes any partially downloaded
// file, and is then reused.
class FileWriter : public BodyReader::Delegate {
public:
using OnDoneCallback = base::OnceCallback<void(net::Error error,
int64_t total_bytes,
const base::FilePath& path)>;
FileWriter(const base::FilePath& path,
bool create_temp_file,
int64_t max_body_size,
base::TaskPriority priority)
: body_handler_task_runner_(base::SequencedTaskRunnerHandle::Get()),
file_writer_task_runner_(base::CreateSequencedTaskRunnerWithTraits(
{base::MayBlock(), priority,
base::TaskShutdownBehavior::BLOCK_SHUTDOWN})),
path_(path),
create_temp_file_(create_temp_file),
max_body_size_(max_body_size) {
DCHECK(body_handler_task_runner_->RunsTasksInCurrentSequence());
DCHECK(create_temp_file_ || !path_.empty());
}
// Starts reading from |body_data_pipe| and writing to the file.
void StartWriting(mojo::ScopedDataPipeConsumerHandle body_data_pipe,
OnDoneCallback on_done_callback) {
DCHECK(body_handler_task_runner_->RunsTasksInCurrentSequence());
file_writer_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&FileWriter::StartWritingOnFileSequence,
base::Unretained(this), std::move(body_data_pipe),
std::move(on_done_callback)));
}
// Deletes any partially downloaded file, and closes the body pipe, if open.
// Must be called if SaveToFileBodyHandler's OnDone() method is never
// invoked, to avoid keeping around partial downloads that were never passed
// to the consumer.
//
// If |on_file_deleted_closure| is non-null, it will be invoked on the
// caller's task runner once the file has been deleted.
void DeleteFile(base::OnceClosure on_file_deleted_closure) {
DCHECK(body_handler_task_runner_->RunsTasksInCurrentSequence());
file_writer_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&FileWriter::DeleteFileOnFileSequence,
base::Unretained(this),
std::move(on_file_deleted_closure)));
}
// Destroys the FileWriter on the file TaskRunner.
static void Destroy(std::unique_ptr<FileWriter> file_writer) {
DCHECK(
file_writer->body_handler_task_runner_->RunsTasksInCurrentSequence());
// Have to stash this pointer before posting a task, since |file_writer|
// is bound to the callback that's posted to the TaskRunner.
base::SequencedTaskRunner* task_runner =
file_writer->file_writer_task_runner_.get();
task_runner->DeleteSoon(FROM_HERE, std::move(file_writer));
}
// Destructor is only public so the consumer can keep it in a unique_ptr.
// Class must be destroyed by using Destroy().
~FileWriter() override {
DCHECK(file_writer_task_runner_->RunsTasksInCurrentSequence());
}
private:
void StartWritingOnFileSequence(
mojo::ScopedDataPipeConsumerHandle body_data_pipe,
OnDoneCallback on_done_callback) {
DCHECK(file_writer_task_runner_->RunsTasksInCurrentSequence());
DCHECK(!file_.IsValid());
DCHECK(!body_reader_);
bool have_path = !create_temp_file_;
if (!have_path) {
DCHECK(create_temp_file_);
have_path = base::CreateTemporaryFile(&path_);
// CreateTemporaryFile() creates an empty file.
if (have_path)
owns_file_ = true;
}
if (have_path) {
// Try to initialize |file_|, creating the file if needed.
file_.Initialize(
path_, base::File::FLAG_WRITE | base::File::FLAG_CREATE_ALWAYS);
}
// If CreateTemporaryFile() or File::Initialize() failed, report failure.
if (!file_.IsValid()) {
body_handler_task_runner_->PostTask(
FROM_HERE, base::BindOnce(std::move(on_done_callback),
net::MapSystemError(
logging::GetLastSystemErrorCode()),
0, base::FilePath()));
return;
}
on_done_callback_ = std::move(on_done_callback);
owns_file_ = true;
body_reader_ = std::make_unique<BodyReader>(this, max_body_size_);
body_reader_->Start(std::move(body_data_pipe));
}
// BodyReader::Delegate implementation:
net::Error OnDataRead(uint32_t length, const char* data) override {
DCHECK(file_writer_task_runner_->RunsTasksInCurrentSequence());
while (length > 0) {
int written = file_.WriteAtCurrentPos(
data, std::min(length, static_cast<uint32_t>(
std::numeric_limits<int>::max())));
if (written < 0)
return net::MapSystemError(logging::GetLastSystemErrorCode());
length -= written;
data += written;
}
return net::OK;
}
void OnDone(net::Error error, int64_t total_bytes) override {
DCHECK(file_writer_task_runner_->RunsTasksInCurrentSequence());
// This should only be called if the file was successfully created.
DCHECK(file_.IsValid());
// Close the file so that there's no ownership contention when the
// consumer uses it.
file_.Close();
body_reader_.reset();
body_handler_task_runner_->PostTask(
FROM_HERE, base::BindOnce(std::move(on_done_callback_), error,
total_bytes, path_));
}
void DeleteFileOnFileSequence(base::OnceClosure on_file_deleted_closure) {
DCHECK(file_writer_task_runner_->RunsTasksInCurrentSequence());
if (owns_file_) {
// Close the file before deleting it, if it's still open.
file_.Close();
// Close the body pipe.
body_reader_.reset();
// May as well clean this up, too.
on_done_callback_.Reset();
DCHECK(!path_.empty());
base::DeleteFile(path_, false /* recursive */);
owns_file_ = false;
}
if (on_file_deleted_closure) {
body_handler_task_runner_->PostTask(FROM_HERE,
std::move(on_file_deleted_closure));
}
}
// These are set on cosntruction and accessed on both task runners.
const scoped_refptr<base::SequencedTaskRunner> body_handler_task_runner_;
const scoped_refptr<base::SequencedTaskRunner> file_writer_task_runner_;
// After construction, all other values are only read and written on the
// |file_writer_task_runner_|.
base::FilePath path_;
const bool create_temp_file_;
const int64_t max_body_size_;
// File being downloaded to. Created just before reading from the data pipe.
base::File file_;
OnDoneCallback on_done_callback_;
std::unique_ptr<BodyReader> body_reader_;
// True if a file was successfully created. Set to false when the file is
// destroyed.
bool owns_file_ = false;
DISALLOW_COPY_AND_ASSIGN(FileWriter);
};
// Called by FileWriter::Destroy after deleting a partially downloaded file.
void InvokeCallbackAsynchronously() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
std::move(download_to_file_complete_callback_).Run(base::FilePath());
}
void OnDone(net::Error error,
int64_t total_bytes,
const base::FilePath& path) {
path_ = path;
simple_url_loader()->OnBodyHandlerDone(error, total_bytes);
}
// Path of the file. Set in OnDone().
base::FilePath path_;
SimpleURLLoader::DownloadToFileCompleteCallback
download_to_file_complete_callback_;
std::unique_ptr<FileWriter> file_writer_;
SEQUENCE_CHECKER(sequence_checker_);
base::WeakPtrFactory<SaveToFileBodyHandler> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(SaveToFileBodyHandler);
};
SimpleURLLoaderImpl::SimpleURLLoaderImpl(
std::unique_ptr<network::ResourceRequest> resource_request,
const net::NetworkTrafficAnnotationTag& annotation_tag)
: resource_request_(std::move(resource_request)),
annotation_tag_(annotation_tag),
client_binding_(this),
request_state_(std::make_unique<RequestState>()),
weak_ptr_factory_(this) {
// Allow creation and use on different threads.
DETACH_FROM_SEQUENCE(sequence_checker_);
#if DCHECK_IS_ON()
if (resource_request_->request_body) {
for (const network::DataElement& element :
*resource_request_->request_body->elements()) {
// Files should be attached with AttachFileForUpload, so that (Once
// supported) they can be opened in the current process.
//
// TODO(mmenke): Add a similar method for bytes, to allow streaming of
// large byte buffers to the network process when uploading.
DCHECK(element.type() != network::DataElement::TYPE_FILE &&
element.type() != network::DataElement::TYPE_BYTES);
}
}
#endif // DCHECK_IS_ON()
}
SimpleURLLoaderImpl::~SimpleURLLoaderImpl() {}
void SimpleURLLoaderImpl::DownloadToString(
network::mojom::URLLoaderFactory* url_loader_factory,
BodyAsStringCallback body_as_string_callback,
size_t max_body_size) {
DCHECK_LE(max_body_size, kMaxBoundedStringDownloadSize);
body_handler_ = std::make_unique<SaveToStringBodyHandler>(
this, std::move(body_as_string_callback), max_body_size);
Start(url_loader_factory);
}
void SimpleURLLoaderImpl::DownloadToStringOfUnboundedSizeUntilCrashAndDie(
network::mojom::URLLoaderFactory* url_loader_factory,
BodyAsStringCallback body_as_string_callback) {
body_handler_ = std::make_unique<SaveToStringBodyHandler>(
this, std::move(body_as_string_callback),
// int64_t because network::URLLoaderCompletionStatus::decoded_body_length
// is an int64_t, not a size_t.
std::numeric_limits<int64_t>::max());
Start(url_loader_factory);
}
void SimpleURLLoaderImpl::DownloadToFile(
network::mojom::URLLoaderFactory* url_loader_factory,
DownloadToFileCompleteCallback download_to_file_complete_callback,
const base::FilePath& file_path,
int64_t max_body_size) {
DCHECK(!file_path.empty());
body_handler_ = std::make_unique<SaveToFileBodyHandler>(
this, std::move(download_to_file_complete_callback), file_path,
false /* create_temp_file */, max_body_size, GetTaskPriority());
Start(url_loader_factory);
}
void SimpleURLLoaderImpl::DownloadToTempFile(
network::mojom::URLLoaderFactory* url_loader_factory,
DownloadToFileCompleteCallback download_to_file_complete_callback,
int64_t max_body_size) {
body_handler_ = std::make_unique<SaveToFileBodyHandler>(
this, std::move(download_to_file_complete_callback), base::FilePath(),
true /* create_temp_file */, max_body_size, GetTaskPriority());
Start(url_loader_factory);
}
void SimpleURLLoaderImpl::SetOnRedirectCallback(
const OnRedirectCallback& on_redirect_callback) {
on_redirect_callback_ = on_redirect_callback;
}
void SimpleURLLoaderImpl::SetAllowPartialResults(bool allow_partial_results) {
// Check if a request has not yet been started.
DCHECK(!body_handler_);
allow_partial_results_ = allow_partial_results;
}
void SimpleURLLoaderImpl::SetAllowHttpErrorResults(
bool allow_http_error_results) {
// Check if a request has not yet been started.
DCHECK(!body_handler_);
allow_http_error_results_ = allow_http_error_results;
}
void SimpleURLLoaderImpl::AttachStringForUpload(
const std::string& upload_data,
const std::string& upload_content_type) {
// Currently only allow a single string to be attached.
DCHECK(!resource_request_->request_body);
DCHECK(resource_request_->method != "GET" &&
resource_request_->method != "HEAD");
resource_request_->request_body = new network::ResourceRequestBody();
if (upload_data.length() <= kMaxUploadStringSizeToCopy) {
int copy_length = static_cast<int>(upload_data.length());
DCHECK_EQ(static_cast<size_t>(copy_length), upload_data.length());
resource_request_->request_body->AppendBytes(upload_data.c_str(),
copy_length);
} else {
// Don't attach the upload body here. A new pipe will need to be created
// each time the request is tried.
string_upload_data_pipe_getter_ =
std::make_unique<StringUploadDataPipeGetter>(upload_data);
}
resource_request_->headers.SetHeader(net::HttpRequestHeaders::kContentType,
upload_content_type);
}
void SimpleURLLoaderImpl::AttachFileForUpload(
const base::FilePath& upload_file_path,
const std::string& upload_content_type) {
DCHECK(!upload_file_path.empty());
// Currently only allow a single file to be attached.
DCHECK(!resource_request_->request_body);
DCHECK(resource_request_->method != "GET" &&
resource_request_->method != "HEAD");
// Create an empty body to make DCHECKing that there's no upload body yet
// simpler.
resource_request_->request_body = new network::ResourceRequestBody();
// TODO(mmenke): Open the file in the current process and append the file
// handle instead of the file path.
resource_request_->request_body->AppendFileRange(
upload_file_path, 0, std::numeric_limits<uint64_t>::max(), base::Time());
resource_request_->headers.SetHeader(net::HttpRequestHeaders::kContentType,
upload_content_type);
}
void SimpleURLLoaderImpl::SetRetryOptions(int max_retries, int retry_mode) {
// Check if a request has not yet been started.
DCHECK(!body_handler_);
DCHECK_GE(max_retries, 0);
// Non-zero |max_retries| makes no sense when retries are disabled.
DCHECK(max_retries > 0 || retry_mode == RETRY_NEVER);
remaining_retries_ = max_retries;
retry_mode_ = retry_mode;
#if DCHECK_IS_ON()
if (max_retries > 0 && resource_request_->request_body) {
for (const network::DataElement& element :
*resource_request_->request_body->elements()) {
// Data pipes are single-use, so can't retry uploads when there's a data
// pipe.
// TODO(mmenke): Data pipes can be Cloned(), though, so maybe update code
// to do that?
DCHECK(element.type() != network::DataElement::TYPE_DATA_PIPE);
}
}
#endif // DCHECK_IS_ON()
}
int SimpleURLLoaderImpl::NetError() const {
// Should only be called once the request is compelete.
DCHECK(request_state_->finished);
DCHECK_NE(net::ERR_IO_PENDING, request_state_->net_error);
return request_state_->net_error;
}
const network::ResourceResponseHead* SimpleURLLoaderImpl::ResponseInfo() const {
// Should only be called once the request is compelete.
DCHECK(request_state_->finished);
return request_state_->response_info.get();
}
void SimpleURLLoaderImpl::OnBodyHandlerDone(net::Error error,
int64_t received_body_size) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(request_state_->body_started);
DCHECK(!request_state_->body_completed);
// If there's an error, fail request and report it immediately.
if (error != net::OK) {
FinishWithResult(error);
return;
}
// Otherwise, need to wait until the URLRequestClient pipe receives a complete
// message or is closed, to determine if the entire body was received.
request_state_->body_completed = true;
request_state_->received_body_size = received_body_size;
MaybeComplete();
}
void SimpleURLLoaderImpl::FinishWithResult(int net_error) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!request_state_->finished);
client_binding_.Close();
url_loader_.reset();
request_state_->finished = true;
request_state_->net_error = net_error;
// If it's a partial download or an error was received, erase the body.
bool destroy_results =
request_state_->net_error != net::OK && !allow_partial_results_;
body_handler_->NotifyConsumerOfCompletion(destroy_results);
}
void SimpleURLLoaderImpl::Start(
network::mojom::URLLoaderFactory* url_loader_factory) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(resource_request_);
// It's illegal to use a single SimpleURLLoaderImpl to make multiple requests.
DCHECK(!request_state_->finished);
DCHECK(!url_loader_);
DCHECK(!request_state_->body_started);
// If retries are enabled, stash the information needed to retry a request.
if (remaining_retries_ > 0) {
// Clone the URLLoaderFactory, to avoid any dependencies on its lifetime.
// Results in an easier to use API, with no shutdown ordering requirements,
// at the cost of some resources.
url_loader_factory->Clone(mojo::MakeRequest(&url_loader_factory_ptr_));
}
StartRequest(url_loader_factory);
}
void SimpleURLLoaderImpl::StartRequest(
network::mojom::URLLoaderFactory* url_loader_factory) {
DCHECK(resource_request_);
DCHECK(url_loader_factory);
network::mojom::URLLoaderClientPtr client_ptr;
client_binding_.Bind(mojo::MakeRequest(&client_ptr));
client_binding_.set_connection_error_handler(base::BindOnce(
&SimpleURLLoaderImpl::OnConnectionError, base::Unretained(this)));
// Data elements that use pipes aren't reuseable, currently (Since the IPC
// code doesn't call the Clone() method), so need to create another one, if
// uploading a string via a data pipe.
if (string_upload_data_pipe_getter_) {
resource_request_->request_body = new network::ResourceRequestBody();
network::mojom::DataPipeGetterPtr data_pipe_getter;
resource_request_->request_body->AppendDataPipe(
string_upload_data_pipe_getter_->GetPtrForNewUpload());
}
url_loader_factory->CreateLoaderAndStart(
mojo::MakeRequest(&url_loader_), 0 /* routing_id */, 0 /* request_id */,
0 /* options */, *resource_request_, std::move(client_ptr),
net::MutableNetworkTrafficAnnotationTag(annotation_tag_));
// If no more retries left, can clean up a little.
if (remaining_retries_ == 0) {
resource_request_.reset();
url_loader_factory_ptr_.reset();
}
}
void SimpleURLLoaderImpl::Retry() {
DCHECK(resource_request_);
DCHECK(url_loader_factory_ptr_);
DCHECK_GT(remaining_retries_, 0);
--remaining_retries_;
client_binding_.Close();
url_loader_.reset();
request_state_ = std::make_unique<RequestState>();
body_handler_->PrepareToRetry(base::BindOnce(
&SimpleURLLoaderImpl::StartRequest, weak_ptr_factory_.GetWeakPtr(),
url_loader_factory_ptr_.get()));
}
void SimpleURLLoaderImpl::OnReceiveResponse(
const network::ResourceResponseHead& response_head,
const base::Optional<net::SSLInfo>& ssl_info,
network::mojom::DownloadedTempFilePtr downloaded_file) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (request_state_->response_info) {
// The final headers have already been received, so the URLLoader is
// violating the API contract.
FinishWithResult(net::ERR_UNEXPECTED);
return;
}
// Assume a 200 response unless headers were received indicating otherwise.
// No headers indicates this was not a real HTTP response (Could be a file
// URL, FTP, response could have been provided by something else, etc).
int response_code = 200;
if (response_head.headers)
response_code = response_head.headers->response_code();
// If a 5xx response was received, and |this| should retry on 5xx errors,
// retry the request.
if (response_code / 100 == 5 && remaining_retries_ > 0 &&
(retry_mode_ & RETRY_ON_5XX)) {
Retry();
return;
}
request_state_->response_info =
std::make_unique<network::ResourceResponseHead>(response_head);
if (!allow_http_error_results_ && response_code / 100 != 2)
FinishWithResult(net::ERR_FAILED);
}
void SimpleURLLoaderImpl::OnReceiveRedirect(
const net::RedirectInfo& redirect_info,
const network::ResourceResponseHead& response_head) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (request_state_->response_info) {
// If the headers have already been received, the URLLoader is violating the
// API contract.
FinishWithResult(net::ERR_UNEXPECTED);
return;
}
if (on_redirect_callback_) {
base::WeakPtr<SimpleURLLoaderImpl> weak_this =
weak_ptr_factory_.GetWeakPtr();
on_redirect_callback_.Run(redirect_info, response_head);
// If deleted by the callback, bail now.
if (!weak_this)
return;
}
url_loader_->FollowRedirect();
}
void SimpleURLLoaderImpl::OnDataDownloaded(int64_t data_length,
int64_t encoded_length) {
NOTIMPLEMENTED();
}
void SimpleURLLoaderImpl::OnReceiveCachedMetadata(
const std::vector<uint8_t>& data) {
NOTREACHED();
}
void SimpleURLLoaderImpl::OnTransferSizeUpdated(int32_t transfer_size_diff) {}
void SimpleURLLoaderImpl::OnUploadProgress(
int64_t current_position,
int64_t total_size,
OnUploadProgressCallback ack_callback) {}
void SimpleURLLoaderImpl::OnStartLoadingResponseBody(
mojo::ScopedDataPipeConsumerHandle body) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (request_state_->body_started || !request_state_->response_info) {
// If this was already called, or the headers have not yet been received,
// the URLLoader is violating the API contract.
FinishWithResult(net::ERR_UNEXPECTED);
return;
}
request_state_->body_started = true;
body_handler_->OnStartLoadingResponseBody(std::move(body));
}
void SimpleURLLoaderImpl::OnComplete(
const network::URLLoaderCompletionStatus& status) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Request should not have been completed yet.
DCHECK(!request_state_->finished);
DCHECK(!request_state_->request_completed);
// Close pipes to ignore any subsequent close notification.
client_binding_.Close();
url_loader_.reset();
request_state_->request_completed = true;
request_state_->expected_body_size = status.decoded_body_length;
request_state_->net_error = status.error_code;
// If |status| indicates success, but the body pipe was never received, the
// URLLoader is violating the API contract.
if (request_state_->net_error == net::OK && !request_state_->body_started)
request_state_->net_error = net::ERR_UNEXPECTED;
MaybeComplete();
}
void SimpleURLLoaderImpl::OnConnectionError() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// |this| closes the pipe to the URLLoader in OnComplete(), so this method
// being called indicates the pipe was closed before completion, most likely
// due to peer death, or peer not calling OnComplete() on cancellation.
// Request should not have been completed yet.
DCHECK(!request_state_->finished);
DCHECK(!request_state_->request_completed);
DCHECK_EQ(net::ERR_IO_PENDING, request_state_->net_error);
request_state_->request_completed = true;
request_state_->net_error = net::ERR_FAILED;
// Wait to receive any pending data on the data pipe before reporting the
// failure.
MaybeComplete();
}
void SimpleURLLoaderImpl::MaybeComplete() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Request should not have completed yet.
DCHECK(!request_state_->finished);
// Make sure the URLLoader's pipe has been closed.
if (!request_state_->request_completed)
return;
// Make sure the body pipe either was never opened or has been closed. Even if
// the request failed, if allow_partial_results_ is true, may still be able to
// read more data.
if (request_state_->body_started && !request_state_->body_completed)
return;
// Retry on network change errors. Waiting for body complete isn't strictly
// necessary, but it guarantees a consistent situation, with no reads pending
// on the body pipe.
if (request_state_->net_error == net::ERR_NETWORK_CHANGED &&
remaining_retries_ > 0 && (retry_mode_ & RETRY_ON_NETWORK_CHANGE)) {
Retry();
return;
}
// When OnCompleted sees a success result, still need to report an error if
// the size isn't what was expected.
if (request_state_->net_error == net::OK &&
request_state_->expected_body_size !=
request_state_->received_body_size) {
if (request_state_->expected_body_size >
request_state_->received_body_size) {
// The body pipe was closed before it received the entire body.
request_state_->net_error = net::ERR_FAILED;
} else {
// The caller provided more data through the pipe than it reported in
// network::URLLoaderCompletionStatus, so the URLLoader is violating the
// API contract. Just fail the request.
request_state_->net_error = net::ERR_UNEXPECTED;
}
}
FinishWithResult(request_state_->net_error);
}
} // namespace
std::unique_ptr<SimpleURLLoader> SimpleURLLoader::Create(
std::unique_ptr<network::ResourceRequest> resource_request,
const net::NetworkTrafficAnnotationTag& annotation_tag) {
DCHECK(resource_request);
return std::make_unique<SimpleURLLoaderImpl>(std::move(resource_request),
annotation_tag);
}
SimpleURLLoader::~SimpleURLLoader() {}
SimpleURLLoader::SimpleURLLoader() {}
} // namespace content