1 /* 2 * Copyright (C) 2010 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 android.os.Bundle; 20 import android.os.Handler; 21 import android.os.Message; 22 import android.util.Log; 23 24 import java.util.ListIterator; 25 import java.util.LinkedList; 26 27 /** 28 * HttpAuthHandler implementation is used only by the Android Java HTTP stack. 29 * <p> 30 * This class is not needed when we're using the Chromium HTTP stack. 31 */ 32 class HttpAuthHandlerImpl extends HttpAuthHandler { 33 /* 34 * It is important that the handler is in Network, because we want to share 35 * it accross multiple loaders and windows (like our subwindow and the main 36 * window). 37 */ 38 39 private static final String LOGTAG = "network"; 40 41 /** 42 * Network. 43 */ 44 private Network mNetwork; 45 46 /** 47 * Loader queue. 48 */ 49 private LinkedList<LoadListener> mLoaderQueue; 50 51 52 // Message id for handling the user response 53 private static final int AUTH_PROCEED = 100; 54 private static final int AUTH_CANCEL = 200; 55 56 // Use to synchronize when making synchronous calls to 57 // onReceivedHttpAuthRequest(). We can't use a single Boolean object for 58 // both the lock and the state, because Boolean is immutable. 59 Object mRequestInFlightLock = new Object(); 60 boolean mRequestInFlight; 61 String mUsername; 62 String mPassword; 63 64 /** 65 * Creates a new HTTP authentication handler with an empty 66 * loader queue 67 * 68 * @param network The parent network object 69 */ 70 /* package */ HttpAuthHandlerImpl(Network network) { 71 mNetwork = network; 72 mLoaderQueue = new LinkedList<LoadListener>(); 73 } 74 75 76 @Override 77 public void handleMessage(Message msg) { 78 LoadListener loader = null; 79 synchronized (mLoaderQueue) { 80 loader = mLoaderQueue.poll(); 81 } 82 assert(loader.isSynchronous() == false); 83 84 switch (msg.what) { 85 case AUTH_PROCEED: 86 String username = msg.getData().getString("username"); 87 String password = msg.getData().getString("password"); 88 89 loader.handleAuthResponse(username, password); 90 break; 91 92 case AUTH_CANCEL: 93 loader.handleAuthResponse(null, null); 94 break; 95 } 96 97 processNextLoader(); 98 } 99 100 /** 101 * Helper method used to unblock handleAuthRequest(), which in the case of a 102 * synchronous request will wait for proxy.onReceivedHttpAuthRequest() to 103 * call back to either proceed() or cancel(). 104 * 105 * @param username The username to use for authentication 106 * @param password The password to use for authentication 107 * @return True if the request is synchronous and handleAuthRequest() has 108 * been unblocked 109 */ 110 private boolean handleResponseForSynchronousRequest(String username, String password) { 111 LoadListener loader = null; 112 synchronized (mLoaderQueue) { 113 loader = mLoaderQueue.peek(); 114 } 115 if (loader.isSynchronous()) { 116 mUsername = username; 117 mPassword = password; 118 return true; 119 } 120 return false; 121 } 122 123 private void signalRequestComplete() { 124 synchronized (mRequestInFlightLock) { 125 assert(mRequestInFlight); 126 mRequestInFlight = false; 127 mRequestInFlightLock.notify(); 128 } 129 } 130 131 /** 132 * Proceed with the authorization with the given credentials 133 * 134 * May be called on the UI thread, rather than the WebCore thread. 135 * 136 * @param username The username to use for authentication 137 * @param password The password to use for authentication 138 */ 139 public void proceed(String username, String password) { 140 if (handleResponseForSynchronousRequest(username, password)) { 141 signalRequestComplete(); 142 return; 143 } 144 Message msg = obtainMessage(AUTH_PROCEED); 145 msg.getData().putString("username", username); 146 msg.getData().putString("password", password); 147 sendMessage(msg); 148 signalRequestComplete(); 149 } 150 151 /** 152 * Cancel the authorization request 153 * 154 * May be called on the UI thread, rather than the WebCore thread. 155 * 156 */ 157 public void cancel() { 158 if (handleResponseForSynchronousRequest(null, null)) { 159 signalRequestComplete(); 160 return; 161 } 162 sendMessage(obtainMessage(AUTH_CANCEL)); 163 signalRequestComplete(); 164 } 165 166 /** 167 * @return True if we can use user credentials on record 168 * (ie, if we did not fail trying to use them last time) 169 */ 170 public boolean useHttpAuthUsernamePassword() { 171 LoadListener loader = null; 172 synchronized (mLoaderQueue) { 173 loader = mLoaderQueue.peek(); 174 } 175 if (loader != null) { 176 return !loader.authCredentialsInvalid(); 177 } 178 179 return false; 180 } 181 182 /** 183 * Enqueues the loader, if the loader is the only element 184 * in the queue, starts processing the loader 185 * 186 * @param loader The loader that resulted in this http 187 * authentication request 188 */ 189 /* package */ void handleAuthRequest(LoadListener loader) { 190 // The call to proxy.onReceivedHttpAuthRequest() may be asynchronous. If 191 // the request is synchronous, we must block here until we have a 192 // response. 193 if (loader.isSynchronous()) { 194 // If there's a request in flight, wait for it to complete. The 195 // response will queue a message on this thread. 196 waitForRequestToComplete(); 197 // Make a request to the proxy for this request, jumping the queue. 198 // We use the queue so that the loader is present in 199 // useHttpAuthUsernamePassword(). 200 synchronized (mLoaderQueue) { 201 mLoaderQueue.addFirst(loader); 202 } 203 processNextLoader(); 204 // Wait for this request to complete. 205 waitForRequestToComplete(); 206 // Pop the loader from the queue. 207 synchronized (mLoaderQueue) { 208 assert(mLoaderQueue.peek() == loader); 209 mLoaderQueue.poll(); 210 } 211 // Call back. 212 loader.handleAuthResponse(mUsername, mPassword); 213 // The message queued by the response from the last asynchronous 214 // request, if present, will start the next request. 215 return; 216 } 217 218 boolean processNext = false; 219 220 synchronized (mLoaderQueue) { 221 mLoaderQueue.offer(loader); 222 processNext = 223 (mLoaderQueue.size() == 1); 224 } 225 226 if (processNext) { 227 processNextLoader(); 228 } 229 } 230 231 /** 232 * Wait for the request in flight, if any, to complete 233 */ 234 private void waitForRequestToComplete() { 235 synchronized (mRequestInFlightLock) { 236 while (mRequestInFlight) { 237 try { 238 mRequestInFlightLock.wait(); 239 } catch(InterruptedException e) { 240 Log.e(LOGTAG, "Interrupted while waiting for request to complete"); 241 } 242 } 243 } 244 } 245 246 /** 247 * Process the next loader in the queue (helper method) 248 */ 249 private void processNextLoader() { 250 LoadListener loader = null; 251 synchronized (mLoaderQueue) { 252 loader = mLoaderQueue.peek(); 253 } 254 if (loader != null) { 255 synchronized (mRequestInFlightLock) { 256 assert(mRequestInFlight == false); 257 mRequestInFlight = true; 258 } 259 260 CallbackProxy proxy = loader.getFrame().getCallbackProxy(); 261 262 String hostname = loader.proxyAuthenticate() ? 263 mNetwork.getProxyHostname() : loader.host(); 264 265 String realm = loader.realm(); 266 267 proxy.onReceivedHttpAuthRequest(this, hostname, realm); 268 } 269 } 270 271 /** 272 * Informs the WebView of a new set of credentials. 273 * @hide Pending API council review 274 */ 275 public static void onReceivedCredentials(LoadListener loader, 276 String host, String realm, String username, String password) { 277 CallbackProxy proxy = loader.getFrame().getCallbackProxy(); 278 proxy.onReceivedHttpAuthCredentials(host, realm, username, password); 279 } 280 } 281