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