1 /* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.webkit; 18 19 import java.util.ArrayList; 20 import java.util.HashMap; 21 import java.util.Iterator; 22 import java.util.Set; 23 import java.util.Map.Entry; 24 25 import android.content.ContentValues; 26 import android.content.Context; 27 import android.database.Cursor; 28 import android.database.DatabaseUtils; 29 import android.database.sqlite.SQLiteDatabase; 30 import android.database.sqlite.SQLiteStatement; 31 import android.util.Log; 32 import android.webkit.CookieManager.Cookie; 33 import android.webkit.CacheManager.CacheResult; 34 35 public class WebViewDatabase { 36 private static final String DATABASE_FILE = "webview.db"; 37 private static final String CACHE_DATABASE_FILE = "webviewCache.db"; 38 39 // log tag 40 protected static final String LOGTAG = "webviewdatabase"; 41 42 private static final int DATABASE_VERSION = 10; 43 // 2 -> 3 Modified Cache table to allow cache of redirects 44 // 3 -> 4 Added Oma-Downloads table 45 // 4 -> 5 Modified Cache table to support persistent contentLength 46 // 5 -> 4 Removed Oma-Downoads table 47 // 5 -> 6 Add INDEX for cache table 48 // 6 -> 7 Change cache localPath from int to String 49 // 7 -> 8 Move cache to its own db 50 // 8 -> 9 Store both scheme and host when storing passwords 51 // 9 -> 10 Update httpauth table UNIQUE 52 private static final int CACHE_DATABASE_VERSION = 3; 53 // 1 -> 2 Add expires String 54 // 2 -> 3 Add content-disposition 55 56 private static WebViewDatabase mInstance = null; 57 58 private static SQLiteDatabase mDatabase = null; 59 private static SQLiteDatabase mCacheDatabase = null; 60 61 // synchronize locks 62 private final Object mCookieLock = new Object(); 63 private final Object mPasswordLock = new Object(); 64 private final Object mFormLock = new Object(); 65 private final Object mHttpAuthLock = new Object(); 66 67 private static final String mTableNames[] = { 68 "cookies", "password", "formurl", "formdata", "httpauth" 69 }; 70 71 // Table ids (they are index to mTableNames) 72 private static final int TABLE_COOKIES_ID = 0; 73 74 private static final int TABLE_PASSWORD_ID = 1; 75 76 private static final int TABLE_FORMURL_ID = 2; 77 78 private static final int TABLE_FORMDATA_ID = 3; 79 80 private static final int TABLE_HTTPAUTH_ID = 4; 81 82 // column id strings for "_id" which can be used by any table 83 private static final String ID_COL = "_id"; 84 85 private static final String[] ID_PROJECTION = new String[] { 86 "_id" 87 }; 88 89 // column id strings for "cookies" table 90 private static final String COOKIES_NAME_COL = "name"; 91 92 private static final String COOKIES_VALUE_COL = "value"; 93 94 private static final String COOKIES_DOMAIN_COL = "domain"; 95 96 private static final String COOKIES_PATH_COL = "path"; 97 98 private static final String COOKIES_EXPIRES_COL = "expires"; 99 100 private static final String COOKIES_SECURE_COL = "secure"; 101 102 // column id strings for "cache" table 103 private static final String CACHE_URL_COL = "url"; 104 105 private static final String CACHE_FILE_PATH_COL = "filepath"; 106 107 private static final String CACHE_LAST_MODIFY_COL = "lastmodify"; 108 109 private static final String CACHE_ETAG_COL = "etag"; 110 111 private static final String CACHE_EXPIRES_COL = "expires"; 112 113 private static final String CACHE_EXPIRES_STRING_COL = "expiresstring"; 114 115 private static final String CACHE_MIMETYPE_COL = "mimetype"; 116 117 private static final String CACHE_ENCODING_COL = "encoding"; 118 119 private static final String CACHE_HTTP_STATUS_COL = "httpstatus"; 120 121 private static final String CACHE_LOCATION_COL = "location"; 122 123 private static final String CACHE_CONTENTLENGTH_COL = "contentlength"; 124 125 private static final String CACHE_CONTENTDISPOSITION_COL = "contentdisposition"; 126 127 // column id strings for "password" table 128 private static final String PASSWORD_HOST_COL = "host"; 129 130 private static final String PASSWORD_USERNAME_COL = "username"; 131 132 private static final String PASSWORD_PASSWORD_COL = "password"; 133 134 // column id strings for "formurl" table 135 private static final String FORMURL_URL_COL = "url"; 136 137 // column id strings for "formdata" table 138 private static final String FORMDATA_URLID_COL = "urlid"; 139 140 private static final String FORMDATA_NAME_COL = "name"; 141 142 private static final String FORMDATA_VALUE_COL = "value"; 143 144 // column id strings for "httpauth" table 145 private static final String HTTPAUTH_HOST_COL = "host"; 146 147 private static final String HTTPAUTH_REALM_COL = "realm"; 148 149 private static final String HTTPAUTH_USERNAME_COL = "username"; 150 151 private static final String HTTPAUTH_PASSWORD_COL = "password"; 152 153 // use InsertHelper to improve insert performance by 40% 154 private static DatabaseUtils.InsertHelper mCacheInserter; 155 private static int mCacheUrlColIndex; 156 private static int mCacheFilePathColIndex; 157 private static int mCacheLastModifyColIndex; 158 private static int mCacheETagColIndex; 159 private static int mCacheExpiresColIndex; 160 private static int mCacheExpiresStringColIndex; 161 private static int mCacheMimeTypeColIndex; 162 private static int mCacheEncodingColIndex; 163 private static int mCacheHttpStatusColIndex; 164 private static int mCacheLocationColIndex; 165 private static int mCacheContentLengthColIndex; 166 private static int mCacheContentDispositionColIndex; 167 168 private static int mCacheTransactionRefcount; 169 WebViewDatabase()170 private WebViewDatabase() { 171 // Singleton only, use getInstance() 172 } 173 getInstance(Context context)174 public static synchronized WebViewDatabase getInstance(Context context) { 175 if (mInstance == null) { 176 mInstance = new WebViewDatabase(); 177 mDatabase = context.openOrCreateDatabase(DATABASE_FILE, 0, null); 178 179 // mDatabase should not be null, 180 // the only case is RequestAPI test has problem to create db 181 if (mDatabase != null && mDatabase.getVersion() != DATABASE_VERSION) { 182 mDatabase.beginTransaction(); 183 try { 184 upgradeDatabase(); 185 mDatabase.setTransactionSuccessful(); 186 } finally { 187 mDatabase.endTransaction(); 188 } 189 } 190 191 if (mDatabase != null) { 192 // use per table Mutex lock, turn off database lock, this 193 // improves performance as database's ReentrantLock is expansive 194 mDatabase.setLockingEnabled(false); 195 } 196 197 mCacheDatabase = context.openOrCreateDatabase(CACHE_DATABASE_FILE, 198 0, null); 199 200 // mCacheDatabase should not be null, 201 // the only case is RequestAPI test has problem to create db 202 if (mCacheDatabase != null 203 && mCacheDatabase.getVersion() != CACHE_DATABASE_VERSION) { 204 mCacheDatabase.beginTransaction(); 205 try { 206 upgradeCacheDatabase(); 207 bootstrapCacheDatabase(); 208 mCacheDatabase.setTransactionSuccessful(); 209 } finally { 210 mCacheDatabase.endTransaction(); 211 } 212 // Erase the files from the file system in the 213 // case that the database was updated and the 214 // there were existing cache content 215 CacheManager.removeAllCacheFiles(); 216 } 217 218 if (mCacheDatabase != null) { 219 // use InsertHelper for faster insertion 220 mCacheInserter = new DatabaseUtils.InsertHelper(mCacheDatabase, 221 "cache"); 222 mCacheUrlColIndex = mCacheInserter 223 .getColumnIndex(CACHE_URL_COL); 224 mCacheFilePathColIndex = mCacheInserter 225 .getColumnIndex(CACHE_FILE_PATH_COL); 226 mCacheLastModifyColIndex = mCacheInserter 227 .getColumnIndex(CACHE_LAST_MODIFY_COL); 228 mCacheETagColIndex = mCacheInserter 229 .getColumnIndex(CACHE_ETAG_COL); 230 mCacheExpiresColIndex = mCacheInserter 231 .getColumnIndex(CACHE_EXPIRES_COL); 232 mCacheExpiresStringColIndex = mCacheInserter 233 .getColumnIndex(CACHE_EXPIRES_STRING_COL); 234 mCacheMimeTypeColIndex = mCacheInserter 235 .getColumnIndex(CACHE_MIMETYPE_COL); 236 mCacheEncodingColIndex = mCacheInserter 237 .getColumnIndex(CACHE_ENCODING_COL); 238 mCacheHttpStatusColIndex = mCacheInserter 239 .getColumnIndex(CACHE_HTTP_STATUS_COL); 240 mCacheLocationColIndex = mCacheInserter 241 .getColumnIndex(CACHE_LOCATION_COL); 242 mCacheContentLengthColIndex = mCacheInserter 243 .getColumnIndex(CACHE_CONTENTLENGTH_COL); 244 mCacheContentDispositionColIndex = mCacheInserter 245 .getColumnIndex(CACHE_CONTENTDISPOSITION_COL); 246 } 247 } 248 249 return mInstance; 250 } 251 upgradeDatabase()252 private static void upgradeDatabase() { 253 int oldVersion = mDatabase.getVersion(); 254 if (oldVersion != 0) { 255 Log.i(LOGTAG, "Upgrading database from version " 256 + oldVersion + " to " 257 + DATABASE_VERSION + ", which will destroy old data"); 258 } 259 boolean justPasswords = 8 == oldVersion && 9 == DATABASE_VERSION; 260 boolean justAuth = 9 == oldVersion && 10 == DATABASE_VERSION; 261 if (justAuth) { 262 mDatabase.execSQL("DROP TABLE IF EXISTS " 263 + mTableNames[TABLE_HTTPAUTH_ID]); 264 mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_HTTPAUTH_ID] 265 + " (" + ID_COL + " INTEGER PRIMARY KEY, " 266 + HTTPAUTH_HOST_COL + " TEXT, " + HTTPAUTH_REALM_COL 267 + " TEXT, " + HTTPAUTH_USERNAME_COL + " TEXT, " 268 + HTTPAUTH_PASSWORD_COL + " TEXT," + " UNIQUE (" 269 + HTTPAUTH_HOST_COL + ", " + HTTPAUTH_REALM_COL 270 + ") ON CONFLICT REPLACE);"); 271 return; 272 } 273 274 if (!justPasswords) { 275 mDatabase.execSQL("DROP TABLE IF EXISTS " 276 + mTableNames[TABLE_COOKIES_ID]); 277 mDatabase.execSQL("DROP TABLE IF EXISTS cache"); 278 mDatabase.execSQL("DROP TABLE IF EXISTS " 279 + mTableNames[TABLE_FORMURL_ID]); 280 mDatabase.execSQL("DROP TABLE IF EXISTS " 281 + mTableNames[TABLE_FORMDATA_ID]); 282 mDatabase.execSQL("DROP TABLE IF EXISTS " 283 + mTableNames[TABLE_HTTPAUTH_ID]); 284 } 285 mDatabase.execSQL("DROP TABLE IF EXISTS " 286 + mTableNames[TABLE_PASSWORD_ID]); 287 288 mDatabase.setVersion(DATABASE_VERSION); 289 290 if (!justPasswords) { 291 // cookies 292 mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_COOKIES_ID] 293 + " (" + ID_COL + " INTEGER PRIMARY KEY, " 294 + COOKIES_NAME_COL + " TEXT, " + COOKIES_VALUE_COL 295 + " TEXT, " + COOKIES_DOMAIN_COL + " TEXT, " 296 + COOKIES_PATH_COL + " TEXT, " + COOKIES_EXPIRES_COL 297 + " INTEGER, " + COOKIES_SECURE_COL + " INTEGER" + ");"); 298 mDatabase.execSQL("CREATE INDEX cookiesIndex ON " 299 + mTableNames[TABLE_COOKIES_ID] + " (path)"); 300 301 // formurl 302 mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_FORMURL_ID] 303 + " (" + ID_COL + " INTEGER PRIMARY KEY, " + FORMURL_URL_COL 304 + " TEXT" + ");"); 305 306 // formdata 307 mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_FORMDATA_ID] 308 + " (" + ID_COL + " INTEGER PRIMARY KEY, " 309 + FORMDATA_URLID_COL + " INTEGER, " + FORMDATA_NAME_COL 310 + " TEXT, " + FORMDATA_VALUE_COL + " TEXT," + " UNIQUE (" 311 + FORMDATA_URLID_COL + ", " + FORMDATA_NAME_COL + ", " 312 + FORMDATA_VALUE_COL + ") ON CONFLICT IGNORE);"); 313 314 // httpauth 315 mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_HTTPAUTH_ID] 316 + " (" + ID_COL + " INTEGER PRIMARY KEY, " 317 + HTTPAUTH_HOST_COL + " TEXT, " + HTTPAUTH_REALM_COL 318 + " TEXT, " + HTTPAUTH_USERNAME_COL + " TEXT, " 319 + HTTPAUTH_PASSWORD_COL + " TEXT," + " UNIQUE (" 320 + HTTPAUTH_HOST_COL + ", " + HTTPAUTH_REALM_COL 321 + ") ON CONFLICT REPLACE);"); 322 } 323 // passwords 324 mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_PASSWORD_ID] 325 + " (" + ID_COL + " INTEGER PRIMARY KEY, " 326 + PASSWORD_HOST_COL + " TEXT, " + PASSWORD_USERNAME_COL 327 + " TEXT, " + PASSWORD_PASSWORD_COL + " TEXT," + " UNIQUE (" 328 + PASSWORD_HOST_COL + ", " + PASSWORD_USERNAME_COL 329 + ") ON CONFLICT REPLACE);"); 330 } 331 upgradeCacheDatabase()332 private static void upgradeCacheDatabase() { 333 int oldVersion = mCacheDatabase.getVersion(); 334 if (oldVersion != 0) { 335 Log.i(LOGTAG, "Upgrading cache database from version " 336 + oldVersion + " to " 337 + DATABASE_VERSION + ", which will destroy all old data"); 338 } 339 mCacheDatabase.execSQL("DROP TABLE IF EXISTS cache"); 340 mCacheDatabase.setVersion(CACHE_DATABASE_VERSION); 341 } 342 bootstrapCacheDatabase()343 private static void bootstrapCacheDatabase() { 344 if (mCacheDatabase != null) { 345 mCacheDatabase.execSQL("CREATE TABLE cache" 346 + " (" + ID_COL + " INTEGER PRIMARY KEY, " + CACHE_URL_COL 347 + " TEXT, " + CACHE_FILE_PATH_COL + " TEXT, " 348 + CACHE_LAST_MODIFY_COL + " TEXT, " + CACHE_ETAG_COL 349 + " TEXT, " + CACHE_EXPIRES_COL + " INTEGER, " 350 + CACHE_EXPIRES_STRING_COL + " TEXT, " 351 + CACHE_MIMETYPE_COL + " TEXT, " + CACHE_ENCODING_COL 352 + " TEXT," + CACHE_HTTP_STATUS_COL + " INTEGER, " 353 + CACHE_LOCATION_COL + " TEXT, " + CACHE_CONTENTLENGTH_COL 354 + " INTEGER, " + CACHE_CONTENTDISPOSITION_COL + " TEXT, " 355 + " UNIQUE (" + CACHE_URL_COL + ") ON CONFLICT REPLACE);"); 356 mCacheDatabase.execSQL("CREATE INDEX cacheUrlIndex ON cache (" 357 + CACHE_URL_COL + ")"); 358 } 359 } 360 hasEntries(int tableId)361 private boolean hasEntries(int tableId) { 362 if (mDatabase == null) { 363 return false; 364 } 365 366 Cursor cursor = mDatabase.query(mTableNames[tableId], ID_PROJECTION, 367 null, null, null, null, null); 368 boolean ret = cursor.moveToFirst() == true; 369 cursor.close(); 370 return ret; 371 } 372 373 // 374 // cookies functions 375 // 376 377 /** 378 * Get cookies in the format of CookieManager.Cookie inside an ArrayList for 379 * a given domain 380 * 381 * @return ArrayList<Cookie> If nothing is found, return an empty list. 382 */ getCookiesForDomain(String domain)383 ArrayList<Cookie> getCookiesForDomain(String domain) { 384 ArrayList<Cookie> list = new ArrayList<Cookie>(); 385 if (domain == null || mDatabase == null) { 386 return list; 387 } 388 389 synchronized (mCookieLock) { 390 final String[] columns = new String[] { 391 ID_COL, COOKIES_DOMAIN_COL, COOKIES_PATH_COL, 392 COOKIES_NAME_COL, COOKIES_VALUE_COL, COOKIES_EXPIRES_COL, 393 COOKIES_SECURE_COL 394 }; 395 final String selection = "(" + COOKIES_DOMAIN_COL 396 + " GLOB '*' || ?)"; 397 Cursor cursor = mDatabase.query(mTableNames[TABLE_COOKIES_ID], 398 columns, selection, new String[] { domain }, null, null, 399 null); 400 if (cursor.moveToFirst()) { 401 int domainCol = cursor.getColumnIndex(COOKIES_DOMAIN_COL); 402 int pathCol = cursor.getColumnIndex(COOKIES_PATH_COL); 403 int nameCol = cursor.getColumnIndex(COOKIES_NAME_COL); 404 int valueCol = cursor.getColumnIndex(COOKIES_VALUE_COL); 405 int expiresCol = cursor.getColumnIndex(COOKIES_EXPIRES_COL); 406 int secureCol = cursor.getColumnIndex(COOKIES_SECURE_COL); 407 do { 408 Cookie cookie = new Cookie(); 409 cookie.domain = cursor.getString(domainCol); 410 cookie.path = cursor.getString(pathCol); 411 cookie.name = cursor.getString(nameCol); 412 cookie.value = cursor.getString(valueCol); 413 if (cursor.isNull(expiresCol)) { 414 cookie.expires = -1; 415 } else { 416 cookie.expires = cursor.getLong(expiresCol); 417 } 418 cookie.secure = cursor.getShort(secureCol) != 0; 419 cookie.mode = Cookie.MODE_NORMAL; 420 list.add(cookie); 421 } while (cursor.moveToNext()); 422 } 423 cursor.close(); 424 return list; 425 } 426 } 427 428 /** 429 * Delete cookies which matches (domain, path, name). 430 * 431 * @param domain If it is null, nothing happens. 432 * @param path If it is null, all the cookies match (domain) will be 433 * deleted. 434 * @param name If it is null, all the cookies match (domain, path) will be 435 * deleted. 436 */ deleteCookies(String domain, String path, String name)437 void deleteCookies(String domain, String path, String name) { 438 if (domain == null || mDatabase == null) { 439 return; 440 } 441 442 synchronized (mCookieLock) { 443 final String where = "(" + COOKIES_DOMAIN_COL + " == ?) AND (" 444 + COOKIES_PATH_COL + " == ?) AND (" + COOKIES_NAME_COL 445 + " == ?)"; 446 mDatabase.delete(mTableNames[TABLE_COOKIES_ID], where, 447 new String[] { domain, path, name }); 448 } 449 } 450 451 /** 452 * Add a cookie to the database 453 * 454 * @param cookie 455 */ addCookie(Cookie cookie)456 void addCookie(Cookie cookie) { 457 if (cookie.domain == null || cookie.path == null || cookie.name == null 458 || mDatabase == null) { 459 return; 460 } 461 462 synchronized (mCookieLock) { 463 ContentValues cookieVal = new ContentValues(); 464 cookieVal.put(COOKIES_DOMAIN_COL, cookie.domain); 465 cookieVal.put(COOKIES_PATH_COL, cookie.path); 466 cookieVal.put(COOKIES_NAME_COL, cookie.name); 467 cookieVal.put(COOKIES_VALUE_COL, cookie.value); 468 if (cookie.expires != -1) { 469 cookieVal.put(COOKIES_EXPIRES_COL, cookie.expires); 470 } 471 cookieVal.put(COOKIES_SECURE_COL, cookie.secure); 472 mDatabase.insert(mTableNames[TABLE_COOKIES_ID], null, cookieVal); 473 } 474 } 475 476 /** 477 * Whether there is any cookies in the database 478 * 479 * @return TRUE if there is cookie. 480 */ hasCookies()481 boolean hasCookies() { 482 synchronized (mCookieLock) { 483 return hasEntries(TABLE_COOKIES_ID); 484 } 485 } 486 487 /** 488 * Clear cookie database 489 */ clearCookies()490 void clearCookies() { 491 if (mDatabase == null) { 492 return; 493 } 494 495 synchronized (mCookieLock) { 496 mDatabase.delete(mTableNames[TABLE_COOKIES_ID], null, null); 497 } 498 } 499 500 /** 501 * Clear session cookies, which means cookie doesn't have EXPIRES. 502 */ clearSessionCookies()503 void clearSessionCookies() { 504 if (mDatabase == null) { 505 return; 506 } 507 508 final String sessionExpired = COOKIES_EXPIRES_COL + " ISNULL"; 509 synchronized (mCookieLock) { 510 mDatabase.delete(mTableNames[TABLE_COOKIES_ID], sessionExpired, 511 null); 512 } 513 } 514 515 /** 516 * Clear expired cookies 517 * 518 * @param now Time for now 519 */ clearExpiredCookies(long now)520 void clearExpiredCookies(long now) { 521 if (mDatabase == null) { 522 return; 523 } 524 525 final String expires = COOKIES_EXPIRES_COL + " <= ?"; 526 synchronized (mCookieLock) { 527 mDatabase.delete(mTableNames[TABLE_COOKIES_ID], expires, 528 new String[] { Long.toString(now) }); 529 } 530 } 531 532 // 533 // cache functions, can only be called from WebCoreThread 534 // 535 startCacheTransaction()536 boolean startCacheTransaction() { 537 if (++mCacheTransactionRefcount == 1) { 538 mCacheDatabase.beginTransaction(); 539 return true; 540 } 541 return false; 542 } 543 endCacheTransaction()544 boolean endCacheTransaction() { 545 if (--mCacheTransactionRefcount == 0) { 546 try { 547 mCacheDatabase.setTransactionSuccessful(); 548 } finally { 549 mCacheDatabase.endTransaction(); 550 } 551 return true; 552 } 553 return false; 554 } 555 556 /** 557 * Get a cache item. 558 * 559 * @param url The url 560 * @return CacheResult The CacheManager.CacheResult 561 */ getCache(String url)562 CacheResult getCache(String url) { 563 if (url == null || mCacheDatabase == null) { 564 return null; 565 } 566 567 Cursor cursor = mCacheDatabase.rawQuery("SELECT filepath, lastmodify, etag, expires, " 568 + "expiresstring, mimetype, encoding, httpstatus, location, contentlength, " 569 + "contentdisposition FROM cache WHERE url = ?", 570 new String[] { url }); 571 572 try { 573 if (cursor.moveToFirst()) { 574 CacheResult ret = new CacheResult(); 575 ret.localPath = cursor.getString(0); 576 ret.lastModified = cursor.getString(1); 577 ret.etag = cursor.getString(2); 578 ret.expires = cursor.getLong(3); 579 ret.expiresString = cursor.getString(4); 580 ret.mimeType = cursor.getString(5); 581 ret.encoding = cursor.getString(6); 582 ret.httpStatusCode = cursor.getInt(7); 583 ret.location = cursor.getString(8); 584 ret.contentLength = cursor.getLong(9); 585 ret.contentdisposition = cursor.getString(10); 586 return ret; 587 } 588 } finally { 589 if (cursor != null) cursor.close(); 590 } 591 return null; 592 } 593 594 /** 595 * Remove a cache item. 596 * 597 * @param url The url 598 */ removeCache(String url)599 void removeCache(String url) { 600 if (url == null || mCacheDatabase == null) { 601 return; 602 } 603 604 mCacheDatabase.execSQL("DELETE FROM cache WHERE url = ?", new String[] { url }); 605 } 606 607 /** 608 * Add or update a cache. CACHE_URL_COL is unique in the table. 609 * 610 * @param url The url 611 * @param c The CacheManager.CacheResult 612 */ addCache(String url, CacheResult c)613 void addCache(String url, CacheResult c) { 614 if (url == null || mCacheDatabase == null) { 615 return; 616 } 617 618 mCacheInserter.prepareForInsert(); 619 mCacheInserter.bind(mCacheUrlColIndex, url); 620 mCacheInserter.bind(mCacheFilePathColIndex, c.localPath); 621 mCacheInserter.bind(mCacheLastModifyColIndex, c.lastModified); 622 mCacheInserter.bind(mCacheETagColIndex, c.etag); 623 mCacheInserter.bind(mCacheExpiresColIndex, c.expires); 624 mCacheInserter.bind(mCacheExpiresStringColIndex, c.expiresString); 625 mCacheInserter.bind(mCacheMimeTypeColIndex, c.mimeType); 626 mCacheInserter.bind(mCacheEncodingColIndex, c.encoding); 627 mCacheInserter.bind(mCacheHttpStatusColIndex, c.httpStatusCode); 628 mCacheInserter.bind(mCacheLocationColIndex, c.location); 629 mCacheInserter.bind(mCacheContentLengthColIndex, c.contentLength); 630 mCacheInserter.bind(mCacheContentDispositionColIndex, 631 c.contentdisposition); 632 mCacheInserter.execute(); 633 } 634 635 /** 636 * Clear cache database 637 */ clearCache()638 void clearCache() { 639 if (mCacheDatabase == null) { 640 return; 641 } 642 643 mCacheDatabase.delete("cache", null, null); 644 } 645 hasCache()646 boolean hasCache() { 647 if (mCacheDatabase == null) { 648 return false; 649 } 650 651 Cursor cursor = mCacheDatabase.query("cache", ID_PROJECTION, 652 null, null, null, null, null); 653 boolean ret = cursor.moveToFirst() == true; 654 cursor.close(); 655 return ret; 656 } 657 getCacheTotalSize()658 long getCacheTotalSize() { 659 long size = 0; 660 Cursor cursor = mCacheDatabase.rawQuery( 661 "SELECT SUM(contentlength) as sum FROM cache", null); 662 if (cursor.moveToFirst()) { 663 size = cursor.getLong(0); 664 } 665 cursor.close(); 666 return size; 667 } 668 trimCache(long amount)669 ArrayList<String> trimCache(long amount) { 670 ArrayList<String> pathList = new ArrayList<String>(100); 671 Cursor cursor = mCacheDatabase.rawQuery( 672 "SELECT contentlength, filepath FROM cache ORDER BY expires ASC", 673 null); 674 if (cursor.moveToFirst()) { 675 int batchSize = 100; 676 StringBuilder pathStr = new StringBuilder(20 + 16 * batchSize); 677 pathStr.append("DELETE FROM cache WHERE filepath IN (?"); 678 for (int i = 1; i < batchSize; i++) { 679 pathStr.append(", ?"); 680 } 681 pathStr.append(")"); 682 SQLiteStatement statement = mCacheDatabase.compileStatement(pathStr 683 .toString()); 684 // as bindString() uses 1-based index, initialize index to 1 685 int index = 1; 686 do { 687 long length = cursor.getLong(0); 688 if (length == 0) { 689 continue; 690 } 691 amount -= length; 692 String filePath = cursor.getString(1); 693 statement.bindString(index, filePath); 694 pathList.add(filePath); 695 if (index++ == batchSize) { 696 statement.execute(); 697 statement.clearBindings(); 698 index = 1; 699 } 700 } while (cursor.moveToNext() && amount > 0); 701 if (index > 1) { 702 // there may be old bindings from the previous statement if 703 // index is less than batchSize, which is Ok. 704 statement.execute(); 705 } 706 statement.close(); 707 } 708 cursor.close(); 709 return pathList; 710 } 711 712 // 713 // password functions 714 // 715 716 /** 717 * Set password. Tuple (PASSWORD_HOST_COL, PASSWORD_USERNAME_COL) is unique. 718 * 719 * @param schemePlusHost The scheme and host for the password 720 * @param username The username for the password. If it is null, it means 721 * password can't be saved. 722 * @param password The password 723 */ setUsernamePassword(String schemePlusHost, String username, String password)724 void setUsernamePassword(String schemePlusHost, String username, 725 String password) { 726 if (schemePlusHost == null || mDatabase == null) { 727 return; 728 } 729 730 synchronized (mPasswordLock) { 731 final ContentValues c = new ContentValues(); 732 c.put(PASSWORD_HOST_COL, schemePlusHost); 733 c.put(PASSWORD_USERNAME_COL, username); 734 c.put(PASSWORD_PASSWORD_COL, password); 735 mDatabase.insert(mTableNames[TABLE_PASSWORD_ID], PASSWORD_HOST_COL, 736 c); 737 } 738 } 739 740 /** 741 * Retrieve the username and password for a given host 742 * 743 * @param schemePlusHost The scheme and host which passwords applies to 744 * @return String[] if found, String[0] is username, which can be null and 745 * String[1] is password. Return null if it can't find anything. 746 */ getUsernamePassword(String schemePlusHost)747 String[] getUsernamePassword(String schemePlusHost) { 748 if (schemePlusHost == null || mDatabase == null) { 749 return null; 750 } 751 752 final String[] columns = new String[] { 753 PASSWORD_USERNAME_COL, PASSWORD_PASSWORD_COL 754 }; 755 final String selection = "(" + PASSWORD_HOST_COL + " == ?)"; 756 synchronized (mPasswordLock) { 757 String[] ret = null; 758 Cursor cursor = mDatabase.query(mTableNames[TABLE_PASSWORD_ID], 759 columns, selection, new String[] { schemePlusHost }, null, 760 null, null); 761 if (cursor.moveToFirst()) { 762 ret = new String[2]; 763 ret[0] = cursor.getString( 764 cursor.getColumnIndex(PASSWORD_USERNAME_COL)); 765 ret[1] = cursor.getString( 766 cursor.getColumnIndex(PASSWORD_PASSWORD_COL)); 767 } 768 cursor.close(); 769 return ret; 770 } 771 } 772 773 /** 774 * Find out if there are any passwords saved. 775 * 776 * @return TRUE if there is passwords saved 777 */ hasUsernamePassword()778 public boolean hasUsernamePassword() { 779 synchronized (mPasswordLock) { 780 return hasEntries(TABLE_PASSWORD_ID); 781 } 782 } 783 784 /** 785 * Clear password database 786 */ clearUsernamePassword()787 public void clearUsernamePassword() { 788 if (mDatabase == null) { 789 return; 790 } 791 792 synchronized (mPasswordLock) { 793 mDatabase.delete(mTableNames[TABLE_PASSWORD_ID], null, null); 794 } 795 } 796 797 // 798 // http authentication password functions 799 // 800 801 /** 802 * Set HTTP authentication password. Tuple (HTTPAUTH_HOST_COL, 803 * HTTPAUTH_REALM_COL, HTTPAUTH_USERNAME_COL) is unique. 804 * 805 * @param host The host for the password 806 * @param realm The realm for the password 807 * @param username The username for the password. If it is null, it means 808 * password can't be saved. 809 * @param password The password 810 */ setHttpAuthUsernamePassword(String host, String realm, String username, String password)811 void setHttpAuthUsernamePassword(String host, String realm, String username, 812 String password) { 813 if (host == null || realm == null || mDatabase == null) { 814 return; 815 } 816 817 synchronized (mHttpAuthLock) { 818 final ContentValues c = new ContentValues(); 819 c.put(HTTPAUTH_HOST_COL, host); 820 c.put(HTTPAUTH_REALM_COL, realm); 821 c.put(HTTPAUTH_USERNAME_COL, username); 822 c.put(HTTPAUTH_PASSWORD_COL, password); 823 mDatabase.insert(mTableNames[TABLE_HTTPAUTH_ID], HTTPAUTH_HOST_COL, 824 c); 825 } 826 } 827 828 /** 829 * Retrieve the HTTP authentication username and password for a given 830 * host+realm pair 831 * 832 * @param host The host the password applies to 833 * @param realm The realm the password applies to 834 * @return String[] if found, String[0] is username, which can be null and 835 * String[1] is password. Return null if it can't find anything. 836 */ getHttpAuthUsernamePassword(String host, String realm)837 String[] getHttpAuthUsernamePassword(String host, String realm) { 838 if (host == null || realm == null || mDatabase == null){ 839 return null; 840 } 841 842 final String[] columns = new String[] { 843 HTTPAUTH_USERNAME_COL, HTTPAUTH_PASSWORD_COL 844 }; 845 final String selection = "(" + HTTPAUTH_HOST_COL + " == ?) AND (" 846 + HTTPAUTH_REALM_COL + " == ?)"; 847 synchronized (mHttpAuthLock) { 848 String[] ret = null; 849 Cursor cursor = mDatabase.query(mTableNames[TABLE_HTTPAUTH_ID], 850 columns, selection, new String[] { host, realm }, null, 851 null, null); 852 if (cursor.moveToFirst()) { 853 ret = new String[2]; 854 ret[0] = cursor.getString( 855 cursor.getColumnIndex(HTTPAUTH_USERNAME_COL)); 856 ret[1] = cursor.getString( 857 cursor.getColumnIndex(HTTPAUTH_PASSWORD_COL)); 858 } 859 cursor.close(); 860 return ret; 861 } 862 } 863 864 /** 865 * Find out if there are any HTTP authentication passwords saved. 866 * 867 * @return TRUE if there are passwords saved 868 */ hasHttpAuthUsernamePassword()869 public boolean hasHttpAuthUsernamePassword() { 870 synchronized (mHttpAuthLock) { 871 return hasEntries(TABLE_HTTPAUTH_ID); 872 } 873 } 874 875 /** 876 * Clear HTTP authentication password database 877 */ clearHttpAuthUsernamePassword()878 public void clearHttpAuthUsernamePassword() { 879 if (mDatabase == null) { 880 return; 881 } 882 883 synchronized (mHttpAuthLock) { 884 mDatabase.delete(mTableNames[TABLE_HTTPAUTH_ID], null, null); 885 } 886 } 887 888 // 889 // form data functions 890 // 891 892 /** 893 * Set form data for a site. Tuple (FORMDATA_URLID_COL, FORMDATA_NAME_COL, 894 * FORMDATA_VALUE_COL) is unique 895 * 896 * @param url The url of the site 897 * @param formdata The form data in HashMap 898 */ setFormData(String url, HashMap<String, String> formdata)899 void setFormData(String url, HashMap<String, String> formdata) { 900 if (url == null || formdata == null || mDatabase == null) { 901 return; 902 } 903 904 final String selection = "(" + FORMURL_URL_COL + " == ?)"; 905 synchronized (mFormLock) { 906 long urlid = -1; 907 Cursor cursor = mDatabase.query(mTableNames[TABLE_FORMURL_ID], 908 ID_PROJECTION, selection, new String[] { url }, null, null, 909 null); 910 if (cursor.moveToFirst()) { 911 urlid = cursor.getLong(cursor.getColumnIndex(ID_COL)); 912 } else { 913 ContentValues c = new ContentValues(); 914 c.put(FORMURL_URL_COL, url); 915 urlid = mDatabase.insert( 916 mTableNames[TABLE_FORMURL_ID], null, c); 917 } 918 cursor.close(); 919 if (urlid >= 0) { 920 Set<Entry<String, String>> set = formdata.entrySet(); 921 Iterator<Entry<String, String>> iter = set.iterator(); 922 ContentValues map = new ContentValues(); 923 map.put(FORMDATA_URLID_COL, urlid); 924 while (iter.hasNext()) { 925 Entry<String, String> entry = iter.next(); 926 map.put(FORMDATA_NAME_COL, entry.getKey()); 927 map.put(FORMDATA_VALUE_COL, entry.getValue()); 928 mDatabase.insert(mTableNames[TABLE_FORMDATA_ID], null, map); 929 } 930 } 931 } 932 } 933 934 /** 935 * Get all the values for a form entry with "name" in a given site 936 * 937 * @param url The url of the site 938 * @param name The name of the form entry 939 * @return A list of values. Return empty list if nothing is found. 940 */ getFormData(String url, String name)941 ArrayList<String> getFormData(String url, String name) { 942 ArrayList<String> values = new ArrayList<String>(); 943 if (url == null || name == null || mDatabase == null) { 944 return values; 945 } 946 947 final String urlSelection = "(" + FORMURL_URL_COL + " == ?)"; 948 final String dataSelection = "(" + FORMDATA_URLID_COL + " == ?) AND (" 949 + FORMDATA_NAME_COL + " == ?)"; 950 synchronized (mFormLock) { 951 Cursor cursor = mDatabase.query(mTableNames[TABLE_FORMURL_ID], 952 ID_PROJECTION, urlSelection, new String[] { url }, null, 953 null, null); 954 if (cursor.moveToFirst()) { 955 long urlid = cursor.getLong(cursor.getColumnIndex(ID_COL)); 956 Cursor dataCursor = mDatabase.query( 957 mTableNames[TABLE_FORMDATA_ID], 958 new String[] { ID_COL, FORMDATA_VALUE_COL }, 959 dataSelection, 960 new String[] { Long.toString(urlid), name }, null, 961 null, null); 962 if (dataCursor.moveToFirst()) { 963 int valueCol = 964 dataCursor.getColumnIndex(FORMDATA_VALUE_COL); 965 do { 966 values.add(dataCursor.getString(valueCol)); 967 } while (dataCursor.moveToNext()); 968 } 969 dataCursor.close(); 970 } 971 cursor.close(); 972 return values; 973 } 974 } 975 976 /** 977 * Find out if there is form data saved. 978 * 979 * @return TRUE if there is form data in the database 980 */ hasFormData()981 public boolean hasFormData() { 982 synchronized (mFormLock) { 983 return hasEntries(TABLE_FORMURL_ID); 984 } 985 } 986 987 /** 988 * Clear form database 989 */ clearFormData()990 public void clearFormData() { 991 if (mDatabase == null) { 992 return; 993 } 994 995 synchronized (mFormLock) { 996 mDatabase.delete(mTableNames[TABLE_FORMURL_ID], null, null); 997 mDatabase.delete(mTableNames[TABLE_FORMDATA_ID], null, null); 998 } 999 } 1000 } 1001