// Copyright (c) 2006-2008 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. // See http://dev.chromium.org/developers/design-documents/multi-process-resource-loading #include #include "chrome/browser/renderer_host/resource_dispatcher_host.h" #include "base/message_loop.h" #include "base/scoped_ptr.h" #include "base/time.h" #include "chrome/browser/cert_store.h" #include "chrome/browser/cross_site_request_manager.h" #include "chrome/browser/renderer_host/async_resource_handler.h" #include "chrome/browser/renderer_host/buffered_resource_handler.h" #include "chrome/browser/renderer_host/cross_site_resource_handler.h" #include "chrome/browser/renderer_host/download_resource_handler.h" #include "chrome/browser/renderer_host/renderer_security_policy.h" #include "chrome/browser/renderer_host/resource_request_details.h" #include "chrome/browser/renderer_host/safe_browsing_resource_handler.h" #include "chrome/browser/renderer_host/save_file_resource_handler.h" #include "chrome/browser/renderer_host/sync_resource_handler.h" #include "chrome/browser/tab_contents/tab_util.h" #include "chrome/browser/tab_contents/web_contents.h" #include "chrome/common/notification_service.h" #include "chrome/common/render_messages.h" #include "chrome/common/stl_util-inl.h" #include "net/base/auth.h" #include "net/base/cert_status_flags.h" #include "net/base/load_flags.h" #include "net/base/mime_util.h" #include "net/base/net_errors.h" #include "net/url_request/url_request.h" // TODO(port): Move these includes to the above section when porting is done. #if defined(OS_POSIX) #include "chrome/common/temp_scaffolding_stubs.h" #elif defined(OS_WIN) #include "chrome/browser/download/download_file.h" #include "chrome/browser/download/download_manager.h" #include "chrome/browser/download/download_request_manager.h" #include "chrome/browser/download/save_file_manager.h" #include "chrome/browser/external_protocol_handler.h" #include "chrome/browser/login_prompt.h" #include "chrome/browser/plugin_service.h" #include "chrome/browser/renderer_host/render_view_host.h" #include "chrome/browser/renderer_host/render_view_host_delegate.h" #include "chrome/browser/safe_browsing/safe_browsing_service.h" #endif // Uncomment to enable logging of request traffic. //#define LOG_RESOURCE_DISPATCHER_REQUESTS #ifdef LOG_RESOURCE_DISPATCHER_REQUESTS # define RESOURCE_LOG(stuff) LOG(INFO) << stuff #else # define RESOURCE_LOG(stuff) #endif using base::Time; using base::TimeDelta; using base::TimeTicks; // ---------------------------------------------------------------------------- // The interval for calls to ResourceDispatcherHost::UpdateLoadStates static const int kUpdateLoadStatesIntervalMsec = 100; // Maximum number of pending data messages sent to the renderer at any // given time for a given request. static const int kMaxPendingDataMessages = 20; // Maximum byte "cost" of all the outstanding requests for a renderer. // See delcaration of |max_outstanding_requests_cost_per_process_| for details. // This bound is 25MB, which allows for around 6000 outstanding requests. static const int kMaxOutstandingRequestsCostPerProcess = 26214400; // A ShutdownTask proxies a shutdown task from the UI thread to the IO thread. // It should be constructed on the UI thread and run in the IO thread. class ResourceDispatcherHost::ShutdownTask : public Task { public: explicit ShutdownTask(ResourceDispatcherHost* resource_dispatcher_host) : rdh_(resource_dispatcher_host) { } void Run() { rdh_->OnShutdown(); } private: ResourceDispatcherHost* rdh_; }; namespace { // Consults the RendererSecurity policy to determine whether the // ResourceDispatcherHost should service this request. A request might be // disallowed if the renderer is not authorized to restrive the request URL or // if the renderer is attempting to upload an unauthorized file. bool ShouldServiceRequest(int render_process_host_id, const ViewHostMsg_Resource_Request& request_data) { // TODO(mpcomplete): remove this when http://b/viewIssue?id=1080959 is fixed. if (render_process_host_id == -1) return true; RendererSecurityPolicy* policy = RendererSecurityPolicy::GetInstance(); // Check if the renderer is permitted to request the requested URL. if (!policy->CanRequestURL(render_process_host_id, request_data.url)) { LOG(INFO) << "Denied unauthorized request for " << request_data.url.possibly_invalid_spec(); return false; } // Check if the renderer is permitted to upload the requested files. const std::vector& uploads = request_data.upload_content; std::vector::const_iterator iter; for (iter = uploads.begin(); iter != uploads.end(); ++iter) { if (iter->type() == net::UploadData::TYPE_FILE && !policy->CanUploadFile(render_process_host_id, iter->file_path())) { NOTREACHED() << "Denied unauthorized upload of " << iter->file_path(); return false; } } return true; } } // namespace ResourceDispatcherHost::ResourceDispatcherHost(MessageLoop* io_loop) : ui_loop_(MessageLoop::current()), io_loop_(io_loop), download_file_manager_(new DownloadFileManager(ui_loop_, this)), download_request_manager_(new DownloadRequestManager(io_loop, ui_loop_)), save_file_manager_(new SaveFileManager(ui_loop_, io_loop, this)), safe_browsing_(new SafeBrowsingService), request_id_(-1), plugin_service_(PluginService::GetInstance()), method_runner_(this), is_shutdown_(false), max_outstanding_requests_cost_per_process_(kMaxOutstandingRequestsCostPerProcess) { } ResourceDispatcherHost::~ResourceDispatcherHost() { AsyncResourceHandler::GlobalCleanup(); STLDeleteValues(&pending_requests_); // Clear blocked requests if any left. // Note that we have to do this in 2 passes as we cannot call // CancelBlockedRequestsForRenderView while iterating over // blocked_requests_map_, as it modifies it. std::set ids; for (BlockedRequestMap::const_iterator iter = blocked_requests_map_.begin(); iter != blocked_requests_map_.end(); ++iter) { std::pair::iterator, bool> result = ids.insert(iter->first); // We should not have duplicates. DCHECK(result.second); } for (std::set::const_iterator iter = ids.begin(); iter != ids.end(); ++iter) { CancelBlockedRequestsForRenderView(iter->first, iter->second); } } void ResourceDispatcherHost::Initialize() { DCHECK(MessageLoop::current() == ui_loop_); download_file_manager_->Initialize(); safe_browsing_->Initialize(io_loop_); } void ResourceDispatcherHost::Shutdown() { DCHECK(MessageLoop::current() == ui_loop_); io_loop_->PostTask(FROM_HERE, new ShutdownTask(this)); } void ResourceDispatcherHost::OnShutdown() { DCHECK(MessageLoop::current() == io_loop_); is_shutdown_ = true; STLDeleteValues(&pending_requests_); // Make sure we shutdown the timer now, otherwise by the time our destructor // runs if the timer is still running the Task is deleted twice (once by // the MessageLoop and the second time by RepeatingTimer). update_load_states_timer_.Stop(); } bool ResourceDispatcherHost::HandleExternalProtocol(int request_id, int render_process_host_id, int tab_contents_id, const GURL& url, ResourceType::Type type, ResourceHandler* handler) { if (!ResourceType::IsFrame(type) || URLRequest::IsHandledURL(url)) return false; ui_loop_->PostTask(FROM_HERE, NewRunnableFunction( &ExternalProtocolHandler::LaunchUrl, url, render_process_host_id, tab_contents_id)); handler->OnResponseCompleted(request_id, URLRequestStatus( URLRequestStatus::FAILED, net::ERR_ABORTED)); return true; } void ResourceDispatcherHost::BeginRequest( Receiver* receiver, base::ProcessHandle render_process_handle, int render_process_host_id, int render_view_id, int request_id, const ViewHostMsg_Resource_Request& request_data, URLRequestContext* request_context, IPC::Message* sync_result) { if (is_shutdown_ || !ShouldServiceRequest(render_process_host_id, request_data)) { // Tell the renderer that this request was disallowed. receiver->Send(new ViewMsg_Resource_RequestComplete( render_view_id, request_id, URLRequestStatus(URLRequestStatus::FAILED, net::ERR_ABORTED))); return; } // Ensure the Chrome plugins are loaded, as they may intercept network // requests. Does nothing if they are already loaded. // TODO(mpcomplete): This takes 200 ms! Investigate parallelizing this by // starting the load earlier in a BG thread. plugin_service_->LoadChromePlugins(this); // Construct the event handler. scoped_refptr handler; if (sync_result) { handler = new SyncResourceHandler(receiver, request_data.url, sync_result); } else { handler = new AsyncResourceHandler(receiver, render_process_host_id, render_view_id, render_process_handle, request_data.url, this); } if (HandleExternalProtocol(request_id, render_process_host_id, render_view_id, request_data.url, request_data.resource_type, handler)) { return; } // Construct the request. URLRequest* request = new URLRequest(request_data.url, this); request->set_method(request_data.method); request->set_policy_url(request_data.policy_url); request->set_referrer(request_data.referrer.spec()); request->SetExtraRequestHeaders(request_data.headers); request->set_load_flags(request_data.load_flags); request->set_context(request_context); request->set_origin_pid(request_data.origin_pid); // Set upload data. uint64 upload_size = 0; if (!request_data.upload_content.empty()) { scoped_refptr upload = new net::UploadData(); upload->set_elements(request_data.upload_content); // Deep copy. request->set_upload(upload); upload_size = upload->GetContentLength(); } // Install a CrossSiteResourceHandler if this request is coming from a // RenderViewHost with a pending cross-site request. We only check this for // MAIN_FRAME requests. // TODO(mpcomplete): remove "render_process_host_id != -1" // when http://b/viewIssue?id=1080959 is fixed. if (request_data.resource_type == ResourceType::MAIN_FRAME && render_process_host_id != -1 && Singleton::get()-> HasPendingCrossSiteRequest(render_process_host_id, render_view_id)) { // Wrap the event handler to be sure the current page's onunload handler // has a chance to run before we render the new page. handler = new CrossSiteResourceHandler(handler, render_process_host_id, render_view_id, this); } if (safe_browsing_->enabled() && safe_browsing_->CanCheckUrl(request_data.url)) { handler = new SafeBrowsingResourceHandler(handler, render_process_host_id, render_view_id, request_data.url, request_data.resource_type, safe_browsing_, this); } // Insert a buffered event handler before the actual one. handler = new BufferedResourceHandler(handler, this, request); // Make extra info and read footer (contains request ID). ExtraRequestInfo* extra_info = new ExtraRequestInfo(handler, request_id, render_process_host_id, render_view_id, request_data.mixed_content, request_data.resource_type, upload_size); extra_info->allow_download = ResourceType::IsFrame(request_data.resource_type); request->set_user_data(extra_info); // takes pointer ownership BeginRequestInternal(request, request_data.mixed_content); } // We are explicitly forcing the download of 'url'. void ResourceDispatcherHost::BeginDownload(const GURL& url, const GURL& referrer, int render_process_host_id, int render_view_id, URLRequestContext* request_context) { if (is_shutdown_) return; // Check if the renderer is permitted to request the requested URL. // // TODO(mpcomplete): remove "render_process_host_id != -1" // when http://b/viewIssue?id=1080959 is fixed. if (render_process_host_id != -1 && !RendererSecurityPolicy::GetInstance()-> CanRequestURL(render_process_host_id, url)) { LOG(INFO) << "Denied unauthorized download request for " << url.possibly_invalid_spec(); return; } // Ensure the Chrome plugins are loaded, as they may intercept network // requests. Does nothing if they are already loaded. plugin_service_->LoadChromePlugins(this); URLRequest* request = new URLRequest(url, this); request_id_--; scoped_refptr handler = new DownloadResourceHandler(this, render_process_host_id, render_view_id, request_id_, url, download_file_manager_.get(), request, true); if (safe_browsing_->enabled() && safe_browsing_->CanCheckUrl(url)) { handler = new SafeBrowsingResourceHandler(handler, render_process_host_id, render_view_id, url, ResourceType::MAIN_FRAME, safe_browsing_, this); } bool known_proto = URLRequest::IsHandledURL(url); if (!known_proto) { CHECK(false); } request->set_method("GET"); request->set_referrer(referrer.spec()); request->set_context(request_context); ExtraRequestInfo* extra_info = new ExtraRequestInfo(handler, request_id_, render_process_host_id, render_view_id, false, // Downloads are not considered mixed-content ResourceType::SUB_RESOURCE, 0 /* upload_size */ ); extra_info->allow_download = true; extra_info->is_download = true; request->set_user_data(extra_info); // Takes pointer ownership. BeginRequestInternal(request, false); } // This function is only used for saving feature. void ResourceDispatcherHost::BeginSaveFile(const GURL& url, const GURL& referrer, int render_process_host_id, int render_view_id, URLRequestContext* request_context) { if (is_shutdown_) return; // Ensure the Chrome plugins are loaded, as they may intercept network // requests. Does nothing if they are already loaded. plugin_service_->LoadChromePlugins(this); scoped_refptr handler = new SaveFileResourceHandler(render_process_host_id, render_view_id, url, save_file_manager_.get()); request_id_--; bool known_proto = URLRequest::IsHandledURL(url); if (!known_proto) { // Since any URLs which have non-standard scheme have been filtered // by save manager(see GURL::SchemeIsStandard). This situation // should not happen. NOTREACHED(); return; } URLRequest* request = new URLRequest(url, this); request->set_method("GET"); request->set_referrer(referrer.spec()); // So far, for saving page, we need fetch content from cache, in the // future, maybe we can use a configuration to configure this behavior. request->set_load_flags(net::LOAD_ONLY_FROM_CACHE); request->set_context(request_context); ExtraRequestInfo* extra_info = new ExtraRequestInfo(handler, request_id_, render_process_host_id, render_view_id, false, ResourceType::SUB_RESOURCE, 0 /* upload_size */); // Just saving some resources we need, disallow downloading. extra_info->allow_download = false; extra_info->is_download = false; request->set_user_data(extra_info); // Takes pointer ownership. BeginRequestInternal(request, false); } void ResourceDispatcherHost::CancelRequest(int render_process_host_id, int request_id, bool from_renderer) { CancelRequest(render_process_host_id, request_id, from_renderer, true); } void ResourceDispatcherHost::CancelRequest(int render_process_host_id, int request_id, bool from_renderer, bool allow_delete) { PendingRequestList::iterator i = pending_requests_.find( GlobalRequestID(render_process_host_id, request_id)); if (i == pending_requests_.end()) { // We probably want to remove this warning eventually, but I wanted to be // able to notice when this happens during initial development since it // should be rare and may indicate a bug. DLOG(WARNING) << "Canceling a request that wasn't found"; return; } // WebKit will send us a cancel for downloads since it no longer handles them. // In this case, ignore the cancel since we handle downloads in the browser. ExtraRequestInfo* info = ExtraInfoForRequest(i->second); if (!from_renderer || !info->is_download) { if (info->login_handler) { info->login_handler->OnRequestCancelled(); info->login_handler = NULL; } if (!i->second->is_pending() && allow_delete) { // No io is pending, canceling the request won't notify us of anything, // so we explicitly remove it. // TODO: removing the request in this manner means we're not notifying // anyone. We need make sure the event handlers and others are notified // so that everything is cleaned up properly. RemovePendingRequest(info->render_process_host_id, info->request_id); } else { i->second->Cancel(); } } // Do not remove from the pending requests, as the request will still // call AllDataReceived, and may even have more data before it does // that. } void ResourceDispatcherHost::OnDataReceivedACK(int render_process_host_id, int request_id) { PendingRequestList::iterator i = pending_requests_.find( GlobalRequestID(render_process_host_id, request_id)); if (i == pending_requests_.end()) return; ExtraRequestInfo* info = ExtraInfoForRequest(i->second); // Decrement the number of pending data messages. info->pending_data_count--; // If the pending data count was higher than the max, resume the request. if (info->pending_data_count == kMaxPendingDataMessages) { // Decrement the pending data count one more time because we also // incremented it before pausing the request. info->pending_data_count--; // Resume the request. PauseRequest(render_process_host_id, request_id, false); } } void ResourceDispatcherHost::OnUploadProgressACK(int render_process_host_id, int request_id) { PendingRequestList::iterator i = pending_requests_.find( GlobalRequestID(render_process_host_id, request_id)); if (i == pending_requests_.end()) return; ExtraRequestInfo* info = ExtraInfoForRequest(i->second); info->waiting_for_upload_progress_ack = false; } bool ResourceDispatcherHost::WillSendData(int render_process_host_id, int request_id) { PendingRequestList::iterator i = pending_requests_.find( GlobalRequestID(render_process_host_id, request_id)); if (i == pending_requests_.end()) { NOTREACHED() << L"WillSendData for invalid request"; return false; } ExtraRequestInfo* info = ExtraInfoForRequest(i->second); info->pending_data_count++; if (info->pending_data_count > kMaxPendingDataMessages) { // We reached the max number of data messages that can be sent to // the renderer for a given request. Pause the request and wait for // the renderer to start processing them before resuming it. PauseRequest(render_process_host_id, request_id, true); return false; } return true; } void ResourceDispatcherHost::PauseRequest(int render_process_host_id, int request_id, bool pause) { GlobalRequestID global_id(render_process_host_id, request_id); PendingRequestList::iterator i = pending_requests_.find(global_id); if (i == pending_requests_.end()) { DLOG(WARNING) << "Pausing a request that wasn't found"; return; } ExtraRequestInfo* info = ExtraInfoForRequest(i->second); int pause_count = info->pause_count + (pause ? 1 : -1); if (pause_count < 0) { NOTREACHED(); // Unbalanced call to pause. return; } info->pause_count = pause_count; RESOURCE_LOG("To pause (" << pause << "): " << i->second->url().spec()); // If we're resuming, kick the request to start reading again. Run the read // asynchronously to avoid recursion problems. if (info->pause_count == 0) { MessageLoop::current()->PostTask(FROM_HERE, method_runner_.NewRunnableMethod( &ResourceDispatcherHost::ResumeRequest, global_id)); } } int ResourceDispatcherHost::GetOutstandingRequestsMemoryCost( int render_process_host_id) const { OutstandingRequestsMemoryCostMap::const_iterator entry = outstanding_requests_memory_cost_map_.find(render_process_host_id); return (entry == outstanding_requests_memory_cost_map_.end()) ? 0 : entry->second; } void ResourceDispatcherHost::OnClosePageACK(int render_process_host_id, int request_id) { GlobalRequestID global_id(render_process_host_id, request_id); PendingRequestList::iterator i = pending_requests_.find(global_id); if (i == pending_requests_.end()) { // If there are no matching pending requests, then this is not a // cross-site navigation and we are just closing the tab/browser. ui_loop_->PostTask(FROM_HERE, NewRunnableFunction( &RenderViewHost::ClosePageIgnoringUnloadEvents, render_process_host_id, request_id)); return; } ExtraRequestInfo* info = ExtraInfoForRequest(i->second); if (info->cross_site_handler) { info->cross_site_handler->ResumeResponse(); } } // The object died, so cancel and detach all requests associated with it except // for downloads, which belong to the browser process even if initiated via a // renderer. void ResourceDispatcherHost::CancelRequestsForProcess( int render_process_host_id) { CancelRequestsForRenderView(render_process_host_id, -1 /* cancel all */); } void ResourceDispatcherHost::CancelRequestsForRenderView( int render_process_host_id, int render_view_id) { // Since pending_requests_ is a map, we first build up a list of all of the // matching requests to be cancelled, and then we cancel them. Since there // may be more than one request to cancel, we cannot simply hold onto the map // iterators found in the first loop. // Find the global ID of all matching elements. std::vector matching_requests; for (PendingRequestList::const_iterator i = pending_requests_.begin(); i != pending_requests_.end(); ++i) { if (i->first.render_process_host_id == render_process_host_id) { ExtraRequestInfo* info = ExtraInfoForRequest(i->second); if (!info->is_download && (render_view_id == -1 || render_view_id == info->render_view_id)) { matching_requests.push_back( GlobalRequestID(render_process_host_id, i->first.request_id)); } } } // Remove matches. for (size_t i = 0; i < matching_requests.size(); ++i) { PendingRequestList::iterator iter = pending_requests_.find(matching_requests[i]); // Although every matching request was in pending_requests_ when we built // matching_requests, it is normal for a matching request to be not found // in pending_requests_ after we have removed some matching requests from // pending_requests_. For example, deleting a URLRequest that has // exclusive (write) access to an HTTP cache entry may unblock another // URLRequest that needs exclusive access to the same cache entry, and // that URLRequest may complete and remove itself from pending_requests_. // So we need to check that iter is not equal to pending_requests_.end(). if (iter != pending_requests_.end()) RemovePendingRequest(iter); } // Now deal with blocked requests if any. if (render_view_id != -1) { if (blocked_requests_map_.find(std::pair(render_process_host_id, render_view_id)) != blocked_requests_map_.end()) { CancelBlockedRequestsForRenderView(render_process_host_id, render_view_id); } } else { // We have to do all render views for the process |render_process_host_id|. // Note that we have to do this in 2 passes as we cannot call // CancelBlockedRequestsForRenderView while iterating over // blocked_requests_map_, as it modifies it. std::set render_view_ids; for (BlockedRequestMap::const_iterator iter = blocked_requests_map_.begin(); iter != blocked_requests_map_.end(); ++iter) { if (iter->first.first == render_process_host_id) render_view_ids.insert(iter->first.second); } for (std::set::const_iterator iter = render_view_ids.begin(); iter != render_view_ids.end(); ++iter) { CancelBlockedRequestsForRenderView(render_process_host_id, *iter); } } } // Cancels the request and removes it from the list. void ResourceDispatcherHost::RemovePendingRequest(int render_process_host_id, int request_id) { PendingRequestList::iterator i = pending_requests_.find( GlobalRequestID(render_process_host_id, request_id)); if (i == pending_requests_.end()) { NOTREACHED() << "Trying to remove a request that's not here"; return; } RemovePendingRequest(i); } void ResourceDispatcherHost::RemovePendingRequest( const PendingRequestList::iterator& iter) { ExtraRequestInfo* info = ExtraInfoForRequest(iter->second); // Remove the memory credit that we added when pushing the request onto // the pending list. IncrementOutstandingRequestsMemoryCost(-1 * info->memory_cost, info->render_process_host_id); // Notify the login handler that this request object is going away. if (info && info->login_handler) info->login_handler->OnRequestCancelled(); delete iter->second; pending_requests_.erase(iter); // If we have no more pending requests, then stop the load state monitor if (pending_requests_.empty()) update_load_states_timer_.Stop(); } // URLRequest::Delegate ------------------------------------------------------- void ResourceDispatcherHost::OnReceivedRedirect(URLRequest* request, const GURL& new_url) { RESOURCE_LOG("OnReceivedRedirect: " << request->url().spec()); ExtraRequestInfo* info = ExtraInfoForRequest(request); DCHECK(request->status().is_success()); // TODO(mpcomplete): remove this when http://b/viewIssue?id=1080959 is fixed. if (info->render_process_host_id != -1 && !RendererSecurityPolicy::GetInstance()-> CanRequestURL(info->render_process_host_id, new_url)) { LOG(INFO) << "Denied unauthorized request for " << new_url.possibly_invalid_spec(); // Tell the renderer that this request was disallowed. CancelRequest(info->render_process_host_id, info->request_id, false); return; } NofityReceivedRedirect(request, info->render_process_host_id, new_url); if (HandleExternalProtocol(info->request_id, info->render_process_host_id, info->render_view_id, new_url, info->resource_type, info->resource_handler)) { // The request is complete so we can remove it. RemovePendingRequest(info->render_process_host_id, info->request_id); return; } if (!info->resource_handler->OnRequestRedirected(info->request_id, new_url)) CancelRequest(info->render_process_host_id, info->request_id, false); } void ResourceDispatcherHost::OnAuthRequired( URLRequest* request, net::AuthChallengeInfo* auth_info) { // Create a login dialog on the UI thread to get authentication data, // or pull from cache and continue on the IO thread. // TODO(mpcomplete): We should block the parent tab while waiting for // authentication. // That would also solve the problem of the URLRequest being cancelled // before we receive authentication. ExtraRequestInfo* info = ExtraInfoForRequest(request); DCHECK(!info->login_handler) << "OnAuthRequired called with login_handler pending"; info->login_handler = CreateLoginPrompt(auth_info, request, ui_loop_); } void ResourceDispatcherHost::OnSSLCertificateError( URLRequest* request, int cert_error, net::X509Certificate* cert) { DCHECK(request); SSLManager::OnSSLCertificateError(this, request, cert_error, cert, ui_loop_); } void ResourceDispatcherHost::OnResponseStarted(URLRequest* request) { RESOURCE_LOG("OnResponseStarted: " << request->url().spec()); ExtraRequestInfo* info = ExtraInfoForRequest(request); if (PauseRequestIfNeeded(info)) { RESOURCE_LOG("OnResponseStarted pausing: " << request->url().spec()); return; } if (request->status().is_success()) { // We want to send a final upload progress message prior to sending // the response complete message even if we're waiting for an ack to // to a previous upload progress message. info->waiting_for_upload_progress_ack = false; MaybeUpdateUploadProgress(info, request); if (!CompleteResponseStarted(request)) { CancelRequest(info->render_process_host_id, info->request_id, false); } else { // Start reading. int bytes_read = 0; if (Read(request, &bytes_read)) { OnReadCompleted(request, bytes_read); } else if (!request->status().is_io_pending()) { DCHECK(!info->is_paused); // If the error is not an IO pending, then we're done reading. OnResponseCompleted(request); } } } else { OnResponseCompleted(request); } } bool ResourceDispatcherHost::CompleteResponseStarted(URLRequest* request) { ExtraRequestInfo* info = ExtraInfoForRequest(request); scoped_refptr response(new ResourceResponse); response->response_head.status = request->status(); response->response_head.request_time = request->request_time(); response->response_head.response_time = request->response_time(); response->response_head.headers = request->response_headers(); request->GetCharset(&response->response_head.charset); response->response_head.filter_policy = info->filter_policy; response->response_head.content_length = request->GetExpectedContentSize(); request->GetMimeType(&response->response_head.mime_type); if (request->ssl_info().cert) { int cert_id = CertStore::GetSharedInstance()->StoreCert( request->ssl_info().cert, info->render_process_host_id); int cert_status = request->ssl_info().cert_status; // EV certificate verification could be expensive. We don't want to spend // time performing EV certificate verification on all resources because // EV status is irrelevant to sub-frames and sub-resources. So we call // IsEV here rather than in the network layer because the network layer // doesn't know the resource type. if (info->resource_type == ResourceType::MAIN_FRAME && request->ssl_info().cert->IsEV(cert_status)) cert_status |= net::CERT_STATUS_IS_EV; response->response_head.security_info = SSLManager::SerializeSecurityInfo(cert_id, cert_status, request->ssl_info().security_bits); } else { // We should not have any SSL state. DCHECK(!request->ssl_info().cert_status && (request->ssl_info().security_bits == -1 || request->ssl_info().security_bits == 0)); } NotifyResponseStarted(request, info->render_process_host_id); return info->resource_handler->OnResponseStarted(info->request_id, response.get()); } int ResourceDispatcherHost::IncrementOutstandingRequestsMemoryCost( int cost, int render_process_host_id) { // Retrieve the previous value (defaulting to 0 if not found). OutstandingRequestsMemoryCostMap::iterator prev_entry = outstanding_requests_memory_cost_map_.find(render_process_host_id); int new_cost = 0; if (prev_entry != outstanding_requests_memory_cost_map_.end()) new_cost = prev_entry->second; // Insert/update the total; delete entries when their value reaches 0. new_cost += cost; CHECK(new_cost >= 0); if (new_cost == 0) outstanding_requests_memory_cost_map_.erase(prev_entry); else outstanding_requests_memory_cost_map_[render_process_host_id] = new_cost; return new_cost; } // static int ResourceDispatcherHost::CalculateApproximateMemoryCost( URLRequest* request) { // The following fields should be a minor size contribution (experimentally // on the order of 100). However since they are variable length, it could // in theory be a sizeable contribution. int strings_cost = request->extra_request_headers().size() + request->original_url().spec().size() + request->referrer().size() + request->method().size(); int upload_cost = 0; // TODO(eroman): don't enable the upload throttling until we have data // showing what a reasonable limit is (limiting to 25MB of uploads may // be too restrictive). #if 0 // Sum all the (non-file) upload data attached to the request, if any. if (request->has_upload()) { const std::vector& uploads = request->get_upload()->elements(); std::vector::const_iterator iter; for (iter = uploads.begin(); iter != uploads.end(); ++iter) { if (iter->type() == net::UploadData::TYPE_BYTES) { int64 element_size = iter->GetContentLength(); // This cast should not result in truncation. upload_cost += static_cast(element_size); } } } #endif // Note that this expression will typically be dominated by: // |kAvgBytesPerOutstandingRequest|. return kAvgBytesPerOutstandingRequest + strings_cost + upload_cost; } void ResourceDispatcherHost::BeginRequestInternal(URLRequest* request, bool mixed_content) { DCHECK(!request->is_pending()); ExtraRequestInfo* info = ExtraInfoForRequest(request); // Add the memory estimate that starting this request will consume. info->memory_cost = CalculateApproximateMemoryCost(request); int memory_cost = IncrementOutstandingRequestsMemoryCost( info->memory_cost, info->render_process_host_id); // If enqueing/starting this request will exceed our per-process memory // bound, abort it right away. if (memory_cost > max_outstanding_requests_cost_per_process_) { // We call "CancelWithError()" as a way of setting the URLRequest's // status -- it has no effect beyond this, since the request hasn't started. request->CancelWithError(net::ERR_INSUFFICIENT_RESOURCES); // TODO(eroman): this is kinda funky -- we insert the unstarted request into // |pending_requests_| simply to please OnResponseCompleted(). GlobalRequestID global_id(info->render_process_host_id, info->request_id); pending_requests_[global_id] = request; OnResponseCompleted(request); return; } std::pair pair_id(info->render_process_host_id, info->render_view_id); BlockedRequestMap::const_iterator iter = blocked_requests_map_.find(pair_id); if (iter != blocked_requests_map_.end()) { // The request should be blocked. iter->second->push_back(BlockedRequest(request, mixed_content)); return; } GlobalRequestID global_id(info->render_process_host_id, info->request_id); pending_requests_[global_id] = request; if (mixed_content) { // We don't start the request in that case. The SSLManager will potentially // change the request (potentially to indicate its content should be // filtered) and start it itself. SSLManager::OnMixedContentRequest(this, request, ui_loop_); return; } request->Start(); // Make sure we have the load state monitor running if (!update_load_states_timer_.IsRunning()) { update_load_states_timer_.Start( TimeDelta::FromMilliseconds(kUpdateLoadStatesIntervalMsec), this, &ResourceDispatcherHost::UpdateLoadStates); } } // This test mirrors the decision that WebKit makes in // WebFrameLoaderClient::dispatchDecidePolicyForMIMEType. // static. bool ResourceDispatcherHost::ShouldDownload( const std::string& mime_type, const std::string& content_disposition) { std::string type = StringToLowerASCII(mime_type); std::string disposition = StringToLowerASCII(content_disposition); // First, examine content-disposition. if (!disposition.empty()) { bool should_download = true; // Some broken sites just send ... // Content-Disposition: ; filename="file" // ... screen those out here. if (disposition[0] == ';') should_download = false; if (disposition.compare(0, 6, "inline") == 0) should_download = false; // Some broken sites just send ... // Content-Disposition: filename="file" // ... without a disposition token... Screen those out. if (disposition.compare(0, 8, "filename") == 0) should_download = false; // Also in use is Content-Disposition: name="file" if (disposition.compare(0, 4, "name") == 0) should_download = false; // We have a content-disposition of "attachment" or unknown. // RFC 2183, section 2.8 says that an unknown disposition // value should be treated as "attachment". if (should_download) return true; } // MIME type checking. if (net::IsSupportedMimeType(type)) return false; // Finally, check the plugin service. bool allow_wildcard = false; return !plugin_service_->HavePluginFor(type, allow_wildcard); } bool ResourceDispatcherHost::PauseRequestIfNeeded(ExtraRequestInfo* info) { if (info->pause_count > 0) info->is_paused = true; return info->is_paused; } void ResourceDispatcherHost::ResumeRequest(const GlobalRequestID& request_id) { PendingRequestList::iterator i = pending_requests_.find(request_id); if (i == pending_requests_.end()) // The request may have been destroyed return; URLRequest* request = i->second; ExtraRequestInfo* info = ExtraInfoForRequest(request); if (!info->is_paused) return; RESOURCE_LOG("Resuming: " << i->second->url().spec()); info->is_paused = false; if (info->has_started_reading) OnReadCompleted(i->second, info->paused_read_bytes); else OnResponseStarted(i->second); } bool ResourceDispatcherHost::Read(URLRequest* request, int* bytes_read) { ExtraRequestInfo* info = ExtraInfoForRequest(request); DCHECK(!info->is_paused); net::IOBuffer* buf; int buf_size; if (!info->resource_handler->OnWillRead(info->request_id, &buf, &buf_size, -1)) { return false; } DCHECK(buf); DCHECK(buf_size > 0); info->has_started_reading = true; return request->Read(buf, buf_size, bytes_read); } void ResourceDispatcherHost::OnReadCompleted(URLRequest* request, int bytes_read) { DCHECK(request); RESOURCE_LOG("OnReadCompleted: " << request->url().spec()); ExtraRequestInfo* info = ExtraInfoForRequest(request); if (PauseRequestIfNeeded(info)) { info->paused_read_bytes = bytes_read; RESOURCE_LOG("OnReadCompleted pausing: " << request->url().spec()); return; } if (request->status().is_success() && CompleteRead(request, &bytes_read)) { // The request can be paused if we realize that the renderer is not // servicing messages fast enough. if (info->pause_count == 0 && Read(request, &bytes_read) && request->status().is_success()) { if (bytes_read == 0) { CompleteRead(request, &bytes_read); } else { // Force the next CompleteRead / Read pair to run as a separate task. // This avoids a fast, large network request from monopolizing the IO // thread and starving other IO operations from running. info->paused_read_bytes = bytes_read; info->is_paused = true; GlobalRequestID id(info->render_process_host_id, info->request_id); MessageLoop::current()->PostTask( FROM_HERE, method_runner_.NewRunnableMethod( &ResourceDispatcherHost::ResumeRequest, id)); return; } } } if (PauseRequestIfNeeded(info)) { info->paused_read_bytes = bytes_read; RESOURCE_LOG("OnReadCompleted (CompleteRead) pausing: " << request->url().spec()); return; } // If the status is not IO pending then we've either finished (success) or we // had an error. Either way, we're done! if (!request->status().is_io_pending()) OnResponseCompleted(request); } bool ResourceDispatcherHost::CompleteRead(URLRequest* request, int* bytes_read) { if (!request->status().is_success()) { NOTREACHED(); return false; } ExtraRequestInfo* info = ExtraInfoForRequest(request); if (!info->resource_handler->OnReadCompleted(info->request_id, bytes_read)) { // Pass in false as the last arg to indicate we don't want |request| // deleted. We do this as callers of us assume |request| is valid after we // return. CancelRequest(info->render_process_host_id, info->request_id, false, false); return false; } return *bytes_read != 0; } void ResourceDispatcherHost::OnResponseCompleted(URLRequest* request) { RESOURCE_LOG("OnResponseCompleted: " << request->url().spec()); ExtraRequestInfo* info = ExtraInfoForRequest(request); if (info->resource_handler->OnResponseCompleted(info->request_id, request->status())) { NotifyResponseCompleted(request, info->render_process_host_id); // The request is complete so we can remove it. RemovePendingRequest(info->render_process_host_id, info->request_id); } // If the handler's OnResponseCompleted returns false, we are deferring the // call until later. We will notify the world and clean up when we resume. } void ResourceDispatcherHost::AddObserver(Observer* obs) { observer_list_.AddObserver(obs); } void ResourceDispatcherHost::RemoveObserver(Observer* obs) { observer_list_.RemoveObserver(obs); } URLRequest* ResourceDispatcherHost::GetURLRequest( GlobalRequestID request_id) const { // This should be running in the IO loop. io_loop_ can be NULL during the // unit_tests. DCHECK(MessageLoop::current() == io_loop_ && io_loop_); PendingRequestList::const_iterator i = pending_requests_.find(request_id); if (i == pending_requests_.end()) return NULL; return i->second; } // A NotificationTask proxies a resource dispatcher notification from the IO // thread to the UI thread. It should be constructed on the IO thread and run // in the UI thread. Takes ownership of |details|. class NotificationTask : public Task { public: NotificationTask(NotificationType type, URLRequest* request, ResourceRequestDetails* details) : type_(type), details_(details) { if (!tab_util::GetTabContentsID(request, &render_process_host_id_, &tab_contents_id_)) NOTREACHED(); } void Run() { // Find the tab associated with this request. TabContents* tab_contents = tab_util::GetWebContentsByID(render_process_host_id_, tab_contents_id_); if (tab_contents) { // Issue the notification. NotificationService::current()->Notify( type_, Source(tab_contents->controller()), Details(details_.get())); } } private: // These IDs let us find the correct tab on the UI thread. int render_process_host_id_; int tab_contents_id_; // The type and details of the notification. NotificationType type_; scoped_ptr details_; }; static int GetCertID(URLRequest* request, int render_process_host_id) { if (request->ssl_info().cert) { return CertStore::GetSharedInstance()->StoreCert(request->ssl_info().cert, render_process_host_id); } // If there is no SSL info attached to this request, we must either be a non // secure request, or the request has been canceled or failed (before the SSL // info was populated), or the response is an error (we have seen 403, 404, // and 501) made up by the proxy. DCHECK(!request->url().SchemeIsSecure() || (request->status().status() == URLRequestStatus::CANCELED) || (request->status().status() == URLRequestStatus::FAILED) || ((request->response_headers()->response_code() >= 400) && (request->response_headers()->response_code() <= 599))); return 0; } void ResourceDispatcherHost::NotifyResponseStarted(URLRequest* request, int render_process_host_id) { // Notify the observers on the IO thread. FOR_EACH_OBSERVER(Observer, observer_list_, OnRequestStarted(this, request)); // Notify the observers on the UI thread. ui_loop_->PostTask(FROM_HERE, new NotificationTask(NotificationType::RESOURCE_RESPONSE_STARTED, request, new ResourceRequestDetails(request, GetCertID(request, render_process_host_id)))); } void ResourceDispatcherHost::NotifyResponseCompleted( URLRequest* request, int render_process_host_id) { // Notify the observers on the IO thread. FOR_EACH_OBSERVER(Observer, observer_list_, OnResponseCompleted(this, request)); // Notify the observers on the UI thread. ui_loop_->PostTask(FROM_HERE, new NotificationTask(NotificationType::RESOURCE_RESPONSE_COMPLETED, request, new ResourceRequestDetails(request, GetCertID(request, render_process_host_id)))); } void ResourceDispatcherHost::NofityReceivedRedirect(URLRequest* request, int render_process_host_id, const GURL& new_url) { // Notify the observers on the IO thread. FOR_EACH_OBSERVER(Observer, observer_list_, OnReceivedRedirect(this, request, new_url)); int cert_id = GetCertID(request, render_process_host_id); // Notify the observers on the UI thread. ui_loop_->PostTask(FROM_HERE, new NotificationTask(NotificationType::RESOURCE_RECEIVED_REDIRECT, request, new ResourceRedirectDetails(request, cert_id, new_url))); } namespace { // This function attempts to return the "more interesting" load state of |a| // and |b|. We don't have temporal information about these load states // (meaning we don't know when we transitioned into these states), so we just // rank them according to how "interesting" the states are. // // We take advantage of the fact that the load states are an enumeration listed // in the order in which they occur during the lifetime of a request, so we can // regard states with larger numeric values as being further along toward // completion. We regard those states as more interesting to report since they // represent progress. // // For example, by this measure "tranferring data" is a more interesting state // than "resolving host" because when we are transferring data we are actually // doing something that corresponds to changes that the user might observe, // whereas waiting for a host name to resolve implies being stuck. // net::LoadState MoreInterestingLoadState(net::LoadState a, net::LoadState b) { return (a < b) ? b : a; } // Carries information about a load state change. struct LoadInfo { GURL url; net::LoadState load_state; }; // Map from ProcessID+ViewID pair to LoadState typedef std::map, LoadInfo> LoadInfoMap; // Used to marshall calls to LoadStateChanged from the IO to UI threads. We do // them all as a single task to avoid spamming the UI thread. class LoadInfoUpdateTask : public Task { public: virtual void Run() { LoadInfoMap::const_iterator i; for (i = info_map.begin(); i != info_map.end(); ++i) { RenderViewHost* view = RenderViewHost::FromID(i->first.first, i->first.second); if (view) // The view could be gone at this point. view->LoadStateChanged(i->second.url, i->second.load_state); } } LoadInfoMap info_map; }; } // namespace void ResourceDispatcherHost::UpdateLoadStates() { // Populate this map with load state changes, and then send them on to the UI // thread where they can be passed along to the respective RVHs. LoadInfoMap info_map; PendingRequestList::const_iterator i; for (i = pending_requests_.begin(); i != pending_requests_.end(); ++i) { URLRequest* request = i->second; net::LoadState load_state = request->GetLoadState(); ExtraRequestInfo* info = ExtraInfoForRequest(request); // We also poll for upload progress on this timer and send upload // progress ipc messages to the plugin process. MaybeUpdateUploadProgress(info, request); if (info->last_load_state != load_state) { info->last_load_state = load_state; std::pair key(info->render_process_host_id, info->render_view_id); net::LoadState to_insert; LoadInfoMap::iterator existing = info_map.find(key); if (existing == info_map.end()) { to_insert = load_state; } else { to_insert = MoreInterestingLoadState(existing->second.load_state, load_state); if (to_insert == existing->second.load_state) continue; } LoadInfo& load_info = info_map[key]; load_info.url = request->url(); load_info.load_state = to_insert; } } if (info_map.empty()) return; LoadInfoUpdateTask* task = new LoadInfoUpdateTask; task->info_map.swap(info_map); ui_loop_->PostTask(FROM_HERE, task); } void ResourceDispatcherHost::MaybeUpdateUploadProgress(ExtraRequestInfo *info, URLRequest *request) { if (!info->upload_size || info->waiting_for_upload_progress_ack || !(request->load_flags() & net::LOAD_ENABLE_UPLOAD_PROGRESS)) return; uint64 size = info->upload_size; uint64 position = request->GetUploadProgress(); if (position == info->last_upload_position) return; // no progress made since last time const uint64 kHalfPercentIncrements = 200; const TimeDelta kOneSecond = TimeDelta::FromMilliseconds(1000); uint64 amt_since_last = position - info->last_upload_position; TimeDelta time_since_last = TimeTicks::Now() - info->last_upload_ticks; bool is_finished = (size == position); bool enough_new_progress = (amt_since_last > (size / kHalfPercentIncrements)); bool too_much_time_passed = time_since_last > kOneSecond; if (is_finished || enough_new_progress || too_much_time_passed) { info->resource_handler->OnUploadProgress(info->request_id, position, size); info->waiting_for_upload_progress_ack = true; info->last_upload_ticks = TimeTicks::Now(); info->last_upload_position = position; } } void ResourceDispatcherHost::BlockRequestsForRenderView( int render_process_host_id, int render_view_id) { std::pair key(render_process_host_id, render_view_id); DCHECK(blocked_requests_map_.find(key) == blocked_requests_map_.end()) << "BlockRequestsForRenderView called multiple time for the same RVH"; blocked_requests_map_[key] = new BlockedRequestsList(); } void ResourceDispatcherHost::ResumeBlockedRequestsForRenderView( int render_process_host_id, int render_view_id) { ProcessBlockedRequestsForRenderView(render_process_host_id, render_view_id, false); } void ResourceDispatcherHost::CancelBlockedRequestsForRenderView( int render_process_host_id, int render_view_id) { ProcessBlockedRequestsForRenderView(render_process_host_id, render_view_id, true); } void ResourceDispatcherHost::ProcessBlockedRequestsForRenderView( int render_process_host_id, int render_view_id, bool cancel_requests) { BlockedRequestMap::iterator iter = blocked_requests_map_.find(std::pair(render_process_host_id, render_view_id)); if (iter == blocked_requests_map_.end()) { NOTREACHED(); return; } BlockedRequestsList* requests = iter->second; // Removing the vector from the map unblocks any subsequent requests. blocked_requests_map_.erase(iter); for (BlockedRequestsList::iterator req_iter = requests->begin(); req_iter != requests->end(); ++req_iter) { // Remove the memory credit that we added when pushing the request onto // the blocked list. ExtraRequestInfo* info = ExtraInfoForRequest(req_iter->url_request); IncrementOutstandingRequestsMemoryCost(-1 * info->memory_cost, info->render_process_host_id); if (cancel_requests) delete req_iter->url_request; else BeginRequestInternal(req_iter->url_request, req_iter->mixed_content); } delete requests; }