| // 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 "base/barrier_closure.h" |
| #include "base/base64.h" |
| #include "base/command_line.h" |
| #include "base/containers/hash_tables.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_session.h" |
| #include "content/browser/devtools/devtools_url_interceptor_request_job.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/render_frame_host_impl.h" |
| #include "content/common/navigation_params.h" |
| #include "content/common/resource_request.h" |
| #include "content/common/resource_request_completion_status.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/render_process_host.h" |
| #include "content/public/browser/resource_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 "content/public/common/resource_devtools_info.h" |
| #include "content/public/common/resource_response.h" |
| #include "net/base/net_errors.h" |
| #include "net/base/upload_bytes_element_reader.h" |
| #include "net/cookies/cookie_store.h" |
| #include "net/http/http_response_headers.h" |
| #include "net/url_request/url_request_context.h" |
| #include "net/url_request/url_request_context_getter.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 DeleteCookieCallback = Network::Backend::DeleteCookieCallback; |
| using ClearBrowserCookiesCallback = |
| Network::Backend::ClearBrowserCookiesCallback; |
| |
| net::URLRequestContext* GetRequestContextOnIO( |
| ResourceContext* resource_context, |
| net::URLRequestContextGetter* context_getter, |
| const GURL& url) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| net::URLRequestContext* context = |
| GetContentClient()->browser()->OverrideRequestContextForURL( |
| url, resource_context); |
| if (!context) |
| context = context_getter->GetURLRequestContext(); |
| return context; |
| } |
| |
| 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( |
| ResourceContext* resource_context, |
| 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 = |
| GetRequestContextOnIO(resource_context, context_getter, url); |
| request_context->cookie_store()->GetAllCookiesForURLAsync(url, |
| base::Bind(&CookieRetriever::GotCookies, this)); |
| } |
| } |
| |
| void RetrieveAllCookiesOnIO( |
| ResourceContext* resource_context, |
| 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::Bind(&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::Bind(&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().ToDoubleT() * 1000) |
| .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, |
| int num_deleted) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, |
| base::Bind(&ClearBrowserCookiesCallback::sendSuccess, |
| base::Passed(std::move(callback)))); |
| } |
| |
| void ClearCookiesOnIO(ResourceContext* resource_context, |
| 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::Bind(&ClearedCookiesOnIO, base::Passed(std::move(callback)))); |
| } |
| |
| void DeletedCookieOnIO(std::unique_ptr<DeleteCookieCallback> callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| BrowserThread::PostTask( |
| BrowserThread::UI, |
| FROM_HERE, |
| base::Bind(&DeleteCookieCallback::sendSuccess, |
| base::Passed(std::move(callback)))); |
| } |
| |
| void DeleteCookieOnIO( |
| ResourceContext* resource_context, |
| net::URLRequestContextGetter* context_getter, |
| const GURL& url, |
| const std::string& cookie_name, |
| std::unique_ptr<DeleteCookieCallback> callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| net::URLRequestContext* request_context = |
| GetRequestContextOnIO(resource_context, context_getter, url); |
| request_context->cookie_store()->DeleteCookieAsync( |
| url, cookie_name, base::Bind(&DeletedCookieOnIO, |
| base::Passed(std::move(callback)))); |
| } |
| |
| void CookieSetOnIO(std::unique_ptr<SetCookieCallback> callback, bool success) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| BrowserThread::PostTask( |
| BrowserThread::UI, |
| FROM_HERE, |
| base::Bind(&SetCookieCallback::sendSuccess, |
| base::Passed(std::move(callback)), |
| success)); |
| } |
| |
| void SetCookieOnIO( |
| ResourceContext* resource_context, |
| net::URLRequestContextGetter* context_getter, |
| const GURL& url, |
| const std::string& name, |
| const std::string& value, |
| const std::string& domain, |
| const std::string& path, |
| bool secure, |
| bool http_only, |
| net::CookieSameSite same_site, |
| base::Time expires, |
| std::unique_ptr<SetCookieCallback> callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| net::URLRequestContext* request_context = |
| GetRequestContextOnIO(resource_context, context_getter, url); |
| |
| request_context->cookie_store()->SetCookieWithDetailsAsync( |
| url, name, value, domain, path, |
| base::Time(), |
| expires, |
| base::Time(), |
| secure, |
| http_only, |
| same_site, |
| net::COOKIE_PRIORITY_DEFAULT, |
| base::Bind(&CookieSetOnIO, base::Passed(std::move(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 { |
| std::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; |
| } |
| |
| net::Error NetErrorFromString(const std::string& error) { |
| 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; |
| return net::ERR_FAILED; |
| } |
| |
| 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 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; |
| } |
| |
| } // namespace |
| |
| NetworkHandler::NetworkHandler() |
| : DevToolsDomainHandler(Network::Metainfo::domainName), |
| host_(nullptr), |
| enabled_(false), |
| interception_enabled_(false), |
| weak_factory_(this) {} |
| |
| 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::SetRenderFrameHost(RenderFrameHostImpl* host) { |
| host_ = host; |
| } |
| |
| Response NetworkHandler::Enable(Maybe<int> max_total_size, |
| Maybe<int> max_resource_size) { |
| enabled_ = true; |
| return Response::FallThrough(); |
| } |
| |
| Response NetworkHandler::Disable() { |
| enabled_ = false; |
| user_agent_ = std::string(); |
| EnableRequestInterception(false); |
| return Response::FallThrough(); |
| } |
| |
| Response NetworkHandler::ClearBrowserCache() { |
| if (host_) { |
| content::BrowsingDataRemover* remover = |
| content::BrowserContext::GetBrowsingDataRemover( |
| host_->GetSiteInstance()->GetProcess()->GetBrowserContext()); |
| remover->Remove(base::Time(), base::Time::Max(), |
| content::BrowsingDataRemover::DATA_TYPE_CACHE, |
| content::BrowsingDataRemover::ORIGIN_TYPE_UNPROTECTED_WEB); |
| } |
| |
| return Response::OK(); |
| } |
| |
| void NetworkHandler::ClearBrowserCookies( |
| std::unique_ptr<ClearBrowserCookiesCallback> callback) { |
| if (!host_) { |
| callback->sendFailure(Response::InternalError()); |
| return; |
| } |
| |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::Bind(&ClearCookiesOnIO, |
| base::Unretained(host_->GetSiteInstance() |
| ->GetBrowserContext() |
| ->GetResourceContext()), |
| base::Unretained(host_->GetProcess() |
| ->GetStoragePartition() |
| ->GetURLRequestContext()), |
| base::Passed(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::Bind(&CookieRetriever::RetrieveCookiesOnIO, |
| retriever, |
| base::Unretained(host_->GetSiteInstance() |
| ->GetBrowserContext() |
| ->GetResourceContext()), |
| base::Unretained(host_->GetProcess() |
| ->GetStoragePartition() |
| ->GetURLRequestContext()), |
| urls)); |
| } |
| |
| void NetworkHandler::GetAllCookies( |
| std::unique_ptr<GetAllCookiesCallback> callback) { |
| if (!host_) { |
| callback->sendFailure(Response::InternalError()); |
| return; |
| } |
| |
| scoped_refptr<CookieRetriever> retriever = |
| new CookieRetriever(std::move(callback)); |
| |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::Bind(&CookieRetriever::RetrieveAllCookiesOnIO, |
| retriever, |
| base::Unretained(host_->GetSiteInstance() |
| ->GetBrowserContext() |
| ->GetResourceContext()), |
| base::Unretained(host_->GetProcess() |
| ->GetStoragePartition() |
| ->GetURLRequestContext()))); |
| } |
| |
| void NetworkHandler::SetCookie( |
| const std::string& url, |
| const std::string& name, |
| const std::string& value, |
| 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 (!host_) { |
| callback->sendFailure(Response::InternalError()); |
| return; |
| } |
| |
| net::CookieSameSite same_site_enum = net::CookieSameSite::DEFAULT_MODE; |
| if (same_site.isJust()) { |
| if (same_site.fromJust() == Network::CookieSameSiteEnum::Lax) |
| same_site_enum = net::CookieSameSite::LAX_MODE; |
| else if (same_site.fromJust() == Network::CookieSameSiteEnum::Strict) |
| same_site_enum = net::CookieSameSite::STRICT_MODE; |
| } |
| |
| base::Time expiration_date; |
| if (expires.isJust()) { |
| expiration_date = expires.fromJust() == 0 |
| ? base::Time::UnixEpoch() |
| : base::Time::FromDoubleT(expires.fromJust()); |
| } |
| |
| BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind( |
| &SetCookieOnIO, |
| base::Unretained(host_->GetSiteInstance()->GetBrowserContext()-> |
| GetResourceContext()), |
| base::Unretained(host_->GetProcess()->GetStoragePartition()-> |
| GetURLRequestContext()), |
| GURL(url), name, value, domain.fromMaybe(""), path.fromMaybe(""), |
| secure.fromMaybe(false), http_only.fromMaybe(false), same_site_enum, |
| expiration_date, base::Passed(std::move(callback)))); |
| } |
| |
| void NetworkHandler::DeleteCookie( |
| const std::string& cookie_name, |
| const std::string& url, |
| std::unique_ptr<DeleteCookieCallback> callback) { |
| if (!host_) { |
| callback->sendFailure(Response::InternalError()); |
| return; |
| } |
| BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind( |
| &DeleteCookieOnIO, |
| base::Unretained(host_->GetSiteInstance()->GetBrowserContext()-> |
| GetResourceContext()), |
| base::Unretained(host_->GetProcess()->GetStoragePartition()-> |
| GetURLRequestContext()), |
| GURL(url), |
| cookie_name, |
| base::Passed(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::CanEmulateNetworkConditions(bool* result) { |
| *result = false; |
| return Response::OK(); |
| } |
| |
| void NetworkHandler::NavigationPreloadRequestSent( |
| int worker_version_id, |
| const std::string& request_id, |
| const ResourceRequest& request) { |
| if (!enabled_) |
| return; |
| const std::string version_id(base::IntToString(worker_version_id)); |
| std::unique_ptr<DictionaryValue> headers_dict(DictionaryValue::create()); |
| net::HttpRequestHeaders headers; |
| headers.AddHeadersFromString(request.headers); |
| for (net::HttpRequestHeaders::Iterator it(headers); it.GetNext();) |
| headers_dict->setString(it.name(), it.value()); |
| frontend_->RequestWillBeSent( |
| request_id, version_id /* frameId */, version_id /* loaderId */, |
| "" /* documentURL */, |
| 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( |
| int worker_version_id, |
| const std::string& request_id, |
| const GURL& url, |
| const ResourceResponseHead& head) { |
| if (!enabled_) |
| return; |
| const std::string version_id(base::IntToString(worker_version_id)); |
| 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.devtools_info) { |
| if (head.devtools_info->http_status_code) { |
| response->SetStatus(head.devtools_info->http_status_code); |
| response->SetStatusText(head.devtools_info->http_status_text); |
| } |
| if (head.devtools_info->request_headers.size()) { |
| response->SetRequestHeaders( |
| getHeaders(head.devtools_info->request_headers)); |
| } |
| if (!head.devtools_info->request_headers_text.empty()) { |
| response->SetRequestHeadersText( |
| head.devtools_info->request_headers_text); |
| } |
| if (head.devtools_info->response_headers.size()) |
| response->SetHeaders(getHeaders(head.devtools_info->response_headers)); |
| if (!head.devtools_info->response_headers_text.empty()) |
| response->SetHeadersText(head.devtools_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, version_id /* frameId */, version_id /* loaderId */, |
| 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 ResourceRequestCompletionStatus& completion_status) { |
| if (!enabled_) |
| return; |
| if (completion_status.error_code != net::OK) { |
| frontend_->LoadingFailed( |
| request_id, |
| base::TimeTicks::Now().ToInternalValue() / |
| static_cast<double>(base::Time::kMicrosecondsPerSecond), |
| Page::ResourceTypeEnum::Other, |
| net::ErrorToString(completion_status.error_code), |
| completion_status.error_code == net::Error::ERR_ABORTED); |
| } |
| frontend_->LoadingFinished( |
| request_id, |
| completion_status.completion_time.ToInternalValue() / |
| static_cast<double>(base::Time::kMicrosecondsPerSecond), |
| completion_status.encoded_data_length); |
| } |
| |
| void NetworkHandler::NavigationFailed( |
| const CommonNavigationParams& common_params, |
| const BeginNavigationParams& begin_params, |
| net::Error error_code) { |
| 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(error_code); |
| bool cancelled = error_code == net::Error::ERR_ABORTED; |
| |
| std::unique_ptr<DictionaryValue> headers_dict(DictionaryValue::create()); |
| net::HttpRequestHeaders headers; |
| headers.AddHeadersFromString(begin_params.headers); |
| for (net::HttpRequestHeaders::Iterator it(headers); it.GetNext();) |
| headers_dict->setString(it.name(), it.value()); |
| frontend_->RequestWillBeSent( |
| request_id, request_id /* frameId */, request_id /* loaderId */, |
| common_params.url.spec(), |
| Network::Request::Create() |
| .SetUrl(common_params.url.spec()) |
| .SetMethod(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(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); |
| } |
| |
| std::string NetworkHandler::UserAgentOverride() const { |
| return enabled_ ? user_agent_ : std::string(); |
| } |
| |
| DispatchResponse NetworkHandler::EnableRequestInterception(bool enabled) { |
| if (interception_enabled_ == enabled) |
| return Response::OK(); // Nothing to do. |
| |
| WebContents* web_contents = WebContents::FromRenderFrameHost(host_); |
| if (!web_contents) |
| return Response::OK(); |
| |
| DevToolsURLRequestInterceptor* devtools_url_request_interceptor = |
| DevToolsURLRequestInterceptor::FromBrowserContext( |
| web_contents->GetBrowserContext()); |
| if (!devtools_url_request_interceptor) |
| return Response::OK(); |
| |
| if (enabled) { |
| devtools_url_request_interceptor->state()->StartInterceptingRequests( |
| web_contents, weak_factory_.GetWeakPtr()); |
| } else { |
| devtools_url_request_interceptor->state()->StopInterceptingRequests( |
| web_contents); |
| } |
| interception_enabled_ = enabled; |
| 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(alexclarke): This should really be base64 encoded. |
| *post_data += std::string(reader->bytes(), reader->length()); |
| } |
| return true; |
| } |
| } // namespace |
| |
| // TODO(alexclarke): Support structured data as well as |base64_raw_response|. |
| 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, |
| std::unique_ptr<ContinueInterceptedRequestCallback> callback) { |
| DevToolsURLRequestInterceptor* devtools_url_request_interceptor = |
| DevToolsURLRequestInterceptor::FromBrowserContext( |
| WebContents::FromRenderFrameHost(host_)->GetBrowserContext()); |
| if (!devtools_url_request_interceptor) { |
| callback->sendFailure(Response::InternalError()); |
| return; |
| } |
| |
| base::Optional<net::Error> error; |
| if (error_reason.isJust()) |
| error = NetErrorFromString(error_reason.fromJust()); |
| |
| 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; |
| } |
| |
| devtools_url_request_interceptor->state()->ContinueInterceptedRequest( |
| interception_id, |
| base::MakeUnique<DevToolsURLRequestInterceptor::Modifications>( |
| std::move(error), std::move(raw_response), std::move(url), |
| std::move(method), std::move(post_data), std::move(headers)), |
| std::move(callback)); |
| } |
| |
| // 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()); |
| } |
| std::unique_ptr<protocol::Network::Request> request_object = |
| 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(); |
| std::string post_data; |
| if (GetPostData(request, &post_data)) |
| request_object->SetPostData(std::move(post_data)); |
| return request_object; |
| } |
| |
| } // namespace protocol |
| } // namespace content |