Home | History | Annotate | Download | only in webkit
      1 /*
      2  * Copyright (C) 2007 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 junit.framework.Assert;
     20 
     21 import android.net.http.SslError;
     22 import android.os.Bundle;
     23 import android.os.Handler;
     24 import android.os.Message;
     25 import android.util.Log;
     26 
     27 import java.util.LinkedList;
     28 import java.util.ListIterator;
     29 
     30 /**
     31  * SslErrorHandler: class responsible for handling SSL errors. This class is
     32  * passed as a parameter to BrowserCallback.displaySslErrorDialog and is meant
     33  * to receive the user's response.
     34  */
     35 public class SslErrorHandler extends Handler {
     36     /* One problem here is that there may potentially be multiple SSL errors
     37      * coming from mutiple loaders. Therefore, we keep a queue of loaders
     38      * that have SSL-related problems and process errors one by one in the
     39      * order they were received.
     40      */
     41 
     42     private static final String LOGTAG = "network";
     43 
     44     /**
     45      * Queue of loaders that experience SSL-related problems.
     46      */
     47     private LinkedList<LoadListener> mLoaderQueue;
     48 
     49     /**
     50      * SSL error preference table.
     51      */
     52     private Bundle mSslPrefTable;
     53 
     54     // These are only used in the client facing SslErrorHandler.
     55     private final SslErrorHandler mOriginHandler;
     56     private final LoadListener mLoadListener;
     57 
     58     // Message id for handling the response
     59     private static final int HANDLE_RESPONSE = 100;
     60 
     61     @Override
     62     public void handleMessage(Message msg) {
     63         switch (msg.what) {
     64             case HANDLE_RESPONSE:
     65                 LoadListener loader = (LoadListener) msg.obj;
     66                 synchronized (SslErrorHandler.this) {
     67                     handleSslErrorResponse(loader, loader.sslError(),
     68                             msg.arg1 == 1);
     69                     mLoaderQueue.remove(loader);
     70                     fastProcessQueuedSslErrors();
     71                 }
     72                 break;
     73         }
     74     }
     75 
     76     /**
     77      * Creates a new error handler with an empty loader queue.
     78      */
     79     /* package */ SslErrorHandler() {
     80         mLoaderQueue = new LinkedList<LoadListener>();
     81         mSslPrefTable = new Bundle();
     82 
     83         // These are used by client facing SslErrorHandlers.
     84         mOriginHandler = null;
     85         mLoadListener = null;
     86     }
     87 
     88     /**
     89      * Create a new error handler that will be passed to the client.
     90      */
     91     private SslErrorHandler(SslErrorHandler origin, LoadListener listener) {
     92         mOriginHandler = origin;
     93         mLoadListener = listener;
     94     }
     95 
     96     /**
     97      * Saves this handler's state into a map.
     98      * @return True iff succeeds.
     99      */
    100     /* package */ synchronized boolean saveState(Bundle outState) {
    101         boolean success = (outState != null);
    102         if (success) {
    103             // TODO?
    104             outState.putBundle("ssl-error-handler", mSslPrefTable);
    105         }
    106 
    107         return success;
    108     }
    109 
    110     /**
    111      * Restores this handler's state from a map.
    112      * @return True iff succeeds.
    113      */
    114     /* package */ synchronized boolean restoreState(Bundle inState) {
    115         boolean success = (inState != null);
    116         if (success) {
    117             success = inState.containsKey("ssl-error-handler");
    118             if (success) {
    119                 mSslPrefTable = inState.getBundle("ssl-error-handler");
    120             }
    121         }
    122 
    123         return success;
    124     }
    125 
    126     /**
    127      * Clears SSL error preference table.
    128      */
    129     /* package */ synchronized void clear() {
    130         mSslPrefTable.clear();
    131     }
    132 
    133     /**
    134      * Handles SSL error(s) on the way up to the user.
    135      */
    136     /* package */ synchronized void handleSslErrorRequest(LoadListener loader) {
    137         if (DebugFlags.SSL_ERROR_HANDLER) {
    138             Log.v(LOGTAG, "SslErrorHandler.handleSslErrorRequest(): " +
    139                   "url=" + loader.url());
    140         }
    141 
    142         if (!loader.cancelled()) {
    143             mLoaderQueue.offer(loader);
    144             if (loader == mLoaderQueue.peek()) {
    145                 fastProcessQueuedSslErrors();
    146             }
    147         }
    148     }
    149 
    150     /**
    151      * Check the preference table for a ssl error that has already been shown
    152      * to the user.
    153      */
    154     /* package */ synchronized boolean checkSslPrefTable(LoadListener loader,
    155             SslError error) {
    156         final String host = loader.host();
    157         final int primary = error.getPrimaryError();
    158 
    159         if (DebugFlags.SSL_ERROR_HANDLER) {
    160             Assert.assertTrue(host != null && primary != 0);
    161         }
    162 
    163         if (mSslPrefTable.containsKey(host)) {
    164             if (primary <= mSslPrefTable.getInt(host)) {
    165                 handleSslErrorResponse(loader, error, true);
    166                 return true;
    167             }
    168         }
    169         return false;
    170     }
    171 
    172     /**
    173      * Processes queued SSL-error confirmation requests in
    174      * a tight loop while there is no need to ask the user.
    175      */
    176     /* package */void fastProcessQueuedSslErrors() {
    177         while (processNextLoader());
    178     }
    179 
    180     /**
    181      * Processes the next loader in the queue.
    182      * @return True iff should proceed to processing the
    183      * following loader in the queue
    184      */
    185     private synchronized boolean processNextLoader() {
    186         LoadListener loader = mLoaderQueue.peek();
    187         if (loader != null) {
    188             // if this loader has been cancelled
    189             if (loader.cancelled()) {
    190                 // go to the following loader in the queue. Make sure this
    191                 // loader has been removed from the queue.
    192                 mLoaderQueue.remove(loader);
    193                 return true;
    194             }
    195 
    196             SslError error = loader.sslError();
    197 
    198             if (DebugFlags.SSL_ERROR_HANDLER) {
    199                 Assert.assertNotNull(error);
    200             }
    201 
    202             // checkSslPrefTable will handle the ssl error response if the
    203             // answer is available. It does not remove the loader from the
    204             // queue.
    205             if (checkSslPrefTable(loader, error)) {
    206                 mLoaderQueue.remove(loader);
    207                 return true;
    208             }
    209 
    210             // if we do not have information on record, ask
    211             // the user (display a dialog)
    212             CallbackProxy proxy = loader.getFrame().getCallbackProxy();
    213             proxy.onReceivedSslError(new SslErrorHandler(this, loader), error);
    214         }
    215 
    216         // the queue must be empty, stop
    217         return false;
    218     }
    219 
    220     /**
    221      * Proceed with the SSL certificate.
    222      */
    223     public void proceed() {
    224         mOriginHandler.sendMessage(
    225                 mOriginHandler.obtainMessage(
    226                         HANDLE_RESPONSE, 1, 0, mLoadListener));
    227     }
    228 
    229     /**
    230      * Cancel this request and all pending requests for the WebView that had
    231      * the error.
    232      */
    233     public void cancel() {
    234         mOriginHandler.sendMessage(
    235                 mOriginHandler.obtainMessage(
    236                         HANDLE_RESPONSE, 0, 0, mLoadListener));
    237     }
    238 
    239     /**
    240      * Handles SSL error(s) on the way down from the user.
    241      */
    242     /* package */ synchronized void handleSslErrorResponse(LoadListener loader,
    243             SslError error, boolean proceed) {
    244         if (DebugFlags.SSL_ERROR_HANDLER) {
    245             Assert.assertNotNull(loader);
    246             Assert.assertNotNull(error);
    247         }
    248 
    249         if (DebugFlags.SSL_ERROR_HANDLER) {
    250             Log.v(LOGTAG, "SslErrorHandler.handleSslErrorResponse():"
    251                   + " proceed: " + proceed
    252                   + " url:" + loader.url());
    253         }
    254 
    255         if (!loader.cancelled()) {
    256             if (proceed) {
    257                 // update the user's SSL error preference table
    258                 int primary = error.getPrimaryError();
    259                 String host = loader.host();
    260 
    261                 if (DebugFlags.SSL_ERROR_HANDLER) {
    262                     Assert.assertTrue(host != null && primary != 0);
    263                 }
    264                 boolean hasKey = mSslPrefTable.containsKey(host);
    265                 if (!hasKey ||
    266                     primary > mSslPrefTable.getInt(host)) {
    267                     mSslPrefTable.putInt(host, primary);
    268                 }
    269             }
    270             loader.handleSslErrorResponse(proceed);
    271         }
    272     }
    273 }
    274