// Copyright (c) 2011 The Chromium Embedded Framework Authors. // Portions 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. // // This file contains an implementation of the ResourceLoaderBridge class. // The class is implemented using net::URLRequest, meaning it is a "simple" // version that directly issues requests. The more complicated one used in the // browser uses IPC. // // Because net::URLRequest only provides an asynchronous resource loading API, // this file makes use of net::URLRequest from a background IO thread. Requests // for cookies and synchronously loaded resources result in the main thread of // the application blocking until the IO thread completes the operation. (See // GetCookies and SyncLoad) // // Main thread IO thread // ----------- --------- // ResourceLoaderBridge <---o---------> RequestProxy (normal case) // \ -> net::URLRequest // o-------> SyncRequestProxy (synchronous case) // -> net::URLRequest // // NOTE: The implementation in this file may be used to have WebKit fetch // resources in-process. For example, it is handy for building a single- // process WebKit embedding (e.g., test_shell) that can use net::URLRequest to // perform URL loads. See renderer/resource_dispatcher.h for details on an // alternate implementation that defers fetching to another process. #include "libcef/browser_resource_loader_bridge.h" #include "libcef/browser_appcache_system.h" #include "libcef/browser_request_context.h" #include "libcef/browser_socket_stream_bridge.h" #include "libcef/browser_webkit_glue.h" #include "libcef/browser_impl.h" #include "libcef/cef_context.h" #include "libcef/cef_process.h" #include "libcef/cef_process_io_thread.h" #include "libcef/external_protocol_handler.h" #include "libcef/request_impl.h" #include "libcef/response_impl.h" #include "libcef/http_header_utils.h" #include "base/bind.h" #include "base/file_path.h" #include "base/file_util.h" #include "base/memory/ref_counted.h" #include "base/message_loop.h" #include "base/message_loop_proxy.h" #include "base/time.h" #include "base/timer.h" #include "base/threading/thread.h" #include "base/utf_string_conversions.h" #include "net/base/auth.h" #include "net/base/file_stream.h" #include "net/base/io_buffer.h" #include "net/base/load_flags.h" #include "net/base/net_errors.h" #include "net/base/net_util.h" #include "net/base/static_cookie_policy.h" #include "net/base/upload_data.h" #include "net/http/http_cache.h" #include "net/http/http_request_headers.h" #include "net/http/http_response_headers.h" #include "net/proxy/proxy_service.h" #include "net/url_request/url_request.h" #include "net/url_request/url_request_job_manager.h" #include "net/url_request/url_request_redirect_job.h" #include "webkit/appcache/appcache_interfaces.h" #include "webkit/blob/blob_storage_controller.h" #include "webkit/blob/shareable_file_reference.h" #include "webkit/fileapi/file_system_context.h" #include "webkit/fileapi/file_system_dir_url_request_job.h" #include "webkit/fileapi/file_system_url_request_job.h" #include "webkit/glue/resource_loader_bridge.h" #include "webkit/glue/resource_request_body.h" #include "webkit/glue/webkit_glue.h" #if defined(OS_MACOSX) || defined(OS_WIN) #include "crypto/nss_util.h" #endif using net::HttpResponseHeaders; using net::StaticCookiePolicy; using net::URLRequestStatus; using webkit_blob::ShareableFileReference; using webkit_glue::ResourceLoaderBridge; using webkit_glue::ResourceRequestBody; using webkit_glue::ResourceResponseInfo; namespace { static const char kCefUserData[] = "cef_userdata"; struct RequestParams { std::string method; GURL url; GURL first_party_for_cookies; GURL referrer; WebKit::WebReferrerPolicy referrer_policy; std::string headers; int load_flags; ResourceType::Type request_type; int appcache_host_id; bool download_to_file; scoped_refptr request_body; net::RequestPriority priority; }; // The interval for calls to RequestProxy::MaybeUpdateUploadProgress static const int kUpdateUploadProgressIntervalMsec = 100; class ExtraRequestInfo : public net::URLRequest::Data { public: ExtraRequestInfo(CefBrowserImpl* browser, ResourceType::Type resource_type) : browser_(browser), resource_type_(resource_type), allow_download_(resource_type == ResourceType::MAIN_FRAME || resource_type == ResourceType::SUB_FRAME) { } // The browser pointer is guaranteed to be valid for the lifespan of the // request. The pointer will be NULL in cases where the request was // initiated via the CefWebURLRequest API instead of by a browser window. CefBrowserImpl* browser() const { return browser_; } // Identifies the type of resource, such as subframe, media, etc. ResourceType::Type resource_type() const { return resource_type_; } bool allow_download() const { return allow_download_; } private: CefBrowserImpl* browser_; ResourceType::Type resource_type_; bool allow_download_; }; // Used to intercept redirect requests. class RequestInterceptor : public net::URLRequest::Interceptor { public: RequestInterceptor() { REQUIRE_IOT(); net::URLRequestJobManager::GetInstance()->RegisterRequestInterceptor(this); } ~RequestInterceptor() { REQUIRE_IOT(); net::URLRequestJobManager::GetInstance()-> UnregisterRequestInterceptor(this); } virtual net::URLRequestJob* MaybeIntercept( net::URLRequest* request, net::NetworkDelegate* network_delegate) OVERRIDE { return NULL; } virtual net::URLRequestJob* MaybeInterceptRedirect( net::URLRequest* request, net::NetworkDelegate* network_delegate, const GURL& location) OVERRIDE { REQUIRE_IOT(); ExtraRequestInfo* info = static_cast(request->GetUserData(kCefUserData)); if (!info) return NULL; CefRefPtr browser = info->browser(); if (!browser.get()) return NULL; CefRefPtr client = browser->GetClient(); CefRefPtr handler; if (client.get()) handler = client->GetRequestHandler(); if (!handler.get()) return NULL; CefString newUrlStr = location.spec(); handler->OnResourceRedirect(browser, request->url().spec(), newUrlStr); if (newUrlStr != location.spec()) { GURL new_url = GURL(std::string(newUrlStr)); if (!new_url.is_empty() && new_url.is_valid()) { return new net::URLRequestRedirectJob( request, network_delegate, new_url, net::URLRequestRedirectJob::REDIRECT_302_FOUND); } } return NULL; } DISALLOW_COPY_AND_ASSIGN(RequestInterceptor); }; // The RequestProxy does most of its work on the IO thread. The Start and // Cancel methods are proxied over to the IO thread, where an net::URLRequest // object is instantiated. class RequestProxy : public net::URLRequest::Delegate, public base::RefCountedThreadSafe { public: // Takes ownership of the params. explicit RequestProxy(CefRefPtr browser) : download_to_file_(false), buf_(new net::IOBuffer(kDataSize)), browser_(browser), owner_loop_(NULL), peer_(NULL), last_upload_position_(0), defers_loading_(false), defers_loading_want_read_(false) { } void DropPeer() { DCHECK(MessageLoop::current() == owner_loop_); peer_ = NULL; } void Start(ResourceLoaderBridge::Peer* peer, RequestParams* params) { peer_ = peer; owner_loop_ = MessageLoop::current(); InitializeParams(params); // proxy over to the io thread CefThread::PostTask(CefThread::IO, FROM_HERE, base::Bind( &RequestProxy::AsyncStart, this, params)); } void Cancel() { DCHECK(MessageLoop::current() == owner_loop_); if (download_handler_.get()) { // WebKit will try to cancel the download but we won't allow it. return; } // proxy over to the io thread CefThread::PostTask(CefThread::IO, FROM_HERE, base::Bind( &RequestProxy::AsyncCancel, this)); } void SetDefersLoading(bool defer) { DCHECK(MessageLoop::current() == owner_loop_); CefThread::PostTask(CefThread::IO, FROM_HERE, base::Bind( &RequestProxy::AsyncSetDefersLoading, this, defer)); } protected: friend class base::RefCountedThreadSafe; virtual ~RequestProxy() { // If we have a request, then we'd better be on the io thread! DCHECK(!request_.get() || CefThread::CurrentlyOn(CefThread::IO)); } virtual void InitializeParams(RequestParams* params) { params->priority = net::MEDIUM; } // -------------------------------------------------------------------------- // The following methods are called on the owner's thread in response to // various net::URLRequest callbacks. The event hooks, defined below, trigger // these methods asynchronously. void NotifyReceivedRedirect(const GURL& new_url, const ResourceResponseInfo& info) { DCHECK(MessageLoop::current() == owner_loop_); bool has_new_first_party_for_cookies = false; GURL new_first_party_for_cookies; if (peer_ && peer_->OnReceivedRedirect(new_url, info, &has_new_first_party_for_cookies, &new_first_party_for_cookies)) { CefThread::PostTask(CefThread::IO, FROM_HERE, base::Bind( &RequestProxy::AsyncFollowDeferredRedirect, this, has_new_first_party_for_cookies, new_first_party_for_cookies)); } else { Cancel(); } } void NotifyReceivedResponse(const ResourceResponseInfo& info, const GURL& url, bool allow_download) { DCHECK(MessageLoop::current() == owner_loop_); if (browser_.get() && info.headers.get()) { CefRefPtr client = browser_->GetClient(); CefRefPtr handler; if (client.get()) handler = client->GetRequestHandler(); if (handler.get()) { CefRefPtr response = new CefResponseImpl(); // Transfer response headers if (info.headers) { CefResponse::HeaderMap headerMap; void* header_index = NULL; std::string name, value; while (info.headers->EnumerateHeaderLines(&header_index, &name, &value)) { if (!name.empty() && !value.empty()) headerMap.insert(std::make_pair(name, value)); } response->SetHeaderMap(headerMap); response->SetStatusText(info.headers->GetStatusText()); response->SetStatus(info.headers->response_code()); } response->SetMimeType(info.mime_type); handler->OnResourceResponse(browser_.get(), url.spec(), response, content_filter_); std::string content_disposition; info.headers->GetNormalizedHeader("Content-Disposition", &content_disposition); if (allow_download && webkit_glue::ShouldDownload(content_disposition, info.mime_type)) { string16 filename = net::GetSuggestedFilename(url, content_disposition, info.charset, "", info.mime_type, "download"); CefRefPtr dl_handler; if (handler->GetDownloadHandler(browser_.get(), info.mime_type, filename, info.content_length, dl_handler)) { download_handler_ = dl_handler; } } } } if (peer_) peer_->OnReceivedResponse(info); } void NotifyReceivedData(int bytes_read) { DCHECK(MessageLoop::current() == owner_loop_); if (!peer_) return; // Make a local copy of buf_, since AsyncReadData reuses it. scoped_array buf_copy(new char[bytes_read]); memcpy(buf_copy.get(), buf_->data(), bytes_read); // Continue reading more data into buf_ // Note: Doing this before notifying our peer ensures our load events get // dispatched in a manner consistent with DumpRenderTree (and also avoids a // race condition). If the order of the next 2 functions were reversed, the // peer could generate new requests in response to the received data, which // when run on the io thread, could race against this function in doing // another InvokeLater. See bug 769249. CefThread::PostTask(CefThread::IO, FROM_HERE, base::Bind( &RequestProxy::AsyncReadData, this)); CefRefPtr resourceStream; if (content_filter_.get()) content_filter_->ProcessData(buf_copy.get(), bytes_read, resourceStream); if (resourceStream.get()) { // The filter made some changes to the data in the buffer. resourceStream->Seek(0, SEEK_END); bytes_read = resourceStream->Tell(); resourceStream->Seek(0, SEEK_SET); buf_copy.reset(new char[bytes_read]); resourceStream->Read(buf_copy.get(), 1, bytes_read); } if (download_handler_.get() && !download_handler_->ReceivedData(buf_copy.get(), bytes_read)) { // Cancel loading by proxying over to the io thread. CefThread::PostTask(CefThread::IO, FROM_HERE, base::Bind( &RequestProxy::AsyncCancel, this)); } peer_->OnReceivedData(buf_copy.get(), bytes_read, -1); } void NotifyDownloadedData(int bytes_read) { DCHECK(MessageLoop::current() == owner_loop_); if (!peer_) return; // Continue reading more data, see the comment in NotifyReceivedData. CefThread::PostTask(CefThread::IO, FROM_HERE, base::Bind( &RequestProxy::AsyncReadData, this)); peer_->OnDownloadedData(bytes_read); } void NotifyCompletedRequest(int error_code, const std::string& security_info, const base::TimeTicks& complete_time) { DCHECK(MessageLoop::current() == owner_loop_); // Drain the content filter of all remaining data if (content_filter_.get()) { CefRefPtr remainder; content_filter_->Drain(remainder); if (remainder.get()) { remainder->Seek(0, SEEK_END); int size = static_cast(remainder->Tell()); if (size > 0) { remainder->Seek(0, SEEK_SET); scoped_array buf(new char[size]); remainder->Read(buf.get(), 1, size); if (download_handler_.get() && !download_handler_->ReceivedData(buf.get(), size)) { // Cancel loading by proxying over to the io thread. CefThread::PostTask(CefThread::IO, FROM_HERE, base::Bind( &RequestProxy::AsyncCancel, this)); } if (peer_) peer_->OnReceivedData(buf.get(), size, -1); } } content_filter_ = NULL; } if (download_handler_.get()) { download_handler_->Complete(); download_handler_ = NULL; } if (peer_) { peer_->OnCompletedRequest(error_code, false, security_info, complete_time); DropPeer(); // ensure no further notifications } } void NotifyUploadProgress(uint64 position, uint64 size) { DCHECK(MessageLoop::current() == owner_loop_); if (peer_) peer_->OnUploadProgress(position, size); } // -------------------------------------------------------------------------- // The following methods are called on the io thread. They correspond to // actions performed on the owner's thread. void AsyncStart(RequestParams* params) { DCHECK(CefThread::CurrentlyOn(CefThread::IO)); bool handled = false; scoped_ptr upload_data_stream; if (params->request_body) { upload_data_stream.reset( params->request_body->ResolveElementsAndCreateUploadDataStream( _Context->request_context()->blob_storage_controller())); } if (browser_.get()) { CefRefPtr client = browser_->GetClient(); CefRefPtr handler; if (client.get()) handler = client->GetRequestHandler(); if (handler.get()) { // Build the request object for passing to the handler CefRefPtr request(new CefRequestImpl()); CefRequestImpl* requestimpl = static_cast(request.get()); std::string originalUrl(params->url.spec()); requestimpl->SetURL(originalUrl); requestimpl->SetMethod(params->method); // Transfer request headers CefRequest::HeaderMap headerMap; HttpHeaderUtils::ParseHeaders(params->headers, headerMap); headerMap.insert(std::make_pair("Referer", params->referrer.spec())); requestimpl->SetHeaderMap(headerMap); // Transfer post data, if any if (upload_data_stream) { CefRefPtr postdata(new CefPostDataImpl()); static_cast(postdata.get())->Set( *upload_data_stream.get()); requestimpl->SetPostData(postdata); } int loadFlags = params->load_flags; // Handler output will be returned in these variables CefString redirectUrl; CefRefPtr resourceStream; CefRefPtr response(new CefResponseImpl()); handled = handler->OnBeforeResourceLoad(browser_.get(), request, redirectUrl, resourceStream, response, loadFlags); if (!handled) { // Observe URL from request. const std::string requestUrl(request->GetURL()); if (requestUrl != originalUrl) params->url = GURL(requestUrl); else if (!redirectUrl.empty()) params->url = GURL(std::string(redirectUrl)); // Observe method from request. params->method = request->GetMethod(); // Observe headers from request. request->GetHeaderMap(headerMap); CefString referrerStr; referrerStr.FromASCII("Referer"); CefRequest::HeaderMap::iterator referrer = headerMap.find(referrerStr); if (referrer == headerMap.end()) { params->referrer = GURL(); } else { params->referrer = GURL(std::string(referrer->second)); headerMap.erase(referrer); } params->headers = HttpHeaderUtils::GenerateHeaders(headerMap); // Observe post data from request. CefRefPtr postData = request->GetPostData(); if (postData.get()) { upload_data_stream.reset( static_cast(postData.get())->Get()); } else { upload_data_stream.reset(NULL); } } if (handled) { // cancel the resource load OnCompletedRequest(net::ERR_ABORTED, std::string(), base::TimeTicks()); } else if (resourceStream.get()) { // load from the provided resource stream handled = true; resourceStream->Seek(0, SEEK_END); int64 offset = resourceStream->Tell(); resourceStream->Seek(0, SEEK_SET); resource_stream_ = resourceStream; CefResponseImpl* responseImpl = static_cast(response.get()); ResourceResponseInfo info; info.content_length = offset; info.mime_type = response->GetMimeType(); info.headers = responseImpl->GetResponseHeaders(); OnReceivedResponse(info, params->url); AsyncReadData(); } else if (response->GetStatus() != 0) { // status set, but no resource stream handled = true; CefResponseImpl* responseImpl = static_cast(response.get()); ResourceResponseInfo info; info.content_length = 0; info.mime_type = response->GetMimeType(); info.headers = responseImpl->GetResponseHeaders(); OnReceivedResponse(info, params->url); AsyncReadData(); } if (!handled && ResourceType::IsFrame(params->request_type) && !net::URLRequest::IsHandledProtocol(params->url.scheme())) { bool allow_os_execution = false; handled = handler->OnProtocolExecution(browser_.get(), params->url.spec(), allow_os_execution); if (!handled && allow_os_execution && ExternalProtocolHandler::HandleExternalProtocol(params->url)) { handled = true; } if (handled) OnCompletedRequest(net::OK, std::string(), base::TimeTicks()); } } } if (!handled) { net::URLRequestContext* context = browser_.get() ? browser_->request_context_proxy() : _Context->request_context(); request_.reset(new net::URLRequest(params->url, this, context)); request_->set_priority(params->priority); request_->set_method(params->method); request_->set_first_party_for_cookies(params->first_party_for_cookies); request_->set_referrer(params->referrer.spec()); webkit_glue::ConfigureURLRequestForReferrerPolicy( request_.get(), params->referrer_policy); net::HttpRequestHeaders headers; headers.AddHeadersFromString(params->headers); request_->SetExtraRequestHeaders(headers); request_->set_load_flags(params->load_flags); if (upload_data_stream) request_->set_upload(upload_data_stream.Pass()); request_->SetUserData(kCefUserData, new ExtraRequestInfo(browser_.get(), params->request_type)); BrowserAppCacheSystem::SetExtraRequestInfo( request_.get(), params->appcache_host_id, params->request_type); download_to_file_ = params->download_to_file; if (download_to_file_) { FilePath path; if (file_util::CreateTemporaryFile(&path)) { downloaded_file_ = ShareableFileReference::GetOrCreate( path, ShareableFileReference::DELETE_ON_FINAL_RELEASE, base::MessageLoopProxy::current()); file_stream_.reset(new net::FileStream(NULL)); file_stream_->OpenSync( path, base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_WRITE); } } request_->Start(); if (request_.get() && request_->has_upload() && params->load_flags & net::LOAD_ENABLE_UPLOAD_PROGRESS) { upload_progress_timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds( kUpdateUploadProgressIntervalMsec), this, &RequestProxy::MaybeUpdateUploadProgress); } } delete params; } void AsyncCancel() { DCHECK(CefThread::CurrentlyOn(CefThread::IO)); // This can be null in cases where the request is already done. if (!resource_stream_.get() && !request_.get()) return; if (request_.get()) request_->Cancel(); Done(); } void AsyncFollowDeferredRedirect(bool has_new_first_party_for_cookies, const GURL& new_first_party_for_cookies) { DCHECK(CefThread::CurrentlyOn(CefThread::IO)); // This can be null in cases where the request is already done. if (!request_.get()) return; if (has_new_first_party_for_cookies) request_->set_first_party_for_cookies(new_first_party_for_cookies); request_->FollowDeferredRedirect(); } void AsyncSetDefersLoading(bool defer) { DCHECK(CefThread::CurrentlyOn(CefThread::IO)); if (defers_loading_ != defer) { defers_loading_ = defer; if (!defers_loading_ && defers_loading_want_read_) { // Perform the pending AsyncReadData now. defers_loading_want_read_ = false; AsyncReadData(); } } } void AsyncReadData() { DCHECK(CefThread::CurrentlyOn(CefThread::IO)); // Pause downloading if we're in deferred mode. if (defers_loading_) { defers_loading_want_read_ = true; return; } if (resource_stream_.get()) { // Read from the handler-provided resource stream int bytes_read = resource_stream_->Read(buf_->data(), 1, kDataSize); if (bytes_read > 0) { OnReceivedData(bytes_read); } else { Done(); } return; } // This can be null in cases where the request is already done. if (!request_.get()) return; if (request_->status().is_success()) { int bytes_read; if (request_->Read(buf_, kDataSize, &bytes_read) && bytes_read) { OnReceivedData(bytes_read); } else if (!request_->status().is_io_pending()) { Done(); } // else wait for OnReadCompleted } else { Done(); } } // -------------------------------------------------------------------------- // The following methods are event hooks (corresponding to net::URLRequest // callbacks) that run on the IO thread. They are designed to be overridden // by the SyncRequestProxy subclass. virtual void OnReceivedRedirect( const GURL& new_url, const ResourceResponseInfo& info, bool* defer_redirect) { DCHECK(CefThread::CurrentlyOn(CefThread::IO)); *defer_redirect = true; // See AsyncFollowDeferredRedirect owner_loop_->PostTask(FROM_HERE, base::Bind( &RequestProxy::NotifyReceivedRedirect, this, new_url, info)); } virtual void OnReceivedResponse( const ResourceResponseInfo& info, // only used when loading from a resource stream const GURL& simulated_url) { DCHECK(CefThread::CurrentlyOn(CefThread::IO)); GURL url; bool allow_download(false); if (request_.get()) { url = request_->url(); ExtraRequestInfo* info = static_cast(request_->GetUserData(kCefUserData)); if (info) allow_download = info->allow_download(); } else if (!simulated_url.is_empty() && simulated_url.is_valid()) { url = simulated_url; } owner_loop_->PostTask(FROM_HERE, base::Bind( &RequestProxy::NotifyReceivedResponse, this, info, url, allow_download)); } virtual void OnReceivedData(int bytes_read) { DCHECK(CefThread::CurrentlyOn(CefThread::IO)); if (download_to_file_) { file_stream_->WriteSync(buf_->data(), bytes_read); owner_loop_->PostTask(FROM_HERE, base::Bind( &RequestProxy::NotifyDownloadedData, this, bytes_read)); return; } owner_loop_->PostTask(FROM_HERE, base::Bind( &RequestProxy::NotifyReceivedData, this, bytes_read)); } virtual void OnCompletedRequest(int error_code, const std::string& security_info, const base::TimeTicks& complete_time) { DCHECK(CefThread::CurrentlyOn(CefThread::IO)); if (download_to_file_) file_stream_.reset(); owner_loop_->PostTask(FROM_HERE, base::Bind( &RequestProxy::NotifyCompletedRequest, this, error_code, security_info, complete_time)); } // -------------------------------------------------------------------------- // net::URLRequest::Delegate implementation: virtual void OnReceivedRedirect(net::URLRequest* request, const GURL& new_url, bool* defer_redirect) OVERRIDE { DCHECK(CefThread::CurrentlyOn(CefThread::IO)); DCHECK(request->status().is_success()); ResourceResponseInfo info; PopulateResponseInfo(request, &info); OnReceivedRedirect(new_url, info, defer_redirect); } virtual void OnResponseStarted(net::URLRequest* request) OVERRIDE { DCHECK(CefThread::CurrentlyOn(CefThread::IO)); if (request->status().is_success()) { ResourceResponseInfo info; PopulateResponseInfo(request, &info); OnReceivedResponse(info, GURL::EmptyGURL()); AsyncReadData(); // start reading } else { Done(); } } virtual void OnAuthRequired(net::URLRequest* request, net::AuthChallengeInfo* auth_info) OVERRIDE { DCHECK(CefThread::CurrentlyOn(CefThread::IO)); if (browser_.get()) { CefRefPtr client = browser_->GetClient(); if (client.get()) { CefRefPtr handler = client->GetRequestHandler(); if (handler.get()) { CefString username, password; if (handler->GetAuthCredentials(browser_.get(), auth_info->is_proxy, auth_info->challenger.host(), auth_info->challenger.port(), auth_info->realm, auth_info->scheme, username, password)) { request->SetAuth(net::AuthCredentials(username, password)); return; } } } } request->CancelAuth(); } virtual void OnSSLCertificateError(net::URLRequest* request, const net::SSLInfo& ssl_info, bool fatal) OVERRIDE { DCHECK(CefThread::CurrentlyOn(CefThread::IO)); // Allow all certificate errors. request->ContinueDespiteLastError(); } virtual void OnReadCompleted(net::URLRequest* request, int bytes_read) OVERRIDE { DCHECK(CefThread::CurrentlyOn(CefThread::IO)); if (request->status().is_success() && bytes_read > 0) { OnReceivedData(bytes_read); } else { Done(); } } // -------------------------------------------------------------------------- // Helpers and data: void Done() { DCHECK(CefThread::CurrentlyOn(CefThread::IO)); if (resource_stream_.get()) { // Resource stream reads always complete successfully OnCompletedRequest(0, std::string(), base::TimeTicks()); resource_stream_ = NULL; } else if (request_.get()) { if (upload_progress_timer_.IsRunning()) { MaybeUpdateUploadProgress(); upload_progress_timer_.Stop(); } DCHECK(request_.get()); OnCompletedRequest(request_->status().error(), std::string(), base::TimeTicks()); request_.reset(); // destroy on the io thread } } void MaybeUpdateUploadProgress() { DCHECK(CefThread::CurrentlyOn(CefThread::IO)); // If a redirect is received upload is cancelled in net::URLRequest, we // should try to stop the |upload_progress_timer_| timer and return. if (!request_->has_upload()) { if (upload_progress_timer_.IsRunning()) upload_progress_timer_.Stop(); return; } net::UploadProgress progress = request_->GetUploadProgress(); if (progress.position() == last_upload_position_) return; // no progress made since last time const uint64 kHalfPercentIncrements = 200; const base::TimeDelta kOneSecond = base::TimeDelta::FromMilliseconds(1000); uint64 amt_since_last = progress.position() - last_upload_position_; base::TimeDelta time_since_last = base::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) { owner_loop_->PostTask( FROM_HERE, base::Bind(&RequestProxy::NotifyUploadProgress, this, progress.position(), progress.size())); last_upload_ticks_ = base::TimeTicks::Now(); last_upload_position_ = progress.position(); } } void PopulateResponseInfo(net::URLRequest* request, ResourceResponseInfo* info) const { DCHECK(CefThread::CurrentlyOn(CefThread::IO)); info->request_time = request->request_time(); info->response_time = request->response_time(); info->headers = request->response_headers(); request->GetMimeType(&info->mime_type); request->GetCharset(&info->charset); info->content_length = request->GetExpectedContentSize(); if (downloaded_file_) info->download_file_path = downloaded_file_->path(); BrowserAppCacheSystem::GetExtraResponseInfo( request, &info->appcache_id, &info->appcache_manifest_url); } scoped_ptr request_; CefRefPtr resource_stream_; // Support for request.download_to_file behavior. bool download_to_file_; scoped_ptr file_stream_; scoped_refptr downloaded_file_; // Size of our async IO data buffers. Limited by the sanity check in // URLRequestJob::Read(). static const int kDataSize = 1000000-1; // read buffer for async IO scoped_refptr buf_; CefRefPtr browser_; MessageLoop* owner_loop_; // This is our peer in WebKit (implemented as ResourceHandleInternal). We do // not manage its lifetime, and we may only access it from the owner's // message loop (owner_loop_). ResourceLoaderBridge::Peer* peer_; // Timer used to pull upload progress info. base::RepeatingTimer upload_progress_timer_; // Info used to determine whether or not to send an upload progress update. uint64 last_upload_position_; base::TimeTicks last_upload_ticks_; CefRefPtr download_handler_; CefRefPtr content_filter_; // True if loading of data is currently deferred. bool defers_loading_; // True if an AsyncReadData was scheduled while we were deferred. bool defers_loading_want_read_; }; //----------------------------------------------------------------------------- class SyncRequestProxy : public RequestProxy { public: explicit SyncRequestProxy(CefRefPtr browser, ResourceLoaderBridge::SyncLoadResponse* result) : RequestProxy(browser), result_(result), event_(true, false) { } void WaitForCompletion() { event_.Wait(); } // -------------------------------------------------------------------------- // Event hooks that run on the IO thread: virtual void OnReceivedRedirect( const GURL& new_url, const ResourceResponseInfo& info, bool* defer_redirect) { DCHECK(CefThread::CurrentlyOn(CefThread::IO)); // TODO(darin): It would be much better if this could live in WebCore, but // doing so requires API changes at all levels. Similar code exists in // WebCore/platform/network/cf/ResourceHandleCFNet.cpp :-( if (new_url.GetOrigin() != result_->url.GetOrigin()) { DLOG(WARNING) << "Cross origin redirect denied"; Cancel(); return; } result_->url = new_url; } virtual void OnReceivedResponse(const ResourceResponseInfo& info, const GURL&) { DCHECK(CefThread::CurrentlyOn(CefThread::IO)); *static_cast(result_) = info; } virtual void OnReceivedData(int bytes_read) { DCHECK(CefThread::CurrentlyOn(CefThread::IO)); if (download_to_file_) file_stream_->WriteSync(buf_->data(), bytes_read); else result_->data.append(buf_->data(), bytes_read); AsyncReadData(); // read more (may recurse) } virtual void OnCompletedRequest(int error_code, const std::string& security_info, const base::TimeTicks& complete_time) { DCHECK(CefThread::CurrentlyOn(CefThread::IO)); if (download_to_file_) file_stream_.reset(); result_->error_code = error_code; event_.Signal(); } protected: virtual void InitializeParams(RequestParams* params) { // For synchronous requests ignore load limits to avoid a deadlock problem // in SyncRequestProxy (issue #192). params->load_flags |= net::LOAD_IGNORE_LIMITS; params->priority = net::HIGHEST; } private: ResourceLoaderBridge::SyncLoadResponse* result_; base::WaitableEvent event_; }; //----------------------------------------------------------------------------- class ResourceLoaderBridgeImpl : public ResourceLoaderBridge, public base::NonThreadSafe { public: ResourceLoaderBridgeImpl(CefRefPtr browser, const webkit_glue::ResourceLoaderBridge::RequestInfo& request_info) : browser_(browser), params_(new RequestParams), proxy_(NULL) { params_->method = request_info.method; params_->url = request_info.url; params_->first_party_for_cookies = request_info.first_party_for_cookies; params_->referrer = request_info.referrer; params_->referrer_policy = request_info.referrer_policy; params_->headers = request_info.headers; params_->load_flags = request_info.load_flags; params_->request_type = request_info.request_type; params_->appcache_host_id = request_info.appcache_host_id; params_->download_to_file = request_info.download_to_file; } virtual ~ResourceLoaderBridgeImpl() { if (proxy_) { proxy_->DropPeer(); // Let the proxy die on the IO thread proxy_->AddRef(); CefThread::ReleaseSoon(CefThread::IO, FROM_HERE, proxy_.get()); } } // -------------------------------------------------------------------------- // ResourceLoaderBridge implementation: virtual void SetRequestBody(ResourceRequestBody* request_body) OVERRIDE { DCHECK(CalledOnValidThread()); DCHECK(params_.get()); DCHECK(!params_->request_body); params_->request_body = request_body; } virtual bool Start(Peer* peer) OVERRIDE { DCHECK(CalledOnValidThread()); DCHECK(!proxy_); proxy_ = new RequestProxy(browser_); proxy_->Start(peer, params_.release()); return true; // Any errors will be reported asynchronously. } virtual void Cancel() OVERRIDE { DCHECK(CalledOnValidThread()); DCHECK(proxy_); proxy_->Cancel(); } virtual void SetDefersLoading(bool value) OVERRIDE { DCHECK(CalledOnValidThread()); DCHECK(proxy_); proxy_->SetDefersLoading(value); } virtual void SyncLoad(SyncLoadResponse* response) OVERRIDE { DCHECK(CalledOnValidThread()); DCHECK(!proxy_); // this may change as the result of a redirect response->url = params_->url; proxy_ = new SyncRequestProxy(browser_, response); proxy_->Start(NULL, params_.release()); static_cast(proxy_.get())->WaitForCompletion(); } private: CefRefPtr browser_; // Ownership of params_ is transfered to the proxy when the proxy is created. scoped_ptr params_; // The request proxy is allocated when we start the request, and then it // sticks around until this ResourceLoaderBridge is destroyed. scoped_refptr proxy_; }; } // anonymous namespace //----------------------------------------------------------------------------- // static webkit_glue::ResourceLoaderBridge* BrowserResourceLoaderBridge::Create( const webkit_glue::ResourceLoaderBridge::RequestInfo& request_info) { DCHECK(CefThread::CurrentlyOn(CefThread::UI)); CefRefPtr browser = _Context->GetBrowserByID(request_info.routing_id); return new ResourceLoaderBridgeImpl(browser.get(), request_info); } //----------------------------------------------------------------------------- // static CefRefPtr BrowserResourceLoaderBridge::GetBrowserForRequest( net::URLRequest* request) { REQUIRE_IOT(); ExtraRequestInfo* extra_info = static_cast(request->GetUserData(kCefUserData)); if (extra_info) return extra_info->browser(); return NULL; } // static scoped_refptr BrowserResourceLoaderBridge::GetCacheThread() { return CefThread::GetMessageLoopProxyForThread(CefThread::FILE); } // static net::URLRequest::Interceptor* BrowserResourceLoaderBridge::CreateRequestInterceptor() { return new RequestInterceptor(); }