Upstream version 8.37.180.0
[platform/framework/web/crosswalk.git] / src / content / browser / loader / cross_site_resource_handler.cc
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
9 #include "base/bind.h"
10 #include "base/command_line.h"
11 #include "base/logging.h"
12 #include "content/browser/appcache/appcache_interceptor.h"
13 #include "content/browser/child_process_security_policy_impl.h"
14 #include "content/browser/cross_site_request_manager.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/public/browser/browser_thread.h"
20 #include "content/public/browser/content_browser_client.h"
21 #include "content/public/browser/global_request_id.h"
22 #include "content/public/browser/resource_controller.h"
23 #include "content/public/browser/site_instance.h"
24 #include "content/public/common/content_switches.h"
25 #include "content/public/common/resource_response.h"
26 #include "content/public/common/url_constants.h"
27 #include "net/http/http_response_headers.h"
28 #include "net/url_request/url_request.h"
29
30 namespace content {
31
32 namespace {
33
34 bool leak_requests_for_testing_ = false;
35
36 // The parameters to OnCrossSiteResponseHelper exceed the number of arguments
37 // base::Bind supports.
38 struct CrossSiteResponseParams {
39   CrossSiteResponseParams(
40       int render_frame_id,
41       const GlobalRequestID& global_request_id,
42       bool is_transfer,
43       const std::vector<GURL>& transfer_url_chain,
44       const Referrer& referrer,
45       PageTransition page_transition,
46       bool should_replace_current_entry)
47       : render_frame_id(render_frame_id),
48         global_request_id(global_request_id),
49         is_transfer(is_transfer),
50         transfer_url_chain(transfer_url_chain),
51         referrer(referrer),
52         page_transition(page_transition),
53         should_replace_current_entry(should_replace_current_entry) {
54   }
55
56   int render_frame_id;
57   GlobalRequestID global_request_id;
58   bool is_transfer;
59   std::vector<GURL> transfer_url_chain;
60   Referrer referrer;
61   PageTransition page_transition;
62   bool should_replace_current_entry;
63 };
64
65 void OnCrossSiteResponseHelper(const CrossSiteResponseParams& params) {
66   scoped_ptr<CrossSiteTransferringRequest> cross_site_transferring_request;
67   if (params.is_transfer) {
68     cross_site_transferring_request.reset(new CrossSiteTransferringRequest(
69         params.global_request_id));
70   }
71
72   RenderFrameHostImpl* rfh =
73       RenderFrameHostImpl::FromID(params.global_request_id.child_id,
74                                   params.render_frame_id);
75   if (rfh) {
76     rfh->OnCrossSiteResponse(
77         params.global_request_id, cross_site_transferring_request.Pass(),
78         params.transfer_url_chain, params.referrer,
79         params.page_transition, params.should_replace_current_entry);
80   } else if (leak_requests_for_testing_ && cross_site_transferring_request) {
81     // Some unit tests expect requests to be leaked in this case, so they can
82     // pass them along manually.
83     cross_site_transferring_request->ReleaseRequest();
84   }
85 }
86
87 bool CheckNavigationPolicyOnUI(GURL url, int process_id, int render_frame_id) {
88   RenderFrameHostImpl* rfh =
89       RenderFrameHostImpl::FromID(process_id, render_frame_id);
90   if (!rfh)
91     return false;
92
93   // TODO(nasko): This check is very simplistic and is used temporarily only
94   // for --site-per-process. It should be updated to match the check performed
95   // by RenderFrameHostManager::UpdateStateForNavigate.
96   return !SiteInstance::IsSameWebSite(
97       rfh->GetSiteInstance()->GetBrowserContext(),
98       rfh->GetSiteInstance()->GetSiteURL(), url);
99 }
100
101 }  // namespace
102
103 CrossSiteResourceHandler::CrossSiteResourceHandler(
104     scoped_ptr<ResourceHandler> next_handler,
105     net::URLRequest* request)
106     : LayeredResourceHandler(request, next_handler.Pass()),
107       has_started_response_(false),
108       in_cross_site_transition_(false),
109       completed_during_transition_(false),
110       did_defer_(false),
111       weak_ptr_factory_(this) {
112 }
113
114 CrossSiteResourceHandler::~CrossSiteResourceHandler() {
115   // Cleanup back-pointer stored on the request info.
116   GetRequestInfo()->set_cross_site_handler(NULL);
117 }
118
119 bool CrossSiteResourceHandler::OnRequestRedirected(
120     const GURL& new_url,
121     ResourceResponse* response,
122     bool* defer) {
123   // Top-level requests change their cookie first-party URL on redirects, while
124   // subframes retain the parent's value.
125   if (GetRequestInfo()->GetResourceType() == ResourceType::MAIN_FRAME)
126     request()->set_first_party_for_cookies(new_url);
127
128   // We should not have started the transition before being redirected.
129   DCHECK(!in_cross_site_transition_);
130   return next_handler_->OnRequestRedirected(new_url, response, defer);
131 }
132
133 bool CrossSiteResourceHandler::OnResponseStarted(
134     ResourceResponse* response,
135     bool* defer) {
136   // At this point, we know that the response is safe to send back to the
137   // renderer: it is not a download, and it has passed the SSL and safe
138   // browsing checks.
139   // We should not have already started the transition before now.
140   DCHECK(!in_cross_site_transition_);
141   has_started_response_ = true;
142
143   ResourceRequestInfoImpl* info = GetRequestInfo();
144
145   // We will need to swap processes if either (1) a redirect that requires a
146   // transfer occurred before we got here, or (2) a pending cross-site request
147   // was already in progress.  Note that a swap may no longer be needed if we
148   // transferred back into the original process due to a redirect.
149   bool should_transfer =
150       GetContentClient()->browser()->ShouldSwapProcessesForRedirect(
151           info->GetContext(), request()->original_url(), request()->url());
152
153   // When the --site-per-process flag is passed, we transfer processes for
154   // cross-site navigations. This is skipped if a transfer is already required
155   // or for WebUI processes for now, since pages like the NTP host multiple
156   // cross-site WebUI iframes.
157   if (!should_transfer &&
158       CommandLine::ForCurrentProcess()->HasSwitch(switches::kSitePerProcess) &&
159       !ChildProcessSecurityPolicyImpl::GetInstance()->HasWebUIBindings(
160           info->GetChildID())) {
161     return DeferForNavigationPolicyCheck(info, response, defer);
162   }
163
164   bool swap_needed = should_transfer ||
165       CrossSiteRequestManager::GetInstance()->
166           HasPendingCrossSiteRequest(info->GetChildID(), info->GetRouteID());
167
168   // If this is a download, just pass the response through without doing a
169   // cross-site check.  The renderer will see it is a download and abort the
170   // request.
171   //
172   // Similarly, HTTP 204 (No Content) responses leave us showing the previous
173   // page.  We should allow the navigation to finish without running the unload
174   // handler or swapping in the pending RenderFrameHost.
175   //
176   // In both cases, any pending RenderFrameHost (if one was created for this
177   // navigation) will stick around until the next cross-site navigation, since
178   // we are unable to tell when to destroy it.
179   // See RenderFrameHostManager::RendererAbortedProvisionalLoad.
180   //
181   // TODO(davidben): Unify IsDownload() and is_stream(). Several places need to
182   // check for both and remembering about streams is error-prone.
183   if (!swap_needed || info->IsDownload() || info->is_stream() ||
184       (response->head.headers.get() &&
185        response->head.headers->response_code() == 204)) {
186     return next_handler_->OnResponseStarted(response, defer);
187   }
188
189   // Now that we know a swap is needed and we have something to commit, we
190   // pause to let the UI thread run the unload handler of the previous page
191   // and set up a transfer if needed.
192   StartCrossSiteTransition(response, should_transfer);
193
194   // Defer loading until after the onunload event handler has run.
195   *defer = true;
196   OnDidDefer();
197   return true;
198 }
199
200 void CrossSiteResourceHandler::ResumeOrTransfer(bool is_transfer) {
201   if (is_transfer) {
202     StartCrossSiteTransition(response_, is_transfer);
203   } else {
204     ResumeResponse();
205   }
206 }
207
208 bool CrossSiteResourceHandler::OnReadCompleted(int bytes_read, bool* defer) {
209   CHECK(!in_cross_site_transition_);
210   return next_handler_->OnReadCompleted(bytes_read, defer);
211 }
212
213 void CrossSiteResourceHandler::OnResponseCompleted(
214     const net::URLRequestStatus& status,
215     const std::string& security_info,
216     bool* defer) {
217   if (!in_cross_site_transition_) {
218     ResourceRequestInfoImpl* info = GetRequestInfo();
219     // If we've already completed the transition, or we're canceling the
220     // request, or an error occurred with no cross-process navigation in
221     // progress, then we should just pass this through.
222     if (has_started_response_ ||
223         status.status() != net::URLRequestStatus::FAILED ||
224         !CrossSiteRequestManager::GetInstance()->HasPendingCrossSiteRequest(
225             info->GetChildID(), info->GetRouteID())) {
226       next_handler_->OnResponseCompleted(status, security_info, defer);
227       return;
228     }
229
230     // An error occurred. We should wait now for the cross-process transition,
231     // so that the error message (e.g., 404) can be displayed to the user.
232     // Also continue with the logic below to remember that we completed
233     // during the cross-site transition.
234     StartCrossSiteTransition(NULL, false);
235   }
236
237   // We have to buffer the call until after the transition completes.
238   completed_during_transition_ = true;
239   completed_status_ = status;
240   completed_security_info_ = security_info;
241
242   // Defer to tell RDH not to notify the world or clean up the pending request.
243   // We will do so in ResumeResponse.
244   *defer = true;
245   OnDidDefer();
246 }
247
248 // We can now send the response to the new renderer, which will cause
249 // WebContentsImpl to swap in the new renderer and destroy the old one.
250 void CrossSiteResourceHandler::ResumeResponse() {
251   DCHECK(request());
252   in_cross_site_transition_ = false;
253   ResourceRequestInfoImpl* info = GetRequestInfo();
254
255   if (has_started_response_) {
256     // Send OnResponseStarted to the new renderer.
257     DCHECK(response_);
258     bool defer = false;
259     if (!next_handler_->OnResponseStarted(response_.get(), &defer)) {
260       controller()->Cancel();
261     } else if (!defer) {
262       // Unpause the request to resume reading.  Any further reads will be
263       // directed toward the new renderer.
264       ResumeIfDeferred();
265     }
266   }
267
268   // Remove ourselves from the ExtraRequestInfo.
269   info->set_cross_site_handler(NULL);
270
271   // If the response completed during the transition, notify the next
272   // event handler.
273   if (completed_during_transition_) {
274     bool defer = false;
275     next_handler_->OnResponseCompleted(completed_status_,
276                                        completed_security_info_,
277                                        &defer);
278     if (!defer)
279       ResumeIfDeferred();
280   }
281 }
282
283 // static
284 void CrossSiteResourceHandler::SetLeakRequestsForTesting(
285     bool leak_requests_for_testing) {
286   leak_requests_for_testing_ = leak_requests_for_testing;
287 }
288
289 // Prepare to render the cross-site response in a new RenderFrameHost, by
290 // telling the old RenderFrameHost to run its onunload handler.
291 void CrossSiteResourceHandler::StartCrossSiteTransition(
292     ResourceResponse* response,
293     bool should_transfer) {
294   in_cross_site_transition_ = true;
295   response_ = response;
296
297   // Store this handler on the ExtraRequestInfo, so that RDH can call our
298   // ResumeResponse method when we are ready to resume.
299   ResourceRequestInfoImpl* info = GetRequestInfo();
300   info->set_cross_site_handler(this);
301
302   GlobalRequestID global_id(info->GetChildID(), info->GetRequestID());
303
304   // Tell the contents responsible for this request that a cross-site response
305   // is starting, so that it can tell its old renderer to run its onunload
306   // handler now.  We will wait until the unload is finished and (if a transfer
307   // is needed) for the new renderer's request to arrive.
308   // The |transfer_url_chain| contains any redirect URLs that have already
309   // occurred, plus the destination URL at the end.
310   std::vector<GURL> transfer_url_chain;
311   Referrer referrer;
312   int render_frame_id = info->GetRenderFrameID();
313   if (should_transfer) {
314     transfer_url_chain = request()->url_chain();
315     referrer = Referrer(GURL(request()->referrer()), info->GetReferrerPolicy());
316
317     AppCacheInterceptor::PrepareForCrossSiteTransfer(
318         request(), global_id.child_id);
319     ResourceDispatcherHostImpl::Get()->MarkAsTransferredNavigation(global_id);
320   }
321   BrowserThread::PostTask(
322       BrowserThread::UI,
323       FROM_HERE,
324       base::Bind(
325           &OnCrossSiteResponseHelper,
326           CrossSiteResponseParams(render_frame_id,
327                                   global_id,
328                                   should_transfer,
329                                   transfer_url_chain,
330                                   referrer,
331                                   info->GetPageTransition(),
332                                   info->should_replace_current_entry())));
333 }
334
335 bool CrossSiteResourceHandler::DeferForNavigationPolicyCheck(
336     ResourceRequestInfoImpl* info,
337     ResourceResponse* response,
338     bool* defer) {
339   // Store the response_ object internally, since the navigation is deferred
340   // regardless of whether it will be a transfer or not.
341   response_ = response;
342
343   // Always defer the navigation to the UI thread to make a policy decision.
344   // It will send the result back to the IO thread to either resume or
345   // transfer it to a new renderer.
346   // TODO(nasko): If the UI thread result is that transfer is required, the
347   // IO thread will defer to the UI thread again through
348   // StartCrossSiteTransition. This is unnecessary and the policy check on the
349   // UI thread should be refactored to avoid the extra hop.
350   BrowserThread::PostTaskAndReplyWithResult(
351       BrowserThread::UI,
352       FROM_HERE,
353       base::Bind(&CheckNavigationPolicyOnUI,
354                  request()->url(),
355                  info->GetChildID(),
356                  info->GetRenderFrameID()),
357       base::Bind(&CrossSiteResourceHandler::ResumeOrTransfer,
358                  weak_ptr_factory_.GetWeakPtr()));
359
360   // Defer loading until it is known whether the navigation will transfer
361   // to a new process or continue in the existing one.
362   *defer = true;
363   OnDidDefer();
364   return true;
365 }
366
367 void CrossSiteResourceHandler::ResumeIfDeferred() {
368   if (did_defer_) {
369     request()->LogUnblocked();
370     did_defer_ = false;
371     controller()->Resume();
372   }
373 }
374
375 void CrossSiteResourceHandler::OnDidDefer() {
376   did_defer_ = true;
377   request()->LogBlockedBy("CrossSiteResourceHandler");
378 }
379
380 }  // namespace content