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