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 libcore.net.http; 19 20 import java.io.BufferedInputStream; 21 import java.io.IOException; 22 import java.io.InputStream; 23 import java.io.OutputStream; 24 import java.net.InetAddress; 25 import java.net.InetSocketAddress; 26 import java.net.Proxy; 27 import java.net.ProxySelector; 28 import java.net.Socket; 29 import java.net.SocketAddress; 30 import java.net.SocketException; 31 import java.net.URI; 32 import java.net.UnknownHostException; 33 import java.util.List; 34 import javax.net.ssl.HostnameVerifier; 35 import javax.net.ssl.SSLSocket; 36 import javax.net.ssl.SSLSocketFactory; 37 import libcore.io.IoUtils; 38 import libcore.util.Objects; 39 import org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl; 40 41 /** 42 * Holds the sockets and streams of an HTTP or HTTPS connection, which may be 43 * used for multiple HTTP request/response exchanges. Connections may be direct 44 * to the origin server or via a proxy. Create an instance using the {@link 45 * Address} inner class. 46 * 47 * <p>Do not confuse this class with the misnamed {@code HttpURLConnection}, 48 * which isn't so much a connection as a single request/response pair. 49 */ 50 final class HttpConnection { 51 private final Address address; 52 private final Socket socket; 53 private InputStream inputStream; 54 private OutputStream outputStream; 55 private SSLSocket unverifiedSocket; 56 private SSLSocket sslSocket; 57 private InputStream sslInputStream; 58 private OutputStream sslOutputStream; 59 private boolean recycled = false; 60 HttpConnection(Address config, int connectTimeout)61 private HttpConnection(Address config, int connectTimeout) throws IOException { 62 this.address = config; 63 64 /* 65 * Try each of the host's addresses for best behavior in mixed IPv4/IPv6 66 * environments. See http://b/2876927 67 * TODO: add a hidden method so that Socket.tryAllAddresses can does this for us 68 */ 69 Socket socketCandidate = null; 70 InetAddress[] addresses = InetAddress.getAllByName(config.socketHost); 71 for (int i = 0; i < addresses.length; i++) { 72 socketCandidate = (config.proxy != null && config.proxy.type() != Proxy.Type.HTTP) 73 ? new Socket(config.proxy) 74 : new Socket(); 75 try { 76 socketCandidate.connect( 77 new InetSocketAddress(addresses[i], config.socketPort), connectTimeout); 78 break; 79 } catch (IOException e) { 80 if (i == addresses.length - 1) { 81 throw e; 82 } 83 } 84 } 85 86 this.socket = socketCandidate; 87 } 88 connect(URI uri, SSLSocketFactory sslSocketFactory, Proxy proxy, boolean requiresTunnel, int connectTimeout)89 public static HttpConnection connect(URI uri, SSLSocketFactory sslSocketFactory, 90 Proxy proxy, boolean requiresTunnel, int connectTimeout) throws IOException { 91 /* 92 * Try an explicitly-specified proxy. 93 */ 94 if (proxy != null) { 95 Address address = (proxy.type() == Proxy.Type.DIRECT) 96 ? new Address(uri, sslSocketFactory) 97 : new Address(uri, sslSocketFactory, proxy, requiresTunnel); 98 return HttpConnectionPool.INSTANCE.get(address, connectTimeout); 99 } 100 101 /* 102 * Try connecting to each of the proxies provided by the ProxySelector 103 * until a connection succeeds. 104 */ 105 ProxySelector selector = ProxySelector.getDefault(); 106 List<Proxy> proxyList = selector.select(uri); 107 if (proxyList != null) { 108 for (Proxy selectedProxy : proxyList) { 109 if (selectedProxy.type() == Proxy.Type.DIRECT) { 110 // the same as NO_PROXY 111 // TODO: if the selector recommends a direct connection, attempt that? 112 continue; 113 } 114 try { 115 Address address = new Address(uri, sslSocketFactory, 116 selectedProxy, requiresTunnel); 117 return HttpConnectionPool.INSTANCE.get(address, connectTimeout); 118 } catch (IOException e) { 119 // failed to connect, tell it to the selector 120 selector.connectFailed(uri, selectedProxy.address(), e); 121 } 122 } 123 } 124 125 /* 126 * Try a direct connection. If this fails, this method will throw. 127 */ 128 return HttpConnectionPool.INSTANCE.get(new Address(uri, sslSocketFactory), connectTimeout); 129 } 130 closeSocketAndStreams()131 public void closeSocketAndStreams() { 132 IoUtils.closeQuietly(sslOutputStream); 133 IoUtils.closeQuietly(sslInputStream); 134 IoUtils.closeQuietly(sslSocket); 135 IoUtils.closeQuietly(outputStream); 136 IoUtils.closeQuietly(inputStream); 137 IoUtils.closeQuietly(socket); 138 } 139 setSoTimeout(int readTimeout)140 public void setSoTimeout(int readTimeout) throws SocketException { 141 socket.setSoTimeout(readTimeout); 142 } 143 getOutputStream()144 public OutputStream getOutputStream() throws IOException { 145 if (sslSocket != null) { 146 if (sslOutputStream == null) { 147 sslOutputStream = sslSocket.getOutputStream(); 148 } 149 return sslOutputStream; 150 } else if(outputStream == null) { 151 outputStream = socket.getOutputStream(); 152 } 153 return outputStream; 154 } 155 getInputStream()156 public InputStream getInputStream() throws IOException { 157 if (sslSocket != null) { 158 if (sslInputStream == null) { 159 sslInputStream = sslSocket.getInputStream(); 160 } 161 return sslInputStream; 162 } else if (inputStream == null) { 163 /* 164 * Buffer the socket stream to permit efficient parsing of HTTP 165 * headers and chunk sizes. Benchmarks suggest 128 is sufficient. 166 * We cannot buffer when setting up a tunnel because we may consume 167 * bytes intended for the SSL socket. 168 */ 169 int bufferSize = 128; 170 inputStream = address.requiresTunnel 171 ? socket.getInputStream() 172 : new BufferedInputStream(socket.getInputStream(), bufferSize); 173 } 174 return inputStream; 175 } 176 getSocket()177 protected Socket getSocket() { 178 return sslSocket != null ? sslSocket : socket; 179 } 180 getAddress()181 public Address getAddress() { 182 return address; 183 } 184 185 /** 186 * Create an {@code SSLSocket} and perform the SSL handshake 187 * (performing certificate validation. 188 * 189 * @param sslSocketFactory Source of new {@code SSLSocket} instances. 190 * @param tlsTolerant If true, assume server can handle common 191 * TLS extensions and SSL deflate compression. If false, use 192 * an SSL3 only fallback mode without compression. 193 */ setupSecureSocket(SSLSocketFactory sslSocketFactory, boolean tlsTolerant)194 public void setupSecureSocket(SSLSocketFactory sslSocketFactory, boolean tlsTolerant) 195 throws IOException { 196 // create the wrapper over connected socket 197 unverifiedSocket = (SSLSocket) sslSocketFactory.createSocket(socket, 198 address.uriHost, address.uriPort, true /* autoClose */); 199 // tlsTolerant mimics Chrome's behavior 200 if (tlsTolerant && unverifiedSocket instanceof OpenSSLSocketImpl) { 201 OpenSSLSocketImpl openSslSocket = (OpenSSLSocketImpl) unverifiedSocket; 202 openSslSocket.setEnabledCompressionMethods(new String[] { "ZLIB"}); 203 openSslSocket.setUseSessionTickets(true); 204 openSslSocket.setHostname(address.socketHost); 205 // use SSLSocketFactory default enabled protocols 206 } else { 207 unverifiedSocket.setEnabledProtocols(new String [] { "SSLv3" }); 208 } 209 // force handshake, which can throw 210 unverifiedSocket.startHandshake(); 211 } 212 213 /** 214 * Return an {@code SSLSocket} that is not only connected but has 215 * also passed hostname verification. 216 * 217 * @param hostnameVerifier Used to verify the hostname we 218 * connected to is an acceptable match for the peer certificate 219 * chain of the SSLSession. 220 */ verifySecureSocketHostname(HostnameVerifier hostnameVerifier)221 public SSLSocket verifySecureSocketHostname(HostnameVerifier hostnameVerifier) 222 throws IOException { 223 if (!hostnameVerifier.verify(address.uriHost, unverifiedSocket.getSession())) { 224 throw new IOException("Hostname '" + address.uriHost + "' was not verified"); 225 } 226 sslSocket = unverifiedSocket; 227 return sslSocket; 228 } 229 230 /** 231 * Return an {@code SSLSocket} if already connected, otherwise null. 232 */ getSecureSocketIfConnected()233 public SSLSocket getSecureSocketIfConnected() { 234 return sslSocket; 235 } 236 237 /** 238 * Returns true if this connection has been used to satisfy an earlier 239 * HTTP request/response pair. 240 */ isRecycled()241 public boolean isRecycled() { 242 return recycled; 243 } 244 setRecycled()245 public void setRecycled() { 246 this.recycled = true; 247 } 248 249 /** 250 * Returns true if this connection is eligible to be reused for another 251 * request/response pair. 252 */ isEligibleForRecycling()253 protected boolean isEligibleForRecycling() { 254 return !socket.isClosed() 255 && !socket.isInputShutdown() 256 && !socket.isOutputShutdown(); 257 } 258 259 /** 260 * This address has two parts: the address we connect to directly and the 261 * origin address of the resource. These are the same unless a proxy is 262 * being used. It also includes the SSL socket factory so that a socket will 263 * not be reused if its SSL configuration is different. 264 */ 265 public static final class Address { 266 private final Proxy proxy; 267 private final boolean requiresTunnel; 268 private final String uriHost; 269 private final int uriPort; 270 private final String socketHost; 271 private final int socketPort; 272 private final SSLSocketFactory sslSocketFactory; 273 Address(URI uri, SSLSocketFactory sslSocketFactory)274 public Address(URI uri, SSLSocketFactory sslSocketFactory) throws UnknownHostException { 275 this.proxy = null; 276 this.requiresTunnel = false; 277 this.uriHost = uri.getHost(); 278 this.uriPort = uri.getEffectivePort(); 279 this.sslSocketFactory = sslSocketFactory; 280 this.socketHost = uriHost; 281 this.socketPort = uriPort; 282 if (uriHost == null) { 283 throw new UnknownHostException(uri.toString()); 284 } 285 } 286 287 /** 288 * @param requiresTunnel true if the HTTP connection needs to tunnel one 289 * protocol over another, such as when using HTTPS through an HTTP 290 * proxy. When doing so, we must avoid buffering bytes intended for 291 * the higher-level protocol. 292 */ Address(URI uri, SSLSocketFactory sslSocketFactory, Proxy proxy, boolean requiresTunnel)293 public Address(URI uri, SSLSocketFactory sslSocketFactory, 294 Proxy proxy, boolean requiresTunnel) throws UnknownHostException { 295 this.proxy = proxy; 296 this.requiresTunnel = requiresTunnel; 297 this.uriHost = uri.getHost(); 298 this.uriPort = uri.getEffectivePort(); 299 this.sslSocketFactory = sslSocketFactory; 300 301 SocketAddress proxyAddress = proxy.address(); 302 if (!(proxyAddress instanceof InetSocketAddress)) { 303 throw new IllegalArgumentException("Proxy.address() is not an InetSocketAddress: " 304 + proxyAddress.getClass()); 305 } 306 InetSocketAddress proxySocketAddress = (InetSocketAddress) proxyAddress; 307 this.socketHost = proxySocketAddress.getHostName(); 308 this.socketPort = proxySocketAddress.getPort(); 309 if (uriHost == null) { 310 throw new UnknownHostException(uri.toString()); 311 } 312 } 313 getProxy()314 public Proxy getProxy() { 315 return proxy; 316 } 317 equals(Object other)318 @Override public boolean equals(Object other) { 319 if (other instanceof Address) { 320 Address that = (Address) other; 321 return Objects.equal(this.proxy, that.proxy) 322 && this.uriHost.equals(that.uriHost) 323 && this.uriPort == that.uriPort 324 && Objects.equal(this.sslSocketFactory, that.sslSocketFactory) 325 && this.requiresTunnel == that.requiresTunnel; 326 } 327 return false; 328 } 329 hashCode()330 @Override public int hashCode() { 331 int result = 17; 332 result = 31 * result + uriHost.hashCode(); 333 result = 31 * result + uriPort; 334 result = 31 * result + (sslSocketFactory != null ? sslSocketFactory.hashCode() : 0); 335 result = 31 * result + (proxy != null ? proxy.hashCode() : 0); 336 result = 31 * result + (requiresTunnel ? 1 : 0); 337 return result; 338 } 339 connect(int connectTimeout)340 public HttpConnection connect(int connectTimeout) throws IOException { 341 return new HttpConnection(this, connectTimeout); 342 } 343 } 344 } 345