| // 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 "services/network/public/cpp/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/ref_counted.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/sequence_checker.h" |
| #include "base/sequenced_task_runner.h" |
| #include "base/strings/string_piece.h" |
| #include "base/task/post_task.h" |
| #include "base/task/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/cpp/simple_url_loader_stream_consumer.h" |
| #include "services/network/public/mojom/data_pipe_getter.mojom.h" |
| #include "services/network/public/mojom/url_loader.mojom.h" |
| #include "services/network/public/mojom/url_loader_factory.mojom.h" |
| |
| namespace network { |
| |
| 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 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. |
| 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(); |
| |
| 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, |
| base::SequencedTaskRunnerHandle::Get()); |
| 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(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<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 mojom::URLLoaderClient { |
| public: |
| SimpleURLLoaderImpl(std::unique_ptr<ResourceRequest> resource_request, |
| const net::NetworkTrafficAnnotationTag& annotation_tag); |
| ~SimpleURLLoaderImpl() override; |
| |
| // SimpleURLLoader implementation. |
| void DownloadToString(mojom::URLLoaderFactory* url_loader_factory, |
| BodyAsStringCallback body_as_string_callback, |
| size_t max_body_size) override; |
| void DownloadToStringOfUnboundedSizeUntilCrashAndDie( |
| mojom::URLLoaderFactory* url_loader_factory, |
| BodyAsStringCallback body_as_string_callback) override; |
| void DownloadHeadersOnly(mojom::URLLoaderFactory* url_loader_factory, |
| HeadersOnlyCallback headers_only_callback) override; |
| void DownloadToFile( |
| mojom::URLLoaderFactory* url_loader_factory, |
| DownloadToFileCompleteCallback download_to_file_complete_callback, |
| const base::FilePath& file_path, |
| int64_t max_body_size) override; |
| void DownloadToTempFile( |
| mojom::URLLoaderFactory* url_loader_factory, |
| DownloadToFileCompleteCallback download_to_file_complete_callback, |
| int64_t max_body_size) override; |
| void DownloadAsStream( |
| mojom::URLLoaderFactory* url_loader_factory, |
| SimpleURLLoaderStreamConsumer* stream_consumer) override; |
| void SetOnRedirectCallback( |
| const OnRedirectCallback& on_redirect_callback) override; |
| void SetOnResponseStartedCallback( |
| OnResponseStartedCallback on_response_started_callback) override; |
| void SetOnUploadProgressCallback( |
| UploadProgressCallback on_upload_progress_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 ResourceResponseHead* ResponseInfo() const override; |
| const GURL& GetFinalURL() const override; |
| bool LoadedFromCache() const override; |
| int64_t GetContentSize() 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 URLLoaderCompletionStatus(), if |
| // 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 |
| // 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; |
| |
| bool loaded_from_cache = false; |
| |
| std::unique_ptr<ResourceResponseHead> response_info; |
| }; |
| |
| // Prepares internal state to start a request, and then calls StartRequest(). |
| // Only used for the initial request (Not retries). |
| void Start(mojom::URLLoaderFactory* url_loader_factory); |
| |
| // Starts a request. Used for both the initial request and retries, if any. |
| void StartRequest(mojom::URLLoaderFactory* url_loader_factory); |
| |
| // Re-initializes state of |this| and |body_handler_| prior to retrying a |
| // request. |
| void Retry(); |
| |
| // mojom::URLLoaderClient implementation; |
| void OnReceiveResponse(const ResourceResponseHead& response_head) override; |
| void OnReceiveRedirect(const net::RedirectInfo& redirect_info, |
| const ResourceResponseHead& response_head) 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 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::BEST_EFFORT; |
| } |
| 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(); |
| |
| std::vector<OnRedirectCallback> on_redirect_callback_; |
| OnResponseStartedCallback on_response_started_callback_; |
| UploadProgressCallback on_upload_progress_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<ResourceRequest> resource_request_; |
| const net::NetworkTrafficAnnotationTag annotation_tag_; |
| // Cloned from the input URLLoaderFactory if it may be needed to follow |
| // redirects. |
| mojom::URLLoaderFactoryPtr url_loader_factory_ptr_; |
| std::unique_ptr<BodyHandler> body_handler_; |
| |
| mojo::Binding<mojom::URLLoaderClient> client_binding_; |
| 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_; |
| |
| GURL final_url_; |
| |
| 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 Delegate should |
| // return net::OK to continue reading, or a value indicating an error if the |
| // pipe should be closed. A return value of net::ERR_IO_PENDING means that |
| // the BodyReader should stop reading, and not call OnDone(), until its |
| // Resume() method is called. Resume() must not be called synchronously. |
| // |
| // It's safe to delete the BodyReader during this call. If that happens, |
| // |data| will still remain valid for the duration of the call, and the |
| // returned net::Error will be ignored. |
| 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), |
| weak_ptr_factory_(this) { |
| 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, |
| base::SequencedTaskRunnerHandle::Get()); |
| 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(); |
| } |
| |
| void Resume() { ReadData(); } |
| |
| private: |
| void MojoReadyCallback(MojoResult result, |
| const mojo::HandleSignalsState& state) { |
| // Shouldn't be watching the pipe when there's a pending error. |
| DCHECK_EQ(net::OK, pending_error_); |
| |
| 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) { |
| if (pending_error_) { |
| ClosePipe(); |
| // This call may delete the BodyReader. |
| delegate_->OnDone(pending_error_, total_bytes_read_); |
| return; |
| } |
| |
| 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(); |
| |
| // This call may delete the BodyReader. |
| 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; |
| |
| if (copy_size < read_size) |
| pending_error_ = net::ERR_INSUFFICIENT_RESOURCES; |
| |
| // Need a weak pointer to |this| to detect deletion. |
| base::WeakPtr<BodyReader> weak_this = |
| this->weak_ptr_factory_.GetWeakPtr(); |
| // Need to keep the data pipe alive if |this| is deleted, to keep |
| // |body_data| alive. Also unclear if it's safe to delete while a read is |
| // in progress. |
| mojo::ScopedDataPipeConsumerHandle body_data_pipe = |
| std::move(body_data_pipe_); |
| |
| // This call may delete the BodyReader. |
| net::Error error = |
| delegate_->OnDataRead(copy_size, static_cast<const char*>(body_data)); |
| body_data_pipe->EndReadData(read_size); |
| if (!weak_this) { |
| // This object was deleted, so nothing else to do. |
| return; |
| } |
| |
| body_data_pipe_ = std::move(body_data_pipe); |
| |
| // Wait for Resume() on net::ERR_IO_PENDING. |
| if (error == net::ERR_IO_PENDING) |
| return; |
| |
| if (error != net::OK) |
| pending_error_ = error; |
| } |
| } |
| |
| // 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; |
| |
| // Set to an error code when Delegate::OnDataRead() returns ERR_IO_PENDING, |
| // and there was a pending error from the BodyReader itself (Generally, length |
| // limit exceeded). When this happens, the error will be passed to the |
| // Delegate only after Resume() is called. |
| net::Error pending_error_ = net::OK; |
| |
| base::WeakPtrFactory<BodyReader> weak_ptr_factory_; |
| |
| 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 that discards the response body. |
| class HeadersOnlyBodyHandler : public BodyHandler, public BodyReader::Delegate { |
| public: |
| HeadersOnlyBodyHandler( |
| SimpleURLLoaderImpl* simple_url_loader, |
| SimpleURLLoader::HeadersOnlyCallback headers_only_callback) |
| : BodyHandler(simple_url_loader), |
| headers_only_callback_(std::move(headers_only_callback)) {} |
| |
| ~HeadersOnlyBodyHandler() override {} |
| |
| // BodyHandler implementation |
| void OnStartLoadingResponseBody( |
| mojo::ScopedDataPipeConsumerHandle body_data_pipe) override { |
| // TODO(crbug.com/871420): The request can be completed at this point |
| // however that requires more changes to SimpleURLLoader as OnComplete() |
| // will not have been called yet. |
| DCHECK(!body_reader_); |
| body_reader_ = |
| std::make_unique<BodyReader>(this, std::numeric_limits<int64_t>::max()); |
| body_reader_->Start(std::move(body_data_pipe)); |
| } |
| |
| void NotifyConsumerOfCompletion(bool destroy_results) override { |
| body_reader_.reset(); |
| std::move(headers_only_callback_) |
| .Run(simple_url_loader()->ResponseInfo() |
| ? simple_url_loader()->ResponseInfo()->headers |
| : nullptr); |
| } |
| |
| void PrepareToRetry(base::OnceClosure retry_callback) override { |
| body_reader_.reset(); |
| std::move(retry_callback).Run(); |
| } |
| |
| private: |
| // BodyReader::Delegate implementation |
| net::Error OnDataRead(uint32_t length, const char* data) override { |
| return net::OK; |
| } |
| |
| void OnDone(net::Error error, int64_t total_bytes) override { |
| simple_url_loader()->OnBodyHandlerDone(error, total_bytes); |
| } |
| |
| SimpleURLLoader::HeadersOnlyCallback headers_only_callback_; |
| std::unique_ptr<BodyReader> body_reader_; |
| |
| DISALLOW_COPY_AND_ASSIGN(HeadersOnlyBodyHandler); |
| }; |
| |
| // 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(std::move(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); |
| }; |
| |
| // Class to handle streaming data to the consumer as it arrives |
| class DownloadAsStreamBodyHandler : public BodyHandler, |
| public BodyReader::Delegate { |
| public: |
| DownloadAsStreamBodyHandler(SimpleURLLoaderImpl* simple_url_loader, |
| SimpleURLLoaderStreamConsumer* stream_consumer) |
| : BodyHandler(simple_url_loader), |
| stream_consumer_(stream_consumer), |
| weak_ptr_factory_(this) {} |
| |
| ~DownloadAsStreamBodyHandler() override {} |
| |
| // BodyHandler implementation: |
| |
| void OnStartLoadingResponseBody( |
| mojo::ScopedDataPipeConsumerHandle body_data_pipe) override { |
| DCHECK(!body_reader_); |
| |
| body_reader_ = |
| std::make_unique<BodyReader>(this, std::numeric_limits<int64_t>::max()); |
| body_reader_->Start(std::move(body_data_pipe)); |
| } |
| |
| void NotifyConsumerOfCompletion(bool destroy_results) override { |
| body_reader_.reset(); |
| stream_consumer_->OnComplete(simple_url_loader()->NetError() == net::OK); |
| } |
| |
| void PrepareToRetry(base::OnceClosure retry_callback) override { |
| body_reader_.reset(); |
| stream_consumer_->OnRetry(std::move(retry_callback)); |
| } |
| |
| private: |
| // BodyReader::Delegate implementation. |
| |
| net::Error OnDataRead(uint32_t length, const char* data) override { |
| in_recursive_call_ = true; |
| base::WeakPtr<DownloadAsStreamBodyHandler> weak_this( |
| weak_ptr_factory_.GetWeakPtr()); |
| stream_consumer_->OnDataReceived( |
| base::StringPiece(data, length), |
| base::BindOnce(&DownloadAsStreamBodyHandler::Resume, |
| weak_ptr_factory_.GetWeakPtr())); |
| // Protect against deletion. |
| if (weak_this) |
| in_recursive_call_ = false; |
| return net::ERR_IO_PENDING; |
| } |
| |
| void OnDone(net::Error error, int64_t total_bytes) override { |
| simple_url_loader()->OnBodyHandlerDone(error, total_bytes); |
| } |
| |
| void Resume() { |
| // Can't call DownloadAsStreamBodyHandler::Resume() immediately when called |
| // recursively from OnDataRead. |
| if (in_recursive_call_) { |
| base::SequencedTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(&DownloadAsStreamBodyHandler::Resume, |
| weak_ptr_factory_.GetWeakPtr())); |
| return; |
| } |
| body_reader_->Resume(); |
| } |
| |
| SimpleURLLoaderStreamConsumer* stream_consumer_; |
| |
| std::unique_ptr<BodyReader> body_reader_; |
| |
| bool in_recursive_call_ = false; |
| |
| base::WeakPtrFactory<DownloadAsStreamBodyHandler> weak_ptr_factory_; |
| |
| DISALLOW_COPY_AND_ASSIGN(DownloadAsStreamBodyHandler); |
| }; |
| |
| SimpleURLLoaderImpl::SimpleURLLoaderImpl( |
| std::unique_ptr<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>()), |
| final_url_(resource_request_->url), |
| 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 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() != DataElement::TYPE_FILE && |
| element.type() != DataElement::TYPE_BYTES); |
| } |
| } |
| #endif // DCHECK_IS_ON() |
| } |
| |
| SimpleURLLoaderImpl::~SimpleURLLoaderImpl() {} |
| |
| void SimpleURLLoaderImpl::DownloadToString( |
| 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( |
| 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 URLLoaderCompletionStatus::decoded_body_length |
| // is an int64_t, not a size_t. |
| std::numeric_limits<int64_t>::max()); |
| Start(url_loader_factory); |
| } |
| |
| void SimpleURLLoaderImpl::DownloadHeadersOnly( |
| mojom::URLLoaderFactory* url_loader_factory, |
| HeadersOnlyCallback headers_only_callback) { |
| body_handler_ = std::make_unique<HeadersOnlyBodyHandler>( |
| this, std::move(headers_only_callback)); |
| Start(url_loader_factory); |
| } |
| |
| void SimpleURLLoaderImpl::DownloadToFile( |
| 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( |
| 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::DownloadAsStream( |
| mojom::URLLoaderFactory* url_loader_factory, |
| SimpleURLLoaderStreamConsumer* stream_consumer) { |
| body_handler_ = |
| std::make_unique<DownloadAsStreamBodyHandler>(this, stream_consumer); |
| Start(url_loader_factory); |
| } |
| |
| void SimpleURLLoaderImpl::SetOnRedirectCallback( |
| const OnRedirectCallback& on_redirect_callback) { |
| on_redirect_callback_.push_back(on_redirect_callback); |
| } |
| |
| void SimpleURLLoaderImpl::SetOnResponseStartedCallback( |
| OnResponseStartedCallback on_response_started_callback) { |
| on_response_started_callback_ = std::move(on_response_started_callback); |
| DCHECK(on_response_started_callback_); |
| } |
| |
| void SimpleURLLoaderImpl::SetOnUploadProgressCallback( |
| UploadProgressCallback on_upload_progress_callback) { |
| on_upload_progress_callback_ = std::move(on_upload_progress_callback); |
| DCHECK(on_upload_progress_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 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 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 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() != 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 GURL& SimpleURLLoaderImpl::GetFinalURL() const { |
| // Should only be called once the request is compelete. |
| DCHECK(request_state_->finished); |
| return final_url_; |
| } |
| |
| bool SimpleURLLoaderImpl::LoadedFromCache() const { |
| // Should only be called once the request is compelete. |
| DCHECK(request_state_->finished); |
| return request_state_->loaded_from_cache; |
| } |
| |
| int64_t SimpleURLLoaderImpl::GetContentSize() const { |
| // Should only be called once the request is compelete. |
| DCHECK(request_state_->finished); |
| return request_state_->received_body_size; |
| } |
| |
| const 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) { |
| // When |allow_partial_results_| is true, a valid body|file_path is |
| // passed to the completion callback even in the case of failures. |
| // For consistency, it makes sense to also hold the actual decompressed |
| // body size in case GetContentSize is called. |
| if (allow_partial_results_) |
| request_state_->received_body_size = received_body_size; |
| 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(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( |
| mojom::URLLoaderFactory* url_loader_factory) { |
| DCHECK(resource_request_); |
| DCHECK(url_loader_factory); |
| |
| 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 ResourceRequestBody(); |
| 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 ResourceResponseHead& response_head) { |
| 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; |
| } |
| |
| if (on_response_started_callback_) { |
| base::WeakPtr<SimpleURLLoaderImpl> weak_this = |
| weak_ptr_factory_.GetWeakPtr(); |
| // Copy |final_url_| to a stack allocated GURL so it remains valid even if |
| // the callback deletes |this|. |
| GURL final_url = final_url_; |
| std::move(on_response_started_callback_).Run(final_url, response_head); |
| // If deleted by the callback, bail now. |
| if (!weak_this) |
| return; |
| } |
| |
| request_state_->response_info = |
| std::make_unique<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 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; |
| } |
| |
| std::vector<std::string> to_be_removed_headers; |
| for (auto callback : on_redirect_callback_) { |
| if (callback) { |
| base::WeakPtr<SimpleURLLoaderImpl> weak_this = |
| weak_ptr_factory_.GetWeakPtr(); |
| callback.Run(redirect_info, response_head, &to_be_removed_headers); |
| // If deleted by the callback, bail now. |
| if (!weak_this) |
| return; |
| } |
| } |
| |
| final_url_ = redirect_info.new_url; |
| if (to_be_removed_headers.empty()) |
| url_loader_->FollowRedirect(base::nullopt, base::nullopt); |
| else |
| url_loader_->FollowRedirect(to_be_removed_headers, base::nullopt); |
| } |
| |
| void SimpleURLLoaderImpl::OnReceiveCachedMetadata( |
| const std::vector<uint8_t>& data) { |
| // Ignored. |
| } |
| |
| void SimpleURLLoaderImpl::OnTransferSizeUpdated(int32_t transfer_size_diff) {} |
| |
| void SimpleURLLoaderImpl::OnUploadProgress( |
| int64_t current_position, |
| int64_t total_size, |
| OnUploadProgressCallback ack_callback) { |
| if (on_upload_progress_callback_) |
| on_upload_progress_callback_.Run(current_position, total_size); |
| std::move(ack_callback).Run(); |
| } |
| |
| 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 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; |
| request_state_->loaded_from_cache = status.exists_in_cache; |
| // 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 |
| // 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<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 network |