WebViewDatabase.java revision f10daf647004e84235d240bb6471aa0dab61a493
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 17package android.webkit; 18 19import java.util.ArrayList; 20import java.util.HashMap; 21import java.util.Iterator; 22import java.util.List; 23import java.util.Set; 24import java.util.Map.Entry; 25 26import android.content.ContentValues; 27import android.content.Context; 28import android.database.Cursor; 29import android.database.DatabaseUtils; 30import android.database.sqlite.SQLiteDatabase; 31import android.database.sqlite.SQLiteException; 32import android.database.sqlite.SQLiteStatement; 33import android.util.Log; 34import android.webkit.CookieManager.Cookie; 35import android.webkit.CacheManager.CacheResult; 36import android.webkit.JniUtil; 37 38public class WebViewDatabase { 39 private static final String DATABASE_FILE = "webview.db"; 40 private static final String CACHE_DATABASE_FILE = "webviewCache.db"; 41 42 // log tag 43 protected static final String LOGTAG = "webviewdatabase"; 44 45 private static final int DATABASE_VERSION = 10; 46 // 2 -> 3 Modified Cache table to allow cache of redirects 47 // 3 -> 4 Added Oma-Downloads table 48 // 4 -> 5 Modified Cache table to support persistent contentLength 49 // 5 -> 4 Removed Oma-Downoads table 50 // 5 -> 6 Add INDEX for cache table 51 // 6 -> 7 Change cache localPath from int to String 52 // 7 -> 8 Move cache to its own db 53 // 8 -> 9 Store both scheme and host when storing passwords 54 // 9 -> 10 Update httpauth table UNIQUE 55 private static final int CACHE_DATABASE_VERSION = 4; 56 // 1 -> 2 Add expires String 57 // 2 -> 3 Add content-disposition 58 // 3 -> 4 Add crossdomain (For x-permitted-cross-domain-policies header) 59 60 private static WebViewDatabase mInstance = null; 61 62 private static SQLiteDatabase mDatabase = null; 63 private static SQLiteDatabase mCacheDatabase = null; 64 65 // synchronize locks 66 private final Object mCookieLock = new Object(); 67 private final Object mPasswordLock = new Object(); 68 private final Object mFormLock = new Object(); 69 private final Object mHttpAuthLock = new Object(); 70 71 // TODO: The Chromium HTTP stack handles cookies independently. 72 // We should consider removing the cookies table if and when we switch to 73 // the Chromium HTTP stack for good. 74 private static final String mTableNames[] = { 75 "cookies", "password", "formurl", "formdata", "httpauth" 76 }; 77 78 // Table ids (they are index to mTableNames) 79 private static final int TABLE_COOKIES_ID = 0; 80 81 private static final int TABLE_PASSWORD_ID = 1; 82 83 private static final int TABLE_FORMURL_ID = 2; 84 85 private static final int TABLE_FORMDATA_ID = 3; 86 87 private static final int TABLE_HTTPAUTH_ID = 4; 88 89 // column id strings for "_id" which can be used by any table 90 private static final String ID_COL = "_id"; 91 92 private static final String[] ID_PROJECTION = new String[] { 93 "_id" 94 }; 95 96 // column id strings for "cookies" table 97 private static final String COOKIES_NAME_COL = "name"; 98 99 private static final String COOKIES_VALUE_COL = "value"; 100 101 private static final String COOKIES_DOMAIN_COL = "domain"; 102 103 private static final String COOKIES_PATH_COL = "path"; 104 105 private static final String COOKIES_EXPIRES_COL = "expires"; 106 107 private static final String COOKIES_SECURE_COL = "secure"; 108 109 // column id strings for "cache" table 110 private static final String CACHE_URL_COL = "url"; 111 112 private static final String CACHE_FILE_PATH_COL = "filepath"; 113 114 private static final String CACHE_LAST_MODIFY_COL = "lastmodify"; 115 116 private static final String CACHE_ETAG_COL = "etag"; 117 118 private static final String CACHE_EXPIRES_COL = "expires"; 119 120 private static final String CACHE_EXPIRES_STRING_COL = "expiresstring"; 121 122 private static final String CACHE_MIMETYPE_COL = "mimetype"; 123 124 private static final String CACHE_ENCODING_COL = "encoding"; 125 126 private static final String CACHE_HTTP_STATUS_COL = "httpstatus"; 127 128 private static final String CACHE_LOCATION_COL = "location"; 129 130 private static final String CACHE_CONTENTLENGTH_COL = "contentlength"; 131 132 private static final String CACHE_CONTENTDISPOSITION_COL = "contentdisposition"; 133 134 private static final String CACHE_CROSSDOMAIN_COL = "crossdomain"; 135 136 // column id strings for "password" table 137 private static final String PASSWORD_HOST_COL = "host"; 138 139 private static final String PASSWORD_USERNAME_COL = "username"; 140 141 private static final String PASSWORD_PASSWORD_COL = "password"; 142 143 // column id strings for "formurl" table 144 private static final String FORMURL_URL_COL = "url"; 145 146 // column id strings for "formdata" table 147 private static final String FORMDATA_URLID_COL = "urlid"; 148 149 private static final String FORMDATA_NAME_COL = "name"; 150 151 private static final String FORMDATA_VALUE_COL = "value"; 152 153 // column id strings for "httpauth" table 154 private static final String HTTPAUTH_HOST_COL = "host"; 155 156 private static final String HTTPAUTH_REALM_COL = "realm"; 157 158 private static final String HTTPAUTH_USERNAME_COL = "username"; 159 160 private static final String HTTPAUTH_PASSWORD_COL = "password"; 161 162 // use InsertHelper to improve insert performance by 40% 163 private static DatabaseUtils.InsertHelper mCacheInserter; 164 private static int mCacheUrlColIndex; 165 private static int mCacheFilePathColIndex; 166 private static int mCacheLastModifyColIndex; 167 private static int mCacheETagColIndex; 168 private static int mCacheExpiresColIndex; 169 private static int mCacheExpiresStringColIndex; 170 private static int mCacheMimeTypeColIndex; 171 private static int mCacheEncodingColIndex; 172 private static int mCacheHttpStatusColIndex; 173 private static int mCacheLocationColIndex; 174 private static int mCacheContentLengthColIndex; 175 private static int mCacheContentDispositionColIndex; 176 private static int mCacheCrossDomainColIndex; 177 178 private static int mCacheTransactionRefcount; 179 180 // Initially true until the background thread completes. 181 private boolean mInitialized = false; 182 183 private WebViewDatabase(final Context context) { 184 new Thread() { 185 @Override 186 public void run() { 187 init(context); 188 } 189 }.start(); 190 191 // Singleton only, use getInstance() 192 } 193 194 public static synchronized WebViewDatabase getInstance(Context context) { 195 if (mInstance == null) { 196 mInstance = new WebViewDatabase(context); 197 } 198 return mInstance; 199 } 200 201 private synchronized void init(Context context) { 202 if (mInitialized) { 203 return; 204 } 205 206 initDatabase(context); 207 if (!JniUtil.useChromiumHttpStack()) { 208 initCacheDatabase(context); 209 } 210 211 // Thread done, notify. 212 mInitialized = true; 213 notify(); 214 } 215 216 private void initDatabase(Context context) { 217 try { 218 mDatabase = context.openOrCreateDatabase(DATABASE_FILE, 0, null); 219 } catch (SQLiteException e) { 220 // try again by deleting the old db and create a new one 221 if (context.deleteDatabase(DATABASE_FILE)) { 222 mDatabase = context.openOrCreateDatabase(DATABASE_FILE, 0, 223 null); 224 } 225 } 226 mDatabase.enableWriteAheadLogging(); 227 228 // mDatabase should not be null, 229 // the only case is RequestAPI test has problem to create db 230 if (mDatabase == null) { 231 mInitialized = true; 232 notify(); 233 return; 234 } 235 236 if (mDatabase.getVersion() != DATABASE_VERSION) { 237 mDatabase.beginTransactionNonExclusive(); 238 try { 239 upgradeDatabase(); 240 mDatabase.setTransactionSuccessful(); 241 } finally { 242 mDatabase.endTransaction(); 243 } 244 } 245 246 // use per table Mutex lock, turn off database lock, this 247 // improves performance as database's ReentrantLock is 248 // expansive 249 mDatabase.setLockingEnabled(false); 250 } 251 252 private void initCacheDatabase(Context context) { 253 assert !JniUtil.useChromiumHttpStack(); 254 255 try { 256 mCacheDatabase = context.openOrCreateDatabase( 257 CACHE_DATABASE_FILE, 0, null); 258 } catch (SQLiteException e) { 259 // try again by deleting the old db and create a new one 260 if (context.deleteDatabase(CACHE_DATABASE_FILE)) { 261 mCacheDatabase = context.openOrCreateDatabase( 262 CACHE_DATABASE_FILE, 0, null); 263 } 264 } 265 mCacheDatabase.enableWriteAheadLogging(); 266 267 // mCacheDatabase should not be null, 268 // the only case is RequestAPI test has problem to create db 269 if (mCacheDatabase == null) { 270 mInitialized = true; 271 notify(); 272 return; 273 } 274 275 if (mCacheDatabase.getVersion() != CACHE_DATABASE_VERSION) { 276 mCacheDatabase.beginTransactionNonExclusive(); 277 try { 278 upgradeCacheDatabase(); 279 bootstrapCacheDatabase(); 280 mCacheDatabase.setTransactionSuccessful(); 281 } finally { 282 mCacheDatabase.endTransaction(); 283 } 284 // Erase the files from the file system in the 285 // case that the database was updated and the 286 // there were existing cache content 287 CacheManager.removeAllCacheFiles(); 288 } 289 290 // use read_uncommitted to speed up READ 291 mCacheDatabase.execSQL("PRAGMA read_uncommitted = true;"); 292 // as only READ can be called in the 293 // non-WebViewWorkerThread, and read_uncommitted is used, 294 // we can turn off database lock to use transaction. 295 mCacheDatabase.setLockingEnabled(false); 296 297 // use InsertHelper for faster insertion 298 mCacheInserter = 299 new DatabaseUtils.InsertHelper(mCacheDatabase, 300 "cache"); 301 mCacheUrlColIndex = mCacheInserter 302 .getColumnIndex(CACHE_URL_COL); 303 mCacheFilePathColIndex = mCacheInserter 304 .getColumnIndex(CACHE_FILE_PATH_COL); 305 mCacheLastModifyColIndex = mCacheInserter 306 .getColumnIndex(CACHE_LAST_MODIFY_COL); 307 mCacheETagColIndex = mCacheInserter 308 .getColumnIndex(CACHE_ETAG_COL); 309 mCacheExpiresColIndex = mCacheInserter 310 .getColumnIndex(CACHE_EXPIRES_COL); 311 mCacheExpiresStringColIndex = mCacheInserter 312 .getColumnIndex(CACHE_EXPIRES_STRING_COL); 313 mCacheMimeTypeColIndex = mCacheInserter 314 .getColumnIndex(CACHE_MIMETYPE_COL); 315 mCacheEncodingColIndex = mCacheInserter 316 .getColumnIndex(CACHE_ENCODING_COL); 317 mCacheHttpStatusColIndex = mCacheInserter 318 .getColumnIndex(CACHE_HTTP_STATUS_COL); 319 mCacheLocationColIndex = mCacheInserter 320 .getColumnIndex(CACHE_LOCATION_COL); 321 mCacheContentLengthColIndex = mCacheInserter 322 .getColumnIndex(CACHE_CONTENTLENGTH_COL); 323 mCacheContentDispositionColIndex = mCacheInserter 324 .getColumnIndex(CACHE_CONTENTDISPOSITION_COL); 325 mCacheCrossDomainColIndex = mCacheInserter 326 .getColumnIndex(CACHE_CROSSDOMAIN_COL); 327 } 328 329 private static void upgradeDatabase() { 330 upgradeDatabaseToV10(); 331 // Add future database upgrade functions here, one version at a 332 // time. 333 } 334 335 private static void upgradeDatabaseToV10() { 336 int oldVersion = mDatabase.getVersion(); 337 338 if (oldVersion >= 10) { 339 // Nothing to do. 340 return; 341 } 342 343 if (oldVersion != 0) { 344 Log.i(LOGTAG, "Upgrading database from version " 345 + oldVersion + " to " 346 + DATABASE_VERSION + ", which will destroy old data"); 347 } 348 349 if (9 == oldVersion) { 350 mDatabase.execSQL("DROP TABLE IF EXISTS " 351 + mTableNames[TABLE_HTTPAUTH_ID]); 352 mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_HTTPAUTH_ID] 353 + " (" + ID_COL + " INTEGER PRIMARY KEY, " 354 + HTTPAUTH_HOST_COL + " TEXT, " + HTTPAUTH_REALM_COL 355 + " TEXT, " + HTTPAUTH_USERNAME_COL + " TEXT, " 356 + HTTPAUTH_PASSWORD_COL + " TEXT," + " UNIQUE (" 357 + HTTPAUTH_HOST_COL + ", " + HTTPAUTH_REALM_COL 358 + ") ON CONFLICT REPLACE);"); 359 mDatabase.setVersion(DATABASE_VERSION); 360 return; 361 } 362 363 mDatabase.execSQL("DROP TABLE IF EXISTS " 364 + mTableNames[TABLE_COOKIES_ID]); 365 mDatabase.execSQL("DROP TABLE IF EXISTS cache"); 366 mDatabase.execSQL("DROP TABLE IF EXISTS " 367 + mTableNames[TABLE_FORMURL_ID]); 368 mDatabase.execSQL("DROP TABLE IF EXISTS " 369 + mTableNames[TABLE_FORMDATA_ID]); 370 mDatabase.execSQL("DROP TABLE IF EXISTS " 371 + mTableNames[TABLE_HTTPAUTH_ID]); 372 mDatabase.execSQL("DROP TABLE IF EXISTS " 373 + mTableNames[TABLE_PASSWORD_ID]); 374 375 // cookies 376 mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_COOKIES_ID] 377 + " (" + ID_COL + " INTEGER PRIMARY KEY, " 378 + COOKIES_NAME_COL + " TEXT, " + COOKIES_VALUE_COL 379 + " TEXT, " + COOKIES_DOMAIN_COL + " TEXT, " 380 + COOKIES_PATH_COL + " TEXT, " + COOKIES_EXPIRES_COL 381 + " INTEGER, " + COOKIES_SECURE_COL + " INTEGER" + ");"); 382 mDatabase.execSQL("CREATE INDEX cookiesIndex ON " 383 + mTableNames[TABLE_COOKIES_ID] + " (path)"); 384 385 // formurl 386 mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_FORMURL_ID] 387 + " (" + ID_COL + " INTEGER PRIMARY KEY, " + FORMURL_URL_COL 388 + " TEXT" + ");"); 389 390 // formdata 391 mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_FORMDATA_ID] 392 + " (" + ID_COL + " INTEGER PRIMARY KEY, " 393 + FORMDATA_URLID_COL + " INTEGER, " + FORMDATA_NAME_COL 394 + " TEXT, " + FORMDATA_VALUE_COL + " TEXT," + " UNIQUE (" 395 + FORMDATA_URLID_COL + ", " + FORMDATA_NAME_COL + ", " 396 + FORMDATA_VALUE_COL + ") ON CONFLICT IGNORE);"); 397 398 // httpauth 399 mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_HTTPAUTH_ID] 400 + " (" + ID_COL + " INTEGER PRIMARY KEY, " 401 + HTTPAUTH_HOST_COL + " TEXT, " + HTTPAUTH_REALM_COL 402 + " TEXT, " + HTTPAUTH_USERNAME_COL + " TEXT, " 403 + HTTPAUTH_PASSWORD_COL + " TEXT," + " UNIQUE (" 404 + HTTPAUTH_HOST_COL + ", " + HTTPAUTH_REALM_COL 405 + ") ON CONFLICT REPLACE);"); 406 // passwords 407 mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_PASSWORD_ID] 408 + " (" + ID_COL + " INTEGER PRIMARY KEY, " 409 + PASSWORD_HOST_COL + " TEXT, " + PASSWORD_USERNAME_COL 410 + " TEXT, " + PASSWORD_PASSWORD_COL + " TEXT," + " UNIQUE (" 411 + PASSWORD_HOST_COL + ", " + PASSWORD_USERNAME_COL 412 + ") ON CONFLICT REPLACE);"); 413 414 mDatabase.setVersion(DATABASE_VERSION); 415 } 416 417 private static void upgradeCacheDatabase() { 418 int oldVersion = mCacheDatabase.getVersion(); 419 if (oldVersion != 0) { 420 Log.i(LOGTAG, "Upgrading cache database from version " 421 + oldVersion + " to " 422 + CACHE_DATABASE_VERSION + ", which will destroy all old data"); 423 } 424 mCacheDatabase.execSQL("DROP TABLE IF EXISTS cache"); 425 mCacheDatabase.setVersion(CACHE_DATABASE_VERSION); 426 } 427 428 private static void bootstrapCacheDatabase() { 429 if (mCacheDatabase != null) { 430 mCacheDatabase.execSQL("CREATE TABLE cache" 431 + " (" + ID_COL + " INTEGER PRIMARY KEY, " + CACHE_URL_COL 432 + " TEXT, " + CACHE_FILE_PATH_COL + " TEXT, " 433 + CACHE_LAST_MODIFY_COL + " TEXT, " + CACHE_ETAG_COL 434 + " TEXT, " + CACHE_EXPIRES_COL + " INTEGER, " 435 + CACHE_EXPIRES_STRING_COL + " TEXT, " 436 + CACHE_MIMETYPE_COL + " TEXT, " + CACHE_ENCODING_COL 437 + " TEXT," + CACHE_HTTP_STATUS_COL + " INTEGER, " 438 + CACHE_LOCATION_COL + " TEXT, " + CACHE_CONTENTLENGTH_COL 439 + " INTEGER, " + CACHE_CONTENTDISPOSITION_COL + " TEXT, " 440 + CACHE_CROSSDOMAIN_COL + " TEXT," 441 + " UNIQUE (" + CACHE_URL_COL + ") ON CONFLICT REPLACE);"); 442 mCacheDatabase.execSQL("CREATE INDEX cacheUrlIndex ON cache (" 443 + CACHE_URL_COL + ")"); 444 } 445 } 446 447 // Wait for the background initialization thread to complete and check the 448 // database creation status. 449 private boolean checkInitialized() { 450 synchronized (this) { 451 while (!mInitialized) { 452 try { 453 wait(); 454 } catch (InterruptedException e) { 455 Log.e(LOGTAG, "Caught exception while checking " + 456 "initialization"); 457 Log.e(LOGTAG, Log.getStackTraceString(e)); 458 } 459 } 460 } 461 return mDatabase != null; 462 } 463 464 private boolean hasEntries(int tableId) { 465 if (!checkInitialized()) { 466 return false; 467 } 468 469 Cursor cursor = null; 470 boolean ret = false; 471 try { 472 cursor = mDatabase.query(mTableNames[tableId], ID_PROJECTION, 473 null, null, null, null, null); 474 ret = cursor.moveToFirst() == true; 475 } catch (IllegalStateException e) { 476 Log.e(LOGTAG, "hasEntries", e); 477 } finally { 478 if (cursor != null) cursor.close(); 479 } 480 return ret; 481 } 482 483 // 484 // cookies functions 485 // 486 487 /** 488 * Get cookies in the format of CookieManager.Cookie inside an ArrayList for 489 * a given domain 490 * 491 * @return ArrayList<Cookie> If nothing is found, return an empty list. 492 */ 493 ArrayList<Cookie> getCookiesForDomain(String domain) { 494 ArrayList<Cookie> list = new ArrayList<Cookie>(); 495 if (domain == null || !checkInitialized()) { 496 return list; 497 } 498 499 synchronized (mCookieLock) { 500 final String[] columns = new String[] { 501 ID_COL, COOKIES_DOMAIN_COL, COOKIES_PATH_COL, 502 COOKIES_NAME_COL, COOKIES_VALUE_COL, COOKIES_EXPIRES_COL, 503 COOKIES_SECURE_COL 504 }; 505 final String selection = "(" + COOKIES_DOMAIN_COL 506 + " GLOB '*' || ?)"; 507 Cursor cursor = null; 508 try { 509 cursor = mDatabase.query(mTableNames[TABLE_COOKIES_ID], 510 columns, selection, new String[] { domain }, null, null, 511 null); 512 if (cursor.moveToFirst()) { 513 int domainCol = cursor.getColumnIndex(COOKIES_DOMAIN_COL); 514 int pathCol = cursor.getColumnIndex(COOKIES_PATH_COL); 515 int nameCol = cursor.getColumnIndex(COOKIES_NAME_COL); 516 int valueCol = cursor.getColumnIndex(COOKIES_VALUE_COL); 517 int expiresCol = cursor.getColumnIndex(COOKIES_EXPIRES_COL); 518 int secureCol = cursor.getColumnIndex(COOKIES_SECURE_COL); 519 do { 520 Cookie cookie = new Cookie(); 521 cookie.domain = cursor.getString(domainCol); 522 cookie.path = cursor.getString(pathCol); 523 cookie.name = cursor.getString(nameCol); 524 cookie.value = cursor.getString(valueCol); 525 if (cursor.isNull(expiresCol)) { 526 cookie.expires = -1; 527 } else { 528 cookie.expires = cursor.getLong(expiresCol); 529 } 530 cookie.secure = cursor.getShort(secureCol) != 0; 531 cookie.mode = Cookie.MODE_NORMAL; 532 list.add(cookie); 533 } while (cursor.moveToNext()); 534 } 535 } catch (IllegalStateException e) { 536 Log.e(LOGTAG, "getCookiesForDomain", e); 537 } finally { 538 if (cursor != null) cursor.close(); 539 } 540 return list; 541 } 542 } 543 544 /** 545 * Delete cookies which matches (domain, path, name). 546 * 547 * @param domain If it is null, nothing happens. 548 * @param path If it is null, all the cookies match (domain) will be 549 * deleted. 550 * @param name If it is null, all the cookies match (domain, path) will be 551 * deleted. 552 */ 553 void deleteCookies(String domain, String path, String name) { 554 if (domain == null || !checkInitialized()) { 555 return; 556 } 557 558 synchronized (mCookieLock) { 559 final String where = "(" + COOKIES_DOMAIN_COL + " == ?) AND (" 560 + COOKIES_PATH_COL + " == ?) AND (" + COOKIES_NAME_COL 561 + " == ?)"; 562 mDatabase.delete(mTableNames[TABLE_COOKIES_ID], where, 563 new String[] { domain, path, name }); 564 } 565 } 566 567 /** 568 * Add a cookie to the database 569 * 570 * @param cookie 571 */ 572 void addCookie(Cookie cookie) { 573 if (cookie.domain == null || cookie.path == null || cookie.name == null 574 || !checkInitialized()) { 575 return; 576 } 577 578 synchronized (mCookieLock) { 579 ContentValues cookieVal = new ContentValues(); 580 cookieVal.put(COOKIES_DOMAIN_COL, cookie.domain); 581 cookieVal.put(COOKIES_PATH_COL, cookie.path); 582 cookieVal.put(COOKIES_NAME_COL, cookie.name); 583 cookieVal.put(COOKIES_VALUE_COL, cookie.value); 584 if (cookie.expires != -1) { 585 cookieVal.put(COOKIES_EXPIRES_COL, cookie.expires); 586 } 587 cookieVal.put(COOKIES_SECURE_COL, cookie.secure); 588 mDatabase.insert(mTableNames[TABLE_COOKIES_ID], null, cookieVal); 589 } 590 } 591 592 /** 593 * Whether there is any cookies in the database 594 * 595 * @return TRUE if there is cookie. 596 */ 597 boolean hasCookies() { 598 synchronized (mCookieLock) { 599 return hasEntries(TABLE_COOKIES_ID); 600 } 601 } 602 603 /** 604 * Clear cookie database 605 */ 606 void clearCookies() { 607 if (!checkInitialized()) { 608 return; 609 } 610 611 synchronized (mCookieLock) { 612 mDatabase.delete(mTableNames[TABLE_COOKIES_ID], null, null); 613 } 614 } 615 616 /** 617 * Clear session cookies, which means cookie doesn't have EXPIRES. 618 */ 619 void clearSessionCookies() { 620 if (!checkInitialized()) { 621 return; 622 } 623 624 final String sessionExpired = COOKIES_EXPIRES_COL + " ISNULL"; 625 synchronized (mCookieLock) { 626 mDatabase.delete(mTableNames[TABLE_COOKIES_ID], sessionExpired, 627 null); 628 } 629 } 630 631 /** 632 * Clear expired cookies 633 * 634 * @param now Time for now 635 */ 636 void clearExpiredCookies(long now) { 637 if (!checkInitialized()) { 638 return; 639 } 640 641 final String expires = COOKIES_EXPIRES_COL + " <= ?"; 642 synchronized (mCookieLock) { 643 mDatabase.delete(mTableNames[TABLE_COOKIES_ID], expires, 644 new String[] { Long.toString(now) }); 645 } 646 } 647 648 // 649 // cache functions 650 // 651 652 // only called from WebViewWorkerThread 653 boolean startCacheTransaction() { 654 if (++mCacheTransactionRefcount == 1) { 655 if (!Thread.currentThread().equals( 656 WebViewWorker.getHandler().getLooper().getThread())) { 657 Log.w(LOGTAG, "startCacheTransaction should be called from " 658 + "WebViewWorkerThread instead of from " 659 + Thread.currentThread().getName()); 660 } 661 mCacheDatabase.beginTransactionNonExclusive(); 662 return true; 663 } 664 return false; 665 } 666 667 // only called from WebViewWorkerThread 668 boolean endCacheTransaction() { 669 if (--mCacheTransactionRefcount == 0) { 670 if (!Thread.currentThread().equals( 671 WebViewWorker.getHandler().getLooper().getThread())) { 672 Log.w(LOGTAG, "endCacheTransaction should be called from " 673 + "WebViewWorkerThread instead of from " 674 + Thread.currentThread().getName()); 675 } 676 try { 677 mCacheDatabase.setTransactionSuccessful(); 678 } finally { 679 mCacheDatabase.endTransaction(); 680 } 681 return true; 682 } 683 return false; 684 } 685 686 /** 687 * Get a cache item. 688 * 689 * @param url The url 690 * @return CacheResult The CacheManager.CacheResult 691 */ 692 CacheResult getCache(String url) { 693 assert !JniUtil.useChromiumHttpStack(); 694 695 if (url == null || !checkInitialized()) { 696 return null; 697 } 698 699 Cursor cursor = null; 700 final String query = "SELECT filepath, lastmodify, etag, expires, " 701 + "expiresstring, mimetype, encoding, httpstatus, location, contentlength, " 702 + "contentdisposition, crossdomain FROM cache WHERE url = ?"; 703 try { 704 cursor = mCacheDatabase.rawQuery(query, new String[] { url }); 705 if (cursor.moveToFirst()) { 706 CacheResult ret = new CacheResult(); 707 ret.localPath = cursor.getString(0); 708 ret.lastModified = cursor.getString(1); 709 ret.etag = cursor.getString(2); 710 ret.expires = cursor.getLong(3); 711 ret.expiresString = cursor.getString(4); 712 ret.mimeType = cursor.getString(5); 713 ret.encoding = cursor.getString(6); 714 ret.httpStatusCode = cursor.getInt(7); 715 ret.location = cursor.getString(8); 716 ret.contentLength = cursor.getLong(9); 717 ret.contentdisposition = cursor.getString(10); 718 ret.crossDomain = cursor.getString(11); 719 return ret; 720 } 721 } catch (IllegalStateException e) { 722 Log.e(LOGTAG, "getCache", e); 723 } finally { 724 if (cursor != null) cursor.close(); 725 } 726 return null; 727 } 728 729 /** 730 * Remove a cache item. 731 * 732 * @param url The url 733 */ 734 void removeCache(String url) { 735 assert !JniUtil.useChromiumHttpStack(); 736 737 if (url == null || !checkInitialized()) { 738 return; 739 } 740 741 mCacheDatabase.execSQL("DELETE FROM cache WHERE url = ?", new String[] { url }); 742 } 743 744 /** 745 * Add or update a cache. CACHE_URL_COL is unique in the table. 746 * 747 * @param url The url 748 * @param c The CacheManager.CacheResult 749 */ 750 void addCache(String url, CacheResult c) { 751 assert !JniUtil.useChromiumHttpStack(); 752 753 if (url == null || !checkInitialized()) { 754 return; 755 } 756 757 mCacheInserter.prepareForInsert(); 758 mCacheInserter.bind(mCacheUrlColIndex, url); 759 mCacheInserter.bind(mCacheFilePathColIndex, c.localPath); 760 mCacheInserter.bind(mCacheLastModifyColIndex, c.lastModified); 761 mCacheInserter.bind(mCacheETagColIndex, c.etag); 762 mCacheInserter.bind(mCacheExpiresColIndex, c.expires); 763 mCacheInserter.bind(mCacheExpiresStringColIndex, c.expiresString); 764 mCacheInserter.bind(mCacheMimeTypeColIndex, c.mimeType); 765 mCacheInserter.bind(mCacheEncodingColIndex, c.encoding); 766 mCacheInserter.bind(mCacheHttpStatusColIndex, c.httpStatusCode); 767 mCacheInserter.bind(mCacheLocationColIndex, c.location); 768 mCacheInserter.bind(mCacheContentLengthColIndex, c.contentLength); 769 mCacheInserter.bind(mCacheContentDispositionColIndex, 770 c.contentdisposition); 771 mCacheInserter.bind(mCacheCrossDomainColIndex, c.crossDomain); 772 mCacheInserter.execute(); 773 } 774 775 /** 776 * Clear cache database 777 */ 778 void clearCache() { 779 if (!checkInitialized()) { 780 return; 781 } 782 783 mCacheDatabase.delete("cache", null, null); 784 } 785 786 boolean hasCache() { 787 if (!checkInitialized()) { 788 return false; 789 } 790 791 Cursor cursor = null; 792 boolean ret = false; 793 try { 794 cursor = mCacheDatabase.query("cache", ID_PROJECTION, 795 null, null, null, null, null); 796 ret = cursor.moveToFirst() == true; 797 } catch (IllegalStateException e) { 798 Log.e(LOGTAG, "hasCache", e); 799 } finally { 800 if (cursor != null) cursor.close(); 801 } 802 return ret; 803 } 804 805 long getCacheTotalSize() { 806 if (mCacheDatabase == null) { 807 return 0; 808 } 809 long size = 0; 810 Cursor cursor = null; 811 final String query = "SELECT SUM(contentlength) as sum FROM cache"; 812 try { 813 cursor = mCacheDatabase.rawQuery(query, null); 814 if (cursor.moveToFirst()) { 815 size = cursor.getLong(0); 816 } 817 } catch (IllegalStateException e) { 818 Log.e(LOGTAG, "getCacheTotalSize", e); 819 } finally { 820 if (cursor != null) cursor.close(); 821 } 822 return size; 823 } 824 825 List<String> trimCache(long amount) { 826 ArrayList<String> pathList = new ArrayList<String>(100); 827 Cursor cursor = null; 828 final String query = "SELECT contentlength, filepath FROM cache ORDER BY expires ASC"; 829 try { 830 cursor = mCacheDatabase.rawQuery(query, null); 831 if (cursor.moveToFirst()) { 832 int batchSize = 100; 833 StringBuilder pathStr = new StringBuilder(20 + 16 * batchSize); 834 pathStr.append("DELETE FROM cache WHERE filepath IN (?"); 835 for (int i = 1; i < batchSize; i++) { 836 pathStr.append(", ?"); 837 } 838 pathStr.append(")"); 839 SQLiteStatement statement = null; 840 try { 841 statement = mCacheDatabase.compileStatement( 842 pathStr.toString()); 843 // as bindString() uses 1-based index, initialize index to 1 844 int index = 1; 845 do { 846 long length = cursor.getLong(0); 847 if (length == 0) { 848 continue; 849 } 850 amount -= length; 851 String filePath = cursor.getString(1); 852 statement.bindString(index, filePath); 853 pathList.add(filePath); 854 if (index++ == batchSize) { 855 statement.execute(); 856 statement.clearBindings(); 857 index = 1; 858 } 859 } while (cursor.moveToNext() && amount > 0); 860 if (index > 1) { 861 // there may be old bindings from the previous statement 862 // if index is less than batchSize, which is Ok. 863 statement.execute(); 864 } 865 } catch (IllegalStateException e) { 866 Log.e(LOGTAG, "trimCache SQLiteStatement", e); 867 } finally { 868 if (statement != null) statement.close(); 869 } 870 } 871 } catch (IllegalStateException e) { 872 Log.e(LOGTAG, "trimCache Cursor", e); 873 } finally { 874 if (cursor != null) cursor.close(); 875 } 876 return pathList; 877 } 878 879 List<String> getAllCacheFileNames() { 880 ArrayList<String> pathList = null; 881 Cursor cursor = null; 882 try { 883 cursor = mCacheDatabase.rawQuery("SELECT filepath FROM cache", 884 null); 885 if (cursor != null && cursor.moveToFirst()) { 886 pathList = new ArrayList<String>(cursor.getCount()); 887 do { 888 pathList.add(cursor.getString(0)); 889 } while (cursor.moveToNext()); 890 } 891 } catch (IllegalStateException e) { 892 Log.e(LOGTAG, "getAllCacheFileNames", e); 893 } finally { 894 if (cursor != null) cursor.close(); 895 } 896 return pathList; 897 } 898 899 // 900 // password functions 901 // 902 903 /** 904 * Set password. Tuple (PASSWORD_HOST_COL, PASSWORD_USERNAME_COL) is unique. 905 * 906 * @param schemePlusHost The scheme and host for the password 907 * @param username The username for the password. If it is null, it means 908 * password can't be saved. 909 * @param password The password 910 */ 911 void setUsernamePassword(String schemePlusHost, String username, 912 String password) { 913 if (schemePlusHost == null || !checkInitialized()) { 914 return; 915 } 916 917 synchronized (mPasswordLock) { 918 final ContentValues c = new ContentValues(); 919 c.put(PASSWORD_HOST_COL, schemePlusHost); 920 c.put(PASSWORD_USERNAME_COL, username); 921 c.put(PASSWORD_PASSWORD_COL, password); 922 mDatabase.insert(mTableNames[TABLE_PASSWORD_ID], PASSWORD_HOST_COL, 923 c); 924 } 925 } 926 927 /** 928 * Retrieve the username and password for a given host 929 * 930 * @param schemePlusHost The scheme and host which passwords applies to 931 * @return String[] if found, String[0] is username, which can be null and 932 * String[1] is password. Return null if it can't find anything. 933 */ 934 String[] getUsernamePassword(String schemePlusHost) { 935 if (schemePlusHost == null || !checkInitialized()) { 936 return null; 937 } 938 939 final String[] columns = new String[] { 940 PASSWORD_USERNAME_COL, PASSWORD_PASSWORD_COL 941 }; 942 final String selection = "(" + PASSWORD_HOST_COL + " == ?)"; 943 synchronized (mPasswordLock) { 944 String[] ret = null; 945 Cursor cursor = null; 946 try { 947 cursor = mDatabase.query(mTableNames[TABLE_PASSWORD_ID], 948 columns, selection, new String[] { schemePlusHost }, null, 949 null, null); 950 if (cursor.moveToFirst()) { 951 ret = new String[2]; 952 ret[0] = cursor.getString( 953 cursor.getColumnIndex(PASSWORD_USERNAME_COL)); 954 ret[1] = cursor.getString( 955 cursor.getColumnIndex(PASSWORD_PASSWORD_COL)); 956 } 957 } catch (IllegalStateException e) { 958 Log.e(LOGTAG, "getUsernamePassword", e); 959 } finally { 960 if (cursor != null) cursor.close(); 961 } 962 return ret; 963 } 964 } 965 966 /** 967 * Find out if there are any passwords saved. 968 * 969 * @return TRUE if there is passwords saved 970 */ 971 public boolean hasUsernamePassword() { 972 synchronized (mPasswordLock) { 973 return hasEntries(TABLE_PASSWORD_ID); 974 } 975 } 976 977 /** 978 * Clear password database 979 */ 980 public void clearUsernamePassword() { 981 if (!checkInitialized()) { 982 return; 983 } 984 985 synchronized (mPasswordLock) { 986 mDatabase.delete(mTableNames[TABLE_PASSWORD_ID], null, null); 987 } 988 } 989 990 // 991 // http authentication password functions 992 // 993 994 /** 995 * Set HTTP authentication password. Tuple (HTTPAUTH_HOST_COL, 996 * HTTPAUTH_REALM_COL, HTTPAUTH_USERNAME_COL) is unique. 997 * 998 * @param host The host for the password 999 * @param realm The realm for the password 1000 * @param username The username for the password. If it is null, it means 1001 * password can't be saved. 1002 * @param password The password 1003 */ 1004 void setHttpAuthUsernamePassword(String host, String realm, String username, 1005 String password) { 1006 if (host == null || realm == null || !checkInitialized()) { 1007 return; 1008 } 1009 1010 synchronized (mHttpAuthLock) { 1011 final ContentValues c = new ContentValues(); 1012 c.put(HTTPAUTH_HOST_COL, host); 1013 c.put(HTTPAUTH_REALM_COL, realm); 1014 c.put(HTTPAUTH_USERNAME_COL, username); 1015 c.put(HTTPAUTH_PASSWORD_COL, password); 1016 mDatabase.insert(mTableNames[TABLE_HTTPAUTH_ID], HTTPAUTH_HOST_COL, 1017 c); 1018 } 1019 } 1020 1021 /** 1022 * Retrieve the HTTP authentication username and password for a given 1023 * host+realm pair 1024 * 1025 * @param host The host the password applies to 1026 * @param realm The realm the password applies to 1027 * @return String[] if found, String[0] is username, which can be null and 1028 * String[1] is password. Return null if it can't find anything. 1029 */ 1030 String[] getHttpAuthUsernamePassword(String host, String realm) { 1031 if (host == null || realm == null || !checkInitialized()){ 1032 return null; 1033 } 1034 1035 final String[] columns = new String[] { 1036 HTTPAUTH_USERNAME_COL, HTTPAUTH_PASSWORD_COL 1037 }; 1038 final String selection = "(" + HTTPAUTH_HOST_COL + " == ?) AND (" 1039 + HTTPAUTH_REALM_COL + " == ?)"; 1040 synchronized (mHttpAuthLock) { 1041 String[] ret = null; 1042 Cursor cursor = null; 1043 try { 1044 cursor = mDatabase.query(mTableNames[TABLE_HTTPAUTH_ID], 1045 columns, selection, new String[] { host, realm }, null, 1046 null, null); 1047 if (cursor.moveToFirst()) { 1048 ret = new String[2]; 1049 ret[0] = cursor.getString( 1050 cursor.getColumnIndex(HTTPAUTH_USERNAME_COL)); 1051 ret[1] = cursor.getString( 1052 cursor.getColumnIndex(HTTPAUTH_PASSWORD_COL)); 1053 } 1054 } catch (IllegalStateException e) { 1055 Log.e(LOGTAG, "getHttpAuthUsernamePassword", e); 1056 } finally { 1057 if (cursor != null) cursor.close(); 1058 } 1059 return ret; 1060 } 1061 } 1062 1063 /** 1064 * Find out if there are any HTTP authentication passwords saved. 1065 * 1066 * @return TRUE if there are passwords saved 1067 */ 1068 public boolean hasHttpAuthUsernamePassword() { 1069 synchronized (mHttpAuthLock) { 1070 return hasEntries(TABLE_HTTPAUTH_ID); 1071 } 1072 } 1073 1074 /** 1075 * Clear HTTP authentication password database 1076 */ 1077 public void clearHttpAuthUsernamePassword() { 1078 if (!checkInitialized()) { 1079 return; 1080 } 1081 1082 synchronized (mHttpAuthLock) { 1083 mDatabase.delete(mTableNames[TABLE_HTTPAUTH_ID], null, null); 1084 } 1085 } 1086 1087 // 1088 // form data functions 1089 // 1090 1091 /** 1092 * Set form data for a site. Tuple (FORMDATA_URLID_COL, FORMDATA_NAME_COL, 1093 * FORMDATA_VALUE_COL) is unique 1094 * 1095 * @param url The url of the site 1096 * @param formdata The form data in HashMap 1097 */ 1098 void setFormData(String url, HashMap<String, String> formdata) { 1099 if (url == null || formdata == null || !checkInitialized()) { 1100 return; 1101 } 1102 1103 final String selection = "(" + FORMURL_URL_COL + " == ?)"; 1104 synchronized (mFormLock) { 1105 long urlid = -1; 1106 Cursor cursor = null; 1107 try { 1108 cursor = mDatabase.query(mTableNames[TABLE_FORMURL_ID], 1109 ID_PROJECTION, selection, new String[] { url }, null, null, 1110 null); 1111 if (cursor.moveToFirst()) { 1112 urlid = cursor.getLong(cursor.getColumnIndex(ID_COL)); 1113 } else { 1114 ContentValues c = new ContentValues(); 1115 c.put(FORMURL_URL_COL, url); 1116 urlid = mDatabase.insert( 1117 mTableNames[TABLE_FORMURL_ID], null, c); 1118 } 1119 } catch (IllegalStateException e) { 1120 Log.e(LOGTAG, "setFormData", e); 1121 } finally { 1122 if (cursor != null) cursor.close(); 1123 } 1124 if (urlid >= 0) { 1125 Set<Entry<String, String>> set = formdata.entrySet(); 1126 Iterator<Entry<String, String>> iter = set.iterator(); 1127 ContentValues map = new ContentValues(); 1128 map.put(FORMDATA_URLID_COL, urlid); 1129 while (iter.hasNext()) { 1130 Entry<String, String> entry = iter.next(); 1131 map.put(FORMDATA_NAME_COL, entry.getKey()); 1132 map.put(FORMDATA_VALUE_COL, entry.getValue()); 1133 mDatabase.insert(mTableNames[TABLE_FORMDATA_ID], null, map); 1134 } 1135 } 1136 } 1137 } 1138 1139 /** 1140 * Get all the values for a form entry with "name" in a given site 1141 * 1142 * @param url The url of the site 1143 * @param name The name of the form entry 1144 * @return A list of values. Return empty list if nothing is found. 1145 */ 1146 ArrayList<String> getFormData(String url, String name) { 1147 ArrayList<String> values = new ArrayList<String>(); 1148 if (url == null || name == null || !checkInitialized()) { 1149 return values; 1150 } 1151 1152 final String urlSelection = "(" + FORMURL_URL_COL + " == ?)"; 1153 final String dataSelection = "(" + FORMDATA_URLID_COL + " == ?) AND (" 1154 + FORMDATA_NAME_COL + " == ?)"; 1155 synchronized (mFormLock) { 1156 Cursor cursor = null; 1157 try { 1158 cursor = mDatabase.query(mTableNames[TABLE_FORMURL_ID], 1159 ID_PROJECTION, urlSelection, new String[] { url }, null, 1160 null, null); 1161 if (cursor.moveToFirst()) { 1162 long urlid = cursor.getLong(cursor.getColumnIndex(ID_COL)); 1163 Cursor dataCursor = null; 1164 try { 1165 dataCursor = mDatabase.query( 1166 mTableNames[TABLE_FORMDATA_ID], 1167 new String[] { ID_COL, FORMDATA_VALUE_COL }, 1168 dataSelection, 1169 new String[] { Long.toString(urlid), name }, 1170 null, null, null); 1171 if (dataCursor.moveToFirst()) { 1172 int valueCol = dataCursor.getColumnIndex( 1173 FORMDATA_VALUE_COL); 1174 do { 1175 values.add(dataCursor.getString(valueCol)); 1176 } while (dataCursor.moveToNext()); 1177 } 1178 } catch (IllegalStateException e) { 1179 Log.e(LOGTAG, "getFormData dataCursor", e); 1180 } finally { 1181 if (dataCursor != null) dataCursor.close(); 1182 } 1183 } 1184 } catch (IllegalStateException e) { 1185 Log.e(LOGTAG, "getFormData cursor", e); 1186 } finally { 1187 if (cursor != null) cursor.close(); 1188 } 1189 return values; 1190 } 1191 } 1192 1193 /** 1194 * Find out if there is form data saved. 1195 * 1196 * @return TRUE if there is form data in the database 1197 */ 1198 public boolean hasFormData() { 1199 synchronized (mFormLock) { 1200 return hasEntries(TABLE_FORMURL_ID); 1201 } 1202 } 1203 1204 /** 1205 * Clear form database 1206 */ 1207 public void clearFormData() { 1208 if (!checkInitialized()) { 1209 return; 1210 } 1211 1212 synchronized (mFormLock) { 1213 mDatabase.delete(mTableNames[TABLE_FORMURL_ID], null, null); 1214 mDatabase.delete(mTableNames[TABLE_FORMDATA_ID], null, null); 1215 } 1216 } 1217} 1218