OLD | NEW |
(Empty) | |
| 1 // Copyright 2017 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "content/browser/devtools/devtools_url_request_interceptor.h" |
| 6 |
| 7 #include "base/memory/ptr_util.h" |
| 8 #include "base/strings/stringprintf.h" |
| 9 #include "base/supports_user_data.h" |
| 10 #include "content/browser/devtools/devtools_agent_host_impl.h" |
| 11 #include "content/browser/devtools/devtools_url_interceptor_request_job.h" |
| 12 #include "content/browser/devtools/protocol/network_handler.h" |
| 13 #include "content/public/browser/browser_context.h" |
| 14 #include "content/public/browser/browser_thread.h" |
| 15 #include "content/public/browser/devtools_agent_host.h" |
| 16 #include "content/public/browser/render_frame_host.h" |
| 17 #include "content/public/browser/render_process_host.h" |
| 18 #include "content/public/browser/resource_request_info.h" |
| 19 #include "content/public/browser/web_contents.h" |
| 20 #include "content/public/browser/web_contents_observer.h" |
| 21 #include "net/http/http_request_headers.h" |
| 22 #include "net/url_request/url_request.h" |
| 23 |
| 24 namespace content { |
| 25 |
| 26 namespace { |
| 27 const char kDevToolsURLRequestInterceptorKeyName[] = |
| 28 "DevToolsURLRequestInterceptor"; |
| 29 |
| 30 class DevToolsURLRequestInterceptorUserData |
| 31 : public base::SupportsUserData::Data { |
| 32 public: |
| 33 explicit DevToolsURLRequestInterceptorUserData( |
| 34 DevToolsURLRequestInterceptor* devtools_url_request_interceptor) |
| 35 : devtools_url_request_interceptor_(devtools_url_request_interceptor) {} |
| 36 |
| 37 DevToolsURLRequestInterceptor* devtools_url_request_interceptor() const { |
| 38 return devtools_url_request_interceptor_; |
| 39 } |
| 40 |
| 41 private: |
| 42 DevToolsURLRequestInterceptor* devtools_url_request_interceptor_; |
| 43 |
| 44 DISALLOW_COPY_AND_ASSIGN(DevToolsURLRequestInterceptorUserData); |
| 45 }; |
| 46 |
| 47 } // namespace |
| 48 |
| 49 DevToolsURLRequestInterceptor::DevToolsURLRequestInterceptor( |
| 50 BrowserContext* browser_context) |
| 51 : browser_context_(browser_context), state_(new State()) { |
| 52 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| 53 browser_context_->SetUserData( |
| 54 kDevToolsURLRequestInterceptorKeyName, |
| 55 base::MakeUnique<DevToolsURLRequestInterceptorUserData>(this)); |
| 56 } |
| 57 |
| 58 DevToolsURLRequestInterceptor::~DevToolsURLRequestInterceptor() { |
| 59 // The BrowserContext owns us, so we don't need to unregister |
| 60 // DevToolsURLRequestInterceptorUserData explicitly. |
| 61 } |
| 62 |
| 63 net::URLRequestJob* DevToolsURLRequestInterceptor::MaybeInterceptRequest( |
| 64 net::URLRequest* request, |
| 65 net::NetworkDelegate* network_delegate) const { |
| 66 DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| 67 return state()->MaybeCreateDevToolsURLInterceptorRequestJob(request, |
| 68 network_delegate); |
| 69 } |
| 70 |
| 71 net::URLRequestJob* DevToolsURLRequestInterceptor::MaybeInterceptRedirect( |
| 72 net::URLRequest* request, |
| 73 net::NetworkDelegate* network_delegate, |
| 74 const GURL& location) const { |
| 75 return nullptr; |
| 76 } |
| 77 |
| 78 net::URLRequestJob* DevToolsURLRequestInterceptor::MaybeInterceptResponse( |
| 79 net::URLRequest* request, |
| 80 net::NetworkDelegate* network_delegate) const { |
| 81 return nullptr; |
| 82 } |
| 83 |
| 84 DevToolsURLRequestInterceptor::State::State() : next_id_(0) {} |
| 85 |
| 86 DevToolsURLRequestInterceptor::State::~State() {} |
| 87 |
| 88 void DevToolsURLRequestInterceptor::State::ContinueInterceptedRequest( |
| 89 std::string interception_id, |
| 90 std::unique_ptr<Modifications> modifications, |
| 91 std::unique_ptr<ContinueInterceptedRequestCallback> callback) { |
| 92 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| 93 |
| 94 BrowserThread::PostTask( |
| 95 BrowserThread::IO, FROM_HERE, |
| 96 base::Bind(&DevToolsURLRequestInterceptor::State:: |
| 97 ContinueInterceptedRequestOnIoThread, |
| 98 this, interception_id, base::Passed(std::move(modifications)), |
| 99 base::Passed(std::move(callback)))); |
| 100 } |
| 101 |
| 102 void DevToolsURLRequestInterceptor::State::ContinueInterceptedRequestOnIoThread( |
| 103 std::string interception_id, |
| 104 std::unique_ptr<Modifications> modifications, |
| 105 std::unique_ptr<ContinueInterceptedRequestCallback> callback) { |
| 106 DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| 107 DevToolsURLInterceptorRequestJob* job = GetJob(interception_id); |
| 108 if (!job) { |
| 109 BrowserThread::PostTask( |
| 110 BrowserThread::UI, FROM_HERE, |
| 111 base::BindOnce( |
| 112 &ContinueInterceptedRequestCallback::sendFailure, |
| 113 base::Passed(std::move(callback)), |
| 114 protocol::Response::InvalidParams("Invalid InterceptionId."))); |
| 115 return; |
| 116 } |
| 117 |
| 118 if (job->ContinueInterceptedRequest(std::move(modifications))) { |
| 119 BrowserThread::PostTask( |
| 120 BrowserThread::UI, FROM_HERE, |
| 121 base::BindOnce(&ContinueInterceptedRequestCallback::sendSuccess, |
| 122 base::Passed(std::move(callback)))); |
| 123 } else { |
| 124 BrowserThread::PostTask( |
| 125 BrowserThread::UI, FROM_HERE, |
| 126 base::BindOnce( |
| 127 &ContinueInterceptedRequestCallback::sendFailure, |
| 128 base::Passed(std::move(callback)), |
| 129 protocol::Response::InvalidParams("Response already processed."))); |
| 130 } |
| 131 } |
| 132 |
| 133 DevToolsURLInterceptorRequestJob* DevToolsURLRequestInterceptor::State:: |
| 134 MaybeCreateDevToolsURLInterceptorRequestJob( |
| 135 net::URLRequest* request, |
| 136 net::NetworkDelegate* network_delegate) { |
| 137 // Bail out if we're not intercepting anything. |
| 138 if (intercepted_render_frames_.empty()) { |
| 139 DCHECK(intercepted_frame_tree_nodes_.empty()); |
| 140 return nullptr; |
| 141 } |
| 142 |
| 143 // Don't try to intercept blob resources. |
| 144 if (request->url().SchemeIsBlob()) |
| 145 return nullptr; |
| 146 |
| 147 const ResourceRequestInfo* resource_request_info = |
| 148 ResourceRequestInfo::ForRequest(request); |
| 149 if (!resource_request_info) |
| 150 return nullptr; |
| 151 int child_id = resource_request_info->GetChildID(); |
| 152 int frame_tree_node_id = resource_request_info->GetFrameTreeNodeId(); |
| 153 const InterceptedPage* intercepted_page; |
| 154 if (frame_tree_node_id == -1) { |
| 155 // |frame_tree_node_id| is not set for renderer side requests, fall back to |
| 156 // the RenderFrameID. |
| 157 int render_frame_id = resource_request_info->GetRenderFrameID(); |
| 158 const auto find_it = intercepted_render_frames_.find( |
| 159 std::make_pair(render_frame_id, child_id)); |
| 160 if (find_it == intercepted_render_frames_.end()) |
| 161 return nullptr; |
| 162 intercepted_page = &find_it->second; |
| 163 } else { |
| 164 // |frame_tree_node_id| is set for browser side navigations, so use that |
| 165 // because the RenderFrameID isn't known (neither is the ChildID). |
| 166 const auto find_it = intercepted_frame_tree_nodes_.find(frame_tree_node_id); |
| 167 if (find_it == intercepted_frame_tree_nodes_.end()) |
| 168 return nullptr; |
| 169 intercepted_page = &find_it->second; |
| 170 } |
| 171 |
| 172 // We don't want to intercept our own sub requests. |
| 173 if (sub_requests_.find(request) != sub_requests_.end()) |
| 174 return nullptr; |
| 175 |
| 176 bool is_redirect; |
| 177 std::string interception_id = GetIdForRequest(request, &is_redirect); |
| 178 DevToolsURLInterceptorRequestJob* job = new DevToolsURLInterceptorRequestJob( |
| 179 this, interception_id, request, network_delegate, |
| 180 intercepted_page->web_contents, intercepted_page->network_handler, |
| 181 is_redirect); |
| 182 interception_id_to_job_map_[interception_id] = job; |
| 183 return job; |
| 184 } |
| 185 |
| 186 class DevToolsURLRequestInterceptor::State::InterceptedWebContentsObserver |
| 187 : public WebContentsObserver { |
| 188 public: |
| 189 InterceptedWebContentsObserver( |
| 190 WebContents* web_contents, |
| 191 scoped_refptr<DevToolsURLRequestInterceptor::State> state, |
| 192 base::WeakPtr<protocol::NetworkHandler> network_handler) |
| 193 : WebContentsObserver(web_contents), |
| 194 state_(state), |
| 195 network_handler_(network_handler) { |
| 196 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| 197 } |
| 198 |
| 199 void RenderFrameCreated(RenderFrameHost* render_frame_host) override { |
| 200 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| 201 BrowserThread::PostTask( |
| 202 BrowserThread::IO, FROM_HERE, |
| 203 base::BindOnce(&DevToolsURLRequestInterceptor::State:: |
| 204 StartInterceptingRequestsInternal, |
| 205 state_, render_frame_host->GetRoutingID(), |
| 206 render_frame_host->GetFrameTreeNodeId(), |
| 207 render_frame_host->GetProcess()->GetID(), web_contents(), |
| 208 network_handler_)); |
| 209 } |
| 210 |
| 211 void RenderFrameDeleted(RenderFrameHost* render_frame_host) override { |
| 212 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| 213 BrowserThread::PostTask( |
| 214 BrowserThread::IO, FROM_HERE, |
| 215 base::BindOnce(&DevToolsURLRequestInterceptor::State:: |
| 216 StopInterceptingRequestsInternal, |
| 217 state_, render_frame_host->GetRoutingID(), |
| 218 render_frame_host->GetFrameTreeNodeId(), |
| 219 render_frame_host->GetProcess()->GetID())); |
| 220 } |
| 221 |
| 222 private: |
| 223 scoped_refptr<DevToolsURLRequestInterceptor::State> state_; |
| 224 base::WeakPtr<protocol::NetworkHandler> network_handler_; |
| 225 }; |
| 226 |
| 227 void DevToolsURLRequestInterceptor::State::StartInterceptingRequestsInternal( |
| 228 int render_frame_id, |
| 229 int frame_tree_node_id, |
| 230 int process_id, |
| 231 WebContents* web_contents, |
| 232 base::WeakPtr<protocol::NetworkHandler> network_handler) { |
| 233 DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| 234 intercepted_render_frames_.emplace( |
| 235 std::piecewise_construct, |
| 236 std::forward_as_tuple(render_frame_id, process_id), |
| 237 std::forward_as_tuple(web_contents, network_handler)); |
| 238 intercepted_frame_tree_nodes_.emplace( |
| 239 std::piecewise_construct, std::forward_as_tuple(frame_tree_node_id), |
| 240 std::forward_as_tuple(web_contents, network_handler)); |
| 241 } |
| 242 |
| 243 void DevToolsURLRequestInterceptor::State::StopInterceptingRequestsInternal( |
| 244 int render_frame_id, |
| 245 int frame_tree_node_id, |
| 246 int process_id) { |
| 247 DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| 248 intercepted_render_frames_.erase(std::make_pair(render_frame_id, process_id)); |
| 249 intercepted_frame_tree_nodes_.erase(frame_tree_node_id); |
| 250 } |
| 251 |
| 252 void DevToolsURLRequestInterceptor::State::StartInterceptingRequests( |
| 253 WebContents* web_contents, |
| 254 base::WeakPtr<protocol::NetworkHandler> network_handler) { |
| 255 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| 256 // WebContents methods are UI thread only. |
| 257 for (RenderFrameHost* render_frame_host : web_contents->GetAllFrames()) { |
| 258 BrowserThread::PostTask( |
| 259 BrowserThread::IO, FROM_HERE, |
| 260 base::BindOnce(&DevToolsURLRequestInterceptor::State:: |
| 261 StartInterceptingRequestsInternal, |
| 262 this, render_frame_host->GetRoutingID(), |
| 263 render_frame_host->GetFrameTreeNodeId(), |
| 264 render_frame_host->GetProcess()->GetID(), web_contents, |
| 265 network_handler)); |
| 266 } |
| 267 |
| 268 // Listen for future updates. |
| 269 observers_.emplace(web_contents, |
| 270 base::MakeUnique<InterceptedWebContentsObserver>( |
| 271 web_contents, this, network_handler)); |
| 272 } |
| 273 |
| 274 void DevToolsURLRequestInterceptor::State::StopInterceptingRequests( |
| 275 WebContents* web_contents) { |
| 276 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| 277 observers_.erase(web_contents); |
| 278 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, |
| 279 base::BindOnce(&DevToolsURLRequestInterceptor::State:: |
| 280 StopInterceptingRequestsOnIoThread, |
| 281 this, web_contents)); |
| 282 } |
| 283 |
| 284 void DevToolsURLRequestInterceptor::State::StopInterceptingRequestsOnIoThread( |
| 285 WebContents* web_contents) { |
| 286 DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| 287 // Remove any intercepted render frames associated with |web_contents|. |
| 288 base::flat_map<std::pair<int, int>, InterceptedPage> |
| 289 remaining_intercepted_render_frames; |
| 290 for (const auto pair : intercepted_render_frames_) { |
| 291 if (pair.second.web_contents == web_contents) |
| 292 continue; |
| 293 remaining_intercepted_render_frames.insert(pair); |
| 294 } |
| 295 std::swap(remaining_intercepted_render_frames, intercepted_render_frames_); |
| 296 |
| 297 // Remove any intercepted frame tree nodes associated with |web_contents|. |
| 298 base::flat_map<int, InterceptedPage> remaining_intercepted_frame_tree_nodes; |
| 299 for (const auto pair : intercepted_frame_tree_nodes_) { |
| 300 if (pair.second.web_contents == web_contents) |
| 301 continue; |
| 302 remaining_intercepted_frame_tree_nodes.insert(pair); |
| 303 } |
| 304 std::swap(remaining_intercepted_frame_tree_nodes, |
| 305 intercepted_frame_tree_nodes_); |
| 306 |
| 307 // Tell any jobs associated with |web_contents| to stop intercepting. |
| 308 for (const auto pair : interception_id_to_job_map_) { |
| 309 if (pair.second->web_contents() == web_contents) |
| 310 pair.second->StopIntercepting(); |
| 311 } |
| 312 } |
| 313 |
| 314 void DevToolsURLRequestInterceptor::State::RegisterSubRequest( |
| 315 const net::URLRequest* sub_request) { |
| 316 DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| 317 DCHECK(sub_requests_.find(sub_request) == sub_requests_.end()); |
| 318 sub_requests_.insert(sub_request); |
| 319 } |
| 320 |
| 321 void DevToolsURLRequestInterceptor::State::UnregisterSubRequest( |
| 322 const net::URLRequest* sub_request) { |
| 323 DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| 324 DCHECK(sub_requests_.find(sub_request) != sub_requests_.end()); |
| 325 sub_requests_.erase(sub_request); |
| 326 } |
| 327 |
| 328 void DevToolsURLRequestInterceptor::State::ExpectRequestAfterRedirect( |
| 329 const net::URLRequest* request, |
| 330 std::string id) { |
| 331 DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| 332 expected_redirects_[request] = id; |
| 333 } |
| 334 |
| 335 std::string DevToolsURLRequestInterceptor::State::GetIdForRequest( |
| 336 const net::URLRequest* request, |
| 337 bool* is_redirect) { |
| 338 DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| 339 auto find_it = expected_redirects_.find(request); |
| 340 if (find_it == expected_redirects_.end()) { |
| 341 *is_redirect = false; |
| 342 return base::StringPrintf("id-%zu", ++next_id_); |
| 343 } |
| 344 *is_redirect = true; |
| 345 std::string id = find_it->second; |
| 346 expected_redirects_.erase(find_it); |
| 347 return id; |
| 348 } |
| 349 |
| 350 DevToolsURLInterceptorRequestJob* DevToolsURLRequestInterceptor::State::GetJob( |
| 351 const std::string& interception_id) const { |
| 352 DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| 353 const auto it = interception_id_to_job_map_.find(interception_id); |
| 354 if (it == interception_id_to_job_map_.end()) |
| 355 return nullptr; |
| 356 return it->second; |
| 357 } |
| 358 |
| 359 void DevToolsURLRequestInterceptor::State::JobFinished( |
| 360 const std::string& interception_id) { |
| 361 DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| 362 interception_id_to_job_map_.erase(interception_id); |
| 363 } |
| 364 |
| 365 // static |
| 366 DevToolsURLRequestInterceptor* |
| 367 DevToolsURLRequestInterceptor::FromBrowserContext(BrowserContext* context) { |
| 368 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| 369 return static_cast<DevToolsURLRequestInterceptorUserData*>( |
| 370 context->GetUserData(kDevToolsURLRequestInterceptorKeyName)) |
| 371 ->devtools_url_request_interceptor(); |
| 372 } |
| 373 |
| 374 DevToolsURLRequestInterceptor::Modifications::Modifications( |
| 375 base::Optional<net::Error> error_reason, |
| 376 base::Optional<std::string> raw_response, |
| 377 protocol::Maybe<std::string> modified_url, |
| 378 protocol::Maybe<std::string> modified_method, |
| 379 protocol::Maybe<std::string> modified_post_data, |
| 380 protocol::Maybe<protocol::Network::Headers> modified_headers) |
| 381 : error_reason(std::move(error_reason)), |
| 382 raw_response(std::move(raw_response)), |
| 383 modified_url(std::move(modified_url)), |
| 384 modified_method(std::move(modified_method)), |
| 385 modified_post_data(std::move(modified_post_data)), |
| 386 modified_headers(std::move(modified_headers)) {} |
| 387 |
| 388 DevToolsURLRequestInterceptor::Modifications::~Modifications() {} |
| 389 |
| 390 DevToolsURLRequestInterceptor::State::InterceptedPage::InterceptedPage() |
| 391 : web_contents(nullptr) {} |
| 392 |
| 393 DevToolsURLRequestInterceptor::State::InterceptedPage::InterceptedPage( |
| 394 const InterceptedPage& other) = default; |
| 395 |
| 396 DevToolsURLRequestInterceptor::State::InterceptedPage::InterceptedPage( |
| 397 WebContents* web_contents, |
| 398 base::WeakPtr<protocol::NetworkHandler> network_handler) |
| 399 : web_contents(web_contents), network_handler(network_handler) {} |
| 400 |
| 401 DevToolsURLRequestInterceptor::State::InterceptedPage::~InterceptedPage() = |
| 402 default; |
| 403 |
| 404 } // namespace content |
OLD | NEW |