LoadListener.java revision 5ac85ccabbaa02623152f140b9233c6cefcf3b77
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. If we received a not-modified response from 1018 // the server and mCacheLoader is not null, do not send the response to 1019 // webkit. This is just a validation response for loading from the 1020 // cache. 1021 if ((mStatusCode >= 301 && mStatusCode <= 303) || mStatusCode == 307 || 1022 (mStatusCode == 304 && mCacheLoader != null)) { 1023 return; 1024 } 1025 1026 commitHeaders(); 1027 } 1028 1029 // This commits the headers without checking the response status code. 1030 private void commitHeaders() { 1031 if (mIsMainPageLoader && sCertificateTypeMap.containsKey(mMimeType)) { 1032 // In the case of downloading certificate, we will save it to the 1033 // KeyStore in commitLoad. Do not call webcore. 1034 return; 1035 } 1036 1037 // If the response is an authentication and we've resent the 1038 // request with some credentials then don't commit the headers 1039 // of this response; wait for the response to the request with the 1040 // credentials. 1041 if (mAuthHeader != null) 1042 return; 1043 1044 // Commit the headers to WebCore 1045 int nativeResponse = createNativeResponse(); 1046 // The native code deletes the native response object. 1047 nativeReceivedResponse(nativeResponse); 1048 } 1049 1050 /** 1051 * Create a WebCore response object so that it can be used by 1052 * nativeReceivedResponse or nativeRedirectedToUrl 1053 * @return native response pointer 1054 */ 1055 private int createNativeResponse() { 1056 // If WebCore sends if-modified-since, mCacheLoader is null. If 1057 // CacheManager sends it, mCacheLoader is not null. In this case, if the 1058 // server responds with a 304, then we treat it like it was a 200 code 1059 // and proceed with loading the file from the cache. 1060 int statusCode = (mStatusCode == HTTP_NOT_MODIFIED && 1061 mCacheLoader != null) ? HTTP_OK : mStatusCode; 1062 // pass content-type content-length and content-encoding 1063 final int nativeResponse = nativeCreateResponse( 1064 originalUrl(), statusCode, mStatusText, 1065 mMimeType, mContentLength, mEncoding); 1066 if (mHeaders != null) { 1067 mHeaders.getHeaders(new Headers.HeaderCallback() { 1068 public void header(String name, String value) { 1069 nativeSetResponseHeader(nativeResponse, name, value); 1070 } 1071 }); 1072 } 1073 return nativeResponse; 1074 } 1075 1076 /** 1077 * Commit the load. It should be ok to call repeatedly but only before 1078 * tearDown is called. 1079 */ 1080 private void commitLoad() { 1081 if (mCancelled) return; 1082 1083 if (mIsMainPageLoader) { 1084 String type = sCertificateTypeMap.get(mMimeType); 1085 if (type != null) { 1086 // This must be synchronized so that no more data can be added 1087 // after getByteSize returns. 1088 synchronized (mDataBuilder) { 1089 // In the case of downloading certificate, we will save it 1090 // to the KeyStore and stop the current loading so that it 1091 // will not generate a new history page 1092 byte[] cert = new byte[mDataBuilder.getByteSize()]; 1093 int offset = 0; 1094 while (true) { 1095 ByteArrayBuilder.Chunk c = mDataBuilder.getFirstChunk(); 1096 if (c == null) break; 1097 1098 if (c.mLength != 0) { 1099 System.arraycopy(c.mArray, 0, cert, offset, c.mLength); 1100 offset += c.mLength; 1101 } 1102 c.release(); 1103 } 1104 CertTool.addCertificate(mContext, type, cert); 1105 mBrowserFrame.stopLoading(); 1106 return; 1107 } 1108 } 1109 } 1110 1111 // Give the data to WebKit now. We don't have to synchronize on 1112 // mDataBuilder here because pulling each chunk removes it from the 1113 // internal list so it cannot be modified. 1114 PerfChecker checker = new PerfChecker(); 1115 ByteArrayBuilder.Chunk c; 1116 while (true) { 1117 c = mDataBuilder.getFirstChunk(); 1118 if (c == null) break; 1119 1120 if (c.mLength != 0) { 1121 nativeAddData(c.mArray, c.mLength); 1122 WebViewWorker.CacheData data = new WebViewWorker.CacheData(); 1123 data.mListener = this; 1124 data.mChunk = c; 1125 WebViewWorker.getHandler().obtainMessage( 1126 WebViewWorker.MSG_APPEND_CACHE, data).sendToTarget(); 1127 } else { 1128 c.release(); 1129 } 1130 checker.responseAlert("res nativeAddData"); 1131 } 1132 } 1133 1134 /** 1135 * Tear down the load. Subclasses should clean up any mess because of 1136 * cancellation or errors during the load. 1137 */ 1138 void tearDown() { 1139 if (getErrorID() == OK) { 1140 WebViewWorker.CacheSaveData data = new WebViewWorker.CacheSaveData(); 1141 data.mListener = this; 1142 data.mUrl = mUrl; 1143 data.mPostId = mPostIdentifier; 1144 WebViewWorker.getHandler().obtainMessage( 1145 WebViewWorker.MSG_SAVE_CACHE, data).sendToTarget(); 1146 } else { 1147 WebViewWorker.getHandler().obtainMessage( 1148 WebViewWorker.MSG_REMOVE_CACHE, this).sendToTarget(); 1149 } 1150 if (mNativeLoader != 0) { 1151 PerfChecker checker = new PerfChecker(); 1152 nativeFinished(); 1153 checker.responseAlert("res nativeFinished"); 1154 clearNativeLoader(); 1155 } 1156 } 1157 1158 /** 1159 * Helper for getting the error ID. 1160 * @return errorID. 1161 */ 1162 private int getErrorID() { 1163 return mErrorID; 1164 } 1165 1166 /** 1167 * Return the error description. 1168 * @return errorDescription. 1169 */ 1170 private String getErrorDescription() { 1171 return mErrorDescription; 1172 } 1173 1174 /** 1175 * Notify the loader we encountered an error. 1176 */ 1177 void notifyError() { 1178 if (mNativeLoader != 0) { 1179 String description = getErrorDescription(); 1180 if (description == null) description = ""; 1181 nativeError(getErrorID(), description, url()); 1182 clearNativeLoader(); 1183 } 1184 } 1185 1186 /** 1187 * Cancel a request. 1188 * FIXME: This will only work if the request has yet to be handled. This 1189 * is in no way guarenteed if requests are served in a separate thread. 1190 * It also causes major problems if cancel is called during an 1191 * EventHandler's method call. 1192 */ 1193 public void cancel() { 1194 if (DebugFlags.LOAD_LISTENER) { 1195 if (mRequestHandle == null) { 1196 Log.v(LOGTAG, "LoadListener.cancel(): no requestHandle"); 1197 } else { 1198 Log.v(LOGTAG, "LoadListener.cancel()"); 1199 } 1200 } 1201 if (mRequestHandle != null) { 1202 mRequestHandle.cancel(); 1203 mRequestHandle = null; 1204 } 1205 1206 WebViewWorker.getHandler().obtainMessage( 1207 WebViewWorker.MSG_REMOVE_CACHE, this).sendToTarget(); 1208 mCancelled = true; 1209 1210 clearNativeLoader(); 1211 } 1212 1213 // This count is transferred from RequestHandle to LoadListener when 1214 // loading from the cache so that we can detect redirect loops that switch 1215 // between the network and the cache. 1216 private int mCacheRedirectCount; 1217 1218 /* 1219 * Perform the actual redirection. This involves setting up the new URL, 1220 * informing WebCore and then telling the Network to start loading again. 1221 */ 1222 private void doRedirect() { 1223 // as cancel() can cancel the load before doRedirect() is 1224 // called through handleMessage, needs to check to see if we 1225 // are canceled before proceed 1226 if (mCancelled) { 1227 return; 1228 } 1229 1230 // Do the same check for a redirect loop that 1231 // RequestHandle.setupRedirect does. 1232 if (mCacheRedirectCount >= RequestHandle.MAX_REDIRECT_COUNT) { 1233 handleError(EventHandler.ERROR_REDIRECT_LOOP, mContext.getString( 1234 R.string.httpErrorRedirectLoop)); 1235 return; 1236 } 1237 1238 String redirectTo = mHeaders.getLocation(); 1239 if (redirectTo != null) { 1240 int nativeResponse = createNativeResponse(); 1241 redirectTo = 1242 nativeRedirectedToUrl(mUrl, redirectTo, nativeResponse); 1243 // nativeRedirectedToUrl() may call cancel(), e.g. when redirect 1244 // from a https site to a http site, check mCancelled again 1245 if (mCancelled) { 1246 return; 1247 } 1248 if (redirectTo == null) { 1249 Log.d(LOGTAG, "Redirection failed for " 1250 + mHeaders.getLocation()); 1251 cancel(); 1252 return; 1253 } else if (!URLUtil.isNetworkUrl(redirectTo)) { 1254 final String text = mContext 1255 .getString(R.string.open_permission_deny) 1256 + "\n" + redirectTo; 1257 nativeAddData(text.getBytes(), text.length()); 1258 nativeFinished(); 1259 clearNativeLoader(); 1260 return; 1261 } 1262 1263 1264 // Cache the redirect response 1265 if (getErrorID() == OK) { 1266 WebViewWorker.CacheSaveData data = new WebViewWorker.CacheSaveData(); 1267 data.mListener = this; 1268 data.mUrl = mUrl; 1269 data.mPostId = mPostIdentifier; 1270 WebViewWorker.getHandler().obtainMessage( 1271 WebViewWorker.MSG_SAVE_CACHE, data).sendToTarget(); 1272 } else { 1273 WebViewWorker.getHandler().obtainMessage( 1274 WebViewWorker.MSG_REMOVE_CACHE, this).sendToTarget(); 1275 } 1276 1277 // Saving a copy of the unstripped url for the response 1278 mOriginalUrl = redirectTo; 1279 // This will strip the anchor 1280 setUrl(redirectTo); 1281 1282 // Redirect may be in the cache 1283 if (mRequestHeaders == null) { 1284 mRequestHeaders = new HashMap<String, String>(); 1285 } 1286 boolean fromCache = false; 1287 if (mCacheLoader != null) { 1288 // This is a redirect from the cache loader. Increment the 1289 // redirect count to avoid redirect loops. 1290 mCacheRedirectCount++; 1291 fromCache = true; 1292 } 1293 if (!checkCache(mRequestHeaders)) { 1294 // mRequestHandle can be null when the request was satisfied 1295 // by the cache, and the cache returned a redirect 1296 if (mRequestHandle != null) { 1297 try { 1298 mRequestHandle.setupRedirect(mUrl, mStatusCode, 1299 mRequestHeaders); 1300 } catch(RuntimeException e) { 1301 Log.e(LOGTAG, e.getMessage()); 1302 // Signal a bad url error if we could not load the 1303 // redirection. 1304 handleError(EventHandler.ERROR_BAD_URL, 1305 mContext.getString(R.string.httpErrorBadUrl)); 1306 return; 1307 } 1308 } else { 1309 // If the original request came from the cache, there is no 1310 // RequestHandle, we have to create a new one through 1311 // Network.requestURL. 1312 Network network = Network.getInstance(getContext()); 1313 if (!network.requestURL(mMethod, mRequestHeaders, 1314 mPostData, this)) { 1315 // Signal a bad url error if we could not load the 1316 // redirection. 1317 handleError(EventHandler.ERROR_BAD_URL, 1318 mContext.getString(R.string.httpErrorBadUrl)); 1319 return; 1320 } 1321 } 1322 if (fromCache) { 1323 // If we are coming from a cache load, we need to transfer 1324 // the redirect count to the new (or old) RequestHandle to 1325 // keep the redirect count in sync. 1326 mRequestHandle.setRedirectCount(mCacheRedirectCount); 1327 } 1328 } else if (!fromCache) { 1329 // Switching from network to cache means we need to grab the 1330 // redirect count from the RequestHandle to keep the count in 1331 // sync. Add 1 to account for the current redirect. 1332 mCacheRedirectCount = mRequestHandle.getRedirectCount() + 1; 1333 } 1334 } else { 1335 commitHeaders(); 1336 commitLoad(); 1337 tearDown(); 1338 } 1339 1340 if (DebugFlags.LOAD_LISTENER) { 1341 Log.v(LOGTAG, "LoadListener.onRedirect(): redirect to: " + 1342 redirectTo); 1343 } 1344 } 1345 1346 /** 1347 * Parses the content-type header. 1348 * The first part only allows '-' if it follows x or X. 1349 */ 1350 private static final Pattern CONTENT_TYPE_PATTERN = 1351 Pattern.compile("^((?:[xX]-)?[a-zA-Z\\*]+/[\\w\\+\\*-]+[\\.[\\w\\+-]+]*)$"); 1352 1353 /* package */ void parseContentTypeHeader(String contentType) { 1354 if (DebugFlags.LOAD_LISTENER) { 1355 Log.v(LOGTAG, "LoadListener.parseContentTypeHeader: " + 1356 "contentType: " + contentType); 1357 } 1358 1359 if (contentType != null) { 1360 int i = contentType.indexOf(';'); 1361 if (i >= 0) { 1362 mMimeType = contentType.substring(0, i); 1363 1364 int j = contentType.indexOf('=', i); 1365 if (j > 0) { 1366 i = contentType.indexOf(';', j); 1367 if (i < j) { 1368 i = contentType.length(); 1369 } 1370 mEncoding = contentType.substring(j + 1, i); 1371 } else { 1372 mEncoding = contentType.substring(i + 1); 1373 } 1374 // Trim excess whitespace. 1375 mEncoding = mEncoding.trim().toLowerCase(); 1376 1377 if (i < contentType.length() - 1) { 1378 // for data: uri the mimeType and encoding have 1379 // the form image/jpeg;base64 or text/plain;charset=utf-8 1380 // or text/html;charset=utf-8;base64 1381 mTransferEncoding = 1382 contentType.substring(i + 1).trim().toLowerCase(); 1383 } 1384 } else { 1385 mMimeType = contentType; 1386 } 1387 1388 // Trim leading and trailing whitespace 1389 mMimeType = mMimeType.trim(); 1390 1391 try { 1392 Matcher m = CONTENT_TYPE_PATTERN.matcher(mMimeType); 1393 if (m.find()) { 1394 mMimeType = m.group(1); 1395 } else { 1396 guessMimeType(); 1397 } 1398 } catch (IllegalStateException ex) { 1399 guessMimeType(); 1400 } 1401 } 1402 // Ensure mMimeType is lower case. 1403 mMimeType = mMimeType.toLowerCase(); 1404 } 1405 1406 /** 1407 * @return The HTTP-authentication object or null if there 1408 * is no supported scheme in the header. 1409 * If there are several valid schemes present, we pick the 1410 * strongest one. If there are several schemes of the same 1411 * strength, we pick the one that comes first. 1412 */ 1413 private HttpAuthHeader parseAuthHeader(String header) { 1414 if (header != null) { 1415 int posMax = 256; 1416 int posLen = 0; 1417 int[] pos = new int [posMax]; 1418 1419 int headerLen = header.length(); 1420 if (headerLen > 0) { 1421 // first, we find all unquoted instances of 'Basic' and 'Digest' 1422 boolean quoted = false; 1423 for (int i = 0; i < headerLen && posLen < posMax; ++i) { 1424 if (header.charAt(i) == '\"') { 1425 quoted = !quoted; 1426 } else { 1427 if (!quoted) { 1428 if (header.regionMatches(true, i, 1429 HttpAuthHeader.BASIC_TOKEN, 0, 1430 HttpAuthHeader.BASIC_TOKEN.length())) { 1431 pos[posLen++] = i; 1432 continue; 1433 } 1434 1435 if (header.regionMatches(true, i, 1436 HttpAuthHeader.DIGEST_TOKEN, 0, 1437 HttpAuthHeader.DIGEST_TOKEN.length())) { 1438 pos[posLen++] = i; 1439 continue; 1440 } 1441 } 1442 } 1443 } 1444 } 1445 1446 if (posLen > 0) { 1447 // consider all digest schemes first (if any) 1448 for (int i = 0; i < posLen; i++) { 1449 if (header.regionMatches(true, pos[i], 1450 HttpAuthHeader.DIGEST_TOKEN, 0, 1451 HttpAuthHeader.DIGEST_TOKEN.length())) { 1452 String sub = header.substring(pos[i], 1453 (i + 1 < posLen ? pos[i + 1] : headerLen)); 1454 1455 HttpAuthHeader rval = new HttpAuthHeader(sub); 1456 if (rval.isSupportedScheme()) { 1457 // take the first match 1458 return rval; 1459 } 1460 } 1461 } 1462 1463 // ...then consider all basic schemes (if any) 1464 for (int i = 0; i < posLen; i++) { 1465 if (header.regionMatches(true, pos[i], 1466 HttpAuthHeader.BASIC_TOKEN, 0, 1467 HttpAuthHeader.BASIC_TOKEN.length())) { 1468 String sub = header.substring(pos[i], 1469 (i + 1 < posLen ? pos[i + 1] : headerLen)); 1470 1471 HttpAuthHeader rval = new HttpAuthHeader(sub); 1472 if (rval.isSupportedScheme()) { 1473 // take the first match 1474 return rval; 1475 } 1476 } 1477 } 1478 } 1479 } 1480 1481 return null; 1482 } 1483 1484 /** 1485 * If the content is a redirect or not modified we should not send 1486 * any data into WebCore as that will cause it create a document with 1487 * the data, then when we try to provide the real content, it will assert. 1488 * 1489 * @return True iff the callback should be ignored. 1490 */ 1491 private boolean ignoreCallbacks() { 1492 return (mCancelled || mAuthHeader != null || 1493 // Allow 305 (Use Proxy) to call through. 1494 (mStatusCode > 300 && mStatusCode < 400 && mStatusCode != 305)); 1495 } 1496 1497 /** 1498 * Sets the current URL associated with this load. 1499 */ 1500 void setUrl(String url) { 1501 if (url != null) { 1502 mUri = null; 1503 if (URLUtil.isNetworkUrl(url)) { 1504 mUrl = URLUtil.stripAnchor(url); 1505 try { 1506 mUri = new WebAddress(mUrl); 1507 } catch (ParseException e) { 1508 e.printStackTrace(); 1509 } 1510 } else { 1511 mUrl = url; 1512 } 1513 } 1514 } 1515 1516 /** 1517 * Guesses MIME type if one was not specified. Defaults to 'text/html'. In 1518 * addition, tries to guess the MIME type based on the extension. 1519 * 1520 */ 1521 private void guessMimeType() { 1522 // Data urls must have a valid mime type or a blank string for the mime 1523 // type (implying text/plain). 1524 if (URLUtil.isDataUrl(mUrl) && mMimeType.length() != 0) { 1525 cancel(); 1526 final String text = mContext.getString(R.string.httpErrorBadUrl); 1527 handleError(EventHandler.ERROR_BAD_URL, text); 1528 } else { 1529 // Note: This is ok because this is used only for the main content 1530 // of frames. If no content-type was specified, it is fine to 1531 // default to text/html. 1532 mMimeType = "text/html"; 1533 String newMimeType = guessMimeTypeFromExtension(mUrl); 1534 if (newMimeType != null) { 1535 mMimeType = newMimeType; 1536 } 1537 } 1538 } 1539 1540 /** 1541 * guess MIME type based on the file extension. 1542 */ 1543 private String guessMimeTypeFromExtension(String url) { 1544 // PENDING: need to normalize url 1545 if (DebugFlags.LOAD_LISTENER) { 1546 Log.v(LOGTAG, "guessMimeTypeFromExtension: url = " + url); 1547 } 1548 1549 return MimeTypeMap.getSingleton().getMimeTypeFromExtension( 1550 MimeTypeMap.getFileExtensionFromUrl(url)); 1551 } 1552 1553 /** 1554 * Either send a message to ourselves or queue the message if this is a 1555 * synchronous load. 1556 */ 1557 private void sendMessageInternal(Message msg) { 1558 if (mSynchronous) { 1559 mMessageQueue.add(msg); 1560 } else { 1561 sendMessage(msg); 1562 } 1563 } 1564 1565 /** 1566 * Cycle through our messages for synchronous loads. 1567 */ 1568 /* package */ void loadSynchronousMessages() { 1569 if (DebugFlags.LOAD_LISTENER && !mSynchronous) { 1570 throw new AssertionError(); 1571 } 1572 // Note: this can be called twice if it is a synchronous network load, 1573 // and there is a cache, but it needs to go to network to validate. If 1574 // validation succeed, the CacheLoader is used so this is first called 1575 // from http thread. Then it is called again from WebViewCore thread 1576 // after the load is completed. So make sure the queue is cleared but 1577 // don't set it to null. 1578 for (int size = mMessageQueue.size(); size > 0; size--) { 1579 handleMessage(mMessageQueue.remove(0)); 1580 } 1581 } 1582 1583 //========================================================================= 1584 // native functions 1585 //========================================================================= 1586 1587 /** 1588 * Create a new native response object. 1589 * @param url The url of the resource. 1590 * @param statusCode The HTTP status code. 1591 * @param statusText The HTTP status text. 1592 * @param mimeType HTTP content-type. 1593 * @param expectedLength An estimate of the content length or the length 1594 * given by the server. 1595 * @param encoding HTTP encoding. 1596 * @return The native response pointer. 1597 */ 1598 private native int nativeCreateResponse(String url, int statusCode, 1599 String statusText, String mimeType, long expectedLength, 1600 String encoding); 1601 1602 /** 1603 * Add a response header to the native object. 1604 * @param nativeResponse The native pointer. 1605 * @param key String key. 1606 * @param val String value. 1607 */ 1608 private native void nativeSetResponseHeader(int nativeResponse, String key, 1609 String val); 1610 1611 /** 1612 * Dispatch the response. 1613 * @param nativeResponse The native pointer. 1614 */ 1615 private native void nativeReceivedResponse(int nativeResponse); 1616 1617 /** 1618 * Add data to the loader. 1619 * @param data Byte array of data. 1620 * @param length Number of objects in data. 1621 */ 1622 private native void nativeAddData(byte[] data, int length); 1623 1624 /** 1625 * Tell the loader it has finished. 1626 */ 1627 private native void nativeFinished(); 1628 1629 /** 1630 * tell the loader to redirect 1631 * @param baseUrl The base url. 1632 * @param redirectTo The url to redirect to. 1633 * @param nativeResponse The native pointer. 1634 * @return The new url that the resource redirected to. 1635 */ 1636 private native String nativeRedirectedToUrl(String baseUrl, 1637 String redirectTo, int nativeResponse); 1638 1639 /** 1640 * Tell the loader there is error 1641 * @param id 1642 * @param desc 1643 * @param failingUrl The url that failed. 1644 */ 1645 private native void nativeError(int id, String desc, String failingUrl); 1646 1647} 1648