1 /*
2  * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package jdk.internal.net.http;
27 
28 import java.io.IOException;
29 import java.net.InetSocketAddress;
30 import java.net.http.HttpTimeoutException;
31 import java.nio.ByteBuffer;
32 import java.nio.channels.SocketChannel;
33 import java.time.Duration;
34 import java.util.concurrent.CompletableFuture;
35 import java.util.concurrent.CompletionException;
36 import java.util.function.Function;
37 import java.net.http.HttpHeaders;
38 import jdk.internal.net.http.common.FlowTube;
39 import jdk.internal.net.http.common.MinimalFuture;
40 import static java.net.http.HttpResponse.BodyHandlers.discarding;
41 
42 /**
43  * A plain text socket tunnel through a proxy. Uses "CONNECT" but does not
44  * encrypt. Used by WebSocket, as well as HTTP over SSL + Proxy.
45  * Wrapped in SSLTunnelConnection or AsyncSSLTunnelConnection for encryption.
46  */
47 final class PlainTunnelingConnection extends HttpConnection {
48 
49     final PlainHttpConnection delegate;
50     final HttpHeaders proxyHeaders;
51     final InetSocketAddress proxyAddr;
52     private volatile boolean connected;
53 
PlainTunnelingConnection(InetSocketAddress addr, InetSocketAddress proxy, HttpClientImpl client, HttpHeaders proxyHeaders)54     protected PlainTunnelingConnection(InetSocketAddress addr,
55                                        InetSocketAddress proxy,
56                                        HttpClientImpl client,
57                                        HttpHeaders proxyHeaders) {
58         super(addr, client);
59         this.proxyAddr = proxy;
60         this.proxyHeaders = proxyHeaders;
61         delegate = new PlainHttpConnection(proxy, client);
62     }
63 
64     @Override
connectAsync(Exchange<?> exchange)65     public CompletableFuture<Void> connectAsync(Exchange<?> exchange) {
66         if (debug.on()) debug.log("Connecting plain connection");
67         return delegate.connectAsync(exchange)
68             .thenCompose(unused -> delegate.finishConnect())
69             .thenCompose((Void v) -> {
70                 if (debug.on()) debug.log("sending HTTP/1.1 CONNECT");
71                 HttpClientImpl client = client();
72                 assert client != null;
73                 HttpRequestImpl req = new HttpRequestImpl("CONNECT", address, proxyHeaders);
74                 MultiExchange<Void> mulEx = new MultiExchange<>(null, req,
75                         client, discarding(), null, null);
76                 Exchange<Void> connectExchange = mulEx.getExchange();
77 
78                 return connectExchange
79                         .responseAsyncImpl(delegate)
80                         .thenCompose((Response resp) -> {
81                             CompletableFuture<Void> cf = new MinimalFuture<>();
82                             if (debug.on()) debug.log("got response: %d", resp.statusCode());
83                             if (resp.statusCode() == 407) {
84                                 return connectExchange.ignoreBody().handle((r,t) -> {
85                                     // close delegate after reading body: we won't
86                                     // be reusing that connection anyway.
87                                     delegate.close();
88                                     ProxyAuthenticationRequired authenticationRequired =
89                                             new ProxyAuthenticationRequired(resp);
90                                     cf.completeExceptionally(authenticationRequired);
91                                     return cf;
92                                 }).thenCompose(Function.identity());
93                             } else if (resp.statusCode() != 200) {
94                                 delegate.close();
95                                 cf.completeExceptionally(new IOException(
96                                         "Tunnel failed, got: "+ resp.statusCode()));
97                             } else {
98                                 // get the initial/remaining bytes
99                                 ByteBuffer b = ((Http1Exchange<?>)connectExchange.exchImpl).drainLeftOverBytes();
100                                 int remaining = b.remaining();
101                                 assert remaining == 0: "Unexpected remaining: " + remaining;
102                                 cf.complete(null);
103                             }
104                             return cf;
105                         })
106                         .handle((result, ex) -> {
107                             if (ex == null) {
108                                 return MinimalFuture.completedFuture(result);
109                             } else {
110                                 if (debug.on())
111                                     debug.log("tunnel failed with \"%s\"", ex.toString());
112                                 Throwable t = ex;
113                                 if (t instanceof CompletionException)
114                                     t = t.getCause();
115                                 if (t instanceof HttpTimeoutException) {
116                                     String msg = "proxy tunneling CONNECT request timed out";
117                                     t = new HttpTimeoutException(msg);
118                                     t.initCause(ex);
119                                 }
120                                 return MinimalFuture.<Void>failedFuture(t);
121                             }
122                         })
123                         .thenCompose(Function.identity());
124             });
125     }
126 
127     public CompletableFuture<Void> finishConnect() {
128         connected = true;
129         return MinimalFuture.completedFuture(null);
130     }
131 
132     @Override
133     boolean isTunnel() { return true; }
134 
135     @Override
136     HttpPublisher publisher() { return delegate.publisher(); }
137 
138     @Override
139     boolean connected() {
140         return connected;
141     }
142 
143     @Override
144     SocketChannel channel() {
145         return delegate.channel();
146     }
147 
148     @Override
149     FlowTube getConnectionFlow() {
150         return delegate.getConnectionFlow();
151     }
152 
153     @Override
154     ConnectionPool.CacheKey cacheKey() {
155         return new ConnectionPool.CacheKey(null, proxyAddr);
156     }
157 
158     @Override
159     public void close() {
160         delegate.close();
161         connected = false;
162     }
163 
164     @Override
165     boolean isSecure() {
166         return false;
167     }
168 
169     @Override
170     boolean isProxied() {
171         return true;
172     }
173 
174 }
175