blob: e1392ae142257a75dfe4aea236db9c738e6c87aa [file] [log] [blame]
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.webkit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Map.Entry;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteStatement;
import android.util.Log;
import android.webkit.CookieManager.Cookie;
import android.webkit.CacheManager.CacheResult;
import android.webkit.JniUtil;
public class WebViewDatabase {
private static final String DATABASE_FILE = "webview.db";
private static final String CACHE_DATABASE_FILE = "webviewCache.db";
// log tag
protected static final String LOGTAG = "webviewdatabase";
private static final int DATABASE_VERSION = 10;
// 2 -> 3 Modified Cache table to allow cache of redirects
// 3 -> 4 Added Oma-Downloads table
// 4 -> 5 Modified Cache table to support persistent contentLength
// 5 -> 4 Removed Oma-Downoads table
// 5 -> 6 Add INDEX for cache table
// 6 -> 7 Change cache localPath from int to String
// 7 -> 8 Move cache to its own db
// 8 -> 9 Store both scheme and host when storing passwords
// 9 -> 10 Update httpauth table UNIQUE
private static final int CACHE_DATABASE_VERSION = 4;
// 1 -> 2 Add expires String
// 2 -> 3 Add content-disposition
// 3 -> 4 Add crossdomain (For x-permitted-cross-domain-policies header)
private static WebViewDatabase mInstance = null;
private static SQLiteDatabase mDatabase = null;
private static SQLiteDatabase mCacheDatabase = null;
// synchronize locks
private final Object mCookieLock = new Object();
private final Object mPasswordLock = new Object();
private final Object mFormLock = new Object();
private final Object mHttpAuthLock = new Object();
// TODO: The Chromium HTTP stack handles cookies independently.
// We should consider removing the cookies table if and when we switch to
// the Chromium HTTP stack for good.
private static final String mTableNames[] = {
"cookies", "password", "formurl", "formdata", "httpauth"
};
// Table ids (they are index to mTableNames)
private static final int TABLE_COOKIES_ID = 0;
private static final int TABLE_PASSWORD_ID = 1;
private static final int TABLE_FORMURL_ID = 2;
private static final int TABLE_FORMDATA_ID = 3;
private static final int TABLE_HTTPAUTH_ID = 4;
// column id strings for "_id" which can be used by any table
private static final String ID_COL = "_id";
private static final String[] ID_PROJECTION = new String[] {
"_id"
};
// column id strings for "cookies" table
private static final String COOKIES_NAME_COL = "name";
private static final String COOKIES_VALUE_COL = "value";
private static final String COOKIES_DOMAIN_COL = "domain";
private static final String COOKIES_PATH_COL = "path";
private static final String COOKIES_EXPIRES_COL = "expires";
private static final String COOKIES_SECURE_COL = "secure";
// column id strings for "cache" table
private static final String CACHE_URL_COL = "url";
private static final String CACHE_FILE_PATH_COL = "filepath";
private static final String CACHE_LAST_MODIFY_COL = "lastmodify";
private static final String CACHE_ETAG_COL = "etag";
private static final String CACHE_EXPIRES_COL = "expires";
private static final String CACHE_EXPIRES_STRING_COL = "expiresstring";
private static final String CACHE_MIMETYPE_COL = "mimetype";
private static final String CACHE_ENCODING_COL = "encoding";
private static final String CACHE_HTTP_STATUS_COL = "httpstatus";
private static final String CACHE_LOCATION_COL = "location";
private static final String CACHE_CONTENTLENGTH_COL = "contentlength";
private static final String CACHE_CONTENTDISPOSITION_COL = "contentdisposition";
private static final String CACHE_CROSSDOMAIN_COL = "crossdomain";
// column id strings for "password" table
private static final String PASSWORD_HOST_COL = "host";
private static final String PASSWORD_USERNAME_COL = "username";
private static final String PASSWORD_PASSWORD_COL = "password";
// column id strings for "formurl" table
private static final String FORMURL_URL_COL = "url";
// column id strings for "formdata" table
private static final String FORMDATA_URLID_COL = "urlid";
private static final String FORMDATA_NAME_COL = "name";
private static final String FORMDATA_VALUE_COL = "value";
// column id strings for "httpauth" table
private static final String HTTPAUTH_HOST_COL = "host";
private static final String HTTPAUTH_REALM_COL = "realm";
private static final String HTTPAUTH_USERNAME_COL = "username";
private static final String HTTPAUTH_PASSWORD_COL = "password";
// use InsertHelper to improve insert performance by 40%
private static DatabaseUtils.InsertHelper mCacheInserter;
private static int mCacheUrlColIndex;
private static int mCacheFilePathColIndex;
private static int mCacheLastModifyColIndex;
private static int mCacheETagColIndex;
private static int mCacheExpiresColIndex;
private static int mCacheExpiresStringColIndex;
private static int mCacheMimeTypeColIndex;
private static int mCacheEncodingColIndex;
private static int mCacheHttpStatusColIndex;
private static int mCacheLocationColIndex;
private static int mCacheContentLengthColIndex;
private static int mCacheContentDispositionColIndex;
private static int mCacheCrossDomainColIndex;
private static int mCacheTransactionRefcount;
// Initially true until the background thread completes.
private boolean mInitialized = false;
private WebViewDatabase(final Context context) {
new Thread() {
@Override
public void run() {
init(context);
}
}.start();
// Singleton only, use getInstance()
}
public static synchronized WebViewDatabase getInstance(Context context) {
if (mInstance == null) {
mInstance = new WebViewDatabase(context);
}
return mInstance;
}
private synchronized void init(Context context) {
if (mInitialized) {
return;
}
initDatabase(context);
if (!JniUtil.useChromiumHttpStack()) {
initCacheDatabase(context);
}
// Thread done, notify.
mInitialized = true;
notify();
}
private void initDatabase(Context context) {
try {
mDatabase = context.openOrCreateDatabase(DATABASE_FILE, 0, null);
} catch (SQLiteException e) {
// try again by deleting the old db and create a new one
if (context.deleteDatabase(DATABASE_FILE)) {
mDatabase = context.openOrCreateDatabase(DATABASE_FILE, 0,
null);
}
}
mDatabase.enableWriteAheadLogging();
// mDatabase should not be null,
// the only case is RequestAPI test has problem to create db
if (mDatabase == null) {
mInitialized = true;
notify();
return;
}
if (mDatabase.getVersion() != DATABASE_VERSION) {
mDatabase.beginTransactionNonExclusive();
try {
upgradeDatabase();
mDatabase.setTransactionSuccessful();
} finally {
mDatabase.endTransaction();
}
}
// use per table Mutex lock, turn off database lock, this
// improves performance as database's ReentrantLock is
// expansive
mDatabase.setLockingEnabled(false);
}
private void initCacheDatabase(Context context) {
assert !JniUtil.useChromiumHttpStack();
try {
mCacheDatabase = context.openOrCreateDatabase(
CACHE_DATABASE_FILE, 0, null);
} catch (SQLiteException e) {
// try again by deleting the old db and create a new one
if (context.deleteDatabase(CACHE_DATABASE_FILE)) {
mCacheDatabase = context.openOrCreateDatabase(
CACHE_DATABASE_FILE, 0, null);
}
}
mCacheDatabase.enableWriteAheadLogging();
// mCacheDatabase should not be null,
// the only case is RequestAPI test has problem to create db
if (mCacheDatabase == null) {
mInitialized = true;
notify();
return;
}
if (mCacheDatabase.getVersion() != CACHE_DATABASE_VERSION) {
mCacheDatabase.beginTransactionNonExclusive();
try {
upgradeCacheDatabase();
bootstrapCacheDatabase();
mCacheDatabase.setTransactionSuccessful();
} finally {
mCacheDatabase.endTransaction();
}
// Erase the files from the file system in the
// case that the database was updated and the
// there were existing cache content
CacheManager.removeAllCacheFiles();
}
// use read_uncommitted to speed up READ
mCacheDatabase.execSQL("PRAGMA read_uncommitted = true;");
// as only READ can be called in the
// non-WebViewWorkerThread, and read_uncommitted is used,
// we can turn off database lock to use transaction.
mCacheDatabase.setLockingEnabled(false);
// use InsertHelper for faster insertion
mCacheInserter =
new DatabaseUtils.InsertHelper(mCacheDatabase,
"cache");
mCacheUrlColIndex = mCacheInserter
.getColumnIndex(CACHE_URL_COL);
mCacheFilePathColIndex = mCacheInserter
.getColumnIndex(CACHE_FILE_PATH_COL);
mCacheLastModifyColIndex = mCacheInserter
.getColumnIndex(CACHE_LAST_MODIFY_COL);
mCacheETagColIndex = mCacheInserter
.getColumnIndex(CACHE_ETAG_COL);
mCacheExpiresColIndex = mCacheInserter
.getColumnIndex(CACHE_EXPIRES_COL);
mCacheExpiresStringColIndex = mCacheInserter
.getColumnIndex(CACHE_EXPIRES_STRING_COL);
mCacheMimeTypeColIndex = mCacheInserter
.getColumnIndex(CACHE_MIMETYPE_COL);
mCacheEncodingColIndex = mCacheInserter
.getColumnIndex(CACHE_ENCODING_COL);
mCacheHttpStatusColIndex = mCacheInserter
.getColumnIndex(CACHE_HTTP_STATUS_COL);
mCacheLocationColIndex = mCacheInserter
.getColumnIndex(CACHE_LOCATION_COL);
mCacheContentLengthColIndex = mCacheInserter
.getColumnIndex(CACHE_CONTENTLENGTH_COL);
mCacheContentDispositionColIndex = mCacheInserter
.getColumnIndex(CACHE_CONTENTDISPOSITION_COL);
mCacheCrossDomainColIndex = mCacheInserter
.getColumnIndex(CACHE_CROSSDOMAIN_COL);
}
private static void upgradeDatabase() {
int oldVersion = mDatabase.getVersion();
if (oldVersion != 0) {
Log.i(LOGTAG, "Upgrading database from version "
+ oldVersion + " to "
+ DATABASE_VERSION + ", which will destroy old data");
}
boolean justPasswords = 8 == oldVersion && 9 == DATABASE_VERSION;
boolean justAuth = 9 == oldVersion && 10 == DATABASE_VERSION;
if (justAuth) {
mDatabase.execSQL("DROP TABLE IF EXISTS "
+ mTableNames[TABLE_HTTPAUTH_ID]);
mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_HTTPAUTH_ID]
+ " (" + ID_COL + " INTEGER PRIMARY KEY, "
+ HTTPAUTH_HOST_COL + " TEXT, " + HTTPAUTH_REALM_COL
+ " TEXT, " + HTTPAUTH_USERNAME_COL + " TEXT, "
+ HTTPAUTH_PASSWORD_COL + " TEXT," + " UNIQUE ("
+ HTTPAUTH_HOST_COL + ", " + HTTPAUTH_REALM_COL
+ ") ON CONFLICT REPLACE);");
return;
}
if (!justPasswords) {
mDatabase.execSQL("DROP TABLE IF EXISTS "
+ mTableNames[TABLE_COOKIES_ID]);
mDatabase.execSQL("DROP TABLE IF EXISTS cache");
mDatabase.execSQL("DROP TABLE IF EXISTS "
+ mTableNames[TABLE_FORMURL_ID]);
mDatabase.execSQL("DROP TABLE IF EXISTS "
+ mTableNames[TABLE_FORMDATA_ID]);
mDatabase.execSQL("DROP TABLE IF EXISTS "
+ mTableNames[TABLE_HTTPAUTH_ID]);
}
mDatabase.execSQL("DROP TABLE IF EXISTS "
+ mTableNames[TABLE_PASSWORD_ID]);
mDatabase.setVersion(DATABASE_VERSION);
if (!justPasswords) {
// cookies
mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_COOKIES_ID]
+ " (" + ID_COL + " INTEGER PRIMARY KEY, "
+ COOKIES_NAME_COL + " TEXT, " + COOKIES_VALUE_COL
+ " TEXT, " + COOKIES_DOMAIN_COL + " TEXT, "
+ COOKIES_PATH_COL + " TEXT, " + COOKIES_EXPIRES_COL
+ " INTEGER, " + COOKIES_SECURE_COL + " INTEGER" + ");");
mDatabase.execSQL("CREATE INDEX cookiesIndex ON "
+ mTableNames[TABLE_COOKIES_ID] + " (path)");
// formurl
mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_FORMURL_ID]
+ " (" + ID_COL + " INTEGER PRIMARY KEY, " + FORMURL_URL_COL
+ " TEXT" + ");");
// formdata
mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_FORMDATA_ID]
+ " (" + ID_COL + " INTEGER PRIMARY KEY, "
+ FORMDATA_URLID_COL + " INTEGER, " + FORMDATA_NAME_COL
+ " TEXT, " + FORMDATA_VALUE_COL + " TEXT," + " UNIQUE ("
+ FORMDATA_URLID_COL + ", " + FORMDATA_NAME_COL + ", "
+ FORMDATA_VALUE_COL + ") ON CONFLICT IGNORE);");
// httpauth
mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_HTTPAUTH_ID]
+ " (" + ID_COL + " INTEGER PRIMARY KEY, "
+ HTTPAUTH_HOST_COL + " TEXT, " + HTTPAUTH_REALM_COL
+ " TEXT, " + HTTPAUTH_USERNAME_COL + " TEXT, "
+ HTTPAUTH_PASSWORD_COL + " TEXT," + " UNIQUE ("
+ HTTPAUTH_HOST_COL + ", " + HTTPAUTH_REALM_COL
+ ") ON CONFLICT REPLACE);");
}
// passwords
mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_PASSWORD_ID]
+ " (" + ID_COL + " INTEGER PRIMARY KEY, "
+ PASSWORD_HOST_COL + " TEXT, " + PASSWORD_USERNAME_COL
+ " TEXT, " + PASSWORD_PASSWORD_COL + " TEXT," + " UNIQUE ("
+ PASSWORD_HOST_COL + ", " + PASSWORD_USERNAME_COL
+ ") ON CONFLICT REPLACE);");
}
private static void upgradeCacheDatabase() {
int oldVersion = mCacheDatabase.getVersion();
if (oldVersion != 0) {
Log.i(LOGTAG, "Upgrading cache database from version "
+ oldVersion + " to "
+ DATABASE_VERSION + ", which will destroy all old data");
}
mCacheDatabase.execSQL("DROP TABLE IF EXISTS cache");
mCacheDatabase.setVersion(CACHE_DATABASE_VERSION);
}
private static void bootstrapCacheDatabase() {
if (mCacheDatabase != null) {
mCacheDatabase.execSQL("CREATE TABLE cache"
+ " (" + ID_COL + " INTEGER PRIMARY KEY, " + CACHE_URL_COL
+ " TEXT, " + CACHE_FILE_PATH_COL + " TEXT, "
+ CACHE_LAST_MODIFY_COL + " TEXT, " + CACHE_ETAG_COL
+ " TEXT, " + CACHE_EXPIRES_COL + " INTEGER, "
+ CACHE_EXPIRES_STRING_COL + " TEXT, "
+ CACHE_MIMETYPE_COL + " TEXT, " + CACHE_ENCODING_COL
+ " TEXT," + CACHE_HTTP_STATUS_COL + " INTEGER, "
+ CACHE_LOCATION_COL + " TEXT, " + CACHE_CONTENTLENGTH_COL
+ " INTEGER, " + CACHE_CONTENTDISPOSITION_COL + " TEXT, "
+ CACHE_CROSSDOMAIN_COL + " TEXT,"
+ " UNIQUE (" + CACHE_URL_COL + ") ON CONFLICT REPLACE);");
mCacheDatabase.execSQL("CREATE INDEX cacheUrlIndex ON cache ("
+ CACHE_URL_COL + ")");
}
}
// Wait for the background initialization thread to complete and check the
// database creation status.
private boolean checkInitialized() {
synchronized (this) {
while (!mInitialized) {
try {
wait();
} catch (InterruptedException e) {
Log.e(LOGTAG, "Caught exception while checking " +
"initialization");
Log.e(LOGTAG, Log.getStackTraceString(e));
}
}
}
return mDatabase != null;
}
private boolean hasEntries(int tableId) {
if (!checkInitialized()) {
return false;
}
Cursor cursor = null;
boolean ret = false;
try {
cursor = mDatabase.query(mTableNames[tableId], ID_PROJECTION,
null, null, null, null, null);
ret = cursor.moveToFirst() == true;
} catch (IllegalStateException e) {
Log.e(LOGTAG, "hasEntries", e);
} finally {
if (cursor != null) cursor.close();
}
return ret;
}
//
// cookies functions
//
/**
* Get cookies in the format of CookieManager.Cookie inside an ArrayList for
* a given domain
*
* @return ArrayList<Cookie> If nothing is found, return an empty list.
*/
ArrayList<Cookie> getCookiesForDomain(String domain) {
ArrayList<Cookie> list = new ArrayList<Cookie>();
if (domain == null || !checkInitialized()) {
return list;
}
synchronized (mCookieLock) {
final String[] columns = new String[] {
ID_COL, COOKIES_DOMAIN_COL, COOKIES_PATH_COL,
COOKIES_NAME_COL, COOKIES_VALUE_COL, COOKIES_EXPIRES_COL,
COOKIES_SECURE_COL
};
final String selection = "(" + COOKIES_DOMAIN_COL
+ " GLOB '*' || ?)";
Cursor cursor = null;
try {
cursor = mDatabase.query(mTableNames[TABLE_COOKIES_ID],
columns, selection, new String[] { domain }, null, null,
null);
if (cursor.moveToFirst()) {
int domainCol = cursor.getColumnIndex(COOKIES_DOMAIN_COL);
int pathCol = cursor.getColumnIndex(COOKIES_PATH_COL);
int nameCol = cursor.getColumnIndex(COOKIES_NAME_COL);
int valueCol = cursor.getColumnIndex(COOKIES_VALUE_COL);
int expiresCol = cursor.getColumnIndex(COOKIES_EXPIRES_COL);
int secureCol = cursor.getColumnIndex(COOKIES_SECURE_COL);
do {
Cookie cookie = new Cookie();
cookie.domain = cursor.getString(domainCol);
cookie.path = cursor.getString(pathCol);
cookie.name = cursor.getString(nameCol);
cookie.value = cursor.getString(valueCol);
if (cursor.isNull(expiresCol)) {
cookie.expires = -1;
} else {
cookie.expires = cursor.getLong(expiresCol);
}
cookie.secure = cursor.getShort(secureCol) != 0;
cookie.mode = Cookie.MODE_NORMAL;
list.add(cookie);
} while (cursor.moveToNext());
}
} catch (IllegalStateException e) {
Log.e(LOGTAG, "getCookiesForDomain", e);
} finally {
if (cursor != null) cursor.close();
}
return list;
}
}
/**
* Delete cookies which matches (domain, path, name).
*
* @param domain If it is null, nothing happens.
* @param path If it is null, all the cookies match (domain) will be
* deleted.
* @param name If it is null, all the cookies match (domain, path) will be
* deleted.
*/
void deleteCookies(String domain, String path, String name) {
if (domain == null || !checkInitialized()) {
return;
}
synchronized (mCookieLock) {
final String where = "(" + COOKIES_DOMAIN_COL + " == ?) AND ("
+ COOKIES_PATH_COL + " == ?) AND (" + COOKIES_NAME_COL
+ " == ?)";
mDatabase.delete(mTableNames[TABLE_COOKIES_ID], where,
new String[] { domain, path, name });
}
}
/**
* Add a cookie to the database
*
* @param cookie
*/
void addCookie(Cookie cookie) {
if (cookie.domain == null || cookie.path == null || cookie.name == null
|| !checkInitialized()) {
return;
}
synchronized (mCookieLock) {
ContentValues cookieVal = new ContentValues();
cookieVal.put(COOKIES_DOMAIN_COL, cookie.domain);
cookieVal.put(COOKIES_PATH_COL, cookie.path);
cookieVal.put(COOKIES_NAME_COL, cookie.name);
cookieVal.put(COOKIES_VALUE_COL, cookie.value);
if (cookie.expires != -1) {
cookieVal.put(COOKIES_EXPIRES_COL, cookie.expires);
}
cookieVal.put(COOKIES_SECURE_COL, cookie.secure);
mDatabase.insert(mTableNames[TABLE_COOKIES_ID], null, cookieVal);
}
}
/**
* Whether there is any cookies in the database
*
* @return TRUE if there is cookie.
*/
boolean hasCookies() {
synchronized (mCookieLock) {
return hasEntries(TABLE_COOKIES_ID);
}
}
/**
* Clear cookie database
*/
void clearCookies() {
if (!checkInitialized()) {
return;
}
synchronized (mCookieLock) {
mDatabase.delete(mTableNames[TABLE_COOKIES_ID], null, null);
}
}
/**
* Clear session cookies, which means cookie doesn't have EXPIRES.
*/
void clearSessionCookies() {
if (!checkInitialized()) {
return;
}
final String sessionExpired = COOKIES_EXPIRES_COL + " ISNULL";
synchronized (mCookieLock) {
mDatabase.delete(mTableNames[TABLE_COOKIES_ID], sessionExpired,
null);
}
}
/**
* Clear expired cookies
*
* @param now Time for now
*/
void clearExpiredCookies(long now) {
if (!checkInitialized()) {
return;
}
final String expires = COOKIES_EXPIRES_COL + " <= ?";
synchronized (mCookieLock) {
mDatabase.delete(mTableNames[TABLE_COOKIES_ID], expires,
new String[] { Long.toString(now) });
}
}
//
// cache functions
//
// only called from WebViewWorkerThread
boolean startCacheTransaction() {
if (++mCacheTransactionRefcount == 1) {
if (!Thread.currentThread().equals(
WebViewWorker.getHandler().getLooper().getThread())) {
Log.w(LOGTAG, "startCacheTransaction should be called from "
+ "WebViewWorkerThread instead of from "
+ Thread.currentThread().getName());
}
mCacheDatabase.beginTransactionNonExclusive();
return true;
}
return false;
}
// only called from WebViewWorkerThread
boolean endCacheTransaction() {
if (--mCacheTransactionRefcount == 0) {
if (!Thread.currentThread().equals(
WebViewWorker.getHandler().getLooper().getThread())) {
Log.w(LOGTAG, "endCacheTransaction should be called from "
+ "WebViewWorkerThread instead of from "
+ Thread.currentThread().getName());
}
try {
mCacheDatabase.setTransactionSuccessful();
} finally {
mCacheDatabase.endTransaction();
}
return true;
}
return false;
}
/**
* Get a cache item.
*
* @param url The url
* @return CacheResult The CacheManager.CacheResult
*/
CacheResult getCache(String url) {
assert !JniUtil.useChromiumHttpStack();
if (url == null || !checkInitialized()) {
return null;
}
Cursor cursor = null;
final String query = "SELECT filepath, lastmodify, etag, expires, "
+ "expiresstring, mimetype, encoding, httpstatus, location, contentlength, "
+ "contentdisposition, crossdomain FROM cache WHERE url = ?";
try {
cursor = mCacheDatabase.rawQuery(query, new String[] { url });
if (cursor.moveToFirst()) {
CacheResult ret = new CacheResult();
ret.localPath = cursor.getString(0);
ret.lastModified = cursor.getString(1);
ret.etag = cursor.getString(2);
ret.expires = cursor.getLong(3);
ret.expiresString = cursor.getString(4);
ret.mimeType = cursor.getString(5);
ret.encoding = cursor.getString(6);
ret.httpStatusCode = cursor.getInt(7);
ret.location = cursor.getString(8);
ret.contentLength = cursor.getLong(9);
ret.contentdisposition = cursor.getString(10);
ret.crossDomain = cursor.getString(11);
return ret;
}
} catch (IllegalStateException e) {
Log.e(LOGTAG, "getCache", e);
} finally {
if (cursor != null) cursor.close();
}
return null;
}
/**
* Remove a cache item.
*
* @param url The url
*/
void removeCache(String url) {
assert !JniUtil.useChromiumHttpStack();
if (url == null || !checkInitialized()) {
return;
}
mCacheDatabase.execSQL("DELETE FROM cache WHERE url = ?", new String[] { url });
}
/**
* Add or update a cache. CACHE_URL_COL is unique in the table.
*
* @param url The url
* @param c The CacheManager.CacheResult
*/
void addCache(String url, CacheResult c) {
assert !JniUtil.useChromiumHttpStack();
if (url == null || !checkInitialized()) {
return;
}
mCacheInserter.prepareForInsert();
mCacheInserter.bind(mCacheUrlColIndex, url);
mCacheInserter.bind(mCacheFilePathColIndex, c.localPath);
mCacheInserter.bind(mCacheLastModifyColIndex, c.lastModified);
mCacheInserter.bind(mCacheETagColIndex, c.etag);
mCacheInserter.bind(mCacheExpiresColIndex, c.expires);
mCacheInserter.bind(mCacheExpiresStringColIndex, c.expiresString);
mCacheInserter.bind(mCacheMimeTypeColIndex, c.mimeType);
mCacheInserter.bind(mCacheEncodingColIndex, c.encoding);
mCacheInserter.bind(mCacheHttpStatusColIndex, c.httpStatusCode);
mCacheInserter.bind(mCacheLocationColIndex, c.location);
mCacheInserter.bind(mCacheContentLengthColIndex, c.contentLength);
mCacheInserter.bind(mCacheContentDispositionColIndex,
c.contentdisposition);
mCacheInserter.bind(mCacheCrossDomainColIndex, c.crossDomain);
mCacheInserter.execute();
}
/**
* Clear cache database
*/
void clearCache() {
if (!checkInitialized()) {
return;
}
mCacheDatabase.delete("cache", null, null);
}
boolean hasCache() {
if (!checkInitialized()) {
return false;
}
Cursor cursor = null;
boolean ret = false;
try {
cursor = mCacheDatabase.query("cache", ID_PROJECTION,
null, null, null, null, null);
ret = cursor.moveToFirst() == true;
} catch (IllegalStateException e) {
Log.e(LOGTAG, "hasCache", e);
} finally {
if (cursor != null) cursor.close();
}
return ret;
}
long getCacheTotalSize() {
if (mCacheDatabase == null) {
return 0;
}
long size = 0;
Cursor cursor = null;
final String query = "SELECT SUM(contentlength) as sum FROM cache";
try {
cursor = mCacheDatabase.rawQuery(query, null);
if (cursor.moveToFirst()) {
size = cursor.getLong(0);
}
} catch (IllegalStateException e) {
Log.e(LOGTAG, "getCacheTotalSize", e);
} finally {
if (cursor != null) cursor.close();
}
return size;
}
List<String> trimCache(long amount) {
ArrayList<String> pathList = new ArrayList<String>(100);
Cursor cursor = null;
final String query = "SELECT contentlength, filepath FROM cache ORDER BY expires ASC";
try {
cursor = mCacheDatabase.rawQuery(query, null);
if (cursor.moveToFirst()) {
int batchSize = 100;
StringBuilder pathStr = new StringBuilder(20 + 16 * batchSize);
pathStr.append("DELETE FROM cache WHERE filepath IN (?");
for (int i = 1; i < batchSize; i++) {
pathStr.append(", ?");
}
pathStr.append(")");
SQLiteStatement statement = null;
try {
statement = mCacheDatabase.compileStatement(
pathStr.toString());
// as bindString() uses 1-based index, initialize index to 1
int index = 1;
do {
long length = cursor.getLong(0);
if (length == 0) {
continue;
}
amount -= length;
String filePath = cursor.getString(1);
statement.bindString(index, filePath);
pathList.add(filePath);
if (index++ == batchSize) {
statement.execute();
statement.clearBindings();
index = 1;
}
} while (cursor.moveToNext() && amount > 0);
if (index > 1) {
// there may be old bindings from the previous statement
// if index is less than batchSize, which is Ok.
statement.execute();
}
} catch (IllegalStateException e) {
Log.e(LOGTAG, "trimCache SQLiteStatement", e);
} finally {
if (statement != null) statement.close();
}
}
} catch (IllegalStateException e) {
Log.e(LOGTAG, "trimCache Cursor", e);
} finally {
if (cursor != null) cursor.close();
}
return pathList;
}
List<String> getAllCacheFileNames() {
ArrayList<String> pathList = null;
Cursor cursor = null;
try {
cursor = mCacheDatabase.rawQuery("SELECT filepath FROM cache",
null);
if (cursor != null && cursor.moveToFirst()) {
pathList = new ArrayList<String>(cursor.getCount());
do {
pathList.add(cursor.getString(0));
} while (cursor.moveToNext());
}
} catch (IllegalStateException e) {
Log.e(LOGTAG, "getAllCacheFileNames", e);
} finally {
if (cursor != null) cursor.close();
}
return pathList;
}
//
// password functions
//
/**
* Set password. Tuple (PASSWORD_HOST_COL, PASSWORD_USERNAME_COL) is unique.
*
* @param schemePlusHost The scheme and host for the password
* @param username The username for the password. If it is null, it means
* password can't be saved.
* @param password The password
*/
void setUsernamePassword(String schemePlusHost, String username,
String password) {
if (schemePlusHost == null || !checkInitialized()) {
return;
}
synchronized (mPasswordLock) {
final ContentValues c = new ContentValues();
c.put(PASSWORD_HOST_COL, schemePlusHost);
c.put(PASSWORD_USERNAME_COL, username);
c.put(PASSWORD_PASSWORD_COL, password);
mDatabase.insert(mTableNames[TABLE_PASSWORD_ID], PASSWORD_HOST_COL,
c);
}
}
/**
* Retrieve the username and password for a given host
*
* @param schemePlusHost The scheme and host which passwords applies to
* @return String[] if found, String[0] is username, which can be null and
* String[1] is password. Return null if it can't find anything.
*/
String[] getUsernamePassword(String schemePlusHost) {
if (schemePlusHost == null || !checkInitialized()) {
return null;
}
final String[] columns = new String[] {
PASSWORD_USERNAME_COL, PASSWORD_PASSWORD_COL
};
final String selection = "(" + PASSWORD_HOST_COL + " == ?)";
synchronized (mPasswordLock) {
String[] ret = null;
Cursor cursor = null;
try {
cursor = mDatabase.query(mTableNames[TABLE_PASSWORD_ID],
columns, selection, new String[] { schemePlusHost }, null,
null, null);
if (cursor.moveToFirst()) {
ret = new String[2];
ret[0] = cursor.getString(
cursor.getColumnIndex(PASSWORD_USERNAME_COL));
ret[1] = cursor.getString(
cursor.getColumnIndex(PASSWORD_PASSWORD_COL));
}
} catch (IllegalStateException e) {
Log.e(LOGTAG, "getUsernamePassword", e);
} finally {
if (cursor != null) cursor.close();
}
return ret;
}
}
/**
* Find out if there are any passwords saved.
*
* @return TRUE if there is passwords saved
*/
public boolean hasUsernamePassword() {
synchronized (mPasswordLock) {
return hasEntries(TABLE_PASSWORD_ID);
}
}
/**
* Clear password database
*/
public void clearUsernamePassword() {
if (!checkInitialized()) {
return;
}
synchronized (mPasswordLock) {
mDatabase.delete(mTableNames[TABLE_PASSWORD_ID], null, null);
}
}
//
// http authentication password functions
//
/**
* Set HTTP authentication password. Tuple (HTTPAUTH_HOST_COL,
* HTTPAUTH_REALM_COL, HTTPAUTH_USERNAME_COL) is unique.
*
* @param host The host for the password
* @param realm The realm for the password
* @param username The username for the password. If it is null, it means
* password can't be saved.
* @param password The password
*/
void setHttpAuthUsernamePassword(String host, String realm, String username,
String password) {
if (host == null || realm == null || !checkInitialized()) {
return;
}
synchronized (mHttpAuthLock) {
final ContentValues c = new ContentValues();
c.put(HTTPAUTH_HOST_COL, host);
c.put(HTTPAUTH_REALM_COL, realm);
c.put(HTTPAUTH_USERNAME_COL, username);
c.put(HTTPAUTH_PASSWORD_COL, password);
mDatabase.insert(mTableNames[TABLE_HTTPAUTH_ID], HTTPAUTH_HOST_COL,
c);
}
}
/**
* Retrieve the HTTP authentication username and password for a given
* host+realm pair
*
* @param host The host the password applies to
* @param realm The realm the password applies to
* @return String[] if found, String[0] is username, which can be null and
* String[1] is password. Return null if it can't find anything.
*/
String[] getHttpAuthUsernamePassword(String host, String realm) {
if (host == null || realm == null || !checkInitialized()){
return null;
}
final String[] columns = new String[] {
HTTPAUTH_USERNAME_COL, HTTPAUTH_PASSWORD_COL
};
final String selection = "(" + HTTPAUTH_HOST_COL + " == ?) AND ("
+ HTTPAUTH_REALM_COL + " == ?)";
synchronized (mHttpAuthLock) {
String[] ret = null;
Cursor cursor = null;
try {
cursor = mDatabase.query(mTableNames[TABLE_HTTPAUTH_ID],
columns, selection, new String[] { host, realm }, null,
null, null);
if (cursor.moveToFirst()) {
ret = new String[2];
ret[0] = cursor.getString(
cursor.getColumnIndex(HTTPAUTH_USERNAME_COL));
ret[1] = cursor.getString(
cursor.getColumnIndex(HTTPAUTH_PASSWORD_COL));
}
} catch (IllegalStateException e) {
Log.e(LOGTAG, "getHttpAuthUsernamePassword", e);
} finally {
if (cursor != null) cursor.close();
}
return ret;
}
}
/**
* Find out if there are any HTTP authentication passwords saved.
*
* @return TRUE if there are passwords saved
*/
public boolean hasHttpAuthUsernamePassword() {
synchronized (mHttpAuthLock) {
return hasEntries(TABLE_HTTPAUTH_ID);
}
}
/**
* Clear HTTP authentication password database
*/
public void clearHttpAuthUsernamePassword() {
if (!checkInitialized()) {
return;
}
synchronized (mHttpAuthLock) {
mDatabase.delete(mTableNames[TABLE_HTTPAUTH_ID], null, null);
}
}
//
// form data functions
//
/**
* Set form data for a site. Tuple (FORMDATA_URLID_COL, FORMDATA_NAME_COL,
* FORMDATA_VALUE_COL) is unique
*
* @param url The url of the site
* @param formdata The form data in HashMap
*/
void setFormData(String url, HashMap<String, String> formdata) {
if (url == null || formdata == null || !checkInitialized()) {
return;
}
final String selection = "(" + FORMURL_URL_COL + " == ?)";
synchronized (mFormLock) {
long urlid = -1;
Cursor cursor = null;
try {
cursor = mDatabase.query(mTableNames[TABLE_FORMURL_ID],
ID_PROJECTION, selection, new String[] { url }, null, null,
null);
if (cursor.moveToFirst()) {
urlid = cursor.getLong(cursor.getColumnIndex(ID_COL));
} else {
ContentValues c = new ContentValues();
c.put(FORMURL_URL_COL, url);
urlid = mDatabase.insert(
mTableNames[TABLE_FORMURL_ID], null, c);
}
} catch (IllegalStateException e) {
Log.e(LOGTAG, "setFormData", e);
} finally {
if (cursor != null) cursor.close();
}
if (urlid >= 0) {
Set<Entry<String, String>> set = formdata.entrySet();
Iterator<Entry<String, String>> iter = set.iterator();
ContentValues map = new ContentValues();
map.put(FORMDATA_URLID_COL, urlid);
while (iter.hasNext()) {
Entry<String, String> entry = iter.next();
map.put(FORMDATA_NAME_COL, entry.getKey());
map.put(FORMDATA_VALUE_COL, entry.getValue());
mDatabase.insert(mTableNames[TABLE_FORMDATA_ID], null, map);
}
}
}
}
/**
* Get all the values for a form entry with "name" in a given site
*
* @param url The url of the site
* @param name The name of the form entry
* @return A list of values. Return empty list if nothing is found.
*/
ArrayList<String> getFormData(String url, String name) {
ArrayList<String> values = new ArrayList<String>();
if (url == null || name == null || !checkInitialized()) {
return values;
}
final String urlSelection = "(" + FORMURL_URL_COL + " == ?)";
final String dataSelection = "(" + FORMDATA_URLID_COL + " == ?) AND ("
+ FORMDATA_NAME_COL + " == ?)";
synchronized (mFormLock) {
Cursor cursor = null;
try {
cursor = mDatabase.query(mTableNames[TABLE_FORMURL_ID],
ID_PROJECTION, urlSelection, new String[] { url }, null,
null, null);
if (cursor.moveToFirst()) {
long urlid = cursor.getLong(cursor.getColumnIndex(ID_COL));
Cursor dataCursor = null;
try {
dataCursor = mDatabase.query(
mTableNames[TABLE_FORMDATA_ID],
new String[] { ID_COL, FORMDATA_VALUE_COL },
dataSelection,
new String[] { Long.toString(urlid), name },
null, null, null);
if (dataCursor.moveToFirst()) {
int valueCol = dataCursor.getColumnIndex(
FORMDATA_VALUE_COL);
do {
values.add(dataCursor.getString(valueCol));
} while (dataCursor.moveToNext());
}
} catch (IllegalStateException e) {
Log.e(LOGTAG, "getFormData dataCursor", e);
} finally {
if (dataCursor != null) dataCursor.close();
}
}
} catch (IllegalStateException e) {
Log.e(LOGTAG, "getFormData cursor", e);
} finally {
if (cursor != null) cursor.close();
}
return values;
}
}
/**
* Find out if there is form data saved.
*
* @return TRUE if there is form data in the database
*/
public boolean hasFormData() {
synchronized (mFormLock) {
return hasEntries(TABLE_FORMURL_ID);
}
}
/**
* Clear form database
*/
public void clearFormData() {
if (!checkInitialized()) {
return;
}
synchronized (mFormLock) {
mDatabase.delete(mTableNames[TABLE_FORMURL_ID], null, null);
mDatabase.delete(mTableNames[TABLE_FORMDATA_ID], null, null);
}
}
}