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