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