| // 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/json/json_reader.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/background_sync/background_sync_manager.h" |
| #include "content/browser/devtools/devtools_interceptor_controller.h" |
| #include "content/browser/devtools/devtools_io_context.h" |
| #include "content/browser/devtools/devtools_session.h" |
| #include "content/browser/devtools/devtools_stream_pipe.h" |
| #include "content/browser/devtools/devtools_url_loader_interceptor.h" |
| #include "content/browser/devtools/protocol/page.h" |
| #include "content/browser/devtools/protocol/security.h" |
| #include "content/browser/devtools/service_worker_devtools_agent_host.h" |
| #include "content/browser/devtools/service_worker_devtools_manager.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/browser/storage_partition_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/browser_side_navigation_policy.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/cert/ct_policy_status.h" |
| #include "net/cert/ct_sct_to_string.h" |
| #include "net/cookies/canonical_cookie.h" |
| #include "net/cookies/cookie_store.h" |
| #include "net/http/http_response_headers.h" |
| #include "net/http/http_status_code.h" |
| #include "net/http/http_util.h" |
| #include "net/ssl/ssl_cipher_suite_names.h" |
| #include "net/ssl/ssl_connection_status_flags.h" |
| #include "net/url_request/url_request_context.h" |
| #include "net/url_request/url_request_context_getter.h" |
| #include "services/network/public/cpp/data_element.h" |
| #include "services/network/public/cpp/features.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" |
| #include "third_party/boringssl/src/include/openssl/ssl.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"; |
| |
| Network::CertificateTransparencyCompliance SerializeCTPolicyCompliance( |
| net::ct::CTPolicyCompliance ct_compliance) { |
| switch (ct_compliance) { |
| case net::ct::CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS: |
| return Network::CertificateTransparencyComplianceEnum::Compliant; |
| case net::ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS: |
| case net::ct::CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS: |
| return Network::CertificateTransparencyComplianceEnum::NotCompliant; |
| case net::ct::CTPolicyCompliance::CT_POLICY_BUILD_NOT_TIMELY: |
| case net::ct::CTPolicyCompliance:: |
| CT_POLICY_COMPLIANCE_DETAILS_NOT_AVAILABLE: |
| return Network::CertificateTransparencyComplianceEnum::Unknown; |
| case net::ct::CTPolicyCompliance::CT_POLICY_MAX: |
| NOTREACHED(); |
| return Network::CertificateTransparencyComplianceEnum::Unknown; |
| } |
| NOTREACHED(); |
| return Network::CertificateTransparencyComplianceEnum::Unknown; |
| } |
| |
| std::unique_ptr<Network::Cookie> BuildCookie( |
| const net::CanonicalCookie& cookie) { |
| 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; |
| } |
| return devtools_cookie; |
| } |
| |
| std::unique_ptr<ProtocolCookieArray> BuildCookieArray( |
| const std::vector<net::CanonicalCookie>& cookie_list) { |
| auto cookies = std::make_unique<ProtocolCookieArray>(); |
| |
| for (const net::CanonicalCookie& cookie : cookie_list) |
| cookies->addItem(BuildCookie(cookie)); |
| |
| return cookies; |
| } |
| |
| class CookieRetriever : public base::RefCountedThreadSafe<CookieRetriever> { |
| public: |
| CookieRetriever(std::unique_ptr<GetCookiesCallback> callback) |
| : callback_(std::move(callback)), all_callback_(nullptr) { |
| DCHECK(!base::FeatureList::IsEnabled(network::features::kNetworkService)); |
| } |
| |
| 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 = |
| BuildCookieArray(cookie_list); |
| |
| 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>; |
| }; |
| |
| class CookieRetrieverNetworkService |
| : public base::RefCounted<CookieRetrieverNetworkService> { |
| public: |
| static void Retrieve(network::mojom::CookieManager* cookie_manager, |
| const std::vector<GURL> urls, |
| std::unique_ptr<GetCookiesCallback> callback) { |
| scoped_refptr<CookieRetrieverNetworkService> self = |
| new CookieRetrieverNetworkService(std::move(callback)); |
| net::CookieOptions cookie_options; |
| cookie_options.set_include_httponly(); |
| cookie_options.set_same_site_cookie_mode( |
| net::CookieOptions::SameSiteCookieMode::INCLUDE_STRICT_AND_LAX); |
| cookie_options.set_do_not_update_access_time(); |
| for (const auto& url : urls) { |
| cookie_manager->GetCookieList( |
| url, cookie_options, |
| base::BindOnce(&CookieRetrieverNetworkService::GotCookies, self)); |
| } |
| } |
| |
| private: |
| friend class base::RefCounted<CookieRetrieverNetworkService>; |
| |
| CookieRetrieverNetworkService(std::unique_ptr<GetCookiesCallback> callback) |
| : callback_(std::move(callback)) {} |
| |
| void GotCookies(const std::vector<net::CanonicalCookie>& cookies) { |
| for (const auto& cookie : cookies) { |
| std::string key = base::StringPrintf( |
| "%s::%s::%s::%d", cookie.Name().c_str(), cookie.Domain().c_str(), |
| cookie.Path().c_str(), cookie.IsSecure()); |
| all_cookies_.emplace(std::move(key), cookie); |
| } |
| } |
| |
| ~CookieRetrieverNetworkService() { |
| auto cookies = std::make_unique<ProtocolCookieArray>(); |
| for (const auto& entry : all_cookies_) |
| cookies->addItem(BuildCookie(entry.second)); |
| callback_->sendSuccess(std::move(cookies)); |
| } |
| |
| std::unique_ptr<GetCookiesCallback> callback_; |
| std::unordered_map<std::string, net::CanonicalCookie> all_cookies_; |
| }; |
| |
| void ClearedCookiesOnIO(std::unique_ptr<ClearBrowserCookiesCallback> callback, |
| uint32_t num_deleted) { |
| DCHECK(!base::FeatureList::IsEnabled(network::features::kNetworkService)); |
| 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(!base::FeatureList::IsEnabled(network::features::kNetworkService)); |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| net::URLRequestContext* request_context = |
| context_getter->GetURLRequestContext(); |
| request_context->cookie_store()->DeleteAllAsync( |
| base::BindOnce(&ClearedCookiesOnIO, 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)); |
| } |
| |
| std::vector<net::CanonicalCookie> FilterCookies( |
| const std::vector<net::CanonicalCookie>& cookies, |
| const std::string& name, |
| const std::string& normalized_domain, |
| const std::string& path) { |
| std::vector<net::CanonicalCookie> result; |
| |
| for (const auto& cookie : cookies) { |
| if (cookie.Name() != name) |
| continue; |
| if (cookie.Domain() != normalized_domain) |
| continue; |
| if (!path.empty() && cookie.Path() != path) |
| continue; |
| result.push_back(cookie); |
| } |
| |
| return result; |
| } |
| |
| void DeleteSelectedCookiesOnIO(net::URLRequestContextGetter* context_getter, |
| const std::string& name, |
| const std::string& normalized_domain, |
| const std::string& path, |
| base::OnceClosure callback, |
| const net::CookieList& cookie_list) { |
| DCHECK(!base::FeatureList::IsEnabled(network::features::kNetworkService)); |
| |
| net::URLRequestContext* request_context = |
| context_getter->GetURLRequestContext(); |
| net::CookieList filtered_list = |
| FilterCookies(cookie_list, name, normalized_domain, path); |
| 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()) |
| DeletedCookiesOnIO(std::move(callback), 0); |
| } |
| |
| void DeleteCookiesOnIO(net::URLRequestContextGetter* context_getter, |
| const std::string& name, |
| const std::string& normalized_domain, |
| const std::string& path, |
| base::OnceClosure callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(!base::FeatureList::IsEnabled(network::features::kNetworkService)); |
| net::URLRequestContext* request_context = |
| context_getter->GetURLRequestContext(); |
| |
| request_context->cookie_store()->GetAllCookiesAsync(base::BindOnce( |
| &DeleteSelectedCookiesOnIO, base::Unretained(context_getter), name, |
| normalized_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 DeleteFilteredCookies(network::mojom::CookieManager* cookie_manager, |
| const std::string& name, |
| const std::string& normalized_domain, |
| const std::string& path, |
| std::unique_ptr<DeleteCookiesCallback> callback, |
| const std::vector<net::CanonicalCookie>& cookies) { |
| base::Time yesterday(base::Time::Now() - base::TimeDelta::FromDays(1)); |
| |
| std::vector<net::CanonicalCookie> filtered_list = |
| FilterCookies(cookies, name, normalized_domain, path); |
| |
| base::RepeatingClosure barrier_closure = base::BarrierClosure( |
| filtered_list.size(), |
| base::BindOnce(&DeleteCookiesCallback::sendSuccess, std::move(callback))); |
| |
| for (auto& cookie : filtered_list) { |
| // Delete a single cookie by setting its expiration time into the past. |
| cookie_manager->SetCanonicalCookie( |
| net::CanonicalCookie(cookie.Name(), cookie.Value(), cookie.Domain(), |
| cookie.Path(), cookie.CreationDate(), yesterday, |
| cookie.LastAccessDate(), cookie.IsSecure(), |
| cookie.IsHttpOnly(), cookie.SameSite(), |
| cookie.Priority()), |
| true /* secure_source */, true /* modify_http_only */, |
| base::BindOnce( |
| [](base::RepeatingClosure callback, bool) { callback.Run(); }, |
| barrier_closure)); |
| } |
| } |
| |
| std::unique_ptr<net::CanonicalCookie> MakeCookieFromProtocolValues( |
| 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) { |
| std::string normalized_domain = domain; |
| |
| if (url_spec.empty() && domain.empty()) |
| return nullptr; |
| |
| if (!url_spec.empty()) { |
| GURL source_url = GURL(url_spec); |
| if (!source_url.SchemeIsHTTPOrHTTPS()) |
| return nullptr; |
| |
| 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; |
| |
| return net::CanonicalCookie::CreateSanitizedCookie( |
| url, name, value, normalized_domain, path, base::Time(), expiration_date, |
| base::Time(), secure, http_only, css, net::COOKIE_PRIORITY_DEFAULT); |
| } |
| |
| void SetCookieOnIO(net::URLRequestContextGetter* context_getter, |
| std::unique_ptr<net::CanonicalCookie> cookie, |
| base::OnceCallback<void(bool)> callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(!base::FeatureList::IsEnabled(network::features::kNetworkService)); |
| net::URLRequestContext* request_context = |
| context_getter->GetURLRequestContext(); |
| |
| request_context->cookie_store()->SetCanonicalCookieAsync( |
| std::move(cookie), true /* secure_source */, true /*modify_http_only*/, |
| std::move(callback)); |
| } |
| |
| void CookiesSetOnIO(std::unique_ptr<SetCookiesCallback> callback) { |
| 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::vector<std::unique_ptr<net::CanonicalCookie>> cookies, |
| base::OnceClosure callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(!base::FeatureList::IsEnabled(network::features::kNetworkService)); |
| |
| base::RepeatingClosure barrier_closure = |
| base::BarrierClosure(cookies.size(), std::move(callback)); |
| for (auto& cookie : cookies) { |
| SetCookieOnIO(context_getter, std::move(cookie), |
| base::BindOnce([](base::RepeatingClosure callback, |
| bool) { callback.Run(); }, |
| barrier_closure)); |
| } |
| } |
| |
| 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; |
| } |
| |
| DevToolsNetworkInterceptor::InterceptionStage ToInterceptorStage( |
| const protocol::Network::InterceptionStage& interceptor_stage) { |
| if (interceptor_stage == protocol::Network::InterceptionStageEnum::Request) |
| return DevToolsNetworkInterceptor::REQUEST; |
| if (interceptor_stage == |
| protocol::Network::InterceptionStageEnum::HeadersReceived) |
| return DevToolsNetworkInterceptor::RESPONSE; |
| NOTREACHED(); |
| return DevToolsNetworkInterceptor::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; |
| if (error == Network::ErrorReasonEnum::BlockedByClient) |
| return net::ERR_BLOCKED_BY_CLIENT; |
| if (error == Network::ErrorReasonEnum::BlockedByResponse) |
| return net::ERR_BLOCKED_BY_RESPONSE; |
| *ok = false; |
| return net::ERR_FAILED; |
| } |
| |
| String NetErrorToString(int net_error) { |
| switch (net_error) { |
| case net::ERR_ABORTED: |
| return Network::ErrorReasonEnum::Aborted; |
| case net::ERR_TIMED_OUT: |
| return Network::ErrorReasonEnum::TimedOut; |
| case net::ERR_ACCESS_DENIED: |
| return Network::ErrorReasonEnum::AccessDenied; |
| case net::ERR_CONNECTION_CLOSED: |
| return Network::ErrorReasonEnum::ConnectionClosed; |
| case net::ERR_CONNECTION_RESET: |
| return Network::ErrorReasonEnum::ConnectionReset; |
| case net::ERR_CONNECTION_REFUSED: |
| return Network::ErrorReasonEnum::ConnectionRefused; |
| case net::ERR_CONNECTION_ABORTED: |
| return Network::ErrorReasonEnum::ConnectionAborted; |
| case net::ERR_CONNECTION_FAILED: |
| return Network::ErrorReasonEnum::ConnectionFailed; |
| case net::ERR_NAME_NOT_RESOLVED: |
| return Network::ErrorReasonEnum::NameNotResolved; |
| case net::ERR_INTERNET_DISCONNECTED: |
| return Network::ErrorReasonEnum::InternetDisconnected; |
| case net::ERR_ADDRESS_UNREACHABLE: |
| return Network::ErrorReasonEnum::AddressUnreachable; |
| case net::ERR_BLOCKED_BY_CLIENT: |
| return Network::ErrorReasonEnum::BlockedByClient; |
| case net::ERR_BLOCKED_BY_RESPONSE: |
| return Network::ErrorReasonEnum::BlockedByResponse; |
| default: |
| 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) { |
| std::string value; |
| bool merge_with_another = headers_dict->getString(pair.first, &value); |
| headers_dict->setString(pair.first, merge_with_another |
| ? value + '\n' + pair.second |
| : pair.second); |
| } |
| return Object::fromValue(headers_dict.get(), nullptr); |
| } |
| |
| String GetProtocol(const GURL& url, const network::ResourceResponseInfo& info) { |
| std::string protocol = info.alpn_negotiated_protocol; |
| if (protocol.empty() || protocol == "unknown") { |
| if (info.was_fetched_via_spdy) { |
| protocol = "spdy"; |
| } else if (url.SchemeIsHTTPOrHTTPS()) { |
| protocol = "http"; |
| if (info.headers->GetHttpVersion() == net::HttpVersion(0, 9)) |
| protocol = "http/0.9"; |
| else if (info.headers->GetHttpVersion() == net::HttpVersion(1, 0)) |
| protocol = "http/1.0"; |
| else if (info.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); |
| } |
| |
| 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->clear(); |
| for (const auto& element_reader : *element_readers) { |
| const net::UploadBytesElementReader* reader = |
| element_reader->AsBytesReader(); |
| // TODO(caseq): Also support blobs. |
| if (!reader) { |
| post_data->clear(); |
| return false; |
| } |
| // TODO(caseq): This should really be base64 encoded. |
| post_data->append(reader->bytes(), reader->length()); |
| } |
| return true; |
| } |
| |
| // TODO(caseq): all problems in the above function should be fixed here as well. |
| bool GetPostData(const network::ResourceRequestBody& request_body, |
| std::string* result) { |
| const std::vector<network::DataElement>* elements = request_body.elements(); |
| if (elements->empty()) |
| return false; |
| for (const auto& element : *elements) { |
| if (element.type() != network::DataElement::TYPE_BYTES) |
| return false; |
| result->append(element.bytes(), element.length()); |
| } |
| return true; |
| } |
| |
| std::string StripFragment(const GURL& url) { |
| url::Replacements<char> replacements; |
| replacements.ClearRef(); |
| return url.ReplaceComponents(replacements).spec(); |
| } |
| |
| } // namespace |
| |
| class BackgroundSyncRestorer { |
| public: |
| BackgroundSyncRestorer(const std::string& host_id, |
| StoragePartition* storage_partition) |
| : host_id_(host_id), storage_partition_(storage_partition) { |
| SetServiceWorkerOffline(true); |
| } |
| |
| ~BackgroundSyncRestorer() { SetServiceWorkerOffline(false); } |
| |
| void SetStoragePartition(StoragePartition* storage_partition) { |
| storage_partition_ = storage_partition; |
| } |
| |
| private: |
| void SetServiceWorkerOffline(bool offline) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| scoped_refptr<DevToolsAgentHost> host = |
| DevToolsAgentHost::GetForId(host_id_); |
| if (!host || !storage_partition_ || |
| host->GetType() != DevToolsAgentHost::kTypeServiceWorker) { |
| return; |
| } |
| scoped_refptr<ServiceWorkerDevToolsAgentHost> service_worker_host = |
| static_cast<ServiceWorkerDevToolsAgentHost*>(host.get()); |
| scoped_refptr<BackgroundSyncContext> sync_context = |
| static_cast<StoragePartitionImpl*>(storage_partition_) |
| ->GetBackgroundSyncContext(); |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::BindOnce( |
| &SetServiceWorkerOfflineOnIO, sync_context, |
| base::RetainedRef(static_cast<ServiceWorkerContextWrapper*>( |
| storage_partition_->GetServiceWorkerContext())), |
| service_worker_host->version_id(), offline)); |
| } |
| |
| static void SetServiceWorkerOfflineOnIO( |
| scoped_refptr<BackgroundSyncContext> sync_context, |
| scoped_refptr<ServiceWorkerContextWrapper> swcontext, |
| int64_t version_id, |
| bool offline) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| ServiceWorkerVersion* version = swcontext.get()->GetLiveVersion(version_id); |
| if (!version) |
| return; |
| sync_context->background_sync_manager()->EmulateServiceWorkerOffline( |
| version->registration_id(), offline); |
| } |
| |
| std::string host_id_; |
| StoragePartition* storage_partition_; |
| |
| DISALLOW_COPY_AND_ASSIGN(BackgroundSyncRestorer); |
| }; |
| |
| NetworkHandler::NetworkHandler(const std::string& host_id, |
| DevToolsIOContext* io_context) |
| : DevToolsDomainHandler(Network::Metainfo::domainName), |
| host_id_(host_id), |
| io_context_(io_context), |
| browser_context_(nullptr), |
| storage_partition_(nullptr), |
| host_(nullptr), |
| enabled_(false), |
| bypass_service_worker_(false), |
| cache_disabled_(false), |
| weak_factory_(this) { |
| DCHECK(io_context_); |
| 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; |
| if (background_sync_restorer_) |
| background_sync_restorer_->SetStoragePartition(storage_partition_); |
| } |
| |
| 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(); |
| url_loader_interceptor_.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; |
| } |
| |
| if (!base::FeatureList::IsEnabled(network::features::kNetworkService)) { |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::BindOnce( |
| &ClearCookiesOnIO, |
| base::Unretained(storage_partition_->GetURLRequestContext()), |
| std::move(callback))); |
| return; |
| } |
| |
| storage_partition_->GetCookieManagerForBrowserProcess()->DeleteCookies( |
| network::mojom::CookieDeletionFilter::New(), |
| base::BindOnce([](std::unique_ptr<ClearBrowserCookiesCallback> callback, |
| uint32_t) { callback->sendSuccess(); }, |
| std::move(callback))); |
| } |
| |
| void NetworkHandler::GetCookies(Maybe<Array<String>> protocol_urls, |
| std::unique_ptr<GetCookiesCallback> callback) { |
| if (!host_ || !storage_partition_) { |
| callback->sendFailure(Response::InternalError()); |
| return; |
| } |
| std::vector<GURL> urls = ComputeCookieURLs(host_, protocol_urls); |
| |
| if (!base::FeatureList::IsEnabled(network::features::kNetworkService)) { |
| 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)); |
| return; |
| } |
| |
| CookieRetrieverNetworkService::Retrieve( |
| storage_partition_->GetCookieManagerForBrowserProcess(), urls, |
| std::move(callback)); |
| } |
| |
| void NetworkHandler::GetAllCookies( |
| std::unique_ptr<GetAllCookiesCallback> callback) { |
| if (!storage_partition_) { |
| callback->sendFailure(Response::InternalError()); |
| return; |
| } |
| if (!base::FeatureList::IsEnabled(network::features::kNetworkService)) { |
| 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()))); |
| return; |
| } |
| |
| storage_partition_->GetCookieManagerForBrowserProcess()->GetAllCookies( |
| base::BindOnce( |
| [](std::unique_ptr<GetAllCookiesCallback> callback, |
| const std::vector<net::CanonicalCookie>& cookies) { |
| callback->sendSuccess(BuildCookieArray(cookies)); |
| }, |
| std::move(callback))); |
| } |
| |
| 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")); |
| } |
| |
| std::unique_ptr<net::CanonicalCookie> cookie = MakeCookieFromProtocolValues( |
| name, value, url.fromMaybe(""), domain.fromMaybe(""), path.fromMaybe(""), |
| secure.fromMaybe(false), http_only.fromMaybe(false), |
| same_site.fromMaybe(""), expires.fromMaybe(-1)); |
| |
| if (!cookie) { |
| // TODO(caseq): Current logic is for compatability only. |
| // Consider returning protocol error here. |
| callback->sendSuccess(false); |
| return; |
| } |
| |
| if (!base::FeatureList::IsEnabled(network::features::kNetworkService)) { |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::BindOnce( |
| &SetCookieOnIO, |
| base::Unretained(storage_partition_->GetURLRequestContext()), |
| std::move(cookie), |
| base::BindOnce(&CookieSetOnIO, std::move(callback)))); |
| return; |
| } |
| |
| storage_partition_->GetCookieManagerForBrowserProcess()->SetCanonicalCookie( |
| *cookie, true /* secure_source */, true /* modify_http_only */, |
| base::BindOnce(&SetCookieCallback::sendSuccess, 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; |
| } |
| |
| std::vector<std::unique_ptr<net::CanonicalCookie>> net_cookies; |
| for (size_t i = 0; i < cookies->length(); i++) { |
| Network::CookieParam* cookie = cookies->get(i); |
| std::unique_ptr<net::CanonicalCookie> net_cookie = |
| MakeCookieFromProtocolValues( |
| cookie->GetName(), cookie->GetValue(), cookie->GetUrl(""), |
| cookie->GetDomain(""), cookie->GetPath(""), |
| cookie->GetSecure(false), cookie->GetHttpOnly(false), |
| cookie->GetSameSite(""), cookie->GetExpires(-1)); |
| if (!cookie) { |
| callback->sendFailure(Response::InvalidParams("Invalid cookie fields")); |
| return; |
| } |
| net_cookies.push_back(std::move(net_cookie)); |
| } |
| |
| if (!base::FeatureList::IsEnabled(network::features::kNetworkService)) { |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::BindOnce( |
| &SetCookiesOnIO, |
| base::Unretained(storage_partition_->GetURLRequestContext()), |
| std::move(net_cookies), |
| base::BindOnce(&CookiesSetOnIO, std::move(callback)))); |
| return; |
| } |
| |
| base::RepeatingClosure barrier_closure = base::BarrierClosure( |
| net_cookies.size(), |
| base::BindOnce(&SetCookiesCallback::sendSuccess, std::move(callback))); |
| |
| auto* cookie_manager = |
| storage_partition_->GetCookieManagerForBrowserProcess(); |
| for (const auto& cookie : net_cookies) { |
| cookie_manager->SetCanonicalCookie( |
| *cookie, true, true, |
| base::BindOnce( |
| [](base::RepeatingClosure callback, bool) { callback.Run(); }, |
| barrier_closure)); |
| } |
| } |
| |
| void NetworkHandler::DeleteCookies( |
| const std::string& name, |
| Maybe<std::string> url_spec, |
| Maybe<std::string> domain, |
| Maybe<std::string> path, |
| std::unique_ptr<DeleteCookiesCallback> callback) { |
| if (!storage_partition_) { |
| callback->sendFailure(Response::InternalError()); |
| return; |
| } |
| if (!url_spec.isJust() && !domain.isJust()) { |
| callback->sendFailure(Response::InvalidParams( |
| "At least one of the url and domain needs to be specified")); |
| } |
| |
| std::string normalized_domain = domain.fromMaybe(""); |
| if (normalized_domain.empty()) { |
| GURL url(url_spec.fromMaybe("")); |
| if (!url.SchemeIsHTTPOrHTTPS()) { |
| callback->sendFailure(Response::InvalidParams( |
| "An http or https url URL must be specified")); |
| return; |
| } |
| normalized_domain = url.host(); |
| } |
| |
| if (!base::FeatureList::IsEnabled(network::features::kNetworkService)) { |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::BindOnce( |
| &DeleteCookiesOnIO, |
| base::Unretained(storage_partition_->GetURLRequestContext()), name, |
| normalized_domain, path.fromMaybe(""), |
| base::BindOnce(&DeleteCookiesCallback::sendSuccess, |
| std::move(callback)))); |
| return; |
| } |
| |
| auto* cookie_manager = |
| storage_partition_->GetCookieManagerForBrowserProcess(); |
| |
| cookie_manager->GetAllCookies(base::BindOnce( |
| &DeleteFilteredCookies, base::Unretained(cookie_manager), name, |
| normalized_domain, path.fromMaybe(""), 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(); |
| } |
| |
| namespace { |
| |
| std::unique_ptr<protocol::Network::SecurityDetails> BuildSecurityDetails( |
| const net::SSLInfo& ssl_info) { |
| if (!ssl_info.cert) |
| return nullptr; |
| std::unique_ptr< |
| protocol::Array<protocol::Network::SignedCertificateTimestamp>> |
| signed_certificate_timestamp_list = |
| protocol::Array<Network::SignedCertificateTimestamp>::create(); |
| for (auto const& sct : ssl_info.signed_certificate_timestamps) { |
| std::unique_ptr<protocol::Network::SignedCertificateTimestamp> |
| signed_certificate_timestamp = |
| Network::SignedCertificateTimestamp::Create() |
| .SetStatus(net::ct::StatusToString(sct.status)) |
| .SetOrigin(net::ct::OriginToString(sct.sct->origin)) |
| .SetLogDescription(sct.sct->log_description) |
| .SetLogId(base::HexEncode(sct.sct->log_id.c_str(), |
| sct.sct->log_id.length())) |
| .SetTimestamp((sct.sct->timestamp - base::Time::UnixEpoch()) |
| .InMillisecondsF()) |
| .SetHashAlgorithm(net::ct::HashAlgorithmToString( |
| sct.sct->signature.hash_algorithm)) |
| .SetSignatureAlgorithm(net::ct::SignatureAlgorithmToString( |
| sct.sct->signature.signature_algorithm)) |
| .SetSignatureData( |
| base::HexEncode(sct.sct->signature.signature_data.c_str(), |
| sct.sct->signature.signature_data.length())) |
| .Build(); |
| signed_certificate_timestamp_list->addItem( |
| std::move(signed_certificate_timestamp)); |
| } |
| std::vector<std::string> san_dns; |
| std::vector<std::string> san_ip; |
| ssl_info.cert->GetSubjectAltName(&san_dns, &san_ip); |
| std::unique_ptr<Array<String>> san_list = Array<String>::create(); |
| for (const std::string& san : san_dns) |
| san_list->addItem(san); |
| for (const std::string& san : san_ip) { |
| san_list->addItem( |
| net::IPAddress(reinterpret_cast<const uint8_t*>(san.data()), san.size()) |
| .ToString()); |
| } |
| |
| const char* protocol = ""; |
| const char* key_exchange = ""; |
| const char* cipher = ""; |
| const char* mac = nullptr; |
| |
| int ssl_version = |
| net::SSLConnectionStatusToVersion(ssl_info.connection_status); |
| |
| if (ssl_info.connection_status) { |
| net::SSLVersionToString(&protocol, ssl_version); |
| |
| bool is_aead; |
| bool is_tls13; |
| uint16_t cipher_suite = |
| net::SSLConnectionStatusToCipherSuite(ssl_info.connection_status); |
| net::SSLCipherSuiteToStrings(&key_exchange, &cipher, &mac, &is_aead, |
| &is_tls13, cipher_suite); |
| if (key_exchange == nullptr) { |
| DCHECK(is_tls13); |
| key_exchange = ""; |
| } |
| } |
| |
| std::unique_ptr<protocol::Network::SecurityDetails> security_details = |
| protocol::Network::SecurityDetails::Create() |
| .SetProtocol(protocol) |
| .SetKeyExchange(key_exchange) |
| .SetCipher(cipher) |
| .SetSubjectName(ssl_info.cert->subject().common_name) |
| .SetSanList(std::move(san_list)) |
| .SetIssuer(ssl_info.cert->issuer().common_name) |
| .SetValidFrom(ssl_info.cert->valid_start().ToDoubleT()) |
| .SetValidTo(ssl_info.cert->valid_expiry().ToDoubleT()) |
| .SetCertificateId(0) // Keep this in protocol for compatability. |
| .SetSignedCertificateTimestampList( |
| std::move(signed_certificate_timestamp_list)) |
| .SetCertificateTransparencyCompliance( |
| SerializeCTPolicyCompliance(ssl_info.ct_policy_compliance)) |
| .Build(); |
| |
| if (ssl_info.key_exchange_group != 0) { |
| const char* key_exchange_group = |
| SSL_get_curve_name(ssl_info.key_exchange_group); |
| if (key_exchange_group) |
| security_details->SetKeyExchangeGroup(key_exchange_group); |
| } |
| if (mac) |
| security_details->SetMac(mac); |
| |
| return security_details; |
| } |
| |
| std::unique_ptr<Network::Response> BuildResponse( |
| const GURL& url, |
| const network::ResourceResponseInfo& info) { |
| std::unique_ptr<DictionaryValue> headers_dict(DictionaryValue::create()); |
| int status = 0; |
| std::string status_text; |
| if (info.headers) { |
| size_t iterator = 0; |
| std::string name; |
| std::string value; |
| while (info.headers->EnumerateHeaderLines(&iterator, &name, &value)) { |
| std::string old_value; |
| bool merge_with_another = headers_dict->getString(name, &old_value); |
| headers_dict->setString( |
| name, merge_with_another ? old_value + '\n' + value : value); |
| } |
| status = info.headers->response_code(); |
| status_text = info.headers->GetStatusText(); |
| } else if (url.SchemeIs(url::kDataScheme)) { |
| status = net::HTTP_OK; |
| status_text = "OK"; |
| } |
| |
| auto response = |
| Network::Response::Create() |
| .SetUrl(StripFragment(url)) |
| .SetStatus(status) |
| .SetStatusText(status_text) |
| .SetHeaders(Object::fromValue(headers_dict.get(), nullptr)) |
| .SetMimeType(info.mime_type) |
| .SetConnectionReused(info.load_timing.socket_reused) |
| .SetConnectionId(info.load_timing.socket_log_id) |
| .SetSecurityState(securityState(url, info.cert_status)) |
| .SetEncodedDataLength(info.encoded_data_length) |
| .SetTiming(GetTiming(info.load_timing)) |
| .SetFromDiskCache(!info.load_timing.request_start_time.is_null() && |
| info.response_time < |
| info.load_timing.request_start_time) |
| .Build(); |
| response->SetFromServiceWorker(info.was_fetched_via_service_worker); |
| network::HttpRawRequestResponseInfo* raw_info = |
| info.raw_request_response_info.get(); |
| if (raw_info) { |
| if (raw_info->http_status_code) { |
| response->SetStatus(raw_info->http_status_code); |
| response->SetStatusText(raw_info->http_status_text); |
| } |
| if (raw_info->request_headers.size()) { |
| response->SetRequestHeaders(GetHeaders(raw_info->request_headers)); |
| } |
| if (!raw_info->request_headers_text.empty()) { |
| response->SetRequestHeadersText(raw_info->request_headers_text); |
| } |
| if (raw_info->response_headers.size()) |
| response->SetHeaders(GetHeaders(raw_info->response_headers)); |
| if (!raw_info->response_headers_text.empty()) |
| response->SetHeadersText(raw_info->response_headers_text); |
| } |
| response->SetProtocol(GetProtocol(url, info)); |
| response->SetRemoteIPAddress(info.socket_address.HostForURL()); |
| response->SetRemotePort(info.socket_address.port()); |
| if (info.ssl_info.has_value()) |
| response->SetSecurityDetails(BuildSecurityDetails(*info.ssl_info)); |
| |
| return response; |
| } |
| } // namespace |
| |
| void NetworkHandler::NavigationRequestWillBeSent( |
| const NavigationRequest& nav_request) { |
| if (!enabled_) |
| return; |
| |
| net::HttpRequestHeaders headers; |
| headers.AddHeadersFromString(nav_request.begin_params()->headers); |
| std::unique_ptr<DictionaryValue> headers_dict(DictionaryValue::create()); |
| for (net::HttpRequestHeaders::Iterator it(headers); it.GetNext();) |
| headers_dict->setString(it.name(), it.value()); |
| |
| const CommonNavigationParams& common_params = nav_request.common_params(); |
| GURL referrer = common_params.referrer.url; |
| // This is normally added down the stack, so we have to fake it here. |
| if (!referrer.is_empty()) |
| headers_dict->setString(net::HttpRequestHeaders::kReferer, referrer.spec()); |
| |
| std::unique_ptr<Network::Response> redirect_response; |
| const RequestNavigationParams& request_params = nav_request.request_params(); |
| if (!request_params.redirect_response.empty()) { |
| redirect_response = BuildResponse(request_params.redirects.back(), |
| request_params.redirect_response.back()); |
| } |
| auto request = |
| Network::Request::Create() |
| .SetUrl(StripFragment(common_params.url)) |
| .SetMethod(common_params.method) |
| .SetHeaders(Object::fromValue(headers_dict.get(), nullptr)) |
| .SetInitialPriority(resourcePriority(net::HIGHEST)) |
| .SetReferrerPolicy(referrerPolicy(common_params.referrer.policy)) |
| .Build(); |
| |
| std::string post_data; |
| if (common_params.post_data && |
| GetPostData(*common_params.post_data, &post_data)) { |
| request->SetPostData(post_data); |
| } |
| // TODO(caseq): report potentially blockable types |
| request->SetMixedContentType(Security::MixedContentTypeEnum::None); |
| |
| std::unique_ptr<Network::Initiator> initiator; |
| const base::Optional<base::Value>& initiator_optional = |
| nav_request.begin_params()->devtools_initiator; |
| if (initiator_optional.has_value()) { |
| ErrorSupport ignored_errors; |
| initiator = Network::Initiator::fromValue( |
| toProtocolValue(&initiator_optional.value(), 1000).get(), |
| &ignored_errors); |
| } |
| if (!initiator) { |
| initiator = Network::Initiator::Create() |
| .SetType(Network::Initiator::TypeEnum::Other) |
| .Build(); |
| } |
| std::string id = nav_request.devtools_navigation_token().ToString(); |
| double current_ticks = |
| (base::TimeTicks::Now() - base::TimeTicks()).InSecondsF(); |
| double current_wall_time = base::Time::Now().ToDoubleT(); |
| std::string frame_token = |
| nav_request.frame_tree_node()->devtools_frame_token().ToString(); |
| frontend_->RequestWillBeSent( |
| id, id, StripFragment(common_params.url), std::move(request), |
| current_ticks, current_wall_time, std::move(initiator), |
| std::move(redirect_response), |
| std::string(Page::ResourceTypeEnum::Document), std::move(frame_token), |
| common_params.has_user_gesture); |
| } |
| |
| void NetworkHandler::RequestSent(const std::string& request_id, |
| const std::string& loader_id, |
| const network::ResourceRequest& request, |
| const char* initiator_type) { |
| 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, StripFragment(request.url), |
| Network::Request::Create() |
| .SetUrl(StripFragment(request.url)) |
| .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(initiator_type).Build(), |
| std::unique_ptr<Network::Response>(), |
| std::string(Page::ResourceTypeEnum::Other), |
| Maybe<std::string>() /* frame_id */, request.has_user_gesture); |
| } |
| |
| void NetworkHandler::ResponseReceived(const std::string& request_id, |
| const std::string& loader_id, |
| const GURL& url, |
| const char* resource_type, |
| const network::ResourceResponseHead& head, |
| Maybe<std::string> frame_id) { |
| if (!enabled_) |
| return; |
| std::unique_ptr<Network::Response> response(BuildResponse(url, head)); |
| frontend_->ResponseReceived( |
| request_id, loader_id, |
| base::TimeTicks::Now().ToInternalValue() / |
| static_cast<double>(base::Time::kMicrosecondsPerSecond), |
| resource_type, std::move(response), std::move(frame_id)); |
| } |
| |
| void NetworkHandler::LoadingComplete( |
| const std::string& request_id, |
| const char* resource_type, |
| 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), |
| resource_type, net::ErrorToString(status.error_code), |
| status.error_code == net::Error::ERR_ABORTED); |
| return; |
| } |
| frontend_->LoadingFinished( |
| request_id, |
| status.completion_time.ToInternalValue() / |
| static_cast<double>(base::Time::kMicrosecondsPerSecond), |
| status.encoded_data_length); |
| } |
| |
| void NetworkHandler::OnSignedExchangeReceived( |
| base::Optional<const base::UnguessableToken> devtools_navigation_token, |
| const GURL& outer_request_url, |
| const network::ResourceResponseHead& outer_response) { |
| if (!enabled_) |
| return; |
| std::unique_ptr<Network::SignedExchangeInfo> signed_exchange_info = |
| Network::SignedExchangeInfo::Create() |
| .SetOuterResponse(BuildResponse(outer_request_url, outer_response)) |
| .Build(); |
| |
| frontend_->SignedExchangeReceived( |
| devtools_navigation_token ? devtools_navigation_token->ToString() : "", |
| std::move(signed_exchange_info)); |
| } |
| |
| DispatchResponse NetworkHandler::SetRequestInterception( |
| std::unique_ptr<protocol::Array<protocol::Network::RequestPattern>> |
| patterns) { |
| if (!patterns->length()) { |
| interception_handle_.reset(); |
| url_loader_interceptor_.reset(); |
| return Response::OK(); |
| } |
| |
| std::vector<DevToolsNetworkInterceptor::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(DevToolsNetworkInterceptor::Pattern( |
| patterns->get(i)->GetUrlPattern("*"), std::move(resource_types), |
| ToInterceptorStage(patterns->get(i)->GetInterceptionStage( |
| protocol::Network::InterceptionStageEnum::Request)))); |
| } |
| |
| if (!host_) |
| return Response::InternalError(); |
| |
| if (base::FeatureList::IsEnabled(network::features::kNetworkService)) { |
| if (!url_loader_interceptor_) { |
| url_loader_interceptor_ = std::make_unique<DevToolsURLLoaderInterceptor>( |
| host_->frame_tree_node(), |
| base::BindRepeating(&NetworkHandler::RequestIntercepted, |
| weak_factory_.GetWeakPtr())); |
| } |
| url_loader_interceptor_->SetPatterns(interceptor_patterns); |
| return Response::OK(); |
| } |
| |
| 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 (interception_handle_) { |
| interception_handle_->UpdatePatterns(interceptor_patterns); |
| } else { |
| interception_handle_ = interceptor->StartInterceptingRequests( |
| host_->frame_tree_node(), interceptor_patterns, |
| base::BindRepeating(&NetworkHandler::RequestIntercepted, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| return Response::OK(); |
| } |
| |
| 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) { |
| 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; |
| } |
| |
| auto modifications = |
| std::make_unique<DevToolsNetworkInterceptor::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); |
| |
| if (url_loader_interceptor_) { |
| url_loader_interceptor_->ContinueInterceptedRequest( |
| interception_id, std::move(modifications), std::move(callback)); |
| return; |
| } |
| |
| DevToolsInterceptorController* interceptor = |
| DevToolsInterceptorController::FromBrowserContext(browser_context_); |
| if (!interceptor) { |
| callback->sendFailure(Response::InternalError()); |
| return; |
| } |
| interceptor->ContinueInterceptedRequest( |
| interception_id, std::move(modifications), std::move(callback)); |
| } |
| |
| void NetworkHandler::GetResponseBodyForInterception( |
| const String& interception_id, |
| std::unique_ptr<GetResponseBodyForInterceptionCallback> callback) { |
| if (url_loader_interceptor_) { |
| url_loader_interceptor_->GetResponseBody(interception_id, |
| std::move(callback)); |
| return; |
| } |
| |
| DevToolsInterceptorController* interceptor = |
| DevToolsInterceptorController::FromBrowserContext(browser_context_); |
| if (!interceptor) { |
| callback->sendFailure(Response::InternalError()); |
| return; |
| } |
| interceptor->GetResponseBody(interception_id, std::move(callback)); |
| } |
| |
| void NetworkHandler::TakeResponseBodyForInterceptionAsStream( |
| const String& interception_id, |
| std::unique_ptr<TakeResponseBodyForInterceptionAsStreamCallback> callback) { |
| if (url_loader_interceptor_) { |
| url_loader_interceptor_->TakeResponseBodyPipe( |
| interception_id, |
| base::BindOnce(&NetworkHandler::OnResponseBodyPipeTaken, |
| weak_factory_.GetWeakPtr(), std::move(callback))); |
| return; |
| } |
| callback->sendFailure(Response::Error( |
| "Network.takeResponseBodyForInterceptionAsStream is only " |
| "currently supported with --enable-features=NetworkService")); |
| } |
| |
| void NetworkHandler::OnResponseBodyPipeTaken( |
| std::unique_ptr<TakeResponseBodyForInterceptionAsStreamCallback> callback, |
| Response response, |
| mojo::ScopedDataPipeConsumerHandle pipe, |
| const std::string& mime_type) { |
| DCHECK_EQ(response.isSuccess(), pipe.is_valid()); |
| if (!response.isSuccess()) { |
| callback->sendFailure(std::move(response)); |
| return; |
| } |
| // The pipe stream is owned only by io_context after we return. |
| bool is_binary = !DevToolsIOContext::IsTextMimeType(mime_type); |
| auto stream = |
| DevToolsStreamPipe::Create(io_context_, std::move(pipe), is_binary); |
| callback->sendSuccess(stream->handle()); |
| } |
| |
| // 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::CreateRequestFromResourceRequest( |
| const network::ResourceRequest& request) { |
| std::unique_ptr<DictionaryValue> headers_dict(DictionaryValue::create()); |
| for (net::HttpRequestHeaders::Iterator it(request.headers); it.GetNext();) |
| headers_dict->setString(it.name(), it.value()); |
| if (request.referrer.is_valid()) { |
| headers_dict->setString(net::HttpRequestHeaders::kReferer, |
| request.referrer.spec()); |
| } |
| 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 (request.request_body && GetPostData(*request.request_body, &post_data)) |
| request_object->SetPostData(std::move(post_data)); |
| return request_object; |
| } |
| |
| // 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); |
| } |
| |
| bool NetworkHandler::MaybeCreateProxyForInterception( |
| const base::UnguessableToken& frame_token, |
| int process_id, |
| bool is_download, |
| network::mojom::URLLoaderFactoryRequest* target_factory_request) { |
| return url_loader_interceptor_ && |
| url_loader_interceptor_->CreateProxyForInterception( |
| frame_token, process_id, is_download, target_factory_request); |
| } |
| |
| void NetworkHandler::ApplyOverrides(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->is_download), |
| 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(); |
| bool offline = conditions ? conditions->offline : false; |
| context->SetNetworkConditions(host_id_, std::move(conditions)); |
| |
| if (offline == !!background_sync_restorer_) |
| return; |
| background_sync_restorer_.reset( |
| offline ? new BackgroundSyncRestorer(host_id_, storage_partition_) |
| : nullptr); |
| } |
| |
| } // namespace protocol |
| } // namespace content |