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.lang.System.Logger.Level;
  30 import java.net.InetSocketAddress;
  31 import java.nio.ByteBuffer;
  32 import java.nio.channels.SocketChannel;
  33 import java.util.concurrent.CompletableFuture;
  34 import java.util.function.Function;
  35 import java.net.http.HttpHeaders;
  36 import jdk.internal.net.http.common.FlowTube;
  37 import jdk.internal.net.http.common.MinimalFuture;
  38 import static java.net.http.HttpResponse.BodyHandlers.discarding;
  39 
  40 /**
  41  * A plain text socket tunnel through a proxy. Uses "CONNECT" but does not
  42  * encrypt. Used by WebSocket, as well as HTTP over SSL + Proxy.
  43  * Wrapped in SSLTunnelConnection or AsyncSSLTunnelConnection for encryption.
  44  */
  45 final class PlainTunnelingConnection extends HttpConnection {
  46 
  47     final PlainHttpConnection delegate;
  48     final HttpHeaders proxyHeaders;
  49     final InetSocketAddress proxyAddr;
  50     private volatile boolean connected;
  51 
  52     protected PlainTunnelingConnection(InetSocketAddress addr,
  53                                        InetSocketAddress proxy,
  54                                        HttpClientImpl client,
  55                                        HttpHeaders proxyHeaders) {
  56         super(addr, client);
  57         this.proxyAddr = proxy;
  58         this.proxyHeaders = proxyHeaders;
  59         delegate = new PlainHttpConnection(proxy, client);
  60     }
  61 
  62     @Override
  63     public CompletableFuture<Void> connectAsync() {
  64         if (debug.on()) debug.log("Connecting plain connection");
  65         return delegate.connectAsync()
  66             .thenCompose((Void v) -> {
  67                 if (debug.on()) debug.log("sending HTTP/1.1 CONNECT");
  68                 HttpClientImpl client = client();
  69                 assert client != null;
  70                 HttpRequestImpl req = new HttpRequestImpl("CONNECT", address, proxyHeaders);
  71                 MultiExchange<Void> mulEx = new MultiExchange<>(null, req,
  72                         client, discarding(), null, null);
  73                 Exchange<Void> connectExchange = new Exchange<>(req, mulEx);
  74 
  75                 return connectExchange
  76                         .responseAsyncImpl(delegate)
  77                         .thenCompose((Response resp) -> {
  78                             CompletableFuture<Void> cf = new MinimalFuture<>();
  79                             if (debug.on()) debug.log("got response: %d", resp.statusCode());
  80                             if (resp.statusCode() == 407) {
  81                                 return connectExchange.ignoreBody().handle((r,t) -> {
  82                                     // close delegate after reading body: we won't
  83                                     // be reusing that connection anyway.
  84                                     delegate.close();
  85                                     ProxyAuthenticationRequired authenticationRequired =
  86                                             new ProxyAuthenticationRequired(resp);
  87                                     cf.completeExceptionally(authenticationRequired);
  88                                     return cf;
  89                                 }).thenCompose(Function.identity());
  90                             } else if (resp.statusCode() != 200) {
  91                                 delegate.close();
  92                                 cf.completeExceptionally(new IOException(
  93                                         "Tunnel failed, got: "+ resp.statusCode()));
  94                             } else {
  95                                 // get the initial/remaining bytes
  96                                 ByteBuffer b = ((Http1Exchange<?>)connectExchange.exchImpl).drainLeftOverBytes();
  97                                 int remaining = b.remaining();
  98                                 assert remaining == 0: "Unexpected remaining: " + remaining;
  99                                 connected = true;
 100                                 cf.complete(null);
 101                             }
 102                             return cf;
 103                         });
 104             });
 105     }
 106 
 107     @Override
 108     boolean isTunnel() { return true; }
 109 
 110     @Override
 111     HttpPublisher publisher() { return delegate.publisher(); }
 112 
 113     @Override
 114     boolean connected() {
 115         return connected;
 116     }
 117 
 118     @Override
 119     SocketChannel channel() {
 120         return delegate.channel();
 121     }
 122 
 123     @Override
 124     FlowTube getConnectionFlow() {
 125         return delegate.getConnectionFlow();
 126     }
 127 
 128     @Override
 129     ConnectionPool.CacheKey cacheKey() {
 130         return new ConnectionPool.CacheKey(null, proxyAddr);
 131     }
 132 
 133     @Override
 134     public void close() {
 135         delegate.close();
 136         connected = false;
 137     }
 138 
 139     @Override
 140     boolean isSecure() {
 141         return false;
 142     }
 143 
 144     @Override
 145     boolean isProxied() {
 146         return true;
 147     }
 148 
 149 }