package org.jivesoftware.smack.proxy;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import javax.net.SocketFactory;

public class Socks5ProxySocketFactory extends SocketFactory {
   private ProxyInfo proxy;

   public Socks5ProxySocketFactory(ProxyInfo proxy) {
      this.proxy = proxy;
   }

   public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
      return this.socks5ProxifiedSocket(host, port);
   }

   public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
      return this.socks5ProxifiedSocket(host, port);
   }

   public Socket createSocket(InetAddress host, int port) throws IOException {
      return this.socks5ProxifiedSocket(host.getHostAddress(), port);
   }

   public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
      return this.socks5ProxifiedSocket(address.getHostAddress(), port);
   }

   private Socket socks5ProxifiedSocket(String host, int port) throws IOException {
      Socket socket = null;
      InputStream in = null;
      OutputStream out = null;
      String proxy_host = this.proxy.getProxyAddress();
      int proxy_port = this.proxy.getProxyPort();
      String user = this.proxy.getProxyUsername();
      String passwd = this.proxy.getProxyPassword();

      try {
         socket = new Socket(proxy_host, proxy_port);
         in = socket.getInputStream();
         out = socket.getOutputStream();
         socket.setTcpNoDelay(true);
         byte[] buf = new byte[1024];
         int index = 0;
         int index = index + 1;
         buf[index] = 5;
         buf[index++] = 2;
         buf[index++] = 0;
         buf[index++] = 2;
         out.write(buf, 0, index);
         this.fill(in, buf, 2);
         boolean check = false;
         switch(buf[1] & 255) {
         case 0:
            check = true;
            break;
         case 2:
            if (user != null && passwd != null) {
               index = 0;
               index = index + 1;
               buf[index] = 1;
               buf[index++] = (byte)user.length();
               System.arraycopy(user.getBytes(), 0, buf, index, user.length());
               index += user.length();
               buf[index++] = (byte)passwd.length();
               System.arraycopy(passwd.getBytes(), 0, buf, index, passwd.length());
               index += passwd.length();
               out.write(buf, 0, index);
               this.fill(in, buf, 2);
               if (buf[1] == 0) {
                  check = true;
               }
            }
         }

         if (!check) {
            try {
               socket.close();
            } catch (Exception var17) {
            }

            throw new ProxyException(ProxyInfo.ProxyType.SOCKS5, "fail in SOCKS5 proxy");
         } else {
            index = 0;
            index = index + 1;
            buf[index] = 5;
            buf[index++] = 1;
            buf[index++] = 0;
            byte[] hostb = host.getBytes();
            int len = hostb.length;
            buf[index++] = 3;
            buf[index++] = (byte)len;
            System.arraycopy(hostb, 0, buf, index, len);
            index += len;
            buf[index++] = (byte)(port >>> 8);
            buf[index++] = (byte)(port & 255);
            out.write(buf, 0, index);
            this.fill(in, buf, 4);
            if (buf[1] != 0) {
               try {
                  socket.close();
               } catch (Exception var18) {
               }

               throw new ProxyException(ProxyInfo.ProxyType.SOCKS5, "server returns " + buf[1]);
            } else {
               switch(buf[3] & 255) {
               case 1:
                  this.fill(in, buf, 6);
               case 2:
               default:
                  break;
               case 3:
                  this.fill(in, buf, 1);
                  this.fill(in, buf, (buf[0] & 255) + 2);
                  break;
               case 4:
                  this.fill(in, buf, 18);
               }

               return socket;
            }
         }
      } catch (RuntimeException var19) {
         throw var19;
      } catch (Exception var20) {
         try {
            if (socket != null) {
               socket.close();
            }
         } catch (Exception var16) {
         }

         String message = "ProxySOCKS5: " + var20.toString();
         if (var20 instanceof Throwable) {
            throw new ProxyException(ProxyInfo.ProxyType.SOCKS5, message, var20);
         } else {
            throw new IOException(message);
         }
      }
   }

   private void fill(InputStream in, byte[] buf, int len) throws IOException {
      int i;
      for(int s = 0; s < len; s += i) {
         i = in.read(buf, s, len - s);
         if (i <= 0) {
            throw new ProxyException(ProxyInfo.ProxyType.SOCKS5, "stream is closed");
         }
      }

   }
}
