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 package libcore.net.http; 18 19 import com.squareup.okhttp.OkHttpConnection; 20 import com.squareup.okhttp.OkHttpsConnection; 21 import java.io.IOException; 22 import java.io.InputStream; 23 import java.io.OutputStream; 24 import java.net.CacheResponse; 25 import java.net.ProtocolException; 26 import java.net.Proxy; 27 import java.net.SecureCacheResponse; 28 import java.net.URL; 29 import java.security.Permission; 30 import java.security.Principal; 31 import java.security.cert.Certificate; 32 import java.security.cert.CertificateException; 33 import java.util.List; 34 import java.util.Map; 35 import javax.net.ssl.SSLHandshakeException; 36 import javax.net.ssl.SSLPeerUnverifiedException; 37 import javax.net.ssl.SSLSocket; 38 import javax.net.ssl.SSLSocketFactory; 39 40 public final class HttpsURLConnectionImpl extends OkHttpsConnection { 41 42 /** HttpUrlConnectionDelegate allows reuse of HttpURLConnectionImpl. */ 43 private final HttpUrlConnectionDelegate delegate; 44 HttpsURLConnectionImpl(URL url, int port)45 public HttpsURLConnectionImpl(URL url, int port) { 46 super(url); 47 delegate = new HttpUrlConnectionDelegate(url, port); 48 } 49 HttpsURLConnectionImpl(URL url, int port, Proxy proxy)50 public HttpsURLConnectionImpl(URL url, int port, Proxy proxy) { 51 super(url); 52 delegate = new HttpUrlConnectionDelegate(url, port, proxy); 53 } 54 checkConnected()55 private void checkConnected() { 56 if (delegate.getSSLSocket() == null) { 57 throw new IllegalStateException("Connection has not yet been established"); 58 } 59 } 60 getHttpEngine()61 HttpEngine getHttpEngine() { 62 return delegate.getHttpEngine(); 63 } 64 65 @Override getCipherSuite()66 public String getCipherSuite() { 67 SecureCacheResponse cacheResponse = delegate.getCacheResponse(); 68 if (cacheResponse != null) { 69 return cacheResponse.getCipherSuite(); 70 } 71 checkConnected(); 72 return delegate.getSSLSocket().getSession().getCipherSuite(); 73 } 74 75 @Override getLocalCertificates()76 public Certificate[] getLocalCertificates() { 77 SecureCacheResponse cacheResponse = delegate.getCacheResponse(); 78 if (cacheResponse != null) { 79 List<Certificate> result = cacheResponse.getLocalCertificateChain(); 80 return result != null ? result.toArray(new Certificate[result.size()]) : null; 81 } 82 checkConnected(); 83 return delegate.getSSLSocket().getSession().getLocalCertificates(); 84 } 85 86 @Override getServerCertificates()87 public Certificate[] getServerCertificates() throws SSLPeerUnverifiedException { 88 SecureCacheResponse cacheResponse = delegate.getCacheResponse(); 89 if (cacheResponse != null) { 90 List<Certificate> result = cacheResponse.getServerCertificateChain(); 91 return result != null ? result.toArray(new Certificate[result.size()]) : null; 92 } 93 checkConnected(); 94 return delegate.getSSLSocket().getSession().getPeerCertificates(); 95 } 96 97 @Override getPeerPrincipal()98 public Principal getPeerPrincipal() throws SSLPeerUnverifiedException { 99 SecureCacheResponse cacheResponse = delegate.getCacheResponse(); 100 if (cacheResponse != null) { 101 return cacheResponse.getPeerPrincipal(); 102 } 103 checkConnected(); 104 return delegate.getSSLSocket().getSession().getPeerPrincipal(); 105 } 106 107 @Override getLocalPrincipal()108 public Principal getLocalPrincipal() { 109 SecureCacheResponse cacheResponse = delegate.getCacheResponse(); 110 if (cacheResponse != null) { 111 return cacheResponse.getLocalPrincipal(); 112 } 113 checkConnected(); 114 return delegate.getSSLSocket().getSession().getLocalPrincipal(); 115 } 116 117 @Override disconnect()118 public void disconnect() { 119 delegate.disconnect(); 120 } 121 122 @Override getErrorStream()123 public InputStream getErrorStream() { 124 return delegate.getErrorStream(); 125 } 126 127 @Override getRequestMethod()128 public String getRequestMethod() { 129 return delegate.getRequestMethod(); 130 } 131 132 @Override getResponseCode()133 public int getResponseCode() throws IOException { 134 return delegate.getResponseCode(); 135 } 136 137 @Override getResponseMessage()138 public String getResponseMessage() throws IOException { 139 return delegate.getResponseMessage(); 140 } 141 142 @Override setRequestMethod(String method)143 public void setRequestMethod(String method) throws ProtocolException { 144 delegate.setRequestMethod(method); 145 } 146 147 @Override usingProxy()148 public boolean usingProxy() { 149 return delegate.usingProxy(); 150 } 151 152 @Override getInstanceFollowRedirects()153 public boolean getInstanceFollowRedirects() { 154 return delegate.getInstanceFollowRedirects(); 155 } 156 157 @Override setInstanceFollowRedirects(boolean followRedirects)158 public void setInstanceFollowRedirects(boolean followRedirects) { 159 delegate.setInstanceFollowRedirects(followRedirects); 160 } 161 162 @Override connect()163 public void connect() throws IOException { 164 connected = true; 165 delegate.connect(); 166 } 167 168 @Override getAllowUserInteraction()169 public boolean getAllowUserInteraction() { 170 return delegate.getAllowUserInteraction(); 171 } 172 173 @Override getContent()174 public Object getContent() throws IOException { 175 return delegate.getContent(); 176 } 177 178 @SuppressWarnings("unchecked") // Spec does not generify 179 @Override getContent(Class[] types)180 public Object getContent(Class[] types) throws IOException { 181 return delegate.getContent(types); 182 } 183 184 @Override getContentEncoding()185 public String getContentEncoding() { 186 return delegate.getContentEncoding(); 187 } 188 189 @Override getContentLength()190 public int getContentLength() { 191 return delegate.getContentLength(); 192 } 193 194 @Override getContentType()195 public String getContentType() { 196 return delegate.getContentType(); 197 } 198 199 @Override getDate()200 public long getDate() { 201 return delegate.getDate(); 202 } 203 204 @Override getDefaultUseCaches()205 public boolean getDefaultUseCaches() { 206 return delegate.getDefaultUseCaches(); 207 } 208 209 @Override getDoInput()210 public boolean getDoInput() { 211 return delegate.getDoInput(); 212 } 213 214 @Override getDoOutput()215 public boolean getDoOutput() { 216 return delegate.getDoOutput(); 217 } 218 219 @Override getExpiration()220 public long getExpiration() { 221 return delegate.getExpiration(); 222 } 223 224 @Override getHeaderField(int pos)225 public String getHeaderField(int pos) { 226 return delegate.getHeaderField(pos); 227 } 228 229 @Override getHeaderFields()230 public Map<String, List<String>> getHeaderFields() { 231 return delegate.getHeaderFields(); 232 } 233 234 @Override getRequestProperties()235 public Map<String, List<String>> getRequestProperties() { 236 return delegate.getRequestProperties(); 237 } 238 239 @Override addRequestProperty(String field, String newValue)240 public void addRequestProperty(String field, String newValue) { 241 delegate.addRequestProperty(field, newValue); 242 } 243 244 @Override getHeaderField(String key)245 public String getHeaderField(String key) { 246 return delegate.getHeaderField(key); 247 } 248 249 @Override getHeaderFieldDate(String field, long defaultValue)250 public long getHeaderFieldDate(String field, long defaultValue) { 251 return delegate.getHeaderFieldDate(field, defaultValue); 252 } 253 254 @Override getHeaderFieldInt(String field, int defaultValue)255 public int getHeaderFieldInt(String field, int defaultValue) { 256 return delegate.getHeaderFieldInt(field, defaultValue); 257 } 258 259 @Override getHeaderFieldKey(int posn)260 public String getHeaderFieldKey(int posn) { 261 return delegate.getHeaderFieldKey(posn); 262 } 263 264 @Override getIfModifiedSince()265 public long getIfModifiedSince() { 266 return delegate.getIfModifiedSince(); 267 } 268 269 @Override getInputStream()270 public InputStream getInputStream() throws IOException { 271 return delegate.getInputStream(); 272 } 273 274 @Override getLastModified()275 public long getLastModified() { 276 return delegate.getLastModified(); 277 } 278 279 @Override getOutputStream()280 public OutputStream getOutputStream() throws IOException { 281 return delegate.getOutputStream(); 282 } 283 284 @Override getPermission()285 public Permission getPermission() throws IOException { 286 return delegate.getPermission(); 287 } 288 289 @Override getRequestProperty(String field)290 public String getRequestProperty(String field) { 291 return delegate.getRequestProperty(field); 292 } 293 294 @Override getURL()295 public URL getURL() { 296 return delegate.getURL(); 297 } 298 299 @Override getUseCaches()300 public boolean getUseCaches() { 301 return delegate.getUseCaches(); 302 } 303 304 @Override setAllowUserInteraction(boolean newValue)305 public void setAllowUserInteraction(boolean newValue) { 306 delegate.setAllowUserInteraction(newValue); 307 } 308 309 @Override setDefaultUseCaches(boolean newValue)310 public void setDefaultUseCaches(boolean newValue) { 311 delegate.setDefaultUseCaches(newValue); 312 } 313 314 @Override setDoInput(boolean newValue)315 public void setDoInput(boolean newValue) { 316 delegate.setDoInput(newValue); 317 } 318 319 @Override setDoOutput(boolean newValue)320 public void setDoOutput(boolean newValue) { 321 delegate.setDoOutput(newValue); 322 } 323 324 @Override setIfModifiedSince(long newValue)325 public void setIfModifiedSince(long newValue) { 326 delegate.setIfModifiedSince(newValue); 327 } 328 329 @Override setRequestProperty(String field, String newValue)330 public void setRequestProperty(String field, String newValue) { 331 delegate.setRequestProperty(field, newValue); 332 } 333 334 @Override setUseCaches(boolean newValue)335 public void setUseCaches(boolean newValue) { 336 delegate.setUseCaches(newValue); 337 } 338 339 @Override setConnectTimeout(int timeoutMillis)340 public void setConnectTimeout(int timeoutMillis) { 341 delegate.setConnectTimeout(timeoutMillis); 342 } 343 344 @Override getConnectTimeout()345 public int getConnectTimeout() { 346 return delegate.getConnectTimeout(); 347 } 348 349 @Override setReadTimeout(int timeoutMillis)350 public void setReadTimeout(int timeoutMillis) { 351 delegate.setReadTimeout(timeoutMillis); 352 } 353 354 @Override getReadTimeout()355 public int getReadTimeout() { 356 return delegate.getReadTimeout(); 357 } 358 359 @Override toString()360 public String toString() { 361 return delegate.toString(); 362 } 363 364 @Override setFixedLengthStreamingMode(int contentLength)365 public void setFixedLengthStreamingMode(int contentLength) { 366 delegate.setFixedLengthStreamingMode(contentLength); 367 } 368 369 @Override setChunkedStreamingMode(int chunkLength)370 public void setChunkedStreamingMode(int chunkLength) { 371 delegate.setChunkedStreamingMode(chunkLength); 372 } 373 374 private final class HttpUrlConnectionDelegate extends HttpURLConnectionImpl { HttpUrlConnectionDelegate(URL url, int port)375 private HttpUrlConnectionDelegate(URL url, int port) { 376 super(url, port); 377 } 378 HttpUrlConnectionDelegate(URL url, int port, Proxy proxy)379 private HttpUrlConnectionDelegate(URL url, int port, Proxy proxy) { 380 super(url, port, proxy); 381 } 382 newHttpEngine(String method, RawHeaders requestHeaders, HttpConnection connection, RetryableOutputStream requestBody)383 @Override protected HttpEngine newHttpEngine(String method, RawHeaders requestHeaders, 384 HttpConnection connection, RetryableOutputStream requestBody) throws IOException { 385 return new HttpsEngine(this, method, requestHeaders, connection, requestBody, 386 HttpsURLConnectionImpl.this); 387 } 388 getCacheResponse()389 public SecureCacheResponse getCacheResponse() { 390 HttpsEngine engine = (HttpsEngine) httpEngine; 391 return engine != null ? (SecureCacheResponse) engine.getCacheResponse() : null; 392 } 393 getSSLSocket()394 public SSLSocket getSSLSocket() { 395 HttpsEngine engine = (HttpsEngine) httpEngine; 396 return engine != null ? engine.sslSocket : null; 397 } 398 } 399 400 private static final class HttpsEngine extends HttpEngine { 401 402 /** 403 * Local stash of HttpsEngine.connection.sslSocket for answering 404 * queries such as getCipherSuite even after 405 * httpsEngine.Connection has been recycled. It's presence is also 406 * used to tell if the HttpsURLConnection is considered connected, 407 * as opposed to the connected field of URLConnection or the a 408 * non-null connect in HttpURLConnectionImpl 409 */ 410 private SSLSocket sslSocket; 411 412 private final HttpsURLConnectionImpl enclosing; 413 414 /** 415 * @param policy the HttpURLConnectionImpl with connection configuration 416 * @param enclosing the HttpsURLConnection with HTTPS features 417 */ HttpsEngine(HttpURLConnectionImpl policy, String method, RawHeaders requestHeaders, HttpConnection connection, RetryableOutputStream requestBody, HttpsURLConnectionImpl enclosing)418 private HttpsEngine(HttpURLConnectionImpl policy, String method, RawHeaders requestHeaders, 419 HttpConnection connection, RetryableOutputStream requestBody, 420 HttpsURLConnectionImpl enclosing) throws IOException { 421 super(policy, method, requestHeaders, connection, requestBody); 422 this.sslSocket = connection != null ? connection.getSecureSocketIfConnected() : null; 423 this.enclosing = enclosing; 424 } 425 connect()426 @Override protected void connect() throws IOException { 427 // First try an SSL connection with compression and various TLS 428 // extensions enabled, if it fails (and its not unheard of that it 429 // will) fallback to a barebones connection. 430 try { 431 makeSslConnection(true); 432 } catch (IOException e) { 433 // If the problem was a CertificateException from the X509TrustManager, 434 // do not retry, we didn't have an abrupt server initiated exception. 435 if (e instanceof SSLHandshakeException 436 && e.getCause() instanceof CertificateException) { 437 throw e; 438 } 439 release(false); 440 makeSslConnection(false); 441 } 442 } 443 444 /** 445 * Attempt to make an HTTPS connection. 446 * 447 * @param tlsTolerant If true, assume server can handle common 448 * TLS extensions and SSL deflate compression. If false, use 449 * an SSL3 only fallback mode without compression. 450 */ makeSslConnection(boolean tlsTolerant)451 private void makeSslConnection(boolean tlsTolerant) throws IOException { 452 // make an SSL Tunnel on the first message pair of each SSL + proxy connection 453 if (connection == null) { 454 connection = openSocketConnection(); 455 if (connection.getAddress().getProxy() != null) { 456 makeTunnel(policy, connection, getRequestHeaders()); 457 } 458 } 459 460 // if super.makeConnection returned a connection from the 461 // pool, sslSocket needs to be initialized here. If it is 462 // a new connection, it will be initialized by 463 // getSecureSocket below. 464 sslSocket = connection.getSecureSocketIfConnected(); 465 466 // we already have an SSL connection, 467 if (sslSocket != null) { 468 return; 469 } 470 471 sslSocket = connection.setupSecureSocket( 472 enclosing.getSSLSocketFactory(), enclosing.getHostnameVerifier(), tlsTolerant); 473 } 474 475 /** 476 * To make an HTTPS connection over an HTTP proxy, send an unencrypted 477 * CONNECT request to create the proxy connection. This may need to be 478 * retried if the proxy requires authorization. 479 */ makeTunnel(HttpURLConnectionImpl policy, HttpConnection connection, RequestHeaders requestHeaders)480 private void makeTunnel(HttpURLConnectionImpl policy, HttpConnection connection, 481 RequestHeaders requestHeaders) throws IOException { 482 RawHeaders rawRequestHeaders = requestHeaders.getHeaders(); 483 while (true) { 484 HttpEngine connect = new ProxyConnectEngine(policy, rawRequestHeaders, connection); 485 connect.sendRequest(); 486 connect.readResponse(); 487 488 int responseCode = connect.getResponseCode(); 489 switch (connect.getResponseCode()) { 490 case HTTP_OK: 491 return; 492 case HTTP_PROXY_AUTH: 493 rawRequestHeaders = new RawHeaders(rawRequestHeaders); 494 boolean credentialsFound = policy.processAuthHeader(HTTP_PROXY_AUTH, 495 connect.getResponseHeaders(), rawRequestHeaders); 496 if (credentialsFound) { 497 continue; 498 } else { 499 throw new IOException("Failed to authenticate with proxy"); 500 } 501 default: 502 throw new IOException("Unexpected response code for CONNECT: " + responseCode); 503 } 504 } 505 } 506 acceptCacheResponseType(CacheResponse cacheResponse)507 @Override protected boolean acceptCacheResponseType(CacheResponse cacheResponse) { 508 return cacheResponse instanceof SecureCacheResponse; 509 } 510 includeAuthorityInRequestLine()511 @Override protected boolean includeAuthorityInRequestLine() { 512 // Even if there is a proxy, it isn't involved. Always request just the file. 513 return false; 514 } 515 getSslSocketFactory()516 @Override protected SSLSocketFactory getSslSocketFactory() { 517 return enclosing.getSSLSocketFactory(); 518 } 519 getHttpConnectionToCache()520 @Override protected OkHttpConnection getHttpConnectionToCache() { 521 return enclosing; 522 } 523 } 524 525 private static class ProxyConnectEngine extends HttpEngine { ProxyConnectEngine(HttpURLConnectionImpl policy, RawHeaders requestHeaders, HttpConnection connection)526 public ProxyConnectEngine(HttpURLConnectionImpl policy, RawHeaders requestHeaders, 527 HttpConnection connection) throws IOException { 528 super(policy, HttpEngine.CONNECT, requestHeaders, connection, null); 529 } 530 requiresTunnel()531 @Override protected boolean requiresTunnel() { 532 return true; 533 } 534 } 535 } 536