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