Home | History | Annotate | Download | only in webkit
      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 
    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 
    144     public static int getNativeLoaderCount() {
    145         return sNativeLoaderCount;
    146     }
    147 
    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      */
    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      */
    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      */
    283     BrowserFrame getFrame() {
    284         return mBrowserFrame;
    285     }
    286 
    287     Context getContext() {
    288         return mContext;
    289     }
    290 
    291     /* package */ boolean isSynchronous() {
    292         return mSynchronous;
    293     }
    294 
    295     /**
    296      * @return True iff the load has been cancelled
    297      */
    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      */
    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.
    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      */
    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      */
    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.
    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      */
    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.
    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      */
    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.
    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      */
    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      */
    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.
    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. */
    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      */
    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      */
    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.
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    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      */
    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      */
    905     String url() {
    906         return mUrl;
    907     }
    908 
    909     /**
    910      * @return The current WebAddress associated with this load.
    911      */
    912     WebAddress getWebAddress() {
    913         return mUri;
    914     }
    915 
    916     /**
    917      * @return URL hostname (current URL).
    918      */
    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      */
    930     String originalUrl() {
    931         if (mOriginalUrl != null) {
    932             return mOriginalUrl;
    933         } else {
    934             return mUrl;
    935         }
    936     }
    937 
    938     long postIdentifier() {
    939         return mPostIdentifier;
    940     }
    941 
    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 
    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      */
    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      */
    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      */
   1005     void resetCancel() {
   1006         mCancelled = false;
   1007     }
   1008 
   1009     String mimeType() {
   1010         return mMimeType;
   1011     }
   1012 
   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      */
   1024     long contentLength() {
   1025         return mContentLength;
   1026     }
   1027 
   1028     // Commit the headers if the status code is not a redirect.
   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.
   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 
   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      */
   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      */
   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         ByteArrayBuilder.Chunk c;
   1140         while (true) {
   1141             c = mDataBuilder.getFirstChunk();
   1142             if (c == null) break;
   1143 
   1144             if (c.mLength != 0) {
   1145                 nativeAddData(c.mArray, c.mLength);
   1146                 WebViewWorker.CacheData data = new WebViewWorker.CacheData();
   1147                 data.mListener = this;
   1148                 data.mChunk = c;
   1149                 WebViewWorker.getHandler().obtainMessage(
   1150                         WebViewWorker.MSG_APPEND_CACHE, data).sendToTarget();
   1151             } else {
   1152                 c.release();
   1153             }
   1154         }
   1155     }
   1156 
   1157     /**
   1158      * Tear down the load. Subclasses should clean up any mess because of
   1159      * cancellation or errors during the load.
   1160      */
   1161     void tearDown() {
   1162         if (getErrorID() == OK) {
   1163             WebViewWorker.CacheSaveData data = new WebViewWorker.CacheSaveData();
   1164             data.mListener = this;
   1165             data.mUrl = mUrl;
   1166             data.mPostId = mPostIdentifier;
   1167             WebViewWorker.getHandler().obtainMessage(
   1168                     WebViewWorker.MSG_SAVE_CACHE, data).sendToTarget();
   1169         } else {
   1170             WebViewWorker.getHandler().obtainMessage(
   1171                     WebViewWorker.MSG_REMOVE_CACHE, this).sendToTarget();
   1172         }
   1173         if (mNativeLoader != 0) {
   1174             if (!mSetNativeResponse) {
   1175                 setNativeResponse();
   1176             }
   1177 
   1178             nativeFinished();
   1179             clearNativeLoader();
   1180         }
   1181     }
   1182 
   1183     /**
   1184      * Helper for getting the error ID.
   1185      * @return errorID.
   1186      */
   1187     private int getErrorID() {
   1188         return mErrorID;
   1189     }
   1190 
   1191     /**
   1192      * Return the error description.
   1193      * @return errorDescription.
   1194      */
   1195     private String getErrorDescription() {
   1196         return mErrorDescription;
   1197     }
   1198 
   1199     /**
   1200      * Notify the loader we encountered an error.
   1201      */
   1202     void notifyError() {
   1203         if (mNativeLoader != 0) {
   1204             String description = getErrorDescription();
   1205             if (description == null) description = "";
   1206             nativeError(getErrorID(), description, url());
   1207             clearNativeLoader();
   1208         }
   1209     }
   1210 
   1211     /**
   1212      * Pause the load. For example, if a plugin is unable to accept more data,
   1213      * we pause reading from the request. Called directly from the WebCore thread.
   1214      */
   1215     void pauseLoad(boolean pause) {
   1216         if (mRequestHandle != null) {
   1217             mRequestHandle.pauseRequest(pause);
   1218         }
   1219     }
   1220 
   1221     /**
   1222      * Cancel a request.
   1223      * FIXME: This will only work if the request has yet to be handled. This
   1224      * is in no way guarenteed if requests are served in a separate thread.
   1225      * It also causes major problems if cancel is called during an
   1226      * EventHandler's method call.
   1227      */
   1228     public void cancel() {
   1229         if (DebugFlags.LOAD_LISTENER) {
   1230             if (mRequestHandle == null) {
   1231                 Log.v(LOGTAG, "LoadListener.cancel(): no requestHandle");
   1232             } else {
   1233                 Log.v(LOGTAG, "LoadListener.cancel()");
   1234             }
   1235         }
   1236         if (mRequestHandle != null) {
   1237             mRequestHandle.cancel();
   1238             mRequestHandle = null;
   1239         }
   1240 
   1241         WebViewWorker.getHandler().obtainMessage(
   1242                 WebViewWorker.MSG_REMOVE_CACHE, this).sendToTarget();
   1243         mCancelled = true;
   1244 
   1245         clearNativeLoader();
   1246     }
   1247 
   1248     // This count is transferred from RequestHandle to LoadListener when
   1249     // loading from the cache so that we can detect redirect loops that switch
   1250     // between the network and the cache.
   1251     private int mCacheRedirectCount;
   1252 
   1253     /*
   1254      * Perform the actual redirection. This involves setting up the new URL,
   1255      * informing WebCore and then telling the Network to start loading again.
   1256      */
   1257     private void doRedirect() {
   1258         // as cancel() can cancel the load before doRedirect() is
   1259         // called through handleMessage, needs to check to see if we
   1260         // are canceled before proceed
   1261         if (mCancelled) {
   1262             return;
   1263         }
   1264 
   1265         // Do the same check for a redirect loop that
   1266         // RequestHandle.setupRedirect does.
   1267         if (mCacheRedirectCount >= RequestHandle.MAX_REDIRECT_COUNT) {
   1268             handleError(EventHandler.ERROR_REDIRECT_LOOP, mContext.getString(
   1269                     R.string.httpErrorRedirectLoop));
   1270             return;
   1271         }
   1272 
   1273         String redirectTo = mHeaders.getLocation();
   1274         if (redirectTo != null) {
   1275             int nativeResponse = createNativeResponse();
   1276             redirectTo =
   1277                     nativeRedirectedToUrl(mUrl, redirectTo, nativeResponse);
   1278             // nativeRedirectedToUrl() may call cancel(), e.g. when redirect
   1279             // from a https site to a http site, check mCancelled again
   1280             if (mCancelled) {
   1281                 return;
   1282             }
   1283             if (redirectTo == null) {
   1284                 Log.d(LOGTAG, "Redirection failed for "
   1285                         + mHeaders.getLocation());
   1286                 cancel();
   1287                 return;
   1288             } else if (!URLUtil.isNetworkUrl(redirectTo)) {
   1289                 final String text = mContext
   1290                         .getString(R.string.open_permission_deny)
   1291                         + "\n" + redirectTo;
   1292                 if (!mSetNativeResponse) {
   1293                     setNativeResponse();
   1294                 }
   1295                 nativeAddData(text.getBytes(), text.length());
   1296                 nativeFinished();
   1297                 clearNativeLoader();
   1298                 return;
   1299             }
   1300 
   1301 
   1302             // Cache the redirect response
   1303             if (getErrorID() == OK) {
   1304                 WebViewWorker.CacheSaveData data = new WebViewWorker.CacheSaveData();
   1305                 data.mListener = this;
   1306                 data.mUrl = mUrl;
   1307                 data.mPostId = mPostIdentifier;
   1308                 WebViewWorker.getHandler().obtainMessage(
   1309                         WebViewWorker.MSG_SAVE_CACHE, data).sendToTarget();
   1310             } else {
   1311                 WebViewWorker.getHandler().obtainMessage(
   1312                         WebViewWorker.MSG_REMOVE_CACHE, this).sendToTarget();
   1313             }
   1314 
   1315             // Saving a copy of the unstripped url for the response
   1316             mOriginalUrl = redirectTo;
   1317             // This will strip the anchor
   1318             setUrl(redirectTo);
   1319 
   1320             // Redirect may be in the cache
   1321             if (mRequestHeaders == null) {
   1322                 mRequestHeaders = new HashMap<String, String>();
   1323             }
   1324             boolean fromCache = false;
   1325             if (mCacheLoader != null) {
   1326                 // This is a redirect from the cache loader. Increment the
   1327                 // redirect count to avoid redirect loops.
   1328                 mCacheRedirectCount++;
   1329                 fromCache = true;
   1330             }
   1331             if (!checkCache(mRequestHeaders)) {
   1332                 // mRequestHandle can be null when the request was satisfied
   1333                 // by the cache, and the cache returned a redirect
   1334                 if (mRequestHandle != null) {
   1335                     try {
   1336                         mRequestHandle.setupRedirect(mUrl, mStatusCode,
   1337                                 mRequestHeaders);
   1338                     } catch(RuntimeException e) {
   1339                         Log.e(LOGTAG, e.getMessage());
   1340                         // Signal a bad url error if we could not load the
   1341                         // redirection.
   1342                         handleError(EventHandler.ERROR_BAD_URL,
   1343                                 mContext.getString(R.string.httpErrorBadUrl));
   1344                         return;
   1345                     }
   1346                 } else {
   1347                     // If the original request came from the cache, there is no
   1348                     // RequestHandle, we have to create a new one through
   1349                     // Network.requestURL.
   1350                     Network network = Network.getInstance(getContext());
   1351                     if (!network.requestURL(mMethod, mRequestHeaders,
   1352                             mPostData, this)) {
   1353                         // Signal a bad url error if we could not load the
   1354                         // redirection.
   1355                         handleError(EventHandler.ERROR_BAD_URL,
   1356                                 mContext.getString(R.string.httpErrorBadUrl));
   1357                         return;
   1358                     }
   1359                 }
   1360                 if (fromCache) {
   1361                     // If we are coming from a cache load, we need to transfer
   1362                     // the redirect count to the new (or old) RequestHandle to
   1363                     // keep the redirect count in sync.
   1364                     mRequestHandle.setRedirectCount(mCacheRedirectCount);
   1365                 }
   1366             } else if (!fromCache) {
   1367                 // Switching from network to cache means we need to grab the
   1368                 // redirect count from the RequestHandle to keep the count in
   1369                 // sync. Add 1 to account for the current redirect.
   1370                 mCacheRedirectCount = mRequestHandle.getRedirectCount() + 1;
   1371             }
   1372         } else {
   1373             commitHeaders();
   1374             commitLoad();
   1375             tearDown();
   1376         }
   1377 
   1378         if (DebugFlags.LOAD_LISTENER) {
   1379             Log.v(LOGTAG, "LoadListener.onRedirect(): redirect to: " +
   1380                     redirectTo);
   1381         }
   1382     }
   1383 
   1384     /**
   1385      * Parses the content-type header.
   1386      * The first part only allows '-' if it follows x or X.
   1387      */
   1388     private static final Pattern CONTENT_TYPE_PATTERN =
   1389             Pattern.compile("^((?:[xX]-)?[a-zA-Z\\*]+/[\\w\\+\\*-]+[\\.[\\w\\+-]+]*)$");
   1390 
   1391     /* package */ void parseContentTypeHeader(String contentType) {
   1392         if (DebugFlags.LOAD_LISTENER) {
   1393             Log.v(LOGTAG, "LoadListener.parseContentTypeHeader: " +
   1394                     "contentType: " + contentType);
   1395         }
   1396 
   1397         if (contentType != null) {
   1398             int i = contentType.indexOf(';');
   1399             if (i >= 0) {
   1400                 mMimeType = contentType.substring(0, i);
   1401 
   1402                 int j = contentType.indexOf('=', i);
   1403                 if (j > 0) {
   1404                     i = contentType.indexOf(';', j);
   1405                     if (i < j) {
   1406                         i = contentType.length();
   1407                     }
   1408                     mEncoding = contentType.substring(j + 1, i);
   1409                 } else {
   1410                     mEncoding = contentType.substring(i + 1);
   1411                 }
   1412                 // Trim excess whitespace.
   1413                 mEncoding = mEncoding.trim().toLowerCase();
   1414 
   1415                 if (i < contentType.length() - 1) {
   1416                     // for data: uri the mimeType and encoding have
   1417                     // the form image/jpeg;base64 or text/plain;charset=utf-8
   1418                     // or text/html;charset=utf-8;base64
   1419                     mTransferEncoding =
   1420                             contentType.substring(i + 1).trim().toLowerCase();
   1421                 }
   1422             } else {
   1423                 mMimeType = contentType;
   1424             }
   1425 
   1426             // Trim leading and trailing whitespace
   1427             mMimeType = mMimeType.trim();
   1428 
   1429             try {
   1430                 Matcher m = CONTENT_TYPE_PATTERN.matcher(mMimeType);
   1431                 if (m.find()) {
   1432                     mMimeType = m.group(1);
   1433                 } else {
   1434                     guessMimeType();
   1435                 }
   1436             } catch (IllegalStateException ex) {
   1437                 guessMimeType();
   1438             }
   1439         }
   1440         // Ensure mMimeType is lower case.
   1441         mMimeType = mMimeType.toLowerCase();
   1442     }
   1443 
   1444     /**
   1445      * @return The HTTP-authentication object or null if there
   1446      * is no supported scheme in the header.
   1447      * If there are several valid schemes present, we pick the
   1448      * strongest one. If there are several schemes of the same
   1449      * strength, we pick the one that comes first.
   1450      */
   1451     private HttpAuthHeader parseAuthHeader(String header) {
   1452         if (header != null) {
   1453             int posMax = 256;
   1454             int posLen = 0;
   1455             int[] pos = new int [posMax];
   1456 
   1457             int headerLen = header.length();
   1458             if (headerLen > 0) {
   1459                 // first, we find all unquoted instances of 'Basic' and 'Digest'
   1460                 boolean quoted = false;
   1461                 for (int i = 0; i < headerLen && posLen < posMax; ++i) {
   1462                     if (header.charAt(i) == '\"') {
   1463                         quoted = !quoted;
   1464                     } else {
   1465                         if (!quoted) {
   1466                             if (header.regionMatches(true, i,
   1467                                     HttpAuthHeader.BASIC_TOKEN, 0,
   1468                                     HttpAuthHeader.BASIC_TOKEN.length())) {
   1469                                 pos[posLen++] = i;
   1470                                 continue;
   1471                             }
   1472 
   1473                             if (header.regionMatches(true, i,
   1474                                     HttpAuthHeader.DIGEST_TOKEN, 0,
   1475                                     HttpAuthHeader.DIGEST_TOKEN.length())) {
   1476                                 pos[posLen++] = i;
   1477                                 continue;
   1478                             }
   1479                         }
   1480                     }
   1481                 }
   1482             }
   1483 
   1484             if (posLen > 0) {
   1485                 // consider all digest schemes first (if any)
   1486                 for (int i = 0; i < posLen; i++) {
   1487                     if (header.regionMatches(true, pos[i],
   1488                                 HttpAuthHeader.DIGEST_TOKEN, 0,
   1489                                 HttpAuthHeader.DIGEST_TOKEN.length())) {
   1490                         String sub = header.substring(pos[i],
   1491                                 (i + 1 < posLen ? pos[i + 1] : headerLen));
   1492 
   1493                         HttpAuthHeader rval = new HttpAuthHeader(sub);
   1494                         if (rval.isSupportedScheme()) {
   1495                             // take the first match
   1496                             return rval;
   1497                         }
   1498                     }
   1499                 }
   1500 
   1501                 // ...then consider all basic schemes (if any)
   1502                 for (int i = 0; i < posLen; i++) {
   1503                     if (header.regionMatches(true, pos[i],
   1504                                 HttpAuthHeader.BASIC_TOKEN, 0,
   1505                                 HttpAuthHeader.BASIC_TOKEN.length())) {
   1506                         String sub = header.substring(pos[i],
   1507                                 (i + 1 < posLen ? pos[i + 1] : headerLen));
   1508 
   1509                         HttpAuthHeader rval = new HttpAuthHeader(sub);
   1510                         if (rval.isSupportedScheme()) {
   1511                             // take the first match
   1512                             return rval;
   1513                         }
   1514                     }
   1515                 }
   1516             }
   1517         }
   1518 
   1519         return null;
   1520     }
   1521 
   1522     /**
   1523      * If the content is a redirect or not modified we should not send
   1524      * any data into WebCore as that will cause it create a document with
   1525      * the data, then when we try to provide the real content, it will assert.
   1526      *
   1527      * @return True iff the callback should be ignored.
   1528      */
   1529     private boolean ignoreCallbacks() {
   1530         return (mCancelled || mAuthHeader != null ||
   1531                 // Allow 305 (Use Proxy) to call through.
   1532                 (mStatusCode > 300 && mStatusCode < 400 && mStatusCode != 305));
   1533     }
   1534 
   1535     /**
   1536      * Sets the current URL associated with this load.
   1537      */
   1538     void setUrl(String url) {
   1539         if (url != null) {
   1540             mUri = null;
   1541             if (URLUtil.isNetworkUrl(url)) {
   1542                 mUrl = URLUtil.stripAnchor(url);
   1543                 try {
   1544                     mUri = new WebAddress(mUrl);
   1545                 } catch (ParseException e) {
   1546                     e.printStackTrace();
   1547                 }
   1548             } else {
   1549                 mUrl = url;
   1550             }
   1551         }
   1552     }
   1553 
   1554     /**
   1555      * Guesses MIME type if one was not specified. Defaults to 'text/html'. In
   1556      * addition, tries to guess the MIME type based on the extension.
   1557      *
   1558      */
   1559     private void guessMimeType() {
   1560         // Data urls must have a valid mime type or a blank string for the mime
   1561         // type (implying text/plain).
   1562         if (URLUtil.isDataUrl(mUrl) && mMimeType.length() != 0) {
   1563             cancel();
   1564             final String text = mContext.getString(R.string.httpErrorBadUrl);
   1565             handleError(EventHandler.ERROR_BAD_URL, text);
   1566         } else {
   1567             // Note: This is ok because this is used only for the main content
   1568             // of frames. If no content-type was specified, it is fine to
   1569             // default to text/html.
   1570             mMimeType = "text/html";
   1571             String newMimeType = guessMimeTypeFromExtension(mUrl);
   1572             if (newMimeType != null) {
   1573                 mMimeType = newMimeType;
   1574             }
   1575         }
   1576     }
   1577 
   1578     /**
   1579      * guess MIME type based on the file extension.
   1580      */
   1581     private String guessMimeTypeFromExtension(String url) {
   1582         // PENDING: need to normalize url
   1583         if (DebugFlags.LOAD_LISTENER) {
   1584             Log.v(LOGTAG, "guessMimeTypeFromExtension: url = " + url);
   1585         }
   1586 
   1587         return MimeTypeMap.getSingleton().getMimeTypeFromExtension(
   1588                 MimeTypeMap.getFileExtensionFromUrl(url));
   1589     }
   1590 
   1591     /**
   1592      * Either send a message to ourselves or queue the message if this is a
   1593      * synchronous load.
   1594      */
   1595     private void sendMessageInternal(Message msg) {
   1596         if (mSynchronous) {
   1597             mMessageQueue.add(msg);
   1598         } else {
   1599             sendMessage(msg);
   1600         }
   1601     }
   1602 
   1603     /**
   1604      * Cycle through our messages for synchronous loads.
   1605      */
   1606     /* package */ void loadSynchronousMessages() {
   1607         if (DebugFlags.LOAD_LISTENER && !mSynchronous) {
   1608             throw new AssertionError();
   1609         }
   1610         // Note: this can be called twice if it is a synchronous network load,
   1611         // and there is a cache, but it needs to go to network to validate. If
   1612         // validation succeed, the CacheLoader is used so this is first called
   1613         // from http thread. Then it is called again from WebViewCore thread
   1614         // after the load is completed. So make sure the queue is cleared but
   1615         // don't set it to null.
   1616         while (!mMessageQueue.isEmpty()) {
   1617             handleMessage(mMessageQueue.remove(0));
   1618         }
   1619     }
   1620 
   1621     //=========================================================================
   1622     // native functions
   1623     //=========================================================================
   1624 
   1625     /**
   1626      * Create a new native response object.
   1627      * @param url The url of the resource.
   1628      * @param statusCode The HTTP status code.
   1629      * @param statusText The HTTP status text.
   1630      * @param mimeType HTTP content-type.
   1631      * @param expectedLength An estimate of the content length or the length
   1632      *                       given by the server.
   1633      * @param encoding HTTP encoding.
   1634      * @return The native response pointer.
   1635      */
   1636     private native int nativeCreateResponse(String url, int statusCode,
   1637             String statusText, String mimeType, long expectedLength,
   1638             String encoding);
   1639 
   1640     /**
   1641      * Add a response header to the native object.
   1642      * @param nativeResponse The native pointer.
   1643      * @param key String key.
   1644      * @param val String value.
   1645      */
   1646     private native void nativeSetResponseHeader(int nativeResponse, String key,
   1647             String val);
   1648 
   1649     /**
   1650      * Dispatch the response.
   1651      * @param nativeResponse The native pointer.
   1652      */
   1653     private native void nativeReceivedResponse(int nativeResponse);
   1654 
   1655     /**
   1656      * Add data to the loader.
   1657      * @param data Byte array of data.
   1658      * @param length Number of objects in data.
   1659      */
   1660     private native void nativeAddData(byte[] data, int length);
   1661 
   1662     /**
   1663      * Tell the loader it has finished.
   1664      */
   1665     private native void nativeFinished();
   1666 
   1667     /**
   1668      * tell the loader to redirect
   1669      * @param baseUrl The base url.
   1670      * @param redirectTo The url to redirect to.
   1671      * @param nativeResponse The native pointer.
   1672      * @return The new url that the resource redirected to.
   1673      */
   1674     private native String nativeRedirectedToUrl(String baseUrl,
   1675             String redirectTo, int nativeResponse);
   1676 
   1677     /**
   1678      * Tell the loader there is error
   1679      * @param id
   1680      * @param desc
   1681      * @param failingUrl The url that failed.
   1682      */
   1683     private native void nativeError(int id, String desc, String failingUrl);
   1684 
   1685 }
   1686