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