// Copyright (c) 2011 The Chromium Embedded Framework Authors. // Portions copyright (c) 2006-2009 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 #include "include/cef_scheme.h" #include "libcef/browser_devtools_scheme_handler.h" #include "libcef/browser_impl.h" #include "libcef/browser_resource_loader_bridge.h" #include "libcef/cef_context.h" #include "libcef/cef_thread.h" #include "libcef/request_impl.h" #include "libcef/response_impl.h" #include "base/bind.h" #include "base/lazy_instance.h" #include "base/logging.h" #include "base/message_loop.h" #include "base/string_util.h" #include "base/synchronization/lock.h" #include "googleurl/src/url_util.h" #include "net/base/completion_callback.h" #include "net/base/load_flags.h" #include "net/cookies/cookie_monster.h" #include "net/base/io_buffer.h" #include "net/base/upload_data.h" #include "net/http/http_response_headers.h" #include "net/http/http_util.h" #include "net/url_request/http_user_agent_settings.h" #include "net/url_request/url_request.h" #include "net/url_request/url_request_about_job.h" #include "net/url_request/url_request_data_job.h" #include "net/url_request/url_request_error_job.h" #include "net/url_request/url_request_file_job.h" #include "net/url_request/url_request_filter.h" #include "net/url_request/url_request_ftp_job.h" #include "net/url_request/url_request_http_job.h" #include "net/url_request/url_request_job.h" #include "net/url_request/url_request_job_factory.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebSecurityPolicy.h" #include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebString.h" using net::URLRequestStatus; using WebKit::WebSecurityPolicy; using WebKit::WebString; namespace { bool IsStandardScheme(const std::string& scheme) { url_parse::Component scheme_comp(0, scheme.length()); return url_util::IsStandard(scheme.c_str(), scheme_comp); } void RegisterStandardScheme(const std::string& scheme) { REQUIRE_UIT(); url_parse::Component scheme_comp(0, scheme.length()); if (!url_util::IsStandard(scheme.c_str(), scheme_comp)) url_util::AddStandardScheme(scheme.c_str()); } // Copied from net/url_request/url_request_job_manager.cc. struct SchemeToFactory { const char* scheme; net::URLRequest::ProtocolFactory* factory; }; static const SchemeToFactory kBuiltinFactories[] = { { "http", net::URLRequestHttpJob::Factory }, { "https", net::URLRequestHttpJob::Factory }, { "file", net::URLRequestFileJob::Factory }, { "ftp", net::URLRequestFtpJob::Factory }, { "about", net::URLRequestAboutJob::Factory }, { "data", net::URLRequestDataJob::Factory }, }; bool IsBuiltinScheme(const std::string& scheme) { for (size_t i = 0; i < arraysize(kBuiltinFactories); ++i) if (LowerCaseEqualsASCII(scheme, kBuiltinFactories[i].scheme)) return true; return false; } net::URLRequestJob* GetBuiltinSchemeRequestJob( net::URLRequest* request, net::NetworkDelegate* network_delegate, const std::string& scheme) { // See if the request should be handled by a built-in protocol factory. for (size_t i = 0; i < arraysize(kBuiltinFactories); ++i) { if (scheme == kBuiltinFactories[i].scheme) { net::URLRequestJob* job = (kBuiltinFactories[i].factory)(request, network_delegate, scheme); DCHECK(job); // The built-in factories are not expected to fail! return job; } } return NULL; } std::string ToLower(const std::string& str) { std::string str_lower = str; std::transform(str_lower.begin(), str_lower.end(), str_lower.begin(), towlower); return str; } bool SetHeaderIfMissing(CefRequest::HeaderMap& headerMap, const std::string& name, const std::string& value) { if (value.empty()) return false; CefRequest::HeaderMap::const_iterator it = headerMap.find(name); if (it == headerMap.end()) { headerMap.insert(std::make_pair(name, value)); return true; } return false; } // net::URLRequestJob implementation. class CefUrlRequestJob : public net::URLRequestJob { public: CefUrlRequestJob(net::URLRequest* request, net::NetworkDelegate* network_delegate, CefRefPtr handler) : net::URLRequestJob(request, network_delegate), handler_(handler), remaining_bytes_(0), response_cookies_save_index_(0), ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) { } virtual ~CefUrlRequestJob() { } virtual void Start() OVERRIDE { REQUIRE_IOT(); cef_request_ = CefRequest::CreateRequest(); // Populate the request data. static_cast(cef_request_.get())->Set(request_); // Add default headers if not already specified. const net::URLRequestContext* context = request_->context(); if (context) { CefRequest::HeaderMap headerMap; cef_request_->GetHeaderMap(headerMap); bool changed = false; const net::HttpUserAgentSettings* ua_settings = context->http_user_agent_settings(); if (ua_settings) { if (SetHeaderIfMissing(headerMap, net::HttpRequestHeaders::kAcceptLanguage, ua_settings->GetAcceptLanguage())) { changed = true; } if (SetHeaderIfMissing(headerMap, net::HttpRequestHeaders::kAcceptCharset, ua_settings->GetAcceptCharset())) { changed = true; } if (SetHeaderIfMissing(headerMap, net::HttpRequestHeaders::kUserAgent, ua_settings->GetUserAgent(request_->url()))) { changed = true; } } if (changed) cef_request_->SetHeaderMap(headerMap); } AddCookieHeaderAndStart(); } void AddCookieHeaderAndStart() { // No matter what, we want to report our status as IO pending since we will // be notifying our consumer asynchronously via OnStartCompleted. SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0)); // If the request was destroyed, then there is no more work to do. if (!request_) return; net::CookieStore* cookie_store = request_->context()->cookie_store(); if (cookie_store && !(request_->load_flags() & net::LOAD_DO_NOT_SEND_COOKIES)) { net::CookieMonster* cookie_monster = cookie_store->GetCookieMonster(); if (cookie_monster) { cookie_monster->GetAllCookiesForURLAsync( request_->url(), base::Bind(&CefUrlRequestJob::CheckCookiePolicyAndLoad, weak_factory_.GetWeakPtr())); } else { DoLoadCookies(); } } else { DoStartTransaction(); } } void DoLoadCookies() { net::CookieOptions options; options.set_include_httponly(); request_->context()->cookie_store()->GetCookiesWithInfoAsync( request_->url(), options, base::Bind(&CefUrlRequestJob::OnCookiesLoaded, weak_factory_.GetWeakPtr())); } void CheckCookiePolicyAndLoad( const net::CookieList& cookie_list) { if (CanGetCookies(cookie_list)) DoLoadCookies(); else DoStartTransaction(); } void OnCookiesLoaded( const std::string& cookie_line, const std::vector& cookie_infos) { if (!cookie_line.empty()) { CefRequest::HeaderMap headerMap; cef_request_->GetHeaderMap(headerMap); headerMap.insert( std::make_pair(net::HttpRequestHeaders::kCookie, cookie_line)); cef_request_->SetHeaderMap(headerMap); } DoStartTransaction(); } void DoStartTransaction() { // We may have been canceled while retrieving cookies. if (GetStatus().is_success()) { StartTransaction(); } else { NotifyCanceled(); } } void StartTransaction() { if (!callback_) callback_ = new Callback(this); // Protect against deletion of this object. base::WeakPtr weak_ptr(weak_factory_.GetWeakPtr()); // Handler can decide whether to process the request. bool rv = handler_->ProcessRequest(cef_request_, callback_.get()); if (weak_ptr.get() && !rv) { // Cancel the request. NotifyCanceled(); } } virtual void Kill() OVERRIDE { REQUIRE_IOT(); // Notify the handler that the request has been canceled. handler_->Cancel(); if (callback_) { callback_->Detach(); callback_ = NULL; } net::URLRequestJob::Kill(); } virtual bool ReadRawData(net::IOBuffer* dest, int dest_size, int *bytes_read) OVERRIDE { REQUIRE_IOT(); DCHECK_NE(dest_size, 0); DCHECK(bytes_read); if (remaining_bytes_ == 0) { // No more data to read. *bytes_read = 0; return true; } else if (remaining_bytes_ > 0 && remaining_bytes_ < dest_size) { // The handler knows the content size beforehand. dest_size = static_cast(remaining_bytes_); } // Read response data from the handler. bool rv = handler_->ReadResponse(dest->data(), dest_size, *bytes_read, callback_.get()); if (!rv) { // The handler has indicated completion of the request. *bytes_read = 0; return true; } else if (*bytes_read == 0) { if (!GetStatus().is_io_pending()) { // Report our status as IO pending. SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0)); callback_->SetDestination(dest, dest_size); } return false; } else if (*bytes_read > dest_size) { // Normalize the return value. *bytes_read = dest_size; } if (remaining_bytes_ > 0) remaining_bytes_ -= *bytes_read; // Continue calling this method. return true; } virtual void GetResponseInfo(net::HttpResponseInfo* info) OVERRIDE { REQUIRE_IOT(); info->headers = GetResponseHeaders(); } virtual bool IsRedirectResponse(GURL* location, int* http_status_code) OVERRIDE { REQUIRE_IOT(); if (redirect_url_.is_valid()) { // Redirect to the new URL. *http_status_code = 303; location->Swap(&redirect_url_); return true; } if (response_.get()) { // Check for HTTP 302 or HTTP 303 redirect. int status = response_->GetStatus(); if (status == 302 || status == 303) { CefResponse::HeaderMap headerMap; response_->GetHeaderMap(headerMap); CefRequest::HeaderMap::iterator iter = headerMap.find("Location"); if (iter != headerMap.end()) { GURL new_url = GURL(std::string(iter->second)); *http_status_code = status; location->Swap(&new_url); return true; } } } return false; } virtual bool GetMimeType(std::string* mime_type) const OVERRIDE { REQUIRE_IOT(); if (response_.get()) *mime_type = response_->GetMimeType(); return true; } CefRefPtr handler_; CefRefPtr response_; private: void SendHeaders() { REQUIRE_IOT(); // We may have been orphaned... if (!request()) return; response_ = new CefResponseImpl(); remaining_bytes_ = 0; CefString redirectUrl; // Get header information from the handler. handler_->GetResponseHeaders(response_, remaining_bytes_, redirectUrl); if (!redirectUrl.empty()) { std::string redirectUrlStr = redirectUrl; redirect_url_ = GURL(redirectUrlStr); } if (remaining_bytes_ > 0) set_expected_content_size(remaining_bytes_); // Continue processing the request. SaveCookiesAndNotifyHeadersComplete(); } net::HttpResponseHeaders* GetResponseHeaders() { DCHECK(response_); if (!response_headers_.get()) { CefResponseImpl* responseImpl = static_cast(response_.get()); response_headers_ = responseImpl->GetResponseHeaders(); } return response_headers_; } void SaveCookiesAndNotifyHeadersComplete() { if (request_->load_flags() & net::LOAD_DO_NOT_SAVE_COOKIES) { SetStatus(URLRequestStatus()); // Clear the IO_PENDING status NotifyHeadersComplete(); return; } response_cookies_.clear(); response_cookies_save_index_ = 0; FetchResponseCookies(&response_cookies_); // Now, loop over the response cookies, and attempt to persist each. SaveNextCookie(); } void SaveNextCookie() { if (response_cookies_save_index_ == response_cookies_.size()) { response_cookies_.clear(); response_cookies_save_index_ = 0; SetStatus(URLRequestStatus()); // Clear the IO_PENDING status NotifyHeadersComplete(); return; } // No matter what, we want to report our status as IO pending since we will // be notifying our consumer asynchronously via OnStartCompleted. SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0)); net::CookieOptions options; options.set_include_httponly(); if (CanSetCookie( response_cookies_[response_cookies_save_index_], &options)) { request_->context()->cookie_store()->SetCookieWithOptionsAsync( request_->url(), response_cookies_[response_cookies_save_index_], options, base::Bind(&CefUrlRequestJob::OnCookieSaved, weak_factory_.GetWeakPtr())); return; } CookieHandled(); } void OnCookieSaved(bool cookie_status) { CookieHandled(); } void CookieHandled() { response_cookies_save_index_++; // We may have been canceled within OnSetCookie. if (GetStatus().is_success()) { SaveNextCookie(); } else { NotifyCanceled(); } } void FetchResponseCookies( std::vector* cookies) { const std::string name = "Set-Cookie"; std::string value; void* iter = NULL; net::HttpResponseHeaders* headers = GetResponseHeaders(); while (headers->EnumerateHeader(&iter, name, &value)) { if (!value.empty()) cookies->push_back(value); } } // Client callback for asynchronous response continuation. class Callback : public CefSchemeHandlerCallback { public: explicit Callback(CefUrlRequestJob* job) : job_(job), dest_(NULL), dest_size_() {} virtual void HeadersAvailable() OVERRIDE { if (CefThread::CurrentlyOn(CefThread::IO)) { // Currently on IO thread. if (job_ && !job_->has_response_started()) { // Send header information. job_->SendHeaders(); } } else { // Execute this method on the IO thread. CefThread::PostTask(CefThread::IO, FROM_HERE, base::Bind(&Callback::HeadersAvailable, this)); } } virtual void BytesAvailable() OVERRIDE { if (CefThread::CurrentlyOn(CefThread::IO)) { // Currently on IO thread. if (job_ && job_->has_response_started() && job_->GetStatus().is_io_pending()) { // Read the bytes. They should be available but, if not, wait again. int bytes_read = 0; if (job_->ReadRawData(dest_, dest_size_, &bytes_read)) { if (bytes_read > 0) { // Clear the IO_PENDING status. job_->SetStatus(URLRequestStatus()); // Notify about the available bytes. job_->NotifyReadComplete(bytes_read); dest_ = NULL; dest_size_ = 0; } else { // All done. job_->NotifyDone(URLRequestStatus()); } } else if (!job_->GetStatus().is_io_pending()) { // Failed due to an error. NOTREACHED() << "ReadRawData returned false without setting IO as pending"; job_->NotifyDone(URLRequestStatus()); } } } else { // Execute this method on the IO thread. CefThread::PostTask(CefThread::IO, FROM_HERE, base::Bind(&Callback::BytesAvailable, this)); } } virtual void Cancel() OVERRIDE { if (CefThread::CurrentlyOn(CefThread::IO)) { // Currently on IO thread. if (job_) job_->Kill(); } else { // Execute this method on the IO thread. CefThread::PostTask(CefThread::IO, FROM_HERE, base::Bind(&Callback::Cancel, this)); } } void Detach() { REQUIRE_IOT(); job_ = NULL; } void SetDestination(net::IOBuffer* dest, int dest_size) { dest_ = dest; dest_size_ = dest_size; } static bool ImplementsThreadSafeReferenceCounting() { return true; } private: CefUrlRequestJob* job_; net::IOBuffer* dest_; int dest_size_; IMPLEMENT_REFCOUNTING(Callback); }; GURL redirect_url_; int64 remaining_bytes_; CefRefPtr cef_request_; CefRefPtr callback_; scoped_refptr response_headers_; std::vector response_cookies_; size_t response_cookies_save_index_; base::WeakPtrFactory weak_factory_; DISALLOW_COPY_AND_ASSIGN(CefUrlRequestJob); }; // Class that manages the CefSchemeHandlerFactory instances. class CefUrlRequestManager { protected: // Class used for creating URLRequestJob instances. The lifespan of this // object is managed by URLRequestJobFactory. class ProtocolHandler : public net::URLRequestJobFactory::ProtocolHandler { public: explicit ProtocolHandler(const std::string& scheme) : scheme_(scheme) {} // From net::URLRequestJobFactory::ProtocolHandler virtual net::URLRequestJob* MaybeCreateJob( net::URLRequest* request, net::NetworkDelegate* network_delegate) const OVERRIDE { REQUIRE_IOT(); return CefUrlRequestManager::GetInstance()->GetRequestJob( request, network_delegate, scheme_); } private: std::string scheme_; }; public: CefUrlRequestManager() {} // Retrieve the singleton instance. static CefUrlRequestManager* GetInstance(); bool AddFactory(const std::string& scheme, const std::string& domain, CefRefPtr factory) { if (!factory.get()) { RemoveFactory(scheme, domain); return true; } REQUIRE_IOT(); std::string scheme_lower = ToLower(scheme); std::string domain_lower = ToLower(domain); // Hostname is only supported for standard schemes. if (!IsStandardScheme(scheme_lower)) domain_lower.clear(); handler_map_[make_pair(scheme_lower, domain_lower)] = factory; net::URLRequestJobFactory* job_factory = const_cast( _Context->request_context()->job_factory()); job_factory->SetProtocolHandler(scheme_lower, new ProtocolHandler(scheme_lower)); return true; } void RemoveFactory(const std::string& scheme, const std::string& domain) { REQUIRE_IOT(); std::string scheme_lower = ToLower(scheme); std::string domain_lower = ToLower(domain); // Hostname is only supported for standard schemes. if (!IsStandardScheme(scheme_lower)) domain_lower.clear(); HandlerMap::iterator iter = handler_map_.find(make_pair(scheme_lower, domain_lower)); if (iter != handler_map_.end()) handler_map_.erase(iter); } // Clear all the existing URL handlers and unregister the ProtocolFactory. void ClearFactories() { REQUIRE_IOT(); net::URLRequestJobFactory* job_factory = const_cast( _Context->request_context()->job_factory()); // Unregister with the ProtocolFactory. std::set schemes; for (HandlerMap::const_iterator i = handler_map_.begin(); i != handler_map_.end(); ++i) { schemes.insert(i->first.first); } for (std::set::const_iterator scheme = schemes.begin(); scheme != schemes.end(); ++scheme) { job_factory->SetProtocolHandler(*scheme, NULL); } handler_map_.clear(); } // Check if a scheme has already been registered. bool HasRegisteredScheme(const std::string& scheme) { std::string scheme_lower = ToLower(scheme); // Don't register builtin schemes. if (IsBuiltinScheme(scheme_lower)) return true; scheme_set_lock_.Acquire(); bool registered = (scheme_set_.find(scheme_lower) != scheme_set_.end()); scheme_set_lock_.Release(); return registered; } // Register a scheme. bool RegisterScheme(const std::string& scheme, bool is_standard, bool is_local, bool is_display_isolated) { if (HasRegisteredScheme(scheme)) { NOTREACHED() << "Scheme already registered: " << scheme; return false; } std::string scheme_lower = ToLower(scheme); scheme_set_lock_.Acquire(); scheme_set_.insert(scheme_lower); scheme_set_lock_.Release(); if (is_standard) RegisterStandardScheme(scheme_lower); if (is_local) { WebSecurityPolicy::registerURLSchemeAsLocal( WebString::fromUTF8(scheme_lower)); } if (is_display_isolated) { WebSecurityPolicy::registerURLSchemeAsDisplayIsolated( WebString::fromUTF8(scheme_lower)); } return true; } private: // Retrieve the matching handler factory, if any. |scheme| will already be in // lower case. CefRefPtr GetHandlerFactory( net::URLRequest* request, const std::string& scheme) { CefRefPtr factory; if (request->url().is_valid() && IsStandardScheme(scheme)) { // Check for a match with a domain first. const std::string& domain = request->url().host(); HandlerMap::iterator i = handler_map_.find(make_pair(scheme, domain)); if (i != handler_map_.end()) factory = i->second; } if (!factory.get()) { // Check for a match with no specified domain. HandlerMap::iterator i = handler_map_.find(make_pair(scheme, std::string())); if (i != handler_map_.end()) factory = i->second; } return factory; } // Create the job that will handle the request. |scheme| will already be in // lower case. net::URLRequestJob* GetRequestJob(net::URLRequest* request, net::NetworkDelegate* network_delegate, const std::string& scheme) { net::URLRequestJob* job = NULL; CefRefPtr factory = GetHandlerFactory(request, scheme); if (factory) { // Call the handler factory to create the handler for the request. CefRefPtr requestPtr(new CefRequestImpl()); static_cast(requestPtr.get())->Set(request); CefRefPtr browser = BrowserResourceLoaderBridge::GetBrowserForRequest(request); CefRefPtr handler = factory->Create(browser.get(), scheme, requestPtr); if (handler.get()) job = new CefUrlRequestJob(request, network_delegate, handler); } if (!job && IsBuiltinScheme(scheme)) { // Give the built-in scheme handler a chance to handle the request. job = GetBuiltinSchemeRequestJob(request, network_delegate, scheme); } #ifndef NDEBUG if (job) DLOG(INFO) << "CefUrlRequestManager hit for " << request->url().spec(); #endif return job; } // Map (scheme, domain) to factories. This map will only be accessed on the IO // thread. typedef std::map, CefRefPtr > HandlerMap; HandlerMap handler_map_; // Set of registered schemes. This set may be accessed from multiple threads. typedef std::set SchemeSet; SchemeSet scheme_set_; base::Lock scheme_set_lock_; DISALLOW_EVIL_CONSTRUCTORS(CefUrlRequestManager); }; base::LazyInstance g_manager = LAZY_INSTANCE_INITIALIZER; CefUrlRequestManager* CefUrlRequestManager::GetInstance() { return g_manager.Pointer(); } } // namespace bool CefRegisterCustomScheme(const CefString& scheme_name, bool is_standard, bool is_local, bool is_display_isolated) { // Verify that the context is in a valid state. if (!CONTEXT_STATE_VALID()) { NOTREACHED() << "context not valid"; return false; } if (CefThread::CurrentlyOn(CefThread::UI)) { // Must be executed on the UI thread because it calls WebKit APIs. return CefUrlRequestManager::GetInstance()->RegisterScheme(scheme_name, is_standard, is_local, is_display_isolated); } else { // Verify that the scheme has not already been registered. if (CefUrlRequestManager::GetInstance()->HasRegisteredScheme(scheme_name)) { NOTREACHED() << "Scheme already registered: " << scheme_name; return false; } CefThread::PostTask(CefThread::UI, FROM_HERE, base::Bind(base::IgnoreResult(&CefRegisterCustomScheme), scheme_name, is_standard, is_local, is_display_isolated)); return true; } } bool CefRegisterSchemeHandlerFactory( const CefString& scheme_name, const CefString& domain_name, CefRefPtr factory) { // Verify that the context is in a valid state. if (!CONTEXT_STATE_VALID()) { NOTREACHED() << "context not valid"; return false; } if (CefThread::CurrentlyOn(CefThread::IO)) { return CefUrlRequestManager::GetInstance()->AddFactory(scheme_name, domain_name, factory); } else { CefThread::PostTask(CefThread::IO, FROM_HERE, base::Bind(base::IgnoreResult(&CefRegisterSchemeHandlerFactory), scheme_name, domain_name, factory)); return true; } } bool CefClearSchemeHandlerFactories() { // Verify that the context is in a valid state. if (!CONTEXT_STATE_VALID()) { NOTREACHED() << "context not valid"; return false; } if (CefThread::CurrentlyOn(CefThread::IO)) { CefUrlRequestManager::GetInstance()->ClearFactories(); // Re-register the DevTools scheme handler. RegisterDevToolsSchemeHandler(false); } else { CefThread::PostTask(CefThread::IO, FROM_HERE, base::Bind(base::IgnoreResult(&CefClearSchemeHandlerFactories))); } return true; }