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 com.squareup.okhttp.internal.http; 18 19 import com.squareup.okhttp.Connection; 20 import com.squareup.okhttp.OkHttpClient; 21 import com.squareup.okhttp.TunnelRequest; 22 import java.io.IOException; 23 import java.io.InputStream; 24 import java.io.OutputStream; 25 import java.net.CacheResponse; 26 import java.net.HttpURLConnection; 27 import java.net.ProtocolException; 28 import java.net.SecureCacheResponse; 29 import java.net.URL; 30 import java.security.Permission; 31 import java.security.Principal; 32 import java.security.cert.Certificate; 33 import java.util.List; 34 import java.util.Map; 35 import javax.net.ssl.HostnameVerifier; 36 import javax.net.ssl.HttpsURLConnection; 37 import javax.net.ssl.SSLPeerUnverifiedException; 38 import javax.net.ssl.SSLSocket; 39 import javax.net.ssl.SSLSocketFactory; 40 41 import static com.squareup.okhttp.internal.Util.getEffectivePort; 42 43 public final class HttpsURLConnectionImpl extends HttpsURLConnection { 44 45 /** HttpUrlConnectionDelegate allows reuse of HttpURLConnectionImpl. */ 46 private final HttpUrlConnectionDelegate delegate; 47 48 public HttpsURLConnectionImpl(URL url, OkHttpClient client) { 49 super(url); 50 delegate = new HttpUrlConnectionDelegate(url, client); 51 } 52 53 @Override public String getCipherSuite() { 54 SecureCacheResponse cacheResponse = delegate.getSecureCacheResponse(); 55 if (cacheResponse != null) { 56 return cacheResponse.getCipherSuite(); 57 } 58 SSLSocket sslSocket = getSslSocket(); 59 if (sslSocket != null) { 60 return sslSocket.getSession().getCipherSuite(); 61 } 62 return null; 63 } 64 65 @Override public Certificate[] getLocalCertificates() { 66 SecureCacheResponse cacheResponse = delegate.getSecureCacheResponse(); 67 if (cacheResponse != null) { 68 List<Certificate> result = cacheResponse.getLocalCertificateChain(); 69 return result != null ? result.toArray(new Certificate[result.size()]) : null; 70 } 71 SSLSocket sslSocket = getSslSocket(); 72 if (sslSocket != null) { 73 return sslSocket.getSession().getLocalCertificates(); 74 } 75 return null; 76 } 77 78 @Override public Certificate[] getServerCertificates() throws SSLPeerUnverifiedException { 79 SecureCacheResponse cacheResponse = delegate.getSecureCacheResponse(); 80 if (cacheResponse != null) { 81 List<Certificate> result = cacheResponse.getServerCertificateChain(); 82 return result != null ? result.toArray(new Certificate[result.size()]) : null; 83 } 84 SSLSocket sslSocket = getSslSocket(); 85 if (sslSocket != null) { 86 return sslSocket.getSession().getPeerCertificates(); 87 } 88 return null; 89 } 90 91 @Override public Principal getPeerPrincipal() throws SSLPeerUnverifiedException { 92 SecureCacheResponse cacheResponse = delegate.getSecureCacheResponse(); 93 if (cacheResponse != null) { 94 return cacheResponse.getPeerPrincipal(); 95 } 96 SSLSocket sslSocket = getSslSocket(); 97 if (sslSocket != null) { 98 return sslSocket.getSession().getPeerPrincipal(); 99 } 100 return null; 101 } 102 103 @Override public Principal getLocalPrincipal() { 104 SecureCacheResponse cacheResponse = delegate.getSecureCacheResponse(); 105 if (cacheResponse != null) { 106 return cacheResponse.getLocalPrincipal(); 107 } 108 SSLSocket sslSocket = getSslSocket(); 109 if (sslSocket != null) { 110 return sslSocket.getSession().getLocalPrincipal(); 111 } 112 return null; 113 } 114 115 HttpEngine getHttpEngine() { 116 return delegate.getHttpEngine(); 117 } 118 119 private SSLSocket getSslSocket() { 120 if (delegate.httpEngine == null || delegate.httpEngine.sentRequestMillis == -1) { 121 throw new IllegalStateException("Connection has not yet been established"); 122 } 123 return delegate.httpEngine instanceof HttpsEngine 124 ? ((HttpsEngine) delegate.httpEngine).sslSocket 125 : null; // Not HTTPS! Probably an https:// to http:// redirect. 126 } 127 128 @Override 129 public void disconnect() { 130 delegate.disconnect(); 131 } 132 133 @Override 134 public InputStream getErrorStream() { 135 return delegate.getErrorStream(); 136 } 137 138 @Override 139 public String getRequestMethod() { 140 return delegate.getRequestMethod(); 141 } 142 143 @Override 144 public int getResponseCode() throws IOException { 145 return delegate.getResponseCode(); 146 } 147 148 @Override 149 public String getResponseMessage() throws IOException { 150 return delegate.getResponseMessage(); 151 } 152 153 @Override 154 public void setRequestMethod(String method) throws ProtocolException { 155 delegate.setRequestMethod(method); 156 } 157 158 @Override 159 public boolean usingProxy() { 160 return delegate.usingProxy(); 161 } 162 163 @Override 164 public boolean getInstanceFollowRedirects() { 165 return delegate.getInstanceFollowRedirects(); 166 } 167 168 @Override 169 public void setInstanceFollowRedirects(boolean followRedirects) { 170 delegate.setInstanceFollowRedirects(followRedirects); 171 } 172 173 @Override 174 public void connect() throws IOException { 175 connected = true; 176 delegate.connect(); 177 } 178 179 @Override 180 public boolean getAllowUserInteraction() { 181 return delegate.getAllowUserInteraction(); 182 } 183 184 @Override 185 public Object getContent() throws IOException { 186 return delegate.getContent(); 187 } 188 189 @SuppressWarnings("unchecked") // Spec does not generify 190 @Override 191 public Object getContent(Class[] types) throws IOException { 192 return delegate.getContent(types); 193 } 194 195 @Override 196 public String getContentEncoding() { 197 return delegate.getContentEncoding(); 198 } 199 200 @Override 201 public int getContentLength() { 202 return delegate.getContentLength(); 203 } 204 205 @Override 206 public String getContentType() { 207 return delegate.getContentType(); 208 } 209 210 @Override 211 public long getDate() { 212 return delegate.getDate(); 213 } 214 215 @Override 216 public boolean getDefaultUseCaches() { 217 return delegate.getDefaultUseCaches(); 218 } 219 220 @Override 221 public boolean getDoInput() { 222 return delegate.getDoInput(); 223 } 224 225 @Override 226 public boolean getDoOutput() { 227 return delegate.getDoOutput(); 228 } 229 230 @Override 231 public long getExpiration() { 232 return delegate.getExpiration(); 233 } 234 235 @Override 236 public String getHeaderField(int pos) { 237 return delegate.getHeaderField(pos); 238 } 239 240 @Override 241 public Map<String, List<String>> getHeaderFields() { 242 return delegate.getHeaderFields(); 243 } 244 245 @Override 246 public Map<String, List<String>> getRequestProperties() { 247 return delegate.getRequestProperties(); 248 } 249 250 @Override 251 public void addRequestProperty(String field, String newValue) { 252 delegate.addRequestProperty(field, newValue); 253 } 254 255 @Override 256 public String getHeaderField(String key) { 257 return delegate.getHeaderField(key); 258 } 259 260 @Override 261 public long getHeaderFieldDate(String field, long defaultValue) { 262 return delegate.getHeaderFieldDate(field, defaultValue); 263 } 264 265 @Override 266 public int getHeaderFieldInt(String field, int defaultValue) { 267 return delegate.getHeaderFieldInt(field, defaultValue); 268 } 269 270 @Override 271 public String getHeaderFieldKey(int position) { 272 return delegate.getHeaderFieldKey(position); 273 } 274 275 @Override 276 public long getIfModifiedSince() { 277 return delegate.getIfModifiedSince(); 278 } 279 280 @Override 281 public InputStream getInputStream() throws IOException { 282 return delegate.getInputStream(); 283 } 284 285 @Override 286 public long getLastModified() { 287 return delegate.getLastModified(); 288 } 289 290 @Override 291 public OutputStream getOutputStream() throws IOException { 292 return delegate.getOutputStream(); 293 } 294 295 @Override 296 public Permission getPermission() throws IOException { 297 return delegate.getPermission(); 298 } 299 300 @Override 301 public String getRequestProperty(String field) { 302 return delegate.getRequestProperty(field); 303 } 304 305 @Override 306 public URL getURL() { 307 return delegate.getURL(); 308 } 309 310 @Override 311 public boolean getUseCaches() { 312 return delegate.getUseCaches(); 313 } 314 315 @Override 316 public void setAllowUserInteraction(boolean newValue) { 317 delegate.setAllowUserInteraction(newValue); 318 } 319 320 @Override 321 public void setDefaultUseCaches(boolean newValue) { 322 delegate.setDefaultUseCaches(newValue); 323 } 324 325 @Override 326 public void setDoInput(boolean newValue) { 327 delegate.setDoInput(newValue); 328 } 329 330 @Override 331 public void setDoOutput(boolean newValue) { 332 delegate.setDoOutput(newValue); 333 } 334 335 @Override 336 public void setIfModifiedSince(long newValue) { 337 delegate.setIfModifiedSince(newValue); 338 } 339 340 @Override 341 public void setRequestProperty(String field, String newValue) { 342 delegate.setRequestProperty(field, newValue); 343 } 344 345 @Override 346 public void setUseCaches(boolean newValue) { 347 delegate.setUseCaches(newValue); 348 } 349 350 @Override 351 public void setConnectTimeout(int timeoutMillis) { 352 delegate.setConnectTimeout(timeoutMillis); 353 } 354 355 @Override 356 public int getConnectTimeout() { 357 return delegate.getConnectTimeout(); 358 } 359 360 @Override 361 public void setReadTimeout(int timeoutMillis) { 362 delegate.setReadTimeout(timeoutMillis); 363 } 364 365 @Override 366 public int getReadTimeout() { 367 return delegate.getReadTimeout(); 368 } 369 370 @Override 371 public String toString() { 372 return delegate.toString(); 373 } 374 375 @Override 376 public void setFixedLengthStreamingMode(int contentLength) { 377 delegate.setFixedLengthStreamingMode(contentLength); 378 } 379 380 @Override 381 public void setChunkedStreamingMode(int chunkLength) { 382 delegate.setChunkedStreamingMode(chunkLength); 383 } 384 385 @Override public void setHostnameVerifier(HostnameVerifier hostnameVerifier) { 386 delegate.hostnameVerifier = hostnameVerifier; 387 } 388 389 @Override public HostnameVerifier getHostnameVerifier() { 390 return delegate.hostnameVerifier; 391 } 392 393 @Override public void setSSLSocketFactory(SSLSocketFactory sslSocketFactory) { 394 delegate.sslSocketFactory = sslSocketFactory; 395 } 396 397 @Override public SSLSocketFactory getSSLSocketFactory() { 398 return delegate.sslSocketFactory; 399 } 400 401 private final class HttpUrlConnectionDelegate extends HttpURLConnectionImpl { 402 private HttpUrlConnectionDelegate(URL url, OkHttpClient client) { 403 super(url, client); 404 } 405 406 @Override protected HttpURLConnection getHttpConnectionToCache() { 407 return HttpsURLConnectionImpl.this; 408 } 409 410 public SecureCacheResponse getSecureCacheResponse() { 411 return httpEngine instanceof HttpsEngine 412 ? (SecureCacheResponse) httpEngine.getCacheResponse() 413 : null; 414 } 415 } 416 417 public static final class HttpsEngine extends HttpEngine { 418 /** 419 * Stash of HttpsEngine.connection.socket to implement requests like 420 * {@link #getCipherSuite} even after the connection has been recycled. 421 */ 422 private SSLSocket sslSocket; 423 424 /** 425 * @param policy the HttpURLConnectionImpl with connection configuration 426 */ 427 public HttpsEngine(HttpURLConnectionImpl policy, String method, RawHeaders requestHeaders, 428 Connection connection, RetryableOutputStream requestBody) 429 throws IOException { 430 super(policy, method, requestHeaders, connection, requestBody); 431 this.sslSocket = connection != null ? (SSLSocket) connection.getSocket() : null; 432 } 433 434 @Override protected void connected(Connection connection) { 435 this.sslSocket = (SSLSocket) connection.getSocket(); 436 } 437 438 @Override protected boolean acceptCacheResponseType(CacheResponse cacheResponse) { 439 return cacheResponse instanceof SecureCacheResponse; 440 } 441 442 @Override protected boolean includeAuthorityInRequestLine() { 443 // Even if there is a proxy, it isn't involved. Always request just the file. 444 return false; 445 } 446 447 @Override protected TunnelRequest getTunnelConfig() { 448 String userAgent = requestHeaders.getUserAgent(); 449 if (userAgent == null) { 450 userAgent = getDefaultUserAgent(); 451 } 452 453 URL url = policy.getURL(); 454 return new TunnelRequest(url.getHost(), getEffectivePort(url), userAgent, 455 requestHeaders.getProxyAuthorization()); 456 } 457 } 458 } 459