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