• 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.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