LoadListener.java revision ce4105002310a22e104d29d56913cbca9cd485be
1/* 2 * Copyright (C) 2006 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.webkit; 18 19import android.content.Context; 20import android.net.WebAddress; 21import android.net.ParseException; 22import android.net.http.EventHandler; 23import android.net.http.Headers; 24import android.net.http.HttpAuthHeader; 25import android.net.http.RequestHandle; 26import android.net.http.SslCertificate; 27import android.net.http.SslError; 28 29import android.os.Handler; 30import android.os.Message; 31import android.security.CertTool; 32import android.util.Log; 33import android.webkit.CacheManager.CacheResult; 34 35import com.android.internal.R; 36 37import java.io.IOException; 38import java.util.ArrayList; 39import java.util.HashMap; 40import java.util.HashSet; 41import java.util.Map; 42import java.util.Vector; 43import java.util.regex.Pattern; 44import java.util.regex.Matcher; 45 46class LoadListener extends Handler implements EventHandler { 47 48 private static final String LOGTAG = "webkit"; 49 50 // Messages used internally to communicate state between the 51 // Network thread and the WebCore thread. 52 private static final int MSG_CONTENT_HEADERS = 100; 53 private static final int MSG_CONTENT_DATA = 110; 54 private static final int MSG_CONTENT_FINISHED = 120; 55 private static final int MSG_CONTENT_ERROR = 130; 56 private static final int MSG_LOCATION_CHANGED = 140; 57 private static final int MSG_LOCATION_CHANGED_REQUEST = 150; 58 private static final int MSG_STATUS = 160; 59 private static final int MSG_SSL_CERTIFICATE = 170; 60 private static final int MSG_SSL_ERROR = 180; 61 62 // Standard HTTP status codes in a more representative format 63 private static final int HTTP_OK = 200; 64 private static final int HTTP_MOVED_PERMANENTLY = 301; 65 private static final int HTTP_FOUND = 302; 66 private static final int HTTP_SEE_OTHER = 303; 67 private static final int HTTP_NOT_MODIFIED = 304; 68 private static final int HTTP_TEMPORARY_REDIRECT = 307; 69 private static final int HTTP_AUTH = 401; 70 private static final int HTTP_NOT_FOUND = 404; 71 private static final int HTTP_PROXY_AUTH = 407; 72 73 private static HashSet<String> sCertificateMimeTypeMap; 74 static { 75 sCertificateMimeTypeMap = new HashSet<String>(); 76 sCertificateMimeTypeMap.add("application/x-x509-ca-cert"); 77 sCertificateMimeTypeMap.add("application/x-x509-user-cert"); 78 sCertificateMimeTypeMap.add("application/x-pkcs12"); 79 } 80 81 private static int sNativeLoaderCount; 82 83 private final ByteArrayBuilder mDataBuilder = new ByteArrayBuilder(8192); 84 85 private String mUrl; 86 private WebAddress mUri; 87 private boolean mPermanent; 88 private String mOriginalUrl; 89 private Context mContext; 90 private BrowserFrame mBrowserFrame; 91 private int mNativeLoader; 92 private String mMimeType; 93 private String mEncoding; 94 private String mTransferEncoding; 95 private int mStatusCode; 96 private String mStatusText; 97 public long mContentLength; // Content length of the incoming data 98 private boolean mCancelled; // The request has been cancelled. 99 private boolean mAuthFailed; // indicates that the prev. auth failed 100 private CacheLoader mCacheLoader; 101 private CacheManager.CacheResult mCacheResult; 102 private HttpAuthHeader mAuthHeader; 103 private int mErrorID = OK; 104 private String mErrorDescription; 105 private SslError mSslError; 106 private RequestHandle mRequestHandle; 107 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 mBrowserFrame.stopLoading(); 1001 return; 1002 } 1003 1004 // Give the data to WebKit now 1005 PerfChecker checker = new PerfChecker(); 1006 ByteArrayBuilder.Chunk c; 1007 while (true) { 1008 c = mDataBuilder.getFirstChunk(); 1009 if (c == null) break; 1010 1011 if (c.mLength != 0) { 1012 if (mCacheResult != null) { 1013 try { 1014 mCacheResult.outStream.write(c.mArray, 0, c.mLength); 1015 } catch (IOException e) { 1016 mCacheResult = null; 1017 } 1018 } 1019 nativeAddData(c.mArray, c.mLength); 1020 } 1021 mDataBuilder.releaseChunk(c); 1022 checker.responseAlert("res nativeAddData"); 1023 } 1024 } 1025 1026 /** 1027 * Tear down the load. Subclasses should clean up any mess because of 1028 * cancellation or errors during the load. 1029 */ 1030 void tearDown() { 1031 if (mCacheResult != null) { 1032 if (getErrorID() == OK) { 1033 CacheManager.saveCacheFile(mUrl, mCacheResult); 1034 } 1035 1036 // we need to reset mCacheResult to be null 1037 // resource loader's tearDown will call into WebCore's 1038 // nativeFinish, which in turn calls loader.cancel(). 1039 // If we don't reset mCacheFile, the file will be deleted. 1040 mCacheResult = null; 1041 } 1042 if (mNativeLoader != 0) { 1043 PerfChecker checker = new PerfChecker(); 1044 nativeFinished(); 1045 checker.responseAlert("res nativeFinished"); 1046 clearNativeLoader(); 1047 } 1048 } 1049 1050 /** 1051 * Helper for getting the error ID. 1052 * @return errorID. 1053 */ 1054 private int getErrorID() { 1055 return mErrorID; 1056 } 1057 1058 /** 1059 * Return the error description. 1060 * @return errorDescription. 1061 */ 1062 private String getErrorDescription() { 1063 return mErrorDescription; 1064 } 1065 1066 /** 1067 * Notify the loader we encountered an error. 1068 */ 1069 void notifyError() { 1070 if (mNativeLoader != 0) { 1071 String description = getErrorDescription(); 1072 if (description == null) description = ""; 1073 nativeError(getErrorID(), description, url()); 1074 clearNativeLoader(); 1075 } 1076 } 1077 1078 /** 1079 * Cancel a request. 1080 * FIXME: This will only work if the request has yet to be handled. This 1081 * is in no way guarenteed if requests are served in a separate thread. 1082 * It also causes major problems if cancel is called during an 1083 * EventHandler's method call. 1084 */ 1085 public void cancel() { 1086 if (DebugFlags.LOAD_LISTENER) { 1087 if (mRequestHandle == null) { 1088 Log.v(LOGTAG, "LoadListener.cancel(): no requestHandle"); 1089 } else { 1090 Log.v(LOGTAG, "LoadListener.cancel()"); 1091 } 1092 } 1093 if (mRequestHandle != null) { 1094 mRequestHandle.cancel(); 1095 mRequestHandle = null; 1096 } 1097 1098 mCacheResult = null; 1099 mCancelled = true; 1100 1101 clearNativeLoader(); 1102 } 1103 1104 // This count is transferred from RequestHandle to LoadListener when 1105 // loading from the cache so that we can detect redirect loops that switch 1106 // between the network and the cache. 1107 private int mCacheRedirectCount; 1108 1109 /* 1110 * Perform the actual redirection. This involves setting up the new URL, 1111 * informing WebCore and then telling the Network to start loading again. 1112 */ 1113 private void doRedirect() { 1114 // as cancel() can cancel the load before doRedirect() is 1115 // called through handleMessage, needs to check to see if we 1116 // are canceled before proceed 1117 if (mCancelled) { 1118 return; 1119 } 1120 1121 // Do the same check for a redirect loop that 1122 // RequestHandle.setupRedirect does. 1123 if (mCacheRedirectCount >= RequestHandle.MAX_REDIRECT_COUNT) { 1124 handleError(EventHandler.ERROR_REDIRECT_LOOP, mContext.getString( 1125 R.string.httpErrorRedirectLoop)); 1126 return; 1127 } 1128 1129 String redirectTo = mHeaders.getLocation(); 1130 if (redirectTo != null) { 1131 int nativeResponse = createNativeResponse(); 1132 redirectTo = 1133 nativeRedirectedToUrl(mUrl, redirectTo, nativeResponse); 1134 // nativeRedirectedToUrl() may call cancel(), e.g. when redirect 1135 // from a https site to a http site, check mCancelled again 1136 if (mCancelled) { 1137 return; 1138 } 1139 if (redirectTo == null) { 1140 Log.d(LOGTAG, "Redirection failed for " 1141 + mHeaders.getLocation()); 1142 cancel(); 1143 return; 1144 } else if (!URLUtil.isNetworkUrl(redirectTo)) { 1145 final String text = mContext 1146 .getString(R.string.open_permission_deny) 1147 + "\n" + redirectTo; 1148 nativeAddData(text.getBytes(), text.length()); 1149 nativeFinished(); 1150 clearNativeLoader(); 1151 return; 1152 } 1153 1154 if (mOriginalUrl == null) { 1155 mOriginalUrl = mUrl; 1156 } 1157 1158 // Cache the redirect response 1159 if (mCacheResult != null) { 1160 if (getErrorID() == OK) { 1161 CacheManager.saveCacheFile(mUrl, mCacheResult); 1162 } 1163 mCacheResult = null; 1164 } 1165 1166 // This will strip the anchor 1167 setUrl(redirectTo); 1168 1169 // Redirect may be in the cache 1170 if (mRequestHeaders == null) { 1171 mRequestHeaders = new HashMap<String, String>(); 1172 } 1173 boolean fromCache = false; 1174 if (mCacheLoader != null) { 1175 // This is a redirect from the cache loader. Increment the 1176 // redirect count to avoid redirect loops. 1177 mCacheRedirectCount++; 1178 fromCache = true; 1179 } 1180 if (!checkCache(mRequestHeaders)) { 1181 // mRequestHandle can be null when the request was satisfied 1182 // by the cache, and the cache returned a redirect 1183 if (mRequestHandle != null) { 1184 mRequestHandle.setupRedirect(mUrl, mStatusCode, 1185 mRequestHeaders); 1186 } else { 1187 // If the original request came from the cache, there is no 1188 // RequestHandle, we have to create a new one through 1189 // Network.requestURL. 1190 Network network = Network.getInstance(getContext()); 1191 if (!network.requestURL(mMethod, mRequestHeaders, 1192 mPostData, this)) { 1193 // Signal a bad url error if we could not load the 1194 // redirection. 1195 handleError(EventHandler.ERROR_BAD_URL, 1196 mContext.getString(R.string.httpErrorBadUrl)); 1197 return; 1198 } 1199 } 1200 if (fromCache) { 1201 // If we are coming from a cache load, we need to transfer 1202 // the redirect count to the new (or old) RequestHandle to 1203 // keep the redirect count in sync. 1204 mRequestHandle.setRedirectCount(mCacheRedirectCount); 1205 } 1206 } else if (!fromCache) { 1207 // Switching from network to cache means we need to grab the 1208 // redirect count from the RequestHandle to keep the count in 1209 // sync. Add 1 to account for the current redirect. 1210 mCacheRedirectCount = mRequestHandle.getRedirectCount() + 1; 1211 } 1212 } else { 1213 commitHeaders(); 1214 commitLoad(); 1215 tearDown(); 1216 } 1217 1218 if (DebugFlags.LOAD_LISTENER) { 1219 Log.v(LOGTAG, "LoadListener.onRedirect(): redirect to: " + 1220 redirectTo); 1221 } 1222 } 1223 1224 /** 1225 * Parses the content-type header. 1226 * The first part only allows '-' if it follows x or X. 1227 */ 1228 private static final Pattern CONTENT_TYPE_PATTERN = 1229 Pattern.compile("^((?:[xX]-)?[a-zA-Z\\*]+/[\\w\\+\\*-]+[\\.[\\w\\+-]+]*)$"); 1230 1231 /* package */ void parseContentTypeHeader(String contentType) { 1232 if (DebugFlags.LOAD_LISTENER) { 1233 Log.v(LOGTAG, "LoadListener.parseContentTypeHeader: " + 1234 "contentType: " + contentType); 1235 } 1236 1237 if (contentType != null) { 1238 int i = contentType.indexOf(';'); 1239 if (i >= 0) { 1240 mMimeType = contentType.substring(0, i); 1241 1242 int j = contentType.indexOf('=', i); 1243 if (j > 0) { 1244 i = contentType.indexOf(';', j); 1245 if (i < j) { 1246 i = contentType.length(); 1247 } 1248 mEncoding = contentType.substring(j + 1, i); 1249 } else { 1250 mEncoding = contentType.substring(i + 1); 1251 } 1252 // Trim excess whitespace. 1253 mEncoding = mEncoding.trim().toLowerCase(); 1254 1255 if (i < contentType.length() - 1) { 1256 // for data: uri the mimeType and encoding have 1257 // the form image/jpeg;base64 or text/plain;charset=utf-8 1258 // or text/html;charset=utf-8;base64 1259 mTransferEncoding = 1260 contentType.substring(i + 1).trim().toLowerCase(); 1261 } 1262 } else { 1263 mMimeType = contentType; 1264 } 1265 1266 // Trim leading and trailing whitespace 1267 mMimeType = mMimeType.trim(); 1268 1269 try { 1270 Matcher m = CONTENT_TYPE_PATTERN.matcher(mMimeType); 1271 if (m.find()) { 1272 mMimeType = m.group(1); 1273 } else { 1274 guessMimeType(); 1275 } 1276 } catch (IllegalStateException ex) { 1277 guessMimeType(); 1278 } 1279 } 1280 // Ensure mMimeType is lower case. 1281 mMimeType = mMimeType.toLowerCase(); 1282 } 1283 1284 /** 1285 * @return The HTTP-authentication object or null if there 1286 * is no supported scheme in the header. 1287 * If there are several valid schemes present, we pick the 1288 * strongest one. If there are several schemes of the same 1289 * strength, we pick the one that comes first. 1290 */ 1291 private HttpAuthHeader parseAuthHeader(String header) { 1292 if (header != null) { 1293 int posMax = 256; 1294 int posLen = 0; 1295 int[] pos = new int [posMax]; 1296 1297 int headerLen = header.length(); 1298 if (headerLen > 0) { 1299 // first, we find all unquoted instances of 'Basic' and 'Digest' 1300 boolean quoted = false; 1301 for (int i = 0; i < headerLen && posLen < posMax; ++i) { 1302 if (header.charAt(i) == '\"') { 1303 quoted = !quoted; 1304 } else { 1305 if (!quoted) { 1306 if (header.regionMatches(true, i, 1307 HttpAuthHeader.BASIC_TOKEN, 0, 1308 HttpAuthHeader.BASIC_TOKEN.length())) { 1309 pos[posLen++] = i; 1310 continue; 1311 } 1312 1313 if (header.regionMatches(true, i, 1314 HttpAuthHeader.DIGEST_TOKEN, 0, 1315 HttpAuthHeader.DIGEST_TOKEN.length())) { 1316 pos[posLen++] = i; 1317 continue; 1318 } 1319 } 1320 } 1321 } 1322 } 1323 1324 if (posLen > 0) { 1325 // consider all digest schemes first (if any) 1326 for (int i = 0; i < posLen; i++) { 1327 if (header.regionMatches(true, pos[i], 1328 HttpAuthHeader.DIGEST_TOKEN, 0, 1329 HttpAuthHeader.DIGEST_TOKEN.length())) { 1330 String sub = header.substring(pos[i], 1331 (i + 1 < posLen ? pos[i + 1] : headerLen)); 1332 1333 HttpAuthHeader rval = new HttpAuthHeader(sub); 1334 if (rval.isSupportedScheme()) { 1335 // take the first match 1336 return rval; 1337 } 1338 } 1339 } 1340 1341 // ...then consider all basic schemes (if any) 1342 for (int i = 0; i < posLen; i++) { 1343 if (header.regionMatches(true, pos[i], 1344 HttpAuthHeader.BASIC_TOKEN, 0, 1345 HttpAuthHeader.BASIC_TOKEN.length())) { 1346 String sub = header.substring(pos[i], 1347 (i + 1 < posLen ? pos[i + 1] : headerLen)); 1348 1349 HttpAuthHeader rval = new HttpAuthHeader(sub); 1350 if (rval.isSupportedScheme()) { 1351 // take the first match 1352 return rval; 1353 } 1354 } 1355 } 1356 } 1357 } 1358 1359 return null; 1360 } 1361 1362 /** 1363 * If the content is a redirect or not modified we should not send 1364 * any data into WebCore as that will cause it create a document with 1365 * the data, then when we try to provide the real content, it will assert. 1366 * 1367 * @return True iff the callback should be ignored. 1368 */ 1369 private boolean ignoreCallbacks() { 1370 return (mCancelled || mAuthHeader != null || 1371 // Allow 305 (Use Proxy) to call through. 1372 (mStatusCode > 300 && mStatusCode < 400 && mStatusCode != 305)); 1373 } 1374 1375 /** 1376 * Sets the current URL associated with this load. 1377 */ 1378 void setUrl(String url) { 1379 if (url != null) { 1380 mUri = null; 1381 if (URLUtil.isNetworkUrl(url)) { 1382 mUrl = URLUtil.stripAnchor(url); 1383 try { 1384 mUri = new WebAddress(mUrl); 1385 } catch (ParseException e) { 1386 e.printStackTrace(); 1387 } 1388 } else { 1389 mUrl = url; 1390 } 1391 } 1392 } 1393 1394 /** 1395 * Guesses MIME type if one was not specified. Defaults to 'text/html'. In 1396 * addition, tries to guess the MIME type based on the extension. 1397 * 1398 */ 1399 private void guessMimeType() { 1400 // Data urls must have a valid mime type or a blank string for the mime 1401 // type (implying text/plain). 1402 if (URLUtil.isDataUrl(mUrl) && mMimeType.length() != 0) { 1403 cancel(); 1404 final String text = mContext.getString(R.string.httpErrorBadUrl); 1405 handleError(EventHandler.ERROR_BAD_URL, text); 1406 } else { 1407 // Note: This is ok because this is used only for the main content 1408 // of frames. If no content-type was specified, it is fine to 1409 // default to text/html. 1410 mMimeType = "text/html"; 1411 String newMimeType = guessMimeTypeFromExtension(mUrl); 1412 if (newMimeType != null) { 1413 mMimeType = newMimeType; 1414 } 1415 } 1416 } 1417 1418 /** 1419 * guess MIME type based on the file extension. 1420 */ 1421 private String guessMimeTypeFromExtension(String url) { 1422 // PENDING: need to normalize url 1423 if (DebugFlags.LOAD_LISTENER) { 1424 Log.v(LOGTAG, "guessMimeTypeFromExtension: url = " + url); 1425 } 1426 1427 return MimeTypeMap.getSingleton().getMimeTypeFromExtension( 1428 MimeTypeMap.getFileExtensionFromUrl(url)); 1429 } 1430 1431 /** 1432 * Either send a message to ourselves or queue the message if this is a 1433 * synchronous load. 1434 */ 1435 private void sendMessageInternal(Message msg) { 1436 if (mSynchronous) { 1437 mMessageQueue.add(msg); 1438 } else { 1439 sendMessage(msg); 1440 } 1441 } 1442 1443 /** 1444 * Cycle through our messages for synchronous loads. 1445 */ 1446 /* package */ void loadSynchronousMessages() { 1447 if (DebugFlags.LOAD_LISTENER && !mSynchronous) { 1448 throw new AssertionError(); 1449 } 1450 // Note: this can be called twice if it is a synchronous network load, 1451 // and there is a cache, but it needs to go to network to validate. If 1452 // validation succeed, the CacheLoader is used so this is first called 1453 // from http thread. Then it is called again from WebViewCore thread 1454 // after the load is completed. So make sure the queue is cleared but 1455 // don't set it to null. 1456 for (int size = mMessageQueue.size(); size > 0; size--) { 1457 handleMessage(mMessageQueue.remove(0)); 1458 } 1459 } 1460 1461 //========================================================================= 1462 // native functions 1463 //========================================================================= 1464 1465 /** 1466 * Create a new native response object. 1467 * @param url The url of the resource. 1468 * @param statusCode The HTTP status code. 1469 * @param statusText The HTTP status text. 1470 * @param mimeType HTTP content-type. 1471 * @param expectedLength An estimate of the content length or the length 1472 * given by the server. 1473 * @param encoding HTTP encoding. 1474 * @return The native response pointer. 1475 */ 1476 private native int nativeCreateResponse(String url, int statusCode, 1477 String statusText, String mimeType, long expectedLength, 1478 String encoding); 1479 1480 /** 1481 * Add a response header to the native object. 1482 * @param nativeResponse The native pointer. 1483 * @param key String key. 1484 * @param val String value. 1485 */ 1486 private native void nativeSetResponseHeader(int nativeResponse, String key, 1487 String val); 1488 1489 /** 1490 * Dispatch the response. 1491 * @param nativeResponse The native pointer. 1492 */ 1493 private native void nativeReceivedResponse(int nativeResponse); 1494 1495 /** 1496 * Add data to the loader. 1497 * @param data Byte array of data. 1498 * @param length Number of objects in data. 1499 */ 1500 private native void nativeAddData(byte[] data, int length); 1501 1502 /** 1503 * Tell the loader it has finished. 1504 */ 1505 private native void nativeFinished(); 1506 1507 /** 1508 * tell the loader to redirect 1509 * @param baseUrl The base url. 1510 * @param redirectTo The url to redirect to. 1511 * @param nativeResponse The native pointer. 1512 * @return The new url that the resource redirected to. 1513 */ 1514 private native String nativeRedirectedToUrl(String baseUrl, 1515 String redirectTo, int nativeResponse); 1516 1517 /** 1518 * Tell the loader there is error 1519 * @param id 1520 * @param desc 1521 * @param failingUrl The url that failed. 1522 */ 1523 private native void nativeError(int id, String desc, String failingUrl); 1524 1525} 1526