blob: 07e03ff8dcfae2c6e38a43a7e48ede9abbb67e5a [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2006 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 android.content.Context;
20import android.net.WebAddress;
21import android.net.ParseException;
22import android.net.http.EventHandler;
23import android.net.http.Headers;
24import android.net.http.HttpAuthHeader;
25import android.net.http.RequestHandle;
26import android.net.http.SslCertificate;
27import android.net.http.SslError;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028
29import android.os.Handler;
30import android.os.Message;
Grace Kloba3af8e932009-06-19 15:03:46 -070031import android.security.Keystore;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080032import android.util.Log;
33import android.webkit.CacheManager.CacheResult;
Grace Kloba3af8e932009-06-19 15:03:46 -070034import android.widget.Toast;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080035
36import com.android.internal.R;
37
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080038import java.io.IOException;
39import java.util.ArrayList;
40import java.util.HashMap;
41import java.util.Map;
42import java.util.Vector;
43import java.util.regex.Pattern;
44import java.util.regex.Matcher;
45
46import org.apache.commons.codec.binary.Base64;
47
48class LoadListener extends Handler implements EventHandler {
49
50 private static final String LOGTAG = "webkit";
51
52 // Messages used internally to communicate state between the
53 // Network thread and the WebCore thread.
54 private static final int MSG_CONTENT_HEADERS = 100;
55 private static final int MSG_CONTENT_DATA = 110;
56 private static final int MSG_CONTENT_FINISHED = 120;
57 private static final int MSG_CONTENT_ERROR = 130;
58 private static final int MSG_LOCATION_CHANGED = 140;
59 private static final int MSG_LOCATION_CHANGED_REQUEST = 150;
60 private static final int MSG_STATUS = 160;
61 private static final int MSG_SSL_CERTIFICATE = 170;
62 private static final int MSG_SSL_ERROR = 180;
63
64 // Standard HTTP status codes in a more representative format
65 private static final int HTTP_OK = 200;
66 private static final int HTTP_MOVED_PERMANENTLY = 301;
67 private static final int HTTP_FOUND = 302;
68 private static final int HTTP_SEE_OTHER = 303;
69 private static final int HTTP_NOT_MODIFIED = 304;
70 private static final int HTTP_TEMPORARY_REDIRECT = 307;
71 private static final int HTTP_AUTH = 401;
72 private static final int HTTP_NOT_FOUND = 404;
73 private static final int HTTP_PROXY_AUTH = 407;
74
Grace Kloba3af8e932009-06-19 15:03:46 -070075 private static final String CERT_MIMETYPE = "application/x-x509-ca-cert";
76
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080077 private static int sNativeLoaderCount;
78
79 private final ByteArrayBuilder mDataBuilder = new ByteArrayBuilder(8192);
80
81 private String mUrl;
82 private WebAddress mUri;
83 private boolean mPermanent;
84 private String mOriginalUrl;
85 private Context mContext;
86 private BrowserFrame mBrowserFrame;
87 private int mNativeLoader;
88 private String mMimeType;
89 private String mEncoding;
90 private String mTransferEncoding;
91 private int mStatusCode;
92 private String mStatusText;
93 public long mContentLength; // Content length of the incoming data
94 private boolean mCancelled; // The request has been cancelled.
95 private boolean mAuthFailed; // indicates that the prev. auth failed
96 private CacheLoader mCacheLoader;
97 private CacheManager.CacheResult mCacheResult;
98 private HttpAuthHeader mAuthHeader;
99 private int mErrorID = OK;
100 private String mErrorDescription;
101 private SslError mSslError;
102 private RequestHandle mRequestHandle;
103
104 // Request data. It is only valid when we are doing a load from the
105 // cache. It is needed if the cache returns a redirect
106 private String mMethod;
107 private Map<String, String> mRequestHeaders;
108 private byte[] mPostData;
109 private boolean mIsHighPriority;
110 // Flag to indicate that this load is synchronous.
111 private boolean mSynchronous;
112 private Vector<Message> mMessageQueue;
113
114 // Does this loader correspond to the main-frame top-level page?
115 private boolean mIsMainPageLoader;
116
117 private Headers mHeaders;
118
119 // =========================================================================
120 // Public functions
121 // =========================================================================
122
123 public static LoadListener getLoadListener(
124 Context context, BrowserFrame frame, String url,
125 int nativeLoader, boolean synchronous, boolean isMainPageLoader) {
126
127 sNativeLoaderCount += 1;
128 return new LoadListener(
129 context, frame, url, nativeLoader, synchronous, isMainPageLoader);
130 }
131
132 public static int getNativeLoaderCount() {
133 return sNativeLoaderCount;
134 }
135
136 LoadListener(Context context, BrowserFrame frame, String url,
137 int nativeLoader, boolean synchronous, boolean isMainPageLoader) {
Dave Bort42bc2ff2009-04-13 15:07:51 -0700138 if (WebView.LOGV_ENABLED) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800139 Log.v(LOGTAG, "LoadListener constructor url=" + url);
140 }
141 mContext = context;
142 mBrowserFrame = frame;
143 setUrl(url);
144 mNativeLoader = nativeLoader;
145 mMimeType = "";
146 mEncoding = "";
147 mSynchronous = synchronous;
148 if (synchronous) {
149 mMessageQueue = new Vector<Message>();
150 }
151 mIsMainPageLoader = isMainPageLoader;
152 }
153
154 /**
155 * We keep a count of refs to the nativeLoader so we do not create
156 * so many LoadListeners that the GREFs blow up
157 */
158 private void clearNativeLoader() {
159 sNativeLoaderCount -= 1;
160 mNativeLoader = 0;
161 }
162
163 /*
164 * This message handler is to facilitate communication between the network
165 * thread and the browser thread.
166 */
167 public void handleMessage(Message msg) {
168 switch (msg.what) {
169 case MSG_CONTENT_HEADERS:
170 /*
171 * This message is sent when the LoadListener has headers
172 * available. The headers are sent onto WebCore to see what we
173 * should do with them.
174 */
175 handleHeaders((Headers) msg.obj);
176 break;
177
178 case MSG_CONTENT_DATA:
179 /*
180 * This message is sent when the LoadListener has data available
181 * in it's data buffer. This data buffer could be filled from a
182 * file (this thread) or from http (Network thread).
183 */
184 if (mNativeLoader != 0 && !ignoreCallbacks()) {
185 commitLoad();
186 }
187 break;
188
189 case MSG_CONTENT_FINISHED:
190 /*
191 * This message is sent when the LoadListener knows that the
192 * load is finished. This message is not sent in the case of an
193 * error.
194 *
195 */
196 handleEndData();
197 break;
198
199 case MSG_CONTENT_ERROR:
200 /*
201 * This message is sent when a load error has occured. The
202 * LoadListener will clean itself up.
203 */
204 handleError(msg.arg1, (String) msg.obj);
205 break;
206
207 case MSG_LOCATION_CHANGED:
208 /*
209 * This message is sent from LoadListener.endData to inform the
210 * browser activity that the location of the top level page
211 * changed.
212 */
213 doRedirect();
214 break;
215
216 case MSG_LOCATION_CHANGED_REQUEST:
217 /*
218 * This message is sent from endData on receipt of a 307
219 * Temporary Redirect in response to a POST -- the user must
220 * confirm whether to continue loading. If the user says Yes,
221 * we simply call MSG_LOCATION_CHANGED. If the user says No,
222 * we call MSG_CONTENT_FINISHED.
223 */
224 Message contMsg = obtainMessage(MSG_LOCATION_CHANGED);
225 Message stopMsg = obtainMessage(MSG_CONTENT_FINISHED);
226 mBrowserFrame.getCallbackProxy().onFormResubmission(
227 stopMsg, contMsg);
228 break;
229
230 case MSG_STATUS:
231 /*
232 * This message is sent from the network thread when the http
233 * stack has received the status response from the server.
234 */
235 HashMap status = (HashMap) msg.obj;
236 handleStatus(((Integer) status.get("major")).intValue(),
237 ((Integer) status.get("minor")).intValue(),
238 ((Integer) status.get("code")).intValue(),
239 (String) status.get("reason"));
240 break;
241
242 case MSG_SSL_CERTIFICATE:
243 /*
244 * This message is sent when the network thread receives a ssl
245 * certificate.
246 */
247 handleCertificate((SslCertificate) msg.obj);
248 break;
249
250 case MSG_SSL_ERROR:
251 /*
252 * This message is sent when the network thread encounters a
253 * ssl error.
254 */
255 handleSslError((SslError) msg.obj);
256 break;
257 }
258 }
259
260 /**
261 * @return The loader's BrowserFrame.
262 */
263 BrowserFrame getFrame() {
264 return mBrowserFrame;
265 }
266
267 Context getContext() {
268 return mContext;
269 }
270
271 /* package */ boolean isSynchronous() {
272 return mSynchronous;
273 }
274
275 /**
276 * @return True iff the load has been cancelled
277 */
278 public boolean cancelled() {
279 return mCancelled;
280 }
281
282 /**
283 * Parse the headers sent from the server.
284 * @param headers gives up the HeaderGroup
285 * IMPORTANT: as this is called from network thread, can't call native
286 * directly
287 */
288 public void headers(Headers headers) {
Dave Bort42bc2ff2009-04-13 15:07:51 -0700289 if (WebView.LOGV_ENABLED) Log.v(LOGTAG, "LoadListener.headers");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800290 sendMessageInternal(obtainMessage(MSG_CONTENT_HEADERS, headers));
291 }
292
293 // Does the header parsing work on the WebCore thread.
294 private void handleHeaders(Headers headers) {
295 if (mCancelled) return;
296 mHeaders = headers;
297 mMimeType = "";
298 mEncoding = "";
299
300 ArrayList<String> cookies = headers.getSetCookie();
301 for (int i = 0; i < cookies.size(); ++i) {
302 CookieManager.getInstance().setCookie(mUri, cookies.get(i));
303 }
304
305 long contentLength = headers.getContentLength();
306 if (contentLength != Headers.NO_CONTENT_LENGTH) {
307 mContentLength = contentLength;
308 } else {
309 mContentLength = 0;
310 }
311
312 String contentType = headers.getContentType();
313 if (contentType != null) {
314 parseContentTypeHeader(contentType);
315
316 // If we have one of "generic" MIME types, try to deduce
317 // the right MIME type from the file extension (if any):
318 if (mMimeType.equalsIgnoreCase("text/plain") ||
319 mMimeType.equalsIgnoreCase("application/octet-stream")) {
320
321 String newMimeType = guessMimeTypeFromExtension();
322 if (newMimeType != null) {
323 mMimeType = newMimeType;
324 }
325 } else if (mMimeType.equalsIgnoreCase("text/vnd.wap.wml")) {
326 // As we don't support wml, render it as plain text
327 mMimeType = "text/plain";
328 } else {
329 // XXX: Until the servers send us either correct xhtml or
330 // text/html, treat application/xhtml+xml as text/html.
331 // It seems that xhtml+xml and vnd.wap.xhtml+xml mime
332 // subtypes are used interchangeably. So treat them the same.
333 if (mMimeType.equalsIgnoreCase("application/xhtml+xml") ||
334 mMimeType.equals("application/vnd.wap.xhtml+xml")) {
335 mMimeType = "text/html";
336 }
337 }
338 } else {
339 /* Often when servers respond with 304 Not Modified or a
340 Redirect, then they don't specify a MIMEType. When this
341 occurs, the function below is called. In the case of
342 304 Not Modified, the cached headers are used rather
343 than the headers that are returned from the server. */
344 guessMimeType();
345 }
346
347 // is it an authentication request?
348 boolean mustAuthenticate = (mStatusCode == HTTP_AUTH ||
349 mStatusCode == HTTP_PROXY_AUTH);
350 // is it a proxy authentication request?
351 boolean isProxyAuthRequest = (mStatusCode == HTTP_PROXY_AUTH);
352 // is this authentication request due to a failed attempt to
353 // authenticate ealier?
354 mAuthFailed = false;
355
356 // if we tried to authenticate ourselves last time
357 if (mAuthHeader != null) {
358 // we failed, if we must to authenticate again now and
359 // we have a proxy-ness match
360 mAuthFailed = (mustAuthenticate &&
361 isProxyAuthRequest == mAuthHeader.isProxy());
362
363 // if we did NOT fail and last authentication request was a
364 // proxy-authentication request
365 if (!mAuthFailed && mAuthHeader.isProxy()) {
366 Network network = Network.getInstance(mContext);
367 // if we have a valid proxy set
368 if (network.isValidProxySet()) {
369 /* The proxy credentials can be read in the WebCore thread
370 */
371 synchronized (network) {
372 // save authentication credentials for pre-emptive proxy
373 // authentication
374 network.setProxyUsername(mAuthHeader.getUsername());
375 network.setProxyPassword(mAuthHeader.getPassword());
376 }
377 }
378 }
379 }
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700380
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800381 // it is only here that we can reset the last mAuthHeader object
382 // (if existed) and start a new one!!!
383 mAuthHeader = null;
384 if (mustAuthenticate) {
385 if (mStatusCode == HTTP_AUTH) {
386 mAuthHeader = parseAuthHeader(
387 headers.getWwwAuthenticate());
388 } else {
389 mAuthHeader = parseAuthHeader(
390 headers.getProxyAuthenticate());
391 // if successfully parsed the header
392 if (mAuthHeader != null) {
393 // mark the auth-header object as a proxy
394 mAuthHeader.setProxy();
395 }
396 }
397 }
398
399 // Only create a cache file if the server has responded positively.
400 if ((mStatusCode == HTTP_OK ||
401 mStatusCode == HTTP_FOUND ||
402 mStatusCode == HTTP_MOVED_PERMANENTLY ||
403 mStatusCode == HTTP_TEMPORARY_REDIRECT) &&
404 mNativeLoader != 0) {
405 // Content arriving from a StreamLoader (eg File, Cache or Data)
406 // will not be cached as they have the header:
407 // cache-control: no-store
408 mCacheResult = CacheManager.createCacheFile(mUrl, mStatusCode,
409 headers, mMimeType, false);
410 if (mCacheResult != null) {
411 mCacheResult.encoding = mEncoding;
412 }
413 }
414 commitHeadersCheckRedirect();
415 }
416
417 /**
418 * @return True iff this loader is in the proxy-authenticate state.
419 */
420 boolean proxyAuthenticate() {
421 if (mAuthHeader != null) {
422 return mAuthHeader.isProxy();
423 }
424
425 return false;
426 }
427
428 /**
429 * Report the status of the response.
430 * TODO: Comments about each parameter.
431 * IMPORTANT: as this is called from network thread, can't call native
432 * directly
433 */
434 public void status(int majorVersion, int minorVersion,
435 int code, /* Status-Code value */ String reasonPhrase) {
Dave Bort42bc2ff2009-04-13 15:07:51 -0700436 if (WebView.LOGV_ENABLED) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800437 Log.v(LOGTAG, "LoadListener: from: " + mUrl
438 + " major: " + majorVersion
439 + " minor: " + minorVersion
440 + " code: " + code
441 + " reason: " + reasonPhrase);
442 }
443 HashMap status = new HashMap();
444 status.put("major", majorVersion);
445 status.put("minor", minorVersion);
446 status.put("code", code);
447 status.put("reason", reasonPhrase);
The Android Open Source Project10592532009-03-18 17:39:46 -0700448 // New status means new data. Clear the old.
449 mDataBuilder.clear();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800450 sendMessageInternal(obtainMessage(MSG_STATUS, status));
451 }
452
453 // Handle the status callback on the WebCore thread.
454 private void handleStatus(int major, int minor, int code, String reason) {
455 if (mCancelled) return;
456
457 mStatusCode = code;
458 mStatusText = reason;
459 mPermanent = false;
460 }
461
462 /**
463 * Implementation of certificate handler for EventHandler.
464 * Called every time a resource is loaded via a secure
465 * connection. In this context, can be called multiple
466 * times if we have redirects
467 * @param certificate The SSL certifcate
468 * IMPORTANT: as this is called from network thread, can't call native
469 * directly
470 */
471 public void certificate(SslCertificate certificate) {
472 sendMessageInternal(obtainMessage(MSG_SSL_CERTIFICATE, certificate));
473 }
474
475 // Handle the certificate on the WebCore thread.
476 private void handleCertificate(SslCertificate certificate) {
477 // if this is the top-most main-frame page loader
478 if (mIsMainPageLoader) {
479 // update the browser frame (ie, the main frame)
480 mBrowserFrame.certificate(certificate);
481 }
482 }
483
484 /**
485 * Implementation of error handler for EventHandler.
486 * Subclasses should call this method to have error fields set.
487 * @param id The error id described by EventHandler.
488 * @param description A string description of the error.
489 * IMPORTANT: as this is called from network thread, can't call native
490 * directly
491 */
492 public void error(int id, String description) {
Dave Bort42bc2ff2009-04-13 15:07:51 -0700493 if (WebView.LOGV_ENABLED) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800494 Log.v(LOGTAG, "LoadListener.error url:" +
495 url() + " id:" + id + " description:" + description);
496 }
497 sendMessageInternal(obtainMessage(MSG_CONTENT_ERROR, id, 0, description));
498 }
499
500 // Handle the error on the WebCore thread.
501 private void handleError(int id, String description) {
502 mErrorID = id;
503 mErrorDescription = description;
504 detachRequestHandle();
505 notifyError();
506 tearDown();
507 }
508
509 /**
510 * Add data to the internal collection of data. This function is used by
511 * the data: scheme, about: scheme and http/https schemes.
512 * @param data A byte array containing the content.
513 * @param length The length of data.
514 * IMPORTANT: as this is called from network thread, can't call native
515 * directly
516 * XXX: Unlike the other network thread methods, this method can do the
517 * work of decoding the data and appending it to the data builder because
518 * mDataBuilder is a thread-safe structure.
519 */
520 public void data(byte[] data, int length) {
Dave Bort42bc2ff2009-04-13 15:07:51 -0700521 if (WebView.LOGV_ENABLED) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800522 Log.v(LOGTAG, "LoadListener.data(): url: " + url());
523 }
524
525 // Decode base64 data
526 // Note: It's fine that we only decode base64 here and not in the other
527 // data call because the only caller of the stream version is not
528 // base64 encoded.
529 if ("base64".equalsIgnoreCase(mTransferEncoding)) {
530 if (length < data.length) {
531 byte[] trimmedData = new byte[length];
532 System.arraycopy(data, 0, trimmedData, 0, length);
533 data = trimmedData;
534 }
535 data = Base64.decodeBase64(data);
536 length = data.length;
537 }
538 // Synchronize on mData because commitLoad may write mData to WebCore
539 // and we don't want to replace mData or mDataLength at the same time
540 // as a write.
541 boolean sendMessage = false;
542 synchronized (mDataBuilder) {
543 sendMessage = mDataBuilder.isEmpty();
544 mDataBuilder.append(data, 0, length);
545 }
546 if (sendMessage) {
547 // Send a message whenever data comes in after a write to WebCore
548 sendMessageInternal(obtainMessage(MSG_CONTENT_DATA));
549 }
550 }
551
552 /**
553 * Event handler's endData call. Send a message to the handler notifying
554 * them that the data has finished.
555 * IMPORTANT: as this is called from network thread, can't call native
556 * directly
557 */
558 public void endData() {
Dave Bort42bc2ff2009-04-13 15:07:51 -0700559 if (WebView.LOGV_ENABLED) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800560 Log.v(LOGTAG, "LoadListener.endData(): url: " + url());
561 }
562 sendMessageInternal(obtainMessage(MSG_CONTENT_FINISHED));
563 }
564
565 // Handle the end of data.
566 private void handleEndData() {
567 if (mCancelled) return;
568
569 switch (mStatusCode) {
570 case HTTP_MOVED_PERMANENTLY:
571 // 301 - permanent redirect
572 mPermanent = true;
573 case HTTP_FOUND:
574 case HTTP_SEE_OTHER:
575 case HTTP_TEMPORARY_REDIRECT:
576 // 301, 302, 303, and 307 - redirect
577 if (mStatusCode == HTTP_TEMPORARY_REDIRECT) {
578 if (mRequestHandle != null &&
579 mRequestHandle.getMethod().equals("POST")) {
580 sendMessageInternal(obtainMessage(
581 MSG_LOCATION_CHANGED_REQUEST));
582 } else if (mMethod != null && mMethod.equals("POST")) {
583 sendMessageInternal(obtainMessage(
584 MSG_LOCATION_CHANGED_REQUEST));
585 } else {
586 sendMessageInternal(obtainMessage(MSG_LOCATION_CHANGED));
587 }
588 } else {
589 sendMessageInternal(obtainMessage(MSG_LOCATION_CHANGED));
590 }
591 return;
592
593 case HTTP_AUTH:
594 case HTTP_PROXY_AUTH:
595 // According to rfc2616, the response for HTTP_AUTH must include
596 // WWW-Authenticate header field and the response for
597 // HTTP_PROXY_AUTH must include Proxy-Authenticate header field.
598 if (mAuthHeader != null &&
599 (Network.getInstance(mContext).isValidProxySet() ||
600 !mAuthHeader.isProxy())) {
601 Network.getInstance(mContext).handleAuthRequest(this);
602 return;
603 }
604 break; // use default
605
606 case HTTP_NOT_MODIFIED:
607 // Server could send back NOT_MODIFIED even if we didn't
608 // ask for it, so make sure we have a valid CacheLoader
609 // before calling it.
610 if (mCacheLoader != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800611 mCacheLoader.load();
Dave Bort42bc2ff2009-04-13 15:07:51 -0700612 if (WebView.LOGV_ENABLED) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800613 Log.v(LOGTAG, "LoadListener cache load url=" + url());
614 }
615 return;
616 }
617 break; // use default
618
619 case HTTP_NOT_FOUND:
620 // Not an error, the server can send back content.
621 default:
622 break;
623 }
624 detachRequestHandle();
625 tearDown();
626 }
627
628 /* This method is called from CacheLoader when the initial request is
629 * serviced by the Cache. */
630 /* package */ void setCacheLoader(CacheLoader c) {
631 mCacheLoader = c;
632 }
633
634 /**
635 * Check the cache for the current URL, and load it if it is valid.
636 *
637 * @param headers for the request
638 * @return true if cached response is used.
639 */
640 boolean checkCache(Map<String, String> headers) {
641 // Get the cache file name for the current URL
642 CacheResult result = CacheManager.getCacheFile(url(),
643 headers);
644
645 // Go ahead and set the cache loader to null in case the result is
646 // null.
647 mCacheLoader = null;
648
649 if (result != null) {
650 // The contents of the cache may need to be revalidated so just
651 // remember the cache loader in the case that the server responds
652 // positively to the cached content. This is also used to detect if
653 // a redirect came from the cache.
654 mCacheLoader = new CacheLoader(this, result);
655
656 // If I got a cachedUrl and the revalidation header was not
657 // added, then the cached content valid, we should use it.
658 if (!headers.containsKey(
659 CacheManager.HEADER_KEY_IFNONEMATCH) &&
660 !headers.containsKey(
661 CacheManager.HEADER_KEY_IFMODIFIEDSINCE)) {
Dave Bort42bc2ff2009-04-13 15:07:51 -0700662 if (WebView.LOGV_ENABLED) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800663 Log.v(LOGTAG, "FrameLoader: HTTP URL in cache " +
664 "and usable: " + url());
665 }
666 // Load the cached file
667 mCacheLoader.load();
668 return true;
669 }
670 }
671 return false;
672 }
673
674 /**
675 * SSL certificate error callback. Handles SSL error(s) on the way up
676 * to the user.
677 * IMPORTANT: as this is called from network thread, can't call native
678 * directly
679 */
680 public void handleSslErrorRequest(SslError error) {
Dave Bort42bc2ff2009-04-13 15:07:51 -0700681 if (WebView.LOGV_ENABLED) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800682 Log.v(LOGTAG,
683 "LoadListener.handleSslErrorRequest(): url:" + url() +
684 " primary error: " + error.getPrimaryError() +
685 " certificate: " + error.getCertificate());
686 }
687 sendMessageInternal(obtainMessage(MSG_SSL_ERROR, error));
688 }
689
690 // Handle the ssl error on the WebCore thread.
691 private void handleSslError(SslError error) {
692 if (!mCancelled) {
693 mSslError = error;
694 Network.getInstance(mContext).handleSslErrorRequest(this);
695 }
696 }
697
698 /**
699 * @return HTTP authentication realm or null if none.
700 */
701 String realm() {
702 if (mAuthHeader == null) {
703 return null;
704 } else {
705 return mAuthHeader.getRealm();
706 }
707 }
708
709 /**
710 * Returns true iff an HTTP authentication problem has
711 * occured (credentials invalid).
712 */
713 boolean authCredentialsInvalid() {
714 // if it is digest and the nonce is stale, we just
715 // resubmit with a new nonce
716 return (mAuthFailed &&
717 !(mAuthHeader.isDigest() && mAuthHeader.getStale()));
718 }
719
720 /**
721 * @return The last SSL error or null if there is none
722 */
723 SslError sslError() {
724 return mSslError;
725 }
726
727 /**
728 * Handles SSL error(s) on the way down from the user
729 * (the user has already provided their feedback).
730 */
731 void handleSslErrorResponse(boolean proceed) {
732 if (mRequestHandle != null) {
733 mRequestHandle.handleSslErrorResponse(proceed);
734 }
The Android Open Source Project10592532009-03-18 17:39:46 -0700735 if (!proceed) {
736 // Commit whatever data we have and tear down the loader.
737 commitLoad();
738 tearDown();
739 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800740 }
741
742 /**
The Android Open Source Project10592532009-03-18 17:39:46 -0700743 * Uses user-supplied credentials to restart a request. If the credentials
744 * are null, cancel the request.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800745 */
746 void handleAuthResponse(String username, String password) {
Dave Bort42bc2ff2009-04-13 15:07:51 -0700747 if (WebView.LOGV_ENABLED) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800748 Log.v(LOGTAG, "LoadListener.handleAuthResponse: url: " + mUrl
749 + " username: " + username
750 + " password: " + password);
751 }
752
753 // create and queue an authentication-response
754 if (username != null && password != null) {
755 if (mAuthHeader != null && mRequestHandle != null) {
756 mAuthHeader.setUsername(username);
757 mAuthHeader.setPassword(password);
758
759 int scheme = mAuthHeader.getScheme();
760 if (scheme == HttpAuthHeader.BASIC) {
761 // create a basic response
762 boolean isProxy = mAuthHeader.isProxy();
763
764 mRequestHandle.setupBasicAuthResponse(isProxy,
765 username, password);
766 } else {
767 if (scheme == HttpAuthHeader.DIGEST) {
768 // create a digest response
769 boolean isProxy = mAuthHeader.isProxy();
770
771 String realm = mAuthHeader.getRealm();
772 String nonce = mAuthHeader.getNonce();
773 String qop = mAuthHeader.getQop();
774 String algorithm = mAuthHeader.getAlgorithm();
775 String opaque = mAuthHeader.getOpaque();
776
777 mRequestHandle.setupDigestAuthResponse
778 (isProxy, username, password, realm,
779 nonce, qop, algorithm, opaque);
780 }
781 }
782 }
The Android Open Source Project10592532009-03-18 17:39:46 -0700783 } else {
784 // Commit whatever data we have and tear down the loader.
785 commitLoad();
786 tearDown();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800787 }
788 }
789
790 /**
791 * This is called when a request can be satisfied by the cache, however,
792 * the cache result could be a redirect. In this case we need to issue
793 * the network request.
794 * @param method
795 * @param headers
796 * @param postData
797 * @param isHighPriority
798 */
799 void setRequestData(String method, Map<String, String> headers,
800 byte[] postData, boolean isHighPriority) {
801 mMethod = method;
802 mRequestHeaders = headers;
803 mPostData = postData;
804 mIsHighPriority = isHighPriority;
805 }
806
807 /**
808 * @return The current URL associated with this load.
809 */
810 String url() {
811 return mUrl;
812 }
813
814 /**
815 * @return The current WebAddress associated with this load.
816 */
817 WebAddress getWebAddress() {
818 return mUri;
819 }
820
821 /**
822 * @return URL hostname (current URL).
823 */
824 String host() {
825 if (mUri != null) {
826 return mUri.mHost;
827 }
828
829 return null;
830 }
831
832 /**
833 * @return The original URL associated with this load.
834 */
835 String originalUrl() {
836 if (mOriginalUrl != null) {
837 return mOriginalUrl;
838 } else {
839 return mUrl;
840 }
841 }
842
843 void attachRequestHandle(RequestHandle requestHandle) {
Dave Bort42bc2ff2009-04-13 15:07:51 -0700844 if (WebView.LOGV_ENABLED) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800845 Log.v(LOGTAG, "LoadListener.attachRequestHandle(): " +
846 "requestHandle: " + requestHandle);
847 }
848 mRequestHandle = requestHandle;
849 }
850
851 void detachRequestHandle() {
Dave Bort42bc2ff2009-04-13 15:07:51 -0700852 if (WebView.LOGV_ENABLED) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800853 Log.v(LOGTAG, "LoadListener.detachRequestHandle(): " +
854 "requestHandle: " + mRequestHandle);
855 }
856 mRequestHandle = null;
857 }
858
859 /*
860 * This function is called from native WebCore code to
861 * notify this LoadListener that the content it is currently
862 * downloading should be saved to a file and not sent to
863 * WebCore.
864 */
865 void downloadFile() {
866 // Setting the Cache Result to null ensures that this
867 // content is not added to the cache
868 mCacheResult = null;
869
870 // Inform the client that they should download a file
871 mBrowserFrame.getCallbackProxy().onDownloadStart(url(),
872 mBrowserFrame.getUserAgentString(),
873 mHeaders.getContentDisposition(),
874 mMimeType, mContentLength);
875
876 // Cancel the download. We need to stop the http load.
877 // The native loader object will get cleared by the call to
878 // cancel() but will also be cleared on the WebCore side
879 // when this function returns.
880 cancel();
881 }
882
883 /*
884 * This function is called from native WebCore code to
885 * find out if the given URL is in the cache, and if it can
886 * be used. This is just for forward/back navigation to a POST
887 * URL.
888 */
889 static boolean willLoadFromCache(String url) {
890 boolean inCache = CacheManager.getCacheFile(url, null) != null;
Dave Bort42bc2ff2009-04-13 15:07:51 -0700891 if (WebView.LOGV_ENABLED) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800892 Log.v(LOGTAG, "willLoadFromCache: " + url + " in cache: " +
893 inCache);
894 }
895 return inCache;
896 }
897
898 /*
899 * Reset the cancel flag. This is used when we are resuming a stopped
900 * download. To suspend a download, we cancel it. It can also be cancelled
901 * when it has run out of disk space. In this situation, the download
902 * can be resumed.
903 */
904 void resetCancel() {
905 mCancelled = false;
906 }
907
908 String mimeType() {
909 return mMimeType;
910 }
911
912 /*
913 * Return the size of the content being downloaded. This represents the
914 * full content size, even under the situation where the download has been
915 * resumed after interruption.
916 *
917 * @ return full content size
918 */
919 long contentLength() {
920 return mContentLength;
921 }
922
923 // Commit the headers if the status code is not a redirect.
924 private void commitHeadersCheckRedirect() {
925 if (mCancelled) return;
926
927 // do not call webcore if it is redirect. According to the code in
928 // InspectorController::willSendRequest(), the response is only updated
929 // when it is not redirect.
930 if ((mStatusCode >= 301 && mStatusCode <= 303) || mStatusCode == 307) {
931 return;
932 }
933
934 commitHeaders();
935 }
936
937 // This commits the headers without checking the response status code.
938 private void commitHeaders() {
Grace Kloba3af8e932009-06-19 15:03:46 -0700939 if (mIsMainPageLoader && CERT_MIMETYPE.equals(mMimeType)) {
940 // In the case of downloading certificate, we will save it to the
941 // Keystore in commitLoad. Do not call webcore.
942 return;
943 }
944
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800945 // Commit the headers to WebCore
946 int nativeResponse = createNativeResponse();
947 // The native code deletes the native response object.
948 nativeReceivedResponse(nativeResponse);
949 }
950
951 /**
952 * Create a WebCore response object so that it can be used by
953 * nativeReceivedResponse or nativeRedirectedToUrl
954 * @return native response pointer
955 */
956 private int createNativeResponse() {
The Android Open Source Project10592532009-03-18 17:39:46 -0700957 // If WebCore sends if-modified-since, mCacheLoader is null. If
958 // CacheManager sends it, mCacheLoader is not null. In this case, if the
959 // server responds with a 304, then we treat it like it was a 200 code
960 // and proceed with loading the file from the cache.
961 int statusCode = (mStatusCode == HTTP_NOT_MODIFIED &&
962 mCacheLoader != null) ? HTTP_OK : mStatusCode;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800963 // pass content-type content-length and content-encoding
964 final int nativeResponse = nativeCreateResponse(
965 mUrl, statusCode, mStatusText,
966 mMimeType, mContentLength, mEncoding,
967 mCacheResult == null ? 0 : mCacheResult.expires / 1000);
968 if (mHeaders != null) {
969 mHeaders.getHeaders(new Headers.HeaderCallback() {
970 public void header(String name, String value) {
971 nativeSetResponseHeader(nativeResponse, name, value);
972 }
973 });
974 }
975 return nativeResponse;
976 }
977
978 /**
979 * Commit the load. It should be ok to call repeatedly but only before
980 * tearDown is called.
981 */
982 private void commitLoad() {
983 if (mCancelled) return;
984
Grace Kloba3af8e932009-06-19 15:03:46 -0700985 if (mIsMainPageLoader && CERT_MIMETYPE.equals(mMimeType)) {
986 // In the case of downloading certificate, we will save it to the
987 // Keystore and stop the current loading so that it will not
988 // generate a new history page
989 byte[] cert = new byte[mDataBuilder.getByteSize()];
990 int position = 0;
991 ByteArrayBuilder.Chunk c;
992 while (true) {
993 c = mDataBuilder.getFirstChunk();
994 if (c == null) break;
995
996 if (c.mLength != 0) {
997 System.arraycopy(c.mArray, 0, cert, position, c.mLength);
998 position += c.mLength;
999 }
1000 mDataBuilder.releaseChunk(c);
1001 }
1002 Keystore.getInstance().addCertificate(cert);
1003 Toast.makeText(mContext, R.string.certificateSaved,
1004 Toast.LENGTH_SHORT).show();
1005 mBrowserFrame.stopLoading();
1006 return;
1007 }
1008
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001009 // Give the data to WebKit now
1010 PerfChecker checker = new PerfChecker();
1011 ByteArrayBuilder.Chunk c;
1012 while (true) {
1013 c = mDataBuilder.getFirstChunk();
1014 if (c == null) break;
1015
1016 if (c.mLength != 0) {
1017 if (mCacheResult != null) {
1018 try {
1019 mCacheResult.outStream.write(c.mArray, 0, c.mLength);
1020 } catch (IOException e) {
1021 mCacheResult = null;
1022 }
1023 }
1024 nativeAddData(c.mArray, c.mLength);
1025 }
1026 mDataBuilder.releaseChunk(c);
1027 checker.responseAlert("res nativeAddData");
1028 }
1029 }
1030
1031 /**
1032 * Tear down the load. Subclasses should clean up any mess because of
1033 * cancellation or errors during the load.
1034 */
1035 void tearDown() {
1036 if (mCacheResult != null) {
1037 if (getErrorID() == OK) {
1038 CacheManager.saveCacheFile(mUrl, mCacheResult);
1039 }
1040
1041 // we need to reset mCacheResult to be null
1042 // resource loader's tearDown will call into WebCore's
1043 // nativeFinish, which in turn calls loader.cancel().
1044 // If we don't reset mCacheFile, the file will be deleted.
1045 mCacheResult = null;
1046 }
1047 if (mNativeLoader != 0) {
1048 PerfChecker checker = new PerfChecker();
1049 nativeFinished();
1050 checker.responseAlert("res nativeFinished");
1051 clearNativeLoader();
1052 }
1053 }
1054
1055 /**
1056 * Helper for getting the error ID.
1057 * @return errorID.
1058 */
1059 private int getErrorID() {
1060 return mErrorID;
1061 }
1062
1063 /**
1064 * Return the error description.
1065 * @return errorDescription.
1066 */
1067 private String getErrorDescription() {
1068 return mErrorDescription;
1069 }
1070
1071 /**
1072 * Notify the loader we encountered an error.
1073 */
1074 void notifyError() {
1075 if (mNativeLoader != 0) {
1076 String description = getErrorDescription();
1077 if (description == null) description = "";
1078 nativeError(getErrorID(), description, url());
1079 clearNativeLoader();
1080 }
1081 }
1082
1083 /**
1084 * Cancel a request.
1085 * FIXME: This will only work if the request has yet to be handled. This
1086 * is in no way guarenteed if requests are served in a separate thread.
1087 * It also causes major problems if cancel is called during an
1088 * EventHandler's method call.
1089 */
1090 public void cancel() {
Dave Bort42bc2ff2009-04-13 15:07:51 -07001091 if (WebView.LOGV_ENABLED) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001092 if (mRequestHandle == null) {
1093 Log.v(LOGTAG, "LoadListener.cancel(): no requestHandle");
1094 } else {
1095 Log.v(LOGTAG, "LoadListener.cancel()");
1096 }
1097 }
1098 if (mRequestHandle != null) {
1099 mRequestHandle.cancel();
1100 mRequestHandle = null;
1101 }
1102
1103 mCacheResult = null;
1104 mCancelled = true;
1105
1106 clearNativeLoader();
1107 }
1108
1109 // This count is transferred from RequestHandle to LoadListener when
1110 // loading from the cache so that we can detect redirect loops that switch
1111 // between the network and the cache.
1112 private int mCacheRedirectCount;
1113
1114 /*
1115 * Perform the actual redirection. This involves setting up the new URL,
1116 * informing WebCore and then telling the Network to start loading again.
1117 */
1118 private void doRedirect() {
1119 // as cancel() can cancel the load before doRedirect() is
1120 // called through handleMessage, needs to check to see if we
1121 // are canceled before proceed
1122 if (mCancelled) {
1123 return;
1124 }
1125
1126 // Do the same check for a redirect loop that
1127 // RequestHandle.setupRedirect does.
1128 if (mCacheRedirectCount >= RequestHandle.MAX_REDIRECT_COUNT) {
1129 handleError(EventHandler.ERROR_REDIRECT_LOOP, mContext.getString(
1130 R.string.httpErrorRedirectLoop));
1131 return;
1132 }
1133
1134 String redirectTo = mHeaders.getLocation();
1135 if (redirectTo != null) {
1136 int nativeResponse = createNativeResponse();
1137 redirectTo =
1138 nativeRedirectedToUrl(mUrl, redirectTo, nativeResponse);
1139 // nativeRedirectedToUrl() may call cancel(), e.g. when redirect
1140 // from a https site to a http site, check mCancelled again
1141 if (mCancelled) {
1142 return;
1143 }
1144 if (redirectTo == null) {
1145 Log.d(LOGTAG, "Redirection failed for "
1146 + mHeaders.getLocation());
1147 cancel();
1148 return;
1149 } else if (!URLUtil.isNetworkUrl(redirectTo)) {
1150 final String text = mContext
1151 .getString(R.string.open_permission_deny)
1152 + "\n" + redirectTo;
1153 nativeAddData(text.getBytes(), text.length());
1154 nativeFinished();
1155 clearNativeLoader();
1156 return;
1157 }
1158
1159 if (mOriginalUrl == null) {
1160 mOriginalUrl = mUrl;
1161 }
1162
1163 // Cache the redirect response
1164 if (mCacheResult != null) {
1165 if (getErrorID() == OK) {
1166 CacheManager.saveCacheFile(mUrl, mCacheResult);
1167 }
1168 mCacheResult = null;
1169 }
1170
The Android Open Source Project4df24232009-03-05 14:34:35 -08001171 // This will strip the anchor
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001172 setUrl(redirectTo);
1173
1174 // Redirect may be in the cache
1175 if (mRequestHeaders == null) {
1176 mRequestHeaders = new HashMap<String, String>();
1177 }
1178 boolean fromCache = false;
1179 if (mCacheLoader != null) {
1180 // This is a redirect from the cache loader. Increment the
1181 // redirect count to avoid redirect loops.
1182 mCacheRedirectCount++;
1183 fromCache = true;
1184 }
1185 if (!checkCache(mRequestHeaders)) {
1186 // mRequestHandle can be null when the request was satisfied
1187 // by the cache, and the cache returned a redirect
1188 if (mRequestHandle != null) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08001189 mRequestHandle.setupRedirect(mUrl, mStatusCode,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001190 mRequestHeaders);
1191 } else {
1192 // If the original request came from the cache, there is no
1193 // RequestHandle, we have to create a new one through
1194 // Network.requestURL.
1195 Network network = Network.getInstance(getContext());
1196 if (!network.requestURL(mMethod, mRequestHeaders,
1197 mPostData, this, mIsHighPriority)) {
1198 // Signal a bad url error if we could not load the
1199 // redirection.
1200 handleError(EventHandler.ERROR_BAD_URL,
1201 mContext.getString(R.string.httpErrorBadUrl));
1202 return;
1203 }
1204 }
1205 if (fromCache) {
1206 // If we are coming from a cache load, we need to transfer
1207 // the redirect count to the new (or old) RequestHandle to
1208 // keep the redirect count in sync.
1209 mRequestHandle.setRedirectCount(mCacheRedirectCount);
1210 }
1211 } else if (!fromCache) {
1212 // Switching from network to cache means we need to grab the
1213 // redirect count from the RequestHandle to keep the count in
1214 // sync. Add 1 to account for the current redirect.
1215 mCacheRedirectCount = mRequestHandle.getRedirectCount() + 1;
1216 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001217 } else {
1218 commitHeaders();
1219 commitLoad();
1220 tearDown();
1221 }
1222
Dave Bort42bc2ff2009-04-13 15:07:51 -07001223 if (WebView.LOGV_ENABLED) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001224 Log.v(LOGTAG, "LoadListener.onRedirect(): redirect to: " +
1225 redirectTo);
1226 }
1227 }
1228
1229 /**
1230 * Parses the content-type header.
The Android Open Source Project10592532009-03-18 17:39:46 -07001231 * The first part only allows '-' if it follows x or X.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001232 */
1233 private static final Pattern CONTENT_TYPE_PATTERN =
The Android Open Source Project10592532009-03-18 17:39:46 -07001234 Pattern.compile("^((?:[xX]-)?[a-zA-Z\\*]+/[\\w\\+\\*-]+[\\.[\\w\\+-]+]*)$");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001235
1236 private void parseContentTypeHeader(String contentType) {
Dave Bort42bc2ff2009-04-13 15:07:51 -07001237 if (WebView.LOGV_ENABLED) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001238 Log.v(LOGTAG, "LoadListener.parseContentTypeHeader: " +
1239 "contentType: " + contentType);
1240 }
1241
1242 if (contentType != null) {
1243 int i = contentType.indexOf(';');
1244 if (i >= 0) {
1245 mMimeType = contentType.substring(0, i);
1246
1247 int j = contentType.indexOf('=', i);
1248 if (j > 0) {
1249 i = contentType.indexOf(';', j);
1250 if (i < j) {
1251 i = contentType.length();
1252 }
1253 mEncoding = contentType.substring(j + 1, i);
1254 } else {
1255 mEncoding = contentType.substring(i + 1);
1256 }
1257 // Trim excess whitespace.
1258 mEncoding = mEncoding.trim();
1259
1260 if (i < contentType.length() - 1) {
1261 // for data: uri the mimeType and encoding have
1262 // the form image/jpeg;base64 or text/plain;charset=utf-8
1263 // or text/html;charset=utf-8;base64
1264 mTransferEncoding = contentType.substring(i + 1).trim();
1265 }
1266 } else {
1267 mMimeType = contentType;
1268 }
1269
1270 // Trim leading and trailing whitespace
1271 mMimeType = mMimeType.trim();
1272
1273 try {
1274 Matcher m = CONTENT_TYPE_PATTERN.matcher(mMimeType);
1275 if (m.find()) {
1276 mMimeType = m.group(1);
1277 } else {
1278 guessMimeType();
1279 }
1280 } catch (IllegalStateException ex) {
1281 guessMimeType();
1282 }
1283 }
1284 }
1285
1286 /**
1287 * @return The HTTP-authentication object or null if there
1288 * is no supported scheme in the header.
1289 * If there are several valid schemes present, we pick the
1290 * strongest one. If there are several schemes of the same
1291 * strength, we pick the one that comes first.
1292 */
1293 private HttpAuthHeader parseAuthHeader(String header) {
1294 if (header != null) {
1295 int posMax = 256;
1296 int posLen = 0;
1297 int[] pos = new int [posMax];
1298
1299 int headerLen = header.length();
1300 if (headerLen > 0) {
1301 // first, we find all unquoted instances of 'Basic' and 'Digest'
1302 boolean quoted = false;
1303 for (int i = 0; i < headerLen && posLen < posMax; ++i) {
1304 if (header.charAt(i) == '\"') {
1305 quoted = !quoted;
1306 } else {
1307 if (!quoted) {
1308 if (header.regionMatches(true, i,
1309 HttpAuthHeader.BASIC_TOKEN, 0,
1310 HttpAuthHeader.BASIC_TOKEN.length())) {
1311 pos[posLen++] = i;
1312 continue;
1313 }
1314
1315 if (header.regionMatches(true, i,
1316 HttpAuthHeader.DIGEST_TOKEN, 0,
1317 HttpAuthHeader.DIGEST_TOKEN.length())) {
1318 pos[posLen++] = i;
1319 continue;
1320 }
1321 }
1322 }
1323 }
1324 }
1325
1326 if (posLen > 0) {
1327 // consider all digest schemes first (if any)
1328 for (int i = 0; i < posLen; i++) {
1329 if (header.regionMatches(true, pos[i],
1330 HttpAuthHeader.DIGEST_TOKEN, 0,
1331 HttpAuthHeader.DIGEST_TOKEN.length())) {
1332 String sub = header.substring(pos[i],
1333 (i + 1 < posLen ? pos[i + 1] : headerLen));
1334
1335 HttpAuthHeader rval = new HttpAuthHeader(sub);
1336 if (rval.isSupportedScheme()) {
1337 // take the first match
1338 return rval;
1339 }
1340 }
1341 }
1342
1343 // ...then consider all basic schemes (if any)
1344 for (int i = 0; i < posLen; i++) {
1345 if (header.regionMatches(true, pos[i],
1346 HttpAuthHeader.BASIC_TOKEN, 0,
1347 HttpAuthHeader.BASIC_TOKEN.length())) {
1348 String sub = header.substring(pos[i],
1349 (i + 1 < posLen ? pos[i + 1] : headerLen));
1350
1351 HttpAuthHeader rval = new HttpAuthHeader(sub);
1352 if (rval.isSupportedScheme()) {
1353 // take the first match
1354 return rval;
1355 }
1356 }
1357 }
1358 }
1359 }
1360
1361 return null;
1362 }
1363
1364 /**
1365 * If the content is a redirect or not modified we should not send
1366 * any data into WebCore as that will cause it create a document with
1367 * the data, then when we try to provide the real content, it will assert.
1368 *
1369 * @return True iff the callback should be ignored.
1370 */
1371 private boolean ignoreCallbacks() {
1372 return (mCancelled || mAuthHeader != null ||
1373 (mStatusCode > 300 && mStatusCode < 400));
1374 }
1375
1376 /**
1377 * Sets the current URL associated with this load.
1378 */
1379 void setUrl(String url) {
1380 if (url != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001381 mUri = null;
The Android Open Source Project4df24232009-03-05 14:34:35 -08001382 if (URLUtil.isNetworkUrl(url)) {
1383 mUrl = URLUtil.stripAnchor(url);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001384 try {
1385 mUri = new WebAddress(mUrl);
1386 } catch (ParseException e) {
1387 e.printStackTrace();
1388 }
The Android Open Source Project4df24232009-03-05 14:34:35 -08001389 } else {
1390 mUrl = url;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001391 }
1392 }
1393 }
1394
1395 /**
1396 * Guesses MIME type if one was not specified. Defaults to 'text/html'. In
1397 * addition, tries to guess the MIME type based on the extension.
1398 *
1399 */
1400 private void guessMimeType() {
1401 // Data urls must have a valid mime type or a blank string for the mime
1402 // type (implying text/plain).
1403 if (URLUtil.isDataUrl(mUrl) && mMimeType.length() != 0) {
1404 cancel();
1405 final String text = mContext.getString(R.string.httpErrorBadUrl);
1406 handleError(EventHandler.ERROR_BAD_URL, text);
1407 } else {
1408 // Note: This is ok because this is used only for the main content
1409 // of frames. If no content-type was specified, it is fine to
1410 // default to text/html.
1411 mMimeType = "text/html";
1412 String newMimeType = guessMimeTypeFromExtension();
1413 if (newMimeType != null) {
1414 mMimeType = newMimeType;
1415 }
1416 }
1417 }
1418
1419 /**
1420 * guess MIME type based on the file extension.
1421 */
1422 private String guessMimeTypeFromExtension() {
1423 // PENDING: need to normalize url
Dave Bort42bc2ff2009-04-13 15:07:51 -07001424 if (WebView.LOGV_ENABLED) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001425 Log.v(LOGTAG, "guessMimeTypeFromExtension: mURL = " + mUrl);
1426 }
1427
1428 String mimeType =
1429 MimeTypeMap.getSingleton().getMimeTypeFromExtension(
1430 MimeTypeMap.getFileExtensionFromUrl(mUrl));
1431
1432 if (mimeType != null) {
1433 // XXX: Until the servers send us either correct xhtml or
1434 // text/html, treat application/xhtml+xml as text/html.
1435 if (mimeType.equals("application/xhtml+xml")) {
1436 mimeType = "text/html";
1437 }
1438 }
1439
1440 return mimeType;
1441 }
1442
1443 /**
1444 * Either send a message to ourselves or queue the message if this is a
1445 * synchronous load.
1446 */
1447 private void sendMessageInternal(Message msg) {
1448 if (mSynchronous) {
1449 mMessageQueue.add(msg);
1450 } else {
1451 sendMessage(msg);
1452 }
1453 }
1454
1455 /**
1456 * Cycle through our messages for synchronous loads.
1457 */
1458 /* package */ void loadSynchronousMessages() {
Dave Bort42bc2ff2009-04-13 15:07:51 -07001459 if (WebView.DEBUG && !mSynchronous) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001460 throw new AssertionError();
1461 }
1462 // Note: this can be called twice if it is a synchronous network load,
1463 // and there is a cache, but it needs to go to network to validate. If
1464 // validation succeed, the CacheLoader is used so this is first called
1465 // from http thread. Then it is called again from WebViewCore thread
1466 // after the load is completed. So make sure the queue is cleared but
1467 // don't set it to null.
1468 for (int size = mMessageQueue.size(); size > 0; size--) {
1469 handleMessage(mMessageQueue.remove(0));
1470 }
1471 }
1472
1473 //=========================================================================
1474 // native functions
1475 //=========================================================================
1476
1477 /**
1478 * Create a new native response object.
1479 * @param url The url of the resource.
1480 * @param statusCode The HTTP status code.
1481 * @param statusText The HTTP status text.
1482 * @param mimeType HTTP content-type.
1483 * @param expectedLength An estimate of the content length or the length
1484 * given by the server.
1485 * @param encoding HTTP encoding.
1486 * @param expireTime HTTP expires converted to seconds since the epoch.
1487 * @return The native response pointer.
1488 */
1489 private native int nativeCreateResponse(String url, int statusCode,
1490 String statusText, String mimeType, long expectedLength,
1491 String encoding, long expireTime);
1492
1493 /**
1494 * Add a response header to the native object.
1495 * @param nativeResponse The native pointer.
1496 * @param key String key.
1497 * @param val String value.
1498 */
1499 private native void nativeSetResponseHeader(int nativeResponse, String key,
1500 String val);
1501
1502 /**
1503 * Dispatch the response.
1504 * @param nativeResponse The native pointer.
1505 */
1506 private native void nativeReceivedResponse(int nativeResponse);
1507
1508 /**
1509 * Add data to the loader.
1510 * @param data Byte array of data.
1511 * @param length Number of objects in data.
1512 */
1513 private native void nativeAddData(byte[] data, int length);
1514
1515 /**
1516 * Tell the loader it has finished.
1517 */
1518 private native void nativeFinished();
1519
1520 /**
1521 * tell the loader to redirect
1522 * @param baseUrl The base url.
1523 * @param redirectTo The url to redirect to.
1524 * @param nativeResponse The native pointer.
1525 * @return The new url that the resource redirected to.
1526 */
1527 private native String nativeRedirectedToUrl(String baseUrl,
1528 String redirectTo, int nativeResponse);
1529
1530 /**
1531 * Tell the loader there is error
1532 * @param id
1533 * @param desc
1534 * @param failingUrl The url that failed.
1535 */
1536 private native void nativeError(int id, String desc, String failingUrl);
1537
1538}