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