1 /* 2 * Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.webkit.network; 27 28 import com.sun.javafx.logging.PlatformLogger; 29 import com.sun.javafx.logging.PlatformLogger.Level; 30 import com.sun.webkit.Invoker; 31 import com.sun.webkit.LoadListenerClient; 32 import com.sun.webkit.WebPage; 33 import static com.sun.webkit.network.URLs.newURL; 34 import java.io.EOFException; 35 import java.io.File; 36 import java.io.FileNotFoundException; 37 import java.io.IOException; 38 import java.io.InputStream; 39 import java.io.OutputStream; 40 import java.io.UnsupportedEncodingException; 41 import java.net.ConnectException; 42 import java.net.HttpRetryException; 43 import java.net.HttpURLConnection; 44 import java.net.MalformedURLException; 45 import java.net.NoRouteToHostException; 46 import java.net.SocketException; 47 import java.net.SocketTimeoutException; 48 import java.net.URL; 49 import java.net.URLConnection; 50 import java.net.URLDecoder; 51 import java.net.UnknownHostException; 52 import java.nio.ByteBuffer; 53 import java.security.AccessControlException; 54 import java.security.AccessController; 55 import java.security.PrivilegedAction; 56 import java.util.List; 57 import java.util.Locale; 58 import java.util.Map; 59 import java.util.concurrent.CountDownLatch; 60 import java.util.zip.GZIPInputStream; 61 import java.util.zip.InflaterInputStream; 62 import javax.net.ssl.SSLHandshakeException; 63 64 /** 65 * A runnable that loads a resource specified by a URL. 66 */ 67 final class URLLoader extends URLLoaderBase implements Runnable { 68 69 private static final PlatformLogger logger = 70 PlatformLogger.getLogger(URLLoader.class.getName()); 71 private static final int MAX_BUF_COUNT = 3; 72 private static final String GET = "GET"; 73 private static final String HEAD = "HEAD"; 74 private static final String DELETE = "DELETE"; 75 76 77 private final WebPage webPage; 78 private final ByteBufferPool byteBufferPool; 79 private final boolean asynchronous; 80 private String url; 81 private String method; 82 private final String headers; 83 private FormDataElement[] formDataElements; 84 private final long data; 85 private volatile boolean canceled = false; 86 87 88 /** 89 * Creates a new {@code URLLoader}. 90 */ 91 URLLoader(WebPage webPage, 92 ByteBufferPool byteBufferPool, 93 boolean asynchronous, 94 String url, 95 String method, 96 String headers, 97 FormDataElement[] formDataElements, 98 long data) 99 { 100 this.webPage = webPage; 101 this.byteBufferPool = byteBufferPool; 102 this.asynchronous = asynchronous; 103 this.url = url; 104 this.method = method; 105 this.headers = headers; 106 this.formDataElements = formDataElements; 107 this.data = data; 108 } 109 110 111 /** 112 * Cancels this loader. 113 */ 114 @Override 115 public void fwkCancel() { 116 if (logger.isLoggable(Level.FINEST)) { 117 logger.finest(String.format("data: [0x%016X]", data)); 118 } 119 canceled = true; 120 } 121 122 /** 123 * {@inheritDoc} 124 */ 125 @Override 126 public void run() { 127 // Run the loader in the page's access control context 128 AccessController.doPrivileged((PrivilegedAction<Void>) () -> { 129 doRun(); 130 return null; 131 }, webPage.getAccessControlContext()); 132 } 133 134 /** 135 * Executes this loader. 136 */ 137 private void doRun() { 138 Throwable error = null; 139 int errorCode = 0; 140 try { 141 boolean streaming = true; 142 boolean connectionResetRetry = true; 143 while (true) { 144 // RT-14438 145 String actualUrl = url; 146 if (url.startsWith("file:")) { 147 int questionMarkPosition = url.indexOf('?'); 148 if (questionMarkPosition != -1) { 149 actualUrl = url.substring(0, questionMarkPosition); 150 } 151 } 152 153 URL urlObject = newURL(actualUrl); 154 155 // RT-22458 156 workaround7177996(urlObject); 157 158 URLConnection c = urlObject.openConnection(); 159 prepareConnection(c); 160 161 try { 162 sendRequest(c, streaming); 163 receiveResponse(c); 164 } catch (HttpRetryException ex) { 165 // RT-19914 166 if (streaming) { 167 streaming = false; 168 continue; // retry without streaming 169 } else { 170 throw ex; 171 } 172 } catch (SocketException ex) { 173 // SocketException: Connection reset, Retry once 174 if ("Connection reset".equals(ex.getMessage()) && connectionResetRetry) { 175 connectionResetRetry = false; 176 continue; 177 } else { 178 throw ex; 179 } 180 } finally { 181 close(c); 182 } 183 break; 184 } 185 } catch (MalformedURLException ex) { 186 error = ex; 187 errorCode = LoadListenerClient.MALFORMED_URL; 188 } catch (AccessControlException ex) { 189 error = ex; 190 errorCode = LoadListenerClient.PERMISSION_DENIED; 191 } catch (UnknownHostException ex) { 192 error = ex; 193 errorCode = LoadListenerClient.UNKNOWN_HOST; 194 } catch (NoRouteToHostException ex) { 195 error = ex; 196 errorCode = LoadListenerClient.NO_ROUTE_TO_HOST; 197 } catch (ConnectException ex) { 198 error = ex; 199 errorCode = LoadListenerClient.CONNECTION_REFUSED; 200 } catch (SocketException ex) { 201 error = ex; 202 errorCode = LoadListenerClient.CONNECTION_RESET; 203 } catch (SSLHandshakeException ex) { 204 error = ex; 205 errorCode = LoadListenerClient.SSL_HANDSHAKE; 206 } catch (SocketTimeoutException ex) { 207 error = ex; 208 errorCode = LoadListenerClient.CONNECTION_TIMED_OUT; 209 } catch (InvalidResponseException ex) { 210 error = ex; 211 errorCode = LoadListenerClient.INVALID_RESPONSE; 212 } catch (FileNotFoundException ex) { 213 error = ex; 214 errorCode = LoadListenerClient.FILE_NOT_FOUND; 215 } catch (Throwable th) { 216 error = th; 217 errorCode = LoadListenerClient.UNKNOWN_ERROR; 218 } 219 220 if (error != null) { 221 if (errorCode == LoadListenerClient.UNKNOWN_ERROR) { 222 logger.warning("Unexpected error", error); 223 } else { 224 logger.finest("Load error", error); 225 } 226 didFail(errorCode, error.getMessage()); 227 } 228 } 229 230 private static void workaround7177996(URL url) 231 throws FileNotFoundException 232 { 233 if (!url.getProtocol().equals("file")) { 234 return; 235 } 236 237 String host = url.getHost(); 238 if (host == null || host.equals("") || host.equals("~") 239 || host.equalsIgnoreCase("localhost") ) 240 { 241 return; 242 } 243 244 if (System.getProperty("os.name").startsWith("Windows")) { 245 String path = null; 246 try { 247 path = URLDecoder.decode(url.getPath(), "UTF-8"); 248 } catch (UnsupportedEncodingException e) { 249 // The system should always have the platform default 250 } 251 path = path.replace('/', '\\'); 252 path = path.replace('|', ':'); 253 File file = new File("\\\\" + host + path); 254 if (!file.exists()) { 255 throw new FileNotFoundException("File not found: " + url); 256 } 257 } else { 258 throw new FileNotFoundException("File not found: " + url); 259 } 260 } 261 262 /** 263 * Prepares a connection. 264 */ 265 private void prepareConnection(URLConnection c) throws IOException { 266 // The following two timeouts are quite arbitrary and should 267 // probably be configurable via an API 268 c.setConnectTimeout(30000); // 30 seconds 269 c.setReadTimeout(60000 * 60); // 60 minutes 270 271 // Given that WebKit has its own cache, do not use 272 // any URLConnection caches, even if someone installs them. 273 // As a side effect, this fixes the problem of WebPane not 274 // working well with the plug-in cache, which was one of 275 // the causes for RT-11880. 276 c.setUseCaches(false); 277 278 Locale loc = Locale.getDefault(); 279 String lang = ""; 280 if (!loc.equals(Locale.US) && !loc.equals(Locale.ENGLISH)) { 281 lang = loc.getCountry().isEmpty() ? 282 loc.getLanguage() + ",": 283 loc.getLanguage() + "-" + loc.getCountry() + ","; 284 } 285 c.setRequestProperty("Accept-Language", lang.toLowerCase() + "en-us;q=0.8,en;q=0.7"); 286 c.setRequestProperty("Accept-Encoding", "gzip"); 287 c.setRequestProperty("Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7"); 288 289 if (headers != null && headers.length() > 0) { 290 for (String h : headers.split("\n")) { 291 int i = h.indexOf(':'); 292 if (i > 0) { 293 c.addRequestProperty(h.substring(0, i), h.substring(i + 2)); 294 } 295 } 296 } 297 298 if (c instanceof HttpURLConnection) { 299 HttpURLConnection httpConnection = (HttpURLConnection) c; 300 httpConnection.setRequestMethod(method); 301 // There are too many bugs in the way HttpURLConnection handles 302 // redirects, so we will deal with them ourselves 303 httpConnection.setInstanceFollowRedirects(false); 304 } 305 } 306 307 /** 308 * Sends request to the server. 309 */ 310 private void sendRequest(URLConnection c, boolean streaming) 311 throws IOException 312 { 313 OutputStream out = null; 314 try { 315 long bytesToBeSent = 0; 316 boolean sendFormData = formDataElements != null 317 && c instanceof HttpURLConnection 318 && !method.equals(DELETE); 319 boolean isGetOrHead = method.equals(GET) || method.equals(HEAD); 320 if (sendFormData) { 321 c.setDoOutput(true); 322 323 for (FormDataElement formDataElement : formDataElements) { 324 formDataElement.open(); 325 bytesToBeSent += formDataElement.getSize(); 326 } 327 328 if (streaming) { 329 HttpURLConnection http = (HttpURLConnection) c; 330 if (bytesToBeSent <= Integer.MAX_VALUE) { 331 http.setFixedLengthStreamingMode((int) bytesToBeSent); 332 } else { 333 http.setChunkedStreamingMode(0); 334 } 335 } 336 } else if (!isGetOrHead && (c instanceof HttpURLConnection)) { 337 c.setRequestProperty("Content-Length", "0"); 338 } 339 340 int maxTryCount = isGetOrHead ? 3 : 1; 341 c.setConnectTimeout(c.getConnectTimeout() / maxTryCount); 342 int tryCount = 0; 343 while (!canceled) { 344 try { 345 c.connect(); 346 break; 347 } catch (SocketTimeoutException ex) { 348 if (++tryCount >= maxTryCount) { 349 throw ex; 350 } 351 } catch (IllegalArgumentException ex) { 352 // Happens with some malformed URLs 353 throw new MalformedURLException(url); 354 } 355 } 356 357 if (sendFormData) { 358 out = c.getOutputStream(); 359 byte[] buffer = new byte[4096]; 360 long bytesSent = 0; 361 for (FormDataElement formDataElement : formDataElements) { 362 InputStream in = formDataElement.getInputStream(); 363 int count; 364 while ((count = in.read(buffer)) > 0) { 365 out.write(buffer, 0, count); 366 bytesSent += count; 367 didSendData(bytesSent, bytesToBeSent); 368 } 369 formDataElement.close(); 370 } 371 out.flush(); 372 out.close(); 373 out = null; 374 } 375 } finally { 376 if (out != null) { 377 try { 378 out.close(); 379 } catch (IOException ignore) {} 380 } 381 if (formDataElements != null && c instanceof HttpURLConnection) { 382 for (FormDataElement formDataElement : formDataElements) { 383 try { 384 formDataElement.close(); 385 } catch (IOException ignore) {} 386 } 387 } 388 } 389 } 390 391 /** 392 * Receives response from the server. 393 */ 394 private void receiveResponse(URLConnection c) 395 throws IOException, InterruptedException 396 { 397 if (canceled) { 398 return; 399 } 400 401 InputStream errorStream = null; 402 403 if (c instanceof HttpURLConnection) { 404 HttpURLConnection http = (HttpURLConnection) c; 405 406 int code = http.getResponseCode(); 407 if (code == -1) { 408 throw new InvalidResponseException(); 409 } 410 411 if (canceled) { 412 return; 413 } 414 415 // See RT-17435 416 switch (code) { 417 case 301: // Moved Permanently 418 case 302: // Found 419 case 303: // See Other 420 case 307: // Temporary Redirect 421 willSendRequest(c); 422 break; 423 424 case 304: // Not Modified 425 didReceiveResponse(c); 426 didFinishLoading(); 427 return; 428 } 429 430 if (code >= 400 && !method.equals(HEAD)) { 431 errorStream = http.getErrorStream(); 432 } 433 } 434 435 // Let's see if it's an ftp (or ftps) URL and we need to transform 436 // a directory listing into HTML 437 if (url.startsWith("ftp:") || url.startsWith("ftps:")) { 438 boolean dir = false; 439 boolean notsure = false; 440 // Unfortunately, there is no clear way to determine if we are 441 // accessing a directory, so a bit of guessing is in order 442 String path = c.getURL().getPath(); 443 if (path == null || path.isEmpty() || path.endsWith("/") 444 || path.contains(";type=d")) 445 { 446 dir = true; 447 } else { 448 String type = c.getContentType(); 449 if ("text/plain".equalsIgnoreCase(type) 450 || "text/html".equalsIgnoreCase(type)) 451 { 452 dir = true; 453 notsure = true; 454 } 455 } 456 if (dir) { 457 c = new DirectoryURLConnection(c, notsure); 458 } 459 } 460 461 // Same is true for FileURLConnection 462 if (url.startsWith("file:")) { 463 if("text/plain".equals(c.getContentType()) 464 && c.getHeaderField("content-length") == null) 465 { 466 // It is a directory 467 c = new DirectoryURLConnection(c); 468 } 469 } 470 471 didReceiveResponse(c); 472 473 if (method.equals(HEAD)) { 474 didFinishLoading(); 475 return; 476 } 477 478 InputStream inputStream = null; 479 try { 480 inputStream = errorStream == null 481 ? c.getInputStream() : errorStream; 482 } catch (HttpRetryException ex) { 483 // HttpRetryException is handled from doRun() method. 484 // Hence rethrowing the exception to caller(doRun() method) 485 throw ex; 486 } catch (IOException e) { 487 if (logger.isLoggable(Level.FINE)) { 488 logger.fine(String.format("Exception caught: [%s], %s", 489 e.getClass().getSimpleName(), 490 e.getMessage())); 491 } 492 } 493 494 String encoding = c.getContentEncoding(); 495 if (inputStream != null) { 496 try { 497 if ("gzip".equalsIgnoreCase(encoding)) { 498 inputStream = new GZIPInputStream(inputStream); 499 } else if ("deflate".equalsIgnoreCase(encoding)) { 500 inputStream = new InflaterInputStream(inputStream); 501 } 502 } catch (IOException e) { 503 if (logger.isLoggable(Level.FINE)) { 504 logger.fine(String.format("Exception caught: [%s], %s", 505 e.getClass().getSimpleName(), 506 e.getMessage())); 507 } 508 } 509 } 510 511 ByteBufferAllocator allocator = 512 byteBufferPool.newAllocator(MAX_BUF_COUNT); 513 ByteBuffer byteBuffer = null; 514 try { 515 if (inputStream != null) { 516 // 8192 is the default size of a BufferedInputStream used in 517 // most URLConnections, by using the same size, we avoid quite 518 // a few System.arrayCopy() calls 519 byte[] buffer = new byte[8192]; 520 while (!canceled) { 521 int count; 522 try { 523 count = inputStream.read(buffer); 524 } catch (EOFException ex) { 525 // can be thrown by GZIPInputStream signaling 526 // the end of the stream 527 count = -1; 528 } 529 530 if (count == -1) { 531 break; 532 } 533 534 if (byteBuffer == null) { 535 byteBuffer = allocator.allocate(); 536 } 537 538 int remaining = byteBuffer.remaining(); 539 if (count < remaining) { 540 byteBuffer.put(buffer, 0, count); 541 } else { 542 byteBuffer.put(buffer, 0, remaining); 543 544 byteBuffer.flip(); 545 didReceiveData(byteBuffer, allocator); 546 byteBuffer = null; 547 548 int outstanding = count - remaining; 549 if (outstanding > 0) { 550 byteBuffer = allocator.allocate(); 551 byteBuffer.put(buffer, remaining, outstanding); 552 } 553 } 554 } 555 } 556 if (!canceled) { 557 if (byteBuffer != null && byteBuffer.position() > 0) { 558 byteBuffer.flip(); 559 didReceiveData(byteBuffer, allocator); 560 byteBuffer = null; 561 } 562 didFinishLoading(); 563 } 564 } finally { 565 if (byteBuffer != null) { 566 allocator.release(byteBuffer); 567 } 568 } 569 } 570 571 /** 572 * Releases the resources that may be associated with a connection. 573 */ 574 private static void close(URLConnection c) { 575 if (c instanceof HttpURLConnection) { 576 InputStream errorStream = ((HttpURLConnection) c).getErrorStream(); 577 if (errorStream != null) { 578 try { 579 errorStream.close(); 580 } catch (IOException ignore) {} 581 } 582 } 583 try { 584 c.getInputStream().close(); 585 } catch (IOException ignore) {} 586 } 587 588 /** 589 * Signals an invalid response from the server. 590 */ 591 private static final class InvalidResponseException extends IOException { 592 private InvalidResponseException() { 593 super("Invalid server response"); 594 } 595 } 596 597 private void didSendData(final long totalBytesSent, 598 final long totalBytesToBeSent) 599 { 600 callBack(() -> { 601 if (!canceled) { 602 notifyDidSendData(totalBytesSent, totalBytesToBeSent); 603 } 604 }); 605 } 606 607 private void notifyDidSendData(long totalBytesSent, 608 long totalBytesToBeSent) 609 { 610 if (logger.isLoggable(Level.FINEST)) { 611 logger.finest(String.format( 612 "totalBytesSent: [%d], " 613 + "totalBytesToBeSent: [%d], " 614 + "data: [0x%016X]", 615 totalBytesSent, 616 totalBytesToBeSent, 617 data)); 618 } 619 twkDidSendData(totalBytesSent, totalBytesToBeSent, data); 620 } 621 622 private void willSendRequest(URLConnection c) throws InterruptedException 623 { 624 final int status = extractStatus(c); 625 final String contentType = c.getContentType(); 626 final String contentEncoding = extractContentEncoding(c); 627 final long contentLength = extractContentLength(c); 628 final String responseHeaders = extractHeaders(c); 629 final String adjustedUrl = adjustUrlForWebKit(url); 630 callBack(() -> { 631 if (!canceled) { 632 notifyWillSendRequest( 633 status, 634 contentType, 635 contentEncoding, 636 contentLength, 637 responseHeaders, 638 adjustedUrl); 639 } 640 }); 641 } 642 643 private void notifyWillSendRequest(int status, 644 String contentType, 645 String contentEncoding, 646 long contentLength, 647 String headers, 648 String url) 649 { 650 if (logger.isLoggable(Level.FINEST)) { 651 logger.finest(String.format( 652 "status: [%d], " 653 + "contentType: [%s], " 654 + "contentEncoding: [%s], " 655 + "contentLength: [%d], " 656 + "url: [%s], " 657 + "data: [0x%016X], " 658 + "headers:%n%s", 659 status, 660 contentType, 661 contentEncoding, 662 contentLength, 663 url, 664 data, 665 Util.formatHeaders(headers))); 666 } 667 twkWillSendRequest( 668 status, 669 contentType, 670 contentEncoding, 671 contentLength, 672 headers, 673 url, 674 data); 675 } 676 677 private void didReceiveResponse(URLConnection c) { 678 final int status = extractStatus(c); 679 final String contentType = c.getContentType(); 680 final String contentEncoding = extractContentEncoding(c); 681 final long contentLength = extractContentLength(c); 682 final String responseHeaders = extractHeaders(c); 683 final String adjustedUrl = adjustUrlForWebKit(url); 684 callBack(() -> { 685 if (!canceled) { 686 notifyDidReceiveResponse( 687 status, 688 contentType, 689 contentEncoding, 690 contentLength, 691 responseHeaders, 692 adjustedUrl); 693 } 694 }); 695 } 696 697 private void notifyDidReceiveResponse(int status, 698 String contentType, 699 String contentEncoding, 700 long contentLength, 701 String headers, 702 String url) 703 { 704 if (logger.isLoggable(Level.FINEST)) { 705 logger.finest(String.format( 706 "status: [%d], " 707 + "contentType: [%s], " 708 + "contentEncoding: [%s], " 709 + "contentLength: [%d], " 710 + "url: [%s], " 711 + "data: [0x%016X], " 712 + "headers:%n%s", 713 status, 714 contentType, 715 contentEncoding, 716 contentLength, 717 url, 718 data, 719 Util.formatHeaders(headers))); 720 } 721 twkDidReceiveResponse( 722 status, 723 contentType, 724 contentEncoding, 725 contentLength, 726 headers, 727 url, 728 data); 729 } 730 731 private void didReceiveData(final ByteBuffer byteBuffer, 732 final ByteBufferAllocator allocator) 733 { 734 callBack(() -> { 735 if (!canceled) { 736 notifyDidReceiveData( 737 byteBuffer, 738 byteBuffer.position(), 739 byteBuffer.remaining()); 740 } 741 allocator.release(byteBuffer); 742 }); 743 } 744 745 private void notifyDidReceiveData(ByteBuffer byteBuffer, 746 int position, 747 int remaining) 748 { 749 if (logger.isLoggable(Level.FINEST)) { 750 logger.finest(String.format( 751 "byteBuffer: [%s], " 752 + "position: [%s], " 753 + "remaining: [%s], " 754 + "data: [0x%016X]", 755 byteBuffer, 756 position, 757 remaining, 758 data)); 759 } 760 twkDidReceiveData(byteBuffer, position, remaining, data); 761 } 762 763 private void didFinishLoading() { 764 callBack(() -> { 765 if (!canceled) { 766 notifyDidFinishLoading(); 767 } 768 }); 769 } 770 771 private void notifyDidFinishLoading() { 772 if (logger.isLoggable(Level.FINEST)) { 773 logger.finest(String.format("data: [0x%016X]", data)); 774 } 775 twkDidFinishLoading(data); 776 } 777 778 private void didFail(final int errorCode, final String message) { 779 final String adjustedUrl = adjustUrlForWebKit(url); 780 callBack(() -> { 781 if (!canceled) { 782 notifyDidFail(errorCode, adjustedUrl, message); 783 } 784 }); 785 } 786 787 private void notifyDidFail(int errorCode, String url, String message) { 788 if (logger.isLoggable(Level.FINEST)) { 789 logger.finest(String.format( 790 "errorCode: [%d], " 791 + "url: [%s], " 792 + "message: [%s], " 793 + "data: [0x%016X]", 794 errorCode, 795 url, 796 message, 797 data)); 798 } 799 twkDidFail(errorCode, url, message, data); 800 } 801 802 private void callBack(Runnable runnable) { 803 if (asynchronous) { 804 Invoker.getInvoker().invokeOnEventThread(runnable); 805 } else { 806 runnable.run(); 807 } 808 } 809 810 /** 811 * Given a {@link URLConnection}, returns the connection status 812 * for passing into native callbacks. 813 */ 814 private static int extractStatus(URLConnection c) { 815 int status = 0; 816 if (c instanceof HttpURLConnection) { 817 try { 818 status = ((HttpURLConnection) c).getResponseCode(); 819 } catch (java.io.IOException ignore) {} 820 } 821 return status; 822 } 823 824 /** 825 * Given a {@link URLConnection}, returns the content encoding 826 * for passing into native callbacks. 827 */ 828 private static String extractContentEncoding(URLConnection c) { 829 String contentEncoding = c.getContentEncoding(); 830 // For compressed streams, the encoding is in Content-Type 831 if ("gzip".equalsIgnoreCase(contentEncoding) || 832 "deflate".equalsIgnoreCase(contentEncoding)) 833 { 834 contentEncoding = null; 835 String contentType = c.getContentType(); 836 if (contentType != null) { 837 int i = contentType.indexOf("charset="); 838 if (i >= 0) { 839 contentEncoding = contentType.substring(i + 8); 840 i = contentEncoding.indexOf(";"); 841 if (i > 0) { 842 contentEncoding = contentEncoding.substring(0, i); 843 } 844 } 845 } 846 } 847 return contentEncoding; 848 } 849 850 /** 851 * Given a {@link URLConnection}, returns the content length 852 * for passing into native callbacks. 853 */ 854 private static long extractContentLength(URLConnection c) { 855 // Cannot use URLConnection.getContentLength() 856 // as it only returns an int 857 try { 858 return Long.parseLong(c.getHeaderField("content-length")); 859 } catch (Exception ex) { 860 return -1; 861 } 862 } 863 864 /** 865 * Given a {@link URLConnection}, returns the headers string 866 * for passing into native callbacks. 867 */ 868 private static String extractHeaders(URLConnection c) { 869 StringBuilder sb = new StringBuilder(); 870 Map<String, List<String>> headers = c.getHeaderFields(); 871 for (Map.Entry<String, List<String>> entry: headers.entrySet()) { 872 String key = entry.getKey(); 873 List<String> values = entry.getValue(); 874 for (String value : values) { 875 sb.append(key != null ? key : ""); 876 sb.append(':').append(value).append('\n'); 877 } 878 } 879 return sb.toString(); 880 } 881 882 /** 883 * Adjust a URL string for passing into WebKit. 884 */ 885 private static String adjustUrlForWebKit(String url) { 886 try { 887 url = Util.adjustUrlForWebKit(url); 888 } catch (Exception ignore) { 889 } 890 return url; 891 } 892 }