Fix HttpAuthHandler for synchronous requests
When HttpAuthHandler queries the WebViewClient to obtain a username and
password, we need to make sure that this is done synchronously when the request
is synchronous.
Bug: 2511043
Change-Id: I9ff2156cfb3c81edaf4a50ec7094a00a8f8ff91f
diff --git a/core/java/android/webkit/HttpAuthHandler.java b/core/java/android/webkit/HttpAuthHandler.java
index 1c17575..6216603 100644
--- a/core/java/android/webkit/HttpAuthHandler.java
+++ b/core/java/android/webkit/HttpAuthHandler.java
@@ -19,6 +19,7 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
+import android.util.Log;
import java.util.ListIterator;
import java.util.LinkedList;
@@ -52,6 +53,14 @@
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
@@ -70,6 +79,7 @@
synchronized (mLoaderQueue) {
loader = mLoaderQueue.poll();
}
+ assert(loader.isSynchronous() == false);
switch (msg.what) {
case AUTH_PROCEED:
@@ -87,25 +97,70 @@
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();
}
/**
@@ -132,6 +187,34 @@
* 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) {
@@ -146,6 +229,21 @@
}
/**
+ * 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() {
@@ -154,6 +252,11 @@
loader = mLoaderQueue.peek();
}
if (loader != null) {
+ synchronized (mRequestInFlightLock) {
+ assert(mRequestInFlight == false);
+ mRequestInFlight = true;
+ }
+
CallbackProxy proxy = loader.getFrame().getCallbackProxy();
String hostname = loader.proxyAuthenticate() ?