// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/net/chrome_network_delegate.h" #include #include #include "base/base_paths.h" #include "base/command_line.h" #include "base/debug/alias.h" #include "base/debug/dump_without_crashing.h" #include "base/debug/stack_trace.h" #include "base/logging.h" #include "base/metrics/histogram.h" #include "base/metrics/sparse_histogram.h" #include "base/metrics/user_metrics.h" #include "base/path_service.h" #include "base/prefs/pref_member.h" #include "base/prefs/pref_service.h" #include "base/profiler/scoped_tracker.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" #include "base/time/time.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/content_settings/cookie_settings_factory.h" #include "chrome/browser/content_settings/tab_specific_content_settings.h" #include "chrome/browser/custom_handlers/protocol_handler_registry.h" #include "chrome/browser/net/chrome_extensions_network_delegate.h" #include "chrome/browser/net/connect_interceptor.h" #include "chrome/browser/net/safe_search_util.h" #include "chrome/browser/profiles/profile_manager.h" #include "chrome/browser/task_manager/task_manager.h" #include "chrome/common/pref_names.h" #include "components/content_settings/core/browser/cookie_settings.h" #include "components/domain_reliability/monitor.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/resource_request_info.h" #include "content/public/common/content_switches.h" #include "content/public/common/process_type.h" #include "net/base/host_port_pair.h" #include "net/base/load_flags.h" #include "net/base/net_errors.h" #include "net/cookies/canonical_cookie.h" #include "net/cookies/cookie_options.h" #include "net/http/http_request_headers.h" #include "net/http/http_response_headers.h" #include "net/http/http_status_code.h" #include "net/log/net_log.h" #include "net/url_request/url_request.h" #if defined(OS_ANDROID) #include "chrome/browser/io_thread.h" #include "chrome/browser/precache/precache_manager_factory.h" #include "components/precache/content/precache_manager.h" #endif #if defined(OS_CHROMEOS) #include "base/sys_info.h" #include "chrome/common/chrome_switches.h" #endif #if defined(ENABLE_CONFIGURATION_POLICY) #include "components/policy/core/browser/url_blacklist_manager.h" #endif #if defined(ENABLE_EXTENSIONS) #include "extensions/common/constants.h" #endif using content::BrowserThread; using content::RenderViewHost; using content::ResourceRequestInfo; using content::ResourceType; // By default we don't allow access to all file:// urls on ChromeOS and // Android. #if defined(OS_CHROMEOS) || defined(OS_ANDROID) bool ChromeNetworkDelegate::g_allow_file_access_ = false; #else bool ChromeNetworkDelegate::g_allow_file_access_ = true; #endif namespace { const char kDNTHeader[] = "DNT"; // Gets called when the extensions finish work on the URL. If the extensions // did not do a redirect (so |new_url| is empty) then we enforce the // SafeSearch parameters. Otherwise we will get called again after the // redirect and we enforce SafeSearch then. void ForceGoogleSafeSearchCallbackWrapper( const net::CompletionCallback& callback, net::URLRequest* request, GURL* new_url, int rv) { if (rv == net::OK && new_url->is_empty()) safe_search_util::ForceGoogleSafeSearch(request, new_url); callback.Run(rv); } #if defined(OS_ANDROID) void RecordPrecacheStatsOnUIThread(const GURL& url, const base::Time& fetch_time, int64 size, bool was_cached, void* profile_id) { DCHECK_CURRENTLY_ON(BrowserThread::UI); Profile* profile = reinterpret_cast(profile_id); if (!g_browser_process->profile_manager()->IsValidProfile(profile)) { return; } precache::PrecacheManager* precache_manager = precache::PrecacheManagerFactory::GetForBrowserContext(profile); if (!precache_manager || !precache_manager->IsPrecachingAllowed()) { // |precache_manager| could be NULL if the profile is off the record. return; } precache_manager->RecordStatsForFetch(url, fetch_time, size, was_cached); } #endif // defined(OS_ANDROID) void ReportInvalidReferrerSendOnUI() { base::RecordAction( base::UserMetricsAction("Net.URLRequest_StartJob_InvalidReferrer")); } void ReportInvalidReferrerSend(const GURL& target_url, const GURL& referrer_url) { LOG(ERROR) << "Cancelling request to " << target_url << " with invalid referrer " << referrer_url; // Record information to help debug http://crbug.com/422871 if (!target_url.SchemeIsHTTPOrHTTPS()) return; BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(&ReportInvalidReferrerSendOnUI)); base::debug::DumpWithoutCrashing(); NOTREACHED(); } // Record network errors that HTTP requests complete with, including OK and // ABORTED. void RecordNetworkErrorHistograms(const net::URLRequest* request) { if (request->url().SchemeIs("http")) { UMA_HISTOGRAM_SPARSE_SLOWLY("Net.HttpRequestCompletionErrorCodes", std::abs(request->status().error())); if (request->load_flags() & net::LOAD_MAIN_FRAME) { UMA_HISTOGRAM_SPARSE_SLOWLY( "Net.HttpRequestCompletionErrorCodes.MainFrame", std::abs(request->status().error())); } } } // Returns whether |request| is likely to be eligible for delta-encoding. // This is only a rough approximation right now, based on MIME type. bool CanRequestBeDeltaEncoded(const net::URLRequest* request) { struct { const char *prefix; const char *suffix; } kEligibleMasks[] = { // All text/ types are eligible, even if not displayable. { "text/", NULL }, // JSON (application/json and application/*+json) is eligible. { "application/", "json" }, // Javascript is eligible. { "application/", "javascript" }, // XML (application/xml and application/*+xml) is eligible. { "application/", "xml" }, }; const bool kCaseSensitive = true; std::string mime_type; request->GetMimeType(&mime_type); for (size_t i = 0; i < arraysize(kEligibleMasks); i++) { const char *prefix = kEligibleMasks[i].prefix; const char *suffix = kEligibleMasks[i].suffix; if (prefix && !base::StartsWithASCII(mime_type, prefix, kCaseSensitive)) continue; if (suffix && !base::EndsWith(mime_type, suffix, kCaseSensitive)) continue; return true; } return false; } // Returns whether |request| was issued by a renderer process, as opposed to // the browser process or a plugin process. bool IsRendererInitiatedRequest(const net::URLRequest* request) { const ResourceRequestInfo* info = ResourceRequestInfo::ForRequest(request); return info && info->GetProcessType() == content::PROCESS_TYPE_RENDERER; } // Uploads UMA histograms for delta encoding eligibility. This method can only // be safely called after the network stack has called both OnStarted and // OnCompleted, since it needs the received response content length and the // response headers. void RecordCacheStateStats(const net::URLRequest* request) { net::HttpRequestHeaders request_headers; if (!request->GetFullRequestHeaders(&request_headers)) { // GetFullRequestHeaders is guaranteed to succeed if OnResponseStarted() has // been called on |request|, so if GetFullRequestHeaders() fails, // RecordCacheStateStats must have been called before // OnResponseStarted(). return; } if (!IsRendererInitiatedRequest(request)) { // Ignore browser-initiated requests. These are internal requests like safe // browsing and sync, and so on. Some of these could be eligible for // delta-encoding, but to be conservative this function ignores all of them. return; } const int kCacheAffectingFlags = net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE | net::LOAD_PREFERRING_CACHE; if (request->load_flags() & kCacheAffectingFlags) { // Ignore requests with cache-affecting flags, which would otherwise mess up // these stats. return; } enum { CACHE_STATE_FROM_CACHE, CACHE_STATE_STILL_VALID, CACHE_STATE_NO_LONGER_VALID, CACHE_STATE_NO_ENTRY, CACHE_STATE_MAX, } state = CACHE_STATE_NO_ENTRY; bool had_cache_headers = request_headers.HasHeader(net::HttpRequestHeaders::kIfModifiedSince) || request_headers.HasHeader(net::HttpRequestHeaders::kIfNoneMatch) || request_headers.HasHeader(net::HttpRequestHeaders::kIfRange); if (request->was_cached() && !had_cache_headers) { // Entry was served directly from cache. state = CACHE_STATE_FROM_CACHE; } else if (request->was_cached() && had_cache_headers) { // Expired entry was present in cache, and server responded with NOT // MODIFIED, indicating the expired entry is still valid. state = CACHE_STATE_STILL_VALID; } else if (!request->was_cached() && had_cache_headers) { // Expired entry was present in cache, and server responded with something // other than NOT MODIFIED, indicating the entry is no longer valid. state = CACHE_STATE_NO_LONGER_VALID; } else if (!request->was_cached() && !had_cache_headers) { // Neither |was_cached| nor |had_cache_headers|, so there's no local cache // entry for this content at all. state = CACHE_STATE_NO_ENTRY; } UMA_HISTOGRAM_ENUMERATION("Net.CacheState.AllRequests", state, CACHE_STATE_MAX); if (CanRequestBeDeltaEncoded(request)) { UMA_HISTOGRAM_ENUMERATION("Net.CacheState.EncodeableRequests", state, CACHE_STATE_MAX); } int64 size = request->received_response_content_length(); if (size >= 0 && state == CACHE_STATE_NO_LONGER_VALID) { UMA_HISTOGRAM_COUNTS("Net.CacheState.AllBytes", size); if (CanRequestBeDeltaEncoded(request)) { UMA_HISTOGRAM_COUNTS("Net.CacheState.EncodeableBytes", size); } } } } // namespace ChromeNetworkDelegate::ChromeNetworkDelegate( extensions::EventRouterForwarder* event_router, BooleanPrefMember* enable_referrers) : profile_(NULL), enable_referrers_(enable_referrers), enable_do_not_track_(NULL), force_google_safe_search_(NULL), force_youtube_safety_mode_(NULL), #if defined(ENABLE_CONFIGURATION_POLICY) url_blacklist_manager_(NULL), #endif domain_reliability_monitor_(NULL), experimental_web_platform_features_enabled_( base::CommandLine::ForCurrentProcess()->HasSwitch( switches::kEnableExperimentalWebPlatformFeatures)) { DCHECK(enable_referrers); extensions_delegate_.reset( ChromeExtensionsNetworkDelegate::Create(event_router)); } ChromeNetworkDelegate::~ChromeNetworkDelegate() {} void ChromeNetworkDelegate::set_extension_info_map( extensions::InfoMap* extension_info_map) { extensions_delegate_->set_extension_info_map(extension_info_map); } void ChromeNetworkDelegate::set_profile(void* profile) { profile_ = profile; extensions_delegate_->set_profile(profile); } void ChromeNetworkDelegate::set_cookie_settings( content_settings::CookieSettings* cookie_settings) { cookie_settings_ = cookie_settings; } void ChromeNetworkDelegate::set_predictor( chrome_browser_net::Predictor* predictor) { connect_interceptor_.reset( new chrome_browser_net::ConnectInterceptor(predictor)); } // static void ChromeNetworkDelegate::InitializePrefsOnUIThread( BooleanPrefMember* enable_referrers, BooleanPrefMember* enable_do_not_track, BooleanPrefMember* force_google_safe_search, BooleanPrefMember* force_youtube_safety_mode, PrefService* pref_service) { DCHECK_CURRENTLY_ON(BrowserThread::UI); enable_referrers->Init(prefs::kEnableReferrers, pref_service); enable_referrers->MoveToThread( BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO)); if (enable_do_not_track) { enable_do_not_track->Init(prefs::kEnableDoNotTrack, pref_service); enable_do_not_track->MoveToThread( BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO)); } if (force_google_safe_search) { force_google_safe_search->Init(prefs::kForceGoogleSafeSearch, pref_service); force_google_safe_search->MoveToThread( BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO)); } if (force_youtube_safety_mode) { force_youtube_safety_mode->Init(prefs::kForceYouTubeSafetyMode, pref_service); force_youtube_safety_mode->MoveToThread( BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO)); } } // static void ChromeNetworkDelegate::AllowAccessToAllFiles() { g_allow_file_access_ = true; } int ChromeNetworkDelegate::OnBeforeURLRequest( net::URLRequest* request, const net::CompletionCallback& callback, GURL* new_url) { // TODO(mmenke): Remove ScopedTracker below once crbug.com/456327 is fixed. tracked_objects::ScopedTracker tracking_profile1( FROM_HERE_WITH_EXPLICIT_FUNCTION( "456327 URLRequest::ChromeNetworkDelegate::OnBeforeURLRequest")); #if defined(ENABLE_CONFIGURATION_POLICY) // TODO(joaodasilva): This prevents extensions from seeing URLs that are // blocked. However, an extension might redirect the request to another URL, // which is not blocked. const ResourceRequestInfo* info = ResourceRequestInfo::ForRequest(request); int error = net::ERR_BLOCKED_BY_ADMINISTRATOR; if (info && content::IsResourceTypeFrame(info->GetResourceType()) && url_blacklist_manager_ && url_blacklist_manager_->ShouldBlockRequestForFrame( request->url(), &error)) { // URL access blocked by policy. request->net_log().AddEvent( net::NetLog::TYPE_CHROME_POLICY_ABORTED_REQUEST, net::NetLog::StringCallback("url", &request->url().possibly_invalid_spec())); return error; } #endif // TODO(mmenke): Remove ScopedTracker below once crbug.com/456327 is fixed. tracked_objects::ScopedTracker tracking_profile2( FROM_HERE_WITH_EXPLICIT_FUNCTION( "456327 URLRequest::ChromeNetworkDelegate::OnBeforeURLRequest 2")); extensions_delegate_->ForwardStartRequestStatus(request); if (!enable_referrers_->GetValue()) request->SetReferrer(std::string()); if (enable_do_not_track_ && enable_do_not_track_->GetValue()) request->SetExtraRequestHeaderByName(kDNTHeader, "1", true /* override */); // TODO(mmenke): Remove ScopedTracker below once crbug.com/456327 is fixed. tracked_objects::ScopedTracker tracking_profile3( FROM_HERE_WITH_EXPLICIT_FUNCTION( "456327 URLRequest::ChromeNetworkDelegate::OnBeforeURLRequest 3")); bool force_safe_search = (force_google_safe_search_ && force_google_safe_search_->GetValue()); net::CompletionCallback wrapped_callback = callback; if (force_safe_search) { wrapped_callback = base::Bind(&ForceGoogleSafeSearchCallbackWrapper, callback, base::Unretained(request), base::Unretained(new_url)); } int rv = extensions_delegate_->OnBeforeURLRequest( request, wrapped_callback, new_url); // TODO(mmenke): Remove ScopedTracker below once crbug.com/456327 is fixed. tracked_objects::ScopedTracker tracking_profile4( FROM_HERE_WITH_EXPLICIT_FUNCTION( "456327 URLRequest::ChromeNetworkDelegate::OnBeforeURLRequest 4")); if (force_safe_search && rv == net::OK && new_url->is_empty()) safe_search_util::ForceGoogleSafeSearch(request, new_url); // TODO(mmenke): Remove ScopedTracker below once crbug.com/456327 is fixed. tracked_objects::ScopedTracker tracking_profile5( FROM_HERE_WITH_EXPLICIT_FUNCTION( "456327 URLRequest::ChromeNetworkDelegate::OnBeforeURLRequest 5")); if (connect_interceptor_) connect_interceptor_->WitnessURLRequest(request); return rv; } int ChromeNetworkDelegate::OnBeforeSendHeaders( net::URLRequest* request, const net::CompletionCallback& callback, net::HttpRequestHeaders* headers) { if (force_youtube_safety_mode_ && force_youtube_safety_mode_->GetValue()) safe_search_util::ForceYouTubeSafetyMode(request, headers); return extensions_delegate_->OnBeforeSendHeaders(request, callback, headers); } void ChromeNetworkDelegate::OnSendHeaders( net::URLRequest* request, const net::HttpRequestHeaders& headers) { extensions_delegate_->OnSendHeaders(request, headers); } int ChromeNetworkDelegate::OnHeadersReceived( net::URLRequest* request, const net::CompletionCallback& callback, const net::HttpResponseHeaders* original_response_headers, scoped_refptr* override_response_headers, GURL* allowed_unsafe_redirect_url) { return extensions_delegate_->OnHeadersReceived( request, callback, original_response_headers, override_response_headers, allowed_unsafe_redirect_url); } void ChromeNetworkDelegate::OnBeforeRedirect(net::URLRequest* request, const GURL& new_location) { if (domain_reliability_monitor_) domain_reliability_monitor_->OnBeforeRedirect(request); extensions_delegate_->OnBeforeRedirect(request, new_location); } void ChromeNetworkDelegate::OnResponseStarted(net::URLRequest* request) { extensions_delegate_->OnResponseStarted(request); } void ChromeNetworkDelegate::OnRawBytesRead(const net::URLRequest& request, int bytes_read) { #if defined(ENABLE_TASK_MANAGER) // This is not completely accurate, but as a first approximation ignore // requests that are served from the cache. See bug 330931 for more info. if (!request.was_cached()) TaskManager::GetInstance()->model()->NotifyBytesRead(request, bytes_read); #endif // defined(ENABLE_TASK_MANAGER) } void ChromeNetworkDelegate::OnCompleted(net::URLRequest* request, bool started) { RecordNetworkErrorHistograms(request); if (started) { // Only call in for requests that were started, to obey the precondition // that RecordCacheStateStats can only be called on requests for which // OnResponseStarted was called. RecordCacheStateStats(request); } if (request->status().status() == net::URLRequestStatus::SUCCESS) { #if defined(OS_ANDROID) // For better accuracy, we use the actual bytes read instead of the length // specified with the Content-Length header, which may be inaccurate, // or missing, as is the case with chunked encoding. int64 received_content_length = request->received_response_content_length(); if (precache::PrecacheManager::IsPrecachingEnabled()) { // Record precache metrics when a fetch is completed successfully, if // precaching is enabled. BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(&RecordPrecacheStatsOnUIThread, request->url(), base::Time::Now(), received_content_length, request->was_cached(), profile_)); } #endif // defined(OS_ANDROID) extensions_delegate_->OnCompleted(request, started); } else if (request->status().status() == net::URLRequestStatus::FAILED || request->status().status() == net::URLRequestStatus::CANCELED) { extensions_delegate_->OnCompleted(request, started); } else { NOTREACHED(); } if (domain_reliability_monitor_) domain_reliability_monitor_->OnCompleted(request, started); extensions_delegate_->ForwardProxyErrors(request); extensions_delegate_->ForwardDoneRequestStatus(request); } void ChromeNetworkDelegate::OnURLRequestDestroyed(net::URLRequest* request) { extensions_delegate_->OnURLRequestDestroyed(request); } void ChromeNetworkDelegate::OnPACScriptError(int line_number, const base::string16& error) { extensions_delegate_->OnPACScriptError(line_number, error); } net::NetworkDelegate::AuthRequiredResponse ChromeNetworkDelegate::OnAuthRequired( net::URLRequest* request, const net::AuthChallengeInfo& auth_info, const AuthCallback& callback, net::AuthCredentials* credentials) { return extensions_delegate_->OnAuthRequired( request, auth_info, callback, credentials); } bool ChromeNetworkDelegate::OnCanGetCookies( const net::URLRequest& request, const net::CookieList& cookie_list) { // NULL during tests, or when we're running in the system context. if (!cookie_settings_.get()) return true; bool allow = cookie_settings_->IsReadingCookieAllowed( request.url(), request.first_party_for_cookies()); int render_process_id = -1; int render_frame_id = -1; if (content::ResourceRequestInfo::GetRenderFrameForRequest( &request, &render_process_id, &render_frame_id)) { BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(&TabSpecificContentSettings::CookiesRead, render_process_id, render_frame_id, request.url(), request.first_party_for_cookies(), cookie_list, !allow)); } return allow; } bool ChromeNetworkDelegate::OnCanSetCookie(const net::URLRequest& request, const std::string& cookie_line, net::CookieOptions* options) { // NULL during tests, or when we're running in the system context. if (!cookie_settings_.get()) return true; bool allow = cookie_settings_->IsSettingCookieAllowed( request.url(), request.first_party_for_cookies()); int render_process_id = -1; int render_frame_id = -1; if (content::ResourceRequestInfo::GetRenderFrameForRequest( &request, &render_process_id, &render_frame_id)) { BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(&TabSpecificContentSettings::CookieChanged, render_process_id, render_frame_id, request.url(), request.first_party_for_cookies(), cookie_line, *options, !allow)); } return allow; } bool ChromeNetworkDelegate::OnCanAccessFile(const net::URLRequest& request, const base::FilePath& path) const { if (g_allow_file_access_) return true; #if !defined(OS_CHROMEOS) && !defined(OS_ANDROID) return true; #else #if defined(OS_CHROMEOS) // If we're running Chrome for ChromeOS on Linux, we want to allow file // access. if (!base::SysInfo::IsRunningOnChromeOS() || base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kTestType)) { return true; } // Use a whitelist to only allow access to files residing in the list of // directories below. static const char* const kLocalAccessWhiteList[] = { "/home/chronos/user/Downloads", "/home/chronos/user/log", "/home/chronos/user/WebRTC Logs", "/media", "/opt/oem", "/usr/share/chromeos-assets", "/tmp", "/var/log", }; // The actual location of "/home/chronos/user/Xyz" is the Xyz directory under // the profile path ("/home/chronos/user' is a hard link to current primary // logged in profile.) For the support of multi-profile sessions, we are // switching to use explicit "$PROFILE_PATH/Xyz" path and here whitelist such // access. if (!profile_path_.empty()) { const base::FilePath downloads = profile_path_.AppendASCII("Downloads"); if (downloads == path.StripTrailingSeparators() || downloads.IsParent(path)) return true; const base::FilePath webrtc_logs = profile_path_.AppendASCII("WebRTC Logs"); if (webrtc_logs == path.StripTrailingSeparators() || webrtc_logs.IsParent(path)) { return true; } } #elif defined(OS_ANDROID) // Access to files in external storage is allowed. base::FilePath external_storage_path; PathService::Get(base::DIR_ANDROID_EXTERNAL_STORAGE, &external_storage_path); if (external_storage_path.IsParent(path)) return true; // Whitelist of other allowed directories. static const char* const kLocalAccessWhiteList[] = { "/sdcard", "/mnt/sdcard", }; #endif for (size_t i = 0; i < arraysize(kLocalAccessWhiteList); ++i) { const base::FilePath white_listed_path(kLocalAccessWhiteList[i]); // base::FilePath::operator== should probably handle trailing separators. if (white_listed_path == path.StripTrailingSeparators() || white_listed_path.IsParent(path)) { return true; } } DVLOG(1) << "File access denied - " << path.value().c_str(); return false; #endif // !defined(OS_CHROMEOS) && !defined(OS_ANDROID) } bool ChromeNetworkDelegate::OnCanEnablePrivacyMode( const GURL& url, const GURL& first_party_for_cookies) const { // NULL during tests, or when we're running in the system context. if (!cookie_settings_.get()) return false; bool reading_cookie_allowed = cookie_settings_->IsReadingCookieAllowed( url, first_party_for_cookies); bool setting_cookie_allowed = cookie_settings_->IsSettingCookieAllowed( url, first_party_for_cookies); bool privacy_mode = !(reading_cookie_allowed && setting_cookie_allowed); return privacy_mode; } bool ChromeNetworkDelegate::OnFirstPartyOnlyCookieExperimentEnabled() const { return experimental_web_platform_features_enabled_; } bool ChromeNetworkDelegate::OnCancelURLRequestWithPolicyViolatingReferrerHeader( const net::URLRequest& request, const GURL& target_url, const GURL& referrer_url) const { ReportInvalidReferrerSend(target_url, referrer_url); return true; }