/* * Copyright 2010, The Android Open Source Project * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "WebCookieJar.h" #include "JNIUtility.h" #include "WebCoreJni.h" #include "WebRequestContext.h" #include "WebUrlLoaderClient.h" #include #include #undef ASSERT #define ASSERT(assertion, ...) do \ if (!(assertion)) { \ android_printLog(ANDROID_LOG_ERROR, __FILE__, __VA_ARGS__); \ } \ while (0) namespace android { static WTF::Mutex instanceMutex; static bool isFirstInstanceCreated = false; static bool fileSchemeCookiesEnabled = false; static const std::string& databaseDirectory() { // This method may be called on any thread, as the Java method is // synchronized. static WTF::Mutex databaseDirectoryMutex; MutexLocker lock(databaseDirectoryMutex); static std::string databaseDirectory; if (databaseDirectory.empty()) { JNIEnv* env = JSC::Bindings::getJNIEnv(); jclass bridgeClass = env->FindClass("android/webkit/JniUtil"); jmethodID method = env->GetStaticMethodID(bridgeClass, "getDatabaseDirectory", "()Ljava/lang/String;"); databaseDirectory = jstringToStdString(env, static_cast(env->CallStaticObjectMethod(bridgeClass, method))); env->DeleteLocalRef(bridgeClass); } return databaseDirectory; } static void removeFileOrDirectory(const char* filename) { struct stat filetype; if (stat(filename, &filetype) != 0) return; if (S_ISDIR(filetype.st_mode)) { DIR* directory = opendir(filename); if (directory) { while (struct dirent* entry = readdir(directory)) { if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue; std::string entryName(filename); entryName.append("/"); entryName.append(entry->d_name); removeFileOrDirectory(entryName.c_str()); } closedir(directory); rmdir(filename); } return; } unlink(filename); } static std::string databaseDirectory(bool isPrivateBrowsing) { static const char* const kDatabaseFilename = "/webviewCookiesChromium.db"; static const char* const kDatabaseFilenamePrivateBrowsing = "/webviewCookiesChromiumPrivate.db"; std::string databaseFilePath = databaseDirectory(); databaseFilePath.append(isPrivateBrowsing ? kDatabaseFilenamePrivateBrowsing : kDatabaseFilename); return databaseFilePath; } static scoped_refptr* instance(bool isPrivateBrowsing) { static scoped_refptr regularInstance; static scoped_refptr privateInstance; return isPrivateBrowsing ? &privateInstance : ®ularInstance; } WebCookieJar* WebCookieJar::get(bool isPrivateBrowsing) { MutexLocker lock(instanceMutex); if (!isFirstInstanceCreated && fileSchemeCookiesEnabled) net::CookieMonster::EnableFileScheme(); isFirstInstanceCreated = true; scoped_refptr* instancePtr = instance(isPrivateBrowsing); if (!instancePtr->get()) *instancePtr = new WebCookieJar(databaseDirectory(isPrivateBrowsing)); return instancePtr->get(); } void WebCookieJar::cleanup(bool isPrivateBrowsing) { // This is called on the UI thread. MutexLocker lock(instanceMutex); scoped_refptr* instancePtr = instance(isPrivateBrowsing); *instancePtr = 0; removeFileOrDirectory(databaseDirectory(isPrivateBrowsing).c_str()); } WebCookieJar::WebCookieJar(const std::string& databaseFilePath) : m_cookieStoreInitialized(false) , m_databaseFilePath(databaseFilePath) , m_allowCookies(true) {} void WebCookieJar::initCookieStore() { MutexLocker lock(m_cookieStoreInitializeMutex); if (m_cookieStoreInitialized) return; // Setup the permissions for the file const char* cDatabasePath = m_databaseFilePath.c_str(); mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP; if (access(cDatabasePath, F_OK) == 0) chmod(cDatabasePath, mode); else { int fd = open(cDatabasePath, O_CREAT, mode); if (fd >= 0) close(fd); } FilePath cookiePath(cDatabasePath); m_cookieDb = new SQLitePersistentCookieStore(cookiePath); m_cookieStore = new net::CookieMonster(m_cookieDb.get(), 0); m_cookieStoreInitialized = true; } bool WebCookieJar::allowCookies() { MutexLocker lock(m_allowCookiesMutex); return m_allowCookies; } void WebCookieJar::setAllowCookies(bool allow) { MutexLocker lock(m_allowCookiesMutex); m_allowCookies = allow; } // From CookiePolicy in chromium int WebCookieJar::CanGetCookies(const GURL&, const GURL&) const { MutexLocker lock(m_allowCookiesMutex); return m_allowCookies ? net::OK : net::ERR_ACCESS_DENIED; } // From CookiePolicy in chromium int WebCookieJar::CanSetCookie(const GURL&, const GURL&, const std::string&) const { MutexLocker lock(m_allowCookiesMutex); return m_allowCookies ? net::OK : net::ERR_ACCESS_DENIED; } net::CookieStore* WebCookieJar::cookieStore() { initCookieStore(); return m_cookieStore.get(); } int WebCookieJar::getNumCookiesInDatabase() { return cookieStore()->GetCookieMonster()->GetAllCookies().size(); } class FlushSemaphore : public base::RefCountedThreadSafe { public: FlushSemaphore() : m_condition(&m_lock) , m_count(0) {} void SendFlushRequest(net::CookieMonster* monster) { // FlushStore() needs to run on a Chrome thread (because it will need // to post the callback, and it may want to do so on its own thread.) // We use the IO thread for this purpose. // // TODO(husky): Our threads are hidden away in various files. Clean this // up and consider integrating with Chrome's browser_thread.h. Might be // a better idea to use the DB thread here rather than the IO thread. base::Thread* ioThread = WebUrlLoaderClient::ioThread(); if (ioThread) { Task* callback = NewRunnableMethod(this, &FlushSemaphore::Callback); ioThread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod( monster, &net::CookieMonster::FlushStore, callback)); } else { Callback(); } } // Block until the given number of callbacks has been made. void Wait(int numCallbacks) { base::AutoLock al(m_lock); int lastCount = m_count; while (m_count < numCallbacks) { // TODO(husky): Maybe use TimedWait() here? But it's not obvious what // to do if the flush fails. Might be okay just to let the OS kill us. m_condition.Wait(); ASSERT(lastCount != m_count, "Wait finished without incrementing m_count %d %d", m_count, lastCount); lastCount = m_count; } m_count -= numCallbacks; } private: friend class base::RefCounted; void Callback() { base::AutoLock al(m_lock); m_count++; m_condition.Broadcast(); } base::Lock m_lock; base::ConditionVariable m_condition; volatile int m_count; }; void WebCookieJar::flush() { // Flush both cookie stores (private and non-private), wait for 2 callbacks. static scoped_refptr semaphore(new FlushSemaphore()); semaphore->SendFlushRequest(get(false)->cookieStore()->GetCookieMonster()); semaphore->SendFlushRequest(get(true)->cookieStore()->GetCookieMonster()); semaphore->Wait(2); } bool WebCookieJar::acceptFileSchemeCookies() { MutexLocker lock(instanceMutex); return fileSchemeCookiesEnabled; } void WebCookieJar::setAcceptFileSchemeCookies(bool accept) { // The Chromium HTTP stack only reflects changes to this flag when creating // a new CookieMonster instance. While we could track whether any // CookieMonster instances currently exist, this would be complicated and is // not required, so we only allow this flag to be changed before the first // instance is created. MutexLocker lock(instanceMutex); if (!isFirstInstanceCreated) fileSchemeCookiesEnabled = accept; } }