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