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