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