| // Copyright (c) 2019 The Chromium Embedded Framework Authors. Portions | |
| // Copyright (c) 2018 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 "libcef/browser/net_service/stream_reader_url_loader.h" | |
| #include "libcef/browser/thread_util.h" | |
| #include "libcef/common/net_service/net_service_util.h" | |
| #include "base/bind.h" | |
| #include "base/callback.h" | |
| #include "base/strings/string_number_conversions.h" | |
| #include "base/strings/string_util.h" | |
| #include "base/strings/stringprintf.h" | |
| #include "base/task/post_task.h" | |
| #include "base/threading/thread.h" | |
| #include "base/threading/thread_task_runner_handle.h" | |
| #include "content/public/browser/browser_thread.h" | |
| #include "net/base/io_buffer.h" | |
| #include "net/http/http_status_code.h" | |
| #include "net/http/http_util.h" | |
| #include "services/network/public/cpp/url_loader_completion_status.h" | |
| namespace net_service { | |
| namespace { | |
| using OnInputStreamOpenedCallback = | |
| base::OnceCallback<void(std::unique_ptr<StreamReaderURLLoader::Delegate>, | |
| std::unique_ptr<InputStream>)>; | |
| // Helper for executing the OnInputStreamOpenedCallback. | |
| class OpenInputStreamWrapper | |
| : public base::RefCountedThreadSafe<OpenInputStreamWrapper> { | |
| public: | |
| static base::OnceClosure Open( | |
| std::unique_ptr<StreamReaderURLLoader::Delegate> delegate, | |
| scoped_refptr<base::SequencedTaskRunner> work_thread_task_runner, | |
| const RequestId& request_id, | |
| const network::ResourceRequest& request, | |
| OnInputStreamOpenedCallback callback) WARN_UNUSED_RESULT { | |
| scoped_refptr<OpenInputStreamWrapper> wrapper = new OpenInputStreamWrapper( | |
| std::move(delegate), work_thread_task_runner, | |
| base::ThreadTaskRunnerHandle::Get(), std::move(callback)); | |
| wrapper->Start(request_id, request); | |
| return wrapper->GetCancelCallback(); | |
| } | |
| private: | |
| friend class base::RefCountedThreadSafe<OpenInputStreamWrapper>; | |
| OpenInputStreamWrapper( | |
| std::unique_ptr<StreamReaderURLLoader::Delegate> delegate, | |
| scoped_refptr<base::SequencedTaskRunner> work_thread_task_runner, | |
| scoped_refptr<base::SingleThreadTaskRunner> job_thread_task_runner, | |
| OnInputStreamOpenedCallback callback) | |
| : delegate_(std::move(delegate)), | |
| work_thread_task_runner_(work_thread_task_runner), | |
| job_thread_task_runner_(job_thread_task_runner), | |
| callback_(std::move(callback)) {} | |
| virtual ~OpenInputStreamWrapper() {} | |
| void Start(const RequestId& request_id, | |
| const network::ResourceRequest& request) { | |
| work_thread_task_runner_->PostTask( | |
| FROM_HERE, | |
| base::BindOnce(&OpenInputStreamWrapper::OpenOnWorkThread, | |
| base::WrapRefCounted(this), request_id, request)); | |
| } | |
| base::OnceClosure GetCancelCallback() { | |
| return base::BindOnce(&OpenInputStreamWrapper::CancelOnJobThread, | |
| base::WrapRefCounted(this)); | |
| } | |
| void CancelOnJobThread() { | |
| DCHECK(job_thread_task_runner_->RunsTasksInCurrentSequence()); | |
| if (callback_.is_null()) | |
| return; | |
| callback_.Reset(); | |
| work_thread_task_runner_->PostTask( | |
| FROM_HERE, base::BindOnce(&OpenInputStreamWrapper::CancelOnWorkThread, | |
| base::WrapRefCounted(this))); | |
| } | |
| void CancelOnWorkThread() { | |
| DCHECK(work_thread_task_runner_->RunsTasksInCurrentSequence()); | |
| if (is_canceled_) | |
| return; | |
| is_canceled_ = true; | |
| OnCallback(nullptr); | |
| } | |
| void OpenOnWorkThread(const RequestId& request_id, | |
| const network::ResourceRequest& request) { | |
| DCHECK(work_thread_task_runner_->RunsTasksInCurrentSequence()); | |
| if (is_canceled_) | |
| return; | |
| // |delegate_| will remain valid until OnCallback() is executed on | |
| // |job_thread_task_runner_|. | |
| if (!delegate_->OpenInputStream( | |
| request_id, request, | |
| base::BindOnce(&OpenInputStreamWrapper::OnCallback, | |
| base::WrapRefCounted(this)))) { | |
| is_canceled_ = true; | |
| OnCallback(nullptr); | |
| } | |
| } | |
| void OnCallback(std::unique_ptr<InputStream> input_stream) { | |
| if (!job_thread_task_runner_->RunsTasksInCurrentSequence()) { | |
| job_thread_task_runner_->PostTask( | |
| FROM_HERE, | |
| base::BindOnce(&OpenInputStreamWrapper::OnCallback, | |
| base::WrapRefCounted(this), std::move(input_stream))); | |
| return; | |
| } | |
| // May be null if CancelOnJobThread() was called on | |
| // |job_thread_task_runner_| while OpenOnWorkThread() was pending on | |
| // |work_thread_task_runner_|. | |
| if (callback_.is_null()) { | |
| delegate_.reset(); | |
| return; | |
| } | |
| std::move(callback_).Run(std::move(delegate_), std::move(input_stream)); | |
| } | |
| std::unique_ptr<StreamReaderURLLoader::Delegate> delegate_; | |
| scoped_refptr<base::SequencedTaskRunner> work_thread_task_runner_; | |
| scoped_refptr<base::SingleThreadTaskRunner> job_thread_task_runner_; | |
| // Only accessed on |job_thread_task_runner_|. | |
| OnInputStreamOpenedCallback callback_; | |
| // Only accessed on |work_thread_task_runner_|. | |
| bool is_canceled_ = false; | |
| DISALLOW_COPY_AND_ASSIGN(OpenInputStreamWrapper); | |
| }; | |
| } // namespace | |
| //============================== | |
| // InputStreamReader | |
| //============================= | |
| // Class responsible for reading from the InputStream. | |
| class InputStreamReader : public base::RefCountedThreadSafe<InputStreamReader> { | |
| public: | |
| // The constructor is called on the IO thread, not on the worker thread. | |
| // Callbacks will be executed on the IO thread. | |
| InputStreamReader( | |
| std::unique_ptr<InputStream> stream, | |
| scoped_refptr<base::SequencedTaskRunner> work_thread_task_runner); | |
| // Skip |skip_bytes| number of bytes from |stream_|. |callback| will be | |
| // executed asynchronously on the IO thread. A negative value passed to | |
| // |callback| will indicate an error code, a positive value will indicate the | |
| // number of bytes skipped. | |
| void Skip(int64_t skip_bytes, InputStream::SkipCallback callback); | |
| // Read up to |dest_size| bytes from |stream_| into |dest|. |callback| will be | |
| // executed asynchronously on the IO thread. A negative value passed to | |
| // |callback| will indicate an error code, a positive value will indicate the | |
| // number of bytes read. | |
| void Read(scoped_refptr<net::IOBuffer> dest, | |
| int dest_size, | |
| InputStream::ReadCallback callback); | |
| private: | |
| friend class base::RefCountedThreadSafe<InputStreamReader>; | |
| virtual ~InputStreamReader(); | |
| void SkipOnWorkThread(int64_t skip_bytes, InputStream::SkipCallback callback); | |
| void ReadOnWorkThread(scoped_refptr<net::IOBuffer> buffer, | |
| int buffer_size, | |
| InputStream::ReadCallback callback); | |
| void SkipToRequestedRange(); | |
| static void ContinueSkipCallback( | |
| scoped_refptr<InputStreamReader> stream, | |
| scoped_refptr<base::SequencedTaskRunner> work_thread_task_runner, | |
| int callback_id, | |
| int64_t bytes_skipped); | |
| static void ContinueReadCallback( | |
| scoped_refptr<InputStreamReader> stream, | |
| scoped_refptr<base::SequencedTaskRunner> work_thread_task_runner, | |
| int callback_id, | |
| int bytes_read); | |
| void ContinueSkipCallbackOnWorkThread(int callback_id, int64_t bytes_skipped); | |
| void ContinueReadCallbackOnWorkThread(int callback_id, int bytes_read); | |
| void RunSkipCallback(int64_t bytes_skipped); | |
| void RunReadCallback(int bytes_read); | |
| static void RunSkipCallbackOnJobThread( | |
| int64_t bytes_skipped, | |
| InputStream::SkipCallback skip_callback); | |
| static void RunReadCallbackOnJobThread( | |
| int bytes_read, | |
| InputStream::ReadCallback read_callback); | |
| std::unique_ptr<InputStream> stream_; | |
| // All InputStream methods are called this task runner. | |
| scoped_refptr<base::SequencedTaskRunner> work_thread_task_runner_; | |
| // All callbacks are executed on this task runner. | |
| scoped_refptr<base::SingleThreadTaskRunner> job_thread_task_runner_; | |
| // The below members are only accessed on the work thread. | |
| int64_t bytes_skipped_; | |
| int64_t bytes_to_skip_; | |
| InputStream::SkipCallback pending_skip_callback_; | |
| scoped_refptr<net::IOBuffer> buffer_; | |
| InputStream::ReadCallback pending_read_callback_; | |
| int pending_callback_id_ = -1; | |
| int next_callback_id_ = 0; | |
| DISALLOW_COPY_AND_ASSIGN(InputStreamReader); | |
| }; | |
| InputStreamReader::InputStreamReader( | |
| std::unique_ptr<InputStream> stream, | |
| scoped_refptr<base::SequencedTaskRunner> work_thread_task_runner) | |
| : stream_(std::move(stream)), | |
| work_thread_task_runner_(work_thread_task_runner), | |
| job_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()) { | |
| CEF_REQUIRE_IOT(); | |
| DCHECK(stream_); | |
| DCHECK(work_thread_task_runner_); | |
| } | |
| InputStreamReader::~InputStreamReader() {} | |
| void InputStreamReader::Skip(int64_t skip_bytes, | |
| InputStream::SkipCallback callback) { | |
| work_thread_task_runner_->PostTask( | |
| FROM_HERE, base::BindOnce(&InputStreamReader::SkipOnWorkThread, | |
| base::WrapRefCounted(this), skip_bytes, | |
| std::move(callback))); | |
| } | |
| void InputStreamReader::Read(scoped_refptr<net::IOBuffer> dest, | |
| int dest_size, | |
| InputStream::ReadCallback callback) { | |
| work_thread_task_runner_->PostTask( | |
| FROM_HERE, base::BindOnce(&InputStreamReader::ReadOnWorkThread, | |
| base::WrapRefCounted(this), dest, dest_size, | |
| std::move(callback))); | |
| } | |
| void InputStreamReader::SkipOnWorkThread(int64_t skip_bytes, | |
| InputStream::SkipCallback callback) { | |
| DCHECK(work_thread_task_runner_->RunsTasksInCurrentSequence()); | |
| // No callback should currently be pending. | |
| DCHECK_EQ(pending_callback_id_, -1); | |
| DCHECK(pending_skip_callback_.is_null()); | |
| pending_skip_callback_ = std::move(callback); | |
| if (skip_bytes <= 0) { | |
| RunSkipCallback(0); | |
| return; | |
| } | |
| bytes_skipped_ = bytes_to_skip_ = skip_bytes; | |
| SkipToRequestedRange(); | |
| } | |
| void InputStreamReader::ReadOnWorkThread(scoped_refptr<net::IOBuffer> dest, | |
| int dest_size, | |
| InputStream::ReadCallback callback) { | |
| DCHECK(work_thread_task_runner_->RunsTasksInCurrentSequence()); | |
| // No callback should currently be pending. | |
| DCHECK_EQ(pending_callback_id_, -1); | |
| DCHECK(pending_read_callback_.is_null()); | |
| pending_read_callback_ = std::move(callback); | |
| if (!dest_size) { | |
| RunReadCallback(0); | |
| return; | |
| } | |
| DCHECK_GT(dest_size, 0); | |
| buffer_ = dest; | |
| pending_callback_id_ = ++next_callback_id_; | |
| int bytes_read = 0; | |
| bool result = stream_->Read( | |
| buffer_.get(), dest_size, &bytes_read, | |
| base::BindOnce(&InputStreamReader::ContinueReadCallback, | |
| base::WrapRefCounted(this), work_thread_task_runner_, | |
| pending_callback_id_)); | |
| // Check if the callback will execute asynchronously. | |
| if (result && bytes_read == 0) | |
| return; | |
| RunReadCallback(result || bytes_read <= 0 ? bytes_read : net::ERR_FAILED); | |
| } | |
| void InputStreamReader::SkipToRequestedRange() { | |
| DCHECK(work_thread_task_runner_->RunsTasksInCurrentSequence()); | |
| // Skip to the start of the requested data. This has to be done in a loop | |
| // because the underlying InputStream is not guaranteed to skip the requested | |
| // number of bytes. | |
| do { | |
| pending_callback_id_ = ++next_callback_id_; | |
| int64_t skipped = 0; | |
| bool result = stream_->Skip( | |
| bytes_to_skip_, &skipped, | |
| base::BindOnce(&InputStreamReader::ContinueSkipCallback, | |
| base::WrapRefCounted(this), work_thread_task_runner_, | |
| pending_callback_id_)); | |
| // Check if the callback will execute asynchronously. | |
| if (result && skipped == 0) | |
| return; | |
| if (!result || skipped <= 0) { | |
| RunSkipCallback(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE); | |
| return; | |
| } | |
| DCHECK_LE(skipped, bytes_to_skip_); | |
| bytes_to_skip_ -= skipped; | |
| } while (bytes_to_skip_ > 0); | |
| // All done, the requested number of bytes were skipped. | |
| RunSkipCallback(bytes_skipped_); | |
| } | |
| // static | |
| void InputStreamReader::ContinueSkipCallback( | |
| scoped_refptr<InputStreamReader> stream, | |
| scoped_refptr<base::SequencedTaskRunner> work_thread_task_runner, | |
| int callback_id, | |
| int64_t bytes_skipped) { | |
| // Always execute asynchronously. | |
| work_thread_task_runner->PostTask( | |
| FROM_HERE, | |
| base::BindOnce(&InputStreamReader::ContinueSkipCallbackOnWorkThread, | |
| stream, callback_id, bytes_skipped)); | |
| } | |
| // static | |
| void InputStreamReader::ContinueReadCallback( | |
| scoped_refptr<InputStreamReader> stream, | |
| scoped_refptr<base::SequencedTaskRunner> work_thread_task_runner, | |
| int callback_id, | |
| int bytes_read) { | |
| // Always execute asynchronously. | |
| work_thread_task_runner->PostTask( | |
| FROM_HERE, | |
| base::BindOnce(&InputStreamReader::ContinueReadCallbackOnWorkThread, | |
| stream, callback_id, bytes_read)); | |
| } | |
| void InputStreamReader::ContinueSkipCallbackOnWorkThread( | |
| int callback_id, | |
| int64_t bytes_skipped) { | |
| DCHECK(work_thread_task_runner_->RunsTasksInCurrentSequence()); | |
| // Check for out of order callbacks. | |
| if (pending_callback_id_ != callback_id) | |
| return; | |
| DCHECK_LE(bytes_skipped, bytes_to_skip_); | |
| if (bytes_to_skip_ > 0 && bytes_skipped > 0) | |
| bytes_to_skip_ -= bytes_skipped; | |
| if (bytes_skipped <= 0) { | |
| RunSkipCallback(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE); | |
| } else if (bytes_to_skip_ > 0) { | |
| // Continue execution asynchronously. | |
| work_thread_task_runner_->PostTask( | |
| FROM_HERE, | |
| base::BindOnce(&InputStreamReader::SkipToRequestedRange, this)); | |
| } else { | |
| // All done, the requested number of bytes were skipped. | |
| RunSkipCallback(bytes_skipped_); | |
| } | |
| } | |
| void InputStreamReader::ContinueReadCallbackOnWorkThread(int callback_id, | |
| int bytes_read) { | |
| DCHECK(work_thread_task_runner_->RunsTasksInCurrentSequence()); | |
| // Check for out of order callbacks. | |
| if (pending_callback_id_ != callback_id) | |
| return; | |
| RunReadCallback(bytes_read); | |
| } | |
| void InputStreamReader::RunSkipCallback(int64_t bytes_skipped) { | |
| DCHECK(work_thread_task_runner_->RunsTasksInCurrentSequence()); | |
| DCHECK(!pending_skip_callback_.is_null()); | |
| job_thread_task_runner_->PostTask( | |
| FROM_HERE, | |
| base::BindOnce(InputStreamReader::RunSkipCallbackOnJobThread, | |
| bytes_skipped, std::move(pending_skip_callback_))); | |
| // Reset callback state. | |
| pending_callback_id_ = -1; | |
| bytes_skipped_ = bytes_to_skip_ = -1; | |
| } | |
| void InputStreamReader::RunReadCallback(int bytes_read) { | |
| DCHECK(work_thread_task_runner_->RunsTasksInCurrentSequence()); | |
| DCHECK(!pending_read_callback_.is_null()); | |
| job_thread_task_runner_->PostTask( | |
| FROM_HERE, base::BindOnce(InputStreamReader::RunReadCallbackOnJobThread, | |
| bytes_read, std::move(pending_read_callback_))); | |
| // Reset callback state. | |
| pending_callback_id_ = -1; | |
| buffer_ = nullptr; | |
| } | |
| // static | |
| void InputStreamReader::RunSkipCallbackOnJobThread( | |
| int64_t bytes_skipped, | |
| InputStream::SkipCallback skip_callback) { | |
| std::move(skip_callback).Run(bytes_skipped); | |
| } | |
| // static | |
| void InputStreamReader::RunReadCallbackOnJobThread( | |
| int bytes_read, | |
| InputStream::ReadCallback read_callback) { | |
| std::move(read_callback).Run(bytes_read); | |
| } | |
| //============================== | |
| // RequestId | |
| //============================== | |
| std::string RequestId::ToString() const { | |
| return base::StringPrintf("RequestId(%u, %u)", request_id_, routing_id_); | |
| } | |
| std::string RequestId::ToString(base::StringPiece debug_label) const { | |
| return base::StringPrintf("RequestId[%s](%u, %u)", | |
| debug_label.as_string().c_str(), request_id_, | |
| routing_id_); | |
| } | |
| std::ostream& operator<<(std::ostream& out, const RequestId& request_id) { | |
| return out << request_id.ToString(); | |
| } | |
| //============================== | |
| // StreamReaderURLLoader | |
| //============================= | |
| StreamReaderURLLoader::StreamReaderURLLoader( | |
| const RequestId& request_id, | |
| const network::ResourceRequest& request, | |
| network::mojom::URLLoaderClientPtr client, | |
| network::mojom::TrustedHeaderClientPtr header_client, | |
| const net::MutableNetworkTrafficAnnotationTag& traffic_annotation, | |
| std::unique_ptr<Delegate> response_delegate) | |
| : request_id_(request_id), | |
| request_(request), | |
| client_(std::move(client)), | |
| header_client_(std::move(header_client)), | |
| traffic_annotation_(traffic_annotation), | |
| response_delegate_(std::move(response_delegate)), | |
| writable_handle_watcher_(FROM_HERE, | |
| mojo::SimpleWatcher::ArmingPolicy::MANUAL, | |
| base::SequencedTaskRunnerHandle::Get()), | |
| weak_factory_(this) { | |
| DCHECK(response_delegate_); | |
| // If there is a client error, clean up the request. | |
| client_.set_connection_error_handler( | |
| base::BindOnce(&StreamReaderURLLoader::RequestComplete, | |
| weak_factory_.GetWeakPtr(), net::ERR_ABORTED)); | |
| // All InputStream work will be performed on this task runner. | |
| stream_work_task_runner_ = | |
| base::CreateSequencedTaskRunner({base::ThreadPool(), base::MayBlock()}); | |
| } | |
| StreamReaderURLLoader::~StreamReaderURLLoader() { | |
| if (open_cancel_callback_) { | |
| // Release the Delegate held by OpenInputStreamWrapper. | |
| std::move(open_cancel_callback_).Run(); | |
| } | |
| } | |
| void StreamReaderURLLoader::Start() { | |
| DCHECK(thread_checker_.CalledOnValidThread()); | |
| if (!ParseRange(request_.headers)) { | |
| RequestComplete(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE); | |
| return; | |
| } | |
| if (header_client_.is_bound()) { | |
| header_client_->OnBeforeSendHeaders( | |
| request_.headers, | |
| base::BindOnce(&StreamReaderURLLoader::ContinueWithRequestHeaders, | |
| weak_factory_.GetWeakPtr())); | |
| } else { | |
| ContinueWithRequestHeaders(net::OK, base::nullopt); | |
| } | |
| } | |
| void StreamReaderURLLoader::ContinueWithRequestHeaders( | |
| int32_t result, | |
| const base::Optional<net::HttpRequestHeaders>& headers) { | |
| if (result != net::OK) { | |
| RequestComplete(result); | |
| return; | |
| } | |
| if (headers) { | |
| DCHECK(header_client_.is_bound()); | |
| request_.headers = *headers; | |
| } | |
| open_cancel_callback_ = OpenInputStreamWrapper::Open( | |
| // This is intentional - the loader could be deleted while | |
| // the callback is executing on the background thread. The | |
| // delegate will be "returned" to the loader once the | |
| // InputStream open attempt is completed. | |
| std::move(response_delegate_), stream_work_task_runner_, request_id_, | |
| request_, | |
| base::BindOnce(&StreamReaderURLLoader::OnInputStreamOpened, | |
| weak_factory_.GetWeakPtr())); | |
| } | |
| void StreamReaderURLLoader::FollowRedirect( | |
| const std::vector<std::string>& removed_headers, | |
| const net::HttpRequestHeaders& modified_headers, | |
| const base::Optional<GURL>& new_url) { | |
| NOTREACHED(); | |
| } | |
| void StreamReaderURLLoader::SetPriority(net::RequestPriority priority, | |
| int intra_priority_value) {} | |
| void StreamReaderURLLoader::PauseReadingBodyFromNet() {} | |
| void StreamReaderURLLoader::ResumeReadingBodyFromNet() {} | |
| void StreamReaderURLLoader::OnInputStreamOpened( | |
| std::unique_ptr<StreamReaderURLLoader::Delegate> returned_delegate, | |
| std::unique_ptr<InputStream> input_stream) { | |
| DCHECK(thread_checker_.CalledOnValidThread()); | |
| DCHECK(returned_delegate); | |
| response_delegate_ = std::move(returned_delegate); | |
| open_cancel_callback_.Reset(); | |
| if (!input_stream) { | |
| bool restarted = false; | |
| response_delegate_->OnInputStreamOpenFailed(request_id_, &restarted); | |
| if (restarted) { | |
| // The request has been restarted with a new loader. | |
| // |this| will be deleted. | |
| CleanUp(); | |
| } else { | |
| HeadersComplete(net::HTTP_NOT_FOUND, -1); | |
| } | |
| return; | |
| } | |
| input_stream_reader_ = base::MakeRefCounted<InputStreamReader>( | |
| std::move(input_stream), stream_work_task_runner_); | |
| if (!byte_range_valid()) { | |
| OnReaderSkipCompleted(0); | |
| } else { | |
| input_stream_reader_->Skip( | |
| byte_range_.first_byte_position(), | |
| base::BindOnce(&StreamReaderURLLoader::OnReaderSkipCompleted, | |
| weak_factory_.GetWeakPtr())); | |
| } | |
| } | |
| void StreamReaderURLLoader::OnReaderSkipCompleted(int64_t bytes_skipped) { | |
| DCHECK(thread_checker_.CalledOnValidThread()); | |
| if (!byte_range_valid()) { | |
| // Expected content length is unspecified. | |
| HeadersComplete(net::HTTP_OK, -1); | |
| } else if (bytes_skipped == byte_range_.first_byte_position()) { | |
| // We skipped the expected number of bytes. | |
| int64_t expected_content_length = -1; | |
| if (byte_range_.HasLastBytePosition()) { | |
| expected_content_length = byte_range_.last_byte_position() - | |
| byte_range_.first_byte_position() + 1; | |
| DCHECK_GE(expected_content_length, 0); | |
| } | |
| HeadersComplete(net::HTTP_OK, expected_content_length); | |
| } else { | |
| RequestComplete(bytes_skipped < 0 ? bytes_skipped : net::ERR_FAILED); | |
| } | |
| } | |
| void StreamReaderURLLoader::HeadersComplete(int orig_status_code, | |
| int64_t expected_content_length) { | |
| DCHECK(thread_checker_.CalledOnValidThread()); | |
| int status_code = orig_status_code; | |
| std::string status_text = | |
| net::GetHttpReasonPhrase(static_cast<net::HttpStatusCode>(status_code)); | |
| std::string mime_type, charset; | |
| int64_t content_length = expected_content_length; | |
| ResourceResponse::HeaderMap extra_headers; | |
| response_delegate_->GetResponseHeaders(request_id_, &status_code, | |
| &status_text, &mime_type, &charset, | |
| &content_length, &extra_headers); | |
| if (status_code < 0) { | |
| // Early exit if the handler reported an error. | |
| RequestComplete(status_code); | |
| return; | |
| } | |
| auto pending_response = network::mojom::URLResponseHead::New(); | |
| pending_response->request_start = base::TimeTicks::Now(); | |
| pending_response->response_start = base::TimeTicks::Now(); | |
| auto headers = MakeResponseHeaders( | |
| status_code, status_text, mime_type, charset, content_length, | |
| extra_headers, false /* allow_existing_header_override */); | |
| pending_response->headers = headers; | |
| if (content_length >= 0) | |
| pending_response->content_length = content_length; | |
| if (!mime_type.empty()) { | |
| pending_response->mime_type = mime_type; | |
| if (!charset.empty()) | |
| pending_response->charset = charset; | |
| } | |
| if (header_client_.is_bound()) { | |
| header_client_->OnHeadersReceived( | |
| headers->raw_headers(), net::IPEndPoint(), | |
| base::BindOnce(&StreamReaderURLLoader::ContinueWithResponseHeaders, | |
| weak_factory_.GetWeakPtr(), | |
| std::move(pending_response))); | |
| } else { | |
| ContinueWithResponseHeaders(std::move(pending_response), net::OK, | |
| base::nullopt, base::nullopt); | |
| } | |
| } | |
| void StreamReaderURLLoader::ContinueWithResponseHeaders( | |
| network::mojom::URLResponseHeadPtr pending_response, | |
| int32_t result, | |
| const base::Optional<std::string>& headers, | |
| const base::Optional<GURL>& redirect_url) { | |
| if (result != net::OK) { | |
| RequestComplete(result); | |
| return; | |
| } | |
| if (headers) { | |
| DCHECK(header_client_.is_bound()); | |
| pending_response->headers = | |
| base::MakeRefCounted<net::HttpResponseHeaders>(*headers); | |
| } | |
| auto pending_headers = pending_response->headers; | |
| // What the length would be if we sent headers over the network. Used to | |
| // calculate data length. | |
| header_length_ = pending_headers->raw_headers().length(); | |
| DCHECK(client_.is_bound()); | |
| std::string location; | |
| const auto has_redirect_url = redirect_url && !redirect_url->is_empty(); | |
| if (has_redirect_url || pending_headers->IsRedirect(&location)) { | |
| pending_response->encoded_data_length = header_length_; | |
| pending_response->content_length = pending_response->encoded_body_length = | |
| 0; | |
| const GURL new_location = | |
| has_redirect_url ? *redirect_url : request_.url.Resolve(location); | |
| client_->OnReceiveRedirect( | |
| MakeRedirectInfo(request_, pending_headers.get(), new_location, | |
| pending_headers->response_code()), | |
| std::move(pending_response)); | |
| // The client will restart the request with a new loader. | |
| // |this| will be deleted. | |
| CleanUp(); | |
| } else { | |
| client_->OnReceiveResponse(std::move(pending_response)); | |
| } | |
| } | |
| void StreamReaderURLLoader::ContinueResponse(bool was_redirected) { | |
| DCHECK(thread_checker_.CalledOnValidThread()); | |
| if (was_redirected) { | |
| // Special case where we allow the client to perform the redirect. | |
| // The client will restart the request with a new loader. | |
| // |this| will be deleted. | |
| CleanUp(); | |
| } else { | |
| SendBody(); | |
| } | |
| } | |
| void StreamReaderURLLoader::SendBody() { | |
| DCHECK(thread_checker_.CalledOnValidThread()); | |
| mojo::ScopedDataPipeConsumerHandle consumer_handle; | |
| if (CreateDataPipe(nullptr /*options*/, &producer_handle_, | |
| &consumer_handle) != MOJO_RESULT_OK) { | |
| RequestComplete(net::ERR_FAILED); | |
| return; | |
| } | |
| writable_handle_watcher_.Watch( | |
| producer_handle_.get(), MOJO_HANDLE_SIGNAL_WRITABLE, | |
| base::BindRepeating(&StreamReaderURLLoader::OnDataPipeWritable, | |
| base::Unretained(this))); | |
| client_->OnStartLoadingResponseBody(std::move(consumer_handle)); | |
| ReadMore(); | |
| } | |
| void StreamReaderURLLoader::ReadMore() { | |
| DCHECK(thread_checker_.CalledOnValidThread()); | |
| DCHECK(!pending_buffer_.get()); | |
| uint32_t num_bytes; | |
| MojoResult mojo_result = network::NetToMojoPendingBuffer::BeginWrite( | |
| &producer_handle_, &pending_buffer_, &num_bytes); | |
| if (mojo_result == MOJO_RESULT_SHOULD_WAIT) { | |
| // The pipe is full. We need to wait for it to have more space. | |
| writable_handle_watcher_.ArmOrNotify(); | |
| return; | |
| } else if (mojo_result == MOJO_RESULT_FAILED_PRECONDITION) { | |
| // The data pipe consumer handle has been closed. | |
| RequestComplete(net::ERR_ABORTED); | |
| return; | |
| } else if (mojo_result != MOJO_RESULT_OK) { | |
| // The body stream is in a bad state. Bail out. | |
| RequestComplete(net::ERR_UNEXPECTED); | |
| return; | |
| } | |
| scoped_refptr<net::IOBuffer> buffer( | |
| new network::NetToMojoIOBuffer(pending_buffer_.get())); | |
| if (!input_stream_reader_.get()) { | |
| // This will happen if opening the InputStream fails in which case the | |
| // error is communicated by setting the HTTP response status header rather | |
| // than failing the request during the header fetch phase. | |
| OnReaderReadCompleted(0); | |
| return; | |
| } | |
| input_stream_reader_->Read( | |
| buffer, base::checked_cast<int>(num_bytes), | |
| base::BindOnce(&StreamReaderURLLoader::OnReaderReadCompleted, | |
| weak_factory_.GetWeakPtr())); | |
| } | |
| void StreamReaderURLLoader::OnDataPipeWritable(MojoResult result) { | |
| if (result == MOJO_RESULT_FAILED_PRECONDITION) { | |
| RequestComplete(net::ERR_ABORTED); | |
| return; | |
| } | |
| DCHECK_EQ(result, MOJO_RESULT_OK) << result; | |
| ReadMore(); | |
| } | |
| void StreamReaderURLLoader::OnReaderReadCompleted(int bytes_read) { | |
| DCHECK(thread_checker_.CalledOnValidThread()); | |
| DCHECK(pending_buffer_); | |
| if (bytes_read < 0) { | |
| // Error case. | |
| RequestComplete(bytes_read); | |
| return; | |
| } | |
| if (bytes_read == 0) { | |
| // Eof, read completed. | |
| pending_buffer_->Complete(0); | |
| RequestComplete(net::OK); | |
| return; | |
| } | |
| producer_handle_ = pending_buffer_->Complete(bytes_read); | |
| pending_buffer_ = nullptr; | |
| client_->OnTransferSizeUpdated(bytes_read); | |
| total_bytes_read_ += bytes_read; | |
| base::ThreadTaskRunnerHandle::Get()->PostTask( | |
| FROM_HERE, base::BindOnce(&StreamReaderURLLoader::ReadMore, | |
| weak_factory_.GetWeakPtr())); | |
| } | |
| void StreamReaderURLLoader::RequestComplete(int status_code) { | |
| DCHECK(thread_checker_.CalledOnValidThread()); | |
| auto status = network::URLLoaderCompletionStatus(status_code); | |
| status.completion_time = base::TimeTicks::Now(); | |
| status.encoded_data_length = total_bytes_read_ + header_length_; | |
| status.encoded_body_length = total_bytes_read_; | |
| // We don't support decoders, so use the same value. | |
| status.decoded_body_length = total_bytes_read_; | |
| client_->OnComplete(status); | |
| CleanUp(); | |
| } | |
| void StreamReaderURLLoader::CleanUp() { | |
| DCHECK(thread_checker_.CalledOnValidThread()); | |
| // Resets the watchers and pipes, so that we will never be called back. | |
| writable_handle_watcher_.Cancel(); | |
| pending_buffer_ = nullptr; | |
| producer_handle_.reset(); | |
| // Manages its own lifetime. | |
| delete this; | |
| } | |
| bool StreamReaderURLLoader::ParseRange(const net::HttpRequestHeaders& headers) { | |
| DCHECK(thread_checker_.CalledOnValidThread()); | |
| std::string range_header; | |
| if (headers.GetHeader(net::HttpRequestHeaders::kRange, &range_header)) { | |
| // This loader only cares about the Range header so that we know how many | |
| // bytes in the stream to skip and how many to read after that. | |
| std::vector<net::HttpByteRange> ranges; | |
| if (net::HttpUtil::ParseRangeHeader(range_header, &ranges)) { | |
| // In case of multi-range request only use the first range. | |
| // We don't support multirange requests. | |
| if (ranges.size() == 1) | |
| byte_range_ = ranges[0]; | |
| } else { | |
| // This happens if the range header could not be parsed or is invalid. | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| bool StreamReaderURLLoader::byte_range_valid() const { | |
| return byte_range_.IsValid() && byte_range_.first_byte_position() >= 0; | |
| } | |
| } // namespace net_service |