1 /* 2 * Copyright (c) 2010, 2013, 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. The 38 * sockets impl is swapped out and replaced with the socket from the HTTP 39 * handler after the tunnel is successfully setup. 40 * 41 * @since 1.8 42 */ 43 44 /*package*/ class HttpConnectSocketImpl extends PlainSocketImpl { 45 46 private static final String httpURLClazzStr = 47 "sun.net.www.protocol.http.HttpURLConnection"; 48 private static final String netClientClazzStr = "sun.net.NetworkClient"; 49 private static final String doTunnelingStr = "doTunneling"; 50 private static final Field httpField; 51 private static final Field serverSocketField; 52 private static final Method doTunneling; 53 54 private final String server; 55 private InetSocketAddress external_address; 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(String server, int port) { 80 this.server = server; 81 this.port = port; 82 } 83 84 HttpConnectSocketImpl(Proxy proxy) { 85 SocketAddress a = proxy.address(); 86 if ( !(a instanceof InetSocketAddress) ) 87 throw new IllegalArgumentException("Unsupported address type"); 88 89 InetSocketAddress ad = (InetSocketAddress) a; 90 server = ad.getHostString(); 91 port = ad.getPort(); 92 } 93 94 @Override 95 protected void connect(SocketAddress endpoint, int timeout) 96 throws IOException 97 { 98 if (endpoint == null || !(endpoint instanceof InetSocketAddress)) 99 throw new IllegalArgumentException("Unsupported address type"); 100 final InetSocketAddress epoint = (InetSocketAddress)endpoint; 101 final String destHost = epoint.isUnresolved() ? epoint.getHostName() 102 : epoint.getAddress().getHostAddress(); 103 final int destPort = epoint.getPort(); 104 105 SecurityManager security = System.getSecurityManager(); 106 if (security != null) 107 security.checkConnect(destHost, destPort); 108 109 // Connect to the HTTP proxy server 110 String urlString = "http://" + destHost + ":" + destPort; 111 Socket httpSocket = privilegedDoTunnel(urlString, timeout); 112 113 // Success! 114 external_address = epoint; 115 116 // close the original socket impl and release its descriptor 117 close(); 118 119 // update the Sockets impl to the impl from the http Socket 120 AbstractPlainSocketImpl psi = (AbstractPlainSocketImpl) httpSocket.impl; 121 this.getSocket().impl = psi; 122 123 // best effort is made to try and reset options previously set 124 Set<Map.Entry<Integer,Object>> options = optionsMap.entrySet(); 125 try { 126 for(Map.Entry<Integer,Object> entry : options) { 127 psi.setOption(entry.getKey(), entry.getValue()); 128 } 129 } catch (IOException x) { /* gulp! */ } 130 } 131 132 @Override 133 public void setOption(int opt, Object val) throws SocketException { 134 super.setOption(opt, val); 135 136 if (external_address != null) 137 return; // we're connected, just return 138 139 // store options so that they can be re-applied to the impl after connect 140 optionsMap.put(opt, val); 141 } 142 143 private Socket privilegedDoTunnel(final String urlString, 144 final int timeout) 145 throws IOException 146 { 147 try { 148 return java.security.AccessController.doPrivileged( 149 new java.security.PrivilegedExceptionAction<>() { 150 public Socket run() throws IOException { 151 return doTunnel(urlString, timeout); 152 } 153 }); 154 } catch (java.security.PrivilegedActionException pae) { 155 throw (IOException) pae.getException(); 156 } 157 } 158 159 private Socket doTunnel(String urlString, int connectTimeout) 160 throws IOException 161 { 162 Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(server, port)); 163 URL destURL = new URL(urlString); 164 HttpURLConnection conn = (HttpURLConnection) destURL.openConnection(proxy); 165 conn.setConnectTimeout(connectTimeout); 166 conn.setReadTimeout(this.timeout); 167 conn.connect(); 168 doTunneling(conn); 169 try { 170 Object httpClient = httpField.get(conn); 171 return (Socket) serverSocketField.get(httpClient); 172 } catch (IllegalAccessException x) { 173 throw new InternalError("Should not reach here", x); 174 } 175 } 176 177 private void doTunneling(HttpURLConnection conn) { 178 try { 179 doTunneling.invoke(conn); 180 } catch (ReflectiveOperationException x) { 181 throw new InternalError("Should not reach here", x); 182 } 183 } 184 185 @Override 186 protected InetAddress getInetAddress() { 187 if (external_address != null) 188 return external_address.getAddress(); 189 else 190 return super.getInetAddress(); 191 } 192 193 @Override 194 protected int getPort() { 195 if (external_address != null) 196 return external_address.getPort(); 197 else 198 return super.getPort(); 199 } 200 201 @Override 202 protected int getLocalPort() { 203 if (socket != null) 204 return super.getLocalPort(); 205 if (external_address != null) 206 return external_address.getPort(); 207 else 208 return super.getLocalPort(); 209 } 210 }