1// Copyright 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5package org.chromium.android_webview; 6 7import android.content.ContentValues; 8import android.content.Context; 9import android.database.Cursor; 10import android.database.sqlite.SQLiteDatabase; 11import android.database.sqlite.SQLiteException; 12import android.util.Log; 13 14/** 15 * This database is used to support WebView's setHttpAuthUsernamePassword and 16 * getHttpAuthUsernamePassword methods, and WebViewDatabase's clearHttpAuthUsernamePassword and 17 * hasHttpAuthUsernamePassword methods. 18 * 19 * While this class is intended to be used as a singleton, this property is not enforced in this 20 * layer, primarily for ease of testing. To line up with the classic implementation and behavior, 21 * there is no specific handling and reporting when SQL errors occur. 22 * 23 * Note on thread-safety: As per the classic implementation, most API functions have thread safety 24 * provided by the underlying SQLiteDatabase instance. The exception is database opening: this 25 * is handled in the dedicated background thread, which also provides a performance gain 26 * if triggered early on (e.g. as a side effect of CookieSyncManager.createInstance() call), 27 * sufficiently in advance of the first blocking usage of the API. 28 */ 29public class HttpAuthDatabase { 30 31 private static final String LOGTAG = "HttpAuthDatabase"; 32 33 private static final int DATABASE_VERSION = 1; 34 35 private SQLiteDatabase mDatabase = null; 36 37 private static final String ID_COL = "_id"; 38 39 private static final String[] ID_PROJECTION = new String[] { 40 ID_COL 41 }; 42 43 // column id strings for "httpauth" table 44 private static final String HTTPAUTH_TABLE_NAME = "httpauth"; 45 private static final String HTTPAUTH_HOST_COL = "host"; 46 private static final String HTTPAUTH_REALM_COL = "realm"; 47 private static final String HTTPAUTH_USERNAME_COL = "username"; 48 private static final String HTTPAUTH_PASSWORD_COL = "password"; 49 50 /** 51 * Initially false until the background thread completes. 52 */ 53 private boolean mInitialized = false; 54 55 private final Object mInitializedLock = new Object(); 56 57 /** 58 * Creates and returns an instance of HttpAuthDatabase for the named file, and kicks-off 59 * background initialization of that database. 60 * 61 * @param context the Context to use for opening the database 62 * @param databaseFile Name of the file to be initialized. 63 */ 64 public static HttpAuthDatabase newInstance(final Context context, final String databaseFile) { 65 final HttpAuthDatabase httpAuthDatabase = new HttpAuthDatabase(); 66 new Thread() { 67 @Override 68 public void run() { 69 httpAuthDatabase.initOnBackgroundThread(context, databaseFile); 70 } 71 }.start(); 72 return httpAuthDatabase; 73 } 74 75 // Prevent instantiation. Callers should use newInstance(). 76 private HttpAuthDatabase() {} 77 78 /** 79 * Initializes the databases and notifies any callers waiting on waitForInit. 80 * 81 * @param context the Context to use for opening the database 82 * @param databaseFile Name of the file to be initialized. 83 */ 84 private void initOnBackgroundThread(Context context, String databaseFile) { 85 synchronized (mInitializedLock) { 86 if (mInitialized) { 87 return; 88 } 89 90 initDatabase(context, databaseFile); 91 92 // Thread done, notify. 93 mInitialized = true; 94 mInitializedLock.notifyAll(); 95 } 96 } 97 98 /** 99 * Opens the database, and upgrades it if necessary. 100 * 101 * @param context the Context to use for opening the database 102 * @param databaseFile Name of the file to be initialized. 103 */ 104 private void initDatabase(Context context, String databaseFile) { 105 try { 106 mDatabase = context.openOrCreateDatabase(databaseFile, 0, null); 107 } catch (SQLiteException e) { 108 // try again by deleting the old db and create a new one 109 if (context.deleteDatabase(databaseFile)) { 110 mDatabase = context.openOrCreateDatabase(databaseFile, 0, null); 111 } 112 } 113 114 if (mDatabase == null) { 115 // Not much we can do to recover at this point 116 Log.e(LOGTAG, "Unable to open or create " + databaseFile); 117 return; 118 } 119 120 if (mDatabase.getVersion() != DATABASE_VERSION) { 121 mDatabase.beginTransactionNonExclusive(); 122 try { 123 createTable(); 124 mDatabase.setTransactionSuccessful(); 125 } finally { 126 mDatabase.endTransaction(); 127 } 128 } 129 } 130 131 private void createTable() { 132 mDatabase.execSQL("CREATE TABLE " + HTTPAUTH_TABLE_NAME 133 + " (" + ID_COL + " INTEGER PRIMARY KEY, " 134 + HTTPAUTH_HOST_COL + " TEXT, " + HTTPAUTH_REALM_COL 135 + " TEXT, " + HTTPAUTH_USERNAME_COL + " TEXT, " 136 + HTTPAUTH_PASSWORD_COL + " TEXT," + " UNIQUE (" 137 + HTTPAUTH_HOST_COL + ", " + HTTPAUTH_REALM_COL 138 + ") ON CONFLICT REPLACE);"); 139 140 mDatabase.setVersion(DATABASE_VERSION); 141 } 142 143 /** 144 * Waits for the background initialization thread to complete and check the database creation 145 * status. 146 * 147 * @return true if the database was initialized, false otherwise 148 */ 149 private boolean waitForInit() { 150 synchronized (mInitializedLock) { 151 while (!mInitialized) { 152 try { 153 mInitializedLock.wait(); 154 } catch (InterruptedException e) { 155 Log.e(LOGTAG, "Caught exception while checking initialization", e); 156 } 157 } 158 } 159 return mDatabase != null; 160 } 161 162 /** 163 * Sets the HTTP authentication password. Tuple (HTTPAUTH_HOST_COL, HTTPAUTH_REALM_COL, 164 * HTTPAUTH_USERNAME_COL) is unique. 165 * 166 * @param host the host for the password 167 * @param realm the realm for the password 168 * @param username the username for the password. 169 * @param password the password 170 */ 171 public void setHttpAuthUsernamePassword(String host, String realm, String username, 172 String password) { 173 if (host == null || realm == null || !waitForInit()) { 174 return; 175 } 176 177 final ContentValues c = new ContentValues(); 178 c.put(HTTPAUTH_HOST_COL, host); 179 c.put(HTTPAUTH_REALM_COL, realm); 180 c.put(HTTPAUTH_USERNAME_COL, username); 181 c.put(HTTPAUTH_PASSWORD_COL, password); 182 mDatabase.insert(HTTPAUTH_TABLE_NAME, HTTPAUTH_HOST_COL, c); 183 } 184 185 /** 186 * Retrieves the HTTP authentication username and password for a given host and realm pair. If 187 * there are multiple username/password combinations for a host/realm, only the first one will 188 * be returned. 189 * 190 * @param host the host the password applies to 191 * @param realm the realm the password applies to 192 * @return a String[] if found where String[0] is username (which can be null) and 193 * String[1] is password. Null is returned if it can't find anything. 194 */ 195 public String[] getHttpAuthUsernamePassword(String host, String realm) { 196 if (host == null || realm == null || !waitForInit()) { 197 return null; 198 } 199 200 final String[] columns = new String[] { 201 HTTPAUTH_USERNAME_COL, HTTPAUTH_PASSWORD_COL 202 }; 203 final String selection = "(" + HTTPAUTH_HOST_COL + " == ?) AND " + 204 "(" + HTTPAUTH_REALM_COL + " == ?)"; 205 206 String[] ret = null; 207 Cursor cursor = null; 208 try { 209 cursor = mDatabase.query(HTTPAUTH_TABLE_NAME, columns, selection, 210 new String[] { host, realm }, null, null, null); 211 if (cursor.moveToFirst()) { 212 ret = new String[] { 213 cursor.getString(cursor.getColumnIndex(HTTPAUTH_USERNAME_COL)), 214 cursor.getString(cursor.getColumnIndex(HTTPAUTH_PASSWORD_COL)), 215 }; 216 } 217 } catch (IllegalStateException e) { 218 Log.e(LOGTAG, "getHttpAuthUsernamePassword", e); 219 } finally { 220 if (cursor != null) cursor.close(); 221 } 222 return ret; 223 } 224 225 /** 226 * Determines if there are any HTTP authentication passwords saved. 227 * 228 * @return true if there are passwords saved 229 */ 230 public boolean hasHttpAuthUsernamePassword() { 231 if (!waitForInit()) { 232 return false; 233 } 234 235 Cursor cursor = null; 236 boolean ret = false; 237 try { 238 cursor = mDatabase.query(HTTPAUTH_TABLE_NAME, ID_PROJECTION, null, null, null, null, 239 null); 240 ret = cursor.moveToFirst(); 241 } catch (IllegalStateException e) { 242 Log.e(LOGTAG, "hasEntries", e); 243 } finally { 244 if (cursor != null) cursor.close(); 245 } 246 return ret; 247 } 248 249 /** 250 * Clears the HTTP authentication password database. 251 */ 252 public void clearHttpAuthUsernamePassword() { 253 if (!waitForInit()) { 254 return; 255 } 256 mDatabase.delete(HTTPAUTH_TABLE_NAME, null, null); 257 } 258} 259