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