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