Home | History | Annotate | Download | only in http
      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