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