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