|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
package sun.net.www.protocol.https; |
|
|
|
import java.io.IOException; |
|
import java.io.UnsupportedEncodingException; |
|
import java.io.PrintStream; |
|
import java.io.BufferedOutputStream; |
|
import java.net.InetAddress; |
|
import java.net.Socket; |
|
import java.net.SocketException; |
|
import java.net.URL; |
|
import java.net.UnknownHostException; |
|
import java.net.InetSocketAddress; |
|
import java.net.Proxy; |
|
import java.security.Principal; |
|
import java.security.cert.*; |
|
import java.util.Objects; |
|
import java.util.StringTokenizer; |
|
import java.util.Vector; |
|
|
|
import javax.security.auth.x500.X500Principal; |
|
|
|
import javax.net.ssl.*; |
|
import sun.net.www.http.HttpClient; |
|
import sun.net.www.protocol.http.AuthenticatorKeys; |
|
import sun.net.www.protocol.http.HttpURLConnection; |
|
import sun.security.action.*; |
|
|
|
import sun.security.util.HostnameChecker; |
|
import sun.security.ssl.SSLSocketImpl; |
|
|
|
import sun.util.logging.PlatformLogger; |
|
import static sun.net.www.protocol.http.HttpURLConnection.TunnelState.*; |
|
|
|
|
|
/** |
|
* This class provides HTTPS client URL support, building on the standard |
|
* "sun.net.www" HTTP protocol handler. HTTPS is the same protocol as HTTP, |
|
* but differs in the transport layer which it uses: <UL> |
|
* |
|
* <LI>There's a <em>Secure Sockets Layer</em> between TCP |
|
* and the HTTP protocol code. |
|
* |
|
* <LI>It uses a different default TCP port. |
|
* |
|
* <LI>It doesn't use application level proxies, which can see and |
|
* manipulate HTTP user level data, compromising privacy. It uses |
|
* low level tunneling instead, which hides HTTP protocol and data |
|
* from all third parties. (Traffic analysis is still possible). |
|
* |
|
* <LI>It does basic server authentication, to protect |
|
* against "URL spoofing" attacks. This involves deciding |
|
* whether the X.509 certificate chain identifying the server |
|
* is trusted, and verifying that the name of the server is |
|
* found in the certificate. (The application may enable an |
|
* anonymous SSL cipher suite, and such checks are not done |
|
* for anonymous ciphers.) |
|
* |
|
* <LI>It exposes key SSL session attributes, specifically the |
|
* cipher suite in use and the server's X509 certificates, to |
|
* application software which knows about this protocol handler. |
|
* |
|
* </UL> |
|
* |
|
* <P> System properties used include: <UL> |
|
* |
|
* <LI><em>https.proxyHost</em> ... the host supporting SSL |
|
* tunneling using the conventional CONNECT syntax |
|
* |
|
* <LI><em>https.proxyPort</em> ... port to use on proxyHost |
|
* |
|
* <LI><em>https.cipherSuites</em> ... comma separated list of |
|
* SSL cipher suite names to enable. |
|
* |
|
* <LI><em>http.nonProxyHosts</em> ... |
|
* |
|
* </UL> |
|
* |
|
* @author David Brownell |
|
* @author Bill Foote |
|
*/ |
|
|
|
|
|
final class HttpsClient extends HttpClient |
|
implements HandshakeCompletedListener |
|
{ |
|
// STATIC STATE and ACCESSORS THERETO |
|
|
|
|
|
private static final int httpsPortNumber = 443; |
|
|
|
|
|
private static final String defaultHVCanonicalName = |
|
"javax.net.ssl.HttpsURLConnection.DefaultHostnameVerifier"; |
|
|
|
|
|
@Override |
|
protected int getDefaultPort() { return httpsPortNumber; } |
|
|
|
private HostnameVerifier hv; |
|
private SSLSocketFactory sslSocketFactory; |
|
|
|
// HttpClient.proxyDisabled will always be false, because we don't |
|
// use an application-level HTTP proxy. We might tunnel through |
|
// our http proxy, though. |
|
|
|
|
|
// INSTANCE DATA |
|
|
|
|
|
private SSLSession session; |
|
|
|
private String [] getCipherSuites() { |
|
// |
|
// If ciphers are assigned, sort them into an array. |
|
|
|
String ciphers []; |
|
String cipherString = |
|
GetPropertyAction.privilegedGetProperty("https.cipherSuites"); |
|
|
|
if (cipherString == null || "".equals(cipherString)) { |
|
ciphers = null; |
|
} else { |
|
StringTokenizer tokenizer; |
|
Vector<String> v = new Vector<String>(); |
|
|
|
tokenizer = new StringTokenizer(cipherString, ","); |
|
while (tokenizer.hasMoreTokens()) |
|
v.addElement(tokenizer.nextToken()); |
|
ciphers = new String [v.size()]; |
|
for (int i = 0; i < ciphers.length; i++) |
|
ciphers [i] = v.elementAt(i); |
|
} |
|
return ciphers; |
|
} |
|
|
|
private String [] getProtocols() { |
|
// |
|
// If protocols are assigned, sort them into an array. |
|
|
|
String protocols []; |
|
String protocolString = |
|
GetPropertyAction.privilegedGetProperty("https.protocols"); |
|
|
|
if (protocolString == null || "".equals(protocolString)) { |
|
protocols = null; |
|
} else { |
|
StringTokenizer tokenizer; |
|
Vector<String> v = new Vector<String>(); |
|
|
|
tokenizer = new StringTokenizer(protocolString, ","); |
|
while (tokenizer.hasMoreTokens()) |
|
v.addElement(tokenizer.nextToken()); |
|
protocols = new String [v.size()]; |
|
for (int i = 0; i < protocols.length; i++) { |
|
protocols [i] = v.elementAt(i); |
|
} |
|
} |
|
return protocols; |
|
} |
|
|
|
private String getUserAgent() { |
|
String userAgent = |
|
GetPropertyAction.privilegedGetProperty("https.agent"); |
|
if (userAgent == null || userAgent.length() == 0) { |
|
userAgent = "JSSE"; |
|
} |
|
return userAgent; |
|
} |
|
|
|
// CONSTRUCTOR, FACTORY |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private HttpsClient(SSLSocketFactory sf, URL url) |
|
throws IOException |
|
{ |
|
// HttpClient-level proxying is always disabled, |
|
|
|
this(sf, url, (String)null, -1); |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
HttpsClient(SSLSocketFactory sf, URL url, String proxyHost, int proxyPort) |
|
throws IOException { |
|
this(sf, url, proxyHost, proxyPort, -1); |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
HttpsClient(SSLSocketFactory sf, URL url, String proxyHost, int proxyPort, |
|
int connectTimeout) |
|
throws IOException { |
|
this(sf, url, |
|
(proxyHost == null? null: |
|
HttpClient.newHttpProxy(proxyHost, proxyPort, "https")), |
|
connectTimeout); |
|
} |
|
|
|
|
|
|
|
*/ |
|
HttpsClient(SSLSocketFactory sf, URL url, Proxy proxy, |
|
int connectTimeout) |
|
throws IOException { |
|
PlatformLogger logger = HttpURLConnection.getHttpLogger(); |
|
if (logger.isLoggable(PlatformLogger.Level.FINEST)) { |
|
logger.finest("Creating new HttpsClient with url:" + url + " and proxy:" + proxy + |
|
" with connect timeout:" + connectTimeout); |
|
} |
|
this.proxy = proxy; |
|
setSSLSocketFactory(sf); |
|
this.proxyDisabled = true; |
|
|
|
this.host = url.getHost(); |
|
this.url = url; |
|
port = url.getPort(); |
|
if (port == -1) { |
|
port = getDefaultPort(); |
|
} |
|
setConnectTimeout(connectTimeout); |
|
openServer(); |
|
} |
|
|
|
|
|
// This code largely ripped off from HttpClient.New, and |
|
// it uses the same keepalive cache. |
|
|
|
static HttpClient New(SSLSocketFactory sf, URL url, HostnameVerifier hv, |
|
HttpURLConnection httpuc) |
|
throws IOException { |
|
return HttpsClient.New(sf, url, hv, true, httpuc); |
|
} |
|
|
|
|
|
static HttpClient New(SSLSocketFactory sf, URL url, |
|
HostnameVerifier hv, boolean useCache, |
|
HttpURLConnection httpuc) throws IOException { |
|
return HttpsClient.New(sf, url, hv, (String)null, -1, useCache, httpuc); |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
static HttpClient New(SSLSocketFactory sf, URL url, HostnameVerifier hv, |
|
String proxyHost, int proxyPort, |
|
HttpURLConnection httpuc) throws IOException { |
|
return HttpsClient.New(sf, url, hv, proxyHost, proxyPort, true, httpuc); |
|
} |
|
|
|
static HttpClient New(SSLSocketFactory sf, URL url, HostnameVerifier hv, |
|
String proxyHost, int proxyPort, boolean useCache, |
|
HttpURLConnection httpuc) |
|
throws IOException { |
|
return HttpsClient.New(sf, url, hv, proxyHost, proxyPort, useCache, -1, |
|
httpuc); |
|
} |
|
|
|
static HttpClient New(SSLSocketFactory sf, URL url, HostnameVerifier hv, |
|
String proxyHost, int proxyPort, boolean useCache, |
|
int connectTimeout, HttpURLConnection httpuc) |
|
throws IOException { |
|
|
|
return HttpsClient.New(sf, url, hv, |
|
(proxyHost == null? null : |
|
HttpClient.newHttpProxy(proxyHost, proxyPort, "https")), |
|
useCache, connectTimeout, httpuc); |
|
} |
|
|
|
static HttpClient New(SSLSocketFactory sf, URL url, HostnameVerifier hv, |
|
Proxy p, boolean useCache, |
|
int connectTimeout, HttpURLConnection httpuc) |
|
throws IOException |
|
{ |
|
if (p == null) { |
|
p = Proxy.NO_PROXY; |
|
} |
|
PlatformLogger logger = HttpURLConnection.getHttpLogger(); |
|
if (logger.isLoggable(PlatformLogger.Level.FINEST)) { |
|
logger.finest("Looking for HttpClient for URL " + url + |
|
" and proxy value of " + p); |
|
} |
|
HttpsClient ret = null; |
|
if (useCache) { |
|
|
|
ret = (HttpsClient) kac.get(url, sf); |
|
if (ret != null && httpuc != null && |
|
httpuc.streaming() && |
|
httpuc.getRequestMethod() == "POST") { |
|
if (!ret.available()) |
|
ret = null; |
|
} |
|
|
|
if (ret != null) { |
|
String ak = httpuc == null ? AuthenticatorKeys.DEFAULT |
|
: httpuc.getAuthenticatorKey(); |
|
boolean compatible = ((ret.proxy != null && ret.proxy.equals(p)) || |
|
(ret.proxy == null && p == Proxy.NO_PROXY)) |
|
&& Objects.equals(ret.getAuthenticatorKey(), ak); |
|
if (compatible) { |
|
synchronized (ret) { |
|
ret.cachedHttpClient = true; |
|
assert ret.inCache; |
|
ret.inCache = false; |
|
if (httpuc != null && ret.needsTunneling()) |
|
httpuc.setTunnelState(TUNNELING); |
|
if (logger.isLoggable(PlatformLogger.Level.FINEST)) { |
|
logger.finest("KeepAlive stream retrieved from the cache, " + ret); |
|
} |
|
} |
|
} else { |
|
// We cannot return this connection to the cache as it's |
|
// KeepAliveTimeout will get reset. We simply close the connection. |
|
// This should be fine as it is very rare that a connection |
|
|
|
synchronized(ret) { |
|
if (logger.isLoggable(PlatformLogger.Level.FINEST)) { |
|
logger.finest("Not returning this connection to cache: " + ret); |
|
} |
|
ret.inCache = false; |
|
ret.closeServer(); |
|
} |
|
ret = null; |
|
} |
|
} |
|
} |
|
if (ret == null) { |
|
ret = new HttpsClient(sf, url, p, connectTimeout); |
|
if (httpuc != null) { |
|
ret.authenticatorKey = httpuc.getAuthenticatorKey(); |
|
} |
|
} else { |
|
SecurityManager security = System.getSecurityManager(); |
|
if (security != null) { |
|
if (ret.proxy == Proxy.NO_PROXY || ret.proxy == null) { |
|
security.checkConnect(InetAddress.getByName(url.getHost()).getHostAddress(), url.getPort()); |
|
} else { |
|
security.checkConnect(url.getHost(), url.getPort()); |
|
} |
|
} |
|
ret.url = url; |
|
} |
|
ret.setHostnameVerifier(hv); |
|
|
|
return ret; |
|
} |
|
|
|
|
|
void setHostnameVerifier(HostnameVerifier hv) { |
|
this.hv = hv; |
|
} |
|
|
|
void setSSLSocketFactory(SSLSocketFactory sf) { |
|
sslSocketFactory = sf; |
|
} |
|
|
|
SSLSocketFactory getSSLSocketFactory() { |
|
return sslSocketFactory; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
protected Socket createSocket() throws IOException { |
|
try { |
|
return sslSocketFactory.createSocket(); |
|
} catch (SocketException se) { |
|
// |
|
// bug 6771432 |
|
// javax.net.SocketFactory throws a SocketException with an |
|
// UnsupportedOperationException as its cause to indicate that |
|
// unconnected sockets have not been implemented. |
|
|
|
Throwable t = se.getCause(); |
|
if (t != null && t instanceof UnsupportedOperationException) { |
|
return super.createSocket(); |
|
} else { |
|
throw se; |
|
} |
|
} |
|
} |
|
|
|
|
|
@Override |
|
public boolean needsTunneling() { |
|
return (proxy != null && proxy.type() != Proxy.Type.DIRECT |
|
&& proxy.type() != Proxy.Type.SOCKS); |
|
} |
|
|
|
@Override |
|
public void afterConnect() throws IOException, UnknownHostException { |
|
if (!isCachedConnection()) { |
|
SSLSocket s = null; |
|
SSLSocketFactory factory = sslSocketFactory; |
|
try { |
|
if (!(serverSocket instanceof SSLSocket)) { |
|
s = (SSLSocket)factory.createSocket(serverSocket, |
|
host, port, true); |
|
} else { |
|
s = (SSLSocket)serverSocket; |
|
if (s instanceof SSLSocketImpl) { |
|
((SSLSocketImpl)s).setHost(host); |
|
} |
|
} |
|
} catch (IOException ex) { |
|
// If we fail to connect through the tunnel, try it |
|
// locally, as a last resort. If this doesn't work, |
|
|
|
try { |
|
s = (SSLSocket)factory.createSocket(host, port); |
|
} catch (IOException ignored) { |
|
throw ex; |
|
} |
|
} |
|
|
|
// |
|
// Force handshaking, so that we get any authentication. |
|
// Register a handshake callback so our session state tracks any |
|
// later session renegotiations. |
|
|
|
String [] protocols = getProtocols(); |
|
String [] ciphers = getCipherSuites(); |
|
if (protocols != null) { |
|
s.setEnabledProtocols(protocols); |
|
} |
|
if (ciphers != null) { |
|
s.setEnabledCipherSuites(ciphers); |
|
} |
|
s.addHandshakeCompletedListener(this); |
|
|
|
// We have two hostname verification approaches. One is in |
|
// SSL/TLS socket layer, where the algorithm is configured with |
|
// SSLParameters.setEndpointIdentificationAlgorithm(), and the |
|
// hostname verification is done by X509ExtendedTrustManager when |
|
// the algorithm is "HTTPS". The other one is in HTTPS layer, |
|
// where the algorithm is customized by |
|
// HttpsURLConnection.setHostnameVerifier(), and the hostname |
|
// verification is done by HostnameVerifier when the default |
|
// rules for hostname verification fail. |
|
// |
|
// The relationship between two hostname verification approaches |
|
// likes the following: |
|
// |
|
// | EIA algorithm |
|
// +---------------------------------------------- |
|
// | null | HTTPS | LDAP/other | |
|
// ------------------------------------------------------------- |
|
// | |1 |2 |3 | |
|
// HNV | default | Set HTTPS EIA | use EIA | HTTPS | |
|
// |-------------------------------------------------------- |
|
// | non - |4 |5 |6 | |
|
// | default | HTTPS/HNV | use EIA | HTTPS/HNV | |
|
// ------------------------------------------------------------- |
|
// |
|
// Abbreviation: |
|
// EIA: the endpoint identification algorithm in SSL/TLS |
|
// socket layer |
|
// HNV: the hostname verification object in HTTPS layer |
|
// Notes: |
|
// case 1. default HNV and EIA is null |
|
// Set EIA as HTTPS, hostname check done in SSL/TLS |
|
// layer. |
|
// case 2. default HNV and EIA is HTTPS |
|
// Use existing EIA, hostname check done in SSL/TLS |
|
// layer. |
|
// case 3. default HNV and EIA is other than HTTPS |
|
// Use existing EIA, EIA check done in SSL/TLS |
|
// layer, then do HTTPS check in HTTPS layer. |
|
// case 4. non-default HNV and EIA is null |
|
// No EIA, no EIA check done in SSL/TLS layer, then do |
|
// HTTPS check in HTTPS layer using HNV as override. |
|
// case 5. non-default HNV and EIA is HTTPS |
|
// Use existing EIA, hostname check done in SSL/TLS |
|
// layer. No HNV override possible. We will review this |
|
// decision and may update the architecture for JDK 7. |
|
// case 6. non-default HNV and EIA is other than HTTPS |
|
// Use existing EIA, EIA check done in SSL/TLS layer, |
|
|
|
boolean needToCheckSpoofing = true; |
|
String identification = |
|
s.getSSLParameters().getEndpointIdentificationAlgorithm(); |
|
if (identification != null && identification.length() != 0) { |
|
if (identification.equalsIgnoreCase("HTTPS")) { |
|
// Do not check server identity again out of SSLSocket, |
|
// the endpoint will be identified during TLS handshaking |
|
|
|
needToCheckSpoofing = false; |
|
} // else, we don't understand the identification algorithm, |
|
// need to check URL spoofing here. |
|
} else { |
|
boolean isDefaultHostnameVerifier = false; |
|
|
|
// We prefer to let the SSLSocket do the spoof checks, but if |
|
// the application has specified a HostnameVerifier (HNV), |
|
|
|
if (hv != null) { |
|
String canonicalName = hv.getClass().getCanonicalName(); |
|
if (canonicalName != null && |
|
canonicalName.equalsIgnoreCase(defaultHVCanonicalName)) { |
|
isDefaultHostnameVerifier = true; |
|
} |
|
} else { |
|
// Unlikely to happen! As the behavior is the same as the |
|
// default hostname verifier, so we prefer to let the |
|
|
|
isDefaultHostnameVerifier = true; |
|
} |
|
|
|
if (isDefaultHostnameVerifier) { |
|
// If the HNV is the default from HttpsURLConnection, we |
|
|
|
SSLParameters paramaters = s.getSSLParameters(); |
|
paramaters.setEndpointIdentificationAlgorithm("HTTPS"); |
|
s.setSSLParameters(paramaters); |
|
|
|
needToCheckSpoofing = false; |
|
} |
|
} |
|
|
|
s.startHandshake(); |
|
session = s.getSession(); |
|
|
|
serverSocket = s; |
|
try { |
|
serverOutput = new PrintStream( |
|
new BufferedOutputStream(serverSocket.getOutputStream()), |
|
false, encoding); |
|
} catch (UnsupportedEncodingException e) { |
|
throw new InternalError(encoding+" encoding not found"); |
|
} |
|
|
|
|
|
if (needToCheckSpoofing) { |
|
checkURLSpoofing(hv); |
|
} |
|
} else { |
|
// if we are reusing a cached https session, |
|
// we don't need to do handshaking etc. But we do need to |
|
|
|
session = ((SSLSocket)serverSocket).getSession(); |
|
} |
|
} |
|
|
|
// Server identity checking is done according to RFC 2818: HTTP over TLS |
|
|
|
private void checkURLSpoofing(HostnameVerifier hostnameVerifier) |
|
throws IOException { |
|
// |
|
// Get authenticated server name, if any |
|
|
|
String host = url.getHost(); |
|
|
|
|
|
if (host != null && host.startsWith("[") && host.endsWith("]")) { |
|
host = host.substring(1, host.length()-1); |
|
} |
|
|
|
Certificate[] peerCerts = null; |
|
String cipher = session.getCipherSuite(); |
|
try { |
|
HostnameChecker checker = HostnameChecker.getInstance( |
|
HostnameChecker.TYPE_TLS); |
|
|
|
|
|
peerCerts = session.getPeerCertificates(); |
|
|
|
X509Certificate peerCert; |
|
if (peerCerts[0] instanceof |
|
java.security.cert.X509Certificate) { |
|
peerCert = (java.security.cert.X509Certificate)peerCerts[0]; |
|
} else { |
|
throw new SSLPeerUnverifiedException(""); |
|
} |
|
checker.match(host, peerCert); |
|
|
|
|
|
return; |
|
|
|
} catch (SSLPeerUnverifiedException e) { |
|
|
|
// |
|
// client explicitly changed default policy and enabled |
|
// anonymous ciphers; we can't check the standard policy |
|
// |
|
// ignore |
|
} catch (java.security.cert.CertificateException cpe) { |
|
// ignore |
|
} |
|
|
|
if ((cipher != null) && (cipher.indexOf("_anon_") != -1)) { |
|
return; |
|
} else if ((hostnameVerifier != null) && |
|
(hostnameVerifier.verify(host, session))) { |
|
return; |
|
} |
|
|
|
serverSocket.close(); |
|
session.invalidate(); |
|
|
|
throw new IOException("HTTPS hostname wrong: should be <" |
|
+ url.getHost() + ">"); |
|
} |
|
|
|
@Override |
|
protected void putInKeepAliveCache() { |
|
if (inCache) { |
|
assert false : "Duplicate put to keep alive cache"; |
|
return; |
|
} |
|
inCache = true; |
|
kac.put(url, sslSocketFactory, this); |
|
} |
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public void closeIdleConnection() { |
|
HttpClient http = kac.get(url, sslSocketFactory); |
|
if (http != null) { |
|
http.closeServer(); |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
String getCipherSuite() { |
|
return session.getCipherSuite(); |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
public java.security.cert.Certificate [] getLocalCertificates() { |
|
return session.getLocalCertificates(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
java.security.cert.Certificate [] getServerCertificates() |
|
throws SSLPeerUnverifiedException |
|
{ |
|
return session.getPeerCertificates(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
Principal getPeerPrincipal() |
|
throws SSLPeerUnverifiedException |
|
{ |
|
Principal principal; |
|
try { |
|
principal = session.getPeerPrincipal(); |
|
} catch (AbstractMethodError e) { |
|
// if the provider does not support it, fallback to peer certs. |
|
|
|
java.security.cert.Certificate[] certs = |
|
session.getPeerCertificates(); |
|
principal = ((X509Certificate)certs[0]).getSubjectX500Principal(); |
|
} |
|
return principal; |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
Principal getLocalPrincipal() |
|
{ |
|
Principal principal; |
|
try { |
|
principal = session.getLocalPrincipal(); |
|
} catch (AbstractMethodError e) { |
|
principal = null; |
|
// if the provider does not support it, fallback to local certs. |
|
|
|
java.security.cert.Certificate[] certs = |
|
session.getLocalCertificates(); |
|
if (certs != null) { |
|
principal = ((X509Certificate)certs[0]).getSubjectX500Principal(); |
|
} |
|
} |
|
return principal; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public void handshakeCompleted(HandshakeCompletedEvent event) |
|
{ |
|
session = event.getSession(); |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public String getProxyHostUsed() { |
|
if (!needsTunneling()) { |
|
return null; |
|
} else { |
|
return super.getProxyHostUsed(); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public int getProxyPortUsed() { |
|
return (proxy == null || proxy.type() == Proxy.Type.DIRECT || |
|
proxy.type() == Proxy.Type.SOCKS)? -1: |
|
((InetSocketAddress)proxy.address()).getPort(); |
|
} |
|
} |