Upstream version 6.35.121.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/child_process_security_policy_impl.h"
13 #include "content/browser/cross_site_request_manager.h"
14 #include "content/browser/frame_host/cross_site_transferring_request.h"
15 #include "content/browser/frame_host/render_frame_host_impl.h"
16 #include "content/browser/loader/resource_dispatcher_host_impl.h"
17 #include "content/browser/loader/resource_request_info_impl.h"
18 #include "content/public/browser/browser_thread.h"
19 #include "content/public/browser/content_browser_client.h"
20 #include "content/public/browser/global_request_id.h"
21 #include "content/public/browser/resource_controller.h"
22 #include "content/public/browser/site_instance.h"
23 #include "content/public/common/content_switches.h"
24 #include "content/public/common/resource_response.h"
25 #include "content/public/common/url_constants.h"
26 #include "net/http/http_response_headers.h"
27 #include "net/url_request/url_request.h"
28 #include "webkit/browser/appcache/appcache_interceptor.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 }  // namespace
88
89 CrossSiteResourceHandler::CrossSiteResourceHandler(
90     scoped_ptr<ResourceHandler> next_handler,
91     net::URLRequest* request)
92     : LayeredResourceHandler(request, next_handler.Pass()),
93       has_started_response_(false),
94       in_cross_site_transition_(false),
95       completed_during_transition_(false),
96       did_defer_(false) {
97 }
98
99 CrossSiteResourceHandler::~CrossSiteResourceHandler() {
100   // Cleanup back-pointer stored on the request info.
101   GetRequestInfo()->set_cross_site_handler(NULL);
102 }
103
104 bool CrossSiteResourceHandler::OnRequestRedirected(
105     int request_id,
106     const GURL& new_url,
107     ResourceResponse* response,
108     bool* defer) {
109   // We should not have started the transition before being redirected.
110   DCHECK(!in_cross_site_transition_);
111   return next_handler_->OnRequestRedirected(
112       request_id, new_url, response, defer);
113 }
114
115 bool CrossSiteResourceHandler::OnResponseStarted(
116     int request_id,
117     ResourceResponse* response,
118     bool* defer) {
119   // At this point, we know that the response is safe to send back to the
120   // renderer: it is not a download, and it has passed the SSL and safe
121   // browsing checks.
122   // We should not have already started the transition before now.
123   DCHECK(!in_cross_site_transition_);
124   has_started_response_ = true;
125
126   ResourceRequestInfoImpl* info = GetRequestInfo();
127
128   // We will need to swap processes if either (1) a redirect that requires a
129   // transfer occurred before we got here, or (2) a pending cross-site request
130   // was already in progress.  Note that a swap may no longer be needed if we
131   // transferred back into the original process due to a redirect.
132   bool should_transfer =
133       GetContentClient()->browser()->ShouldSwapProcessesForRedirect(
134           info->GetContext(), request()->original_url(), request()->url());
135
136   // When the --site-per-process flag is passed, we transfer processes for
137   // cross-site subframe navigations.
138   if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kSitePerProcess)) {
139     GURL referrer(request()->referrer());
140     // We skip this for WebUI processes for now, since pages like the NTP host
141     // cross-site WebUI iframes but don't have referrers.
142     bool is_webui_process = ChildProcessSecurityPolicyImpl::GetInstance()->
143         HasWebUIBindings(info->GetChildID());
144
145     // TODO(creis): This shouldn't rely on the referrer to determine the parent
146     // frame's URL.  This also doesn't work for hosted apps, due to passing NULL
147     // to IsSameWebSite.  It should be possible to always send the navigation to
148     // the UI thread to make a policy decision, which could let us eliminate the
149     // renderer-side check in RenderViewImpl::decidePolicyForNavigation as well.
150     if (info->GetResourceType() == ResourceType::SUB_FRAME &&
151         !is_webui_process &&
152         !SiteInstance::IsSameWebSite(NULL, request()->url(), referrer)) {
153       should_transfer = true;
154     }
155   }
156
157   bool swap_needed = should_transfer ||
158       CrossSiteRequestManager::GetInstance()->
159           HasPendingCrossSiteRequest(info->GetChildID(), info->GetRouteID());
160
161   // If this is a download, just pass the response through without doing a
162   // cross-site check.  The renderer will see it is a download and abort the
163   // request.
164   //
165   // Similarly, HTTP 204 (No Content) responses leave us showing the previous
166   // page.  We should allow the navigation to finish without running the unload
167   // handler or swapping in the pending RenderFrameHost.
168   //
169   // In both cases, any pending RenderFrameHost (if one was created for this
170   // navigation) will stick around until the next cross-site navigation, since
171   // we are unable to tell when to destroy it.
172   // See RenderFrameHostManager::RendererAbortedProvisionalLoad.
173   //
174   // TODO(davidben): Unify IsDownload() and is_stream(). Several places need to
175   // check for both and remembering about streams is error-prone.
176   if (!swap_needed || info->IsDownload() || info->is_stream() ||
177       (response->head.headers.get() &&
178        response->head.headers->response_code() == 204)) {
179     return next_handler_->OnResponseStarted(request_id, response, defer);
180   }
181
182   // Now that we know a swap is needed and we have something to commit, we
183   // pause to let the UI thread run the unload handler of the previous page
184   // and set up a transfer if needed.
185   StartCrossSiteTransition(request_id, response, should_transfer);
186
187   // Defer loading until after the onunload event handler has run.
188   *defer = true;
189   OnDidDefer();
190   return true;
191 }
192
193 bool CrossSiteResourceHandler::OnReadCompleted(int request_id,
194                                                int bytes_read,
195                                                bool* defer) {
196   CHECK(!in_cross_site_transition_);
197   return next_handler_->OnReadCompleted(request_id, bytes_read, defer);
198 }
199
200 void CrossSiteResourceHandler::OnResponseCompleted(
201     int request_id,
202     const net::URLRequestStatus& status,
203     const std::string& security_info,
204     bool* defer) {
205   if (!in_cross_site_transition_) {
206     ResourceRequestInfoImpl* info = GetRequestInfo();
207     // If we've already completed the transition, or we're canceling the
208     // request, or an error occurred with no cross-process navigation in
209     // progress, then we should just pass this through.
210     if (has_started_response_ ||
211         status.status() != net::URLRequestStatus::FAILED ||
212         !CrossSiteRequestManager::GetInstance()->HasPendingCrossSiteRequest(
213             info->GetChildID(), info->GetRouteID())) {
214       next_handler_->OnResponseCompleted(request_id, status,
215                                          security_info, defer);
216       return;
217     }
218
219     // An error occurred. We should wait now for the cross-process transition,
220     // so that the error message (e.g., 404) can be displayed to the user.
221     // Also continue with the logic below to remember that we completed
222     // during the cross-site transition.
223     StartCrossSiteTransition(request_id, NULL, false);
224   }
225
226   // We have to buffer the call until after the transition completes.
227   completed_during_transition_ = true;
228   completed_status_ = status;
229   completed_security_info_ = security_info;
230
231   // Defer to tell RDH not to notify the world or clean up the pending request.
232   // We will do so in ResumeResponse.
233   *defer = true;
234   OnDidDefer();
235 }
236
237 // We can now send the response to the new renderer, which will cause
238 // WebContentsImpl to swap in the new renderer and destroy the old one.
239 void CrossSiteResourceHandler::ResumeResponse() {
240   DCHECK(request());
241   DCHECK(in_cross_site_transition_);
242   in_cross_site_transition_ = false;
243   ResourceRequestInfoImpl* info = GetRequestInfo();
244
245   if (has_started_response_) {
246     // Send OnResponseStarted to the new renderer.
247     DCHECK(response_);
248     bool defer = false;
249     if (!next_handler_->OnResponseStarted(info->GetRequestID(), response_.get(),
250                                           &defer)) {
251       controller()->Cancel();
252     } else if (!defer) {
253       // Unpause the request to resume reading.  Any further reads will be
254       // directed toward the new renderer.
255       ResumeIfDeferred();
256     }
257   }
258
259   // Remove ourselves from the ExtraRequestInfo.
260   info->set_cross_site_handler(NULL);
261
262   // If the response completed during the transition, notify the next
263   // event handler.
264   if (completed_during_transition_) {
265     bool defer = false;
266     next_handler_->OnResponseCompleted(info->GetRequestID(),
267                                        completed_status_,
268                                        completed_security_info_,
269                                        &defer);
270     if (!defer)
271       ResumeIfDeferred();
272   }
273 }
274
275 // static
276 void CrossSiteResourceHandler::SetLeakRequestsForTesting(
277     bool leak_requests_for_testing) {
278   leak_requests_for_testing_ = leak_requests_for_testing;
279 }
280
281 // Prepare to render the cross-site response in a new RenderFrameHost, by
282 // telling the old RenderFrameHost to run its onunload handler.
283 void CrossSiteResourceHandler::StartCrossSiteTransition(
284     int request_id,
285     ResourceResponse* response,
286     bool should_transfer) {
287   in_cross_site_transition_ = true;
288   response_ = response;
289
290   // Store this handler on the ExtraRequestInfo, so that RDH can call our
291   // ResumeResponse method when we are ready to resume.
292   ResourceRequestInfoImpl* info = GetRequestInfo();
293   info->set_cross_site_handler(this);
294
295   DCHECK_EQ(request_id, info->GetRequestID());
296   GlobalRequestID global_id(info->GetChildID(), info->GetRequestID());
297
298   // Tell the contents responsible for this request that a cross-site response
299   // is starting, so that it can tell its old renderer to run its onunload
300   // handler now.  We will wait until the unload is finished and (if a transfer
301   // is needed) for the new renderer's request to arrive.
302   // The |transfer_url_chain| contains any redirect URLs that have already
303   // occurred, plus the destination URL at the end.
304   std::vector<GURL> transfer_url_chain;
305   Referrer referrer;
306   int render_frame_id = info->GetRenderFrameID();
307   if (should_transfer) {
308     transfer_url_chain = request()->url_chain();
309     referrer = Referrer(GURL(request()->referrer()), info->GetReferrerPolicy());
310
311     appcache::AppCacheInterceptor::PrepareForCrossSiteTransfer(
312         request(), global_id.child_id);
313     ResourceDispatcherHostImpl::Get()->MarkAsTransferredNavigation(global_id);
314   }
315   BrowserThread::PostTask(
316       BrowserThread::UI,
317       FROM_HERE,
318       base::Bind(
319           &OnCrossSiteResponseHelper,
320           CrossSiteResponseParams(render_frame_id,
321                                   global_id,
322                                   should_transfer,
323                                   transfer_url_chain,
324                                   referrer,
325                                   info->GetPageTransition(),
326                                   info->should_replace_current_entry())));
327 }
328
329 void CrossSiteResourceHandler::ResumeIfDeferred() {
330   if (did_defer_) {
331     request()->LogUnblocked();
332     did_defer_ = false;
333     controller()->Resume();
334   }
335 }
336
337 void CrossSiteResourceHandler::OnDidDefer() {
338   did_defer_ = true;
339   request()->LogBlockedBy("CrossSiteResourceHandler");
340 }
341
342 }  // namespace content