| // Copyright 2014 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 "mojo/services/network/url_loader_impl.h" |
| |
| #include <memory> |
| |
| #include "base/memory/scoped_vector.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/trace_event/trace_event.h" |
| #include "mojo/common/common_type_converters.h" |
| #include "mojo/common/url_type_converters.h" |
| #include "mojo/services/network/net_adapters.h" |
| #include "mojo/services/network/network_context.h" |
| #include "net/base/elements_upload_data_stream.h" |
| #include "net/base/io_buffer.h" |
| #include "net/base/load_flags.h" |
| #include "net/base/upload_bytes_element_reader.h" |
| #include "net/http/http_response_headers.h" |
| #include "net/url_request/redirect_info.h" |
| #include "net/url_request/url_request_context.h" |
| |
| namespace mojo { |
| namespace { |
| |
| // Generates an URLResponsePtr from the response state of a net::URLRequest. |
| URLResponsePtr MakeURLResponse(const net::URLRequest* url_request) { |
| URLResponsePtr response(URLResponse::New()); |
| response->url = String::From(url_request->url()); |
| |
| const net::HttpResponseHeaders* headers = url_request->response_headers(); |
| if (headers) { |
| response->status_code = headers->response_code(); |
| response->status_line = headers->GetStatusLine(); |
| |
| response->headers = Array<HttpHeaderPtr>::New(0); |
| std::vector<String> header_lines; |
| void* iter = nullptr; |
| std::string name, value; |
| while (headers->EnumerateHeaderLines(&iter, &name, &value)) { |
| HttpHeaderPtr header = HttpHeader::New(); |
| header->name = name; |
| header->value = value; |
| response->headers.push_back(header.Pass()); |
| } |
| } |
| |
| std::string mime_type; |
| url_request->GetMimeType(&mime_type); |
| response->mime_type = mime_type; |
| |
| std::string charset; |
| url_request->GetCharset(&charset); |
| response->charset = charset; |
| |
| return response.Pass(); |
| } |
| |
| void RunCallbackAndTrace(const char* event_name, |
| void* id, |
| const Callback<void(URLResponsePtr)>& callback, |
| URLResponsePtr response) { |
| callback.Run(response.Pass()); |
| TRACE_EVENT_ASYNC_END0("net", event_name, id); |
| } |
| |
| // Reads the request body upload data from a DataPipe. |
| class UploadDataPipeElementReader : public net::UploadElementReader { |
| public: |
| UploadDataPipeElementReader(ScopedDataPipeConsumerHandle pipe) |
| : pipe_(pipe.Pass()), num_bytes_(0) {} |
| ~UploadDataPipeElementReader() override {} |
| |
| // UploadElementReader overrides: |
| int Init(const net::CompletionCallback& callback) override { |
| offset_ = 0; |
| ReadDataRaw(pipe_.get(), nullptr, &num_bytes_, MOJO_READ_DATA_FLAG_QUERY); |
| return net::OK; |
| } |
| uint64 GetContentLength() const override { return num_bytes_; } |
| uint64 BytesRemaining() const override { return num_bytes_ - offset_; } |
| bool IsInMemory() const override { return false; } |
| int Read(net::IOBuffer* buf, |
| int buf_length, |
| const net::CompletionCallback& callback) override { |
| uint32_t bytes_read = std::min(static_cast<uint32_t>(BytesRemaining()), |
| static_cast<uint32_t>(buf_length)); |
| if (bytes_read > 0) { |
| ReadDataRaw(pipe_.get(), buf->data(), &bytes_read, |
| MOJO_READ_DATA_FLAG_NONE); |
| } |
| |
| offset_ += bytes_read; |
| return bytes_read; |
| } |
| |
| private: |
| ScopedDataPipeConsumerHandle pipe_; |
| uint32_t num_bytes_; |
| uint32_t offset_; |
| |
| DISALLOW_COPY_AND_ASSIGN(UploadDataPipeElementReader); |
| }; |
| |
| bool IsValidCacheMode(URLRequest::CacheMode cache_mode) { |
| switch (cache_mode) { |
| case URLRequest::CacheMode::DEFAULT: |
| case URLRequest::CacheMode::BYPASS_CACHE: |
| case URLRequest::CacheMode::ONLY_FROM_CACHE: |
| return true; |
| } |
| return false; |
| } |
| |
| } // namespace |
| |
| // Each body fetcher takes ownership of a net::URLRequest and stream its data |
| // to a data pipe. It is owned by an URLLoaderImpl and will notify its owner |
| // when either the data pipe is closed or the request is finished. |
| class URLLoaderImpl::BodyFetcher : public net::URLRequest::Delegate { |
| public: |
| BodyFetcher(URLLoaderImpl* loader, |
| uint32_t id, |
| scoped_ptr<net::URLRequest> url_request, |
| ScopedDataPipeProducerHandle response_body_stream) |
| : loader_(loader), |
| id_(id), |
| url_request_(url_request.Pass()), |
| response_body_stream_(response_body_stream.Pass()), |
| weak_ptr_factory_(this) { |
| url_request_->set_delegate(this); |
| ListenForPeerClosed(); |
| } |
| |
| void Start() { |
| ReadMore(); |
| } |
| |
| uint32_t id() { return id_; } |
| |
| URLLoaderStatusPtr QueryStatus() { |
| URLLoaderStatusPtr status(URLLoaderStatus::New()); |
| if (url_request_) { |
| status->is_loading = url_request_->is_pending(); |
| if (!url_request_->status().is_success()) |
| status->error = MakeNetworkError(url_request_->status().error()); |
| } else { |
| status->is_loading = false; |
| } |
| return status.Pass(); |
| } |
| |
| private: |
| // net::URLRequest::Delegate methods: |
| void OnReceivedRedirect(net::URLRequest* url_request, |
| const net::RedirectInfo& redirect_info, |
| bool* defer_redirect) override { |
| // This should already have been called. |
| DCHECK(false); |
| } |
| |
| void OnResponseStarted(net::URLRequest* url_request) override { |
| // This should already have been called. |
| DCHECK(false); |
| } |
| |
| void OnReadCompleted(net::URLRequest* url_request, int bytes_read) override { |
| DCHECK(url_request == url_request_.get()); |
| |
| if (url_request->status().is_success()) { |
| DidRead(static_cast<uint32_t>(bytes_read), false); |
| } else { |
| handle_watcher_.Stop(); |
| pending_write_ = nullptr; // This closes the data pipe. |
| DeleteIfNeeded(); |
| return; |
| } |
| } |
| |
| void OnResponseBodyStreamReady(MojoResult result) { |
| // TODO(darin): Handle a bad |result| value. |
| |
| // Continue watching the handle in case the peer is closed. |
| ListenForPeerClosed(); |
| ReadMore(); |
| } |
| |
| void OnResponseBodyStreamClosed(MojoResult result) { |
| url_request_.reset(); |
| response_body_stream_.reset(); |
| pending_write_ = nullptr; |
| DeleteIfNeeded(); |
| } |
| |
| void ReadMore() { |
| DCHECK(!pending_write_.get()); |
| |
| uint32_t num_bytes; |
| MojoResult result = NetToMojoPendingBuffer::BeginWrite( |
| &response_body_stream_, &pending_write_, &num_bytes); |
| |
| if (result == MOJO_RESULT_SHOULD_WAIT) { |
| // The pipe is full. We need to wait for it to have more space. |
| handle_watcher_.Start( |
| response_body_stream_.get(), MOJO_HANDLE_SIGNAL_WRITABLE, |
| MOJO_DEADLINE_INDEFINITE, |
| base::Bind(&URLLoaderImpl::BodyFetcher::OnResponseBodyStreamReady, |
| base::Unretained(this))); |
| return; |
| } else if (result != MOJO_RESULT_OK) { |
| // The response body stream is in a bad state. Bail. |
| // TODO(darin): How should this be communicated to our client? |
| handle_watcher_.Stop(); |
| response_body_stream_.reset(); |
| DeleteIfNeeded(); |
| return; |
| } |
| CHECK_GT(static_cast<uint32_t>(std::numeric_limits<int>::max()), num_bytes); |
| |
| scoped_refptr<net::IOBuffer> buf( |
| new NetToMojoIOBuffer(pending_write_.get())); |
| |
| int bytes_read; |
| url_request_->Read(buf.get(), static_cast<int>(num_bytes), &bytes_read); |
| if (url_request_->status().is_io_pending()) { |
| // Wait for OnReadCompleted. |
| } else if (url_request_->status().is_success() && bytes_read > 0) { |
| DidRead(static_cast<uint32_t>(bytes_read), true); |
| } else { |
| handle_watcher_.Stop(); |
| pending_write_->Complete(0); |
| pending_write_ = nullptr; // This closes the data pipe. |
| DeleteIfNeeded(); |
| return; |
| } |
| } |
| |
| void DidRead(uint32_t num_bytes, bool completed_synchronously) { |
| DCHECK(url_request_->status().is_success()); |
| |
| response_body_stream_ = pending_write_->Complete(num_bytes); |
| pending_write_ = nullptr; |
| |
| if (completed_synchronously) { |
| base::MessageLoop::current()->PostTask( |
| FROM_HERE, base::Bind(&URLLoaderImpl::BodyFetcher::ReadMore, |
| weak_ptr_factory_.GetWeakPtr())); |
| } else { |
| ReadMore(); |
| } |
| } |
| |
| void DeleteIfNeeded() { |
| bool has_data_pipe = |
| pending_write_.get() || response_body_stream_.is_valid(); |
| if (!has_data_pipe) { |
| loader_->OnBodyFetcherDone(this); |
| // The callback deleted this object. |
| } |
| } |
| |
| void ListenForPeerClosed() { |
| handle_watcher_.Start( |
| response_body_stream_.get(), MOJO_HANDLE_SIGNAL_PEER_CLOSED, |
| MOJO_DEADLINE_INDEFINITE, |
| base::Bind(&URLLoaderImpl::BodyFetcher::OnResponseBodyStreamClosed, |
| base::Unretained(this))); |
| } |
| |
| URLLoaderImpl* loader_; |
| uint32_t id_; |
| scoped_ptr<net::URLRequest> url_request_; |
| ScopedDataPipeProducerHandle response_body_stream_; |
| scoped_refptr<NetToMojoPendingBuffer> pending_write_; |
| common::HandleWatcher handle_watcher_; |
| base::WeakPtrFactory<URLLoaderImpl::BodyFetcher> weak_ptr_factory_; |
| }; |
| |
| URLLoaderImpl::URLLoaderImpl(NetworkContext* context, |
| InterfaceRequest<URLLoader> request, |
| std::vector<URLLoaderInterceptorPtr> interceptors) |
| : context_(context), |
| interceptors_(std::move(interceptors)), |
| response_body_buffer_size_(0), |
| auto_follow_redirects_(true), |
| current_fetcher_id_(0), |
| binding_(this, request.Pass()) { |
| for (auto& interceptor : interceptors_) { |
| interceptor.set_connection_error_handler([this]() { delete this; }); |
| } |
| interceptor_index_ = interceptors_.size() - 1; |
| binding_.set_connection_error_handler([this]() { OnConnectionError(); }); |
| context_->RegisterURLLoader(this); |
| } |
| |
| URLLoaderImpl::~URLLoaderImpl() { |
| context_->DeregisterURLLoader(this); |
| } |
| |
| void URLLoaderImpl::Cleanup() { |
| // The associated network context is going away and we have to destroy |
| // net::URLRequest hold by this loader. |
| delete this; |
| } |
| |
| void URLLoaderImpl::Start(URLRequestPtr request, |
| const Callback<void(URLResponsePtr)>& callback) { |
| TRACE_EVENT_ASYNC_BEGIN1("net", "URLLoaderImpl::Start", this, "url", |
| request->url.get()); |
| Callback<void(URLResponsePtr)> traced_callback = |
| base::Bind(&RunCallbackAndTrace, "URLLoaderImpl::Start", this, callback); |
| |
| if (url_request_) { |
| SendError(net::ERR_UNEXPECTED, traced_callback); |
| return; |
| } |
| |
| callback_ = traced_callback; |
| StartInternal(request.Pass()); |
| } |
| |
| void URLLoaderImpl::FollowRedirect( |
| const Callback<void(URLResponsePtr)>& callback) { |
| TRACE_EVENT_ASYNC_BEGIN0("net", "URLLoaderImpl::FollowRedirect", this); |
| Callback<void(URLResponsePtr)> traced_callback = base::Bind( |
| &RunCallbackAndTrace, "URLLoaderImpl::FollowRedirect", this, callback); |
| |
| if (!redirect_info_) { |
| DLOG(ERROR) << "Spurious call to FollowRedirect"; |
| SendError(net::ERR_UNEXPECTED, traced_callback); |
| return; |
| } |
| DCHECK(url_request_); |
| DCHECK(url_request_->is_redirecting()); |
| DCHECK(!auto_follow_redirects_); |
| |
| callback_ = traced_callback; |
| FollowRedirectInternal(); |
| } |
| |
| void URLLoaderImpl::QueryStatus( |
| const Callback<void(URLLoaderStatusPtr)>& callback) { |
| URLLoaderStatusPtr status(URLLoaderStatus::New()); |
| if (url_request_) { |
| // A url request is owned by this class, the status is deduced from it. |
| status->is_loading = url_request_->is_pending(); |
| if (!url_request_->status().is_success()) |
| status->error = MakeNetworkError(url_request_->status().error()); |
| } else { |
| if (!body_fetchers_.empty()) { |
| // At least one body fetcher is active, the status is deduced from it. |
| status = body_fetchers_.back()->QueryStatus(); |
| } else if (last_status_) { |
| // At least one body fetcher has been started and finished, the status is |
| // deduced from its status when it finished. |
| status = last_status_.Clone(); |
| } else { |
| // No network request has been made yet. |
| status->is_loading = false; |
| } |
| } |
| // TODO(darin): Populate more status fields. |
| callback.Run(status.Pass()); |
| } |
| |
| void URLLoaderImpl::OnReceivedRedirect(net::URLRequest* url_request, |
| const net::RedirectInfo& redirect_info, |
| bool* defer_redirect) { |
| DCHECK(url_request == url_request_.get()); |
| DCHECK(url_request->status().is_success()); |
| DCHECK(!redirect_info_); |
| |
| if (auto_follow_redirects_) |
| return; |
| |
| // Send the redirect response to the client, allowing them to inspect it and |
| // optionally follow the redirect. |
| *defer_redirect = true; |
| |
| redirect_info_.reset(new net::RedirectInfo(redirect_info)); |
| URLResponsePtr response = MakeURLResponse(url_request); |
| response->redirect_method = redirect_info.new_method; |
| response->redirect_url = String::From(redirect_info.new_url); |
| response->redirect_referrer = redirect_info.new_referrer; |
| |
| SendResponse(response.Pass()); |
| } |
| |
| void URLLoaderImpl::OnResponseStarted(net::URLRequest* url_request) { |
| DCHECK(url_request == url_request_.get()); |
| DCHECK(!redirect_info_); |
| |
| if (!url_request->status().is_success()) { |
| SendError(url_request->status().error(), callback_); |
| callback_ = Callback<void(URLResponsePtr)>(); |
| return; |
| } |
| |
| // TODO(darin): Add support for optional MIME sniffing. |
| |
| DataPipe data_pipe; |
| // TODO(darin): Honor given buffer size. |
| |
| URLResponsePtr response = MakeURLResponse(url_request); |
| response->body = data_pipe.consumer_handle.Pass(); |
| |
| // Build the body fetcher. |
| std::unique_ptr<BodyFetcher> body_fetcher( |
| new BodyFetcher(this, ++current_fetcher_id_, url_request_.Pass(), |
| data_pipe.producer_handle.Pass())); |
| |
| SendResponse(response.Pass()); |
| body_fetchers_.push_back(std::move(body_fetcher)); |
| body_fetchers_.back()->Start(); |
| } |
| |
| void URLLoaderImpl::OnReadCompleted(net::URLRequest* url_request, |
| int bytes_read) { |
| // This should never be called on this object. |
| DCHECK(false); |
| } |
| |
| void URLLoaderImpl::OnConnectionError() { |
| binding_.Close(); |
| if (body_fetchers_.empty()) |
| delete this; |
| } |
| |
| void URLLoaderImpl::SendError( |
| int error_code, |
| const Callback<void(URLResponsePtr)>& callback) { |
| URLResponsePtr response(URLResponse::New()); |
| if (url_request_) |
| response->url = String::From(url_request_->url()); |
| response->error = MakeNetworkError(error_code); |
| callback.Run(response.Pass()); |
| } |
| |
| void URLLoaderImpl::OnIntercept( |
| URLLoaderInterceptorResponsePtr interceptor_response) { |
| if (interceptor_response && interceptor_response->request && |
| interceptor_response->response) { |
| SendError(net::ERR_INVALID_ARGUMENT, callback_); |
| return; |
| } |
| |
| if (!interceptor_response) { |
| // Interceptor can follow redirect only if a redirect is in progress. |
| if (!redirect_info_) { |
| SendError(net::ERR_INVALID_ARGUMENT, callback_); |
| return; |
| } |
| // Request from the user to the the loader are intercepted from the newest |
| // to the oldest interceptor. |
| interceptor_index_--; |
| FollowRedirectInternal(); |
| return; |
| } |
| |
| // The interceptor returned a request. |
| if (interceptor_response->request) { |
| url_request_.reset(); |
| redirect_info_.reset(); |
| // Request from the user to the the loader are intercepted from the newest |
| // to the oldest interceptor. |
| interceptor_index_--; |
| StartInternal(interceptor_response->request.Pass()); |
| return; |
| } |
| |
| // The interceptor returned a response. |
| // Check that if the response is a redirect, it didn't change the redirect |
| // info. |
| // TODO(qsr) Handles this case. The problem is that if the interceptor changed |
| // the redirect info and then later on the client send a follow redirect, this |
| // class will just transmit the follow redirect to the underlying url_fetcher |
| // that will just redirect to the initial URL. To fix this, this class will |
| // need to detect this and recreate a new url fetcher with the correct |
| // request. |
| if (interceptor_response->response->redirect_method) { |
| if (!redirect_info_ || |
| interceptor_response->response->redirect_method != |
| redirect_info_->new_method || |
| interceptor_response->response->redirect_url != |
| String::From(redirect_info_->new_url) || |
| interceptor_response->response->redirect_referrer != |
| redirect_info_->new_referrer) { |
| LOG(WARNING) << "Interceptor returned a response for a redirect."; |
| SendError(net::ERR_INVALID_ARGUMENT, callback_); |
| return; |
| } |
| } else { |
| url_request_.reset(); |
| redirect_info_.reset(); |
| } |
| // Response from the loader to the user are intercepted from the oldest to the |
| // newest interceptor. |
| interceptor_index_++; |
| SendResponse(interceptor_response->response.Pass()); |
| } |
| |
| void URLLoaderImpl::FollowRedirectInternal() { |
| DCHECK(url_request_); |
| DCHECK(url_request_->is_redirecting()); |
| DCHECK(redirect_info_); |
| |
| if (interceptor_index_ >= 0 && |
| interceptor_index_ < static_cast<int>(interceptors_.size())) { |
| interceptors_[interceptor_index_]->InterceptFollowRedirect(base::Bind( |
| &URLLoaderImpl::OnIntercept, base::Unretained(this))); |
| return; |
| } |
| |
| DCHECK(interceptor_index_ == -1); |
| interceptor_index_ = 0; |
| redirect_info_.reset(); |
| // TODO(darin): Verify that it makes sense to call FollowDeferredRedirect. |
| url_request_->FollowDeferredRedirect(); |
| } |
| |
| void URLLoaderImpl::StartInternal(URLRequestPtr request) { |
| DCHECK(!url_request_); |
| DCHECK(!redirect_info_); |
| |
| if (!IsValidCacheMode(request->cache_mode)) { |
| // Unknown cache mode. The request must fail. |
| delete this; |
| return; |
| } |
| |
| if (interceptor_index_ >= 0 && |
| interceptor_index_ < static_cast<int>(interceptors_.size())) { |
| interceptors_[interceptor_index_]->InterceptRequest( |
| request.Pass(), base::Bind(&URLLoaderImpl::OnIntercept, |
| base::Unretained(this))); |
| return; |
| } |
| |
| DCHECK(interceptor_index_ == -1); |
| interceptor_index_ = 0; |
| // Start the network request. |
| url_request_ = context_->url_request_context()->CreateRequest( |
| GURL(request->url), net::DEFAULT_PRIORITY, this); |
| url_request_->set_method(request->method); |
| // TODO(jam): need to specify this policy. |
| url_request_->set_referrer_policy( |
| net::URLRequest::CLEAR_REFERRER_ON_TRANSITION_FROM_SECURE_TO_INSECURE); |
| if (request->headers) { |
| net::HttpRequestHeaders headers; |
| for (size_t i = 0; i < request->headers.size(); ++i) { |
| base::StringPiece header = |
| request->headers[i]->name.To<base::StringPiece>(); |
| base::StringPiece value = |
| request->headers[i]->value.To<base::StringPiece>(); |
| if (header == net::HttpRequestHeaders::kReferer) { |
| url_request_->SetReferrer(value.as_string()); |
| } else { |
| headers.SetHeader(header, value); |
| } |
| } |
| url_request_->SetExtraRequestHeaders(headers); |
| } |
| if (request->body) { |
| ScopedVector<net::UploadElementReader> element_readers; |
| for (size_t i = 0; i < request->body.size(); ++i) { |
| element_readers.push_back( |
| new UploadDataPipeElementReader(request->body[i].Pass())); |
| } |
| url_request_->set_upload(make_scoped_ptr<net::UploadDataStream>( |
| new net::ElementsUploadDataStream(element_readers.Pass(), 0))); |
| } |
| int load_flags = 0; |
| switch (request->cache_mode) { |
| case URLRequest::CacheMode::DEFAULT: |
| break; |
| case URLRequest::CacheMode::BYPASS_CACHE: |
| load_flags |= net::LOAD_BYPASS_CACHE; |
| break; |
| case URLRequest::CacheMode::ONLY_FROM_CACHE: |
| load_flags |= net::LOAD_ONLY_FROM_CACHE; |
| break; |
| } |
| if (load_flags) |
| url_request_->SetLoadFlags(load_flags); |
| |
| response_body_buffer_size_ = request->response_body_buffer_size; |
| auto_follow_redirects_ = request->auto_follow_redirects; |
| |
| url_request_->Start(); |
| } |
| |
| void URLLoaderImpl::SendResponse(URLResponsePtr response) { |
| if (interceptor_index_ >= 0 && |
| interceptor_index_ < static_cast<int>(interceptors_.size())) { |
| interceptors_[interceptor_index_]->InterceptResponse( |
| response.Pass(), |
| base::Bind(&URLLoaderImpl::OnIntercept, base::Unretained(this))); |
| return; |
| } |
| |
| DCHECK(interceptor_index_ == static_cast<int>(interceptors_.size())); |
| interceptor_index_ = interceptors_.size() - 1; |
| Callback<void(URLResponsePtr)> callback; |
| std::swap(callback_, callback); |
| callback.Run(response.Pass()); |
| } |
| |
| void URLLoaderImpl::OnBodyFetcherDone(BodyFetcher* fetcher) { |
| auto it = std::find_if(body_fetchers_.begin(), body_fetchers_.end(), |
| [fetcher](const std::unique_ptr<BodyFetcher>& f) { |
| return fetcher == f.get(); |
| }); |
| DCHECK(it != body_fetchers_.end()); |
| // This is the current fetcher. Capture the status. |
| if ((*it)->id() == current_fetcher_id_) { |
| last_status_ = fetcher->QueryStatus(); |
| last_status_->is_loading = false; |
| } |
| body_fetchers_.erase(it); |
| if (body_fetchers_.empty() and !binding_.is_bound()) |
| delete this; |
| } |
| |
| } // namespace mojo |