All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.netbeans.modules.proxy.ProxySocketFactory Maven / Gradle / Ivy

There is a newer version: RELEASE240
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.netbeans.modules.proxy;

import javax.net.SocketFactory;
import java.net.*;
import java.util.*;
import java.util.regex.Pattern;
import java.io.*;
import java.nio.channels.SocketChannel;
import org.netbeans.api.keyring.Keyring;
import org.openide.util.NetworkSettings;

/**
 * Creates sockets capable of connecting through HTTPS and SOCKS proxies.
 * 
 * @author Maros Sandor
 */
public class ProxySocketFactory extends SocketFactory {
    
    private static final int CONNECT_TIMEOUT = 1000 * 20; /// 20 seconds timeout
    
    private static final String AUTH_NONE = "";
    private static final String AUTH_BASIC = "Basic";
    private static final Pattern sConnectionEstablishedPattern = Pattern.compile("HTTP\\/\\d+\\.\\d+\\s+200\\s+");
    private static final Pattern sProxyAuthRequiredPattern = Pattern.compile("HTTP\\/\\d+\\.\\d+\\s+407\\s+");
        
    private static final ProxySocketFactory instance = new ProxySocketFactory();
    
    private final Map lastKnownSettings = Collections.synchronizedMap(new HashMap(2));
    
    public static ProxySocketFactory getDefault() {
        return instance;
    }
    
    private ProxySocketFactory() {
    }
    
    /**
     * Creates probe socket that supports only
     * connect(SocketAddressm, int timeout).
     */
    public Socket createSocket() throws IOException {
        return new Socket() {
            public void connect(SocketAddress endpoint, int timeout) throws IOException {
                Socket s = createSocket((InetSocketAddress)endpoint, timeout);
                s.close();
            }

            public void bind(SocketAddress bindpoint) {
                throw new UnsupportedOperationException();
            }

            protected Object clone() {
                throw new UnsupportedOperationException();
            }

            public synchronized void close() {
            }

            public void connect(SocketAddress endpoint) {
                throw new UnsupportedOperationException();
            }

            public SocketChannel getChannel() {
                throw new UnsupportedOperationException();
            }

            public InetAddress getInetAddress() {
                throw new UnsupportedOperationException();
            }

            public InputStream getInputStream() {
                throw new UnsupportedOperationException();
            }

            public boolean getKeepAlive() {
                throw new UnsupportedOperationException();
            }

            public InetAddress getLocalAddress() {
                throw new UnsupportedOperationException();
            }

            public int getLocalPort() {
                throw new UnsupportedOperationException();
            }

            public SocketAddress getLocalSocketAddress() {
                throw new UnsupportedOperationException();
            }

            public boolean getOOBInline() {
                throw new UnsupportedOperationException();
            }

            public OutputStream getOutputStream() {
                throw new UnsupportedOperationException();
            }

            public int getPort() {
                throw new UnsupportedOperationException();
            }

            public synchronized int getReceiveBufferSize() {
                throw new UnsupportedOperationException();
            }

            public SocketAddress getRemoteSocketAddress() {
                throw new UnsupportedOperationException();
            }

            public boolean getReuseAddress() {
                throw new UnsupportedOperationException();
            }

            public synchronized int getSendBufferSize() {
                throw new UnsupportedOperationException();
            }

            public int getSoLinger() {
                throw new UnsupportedOperationException();
            }

            public synchronized int getSoTimeout() {
                throw new UnsupportedOperationException();
            }

            public boolean getTcpNoDelay() {
                throw new UnsupportedOperationException();
            }

            public int getTrafficClass() {
                throw new UnsupportedOperationException();
            }

            public boolean isBound() {
                throw new UnsupportedOperationException();
            }

            public boolean isClosed() {
                throw new UnsupportedOperationException();
            }

            public boolean isConnected() {
                throw new UnsupportedOperationException();
            }

            public boolean isInputShutdown() {
                throw new UnsupportedOperationException();
            }

            public boolean isOutputShutdown() {
                throw new UnsupportedOperationException();
            }

            public void sendUrgentData(int data) {
                throw new UnsupportedOperationException();
            }

            public void setKeepAlive(boolean on) {
                throw new UnsupportedOperationException();
            }

            public void setOOBInline(boolean on) {
                throw new UnsupportedOperationException();
            }

            public synchronized void setReceiveBufferSize(int size) {
                throw new UnsupportedOperationException();
            }

            public void setReuseAddress(boolean on) {
                throw new UnsupportedOperationException();
            }

            public synchronized void setSendBufferSize(int size) {
                throw new UnsupportedOperationException();
            }

            public void setSoLinger(boolean on, int linger) {
                throw new UnsupportedOperationException();
            }

            public synchronized void setSoTimeout(int timeout) {
                throw new UnsupportedOperationException();
            }

            public void setTcpNoDelay(boolean on) {
                throw new UnsupportedOperationException();
            }

            public void setTrafficClass(int tc) {
                throw new UnsupportedOperationException();
            }

            public void shutdownInput() {
                throw new UnsupportedOperationException();
            }

            public void shutdownOutput() {
                throw new UnsupportedOperationException();
            }
        };
    }
    
