// 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. // // 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 // SetCookie <------------------------> CookieSetter // -> net_util::SetCookie // GetCookies <-----------------------> CookieGetter // -> net_util::GetCookies // // 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 "webkit/support/simple_resource_loader_bridge.h" #include "base/bind.h" #include "base/compiler_specific.h" #include "base/file_util.h" #include "base/files/file_path.h" #include "base/logging.h" #include "base/memory/ref_counted.h" #include "base/message_loop.h" #include "base/message_loop_proxy.h" #include "base/string_util.h" #include "base/synchronization/waitable_event.h" #include "base/threading/thread.h" #include "base/time.h" #include "base/timer.h" #include "net/base/file_stream.h" #include "net/base/io_buffer.h" #include "net/base/load_flags.h" #include "net/base/mime_util.h" #include "net/base/net_errors.h" #include "net/base/net_util.h" #include "net/base/network_delegate.h" #include "net/base/static_cookie_policy.h" #include "net/base/upload_data_stream.h" #include "net/cookies/cookie_store.h" #include "net/http/http_cache.h" #include "net/http/http_request_headers.h" #include "net/http/http_response_headers.h" #include "net/url_request/url_request.h" #include "net/url_request/url_request_context.h" #include "net/url_request/url_request_job.h" #include "webkit/browser/blob/blob_storage_controller.h" #include "webkit/browser/blob/blob_url_request_job.h" #include "webkit/browser/fileapi/file_system_context.h" #include "webkit/browser/fileapi/file_system_dir_url_request_job.h" #include "webkit/browser/fileapi/file_system_url_request_job.h" #include "webkit/common/appcache/appcache_interfaces.h" #include "webkit/common/blob/shareable_file_reference.h" #include "webkit/glue/resource_loader_bridge.h" #include "webkit/glue/resource_request_body.h" #include "webkit/glue/webkit_glue.h" #include "webkit/support/simple_appcache_system.h" #include "webkit/support/simple_file_system.h" #include "webkit/support/simple_file_writer.h" #include "webkit/support/simple_socket_stream_bridge.h" #include "webkit/support/test_shell_request_context.h" #include "webkit/support/test_shell_webblobregistry_impl.h" #if defined(OS_MACOSX) || defined(OS_WIN) #include "crypto/nss_util.h" #endif using webkit_glue::ResourceLoaderBridge; using webkit_glue::ResourceRequestBody; using webkit_glue::ResourceResponseInfo; using net::StaticCookiePolicy; using net::HttpResponseHeaders; using webkit_blob::ShareableFileReference; namespace { struct TestShellRequestContextParams { TestShellRequestContextParams( const base::FilePath& in_cache_path, net::HttpCache::Mode in_cache_mode, bool in_no_proxy) : cache_path(in_cache_path), cache_mode(in_cache_mode), no_proxy(in_no_proxy) {} base::FilePath cache_path; net::HttpCache::Mode cache_mode; bool no_proxy; }; //----------------------------------------------------------------------------- bool g_accept_all_cookies = false; class TestShellNetworkDelegate : public net::NetworkDelegate { public: virtual ~TestShellNetworkDelegate() {} protected: // net::NetworkDelegate implementation. virtual int OnBeforeURLRequest(net::URLRequest* request, const net::CompletionCallback& callback, GURL* new_url) OVERRIDE { return net::OK; } virtual int OnBeforeSendHeaders(net::URLRequest* request, const net::CompletionCallback& callback, net::HttpRequestHeaders* headers) OVERRIDE { return net::OK; } virtual void OnSendHeaders(net::URLRequest* request, const net::HttpRequestHeaders& headers) OVERRIDE {} virtual int OnHeadersReceived( net::URLRequest* request, const net::CompletionCallback& callback, const net::HttpResponseHeaders* original_response_headers, scoped_refptr* override_response_headers) OVERRIDE { return net::OK; } virtual void OnBeforeRedirect(net::URLRequest* request, const GURL& new_location) OVERRIDE {} virtual void OnResponseStarted(net::URLRequest* request) OVERRIDE {} virtual void OnRawBytesRead(const net::URLRequest& request, int bytes_read) OVERRIDE {} virtual void OnCompleted(net::URLRequest* request, bool started) OVERRIDE {} virtual void OnURLRequestDestroyed(net::URLRequest* request) OVERRIDE {} virtual void OnPACScriptError(int line_number, const base::string16& error) OVERRIDE { } virtual AuthRequiredResponse OnAuthRequired( net::URLRequest* request, const net::AuthChallengeInfo& auth_info, const AuthCallback& callback, net::AuthCredentials* credentials) OVERRIDE { return AUTH_REQUIRED_RESPONSE_NO_ACTION; } virtual bool OnCanGetCookies(const net::URLRequest& request, const net::CookieList& cookie_list) OVERRIDE { StaticCookiePolicy::Type policy_type = g_accept_all_cookies ? StaticCookiePolicy::ALLOW_ALL_COOKIES : StaticCookiePolicy::BLOCK_SETTING_THIRD_PARTY_COOKIES; StaticCookiePolicy policy(policy_type); int rv = policy.CanGetCookies( request.url(), request.first_party_for_cookies()); return rv == net::OK; } virtual bool OnCanSetCookie(const net::URLRequest& request, const std::string& cookie_line, net::CookieOptions* options) OVERRIDE { StaticCookiePolicy::Type policy_type = g_accept_all_cookies ? StaticCookiePolicy::ALLOW_ALL_COOKIES : StaticCookiePolicy::BLOCK_SETTING_THIRD_PARTY_COOKIES; StaticCookiePolicy policy(policy_type); int rv = policy.CanSetCookie( request.url(), request.first_party_for_cookies()); return rv == net::OK; } virtual bool OnCanAccessFile(const net::URLRequest& request, const base::FilePath& path) const OVERRIDE { return true; } virtual bool OnCanThrottleRequest( const net::URLRequest& request) const OVERRIDE { return false; } virtual int OnBeforeSocketStreamConnect( net::SocketStream* stream, const net::CompletionCallback& callback) OVERRIDE { return net::OK; } virtual void OnRequestWaitStateChange(const net::URLRequest& request, RequestWaitState state) OVERRIDE { } }; TestShellRequestContextParams* g_request_context_params = NULL; TestShellRequestContext* g_request_context = NULL; TestShellNetworkDelegate* g_network_delegate = NULL; base::Thread* g_cache_thread = NULL; //----------------------------------------------------------------------------- struct FileOverHTTPParams { FileOverHTTPParams(std::string in_file_path_template, GURL in_http_prefix) : file_path_template(in_file_path_template), http_prefix(in_http_prefix) {} std::string file_path_template; GURL http_prefix; }; class FileOverHTTPPathMappings { public: FileOverHTTPPathMappings() : redirections_() {} void AddMapping(std::string file_path_template, GURL http_prefix) { redirections_.push_back(FileOverHTTPParams(file_path_template, http_prefix)); } const FileOverHTTPParams* ParamsForRequest(std::string request, std::string::size_type& offset) { std::vector::iterator it; for (it = redirections_.begin(); it != redirections_.end(); ++it) { offset = request.find(it->file_path_template); if (offset != std::string::npos) return &*it; } return 0; } const FileOverHTTPParams* ParamsForResponse(std::string response_url) { std::vector::iterator it; for (it = redirections_.begin(); it != redirections_.end(); ++it) { if (response_url.find(it->http_prefix.spec()) == 0) return &*it; } return 0; } private: std::vector redirections_; }; FileOverHTTPPathMappings* g_file_over_http_mappings = NULL; //----------------------------------------------------------------------------- class IOThread : public base::Thread { public: IOThread() : base::Thread("IOThread") {} virtual ~IOThread() { Stop(); } virtual void Init() OVERRIDE { if (g_request_context_params) { g_request_context = new TestShellRequestContext( g_request_context_params->cache_path, g_request_context_params->cache_mode, g_request_context_params->no_proxy); delete g_request_context_params; g_request_context_params = NULL; } else { g_request_context = new TestShellRequestContext(); } g_network_delegate = new TestShellNetworkDelegate(); g_request_context->set_network_delegate(g_network_delegate); SimpleAppCacheSystem::InitializeOnIOThread(g_request_context); SimpleSocketStreamBridge::InitializeOnIOThread(g_request_context); SimpleFileWriter::InitializeOnIOThread(g_request_context); SimpleFileSystem::InitializeOnIOThread( g_request_context->blob_storage_controller()); TestShellWebBlobRegistryImpl::InitializeOnIOThread( g_request_context->blob_storage_controller()); } virtual void CleanUp() OVERRIDE { // In reverse order of initialization. TestShellWebBlobRegistryImpl::Cleanup(); SimpleFileSystem::CleanupOnIOThread(); SimpleFileWriter::CleanupOnIOThread(); SimpleSocketStreamBridge::Cleanup(); SimpleAppCacheSystem::CleanupOnIOThread(); if (g_request_context) { g_request_context->set_network_delegate(NULL); delete g_request_context; g_request_context = NULL; } if (g_network_delegate) { delete g_network_delegate; g_network_delegate = NULL; } } }; IOThread* g_io_thread = NULL; //----------------------------------------------------------------------------- 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; }; // The interval for calls to RequestProxy::MaybeUpdateUploadProgress static const int kUpdateUploadProgressIntervalMsec = 100; // 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. struct DeleteOnIOThread; // See below. class RequestProxy : public net::URLRequest::Delegate, public base::RefCountedThreadSafe { public: // Takes ownership of the params. RequestProxy() : download_to_file_(false), buf_(new net::IOBuffer(kDataSize)), last_upload_position_(0) { } void DropPeer() { peer_ = NULL; } void Start(ResourceLoaderBridge::Peer* peer, RequestParams* params) { peer_ = peer; owner_loop_ = base::MessageLoop::current(); ConvertRequestParamsForFileOverHTTPIfNeeded(params); // proxy over to the io thread g_io_thread->message_loop()->PostTask( FROM_HERE, base::Bind(&RequestProxy::AsyncStart, this, params)); } void Cancel() { // proxy over to the io thread g_io_thread->message_loop()->PostTask( FROM_HERE, base::Bind(&RequestProxy::AsyncCancel, this)); } protected: friend class base::DeleteHelper; friend class base::RefCountedThreadSafe; friend struct DeleteOnIOThread; virtual ~RequestProxy() { // Ensure we are deleted on the IO thread because base::Timer requires that. // (guaranteed by the Traits class template parameter). DCHECK(base::MessageLoop::current() == g_io_thread->message_loop()); } // -------------------------------------------------------------------------- // 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) { 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)) { g_io_thread->message_loop()->PostTask( 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) { if (peer_) peer_->OnReceivedResponse(info); } void NotifyReceivedData(int bytes_read) { if (!peer_) return; // Make a local copy of buf_, since AsyncReadData reuses it. scoped_ptr 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 reponse to the received data, which // when run on the io thread, could race against this function in doing // another InvokeLater. See bug 769249. g_io_thread->message_loop()->PostTask( FROM_HERE, base::Bind(&RequestProxy::AsyncReadData, this)); peer_->OnReceivedData(buf_copy.get(), bytes_read, -1); } void NotifyDownloadedData(int bytes_read) { if (!peer_) return; // Continue reading more data, see the comment in NotifyReceivedData. g_io_thread->message_loop()->PostTask( 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) { if (peer_) { peer_->OnCompletedRequest(error_code, false, security_info, complete_time); DropPeer(); // ensure no further notifications } } void NotifyUploadProgress(uint64 position, uint64 size) { 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) { request_.reset(g_request_context->CreateRequest(params->url, this)); request_->set_method(params->method); request_->set_first_party_for_cookies(params->first_party_for_cookies); request_->SetReferrer(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 (params->request_body.get()) { request_->set_upload(make_scoped_ptr( params->request_body->ResolveElementsAndCreateUploadDataStream( static_cast(g_request_context) ->blob_storage_controller(), static_cast(g_request_context) ->file_system_context(), base::MessageLoopProxy::current()))); } SimpleAppCacheSystem::SetExtraRequestInfo( request_.get(), params->appcache_host_id, params->request_type); download_to_file_ = params->download_to_file; if (download_to_file_) { base::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_->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() { // This can be null in cases where the request is already done. if (!request_) return; request_->Cancel(); Done(); } void AsyncFollowDeferredRedirect(bool has_new_first_party_for_cookies, const GURL& new_first_party_for_cookies) { // This can be null in cases where the request is already done. if (!request_) return; if (has_new_first_party_for_cookies) request_->set_first_party_for_cookies(new_first_party_for_cookies); request_->FollowDeferredRedirect(); } void AsyncReadData() { // This can be null in cases where the request is already done. if (!request_) return; if (request_->status().is_success()) { int bytes_read; if (request_->Read(buf_.get(), 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) { *defer_redirect = true; // See AsyncFollowDeferredRedirect owner_loop_->PostTask( FROM_HERE, base::Bind(&RequestProxy::NotifyReceivedRedirect, this, new_url, info)); } virtual void OnReceivedResponse( const ResourceResponseInfo& info) { owner_loop_->PostTask( FROM_HERE, base::Bind(&RequestProxy::NotifyReceivedResponse, this, info)); } virtual void OnReceivedData(int bytes_read) { 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) { 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(request->status().is_success()); ResourceResponseInfo info; PopulateResponseInfo(request, &info); // For file protocol, should never have the redirect situation. DCHECK(!ConvertResponseInfoForFileOverHTTPIfNeeded(request, &info)); OnReceivedRedirect(new_url, info, defer_redirect); } virtual void OnResponseStarted(net::URLRequest* request) OVERRIDE { if (request->status().is_success()) { ResourceResponseInfo info; PopulateResponseInfo(request, &info); // If encountering error when requesting the file, cancel the request. if (ConvertResponseInfoForFileOverHTTPIfNeeded(request, &info) && failed_file_request_status_.get()) { AsyncCancel(); } else { OnReceivedResponse(info); AsyncReadData(); // start reading } } else { Done(); } } virtual void OnSSLCertificateError(net::URLRequest* request, const net::SSLInfo& ssl_info, bool fatal) OVERRIDE { // Allow all certificate errors. request->ContinueDespiteLastError(); } virtual void OnReadCompleted(net::URLRequest* request, int bytes_read) OVERRIDE { if (request->status().is_success() && bytes_read > 0) { OnReceivedData(bytes_read); } else { Done(); } } // -------------------------------------------------------------------------- // Helpers and data: void Done() { if (upload_progress_timer_.IsRunning()) { MaybeUpdateUploadProgress(); upload_progress_timer_.Stop(); } DCHECK(request_.get()); // If |failed_file_request_status_| is not empty, which means the request // was a file request and encountered an error, then we need to use the // |failed_file_request_status_|. Otherwise use request_'s status. OnCompletedRequest(failed_file_request_status_.get() ? failed_file_request_status_->error() : request_->status().error(), std::string(), base::TimeTicks::Now()); request_.reset(); // destroy on the io thread } // Called on the IO thread. void MaybeUpdateUploadProgress() { // 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 { if (request->load_flags() & net::LOAD_ENABLE_LOAD_TIMING) request->GetLoadTimingInfo(&info->load_timing); 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_.get()) info->download_file_path = downloaded_file_->path(); SimpleAppCacheSystem::GetExtraResponseInfo( request, &info->appcache_id, &info->appcache_manifest_url); } // Called on owner thread void ConvertRequestParamsForFileOverHTTPIfNeeded(RequestParams* params) { // Reset the status. file_url_prefix_ .clear(); failed_file_request_status_.reset(); // Only do this when enabling file-over-http and request is file scheme. if (!g_file_over_http_mappings || !params->url.SchemeIsFile()) return; // For file protocol, method must be GET, POST or NULL. DCHECK(params->method == "GET" || params->method == "POST" || params->method.empty()); DCHECK(!params->download_to_file); if (params->method.empty()) params->method = "GET"; std::string original_request = params->url.spec(); std::string::size_type offset = 0; const FileOverHTTPParams* redirection_params = g_file_over_http_mappings->ParamsForRequest(original_request, offset); if (!redirection_params) return; offset += redirection_params->file_path_template.size(); file_url_prefix_ = original_request.substr(0, offset); original_request.replace(0, offset, redirection_params->http_prefix.spec()); params->url = GURL(original_request); params->first_party_for_cookies = params->url; // For file protocol, nerver use cache. params->load_flags = net::LOAD_BYPASS_CACHE; } // Called on IO thread. bool ConvertResponseInfoForFileOverHTTPIfNeeded(net::URLRequest* request, ResourceResponseInfo* info) { // Only do this when enabling file-over-http and request url // matches the http prefix for file-over-http feature. if (!g_file_over_http_mappings || file_url_prefix_.empty()) return false; std::string original_request = request->url().spec(); DCHECK(!original_request.empty()); const FileOverHTTPParams* redirection_params = g_file_over_http_mappings->ParamsForResponse(original_request); DCHECK(redirection_params); std::string http_prefix = redirection_params->http_prefix.spec(); DCHECK(StartsWithASCII(original_request, http_prefix, true)); // Get the File URL. original_request.replace(0, http_prefix.size(), file_url_prefix_); base::FilePath file_path; if (!net::FileURLToFilePath(GURL(original_request), &file_path)) { NOTREACHED(); } info->mime_type.clear(); DCHECK(info->headers.get()); int status_code = info->headers->response_code(); // File protocol does not support response headers. info->headers = NULL; if (200 == status_code) { // Don't use the MIME type from HTTP server, use net::GetMimeTypeFromFile // instead. net::GetMimeTypeFromFile(file_path, &info->mime_type); } else { // If the file does not exist, immediately call OnCompletedRequest with // setting URLRequestStatus to FAILED. DCHECK(status_code == 404 || status_code == 403); if (status_code == 404) { failed_file_request_status_.reset( new net::URLRequestStatus(net::URLRequestStatus::FAILED, net::ERR_FILE_NOT_FOUND)); } else { failed_file_request_status_.reset( new net::URLRequestStatus(net::URLRequestStatus::FAILED, net::ERR_ACCESS_DENIED)); } } return true; } scoped_ptr request_; // 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 static const int kDataSize = 16*1024; // read buffer for async IO scoped_refptr buf_; base::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_; // Save the real FILE URL prefix for the FILE URL which converts to HTTP URL. std::string file_url_prefix_; // Save a failed file request status to pass it to webkit. scoped_ptr failed_file_request_status_; }; // Helper guaranteeing deletion on the IO thread (like // content::BrowserThread::DeleteOnIOThread, but without the dependency). struct DeleteOnIOThread { static void Destruct(const RequestProxy* obj) { if (base::MessageLoop::current() == g_io_thread->message_loop()) delete obj; else g_io_thread->message_loop()->DeleteSoon(FROM_HERE, obj); } }; //----------------------------------------------------------------------------- class SyncRequestProxy : public RequestProxy { public: explicit SyncRequestProxy(ResourceLoaderBridge::SyncLoadResponse* result) : result_(result), event_(true, false) { } void WaitForCompletion() { event_.Wait(); } // -------------------------------------------------------------------------- // RequestProxy event hooks that run on the IO thread: virtual void OnReceivedRedirect( const GURL& new_url, const ResourceResponseInfo& info, bool* defer_redirect) OVERRIDE { // 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) OVERRIDE { *static_cast(result_) = info; } virtual void OnReceivedData(int bytes_read) OVERRIDE { 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) OVERRIDE { if (download_to_file_) file_stream_.reset(); result_->error_code = error_code; event_.Signal(); } protected: virtual ~SyncRequestProxy() {} private: ResourceLoaderBridge::SyncLoadResponse* result_; base::WaitableEvent event_; }; //----------------------------------------------------------------------------- class ResourceLoaderBridgeImpl : public ResourceLoaderBridge { public: ResourceLoaderBridgeImpl( const webkit_glue::ResourceLoaderBridge::RequestInfo& request_info) : 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 g_io_thread->message_loop()->ReleaseSoon(FROM_HERE, proxy_); } } // -------------------------------------------------------------------------- // ResourceLoaderBridge implementation: virtual void SetRequestBody(ResourceRequestBody* request_body) OVERRIDE { DCHECK(params_.get()); DCHECK(!params_->request_body.get()); params_->request_body = request_body; } virtual bool Start(Peer* peer) OVERRIDE { DCHECK(!proxy_); if (!SimpleResourceLoaderBridge::EnsureIOThread()) return false; proxy_ = new RequestProxy(); proxy_->AddRef(); proxy_->Start(peer, params_.release()); return true; // Any errors will be reported asynchronously. } virtual void Cancel() OVERRIDE { DCHECK(proxy_); proxy_->Cancel(); } virtual void SetDefersLoading(bool value) OVERRIDE { // TODO(darin): implement me } virtual void SyncLoad(SyncLoadResponse* response) OVERRIDE { DCHECK(!proxy_); if (!SimpleResourceLoaderBridge::EnsureIOThread()) return; // this may change as the result of a redirect response->url = params_->url; proxy_ = new SyncRequestProxy(response); proxy_->AddRef(); proxy_->Start(NULL, params_.release()); static_cast(proxy_)->WaitForCompletion(); } virtual void DidChangePriority(net::RequestPriority new_priority) OVERRIDE { // Not really needed for DRT. } private: // 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. RequestProxy* proxy_; }; //----------------------------------------------------------------------------- class CookieSetter : public base::RefCountedThreadSafe { public: void Set(const GURL& url, const std::string& cookie) { DCHECK(base::MessageLoop::current() == g_io_thread->message_loop()); g_request_context->cookie_store()->SetCookieWithOptionsAsync( url, cookie, net::CookieOptions(), net::CookieStore::SetCookiesCallback()); } private: friend class base::RefCountedThreadSafe; ~CookieSetter() {} }; class CookieGetter : public base::RefCountedThreadSafe { public: CookieGetter() : event_(false, false) { } void Get(const GURL& url) { g_request_context->cookie_store()->GetCookiesWithOptionsAsync( url, net::CookieOptions(), base::Bind(&CookieGetter::OnGetCookies, this)); } std::string GetResult() { event_.Wait(); return result_; } private: friend class base::RefCountedThreadSafe; ~CookieGetter() {} void OnGetCookies(const std::string& cookie_line) { result_ = cookie_line; event_.Signal(); } base::WaitableEvent event_; std::string result_; }; } // anonymous namespace //----------------------------------------------------------------------------- // static void SimpleResourceLoaderBridge::Init( const base::FilePath& cache_path, net::HttpCache::Mode cache_mode, bool no_proxy) { // Make sure to stop any existing IO thread since it may be using the // current request context. Shutdown(); DCHECK(!g_request_context_params); DCHECK(!g_request_context); DCHECK(!g_network_delegate); DCHECK(!g_io_thread); g_request_context_params = new TestShellRequestContextParams( cache_path, cache_mode, no_proxy); } // static void SimpleResourceLoaderBridge::Shutdown() { if (g_io_thread) { delete g_io_thread; g_io_thread = NULL; DCHECK(g_cache_thread); delete g_cache_thread; g_cache_thread = NULL; DCHECK(!g_request_context) << "should have been nulled by thread dtor"; DCHECK(!g_network_delegate) << "should have been nulled by thread dtor"; } else { delete g_request_context_params; g_request_context_params = NULL; delete g_file_over_http_mappings; g_file_over_http_mappings = NULL; } } // static void SimpleResourceLoaderBridge::SetCookie(const GURL& url, const GURL& first_party_for_cookies, const std::string& cookie) { // Proxy to IO thread to synchronize w/ network loading. if (!EnsureIOThread()) { NOTREACHED(); return; } scoped_refptr cookie_setter(new CookieSetter()); g_io_thread->message_loop()->PostTask( FROM_HERE, base::Bind(&CookieSetter::Set, cookie_setter.get(), url, cookie)); } // static std::string SimpleResourceLoaderBridge::GetCookies( const GURL& url, const GURL& first_party_for_cookies) { // Proxy to IO thread to synchronize w/ network loading if (!EnsureIOThread()) { NOTREACHED(); return std::string(); } scoped_refptr getter(new CookieGetter()); g_io_thread->message_loop()->PostTask( FROM_HERE, base::Bind(&CookieGetter::Get, getter.get(), url)); return getter->GetResult(); } // static bool SimpleResourceLoaderBridge::EnsureIOThread() { if (g_io_thread) return true; #if defined(OS_MACOSX) || defined(OS_WIN) // We want to be sure to init NSPR on the main thread. crypto::EnsureNSPRInit(); #endif // Create the cache thread. We want the cache thread to outlive the IO thread, // so its lifetime is bonded to the IO thread lifetime. DCHECK(!g_cache_thread); g_cache_thread = new base::Thread("cache"); CHECK(g_cache_thread->StartWithOptions( base::Thread::Options(base::MessageLoop::TYPE_IO, 0))); g_io_thread = new IOThread(); base::Thread::Options options; options.message_loop_type = base::MessageLoop::TYPE_IO; return g_io_thread->StartWithOptions(options); } // static void SimpleResourceLoaderBridge::SetAcceptAllCookies(bool accept_all_cookies) { g_accept_all_cookies = accept_all_cookies; } // static scoped_refptr SimpleResourceLoaderBridge::GetCacheThread() { return g_cache_thread->message_loop_proxy(); } // static scoped_refptr SimpleResourceLoaderBridge::GetIoThread() { if (!EnsureIOThread()) { LOG(DFATAL) << "Failed to create IO thread."; return NULL; } return g_io_thread->message_loop_proxy(); } // static void SimpleResourceLoaderBridge::AllowFileOverHTTP( const std::string& file_path_template, const GURL& http_prefix) { DCHECK(!file_path_template.empty()); DCHECK(http_prefix.is_valid() && (http_prefix.SchemeIs("http") || http_prefix.SchemeIs("https"))); if (!g_file_over_http_mappings) g_file_over_http_mappings = new FileOverHTTPPathMappings(); g_file_over_http_mappings->AddMapping(file_path_template, http_prefix); } // static webkit_glue::ResourceLoaderBridge* SimpleResourceLoaderBridge::Create( const webkit_glue::ResourceLoaderBridge::RequestInfo& request_info) { return new ResourceLoaderBridgeImpl(request_info); }