| // Copyright 2017 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/devtools_url_request_interceptor.h" |
| |
| #include "base/bind.h" |
| #include "base/strings/pattern.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/task/post_task.h" |
| #include "content/browser/devtools/devtools_interceptor_controller.h" |
| #include "content/browser/devtools/devtools_url_interceptor_request_job.h" |
| #include "content/browser/devtools/protocol/network_handler.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/resource_request_info.h" |
| #include "net/url_request/url_request.h" |
| #include "services/network/public/cpp/features.h" |
| |
| namespace content { |
| |
| // static |
| std::unique_ptr<DevToolsURLRequestInterceptor> |
| DevToolsURLRequestInterceptor::MaybeCreate(BrowserContext* browser_context) { |
| if (base::FeatureList::IsEnabled(network::features::kNetworkService)) |
| return nullptr; |
| |
| // TODO(caseq): Technically, this is wrong -- we currently maintain one |
| // interception controller per browser context, while in reality it is |
| // created per StoragePartition. |
| // So we only support interception for the first StoragePartition, which |
| // results in extensions not being supported. Luckily, this implementation |
| // of the interception is going away when network service is enabled. |
| if (DevToolsInterceptorController::FromBrowserContext(browser_context)) |
| return nullptr; |
| return std::make_unique<DevToolsURLRequestInterceptor>(browser_context); |
| } |
| |
| // static |
| bool DevToolsURLRequestInterceptor::IsNavigationRequest( |
| ResourceType resource_type) { |
| return resource_type == RESOURCE_TYPE_MAIN_FRAME || |
| resource_type == RESOURCE_TYPE_SUB_FRAME; |
| } |
| |
| DevToolsURLRequestInterceptor::DevToolsURLRequestInterceptor( |
| BrowserContext* browser_context) |
| : next_id_(0), weak_factory_(this) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| auto target_registry = std::make_unique<DevToolsTargetRegistry>( |
| base::CreateSingleThreadTaskRunnerWithTraits( |
| {content::BrowserThread::IO})); |
| target_resolver_ = target_registry->CreateResolver(); |
| // Controller lifetime is managed by the browser context. |
| auto* controller = new DevToolsInterceptorController( |
| weak_factory_.GetWeakPtr(), std::move(target_registry), browser_context); |
| controller_ = controller->weak_factory_.GetWeakPtr(); |
| } |
| |
| DevToolsURLRequestInterceptor::~DevToolsURLRequestInterceptor() { |
| // The BrowserContext owns us, so we don't need to unregister |
| // DevToolsURLRequestInterceptorUserData explicitly. |
| } |
| |
| const DevToolsTargetRegistry::TargetInfo* |
| DevToolsURLRequestInterceptor::TargetInfoForRequestInfo( |
| const ResourceRequestInfo* request_info) const { |
| int frame_node_id = request_info->GetFrameTreeNodeId(); |
| if (frame_node_id != -1) |
| return target_resolver_->GetInfoByFrameTreeNodeId(frame_node_id); |
| return target_resolver_->GetInfoByRenderFramePair( |
| request_info->GetChildID(), request_info->GetRenderFrameID()); |
| } |
| |
| void DevToolsURLRequestInterceptor::ContinueInterceptedRequest( |
| std::string interception_id, |
| std::unique_ptr<Modifications> modifications, |
| std::unique_ptr<ContinueInterceptedRequestCallback> callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DevToolsURLInterceptorRequestJob* job = GetJob(interception_id); |
| if (!job) { |
| base::PostTaskWithTraits( |
| FROM_HERE, {BrowserThread::UI}, |
| base::BindOnce( |
| &ContinueInterceptedRequestCallback::sendFailure, |
| std::move(callback), |
| protocol::Response::InvalidParams("Invalid InterceptionId."))); |
| return; |
| } |
| |
| job->ContinueInterceptedRequest(std::move(modifications), |
| std::move(callback)); |
| } |
| |
| net::URLRequestJob* DevToolsURLRequestInterceptor::MaybeInterceptRequest( |
| net::URLRequest* request, |
| net::NetworkDelegate* network_delegate) const { |
| return const_cast<DevToolsURLRequestInterceptor*>(this) |
| ->InnerMaybeInterceptRequest(request, network_delegate); |
| } |
| |
| net::URLRequestJob* DevToolsURLRequestInterceptor::MaybeInterceptRedirect( |
| net::URLRequest* request, |
| net::NetworkDelegate* network_delegate, |
| const GURL& location) const { |
| return nullptr; |
| } |
| |
| net::URLRequestJob* DevToolsURLRequestInterceptor::MaybeInterceptResponse( |
| net::URLRequest* request, |
| net::NetworkDelegate* network_delegate) const { |
| return nullptr; |
| } |
| |
| void DevToolsURLRequestInterceptor::GetResponseBody( |
| std::string interception_id, |
| std::unique_ptr<GetResponseBodyForInterceptionCallback> callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DevToolsURLInterceptorRequestJob* job = GetJob(interception_id); |
| if (!job) { |
| base::PostTaskWithTraits( |
| FROM_HERE, {BrowserThread::UI}, |
| base::BindOnce( |
| &GetResponseBodyForInterceptionCallback::sendFailure, |
| std::move(callback), |
| protocol::Response::InvalidParams("Invalid InterceptionId."))); |
| return; |
| } |
| |
| job->GetResponseBody(std::move(callback)); |
| } |
| |
| net::URLRequestJob* DevToolsURLRequestInterceptor::InnerMaybeInterceptRequest( |
| net::URLRequest* request, |
| net::NetworkDelegate* network_delegate) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| // Bail out if we're not intercepting anything. |
| if (target_id_to_entries_.empty()) |
| return nullptr; |
| // Don't try to intercept blob resources. |
| if (request->url().SchemeIsBlob()) |
| return nullptr; |
| const ResourceRequestInfo* resource_request_info = |
| ResourceRequestInfo::ForRequest(request); |
| if (!resource_request_info) |
| return nullptr; |
| const DevToolsTargetRegistry::TargetInfo* target_info = |
| TargetInfoForRequestInfo(resource_request_info); |
| if (!target_info) |
| return nullptr; |
| |
| // We don't want to intercept our own sub requests. |
| if (sub_requests_.find(request) != sub_requests_.end()) |
| return nullptr; |
| |
| ResourceType resource_type = resource_request_info->GetResourceType(); |
| InterceptionStage interception_stage; |
| FilterEntry* entry = |
| FilterEntryForRequest(target_info->devtools_target_id, request->url(), |
| resource_type, &interception_stage); |
| if (!entry) |
| return nullptr; |
| DCHECK(interception_stage != DONT_INTERCEPT); |
| |
| std::string interception_id = base::StringPrintf("id-%zu", ++next_id_); |
| |
| if (IsNavigationRequest(resource_type)) { |
| base::PostTaskWithTraits( |
| FROM_HERE, {BrowserThread::UI}, |
| base::BindOnce(&DevToolsInterceptorController::NavigationStarted, |
| controller_, interception_id, |
| resource_request_info->GetGlobalRequestID())); |
| } |
| |
| DevToolsURLInterceptorRequestJob* job = new DevToolsURLInterceptorRequestJob( |
| this, interception_id, reinterpret_cast<intptr_t>(entry), request, |
| network_delegate, target_info->devtools_token, entry->callback, |
| resource_request_info->GetResourceType(), interception_stage); |
| interception_id_to_job_map_[interception_id] = job; |
| return job; |
| } |
| |
| void DevToolsURLRequestInterceptor::AddFilterEntry( |
| std::unique_ptr<FilterEntry> entry) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| const base::UnguessableToken& target_id = entry->target_id; |
| auto it = target_id_to_entries_.find(target_id); |
| if (it == target_id_to_entries_.end()) { |
| it = target_id_to_entries_ |
| .emplace(target_id, std::vector<std::unique_ptr<FilterEntry>>()) |
| .first; |
| } |
| it->second.push_back(std::move(entry)); |
| } |
| |
| void DevToolsURLRequestInterceptor::RemoveFilterEntry( |
| const FilterEntry* entry) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| // NOTE: Calling DevToolsURLInterceptorRequestJob::StopIntercepting can |
| // destruct the jobs which can remove entries in |
| // |interception_id_to_job_map_|, so we make a copy. |
| base::flat_map<std::string, DevToolsURLInterceptorRequestJob*> jobs( |
| interception_id_to_job_map_); |
| for (const auto pair : jobs) { |
| if (pair.second->owning_entry_id() == reinterpret_cast<intptr_t>(entry)) |
| pair.second->StopIntercepting(); |
| } |
| |
| auto it = target_id_to_entries_.find(entry->target_id); |
| if (it == target_id_to_entries_.end()) |
| return; |
| base::EraseIf(it->second, [entry](const std::unique_ptr<FilterEntry>& e) { |
| return e.get() == entry; |
| }); |
| if (it->second.empty()) |
| target_id_to_entries_.erase(it); |
| } |
| |
| void DevToolsURLRequestInterceptor::UpdatePatterns( |
| FilterEntry* entry, |
| std::vector<Pattern> patterns) { |
| entry->patterns = std::move(patterns); |
| } |
| |
| DevToolsURLRequestInterceptor::FilterEntry* |
| DevToolsURLRequestInterceptor::FilterEntryForRequest( |
| const base::UnguessableToken target_id, |
| const GURL& url, |
| ResourceType resource_type, |
| InterceptionStage* stage) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| *stage = DONT_INTERCEPT; |
| |
| auto it = target_id_to_entries_.find(target_id); |
| if (it == target_id_to_entries_.end()) |
| return nullptr; |
| |
| const std::vector<std::unique_ptr<FilterEntry>>& entries = it->second; |
| std::string unused; |
| const std::string url_str = |
| protocol::NetworkHandler::ExtractFragment(url, &unused); |
| for (const auto& entry : entries) { |
| for (const Pattern& pattern : entry->patterns) { |
| if (!pattern.resource_types.empty() && |
| pattern.resource_types.find(resource_type) == |
| pattern.resource_types.end()) { |
| continue; |
| } |
| if (base::MatchPattern(url_str, pattern.url_pattern)) { |
| if (pattern.interception_stage == REQUEST && *stage == RESPONSE) { |
| *stage = BOTH; |
| break; |
| } else if (pattern.interception_stage == RESPONSE && |
| *stage == REQUEST) { |
| *stage = BOTH; |
| break; |
| } |
| *stage = pattern.interception_stage; |
| } |
| } |
| if (*stage != DONT_INTERCEPT) |
| return entry.get(); |
| } |
| return nullptr; |
| } |
| |
| void DevToolsURLRequestInterceptor::RegisterSubRequest( |
| const net::URLRequest* sub_request) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(sub_requests_.find(sub_request) == sub_requests_.end()); |
| sub_requests_.insert(sub_request); |
| } |
| |
| void DevToolsURLRequestInterceptor::UnregisterSubRequest( |
| const net::URLRequest* sub_request) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(sub_requests_.find(sub_request) != sub_requests_.end()); |
| sub_requests_.erase(sub_request); |
| } |
| |
| DevToolsURLInterceptorRequestJob* DevToolsURLRequestInterceptor::GetJob( |
| const std::string& interception_id) const { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| const auto it = interception_id_to_job_map_.find(interception_id); |
| if (it == interception_id_to_job_map_.end()) |
| return nullptr; |
| return it->second; |
| } |
| |
| void DevToolsURLRequestInterceptor::JobFinished( |
| const std::string& interception_id, |
| bool is_navigation) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| interception_id_to_job_map_.erase(interception_id); |
| if (!is_navigation) |
| return; |
| base::PostTaskWithTraits( |
| FROM_HERE, {BrowserThread::UI}, |
| base::BindOnce(&DevToolsInterceptorController::NavigationFinished, |
| controller_, interception_id)); |
| } |
| |
| } // namespace content |