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