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         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      */
   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      */
   1191     private int getErrorID() {
   1192         return mErrorID;
   1193     }
   1194 
   1195     /**
   1196      * Return the error description.
   1197      * @return errorDescription.
   1198      */
   1199     private String getErrorDescription() {
   1200         return mErrorDescription;
   1201     }
   1202 
   1203     /**
   1204      * Notify the loader we encountered an error.
   1205      */
   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      */
   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      */
   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      */
   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 
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   1664     private native void nativeAddData(byte[] data, int length);
   1665 
   1666     /**
   1667      * Tell the loader it has finished.
   1668      */
   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      */
   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      */
   1687     private native void nativeError(int id, String desc, String failingUrl);
   1688 
   1689 }
   1690