Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c877c6b8 authored by Steve Block's avatar Steve Block
Browse files

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
parent 9b63c8ee
Loading
Loading
Loading
Loading
+103 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ 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;
@@ -52,6 +53,14 @@ public class HttpAuthHandler extends Handler {
    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 @@ public class HttpAuthHandler extends Handler {
        synchronized (mLoaderQueue) {
            loader = mLoaderQueue.poll();
        }
        assert(loader.isSynchronous() == false);

        switch (msg.what) {
            case AUTH_PROCEED:
@@ -87,25 +97,70 @@ public class HttpAuthHandler extends Handler {
        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 @@ public class HttpAuthHandler extends Handler {
     * 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) {
@@ -145,6 +228,21 @@ public class HttpAuthHandler extends Handler {
        }
    }

    /**
     * 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)
     */
@@ -154,6 +252,11 @@ public class HttpAuthHandler extends Handler {
            loader = mLoaderQueue.peek();
        }
        if (loader != null) {
            synchronized (mRequestInFlightLock) {
                assert(mRequestInFlight == false);
                mRequestInFlight = true;
            }

            CallbackProxy proxy = loader.getFrame().getCallbackProxy();

            String hostname = loader.proxyAuthenticate() ?