LoadListener.java revision 5ac85ccabbaa02623152f140b9233c6cefcf3b77
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. If we received a not-modified response from
1018        // the server and mCacheLoader is not null, do not send the response to
1019        // webkit. This is just a validation response for loading from the
1020        // cache.
1021        if ((mStatusCode >= 301 && mStatusCode <= 303) || mStatusCode == 307 ||
1022                (mStatusCode == 304 && mCacheLoader != null)) {
1023            return;
1024        }
1025
1026        commitHeaders();
1027    }
1028
1029    // This commits the headers without checking the response status code.
1030    private void commitHeaders() {
1031        if (mIsMainPageLoader && sCertificateTypeMap.containsKey(mMimeType)) {
1032            // In the case of downloading certificate, we will save it to the
1033            // KeyStore in commitLoad. Do not call webcore.
1034            return;
1035        }
1036
1037        // If the response is an authentication and we've resent the
1038        // request with some credentials then don't commit the headers
1039        // of this response; wait for the response to the request with the
1040        // credentials.
1041        if (mAuthHeader != null)
1042            return;
1043
1044        // Commit the headers to WebCore
1045        int nativeResponse = createNativeResponse();
1046        // The native code deletes the native response object.
1047        nativeReceivedResponse(nativeResponse);
1048    }
1049
1050    /**
1051     * Create a WebCore response object so that it can be used by
1052     * nativeReceivedResponse or nativeRedirectedToUrl
1053     * @return native response pointer
1054     */
1055    private int createNativeResponse() {
1056        // If WebCore sends if-modified-since, mCacheLoader is null. If
1057        // CacheManager sends it, mCacheLoader is not null. In this case, if the
1058        // server responds with a 304, then we treat it like it was a 200 code
1059        // and proceed with loading the file from the cache.
1060        int statusCode = (mStatusCode == HTTP_NOT_MODIFIED &&
1061                mCacheLoader != null) ? HTTP_OK : mStatusCode;
1062        // pass content-type content-length and content-encoding
1063        final int nativeResponse = nativeCreateResponse(
1064                originalUrl(), statusCode, mStatusText,
1065                mMimeType, mContentLength, mEncoding);
1066        if (mHeaders != null) {
1067            mHeaders.getHeaders(new Headers.HeaderCallback() {
1068                    public void header(String name, String value) {
1069                        nativeSetResponseHeader(nativeResponse, name, value);
1070                    }
1071                });
1072        }
1073        return nativeResponse;
1074    }
1075
1076    /**
1077     * Commit the load.  It should be ok to call repeatedly but only before
1078     * tearDown is called.
1079     */
1080    private void commitLoad() {
1081        if (mCancelled) return;
1082
1083        if (mIsMainPageLoader) {
1084            String type = sCertificateTypeMap.get(mMimeType);
1085            if (type != null) {
1086                // This must be synchronized so that no more data can be added
1087                // after getByteSize returns.
1088                synchronized (mDataBuilder) {
1089                    // In the case of downloading certificate, we will save it
1090                    // to the KeyStore and stop the current loading so that it
1091                    // will not generate a new history page
1092                    byte[] cert = new byte[mDataBuilder.getByteSize()];
1093                    int offset = 0;
1094                    while (true) {
1095                        ByteArrayBuilder.Chunk c = mDataBuilder.getFirstChunk();
1096                        if (c == null) break;
1097
1098                        if (c.mLength != 0) {
1099                            System.arraycopy(c.mArray, 0, cert, offset, c.mLength);
1100                            offset += c.mLength;
1101                        }
1102                        c.release();
1103                    }
1104                    CertTool.addCertificate(mContext, type, cert);
1105                    mBrowserFrame.stopLoading();
1106                    return;
1107                }
1108            }
1109        }
1110
1111        // Give the data to WebKit now. We don't have to synchronize on
1112        // mDataBuilder here because pulling each chunk removes it from the
1113        // internal list so it cannot be modified.
1114        PerfChecker checker = new PerfChecker();
1115        ByteArrayBuilder.Chunk c;
1116        while (true) {
1117            c = mDataBuilder.getFirstChunk();
1118            if (c == null) break;
1119
1120            if (c.mLength != 0) {
1121                nativeAddData(c.mArray, c.mLength);
1122                WebViewWorker.CacheData data = new WebViewWorker.CacheData();
1123                data.mListener = this;
1124                data.mChunk = c;
1125                WebViewWorker.getHandler().obtainMessage(
1126                        WebViewWorker.MSG_APPEND_CACHE, data).sendToTarget();
1127            } else {
1128                c.release();
1129            }
1130            checker.responseAlert("res nativeAddData");
1131        }
1132    }
1133
1134    /**
1135     * Tear down the load. Subclasses should clean up any mess because of
1136     * cancellation or errors during the load.
1137     */
1138    void tearDown() {
1139        if (getErrorID() == OK) {
1140            WebViewWorker.CacheSaveData data = new WebViewWorker.CacheSaveData();
1141            data.mListener = this;
1142            data.mUrl = mUrl;
1143            data.mPostId = mPostIdentifier;
1144            WebViewWorker.getHandler().obtainMessage(
1145                    WebViewWorker.MSG_SAVE_CACHE, data).sendToTarget();
1146        } else {
1147            WebViewWorker.getHandler().obtainMessage(
1148                    WebViewWorker.MSG_REMOVE_CACHE, this).sendToTarget();
1149        }
1150        if (mNativeLoader != 0) {
1151            PerfChecker checker = new PerfChecker();
1152            nativeFinished();
1153            checker.responseAlert("res nativeFinished");
1154            clearNativeLoader();
1155        }
1156    }
1157
1158    /**
1159     * Helper for getting the error ID.
1160     * @return errorID.
1161     */
1162    private int getErrorID() {
1163        return mErrorID;
1164    }
1165
1166    /**
1167     * Return the error description.
1168     * @return errorDescription.
1169     */
1170    private String getErrorDescription() {
1171        return mErrorDescription;
1172    }
1173
1174    /**
1175     * Notify the loader we encountered an error.
1176     */
1177    void notifyError() {
1178        if (mNativeLoader != 0) {
1179            String description = getErrorDescription();
1180            if (description == null) description = "";
1181            nativeError(getErrorID(), description, url());
1182            clearNativeLoader();
1183        }
1184    }
1185
1186    /**
1187     * Cancel a request.
1188     * FIXME: This will only work if the request has yet to be handled. This
1189     * is in no way guarenteed if requests are served in a separate thread.
1190     * It also causes major problems if cancel is called during an
1191     * EventHandler's method call.
1192     */
1193    public void cancel() {
1194        if (DebugFlags.LOAD_LISTENER) {
1195            if (mRequestHandle == null) {
1196                Log.v(LOGTAG, "LoadListener.cancel(): no requestHandle");
1197            } else {
1198                Log.v(LOGTAG, "LoadListener.cancel()");
1199            }
1200        }
1201        if (mRequestHandle != null) {
1202            mRequestHandle.cancel();
1203            mRequestHandle = null;
1204        }
1205
1206        WebViewWorker.getHandler().obtainMessage(
1207                WebViewWorker.MSG_REMOVE_CACHE, this).sendToTarget();
1208        mCancelled = true;
1209
1210        clearNativeLoader();
1211    }
1212
1213    // This count is transferred from RequestHandle to LoadListener when
1214    // loading from the cache so that we can detect redirect loops that switch
1215    // between the network and the cache.
1216    private int mCacheRedirectCount;
1217
1218    /*
1219     * Perform the actual redirection. This involves setting up the new URL,
1220     * informing WebCore and then telling the Network to start loading again.
1221     */
1222    private void doRedirect() {
1223        // as cancel() can cancel the load before doRedirect() is
1224        // called through handleMessage, needs to check to see if we
1225        // are canceled before proceed
1226        if (mCancelled) {
1227            return;
1228        }
1229
1230        // Do the same check for a redirect loop that
1231        // RequestHandle.setupRedirect does.
1232        if (mCacheRedirectCount >= RequestHandle.MAX_REDIRECT_COUNT) {
1233            handleError(EventHandler.ERROR_REDIRECT_LOOP, mContext.getString(
1234                    R.string.httpErrorRedirectLoop));
1235            return;
1236        }
1237
1238        String redirectTo = mHeaders.getLocation();
1239        if (redirectTo != null) {
1240            int nativeResponse = createNativeResponse();
1241            redirectTo =
1242                    nativeRedirectedToUrl(mUrl, redirectTo, nativeResponse);
1243            // nativeRedirectedToUrl() may call cancel(), e.g. when redirect
1244            // from a https site to a http site, check mCancelled again
1245            if (mCancelled) {
1246                return;
1247            }
1248            if (redirectTo == null) {
1249                Log.d(LOGTAG, "Redirection failed for "
1250                        + mHeaders.getLocation());
1251                cancel();
1252                return;
1253            } else if (!URLUtil.isNetworkUrl(redirectTo)) {
1254                final String text = mContext
1255                        .getString(R.string.open_permission_deny)
1256                        + "\n" + redirectTo;
1257                nativeAddData(text.getBytes(), text.length());
1258                nativeFinished();
1259                clearNativeLoader();
1260                return;
1261            }
1262
1263
1264            // Cache the redirect response
1265            if (getErrorID() == OK) {
1266                WebViewWorker.CacheSaveData data = new WebViewWorker.CacheSaveData();
1267                data.mListener = this;
1268                data.mUrl = mUrl;
1269                data.mPostId = mPostIdentifier;
1270                WebViewWorker.getHandler().obtainMessage(
1271                        WebViewWorker.MSG_SAVE_CACHE, data).sendToTarget();
1272            } else {
1273                WebViewWorker.getHandler().obtainMessage(
1274                        WebViewWorker.MSG_REMOVE_CACHE, this).sendToTarget();
1275            }
1276
1277            // Saving a copy of the unstripped url for the response
1278            mOriginalUrl = redirectTo;
1279            // This will strip the anchor
1280            setUrl(redirectTo);
1281
1282            // Redirect may be in the cache
1283            if (mRequestHeaders == null) {
1284                mRequestHeaders = new HashMap<String, String>();
1285            }
1286            boolean fromCache = false;
1287            if (mCacheLoader != null) {
1288                // This is a redirect from the cache loader. Increment the
1289                // redirect count to avoid redirect loops.
1290                mCacheRedirectCount++;
1291                fromCache = true;
1292            }
1293            if (!checkCache(mRequestHeaders)) {
1294                // mRequestHandle can be null when the request was satisfied
1295                // by the cache, and the cache returned a redirect
1296                if (mRequestHandle != null) {
1297                    try {
1298                        mRequestHandle.setupRedirect(mUrl, mStatusCode,
1299                                mRequestHeaders);
1300                    } catch(RuntimeException e) {
1301                        Log.e(LOGTAG, e.getMessage());
1302                        // Signal a bad url error if we could not load the
1303                        // redirection.
1304                        handleError(EventHandler.ERROR_BAD_URL,
1305                                mContext.getString(R.string.httpErrorBadUrl));
1306                        return;
1307                    }
1308                } else {
1309                    // If the original request came from the cache, there is no
1310                    // RequestHandle, we have to create a new one through
1311                    // Network.requestURL.
1312                    Network network = Network.getInstance(getContext());
1313                    if (!network.requestURL(mMethod, mRequestHeaders,
1314                            mPostData, this)) {
1315                        // Signal a bad url error if we could not load the
1316                        // redirection.
1317                        handleError(EventHandler.ERROR_BAD_URL,
1318                                mContext.getString(R.string.httpErrorBadUrl));
1319                        return;
1320                    }
1321                }
1322                if (fromCache) {
1323                    // If we are coming from a cache load, we need to transfer
1324                    // the redirect count to the new (or old) RequestHandle to
1325                    // keep the redirect count in sync.
1326                    mRequestHandle.setRedirectCount(mCacheRedirectCount);
1327                }
1328            } else if (!fromCache) {
1329                // Switching from network to cache means we need to grab the
1330                // redirect count from the RequestHandle to keep the count in
1331                // sync. Add 1 to account for the current redirect.
1332                mCacheRedirectCount = mRequestHandle.getRedirectCount() + 1;
1333            }
1334        } else {
1335            commitHeaders();
1336            commitLoad();
1337            tearDown();
1338        }
1339
1340        if (DebugFlags.LOAD_LISTENER) {
1341            Log.v(LOGTAG, "LoadListener.onRedirect(): redirect to: " +
1342                    redirectTo);
1343        }
1344    }
1345
1346    /**
1347     * Parses the content-type header.
1348     * The first part only allows '-' if it follows x or X.
1349     */
1350    private static final Pattern CONTENT_TYPE_PATTERN =
1351            Pattern.compile("^((?:[xX]-)?[a-zA-Z\\*]+/[\\w\\+\\*-]+[\\.[\\w\\+-]+]*)$");
1352
1353    /* package */ void parseContentTypeHeader(String contentType) {
1354        if (DebugFlags.LOAD_LISTENER) {
1355            Log.v(LOGTAG, "LoadListener.parseContentTypeHeader: " +
1356                    "contentType: " + contentType);
1357        }
1358
1359        if (contentType != null) {
1360            int i = contentType.indexOf(';');
1361            if (i >= 0) {
1362                mMimeType = contentType.substring(0, i);
1363
1364                int j = contentType.indexOf('=', i);
1365                if (j > 0) {
1366                    i = contentType.indexOf(';', j);
1367                    if (i < j) {
1368                        i = contentType.length();
1369                    }
1370                    mEncoding = contentType.substring(j + 1, i);
1371                } else {
1372                    mEncoding = contentType.substring(i + 1);
1373                }
1374                // Trim excess whitespace.
1375                mEncoding = mEncoding.trim().toLowerCase();
1376
1377                if (i < contentType.length() - 1) {
1378                    // for data: uri the mimeType and encoding have
1379                    // the form image/jpeg;base64 or text/plain;charset=utf-8
1380                    // or text/html;charset=utf-8;base64
1381                    mTransferEncoding =
1382                            contentType.substring(i + 1).trim().toLowerCase();
1383                }
1384            } else {
1385                mMimeType = contentType;
1386            }
1387
1388            // Trim leading and trailing whitespace
1389            mMimeType = mMimeType.trim();
1390
1391            try {
1392                Matcher m = CONTENT_TYPE_PATTERN.matcher(mMimeType);
1393                if (m.find()) {
1394                    mMimeType = m.group(1);
1395                } else {
1396                    guessMimeType();
1397                }
1398            } catch (IllegalStateException ex) {
1399                guessMimeType();
1400            }
1401        }
1402        // Ensure mMimeType is lower case.
1403        mMimeType = mMimeType.toLowerCase();
1404    }
1405
1406    /**
1407     * @return The HTTP-authentication object or null if there
1408     * is no supported scheme in the header.
1409     * If there are several valid schemes present, we pick the
1410     * strongest one. If there are several schemes of the same
1411     * strength, we pick the one that comes first.
1412     */
1413    private HttpAuthHeader parseAuthHeader(String header) {
1414        if (header != null) {
1415            int posMax = 256;
1416            int posLen = 0;
1417            int[] pos = new int [posMax];
1418
1419            int headerLen = header.length();
1420            if (headerLen > 0) {
1421                // first, we find all unquoted instances of 'Basic' and 'Digest'
1422                boolean quoted = false;
1423                for (int i = 0; i < headerLen && posLen < posMax; ++i) {
1424                    if (header.charAt(i) == '\"') {
1425                        quoted = !quoted;
1426                    } else {
1427                        if (!quoted) {
1428                            if (header.regionMatches(true, i,
1429                                    HttpAuthHeader.BASIC_TOKEN, 0,
1430                                    HttpAuthHeader.BASIC_TOKEN.length())) {
1431                                pos[posLen++] = i;
1432                                continue;
1433                            }
1434
1435                            if (header.regionMatches(true, i,
1436                                    HttpAuthHeader.DIGEST_TOKEN, 0,
1437                                    HttpAuthHeader.DIGEST_TOKEN.length())) {
1438                                pos[posLen++] = i;
1439                                continue;
1440                            }
1441                        }
1442                    }
1443                }
1444            }
1445
1446            if (posLen > 0) {
1447                // consider all digest schemes first (if any)
1448                for (int i = 0; i < posLen; i++) {
1449                    if (header.regionMatches(true, pos[i],
1450                                HttpAuthHeader.DIGEST_TOKEN, 0,
1451                                HttpAuthHeader.DIGEST_TOKEN.length())) {
1452                        String sub = header.substring(pos[i],
1453                                (i + 1 < posLen ? pos[i + 1] : headerLen));
1454
1455                        HttpAuthHeader rval = new HttpAuthHeader(sub);
1456                        if (rval.isSupportedScheme()) {
1457                            // take the first match
1458                            return rval;
1459                        }
1460                    }
1461                }
1462
1463                // ...then consider all basic schemes (if any)
1464                for (int i = 0; i < posLen; i++) {
1465                    if (header.regionMatches(true, pos[i],
1466                                HttpAuthHeader.BASIC_TOKEN, 0,
1467                                HttpAuthHeader.BASIC_TOKEN.length())) {
1468                        String sub = header.substring(pos[i],
1469                                (i + 1 < posLen ? pos[i + 1] : headerLen));
1470
1471                        HttpAuthHeader rval = new HttpAuthHeader(sub);
1472                        if (rval.isSupportedScheme()) {
1473                            // take the first match
1474                            return rval;
1475                        }
1476                    }
1477                }
1478            }
1479        }
1480
1481        return null;
1482    }
1483
1484    /**
1485     * If the content is a redirect or not modified we should not send
1486     * any data into WebCore as that will cause it create a document with
1487     * the data, then when we try to provide the real content, it will assert.
1488     *
1489     * @return True iff the callback should be ignored.
1490     */
1491    private boolean ignoreCallbacks() {
1492        return (mCancelled || mAuthHeader != null ||
1493                // Allow 305 (Use Proxy) to call through.
1494                (mStatusCode > 300 && mStatusCode < 400 && mStatusCode != 305));
1495    }
1496
1497    /**
1498     * Sets the current URL associated with this load.
1499     */
1500    void setUrl(String url) {
1501        if (url != null) {
1502            mUri = null;
1503            if (URLUtil.isNetworkUrl(url)) {
1504                mUrl = URLUtil.stripAnchor(url);
1505                try {
1506                    mUri = new WebAddress(mUrl);
1507                } catch (ParseException e) {
1508                    e.printStackTrace();
1509                }
1510            } else {
1511                mUrl = url;
1512            }
1513        }
1514    }
1515
1516    /**
1517     * Guesses MIME type if one was not specified. Defaults to 'text/html'. In
1518     * addition, tries to guess the MIME type based on the extension.
1519     *
1520     */
1521    private void guessMimeType() {
1522        // Data urls must have a valid mime type or a blank string for the mime
1523        // type (implying text/plain).
1524        if (URLUtil.isDataUrl(mUrl) && mMimeType.length() != 0) {
1525            cancel();
1526            final String text = mContext.getString(R.string.httpErrorBadUrl);
1527            handleError(EventHandler.ERROR_BAD_URL, text);
1528        } else {
1529            // Note: This is ok because this is used only for the main content
1530            // of frames. If no content-type was specified, it is fine to
1531            // default to text/html.
1532            mMimeType = "text/html";
1533            String newMimeType = guessMimeTypeFromExtension(mUrl);
1534            if (newMimeType != null) {
1535                mMimeType = newMimeType;
1536            }
1537        }
1538    }
1539
1540    /**
1541     * guess MIME type based on the file extension.
1542     */
1543    private String guessMimeTypeFromExtension(String url) {
1544        // PENDING: need to normalize url
1545        if (DebugFlags.LOAD_LISTENER) {
1546            Log.v(LOGTAG, "guessMimeTypeFromExtension: url = " + url);
1547        }
1548
1549        return MimeTypeMap.getSingleton().getMimeTypeFromExtension(
1550                MimeTypeMap.getFileExtensionFromUrl(url));
1551    }
1552
1553    /**
1554     * Either send a message to ourselves or queue the message if this is a
1555     * synchronous load.
1556     */
1557    private void sendMessageInternal(Message msg) {
1558        if (mSynchronous) {
1559            mMessageQueue.add(msg);
1560        } else {
1561            sendMessage(msg);
1562        }
1563    }
1564
1565    /**
1566     * Cycle through our messages for synchronous loads.
1567     */
1568    /* package */ void loadSynchronousMessages() {
1569        if (DebugFlags.LOAD_LISTENER && !mSynchronous) {
1570            throw new AssertionError();
1571        }
1572        // Note: this can be called twice if it is a synchronous network load,
1573        // and there is a cache, but it needs to go to network to validate. If
1574        // validation succeed, the CacheLoader is used so this is first called
1575        // from http thread. Then it is called again from WebViewCore thread
1576        // after the load is completed. So make sure the queue is cleared but
1577        // don't set it to null.
1578        for (int size = mMessageQueue.size(); size > 0; size--) {
1579            handleMessage(mMessageQueue.remove(0));
1580        }
1581    }
1582
1583    //=========================================================================
1584    // native functions
1585    //=========================================================================
1586
1587    /**
1588     * Create a new native response object.
1589     * @param url The url of the resource.
1590     * @param statusCode The HTTP status code.
1591     * @param statusText The HTTP status text.
1592     * @param mimeType HTTP content-type.
1593     * @param expectedLength An estimate of the content length or the length
1594     *                       given by the server.
1595     * @param encoding HTTP encoding.
1596     * @return The native response pointer.
1597     */
1598    private native int nativeCreateResponse(String url, int statusCode,
1599            String statusText, String mimeType, long expectedLength,
1600            String encoding);
1601
1602    /**
1603     * Add a response header to the native object.
1604     * @param nativeResponse The native pointer.
1605     * @param key String key.
1606     * @param val String value.
1607     */
1608    private native void nativeSetResponseHeader(int nativeResponse, String key,
1609            String val);
1610
1611    /**
1612     * Dispatch the response.
1613     * @param nativeResponse The native pointer.
1614     */
1615    private native void nativeReceivedResponse(int nativeResponse);
1616
1617    /**
1618     * Add data to the loader.
1619     * @param data Byte array of data.
1620     * @param length Number of objects in data.
1621     */
1622    private native void nativeAddData(byte[] data, int length);
1623
1624    /**
1625     * Tell the loader it has finished.
1626     */
1627    private native void nativeFinished();
1628
1629    /**
1630     * tell the loader to redirect
1631     * @param baseUrl The base url.
1632     * @param redirectTo The url to redirect to.
1633     * @param nativeResponse The native pointer.
1634     * @return The new url that the resource redirected to.
1635     */
1636    private native String nativeRedirectedToUrl(String baseUrl,
1637            String redirectTo, int nativeResponse);
1638
1639    /**
1640     * Tell the loader there is error
1641     * @param id
1642     * @param desc
1643     * @param failingUrl The url that failed.
1644     */
1645    private native void nativeError(int id, String desc, String failingUrl);
1646
1647}
1648