// Copyright (c) 2012 The Chromium Embedded Framework 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 "libcef/browser/cookie_manager_impl.h" #include #include "libcef/browser/browser_context.h" #include "libcef/browser/context.h" #include "libcef/browser/thread_util.h" #include "libcef/browser/url_request_context_getter.h" #include "libcef/common/time_util.h" #include "base/bind.h" #include "base/file_util.h" #include "base/format_macros.h" #include "base/logging.h" #include "base/threading/thread_restrictions.h" #include "chrome/browser/net/sqlite_persistent_cookie_store.h" #include "googleurl/src/gurl.h" #include "net/cookies/cookie_util.h" #include "net/url_request/url_request_context.h" namespace { // Callback class for visiting cookies. class VisitCookiesCallback : public base::RefCounted { public: explicit VisitCookiesCallback(net::CookieMonster* cookie_monster, CefRefPtr visitor) : cookie_monster_(cookie_monster), visitor_(visitor) { } void Run(const net::CookieList& list) { CEF_REQUIRE_IOT(); int total = list.size(), count = 0; net::CookieList::const_iterator it = list.begin(); for (; it != list.end(); ++it, ++count) { CefCookie cookie; const net::CookieMonster::CanonicalCookie& cc = *(it); CefCookieManagerImpl::GetCefCookie(cc, cookie); bool deleteCookie = false; bool keepLooping = visitor_->Visit(cookie, count, total, deleteCookie); if (deleteCookie) { cookie_monster_->DeleteCanonicalCookieAsync(cc, net::CookieMonster::DeleteCookieCallback()); } if (!keepLooping) break; } } private: scoped_refptr cookie_monster_; CefRefPtr visitor_; }; // Methods extracted from net/cookies/cookie_monster.cc // Determine the cookie domain to use for setting the specified cookie. bool GetCookieDomain(const GURL& url, const net::CookieMonster::ParsedCookie& pc, std::string* result) { std::string domain_string; if (pc.HasDomain()) domain_string = pc.Domain(); return net::cookie_util::GetCookieDomainWithString(url, domain_string, result); } std::string CanonPathWithString(const GURL& url, const std::string& path_string) { // The RFC says the path should be a prefix of the current URL path. // However, Mozilla allows you to set any path for compatibility with // broken websites. We unfortunately will mimic this behavior. We try // to be generous and accept cookies with an invalid path attribute, and // default the path to something reasonable. // The path was supplied in the cookie, we'll take it. if (!path_string.empty() && path_string[0] == '/') return path_string; // The path was not supplied in the cookie or invalid, we will default // to the current URL path. // """Defaults to the path of the request URL that generated the // Set-Cookie response, up to, but not including, the // right-most /.""" // How would this work for a cookie on /? We will include it then. const std::string& url_path = url.path(); size_t idx = url_path.find_last_of('/'); // The cookie path was invalid or a single '/'. if (idx == 0 || idx == std::string::npos) return std::string("/"); // Return up to the rightmost '/'. return url_path.substr(0, idx); } std::string CanonPath(const GURL& url, const net::CookieMonster::ParsedCookie& pc) { std::string path_string; if (pc.HasPath()) path_string = pc.Path(); return CanonPathWithString(url, path_string); } base::Time CanonExpiration(const net::CookieMonster::ParsedCookie& pc, const base::Time& current) { // First, try the Max-Age attribute. uint64 max_age = 0; if (pc.HasMaxAge() && #ifdef COMPILER_MSVC sscanf_s( #else sscanf( #endif pc.MaxAge().c_str(), " %" PRIu64, &max_age) == 1) { return current + base::TimeDelta::FromSeconds(max_age); } // Try the Expires attribute. if (pc.HasExpires()) return net::CookieMonster::ParseCookieTime(pc.Expires()); // Invalid or no expiration, persistent cookie. return base::Time(); } } // namespace CefCookieManagerImpl::CefCookieManagerImpl(bool is_global) : is_global_(is_global) { } CefCookieManagerImpl::~CefCookieManagerImpl() { } void CefCookieManagerImpl::Initialize(const CefString& path) { if (is_global_) SetGlobal(); else SetStoragePath(path); } void CefCookieManagerImpl::SetSupportedSchemes( const std::vector& schemes) { if (CEF_CURRENTLY_ON_IOT()) { if (!cookie_monster_) return; if (is_global_) { // Global changes are handled by the request context. CefURLRequestContextGetter* getter = static_cast( _Context->browser_context()->GetRequestContext()); std::vector scheme_vec; std::vector::const_iterator it = schemes.begin(); for (; it != schemes.end(); ++it) scheme_vec.push_back(it->ToString()); getter->SetCookieSupportedSchemes(scheme_vec); return; } supported_schemes_ = schemes; if (supported_schemes_.empty()) { supported_schemes_.push_back("http"); supported_schemes_.push_back("https"); } std::set scheme_set; std::vector::const_iterator it = supported_schemes_.begin(); for (; it != supported_schemes_.end(); ++it) scheme_set.insert(*it); const char** arr = new const char*[scheme_set.size()]; std::set::const_iterator it2 = scheme_set.begin(); for (int i = 0; it2 != scheme_set.end(); ++it2, ++i) arr[i] = it2->c_str(); cookie_monster_->SetCookieableSchemes(arr, scheme_set.size()); delete [] arr; } else { // Execute on the IO thread. CEF_POST_TASK(CEF_IOT, base::Bind(&CefCookieManagerImpl::SetSupportedSchemes, this, schemes)); } } bool CefCookieManagerImpl::VisitAllCookies( CefRefPtr visitor) { if (CEF_CURRENTLY_ON_IOT()) { if (!cookie_monster_) return false; scoped_refptr callback( new VisitCookiesCallback(cookie_monster_, visitor)); cookie_monster_->GetAllCookiesAsync( base::Bind(&VisitCookiesCallback::Run, callback.get())); } else { // Execute on the IO thread. CEF_POST_TASK(CEF_IOT, base::Bind(base::IgnoreResult(&CefCookieManagerImpl::VisitAllCookies), this, visitor)); } return true; } bool CefCookieManagerImpl::VisitUrlCookies( const CefString& url, bool includeHttpOnly, CefRefPtr visitor) { if (CEF_CURRENTLY_ON_IOT()) { if (!cookie_monster_) return false; net::CookieOptions options; if (includeHttpOnly) options.set_include_httponly(); scoped_refptr callback( new VisitCookiesCallback(cookie_monster_, visitor)); GURL gurl = GURL(url.ToString()); cookie_monster_->GetAllCookiesForURLWithOptionsAsync(gurl, options, base::Bind(&VisitCookiesCallback::Run, callback.get())); } else { // Execute on the IO thread. CEF_POST_TASK(CEF_IOT, base::Bind(base::IgnoreResult(&CefCookieManagerImpl::VisitUrlCookies), this, url, includeHttpOnly, visitor)); } return true; } bool CefCookieManagerImpl::SetCookie(const CefString& url, const CefCookie& cookie) { CEF_REQUIRE_IOT_RETURN(false); if (!cookie_monster_) return false; GURL gurl = GURL(url.ToString()); if (!gurl.is_valid()) return false; std::string name = CefString(&cookie.name).ToString(); std::string value = CefString(&cookie.value).ToString(); std::string domain = CefString(&cookie.domain).ToString(); std::string path = CefString(&cookie.path).ToString(); base::Time expiration_time; if (cookie.has_expires) cef_time_to_basetime(cookie.expires, expiration_time); cookie_monster_->SetCookieWithDetailsAsync(gurl, name, value, domain, path, expiration_time, cookie.secure, cookie.httponly, net::CookieStore::SetCookiesCallback()); return true; } bool CefCookieManagerImpl::DeleteCookies(const CefString& url, const CefString& cookie_name) { CEF_REQUIRE_IOT_RETURN(false); if (!cookie_monster_) return false; if (url.empty()) { // Delete all cookies. cookie_monster_->DeleteAllAsync(net::CookieMonster::DeleteCallback()); return true; } GURL gurl = GURL(url.ToString()); if (!gurl.is_valid()) return false; if (cookie_name.empty()) { // Delete all matching host cookies. cookie_monster_->DeleteAllForHostAsync(gurl, net::CookieMonster::DeleteCallback()); } else { // Delete all matching host and domain cookies. cookie_monster_->DeleteCookieAsync(gurl, cookie_name, base::Closure()); } return true; } bool CefCookieManagerImpl::SetStoragePath(const CefString& path) { if (CEF_CURRENTLY_ON_IOT()) { FilePath new_path; if (!path.empty()) new_path = FilePath(path); if (is_global_) { // Global path changes are handled by the request context. CefURLRequestContextGetter* getter = static_cast( _Context->browser_context()->GetRequestContext()); getter->SetCookieStoragePath(new_path); cookie_monster_ = getter->GetURLRequestContext()->cookie_store()-> GetCookieMonster(); return true; } if (cookie_monster_ && ((storage_path_.empty() && path.empty()) || storage_path_ == new_path)) { // The path has not changed so don't do anything. return true; } scoped_refptr persistent_store; if (!new_path.empty()) { // TODO(cef): Move directory creation to the blocking pool instead of // allowing file IO on this thread. base::ThreadRestrictions::ScopedAllowIO allow_io; if (file_util::DirectoryExists(new_path) || file_util::CreateDirectory(new_path)) { const FilePath& cookie_path = new_path.AppendASCII("Cookies"); persistent_store = new SQLitePersistentCookieStore(cookie_path, false, NULL); } else { NOTREACHED() << "The cookie storage directory could not be created"; storage_path_.clear(); } } // Set the new cookie store that will be used for all new requests. The old // cookie store, if any, will be automatically flushed and closed when no // longer referenced. cookie_monster_ = new net::CookieMonster(persistent_store.get(), NULL); storage_path_ = new_path; // Restore the previously supported schemes. SetSupportedSchemes(supported_schemes_); } else { // Execute on the IO thread. CEF_POST_TASK(CEF_IOT, base::Bind(base::IgnoreResult(&CefCookieManagerImpl::SetStoragePath), this, path)); } return true; } void CefCookieManagerImpl::SetGlobal() { if (CEF_CURRENTLY_ON_IOT()) { if (_Context->browser_context()) { cookie_monster_ = _Context->browser_context()->GetRequestContext()-> GetURLRequestContext()->cookie_store()->GetCookieMonster(); DCHECK(cookie_monster_); } } else { // Execute on the IO thread. CEF_POST_TASK(CEF_IOT, base::Bind(&CefCookieManagerImpl::SetGlobal, this)); } } // static bool CefCookieManagerImpl::GetCefCookie( const net::CookieMonster::CanonicalCookie& cc, CefCookie& cookie) { CefString(&cookie.name).FromString(cc.Name()); CefString(&cookie.value).FromString(cc.Value()); CefString(&cookie.domain).FromString(cc.Domain()); CefString(&cookie.path).FromString(cc.Path()); cookie.secure = cc.IsSecure(); cookie.httponly = cc.IsHttpOnly(); cef_time_from_basetime(cc.CreationDate(), cookie.creation); cef_time_from_basetime(cc.LastAccessDate(), cookie.last_access); cookie.has_expires = cc.DoesExpire(); if (cookie.has_expires) cef_time_from_basetime(cc.ExpiryDate(), cookie.expires); return true; } // static bool CefCookieManagerImpl::GetCefCookie(const GURL& url, const std::string& cookie_line, CefCookie& cookie) { // Parse the cookie. net::CookieMonster::ParsedCookie pc(cookie_line); if (!pc.IsValid()) return false; std::string cookie_domain; if (!GetCookieDomain(url, pc, &cookie_domain)) return false; std::string cookie_path = CanonPath(url, pc); base::Time creation_time = base::Time::Now(); base::Time cookie_expires = CanonExpiration(pc, creation_time); CefString(&cookie.name).FromString(pc.Name()); CefString(&cookie.value).FromString(pc.Value()); CefString(&cookie.domain).FromString(cookie_domain); CefString(&cookie.path).FromString(cookie_path); cookie.secure = pc.IsSecure(); cookie.httponly = pc.IsHttpOnly(); cef_time_from_basetime(creation_time, cookie.creation); cef_time_from_basetime(creation_time, cookie.last_access); cookie.has_expires = !cookie_expires.is_null(); if (cookie.has_expires) cef_time_from_basetime(cookie_expires, cookie.expires); return true; } // CefCookieManager methods ---------------------------------------------------- // static CefRefPtr CefCookieManager::GetGlobalManager() { // Verify that the context is in a valid state. if (!CONTEXT_STATE_VALID()) { NOTREACHED() << "context not valid"; return NULL; } CefRefPtr manager(new CefCookieManagerImpl(true)); manager->Initialize(CefString()); return manager.get(); } // static CefRefPtr CefCookieManager::CreateManager( const CefString& path) { // Verify that the context is in a valid state. if (!CONTEXT_STATE_VALID()) { NOTREACHED() << "context not valid"; return NULL; } CefRefPtr manager(new CefCookieManagerImpl(false)); manager->Initialize(path); return manager.get(); }