1 /* 2 * Copyright (c) 2010, 2019, 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 java.net; 27 28 import java.io.IOException; 29 import java.lang.reflect.Field; 30 import java.lang.reflect.Method; 31 import java.util.HashMap; 32 import java.util.Map; 33 import java.util.Set; 34 35 /** 36 * Basic SocketImpl that relies on the internal HTTP protocol handler 37 * implementation to perform the HTTP tunneling and authentication. Once 38 * connected, all socket operations delegate to a platform SocketImpl. 39 * 40 * @since 1.8 41 */ 42 43 /*package*/ class HttpConnectSocketImpl extends DelegatingSocketImpl { 44 45 private static final String httpURLClazzStr = 46 "sun.net.www.protocol.http.HttpURLConnection"; 47 private static final String netClientClazzStr = "sun.net.NetworkClient"; 48 private static final String doTunnelingStr = "doTunneling"; 49 private static final Field httpField; 50 private static final Field serverSocketField; 51 private static final Method doTunneling; 52 53 private final String server; 54 private InetSocketAddress external_address; 55 private final Socket socket; 56 private HashMap<Integer, Object> optionsMap = new HashMap<>(); 57 58 static { 59 try { 60 Class<?> httpClazz = Class.forName(httpURLClazzStr, true, null); 61 httpField = httpClazz.getDeclaredField("http"); 62 doTunneling = httpClazz.getDeclaredMethod(doTunnelingStr); 63 Class<?> netClientClazz = Class.forName(netClientClazzStr, true, null); 64 serverSocketField = netClientClazz.getDeclaredField("serverSocket"); 65 66 java.security.AccessController.doPrivileged( 67 new java.security.PrivilegedAction<>() { 68 public Void run() { 69 httpField.setAccessible(true); 70 serverSocketField.setAccessible(true); 71 return null; 72 } 73 }); 74 } catch (ReflectiveOperationException x) { 75 throw new InternalError("Should not reach here", x); 76 } 77 } 78 79 HttpConnectSocketImpl(Proxy proxy, SocketImpl delegate, Socket socket) { 80 super(delegate); 81 SocketAddress a = proxy.address(); 82 if ( !(a instanceof InetSocketAddress) ) 83 throw new IllegalArgumentException("Unsupported address type"); 84 85 InetSocketAddress ad = (InetSocketAddress) a; 86 server = ad.getHostString(); 87 port = ad.getPort(); 88 this.socket = socket; 89 } 90 91 @Override 92 protected void connect(String host, int port) throws IOException { 93 connect(new InetSocketAddress(host, port), 0); 94 } 95 96 @Override 97 protected void connect(InetAddress address, int port) throws IOException { 98 connect(new InetSocketAddress(address, port), 0); 99 } 100 101 @Override 102 protected void connect(SocketAddress endpoint, int timeout) 103 throws IOException 104 { 105 if (endpoint == null || !(endpoint instanceof InetSocketAddress)) 106 throw new IllegalArgumentException("Unsupported address type"); 107 final InetSocketAddress epoint = (InetSocketAddress)endpoint; 108 String destHost = epoint.isUnresolved() ? epoint.getHostName() 109 : epoint.getAddress().getHostAddress(); 110 final int destPort = epoint.getPort(); 111 112 SecurityManager security = System.getSecurityManager(); 113 if (security != null) 114 security.checkConnect(destHost, destPort); 115 116 if (destHost.contains(":")) 117 destHost = "[" + destHost + "]"; 118 119 // Connect to the HTTP proxy server 120 String urlString = "http://" + destHost + ":" + destPort; 121 Socket httpSocket = privilegedDoTunnel(urlString, timeout); 122 123 // Success! 124 external_address = epoint; 125 126 // close the original socket impl and release its descriptor 127 close(); 128 129 // update the Sockets impl to the impl from the http Socket 130 SocketImpl si = httpSocket.impl; 131 socket.setImpl(si); // TODO REMOVE: Is this ok, or delegating? 132 133 // best effort is made to try and reset options previously set 134 Set<Map.Entry<Integer,Object>> options = optionsMap.entrySet(); 135 try { 136 for(Map.Entry<Integer,Object> entry : options) { 137 si.setOption(entry.getKey(), entry.getValue()); 138 } 139 } catch (IOException x) { /* gulp! */ } 140 } 141 142 143 @Override 144 protected void listen(int backlog) { 145 throw new InternalError("should not get here"); 146 } 147 148 @Override 149 protected void accept(SocketImpl s) { 150 throw new InternalError("should not get here"); 151 } 152 153 @Override 154 void reset() { 155 throw new InternalError("should not get here"); 156 } 157 158 @Override 159 public void setOption(int opt, Object val) throws SocketException { 160 delegate.setOption(opt, val); 161 162 if (external_address != null) 163 return; // we're connected, just return 164 165 // store options so that they can be re-applied to the impl after connect 166 optionsMap.put(opt, val); 167 } 168 169 private Socket privilegedDoTunnel(final String urlString, 170 final int timeout) 171 throws IOException 172 { 173 try { 174 return java.security.AccessController.doPrivileged( 175 new java.security.PrivilegedExceptionAction<>() { 176 public Socket run() throws IOException { 177 return doTunnel(urlString, timeout); 178 } 179 }); 180 } catch (java.security.PrivilegedActionException pae) { 181 throw (IOException) pae.getException(); 182 } 183 } 184 185 private Socket doTunnel(String urlString, int connectTimeout) 186 throws IOException 187 { 188 Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(server, port)); 189 URL destURL = new URL(urlString); 190 HttpURLConnection conn = (HttpURLConnection) destURL.openConnection(proxy); 191 conn.setConnectTimeout(connectTimeout); 192 int timeout = (int) getOption(SocketOptions.SO_TIMEOUT); 193 if (timeout > 0) { 194 conn.setReadTimeout(timeout); 195 } 196 conn.connect(); 197 doTunneling(conn); 198 try { 199 Object httpClient = httpField.get(conn); 200 return (Socket) serverSocketField.get(httpClient); 201 } catch (IllegalAccessException x) { 202 throw new InternalError("Should not reach here", x); 203 } 204 } 205 206 private void doTunneling(HttpURLConnection conn) throws IOException { 207 try { 208 doTunneling.invoke(conn); 209 } catch (ReflectiveOperationException x) { 210 Throwable cause = x.getCause(); 211 if (cause instanceof IOException) { 212 throw (IOException) cause; 213 } 214 throw new InternalError("Should not reach here", x); 215 } 216 } 217 218 @Override 219 protected InetAddress getInetAddress() { 220 if (external_address != null) 221 return external_address.getAddress(); 222 else 223 return delegate.getInetAddress(); 224 } 225 226 @Override 227 protected int getPort() { 228 if (external_address != null) 229 return external_address.getPort(); 230 else 231 return delegate.getPort(); 232 } 233 }