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