    public Socket createSocket(String host, int port) throws IOException {
        return createSocket(new InetSocketAddress(host, port), CONNECT_TIMEOUT);
    }

    public Socket createSocket(InetAddress inetAddress, int port) throws IOException {
        return createSocket(new InetSocketAddress(inetAddress, port), CONNECT_TIMEOUT);
    }

    public Socket createSocket(String s, int i, InetAddress inetAddress, int i1) throws IOException {
        throw new IOException("Unsupported operation");
    }

    public Socket createSocket(InetAddress inetAddress, int i, InetAddress inetAddress1, int i1) throws IOException {
        throw new IOException("Unsupported operation");
    }

    /**
     * Connects to the remote machine by establishing a tunnel through a HTTP proxy. It issues a CONNECT request and
     * eventually authenticates with the HTTP proxy. Supported authentication methods include: Basic.
     *
     * @param address remote machine to connect to
     * @return a TCP/IP socket connected to the remote machine
     * @throws UnknownHostException if the proxy host name cannot be resolved
     * @throws IOException          if an I/O error occurs during handshake (a network problem)
     */
    private Socket getHttpsTunnelSocket(InetSocketAddress address, ConnectivitySettings cs, int timeout) throws IOException {
        Socket proxy = new Socket();
        proxy.connect(new InetSocketAddress(cs.getProxyHost(), cs.getProxyPort()), timeout);
        BufferedReader r = new BufferedReader(new InputStreamReader(new InterruptibleInputStream(proxy.getInputStream())));
        DataOutputStream dos = new DataOutputStream(proxy.getOutputStream());

        dos.writeBytes("CONNECT ");
        dos.writeBytes(address.getHostName() + ":" + address.getPort());
        dos.writeBytes(" HTTP/1.0\r\n");
        dos.writeBytes("Connection: Keep-Alive\r\n\r\n");
        dos.flush();

        String line;
        line = r.readLine();

        if (sConnectionEstablishedPattern.matcher(line).find()) {
            for (; ;) {
                line = r.readLine();
                if (line.length() == 0) break;
            }
            return proxy;
        } else if (sProxyAuthRequiredPattern.matcher(line).find()) {
            boolean authMethodSelected = false;
            String authMethod = AUTH_NONE;
            for (; ;) {
                line = r.readLine();
                if (line.length() == 0) break;
                if (line.startsWith("Proxy-Authenticate:") && !authMethodSelected) {
                    authMethod = line.substring(19).trim();
                    if (authMethod.equals(AUTH_BASIC)) {
                        authMethodSelected = true;
                    }
                }
            }
            // TODO: need to read full response before closing connection?
            proxy.close();

            if (authMethod.startsWith(AUTH_BASIC)) {
                return authenticateBasic(address, cs);
            } else {
                throw new IOException("Unsupported authentication method: " + authMethod);
            }
        } else {
            proxy.close();
            throw new IOException("HTTP proxy does not support CONNECT command. Received reply: " + line);
        }
    }

    /**
     * Connects to the remote machine by establishing a tunnel through a HTTP proxy with Basic authentication.
     * It issues a CONNECT request and authenticates with the HTTP proxy with Basic protocol.
     *
     * @param address remote machine to connect to
     * @return a TCP/IP socket connected to the remote machine
     * @throws IOException     if an I/O error occurs during handshake (a network problem)
     */
    private Socket authenticateBasic(InetSocketAddress address, ConnectivitySettings cs) throws IOException {
        Socket proxy = new Socket(cs.getProxyHost(), cs.getProxyPort());
        BufferedReader r = new BufferedReader(new InputStreamReader(new InterruptibleInputStream(proxy.getInputStream())));
        DataOutputStream dos = new DataOutputStream(proxy.getOutputStream());

        String username = cs.getProxyUsername() == null ? "" : cs.getProxyUsername();
        String password = cs.getProxyPassword() == null ? "" : String.valueOf(cs.getProxyPassword());
        String credentials = username + ":" + password;
        String basicCookie = encodeCredentials(credentials);

        dos.writeBytes("CONNECT ");
        dos.writeBytes(address.getHostName() + ":" + address.getPort());
        dos.writeBytes(" HTTP/1.0\r\n");
        dos.writeBytes("Connection: Keep-Alive\r\n");
        dos.writeBytes("Proxy-Authorization: Basic " + basicCookie + "\r\n");
        dos.writeBytes("\r\n");
        dos.flush();

        String line = r.readLine();
        if (sConnectionEstablishedPattern.matcher(line).find()) {
            for (; ;) {
                line = r.readLine();
                if (line.length() == 0) break;
            }
            return proxy;
        }
        throw new IOException("Basic authentication failed: " + line);
    }

