| // 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 "content/browser/devtools/protocol/network_handler.h" |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include "base/barrier_closure.h" |
| #include "base/base64.h" |
| #include "base/command_line.h" |
| #include "base/containers/hash_tables.h" |
| #include "base/containers/queue.h" |
| #include "base/process/process_handle.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/time/time.h" |
| #include "content/browser/devtools/devtools_interceptor_controller.h" |
| #include "content/browser/devtools/devtools_session.h" |
| #include "content/browser/devtools/protocol/page.h" |
| #include "content/browser/devtools/protocol/security.h" |
| #include "content/browser/frame_host/frame_tree_node.h" |
| #include "content/browser/frame_host/navigation_request.h" |
| #include "content/browser/frame_host/render_frame_host_impl.h" |
| #include "content/common/navigation_params.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/browsing_data_remover.h" |
| #include "content/public/browser/content_browser_client.h" |
| #include "content/public/browser/global_request_id.h" |
| #include "content/public/browser/navigation_handle.h" |
| #include "content/public/browser/network_service_instance.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/resource_context.h" |
| #include "content/public/browser/service_worker_context.h" |
| #include "content/public/browser/site_instance.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/common/content_client.h" |
| #include "content/public/common/content_switches.h" |
| #include "net/base/net_errors.h" |
| #include "net/base/upload_bytes_element_reader.h" |
| #include "net/cookies/canonical_cookie.h" |
| #include "net/cookies/cookie_store.h" |
| #include "net/http/http_response_headers.h" |
| #include "net/http/http_util.h" |
| #include "net/url_request/url_request_context.h" |
| #include "net/url_request/url_request_context_getter.h" |
| #include "services/network/public/cpp/http_raw_request_response_info.h" |
| #include "services/network/public/cpp/resource_request.h" |
| #include "services/network/public/cpp/resource_response.h" |
| #include "services/network/public/cpp/url_loader_completion_status.h" |
| |
| namespace content { |
| namespace protocol { |
| |
| namespace { |
| |
| using ProtocolCookieArray = Array<Network::Cookie>; |
| using GetCookiesCallback = Network::Backend::GetCookiesCallback; |
| using GetAllCookiesCallback = Network::Backend::GetAllCookiesCallback; |
| using SetCookieCallback = Network::Backend::SetCookieCallback; |
| using SetCookiesCallback = Network::Backend::SetCookiesCallback; |
| using DeleteCookiesCallback = Network::Backend::DeleteCookiesCallback; |
| using ClearBrowserCookiesCallback = |
| Network::Backend::ClearBrowserCookiesCallback; |
| |
| const char kDevToolsEmulateNetworkConditionsClientId[] = |
| "X-DevTools-Emulate-Network-Conditions-Client-Id"; |
| |
| class CookieRetriever : public base::RefCountedThreadSafe<CookieRetriever> { |
| public: |
| CookieRetriever(std::unique_ptr<GetCookiesCallback> callback) |
| : callback_(std::move(callback)), |
| all_callback_(nullptr) {} |
| |
| CookieRetriever(std::unique_ptr<GetAllCookiesCallback> callback) |
| : callback_(nullptr), |
| all_callback_(std::move(callback)) {} |
| |
| void RetrieveCookiesOnIO( |
| net::URLRequestContextGetter* context_getter, |
| const std::vector<GURL>& urls) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| callback_count_ = urls.size(); |
| |
| if (callback_count_ == 0) { |
| GotAllCookies(); |
| return; |
| } |
| |
| for (const GURL& url : urls) { |
| net::URLRequestContext* request_context = |
| context_getter->GetURLRequestContext(); |
| request_context->cookie_store()->GetAllCookiesForURLAsync( |
| url, base::BindOnce(&CookieRetriever::GotCookies, this)); |
| } |
| } |
| |
| void RetrieveAllCookiesOnIO( |
| net::URLRequestContextGetter* context_getter) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| callback_count_ = 1; |
| |
| net::URLRequestContext* request_context = |
| context_getter->GetURLRequestContext(); |
| request_context->cookie_store()->GetAllCookiesAsync( |
| base::BindOnce(&CookieRetriever::GotCookies, this)); |
| } |
| protected: |
| virtual ~CookieRetriever() {} |
| |
| void GotCookies(const net::CookieList& cookie_list) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| for (const net::CanonicalCookie& cookie : cookie_list) { |
| std::string key = base::StringPrintf( |
| "%s::%s::%s::%d", cookie.Name().c_str(), cookie.Domain().c_str(), |
| cookie.Path().c_str(), cookie.IsSecure()); |
| cookies_[key] = cookie; |
| } |
| |
| --callback_count_; |
| if (callback_count_ == 0) |
| GotAllCookies(); |
| } |
| |
| void GotAllCookies() { |
| net::CookieList master_cookie_list; |
| for (const auto& pair : cookies_) |
| master_cookie_list.push_back(pair.second); |
| |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, |
| base::BindOnce(&CookieRetriever::SendCookiesResponseOnUI, this, |
| master_cookie_list)); |
| } |
| |
| void SendCookiesResponseOnUI(const net::CookieList& cookie_list) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| std::unique_ptr<ProtocolCookieArray> cookies = |
| ProtocolCookieArray::create(); |
| |
| for (const net::CanonicalCookie& cookie : cookie_list) { |
| std::unique_ptr<Network::Cookie> devtools_cookie = |
| Network::Cookie::Create() |
| .SetName(cookie.Name()) |
| .SetValue(cookie.Value()) |
| .SetDomain(cookie.Domain()) |
| .SetPath(cookie.Path()) |
| .SetExpires(cookie.ExpiryDate().is_null() ? -1 : cookie.ExpiryDate().ToDoubleT()) |
| .SetSize(cookie.Name().length() + cookie.Value().length()) |
| .SetHttpOnly(cookie.IsHttpOnly()) |
| .SetSecure(cookie.IsSecure()) |
| .SetSession(!cookie.IsPersistent()) |
| .Build(); |
| |
| switch (cookie.SameSite()) { |
| case net::CookieSameSite::STRICT_MODE: |
| devtools_cookie->SetSameSite(Network::CookieSameSiteEnum::Strict); |
| break; |
| case net::CookieSameSite::LAX_MODE: |
| devtools_cookie->SetSameSite(Network::CookieSameSiteEnum::Lax); |
| break; |
| case net::CookieSameSite::NO_RESTRICTION: |
| break; |
| } |
| |
| cookies->addItem(std::move(devtools_cookie)); |
| } |
| |
| if (callback_) { |
| callback_->sendSuccess(std::move(cookies)); |
| } else { |
| DCHECK(all_callback_); |
| all_callback_->sendSuccess(std::move(cookies)); |
| } |
| } |
| |
| std::unique_ptr<GetCookiesCallback> callback_; |
| std::unique_ptr<GetAllCookiesCallback> all_callback_; |
| int callback_count_ = 0; |
| std::unordered_map<std::string, net::CanonicalCookie> cookies_; |
| |
| private: |
| friend class base::RefCountedThreadSafe<CookieRetriever>; |
| }; |
| |
| void ClearedCookiesOnIO(std::unique_ptr<ClearBrowserCookiesCallback> callback, |
| uint32_t num_deleted) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, |
| base::BindOnce(&ClearBrowserCookiesCallback::sendSuccess, |
| std::move(callback))); |
| } |
| |
| void ClearCookiesOnIO(net::URLRequestContextGetter* context_getter, |
| std::unique_ptr<ClearBrowserCookiesCallback> callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| net::URLRequestContext* request_context = |
| context_getter->GetURLRequestContext(); |
| request_context->cookie_store()->DeleteAllAsync( |
| base::BindOnce(&ClearedCookiesOnIO, base::Passed(std::move(callback)))); |
| } |
| |
| void DeletedCookiesOnIO(base::OnceClosure callback, uint32_t num_deleted) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, std::move(callback)); |
| } |
| |
| void DeleteSelectedCookiesOnIO(net::URLRequestContextGetter* context_getter, |
| const std::string& name, |
| const std::string& url_spec, |
| const std::string& domain, |
| const std::string& path, |
| base::OnceClosure callback, |
| const net::CookieList& cookie_list) { |
| net::URLRequestContext* request_context = |
| context_getter->GetURLRequestContext(); |
| std::string normalized_domain = domain; |
| if (normalized_domain.empty()) { |
| GURL url(url_spec); |
| if (!url.SchemeIsHTTPOrHTTPS()) { |
| std::move(callback).Run(); |
| return; |
| } |
| normalized_domain = url.host(); |
| } |
| |
| net::CookieList filtered_list; |
| for (const auto& cookie : cookie_list) { |
| if (cookie.Name() != name) |
| continue; |
| if (cookie.Domain() != normalized_domain) |
| continue; |
| if (!path.empty() && cookie.Path() != path) |
| continue; |
| filtered_list.push_back(cookie); |
| } |
| |
| for (size_t i = 0; i < filtered_list.size(); ++i) { |
| const auto& cookie = filtered_list[i]; |
| base::OnceCallback<void(uint32_t)> once_callback; |
| if (i == filtered_list.size() - 1) |
| once_callback = base::BindOnce(&DeletedCookiesOnIO, std::move(callback)); |
| request_context->cookie_store()->DeleteCanonicalCookieAsync( |
| cookie, std::move(once_callback)); |
| } |
| if (!filtered_list.size()) |
| std::move(callback).Run(); |
| } |
| |
| void DeleteCookiesOnIO(net::URLRequestContextGetter* context_getter, |
| const std::string& name, |
| const std::string& url, |
| const std::string& domain, |
| const std::string& path, |
| base::OnceClosure callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| net::URLRequestContext* request_context = |
| context_getter->GetURLRequestContext(); |
| |
| request_context->cookie_store()->GetAllCookiesAsync(base::BindOnce( |
| &DeleteSelectedCookiesOnIO, base::Unretained(context_getter), name, url, |
| domain, path, std::move(callback))); |
| } |
| |
| void CookieSetOnIO(std::unique_ptr<SetCookieCallback> callback, bool success) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, |
| base::BindOnce(&SetCookieCallback::sendSuccess, |
| std::move(callback), success)); |
| } |
| |
| void SetCookieOnIO(net::URLRequestContextGetter* context_getter, |
| const std::string& name, |
| const std::string& value, |
| const std::string& url_spec, |
| const std::string& domain, |
| const std::string& path, |
| bool secure, |
| bool http_only, |
| const std::string& same_site, |
| double expires, |
| base::OnceCallback<void(bool)> callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| net::URLRequestContext* request_context = |
| context_getter->GetURLRequestContext(); |
| |
| if (url_spec.empty() && domain.empty()) { |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| std::string normalized_domain = domain; |
| if (!url_spec.empty()) { |
| GURL source_url = GURL(url_spec); |
| if (!source_url.SchemeIsHTTPOrHTTPS()) { |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| secure = secure || source_url.SchemeIsCryptographic(); |
| if (normalized_domain.empty()) |
| normalized_domain = source_url.host(); |
| } |
| |
| GURL url = GURL((secure ? "https://" : "http://") + normalized_domain); |
| if (!normalized_domain.empty() && normalized_domain[0] != '.') |
| normalized_domain = ""; |
| |
| base::Time expiration_date; |
| if (expires >= 0) { |
| expiration_date = |
| expires ? base::Time::FromDoubleT(expires) : base::Time::UnixEpoch(); |
| } |
| |
| net::CookieSameSite css = net::CookieSameSite::NO_RESTRICTION; |
| if (same_site == Network::CookieSameSiteEnum::Lax) |
| css = net::CookieSameSite::LAX_MODE; |
| if (same_site == Network::CookieSameSiteEnum::Strict) |
| css = net::CookieSameSite::STRICT_MODE; |
| |
| std::unique_ptr<net::CanonicalCookie> cc( |
| net::CanonicalCookie::CreateSanitizedCookie( |
| url, name, value, normalized_domain, path, base::Time(), |
| expiration_date, base::Time(), secure, http_only, css, |
| net::COOKIE_PRIORITY_DEFAULT)); |
| if (!cc) { |
| std::move(callback).Run(false); |
| return; |
| } |
| request_context->cookie_store()->SetCanonicalCookieAsync( |
| std::move(cc), secure, true /*modify_http_only*/, std::move(callback)); |
| } |
| |
| void CookiesSetOnIO(std::unique_ptr<SetCookiesCallback> callback, |
| bool success) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, |
| base::BindOnce(&SetCookiesCallback::sendSuccess, std::move(callback))); |
| } |
| |
| void SetCookiesOnIO( |
| net::URLRequestContextGetter* context_getter, |
| std::unique_ptr<protocol::Array<Network::CookieParam>> cookies, |
| base::OnceCallback<void(bool)> callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| for (size_t i = 0; i < cookies->length(); i++) { |
| Network::CookieParam* cookie = cookies->get(i); |
| |
| base::OnceCallback<void(bool)> once_callback; |
| if (i == cookies->length() - 1) |
| once_callback = std::move(callback); |
| |
| SetCookieOnIO(context_getter, cookie->GetName(), cookie->GetValue(), |
| cookie->GetUrl(""), cookie->GetDomain(""), |
| cookie->GetPath(""), cookie->GetSecure(false), |
| cookie->GetHttpOnly(false), cookie->GetSameSite(""), |
| cookie->GetExpires(-1), std::move(once_callback)); |
| } |
| } |
| |
| std::vector<GURL> ComputeCookieURLs(RenderFrameHostImpl* frame_host, |
| Maybe<Array<String>>& protocol_urls) { |
| std::vector<GURL> urls; |
| |
| if (protocol_urls.isJust()) { |
| std::unique_ptr<Array<std::string>> actual_urls = protocol_urls.takeJust(); |
| |
| for (size_t i = 0; i < actual_urls->length(); i++) |
| urls.push_back(GURL(actual_urls->get(i))); |
| } else { |
| base::queue<FrameTreeNode*> queue; |
| queue.push(frame_host->frame_tree_node()); |
| while (!queue.empty()) { |
| FrameTreeNode* node = queue.front(); |
| queue.pop(); |
| |
| urls.push_back(node->current_url()); |
| for (size_t i = 0; i < node->child_count(); ++i) |
| queue.push(node->child_at(i)); |
| } |
| } |
| |
| return urls; |
| } |
| |
| String resourcePriority(net::RequestPriority priority) { |
| switch (priority) { |
| case net::MINIMUM_PRIORITY: |
| case net::IDLE: |
| return Network::ResourcePriorityEnum::VeryLow; |
| case net::LOWEST: |
| return Network::ResourcePriorityEnum::Low; |
| case net::LOW: |
| return Network::ResourcePriorityEnum::Medium; |
| case net::MEDIUM: |
| return Network::ResourcePriorityEnum::High; |
| case net::HIGHEST: |
| return Network::ResourcePriorityEnum::VeryHigh; |
| } |
| NOTREACHED(); |
| return Network::ResourcePriorityEnum::Medium; |
| } |
| |
| String referrerPolicy(blink::WebReferrerPolicy referrer_policy) { |
| switch (referrer_policy) { |
| case blink::kWebReferrerPolicyAlways: |
| return Network::Request::ReferrerPolicyEnum::UnsafeUrl; |
| case blink::kWebReferrerPolicyDefault: |
| if (base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kReducedReferrerGranularity)) { |
| return Network::Request::ReferrerPolicyEnum:: |
| StrictOriginWhenCrossOrigin; |
| } else { |
| return Network::Request::ReferrerPolicyEnum::NoReferrerWhenDowngrade; |
| } |
| case blink::kWebReferrerPolicyNoReferrerWhenDowngrade: |
| return Network::Request::ReferrerPolicyEnum::NoReferrerWhenDowngrade; |
| case blink::kWebReferrerPolicyNever: |
| return Network::Request::ReferrerPolicyEnum::NoReferrer; |
| case blink::kWebReferrerPolicyOrigin: |
| return Network::Request::ReferrerPolicyEnum::Origin; |
| case blink::kWebReferrerPolicyOriginWhenCrossOrigin: |
| return Network::Request::ReferrerPolicyEnum::OriginWhenCrossOrigin; |
| case blink::kWebReferrerPolicySameOrigin: |
| return Network::Request::ReferrerPolicyEnum::SameOrigin; |
| case blink::kWebReferrerPolicyStrictOrigin: |
| return Network::Request::ReferrerPolicyEnum::StrictOrigin; |
| case blink::kWebReferrerPolicyNoReferrerWhenDowngradeOriginWhenCrossOrigin: |
| return Network::Request::ReferrerPolicyEnum::StrictOriginWhenCrossOrigin; |
| } |
| NOTREACHED(); |
| return Network::Request::ReferrerPolicyEnum::NoReferrerWhenDowngrade; |
| } |
| |
| String referrerPolicy(net::URLRequest::ReferrerPolicy referrer_policy) { |
| switch (referrer_policy) { |
| case net::URLRequest::CLEAR_REFERRER_ON_TRANSITION_FROM_SECURE_TO_INSECURE: |
| return Network::Request::ReferrerPolicyEnum::NoReferrerWhenDowngrade; |
| case net::URLRequest:: |
| REDUCE_REFERRER_GRANULARITY_ON_TRANSITION_CROSS_ORIGIN: |
| return Network::Request::ReferrerPolicyEnum::StrictOriginWhenCrossOrigin; |
| case net::URLRequest::ORIGIN_ONLY_ON_TRANSITION_CROSS_ORIGIN: |
| return Network::Request::ReferrerPolicyEnum::OriginWhenCrossOrigin; |
| case net::URLRequest::NEVER_CLEAR_REFERRER: |
| return Network::Request::ReferrerPolicyEnum::Origin; |
| case net::URLRequest::ORIGIN: |
| return Network::Request::ReferrerPolicyEnum::Origin; |
| case net::URLRequest::NO_REFERRER: |
| return Network::Request::ReferrerPolicyEnum::NoReferrer; |
| default: |
| break; |
| } |
| NOTREACHED(); |
| return Network::Request::ReferrerPolicyEnum::NoReferrerWhenDowngrade; |
| } |
| |
| String securityState(const GURL& url, const net::CertStatus& cert_status) { |
| if (!url.SchemeIsCryptographic()) |
| return Security::SecurityStateEnum::Neutral; |
| if (net::IsCertStatusError(cert_status) && |
| !net::IsCertStatusMinorError(cert_status)) { |
| return Security::SecurityStateEnum::Insecure; |
| } |
| return Security::SecurityStateEnum::Secure; |
| } |
| |
| DevToolsURLRequestInterceptor::InterceptionStage ToInterceptorStage( |
| const protocol::Network::InterceptionStage& interceptor_stage) { |
| if (interceptor_stage == protocol::Network::InterceptionStageEnum::Request) |
| return DevToolsURLRequestInterceptor::REQUEST; |
| if (interceptor_stage == |
| protocol::Network::InterceptionStageEnum::HeadersReceived) |
| return DevToolsURLRequestInterceptor::RESPONSE; |
| NOTREACHED(); |
| return DevToolsURLRequestInterceptor::REQUEST; |
| } |
| |
| net::Error NetErrorFromString(const std::string& error, bool* ok) { |
| *ok = true; |
| if (error == Network::ErrorReasonEnum::Failed) |
| return net::ERR_FAILED; |
| if (error == Network::ErrorReasonEnum::Aborted) |
| return net::ERR_ABORTED; |
| if (error == Network::ErrorReasonEnum::TimedOut) |
| return net::ERR_TIMED_OUT; |
| if (error == Network::ErrorReasonEnum::AccessDenied) |
| return net::ERR_ACCESS_DENIED; |
| if (error == Network::ErrorReasonEnum::ConnectionClosed) |
| return net::ERR_CONNECTION_CLOSED; |
| if (error == Network::ErrorReasonEnum::ConnectionReset) |
| return net::ERR_CONNECTION_RESET; |
| if (error == Network::ErrorReasonEnum::ConnectionRefused) |
| return net::ERR_CONNECTION_REFUSED; |
| if (error == Network::ErrorReasonEnum::ConnectionAborted) |
| return net::ERR_CONNECTION_ABORTED; |
| if (error == Network::ErrorReasonEnum::ConnectionFailed) |
| return net::ERR_CONNECTION_FAILED; |
| if (error == Network::ErrorReasonEnum::NameNotResolved) |
| return net::ERR_NAME_NOT_RESOLVED; |
| if (error == Network::ErrorReasonEnum::InternetDisconnected) |
| return net::ERR_INTERNET_DISCONNECTED; |
| if (error == Network::ErrorReasonEnum::AddressUnreachable) |
| return net::ERR_ADDRESS_UNREACHABLE; |
| *ok = false; |
| return net::ERR_FAILED; |
| } |
| |
| String NetErrorToString(int net_error) { |
| if (net_error == net::ERR_ABORTED) |
| return Network::ErrorReasonEnum::Aborted; |
| if (net_error == net::ERR_TIMED_OUT) |
| return Network::ErrorReasonEnum::TimedOut; |
| if (net_error == net::ERR_ACCESS_DENIED) |
| return Network::ErrorReasonEnum::AccessDenied; |
| if (net_error == net::ERR_CONNECTION_CLOSED) |
| return Network::ErrorReasonEnum::ConnectionClosed; |
| if (net_error == net::ERR_CONNECTION_RESET) |
| return Network::ErrorReasonEnum::ConnectionReset; |
| if (net_error == net::ERR_CONNECTION_REFUSED) |
| return Network::ErrorReasonEnum::ConnectionRefused; |
| if (net_error == net::ERR_CONNECTION_ABORTED) |
| return Network::ErrorReasonEnum::ConnectionAborted; |
| if (net_error == net::ERR_CONNECTION_FAILED) |
| return Network::ErrorReasonEnum::ConnectionFailed; |
| if (net_error == net::ERR_NAME_NOT_RESOLVED) |
| return Network::ErrorReasonEnum::NameNotResolved; |
| if (net_error == net::ERR_INTERNET_DISCONNECTED) |
| return Network::ErrorReasonEnum::InternetDisconnected; |
| if (net_error == net::ERR_ADDRESS_UNREACHABLE) |
| return Network::ErrorReasonEnum::AddressUnreachable; |
| return Network::ErrorReasonEnum::Failed; |
| } |
| |
| bool AddInterceptedResourceType( |
| const std::string& resource_type, |
| base::flat_set<ResourceType>* intercepted_resource_types) { |
| if (resource_type == protocol::Page::ResourceTypeEnum::Document) { |
| intercepted_resource_types->insert(RESOURCE_TYPE_MAIN_FRAME); |
| intercepted_resource_types->insert(RESOURCE_TYPE_SUB_FRAME); |
| return true; |
| } |
| if (resource_type == protocol::Page::ResourceTypeEnum::Stylesheet) { |
| intercepted_resource_types->insert(RESOURCE_TYPE_STYLESHEET); |
| return true; |
| } |
| if (resource_type == protocol::Page::ResourceTypeEnum::Image) { |
| intercepted_resource_types->insert(RESOURCE_TYPE_IMAGE); |
| return true; |
| } |
| if (resource_type == protocol::Page::ResourceTypeEnum::Media) { |
| intercepted_resource_types->insert(RESOURCE_TYPE_MEDIA); |
| return true; |
| } |
| if (resource_type == protocol::Page::ResourceTypeEnum::Font) { |
| intercepted_resource_types->insert(RESOURCE_TYPE_FONT_RESOURCE); |
| return true; |
| } |
| if (resource_type == protocol::Page::ResourceTypeEnum::Script) { |
| intercepted_resource_types->insert(RESOURCE_TYPE_SCRIPT); |
| return true; |
| } |
| if (resource_type == protocol::Page::ResourceTypeEnum::XHR) { |
| intercepted_resource_types->insert(RESOURCE_TYPE_XHR); |
| return true; |
| } |
| if (resource_type == protocol::Page::ResourceTypeEnum::Fetch) { |
| intercepted_resource_types->insert(RESOURCE_TYPE_PREFETCH); |
| return true; |
| } |
| if (resource_type == protocol::Page::ResourceTypeEnum::Other) { |
| intercepted_resource_types->insert(RESOURCE_TYPE_SUB_RESOURCE); |
| intercepted_resource_types->insert(RESOURCE_TYPE_OBJECT); |
| intercepted_resource_types->insert(RESOURCE_TYPE_WORKER); |
| intercepted_resource_types->insert(RESOURCE_TYPE_SHARED_WORKER); |
| intercepted_resource_types->insert(RESOURCE_TYPE_FAVICON); |
| intercepted_resource_types->insert(RESOURCE_TYPE_PING); |
| intercepted_resource_types->insert(RESOURCE_TYPE_SERVICE_WORKER); |
| intercepted_resource_types->insert(RESOURCE_TYPE_CSP_REPORT); |
| intercepted_resource_types->insert(RESOURCE_TYPE_PLUGIN_RESOURCE); |
| return true; |
| } |
| return false; |
| } |
| |
| double timeDelta(base::TimeTicks time, |
| base::TimeTicks start, |
| double invalid_value = -1) { |
| return time.is_null() ? invalid_value : (time - start).InMillisecondsF(); |
| } |
| |
| std::unique_ptr<Network::ResourceTiming> getTiming( |
| const net::LoadTimingInfo& load_timing) { |
| const base::TimeTicks kNullTicks; |
| return Network::ResourceTiming::Create() |
| .SetRequestTime((load_timing.request_start - kNullTicks).InSecondsF()) |
| .SetProxyStart( |
| timeDelta(load_timing.proxy_resolve_start, load_timing.request_start)) |
| .SetProxyEnd( |
| timeDelta(load_timing.proxy_resolve_end, load_timing.request_start)) |
| .SetDnsStart(timeDelta(load_timing.connect_timing.dns_start, |
| load_timing.request_start)) |
| .SetDnsEnd(timeDelta(load_timing.connect_timing.dns_end, |
| load_timing.request_start)) |
| .SetConnectStart(timeDelta(load_timing.connect_timing.connect_start, |
| load_timing.request_start)) |
| .SetConnectEnd(timeDelta(load_timing.connect_timing.connect_end, |
| load_timing.request_start)) |
| .SetSslStart(timeDelta(load_timing.connect_timing.ssl_start, |
| load_timing.request_start)) |
| .SetSslEnd(timeDelta(load_timing.connect_timing.ssl_end, |
| load_timing.request_start)) |
| .SetWorkerStart(-1) |
| .SetWorkerReady(-1) |
| .SetSendStart( |
| timeDelta(load_timing.send_start, load_timing.request_start)) |
| .SetSendEnd(timeDelta(load_timing.send_end, load_timing.request_start)) |
| .SetPushStart( |
| timeDelta(load_timing.push_start, load_timing.request_start, 0)) |
| .SetPushEnd(timeDelta(load_timing.push_end, load_timing.request_start, 0)) |
| .SetReceiveHeadersEnd( |
| timeDelta(load_timing.receive_headers_end, load_timing.request_start)) |
| .Build(); |
| } |
| |
| std::unique_ptr<Object> getHeaders(const base::StringPairs& pairs) { |
| std::unique_ptr<DictionaryValue> headers_dict(DictionaryValue::create()); |
| for (const auto& pair : pairs) { |
| headers_dict->setString(pair.first, pair.second); |
| } |
| return Object::fromValue(headers_dict.get(), nullptr); |
| } |
| |
| String getProtocol(const GURL& url, const network::ResourceResponseHead& head) { |
| std::string protocol = head.alpn_negotiated_protocol; |
| if (protocol.empty() || protocol == "unknown") { |
| if (head.was_fetched_via_spdy) { |
| protocol = "spdy"; |
| } else if (url.SchemeIsHTTPOrHTTPS()) { |
| protocol = "http"; |
| if (head.headers->GetHttpVersion() == net::HttpVersion(0, 9)) |
| protocol = "http/0.9"; |
| else if (head.headers->GetHttpVersion() == net::HttpVersion(1, 0)) |
| protocol = "http/1.0"; |
| else if (head.headers->GetHttpVersion() == net::HttpVersion(1, 1)) |
| protocol = "http/1.1"; |
| } else { |
| protocol = url.scheme(); |
| } |
| } |
| return protocol; |
| } |
| |
| class NetworkNavigationThrottle : public content::NavigationThrottle { |
| public: |
| NetworkNavigationThrottle( |
| base::WeakPtr<protocol::NetworkHandler> network_handler, |
| content::NavigationHandle* navigation_handle) |
| : content::NavigationThrottle(navigation_handle), |
| network_handler_(network_handler) {} |
| |
| ~NetworkNavigationThrottle() override {} |
| |
| // content::NavigationThrottle implementation: |
| NavigationThrottle::ThrottleCheckResult WillProcessResponse() override { |
| if (network_handler_ && network_handler_->ShouldCancelNavigation( |
| navigation_handle()->GetGlobalRequestID())) { |
| return CANCEL_AND_IGNORE; |
| } |
| return PROCEED; |
| } |
| |
| const char* GetNameForLogging() override { |
| return "DevToolsNetworkNavigationThrottle"; |
| } |
| |
| private: |
| base::WeakPtr<protocol::NetworkHandler> network_handler_; |
| DISALLOW_COPY_AND_ASSIGN(NetworkNavigationThrottle); |
| }; |
| |
| void ConfigureServiceWorkerContextOnIO() { |
| std::set<std::string> headers; |
| headers.insert(kDevToolsEmulateNetworkConditionsClientId); |
| content::ServiceWorkerContext::AddExcludedHeadersForFetchEvent(headers); |
| } |
| |
| } // namespace |
| |
| NetworkHandler::NetworkHandler(const std::string& host_id) |
| : DevToolsDomainHandler(Network::Metainfo::domainName), |
| browser_context_(nullptr), |
| storage_partition_(nullptr), |
| host_(nullptr), |
| enabled_(false), |
| host_id_(host_id), |
| bypass_service_worker_(false), |
| cache_disabled_(false), |
| weak_factory_(this) { |
| static bool have_configured_service_worker_context = false; |
| if (have_configured_service_worker_context) |
| return; |
| have_configured_service_worker_context = true; |
| BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, |
| base::BindOnce(&ConfigureServiceWorkerContextOnIO)); |
| } |
| |
| NetworkHandler::~NetworkHandler() { |
| } |
| |
| // static |
| std::vector<NetworkHandler*> NetworkHandler::ForAgentHost( |
| DevToolsAgentHostImpl* host) { |
| return DevToolsSession::HandlersForAgentHost<NetworkHandler>( |
| host, Network::Metainfo::domainName); |
| } |
| |
| void NetworkHandler::Wire(UberDispatcher* dispatcher) { |
| frontend_.reset(new Network::Frontend(dispatcher->channel())); |
| Network::Dispatcher::wire(dispatcher, this); |
| } |
| |
| void NetworkHandler::SetRenderer(int render_process_host_id, |
| RenderFrameHostImpl* frame_host) { |
| RenderProcessHost* process_host = |
| RenderProcessHost::FromID(render_process_host_id); |
| if (process_host) { |
| storage_partition_ = process_host->GetStoragePartition(); |
| browser_context_ = process_host->GetBrowserContext(); |
| } else { |
| storage_partition_ = nullptr; |
| browser_context_ = nullptr; |
| } |
| host_ = frame_host; |
| } |
| |
| Response NetworkHandler::Enable(Maybe<int> max_total_size, |
| Maybe<int> max_resource_size, |
| Maybe<int> max_post_data_size) { |
| enabled_ = true; |
| return Response::FallThrough(); |
| } |
| |
| Response NetworkHandler::Disable() { |
| enabled_ = false; |
| user_agent_ = std::string(); |
| interception_handle_.reset(); |
| SetNetworkConditions(nullptr); |
| extra_headers_.clear(); |
| return Response::FallThrough(); |
| } |
| |
| Response NetworkHandler::SetCacheDisabled(bool cache_disabled) { |
| cache_disabled_ = cache_disabled; |
| return Response::FallThrough(); |
| } |
| |
| class DevtoolsClearCacheObserver |
| : public content::BrowsingDataRemover::Observer { |
| public: |
| explicit DevtoolsClearCacheObserver( |
| content::BrowsingDataRemover* remover, |
| std::unique_ptr<NetworkHandler::ClearBrowserCacheCallback> callback) |
| : remover_(remover), callback_(std::move(callback)) { |
| remover_->AddObserver(this); |
| } |
| |
| ~DevtoolsClearCacheObserver() override { remover_->RemoveObserver(this); } |
| void OnBrowsingDataRemoverDone() override { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| callback_->sendSuccess(); |
| delete this; |
| } |
| |
| private: |
| content::BrowsingDataRemover* remover_; |
| std::unique_ptr<NetworkHandler::ClearBrowserCacheCallback> callback_; |
| }; |
| |
| void NetworkHandler::ClearBrowserCache( |
| std::unique_ptr<ClearBrowserCacheCallback> callback) { |
| if (!browser_context_) { |
| callback->sendFailure(Response::InternalError()); |
| return; |
| } |
| content::BrowsingDataRemover* remover = |
| content::BrowserContext::GetBrowsingDataRemover(browser_context_); |
| remover->RemoveAndReply( |
| base::Time(), base::Time::Max(), |
| content::BrowsingDataRemover::DATA_TYPE_CACHE, |
| content::BrowsingDataRemover::ORIGIN_TYPE_UNPROTECTED_WEB, |
| new DevtoolsClearCacheObserver(remover, std::move(callback))); |
| } |
| |
| void NetworkHandler::ClearBrowserCookies( |
| std::unique_ptr<ClearBrowserCookiesCallback> callback) { |
| if (!storage_partition_) { |
| callback->sendFailure(Response::InternalError()); |
| return; |
| } |
| |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::BindOnce( |
| &ClearCookiesOnIO, |
| base::Unretained(storage_partition_->GetURLRequestContext()), |
| std::move(callback))); |
| } |
| |
| void NetworkHandler::GetCookies(Maybe<Array<String>> protocol_urls, |
| std::unique_ptr<GetCookiesCallback> callback) { |
| if (!host_) { |
| callback->sendFailure(Response::InternalError()); |
| return; |
| } |
| |
| std::vector<GURL> urls = ComputeCookieURLs(host_, protocol_urls); |
| scoped_refptr<CookieRetriever> retriever = |
| new CookieRetriever(std::move(callback)); |
| |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::BindOnce( |
| &CookieRetriever::RetrieveCookiesOnIO, retriever, |
| base::Unretained(storage_partition_->GetURLRequestContext()), urls)); |
| } |
| |
| void NetworkHandler::GetAllCookies( |
| std::unique_ptr<GetAllCookiesCallback> callback) { |
| if (!storage_partition_) { |
| callback->sendFailure(Response::InternalError()); |
| return; |
| } |
| |
| scoped_refptr<CookieRetriever> retriever = |
| new CookieRetriever(std::move(callback)); |
| |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::BindOnce( |
| &CookieRetriever::RetrieveAllCookiesOnIO, retriever, |
| base::Unretained(storage_partition_->GetURLRequestContext()))); |
| } |
| |
| void NetworkHandler::SetCookie(const std::string& name, |
| const std::string& value, |
| Maybe<std::string> url, |
| Maybe<std::string> domain, |
| Maybe<std::string> path, |
| Maybe<bool> secure, |
| Maybe<bool> http_only, |
| Maybe<std::string> same_site, |
| Maybe<double> expires, |
| std::unique_ptr<SetCookieCallback> callback) { |
| if (!storage_partition_) { |
| callback->sendFailure(Response::InternalError()); |
| return; |
| } |
| |
| if (!url.isJust() && !domain.isJust()) { |
| callback->sendFailure(Response::InvalidParams( |
| "At least one of the url and domain needs to be specified")); |
| } |
| |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::BindOnce( |
| &SetCookieOnIO, |
| base::Unretained(storage_partition_->GetURLRequestContext()), name, |
| value, url.fromMaybe(""), domain.fromMaybe(""), path.fromMaybe(""), |
| secure.fromMaybe(false), http_only.fromMaybe(false), |
| same_site.fromMaybe(""), expires.fromMaybe(-1), |
| base::BindOnce(&CookieSetOnIO, std::move(callback)))); |
| } |
| |
| void NetworkHandler::SetCookies( |
| std::unique_ptr<protocol::Array<Network::CookieParam>> cookies, |
| std::unique_ptr<SetCookiesCallback> callback) { |
| if (!storage_partition_) { |
| callback->sendFailure(Response::InternalError()); |
| return; |
| } |
| |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::BindOnce( |
| &SetCookiesOnIO, |
| base::Unretained(storage_partition_->GetURLRequestContext()), |
| std::move(cookies), |
| base::BindOnce(&CookiesSetOnIO, std::move(callback)))); |
| } |
| |
| void NetworkHandler::DeleteCookies( |
| const std::string& name, |
| Maybe<std::string> url, |
| Maybe<std::string> domain, |
| Maybe<std::string> path, |
| std::unique_ptr<DeleteCookiesCallback> callback) { |
| if (!storage_partition_) { |
| callback->sendFailure(Response::InternalError()); |
| return; |
| } |
| |
| if (!url.isJust() && !domain.isJust()) { |
| callback->sendFailure(Response::InvalidParams( |
| "At least one of the url and domain needs to be specified")); |
| } |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::BindOnce( |
| &DeleteCookiesOnIO, |
| base::Unretained(storage_partition_->GetURLRequestContext()), name, |
| url.fromMaybe(""), domain.fromMaybe(""), path.fromMaybe(""), |
| base::BindOnce(&DeleteCookiesCallback::sendSuccess, |
| std::move(callback)))); |
| } |
| |
| Response NetworkHandler::SetUserAgentOverride(const std::string& user_agent) { |
| if (user_agent.find('\n') != std::string::npos || |
| user_agent.find('\r') != std::string::npos || |
| user_agent.find('\0') != std::string::npos) { |
| return Response::InvalidParams("Invalid characters found in userAgent"); |
| } |
| user_agent_ = user_agent; |
| return Response::FallThrough(); |
| } |
| |
| Response NetworkHandler::SetExtraHTTPHeaders( |
| std::unique_ptr<protocol::Network::Headers> headers) { |
| std::vector<std::pair<std::string, std::string>> new_headers; |
| std::unique_ptr<protocol::DictionaryValue> object = headers->toValue(); |
| for (size_t i = 0; i < object->size(); ++i) { |
| auto entry = object->at(i); |
| std::string value; |
| if (!entry.second->asString(&value)) |
| return Response::InvalidParams("Invalid header value, string expected"); |
| if (!net::HttpUtil::IsValidHeaderName(entry.first)) |
| return Response::InvalidParams("Invalid header name"); |
| if (!net::HttpUtil::IsValidHeaderValue(value)) |
| return Response::InvalidParams("Invalid header value"); |
| new_headers.emplace_back(entry.first, value); |
| } |
| extra_headers_.swap(new_headers); |
| return Response::FallThrough(); |
| } |
| |
| Response NetworkHandler::CanEmulateNetworkConditions(bool* result) { |
| *result = true; |
| return Response::OK(); |
| } |
| |
| Response NetworkHandler::EmulateNetworkConditions( |
| bool offline, |
| double latency, |
| double download_throughput, |
| double upload_throughput, |
| Maybe<protocol::Network::ConnectionType>) { |
| network::mojom::NetworkConditionsPtr network_conditions; |
| bool throttling_enabled = offline || latency > 0 || download_throughput > 0 || |
| upload_throughput > 0; |
| if (throttling_enabled) { |
| network_conditions = network::mojom::NetworkConditions::New(); |
| network_conditions->offline = offline; |
| network_conditions->latency = base::TimeDelta::FromMilliseconds(latency); |
| network_conditions->download_throughput = download_throughput; |
| network_conditions->upload_throughput = upload_throughput; |
| } |
| SetNetworkConditions(std::move(network_conditions)); |
| return Response::FallThrough(); |
| } |
| |
| Response NetworkHandler::SetBypassServiceWorker(bool bypass) { |
| bypass_service_worker_ = bypass; |
| return Response::FallThrough(); |
| } |
| |
| void NetworkHandler::NavigationPreloadRequestSent( |
| const std::string& request_id, |
| const network::ResourceRequest& request) { |
| if (!enabled_) |
| return; |
| std::unique_ptr<DictionaryValue> headers_dict(DictionaryValue::create()); |
| for (net::HttpRequestHeaders::Iterator it(request.headers); it.GetNext();) |
| headers_dict->setString(it.name(), it.value()); |
| frontend_->RequestWillBeSent( |
| request_id, "" /* loader_id */, request.url.spec(), |
| Network::Request::Create() |
| .SetUrl(request.url.spec()) |
| .SetMethod(request.method) |
| .SetHeaders(Object::fromValue(headers_dict.get(), nullptr)) |
| .SetInitialPriority(resourcePriority(request.priority)) |
| .SetReferrerPolicy(referrerPolicy(request.referrer_policy)) |
| .Build(), |
| base::TimeTicks::Now().ToInternalValue() / |
| static_cast<double>(base::Time::kMicrosecondsPerSecond), |
| base::Time::Now().ToDoubleT(), |
| Network::Initiator::Create() |
| .SetType(Network::Initiator::TypeEnum::Preload) |
| .Build(), |
| std::unique_ptr<Network::Response>(), |
| std::string(Page::ResourceTypeEnum::Other)); |
| } |
| |
| void NetworkHandler::NavigationPreloadResponseReceived( |
| const std::string& request_id, |
| const GURL& url, |
| const network::ResourceResponseHead& head) { |
| if (!enabled_) |
| return; |
| std::unique_ptr<DictionaryValue> headers_dict(DictionaryValue::create()); |
| size_t iterator = 0; |
| std::string name; |
| std::string value; |
| while (head.headers->EnumerateHeaderLines(&iterator, &name, &value)) |
| headers_dict->setString(name, value); |
| std::unique_ptr<Network::Response> response( |
| Network::Response::Create() |
| .SetUrl(url.spec()) |
| .SetStatus(head.headers->response_code()) |
| .SetStatusText(head.headers->GetStatusText()) |
| .SetHeaders(Object::fromValue(headers_dict.get(), nullptr)) |
| .SetMimeType(head.mime_type) |
| .SetConnectionReused(head.load_timing.socket_reused) |
| .SetConnectionId(head.load_timing.socket_log_id) |
| .SetSecurityState(securityState(url, head.cert_status)) |
| .SetEncodedDataLength(head.encoded_data_length) |
| .SetTiming(getTiming(head.load_timing)) |
| .SetFromDiskCache(!head.load_timing.request_start_time.is_null() && |
| head.response_time < |
| head.load_timing.request_start_time) |
| .Build()); |
| if (head.raw_request_response_info) { |
| if (head.raw_request_response_info->http_status_code) { |
| response->SetStatus(head.raw_request_response_info->http_status_code); |
| response->SetStatusText(head.raw_request_response_info->http_status_text); |
| } |
| if (head.raw_request_response_info->request_headers.size()) { |
| response->SetRequestHeaders( |
| getHeaders(head.raw_request_response_info->request_headers)); |
| } |
| if (!head.raw_request_response_info->request_headers_text.empty()) { |
| response->SetRequestHeadersText( |
| head.raw_request_response_info->request_headers_text); |
| } |
| if (head.raw_request_response_info->response_headers.size()) |
| response->SetHeaders( |
| getHeaders(head.raw_request_response_info->response_headers)); |
| if (!head.raw_request_response_info->response_headers_text.empty()) |
| response->SetHeadersText( |
| head.raw_request_response_info->response_headers_text); |
| } |
| response->SetProtocol(getProtocol(url, head)); |
| response->SetRemoteIPAddress(head.socket_address.HostForURL()); |
| response->SetRemotePort(head.socket_address.port()); |
| frontend_->ResponseReceived( |
| request_id, "" /* loader_id */, |
| base::TimeTicks::Now().ToInternalValue() / |
| static_cast<double>(base::Time::kMicrosecondsPerSecond), |
| Page::ResourceTypeEnum::Other, std::move(response)); |
| } |
| |
| void NetworkHandler::NavigationPreloadCompleted( |
| const std::string& request_id, |
| const network::URLLoaderCompletionStatus& status) { |
| if (!enabled_) |
| return; |
| if (status.error_code != net::OK) { |
| frontend_->LoadingFailed( |
| request_id, |
| base::TimeTicks::Now().ToInternalValue() / |
| static_cast<double>(base::Time::kMicrosecondsPerSecond), |
| Page::ResourceTypeEnum::Other, net::ErrorToString(status.error_code), |
| status.error_code == net::Error::ERR_ABORTED); |
| } |
| frontend_->LoadingFinished( |
| request_id, |
| status.completion_time.ToInternalValue() / |
| static_cast<double>(base::Time::kMicrosecondsPerSecond), |
| status.encoded_data_length); |
| } |
| |
| void NetworkHandler::NavigationFailed(NavigationRequest* navigation_request) { |
| if (!enabled_) |
| return; |
| |
| static int next_id = 0; |
| std::string request_id = base::IntToString(base::GetCurrentProcId()) + "." + |
| base::IntToString(++next_id); |
| std::string error_string = |
| net::ErrorToString(navigation_request->net_error()); |
| bool cancelled = navigation_request->net_error() == net::Error::ERR_ABORTED; |
| |
| std::unique_ptr<DictionaryValue> headers_dict(DictionaryValue::create()); |
| net::HttpRequestHeaders headers; |
| headers.AddHeadersFromString(navigation_request->begin_params()->headers); |
| for (net::HttpRequestHeaders::Iterator it(headers); it.GetNext();) |
| headers_dict->setString(it.name(), it.value()); |
| frontend_->RequestWillBeSent( |
| request_id, "" /* loader_id */, |
| navigation_request->common_params().url.spec(), |
| Network::Request::Create() |
| .SetUrl(navigation_request->common_params().url.spec()) |
| .SetMethod(navigation_request->common_params().method) |
| .SetHeaders(Object::fromValue(headers_dict.get(), nullptr)) |
| // Note: the priority value is copied from |
| // ResourceDispatcherHostImpl::BeginNavigationRequest but there isn't |
| // a good way of sharing this. |
| .SetInitialPriority(resourcePriority(net::HIGHEST)) |
| .SetReferrerPolicy(referrerPolicy( |
| navigation_request->common_params().referrer.policy)) |
| .Build(), |
| base::TimeTicks::Now().ToInternalValue() / |
| static_cast<double>(base::Time::kMicrosecondsPerSecond), |
| base::Time::Now().ToDoubleT(), |
| Network::Initiator::Create() |
| .SetType(Network::Initiator::TypeEnum::Parser) |
| .Build(), |
| std::unique_ptr<Network::Response>(), |
| std::string(Page::ResourceTypeEnum::Document)); |
| |
| frontend_->LoadingFailed( |
| request_id, |
| base::TimeTicks::Now().ToInternalValue() / |
| static_cast<double>(base::Time::kMicrosecondsPerSecond), |
| Page::ResourceTypeEnum::Document, error_string, cancelled); |
| } |
| |
| DispatchResponse NetworkHandler::SetRequestInterception( |
| std::unique_ptr<protocol::Array<protocol::Network::RequestPattern>> |
| patterns) { |
| WebContents* web_contents = WebContents::FromRenderFrameHost(host_); |
| if (!web_contents) |
| return Response::InternalError(); |
| |
| DevToolsInterceptorController* interceptor = |
| DevToolsInterceptorController::FromBrowserContext( |
| web_contents->GetBrowserContext()); |
| if (!interceptor) |
| return Response::Error("Interception not supported"); |
| |
| if (!patterns->length()) { |
| interception_handle_.reset(); |
| return Response::OK(); |
| } |
| |
| std::vector<DevToolsURLRequestInterceptor::Pattern> interceptor_patterns; |
| for (size_t i = 0; i < patterns->length(); ++i) { |
| base::flat_set<ResourceType> resource_types; |
| std::string resource_type = patterns->get(i)->GetResourceType(""); |
| if (!resource_type.empty()) { |
| if (!AddInterceptedResourceType(resource_type, &resource_types)) { |
| return Response::InvalidParams(base::StringPrintf( |
| "Cannot intercept resources of type '%s'", resource_type.c_str())); |
| } |
| } |
| interceptor_patterns.push_back(DevToolsURLRequestInterceptor::Pattern( |
| patterns->get(i)->GetUrlPattern("*"), std::move(resource_types), |
| ToInterceptorStage(patterns->get(i)->GetInterceptionStage( |
| protocol::Network::InterceptionStageEnum::Request)))); |
| } |
| |
| if (interception_handle_) { |
| interception_handle_->UpdatePatterns(std::move(interceptor_patterns)); |
| } else { |
| interception_handle_ = interceptor->StartInterceptingRequests( |
| host_->frame_tree_node(), std::move(interceptor_patterns), |
| base::Bind(&NetworkHandler::RequestIntercepted, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| return Response::OK(); |
| } |
| |
| namespace { |
| bool GetPostData(const net::URLRequest* request, std::string* post_data) { |
| if (!request->has_upload()) |
| return false; |
| |
| const net::UploadDataStream* stream = request->get_upload(); |
| if (!stream->GetElementReaders()) |
| return false; |
| |
| const auto* element_readers = stream->GetElementReaders(); |
| |
| if (element_readers->empty()) |
| return false; |
| |
| *post_data = ""; |
| for (const auto& element_reader : *element_readers) { |
| const net::UploadBytesElementReader* reader = |
| element_reader->AsBytesReader(); |
| // TODO(caseq): Also support blobs. |
| if (!reader) { |
| *post_data = ""; |
| return false; |
| } |
| // TODO(alexclarke): This should really be base64 encoded. |
| *post_data += std::string(reader->bytes(), reader->length()); |
| } |
| return true; |
| } |
| } // namespace |
| |
| void NetworkHandler::ContinueInterceptedRequest( |
| const std::string& interception_id, |
| Maybe<std::string> error_reason, |
| Maybe<std::string> base64_raw_response, |
| Maybe<std::string> url, |
| Maybe<std::string> method, |
| Maybe<std::string> post_data, |
| Maybe<protocol::Network::Headers> headers, |
| Maybe<protocol::Network::AuthChallengeResponse> auth_challenge_response, |
| std::unique_ptr<ContinueInterceptedRequestCallback> callback) { |
| DevToolsInterceptorController* interceptor = |
| DevToolsInterceptorController::FromBrowserContext(browser_context_); |
| if (!interceptor) { |
| callback->sendFailure(Response::InternalError()); |
| return; |
| } |
| |
| base::Optional<std::string> raw_response; |
| if (base64_raw_response.isJust()) { |
| std::string decoded; |
| if (!base::Base64Decode(base64_raw_response.fromJust(), &decoded)) { |
| callback->sendFailure(Response::InvalidParams("Invalid rawResponse.")); |
| return; |
| } |
| raw_response = decoded; |
| } |
| |
| base::Optional<net::Error> error; |
| bool mark_as_canceled = false; |
| if (error_reason.isJust()) { |
| bool ok; |
| error = NetErrorFromString(error_reason.fromJust(), &ok); |
| if (!ok) { |
| callback->sendFailure(Response::InvalidParams("Invalid errorReason.")); |
| return; |
| } |
| |
| mark_as_canceled = true; |
| } |
| |
| interceptor->ContinueInterceptedRequest( |
| interception_id, |
| std::make_unique<DevToolsURLRequestInterceptor::Modifications>( |
| std::move(error), std::move(raw_response), std::move(url), |
| std::move(method), std::move(post_data), std::move(headers), |
| std::move(auth_challenge_response), mark_as_canceled), |
| std::move(callback)); |
| } |
| |
| void NetworkHandler::GetResponseBodyForInterception( |
| const String& interception_id, |
| std::unique_ptr<GetResponseBodyForInterceptionCallback> callback) { |
| DevToolsInterceptorController* interceptor = |
| DevToolsInterceptorController::FromBrowserContext(browser_context_); |
| if (!interceptor) { |
| callback->sendFailure(Response::InternalError()); |
| return; |
| } |
| interceptor->GetResponseBody(interception_id, std::move(callback)); |
| } |
| |
| // static |
| GURL NetworkHandler::ClearUrlRef(const GURL& url) { |
| if (!url.has_ref()) |
| return url; |
| GURL::Replacements replacements; |
| replacements.ClearRef(); |
| return url.ReplaceComponents(replacements); |
| } |
| |
| // static |
| std::unique_ptr<Network::Request> NetworkHandler::CreateRequestFromURLRequest( |
| const net::URLRequest* request) { |
| std::unique_ptr<DictionaryValue> headers_dict(DictionaryValue::create()); |
| for (net::HttpRequestHeaders::Iterator it(request->extra_request_headers()); |
| it.GetNext();) { |
| headers_dict->setString(it.name(), it.value()); |
| } |
| if (!request->referrer().empty()) { |
| headers_dict->setString(net::HttpRequestHeaders::kReferer, |
| request->referrer()); |
| } |
| std::unique_ptr<protocol::Network::Request> request_object = |
| Network::Request::Create() |
| .SetUrl(ClearUrlRef(request->url()).spec()) |
| .SetMethod(request->method()) |
| .SetHeaders(Object::fromValue(headers_dict.get(), nullptr)) |
| .SetInitialPriority(resourcePriority(request->priority())) |
| .SetReferrerPolicy(referrerPolicy(request->referrer_policy())) |
| .Build(); |
| std::string post_data; |
| if (GetPostData(request, &post_data)) |
| request_object->SetPostData(std::move(post_data)); |
| return request_object; |
| } |
| |
| std::unique_ptr<NavigationThrottle> NetworkHandler::CreateThrottleForNavigation( |
| NavigationHandle* navigation_handle) { |
| if (!interception_handle_) |
| return nullptr; |
| std::unique_ptr<NavigationThrottle> throttle(new NetworkNavigationThrottle( |
| weak_factory_.GetWeakPtr(), navigation_handle)); |
| return throttle; |
| } |
| |
| bool NetworkHandler::ShouldCancelNavigation( |
| const GlobalRequestID& global_request_id) { |
| WebContents* web_contents = WebContents::FromRenderFrameHost(host_); |
| if (!web_contents) |
| return false; |
| DevToolsInterceptorController* interceptor = |
| DevToolsInterceptorController::FromBrowserContext( |
| web_contents->GetBrowserContext()); |
| return interceptor && interceptor->ShouldCancelNavigation(global_request_id); |
| } |
| |
| void NetworkHandler::WillSendNavigationRequest(net::HttpRequestHeaders* headers, |
| bool* skip_service_worker, |
| bool* disable_cache) { |
| headers->SetHeader(kDevToolsEmulateNetworkConditionsClientId, host_id_); |
| if (!user_agent_.empty()) |
| headers->SetHeader(net::HttpRequestHeaders::kUserAgent, user_agent_); |
| for (auto& entry : extra_headers_) |
| headers->SetHeader(entry.first, entry.second); |
| *skip_service_worker |= bypass_service_worker_; |
| *disable_cache |= cache_disabled_; |
| } |
| |
| namespace { |
| |
| const char* ResourceTypeToString(ResourceType resource_type) { |
| switch (resource_type) { |
| case RESOURCE_TYPE_MAIN_FRAME: |
| return protocol::Page::ResourceTypeEnum::Document; |
| case RESOURCE_TYPE_SUB_FRAME: |
| return protocol::Page::ResourceTypeEnum::Document; |
| case RESOURCE_TYPE_STYLESHEET: |
| return protocol::Page::ResourceTypeEnum::Stylesheet; |
| case RESOURCE_TYPE_SCRIPT: |
| return protocol::Page::ResourceTypeEnum::Script; |
| case RESOURCE_TYPE_IMAGE: |
| return protocol::Page::ResourceTypeEnum::Image; |
| case RESOURCE_TYPE_FONT_RESOURCE: |
| return protocol::Page::ResourceTypeEnum::Font; |
| case RESOURCE_TYPE_SUB_RESOURCE: |
| return protocol::Page::ResourceTypeEnum::Other; |
| case RESOURCE_TYPE_OBJECT: |
| return protocol::Page::ResourceTypeEnum::Other; |
| case RESOURCE_TYPE_MEDIA: |
| return protocol::Page::ResourceTypeEnum::Media; |
| case RESOURCE_TYPE_WORKER: |
| return protocol::Page::ResourceTypeEnum::Other; |
| case RESOURCE_TYPE_SHARED_WORKER: |
| return protocol::Page::ResourceTypeEnum::Other; |
| case RESOURCE_TYPE_PREFETCH: |
| return protocol::Page::ResourceTypeEnum::Fetch; |
| case RESOURCE_TYPE_FAVICON: |
| return protocol::Page::ResourceTypeEnum::Other; |
| case RESOURCE_TYPE_XHR: |
| return protocol::Page::ResourceTypeEnum::XHR; |
| case RESOURCE_TYPE_PING: |
| return protocol::Page::ResourceTypeEnum::Other; |
| case RESOURCE_TYPE_SERVICE_WORKER: |
| return protocol::Page::ResourceTypeEnum::Other; |
| case RESOURCE_TYPE_CSP_REPORT: |
| return protocol::Page::ResourceTypeEnum::Other; |
| case RESOURCE_TYPE_PLUGIN_RESOURCE: |
| return protocol::Page::ResourceTypeEnum::Other; |
| default: |
| return protocol::Page::ResourceTypeEnum::Other; |
| } |
| } |
| |
| } // namespace |
| |
| void NetworkHandler::RequestIntercepted( |
| std::unique_ptr<InterceptedRequestInfo> info) { |
| protocol::Maybe<protocol::Network::ErrorReason> error_reason; |
| if (info->response_error_code < 0) |
| error_reason = NetErrorToString(info->response_error_code); |
| frontend_->RequestIntercepted( |
| info->interception_id, std::move(info->network_request), |
| info->frame_id.ToString(), ResourceTypeToString(info->resource_type), |
| info->is_navigation, std::move(info->redirect_url), |
| std::move(info->auth_challenge), std::move(error_reason), |
| std::move(info->http_response_status_code), |
| std::move(info->response_headers)); |
| } |
| |
| void NetworkHandler::SetNetworkConditions( |
| network::mojom::NetworkConditionsPtr conditions) { |
| if (!storage_partition_) |
| return; |
| network::mojom::NetworkContext* context = |
| storage_partition_->GetNetworkContext(); |
| context->SetNetworkConditions(host_id_, std::move(conditions)); |
| } |
| |
| } // namespace protocol |
| } // namespace content |