/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.webkit; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.util.Log; import java.util.ListIterator; import java.util.LinkedList; /** * HttpAuthHandler implementation is used only by the Android Java HTTP stack. * <p> * This class is not needed when we're using the Chromium HTTP stack. */ class HttpAuthHandlerImpl extends HttpAuthHandler { /* * It is important that the handler is in Network, because we want to share * it accross multiple loaders and windows (like our subwindow and the main * window). */ private static final String LOGTAG = "network"; /** * Network. */ private Network mNetwork; /** * Loader queue. */ private LinkedList<LoadListener> mLoaderQueue; // Message id for handling the user response private static final int AUTH_PROCEED = 100; private static final int AUTH_CANCEL = 200; // Use to synchronize when making synchronous calls to // onReceivedHttpAuthRequest(). We can't use a single Boolean object for // both the lock and the state, because Boolean is immutable. Object mRequestInFlightLock = new Object(); boolean mRequestInFlight; String mUsername; String mPassword; /** * Creates a new HTTP authentication handler with an empty * loader queue * * @param network The parent network object */ /* package */ HttpAuthHandlerImpl(Network network) { mNetwork = network; mLoaderQueue = new LinkedList<LoadListener>(); } @Override public void handleMessage(Message msg) { LoadListener loader = null; synchronized (mLoaderQueue) { loader = mLoaderQueue.poll(); } assert(loader.isSynchronous() == false); switch (msg.what) { case AUTH_PROCEED: String username = msg.getData().getString("username"); String password = msg.getData().getString("password"); loader.handleAuthResponse(username, password); break; case AUTH_CANCEL: loader.handleAuthResponse(null, null); break; } processNextLoader(); } /** * Helper method used to unblock handleAuthRequest(), which in the case of a * synchronous request will wait for proxy.onReceivedHttpAuthRequest() to * call back to either proceed() or cancel(). * * @param username The username to use for authentication * @param password The password to use for authentication * @return True if the request is synchronous and handleAuthRequest() has * been unblocked */ private boolean handleResponseForSynchronousRequest(String username, String password) { LoadListener loader = null; synchronized (mLoaderQueue) { loader = mLoaderQueue.peek(); } if (loader.isSynchronous()) { mUsername = username; mPassword = password; return true; } return false; } private void signalRequestComplete() { synchronized (mRequestInFlightLock) { assert(mRequestInFlight); mRequestInFlight = false; mRequestInFlightLock.notify(); } } /** * Proceed with the authorization with the given credentials * * May be called on the UI thread, rather than the WebCore thread. * * @param username The username to use for authentication * @param password The password to use for authentication */ public void proceed(String username, String password) { if (handleResponseForSynchronousRequest(username, password)) { signalRequestComplete(); return; } Message msg = obtainMessage(AUTH_PROCEED); msg.getData().putString("username", username); msg.getData().putString("password", password); sendMessage(msg); signalRequestComplete(); } /** * Cancel the authorization request * * May be called on the UI thread, rather than the WebCore thread. * */ public void cancel() { if (handleResponseForSynchronousRequest(null, null)) { signalRequestComplete(); return; } sendMessage(obtainMessage(AUTH_CANCEL)); signalRequestComplete(); } /** * @return True if we can use user credentials on record * (ie, if we did not fail trying to use them last time) */ public boolean useHttpAuthUsernamePassword() { LoadListener loader = null; synchronized (mLoaderQueue) { loader = mLoaderQueue.peek(); } if (loader != null) { return !loader.authCredentialsInvalid(); } return false; } /** * Enqueues the loader, if the loader is the only element * in the queue, starts processing the loader * * @param loader The loader that resulted in this http * authentication request */ /* package */ void handleAuthRequest(LoadListener loader) { // The call to proxy.onReceivedHttpAuthRequest() may be asynchronous. If // the request is synchronous, we must block here until we have a // response. if (loader.isSynchronous()) { // If there's a request in flight, wait for it to complete. The // response will queue a message on this thread. waitForRequestToComplete(); // Make a request to the proxy for this request, jumping the queue. // We use the queue so that the loader is present in // useHttpAuthUsernamePassword(). synchronized (mLoaderQueue) { mLoaderQueue.addFirst(loader); } processNextLoader(); // Wait for this request to complete. waitForRequestToComplete(); // Pop the loader from the queue. synchronized (mLoaderQueue) { assert(mLoaderQueue.peek() == loader); mLoaderQueue.poll(); } // Call back. loader.handleAuthResponse(mUsername, mPassword); // The message queued by the response from the last asynchronous // request, if present, will start the next request. return; } boolean processNext = false; synchronized (mLoaderQueue) { mLoaderQueue.offer(loader); processNext = (mLoaderQueue.size() == 1); } if (processNext) { processNextLoader(); } } /** * Wait for the request in flight, if any, to complete */ private void waitForRequestToComplete() { synchronized (mRequestInFlightLock) { while (mRequestInFlight) { try { mRequestInFlightLock.wait(); } catch(InterruptedException e) { Log.e(LOGTAG, "Interrupted while waiting for request to complete"); } } } } /** * Process the next loader in the queue (helper method) */ private void processNextLoader() { LoadListener loader = null; synchronized (mLoaderQueue) { loader = mLoaderQueue.peek(); } if (loader != null) { synchronized (mRequestInFlightLock) { assert(mRequestInFlight == false); mRequestInFlight = true; } CallbackProxy proxy = loader.getFrame().getCallbackProxy(); String hostname = loader.proxyAuthenticate() ? mNetwork.getProxyHostname() : loader.host(); String realm = loader.realm(); proxy.onReceivedHttpAuthRequest(this, hostname, realm); } } /** * Informs the WebView of a new set of credentials. * @hide Pending API council review */ public static void onReceivedCredentials(LoadListener loader, String host, String realm, String username, String password) { CallbackProxy proxy = loader.getFrame().getCallbackProxy(); proxy.onReceivedHttpAuthCredentials(host, realm, username, password); } }