| // 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/proxy_url_loader_factory.h" |
| |
| #include "libcef/browser/context.h" |
| #include "libcef/browser/thread_util.h" |
| #include "libcef/common/net_service/net_service_util.h" |
| |
| #include "base/barrier_closure.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "components/safe_browsing/core/common/safebrowsing_constants.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/resource_context.h" |
| #include "content/public/browser/web_contents.h" |
| #include "mojo/public/cpp/base/big_buffer.h" |
| #include "net/http/http_status_code.h" |
| #include "services/network/public/cpp/cors/cors.h" |
| |
| namespace net_service { |
| |
| namespace { |
| |
| // User data key for ResourceContextData. |
| const void* const kResourceContextUserDataKey = &kResourceContextUserDataKey; |
| |
| } // namespace |
| |
| // Owns all of the ProxyURLLoaderFactorys for a given BrowserContext. Since |
| // these live on the IO thread this is done indirectly through the |
| // ResourceContext. |
| class ResourceContextData : public base::SupportsUserData::Data { |
| public: |
| ~ResourceContextData() override {} |
| |
| static void AddProxyOnUIThread( |
| ProxyURLLoaderFactory* proxy, |
| content::WebContents::Getter web_contents_getter) { |
| CEF_REQUIRE_UIT(); |
| |
| content::WebContents* web_contents = web_contents_getter.Run(); |
| |
| // Maybe the browser was destroyed while AddProxyOnUIThread was pending. |
| if (!web_contents) { |
| // Delete on the IO thread as expected by mojo bindings. |
| content::BrowserThread::DeleteSoon(content::BrowserThread::IO, FROM_HERE, |
| proxy); |
| return; |
| } |
| |
| content::BrowserContext* browser_context = |
| web_contents->GetBrowserContext(); |
| DCHECK(browser_context); |
| |
| content::ResourceContext* resource_context = |
| browser_context->GetResourceContext(); |
| DCHECK(resource_context); |
| |
| CEF_POST_TASK(CEF_IOT, base::BindOnce(ResourceContextData::AddProxy, |
| base::Unretained(proxy), |
| base::Unretained(resource_context))); |
| } |
| |
| static void AddProxy(ProxyURLLoaderFactory* proxy, |
| content::ResourceContext* resource_context) { |
| CEF_REQUIRE_IOT(); |
| |
| // Maybe the proxy was destroyed while AddProxyOnUIThread was pending. |
| if (proxy->destroyed_) { |
| delete proxy; |
| return; |
| } |
| |
| auto* self = static_cast<ResourceContextData*>( |
| resource_context->GetUserData(kResourceContextUserDataKey)); |
| if (!self) { |
| self = new ResourceContextData(); |
| resource_context->SetUserData(kResourceContextUserDataKey, |
| base::WrapUnique(self)); |
| } |
| |
| proxy->SetDisconnectCallback(base::BindOnce( |
| &ResourceContextData::RemoveProxy, self->weak_factory_.GetWeakPtr())); |
| self->proxies_.emplace(base::WrapUnique(proxy)); |
| } |
| |
| private: |
| void RemoveProxy(ProxyURLLoaderFactory* proxy) { |
| CEF_REQUIRE_IOT(); |
| |
| auto it = proxies_.find(proxy); |
| DCHECK(it != proxies_.end()); |
| proxies_.erase(it); |
| } |
| |
| ResourceContextData() : weak_factory_(this) {} |
| |
| std::set<std::unique_ptr<ProxyURLLoaderFactory>, base::UniquePtrComparator> |
| proxies_; |
| |
| base::WeakPtrFactory<ResourceContextData> weak_factory_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ResourceContextData); |
| }; |
| |
| //============================== |
| // InterceptedRequest |
| //============================= |
| |
| // Handles intercepted, in-progress requests/responses, so that they can be |
| // controlled and modified accordingly. |
| class InterceptedRequest : public network::mojom::URLLoader, |
| public network::mojom::URLLoaderClient, |
| public network::mojom::TrustedHeaderClient { |
| public: |
| InterceptedRequest( |
| ProxyURLLoaderFactory* factory, |
| RequestId id, |
| uint32_t options, |
| const network::ResourceRequest& request, |
| const net::MutableNetworkTrafficAnnotationTag& traffic_annotation, |
| mojo::PendingReceiver<network::mojom::URLLoader> loader_receiver, |
| mojo::PendingRemote<network::mojom::URLLoaderClient> client, |
| mojo::PendingRemote<network::mojom::URLLoaderFactory> target_factory); |
| ~InterceptedRequest() override; |
| |
| // Restart the request. This happens on initial start and after redirect. |
| void Restart(); |
| |
| // Called from ProxyURLLoaderFactory::OnLoaderCreated. |
| void OnLoaderCreated( |
| mojo::PendingReceiver<network::mojom::TrustedHeaderClient> receiver); |
| |
| // Called from InterceptDelegate::OnInputStreamOpenFailed. |
| void InputStreamFailed(bool restart_needed); |
| |
| // mojom::TrustedHeaderClient methods: |
| void OnBeforeSendHeaders(const net::HttpRequestHeaders& headers, |
| OnBeforeSendHeadersCallback callback) override; |
| void OnHeadersReceived(const std::string& headers, |
| const net::IPEndPoint& remote_endpoint, |
| OnHeadersReceivedCallback callback) override; |
| |
| // mojom::URLLoaderClient methods: |
| void OnReceiveResponse(network::mojom::URLResponseHeadPtr head) override; |
| void OnReceiveRedirect(const net::RedirectInfo& redirect_info, |
| network::mojom::URLResponseHeadPtr head) override; |
| void OnUploadProgress(int64_t current_position, |
| int64_t total_size, |
| OnUploadProgressCallback callback) override; |
| void OnReceiveCachedMetadata(mojo_base::BigBuffer data) override; |
| void OnTransferSizeUpdated(int32_t transfer_size_diff) override; |
| void OnStartLoadingResponseBody( |
| mojo::ScopedDataPipeConsumerHandle body) override; |
| void OnComplete(const network::URLLoaderCompletionStatus& status) override; |
| |
| // mojom::URLLoader methods: |
| void FollowRedirect(const std::vector<std::string>& removed_headers, |
| const net::HttpRequestHeaders& modified_headers, |
| const base::Optional<GURL>& new_url) override; |
| void SetPriority(net::RequestPriority priority, |
| int32_t intra_priority_value) override; |
| void PauseReadingBodyFromNet() override; |
| void ResumeReadingBodyFromNet() override; |
| |
| const RequestId id() const { return id_; } |
| |
| private: |
| // Helpers for determining the request handler. |
| void BeforeRequestReceived(const GURL& original_url, |
| bool intercept_request, |
| bool intercept_only); |
| void InterceptResponseReceived(const GURL& original_url, |
| std::unique_ptr<ResourceResponse> response); |
| void ContinueAfterIntercept(); |
| void ContinueAfterInterceptWithOverride( |
| std::unique_ptr<ResourceResponse> response); |
| |
| // Helpers for optionally overriding headers. |
| void HandleResponseOrRedirectHeaders( |
| base::Optional<net::RedirectInfo> redirect_info, |
| net::CompletionOnceCallback continuation); |
| void ContinueResponseOrRedirect( |
| net::CompletionOnceCallback continuation, |
| InterceptedRequestHandler::ResponseMode response_mode, |
| scoped_refptr<net::HttpResponseHeaders> override_headers, |
| const GURL& redirect_url); |
| void ContinueToHandleOverrideHeaders(int error_code); |
| net::RedirectInfo MakeRedirectResponseAndInfo(const GURL& new_location); |
| |
| // Helpers for redirect handling. |
| void ContinueToBeforeRedirect(const net::RedirectInfo& redirect_info, |
| int error_code); |
| |
| // Helpers for response handling. |
| void ContinueToResponseStarted(int error_code); |
| |
| void OnDestroy(); |
| |
| void OnProcessRequestHeaders(const GURL& redirect_url, |
| net::HttpRequestHeaders* modified_headers, |
| std::vector<std::string>* removed_headers); |
| |
| // This is called when the original URLLoaderClient has a connection error. |
| void OnURLLoaderClientError(); |
| |
| // This is called when the original URLLoader has a connection error. |
| void OnURLLoaderError(uint32_t custom_reason, const std::string& description); |
| |
| // Call OnComplete on |target_client_|. If |wait_for_loader_error| is true |
| // then this object will wait for |proxied_loader_binding_| to have a |
| // connection error before destructing. |
| void CallOnComplete(const network::URLLoaderCompletionStatus& status, |
| bool wait_for_loader_error); |
| |
| void SendErrorAndCompleteImmediately(int error_code); |
| |
| void SendErrorCallback(int error_code, bool safebrowsing_hit); |
| |
| void OnUploadProgressACK(); |
| |
| ProxyURLLoaderFactory* const factory_; |
| const RequestId id_; |
| const uint32_t options_; |
| bool input_stream_previously_failed_ = false; |
| bool request_was_redirected_ = false; |
| |
| // To avoid sending multiple OnReceivedError callbacks. |
| bool sent_error_callback_ = false; |
| |
| // When true, the loader will provide the option to intercept the request. |
| bool intercept_request_ = true; |
| |
| // When true, the loader will not proceed unless the intercept request |
| // callback provided a non-null response. |
| bool intercept_only_ = false; |
| |
| network::URLLoaderCompletionStatus status_; |
| bool got_loader_error_ = false; |
| |
| // Used for rate limiting OnUploadProgress callbacks. |
| bool waiting_for_upload_progress_ack_ = false; |
| |
| network::ResourceRequest request_; |
| network::mojom::URLResponseHeadPtr current_response_; |
| scoped_refptr<net::HttpResponseHeaders> current_headers_; |
| scoped_refptr<net::HttpResponseHeaders> override_headers_; |
| GURL original_url_; |
| GURL redirect_url_; |
| GURL header_client_redirect_url_; |
| const net::MutableNetworkTrafficAnnotationTag traffic_annotation_; |
| |
| mojo::Binding<network::mojom::URLLoader> proxied_loader_binding_; |
| network::mojom::URLLoaderClientPtr target_client_; |
| |
| mojo::Binding<network::mojom::URLLoaderClient> proxied_client_binding_; |
| network::mojom::URLLoaderPtr target_loader_; |
| network::mojom::URLLoaderFactoryPtr target_factory_; |
| |
| bool current_request_uses_header_client_ = false; |
| OnHeadersReceivedCallback on_headers_received_callback_; |
| mojo::Receiver<network::mojom::TrustedHeaderClient> header_client_receiver_{ |
| this}; |
| |
| StreamReaderURLLoader* stream_loader_ = nullptr; |
| |
| base::WeakPtrFactory<InterceptedRequest> weak_factory_; |
| |
| DISALLOW_COPY_AND_ASSIGN(InterceptedRequest); |
| }; |
| |
| class InterceptDelegate : public StreamReaderURLLoader::Delegate { |
| public: |
| explicit InterceptDelegate(std::unique_ptr<ResourceResponse> response, |
| base::WeakPtr<InterceptedRequest> request) |
| : response_(std::move(response)), request_(request) {} |
| |
| bool OpenInputStream(const RequestId& request_id, |
| const network::ResourceRequest& request, |
| OpenCallback callback) override { |
| return response_->OpenInputStream(request_id, request, std::move(callback)); |
| } |
| |
| void OnInputStreamOpenFailed(const RequestId& request_id, |
| bool* restarted) override { |
| request_->InputStreamFailed(false /* restart_needed */); |
| *restarted = false; |
| } |
| |
| void GetResponseHeaders(const RequestId& request_id, |
| int* status_code, |
| std::string* reason_phrase, |
| std::string* mime_type, |
| std::string* charset, |
| int64_t* content_length, |
| HeaderMap* extra_headers) override { |
| response_->GetResponseHeaders(request_id, status_code, reason_phrase, |
| mime_type, charset, content_length, |
| extra_headers); |
| } |
| |
| private: |
| std::unique_ptr<ResourceResponse> response_; |
| base::WeakPtr<InterceptedRequest> request_; |
| }; |
| |
| InterceptedRequest::InterceptedRequest( |
| ProxyURLLoaderFactory* factory, |
| RequestId id, |
| uint32_t options, |
| const network::ResourceRequest& request, |
| const net::MutableNetworkTrafficAnnotationTag& traffic_annotation, |
| mojo::PendingReceiver<network::mojom::URLLoader> loader_receiver, |
| mojo::PendingRemote<network::mojom::URLLoaderClient> client, |
| mojo::PendingRemote<network::mojom::URLLoaderFactory> target_factory) |
| : factory_(factory), |
| id_(id), |
| options_(options), |
| request_(request), |
| traffic_annotation_(traffic_annotation), |
| proxied_loader_binding_(this, std::move(loader_receiver)), |
| target_client_(std::move(client)), |
| proxied_client_binding_(this), |
| target_factory_(std::move(target_factory)), |
| weak_factory_(this) { |
| status_ = network::URLLoaderCompletionStatus(net::OK); |
| |
| net::HttpRequestHeaders modified_headers; |
| std::vector<std::string> removed_headers; |
| OnProcessRequestHeaders(GURL() /* redirect_url */, &modified_headers, |
| &removed_headers); |
| |
| // If there is a client error, clean up the request. |
| target_client_.set_connection_error_handler(base::BindOnce( |
| &InterceptedRequest::OnURLLoaderClientError, base::Unretained(this))); |
| proxied_loader_binding_.set_connection_error_with_reason_handler( |
| base::BindOnce(&InterceptedRequest::OnURLLoaderError, |
| base::Unretained(this))); |
| } |
| |
| InterceptedRequest::~InterceptedRequest() { |
| if (status_.error_code != net::OK) |
| SendErrorCallback(status_.error_code, false); |
| if (on_headers_received_callback_) { |
| std::move(on_headers_received_callback_) |
| .Run(net::ERR_ABORTED, base::nullopt, GURL()); |
| } |
| } |
| |
| void InterceptedRequest::Restart() { |
| stream_loader_ = nullptr; |
| if (proxied_client_binding_.is_bound()) { |
| proxied_client_binding_.Unbind(); |
| target_loader_.reset(); |
| } |
| |
| if (header_client_receiver_.is_bound()) |
| ignore_result(header_client_receiver_.Unbind()); |
| |
| current_request_uses_header_client_ = |
| !!factory_->url_loader_header_client_receiver_; |
| |
| const GURL original_url = request_.url; |
| |
| factory_->request_handler_->OnBeforeRequest( |
| id_, &request_, request_was_redirected_, |
| base::BindOnce(&InterceptedRequest::BeforeRequestReceived, |
| weak_factory_.GetWeakPtr(), original_url), |
| base::BindOnce(&InterceptedRequest::SendErrorAndCompleteImmediately, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void InterceptedRequest::OnLoaderCreated( |
| mojo::PendingReceiver<network::mojom::TrustedHeaderClient> receiver) { |
| DCHECK(current_request_uses_header_client_); |
| |
| // Only called if we're using the default loader. |
| header_client_receiver_.Bind(std::move(receiver)); |
| } |
| |
| void InterceptedRequest::InputStreamFailed(bool restart_needed) { |
| DCHECK(!input_stream_previously_failed_); |
| |
| if (intercept_only_) { |
| // This can happen for unsupported schemes, when no proper |
| // response from the intercept handler is received, i.e. |
| // the provided input stream in response failed to load. In |
| // this case we send and error and stop loading. |
| SendErrorAndCompleteImmediately(net::ERR_UNKNOWN_URL_SCHEME); |
| return; |
| } |
| |
| if (!restart_needed) |
| return; |
| |
| input_stream_previously_failed_ = true; |
| Restart(); |
| } |
| |
| // TrustedHeaderClient methods. |
| |
| void InterceptedRequest::OnBeforeSendHeaders( |
| const net::HttpRequestHeaders& headers, |
| OnBeforeSendHeadersCallback callback) { |
| if (!current_request_uses_header_client_) { |
| std::move(callback).Run(net::OK, base::nullopt); |
| return; |
| } |
| |
| request_.headers = headers; |
| std::move(callback).Run(net::OK, base::nullopt); |
| |
| // Resume handling of client messages after continuing from an async callback. |
| if (proxied_client_binding_) |
| proxied_client_binding_.ResumeIncomingMethodCallProcessing(); |
| } |
| |
| void InterceptedRequest::OnHeadersReceived( |
| const std::string& headers, |
| const net::IPEndPoint& remote_endpoint, |
| OnHeadersReceivedCallback callback) { |
| if (!current_request_uses_header_client_) { |
| std::move(callback).Run(net::OK, base::nullopt, GURL()); |
| return; |
| } |
| |
| current_headers_ = base::MakeRefCounted<net::HttpResponseHeaders>(headers); |
| on_headers_received_callback_ = std::move(callback); |
| |
| base::Optional<net::RedirectInfo> redirect_info; |
| std::string location; |
| if (current_headers_->IsRedirect(&location)) { |
| const GURL new_url = request_.url.Resolve(location); |
| redirect_info = |
| MakeRedirectInfo(request_, current_headers_.get(), new_url, 0); |
| } |
| |
| HandleResponseOrRedirectHeaders( |
| redirect_info, |
| base::BindOnce(&InterceptedRequest::ContinueToHandleOverrideHeaders, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| // URLLoaderClient methods. |
| |
| void InterceptedRequest::OnReceiveResponse( |
| network::mojom::URLResponseHeadPtr head) { |
| current_response_ = std::move(head); |
| |
| if (current_request_uses_header_client_) { |
| // Use the headers we got from OnHeadersReceived as that'll contain |
| // Set-Cookie if it existed. |
| DCHECK(current_headers_); |
| current_response_->headers = current_headers_; |
| current_headers_ = nullptr; |
| ContinueToResponseStarted(net::OK); |
| } else { |
| HandleResponseOrRedirectHeaders( |
| base::nullopt, |
| base::BindOnce(&InterceptedRequest::ContinueToResponseStarted, |
| weak_factory_.GetWeakPtr())); |
| } |
| } |
| |
| void InterceptedRequest::OnReceiveRedirect( |
| const net::RedirectInfo& redirect_info, |
| network::mojom::URLResponseHeadPtr head) { |
| bool needs_callback = false; |
| |
| current_response_ = std::move(head); |
| |
| if (current_request_uses_header_client_) { |
| // Use the headers we got from OnHeadersReceived as that'll contain |
| // Set-Cookie if it existed. May be null for synthetic redirects. |
| if (current_headers_) { |
| current_response_->headers = current_headers_; |
| current_headers_ = nullptr; |
| } |
| } else { |
| needs_callback = true; |
| } |
| |
| net::RedirectInfo new_redirect_info; |
| |
| // When we redirect via ContinueToHandleOverrideHeaders the |redirect_info| |
| // value is sometimes nonsense (HTTP_OK). Also, we won't get another call to |
| // OnHeadersReceived for the new URL so we need to execute the callback here. |
| if (header_client_redirect_url_.is_valid() && |
| redirect_info.status_code == net::HTTP_OK) { |
| DCHECK(current_request_uses_header_client_); |
| needs_callback = true; |
| new_redirect_info = |
| MakeRedirectResponseAndInfo(header_client_redirect_url_); |
| } else { |
| new_redirect_info = redirect_info; |
| } |
| |
| if (needs_callback) { |
| HandleResponseOrRedirectHeaders( |
| new_redirect_info, |
| base::BindOnce(&InterceptedRequest::ContinueToBeforeRedirect, |
| weak_factory_.GetWeakPtr(), new_redirect_info)); |
| } else { |
| ContinueToBeforeRedirect(new_redirect_info, net::OK); |
| } |
| } |
| |
| void InterceptedRequest::OnUploadProgress(int64_t current_position, |
| int64_t total_size, |
| OnUploadProgressCallback callback) { |
| // Implement our own rate limiting for OnUploadProgress calls. |
| if (!waiting_for_upload_progress_ack_) { |
| waiting_for_upload_progress_ack_ = true; |
| target_client_->OnUploadProgress( |
| current_position, total_size, |
| base::BindOnce(&InterceptedRequest::OnUploadProgressACK, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| // Always execute the callback immediately to avoid a race between |
| // URLLoaderClient_OnUploadProgress_ProxyToResponder::Run() (which would |
| // otherwise be blocked on the target client executing the callback) and |
| // CallOnComplete(). If CallOnComplete() is executed first the interface pipe |
| // will be closed and the callback destructor will generate an assertion like: |
| // "URLLoaderClient::OnUploadProgressCallback was destroyed without first |
| // either being run or its corresponding binding being closed. It is an error |
| // to drop response callbacks which still correspond to an open interface |
| // pipe." |
| std::move(callback).Run(); |
| } |
| |
| void InterceptedRequest::OnReceiveCachedMetadata(mojo_base::BigBuffer data) { |
| target_client_->OnReceiveCachedMetadata(std::move(data)); |
| } |
| |
| void InterceptedRequest::OnTransferSizeUpdated(int32_t transfer_size_diff) { |
| target_client_->OnTransferSizeUpdated(transfer_size_diff); |
| } |
| |
| void InterceptedRequest::OnStartLoadingResponseBody( |
| mojo::ScopedDataPipeConsumerHandle body) { |
| target_client_->OnStartLoadingResponseBody( |
| factory_->request_handler_->OnFilterResponseBody(id_, request_, |
| std::move(body))); |
| } |
| |
| void InterceptedRequest::OnComplete( |
| const network::URLLoaderCompletionStatus& status) { |
| // Only wait for the original loader to possibly have a custom error if the |
| // target loader exists and succeeded. If the target loader failed, then it |
| // was a race as to whether that error or the safe browsing error would be |
| // reported. |
| CallOnComplete(status, !stream_loader_ && status.error_code == net::OK); |
| } |
| |
| // URLLoader methods. |
| |
| void InterceptedRequest::FollowRedirect( |
| const std::vector<std::string>& removed_headers_ext, |
| const net::HttpRequestHeaders& modified_headers_ext, |
| const base::Optional<GURL>& new_url) { |
| std::vector<std::string> removed_headers = removed_headers_ext; |
| net::HttpRequestHeaders modified_headers = modified_headers_ext; |
| OnProcessRequestHeaders(new_url.value_or(GURL()), &modified_headers, |
| &removed_headers); |
| |
| // If |OnURLLoaderClientError| was called then we're just waiting for the |
| // connection error handler of |proxied_loader_binding_|. Don't restart the |
| // job since that'll create another URLLoader. |
| if (!target_client_) |
| return; |
| |
| // Normally we would call FollowRedirect on the target loader and it would |
| // begin loading the redirected request. However, the client might want to |
| // intercept that request so restart the job instead. |
| Restart(); |
| } |
| |
| void InterceptedRequest::SetPriority(net::RequestPriority priority, |
| int32_t intra_priority_value) { |
| if (target_loader_) |
| target_loader_->SetPriority(priority, intra_priority_value); |
| } |
| |
| void InterceptedRequest::PauseReadingBodyFromNet() { |
| if (target_loader_) |
| target_loader_->PauseReadingBodyFromNet(); |
| } |
| |
| void InterceptedRequest::ResumeReadingBodyFromNet() { |
| if (target_loader_) |
| target_loader_->ResumeReadingBodyFromNet(); |
| } |
| |
| // Helper methods. |
| |
| void InterceptedRequest::BeforeRequestReceived(const GURL& original_url, |
| bool intercept_request, |
| bool intercept_only) { |
| intercept_request_ = intercept_request; |
| intercept_only_ = intercept_only; |
| |
| if (input_stream_previously_failed_ || !intercept_request_) { |
| // Equivalent to no interception. |
| InterceptResponseReceived(original_url, nullptr); |
| } else { |
| // TODO(network): Verify the case when WebContents::RenderFrameDeleted is |
| // called before network request is intercepted (i.e. if that's possible |
| // and whether it can result in any issues). |
| factory_->request_handler_->ShouldInterceptRequest( |
| id_, &request_, |
| base::BindOnce(&InterceptedRequest::InterceptResponseReceived, |
| weak_factory_.GetWeakPtr(), original_url)); |
| } |
| } |
| |
| void InterceptedRequest::InterceptResponseReceived( |
| const GURL& original_url, |
| std::unique_ptr<ResourceResponse> response) { |
| if (request_.url != original_url) { |
| // A response object shouldn't be created if we're redirecting. |
| DCHECK(!response); |
| |
| // Perform the redirect. |
| current_response_ = network::mojom::URLResponseHead::New(); |
| current_response_->request_start = base::TimeTicks::Now(); |
| current_response_->response_start = base::TimeTicks::Now(); |
| |
| auto headers = MakeResponseHeaders( |
| net::HTTP_TEMPORARY_REDIRECT, std::string(), std::string(), |
| std::string(), -1, {}, false /* allow_existing_header_override */); |
| current_response_->headers = headers; |
| |
| current_response_->encoded_data_length = headers->raw_headers().length(); |
| current_response_->content_length = current_response_->encoded_body_length = |
| 0; |
| |
| std::string origin; |
| if (request_.headers.GetHeader(net::HttpRequestHeaders::kOrigin, &origin) && |
| origin != url::Origin().Serialize()) { |
| // Allow redirects of cross-origin resource loads. |
| headers->AddHeader(MakeHeader( |
| network::cors::header_names::kAccessControlAllowOrigin, origin)); |
| } |
| |
| if (request_.credentials_mode == |
| network::mojom::CredentialsMode::kInclude) { |
| headers->AddHeader(MakeHeader( |
| network::cors::header_names::kAccessControlAllowCredentials, "true")); |
| } |
| |
| const net::RedirectInfo& redirect_info = |
| MakeRedirectInfo(request_, headers.get(), request_.url, 0); |
| HandleResponseOrRedirectHeaders( |
| redirect_info, |
| base::BindOnce(&InterceptedRequest::ContinueToBeforeRedirect, |
| weak_factory_.GetWeakPtr(), redirect_info)); |
| return; |
| } |
| |
| if (response) { |
| // Non-null response: make sure to use it as an override for the |
| // normal network data. |
| ContinueAfterInterceptWithOverride(std::move(response)); |
| } else { |
| // Request was not intercepted/overridden. Proceed with loading |
| // from network, unless this is a special |intercept_only_| loader, |
| // which happens for external schemes (e.g. unsupported schemes). |
| if (intercept_only_) { |
| SendErrorAndCompleteImmediately(net::ERR_UNKNOWN_URL_SCHEME); |
| return; |
| } |
| ContinueAfterIntercept(); |
| } |
| } |
| |
| void InterceptedRequest::ContinueAfterIntercept() { |
| if (!target_loader_ && target_factory_) { |
| network::mojom::URLLoaderClientPtr proxied_client; |
| proxied_client_binding_.Bind(mojo::MakeRequest(&proxied_client)); |
| |
| // Even if this request does not use the header client, future redirects |
| // might, so we need to set the option on the loader. |
| uint32_t options = options_ | network::mojom::kURLLoadOptionUseHeaderClient; |
| target_factory_->CreateLoaderAndStart( |
| mojo::MakeRequest(&target_loader_), id_.routing_id(), id_.request_id(), |
| options, request_, proxied_client.PassInterface(), traffic_annotation_); |
| } |
| } |
| |
| void InterceptedRequest::ContinueAfterInterceptWithOverride( |
| std::unique_ptr<ResourceResponse> response) { |
| network::mojom::URLLoaderClientPtr proxied_client; |
| proxied_client_binding_.Bind(mojo::MakeRequest(&proxied_client)); |
| |
| // StreamReaderURLLoader will synthesize TrustedHeaderClient callbacks to |
| // avoid having Set-Cookie headers stripped by the IPC layer. |
| current_request_uses_header_client_ = true; |
| network::mojom::TrustedHeaderClientPtr header_client; |
| header_client_receiver_.Bind(mojo::MakeRequest(&header_client)); |
| |
| stream_loader_ = new StreamReaderURLLoader( |
| id_, request_, std::move(proxied_client), std::move(header_client), |
| traffic_annotation_, |
| std::make_unique<InterceptDelegate>(std::move(response), |
| weak_factory_.GetWeakPtr())); |
| stream_loader_->Start(); |
| } |
| |
| void InterceptedRequest::HandleResponseOrRedirectHeaders( |
| base::Optional<net::RedirectInfo> redirect_info, |
| net::CompletionOnceCallback continuation) { |
| override_headers_ = nullptr; |
| redirect_url_ = redirect_info.has_value() ? redirect_info->new_url : GURL(); |
| original_url_ = request_.url; |
| |
| // |current_response_| may be nullptr when called from OnHeadersReceived. |
| auto headers = |
| current_response_ ? current_response_->headers : current_headers_; |
| |
| // Even though |head| is const we can get a non-const pointer to the headers |
| // and modifications we make are passed to the target client. |
| factory_->request_handler_->ProcessResponseHeaders( |
| id_, request_, redirect_url_, headers.get()); |
| |
| // Pause handling of client messages before waiting on an async callback. |
| if (proxied_client_binding_) |
| proxied_client_binding_.PauseIncomingMethodCallProcessing(); |
| |
| factory_->request_handler_->OnRequestResponse( |
| id_, &request_, headers.get(), redirect_info, |
| base::BindOnce(&InterceptedRequest::ContinueResponseOrRedirect, |
| weak_factory_.GetWeakPtr(), std::move(continuation))); |
| } |
| |
| void InterceptedRequest::ContinueResponseOrRedirect( |
| net::CompletionOnceCallback continuation, |
| InterceptedRequestHandler::ResponseMode response_mode, |
| scoped_refptr<net::HttpResponseHeaders> override_headers, |
| const GURL& redirect_url) { |
| if (response_mode == InterceptedRequestHandler::ResponseMode::CANCEL) { |
| std::move(continuation).Run(net::ERR_ABORTED); |
| return; |
| } else if (response_mode == |
| InterceptedRequestHandler::ResponseMode::RESTART) { |
| Restart(); |
| return; |
| } |
| |
| override_headers_ = override_headers; |
| if (override_headers_) { |
| // Make sure to update current_response_, since when OnReceiveResponse |
| // is called we will not use its headers as it might be missing the |
| // Set-Cookie line (which gets stripped by the IPC layer). |
| current_response_->headers = override_headers_; |
| } |
| redirect_url_ = redirect_url; |
| |
| std::move(continuation).Run(net::OK); |
| } |
| |
| void InterceptedRequest::ContinueToHandleOverrideHeaders(int error_code) { |
| if (error_code != net::OK) { |
| SendErrorAndCompleteImmediately(error_code); |
| return; |
| } |
| |
| DCHECK(on_headers_received_callback_); |
| base::Optional<std::string> headers; |
| if (override_headers_) |
| headers = override_headers_->raw_headers(); |
| header_client_redirect_url_ = redirect_url_; |
| std::move(on_headers_received_callback_).Run(net::OK, headers, redirect_url_); |
| |
| override_headers_ = nullptr; |
| redirect_url_ = GURL(); |
| |
| // Resume handling of client messages after continuing from an async callback. |
| if (proxied_client_binding_) |
| proxied_client_binding_.ResumeIncomingMethodCallProcessing(); |
| } |
| |
| net::RedirectInfo InterceptedRequest::MakeRedirectResponseAndInfo( |
| const GURL& new_location) { |
| // Clear the Content-Type values. |
| current_response_->mime_type = current_response_->charset = std::string(); |
| current_response_->headers->RemoveHeader( |
| net::HttpRequestHeaders::kContentType); |
| |
| // Clear the Content-Length values. |
| current_response_->content_length = current_response_->encoded_body_length = |
| 0; |
| current_response_->headers->RemoveHeader( |
| net::HttpRequestHeaders::kContentLength); |
| |
| current_response_->encoded_data_length = |
| current_response_->headers->raw_headers().size(); |
| |
| const net::RedirectInfo& redirect_info = MakeRedirectInfo( |
| request_, current_response_->headers.get(), new_location, 0); |
| current_response_->headers->ReplaceStatusLine( |
| MakeStatusLine(redirect_info.status_code, std::string(), true)); |
| |
| return redirect_info; |
| } |
| |
| void InterceptedRequest::ContinueToBeforeRedirect( |
| const net::RedirectInfo& redirect_info, |
| int error_code) { |
| if (error_code != net::OK) { |
| SendErrorAndCompleteImmediately(error_code); |
| return; |
| } |
| |
| request_was_redirected_ = true; |
| |
| if (header_client_redirect_url_.is_valid()) |
| header_client_redirect_url_ = GURL(); |
| |
| const GURL redirect_url = redirect_url_; |
| override_headers_ = nullptr; |
| redirect_url_ = GURL(); |
| |
| // Resume handling of client messages after continuing from an async callback. |
| if (proxied_client_binding_) |
| proxied_client_binding_.ResumeIncomingMethodCallProcessing(); |
| |
| if (redirect_url.is_valid()) { |
| net::RedirectInfo new_redirect_info = redirect_info; |
| new_redirect_info.new_url = redirect_url; |
| target_client_->OnReceiveRedirect(new_redirect_info, |
| std::move(current_response_)); |
| request_.url = redirect_url; |
| } else { |
| target_client_->OnReceiveRedirect(redirect_info, |
| std::move(current_response_)); |
| request_.url = redirect_info.new_url; |
| } |
| |
| // If request_ changes from POST to GET, strip POST headers. |
| const bool post_to_get = |
| request_.method == "POST" && |
| redirect_info.new_method == net::HttpRequestHeaders::kGetMethod; |
| |
| request_.method = redirect_info.new_method; |
| request_.site_for_cookies = redirect_info.new_site_for_cookies; |
| request_.referrer = GURL(redirect_info.new_referrer); |
| request_.referrer_policy = redirect_info.new_referrer_policy; |
| |
| // The request method can be changed to "GET". In this case we need to |
| // reset the request body manually, and strip the POST headers. |
| if (request_.method == net::HttpRequestHeaders::kGetMethod) { |
| request_.request_body = nullptr; |
| |
| if (post_to_get) { |
| request_.headers.RemoveHeader(net::HttpRequestHeaders::kContentLength); |
| request_.headers.RemoveHeader(net::HttpRequestHeaders::kContentType); |
| } |
| } |
| } |
| |
| void InterceptedRequest::ContinueToResponseStarted(int error_code) { |
| if (error_code != net::OK) { |
| SendErrorAndCompleteImmediately(error_code); |
| return; |
| } |
| |
| const GURL redirect_url = redirect_url_; |
| override_headers_ = nullptr; |
| redirect_url_ = GURL(); |
| |
| std::string location; |
| const bool is_redirect = redirect_url.is_valid() || |
| (current_response_->headers && |
| current_response_->headers->IsRedirect(&location)); |
| if (stream_loader_ && is_redirect) { |
| // Redirecting from OnReceiveResponse generally isn't supported by the |
| // NetworkService, so we can only support it when using a custom loader. |
| // TODO(network): Remove this special case. |
| const GURL new_location = redirect_url.is_valid() |
| ? redirect_url |
| : original_url_.Resolve(location); |
| const net::RedirectInfo& redirect_info = |
| MakeRedirectResponseAndInfo(new_location); |
| |
| HandleResponseOrRedirectHeaders( |
| redirect_info, |
| base::BindOnce(&InterceptedRequest::ContinueToBeforeRedirect, |
| weak_factory_.GetWeakPtr(), redirect_info)); |
| } else { |
| LOG_IF(WARNING, is_redirect) << "Redirect at this time is not supported by " |
| "the default network loader."; |
| |
| // Resume handling of client messages after continuing from an async |
| // callback. |
| if (proxied_client_binding_) |
| proxied_client_binding_.ResumeIncomingMethodCallProcessing(); |
| |
| target_client_->OnReceiveResponse(std::move(current_response_)); |
| } |
| |
| if (stream_loader_) |
| stream_loader_->ContinueResponse(is_redirect); |
| } |
| |
| void InterceptedRequest::OnDestroy() { |
| // We don't want any callbacks after this point. |
| weak_factory_.InvalidateWeakPtrs(); |
| |
| factory_->request_handler_->OnRequestComplete(id_, request_, status_); |
| |
| // Destroys |this|. |
| factory_->RemoveRequest(this); |
| } |
| |
| void InterceptedRequest::OnProcessRequestHeaders( |
| const GURL& redirect_url, |
| net::HttpRequestHeaders* modified_headers, |
| std::vector<std::string>* removed_headers) { |
| factory_->request_handler_->ProcessRequestHeaders( |
| id_, request_, redirect_url, modified_headers, removed_headers); |
| |
| if (!modified_headers->IsEmpty() || !removed_headers->empty()) { |
| request_.headers.MergeFrom(*modified_headers); |
| for (const std::string& name : *removed_headers) |
| request_.headers.RemoveHeader(name); |
| } |
| } |
| |
| void InterceptedRequest::OnURLLoaderClientError() { |
| // We set |wait_for_loader_error| to true because if the loader did have a |
| // custom_reason error then the client would be reset as well and it would be |
| // a race as to which connection error we saw first. |
| CallOnComplete(network::URLLoaderCompletionStatus(net::ERR_ABORTED), |
| true /* wait_for_loader_error */); |
| } |
| |
| void InterceptedRequest::OnURLLoaderError(uint32_t custom_reason, |
| const std::string& description) { |
| if (custom_reason == network::mojom::URLLoader::kClientDisconnectReason) |
| SendErrorCallback(safe_browsing::GetNetErrorCodeForSafeBrowsing(), true); |
| |
| got_loader_error_ = true; |
| |
| // If CallOnComplete was already called, then this object is ready to be |
| // deleted. |
| if (!target_client_) |
| OnDestroy(); |
| } |
| |
| void InterceptedRequest::CallOnComplete( |
| const network::URLLoaderCompletionStatus& status, |
| bool wait_for_loader_error) { |
| status_ = status; |
| |
| if (target_client_) |
| target_client_->OnComplete(status); |
| |
| if (proxied_loader_binding_ && |
| (wait_for_loader_error && !got_loader_error_)) { |
| // Don't delete |this| yet, in case the |proxied_loader_binding_|'s |
| // error_handler is called with a reason to indicate an error which we want |
| // to send to the client bridge. Also reset |target_client_| so we don't |
| // get its error_handler called and then delete |this|. |
| target_client_.reset(); |
| |
| // Since the original client is gone no need to continue loading the |
| // request. |
| proxied_client_binding_.Close(); |
| header_client_receiver_.reset(); |
| target_loader_.reset(); |
| |
| // In case there are pending checks as to whether this request should be |
| // intercepted, we don't want that causing |target_client_| to be used |
| // later. |
| weak_factory_.InvalidateWeakPtrs(); |
| } else { |
| OnDestroy(); |
| } |
| } |
| |
| void InterceptedRequest::SendErrorAndCompleteImmediately(int error_code) { |
| status_ = network::URLLoaderCompletionStatus(error_code); |
| SendErrorCallback(status_.error_code, false); |
| target_client_->OnComplete(status_); |
| OnDestroy(); |
| } |
| |
| void InterceptedRequest::SendErrorCallback(int error_code, |
| bool safebrowsing_hit) { |
| // Ensure we only send one error callback, e.g. to avoid sending two if |
| // there's both a networking error and safe browsing blocked the request. |
| if (sent_error_callback_) |
| return; |
| |
| sent_error_callback_ = true; |
| factory_->request_handler_->OnRequestError(id_, request_, error_code, |
| safebrowsing_hit); |
| } |
| |
| void InterceptedRequest::OnUploadProgressACK() { |
| DCHECK(waiting_for_upload_progress_ack_); |
| waiting_for_upload_progress_ack_ = false; |
| } |
| |
| //============================== |
| // InterceptedRequestHandler |
| //============================== |
| |
| InterceptedRequestHandler::InterceptedRequestHandler() {} |
| InterceptedRequestHandler::~InterceptedRequestHandler() {} |
| |
| void InterceptedRequestHandler::OnBeforeRequest( |
| const RequestId& id, |
| network::ResourceRequest* request, |
| bool request_was_redirected, |
| OnBeforeRequestResultCallback callback, |
| CancelRequestCallback cancel_callback) { |
| std::move(callback).Run(false, false); |
| } |
| |
| void InterceptedRequestHandler::ShouldInterceptRequest( |
| const RequestId& id, |
| network::ResourceRequest* request, |
| ShouldInterceptRequestResultCallback callback) { |
| std::move(callback).Run(nullptr); |
| } |
| |
| void InterceptedRequestHandler::OnRequestResponse( |
| const RequestId& id, |
| network::ResourceRequest* request, |
| net::HttpResponseHeaders* headers, |
| base::Optional<net::RedirectInfo> redirect_info, |
| OnRequestResponseResultCallback callback) { |
| std::move(callback).Run( |
| ResponseMode::CONTINUE, nullptr, |
| redirect_info.has_value() ? redirect_info->new_url : GURL()); |
| } |
| |
| mojo::ScopedDataPipeConsumerHandle |
| InterceptedRequestHandler::OnFilterResponseBody( |
| const RequestId& id, |
| const network::ResourceRequest& request, |
| mojo::ScopedDataPipeConsumerHandle body) { |
| return body; |
| } |
| |
| //============================== |
| // ProxyURLLoaderFactory |
| //============================== |
| |
| ProxyURLLoaderFactory::ProxyURLLoaderFactory( |
| mojo::PendingReceiver<network::mojom::URLLoaderFactory> factory_receiver, |
| network::mojom::URLLoaderFactoryPtrInfo target_factory_info, |
| mojo::PendingReceiver<network::mojom::TrustedURLLoaderHeaderClient> |
| header_client_receiver, |
| std::unique_ptr<InterceptedRequestHandler> request_handler) |
| : request_handler_(std::move(request_handler)), weak_factory_(this) { |
| CEF_REQUIRE_IOT(); |
| DCHECK(request_handler_); |
| |
| // Actual creation of the factory. |
| if (target_factory_info) { |
| target_factory_.Bind(std::move(target_factory_info)); |
| target_factory_.set_connection_error_handler(base::BindOnce( |
| &ProxyURLLoaderFactory::OnTargetFactoryError, base::Unretained(this))); |
| } |
| proxy_bindings_.AddBinding(this, std::move(factory_receiver)); |
| proxy_bindings_.set_connection_error_handler(base::BindRepeating( |
| &ProxyURLLoaderFactory::OnProxyBindingError, base::Unretained(this))); |
| |
| if (header_client_receiver) |
| url_loader_header_client_receiver_.Bind(std::move(header_client_receiver)); |
| } |
| |
| ProxyURLLoaderFactory::~ProxyURLLoaderFactory() { |
| CEF_REQUIRE_IOT(); |
| } |
| |
| // static |
| void ProxyURLLoaderFactory::CreateOnIOThread( |
| mojo::PendingReceiver<network::mojom::URLLoaderFactory> factory_receiver, |
| network::mojom::URLLoaderFactoryPtrInfo target_factory_info, |
| mojo::PendingReceiver<network::mojom::TrustedURLLoaderHeaderClient> |
| header_client_receiver, |
| content::ResourceContext* resource_context, |
| std::unique_ptr<InterceptedRequestHandler> request_handler) { |
| CEF_REQUIRE_IOT(); |
| auto proxy = new ProxyURLLoaderFactory( |
| std::move(factory_receiver), std::move(target_factory_info), |
| std::move(header_client_receiver), std::move(request_handler)); |
| ResourceContextData::AddProxy(proxy, resource_context); |
| } |
| |
| void ProxyURLLoaderFactory::SetDisconnectCallback( |
| DisconnectCallback on_disconnect) { |
| CEF_REQUIRE_IOT(); |
| DCHECK(!destroyed_); |
| DCHECK(!on_disconnect_); |
| on_disconnect_ = std::move(on_disconnect); |
| } |
| |
| // static |
| void ProxyURLLoaderFactory::CreateProxy( |
| content::BrowserContext* browser_context, |
| mojo::PendingReceiver<network::mojom::URLLoaderFactory>* factory_receiver, |
| mojo::PendingRemote<network::mojom::TrustedURLLoaderHeaderClient>* |
| header_client, |
| std::unique_ptr<InterceptedRequestHandler> request_handler) { |
| CEF_REQUIRE_UIT(); |
| DCHECK(request_handler); |
| |
| auto proxied_receiver = std::move(*factory_receiver); |
| network::mojom::URLLoaderFactoryPtrInfo target_factory_info; |
| *factory_receiver = mojo::MakeRequest(&target_factory_info); |
| |
| mojo::PendingReceiver<network::mojom::TrustedURLLoaderHeaderClient> |
| header_client_receiver; |
| if (header_client) |
| header_client_receiver = header_client->InitWithNewPipeAndPassReceiver(); |
| |
| content::ResourceContext* resource_context = |
| browser_context->GetResourceContext(); |
| DCHECK(resource_context); |
| |
| CEF_POST_TASK( |
| CEF_IOT, |
| base::BindOnce( |
| &ProxyURLLoaderFactory::CreateOnIOThread, std::move(proxied_receiver), |
| std::move(target_factory_info), std::move(header_client_receiver), |
| base::Unretained(resource_context), std::move(request_handler))); |
| } |
| |
| // static |
| ProxyURLLoaderFactory* ProxyURLLoaderFactory::CreateProxy( |
| content::WebContents::Getter web_contents_getter, |
| mojo::PendingReceiver<network::mojom::URLLoaderFactory> loader_receiver, |
| std::unique_ptr<InterceptedRequestHandler> request_handler) { |
| CEF_REQUIRE_IOT(); |
| DCHECK(request_handler); |
| |
| auto proxy = new ProxyURLLoaderFactory( |
| std::move(loader_receiver), nullptr, |
| mojo::PendingReceiver<network::mojom::TrustedURLLoaderHeaderClient>(), |
| std::move(request_handler)); |
| CEF_POST_TASK(CEF_UIT, |
| base::BindOnce(ResourceContextData::AddProxyOnUIThread, |
| base::Unretained(proxy), web_contents_getter)); |
| return proxy; |
| } |
| |
| void ProxyURLLoaderFactory::CreateLoaderAndStart( |
| mojo::PendingReceiver<network::mojom::URLLoader> receiver, |
| int32_t routing_id, |
| int32_t request_id, |
| uint32_t options, |
| const network::ResourceRequest& request, |
| mojo::PendingRemote<network::mojom::URLLoaderClient> client, |
| const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) { |
| CEF_REQUIRE_IOT(); |
| if (!CONTEXT_STATE_VALID()) { |
| // Don't start a request while we're shutting down. |
| return; |
| } |
| |
| bool pass_through = false; |
| if (pass_through) { |
| // This is the so-called pass-through, no-op option. |
| target_factory_->CreateLoaderAndStart( |
| std::move(receiver), routing_id, request_id, options, request, |
| std::move(client), traffic_annotation); |
| return; |
| } |
| |
| mojo::PendingRemote<network::mojom::URLLoaderFactory> target_factory_clone; |
| if (target_factory_) |
| target_factory_->Clone( |
| target_factory_clone.InitWithNewPipeAndPassReceiver()); |
| |
| InterceptedRequest* req = new InterceptedRequest( |
| this, RequestId(request_id, routing_id), options, request, |
| traffic_annotation, std::move(receiver), std::move(client), |
| std::move(target_factory_clone)); |
| requests_.insert(std::make_pair(request_id, base::WrapUnique(req))); |
| req->Restart(); |
| } |
| |
| void ProxyURLLoaderFactory::Clone( |
| mojo::PendingReceiver<network::mojom::URLLoaderFactory> factory) { |
| CEF_REQUIRE_IOT(); |
| proxy_bindings_.AddBinding(this, std::move(factory)); |
| } |
| |
| void ProxyURLLoaderFactory::OnLoaderCreated( |
| int32_t request_id, |
| mojo::PendingReceiver<network::mojom::TrustedHeaderClient> receiver) { |
| CEF_REQUIRE_IOT(); |
| auto request_it = requests_.find(request_id); |
| if (request_it != requests_.end()) |
| request_it->second->OnLoaderCreated(std::move(receiver)); |
| } |
| |
| void ProxyURLLoaderFactory::OnLoaderForCorsPreflightCreated( |
| const ::network::ResourceRequest& request, |
| mojo::PendingReceiver<network::mojom::TrustedHeaderClient> header_client) { |
| CEF_REQUIRE_IOT(); |
| // TODO(cef): Should we do something here? |
| } |
| |
| void ProxyURLLoaderFactory::OnTargetFactoryError() { |
| // Stop calls to CreateLoaderAndStart() when |target_factory_| is invalid. |
| target_factory_.reset(); |
| proxy_bindings_.CloseAllBindings(); |
| |
| MaybeDestroySelf(); |
| } |
| |
| void ProxyURLLoaderFactory::OnProxyBindingError() { |
| if (proxy_bindings_.empty()) |
| target_factory_.reset(); |
| |
| MaybeDestroySelf(); |
| } |
| |
| void ProxyURLLoaderFactory::RemoveRequest(InterceptedRequest* request) { |
| auto it = requests_.find(request->id().request_id()); |
| DCHECK(it != requests_.end()); |
| requests_.erase(it); |
| |
| MaybeDestroySelf(); |
| } |
| |
| void ProxyURLLoaderFactory::MaybeDestroySelf() { |
| // Even if all URLLoaderFactory pipes connected to this object have been |
| // closed it has to stay alive until all active requests have completed. |
| if (target_factory_.is_bound() || !requests_.empty()) |
| return; |
| |
| destroyed_ = true; |
| |
| // In some cases we may be destroyed before SetDisconnectCallback is called. |
| if (on_disconnect_) { |
| // Deletes |this|. |
| std::move(on_disconnect_).Run(this); |
| } |
| } |
| |
| } // namespace net_service |