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