/*
* Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package java.net;
import java.io.
IOException;
import java.lang.reflect.
Field;
import java.lang.reflect.
Method;
import java.util.
HashMap;
import java.util.
Map;
import java.util.
Set;
/**
* Basic SocketImpl that relies on the internal HTTP protocol handler
* implementation to perform the HTTP tunneling and authentication. The
* sockets impl is swapped out and replaced with the socket from the HTTP
* handler after the tunnel is successfully setup.
*
* @since 1.8
*/
/*package*/ class
HttpConnectSocketImpl extends
PlainSocketImpl {
private static final
String httpURLClazzStr =
"sun.net.www.protocol.http.HttpURLConnection";
private static final
String netClientClazzStr = "sun.net.NetworkClient";
private static final
String doTunnelingStr = "doTunneling";
private static final
Field httpField;
private static final
Field serverSocketField;
private static final
Method doTunneling;
private final
String server;
private
InetSocketAddress external_address;
private
HashMap<
Integer,
Object>
optionsMap = new
HashMap<>();
static {
try {
Class<?>
httpClazz =
Class.
forName(
httpURLClazzStr, true, null);
httpField =
httpClazz.
getDeclaredField("http");
doTunneling =
httpClazz.
getDeclaredMethod(
doTunnelingStr);
Class<?>
netClientClazz =
Class.
forName(
netClientClazzStr, true, null);
serverSocketField =
netClientClazz.
getDeclaredField("serverSocket");
java.security.
AccessController.
doPrivileged(
new java.security.
PrivilegedAction<
Void>() {
public
Void run() {
httpField.
setAccessible(true);
serverSocketField.
setAccessible(true);
return null;
}
});
} catch (
ReflectiveOperationException x) {
throw new
InternalError("Should not reach here",
x);
}
}
HttpConnectSocketImpl(
String server, int
port) {
this.
server =
server;
this.
port =
port;
}
HttpConnectSocketImpl(
Proxy proxy) {
SocketAddress a =
proxy.
address();
if ( !(
a instanceof
InetSocketAddress) )
throw new
IllegalArgumentException("Unsupported address type");
InetSocketAddress ad = (
InetSocketAddress)
a;
server =
ad.
getHostString();
port =
ad.
getPort();
}
@
Override
protected void
connect(
SocketAddress endpoint, int
timeout)
throws
IOException
{
if (
endpoint == null || !(
endpoint instanceof
InetSocketAddress))
throw new
IllegalArgumentException("Unsupported address type");
final
InetSocketAddress epoint = (
InetSocketAddress)
endpoint;
final
String destHost =
epoint.
isUnresolved() ?
epoint.
getHostName()
:
epoint.
getAddress().
getHostAddress();
final int
destPort =
epoint.
getPort();
SecurityManager security =
System.
getSecurityManager();
if (
security != null)
security.
checkConnect(
destHost,
destPort);
// Connect to the HTTP proxy server
String urlString = "http://" +
destHost + ":" +
destPort;
Socket httpSocket =
privilegedDoTunnel(
urlString,
timeout);
// Success!
external_address =
epoint;
// close the original socket impl and release its descriptor
close();
// update the Sockets impl to the impl from the http Socket
AbstractPlainSocketImpl psi = (
AbstractPlainSocketImpl)
httpSocket.
impl;
this.
getSocket().
impl =
psi;
// best effort is made to try and reset options previously set
Set<
Map.
Entry<
Integer,
Object>>
options =
optionsMap.
entrySet();
try {
for(
Map.
Entry<
Integer,
Object>
entry :
options) {
psi.
setOption(
entry.
getKey(),
entry.
getValue());
}
} catch (
IOException x) { /* gulp! */ }
}
@
Override
public void
setOption(int
opt,
Object val) throws
SocketException {
super.setOption(
opt,
val);
if (
external_address != null)
return; // we're connected, just return
// store options so that they can be re-applied to the impl after connect
optionsMap.
put(
opt,
val);
}
private
Socket privilegedDoTunnel(final
String urlString,
final int
timeout)
throws
IOException
{
try {
return java.security.
AccessController.
doPrivileged(
new java.security.
PrivilegedExceptionAction<
Socket>() {
public
Socket run() throws
IOException {
return
doTunnel(
urlString,
timeout);
}
});
} catch (java.security.
PrivilegedActionException pae) {
throw (
IOException)
pae.
getException();
}
}
private
Socket doTunnel(
String urlString, int
connectTimeout)
throws
IOException
{
Proxy proxy = new
Proxy(
Proxy.
Type.
HTTP, new
InetSocketAddress(
server,
port));
URL destURL = new
URL(
urlString);
HttpURLConnection conn = (
HttpURLConnection)
destURL.
openConnection(
proxy);
conn.
setConnectTimeout(
connectTimeout);
conn.
setReadTimeout(this.
timeout);
conn.
connect();
doTunneling(
conn);
try {
Object httpClient =
httpField.
get(
conn);
return (
Socket)
serverSocketField.
get(
httpClient);
} catch (
IllegalAccessException x) {
throw new
InternalError("Should not reach here",
x);
}
}
private void
doTunneling(
HttpURLConnection conn) {
try {
doTunneling.
invoke(
conn);
} catch (
ReflectiveOperationException x) {
throw new
InternalError("Should not reach here",
x);
}
}
@
Override
protected
InetAddress getInetAddress() {
if (
external_address != null)
return
external_address.
getAddress();
else
return super.getInetAddress();
}
@
Override
protected int
getPort() {
if (
external_address != null)
return
external_address.
getPort();
else
return super.getPort();
}
@
Override
protected int
getLocalPort() {
if (
socket != null)
return super.getLocalPort();
if (
external_address != null)
return
external_address.
getPort();
else
return super.getLocalPort();
}
}