HttpAuthHandler.java revision c6a90a59b342114d760d899e09d8def672b49ca8
1/*
2 * Copyright (C) 2006 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
17package android.webkit;
18
19import android.os.Bundle;
20import android.os.Handler;
21import android.os.Message;
22import android.util.Log;
23
24import java.util.ListIterator;
25import java.util.LinkedList;
26
27/**
28 * HTTP authentication handler: local handler that takes care
29 * of HTTP authentication requests. This class is passed as a
30 * parameter to BrowserCallback.displayHttpAuthDialog and is
31 * meant to receive the user's response.
32 */
33public class HttpAuthHandler extends Handler {
34    /* It is important that the handler is in Network, because
35     * we want to share it accross multiple loaders and windows
36     * (like our subwindow and the main 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 */ HttpAuthHandler(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 proxy 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