Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(732)

Side by Side Diff: content/browser/loader/cross_site_resource_handler.cc

Issue 2321543002: Merge CrossSiteResourceHandler and NavigationResourceThrottle (Closed)
Patch Set: Fixed test compilation error Created 4 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2012 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/loader/cross_site_resource_handler.h"
6
7 #include <string>
8 #include <utility>
9
10 #include "base/bind.h"
11 #include "base/command_line.h"
12 #include "base/logging.h"
13 #include "content/browser/appcache/appcache_interceptor.h"
14 #include "content/browser/child_process_security_policy_impl.h"
15 #include "content/browser/frame_host/cross_site_transferring_request.h"
16 #include "content/browser/frame_host/render_frame_host_impl.h"
17 #include "content/browser/loader/resource_dispatcher_host_impl.h"
18 #include "content/browser/loader/resource_request_info_impl.h"
19 #include "content/browser/site_instance_impl.h"
20 #include "content/browser/web_contents/web_contents_impl.h"
21 #include "content/common/site_isolation_policy.h"
22 #include "content/public/browser/browser_thread.h"
23 #include "content/public/browser/content_browser_client.h"
24 #include "content/public/browser/global_request_id.h"
25 #include "content/public/browser/resource_controller.h"
26 #include "content/public/browser/site_instance.h"
27 #include "content/public/common/content_switches.h"
28 #include "content/public/common/resource_response.h"
29 #include "content/public/common/url_constants.h"
30 #include "net/http/http_response_headers.h"
31 #include "net/url_request/url_request.h"
32
33 namespace content {
34
35 namespace {
36
37 bool leak_requests_for_testing_ = false;
38
39 // The parameters to OnCrossSiteResponseHelper exceed the number of arguments
40 // base::Bind supports.
41 struct CrossSiteResponseParams {
42 CrossSiteResponseParams(
43 int render_frame_id,
44 const GlobalRequestID& global_request_id,
45 const std::vector<GURL>& transfer_url_chain,
46 const Referrer& referrer,
47 ui::PageTransition page_transition,
48 bool should_replace_current_entry)
49 : render_frame_id(render_frame_id),
50 global_request_id(global_request_id),
51 transfer_url_chain(transfer_url_chain),
52 referrer(referrer),
53 page_transition(page_transition),
54 should_replace_current_entry(should_replace_current_entry) {
55 }
56
57 int render_frame_id;
58 GlobalRequestID global_request_id;
59 std::vector<GURL> transfer_url_chain;
60 Referrer referrer;
61 ui::PageTransition page_transition;
62 bool should_replace_current_entry;
63 };
64
65 void OnCrossSiteResponseHelper(const CrossSiteResponseParams& params) {
66 std::unique_ptr<CrossSiteTransferringRequest> cross_site_transferring_request(
67 new CrossSiteTransferringRequest(params.global_request_id));
68
69 RenderFrameHostImpl* rfh =
70 RenderFrameHostImpl::FromID(params.global_request_id.child_id,
71 params.render_frame_id);
72 if (rfh && rfh->is_active()) {
73 if (rfh->GetParent()) {
74 // We should only swap processes for subframes in --site-per-process mode.
75 // CrossSiteResourceHandler is not installed on subframe requests in
76 // default Chrome.
77 CHECK(SiteIsolationPolicy::AreCrossProcessFramesPossible());
78 }
79 rfh->OnCrossSiteResponse(
80 params.global_request_id, std::move(cross_site_transferring_request),
81 params.transfer_url_chain, params.referrer, params.page_transition,
82 params.should_replace_current_entry);
83 } else if (leak_requests_for_testing_) {
84 // Some unit tests expect requests to be leaked in this case, so they can
85 // pass them along manually.
86 cross_site_transferring_request->ReleaseRequest();
87 }
88 }
89
90 // Returns whether a transfer is needed by doing a check on the UI thread.
91 CrossSiteResourceHandler::NavigationDecision
92 CheckNavigationPolicyOnUI(GURL real_url, int process_id, int render_frame_id) {
93 CHECK(SiteIsolationPolicy::AreCrossProcessFramesPossible());
94 RenderFrameHostImpl* rfh =
95 RenderFrameHostImpl::FromID(process_id, render_frame_id);
96
97 // Without a valid RFH against which to check, we must cancel the request,
98 // to prevent the resource at |url| from being delivered to a potentially
99 // unsuitable renderer process.
100 if (!rfh || !rfh->is_active())
101 return CrossSiteResourceHandler::NavigationDecision::CANCEL_REQUEST;
102
103 RenderFrameHostManager* manager = rfh->frame_tree_node()->render_manager();
104 if (manager->IsRendererTransferNeededForNavigation(rfh, real_url))
105 return CrossSiteResourceHandler::NavigationDecision::TRANSFER_REQUIRED;
106 else
107 return CrossSiteResourceHandler::NavigationDecision::USE_EXISTING_RENDERER;
108 }
109
110 } // namespace
111
112 CrossSiteResourceHandler::CrossSiteResourceHandler(
113 std::unique_ptr<ResourceHandler> next_handler,
114 net::URLRequest* request)
115 : LayeredResourceHandler(request, std::move(next_handler)),
116 has_started_response_(false),
117 in_cross_site_transition_(false),
118 completed_during_transition_(false),
119 did_defer_(false),
120 weak_ptr_factory_(this) {}
121
122 CrossSiteResourceHandler::~CrossSiteResourceHandler() {
123 // Cleanup back-pointer stored on the request info.
124 GetRequestInfo()->set_cross_site_handler(NULL);
125 }
126
127 bool CrossSiteResourceHandler::OnRequestRedirected(
128 const net::RedirectInfo& redirect_info,
129 ResourceResponse* response,
130 bool* defer) {
131 // We should not have started the transition before being redirected.
132 DCHECK(!in_cross_site_transition_);
133 return next_handler_->OnRequestRedirected(redirect_info, response, defer);
134 }
135
136 bool CrossSiteResourceHandler::OnResponseStarted(
137 ResourceResponse* response,
138 bool* defer) {
139 response_ = response;
140 has_started_response_ = true;
141
142 // Store this handler on the ExtraRequestInfo, so that RDH can call our
143 // ResumeResponse method when we are ready to resume.
144 ResourceRequestInfoImpl* info = GetRequestInfo();
145 info->set_cross_site_handler(this);
146
147 return OnNormalResponseStarted(response, defer);
148 }
149
150 bool CrossSiteResourceHandler::OnNormalResponseStarted(
151 ResourceResponse* response,
152 bool* defer) {
153 // At this point, we know that the response is safe to send back to the
154 // renderer: it is not a download, and it has passed the SSL and safe
155 // browsing checks.
156 // We should not have already started the transition before now.
157 DCHECK(!in_cross_site_transition_);
158
159 ResourceRequestInfoImpl* info = GetRequestInfo();
160
161 // The content embedder can decide that a transfer to a different process is
162 // required for this URL. If so, pause the response now. Other cross process
163 // navigations can proceed immediately, since we run the unload handler at
164 // commit time. Note that a process swap may no longer be necessary if we
165 // transferred back into the original process due to a redirect.
166 bool definitely_transfer =
167 GetContentClient()->browser()->ShouldSwapProcessesForRedirect(
168 info->GetContext(), request()->original_url(), request()->url());
169
170 // If this is a download, just pass the response through without doing a
171 // cross-site check. The renderer will see it is a download and abort the
172 // request.
173 //
174 // Similarly, HTTP 204 (No Content) responses leave us showing the previous
175 // page. We should allow the navigation to finish without running the unload
176 // handler or swapping in the pending RenderFrameHost.
177 //
178 // In both cases, any pending RenderFrameHost (if one was created for this
179 // navigation) will stick around until the next cross-site navigation, since
180 // we are unable to tell when to destroy it.
181 // See RenderFrameHostManager::RendererAbortedProvisionalLoad.
182 //
183 // TODO(davidben): Unify IsDownload() and is_stream(). Several places need to
184 // check for both and remembering about streams is error-prone.
185 if (info->IsDownload() || info->is_stream() ||
186 (response->head.headers.get() &&
187 response->head.headers->response_code() == 204)) {
188 return next_handler_->OnResponseStarted(response, defer);
189 }
190
191 if (definitely_transfer) {
192 // Now that we know a transfer is needed and we have something to commit, we
193 // pause to let the UI thread set up the transfer.
194 StartCrossSiteTransition(response);
195
196 // Defer loading until after the new renderer process has issued a
197 // corresponding request.
198 *defer = true;
199 OnDidDefer();
200 return true;
201 }
202
203 // In the site-per-process model, we may also decide (independently from the
204 // content embedder's ShouldSwapProcessesForRedirect decision above) that a
205 // process transfer is needed. For that we need to consult the navigation
206 // policy on the UI thread, so pause the response. Process transfers are
207 // skipped for WebUI processes for now, since e.g. chrome://settings has
208 // multiple "cross-site" chrome:// frames, and that doesn't yet work cross-
209 // process.
210 if (SiteIsolationPolicy::AreCrossProcessFramesPossible() &&
211 !ChildProcessSecurityPolicyImpl::GetInstance()->HasWebUIBindings(
212 info->GetChildID())) {
213 return DeferForNavigationPolicyCheck(info, response, defer);
214 }
215
216 // No deferral needed. Pass the response through.
217 return next_handler_->OnResponseStarted(response, defer);
218 }
219
220 void CrossSiteResourceHandler::ResumeOrTransfer(NavigationDecision decision) {
221 switch (decision) {
222 case NavigationDecision::CANCEL_REQUEST:
223 // TODO(nick): What kind of cleanup do we need here?
224 controller()->Cancel();
225 break;
226 case NavigationDecision::USE_EXISTING_RENDERER:
227 ResumeResponse();
228 break;
229 case NavigationDecision::TRANSFER_REQUIRED:
230 StartCrossSiteTransition(response_.get());
231 break;
232 }
233 }
234
235 bool CrossSiteResourceHandler::OnReadCompleted(int bytes_read, bool* defer) {
236 CHECK(!in_cross_site_transition_);
237 return next_handler_->OnReadCompleted(bytes_read, defer);
238 }
239
240 void CrossSiteResourceHandler::OnResponseCompleted(
241 const net::URLRequestStatus& status,
242 const std::string& security_info,
243 bool* defer) {
244 if (!in_cross_site_transition_) {
245 // If we're not transferring, then we should pass this through.
246 next_handler_->OnResponseCompleted(status, security_info, defer);
247 return;
248 }
249
250 // We have to buffer the call until after the transition completes.
251 completed_during_transition_ = true;
252 completed_status_ = status;
253 completed_security_info_ = security_info;
254
255 // Defer to tell RDH not to notify the world or clean up the pending request.
256 // We will do so in ResumeResponse.
257 *defer = true;
258 OnDidDefer();
259 }
260
261 // We can now send the response to the new renderer, which will cause
262 // WebContentsImpl to swap in the new renderer and destroy the old one.
263 void CrossSiteResourceHandler::ResumeResponse() {
264 TRACE_EVENT_ASYNC_END0(
265 "navigation", "CrossSiteResourceHandler transition", this);
266 DCHECK(request());
267 in_cross_site_transition_ = false;
268 ResourceRequestInfoImpl* info = GetRequestInfo();
269
270 if (has_started_response_) {
271 // Send OnResponseStarted to the new renderer.
272 DCHECK(response_.get());
273 bool defer = false;
274 if (!next_handler_->OnResponseStarted(response_.get(), &defer)) {
275 controller()->Cancel();
276 } else if (!defer) {
277 // Unpause the request to resume reading. Any further reads will be
278 // directed toward the new renderer.
279 ResumeIfDeferred();
280 }
281 }
282
283 // Remove ourselves from the ExtraRequestInfo.
284 info->set_cross_site_handler(NULL);
285
286 // If the response completed during the transition, notify the next
287 // event handler.
288 if (completed_during_transition_) {
289 bool defer = false;
290 next_handler_->OnResponseCompleted(completed_status_,
291 completed_security_info_,
292 &defer);
293 if (!defer)
294 ResumeIfDeferred();
295 }
296 }
297
298 // static
299 void CrossSiteResourceHandler::SetLeakRequestsForTesting(
300 bool leak_requests_for_testing) {
301 leak_requests_for_testing_ = leak_requests_for_testing;
302 }
303
304 // Prepare to transfer the response to a new RenderFrameHost.
305 void CrossSiteResourceHandler::StartCrossSiteTransition(
306 ResourceResponse* response) {
307 TRACE_EVENT_ASYNC_BEGIN0(
308 "navigation", "CrossSiteResourceHandler transition", this);
309 in_cross_site_transition_ = true;
310 response_ = response;
311
312 // Store this handler on the ExtraRequestInfo, so that RDH can call our
313 // ResumeResponse method when we are ready to resume.
314 ResourceRequestInfoImpl* info = GetRequestInfo();
315 info->set_cross_site_handler(this);
nasko 2016/09/08 23:45:39 We should be able to remove set_cross_site_handler
clamy 2016/09/09 15:06:41 Done. I thought I had done it in this patch, but a
316
317 GlobalRequestID global_id(info->GetChildID(), info->GetRequestID());
318
319 // Tell the contents responsible for this request that a cross-site response
320 // is starting, so that it can tell its old renderer to run its onunload
321 // handler now. We will wait until the unload is finished and (if a transfer
322 // is needed) for the new renderer's request to arrive.
323 // The |transfer_url_chain| contains any redirect URLs that have already
324 // occurred, plus the destination URL at the end.
325 std::vector<GURL> transfer_url_chain;
326 Referrer referrer;
327 int render_frame_id = info->GetRenderFrameID();
328 transfer_url_chain = request()->url_chain();
329 referrer = Referrer(GURL(request()->referrer()), info->GetReferrerPolicy());
330 ResourceDispatcherHostImpl::Get()->MarkAsTransferredNavigation(global_id,
331 response_);
332
333 BrowserThread::PostTask(
334 BrowserThread::UI,
335 FROM_HERE,
336 base::Bind(
337 &OnCrossSiteResponseHelper,
338 CrossSiteResponseParams(render_frame_id,
339 global_id,
340 transfer_url_chain,
341 referrer,
342 info->GetPageTransition(),
343 info->should_replace_current_entry())));
344 }
345
346 bool CrossSiteResourceHandler::DeferForNavigationPolicyCheck(
347 ResourceRequestInfoImpl* info,
348 ResourceResponse* response,
349 bool* defer) {
350 // Store the response_ object internally, since the navigation is deferred
351 // regardless of whether it will be a transfer or not.
352 response_ = response;
353
354 // Always defer the navigation to the UI thread to make a policy decision.
355 // It will send the result back to the IO thread to either resume or
356 // transfer it to a new renderer.
357 // TODO(nasko): If the UI thread result is that transfer is required, the
358 // IO thread will defer to the UI thread again through
359 // StartCrossSiteTransition. This is unnecessary and the policy check on the
360 // UI thread should be refactored to avoid the extra hop.
361 BrowserThread::PostTaskAndReplyWithResult(
362 BrowserThread::UI,
363 FROM_HERE,
364 base::Bind(&CheckNavigationPolicyOnUI,
365 request()->url(),
366 info->GetChildID(),
367 info->GetRenderFrameID()),
368 base::Bind(&CrossSiteResourceHandler::ResumeOrTransfer,
369 weak_ptr_factory_.GetWeakPtr()));
370
371 // Defer loading until it is known whether the navigation will transfer
372 // to a new process or continue in the existing one.
373 *defer = true;
374 OnDidDefer();
375 return true;
376 }
377
378 void CrossSiteResourceHandler::ResumeIfDeferred() {
379 if (did_defer_) {
380 request()->LogUnblocked();
381 did_defer_ = false;
382 controller()->Resume();
383 }
384 }
385
386 void CrossSiteResourceHandler::OnDidDefer() {
387 did_defer_ = true;
388 request()->LogBlockedBy("CrossSiteResourceHandler");
nasko 2016/09/08 23:45:39 Can we port this over to the NavigationResourceHan
clamy 2016/09/09 15:06:41 We already have logging when the NavigationResourc
nasko 2016/09/09 23:30:27 Acknowledged.
389 }
390
391 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698