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