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