• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.List;
23 import java.util.Set;
24 import java.util.Map.Entry;
25 
26 import android.content.ContentValues;
27 import android.content.Context;
28 import android.database.Cursor;
29 import android.database.DatabaseUtils;
30 import android.database.sqlite.SQLiteDatabase;
31 import android.database.sqlite.SQLiteException;
32 import android.database.sqlite.SQLiteStatement;
33 import android.util.Log;
34 import android.webkit.CookieManager.Cookie;
35 import android.webkit.CacheManager.CacheResult;
36 import android.webkit.JniUtil;
37 
38 public 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 
WebViewDatabase(final Context context)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 
getInstance(Context context)194     public static synchronized WebViewDatabase getInstance(Context context) {
195         if (mInstance == null) {
196             mInstance = new WebViewDatabase(context);
197         }
198         return mInstance;
199     }
200 
init(Context context)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 
initDatabase(Context context)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 
initCacheDatabase(Context context)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 
upgradeDatabase()329     private static void upgradeDatabase() {
330         int oldVersion = mDatabase.getVersion();
331         if (oldVersion != 0) {
332             Log.i(LOGTAG, "Upgrading database from version "
333                     + oldVersion + " to "
334                     + DATABASE_VERSION + ", which will destroy old data");
335         }
336         boolean justPasswords = 8 == oldVersion && 9 == DATABASE_VERSION;
337         boolean justAuth = 9 == oldVersion && 10 == DATABASE_VERSION;
338         if (justAuth) {
339             mDatabase.execSQL("DROP TABLE IF EXISTS "
340                     + mTableNames[TABLE_HTTPAUTH_ID]);
341             mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_HTTPAUTH_ID]
342                     + " (" + ID_COL + " INTEGER PRIMARY KEY, "
343                     + HTTPAUTH_HOST_COL + " TEXT, " + HTTPAUTH_REALM_COL
344                     + " TEXT, " + HTTPAUTH_USERNAME_COL + " TEXT, "
345                     + HTTPAUTH_PASSWORD_COL + " TEXT," + " UNIQUE ("
346                     + HTTPAUTH_HOST_COL + ", " + HTTPAUTH_REALM_COL
347                     + ") ON CONFLICT REPLACE);");
348             return;
349         }
350 
351         if (!justPasswords) {
352             mDatabase.execSQL("DROP TABLE IF EXISTS "
353                     + mTableNames[TABLE_COOKIES_ID]);
354             mDatabase.execSQL("DROP TABLE IF EXISTS cache");
355             mDatabase.execSQL("DROP TABLE IF EXISTS "
356                     + mTableNames[TABLE_FORMURL_ID]);
357             mDatabase.execSQL("DROP TABLE IF EXISTS "
358                     + mTableNames[TABLE_FORMDATA_ID]);
359             mDatabase.execSQL("DROP TABLE IF EXISTS "
360                     + mTableNames[TABLE_HTTPAUTH_ID]);
361         }
362         mDatabase.execSQL("DROP TABLE IF EXISTS "
363                 + mTableNames[TABLE_PASSWORD_ID]);
364 
365         mDatabase.setVersion(DATABASE_VERSION);
366 
367         if (!justPasswords) {
368             // cookies
369             mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_COOKIES_ID]
370                     + " (" + ID_COL + " INTEGER PRIMARY KEY, "
371                     + COOKIES_NAME_COL + " TEXT, " + COOKIES_VALUE_COL
372                     + " TEXT, " + COOKIES_DOMAIN_COL + " TEXT, "
373                     + COOKIES_PATH_COL + " TEXT, " + COOKIES_EXPIRES_COL
374                     + " INTEGER, " + COOKIES_SECURE_COL + " INTEGER" + ");");
375             mDatabase.execSQL("CREATE INDEX cookiesIndex ON "
376                     + mTableNames[TABLE_COOKIES_ID] + " (path)");
377 
378             // formurl
379             mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_FORMURL_ID]
380                     + " (" + ID_COL + " INTEGER PRIMARY KEY, " + FORMURL_URL_COL
381                     + " TEXT" + ");");
382 
383             // formdata
384             mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_FORMDATA_ID]
385                     + " (" + ID_COL + " INTEGER PRIMARY KEY, "
386                     + FORMDATA_URLID_COL + " INTEGER, " + FORMDATA_NAME_COL
387                     + " TEXT, " + FORMDATA_VALUE_COL + " TEXT," + " UNIQUE ("
388                     + FORMDATA_URLID_COL + ", " + FORMDATA_NAME_COL + ", "
389                     + FORMDATA_VALUE_COL + ") ON CONFLICT IGNORE);");
390 
391             // httpauth
392             mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_HTTPAUTH_ID]
393                     + " (" + ID_COL + " INTEGER PRIMARY KEY, "
394                     + HTTPAUTH_HOST_COL + " TEXT, " + HTTPAUTH_REALM_COL
395                     + " TEXT, " + HTTPAUTH_USERNAME_COL + " TEXT, "
396                     + HTTPAUTH_PASSWORD_COL + " TEXT," + " UNIQUE ("
397                     + HTTPAUTH_HOST_COL + ", " + HTTPAUTH_REALM_COL
398                     + ") ON CONFLICT REPLACE);");
399         }
400         // passwords
401         mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_PASSWORD_ID]
402                 + " (" + ID_COL + " INTEGER PRIMARY KEY, "
403                 + PASSWORD_HOST_COL + " TEXT, " + PASSWORD_USERNAME_COL
404                 + " TEXT, " + PASSWORD_PASSWORD_COL + " TEXT," + " UNIQUE ("
405                 + PASSWORD_HOST_COL + ", " + PASSWORD_USERNAME_COL
406                 + ") ON CONFLICT REPLACE);");
407     }
408 
upgradeCacheDatabase()409     private static void upgradeCacheDatabase() {
410         int oldVersion = mCacheDatabase.getVersion();
411         if (oldVersion != 0) {
412             Log.i(LOGTAG, "Upgrading cache database from version "
413                     + oldVersion + " to "
414                     + DATABASE_VERSION + ", which will destroy all old data");
415         }
416         mCacheDatabase.execSQL("DROP TABLE IF EXISTS cache");
417         mCacheDatabase.setVersion(CACHE_DATABASE_VERSION);
418     }
419 
bootstrapCacheDatabase()420     private static void bootstrapCacheDatabase() {
421         if (mCacheDatabase != null) {
422             mCacheDatabase.execSQL("CREATE TABLE cache"
423                     + " (" + ID_COL + " INTEGER PRIMARY KEY, " + CACHE_URL_COL
424                     + " TEXT, " + CACHE_FILE_PATH_COL + " TEXT, "
425                     + CACHE_LAST_MODIFY_COL + " TEXT, " + CACHE_ETAG_COL
426                     + " TEXT, " + CACHE_EXPIRES_COL + " INTEGER, "
427                     + CACHE_EXPIRES_STRING_COL + " TEXT, "
428                     + CACHE_MIMETYPE_COL + " TEXT, " + CACHE_ENCODING_COL
429                     + " TEXT," + CACHE_HTTP_STATUS_COL + " INTEGER, "
430                     + CACHE_LOCATION_COL + " TEXT, " + CACHE_CONTENTLENGTH_COL
431                     + " INTEGER, " + CACHE_CONTENTDISPOSITION_COL + " TEXT, "
432                     + CACHE_CROSSDOMAIN_COL + " TEXT,"
433                     + " UNIQUE (" + CACHE_URL_COL + ") ON CONFLICT REPLACE);");
434             mCacheDatabase.execSQL("CREATE INDEX cacheUrlIndex ON cache ("
435                     + CACHE_URL_COL + ")");
436         }
437     }
438 
439     // Wait for the background initialization thread to complete and check the
440     // database creation status.
checkInitialized()441     private boolean checkInitialized() {
442         synchronized (this) {
443             while (!mInitialized) {
444                 try {
445                     wait();
446                 } catch (InterruptedException e) {
447                     Log.e(LOGTAG, "Caught exception while checking " +
448                                   "initialization");
449                     Log.e(LOGTAG, Log.getStackTraceString(e));
450                 }
451             }
452         }
453         return mDatabase != null;
454     }
455 
hasEntries(int tableId)456     private boolean hasEntries(int tableId) {
457         if (!checkInitialized()) {
458             return false;
459         }
460 
461         Cursor cursor = null;
462         boolean ret = false;
463         try {
464             cursor = mDatabase.query(mTableNames[tableId], ID_PROJECTION,
465                     null, null, null, null, null);
466             ret = cursor.moveToFirst() == true;
467         } catch (IllegalStateException e) {
468             Log.e(LOGTAG, "hasEntries", e);
469         } finally {
470             if (cursor != null) cursor.close();
471         }
472         return ret;
473     }
474 
475     //
476     // cookies functions
477     //
478 
479     /**
480      * Get cookies in the format of CookieManager.Cookie inside an ArrayList for
481      * a given domain
482      *
483      * @return ArrayList<Cookie> If nothing is found, return an empty list.
484      */
getCookiesForDomain(String domain)485     ArrayList<Cookie> getCookiesForDomain(String domain) {
486         ArrayList<Cookie> list = new ArrayList<Cookie>();
487         if (domain == null || !checkInitialized()) {
488             return list;
489         }
490 
491         synchronized (mCookieLock) {
492             final String[] columns = new String[] {
493                     ID_COL, COOKIES_DOMAIN_COL, COOKIES_PATH_COL,
494                     COOKIES_NAME_COL, COOKIES_VALUE_COL, COOKIES_EXPIRES_COL,
495                     COOKIES_SECURE_COL
496             };
497             final String selection = "(" + COOKIES_DOMAIN_COL
498                     + " GLOB '*' || ?)";
499             Cursor cursor = null;
500             try {
501                 cursor = mDatabase.query(mTableNames[TABLE_COOKIES_ID],
502                         columns, selection, new String[] { domain }, null, null,
503                         null);
504                 if (cursor.moveToFirst()) {
505                     int domainCol = cursor.getColumnIndex(COOKIES_DOMAIN_COL);
506                     int pathCol = cursor.getColumnIndex(COOKIES_PATH_COL);
507                     int nameCol = cursor.getColumnIndex(COOKIES_NAME_COL);
508                     int valueCol = cursor.getColumnIndex(COOKIES_VALUE_COL);
509                     int expiresCol = cursor.getColumnIndex(COOKIES_EXPIRES_COL);
510                     int secureCol = cursor.getColumnIndex(COOKIES_SECURE_COL);
511                     do {
512                         Cookie cookie = new Cookie();
513                         cookie.domain = cursor.getString(domainCol);
514                         cookie.path = cursor.getString(pathCol);
515                         cookie.name = cursor.getString(nameCol);
516                         cookie.value = cursor.getString(valueCol);
517                         if (cursor.isNull(expiresCol)) {
518                             cookie.expires = -1;
519                         } else {
520                             cookie.expires = cursor.getLong(expiresCol);
521                         }
522                         cookie.secure = cursor.getShort(secureCol) != 0;
523                         cookie.mode = Cookie.MODE_NORMAL;
524                         list.add(cookie);
525                     } while (cursor.moveToNext());
526                 }
527             } catch (IllegalStateException e) {
528                 Log.e(LOGTAG, "getCookiesForDomain", e);
529             } finally {
530                 if (cursor != null) cursor.close();
531             }
532             return list;
533         }
534     }
535 
536     /**
537      * Delete cookies which matches (domain, path, name).
538      *
539      * @param domain If it is null, nothing happens.
540      * @param path If it is null, all the cookies match (domain) will be
541      *            deleted.
542      * @param name If it is null, all the cookies match (domain, path) will be
543      *            deleted.
544      */
deleteCookies(String domain, String path, String name)545     void deleteCookies(String domain, String path, String name) {
546         if (domain == null || !checkInitialized()) {
547             return;
548         }
549 
550         synchronized (mCookieLock) {
551             final String where = "(" + COOKIES_DOMAIN_COL + " == ?) AND ("
552                     + COOKIES_PATH_COL + " == ?) AND (" + COOKIES_NAME_COL
553                     + " == ?)";
554             mDatabase.delete(mTableNames[TABLE_COOKIES_ID], where,
555                     new String[] { domain, path, name });
556         }
557     }
558 
559     /**
560      * Add a cookie to the database
561      *
562      * @param cookie
563      */
addCookie(Cookie cookie)564     void addCookie(Cookie cookie) {
565         if (cookie.domain == null || cookie.path == null || cookie.name == null
566                 || !checkInitialized()) {
567             return;
568         }
569 
570         synchronized (mCookieLock) {
571             ContentValues cookieVal = new ContentValues();
572             cookieVal.put(COOKIES_DOMAIN_COL, cookie.domain);
573             cookieVal.put(COOKIES_PATH_COL, cookie.path);
574             cookieVal.put(COOKIES_NAME_COL, cookie.name);
575             cookieVal.put(COOKIES_VALUE_COL, cookie.value);
576             if (cookie.expires != -1) {
577                 cookieVal.put(COOKIES_EXPIRES_COL, cookie.expires);
578             }
579             cookieVal.put(COOKIES_SECURE_COL, cookie.secure);
580             mDatabase.insert(mTableNames[TABLE_COOKIES_ID], null, cookieVal);
581         }
582     }
583 
584     /**
585      * Whether there is any cookies in the database
586      *
587      * @return TRUE if there is cookie.
588      */
hasCookies()589     boolean hasCookies() {
590         synchronized (mCookieLock) {
591             return hasEntries(TABLE_COOKIES_ID);
592         }
593     }
594 
595     /**
596      * Clear cookie database
597      */
clearCookies()598     void clearCookies() {
599         if (!checkInitialized()) {
600             return;
601         }
602 
603         synchronized (mCookieLock) {
604             mDatabase.delete(mTableNames[TABLE_COOKIES_ID], null, null);
605         }
606     }
607 
608     /**
609      * Clear session cookies, which means cookie doesn't have EXPIRES.
610      */
clearSessionCookies()611     void clearSessionCookies() {
612         if (!checkInitialized()) {
613             return;
614         }
615 
616         final String sessionExpired = COOKIES_EXPIRES_COL + " ISNULL";
617         synchronized (mCookieLock) {
618             mDatabase.delete(mTableNames[TABLE_COOKIES_ID], sessionExpired,
619                     null);
620         }
621     }
622 
623     /**
624      * Clear expired cookies
625      *
626      * @param now Time for now
627      */
clearExpiredCookies(long now)628     void clearExpiredCookies(long now) {
629         if (!checkInitialized()) {
630             return;
631         }
632 
633         final String expires = COOKIES_EXPIRES_COL + " <= ?";
634         synchronized (mCookieLock) {
635             mDatabase.delete(mTableNames[TABLE_COOKIES_ID], expires,
636                     new String[] { Long.toString(now) });
637         }
638     }
639 
640     //
641     // cache functions
642     //
643 
644     // only called from WebViewWorkerThread
startCacheTransaction()645     boolean startCacheTransaction() {
646         if (++mCacheTransactionRefcount == 1) {
647             if (!Thread.currentThread().equals(
648                     WebViewWorker.getHandler().getLooper().getThread())) {
649                 Log.w(LOGTAG, "startCacheTransaction should be called from "
650                         + "WebViewWorkerThread instead of from "
651                         + Thread.currentThread().getName());
652             }
653             mCacheDatabase.beginTransactionNonExclusive();
654             return true;
655         }
656         return false;
657     }
658 
659     // only called from WebViewWorkerThread
endCacheTransaction()660     boolean endCacheTransaction() {
661         if (--mCacheTransactionRefcount == 0) {
662             if (!Thread.currentThread().equals(
663                     WebViewWorker.getHandler().getLooper().getThread())) {
664                 Log.w(LOGTAG, "endCacheTransaction should be called from "
665                         + "WebViewWorkerThread instead of from "
666                         + Thread.currentThread().getName());
667             }
668             try {
669                 mCacheDatabase.setTransactionSuccessful();
670             } finally {
671                 mCacheDatabase.endTransaction();
672             }
673             return true;
674         }
675         return false;
676     }
677 
678     /**
679      * Get a cache item.
680      *
681      * @param url The url
682      * @return CacheResult The CacheManager.CacheResult
683      */
getCache(String url)684     CacheResult getCache(String url) {
685         assert !JniUtil.useChromiumHttpStack();
686 
687         if (url == null || !checkInitialized()) {
688             return null;
689         }
690 
691         Cursor cursor = null;
692         final String query = "SELECT filepath, lastmodify, etag, expires, "
693                 + "expiresstring, mimetype, encoding, httpstatus, location, contentlength, "
694                 + "contentdisposition, crossdomain FROM cache WHERE url = ?";
695         try {
696             cursor = mCacheDatabase.rawQuery(query, new String[] { url });
697             if (cursor.moveToFirst()) {
698                 CacheResult ret = new CacheResult();
699                 ret.localPath = cursor.getString(0);
700                 ret.lastModified = cursor.getString(1);
701                 ret.etag = cursor.getString(2);
702                 ret.expires = cursor.getLong(3);
703                 ret.expiresString = cursor.getString(4);
704                 ret.mimeType = cursor.getString(5);
705                 ret.encoding = cursor.getString(6);
706                 ret.httpStatusCode = cursor.getInt(7);
707                 ret.location = cursor.getString(8);
708                 ret.contentLength = cursor.getLong(9);
709                 ret.contentdisposition = cursor.getString(10);
710                 ret.crossDomain = cursor.getString(11);
711                 return ret;
712             }
713         } catch (IllegalStateException e) {
714             Log.e(LOGTAG, "getCache", e);
715         } finally {
716             if (cursor != null) cursor.close();
717         }
718         return null;
719     }
720 
721     /**
722      * Remove a cache item.
723      *
724      * @param url The url
725      */
removeCache(String url)726     void removeCache(String url) {
727         assert !JniUtil.useChromiumHttpStack();
728 
729         if (url == null || !checkInitialized()) {
730             return;
731         }
732 
733         mCacheDatabase.execSQL("DELETE FROM cache WHERE url = ?", new String[] { url });
734     }
735 
736     /**
737      * Add or update a cache. CACHE_URL_COL is unique in the table.
738      *
739      * @param url The url
740      * @param c The CacheManager.CacheResult
741      */
addCache(String url, CacheResult c)742     void addCache(String url, CacheResult c) {
743         assert !JniUtil.useChromiumHttpStack();
744 
745         if (url == null || !checkInitialized()) {
746             return;
747         }
748 
749         mCacheInserter.prepareForInsert();
750         mCacheInserter.bind(mCacheUrlColIndex, url);
751         mCacheInserter.bind(mCacheFilePathColIndex, c.localPath);
752         mCacheInserter.bind(mCacheLastModifyColIndex, c.lastModified);
753         mCacheInserter.bind(mCacheETagColIndex, c.etag);
754         mCacheInserter.bind(mCacheExpiresColIndex, c.expires);
755         mCacheInserter.bind(mCacheExpiresStringColIndex, c.expiresString);
756         mCacheInserter.bind(mCacheMimeTypeColIndex, c.mimeType);
757         mCacheInserter.bind(mCacheEncodingColIndex, c.encoding);
758         mCacheInserter.bind(mCacheHttpStatusColIndex, c.httpStatusCode);
759         mCacheInserter.bind(mCacheLocationColIndex, c.location);
760         mCacheInserter.bind(mCacheContentLengthColIndex, c.contentLength);
761         mCacheInserter.bind(mCacheContentDispositionColIndex,
762                 c.contentdisposition);
763         mCacheInserter.bind(mCacheCrossDomainColIndex, c.crossDomain);
764         mCacheInserter.execute();
765     }
766 
767     /**
768      * Clear cache database
769      */
clearCache()770     void clearCache() {
771         if (!checkInitialized()) {
772             return;
773         }
774 
775         mCacheDatabase.delete("cache", null, null);
776     }
777 
hasCache()778     boolean hasCache() {
779         if (!checkInitialized()) {
780             return false;
781         }
782 
783         Cursor cursor = null;
784         boolean ret = false;
785         try {
786             cursor = mCacheDatabase.query("cache", ID_PROJECTION,
787                     null, null, null, null, null);
788             ret = cursor.moveToFirst() == true;
789         } catch (IllegalStateException e) {
790             Log.e(LOGTAG, "hasCache", e);
791         } finally {
792             if (cursor != null) cursor.close();
793         }
794         return ret;
795     }
796 
getCacheTotalSize()797     long getCacheTotalSize() {
798         if (mCacheDatabase == null) {
799             return 0;
800         }
801         long size = 0;
802         Cursor cursor = null;
803         final String query = "SELECT SUM(contentlength) as sum FROM cache";
804         try {
805             cursor = mCacheDatabase.rawQuery(query, null);
806             if (cursor.moveToFirst()) {
807                 size = cursor.getLong(0);
808             }
809         } catch (IllegalStateException e) {
810             Log.e(LOGTAG, "getCacheTotalSize", e);
811         } finally {
812             if (cursor != null) cursor.close();
813         }
814         return size;
815     }
816 
trimCache(long amount)817     List<String> trimCache(long amount) {
818         ArrayList<String> pathList = new ArrayList<String>(100);
819         Cursor cursor = null;
820         final String query = "SELECT contentlength, filepath FROM cache ORDER BY expires ASC";
821         try {
822             cursor = mCacheDatabase.rawQuery(query, null);
823             if (cursor.moveToFirst()) {
824                 int batchSize = 100;
825                 StringBuilder pathStr = new StringBuilder(20 + 16 * batchSize);
826                 pathStr.append("DELETE FROM cache WHERE filepath IN (?");
827                 for (int i = 1; i < batchSize; i++) {
828                     pathStr.append(", ?");
829                 }
830                 pathStr.append(")");
831                 SQLiteStatement statement = null;
832                 try {
833                     statement = mCacheDatabase.compileStatement(
834                             pathStr.toString());
835                     // as bindString() uses 1-based index, initialize index to 1
836                     int index = 1;
837                     do {
838                         long length = cursor.getLong(0);
839                         if (length == 0) {
840                             continue;
841                         }
842                         amount -= length;
843                         String filePath = cursor.getString(1);
844                         statement.bindString(index, filePath);
845                         pathList.add(filePath);
846                         if (index++ == batchSize) {
847                             statement.execute();
848                             statement.clearBindings();
849                             index = 1;
850                         }
851                     } while (cursor.moveToNext() && amount > 0);
852                     if (index > 1) {
853                         // there may be old bindings from the previous statement
854                         // if index is less than batchSize, which is Ok.
855                         statement.execute();
856                     }
857                 } catch (IllegalStateException e) {
858                     Log.e(LOGTAG, "trimCache SQLiteStatement", e);
859                 } finally {
860                     if (statement != null) statement.close();
861                 }
862             }
863         } catch (IllegalStateException e) {
864             Log.e(LOGTAG, "trimCache Cursor", e);
865         } finally {
866             if (cursor != null) cursor.close();
867         }
868         return pathList;
869     }
870 
getAllCacheFileNames()871     List<String> getAllCacheFileNames() {
872         ArrayList<String> pathList = null;
873         Cursor cursor = null;
874         try {
875             cursor = mCacheDatabase.rawQuery("SELECT filepath FROM cache",
876                     null);
877             if (cursor != null && cursor.moveToFirst()) {
878                 pathList = new ArrayList<String>(cursor.getCount());
879                 do {
880                     pathList.add(cursor.getString(0));
881                 } while (cursor.moveToNext());
882             }
883         } catch (IllegalStateException e) {
884             Log.e(LOGTAG, "getAllCacheFileNames", e);
885         } finally {
886             if (cursor != null) cursor.close();
887         }
888         return pathList;
889     }
890 
891     //
892     // password functions
893     //
894 
895     /**
896      * Set password. Tuple (PASSWORD_HOST_COL, PASSWORD_USERNAME_COL) is unique.
897      *
898      * @param schemePlusHost The scheme and host for the password
899      * @param username The username for the password. If it is null, it means
900      *            password can't be saved.
901      * @param password The password
902      */
setUsernamePassword(String schemePlusHost, String username, String password)903     void setUsernamePassword(String schemePlusHost, String username,
904                 String password) {
905         if (schemePlusHost == null || !checkInitialized()) {
906             return;
907         }
908 
909         synchronized (mPasswordLock) {
910             final ContentValues c = new ContentValues();
911             c.put(PASSWORD_HOST_COL, schemePlusHost);
912             c.put(PASSWORD_USERNAME_COL, username);
913             c.put(PASSWORD_PASSWORD_COL, password);
914             mDatabase.insert(mTableNames[TABLE_PASSWORD_ID], PASSWORD_HOST_COL,
915                     c);
916         }
917     }
918 
919     /**
920      * Retrieve the username and password for a given host
921      *
922      * @param schemePlusHost The scheme and host which passwords applies to
923      * @return String[] if found, String[0] is username, which can be null and
924      *         String[1] is password. Return null if it can't find anything.
925      */
getUsernamePassword(String schemePlusHost)926     String[] getUsernamePassword(String schemePlusHost) {
927         if (schemePlusHost == null || !checkInitialized()) {
928             return null;
929         }
930 
931         final String[] columns = new String[] {
932                 PASSWORD_USERNAME_COL, PASSWORD_PASSWORD_COL
933         };
934         final String selection = "(" + PASSWORD_HOST_COL + " == ?)";
935         synchronized (mPasswordLock) {
936             String[] ret = null;
937             Cursor cursor = null;
938             try {
939                 cursor = mDatabase.query(mTableNames[TABLE_PASSWORD_ID],
940                         columns, selection, new String[] { schemePlusHost }, null,
941                         null, null);
942                 if (cursor.moveToFirst()) {
943                     ret = new String[2];
944                     ret[0] = cursor.getString(
945                             cursor.getColumnIndex(PASSWORD_USERNAME_COL));
946                     ret[1] = cursor.getString(
947                             cursor.getColumnIndex(PASSWORD_PASSWORD_COL));
948                 }
949             } catch (IllegalStateException e) {
950                 Log.e(LOGTAG, "getUsernamePassword", e);
951             } finally {
952                 if (cursor != null) cursor.close();
953             }
954             return ret;
955         }
956     }
957 
958     /**
959      * Find out if there are any passwords saved.
960      *
961      * @return TRUE if there is passwords saved
962      */
hasUsernamePassword()963     public boolean hasUsernamePassword() {
964         synchronized (mPasswordLock) {
965             return hasEntries(TABLE_PASSWORD_ID);
966         }
967     }
968 
969     /**
970      * Clear password database
971      */
clearUsernamePassword()972     public void clearUsernamePassword() {
973         if (!checkInitialized()) {
974             return;
975         }
976 
977         synchronized (mPasswordLock) {
978             mDatabase.delete(mTableNames[TABLE_PASSWORD_ID], null, null);
979         }
980     }
981 
982     //
983     // http authentication password functions
984     //
985 
986     /**
987      * Set HTTP authentication password. Tuple (HTTPAUTH_HOST_COL,
988      * HTTPAUTH_REALM_COL, HTTPAUTH_USERNAME_COL) is unique.
989      *
990      * @param host The host for the password
991      * @param realm The realm for the password
992      * @param username The username for the password. If it is null, it means
993      *            password can't be saved.
994      * @param password The password
995      */
setHttpAuthUsernamePassword(String host, String realm, String username, String password)996     void setHttpAuthUsernamePassword(String host, String realm, String username,
997             String password) {
998         if (host == null || realm == null || !checkInitialized()) {
999             return;
1000         }
1001 
1002         synchronized (mHttpAuthLock) {
1003             final ContentValues c = new ContentValues();
1004             c.put(HTTPAUTH_HOST_COL, host);
1005             c.put(HTTPAUTH_REALM_COL, realm);
1006             c.put(HTTPAUTH_USERNAME_COL, username);
1007             c.put(HTTPAUTH_PASSWORD_COL, password);
1008             mDatabase.insert(mTableNames[TABLE_HTTPAUTH_ID], HTTPAUTH_HOST_COL,
1009                     c);
1010         }
1011     }
1012 
1013     /**
1014      * Retrieve the HTTP authentication username and password for a given
1015      * host+realm pair
1016      *
1017      * @param host The host the password applies to
1018      * @param realm The realm the password applies to
1019      * @return String[] if found, String[0] is username, which can be null and
1020      *         String[1] is password. Return null if it can't find anything.
1021      */
getHttpAuthUsernamePassword(String host, String realm)1022     String[] getHttpAuthUsernamePassword(String host, String realm) {
1023         if (host == null || realm == null || !checkInitialized()){
1024             return null;
1025         }
1026 
1027         final String[] columns = new String[] {
1028                 HTTPAUTH_USERNAME_COL, HTTPAUTH_PASSWORD_COL
1029         };
1030         final String selection = "(" + HTTPAUTH_HOST_COL + " == ?) AND ("
1031                 + HTTPAUTH_REALM_COL + " == ?)";
1032         synchronized (mHttpAuthLock) {
1033             String[] ret = null;
1034             Cursor cursor = null;
1035             try {
1036                 cursor = mDatabase.query(mTableNames[TABLE_HTTPAUTH_ID],
1037                         columns, selection, new String[] { host, realm }, null,
1038                         null, null);
1039                 if (cursor.moveToFirst()) {
1040                     ret = new String[2];
1041                     ret[0] = cursor.getString(
1042                             cursor.getColumnIndex(HTTPAUTH_USERNAME_COL));
1043                     ret[1] = cursor.getString(
1044                             cursor.getColumnIndex(HTTPAUTH_PASSWORD_COL));
1045                 }
1046             } catch (IllegalStateException e) {
1047                 Log.e(LOGTAG, "getHttpAuthUsernamePassword", e);
1048             } finally {
1049                 if (cursor != null) cursor.close();
1050             }
1051             return ret;
1052         }
1053     }
1054 
1055     /**
1056      *  Find out if there are any HTTP authentication passwords saved.
1057      *
1058      * @return TRUE if there are passwords saved
1059      */
hasHttpAuthUsernamePassword()1060     public boolean hasHttpAuthUsernamePassword() {
1061         synchronized (mHttpAuthLock) {
1062             return hasEntries(TABLE_HTTPAUTH_ID);
1063         }
1064     }
1065 
1066     /**
1067      * Clear HTTP authentication password database
1068      */
clearHttpAuthUsernamePassword()1069     public void clearHttpAuthUsernamePassword() {
1070         if (!checkInitialized()) {
1071             return;
1072         }
1073 
1074         synchronized (mHttpAuthLock) {
1075             mDatabase.delete(mTableNames[TABLE_HTTPAUTH_ID], null, null);
1076         }
1077     }
1078 
1079     //
1080     // form data functions
1081     //
1082 
1083     /**
1084      * Set form data for a site. Tuple (FORMDATA_URLID_COL, FORMDATA_NAME_COL,
1085      * FORMDATA_VALUE_COL) is unique
1086      *
1087      * @param url The url of the site
1088      * @param formdata The form data in HashMap
1089      */
setFormData(String url, HashMap<String, String> formdata)1090     void setFormData(String url, HashMap<String, String> formdata) {
1091         if (url == null || formdata == null || !checkInitialized()) {
1092             return;
1093         }
1094 
1095         final String selection = "(" + FORMURL_URL_COL + " == ?)";
1096         synchronized (mFormLock) {
1097             long urlid = -1;
1098             Cursor cursor = null;
1099             try {
1100                 cursor = mDatabase.query(mTableNames[TABLE_FORMURL_ID],
1101                         ID_PROJECTION, selection, new String[] { url }, null, null,
1102                         null);
1103                 if (cursor.moveToFirst()) {
1104                     urlid = cursor.getLong(cursor.getColumnIndex(ID_COL));
1105                 } else {
1106                     ContentValues c = new ContentValues();
1107                     c.put(FORMURL_URL_COL, url);
1108                     urlid = mDatabase.insert(
1109                             mTableNames[TABLE_FORMURL_ID], null, c);
1110                 }
1111             } catch (IllegalStateException e) {
1112                 Log.e(LOGTAG, "setFormData", e);
1113             } finally {
1114                 if (cursor != null) cursor.close();
1115             }
1116             if (urlid >= 0) {
1117                 Set<Entry<String, String>> set = formdata.entrySet();
1118                 Iterator<Entry<String, String>> iter = set.iterator();
1119                 ContentValues map = new ContentValues();
1120                 map.put(FORMDATA_URLID_COL, urlid);
1121                 while (iter.hasNext()) {
1122                     Entry<String, String> entry = iter.next();
1123                     map.put(FORMDATA_NAME_COL, entry.getKey());
1124                     map.put(FORMDATA_VALUE_COL, entry.getValue());
1125                     mDatabase.insert(mTableNames[TABLE_FORMDATA_ID], null, map);
1126                 }
1127             }
1128         }
1129     }
1130 
1131     /**
1132      * Get all the values for a form entry with "name" in a given site
1133      *
1134      * @param url The url of the site
1135      * @param name The name of the form entry
1136      * @return A list of values. Return empty list if nothing is found.
1137      */
getFormData(String url, String name)1138     ArrayList<String> getFormData(String url, String name) {
1139         ArrayList<String> values = new ArrayList<String>();
1140         if (url == null || name == null || !checkInitialized()) {
1141             return values;
1142         }
1143 
1144         final String urlSelection = "(" + FORMURL_URL_COL + " == ?)";
1145         final String dataSelection = "(" + FORMDATA_URLID_COL + " == ?) AND ("
1146                 + FORMDATA_NAME_COL + " == ?)";
1147         synchronized (mFormLock) {
1148             Cursor cursor = null;
1149             try {
1150                 cursor = mDatabase.query(mTableNames[TABLE_FORMURL_ID],
1151                         ID_PROJECTION, urlSelection, new String[] { url }, null,
1152                         null, null);
1153                 if (cursor.moveToFirst()) {
1154                     long urlid = cursor.getLong(cursor.getColumnIndex(ID_COL));
1155                     Cursor dataCursor = null;
1156                     try {
1157                         dataCursor = mDatabase.query(
1158                                 mTableNames[TABLE_FORMDATA_ID],
1159                                 new String[] { ID_COL, FORMDATA_VALUE_COL },
1160                                 dataSelection,
1161                                 new String[] { Long.toString(urlid), name },
1162                                 null, null, null);
1163                         if (dataCursor.moveToFirst()) {
1164                             int valueCol = dataCursor.getColumnIndex(
1165                                     FORMDATA_VALUE_COL);
1166                             do {
1167                                 values.add(dataCursor.getString(valueCol));
1168                             } while (dataCursor.moveToNext());
1169                         }
1170                     } catch (IllegalStateException e) {
1171                         Log.e(LOGTAG, "getFormData dataCursor", e);
1172                     } finally {
1173                         if (dataCursor != null) dataCursor.close();
1174                     }
1175                 }
1176             } catch (IllegalStateException e) {
1177                 Log.e(LOGTAG, "getFormData cursor", e);
1178             } finally {
1179                 if (cursor != null) cursor.close();
1180             }
1181             return values;
1182         }
1183     }
1184 
1185     /**
1186      * Find out if there is form data saved.
1187      *
1188      * @return TRUE if there is form data in the database
1189      */
hasFormData()1190     public boolean hasFormData() {
1191         synchronized (mFormLock) {
1192             return hasEntries(TABLE_FORMURL_ID);
1193         }
1194     }
1195 
1196     /**
1197      * Clear form database
1198      */
clearFormData()1199     public void clearFormData() {
1200         if (!checkInitialized()) {
1201             return;
1202         }
1203 
1204         synchronized (mFormLock) {
1205             mDatabase.delete(mTableNames[TABLE_FORMURL_ID], null, null);
1206             mDatabase.delete(mTableNames[TABLE_FORMDATA_ID], null, null);
1207         }
1208     }
1209 }
1210