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 */
17package com.squareup.okhttp.internal.io;
18
19import com.squareup.okhttp.Address;
20import com.squareup.okhttp.CertificatePinner;
21import com.squareup.okhttp.Connection;
22import com.squareup.okhttp.ConnectionSpec;
23import com.squareup.okhttp.Handshake;
24import com.squareup.okhttp.HttpUrl;
25import com.squareup.okhttp.Protocol;
26import com.squareup.okhttp.Request;
27import com.squareup.okhttp.Response;
28import com.squareup.okhttp.Route;
29import com.squareup.okhttp.internal.ConnectionSpecSelector;
30import com.squareup.okhttp.internal.Platform;
31import com.squareup.okhttp.internal.Util;
32import com.squareup.okhttp.internal.Version;
33import com.squareup.okhttp.internal.framed.FramedConnection;
34import com.squareup.okhttp.internal.http.Http1xStream;
35import com.squareup.okhttp.internal.http.OkHeaders;
36import com.squareup.okhttp.internal.http.RouteException;
37import com.squareup.okhttp.internal.http.StreamAllocation;
38import com.squareup.okhttp.internal.tls.CertificateChainCleaner;
39import com.squareup.okhttp.internal.tls.OkHostnameVerifier;
40import com.squareup.okhttp.internal.tls.TrustRootIndex;
41import java.io.IOException;
42import java.lang.ref.Reference;
43import java.net.ConnectException;
44import java.net.Proxy;
45import java.net.Socket;
46import java.net.SocketTimeoutException;
47import java.net.UnknownServiceException;
48import java.security.cert.Certificate;
49import java.security.cert.X509Certificate;
50import java.util.ArrayList;
51import java.util.List;
52import java.util.concurrent.TimeUnit;
53import javax.net.ssl.SSLPeerUnverifiedException;
54import javax.net.ssl.SSLSocket;
55import javax.net.ssl.SSLSocketFactory;
56import javax.net.ssl.X509TrustManager;
57import okio.BufferedSink;
58import okio.BufferedSource;
59import okio.Okio;
60import okio.Source;
61
62import static com.squareup.okhttp.internal.Util.closeQuietly;
63import static java.net.HttpURLConnection.HTTP_OK;
64import static java.net.HttpURLConnection.HTTP_PROXY_AUTH;
65import static java.util.concurrent.TimeUnit.MILLISECONDS;
66
67public final class RealConnection implements Connection {
68  private final Route route;
69
70  /** The low-level TCP socket. */
71  private Socket rawSocket;
72
73  /**
74   * The application layer socket. Either an {@link SSLSocket} layered over {@link #rawSocket}, or
75   * {@link #rawSocket} itself if this connection does not use SSL.
76   */
77  public Socket socket;
78  private Handshake handshake;
79  private Protocol protocol;
80  public volatile FramedConnection framedConnection;
81  public int streamCount;
82  public BufferedSource source;
83  public BufferedSink sink;
84  public final List<Reference<StreamAllocation>> allocations = new ArrayList<>();
85  public boolean noNewStreams;
86  public long idleAtNanos = Long.MAX_VALUE;
87
88  public RealConnection(Route route) {
89    this.route = route;
90  }
91
92  public void connect(int connectTimeout, int readTimeout, int writeTimeout,
93      List<ConnectionSpec> connectionSpecs, boolean connectionRetryEnabled) throws RouteException {
94    if (protocol != null) throw new IllegalStateException("already connected");
95
96    RouteException routeException = null;
97    ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs);
98    Proxy proxy = route.getProxy();
99    Address address = route.getAddress();
100
101    if (route.getAddress().getSslSocketFactory() == null
102        && !connectionSpecs.contains(ConnectionSpec.CLEARTEXT)) {
103      throw new RouteException(new UnknownServiceException(
104          "CLEARTEXT communication not supported: " + connectionSpecs));
105    }
106
107    while (protocol == null) {
108      try {
109        rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
110            ? address.getSocketFactory().createSocket()
111            : new Socket(proxy);
112        connectSocket(connectTimeout, readTimeout, writeTimeout, connectionSpecSelector);
113      } catch (IOException e) {
114        Util.closeQuietly(socket);
115        Util.closeQuietly(rawSocket);
116        socket = null;
117        rawSocket = null;
118        source = null;
119        sink = null;
120        handshake = null;
121        protocol = null;
122
123        if (routeException == null) {
124          routeException = new RouteException(e);
125        } else {
126          routeException.addConnectException(e);
127        }
128
129        if (!connectionRetryEnabled || !connectionSpecSelector.connectionFailed(e)) {
130          throw routeException;
131        }
132      }
133    }
134  }
135
136  /** Does all the work necessary to build a full HTTP or HTTPS connection on a raw socket. */
137  private void connectSocket(int connectTimeout, int readTimeout, int writeTimeout,
138      ConnectionSpecSelector connectionSpecSelector) throws IOException {
139    rawSocket.setSoTimeout(readTimeout);
140    try {
141      Platform.get().connectSocket(rawSocket, route.getSocketAddress(), connectTimeout);
142    } catch (ConnectException e) {
143      throw new ConnectException("Failed to connect to " + route.getSocketAddress());
144    }
145    source = Okio.buffer(Okio.source(rawSocket));
146    sink = Okio.buffer(Okio.sink(rawSocket));
147
148    if (route.getAddress().getSslSocketFactory() != null) {
149      connectTls(readTimeout, writeTimeout, connectionSpecSelector);
150    } else {
151      protocol = Protocol.HTTP_1_1;
152      socket = rawSocket;
153    }
154
155    if (protocol == Protocol.SPDY_3 || protocol == Protocol.HTTP_2) {
156      socket.setSoTimeout(0); // Framed connection timeouts are set per-stream.
157
158      FramedConnection framedConnection = new FramedConnection.Builder(true)
159          .socket(socket, route.getAddress().url().host(), source, sink)
160          .protocol(protocol)
161          .build();
162      framedConnection.sendConnectionPreface();
163
164      // Only assign the framed connection once the preface has been sent successfully.
165      this.framedConnection = framedConnection;
166    }
167  }
168
169  private void connectTls(int readTimeout, int writeTimeout,
170      ConnectionSpecSelector connectionSpecSelector) throws IOException {
171    if (route.requiresTunnel()) {
172      createTunnel(readTimeout, writeTimeout);
173    }
174
175    Address address = route.getAddress();
176    SSLSocketFactory sslSocketFactory = address.getSslSocketFactory();
177    boolean success = false;
178    SSLSocket sslSocket = null;
179    try {
180      // Create the wrapper over the connected socket.
181      sslSocket = (SSLSocket) sslSocketFactory.createSocket(
182          rawSocket, address.getUriHost(), address.getUriPort(), true /* autoClose */);
183
184      // Configure the socket's ciphers, TLS versions, and extensions.
185      ConnectionSpec connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket);
186      if (connectionSpec.supportsTlsExtensions()) {
187        Platform.get().configureTlsExtensions(
188            sslSocket, address.getUriHost(), address.getProtocols());
189      }
190
191      // Force handshake. This can throw!
192      sslSocket.startHandshake();
193      Handshake unverifiedHandshake = Handshake.get(sslSocket.getSession());
194
195      // Verify that the socket's certificates are acceptable for the target host.
196      if (!address.getHostnameVerifier().verify(address.getUriHost(), sslSocket.getSession())) {
197        X509Certificate cert = (X509Certificate) unverifiedHandshake.peerCertificates().get(0);
198        throw new SSLPeerUnverifiedException("Hostname " + address.getUriHost() + " not verified:"
199            + "\n    certificate: " + CertificatePinner.pin(cert)
200            + "\n    DN: " + cert.getSubjectDN().getName()
201            + "\n    subjectAltNames: " + OkHostnameVerifier.allSubjectAltNames(cert));
202      }
203
204      // Check that the certificate pinner is satisfied by the certificates presented.
205      if (address.getCertificatePinner() != CertificatePinner.DEFAULT) {
206        TrustRootIndex trustRootIndex = trustRootIndex(address.getSslSocketFactory());
207        List<Certificate> certificates = new CertificateChainCleaner(trustRootIndex)
208            .clean(unverifiedHandshake.peerCertificates());
209        address.getCertificatePinner().check(address.getUriHost(), certificates);
210      }
211
212      // Success! Save the handshake and the ALPN protocol.
213      String maybeProtocol = connectionSpec.supportsTlsExtensions()
214          ? Platform.get().getSelectedProtocol(sslSocket)
215          : null;
216      socket = sslSocket;
217      source = Okio.buffer(Okio.source(socket));
218      sink = Okio.buffer(Okio.sink(socket));
219      handshake = unverifiedHandshake;
220      protocol = maybeProtocol != null
221          ? Protocol.get(maybeProtocol)
222          : Protocol.HTTP_1_1;
223      success = true;
224    } catch (AssertionError e) {
225      if (Util.isAndroidGetsocknameError(e)) throw new IOException(e);
226      throw e;
227    } finally {
228      if (sslSocket != null) {
229        Platform.get().afterHandshake(sslSocket);
230      }
231      if (!success) {
232        closeQuietly(sslSocket);
233      }
234    }
235  }
236
237  private static SSLSocketFactory lastSslSocketFactory;
238  private static TrustRootIndex lastTrustRootIndex;
239
240  /**
241   * Returns a trust root index for {@code sslSocketFactory}. This uses a static, single-element
242   * cache to avoid redoing reflection and SSL indexing in the common case where most SSL
243   * connections use the same SSL socket factory.
244   */
245  private static synchronized TrustRootIndex trustRootIndex(SSLSocketFactory sslSocketFactory) {
246    if (sslSocketFactory != lastSslSocketFactory) {
247      X509TrustManager trustManager = Platform.get().trustManager(sslSocketFactory);
248      lastTrustRootIndex = Platform.get().trustRootIndex(trustManager);
249      lastSslSocketFactory = sslSocketFactory;
250    }
251    return lastTrustRootIndex;
252  }
253
254  /**
255   * To make an HTTPS connection over an HTTP proxy, send an unencrypted
256   * CONNECT request to create the proxy connection. This may need to be
257   * retried if the proxy requires authorization.
258   */
259  private void createTunnel(int readTimeout, int writeTimeout) throws IOException {
260    // Make an SSL Tunnel on the first message pair of each SSL + proxy connection.
261    Request tunnelRequest = createTunnelRequest();
262    HttpUrl url = tunnelRequest.httpUrl();
263    String requestLine = "CONNECT " + Util.hostHeader(url, true) + " HTTP/1.1";
264    while (true) {
265      Http1xStream tunnelConnection = new Http1xStream(null, source, sink);
266      source.timeout().timeout(readTimeout, MILLISECONDS);
267      sink.timeout().timeout(writeTimeout, MILLISECONDS);
268      tunnelConnection.writeRequest(tunnelRequest.headers(), requestLine);
269      tunnelConnection.finishRequest();
270      Response response = tunnelConnection.readResponse().request(tunnelRequest).build();
271      // The response body from a CONNECT should be empty, but if it is not then we should consume
272      // it before proceeding.
273      long contentLength = OkHeaders.contentLength(response);
274      if (contentLength == -1L) {
275        contentLength = 0L;
276      }
277      Source body = tunnelConnection.newFixedLengthSource(contentLength);
278      Util.skipAll(body, Integer.MAX_VALUE, TimeUnit.MILLISECONDS);
279      body.close();
280
281      switch (response.code()) {
282        case HTTP_OK:
283          // Assume the server won't send a TLS ServerHello until we send a TLS ClientHello. If
284          // that happens, then we will have buffered bytes that are needed by the SSLSocket!
285          // This check is imperfect: it doesn't tell us whether a handshake will succeed, just
286          // that it will almost certainly fail because the proxy has sent unexpected data.
287          if (!source.buffer().exhausted() || !sink.buffer().exhausted()) {
288            throw new IOException("TLS tunnel buffered too many bytes!");
289          }
290          return;
291
292        case HTTP_PROXY_AUTH:
293          tunnelRequest = OkHeaders.processAuthHeader(
294              route.getAddress().getAuthenticator(), response, route.getProxy());
295          if (tunnelRequest != null) continue;
296          throw new IOException("Failed to authenticate with proxy");
297
298        default:
299          throw new IOException(
300              "Unexpected response code for CONNECT: " + response.code());
301      }
302    }
303  }
304
305  /**
306   * Returns a request that creates a TLS tunnel via an HTTP proxy, or null if
307   * no tunnel is necessary. Everything in the tunnel request is sent
308   * unencrypted to the proxy server, so tunnels include only the minimum set of
309   * headers. This avoids sending potentially sensitive data like HTTP cookies
310   * to the proxy unencrypted.
311   */
312  private Request createTunnelRequest() throws IOException {
313    return new Request.Builder()
314        .url(route.getAddress().url())
315        .header("Host", Util.hostHeader(route.getAddress().url(), true))
316        .header("Proxy-Connection", "Keep-Alive")
317        .header("User-Agent", Version.userAgent()) // For HTTP/1.0 proxies like Squid.
318        .build();
319  }
320
321  /** Returns true if {@link #connect} has been attempted on this connection. */
322  boolean isConnected() {
323    return protocol != null;
324  }
325
326  @Override public Route getRoute() {
327    return route;
328  }
329
330  public void cancel() {
331    // Close the raw socket so we don't end up doing synchronous I/O.
332    Util.closeQuietly(rawSocket);
333  }
334
335  @Override public Socket getSocket() {
336    return socket;
337  }
338
339  public int allocationLimit() {
340    FramedConnection framedConnection = this.framedConnection;
341    return framedConnection != null
342        ? framedConnection.maxConcurrentStreams()
343        : 1;
344  }
345
346  /** Returns true if this connection is ready to host new streams. */
347  public boolean isHealthy(boolean doExtensiveChecks) {
348    if (socket.isClosed() || socket.isInputShutdown() || socket.isOutputShutdown()) {
349      return false;
350    }
351
352    if (framedConnection != null) {
353      return true; // TODO: check framedConnection.shutdown.
354    }
355
356    if (doExtensiveChecks) {
357      try {
358        int readTimeout = socket.getSoTimeout();
359        try {
360          socket.setSoTimeout(1);
361          if (source.exhausted()) {
362            return false; // Stream is exhausted; socket is closed.
363          }
364          return true;
365        } finally {
366          socket.setSoTimeout(readTimeout);
367        }
368      } catch (SocketTimeoutException ignored) {
369        // Read timed out; socket is good.
370      } catch (IOException e) {
371        return false; // Couldn't read; socket is closed.
372      }
373    }
374
375    return true;
376  }
377
378  @Override public Handshake getHandshake() {
379    return handshake;
380  }
381
382  /**
383   * Returns true if this is a SPDY connection. Such connections can be used
384   * in multiple HTTP requests simultaneously.
385   */
386  public boolean isMultiplexed() {
387    return framedConnection != null;
388  }
389
390  @Override public Protocol getProtocol() {
391    return protocol != null ? protocol : Protocol.HTTP_1_1;
392  }
393
394  @Override public String toString() {
395    return "Connection{"
396        + route.getAddress().url().host() + ":" + route.getAddress().url().port()
397        + ", proxy="
398        + route.getProxy()
399        + " hostAddress="
400        + route.getSocketAddress()
401        + " cipherSuite="
402        + (handshake != null ? handshake.cipherSuite() : "none")
403        + " protocol="
404        + protocol
405        + '}';
406  }
407}
408