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