1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package org.apache.harmony.luni.internal.net.www.protocol.http; 19 20 import java.io.ByteArrayOutputStream; 21 import java.io.FileNotFoundException; 22 import java.io.IOException; 23 import java.io.InputStream; 24 import java.io.OutputStream; 25 import java.net.Authenticator; 26 import java.net.CacheRequest; 27 import java.net.CacheResponse; 28 import java.net.HttpURLConnection; 29 import java.net.InetAddress; 30 import java.net.InetSocketAddress; 31 import java.net.PasswordAuthentication; 32 import java.net.ProtocolException; 33 import java.net.Proxy; 34 import java.net.ProxySelector; 35 import java.net.ResponseCache; 36 import java.net.SocketPermission; 37 import java.net.URI; 38 import java.net.URISyntaxException; 39 import java.net.URL; 40 import java.net.URLConnection; 41 import java.net.URLStreamHandler; 42 import java.security.AccessController; 43 import java.security.Permission; 44 import java.security.PrivilegedAction; 45 import java.text.SimpleDateFormat; 46 import java.util.Date; 47 import java.util.List; 48 import java.util.Locale; 49 import java.util.Map; 50 import java.util.TimeZone; 51 52 import org.apache.harmony.luni.util.Base64; 53 import org.apache.harmony.luni.util.Msg; 54 import org.apache.harmony.luni.util.PriviAction; 55 56 /** 57 * This subclass extends <code>HttpURLConnection</code> which in turns extends 58 * <code>URLConnection</code> This is the actual class that "does the work", 59 * such as connecting, sending request and getting the content from the remote 60 * server. 61 */ 62 public class HttpURLConnectionImpl extends HttpURLConnection { 63 private static final String POST = "POST"; 64 private static final String GET = "GET"; 65 private static final String PUT = "PUT"; 66 private static final String HEAD = "HEAD"; 67 private static final byte[] CRLF = new byte[] { '\r', '\n' }; 68 private static final byte[] HEX_DIGITS = new byte[] { 69 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' 70 }; 71 72 private final int defaultPort; 73 74 private int httpVersion = 1; // Assume HTTP/1.1 75 76 protected HttpConnection connection; 77 78 private InputStream is; 79 80 private InputStream uis; 81 82 private OutputStream socketOut; 83 84 private OutputStream cacheOut; 85 86 private ResponseCache responseCache; 87 88 private CacheResponse cacheResponse; 89 90 private CacheRequest cacheRequest; 91 92 private boolean hasTriedCache; 93 94 private HttpOutputStream os; 95 96 private boolean sentRequest; 97 98 boolean sendChunked; 99 100 private String proxyName; 101 102 private int hostPort = -1; 103 104 private String hostName; 105 106 private InetAddress hostAddress; 107 108 // proxy which is used to make the connection. 109 private Proxy proxy; 110 111 // the destination URI 112 private URI uri; 113 114 // default request header 115 private static Header defaultReqHeader = new Header(); 116 117 // request header that will be sent to the server 118 private Header reqHeader; 119 120 // response header received from the server 121 private Header resHeader; 122 123 // BEGIN android-added 124 /** 125 * An <code>InputStream</code> wrapper that does <i>not</i> pass 126 * <code>close()</code> calls to the wrapped stream but instead 127 * treats it as a local shutoff. 128 */ 129 private class LocalCloseInputStream extends InputStream { 130 private boolean closed; 131 132 public LocalCloseInputStream() { 133 closed = false; 134 } 135 136 public int read() throws IOException { 137 if (closed) { 138 throwClosed(); 139 } 140 141 int result = is.read(); 142 if (useCaches && cacheOut != null) { 143 cacheOut.write(result); 144 } 145 return result; 146 } 147 148 public int read(byte[] b, int off, int len) throws IOException { 149 if (closed) { 150 throwClosed(); 151 } 152 int result = is.read(b, off, len); 153 if (result > 0) { 154 // if user has set useCache to true and cache exists, writes to 155 // it 156 if (useCaches && cacheOut != null) { 157 cacheOut.write(b, off, result); 158 } 159 } 160 return result; 161 } 162 163 public int read(byte[] b) throws IOException { 164 if (closed) { 165 throwClosed(); 166 } 167 int result = is.read(b); 168 if (result > 0) { 169 // if user has set useCache to true and cache exists, writes to 170 // it 171 if (useCaches && cacheOut != null) { 172 cacheOut.write(b, 0, result); 173 } 174 } 175 return result; 176 } 177 178 public long skip(long n) throws IOException { 179 if (closed) { 180 throwClosed(); 181 } 182 183 return is.skip(n); 184 } 185 186 public int available() throws IOException { 187 if (closed) { 188 throwClosed(); 189 } 190 191 return is.available(); 192 } 193 194 public void close() { 195 closed = true; 196 if (useCaches && cacheRequest != null) { 197 cacheRequest.abort(); 198 } 199 } 200 201 public void mark(int readLimit) { 202 if (! closed) { 203 is.mark(readLimit); 204 } 205 } 206 207 public void reset() throws IOException { 208 if (closed) { 209 throwClosed(); 210 } 211 212 is.reset(); 213 } 214 215 public boolean markSupported() { 216 return is.markSupported(); 217 } 218 219 private void throwClosed() throws IOException { 220 throw new IOException("stream closed"); 221 } 222 } 223 // END android-added 224 225 private class LimitedInputStream extends InputStream { 226 int bytesRemaining; 227 228 public LimitedInputStream(int length) { 229 bytesRemaining = length; 230 } 231 232 @Override 233 public void close() throws IOException { 234 if(bytesRemaining > 0) { 235 bytesRemaining = 0; 236 disconnect(true); // Should close the socket if client hasn't read all the data 237 } else { 238 disconnect(false); 239 } 240 /* 241 * if user has set useCache to true and cache exists, aborts it when 242 * closing 243 */ 244 if (useCaches && null != cacheRequest) { 245 cacheRequest.abort(); 246 } 247 } 248 249 @Override 250 public int available() throws IOException { 251 // BEGIN android-added 252 if (bytesRemaining <= 0) { 253 // There is nothing left to read, so don't bother asking "is". 254 return 0; 255 } 256 // END android-added 257 int result = is.available(); 258 if (result > bytesRemaining) { 259 return bytesRemaining; 260 } 261 return result; 262 } 263 264 @Override 265 public int read() throws IOException { 266 if (bytesRemaining <= 0) { 267 disconnect(false); 268 return -1; 269 } 270 int result = is.read(); 271 // if user has set useCache to true and cache exists, writes to 272 // cache 273 if (useCaches && null != cacheOut) { 274 cacheOut.write(result); 275 } 276 bytesRemaining--; 277 if (bytesRemaining <= 0) { 278 disconnect(false); 279 } 280 return result; 281 } 282 283 @Override 284 public int read(byte[] buf, int offset, int length) throws IOException { 285 // Force buf null check first, and avoid int overflow 286 if (offset < 0 || offset > buf.length) { 287 // K002e=Offset out of bounds \: {0} 288 throw new ArrayIndexOutOfBoundsException(Msg.getString("K002e", offset)); 289 } 290 if (length < 0 || buf.length - offset < length) { 291 // K0031=Length out of bounds \: {0} 292 throw new ArrayIndexOutOfBoundsException(Msg.getString("K0031", length)); 293 } 294 if (bytesRemaining <= 0) { 295 disconnect(false); 296 return -1; 297 } 298 if (length > bytesRemaining) { 299 length = bytesRemaining; 300 } 301 int result = is.read(buf, offset, length); 302 if (result > 0) { 303 bytesRemaining -= result; 304 // if user has set useCache to true and cache exists, writes to 305 // it 306 if (useCaches && null != cacheOut) { 307 cacheOut.write(buf, offset, result); 308 } 309 } 310 if (bytesRemaining <= 0) { 311 disconnect(false); 312 } 313 return result; 314 } 315 316 public long skip(int amount) throws IOException { 317 if (bytesRemaining <= 0) { 318 disconnect(false); 319 return -1; 320 } 321 if (amount > bytesRemaining) { 322 amount = bytesRemaining; 323 } 324 long result = is.skip(amount); 325 if (result > 0) { 326 bytesRemaining -= result; 327 } 328 if (bytesRemaining <= 0) { 329 disconnect(false); 330 } 331 return result; 332 } 333 } 334 335 private class ChunkedInputStream extends InputStream { 336 private int bytesRemaining = -1; 337 private boolean atEnd; 338 339 public ChunkedInputStream() throws IOException { 340 readChunkSize(); 341 } 342 343 @Override 344 public void close() throws IOException { 345 // BEGIN android-added 346 if (atEnd) { 347 return; 348 } 349 skipOutstandingChunks(); 350 // END android-added 351 352 // BEGIN android-note 353 // Removed "!atEnd" below because of the check added above. 354 // END android-note 355 if (available() > 0) { 356 disconnect(true); 357 } else { 358 disconnect(false); 359 } 360 atEnd = true; 361 // if user has set useCache to true and cache exists, abort 362 if (useCaches && null != cacheRequest) { 363 cacheRequest.abort(); 364 } 365 } 366 367 // BEGIN android-added 368 // If we're asked to close a stream with unread chunks, we need to skip them. 369 // Otherwise the next caller on this connection will receive that data. 370 // See: http://code.google.com/p/android/issues/detail?id=2939 371 private void skipOutstandingChunks() throws IOException { 372 while (!atEnd) { 373 while (bytesRemaining > 0) { 374 long skipped = is.skip(bytesRemaining); 375 bytesRemaining -= skipped; 376 } 377 readChunkSize(); 378 } 379 } 380 // END android-added 381 382 @Override 383 public int available() throws IOException { 384 // BEGIN android-added 385 if (atEnd) { 386 return 0; 387 } 388 // END android-added 389 390 int result = is.available(); 391 if (result > bytesRemaining) { 392 return bytesRemaining; 393 } 394 return result; 395 } 396 397 private void readChunkSize() throws IOException { 398 if (atEnd) { 399 return; 400 } 401 if (bytesRemaining == 0) { 402 readln(); // read CR/LF 403 } 404 String size = readln(); 405 int index = size.indexOf(";"); 406 if (index >= 0) { 407 size = size.substring(0, index); 408 } 409 bytesRemaining = Integer.parseInt(size.trim(), 16); 410 if (bytesRemaining == 0) { 411 atEnd = true; 412 // BEGIN android-note 413 // What is the point of calling readHeaders() here? 414 // END android-note 415 readHeaders(); 416 } 417 } 418 419 @Override 420 public int read() throws IOException { 421 if (bytesRemaining <= 0) { 422 readChunkSize(); 423 } 424 if (atEnd) { 425 disconnect(false); 426 return -1; 427 } 428 bytesRemaining--; 429 int result = is.read(); 430 // if user has set useCache to true and cache exists, write to cache 431 if (useCaches && null != cacheOut) { 432 cacheOut.write(result); 433 } 434 return result; 435 } 436 437 @Override 438 public int read(byte[] buf, int offset, int length) throws IOException { 439 // Force buf null check first, and avoid int overflow 440 if (offset > buf.length || offset < 0) { 441 // K002e=Offset out of bounds \: {0} 442 throw new ArrayIndexOutOfBoundsException(Msg.getString("K002e", offset)); 443 } 444 if (length < 0 || buf.length - offset < length) { 445 // K0031=Length out of bounds \: {0} 446 throw new ArrayIndexOutOfBoundsException(Msg.getString("K0031", length)); 447 } 448 if (bytesRemaining <= 0) { 449 readChunkSize(); 450 } 451 if (atEnd) { 452 disconnect(false); 453 return -1; 454 } 455 if (length > bytesRemaining) { 456 length = bytesRemaining; 457 } 458 int result = is.read(buf, offset, length); 459 if (result > 0) { 460 bytesRemaining -= result; 461 // if user has set useCache to true and cache exists, write to 462 // it 463 if (useCaches && null != cacheOut) { 464 cacheOut.write(buf, offset, result); 465 } 466 } 467 return result; 468 } 469 470 public long skip(int amount) throws IOException { 471 if (atEnd) { 472 // BEGIN android-deleted 473 // disconnect(false); 474 // END android-deleted 475 return -1; 476 } 477 if (bytesRemaining <= 0) { 478 readChunkSize(); 479 } 480 // BEGIN android-added 481 if (atEnd) { 482 disconnect(false); 483 return -1; 484 } 485 // END android-added 486 if (amount > bytesRemaining) { 487 amount = bytesRemaining; 488 } 489 long result = is.skip(amount); 490 if (result > 0) { 491 bytesRemaining -= result; 492 } 493 return result; 494 } 495 } 496 497 /** 498 * An HttpOutputStream used to implement setFixedLengthStreamingMode. 499 */ 500 private class FixedLengthHttpOutputStream extends HttpOutputStream { 501 private final int fixedLength; 502 private int actualLength; 503 504 public FixedLengthHttpOutputStream(int fixedLength) { 505 this.fixedLength = fixedLength; 506 } 507 508 @Override public void close() throws IOException { 509 if (closed) { 510 return; 511 } 512 closed = true; 513 socketOut.flush(); 514 if (actualLength != fixedLength) { 515 throw new IOException("actual length of " + actualLength + 516 " did not match declared fixed length of " + fixedLength); 517 } 518 } 519 520 @Override public void flush() throws IOException { 521 checkClosed(); 522 socketOut.flush(); 523 } 524 525 @Override public void write(byte[] buffer, int offset, int count) throws IOException { 526 checkClosed(); 527 if (buffer == null) { 528 throw new NullPointerException(); 529 } 530 if (offset < 0 || count < 0 || offset > buffer.length || buffer.length - offset < count) { 531 throw new ArrayIndexOutOfBoundsException(Msg.getString("K002f")); 532 } 533 checkSpace(count); 534 socketOut.write(buffer, offset, count); 535 actualLength += count; 536 } 537 538 @Override public void write(int oneByte) throws IOException { 539 checkClosed(); 540 checkSpace(1); 541 socketOut.write(oneByte); 542 ++actualLength; 543 } 544 545 @Override public int size() { 546 return fixedLength; 547 } 548 549 private void checkSpace(int byteCount) throws IOException { 550 if (actualLength + byteCount > fixedLength) { 551 throw new IOException("declared fixed content length of " + fixedLength + 552 " bytes exceeded"); 553 } 554 } 555 } 556 557 private abstract class HttpOutputStream extends OutputStream { 558 public boolean closed; 559 560 protected void checkClosed() throws IOException { 561 if (closed) { 562 throw new IOException(Msg.getString("K0059")); 563 } 564 } 565 566 public boolean isCached() { 567 return false; 568 } 569 570 public boolean isChunked() { 571 return false; 572 } 573 574 public void flushToSocket() throws IOException { 575 } 576 577 public abstract int size(); 578 } 579 580 private static final byte[] FINAL_CHUNK = new byte[] { '0', '\r', '\n', '\r', '\n' }; 581 582 // TODO: pull ChunkedHttpOutputStream out of here. 583 private class DefaultHttpOutputStream extends HttpOutputStream { 584 private int cacheLength; 585 private int defaultCacheSize = 1024; 586 private ByteArrayOutputStream cache; 587 private boolean writeToSocket; 588 private int limit; 589 590 public DefaultHttpOutputStream() { 591 cacheLength = defaultCacheSize; 592 cache = new ByteArrayOutputStream(cacheLength); 593 limit = -1; 594 } 595 596 public DefaultHttpOutputStream(int limit, int chunkLength) { 597 writeToSocket = true; 598 this.limit = limit; 599 if (limit > 0) { 600 cacheLength = limit; 601 } else { 602 // chunkLength must be larger than 3 603 if (chunkLength > 3) { 604 defaultCacheSize = chunkLength; 605 } 606 cacheLength = calculateChunkDataLength(); 607 } 608 cache = new ByteArrayOutputStream(cacheLength); 609 } 610 611 /** 612 * Calculates the exact size of chunk data, chunk data size is chunk 613 * size minus chunk head (which writes chunk data size in HEX and 614 * "\r\n") size. For example, a string "abcd" use chunk whose size is 5 615 * must be written to socket as "2\r\nab","2\r\ncd" ... 616 * 617 */ 618 private int calculateChunkDataLength() { 619 /* 620 * chunk head size is the hex string length of the cache size plus 2 621 * (which is the length of "\r\n"), it must be suitable to express 622 * the size of chunk data, as short as possible. Notices that 623 * according to RI, if chunklength is 19, chunk head length is 4 624 * (expressed as "10\r\n"), chunk data length is 16 (which real sum 625 * is 20,not 19); while if chunklength is 18, chunk head length is 626 * 3. Thus the cacheSize = chunkdataSize + sizeof(string length of 627 * chunk head in HEX) + sizeof("\r\n"); 628 */ 629 int bitSize = Integer.toHexString(defaultCacheSize).length(); 630 /* 631 * here is the calculated head size, not real size (for 19, it 632 * counts 3, not real size 4) 633 */ 634 int headSize = (Integer.toHexString(defaultCacheSize - bitSize - 2).length()) + 2; 635 return defaultCacheSize - headSize; 636 } 637 638 /** 639 * Equivalent to, but cheaper than, Integer.toHexString().getBytes(). 640 */ 641 private void writeHex(int i) throws IOException { 642 int cursor = 8; 643 do { 644 hex[--cursor] = HEX_DIGITS[i & 0xf]; 645 } while ((i >>>= 4) != 0); 646 socketOut.write(hex, cursor, 8 - cursor); 647 } 648 private byte[] hex = new byte[8]; 649 650 private void sendCache(boolean close) throws IOException { 651 int size = cache.size(); 652 if (size > 0 || close) { 653 if (limit < 0) { 654 if (size > 0) { 655 writeHex(size); 656 socketOut.write(CRLF); 657 cache.writeTo(socketOut); 658 cache.reset(); 659 socketOut.write(CRLF); 660 } 661 if (close) { 662 socketOut.write(FINAL_CHUNK); 663 } 664 } 665 } 666 } 667 668 @Override 669 public synchronized void flush() throws IOException { 670 checkClosed(); 671 if (writeToSocket) { 672 sendCache(false); 673 socketOut.flush(); 674 } 675 } 676 677 @Override 678 public void flushToSocket() throws IOException { 679 if (isCached()) { 680 cache.writeTo(socketOut); 681 } 682 } 683 684 @Override 685 public synchronized void close() throws IOException { 686 if (closed) { 687 return; 688 } 689 closed = true; 690 if (writeToSocket) { 691 if (limit > 0) { 692 throw new IOException(Msg.getString("K00a4")); 693 } 694 sendCache(closed); 695 } 696 // BEGIN android-added 697 /* 698 * Note: We don't disconnect here, since that will either 699 * cause the connection to be closed entirely or returned 700 * to the connection pool. In the former case, we simply 701 * won't be able to read the response at all. In the 702 * latter, we might end up trying to read the response 703 * while, meanwhile, the connection has been handed back 704 * out and is in use for another request. 705 */ 706 // END android-added 707 // BEGIN android-deleted 708 // disconnect(false); 709 // END android-deleted 710 } 711 712 @Override 713 public synchronized void write(int data) throws IOException { 714 checkClosed(); 715 if (limit >= 0) { 716 if (limit == 0) { 717 throw new IOException(Msg.getString("K00b2")); 718 } 719 limit--; 720 } 721 cache.write(data); 722 if (writeToSocket && cache.size() >= cacheLength) { 723 sendCache(false); 724 } 725 } 726 727 @Override 728 public synchronized void write(byte[] buffer, int offset, int count) throws IOException { 729 checkClosed(); 730 if (buffer == null) { 731 throw new NullPointerException(); 732 } 733 // avoid int overflow 734 if (offset < 0 || count < 0 || offset > buffer.length 735 || buffer.length - offset < count) { 736 throw new ArrayIndexOutOfBoundsException(Msg.getString("K002f")); 737 } 738 739 if (limit >= 0) { 740 if (count > limit) { 741 throw new IOException(Msg.getString("K00b2")); 742 } 743 limit -= count; 744 cache.write(buffer, offset, count); 745 if (limit == 0) { 746 cache.writeTo(socketOut); 747 } 748 } else { 749 if (!writeToSocket || cache.size() + count < cacheLength) { 750 cache.write(buffer, offset, count); 751 } else { 752 writeHex(cacheLength); 753 socketOut.write(CRLF); 754 int writeNum = cacheLength - cache.size(); 755 cache.write(buffer, offset, writeNum); 756 cache.writeTo(socketOut); 757 cache.reset(); 758 socketOut.write(CRLF); 759 int left = count - writeNum; 760 int position = offset + writeNum; 761 while (left > cacheLength) { 762 writeHex(cacheLength); 763 socketOut.write(CRLF); 764 socketOut.write(buffer, position, cacheLength); 765 socketOut.write(CRLF); 766 left = left - cacheLength; 767 position = position + cacheLength; 768 } 769 cache.write(buffer, position, left); 770 } 771 } 772 } 773 774 @Override 775 public synchronized int size() { 776 return cache.size(); 777 } 778 779 @Override public boolean isCached() { 780 return !writeToSocket; 781 } 782 783 @Override public boolean isChunked() { 784 return writeToSocket && limit == -1; 785 } 786 } 787 788 /** 789 * Creates an instance of the <code>HttpURLConnection</code> using default 790 * port 80. 791 * 792 * @param url 793 * URL The URL this connection is connecting 794 */ 795 protected HttpURLConnectionImpl(URL url) { 796 this(url, 80); 797 } 798 799 /** 800 * Creates an instance of the <code>HttpURLConnection</code> 801 * 802 * @param url 803 * URL The URL this connection is connecting 804 * @param port 805 * int The default connection port 806 */ 807 protected HttpURLConnectionImpl(URL url, int port) { 808 super(url); 809 defaultPort = port; 810 reqHeader = (Header) defaultReqHeader.clone(); 811 812 try { 813 uri = url.toURI(); 814 } catch (URISyntaxException e) { 815 // do nothing. 816 } 817 responseCache = AccessController 818 .doPrivileged(new PrivilegedAction<ResponseCache>() { 819 public ResponseCache run() { 820 return ResponseCache.getDefault(); 821 } 822 }); 823 } 824 825 /** 826 * Creates an instance of the <code>HttpURLConnection</code> 827 * 828 * @param url 829 * URL The URL this connection is connecting 830 * @param port 831 * int The default connection port 832 * @param proxy 833 * Proxy The proxy which is used to make the connection 834 */ 835 protected HttpURLConnectionImpl(URL url, int port, Proxy proxy) { 836 this(url, port); 837 this.proxy = proxy; 838 } 839 840 /** 841 * Establishes the connection to the remote HTTP server 842 * 843 * Any methods that requires a valid connection to the resource will call 844 * this method implicitly. After the connection is established, 845 * <code>connected</code> is set to true. 846 * 847 * 848 * @see #connected 849 * @see java.io.IOException 850 * @see URLStreamHandler 851 */ 852 @Override 853 public void connect() throws IOException { 854 if (connected) { 855 return; 856 } 857 if (getFromCache()) { 858 return; 859 } 860 // BEGIN android-changed 861 // url.toURI(); throws an URISyntaxException if the url contains 862 // illegal characters in e.g. the query. 863 // Since the query is not needed for proxy selection, we just create an 864 // URI that only contains the necessary information. 865 try { 866 uri = new URI(url.getProtocol(), 867 null, 868 url.getHost(), 869 url.getPort(), 870 null, 871 null, 872 null); 873 } catch (URISyntaxException e1) { 874 throw new IOException(e1.getMessage()); 875 } 876 // END android-changed 877 // socket to be used for connection 878 connection = null; 879 // try to determine: to use the proxy or not 880 if (proxy != null) { 881 // try to make the connection to the proxy 882 // specified in constructor. 883 // IOException will be thrown in the case of failure 884 connection = getHTTPConnection(proxy); 885 } else { 886 // Use system-wide ProxySelect to select proxy list, 887 // then try to connect via elements in the proxy list. 888 ProxySelector selector = ProxySelector.getDefault(); 889 List<Proxy> proxyList = selector.select(uri); 890 if (proxyList != null) { 891 for (Proxy selectedProxy : proxyList) { 892 if (selectedProxy.type() == Proxy.Type.DIRECT) { 893 // the same as NO_PROXY 894 continue; 895 } 896 try { 897 connection = getHTTPConnection(selectedProxy); 898 proxy = selectedProxy; 899 break; // connected 900 } catch (IOException e) { 901 // failed to connect, tell it to the selector 902 selector.connectFailed(uri, selectedProxy.address(), e); 903 } 904 } 905 } 906 } 907 if (connection == null) { 908 // make direct connection 909 connection = getHTTPConnection(null); 910 } 911 connection.setSoTimeout(getReadTimeout()); 912 setUpTransportIO(connection); 913 connected = true; 914 } 915 916 /** 917 * Returns connected socket to be used for this HTTP connection. 918 */ 919 protected HttpConnection getHTTPConnection(Proxy proxy) throws IOException { 920 HttpConfiguration configuration; 921 if (proxy == null || proxy.type() == Proxy.Type.DIRECT) { 922 this.proxy = null; // not using proxy 923 configuration = new HttpConfiguration(uri); 924 } else { 925 configuration = new HttpConfiguration(uri, proxy); 926 } 927 return HttpConnectionPool.INSTANCE.get(configuration, getConnectTimeout()); 928 } 929 930 /** 931 * Sets up the data streams used to send request[s] and read response[s]. 932 * 933 * @param connection 934 * HttpConnection to be used 935 */ 936 protected void setUpTransportIO(HttpConnection connection) throws IOException { 937 socketOut = connection.getOutputStream(); 938 is = connection.getInputStream(); 939 } 940 941 // Tries to get head and body from cache, return true if has got this time 942 // or 943 // already got before 944 private boolean getFromCache() throws IOException { 945 if (useCaches && null != responseCache && !hasTriedCache) { 946 hasTriedCache = true; 947 if (null == resHeader) { 948 resHeader = new Header(); 949 } 950 cacheResponse = responseCache.get(uri, method, resHeader 951 .getFieldMap()); 952 if (null != cacheResponse) { 953 Map<String, List<String>> headMap = cacheResponse.getHeaders(); 954 if (null != headMap) { 955 resHeader = new Header(headMap); 956 } 957 is = cacheResponse.getBody(); 958 if (null != is) { 959 return true; 960 } 961 } 962 } 963 if (hasTriedCache && null != is) { 964 return true; 965 } 966 return false; 967 } 968 969 // if user sets useCache to true, tries to put response to cache if cache 970 // exists 971 private void putToCache() throws IOException { 972 if (useCaches && null != responseCache) { 973 cacheRequest = responseCache.put(uri, this); 974 if (null != cacheRequest) { 975 cacheOut = cacheRequest.getBody(); 976 } 977 } 978 } 979 980 /** 981 * Closes the connection with the HTTP server 982 * 983 * 984 * @see URLConnection#connect() 985 */ 986 @Override 987 public void disconnect() { 988 disconnect(true); 989 } 990 991 // BEGIN android-changed 992 private synchronized void disconnect(boolean closeSocket) { 993 if (connection != null) { 994 if (closeSocket || ((os != null) && !os.closed)) { 995 /* 996 * In addition to closing the socket if explicitly 997 * requested to do so, we also close it if there was 998 * an output stream associated with the request and it 999 * wasn't cleanly closed. 1000 */ 1001 connection.closeSocketAndStreams(); 1002 } else { 1003 HttpConnectionPool.INSTANCE.recycle(connection); 1004 } 1005 connection = null; 1006 } 1007 1008 /* 1009 * Clear "is" and "os" to ensure that no further I/O attempts 1010 * from this instance make their way to the underlying 1011 * connection (which may get recycled). 1012 */ 1013 is = null; 1014 os = null; 1015 } 1016 // END android-changed 1017 1018 protected void endRequest() throws IOException { 1019 if (os != null) { 1020 os.close(); 1021 } 1022 sentRequest = false; 1023 } 1024 1025 /** 1026 * Returns the default value for the field specified by <code>field</code>, 1027 * null if there's no such a field. 1028 */ 1029 public static String getDefaultRequestProperty(String field) { 1030 return defaultReqHeader.get(field); 1031 } 1032 1033 /** 1034 * Returns an input stream from the server in the case of error such as the 1035 * requested file (txt, htm, html) is not found on the remote server. 1036 * <p> 1037 * If the content type is not what stated above, 1038 * <code>FileNotFoundException</code> is thrown. 1039 * 1040 * @return InputStream the error input stream returned by the server. 1041 */ 1042 @Override 1043 public InputStream getErrorStream() { 1044 if (connected && method != HEAD && responseCode >= HTTP_BAD_REQUEST) { 1045 return uis; 1046 } 1047 return null; 1048 } 1049 1050 /** 1051 * Returns the value of the field at position <code>pos<code>. 1052 * Returns <code>null</code> if there is fewer than <code>pos</code> fields 1053 * in the response header. 1054 * 1055 * @return java.lang.String The value of the field 1056 * @param pos int the position of the field from the top 1057 * 1058 * @see #getHeaderField(String) 1059 * @see #getHeaderFieldKey 1060 */ 1061 @Override 1062 public String getHeaderField(int pos) { 1063 try { 1064 getInputStream(); 1065 } catch (IOException e) { 1066 // ignore 1067 } 1068 if (null == resHeader) { 1069 return null; 1070 } 1071 return resHeader.get(pos); 1072 } 1073 1074 /** 1075 * Returns the value of the field corresponding to the <code>key</code> 1076 * Returns <code>null</code> if there is no such field. 1077 * 1078 * If there are multiple fields with that key, the last field value is 1079 * returned. 1080 * 1081 * @return java.lang.String The value of the header field 1082 * @param key 1083 * java.lang.String the name of the header field 1084 * 1085 * @see #getHeaderField(int) 1086 * @see #getHeaderFieldKey 1087 */ 1088 @Override 1089 public String getHeaderField(String key) { 1090 try { 1091 getInputStream(); 1092 } catch (IOException e) { 1093 // ignore 1094 } 1095 if (null == resHeader) { 1096 return null; 1097 } 1098 return resHeader.get(key); 1099 } 1100 1101 @Override 1102 public String getHeaderFieldKey(int pos) { 1103 try { 1104 getInputStream(); 1105 } catch (IOException e) { 1106 // ignore 1107 } 1108 if (null == resHeader) { 1109 return null; 1110 } 1111 return resHeader.getKey(pos); 1112 } 1113 1114 /** 1115 * Provides an unmodifiable map of the connection header values. The map 1116 * keys are the String header field names. Each map value is a list of the 1117 * header field values associated with that key name. 1118 * 1119 * @return the mapping of header field names to values 1120 * 1121 * @since 1.4 1122 */ 1123 @Override 1124 public Map<String, List<String>> getHeaderFields() { 1125 try { 1126 // ensure that resHeader exists 1127 getInputStream(); 1128 } catch (IOException e) { 1129 // ignore 1130 } 1131 if (null == resHeader) { 1132 return null; 1133 } 1134 return resHeader.getFieldMap(); 1135 } 1136 1137 @Override 1138 public Map<String, List<String>> getRequestProperties() { 1139 if (connected) { 1140 throw new IllegalStateException(Msg.getString("K0091")); 1141 } 1142 return reqHeader.getFieldMap(); 1143 } 1144 1145 @Override 1146 public InputStream getInputStream() throws IOException { 1147 if (!doInput) { 1148 throw new ProtocolException(Msg.getString("K008d")); 1149 } 1150 1151 // connect before sending requests 1152 connect(); 1153 doRequest(); 1154 1155 /* 1156 * if the requested file does not exist, throw an exception formerly the 1157 * Error page from the server was returned if the requested file was 1158 * text/html this has changed to return FileNotFoundException for all 1159 * file types 1160 */ 1161 if (responseCode >= HTTP_BAD_REQUEST) { 1162 throw new FileNotFoundException(url.toString()); 1163 } 1164 1165 return uis; 1166 } 1167 1168 private InputStream getContentStream() throws IOException { 1169 if (uis != null) { 1170 return uis; 1171 } 1172 1173 String encoding = resHeader.get("Transfer-Encoding"); 1174 if (encoding != null && encoding.toLowerCase().equals("chunked")) { 1175 return uis = new ChunkedInputStream(); 1176 } 1177 1178 String sLength = resHeader.get("Content-Length"); 1179 if (sLength != null) { 1180 try { 1181 int length = Integer.parseInt(sLength); 1182 return uis = new LimitedInputStream(length); 1183 } catch (NumberFormatException e) { 1184 } 1185 } 1186 1187 // BEGIN android-changed 1188 /* 1189 * Wrap the input stream from the HttpConnection (rather than 1190 * just returning "is" directly here), so that we can control 1191 * its use after the reference escapes. 1192 */ 1193 return uis = new LocalCloseInputStream(); 1194 // END android-changed 1195 } 1196 1197 @Override 1198 public OutputStream getOutputStream() throws IOException { 1199 if (!doOutput) { 1200 throw new ProtocolException(Msg.getString("K008e")); 1201 } 1202 1203 // you can't write after you read 1204 if (sentRequest) { 1205 throw new ProtocolException(Msg.getString("K0090")); 1206 } 1207 1208 if (os != null) { 1209 return os; 1210 } 1211 1212 // they are requesting a stream to write to. This implies a POST method 1213 if (method == GET) { 1214 method = POST; 1215 } 1216 1217 // If the request method is neither PUT or POST, then you're not writing 1218 if (method != PUT && method != POST) { 1219 throw new ProtocolException(Msg.getString("K008f", method)); 1220 } 1221 1222 int limit = -1; 1223 String contentLength = reqHeader.get("Content-Length"); 1224 if (contentLength != null) { 1225 limit = Integer.parseInt(contentLength); 1226 } 1227 1228 String encoding = reqHeader.get("Transfer-Encoding"); 1229 if (httpVersion > 0 && encoding != null) { 1230 encoding = encoding.toLowerCase(); 1231 if ("chunked".equals(encoding)) { 1232 sendChunked = true; 1233 limit = -1; 1234 } 1235 } 1236 // if user has set chunk/fixedLength mode, use that value 1237 if (chunkLength > 0) { 1238 sendChunked = true; 1239 limit = -1; 1240 } 1241 if (fixedContentLength >= 0) { 1242 os = new FixedLengthHttpOutputStream(fixedContentLength); 1243 doRequest(); 1244 return os; 1245 } 1246 if ((httpVersion > 0 && sendChunked) || limit >= 0) { 1247 os = new DefaultHttpOutputStream(limit, chunkLength); 1248 doRequest(); 1249 return os; 1250 } 1251 if (!connected) { 1252 // connect and see if there is cache available. 1253 connect(); 1254 } 1255 return os = new DefaultHttpOutputStream(); 1256 } 1257 1258 @Override 1259 public Permission getPermission() throws IOException { 1260 return new SocketPermission(getHostName() + ":" + getHostPort(), "connect, resolve"); 1261 } 1262 1263 @Override 1264 public String getRequestProperty(String field) { 1265 if (null == field) { 1266 return null; 1267 } 1268 return reqHeader.get(field); 1269 } 1270 1271 /** 1272 * Returns a line read from the input stream. Does not include the \n 1273 * 1274 * @return The line that was read. 1275 */ 1276 String readln() throws IOException { 1277 boolean lastCr = false; 1278 StringBuilder result = new StringBuilder(80); 1279 int c = is.read(); 1280 if (c < 0) { 1281 return null; 1282 } 1283 while (c != '\n') { 1284 if (lastCr) { 1285 result.append('\r'); 1286 lastCr = false; 1287 } 1288 if (c == '\r') { 1289 lastCr = true; 1290 } else { 1291 result.append((char) c); 1292 } 1293 c = is.read(); 1294 if (c < 0) { 1295 break; 1296 } 1297 } 1298 return result.toString(); 1299 } 1300 1301 protected String requestString() { 1302 if (usingProxy() || proxyName != null) { 1303 return url.toString(); 1304 } 1305 String file = url.getFile(); 1306 if (file == null || file.length() == 0) { 1307 file = "/"; 1308 } 1309 return file; 1310 } 1311 1312 /** 1313 * Sends the request header to the remote HTTP server Not all of them are 1314 * guaranteed to have any effect on the content the server will return, 1315 * depending on if the server supports that field. 1316 * 1317 * Examples : Accept: text/*, text/html, text/html;level=1, Accept-Charset: 1318 * iso-8859-5, unicode-1-1;q=0.8 1319 */ 1320 private boolean sendRequest() throws IOException { 1321 byte[] request = createRequest(); 1322 1323 // make sure we have a connection 1324 if (!connected) { 1325 connect(); 1326 } 1327 if (null != cacheResponse) { 1328 // does not send if already has a response cache 1329 return true; 1330 } 1331 // send out the HTTP request 1332 socketOut.write(request); 1333 sentRequest = true; 1334 // send any output to the socket (i.e. POST data) 1335 if (os != null) { 1336 os.flushToSocket(); 1337 } 1338 if (os == null || os.isCached()) { 1339 readServerResponse(); 1340 return true; 1341 } 1342 return false; 1343 } 1344 1345 void readServerResponse() throws IOException { 1346 socketOut.flush(); 1347 do { 1348 responseCode = -1; 1349 responseMessage = null; 1350 resHeader = new Header(); 1351 String line = readln(); 1352 // Add the response, it may contain ':' which we ignore 1353 if (line != null) { 1354 resHeader.setStatusLine(line.trim()); 1355 readHeaders(); 1356 } 1357 } while (getResponseCode() == 100); 1358 1359 if (method == HEAD || (responseCode >= 100 && responseCode < 200) 1360 || responseCode == HTTP_NO_CONTENT 1361 || responseCode == HTTP_NOT_MODIFIED) { 1362 disconnect(); 1363 uis = new LimitedInputStream(0); 1364 } 1365 putToCache(); 1366 } 1367 1368 @Override 1369 public int getResponseCode() throws IOException { 1370 // Response Code Sample : "HTTP/1.0 200 OK" 1371 1372 // Call connect() first since getHeaderField() doesn't return exceptions 1373 connect(); 1374 doRequest(); 1375 if (responseCode != -1) { 1376 return responseCode; 1377 } 1378 String response = resHeader.getStatusLine(); 1379 if (response == null || !response.startsWith("HTTP/")) { 1380 return -1; 1381 } 1382 response = response.trim(); 1383 int mark = response.indexOf(" ") + 1; 1384 if (mark == 0) { 1385 return -1; 1386 } 1387 if (response.charAt(mark - 2) != '1') { 1388 httpVersion = 0; 1389 } 1390 int last = mark + 3; 1391 if (last > response.length()) { 1392 last = response.length(); 1393 } 1394 responseCode = Integer.parseInt(response.substring(mark, last)); 1395 if (last + 1 <= response.length()) { 1396 responseMessage = response.substring(last + 1); 1397 } 1398 return responseCode; 1399 } 1400 1401 void readHeaders() throws IOException { 1402 // parse the result headers until the first blank line 1403 String line; 1404 while (((line = readln()) != null) && (line.length() > 1)) { 1405 // Header parsing 1406 int idx; 1407 if ((idx = line.indexOf(":")) < 0) { 1408 resHeader.add("", line.trim()); 1409 } else { 1410 resHeader.add(line.substring(0, idx), line.substring(idx + 1).trim()); 1411 } 1412 } 1413 } 1414 1415 private byte[] createRequest() throws IOException { 1416 StringBuilder output = new StringBuilder(256); 1417 output.append(method); 1418 output.append(' '); 1419 output.append(requestString()); 1420 output.append(' '); 1421 output.append("HTTP/1."); 1422 if (httpVersion == 0) { 1423 output.append("0\r\n"); 1424 } else { 1425 output.append("1\r\n"); 1426 } 1427 // add user-specified request headers if any 1428 boolean hasContentLength = false; 1429 for (int i = 0; i < reqHeader.length(); i++) { 1430 String key = reqHeader.getKey(i); 1431 if (key != null) { 1432 String lKey = key.toLowerCase(); 1433 if ((os != null && !os.isChunked()) 1434 || (!lKey.equals("transfer-encoding") && !lKey.equals("content-length"))) { 1435 output.append(key); 1436 String value = reqHeader.get(i); 1437 /* 1438 * duplicates are allowed under certain conditions see 1439 * http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 1440 */ 1441 if (lKey.equals("content-length")) { 1442 hasContentLength = true; 1443 /* 1444 * if both setFixedLengthStreamingMode and 1445 * content-length are set, use fixedContentLength first 1446 */ 1447 if(fixedContentLength >= 0){ 1448 value = String.valueOf(fixedContentLength); 1449 } 1450 } 1451 if (value != null) { 1452 output.append(": "); 1453 output.append(value); 1454 } 1455 output.append("\r\n"); 1456 } 1457 } 1458 } 1459 if (fixedContentLength >= 0 && !hasContentLength) { 1460 output.append("content-length: "); 1461 output.append(String.valueOf(fixedContentLength)); 1462 output.append("\r\n"); 1463 } 1464 1465 if (reqHeader.get("User-Agent") == null) { 1466 output.append("User-Agent: "); 1467 String agent = getSystemProperty("http.agent"); 1468 if (agent == null) { 1469 output.append("Java"); 1470 output.append(getSystemProperty("java.version")); 1471 } else { 1472 output.append(agent); 1473 } 1474 output.append("\r\n"); 1475 } 1476 if (reqHeader.get("Host") == null) { 1477 output.append("Host: "); 1478 output.append(url.getHost()); 1479 int port = url.getPort(); 1480 if (port > 0 && port != defaultPort) { 1481 output.append(':'); 1482 output.append(Integer.toString(port)); 1483 } 1484 output.append("\r\n"); 1485 } 1486 // BEGIN android-removed 1487 // there's no utility in sending an "accept everything" header "*/*" 1488 // if (reqHeader.get("Accept") == null) { 1489 // } 1490 // END android-removed 1491 if (httpVersion > 0 && reqHeader.get("Connection") == null) { 1492 output.append("Connection: Keep-Alive\r\n"); 1493 } 1494 1495 // if we are doing output make sure the appropriate headers are sent 1496 if (os != null) { 1497 if (reqHeader.get("Content-Type") == null) { 1498 output.append("Content-Type: application/x-www-form-urlencoded\r\n"); 1499 } 1500 if (os.isCached()) { 1501 if (reqHeader.get("Content-Length") == null) { 1502 output.append("Content-Length: "); 1503 output.append(Integer.toString(os.size())); 1504 output.append("\r\n"); 1505 } 1506 } else if (os.isChunked()) { 1507 output.append("Transfer-Encoding: chunked\r\n"); 1508 } 1509 } 1510 // end the headers 1511 output.append("\r\n"); 1512 return output.toString().getBytes("ISO8859_1"); 1513 } 1514 1515 /** 1516 * Sets the default request header fields to be sent to the remote server. 1517 * This does not affect the current URL Connection, only newly created ones. 1518 * 1519 * @param field 1520 * java.lang.String The name of the field to be changed 1521 * @param value 1522 * java.lang.String The new value of the field 1523 */ 1524 public static void setDefaultRequestProperty(String field, String value) { 1525 defaultReqHeader.add(field, value); 1526 } 1527 1528 /** 1529 * A slightly different implementation from this parent's 1530 * <code>setIfModifiedSince()</code> Since this HTTP impl supports 1531 * IfModifiedSince as one of the header field, the request header is updated 1532 * with the new value. 1533 * 1534 * 1535 * @param newValue 1536 * the number of millisecond since epoch 1537 * 1538 * @throws IllegalStateException 1539 * if already connected. 1540 */ 1541 @Override 1542 public void setIfModifiedSince(long newValue) { 1543 super.setIfModifiedSince(newValue); 1544 // convert from millisecond since epoch to date string 1545 SimpleDateFormat sdf = new SimpleDateFormat("E, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US); 1546 sdf.setTimeZone(TimeZone.getTimeZone("GMT")); 1547 String date = sdf.format(new Date(newValue)); 1548 reqHeader.add("If-Modified-Since", date); 1549 } 1550 1551 @Override 1552 public void setRequestProperty(String field, String newValue) { 1553 if (connected) { 1554 throw new IllegalStateException(Msg.getString("K0092")); 1555 } 1556 if (field == null) { 1557 throw new NullPointerException(); 1558 } 1559 reqHeader.set(field, newValue); 1560 } 1561 1562 @Override 1563 public void addRequestProperty(String field, String value) { 1564 if (connected) { 1565 throw new IllegalAccessError(Msg.getString("K0092")); 1566 } 1567 if (field == null) { 1568 throw new NullPointerException(); 1569 } 1570 reqHeader.add(field, value); 1571 } 1572 1573 /** 1574 * Get the connection port. This is either the URL's port or the proxy port 1575 * if a proxy port has been set. 1576 */ 1577 private int getHostPort() { 1578 if (hostPort < 0) { 1579 // the value was not set yet 1580 if (proxy != null) { 1581 hostPort = ((InetSocketAddress) proxy.address()).getPort(); 1582 } else { 1583 hostPort = url.getPort(); 1584 } 1585 if (hostPort < 0) { 1586 hostPort = defaultPort; 1587 } 1588 } 1589 return hostPort; 1590 } 1591 1592 /** 1593 * Get the InetAddress of the connection machine. This is either the address 1594 * given in the URL or the address of the proxy server. 1595 */ 1596 private InetAddress getHostAddress() throws IOException { 1597 if (hostAddress == null) { 1598 // the value was not set yet 1599 if (proxy != null && proxy.type() != Proxy.Type.DIRECT) { 1600 hostAddress = ((InetSocketAddress) proxy.address()) 1601 .getAddress(); 1602 } else { 1603 hostAddress = InetAddress.getByName(url.getHost()); 1604 } 1605 } 1606 return hostAddress; 1607 } 1608 1609 /** 1610 * Get the hostname of the connection machine. This is either the name given 1611 * in the URL or the name of the proxy server. 1612 */ 1613 private String getHostName() { 1614 if (hostName == null) { 1615 // the value was not set yet 1616 if (proxy != null) { 1617 hostName = ((InetSocketAddress) proxy.address()).getHostName(); 1618 } else { 1619 hostName = url.getHost(); 1620 } 1621 } 1622 return hostName; 1623 } 1624 1625 private String getSystemProperty(final String property) { 1626 return AccessController.doPrivileged(new PriviAction<String>(property)); 1627 } 1628 1629 @Override 1630 public boolean usingProxy() { 1631 return (proxy != null && proxy.type() != Proxy.Type.DIRECT); 1632 } 1633 1634 /** 1635 * Handles an HTTP request along with its redirects and authentication 1636 */ 1637 protected void doRequest() throws IOException { 1638 // do nothing if we've already sent the request 1639 if (sentRequest) { 1640 // If necessary, finish the request by 1641 // closing the uncached output stream. 1642 if (resHeader == null && os != null) { 1643 os.close(); 1644 readServerResponse(); 1645 getContentStream(); 1646 } 1647 return; 1648 } 1649 doRequestInternal(); 1650 } 1651 1652 void doRequestInternal() throws IOException { 1653 int redirect = 0; 1654 while (true) { 1655 // send the request and process the results 1656 if (!sendRequest()) { 1657 return; 1658 } 1659 // proxy authorization failed ? 1660 if (responseCode == HTTP_PROXY_AUTH) { 1661 if (!usingProxy()) { 1662 // KA017=Received HTTP_PROXY_AUTH (407) code while not using 1663 // proxy 1664 throw new IOException(Msg.getString("KA017")); 1665 } 1666 // username/password 1667 // until authorized 1668 String challenge = resHeader.get("Proxy-Authenticate"); 1669 if (challenge == null) { 1670 // KA016=Received authentication challenge is null. 1671 throw new IOException(Msg.getString("KA016")); 1672 } 1673 // drop everything and reconnect, might not be required for 1674 // HTTP/1.1 1675 endRequest(); 1676 disconnect(); 1677 connected = false; 1678 String credentials = getAuthorizationCredentials(challenge); 1679 if (credentials == null) { 1680 // could not find credentials, end request cycle 1681 break; 1682 } 1683 // set up the authorization credentials 1684 setRequestProperty("Proxy-Authorization", credentials); 1685 // continue to send request 1686 continue; 1687 } 1688 // HTTP authorization failed ? 1689 if (responseCode == HTTP_UNAUTHORIZED) { 1690 // keep asking for username/password until authorized 1691 String challenge = resHeader.get("WWW-Authenticate"); 1692 if (challenge == null) { 1693 // KA018=Received authentication challenge is null 1694 throw new IOException(Msg.getString("KA018")); 1695 } 1696 // drop everything and reconnect, might not be required for 1697 // HTTP/1.1 1698 endRequest(); 1699 disconnect(); 1700 connected = false; 1701 String credentials = getAuthorizationCredentials(challenge); 1702 if (credentials == null) { 1703 // could not find credentials, end request cycle 1704 break; 1705 } 1706 // set up the authorization credentials 1707 setRequestProperty("Authorization", credentials); 1708 // continue to send request 1709 continue; 1710 } 1711 /* 1712 * See if there is a server redirect to the URL, but only handle 1 1713 * level of URL redirection from the server to avoid being caught in 1714 * an infinite loop 1715 */ 1716 if (getInstanceFollowRedirects()) { 1717 if ((responseCode == HTTP_MULT_CHOICE 1718 || responseCode == HTTP_MOVED_PERM 1719 || responseCode == HTTP_MOVED_TEMP 1720 || responseCode == HTTP_SEE_OTHER || responseCode == HTTP_USE_PROXY) 1721 && os == null) { 1722 1723 if (++redirect > 4) { 1724 throw new ProtocolException(Msg.getString("K0093")); 1725 } 1726 String location = getHeaderField("Location"); 1727 if (location != null) { 1728 // start over 1729 if (responseCode == HTTP_USE_PROXY) { 1730 int start = 0; 1731 if (location.startsWith(url.getProtocol() + ':')) { 1732 start = url.getProtocol().length() + 1; 1733 } 1734 if (location.startsWith("//", start)) { 1735 start += 2; 1736 } 1737 setProxy(location.substring(start)); 1738 } else { 1739 url = new URL(url, location); 1740 hostName = url.getHost(); 1741 // update the port 1742 hostPort = -1; 1743 } 1744 endRequest(); 1745 disconnect(); 1746 connected = false; 1747 continue; 1748 } 1749 } 1750 } 1751 break; 1752 } 1753 // Cache the content stream and read the first chunked header 1754 getContentStream(); 1755 } 1756 1757 /** 1758 * Returns the authorization credentials on the base of provided 1759 * authorization challenge 1760 * 1761 * @param challenge 1762 * @return authorization credentials 1763 * @throws IOException 1764 */ 1765 private String getAuthorizationCredentials(String challenge) 1766 throws IOException { 1767 1768 int idx = challenge.indexOf(" "); 1769 String scheme = challenge.substring(0, idx); 1770 int realm = challenge.indexOf("realm=\"") + 7; 1771 String prompt = null; 1772 if (realm != -1) { 1773 int end = challenge.indexOf('"', realm); 1774 if (end != -1) { 1775 prompt = challenge.substring(realm, end); 1776 } 1777 } 1778 // The following will use the user-defined authenticator to get 1779 // the password 1780 PasswordAuthentication pa = Authenticator 1781 .requestPasswordAuthentication(getHostAddress(), getHostPort(), 1782 url.getProtocol(), prompt, scheme); 1783 if (pa == null) { 1784 // could not retrieve the credentials 1785 return null; 1786 } 1787 // base64 encode the username and password 1788 byte[] bytes = (pa.getUserName() + ":" + new String(pa.getPassword())) 1789 .getBytes("ISO8859_1"); 1790 String encoded = Base64.encode(bytes, "ISO8859_1"); 1791 return scheme + " " + encoded; 1792 } 1793 1794 private void setProxy(String proxy) { 1795 int index = proxy.indexOf(':'); 1796 if (index == -1) { 1797 proxyName = proxy; 1798 hostPort = defaultPort; 1799 } else { 1800 proxyName = proxy.substring(0, index); 1801 String port = proxy.substring(index + 1); 1802 try { 1803 hostPort = Integer.parseInt(port); 1804 } catch (NumberFormatException e) { 1805 throw new IllegalArgumentException(Msg.getString("K00af", port)); 1806 } 1807 if (hostPort < 0 || hostPort > 65535) { 1808 throw new IllegalArgumentException(Msg.getString("K00b0")); 1809 } 1810 } 1811 } 1812 } 1813