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