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