    static String encodeCredentials(String credentials) throws UnsupportedEncodingException {
        return Base64.getEncoder().encodeToString(credentials.getBytes("US-ASCII"));
    }

    /**
     * Creates a new Socket connected to the given IP address. The method uses connection settings supplied
     * in the constructor for connecting the socket.
     *
     * @param address the IP address to connect to
     * @return connected socket
     * @throws java.net.UnknownHostException  if the hostname of the address or the proxy cannot be resolved
     * @throws java.io.IOException            if an I/O error occured while connecting to the remote end or to the proxy
     */
    private Socket createSocket(InetSocketAddress address, int timeout) throws IOException {
        String socksProxyHost = System.getProperty("socksProxyHost");
        System.getProperties().remove("socksProxyHost");
        try {
            ConnectivitySettings cs = lastKnownSettings.get(address);
            if (cs != null) {
                try {
                    return createSocket(cs, address, timeout);
                } catch (IOException e) {
                    // not good anymore, try all proxies
                    lastKnownSettings.remove(address);
                }
            }

            URI uri = addressToURI(address, "socket");
            try {
                return createSocket(uri, address,  timeout);
            } catch (IOException e) {
                // we will also try https
            }

            uri = addressToURI(address, "https");
            return createSocket(uri, address,  timeout);
            
        } finally {
            if (socksProxyHost != null) {
                System.setProperty("socksProxyHost", socksProxyHost);
            }
        }
    }

    private URI addressToURI(InetSocketAddress address, String schema) {
        URI uri;
        try {
            if (address.isUnresolved()) {
                uri = new URI(schema + "://" + address.getHostName() + ":" + address.getPort());
            } else {
                uri = new URI(schema + "://" + address.getAddress().getHostAddress() + ":" + address.getPort());
            }
        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        }
        return uri;
    }

    private Socket createSocket(URI uri, InetSocketAddress address, int timeout) throws IOException {
        List proxies = ProxySelector.getDefault().select(uri);
        IOException lastFailure = null;
        for (Proxy proxy : proxies) {
            ConnectivitySettings cs = proxyToCs(proxy, uri);
            try {
                Socket s = createSocket(cs, address, timeout);
                lastKnownSettings.put(address, cs);
                return s;
            } catch (IOException e) {
                lastFailure = e;
            }
        }
        
        throw lastFailure;
    }
    
    private ConnectivitySettings proxyToCs(Proxy proxy, URI uri) {
        ConnectivitySettings cs = new ConnectivitySettings();
        InetSocketAddress isa = (InetSocketAddress) proxy.address();
        switch (proxy.type()) {
        case HTTP:
            setupProxy(cs, ConnectivitySettings.CONNECTION_VIA_HTTPS, isa);
            break;
        case SOCKS:
            setupProxy(cs, ConnectivitySettings.CONNECTION_VIA_SOCKS, isa);
            break;
        default:
        }
        
        String prosyUser = NetworkSettings.getAuthenticationUsername(uri);
        if (prosyUser != null && !prosyUser.isEmpty()) {
            cs.setProxyUsername(prosyUser);
            cs.setProxyPassword(Keyring.read(NetworkSettings.getKeyForAuthenticationPassword(uri)));
        }
        return cs;
    }

    private void setupProxy(ConnectivitySettings cs, int connectionType, InetSocketAddress inetSocketAddress) {
        cs.setConnectionType(connectionType);
        InetAddress address = inetSocketAddress.getAddress();
        cs.setProxyHost((address != null) ? address.getHostAddress() : inetSocketAddress.getHostName());
        cs.setProxyPort(inetSocketAddress.getPort());
    }

    private Socket createSocket(ConnectivitySettings cs, InetSocketAddress address, int timeout) throws IOException {
        switch (cs.getConnectionType()) {
        case ConnectivitySettings.CONNECTION_VIA_SOCKS:
        case ConnectivitySettings.CONNECTION_DIRECT:
            Socket s = new Socket();
            s.connect(address, timeout);
            return s;

        case ConnectivitySettings.CONNECTION_VIA_HTTPS:
            return getHttpsTunnelSocket(address, cs, timeout);

        default:
            throw new IllegalArgumentException("Illegal connection type: " + cs.getConnectionType());
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy