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