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