• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 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 
35 final class WebViewDatabaseClassic extends WebViewDatabase {
36     private static final String LOGTAG = "WebViewDatabaseClassic";
37     private static final String DATABASE_FILE = "webview.db";
38     private static final String CACHE_DATABASE_FILE = "webviewCache.db";
39 
40     private static final int DATABASE_VERSION = 11;
41     // 2 -> 3 Modified Cache table to allow cache of redirects
42     // 3 -> 4 Added Oma-Downloads table
43     // 4 -> 5 Modified Cache table to support persistent contentLength
44     // 5 -> 4 Removed Oma-Downoads table
45     // 5 -> 6 Add INDEX for cache table
46     // 6 -> 7 Change cache localPath from int to String
47     // 7 -> 8 Move cache to its own db
48     // 8 -> 9 Store both scheme and host when storing passwords
49     // 9 -> 10 Update httpauth table UNIQUE
50     // 10 -> 11 Drop cookies and cache now managed by the chromium stack,
51     //          and update the form data table to use the new format
52     //          implemented for b/5265606.
53 
54     private static WebViewDatabaseClassic sInstance = null;
55     private static final Object sInstanceLock = new Object();
56 
57     private static SQLiteDatabase sDatabase = null;
58 
59     // synchronize locks
60     private final Object mPasswordLock = new Object();
61     private final Object mFormLock = new Object();
62     private final Object mHttpAuthLock = new Object();
63 
64     private static final String mTableNames[] = {
65         "password", "formurl", "formdata", "httpauth"
66     };
67 
68     // Table ids (they are index to mTableNames)
69     private static final int TABLE_PASSWORD_ID = 0;
70     private static final int TABLE_FORMURL_ID = 1;
71     private static final int TABLE_FORMDATA_ID = 2;
72     private static final int TABLE_HTTPAUTH_ID = 3;
73 
74     // column id strings for "_id" which can be used by any table
75     private static final String ID_COL = "_id";
76 
77     private static final String[] ID_PROJECTION = new String[] {
78         "_id"
79     };
80 
81     // column id strings for "password" table
82     private static final String PASSWORD_HOST_COL = "host";
83     private static final String PASSWORD_USERNAME_COL = "username";
84     private static final String PASSWORD_PASSWORD_COL = "password";
85 
86     // column id strings for "formurl" table
87     private static final String FORMURL_URL_COL = "url";
88 
89     // column id strings for "formdata" table
90     private static final String FORMDATA_URLID_COL = "urlid";
91     private static final String FORMDATA_NAME_COL = "name";
92     private static final String FORMDATA_VALUE_COL = "value";
93 
94     // column id strings for "httpauth" table
95     private static final String HTTPAUTH_HOST_COL = "host";
96     private static final String HTTPAUTH_REALM_COL = "realm";
97     private static final String HTTPAUTH_USERNAME_COL = "username";
98     private static final String HTTPAUTH_PASSWORD_COL = "password";
99 
100     // Initially true until the background thread completes.
101     private boolean mInitialized = false;
102 
WebViewDatabaseClassic(final Context context)103     private WebViewDatabaseClassic(final Context context) {
104         JniUtil.setContext(context);
105         new Thread() {
106             @Override
107             public void run() {
108                 init(context);
109             }
110         }.start();
111 
112         // Singleton only, use getInstance()
113     }
114 
getInstance(Context context)115     public static WebViewDatabaseClassic getInstance(Context context) {
116         synchronized (sInstanceLock) {
117             if (sInstance == null) {
118                 sInstance = new WebViewDatabaseClassic(context);
119             }
120             return sInstance;
121         }
122     }
123 
init(Context context)124     private synchronized void init(Context context) {
125         if (mInitialized) {
126             return;
127         }
128 
129         initDatabase(context);
130         // Before using the Chromium HTTP stack, we stored the WebKit cache in
131         // our own DB. Clean up the DB file if it's still around.
132         context.deleteDatabase(CACHE_DATABASE_FILE);
133 
134         // Thread done, notify.
135         mInitialized = true;
136         notify();
137     }
138 
initDatabase(Context context)139     private void initDatabase(Context context) {
140         try {
141             sDatabase = context.openOrCreateDatabase(DATABASE_FILE, 0, null);
142         } catch (SQLiteException e) {
143             // try again by deleting the old db and create a new one
144             if (context.deleteDatabase(DATABASE_FILE)) {
145                 sDatabase = context.openOrCreateDatabase(DATABASE_FILE, 0,
146                         null);
147             }
148         }
149 
150         // sDatabase should not be null,
151         // the only case is RequestAPI test has problem to create db
152         if (sDatabase == null) {
153             mInitialized = true;
154             notify();
155             return;
156         }
157 
158         if (sDatabase.getVersion() != DATABASE_VERSION) {
159             sDatabase.beginTransactionNonExclusive();
160             try {
161                 upgradeDatabase();
162                 sDatabase.setTransactionSuccessful();
163             } finally {
164                 sDatabase.endTransaction();
165             }
166         }
167     }
168 
upgradeDatabase()169     private static void upgradeDatabase() {
170         upgradeDatabaseToV10();
171         upgradeDatabaseFromV10ToV11();
172         // Add future database upgrade functions here, one version at a
173         // time.
174         sDatabase.setVersion(DATABASE_VERSION);
175     }
176 
upgradeDatabaseFromV10ToV11()177     private static void upgradeDatabaseFromV10ToV11() {
178         int oldVersion = sDatabase.getVersion();
179 
180         if (oldVersion >= 11) {
181             // Nothing to do.
182             return;
183         }
184 
185         // Clear out old java stack cookies - this data is now stored in
186         // a separate database managed by the Chrome stack.
187         sDatabase.execSQL("DROP TABLE IF EXISTS cookies");
188 
189         // Likewise for the old cache table.
190         sDatabase.execSQL("DROP TABLE IF EXISTS cache");
191 
192         // Update form autocomplete  URLs to match new ICS formatting.
193         Cursor c = sDatabase.query(mTableNames[TABLE_FORMURL_ID], null, null,
194                 null, null, null, null);
195         while (c.moveToNext()) {
196             String urlId = Long.toString(c.getLong(c.getColumnIndex(ID_COL)));
197             String url = c.getString(c.getColumnIndex(FORMURL_URL_COL));
198             ContentValues cv = new ContentValues(1);
199             cv.put(FORMURL_URL_COL, WebTextView.urlForAutoCompleteData(url));
200             sDatabase.update(mTableNames[TABLE_FORMURL_ID], cv, ID_COL + "=?",
201                     new String[] { urlId });
202         }
203         c.close();
204     }
205 
upgradeDatabaseToV10()206     private static void upgradeDatabaseToV10() {
207         int oldVersion = sDatabase.getVersion();
208 
209         if (oldVersion >= 10) {
210             // Nothing to do.
211             return;
212         }
213 
214         if (oldVersion != 0) {
215             Log.i(LOGTAG, "Upgrading database from version "
216                     + oldVersion + " to "
217                     + DATABASE_VERSION + ", which will destroy old data");
218         }
219 
220         if (9 == oldVersion) {
221             sDatabase.execSQL("DROP TABLE IF EXISTS "
222                     + mTableNames[TABLE_HTTPAUTH_ID]);
223             sDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_HTTPAUTH_ID]
224                     + " (" + ID_COL + " INTEGER PRIMARY KEY, "
225                     + HTTPAUTH_HOST_COL + " TEXT, " + HTTPAUTH_REALM_COL
226                     + " TEXT, " + HTTPAUTH_USERNAME_COL + " TEXT, "
227                     + HTTPAUTH_PASSWORD_COL + " TEXT," + " UNIQUE ("
228                     + HTTPAUTH_HOST_COL + ", " + HTTPAUTH_REALM_COL
229                     + ") ON CONFLICT REPLACE);");
230             return;
231         }
232 
233         sDatabase.execSQL("DROP TABLE IF EXISTS cookies");
234         sDatabase.execSQL("DROP TABLE IF EXISTS cache");
235         sDatabase.execSQL("DROP TABLE IF EXISTS "
236                 + mTableNames[TABLE_FORMURL_ID]);
237         sDatabase.execSQL("DROP TABLE IF EXISTS "
238                 + mTableNames[TABLE_FORMDATA_ID]);
239         sDatabase.execSQL("DROP TABLE IF EXISTS "
240                 + mTableNames[TABLE_HTTPAUTH_ID]);
241         sDatabase.execSQL("DROP TABLE IF EXISTS "
242                 + mTableNames[TABLE_PASSWORD_ID]);
243 
244         // formurl
245         sDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_FORMURL_ID]
246                 + " (" + ID_COL + " INTEGER PRIMARY KEY, " + FORMURL_URL_COL
247                 + " TEXT" + ");");
248 
249         // formdata
250         sDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_FORMDATA_ID]
251                 + " (" + ID_COL + " INTEGER PRIMARY KEY, "
252                 + FORMDATA_URLID_COL + " INTEGER, " + FORMDATA_NAME_COL
253                 + " TEXT, " + FORMDATA_VALUE_COL + " TEXT," + " UNIQUE ("
254                 + FORMDATA_URLID_COL + ", " + FORMDATA_NAME_COL + ", "
255                 + FORMDATA_VALUE_COL + ") ON CONFLICT IGNORE);");
256 
257         // httpauth
258         sDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_HTTPAUTH_ID]
259                 + " (" + ID_COL + " INTEGER PRIMARY KEY, "
260                 + HTTPAUTH_HOST_COL + " TEXT, " + HTTPAUTH_REALM_COL
261                 + " TEXT, " + HTTPAUTH_USERNAME_COL + " TEXT, "
262                 + HTTPAUTH_PASSWORD_COL + " TEXT," + " UNIQUE ("
263                 + HTTPAUTH_HOST_COL + ", " + HTTPAUTH_REALM_COL
264                 + ") ON CONFLICT REPLACE);");
265         // passwords
266         sDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_PASSWORD_ID]
267                 + " (" + ID_COL + " INTEGER PRIMARY KEY, "
268                 + PASSWORD_HOST_COL + " TEXT, " + PASSWORD_USERNAME_COL
269                 + " TEXT, " + PASSWORD_PASSWORD_COL + " TEXT," + " UNIQUE ("
270                 + PASSWORD_HOST_COL + ", " + PASSWORD_USERNAME_COL
271                 + ") ON CONFLICT REPLACE);");
272     }
273 
274     // Wait for the background initialization thread to complete and check the
275     // database creation status.
checkInitialized()276     private boolean checkInitialized() {
277         synchronized (this) {
278             while (!mInitialized) {
279                 try {
280                     wait();
281                 } catch (InterruptedException e) {
282                     Log.e(LOGTAG, "Caught exception while checking " +
283                                   "initialization");
284                     Log.e(LOGTAG, Log.getStackTraceString(e));
285                 }
286             }
287         }
288         return sDatabase != null;
289     }
290 
hasEntries(int tableId)291     private boolean hasEntries(int tableId) {
292         if (!checkInitialized()) {
293             return false;
294         }
295 
296         Cursor cursor = null;
297         boolean ret = false;
298         try {
299             cursor = sDatabase.query(mTableNames[tableId], ID_PROJECTION,
300                     null, null, null, null, null);
301             ret = cursor.moveToFirst() == true;
302         } catch (IllegalStateException e) {
303             Log.e(LOGTAG, "hasEntries", e);
304         } finally {
305             if (cursor != null) cursor.close();
306         }
307         return ret;
308     }
309 
310     //
311     // password functions
312     //
313 
314     /**
315      * Set password. Tuple (PASSWORD_HOST_COL, PASSWORD_USERNAME_COL) is unique.
316      *
317      * @param schemePlusHost The scheme and host for the password
318      * @param username The username for the password. If it is null, it means
319      *            password can't be saved.
320      * @param password The password
321      */
setUsernamePassword(String schemePlusHost, String username, String password)322     void setUsernamePassword(String schemePlusHost, String username,
323                 String password) {
324         if (schemePlusHost == null || !checkInitialized()) {
325             return;
326         }
327 
328         synchronized (mPasswordLock) {
329             final ContentValues c = new ContentValues();
330             c.put(PASSWORD_HOST_COL, schemePlusHost);
331             c.put(PASSWORD_USERNAME_COL, username);
332             c.put(PASSWORD_PASSWORD_COL, password);
333             sDatabase.insert(mTableNames[TABLE_PASSWORD_ID], PASSWORD_HOST_COL,
334                     c);
335         }
336     }
337 
338     /**
339      * Retrieve the username and password for a given host
340      *
341      * @param schemePlusHost The scheme and host which passwords applies to
342      * @return String[] if found, String[0] is username, which can be null and
343      *         String[1] is password. Return null if it can't find anything.
344      */
getUsernamePassword(String schemePlusHost)345     String[] getUsernamePassword(String schemePlusHost) {
346         if (schemePlusHost == null || !checkInitialized()) {
347             return null;
348         }
349 
350         final String[] columns = new String[] {
351                 PASSWORD_USERNAME_COL, PASSWORD_PASSWORD_COL
352         };
353         final String selection = "(" + PASSWORD_HOST_COL + " == ?)";
354         synchronized (mPasswordLock) {
355             String[] ret = null;
356             Cursor cursor = null;
357             try {
358                 cursor = sDatabase.query(mTableNames[TABLE_PASSWORD_ID],
359                         columns, selection, new String[] { schemePlusHost }, null,
360                         null, null);
361                 if (cursor.moveToFirst()) {
362                     ret = new String[2];
363                     ret[0] = cursor.getString(
364                             cursor.getColumnIndex(PASSWORD_USERNAME_COL));
365                     ret[1] = cursor.getString(
366                             cursor.getColumnIndex(PASSWORD_PASSWORD_COL));
367                 }
368             } catch (IllegalStateException e) {
369                 Log.e(LOGTAG, "getUsernamePassword", e);
370             } finally {
371                 if (cursor != null) cursor.close();
372             }
373             return ret;
374         }
375     }
376 
377     /**
378      * @see WebViewDatabase#hasUsernamePassword
379      */
380     @Override
hasUsernamePassword()381     public boolean hasUsernamePassword() {
382         synchronized (mPasswordLock) {
383             return hasEntries(TABLE_PASSWORD_ID);
384         }
385     }
386 
387     /**
388      * @see WebViewDatabase#clearUsernamePassword
389      */
390     @Override
clearUsernamePassword()391     public void clearUsernamePassword() {
392         if (!checkInitialized()) {
393             return;
394         }
395 
396         synchronized (mPasswordLock) {
397             sDatabase.delete(mTableNames[TABLE_PASSWORD_ID], null, null);
398         }
399     }
400 
401     //
402     // http authentication password functions
403     //
404 
405     /**
406      * Set HTTP authentication password. Tuple (HTTPAUTH_HOST_COL,
407      * HTTPAUTH_REALM_COL, HTTPAUTH_USERNAME_COL) is unique.
408      *
409      * @param host The host for the password
410      * @param realm The realm for the password
411      * @param username The username for the password. If it is null, it means
412      *            password can't be saved.
413      * @param password The password
414      */
setHttpAuthUsernamePassword(String host, String realm, String username, String password)415     void setHttpAuthUsernamePassword(String host, String realm, String username,
416             String password) {
417         if (host == null || realm == null || !checkInitialized()) {
418             return;
419         }
420 
421         synchronized (mHttpAuthLock) {
422             final ContentValues c = new ContentValues();
423             c.put(HTTPAUTH_HOST_COL, host);
424             c.put(HTTPAUTH_REALM_COL, realm);
425             c.put(HTTPAUTH_USERNAME_COL, username);
426             c.put(HTTPAUTH_PASSWORD_COL, password);
427             sDatabase.insert(mTableNames[TABLE_HTTPAUTH_ID], HTTPAUTH_HOST_COL,
428                     c);
429         }
430     }
431 
432     /**
433      * Retrieve the HTTP authentication username and password for a given
434      * host+realm pair
435      *
436      * @param host The host the password applies to
437      * @param realm The realm the password applies to
438      * @return String[] if found, String[0] is username, which can be null and
439      *         String[1] is password. Return null if it can't find anything.
440      */
getHttpAuthUsernamePassword(String host, String realm)441     String[] getHttpAuthUsernamePassword(String host, String realm) {
442         if (host == null || realm == null || !checkInitialized()){
443             return null;
444         }
445 
446         final String[] columns = new String[] {
447                 HTTPAUTH_USERNAME_COL, HTTPAUTH_PASSWORD_COL
448         };
449         final String selection = "(" + HTTPAUTH_HOST_COL + " == ?) AND ("
450                 + HTTPAUTH_REALM_COL + " == ?)";
451         synchronized (mHttpAuthLock) {
452             String[] ret = null;
453             Cursor cursor = null;
454             try {
455                 cursor = sDatabase.query(mTableNames[TABLE_HTTPAUTH_ID],
456                         columns, selection, new String[] { host, realm }, null,
457                         null, null);
458                 if (cursor.moveToFirst()) {
459                     ret = new String[2];
460                     ret[0] = cursor.getString(
461                             cursor.getColumnIndex(HTTPAUTH_USERNAME_COL));
462                     ret[1] = cursor.getString(
463                             cursor.getColumnIndex(HTTPAUTH_PASSWORD_COL));
464                 }
465             } catch (IllegalStateException e) {
466                 Log.e(LOGTAG, "getHttpAuthUsernamePassword", e);
467             } finally {
468                 if (cursor != null) cursor.close();
469             }
470             return ret;
471         }
472     }
473 
474     /**
475      * @see WebViewDatabase#hasHttpAuthUsernamePassword
476      */
477     @Override
hasHttpAuthUsernamePassword()478     public boolean hasHttpAuthUsernamePassword() {
479         synchronized (mHttpAuthLock) {
480             return hasEntries(TABLE_HTTPAUTH_ID);
481         }
482     }
483 
484     /**
485      * @see WebViewDatabase#clearHttpAuthUsernamePassword
486      */
487     @Override
clearHttpAuthUsernamePassword()488     public void clearHttpAuthUsernamePassword() {
489         if (!checkInitialized()) {
490             return;
491         }
492 
493         synchronized (mHttpAuthLock) {
494             sDatabase.delete(mTableNames[TABLE_HTTPAUTH_ID], null, null);
495         }
496     }
497 
498     //
499     // form data functions
500     //
501 
502     /**
503      * Set form data for a site. Tuple (FORMDATA_URLID_COL, FORMDATA_NAME_COL,
504      * FORMDATA_VALUE_COL) is unique
505      *
506      * @param url The url of the site
507      * @param formdata The form data in HashMap
508      */
setFormData(String url, HashMap<String, String> formdata)509     void setFormData(String url, HashMap<String, String> formdata) {
510         if (url == null || formdata == null || !checkInitialized()) {
511             return;
512         }
513 
514         final String selection = "(" + FORMURL_URL_COL + " == ?)";
515         synchronized (mFormLock) {
516             long urlid = -1;
517             Cursor cursor = null;
518             try {
519                 cursor = sDatabase.query(mTableNames[TABLE_FORMURL_ID],
520                         ID_PROJECTION, selection, new String[] { url }, null, null,
521                         null);
522                 if (cursor.moveToFirst()) {
523                     urlid = cursor.getLong(cursor.getColumnIndex(ID_COL));
524                 } else {
525                     ContentValues c = new ContentValues();
526                     c.put(FORMURL_URL_COL, url);
527                     urlid = sDatabase.insert(
528                             mTableNames[TABLE_FORMURL_ID], null, c);
529                 }
530             } catch (IllegalStateException e) {
531                 Log.e(LOGTAG, "setFormData", e);
532             } finally {
533                 if (cursor != null) cursor.close();
534             }
535             if (urlid >= 0) {
536                 Set<Entry<String, String>> set = formdata.entrySet();
537                 Iterator<Entry<String, String>> iter = set.iterator();
538                 ContentValues map = new ContentValues();
539                 map.put(FORMDATA_URLID_COL, urlid);
540                 while (iter.hasNext()) {
541                     Entry<String, String> entry = iter.next();
542                     map.put(FORMDATA_NAME_COL, entry.getKey());
543                     map.put(FORMDATA_VALUE_COL, entry.getValue());
544                     sDatabase.insert(mTableNames[TABLE_FORMDATA_ID], null, map);
545                 }
546             }
547         }
548     }
549 
550     /**
551      * Get all the values for a form entry with "name" in a given site
552      *
553      * @param url The url of the site
554      * @param name The name of the form entry
555      * @return A list of values. Return empty list if nothing is found.
556      */
getFormData(String url, String name)557     ArrayList<String> getFormData(String url, String name) {
558         ArrayList<String> values = new ArrayList<String>();
559         if (url == null || name == null || !checkInitialized()) {
560             return values;
561         }
562 
563         final String urlSelection = "(" + FORMURL_URL_COL + " == ?)";
564         final String dataSelection = "(" + FORMDATA_URLID_COL + " == ?) AND ("
565                 + FORMDATA_NAME_COL + " == ?)";
566         synchronized (mFormLock) {
567             Cursor cursor = null;
568             try {
569                 cursor = sDatabase.query(mTableNames[TABLE_FORMURL_ID],
570                         ID_PROJECTION, urlSelection, new String[] { url }, null,
571                         null, null);
572                 while (cursor.moveToNext()) {
573                     long urlid = cursor.getLong(cursor.getColumnIndex(ID_COL));
574                     Cursor dataCursor = null;
575                     try {
576                         dataCursor = sDatabase.query(
577                                 mTableNames[TABLE_FORMDATA_ID],
578                                 new String[] { ID_COL, FORMDATA_VALUE_COL },
579                                 dataSelection,
580                                 new String[] { Long.toString(urlid), name },
581                                 null, null, null);
582                         if (dataCursor.moveToFirst()) {
583                             int valueCol = dataCursor.getColumnIndex(
584                                     FORMDATA_VALUE_COL);
585                             do {
586                                 values.add(dataCursor.getString(valueCol));
587                             } while (dataCursor.moveToNext());
588                         }
589                     } catch (IllegalStateException e) {
590                         Log.e(LOGTAG, "getFormData dataCursor", e);
591                     } finally {
592                         if (dataCursor != null) dataCursor.close();
593                     }
594                 }
595             } catch (IllegalStateException e) {
596                 Log.e(LOGTAG, "getFormData cursor", e);
597             } finally {
598                 if (cursor != null) cursor.close();
599             }
600             return values;
601         }
602     }
603 
604     /**
605      * @see WebViewDatabase#hasFormData
606      */
607     @Override
hasFormData()608     public boolean hasFormData() {
609         synchronized (mFormLock) {
610             return hasEntries(TABLE_FORMURL_ID);
611         }
612     }
613 
614     /**
615      * @see WebViewDatabase#clearFormData
616      */
617     @Override
clearFormData()618     public void clearFormData() {
619         if (!checkInitialized()) {
620             return;
621         }
622 
623         synchronized (mFormLock) {
624             sDatabase.delete(mTableNames[TABLE_FORMURL_ID], null, null);
625             sDatabase.delete(mTableNames[TABLE_FORMDATA_ID], null, null);
626         }
627     }
628 }
629