Home | History | Annotate | Download | only in webkit
      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