// Copyright (c) 2019 The Chromium Embedded Framework 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 "cef/libcef/browser/net_service/resource_request_handler_wrapper.h" #include #include "base/memory/raw_ptr.h" #include "cef/libcef/browser/browser_host_base.h" #include "cef/libcef/browser/context.h" #include "cef/libcef/browser/iothread_state.h" #include "cef/libcef/browser/net_service/cookie_helper.h" #include "cef/libcef/browser/net_service/proxy_url_loader_factory.h" #include "cef/libcef/browser/net_service/resource_handler_wrapper.h" #include "cef/libcef/browser/net_service/response_filter_wrapper.h" #include "cef/libcef/browser/prefs/browser_prefs.h" #include "cef/libcef/browser/thread_util.h" #include "cef/libcef/common/app_manager.h" #include "cef/libcef/common/net/scheme_registration.h" #include "cef/libcef/common/net_service/net_service_util.h" #include "cef/libcef/common/request_impl.h" #include "cef/libcef/common/response_impl.h" #include "chrome/browser/profiles/profile.h" #include "components/language/core/browser/pref_names.h" #include "components/prefs/pref_service.h" #include "content/browser/renderer_host/frame_tree_node.h" #include "content/browser/renderer_host/render_frame_host_impl.h" #include "content/browser/storage_partition_impl.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/web_contents.h" #include "net/base/load_flags.h" #include "net/http/http_status_code.h" #include "third_party/blink/public/mojom/loader/resource_load_info.mojom-shared.h" #include "ui/base/page_transition_types.h" #include "url/origin.h" namespace net_service { namespace { const int kLoadNoCookiesFlags = net::LOAD_DO_NOT_SEND_COOKIES | net::LOAD_DO_NOT_SAVE_COOKIES; class RequestCallbackWrapper : public CefCallback { public: using Callback = base::OnceCallback; explicit RequestCallbackWrapper(Callback callback) : callback_(std::move(callback)), work_thread_task_runner_( base::SequencedTaskRunner::GetCurrentDefault()) {} RequestCallbackWrapper(const RequestCallbackWrapper&) = delete; RequestCallbackWrapper& operator=(const RequestCallbackWrapper&) = delete; ~RequestCallbackWrapper() override { if (!callback_.is_null()) { // Make sure it executes on the correct thread. work_thread_task_runner_->PostTask( FROM_HERE, base::BindOnce(std::move(callback_), true)); } } void Continue() override { ContinueNow(true); } void Cancel() override { ContinueNow(false); } private: void ContinueNow(bool allow) { if (!work_thread_task_runner_->RunsTasksInCurrentSequence()) { work_thread_task_runner_->PostTask( FROM_HERE, base::BindOnce(&RequestCallbackWrapper::ContinueNow, this, allow)); return; } if (!callback_.is_null()) { std::move(callback_).Run(allow); } } Callback callback_; scoped_refptr work_thread_task_runner_; IMPLEMENT_REFCOUNTING(RequestCallbackWrapper); }; class InterceptedRequestHandlerWrapper : public InterceptedRequestHandler { public: struct RequestState { RequestState() = default; void Reset(CefRefPtr handler, CefRefPtr scheme_factory, CefRefPtr pending_request, network::ResourceRequest* request, bool request_was_redirected, CancelRequestCallback cancel_callback) { handler_ = handler; scheme_factory_ = scheme_factory; cookie_filter_ = nullptr; pending_request_ = pending_request; pending_response_ = nullptr; request_ = request; request_was_redirected_ = request_was_redirected; was_custom_handled_ = false; cancel_callback_ = std::move(cancel_callback); } CefRefPtr handler_; CefRefPtr scheme_factory_; CefRefPtr cookie_filter_; CefRefPtr pending_request_; CefRefPtr pending_response_; raw_ptr request_; bool request_was_redirected_ = false; bool was_custom_handled_ = false; bool accept_language_added_ = false; CancelRequestCallback cancel_callback_; }; struct PendingRequest { PendingRequest(int32_t request_id, network::ResourceRequest* request, bool request_was_redirected, OnBeforeRequestResultCallback callback, CancelRequestCallback cancel_callback) : id_(request_id), request_(request), request_was_redirected_(request_was_redirected), callback_(std::move(callback)), cancel_callback_(std::move(cancel_callback)) {} ~PendingRequest() { if (cancel_callback_) { std::move(cancel_callback_).Run(net::ERR_ABORTED); } } void Run(InterceptedRequestHandlerWrapper* self) { self->OnBeforeRequest(id_, request_, request_was_redirected_, std::move(callback_), std::move(cancel_callback_)); request_ = nullptr; } const int32_t id_; raw_ptr request_; const bool request_was_redirected_; OnBeforeRequestResultCallback callback_; CancelRequestCallback cancel_callback_; }; // Observer to receive notification of CEF context or associated browser // destruction. Only one of the *Destroyed() methods will be called. class DestructionObserver : public CefBrowserHostBase::Observer, public CefContext::Observer { public: explicit DestructionObserver(CefBrowserHostBase* browser) { if (browser) { browser_info_ = browser->browser_info(); browser->AddObserver(this); } else { CefContext::Get()->AddObserver(this); } } DestructionObserver(const DestructionObserver&) = delete; DestructionObserver& operator=(const DestructionObserver&) = delete; ~DestructionObserver() override { CEF_REQUIRE_UIT(); if (!registered_) { return; } // Verify that the browser or context still exists before attempting to // remove the observer. if (browser_info_) { auto browser = browser_info_->browser(); if (browser) { browser->RemoveObserver(this); } } else if (CefContext::Get()) { // Network requests may be torn down during shutdown, so we can't check // CONTEXT_STATE_VALID() here. CefContext::Get()->RemoveObserver(this); } } void SetWrapper(base::WeakPtr wrapper) { CEF_REQUIRE_IOT(); wrapper_ = wrapper; } void OnBrowserDestroyed(CefBrowserHostBase* browser) override { CEF_REQUIRE_UIT(); browser->RemoveObserver(this); registered_ = false; browser_info_ = nullptr; NotifyOnDestroyed(); } void OnContextDestroyed() override { CEF_REQUIRE_UIT(); CefContext::Get()->RemoveObserver(this); registered_ = false; NotifyOnDestroyed(); } private: void NotifyOnDestroyed() { if (wrapper_.MaybeValid()) { // This will be a no-op if the WeakPtr is invalid. CEF_POST_TASK( CEF_IOT, base::BindOnce(&InterceptedRequestHandlerWrapper::OnDestroyed, wrapper_)); } } scoped_refptr browser_info_; bool registered_ = true; base::WeakPtr wrapper_; }; // Holds state information for InterceptedRequestHandlerWrapper. State is // initialized on the UI thread and later passed to the *Wrapper object on // the IO thread. struct InitState { InitState() = default; ~InitState() { if (destruction_observer_) { if (initialized_) { // Clear the reference added in // InterceptedRequestHandlerWrapper::SetInitialized(). destruction_observer_->SetWrapper(nullptr); } DeleteDestructionObserver(); } } void Initialize(content::BrowserContext* browser_context, CefRefPtr browser, CefRefPtr frame, const content::GlobalRenderFrameHostId& global_id, bool is_navigation, bool is_download, const url::Origin& request_initiator, const base::RepeatingClosure& unhandled_request_callback) { CEF_REQUIRE_UIT(); auto profile = Profile::FromBrowserContext(browser_context); auto cef_browser_context = CefBrowserContext::FromProfile(profile); browser_context_getter_ = cef_browser_context->getter(); iothread_state_ = cef_browser_context->iothread_state(); CHECK(iothread_state_); cookieable_schemes_ = cef_browser_context->GetCookieableSchemes(); // We register to be notified of CEF context or browser destruction so // that we can stop accepting new requests and cancel pending/in-progress // requests in a timely manner (e.g. before we start asserting about // leaked objects during CEF shutdown). destruction_observer_ = std::make_unique(browser.get()); if (browser) { // These references will be released in OnDestroyed(). browser_ = browser; frame_ = frame; } global_id_ = global_id; is_navigation_ = is_navigation; is_download_ = is_download; request_initiator_ = request_initiator.Serialize(); unhandled_request_callback_ = unhandled_request_callback; // Default values for standard headers. accept_language_ = browser_prefs::GetAcceptLanguageList(profile); DCHECK(!accept_language_.empty()); user_agent_ = CefAppManager::Get()->GetContentClient()->browser()->GetUserAgent(); DCHECK(!user_agent_.empty()); } void DeleteDestructionObserver() { DCHECK(destruction_observer_); CEF_POST_TASK( CEF_UIT, base::BindOnce(&InitState::DeleteDestructionObserverOnUIThread, std::move(destruction_observer_))); } static void DeleteDestructionObserverOnUIThread( std::unique_ptr observer) {} // Only accessed on the UI thread. CefBrowserContext::Getter browser_context_getter_; bool initialized_ = false; CefRefPtr browser_; CefRefPtr frame_; scoped_refptr iothread_state_; CefBrowserContext::CookieableSchemes cookieable_schemes_; content::GlobalRenderFrameHostId global_id_; bool is_navigation_ = true; bool is_download_ = false; CefString request_initiator_; base::RepeatingClosure unhandled_request_callback_; // Default values for standard headers. std::string accept_language_; std::string user_agent_; // Used to route authentication and certificate callbacks through the // associated StoragePartition instance. mojo::PendingRemote url_loader_network_observer_; bool did_try_create_url_loader_network_observer_ = false; // Used to receive destruction notification. std::unique_ptr destruction_observer_; }; // Manages InterceptedRequestHandlerWrapper initialization. The *Wrapper // object is owned by ProxyURLLoaderFactory and may be deleted before // SetInitialized() is called. struct InitHelper : base::RefCountedThreadSafe { public: explicit InitHelper(InterceptedRequestHandlerWrapper* wrapper) : wrapper_(wrapper) {} InitHelper(const InitHelper&) = delete; InitHelper& operator=(const InitHelper&) = delete; void MaybeSetInitialized(std::unique_ptr init_state) { CEF_POST_TASK(CEF_IOT, base::BindOnce(&InitHelper::SetInitialized, this, std::move(init_state))); } void Disconnect() { base::AutoLock lock_scope(lock_); wrapper_ = nullptr; } private: void SetInitialized(std::unique_ptr init_state) { base::AutoLock lock_scope(lock_); // May be nullptr if the InterceptedRequestHandlerWrapper has already // been deleted. if (!wrapper_) { return; } wrapper_->SetInitialized(std::move(init_state)); wrapper_ = nullptr; } base::Lock lock_; raw_ptr wrapper_; }; InterceptedRequestHandlerWrapper() : init_helper_(base::MakeRefCounted(this)), weak_ptr_factory_(this) {} InterceptedRequestHandlerWrapper(const InterceptedRequestHandlerWrapper&) = delete; InterceptedRequestHandlerWrapper& operator=( const InterceptedRequestHandlerWrapper&) = delete; ~InterceptedRequestHandlerWrapper() override { CEF_REQUIRE_IOT(); // There should be no in-progress requests during destruction. DCHECK(request_map_.empty()); // Don't continue with initialization if we get deleted before // SetInitialized is called asynchronously. init_helper_->Disconnect(); } scoped_refptr init_helper() const { return init_helper_; } void SetInitialized(std::unique_ptr init_state) { CEF_REQUIRE_IOT(); DCHECK(!init_state_); init_state_ = std::move(init_state); // Check that the CEF context or associated browser was not destroyed // between the calls to Initialize and SetInitialized, in which case // we won't get an OnDestroyed callback from DestructionObserver. if (init_state_->browser_) { if (!init_state_->browser_->browser_info()->browser()) { OnDestroyed(); return; } } else if (!CONTEXT_STATE_VALID()) { OnDestroyed(); return; } init_state_->initialized_ = true; init_state_->destruction_observer_->SetWrapper( weak_ptr_factory_.GetWeakPtr()); // Continue any pending requests. if (!pending_requests_.empty()) { for (const auto& request : pending_requests_) { request->Run(this); } pending_requests_.clear(); } } static void TryCreateURLLoaderNetworkObserver( std::unique_ptr pending_request, CefRefPtr frame, const CefBrowserContext::Getter& browser_context_getter, base::WeakPtr self) { CEF_REQUIRE_UIT(); mojo::PendingRemote url_loader_network_observer; if (frame) { // The request will be associated with this frame/browser if it's valid, // otherwise the request will be canceled. content::RenderFrameHostImpl* rfh = static_cast( static_cast(frame.get()) ->GetRenderFrameHost()); if (rfh) { if (rfh->frame_tree_node() && rfh->frame_tree_node()->navigation_request()) { // Associate the Observer with the current NavigationRequest. This is // necessary for |is_main_frame_request| to report true (the expected // value) in AllowCertificateError. // TODO(cef): This approach for retrieving the NavigationRequest is // deprecated, see https://crbug.com/1179502#c36. url_loader_network_observer = rfh->GetStoragePartition() ->CreateURLLoaderNetworkObserverForNavigationRequest( *(rfh->frame_tree_node()->navigation_request())); } else { // Associate the Observer with the RenderFrameHost. url_loader_network_observer = rfh->CreateURLLoaderNetworkObserver(); } } } else { auto cef_browser_context = browser_context_getter.Run(); auto browser_context = cef_browser_context ? cef_browser_context->AsBrowserContext() : nullptr; if (browser_context) { url_loader_network_observer = static_cast( browser_context->GetDefaultStoragePartition()) ->CreateAuthCertObserverForServiceWorker( content::ChildProcessHost::kInvalidUniqueID); } } CEF_POST_TASK(CEF_IOT, base::BindOnce(&InterceptedRequestHandlerWrapper:: ContinueCreateURLLoaderNetworkObserver, self, std::move(pending_request), std::move(url_loader_network_observer))); } void ContinueCreateURLLoaderNetworkObserver( std::unique_ptr pending_request, mojo::PendingRemote url_loader_network_observer) { CEF_REQUIRE_IOT(); DCHECK(!init_state_->did_try_create_url_loader_network_observer_); init_state_->did_try_create_url_loader_network_observer_ = true; init_state_->url_loader_network_observer_ = std::move(url_loader_network_observer); pending_request->Run(this); } // InterceptedRequestHandler methods: void OnBeforeRequest(int32_t request_id, network::ResourceRequest* request, bool request_was_redirected, OnBeforeRequestResultCallback callback, CancelRequestCallback cancel_callback) override { CEF_REQUIRE_IOT(); if (shutting_down_) { // Abort immediately. std::move(cancel_callback).Run(net::ERR_ABORTED); return; } if (!init_state_) { // Queue requests until we're initialized. pending_requests_.push_back(std::make_unique( request_id, request, request_was_redirected, std::move(callback), std::move(cancel_callback))); return; } if (request->trusted_params && !request->trusted_params->url_loader_network_observer && !init_state_->did_try_create_url_loader_network_observer_) { // Restarted/redirected requests won't already have an observer, so we // need to create one. CEF_POST_TASK( CEF_UIT, base::BindOnce(&InterceptedRequestHandlerWrapper:: TryCreateURLLoaderNetworkObserver, std::make_unique( request_id, request, request_was_redirected, std::move(callback), std::move(cancel_callback)), init_state_->frame_, init_state_->browser_context_getter_, weak_ptr_factory_.GetWeakPtr())); return; } // State may already exist for restarted requests. RequestState* state = GetOrCreateState(request_id); if (init_state_->did_try_create_url_loader_network_observer_) { if (init_state_->url_loader_network_observer_) { request->trusted_params->url_loader_network_observer = std::move(init_state_->url_loader_network_observer_); } // Reset state so that the observer will be recreated on the next // restart/redirect. init_state_->did_try_create_url_loader_network_observer_ = false; } // Add standard headers, if currently unspecified. if (!request->headers.HasHeader(net::HttpRequestHeaders::kAcceptLanguage)) { request->headers.SetHeaderIfMissing( net::HttpRequestHeaders::kAcceptLanguage, init_state_->accept_language_); state->accept_language_added_ = true; } request->headers.SetHeaderIfMissing(net::HttpRequestHeaders::kUserAgent, init_state_->user_agent_); const bool is_external = IsExternalRequest(request); // External requests will not have a default handler. bool intercept_only = is_external; CefRefPtr requestPtr; CefRefPtr handler = GetHandler(request_id, request, &intercept_only, requestPtr); CefRefPtr scheme_factory = init_state_->iothread_state_->GetSchemeHandlerFactory(request->url); if (scheme_factory && !requestPtr) { requestPtr = MakeRequest(request, request_id, true); } // True if there's a possibility that the client might handle the request. const bool maybe_intercept_request = handler || scheme_factory; if (!maybe_intercept_request && requestPtr) { requestPtr = nullptr; } // May have a handler and/or scheme factory. state->Reset(handler, scheme_factory, requestPtr, request, request_was_redirected, std::move(cancel_callback)); if (handler) { state->cookie_filter_ = handler->GetCookieAccessFilter( init_state_->browser_, init_state_->frame_, requestPtr.get()); } auto exec_callback = base::BindOnce(std::move(callback), maybe_intercept_request, is_external ? true : intercept_only); if (!maybe_intercept_request) { // Cookies will be handled by the NetworkService. std::move(exec_callback).Run(); return; } MaybeLoadCookies(request_id, state, std::move(exec_callback)); } void MaybeLoadCookies(int32_t request_id, RequestState* state, base::OnceClosure callback) { CEF_REQUIRE_IOT(); if (!cookie_helper::IsCookieableScheme(state->request_->url, init_state_->cookieable_schemes_)) { // The scheme does not support cookies. std::move(callback).Run(); return; } // We need to load/save cookies ourselves for custom-handled requests, or // if we're using a cookie filter. auto allow_cookie_callback = state->cookie_filter_ ? base::BindRepeating( &InterceptedRequestHandlerWrapper::AllowCookieLoad, weak_ptr_factory_.GetWeakPtr(), request_id) : base::BindRepeating( &InterceptedRequestHandlerWrapper::AllowCookieAlways); auto done_cookie_callback = base::BindOnce( &InterceptedRequestHandlerWrapper::ContinueWithLoadedCookies, weak_ptr_factory_.GetWeakPtr(), request_id, std::move(callback)); cookie_helper::LoadCookies(init_state_->browser_context_getter_, *(state->request_), allow_cookie_callback, std::move(done_cookie_callback)); } static void AllowCookieAlways(const net::CanonicalCookie& cookie, bool* allow) { *allow = true; } void AllowCookieLoad(int32_t request_id, const net::CanonicalCookie& cookie, bool* allow) { CEF_REQUIRE_IOT(); RequestState* state = GetState(request_id); if (!state) { // The request may have been canceled while the async callback was // pending. return; } DCHECK(state->cookie_filter_); CefCookie cef_cookie; if (net_service::MakeCefCookie(cookie, cef_cookie)) { *allow = state->cookie_filter_->CanSendCookie( init_state_->browser_, init_state_->frame_, state->pending_request_.get(), cef_cookie); } } void ContinueWithLoadedCookies(int32_t request_id, base::OnceClosure callback, int total_count, net::CookieList allowed_cookies) { CEF_REQUIRE_IOT(); RequestState* state = GetState(request_id); if (!state) { // The request may have been canceled while the async callback was // pending. return; } if (state->cookie_filter_) { // Also add/save cookies ourselves for default-handled network requests // so that we can filter them. This will be a no-op for custom-handled // requests. state->request_->load_flags |= kLoadNoCookiesFlags; } if (!allowed_cookies.empty()) { const std::string& cookie_line = net::CanonicalCookie::BuildCookieLine(allowed_cookies); state->request_->headers.SetHeader(net::HttpRequestHeaders::kCookie, cookie_line); state->pending_request_->SetReadOnly(false); state->pending_request_->SetHeaderByName(net::HttpRequestHeaders::kCookie, cookie_line, true); state->pending_request_->SetReadOnly(true); } std::move(callback).Run(); } void ShouldInterceptRequest( int32_t request_id, network::ResourceRequest* request, ShouldInterceptRequestResultCallback callback) override { CEF_REQUIRE_IOT(); RequestState* state = GetState(request_id); if (!state) { // The request may have been canceled during destruction. return; } // Must have a handler and/or scheme factory. DCHECK(state->handler_ || state->scheme_factory_); DCHECK(state->pending_request_); if (state->handler_) { // The client may modify |pending_request_| before executing the callback. state->pending_request_->SetReadOnly(false); state->pending_request_->SetTrackChanges(true, true /* backup_on_change */); CefRefPtr callbackPtr = new RequestCallbackWrapper(base::BindOnce( &InterceptedRequestHandlerWrapper::ContinueShouldInterceptRequest, weak_ptr_factory_.GetWeakPtr(), request_id, std::move(callback))); cef_return_value_t retval = state->handler_->OnBeforeResourceLoad( init_state_->browser_, init_state_->frame_, state->pending_request_.get(), callbackPtr.get()); if (retval != RV_CONTINUE_ASYNC) { if (retval == RV_CONTINUE) { // Continue the request immediately. callbackPtr->Continue(); } else { // Cancel the request immediately. callbackPtr->Cancel(); } } } else { // The scheme factory may choose to handle it. ContinueShouldInterceptRequest(request_id, std::move(callback), true); } } void ContinueShouldInterceptRequest( int32_t request_id, ShouldInterceptRequestResultCallback callback, bool allow) { CEF_REQUIRE_IOT(); RequestState* state = GetState(request_id); if (!state) { // The request may have been canceled while the async callback was // pending. return; } // Must have a handler and/or scheme factory. DCHECK(state->handler_ || state->scheme_factory_); DCHECK(state->pending_request_); if (state->handler_) { if (allow) { // Apply any |requestPtr| changes to |request|. state->pending_request_->Get(state->request_, true /* changed_only */); } const bool redirect = (state->pending_request_->GetChanges() & CefRequestImpl::kChangedUrl); if (redirect) { // Revert any changes for now. We'll get them back after the redirect. state->pending_request_->RevertChanges(); } state->pending_request_->SetReadOnly(true); state->pending_request_->SetTrackChanges(false); if (!allow) { // Cancel the request. if (state->cancel_callback_) { std::move(state->cancel_callback_).Run(net::ERR_ABORTED); } return; } if (redirect) { // Performing a redirect. std::move(callback).Run(nullptr); return; } } CefRefPtr resource_handler; if (state->handler_) { // Does the client want to handle the request? resource_handler = state->handler_->GetResourceHandler( init_state_->browser_, init_state_->frame_, state->pending_request_.get()); } if (!resource_handler && state->scheme_factory_) { // Does the scheme factory want to handle the request? resource_handler = state->scheme_factory_->Create( init_state_->browser_, init_state_->frame_, state->request_->url.scheme(), state->pending_request_.get()); } std::unique_ptr resource_response; if (resource_handler) { resource_response = CreateResourceResponse(request_id, resource_handler); DCHECK(resource_response); state->was_custom_handled_ = true; } else if (state->accept_language_added_) { // The request will be handled by the NetworkService. Remove the // "Accept-Language" header here so that it can be re-added in // URLRequestHttpJob::AddExtraHeaders with correct ordering applied. state->request_->headers.RemoveHeader( net::HttpRequestHeaders::kAcceptLanguage); } // Continue the request. std::move(callback).Run(std::move(resource_response)); } void ProcessResponseHeaders(int32_t request_id, const network::ResourceRequest& request, const GURL& redirect_url, net::HttpResponseHeaders* headers) override { CEF_REQUIRE_IOT(); RequestState* state = GetState(request_id); if (!state) { // The request may have been canceled during destruction. return; } if (!state->handler_) { return; } if (!state->pending_response_) { state->pending_response_ = new CefResponseImpl(); } else { state->pending_response_->SetReadOnly(false); } if (headers) { state->pending_response_->SetResponseHeaders(*headers); } state->pending_response_->SetReadOnly(true); } void OnRequestResponse(int32_t request_id, network::ResourceRequest* request, net::HttpResponseHeaders* headers, std::optional redirect_info, OnRequestResponseResultCallback callback) override { CEF_REQUIRE_IOT(); RequestState* state = GetState(request_id); if (!state) { // The request may have been canceled during destruction. return; } if (state->cookie_filter_) { // Remove the flags that were added in ContinueWithLoadedCookies. request->load_flags &= ~kLoadNoCookiesFlags; } if (!state->handler_) { // Cookies may come from a scheme handler. MaybeSaveCookies( request_id, state, headers, base::BindOnce( std::move(callback), ResponseMode::CONTINUE, nullptr, redirect_info.has_value() ? redirect_info->new_url : GURL())); return; } DCHECK(state->pending_request_); DCHECK(state->pending_response_); if (redirect_info.has_value()) { HandleRedirect(request_id, state, headers, *redirect_info, std::move(callback)); } else { HandleResponse(request_id, state, headers, std::move(callback)); } } void HandleRedirect(int32_t request_id, RequestState* state, net::HttpResponseHeaders* headers, const net::RedirectInfo& redirect_info, OnRequestResponseResultCallback callback) { GURL new_url = redirect_info.new_url; CefString newUrl = redirect_info.new_url.spec(); CefString oldUrl = newUrl; bool url_changed = false; state->handler_->OnResourceRedirect( init_state_->browser_, init_state_->frame_, state->pending_request_.get(), state->pending_response_.get(), newUrl); if (newUrl != oldUrl) { // Also support relative URLs. const GURL& url = redirect_info.new_url.Resolve(newUrl.ToString()); if (url.is_valid()) { url_changed = true; new_url = url; } } // Update the |pending_request_| object with the new info. state->pending_request_->SetReadOnly(false); state->pending_request_->Set(redirect_info); if (url_changed) { state->pending_request_->SetURL(new_url.spec()); } state->pending_request_->SetReadOnly(true); auto exec_callback = base::BindOnce( std::move(callback), ResponseMode::CONTINUE, nullptr, new_url); MaybeSaveCookies(request_id, state, headers, std::move(exec_callback)); } void HandleResponse(int32_t request_id, RequestState* state, net::HttpResponseHeaders* headers, OnRequestResponseResultCallback callback) { // The client may modify |pending_request_| in OnResourceResponse. state->pending_request_->SetReadOnly(false); state->pending_request_->SetTrackChanges(true, true /* backup_on_change */); auto response_mode = ResponseMode::CONTINUE; GURL new_url; if (state->handler_->OnResourceResponse( init_state_->browser_, init_state_->frame_, state->pending_request_.get(), state->pending_response_.get())) { // The request may have been modified. const auto changes = state->pending_request_->GetChanges(); if (changes) { state->pending_request_->Get(state->request_, true /* changed_only */); if (changes & CefRequestImpl::kChangedUrl) { // Redirect to the new URL. new_url = GURL(state->pending_request_->GetURL().ToString()); } else { // Restart the request. response_mode = ResponseMode::RESTART; } } } // Revert any changes for now. We'll get them back after the redirect or // restart. state->pending_request_->RevertChanges(); state->pending_request_->SetReadOnly(true); state->pending_request_->SetTrackChanges(false); auto exec_callback = base::BindOnce(std::move(callback), response_mode, nullptr, new_url); if (response_mode == ResponseMode::RESTART) { // Get any cookies after the restart. std::move(exec_callback).Run(); return; } MaybeSaveCookies(request_id, state, headers, std::move(exec_callback)); } void MaybeSaveCookies(int32_t request_id, RequestState* state, net::HttpResponseHeaders* headers, base::OnceClosure callback) { CEF_REQUIRE_IOT(); if (!state->cookie_filter_ && !state->was_custom_handled_) { // The NetworkService saves the cookies for default-handled requests. std::move(callback).Run(); return; } if (!cookie_helper::IsCookieableScheme(state->request_->url, init_state_->cookieable_schemes_)) { // The scheme does not support cookies. std::move(callback).Run(); return; } // We need to load/save cookies ourselves for custom-handled requests, or // if we're using a cookie filter. auto allow_cookie_callback = state->cookie_filter_ ? base::BindRepeating( &InterceptedRequestHandlerWrapper::AllowCookieSave, weak_ptr_factory_.GetWeakPtr(), request_id) : base::BindRepeating( &InterceptedRequestHandlerWrapper::AllowCookieAlways); auto done_cookie_callback = base::BindOnce( &InterceptedRequestHandlerWrapper::ContinueWithSavedCookies, weak_ptr_factory_.GetWeakPtr(), request_id, std::move(callback)); cookie_helper::SaveCookies( init_state_->browser_context_getter_, *(state->request_), headers, allow_cookie_callback, std::move(done_cookie_callback)); } void AllowCookieSave(int32_t request_id, const net::CanonicalCookie& cookie, bool* allow) { CEF_REQUIRE_IOT(); RequestState* state = GetState(request_id); if (!state) { // The request may have been canceled while the async callback was // pending. return; } DCHECK(state->cookie_filter_); CefCookie cef_cookie; if (net_service::MakeCefCookie(cookie, cef_cookie)) { *allow = state->cookie_filter_->CanSaveCookie( init_state_->browser_, init_state_->frame_, state->pending_request_.get(), state->pending_response_.get(), cef_cookie); } } void ContinueWithSavedCookies(int32_t request_id, base::OnceClosure callback, int total_count, net::CookieList allowed_cookies) { CEF_REQUIRE_IOT(); std::move(callback).Run(); } mojo::ScopedDataPipeConsumerHandle OnFilterResponseBody( int32_t request_id, const network::ResourceRequest& request, mojo::ScopedDataPipeConsumerHandle body) override { CEF_REQUIRE_IOT(); RequestState* state = GetState(request_id); if (!state) { // The request may have been canceled during destruction. return body; } if (state->handler_) { auto filter = state->handler_->GetResourceResponseFilter( init_state_->browser_, init_state_->frame_, state->pending_request_.get(), state->pending_response_.get()); if (filter) { return CreateResponseFilterHandler( filter, std::move(body), base::BindOnce(&InterceptedRequestHandlerWrapper::OnFilterError, weak_ptr_factory_.GetWeakPtr(), request_id)); } } return body; } void OnFilterError(int32_t request_id) { CEF_REQUIRE_IOT(); RequestState* state = GetState(request_id); if (!state) { // The request may have been canceled while the async callback was // pending. return; } if (state->cancel_callback_) { std::move(state->cancel_callback_).Run(net::ERR_CONTENT_DECODING_FAILED); } } void OnRequestComplete( int32_t request_id, const network::ResourceRequest& request, const network::URLLoaderCompletionStatus& status) override { CEF_REQUIRE_IOT(); RequestState* state = GetState(request_id); if (!state) { // The request may have been aborted during initialization or canceled // during destruction. This method will always be called before a request // is deleted, so if the request is currently pending also remove it from // the list. if (!pending_requests_.empty()) { PendingRequests::iterator it = pending_requests_.begin(); for (; it != pending_requests_.end(); ++it) { if ((*it)->id_ == request_id) { pending_requests_.erase(it); break; } } } return; } const bool is_external = IsExternalRequest(&request); // Redirection of standard custom schemes is handled with a restart, so we // get completion notifications for both the original (redirected) request // and the final request. Don't report completion of the redirected request. const bool ignore_result = is_external && request.url.IsStandard() && status.error_code == net::ERR_ABORTED && state->pending_response_.get() && net::HttpResponseHeaders::IsRedirectResponseCode( state->pending_response_->GetStatus()); if (state->handler_ && !ignore_result) { DCHECK(state->pending_request_); CallHandlerOnComplete(state, status); if (status.error_code != 0 && status.error_code != ERR_ABORTED && is_external) { bool allow_os_execution = false; state->handler_->OnProtocolExecution( init_state_->browser_, init_state_->frame_, state->pending_request_.get(), allow_os_execution); if (allow_os_execution && init_state_->unhandled_request_callback_) { init_state_->unhandled_request_callback_.Run(); } } } RemoveState(request_id); } private: void CallHandlerOnComplete(RequestState* state, const network::URLLoaderCompletionStatus& status) { if (!state->handler_ || !state->pending_request_) { return; } // The request object may be currently flagged as writable in cases where we // abort a request that is waiting on a pending callack. if (!state->pending_request_->IsReadOnly()) { state->pending_request_->SetReadOnly(true); } if (!state->pending_response_) { // If the request failed there may not be a response object yet. state->pending_response_ = new CefResponseImpl(); } else { state->pending_response_->SetReadOnly(false); } state->pending_response_->SetError( static_cast(status.error_code)); state->pending_response_->SetReadOnly(true); state->handler_->OnResourceLoadComplete( init_state_->browser_, init_state_->frame_, state->pending_request_.get(), state->pending_response_.get(), status.error_code == 0 ? UR_SUCCESS : UR_FAILED, status.encoded_body_length); } // Returns the handler, if any, that should be used for this request. CefRefPtr GetHandler( int32_t request_id, network::ResourceRequest* request, bool* intercept_only, CefRefPtr& requestPtr) const { CefRefPtr handler; if (init_state_->browser_) { // Maybe the browser's client wants to handle it? CefRefPtr client = init_state_->browser_->GetHost()->GetClient(); if (client) { CefRefPtr request_handler = client->GetRequestHandler(); if (request_handler) { requestPtr = MakeRequest(request, request_id, true); handler = request_handler->GetResourceRequestHandler( init_state_->browser_, init_state_->frame_, requestPtr.get(), init_state_->is_navigation_, init_state_->is_download_, init_state_->request_initiator_, *intercept_only); } } } if (!handler) { // Maybe the request context wants to handle it? CefRefPtr context_handler = init_state_->iothread_state_->GetHandler( init_state_->global_id_, /*require_frame_match=*/false); if (context_handler) { if (!requestPtr) { requestPtr = MakeRequest(request, request_id, true); } handler = context_handler->GetResourceRequestHandler( init_state_->browser_, init_state_->frame_, requestPtr.get(), init_state_->is_navigation_, init_state_->is_download_, init_state_->request_initiator_, *intercept_only); } } return handler; } RequestState* GetOrCreateState(int32_t request_id) { RequestState* state = GetState(request_id); if (!state) { state = new RequestState(); request_map_.insert(std::make_pair(request_id, base::WrapUnique(state))); } return state; } RequestState* GetState(int32_t request_id) const { RequestMap::const_iterator it = request_map_.find(request_id); if (it != request_map_.end()) { return it->second.get(); } return nullptr; } void RemoveState(int32_t request_id) { RequestMap::iterator it = request_map_.find(request_id); DCHECK(it != request_map_.end()); if (it != request_map_.end()) { request_map_.erase(it); } } // Stop accepting new requests and cancel pending/in-flight requests when the // CEF context or associated browser is destroyed. void OnDestroyed() { CEF_REQUIRE_IOT(); DCHECK(init_state_); init_state_->DeleteDestructionObserver(); // Stop accepting new requests. shutting_down_ = true; // Stop the delivery of pending callbacks. weak_ptr_factory_.InvalidateWeakPtrs(); // Take ownership of any pending requests. PendingRequests pending_requests; pending_requests.swap(pending_requests_); // Take ownership of any in-progress requests. RequestMap request_map; request_map.swap(request_map_); // Notify handlers for in-progress requests. for (const auto& pair : request_map) { CallHandlerOnComplete( pair.second.get(), network::URLLoaderCompletionStatus(net::ERR_ABORTED)); } if (init_state_->browser_) { // Clear objects that reference the browser. init_state_->browser_ = nullptr; init_state_->frame_ = nullptr; } // Execute cancel callbacks and delete pending and in-progress requests. // This may result in the request being torn down sooner, or it may be // ignored if the request is already in the process of being torn down. When // the last callback is executed it may result in |this| being deleted. pending_requests.clear(); for (auto& pair : request_map) { auto state = std::move(pair.second); if (state->cancel_callback_) { // The cancel callback may trigger a call to OnRequestComplete followed // by destruction of the InterceptedRequest that owns the // ResourceRequest. We therefore need to clear the // raw_ptr first to avoid dangling pointer errors. state->request_ = nullptr; std::move(state->cancel_callback_).Run(net::ERR_ABORTED); } } } static CefRefPtr MakeRequest( const network::ResourceRequest* request, int64_t request_id, bool read_only) { CefRefPtr requestPtr = new CefRequestImpl(); requestPtr->Set(request, request_id); if (read_only) { requestPtr->SetReadOnly(true); } else { requestPtr->SetTrackChanges(true); } return requestPtr; } // Returns true if |request| cannot be handled internally. static bool IsExternalRequest(const network::ResourceRequest* request) { return !scheme::IsInternalHandledScheme(request->url.scheme()); } scoped_refptr init_helper_; std::unique_ptr init_state_; bool shutting_down_ = false; using RequestMap = std::map>; RequestMap request_map_; using PendingRequests = std::vector>; PendingRequests pending_requests_; base::WeakPtrFactory weak_ptr_factory_; }; } // namespace std::unique_ptr CreateInterceptedRequestHandler( content::BrowserContext* browser_context, content::RenderFrameHost* frame, int render_process_id, bool is_navigation, bool is_download, const url::Origin& request_initiator) { CEF_REQUIRE_UIT(); CHECK(browser_context); CefRefPtr browserPtr; CefRefPtr framePtr; // Default to handlers for the same process in case |frame| doesn't have an // associated CefBrowserHost. content::GlobalRenderFrameHostId global_id(render_process_id, MSG_ROUTING_NONE); // |frame| may be nullptr for service worker requests. if (frame) { browserPtr = CefBrowserHostBase::GetBrowserForHost(frame); if (browserPtr) { // May return nullptr for excluded view requests. framePtr = browserPtr->GetFrameForHost(frame); if (!framePtr) { framePtr = browserPtr->GetMainFrame(); } global_id = frame->GetGlobalId(); } } auto init_state = std::make_unique(); init_state->Initialize(browser_context, browserPtr, framePtr, global_id, is_navigation, is_download, request_initiator, base::RepeatingClosure()); auto wrapper = std::make_unique(); wrapper->init_helper()->MaybeSetInitialized(std::move(init_state)); return wrapper; } std::unique_ptr CreateInterceptedRequestHandler( content::WebContents::Getter web_contents_getter, int frame_tree_node_id, const network::ResourceRequest& request, const base::RepeatingClosure& unhandled_request_callback) { CEF_REQUIRE_UIT(); content::WebContents* web_contents = web_contents_getter.Run(); CHECK(web_contents); content::BrowserContext* browser_context = web_contents->GetBrowserContext(); CHECK(browser_context); content::RenderFrameHost* frame = nullptr; if (request.is_outermost_main_frame || static_cast(request.resource_type) == blink::mojom::ResourceType::kMainFrame) { frame = web_contents->GetPrimaryMainFrame(); CHECK(frame); } else { // May return nullptr for frames in inner WebContents. auto node = content::FrameTreeNode::GloballyFindByID(frame_tree_node_id); if (node) { frame = node->current_frame_host(); // RFHs can move between FrameTreeNodes. Make sure this one hasn't. See // documentation on RenderFrameHost::GetFrameTreeNodeId() for background. if (content::WebContents::FromRenderFrameHost(frame) != web_contents) { frame = nullptr; } } if (!frame) { // Use the main frame for the CefBrowserHost. frame = web_contents->GetPrimaryMainFrame(); CHECK(frame); } } CefRefPtr browserPtr; CefRefPtr framePtr; // Default to handlers for the same process in case |frame| doesn't have an // associated CefBrowserHost. content::GlobalRenderFrameHostId global_id(frame->GetProcess()->GetID(), MSG_ROUTING_NONE); browserPtr = CefBrowserHostBase::GetBrowserForHost(frame); if (browserPtr) { // May return nullptr for excluded view requests. framePtr = browserPtr->GetFrameForHost(frame); if (!framePtr) { framePtr = browserPtr->GetMainFrame(); } global_id = frame->GetGlobalId(); } const bool is_navigation = ui::PageTransitionIsNewNavigation( static_cast(request.transition_type)); // TODO(navigation): Can we determine the |is_download| value? const bool is_download = false; url::Origin request_initiator; if (request.request_initiator.has_value()) { request_initiator = *request.request_initiator; } auto init_state = std::make_unique(); init_state->Initialize(browser_context, browserPtr, framePtr, global_id, is_navigation, is_download, request_initiator, unhandled_request_callback); auto wrapper = std::make_unique(); wrapper->init_helper()->MaybeSetInitialized(std::move(init_state)); return wrapper; } } // namespace net_service