| // 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 "content/browser/renderer_host/resource_loader.h" |
| |
| #include "base/message_loop.h" |
| #include "base/time.h" |
| #include "content/browser/child_process_security_policy_impl.h" |
| #include "content/browser/renderer_host/doomed_resource_handler.h" |
| #include "content/browser/renderer_host/resource_loader_delegate.h" |
| #include "content/browser/renderer_host/resource_request_info_impl.h" |
| #include "content/browser/ssl/ssl_client_auth_handler.h" |
| #include "content/browser/ssl/ssl_manager.h" |
| #include "content/common/ssl_status_serialization.h" |
| #include "content/public/browser/cert_store.h" |
| #include "content/public/browser/resource_dispatcher_host_login_delegate.h" |
| #include "content/public/common/resource_response.h" |
| #include "net/base/load_flags.h" |
| #include "net/http/http_response_headers.h" |
| #include "webkit/appcache/appcache_interceptor.h" |
| |
| using base::TimeDelta; |
| using base::TimeTicks; |
| |
| namespace content { |
| namespace { |
| |
| void PopulateResourceResponse(net::URLRequest* request, |
| ResourceResponse* response) { |
| response->head.error_code = request->status().error(); |
| response->head.request_time = request->request_time(); |
| response->head.response_time = request->response_time(); |
| response->head.headers = request->response_headers(); |
| request->GetCharset(&response->head.charset); |
| response->head.content_length = request->GetExpectedContentSize(); |
| request->GetMimeType(&response->head.mime_type); |
| net::HttpResponseInfo response_info = request->response_info(); |
| response->head.was_fetched_via_spdy = response_info.was_fetched_via_spdy; |
| response->head.was_npn_negotiated = response_info.was_npn_negotiated; |
| response->head.npn_negotiated_protocol = |
| response_info.npn_negotiated_protocol; |
| response->head.was_fetched_via_proxy = request->was_fetched_via_proxy(); |
| response->head.socket_address = request->GetSocketAddress(); |
| appcache::AppCacheInterceptor::GetExtraResponseInfo( |
| request, |
| &response->head.appcache_id, |
| &response->head.appcache_manifest_url); |
| } |
| |
| } // namespace |
| |
| ResourceLoader::ResourceLoader(scoped_ptr<net::URLRequest> request, |
| scoped_ptr<ResourceHandler> handler, |
| ResourceLoaderDelegate* delegate) |
| : deferred_stage_(DEFERRED_NONE), |
| request_(request.Pass()), |
| handler_(handler.Pass()), |
| delegate_(delegate), |
| last_upload_position_(0), |
| waiting_for_upload_progress_ack_(false), |
| is_transferring_(false), |
| weak_ptr_factory_(this) { |
| request_->set_delegate(this); |
| handler_->SetController(this); |
| } |
| |
| ResourceLoader::~ResourceLoader() { |
| if (login_delegate_) |
| login_delegate_->OnRequestCancelled(); |
| if (ssl_client_auth_handler_) |
| ssl_client_auth_handler_->OnRequestCancelled(); |
| |
| // Run ResourceHandler destructor before we tear-down the rest of our state |
| // as the ResourceHandler may want to inspect the URLRequest and other state. |
| handler_.reset(); |
| } |
| |
| void ResourceLoader::StartRequest() { |
| if (delegate_->HandleExternalProtocol(this, request_->url())) { |
| CancelAndIgnore(); |
| return; |
| } |
| |
| // Give the handler a chance to delay the URLRequest from being started. |
| bool defer_start = false; |
| if (!handler_->OnWillStart(GetRequestInfo()->GetRequestID(), request_->url(), |
| &defer_start)) { |
| Cancel(); |
| return; |
| } |
| |
| if (defer_start) { |
| deferred_stage_ = DEFERRED_START; |
| } else { |
| StartRequestInternal(); |
| } |
| } |
| |
| void ResourceLoader::CancelRequest(bool from_renderer) { |
| CancelRequestInternal(net::ERR_ABORTED, from_renderer); |
| } |
| |
| void ResourceLoader::CancelAndIgnore() { |
| ResourceRequestInfoImpl* info = GetRequestInfo(); |
| info->set_was_ignored_by_handler(true); |
| CancelRequest(false); |
| } |
| |
| void ResourceLoader::ReportUploadProgress() { |
| ResourceRequestInfoImpl* info = GetRequestInfo(); |
| |
| if (waiting_for_upload_progress_ack_) |
| return; // Send one progress event at a time. |
| |
| net::UploadProgress progress = request_->GetUploadProgress(); |
| if (!progress.size()) |
| return; // Nothing to upload. |
| |
| if (progress.position() == last_upload_position_) |
| return; // No progress made since last time. |
| |
| const uint64 kHalfPercentIncrements = 200; |
| const TimeDelta kOneSecond = TimeDelta::FromMilliseconds(1000); |
| |
| uint64 amt_since_last = progress.position() - last_upload_position_; |
| TimeDelta time_since_last = TimeTicks::Now() - last_upload_ticks_; |
| |
| bool is_finished = (progress.size() == progress.position()); |
| bool enough_new_progress = |
| (amt_since_last > (progress.size() / kHalfPercentIncrements)); |
| bool too_much_time_passed = time_since_last > kOneSecond; |
| |
| if (is_finished || enough_new_progress || too_much_time_passed) { |
| if (request_->load_flags() & net::LOAD_ENABLE_UPLOAD_PROGRESS) { |
| handler_->OnUploadProgress( |
| info->GetRequestID(), progress.position(), progress.size()); |
| waiting_for_upload_progress_ack_ = true; |
| } |
| last_upload_ticks_ = TimeTicks::Now(); |
| last_upload_position_ = progress.position(); |
| } |
| } |
| |
| void ResourceLoader::MarkAsTransferring() { |
| is_transferring_ = true; |
| |
| // When an URLRequest is transferred to a new RenderViewHost, its |
| // ResourceHandler should not receive any notifications because it may depend |
| // on the state of the old RVH. We set a ResourceHandler that only allows |
| // canceling requests, because on shutdown of the RDH all pending requests |
| // are canceled. The RVH of requests that are being transferred may be gone |
| // by that time. In CompleteTransfer, the ResoureHandlers are substituted |
| // again. |
| handler_.reset(new DoomedResourceHandler(handler_.Pass())); |
| } |
| |
| void ResourceLoader::WillCompleteTransfer() { |
| handler_.reset(); |
| } |
| |
| void ResourceLoader::CompleteTransfer(scoped_ptr<ResourceHandler> new_handler) { |
| DCHECK_EQ(DEFERRED_REDIRECT, deferred_stage_); |
| DCHECK(!handler_.get()); |
| |
| handler_ = new_handler.Pass(); |
| handler_->SetController(this); |
| is_transferring_ = false; |
| |
| Resume(); |
| } |
| |
| ResourceRequestInfoImpl* ResourceLoader::GetRequestInfo() { |
| return ResourceRequestInfoImpl::ForRequest(request_.get()); |
| } |
| |
| void ResourceLoader::ClearLoginDelegate() { |
| login_delegate_ = NULL; |
| } |
| |
| void ResourceLoader::ClearSSLClientAuthHandler() { |
| ssl_client_auth_handler_ = NULL; |
| } |
| |
| void ResourceLoader::OnUploadProgressACK() { |
| waiting_for_upload_progress_ack_ = false; |
| } |
| |
| void ResourceLoader::OnReceivedRedirect(net::URLRequest* unused, |
| const GURL& new_url, |
| bool* defer) { |
| DCHECK_EQ(request_.get(), unused); |
| |
| VLOG(1) << "OnReceivedRedirect: " << request_->url().spec(); |
| DCHECK(request_->status().is_success()); |
| |
| ResourceRequestInfoImpl* info = GetRequestInfo(); |
| |
| if (info->process_type() != PROCESS_TYPE_PLUGIN && |
| !ChildProcessSecurityPolicyImpl::GetInstance()-> |
| CanRequestURL(info->GetChildID(), new_url)) { |
| VLOG(1) << "Denied unauthorized request for " |
| << new_url.possibly_invalid_spec(); |
| |
| // Tell the renderer that this request was disallowed. |
| Cancel(); |
| return; |
| } |
| |
| delegate_->DidReceiveRedirect(this, new_url); |
| |
| if (delegate_->HandleExternalProtocol(this, new_url)) { |
| // The request is complete so we can remove it. |
| CancelAndIgnore(); |
| return; |
| } |
| |
| scoped_refptr<ResourceResponse> response(new ResourceResponse()); |
| PopulateResourceResponse(request_.get(), response); |
| |
| if (!handler_->OnRequestRedirected(info->GetRequestID(), new_url, response, |
| defer)) { |
| Cancel(); |
| } else if (*defer) { |
| deferred_stage_ = DEFERRED_REDIRECT; // Follow redirect when resumed. |
| } |
| } |
| |
| void ResourceLoader::OnAuthRequired(net::URLRequest* unused, |
| net::AuthChallengeInfo* auth_info) { |
| DCHECK_EQ(request_.get(), unused); |
| |
| if (request_->load_flags() & net::LOAD_DO_NOT_PROMPT_FOR_LOGIN) { |
| request_->CancelAuth(); |
| return; |
| } |
| |
| if (!delegate_->AcceptAuthRequest(this, auth_info)) { |
| request_->CancelAuth(); |
| return; |
| } |
| |
| // Create a login dialog on the UI thread to get authentication data, or pull |
| // from cache and continue on the IO thread. |
| |
| DCHECK(!login_delegate_) << |
| "OnAuthRequired called with login_delegate pending"; |
| login_delegate_ = delegate_->CreateLoginDelegate(this, auth_info); |
| if (!login_delegate_) |
| request_->CancelAuth(); |
| } |
| |
| void ResourceLoader::OnCertificateRequested( |
| net::URLRequest* unused, |
| net::SSLCertRequestInfo* cert_info) { |
| DCHECK_EQ(request_.get(), unused); |
| |
| if (!delegate_->AcceptSSLClientCertificateRequest(this, cert_info)) { |
| request_->Cancel(); |
| return; |
| } |
| |
| if (cert_info->client_certs.empty()) { |
| // No need to query the user if there are no certs to choose from. |
| request_->ContinueWithCertificate(NULL); |
| return; |
| } |
| |
| DCHECK(!ssl_client_auth_handler_) << |
| "OnCertificateRequested called with ssl_client_auth_handler pending"; |
| ssl_client_auth_handler_ = new SSLClientAuthHandler(request_.get(), |
| cert_info); |
| ssl_client_auth_handler_->SelectCertificate(); |
| } |
| |
| void ResourceLoader::OnSSLCertificateError(net::URLRequest* request, |
| const net::SSLInfo& ssl_info, |
| bool fatal) { |
| ResourceRequestInfoImpl* info = GetRequestInfo(); |
| |
| int render_process_id; |
| int render_view_id; |
| if (!info->GetAssociatedRenderView(&render_process_id, &render_view_id)) |
| NOTREACHED(); |
| |
| SSLManager::OnSSLCertificateError( |
| weak_ptr_factory_.GetWeakPtr(), |
| info->GetGlobalRequestID(), |
| info->GetResourceType(), |
| request_->url(), |
| render_process_id, |
| render_view_id, |
| ssl_info, |
| fatal); |
| } |
| |
| void ResourceLoader::OnResponseStarted(net::URLRequest* unused) { |
| DCHECK_EQ(request_.get(), unused); |
| |
| VLOG(1) << "OnResponseStarted: " << request_->url().spec(); |
| |
| if (!request_->status().is_success()) { |
| ResponseCompleted(); |
| return; |
| } |
| |
| // 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. |
| waiting_for_upload_progress_ack_ = false; |
| ReportUploadProgress(); |
| |
| CompleteResponseStarted(); |
| |
| if (is_deferred()) |
| return; |
| |
| if (request_->status().is_success()) { |
| StartReading(false); // Read the first chunk. |
| } else { |
| ResponseCompleted(); |
| } |
| } |
| |
| void ResourceLoader::OnReadCompleted(net::URLRequest* unused, int bytes_read) { |
| DCHECK_EQ(request_.get(), unused); |
| VLOG(1) << "OnReadCompleted: \"" << request_->url().spec() << "\"" |
| << " bytes_read = " << bytes_read; |
| |
| // bytes_read == -1 always implies an error. |
| if (bytes_read == -1 || !request_->status().is_success()) { |
| ResponseCompleted(); |
| return; |
| } |
| |
| CompleteRead(bytes_read); |
| |
| if (is_deferred()) |
| return; |
| |
| if (request_->status().is_success() && bytes_read > 0) { |
| StartReading(true); // Read the next chunk. |
| } else { |
| ResponseCompleted(); |
| } |
| } |
| |
| void ResourceLoader::CancelSSLRequest(const GlobalRequestID& id, |
| int error, |
| const net::SSLInfo* ssl_info) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| |
| // The request can be NULL if it was cancelled by the renderer (as the |
| // request of the user navigating to a new page from the location bar). |
| if (!request_->is_pending()) |
| return; |
| DVLOG(1) << "CancelSSLRequest() url: " << request_->url().spec(); |
| |
| if (ssl_info) { |
| request_->CancelWithSSLError(error, *ssl_info); |
| } else { |
| request_->CancelWithError(error); |
| } |
| } |
| |
| void ResourceLoader::ContinueSSLRequest(const GlobalRequestID& id) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| |
| DVLOG(1) << "ContinueSSLRequest() url: " << request_->url().spec(); |
| |
| request_->ContinueDespiteLastError(); |
| } |
| |
| void ResourceLoader::Resume() { |
| DCHECK(!is_transferring_); |
| |
| DeferredStage stage = deferred_stage_; |
| deferred_stage_ = DEFERRED_NONE; |
| switch (stage) { |
| case DEFERRED_NONE: |
| NOTREACHED(); |
| break; |
| case DEFERRED_START: |
| StartRequestInternal(); |
| break; |
| case DEFERRED_REDIRECT: |
| request_->FollowDeferredRedirect(); |
| break; |
| case DEFERRED_READ: |
| MessageLoop::current()->PostTask( |
| FROM_HERE, |
| base::Bind(&ResourceLoader::ResumeReading, |
| weak_ptr_factory_.GetWeakPtr())); |
| break; |
| case DEFERRED_FINISH: |
| // Delay self-destruction since we don't know how we were reached. |
| MessageLoop::current()->PostTask( |
| FROM_HERE, |
| base::Bind(&ResourceLoader::CallDidFinishLoading, |
| weak_ptr_factory_.GetWeakPtr())); |
| break; |
| } |
| } |
| |
| void ResourceLoader::Cancel() { |
| CancelRequest(false); |
| } |
| |
| void ResourceLoader::StartRequestInternal() { |
| DCHECK(!request_->is_pending()); |
| request_->Start(); |
| |
| delegate_->DidStartRequest(this); |
| } |
| |
| void ResourceLoader::CancelRequestInternal(int error, bool from_renderer) { |
| VLOG(1) << "CancelRequestInternal: " << request_->url().spec(); |
| |
| ResourceRequestInfoImpl* info = GetRequestInfo(); |
| |
| // 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. |
| if (from_renderer && info->is_download()) |
| return; |
| |
| // TODO(darin): Perhaps we should really be looking to see if the status is |
| // IO_PENDING? |
| bool was_pending = request_->is_pending(); |
| |
| if (login_delegate_) { |
| login_delegate_->OnRequestCancelled(); |
| login_delegate_ = NULL; |
| } |
| if (ssl_client_auth_handler_) { |
| ssl_client_auth_handler_->OnRequestCancelled(); |
| ssl_client_auth_handler_ = NULL; |
| } |
| |
| request_->CancelWithError(error); |
| |
| if (!was_pending) { |
| // If the request isn't in flight, then we won't get an asynchronous |
| // notification from the request, so we have to signal ourselves to finish |
| // this request. |
| MessageLoop::current()->PostTask( |
| FROM_HERE, base::Bind(&ResourceLoader::ResponseCompleted, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| } |
| |
| void ResourceLoader::CompleteResponseStarted() { |
| ResourceRequestInfoImpl* info = GetRequestInfo(); |
| |
| scoped_refptr<ResourceResponse> response(new ResourceResponse()); |
| PopulateResourceResponse(request_.get(), response); |
| |
| if (request_->ssl_info().cert) { |
| int cert_id = |
| CertStore::GetInstance()->StoreCert(request_->ssl_info().cert, |
| info->GetChildID()); |
| response->head.security_info = SerializeSecurityInfo( |
| cert_id, |
| request_->ssl_info().cert_status, |
| request_->ssl_info().security_bits, |
| request_->ssl_info().connection_status); |
| } else { |
| // We should not have any SSL state. |
| DCHECK(!request_->ssl_info().cert_status && |
| request_->ssl_info().security_bits == -1 && |
| !request_->ssl_info().connection_status); |
| } |
| |
| delegate_->DidReceiveResponse(this); |
| |
| bool defer = false; |
| if (!handler_->OnResponseStarted(info->GetRequestID(), response, &defer)) { |
| Cancel(); |
| } else if (defer) { |
| deferred_stage_ = DEFERRED_READ; // Read first chunk when resumed. |
| } |
| } |
| |
| void ResourceLoader::StartReading(bool is_continuation) { |
| int bytes_read = 0; |
| ReadMore(&bytes_read); |
| |
| // If IO is pending, wait for the URLRequest to call OnReadCompleted. |
| if (request_->status().is_io_pending()) |
| return; |
| |
| if (!is_continuation || bytes_read <= 0) { |
| OnReadCompleted(request_.get(), bytes_read); |
| } else { |
| // Else, trigger OnReadCompleted asynchronously to avoid starving the IO |
| // thread in case the URLRequest can provide data synchronously. |
| MessageLoop::current()->PostTask( |
| FROM_HERE, |
| base::Bind(&ResourceLoader::OnReadCompleted, |
| weak_ptr_factory_.GetWeakPtr(), |
| request_.get(), bytes_read)); |
| } |
| } |
| |
| void ResourceLoader::ResumeReading() { |
| DCHECK(!is_deferred()); |
| |
| if (request_->status().is_success()) { |
| StartReading(false); // Read the next chunk (OK to complete synchronously). |
| } else { |
| ResponseCompleted(); |
| } |
| } |
| |
| void ResourceLoader::ReadMore(int* bytes_read) { |
| ResourceRequestInfoImpl* info = GetRequestInfo(); |
| DCHECK(!is_deferred()); |
| |
| net::IOBuffer* buf; |
| int buf_size; |
| if (!handler_->OnWillRead(info->GetRequestID(), &buf, &buf_size, -1)) { |
| Cancel(); |
| return; |
| } |
| |
| DCHECK(buf); |
| DCHECK(buf_size > 0); |
| |
| request_->Read(buf, buf_size, bytes_read); |
| |
| // No need to check the return value here as we'll detect errors by |
| // inspecting the URLRequest's status. |
| } |
| |
| void ResourceLoader::CompleteRead(int bytes_read) { |
| DCHECK(bytes_read >= 0); |
| DCHECK(request_->status().is_success()); |
| |
| ResourceRequestInfoImpl* info = GetRequestInfo(); |
| |
| bool defer = false; |
| if (!handler_->OnReadCompleted(info->GetRequestID(), bytes_read, &defer)) { |
| Cancel(); |
| } else if (defer) { |
| deferred_stage_ = DEFERRED_READ; // Read next chunk when resumed. |
| } |
| } |
| |
| void ResourceLoader::ResponseCompleted() { |
| VLOG(1) << "ResponseCompleted: " << request_->url().spec(); |
| ResourceRequestInfoImpl* info = GetRequestInfo(); |
| |
| std::string security_info; |
| const net::SSLInfo& ssl_info = request_->ssl_info(); |
| if (ssl_info.cert != NULL) { |
| int cert_id = CertStore::GetInstance()->StoreCert(ssl_info.cert, |
| info->GetChildID()); |
| security_info = SerializeSecurityInfo( |
| cert_id, ssl_info.cert_status, ssl_info.security_bits, |
| ssl_info.connection_status); |
| } |
| |
| if (handler_->OnResponseCompleted(info->GetRequestID(), request_->status(), |
| security_info)) { |
| // This will result in our destruction. |
| CallDidFinishLoading(); |
| } else { |
| // The handler is not ready to die yet. We will call DidFinishLoading when |
| // we resume. |
| deferred_stage_ = DEFERRED_FINISH; |
| } |
| } |
| |
| void ResourceLoader::CallDidFinishLoading() { |
| delegate_->DidFinishLoading(this); |
| } |
| |
| } // namespace content |