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