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