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