• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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