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