#include "android_webview/native/cookie_manager.h"
+#include "android_webview/browser/aw_browser_context.h"
#include "android_webview/browser/aw_cookie_access_policy.h"
+#include "android_webview/browser/net/init_native_callback.h"
#include "android_webview/browser/scoped_allow_wait_for_legacy_web_view_api.h"
#include "android_webview/native/aw_browser_dependency_factory.h"
#include "base/android/jni_string.h"
+#include "base/android/path_utils.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
+#include "base/file_util.h"
+#include "base/files/file_path.h"
#include "base/lazy_instance.h"
#include "base/message_loop/message_loop.h"
#include "base/message_loop/message_loop_proxy.h"
+#include "base/path_service.h"
+#include "base/synchronization/lock.h"
#include "base/synchronization/waitable_event.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "base/threading/thread.h"
#include "base/threading/thread_restrictions.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/cookie_crypto_delegate.h"
+#include "content/public/browser/cookie_store_factory.h"
#include "content/public/common/url_constants.h"
#include "jni/AwCookieManager_jni.h"
#include "net/cookies/cookie_monster.h"
#include "net/cookies/cookie_options.h"
#include "net/url_request/url_request_context.h"
+using base::FilePath;
using base::android::ConvertJavaStringToUTF8;
using base::android::ConvertJavaStringToUTF16;
using content::BrowserThread;
namespace {
+// Are cookies allowed for file:// URLs by default?
+const bool kDefaultFileSchemeAllowed = false;
+
+void ImportLegacyCookieStore(const FilePath& cookie_store_path) {
+ // We use the old cookie store to create the new cookie store only if the
+ // new cookie store does not exist.
+ if (base::PathExists(cookie_store_path))
+ return;
+
+ // WebViewClassic gets the database path from Context and appends a
+ // hardcoded name. See:
+ // https://android.googlesource.com/platform/frameworks/base/+/bf6f6f9d/core/java/android/webkit/JniUtil.java
+ // https://android.googlesource.com/platform/external/webkit/+/7151e/
+ // Source/WebKit/android/WebCoreSupport/WebCookieJar.cpp
+ FilePath old_cookie_store_path;
+ base::android::GetDatabaseDirectory(&old_cookie_store_path);
+ old_cookie_store_path = old_cookie_store_path.Append(
+ FILE_PATH_LITERAL("webviewCookiesChromium.db"));
+ if (base::PathExists(old_cookie_store_path) &&
+ !base::Move(old_cookie_store_path, cookie_store_path)) {
+ LOG(WARNING) << "Failed to move old cookie store path from "
+ << old_cookie_store_path.AsUTF8Unsafe() << " to "
+ << cookie_store_path.AsUTF8Unsafe();
+ }
+}
+
+void GetUserDataDir(FilePath* user_data_dir) {
+ if (!PathService::Get(base::DIR_ANDROID_APP_DATA, user_data_dir)) {
+ NOTREACHED() << "Failed to get app data directory for Android WebView";
+ }
+}
+
class CookieManager {
public:
static CookieManager* GetInstance();
- void SetCookieMonster(net::CookieMonster* cookie_monster);
+ scoped_refptr<net::CookieStore> CreateBrowserThreadCookieStore(
+ AwBrowserContext* browser_context);
void SetAcceptCookie(bool accept);
bool AcceptCookie();
+ void SetAcceptThirdPartyCookie(bool accept);
+ bool AcceptThirdPartyCookie();
void SetCookie(const GURL& host, const std::string& cookie_value);
std::string GetCookie(const GURL& host);
void RemoveSessionCookie();
~CookieManager();
typedef base::Callback<void(base::WaitableEvent*)> CookieTask;
- void ExecCookieTask(const CookieTask& task,
- const bool wait_for_completion);
+ void ExecCookieTask(const CookieTask& task);
void SetCookieAsyncHelper(
const GURL& host,
const std::string& value,
base::WaitableEvent* completion);
- void SetCookieCompleted(bool success);
+ void SetCookieCompleted(base::WaitableEvent* completion, bool success);
void GetCookieValueAsyncHelper(
const GURL& host,
void RemoveSessionCookieAsyncHelper(base::WaitableEvent* completion);
void RemoveAllCookieAsyncHelper(base::WaitableEvent* completion);
- void RemoveCookiesCompleted(int num_deleted);
+ void RemoveCookiesCompleted(base::WaitableEvent* completion, int num_deleted);
void FlushCookieStoreAsyncHelper(base::WaitableEvent* completion);
bool* result,
const CookieList& cookies);
+ void CreateCookieMonster(
+ const FilePath& user_data_dir,
+ const scoped_refptr<base::SequencedTaskRunner>& client_task_runner,
+ const scoped_refptr<base::SequencedTaskRunner>& background_task_runner);
+ void EnsureCookieMonsterExistsLocked();
+ bool AllowFileSchemeCookiesLocked();
+ void SetAcceptFileSchemeCookiesLocked(bool accept);
+
scoped_refptr<net::CookieMonster> cookie_monster_;
+ scoped_refptr<base::MessageLoopProxy> cookie_monster_proxy_;
+ base::Lock cookie_monster_lock_;
+
+ // Both these threads are normally NULL. They only exist if CookieManager was
+ // accessed before Chromium was started.
+ scoped_ptr<base::Thread> cookie_monster_client_thread_;
+ scoped_ptr<base::Thread> cookie_monster_backend_thread_;
DISALLOW_COPY_AND_ASSIGN(CookieManager);
};
CookieManager::~CookieManager() {
}
-// Executes the |task| on the FILE thread. |wait_for_completion| should only be
-// true if the Java API method returns a value or is explicitly stated to be
-// synchronous.
-void CookieManager::ExecCookieTask(const CookieTask& task,
- const bool wait_for_completion) {
+void CookieManager::CreateCookieMonster(
+ const FilePath& user_data_dir,
+ const scoped_refptr<base::SequencedTaskRunner>& client_task_runner,
+ const scoped_refptr<base::SequencedTaskRunner>& background_task_runner) {
+ FilePath cookie_store_path =
+ user_data_dir.Append(FILE_PATH_LITERAL("Cookies"));
+
+ background_task_runner->PostTask(
+ FROM_HERE,
+ base::Bind(ImportLegacyCookieStore, cookie_store_path));
+
+ content::CookieStoreConfig cookie_config(
+ cookie_store_path,
+ content::CookieStoreConfig::RESTORED_SESSION_COOKIES,
+ NULL, NULL);
+ cookie_config.client_task_runner = client_task_runner;
+ cookie_config.background_task_runner = background_task_runner;
+ net::CookieStore* cookie_store = content::CreateCookieStore(cookie_config);
+ cookie_monster_ = cookie_store->GetCookieMonster();
+ SetAcceptFileSchemeCookiesLocked(kDefaultFileSchemeAllowed);
+}
+
+void CookieManager::EnsureCookieMonsterExistsLocked() {
+ cookie_monster_lock_.AssertAcquired();
+ if (cookie_monster_.get()) {
+ return;
+ }
+
+ // Create cookie monster using WebView-specific threads, as the rest of the
+ // browser has not been started yet.
+ FilePath user_data_dir;
+ GetUserDataDir(&user_data_dir);
+ cookie_monster_client_thread_.reset(
+ new base::Thread("CookieMonsterClient"));
+ cookie_monster_client_thread_->Start();
+ cookie_monster_proxy_ = cookie_monster_client_thread_->message_loop_proxy();
+ cookie_monster_backend_thread_.reset(
+ new base::Thread("CookieMonsterBackend"));
+ cookie_monster_backend_thread_->Start();
+
+ CreateCookieMonster(user_data_dir,
+ cookie_monster_proxy_,
+ cookie_monster_backend_thread_->message_loop_proxy());
+}
+
+// Executes the |task| on the |cookie_monster_proxy_| message loop.
+void CookieManager::ExecCookieTask(const CookieTask& task) {
base::WaitableEvent completion(false, false);
+ base::AutoLock lock(cookie_monster_lock_);
- DCHECK(cookie_monster_.get());
+ EnsureCookieMonsterExistsLocked();
- BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
- base::Bind(task, wait_for_completion ? &completion : NULL));
+ cookie_monster_proxy_->PostTask(FROM_HERE, base::Bind(task, &completion));
- if (wait_for_completion) {
- ScopedAllowWaitForLegacyWebViewApi wait;
- completion.Wait();
- }
+ // We always wait for the posted task to complete, even when it doesn't return
+ // a value, because previous versions of the CookieManager API were
+ // synchronous in most/all cases and the caller may be relying on this.
+ ScopedAllowWaitForLegacyWebViewApi wait;
+ completion.Wait();
}
-void CookieManager::SetCookieMonster(net::CookieMonster* cookie_monster) {
+scoped_refptr<net::CookieStore> CookieManager::CreateBrowserThreadCookieStore(
+ AwBrowserContext* browser_context) {
+ base::AutoLock lock(cookie_monster_lock_);
+
+ if (cookie_monster_client_thread_) {
+ // We created a cookie monster already on its own threads; we'll just keep
+ // using it rather than creating one on the normal Chromium threads.
+ // CookieMonster is threadsafe, so this is fine.
+ return cookie_monster_;
+ }
+
+ // Go ahead and create the cookie monster using the normal Chromium threads.
DCHECK(!cookie_monster_.get());
- cookie_monster_ = cookie_monster;
+ DCHECK(BrowserThread::IsMessageLoopValid(BrowserThread::IO));
+
+ FilePath user_data_dir;
+ GetUserDataDir(&user_data_dir);
+ DCHECK(browser_context->GetPath() == user_data_dir);
+
+ cookie_monster_proxy_ =
+ BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO);
+ scoped_refptr<base::SequencedTaskRunner> background_task_runner =
+ BrowserThread::GetBlockingPool()->GetSequencedTaskRunner(
+ BrowserThread::GetBlockingPool()->GetSequenceToken());
+ CreateCookieMonster(user_data_dir,
+ cookie_monster_proxy_,
+ background_task_runner);
+ return cookie_monster_;
}
void CookieManager::SetAcceptCookie(bool accept) {
return AwCookieAccessPolicy::GetInstance()->GetGlobalAllowAccess();
}
+void CookieManager::SetAcceptThirdPartyCookie(bool accept) {
+ AwCookieAccessPolicy::GetInstance()->SetThirdPartyAllowAccess(accept);
+}
+
+bool CookieManager::AcceptThirdPartyCookie() {
+ return AwCookieAccessPolicy::GetInstance()->GetThirdPartyAllowAccess();
+}
+
void CookieManager::SetCookie(const GURL& host,
const std::string& cookie_value) {
ExecCookieTask(base::Bind(&CookieManager::SetCookieAsyncHelper,
base::Unretained(this),
host,
- cookie_value), false);
+ cookie_value));
}
void CookieManager::SetCookieAsyncHelper(
const GURL& host,
const std::string& value,
base::WaitableEvent* completion) {
- DCHECK(!completion);
net::CookieOptions options;
options.set_include_httponly();
cookie_monster_->SetCookieWithOptionsAsync(
host, value, options,
- base::Bind(&CookieManager::SetCookieCompleted, base::Unretained(this)));
+ base::Bind(&CookieManager::SetCookieCompleted,
+ base::Unretained(this),
+ completion));
}
-void CookieManager::SetCookieCompleted(bool success) {
+void CookieManager::SetCookieCompleted(base::WaitableEvent* completion,
+ bool success) {
// The CookieManager API does not return a value for SetCookie,
// so we don't need to propagate the |success| value back to the caller.
+ completion->Signal();
}
std::string CookieManager::GetCookie(const GURL& host) {
ExecCookieTask(base::Bind(&CookieManager::GetCookieValueAsyncHelper,
base::Unretained(this),
host,
- &cookie_value), true);
+ &cookie_value));
return cookie_value;
}
std::string* result,
const std::string& value) {
*result = value;
- DCHECK(completion);
completion->Signal();
}
void CookieManager::RemoveSessionCookie() {
ExecCookieTask(base::Bind(&CookieManager::RemoveSessionCookieAsyncHelper,
- base::Unretained(this)), false);
+ base::Unretained(this)));
}
void CookieManager::RemoveSessionCookieAsyncHelper(
base::WaitableEvent* completion) {
- DCHECK(!completion);
cookie_monster_->DeleteSessionCookiesAsync(
base::Bind(&CookieManager::RemoveCookiesCompleted,
- base::Unretained(this)));
+ base::Unretained(this),
+ completion));
}
-void CookieManager::RemoveCookiesCompleted(int num_deleted) {
+void CookieManager::RemoveCookiesCompleted(base::WaitableEvent* completion,
+ int num_deleted) {
// The CookieManager API does not return a value for removeSessionCookie or
// removeAllCookie, so we don't need to propagate the |num_deleted| value back
// to the caller.
+ completion->Signal();
}
void CookieManager::RemoveAllCookie() {
ExecCookieTask(base::Bind(&CookieManager::RemoveAllCookieAsyncHelper,
- base::Unretained(this)), false);
+ base::Unretained(this)));
}
-// TODO(kristianm): Pass a null callback so it will not be invoked
-// across threads.
void CookieManager::RemoveAllCookieAsyncHelper(
base::WaitableEvent* completion) {
- DCHECK(!completion);
cookie_monster_->DeleteAllAsync(
base::Bind(&CookieManager::RemoveCookiesCompleted,
- base::Unretained(this)));
+ base::Unretained(this),
+ completion));
}
void CookieManager::RemoveExpiredCookie() {
void CookieManager::FlushCookieStoreAsyncHelper(
base::WaitableEvent* completion) {
- DCHECK(!completion);
- cookie_monster_->FlushStore(base::Bind(&base::DoNothing));
+ cookie_monster_->FlushStore(base::Bind(&base::WaitableEvent::Signal,
+ base::Unretained(completion)));
}
void CookieManager::FlushCookieStore() {
ExecCookieTask(base::Bind(&CookieManager::FlushCookieStoreAsyncHelper,
- base::Unretained(this)), false);
+ base::Unretained(this)));
}
bool CookieManager::HasCookies() {
bool has_cookies;
ExecCookieTask(base::Bind(&CookieManager::HasCookiesAsyncHelper,
base::Unretained(this),
- &has_cookies), true);
+ &has_cookies));
return has_cookies;
}
bool* result,
const CookieList& cookies) {
*result = cookies.size() != 0;
- DCHECK(completion);
completion->Signal();
}
bool CookieManager::AllowFileSchemeCookies() {
- return cookie_monster_->IsCookieableScheme(chrome::kFileScheme);
+ base::AutoLock lock(cookie_monster_lock_);
+ EnsureCookieMonsterExistsLocked();
+ return AllowFileSchemeCookiesLocked();
+}
+
+bool CookieManager::AllowFileSchemeCookiesLocked() {
+ return cookie_monster_->IsCookieableScheme(content::kFileScheme);
}
void CookieManager::SetAcceptFileSchemeCookies(bool accept) {
+ base::AutoLock lock(cookie_monster_lock_);
+ EnsureCookieMonsterExistsLocked();
+ SetAcceptFileSchemeCookiesLocked(accept);
+}
+
+void CookieManager::SetAcceptFileSchemeCookiesLocked(bool accept) {
// The docs on CookieManager base class state the API must not be called after
// creating a CookieManager instance (which contradicts its own internal
// implementation) but this code does rely on the essence of that comment, as
return CookieManager::GetInstance()->AcceptCookie();
}
+static void SetAcceptThirdPartyCookie(JNIEnv* env,
+ jobject obj,
+ jboolean accept) {
+ CookieManager::GetInstance()->SetAcceptThirdPartyCookie(accept);
+}
+
+static jboolean AcceptThirdPartyCookie(JNIEnv* env, jobject obj) {
+ return CookieManager::GetInstance()->AcceptThirdPartyCookie();
+}
+
static void SetCookie(JNIEnv* env, jobject obj, jstring url, jstring value) {
GURL host(ConvertJavaStringToUTF16(env, url));
std::string cookie_value(ConvertJavaStringToUTF8(env, value));
return CookieManager::GetInstance()->SetAcceptFileSchemeCookies(accept);
}
-void SetCookieMonsterOnNetworkStackInit(net::CookieMonster* cookie_monster) {
- CookieManager::GetInstance()->SetCookieMonster(cookie_monster);
+scoped_refptr<net::CookieStore> CreateCookieStore(
+ AwBrowserContext* browser_context) {
+ return CookieManager::GetInstance()->CreateBrowserThreadCookieStore(
+ browser_context);
}
bool RegisterCookieManager(JNIEnv* env) {