blob: 01e8eb8a644f8384e9ec4543f8b1b9ad53b753c3 [file] [log] [blame]
Iain Merrick10229b22010-08-31 11:57:15 +01001/*
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
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 * 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 */
32class 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.
Iain Merrick10229b22010-08-31 11:57:15 +0100273 */
274 public static void onReceivedCredentials(LoadListener loader,
275 String host, String realm, String username, String password) {
276 CallbackProxy proxy = loader.getFrame().getCallbackProxy();
277 proxy.onReceivedHttpAuthCredentials(host, realm, username, password);
278 }
279}