HttpsClient.java revision 0
869N/A/*
869N/A * Copyright 2001-2007 Sun Microsystems, Inc. All Rights Reserved.
869N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
869N/A *
869N/A * This code is free software; you can redistribute it and/or modify it
869N/A * under the terms of the GNU General Public License version 2 only, as
869N/A * published by the Free Software Foundation. Sun designates this
869N/A * particular file as subject to the "Classpath" exception as provided
869N/A * by Sun in the LICENSE file that accompanied this code.
869N/A *
869N/A * This code is distributed in the hope that it will be useful, but WITHOUT
869N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
869N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
869N/A * version 2 for more details (a copy is included in the LICENSE file that
869N/A * accompanied this code).
869N/A *
869N/A * You should have received a copy of the GNU General Public License version
873N/A * 2 along with this work; if not, write to the Free Software Foundation,
869N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
869N/A *
869N/A * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
869N/A * CA 95054 USA or visit www.sun.com if you need additional information or
869N/A * have any questions.
869N/A */
869N/A
0N/A
0N/Apackage sun.net.www.protocol.https;
0N/A
0N/Aimport java.io.IOException;
869N/Aimport java.io.UnsupportedEncodingException;
0N/Aimport java.io.InputStream;
0N/Aimport java.io.OutputStream;
0N/Aimport java.io.FileInputStream;
869N/Aimport java.io.PrintStream;
0N/Aimport java.io.BufferedOutputStream;
869N/Aimport java.net.Socket;
0N/Aimport java.net.URL;
869N/Aimport java.net.UnknownHostException;
869N/Aimport java.net.InetAddress;
869N/Aimport java.net.InetSocketAddress;
0N/Aimport java.net.Proxy;
869N/Aimport java.net.CookieHandler;
48N/Aimport java.net.Authenticator;
869N/Aimport java.net.PasswordAuthentication;
0N/Aimport java.security.Principal;
869N/Aimport java.security.KeyStore;
716N/Aimport java.security.PrivateKey;
869N/Aimport java.security.cert.*;
1958N/Aimport java.util.StringTokenizer;
1963N/Aimport java.util.Vector;
1954N/Aimport java.util.Collection;
1954N/Aimport java.util.List;
1954N/Aimport java.util.Iterator;
1954N/Aimport java.security.AccessController;
1954N/A
1954N/Aimport javax.security.auth.x500.X500Principal;
1954N/Aimport javax.security.auth.kerberos.KerberosPrincipal;
1954N/A
1954N/Aimport javax.net.ssl.*;
1954N/Aimport sun.security.x509.X500Name;
1954N/Aimport sun.misc.Regexp;
1954N/Aimport sun.misc.RegexpPool;
0N/Aimport sun.net.www.HeaderParser;
0N/Aimport sun.net.www.MessageHeader;
869N/Aimport sun.net.www.http.HttpClient;
868N/Aimport sun.security.action.*;
983N/A
1928N/Aimport sun.security.util.HostnameChecker;
2042N/Aimport sun.security.ssl.SSLSocketImpl;
1004N/A
1007N/A
1378N/A/**
1411N/A * This class provides HTTPS client URL support, building on the standard
1503N/A * "sun.net.www" HTTP protocol handler. HTTPS is the same protocol as HTTP,
869N/A * but differs in the transport layer which it uses: <UL>
1774N/A *
1004N/A * <LI>There's a <em>Secure Sockets Layer</em> between TCP
0N/A * and the HTTP protocol code.
0N/A *
869N/A * <LI>It uses a different default TCP port.
868N/A *
0N/A * <LI>It doesn't use application level proxies, which can see and
0N/A * manipulate HTTP user level data, compromising privacy. It uses
65N/A * low level tunneling instead, which hides HTTP protocol and data
869N/A * from all third parties. (Traffic analysis is still possible).
868N/A *
65N/A * <LI>It does basic server authentication, to protect
869N/A * against "URL spoofing" attacks. This involves deciding
65N/A * whether the X.509 certificate chain identifying the server
65N/A * is trusted, and verifying that the name of the server is
65N/A * found in the certificate. (The application may enable an
65N/A * anonymous SSL cipher suite, and such checks are not done
65N/A * for anonymous ciphers.)
65N/A *
65N/A * <LI>It exposes key SSL session attributes, specifically the
65N/A * cipher suite in use and the server's X509 certificates, to
65N/A * application software which knows about this protocol handler.
65N/A *
65N/A * </UL>
65N/A *
65N/A * <P> System properties used include: <UL>
65N/A *
65N/A * <LI><em>https.proxyHost</em> ... the host supporting SSL
0N/A * tunneling using the conventional CONNECT syntax
869N/A *
868N/A * <LI><em>https.proxyPort</em> ... port to use on proxyHost
0N/A *
0N/A * <LI><em>https.cipherSuites</em> ... comma separated list of
0N/A * SSL cipher suite names to enable.
0N/A *
0N/A * <LI><em>http.nonProxyHosts</em> ...
0N/A *
0N/A * </UL>
1501N/A *
0N/A * @author David Brownell
0N/A * @author Bill Foote
869N/A */
868N/A
0N/A// final for export control reasons (access to APIs); remove with care
0N/Afinal class HttpsClient extends HttpClient
869N/A implements HandshakeCompletedListener
0N/A{
0N/A // STATIC STATE and ACCESSORS THERETO
869N/A
868N/A // HTTPS uses a different default port number than HTTP.
869N/A private static final int httpsPortNumber = 443;
869N/A
868N/A /** Returns the default HTTPS port (443) */
868N/A protected int getDefaultPort() { return httpsPortNumber; }
869N/A
869N/A private HostnameVerifier hv;
0N/A private SSLSocketFactory sslSocketFactory;
0N/A
1790N/A // HttpClient.proxyDisabled will always be false, because we don't
48N/A // use an application-level HTTP proxy. We might tunnel through
869N/A // our http proxy, though.
869N/A
869N/A
868N/A // INSTANCE DATA
869N/A
868N/A // last negotiated SSL session
869N/A private SSLSession session;
2222N/A
1155N/A private String [] getCipherSuites() {
1155N/A //
1155N/A // If ciphers are assigned, sort them into an array.
1155N/A //
1155N/A String ciphers [];
1155N/A String cipherString = AccessController.doPrivileged(
1155N/A new GetPropertyAction("https.cipherSuites"));
1155N/A
1155N/A if (cipherString == null || "".equals(cipherString)) {
1155N/A ciphers = null;
1155N/A } else {
1155N/A StringTokenizer tokenizer;
1155N/A Vector<String> v = new Vector<String>();
1155N/A
1155N/A tokenizer = new StringTokenizer(cipherString, ",");
0N/A while (tokenizer.hasMoreTokens())
0N/A v.addElement(tokenizer.nextToken());
869N/A ciphers = new String [v.size()];
868N/A for (int i = 0; i < ciphers.length; i++)
0N/A ciphers [i] = v.elementAt(i);
0N/A }
848N/A return ciphers;
848N/A }
848N/A
869N/A private String [] getProtocols() {
868N/A //
848N/A // If protocols are assigned, sort them into an array.
0N/A //
0N/A String protocols [];
0N/A String protocolString = AccessController.doPrivileged(
869N/A new GetPropertyAction("https.protocols"));
0N/A
0N/A if (protocolString == null || "".equals(protocolString)) {
0N/A protocols = null;
868N/A } else {
2222N/A StringTokenizer tokenizer;
2222N/A Vector<String> v = new Vector<String>();
2222N/A
2222N/A tokenizer = new StringTokenizer(protocolString, ",");
2222N/A while (tokenizer.hasMoreTokens())
2222N/A v.addElement(tokenizer.nextToken());
2222N/A protocols = new String [v.size()];
2222N/A for (int i = 0; i < protocols.length; i++) {
2222N/A protocols [i] = v.elementAt(i);
2222N/A }
2222N/A }
2222N/A return protocols;
868N/A }
868N/A
868N/A private String getUserAgent() {
869N/A String userAgent = java.security.AccessController.doPrivileged(
868N/A new sun.security.action.GetPropertyAction("https.agent"));
115N/A if (userAgent == null || userAgent.length() == 0) {
868N/A userAgent = "JSSE";
868N/A }
868N/A return userAgent;
868N/A }
868N/A
869N/A // should remove once HttpClient.newHttpProxy is putback
868N/A private static Proxy newHttpProxy(String proxyHost, int proxyPort) {
868N/A InetSocketAddress saddr = null;
868N/A final String phost = proxyHost;
868N/A final int pport = proxyPort < 0 ? httpsPortNumber : proxyPort;
868N/A try {
868N/A saddr = java.security.AccessController.doPrivileged(new
868N/A java.security.PrivilegedExceptionAction<InetSocketAddress>() {
869N/A public InetSocketAddress run() {
868N/A return new InetSocketAddress(phost, pport);
868N/A }});
868N/A } catch (java.security.PrivilegedActionException pae) {
868N/A }
868N/A return new Proxy(Proxy.Type.HTTP, saddr);
869N/A }
868N/A
868N/A // CONSTRUCTOR, FACTORY
868N/A
868N/A
868N/A /**
869N/A * Create an HTTPS client URL. Traffic will be tunneled through any
868N/A * intermediate nodes rather than proxied, so that confidentiality
868N/A * of data exchanged can be preserved. However, note that all the
868N/A * anonymous SSL flavors are subject to "person-in-the-middle"
868N/A * attacks against confidentiality. If you enable use of those
868N/A * flavors, you may be giving up the protection you get through
868N/A * SSL tunneling.
868N/A *
869N/A * Use New to get new HttpsClient. This constructor is meant to be
868N/A * used only by New method. New properly checks for URL spoofing.
868N/A *
868N/A * @param URL https URL with which a connection must be established
868N/A */
868N/A private HttpsClient(SSLSocketFactory sf, URL url)
868N/A throws IOException
868N/A {
869N/A // HttpClient-level proxying is always disabled,
868N/A // because we override doConnect to do tunneling instead.
0N/A this(sf, url, (String)null, -1);
2222N/A }
0N/A
868N/A /**
2222N/A * Create an HTTPS client URL. Traffic will be tunneled through
868N/A * the specified proxy server.
868N/A */
1948N/A HttpsClient(SSLSocketFactory sf, URL url, String proxyHost, int proxyPort)
1948N/A throws IOException {
1948N/A this(sf, url, proxyHost, proxyPort, -1);
1948N/A }
1948N/A
869N/A /**
869N/A * Create an HTTPS client URL. Traffic will be tunneled through
869N/A * the specified proxy server, with a connect timeout
869N/A */
869N/A HttpsClient(SSLSocketFactory sf, URL url, String proxyHost, int proxyPort,
869N/A int connectTimeout)
869N/A throws IOException {
869N/A this(sf, url,
869N/A (proxyHost == null? null:
869N/A HttpsClient.newHttpProxy(proxyHost, proxyPort)),
869N/A connectTimeout);
869N/A }
869N/A
869N/A /**
869N/A * Same as previous constructor except using a Proxy
869N/A */
869N/A HttpsClient(SSLSocketFactory sf, URL url, Proxy proxy,
869N/A int connectTimeout)
869N/A throws IOException {
869N/A this.proxy = proxy;
869N/A setSSLSocketFactory(sf);
869N/A this.proxyDisabled = true;
869N/A
869N/A this.host = url.getHost();
869N/A this.url = url;
869N/A port = url.getPort();
869N/A if (port == -1) {
869N/A port = getDefaultPort();
869N/A }
869N/A setConnectTimeout(connectTimeout);
869N/A // get the cookieHandler if there is any
869N/A cookieHandler = java.security.AccessController.doPrivileged(
869N/A new java.security.PrivilegedAction<CookieHandler>() {
869N/A public CookieHandler run() {
869N/A return CookieHandler.getDefault();
869N/A }
869N/A });
869N/A openServer();
869N/A }
869N/A
869N/A
869N/A // This code largely ripped off from HttpClient.New, and
869N/A // it uses the same keepalive cache.
869N/A
869N/A static HttpClient New(SSLSocketFactory sf, URL url, HostnameVerifier hv)
869N/A throws IOException {
869N/A return HttpsClient.New(sf, url, hv, true);
869N/A }
869N/A
869N/A /** See HttpClient for the model for this method. */
869N/A static HttpClient New(SSLSocketFactory sf, URL url,
0N/A HostnameVerifier hv, boolean useCache) throws IOException {
824N/A return HttpsClient.New(sf, url, hv, (String)null, -1, useCache);
869N/A }
868N/A
824N/A /**
824N/A * Get a HTTPS client to the URL. Traffic will be tunneled through
824N/A * the specified proxy server.
824N/A */
824N/A static HttpClient New(SSLSocketFactory sf, URL url, HostnameVerifier hv,
869N/A String proxyHost, int proxyPort) throws IOException {
824N/A return HttpsClient.New(sf, url, hv, proxyHost, proxyPort, true);
824N/A }
869N/A
869N/A static HttpClient New(SSLSocketFactory sf, URL url, HostnameVerifier hv,
869N/A String proxyHost, int proxyPort, boolean useCache)
869N/A throws IOException {
869N/A return HttpsClient.New(sf, url, hv, proxyHost, proxyPort, useCache, -1);
869N/A }
869N/A
869N/A static HttpClient New(SSLSocketFactory sf, URL url, HostnameVerifier hv,
869N/A String proxyHost, int proxyPort, boolean useCache,
869N/A int connectTimeout)
869N/A throws IOException {
869N/A
869N/A return HttpsClient.New(sf, url, hv,
869N/A (proxyHost == null? null :
869N/A HttpsClient.newHttpProxy(proxyHost, proxyPort)),
869N/A useCache, connectTimeout);
869N/A }
869N/A
869N/A static HttpClient New(SSLSocketFactory sf, URL url, HostnameVerifier hv,
869N/A Proxy p, boolean useCache,
869N/A int connectTimeout)
869N/A throws IOException {
869N/A HttpsClient ret = null;
869N/A if (useCache) {
869N/A /* see if one's already around */
869N/A ret = (HttpsClient) kac.get(url, sf);
869N/A if (ret != null) {
869N/A ret.cachedHttpClient = true;
869N/A }
849N/A }
0N/A if (ret == null) {
869N/A ret = new HttpsClient(sf, url, p, connectTimeout);
868N/A } else {
0N/A SecurityManager security = System.getSecurityManager();
0N/A if (security != null) {
0N/A security.checkConnect(url.getHost(), url.getPort());
0N/A }
0N/A ret.url = url;
0N/A }
869N/A ret.setHostnameVerifier(hv);
869N/A
869N/A return ret;
869N/A }
869N/A
869N/A // METHODS
869N/A void setHostnameVerifier(HostnameVerifier hv) {
869N/A this.hv = hv;
0N/A }
869N/A
0N/A void setSSLSocketFactory(SSLSocketFactory sf) {
0N/A sslSocketFactory = sf;
869N/A }
869N/A
869N/A SSLSocketFactory getSSLSocketFactory() {
869N/A return sslSocketFactory;
869N/A }
868N/A
0N/A public boolean needsTunneling() {
824N/A return (proxy != null && proxy.type() != Proxy.Type.DIRECT
824N/A && proxy.type() != Proxy.Type.SOCKS);
824N/A }
824N/A
869N/A public void afterConnect() throws IOException, UnknownHostException {
869N/A if (!isCachedConnection()) {
869N/A SSLSocket s = null;
869N/A SSLSocketFactory factory = sslSocketFactory;
869N/A try {
869N/A if (!(serverSocket instanceof SSLSocket)) {
869N/A s = (SSLSocket)factory.createSocket(serverSocket,
869N/A host, port, true);
869N/A } else {
868N/A s = (SSLSocket)serverSocket;
824N/A }
868N/A } catch (IOException ex) {
869N/A // If we fail to connect through the tunnel, try it
869N/A // locally, as a last resort. If this doesn't work,
868N/A // throw the original exception.
869N/A try {
869N/A s = (SSLSocket)factory.createSocket(host, port);
824N/A } catch (IOException ignored) {
824N/A throw ex;
869N/A }
869N/A }
869N/A
869N/A //
869N/A // Force handshaking, so that we get any authentication.
869N/A // Register a handshake callback so our session state tracks any
869N/A // later session renegotiations.
869N/A //
1501N/A String [] protocols = getProtocols();
869N/A String [] ciphers = getCipherSuites();
869N/A if (protocols != null) {
869N/A s.setEnabledProtocols(protocols);
0N/A }
0N/A if (ciphers != null) {
869N/A s.setEnabledCipherSuites(ciphers);
0N/A }
869N/A s.addHandshakeCompletedListener(this);
869N/A
0N/A // if the HostnameVerifier is not set, try to enable endpoint
0N/A // identification during handshaking
0N/A boolean enabledIdentification = false;
0N/A if (hv instanceof DefaultHostnameVerifier &&
869N/A (s instanceof SSLSocketImpl) &&
868N/A ((SSLSocketImpl)s).trySetHostnameVerification("HTTPS")) {
0N/A enabledIdentification = true;
0N/A }
0N/A
0N/A s.startHandshake();
0N/A session = s.getSession();
869N/A // change the serverSocket and serverOutput
0N/A serverSocket = s;
0N/A try {
0N/A serverOutput = new PrintStream(
1952N/A new BufferedOutputStream(serverSocket.getOutputStream()),
1952N/A false, encoding);
1952N/A } catch (UnsupportedEncodingException e) {
1952N/A throw new InternalError(encoding+" encoding not found");
1952N/A }
1952N/A
1952N/A // check URL spoofing if it has not been checked under handshaking
0N/A if (!enabledIdentification) {
0N/A checkURLSpoofing(hv);
0N/A }
46N/A } else {
869N/A // if we are reusing a cached https session,
0N/A // we don't need to do handshaking etc. But we do need to
0N/A // set the ssl session
46N/A session = ((SSLSocket)serverSocket).getSession();
0N/A }
1653N/A }
1653N/A
1653N/A // Server identity checking is done according to RFC 2818: HTTP over TLS
1653N/A // Section 3.1 Server Identity
1653N/A private void checkURLSpoofing(HostnameVerifier hostnameVerifier)
1653N/A throws IOException
1653N/A {
0N/A //
0N/A // Get authenticated server name, if any
0N/A //
869N/A boolean done = false;
0N/A String host = url.getHost();
0N/A
0N/A // if IPv6 strip off the "[]"
0N/A if (host != null && host.startsWith("[") && host.endsWith("]")) {
0N/A host = host.substring(1, host.length()-1);
0N/A }
869N/A
0N/A Certificate[] peerCerts = null;
0N/A try {
0N/A HostnameChecker checker = HostnameChecker.getInstance(
756N/A HostnameChecker.TYPE_TLS);
869N/A
868N/A Principal principal = getPeerPrincipal();
756N/A if (principal instanceof KerberosPrincipal) {
756N/A if (!checker.match(host, (KerberosPrincipal)principal)) {
988N/A throw new SSLPeerUnverifiedException("Hostname checker" +
988N/A " failed for Kerberos");
988N/A }
988N/A } else {
988N/A // get the subject's certificate
988N/A peerCerts = session.getPeerCertificates();
988N/A
756N/A X509Certificate peerCert;
756N/A if (peerCerts[0] instanceof
756N/A java.security.cert.X509Certificate) {
869N/A peerCert = (java.security.cert.X509Certificate)peerCerts[0];
756N/A } else {
869N/A throw new SSLPeerUnverifiedException("");
756N/A }
1102N/A checker.match(host, peerCert);
1102N/A }
1102N/A
1102N/A // if it doesn't throw an exception, we passed. Return.
1102N/A return;
1102N/A
1102N/A } catch (SSLPeerUnverifiedException e) {
0N/A
869N/A //
868N/A // client explicitly changed default policy and enabled
0N/A // anonymous ciphers; we can't check the standard policy
0N/A //
0N/A // ignore
0N/A } catch (java.security.cert.CertificateException cpe) {
0N/A // ignore
869N/A }
869N/A
0N/A String cipher = session.getCipherSuite();
0N/A if ((cipher != null) && (cipher.indexOf("_anon_") != -1)) {
0N/A return;
0N/A } else if ((hostnameVerifier != null) &&
2048N/A (hostnameVerifier.verify(host, session))) {
2048N/A return;
2048N/A }
2048N/A
2048N/A serverSocket.close();
2048N/A session.invalidate();
2048N/A
2048N/A throw new IOException("HTTPS hostname wrong: should be <"
2048N/A + url.getHost() + ">");
2048N/A }
2048N/A
824N/A protected void putInKeepAliveCache() {
869N/A kac.put(url, sslSocketFactory, this);
868N/A }
824N/A
824N/A /**
824N/A * Returns the cipher suite in use on this connection.
869N/A */
868N/A String getCipherSuite() {
824N/A return session.getCipherSuite();
869N/A }
824N/A
869N/A /**
869N/A * Returns the certificate chain the client sent to the
869N/A * server, or null if the client did not authenticate.
869N/A */
869N/A public java.security.cert.Certificate [] getLocalCertificates() {
869N/A return session.getLocalCertificates();
869N/A }
869N/A
869N/A /**
869N/A * Returns the certificate chain with which the server
869N/A * authenticated itself, or throw a SSLPeerUnverifiedException
869N/A * if the server did not authenticate.
869N/A */
869N/A java.security.cert.Certificate [] getServerCertificates()
869N/A throws SSLPeerUnverifiedException
868N/A {
868N/A return session.getPeerCertificates();
869N/A }
868N/A
868N/A /**
869N/A * Returns the X.509 certificate chain with which the server
868N/A * authenticated itself, or null if the server did not authenticate.
824N/A */
869N/A javax.security.cert.X509Certificate [] getServerCertificateChain()
824N/A throws SSLPeerUnverifiedException
0N/A {
869N/A return session.getPeerCertificateChain();
868N/A }
0N/A
0N/A /**
0N/A * Returns the principal with which the server authenticated
869N/A * itself, or throw a SSLPeerUnverifiedException if the
868N/A * server did not authenticate.
0N/A */
869N/A Principal getPeerPrincipal()
869N/A throws SSLPeerUnverifiedException
1280N/A {
868N/A Principal principal;
869N/A try {
1280N/A principal = session.getPeerPrincipal();
869N/A } catch (AbstractMethodError e) {
1801N/A // if the provider does not support it, fallback to peer certs.
1280N/A // return the X500Principal of the end-entity cert.
1280N/A java.security.cert.Certificate[] certs =
1280N/A session.getPeerCertificates();
1280N/A principal = (X500Principal)
869N/A ((X509Certificate)certs[0]).getSubjectX500Principal();
869N/A }
869N/A return principal;
869N/A }
869N/A
869N/A /**
869N/A * Returns the principal the client sent to the
1280N/A * server, or null if the client did not authenticate.
1308N/A */
869N/A Principal getLocalPrincipal()
1280N/A {
868N/A Principal principal;
1801N/A try {
1280N/A principal = session.getLocalPrincipal();
1280N/A } catch (AbstractMethodError e) {
1280N/A principal = null;
1280N/A // if the provider does not support it, fallback to local certs.
1280N/A // return the X500Principal of the end-entity cert.
1280N/A java.security.cert.Certificate[] certs =
1280N/A session.getLocalCertificates();
1280N/A if (certs != null) {
1280N/A principal = (X500Principal)
1280N/A ((X509Certificate)certs[0]).getSubjectX500Principal();
1280N/A }
1280N/A }
1280N/A return principal;
1458N/A }
1280N/A
1280N/A /**
1280N/A * This method implements the SSL HandshakeCompleted callback,
1280N/A * remembering the resulting session so that it may be queried
1280N/A * for the current cipher suite and peer certificates. Servers
1280N/A * sometimes re-initiate handshaking, so the session in use on
1280N/A * a given connection may change. When sessions change, so may
1280N/A * peer identities and cipher suites.
1280N/A */
0N/A public void handshakeCompleted(HandshakeCompletedEvent event)
2132N/A {
2132N/A session = event.getSession();
2132N/A }
2132N/A
2132N/A /**
2132N/A * @return the proxy host being used for this client, or null
2132N/A * if we're not going through a proxy
2132N/A */
2132N/A public String getProxyHostUsed() {
2132N/A if (!needsTunneling()) {
2132N/A return null;
2132N/A } else {
2132N/A return ((InetSocketAddress)proxy.address()).getHostName();
2132N/A }
2132N/A }
2132N/A
2132N/A /**
0N/A * @return the proxy port being used for this client. Meaningless
869N/A * if getProxyHostUsed() gives null.
868N/A */
868N/A public int getProxyPortUsed() {
869N/A return (proxy == null || proxy.type() == Proxy.Type.DIRECT ||
869N/A proxy.type() == Proxy.Type.SOCKS)? -1:
1280N/A ((InetSocketAddress)proxy.address()).getPort();
1280N/A }
868N/A}
1280N/A