chrome_resource_dispatcher_host_delegate.cc revision 90dce4d38c5ff5333bea97d859d4e484e27edf0c
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 "chrome/browser/renderer_host/chrome_resource_dispatcher_host_delegate.h"
6
7#include <string>
8
9#include "base/base64.h"
10#include "base/logging.h"
11#include "chrome/browser/browser_process.h"
12#include "chrome/browser/content_settings/host_content_settings_map.h"
13#include "chrome/browser/download/download_request_limiter.h"
14#include "chrome/browser/download/download_resource_throttle.h"
15#include "chrome/browser/download/download_util.h"
16#include "chrome/browser/extensions/api/streams_private/streams_private_api.h"
17#include "chrome/browser/extensions/extension_info_map.h"
18#include "chrome/browser/extensions/user_script_listener.h"
19#include "chrome/browser/external_protocol/external_protocol_handler.h"
20#include "chrome/browser/google/google_util.h"
21#include "chrome/browser/metrics/variations/variations_http_header_provider.h"
22#include "chrome/browser/net/resource_prefetch_predictor_observer.h"
23#include "chrome/browser/prerender/prerender_manager.h"
24#include "chrome/browser/prerender/prerender_tracker.h"
25#include "chrome/browser/prerender/prerender_util.h"
26#include "chrome/browser/profiles/profile.h"
27#include "chrome/browser/profiles/profile_io_data.h"
28#include "chrome/browser/renderer_host/chrome_url_request_user_data.h"
29#include "chrome/browser/renderer_host/safe_browsing_resource_throttle_factory.h"
30#include "chrome/browser/safe_browsing/safe_browsing_service.h"
31#include "chrome/browser/ui/auto_login_prompter.h"
32#include "chrome/browser/ui/login/login_prompt.h"
33#include "chrome/browser/ui/sync/one_click_signin_helper.h"
34#include "chrome/common/chrome_notification_types.h"
35#include "chrome/common/extensions/mime_types_handler.h"
36#include "chrome/common/extensions/user_script.h"
37#include "chrome/common/render_messages.h"
38#include "content/public/browser/browser_thread.h"
39#include "content/public/browser/notification_service.h"
40#include "content/public/browser/render_view_host.h"
41#include "content/public/browser/resource_context.h"
42#include "content/public/browser/resource_dispatcher_host.h"
43#include "content/public/browser/resource_request_info.h"
44#include "content/public/browser/stream_handle.h"
45#include "content/public/common/resource_response.h"
46#include "extensions/common/constants.h"
47#include "net/base/load_flags.h"
48#include "net/base/load_timing_info.h"
49#include "net/http/http_response_headers.h"
50#include "net/ssl/ssl_config_service.h"
51#include "net/url_request/url_request.h"
52
53#if defined(ENABLE_MANAGED_USERS)
54#include "chrome/browser/managed_mode/managed_mode_resource_throttle.h"
55#endif
56
57#if defined(USE_SYSTEM_PROTOBUF)
58#include <google/protobuf/repeated_field.h>
59#else
60#include "third_party/protobuf/src/google/protobuf/repeated_field.h"
61#endif
62
63#if defined(OS_ANDROID)
64#include "chrome/browser/android/intercept_download_resource_throttle.h"
65#include "components/navigation_interception/intercept_navigation_delegate.h"
66#endif
67
68#if defined(OS_CHROMEOS)
69#include "chrome/browser/chromeos/login/merge_session_throttle.h"
70// TODO(oshima): Enable this for other platforms.
71#include "chrome/browser/renderer_host/offline_resource_throttle.h"
72#endif
73
74using content::BrowserThread;
75using content::RenderViewHost;
76using content::ResourceDispatcherHostLoginDelegate;
77using content::ResourceRequestInfo;
78using extensions::Extension;
79using extensions::StreamsPrivateAPI;
80
81#if defined(OS_ANDROID)
82using navigation_interception::InterceptNavigationDelegate;
83#endif
84
85namespace {
86
87void NotifyDownloadInitiatedOnUI(int render_process_id, int render_view_id) {
88  RenderViewHost* rvh = RenderViewHost::FromID(render_process_id,
89                                               render_view_id);
90  if (!rvh)
91    return;
92
93  content::NotificationService::current()->Notify(
94      chrome::NOTIFICATION_DOWNLOAD_INITIATED,
95      content::Source<RenderViewHost>(rvh),
96      content::NotificationService::NoDetails());
97}
98
99// Goes through the extension's file browser handlers and checks if there is one
100// that can handle the |mime_type|.
101// |extension| must not be NULL.
102bool ExtensionCanHandleMimeType(const Extension* extension,
103                                const std::string& mime_type) {
104  MimeTypesHandler* handler = MimeTypesHandler::GetHandler(extension);
105  if (!handler)
106    return false;
107
108  return handler->CanHandleMIMEType(mime_type);
109}
110
111void SendExecuteMimeTypeHandlerEvent(scoped_ptr<content::StreamHandle> stream,
112                                     int64 expected_content_size,
113                                     int render_process_id,
114                                     int render_view_id,
115                                     const std::string& extension_id) {
116  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
117
118  content::RenderViewHost* render_view_host =
119      content::RenderViewHost::FromID(render_process_id, render_view_id);
120  if (!render_view_host)
121    return;
122
123  content::WebContents* web_contents =
124      content::WebContents::FromRenderViewHost(render_view_host);
125  if (!web_contents)
126    return;
127
128  content::BrowserContext* browser_context = web_contents->GetBrowserContext();
129  if (!browser_context)
130    return;
131
132  Profile* profile = Profile::FromBrowserContext(browser_context);
133  if (!profile)
134    return;
135
136  StreamsPrivateAPI* streams_private = StreamsPrivateAPI::Get(profile);
137  if (!streams_private)
138    return;
139  streams_private->ExecuteMimeTypeHandler(
140      extension_id, web_contents, stream.Pass(), expected_content_size);
141}
142
143}  // end namespace
144
145ChromeResourceDispatcherHostDelegate::ChromeResourceDispatcherHostDelegate(
146    prerender::PrerenderTracker* prerender_tracker)
147    : download_request_limiter_(g_browser_process->download_request_limiter()),
148      safe_browsing_(g_browser_process->safe_browsing_service()),
149      user_script_listener_(new extensions::UserScriptListener()),
150      prerender_tracker_(prerender_tracker) {
151}
152
153ChromeResourceDispatcherHostDelegate::~ChromeResourceDispatcherHostDelegate() {
154}
155
156bool ChromeResourceDispatcherHostDelegate::ShouldBeginRequest(
157    int child_id,
158    int route_id,
159    const std::string& method,
160    const GURL& url,
161    ResourceType::Type resource_type,
162    content::ResourceContext* resource_context,
163    const content::Referrer& referrer) {
164  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
165
166  // Handle a PREFETCH resource type. If prefetch is disabled, squelch the
167  // request.  Otherwise, do a normal request to warm the cache.
168  if (resource_type == ResourceType::PREFETCH) {
169    // All PREFETCH requests should be GETs, but be defensive about it.
170    if (method != "GET")
171      return false;
172
173    // If prefetch is disabled, kill the request.
174    if (!prerender::PrerenderManager::IsPrefetchEnabled())
175      return false;
176  }
177
178  // Abort any prerenders that spawn requests that use invalid HTTP methods
179  // or invalid schemes.
180  if (prerender_tracker_->IsPrerenderingOnIOThread(child_id, route_id)) {
181    if (!prerender::PrerenderManager::IsValidHttpMethod(method)) {
182      prerender_tracker_->TryCancelOnIOThread(
183          child_id, route_id, prerender::FINAL_STATUS_INVALID_HTTP_METHOD);
184      return false;
185    }
186    if (!prerender::PrerenderManager::DoesURLHaveValidScheme(url)) {
187      prerender_tracker_->TryCancelOnIOThread(
188          child_id, route_id, prerender::FINAL_STATUS_UNSUPPORTED_SCHEME);
189      return false;
190    }
191  }
192
193  return true;
194}
195
196void ChromeResourceDispatcherHostDelegate::RequestBeginning(
197    net::URLRequest* request,
198    content::ResourceContext* resource_context,
199    appcache::AppCacheService* appcache_service,
200    ResourceType::Type resource_type,
201    int child_id,
202    int route_id,
203    bool is_continuation_of_transferred_request,
204    ScopedVector<content::ResourceThrottle>* throttles) {
205  if (is_continuation_of_transferred_request)
206    ChromeURLRequestUserData::Delete(request);
207
208  ChromeURLRequestUserData* user_data =
209      ChromeURLRequestUserData::Create(request);
210  bool is_prerendering = prerender_tracker_->IsPrerenderingOnIOThread(
211      child_id, route_id);
212  if (is_prerendering) {
213    user_data->set_is_prerender(true);
214    request->SetPriority(net::IDLE);
215  }
216
217#if defined(OS_ANDROID)
218  if (!is_prerendering && resource_type == ResourceType::MAIN_FRAME) {
219    throttles->push_back(
220        InterceptNavigationDelegate::CreateThrottleFor(request));
221  }
222#endif
223#if defined(OS_CHROMEOS)
224  if (resource_type == ResourceType::MAIN_FRAME) {
225    // We check offline first, then check safe browsing so that we still can
226    // block unsafe site after we remove offline page.
227    throttles->push_back(new OfflineResourceThrottle(
228        child_id, route_id, request, appcache_service));
229    // Add interstitial page while merge session process (cookie
230    // reconstruction from OAuth2 refresh token in ChromeOS login) is still in
231    // progress while we are attempting to load a google property.
232    throttles->push_back(new MergeSessionThrottle(
233        child_id, route_id, request));
234  }
235#endif
236
237  // Don't attempt to append headers to requests that have already started.
238  // TODO(stevet): Remove this once the request ordering issues are resolved
239  // in crbug.com/128048.
240  if (!request->is_pending()) {
241    net::HttpRequestHeaders headers;
242    headers.CopyFrom(request->extra_request_headers());
243    ProfileIOData* io_data = ProfileIOData::FromResourceContext(
244        resource_context);
245    bool incognito = io_data->is_incognito();
246    chrome_variations::VariationsHttpHeaderProvider::GetInstance()->
247        AppendHeaders(request->url(),
248                      incognito,
249                      !incognito && io_data->GetMetricsEnabledStateOnIOThread(),
250                      &headers);
251    request->SetExtraRequestHeaders(headers);
252  }
253
254#if defined(ENABLE_ONE_CLICK_SIGNIN)
255  AppendChromeSyncGaiaHeader(request, resource_context);
256#endif
257
258  AppendStandardResourceThrottles(request,
259                                  resource_context,
260                                  child_id,
261                                  route_id,
262                                  resource_type,
263                                  throttles);
264
265  ProfileIOData* io_data = ProfileIOData::FromResourceContext(resource_context);
266  if (io_data->resource_prefetch_predictor_observer()) {
267    io_data->resource_prefetch_predictor_observer()->OnRequestStarted(
268        request, resource_type, child_id, route_id);
269  }
270}
271
272void ChromeResourceDispatcherHostDelegate::DownloadStarting(
273    net::URLRequest* request,
274    content::ResourceContext* resource_context,
275    int child_id,
276    int route_id,
277    int request_id,
278    bool is_content_initiated,
279    bool must_download,
280    ScopedVector<content::ResourceThrottle>* throttles) {
281  BrowserThread::PostTask(
282      BrowserThread::UI, FROM_HERE,
283      base::Bind(&NotifyDownloadInitiatedOnUI, child_id, route_id));
284
285  // If it's from the web, we don't trust it, so we push the throttle on.
286  if (is_content_initiated) {
287    throttles->push_back(new DownloadResourceThrottle(
288        download_request_limiter_, child_id, route_id, request_id,
289        request->method()));
290#if defined(OS_ANDROID)
291    throttles->push_back(
292        new chrome::InterceptDownloadResourceThrottle(
293            request, child_id, route_id, request_id));
294#endif
295  }
296
297  // If this isn't a new request, we've seen this before and added the standard
298  //  resource throttles already so no need to add it again.
299  if (!request->is_pending()) {
300    AppendStandardResourceThrottles(request,
301                                    resource_context,
302                                    child_id,
303                                    route_id,
304                                    ResourceType::MAIN_FRAME,
305                                    throttles);
306  }
307}
308
309bool ChromeResourceDispatcherHostDelegate::AcceptSSLClientCertificateRequest(
310    net::URLRequest* request, net::SSLCertRequestInfo* cert_request_info) {
311  if (request->load_flags() & net::LOAD_PREFETCH)
312    return false;
313
314  ChromeURLRequestUserData* user_data = ChromeURLRequestUserData::Get(request);
315  if (user_data && user_data->is_prerender()) {
316    int child_id, route_id;
317    if (ResourceRequestInfo::ForRequest(request)->GetAssociatedRenderView(
318            &child_id, &route_id)) {
319      if (prerender_tracker_->TryCancel(
320              child_id, route_id,
321              prerender::FINAL_STATUS_SSL_CLIENT_CERTIFICATE_REQUESTED)) {
322        return false;
323      }
324    }
325  }
326
327  return true;
328}
329
330bool ChromeResourceDispatcherHostDelegate::AcceptAuthRequest(
331    net::URLRequest* request,
332    net::AuthChallengeInfo* auth_info) {
333  ChromeURLRequestUserData* user_data = ChromeURLRequestUserData::Get(request);
334  if (!user_data || !user_data->is_prerender())
335    return true;
336
337  int child_id, route_id;
338  if (!ResourceRequestInfo::ForRequest(request)->GetAssociatedRenderView(
339          &child_id, &route_id)) {
340    NOTREACHED();
341    return true;
342  }
343
344  if (!prerender_tracker_->TryCancelOnIOThread(
345          child_id, route_id, prerender::FINAL_STATUS_AUTH_NEEDED)) {
346    return true;
347  }
348
349  return false;
350}
351
352ResourceDispatcherHostLoginDelegate*
353    ChromeResourceDispatcherHostDelegate::CreateLoginDelegate(
354        net::AuthChallengeInfo* auth_info, net::URLRequest* request) {
355  return CreateLoginPrompt(auth_info, request);
356}
357
358bool ChromeResourceDispatcherHostDelegate::HandleExternalProtocol(
359    const GURL& url, int child_id, int route_id) {
360#if defined(OS_ANDROID)
361  // Android use a resource throttle to handle external as well as internal
362  // protocols.
363  return false;
364#else
365
366  if (prerender_tracker_->IsPrerenderingOnIOThread(child_id, route_id)) {
367    prerender_tracker_->TryCancel(
368        child_id, route_id, prerender::FINAL_STATUS_UNSUPPORTED_SCHEME);
369    return false;
370  }
371
372  BrowserThread::PostTask(
373      BrowserThread::UI, FROM_HERE,
374      base::Bind(&ExternalProtocolHandler::LaunchUrl, url, child_id, route_id));
375  return true;
376#endif
377}
378
379void ChromeResourceDispatcherHostDelegate::AppendStandardResourceThrottles(
380    net::URLRequest* request,
381    content::ResourceContext* resource_context,
382    int child_id,
383    int route_id,
384    ResourceType::Type resource_type,
385    ScopedVector<content::ResourceThrottle>* throttles) {
386  ProfileIOData* io_data = ProfileIOData::FromResourceContext(resource_context);
387#if defined(FULL_SAFE_BROWSING) || defined(MOBILE_SAFE_BROWSING)
388  // Insert safe browsing at the front of the list, so it gets to decide on
389  // policies first.
390  if (io_data->safe_browsing_enabled()->GetValue()) {
391    bool is_subresource_request = resource_type != ResourceType::MAIN_FRAME;
392    content::ResourceThrottle* throttle =
393        SafeBrowsingResourceThrottleFactory::Create(request, child_id, route_id,
394            is_subresource_request, safe_browsing_);
395    if (throttle)
396      throttles->push_back(throttle);
397  }
398#endif
399
400#if defined(ENABLE_MANAGED_USERS)
401  bool is_subresource_request = resource_type != ResourceType::MAIN_FRAME;
402  throttles->push_back(new ManagedModeResourceThrottle(
403        request, child_id, route_id, !is_subresource_request,
404        io_data->managed_mode_url_filter()));
405#endif
406
407  content::ResourceThrottle* throttle =
408      user_script_listener_->CreateResourceThrottle(request->url(),
409                                                    resource_type);
410  if (throttle)
411    throttles->push_back(throttle);
412}
413
414#if defined(ENABLE_ONE_CLICK_SIGNIN)
415void ChromeResourceDispatcherHostDelegate::AppendChromeSyncGaiaHeader(
416    net::URLRequest* request,
417    content::ResourceContext* resource_context) {
418  static const char kAllowChromeSignIn[] = "Allow-Chrome-SignIn";
419
420  ProfileIOData* io_data = ProfileIOData::FromResourceContext(resource_context);
421  OneClickSigninHelper::Offer offer =
422      OneClickSigninHelper::CanOfferOnIOThread(request, io_data);
423  switch (offer) {
424    case OneClickSigninHelper::CAN_OFFER:
425      request->SetExtraRequestHeaderByName(kAllowChromeSignIn, "1", false);
426      break;
427    case OneClickSigninHelper::DONT_OFFER:
428      request->RemoveRequestHeaderByName(kAllowChromeSignIn);
429      break;
430    case OneClickSigninHelper::IGNORE_REQUEST:
431      break;
432  }
433}
434#endif
435
436bool ChromeResourceDispatcherHostDelegate::ShouldForceDownloadResource(
437    const GURL& url, const std::string& mime_type) {
438  // Special-case user scripts to get downloaded instead of viewed.
439  return extensions::UserScript::IsURLUserScript(url, mime_type);
440}
441
442bool ChromeResourceDispatcherHostDelegate::ShouldInterceptResourceAsStream(
443    content::ResourceContext* resource_context,
444    const GURL& url,
445    const std::string& mime_type,
446    GURL* security_origin,
447    std::string* target_id) {
448#if !defined(OS_ANDROID)
449  ProfileIOData* io_data =
450      ProfileIOData::FromResourceContext(resource_context);
451  bool profile_is_incognito = io_data->is_incognito();
452  const scoped_refptr<const ExtensionInfoMap> extension_info_map(
453      io_data->GetExtensionInfoMap());
454  std::vector<std::string> whitelist = MimeTypesHandler::GetMIMETypeWhitelist();
455  // Go through the white-listed extensions and try to use them to intercept
456  // the URL request.
457  for (size_t i = 0; i < whitelist.size(); ++i) {
458    const char* extension_id = whitelist[i].c_str();
459    const Extension* extension =
460        extension_info_map->extensions().GetByID(extension_id);
461    // The white-listed extension may not be installed, so we have to NULL check
462    // |extension|.
463    if (!extension ||
464        (profile_is_incognito &&
465         !extension_info_map->IsIncognitoEnabled(extension_id))) {
466      continue;
467    }
468
469    if (ExtensionCanHandleMimeType(extension, mime_type)) {
470      *security_origin = Extension::GetBaseURLFromExtensionId(extension_id);
471      *target_id = extension_id;
472      return true;
473    }
474  }
475#endif
476  return false;
477}
478
479void ChromeResourceDispatcherHostDelegate::OnStreamCreated(
480    content::ResourceContext* resource_context,
481    int render_process_id,
482    int render_view_id,
483    const std::string& target_id,
484    scoped_ptr<content::StreamHandle> stream,
485    int64 expected_content_size) {
486#if !defined(OS_ANDROID)
487  content::BrowserThread::PostTask(
488      content::BrowserThread::UI, FROM_HERE,
489      base::Bind(&SendExecuteMimeTypeHandlerEvent, base::Passed(&stream),
490                 expected_content_size, render_process_id, render_view_id,
491                 target_id));
492#endif
493}
494
495void ChromeResourceDispatcherHostDelegate::OnResponseStarted(
496    net::URLRequest* request,
497    content::ResourceContext* resource_context,
498    content::ResourceResponse* response,
499    IPC::Sender* sender) {
500  // TODO(mmenke):  Figure out if LOAD_ENABLE_LOAD_TIMING is safe to remove.
501  if (request->load_flags() & net::LOAD_ENABLE_LOAD_TIMING)
502    request->GetLoadTimingInfo(&response->head.load_timing);
503
504  const ResourceRequestInfo* info = ResourceRequestInfo::ForRequest(request);
505
506  if (request->url().SchemeIsSecure()) {
507    const net::URLRequestContext* context = request->context();
508    net::TransportSecurityState* state = context->transport_security_state();
509    if (state) {
510      net::TransportSecurityState::DomainState domain_state;
511      bool has_sni = net::SSLConfigService::IsSNIAvailable(
512          context->ssl_config_service());
513      if (state->GetDomainState(request->url().host(), has_sni,
514                                &domain_state) &&
515          domain_state.ShouldUpgradeToSSL()) {
516        sender->Send(new ChromeViewMsg_AddStrictSecurityHost(
517            info->GetRouteID(), request->url().host()));
518      }
519    }
520  }
521
522  // See if the response contains the X-Auto-Login header.  If so, this was
523  // a request for a login page, and the server is allowing the browser to
524  // suggest auto-login, if available.
525  AutoLoginPrompter::ShowInfoBarIfPossible(request, info->GetChildID(),
526                                           info->GetRouteID());
527
528  ProfileIOData* io_data = ProfileIOData::FromResourceContext(resource_context);
529
530#if defined(ENABLE_ONE_CLICK_SIGNIN)
531  // See if the response contains the Google-Accounts-SignIn header.  If so,
532  // then the user has just finished signing in, and the server is allowing the
533  // browser to suggest connecting the user's profile to the account.
534  OneClickSigninHelper::ShowInfoBarIfPossible(request, io_data,
535                                              info->GetChildID(),
536                                              info->GetRouteID());
537#endif
538
539  // Build in additional protection for the chrome web store origin.
540  GURL webstore_url(extension_urls::GetWebstoreLaunchURL());
541  if (request->url().DomainIs(webstore_url.host().c_str())) {
542    net::HttpResponseHeaders* response_headers = request->response_headers();
543    if (!response_headers->HasHeaderValue("x-frame-options", "deny") &&
544        !response_headers->HasHeaderValue("x-frame-options", "sameorigin")) {
545      response_headers->RemoveHeader("x-frame-options");
546      response_headers->AddHeader("x-frame-options: sameorigin");
547    }
548  }
549
550  if (io_data->resource_prefetch_predictor_observer())
551    io_data->resource_prefetch_predictor_observer()->OnResponseStarted(request);
552
553  prerender::URLRequestResponseStarted(request);
554}
555
556void ChromeResourceDispatcherHostDelegate::OnRequestRedirected(
557    const GURL& redirect_url,
558    net::URLRequest* request,
559    content::ResourceContext* resource_context,
560    content::ResourceResponse* response) {
561  // TODO(mmenke):  Figure out if LOAD_ENABLE_LOAD_TIMING is safe to remove.
562  if (request->load_flags() & net::LOAD_ENABLE_LOAD_TIMING)
563    request->GetLoadTimingInfo(&response->head.load_timing);
564
565  ProfileIOData* io_data = ProfileIOData::FromResourceContext(resource_context);
566
567#if defined(ENABLE_ONE_CLICK_SIGNIN)
568  const ResourceRequestInfo* info = ResourceRequestInfo::ForRequest(request);
569
570  // See if the response contains the Google-Accounts-SignIn header.  If so,
571  // then the user has just finished signing in, and the server is allowing the
572  // browser to suggest connecting the user's profile to the account.
573  OneClickSigninHelper::ShowInfoBarIfPossible(request, io_data,
574                                              info->GetChildID(),
575                                              info->GetRouteID());
576  AppendChromeSyncGaiaHeader(request, resource_context);
577#endif
578
579  if (io_data->resource_prefetch_predictor_observer()) {
580    io_data->resource_prefetch_predictor_observer()->OnRequestRedirected(
581        redirect_url, request);
582  }
583
584  int child_id, route_id;
585  if (!prerender::PrerenderManager::DoesURLHaveValidScheme(redirect_url) &&
586      ResourceRequestInfo::ForRequest(request)->GetAssociatedRenderView(
587          &child_id, &route_id) &&
588      prerender_tracker_->IsPrerenderingOnIOThread(child_id, route_id)) {
589    prerender_tracker_->TryCancel(
590        child_id, route_id, prerender::FINAL_STATUS_UNSUPPORTED_SCHEME);
591    request->Cancel();
592  }
593}
594