using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using Server.Connection;
using Server.Helper;

namespace Server.ReverseProxy;

public class ReverseProxyClient
{
	public enum ProxyType
	{
		Unknown,
		SOCKS5,
		HTTPS
	}

	public const int SOCKS5_DEFAULT_PORT = 3218;

	public const byte SOCKS5_VERSION_NUMBER = 5;

	public const byte SOCKS5_RESERVED = 0;

	public const byte SOCKS5_AUTH_NUMBER_OF_AUTH_METHODS_SUPPORTED = 2;

	public const byte SOCKS5_AUTH_METHOD_NO_AUTHENTICATION_REQUIRED = 0;

	public const byte SOCKS5_AUTH_METHOD_GSSAPI = 1;

	public const byte SOCKS5_AUTH_METHOD_USERNAME_PASSWORD = 2;

	public const byte SOCKS5_AUTH_METHOD_IANA_ASSIGNED_RANGE_BEGIN = 3;

	public const byte SOCKS5_AUTH_METHOD_IANA_ASSIGNED_RANGE_END = 127;

	public const byte SOCKS5_AUTH_METHOD_RESERVED_RANGE_BEGIN = 128;

	public const byte SOCKS5_AUTH_METHOD_RESERVED_RANGE_END = 254;

	public const byte SOCKS5_AUTH_METHOD_REPLY_NO_ACCEPTABLE_METHODS = byte.MaxValue;

	public const byte SOCKS5_CMD_REPLY_SUCCEEDED = 0;

	public const byte SOCKS5_CMD_REPLY_GENERAL_SOCKS_SERVER_FAILURE = 1;

	public const byte SOCKS5_CMD_REPLY_CONNECTION_NOT_ALLOWED_BY_RULESET = 2;

	public const byte SOCKS5_CMD_REPLY_NETWORK_UNREACHABLE = 3;

	public const byte SOCKS5_CMD_REPLY_HOST_UNREACHABLE = 4;

	public const byte SOCKS5_CMD_REPLY_CONNECTION_REFUSED = 5;

	public const byte SOCKS5_CMD_REPLY_TTL_EXPIRED = 6;

	public const byte SOCKS5_CMD_REPLY_COMMAND_NOT_SUPPORTED = 7;

	public const byte SOCKS5_CMD_REPLY_ADDRESS_TYPE_NOT_SUPPORTED = 8;

	public const byte SOCKS5_ADDRTYPE_IPV4 = 1;

	public const byte SOCKS5_ADDRTYPE_DOMAIN_NAME = 3;

	public const byte SOCKS5_ADDRTYPE_IPV6 = 4;

	public const int BUFFER_SIZE = 8192;

	private bool _receivedConnResponse;

	private MemoryStream _handshakeStream;

	private byte[] _buffer;

	private bool _isBindCommand;

	private bool _isUdpCommand;

	private bool _isConnectCommand;

	private bool _isIpType;

	private bool _isIPv6NameType;

	private bool _isDomainNameType;

	private bool _disconnectIsSend;

	private ReverseProxyServer Server;

	public Socket Handle { get; private set; }

	public string UserIP
	{
		get
		{
			try
			{
				return ((IPEndPoint)Handle.RemoteEndPoint).Address.ToString();
			}
			catch
			{
			}
			return "Unknown";
		}
	}

	public string ClientIP => Client.Ip;

	public string TypeStr
	{
		get
		{
			if (Type == ProxyType.SOCKS5)
			{
				return "SOCK5";
			}
			if (Type == ProxyType.HTTPS)
			{
				return "HTTPS";
			}
			return "Unknown";
		}
	}

	public string UserCountry => Utils.GetCountryName(UserIP);

	public string ClientCountry => Utils.GetCountryName(ClientIP);

	public string ReceivedStr => Utils.GetSizeString(LengthReceived);

	public string SendStr => Utils.GetSizeString(LengthSent);

	public string TargetStr => $"{TargetServer} : {TargetPort}";

	public Clients Client { get; private set; }

	public long PacketsReceived { get; private set; }

	public long PacketsSended { get; private set; }

	public long LengthReceived { get; private set; }

	public long LengthSent { get; private set; }

	public int ConnectionId => Handle.Handle.ToInt32();

	public string TargetServer { get; private set; }

	public ushort TargetPort { get; private set; }

	public bool IsConnected { get; private set; }

	public ProxyType Type { get; private set; }

	public string HostName { get; private set; }

	public bool ProxySuccessful { get; private set; }

	public ReverseProxyClient(Clients client, Socket socket, ReverseProxyServer server)
	{
		Handle = socket;
		Client = client;
		_handshakeStream = new MemoryStream();
		_buffer = new byte[8192];
		IsConnected = true;
		TargetServer = "";
		Type = ProxyType.Unknown;
		Server = server;
		try
		{
			socket.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, AsyncReceive, null);
		}
		catch
		{
			Disconnect();
		}
	}

	private void AsyncReceive(IAsyncResult ar)
	{
		try
		{
			int num = Handle.EndReceive(ar);
			if (num <= 0)
			{
				Disconnect();
				return;
			}
			if (num > 5000 || _handshakeStream.Length + num > 5000)
			{
				Disconnect();
				return;
			}
			LengthReceived += num;
			_handshakeStream.Write(_buffer, 0, num);
		}
		catch
		{
			Disconnect();
			return;
		}
		byte[] array = _handshakeStream.ToArray();
		switch (PacketsReceived)
		{
		case 0L:
		{
			if (array.Length < 3)
			{
				break;
			}
			string @string = Encoding.ASCII.GetString(array);
			if (array[0] == 5)
			{
				Type = ProxyType.SOCKS5;
			}
			if (!@string.StartsWith("CONNECT") || !@string.Contains(":"))
			{
				break;
			}
			Type = ProxyType.HTTPS;
			using StreamReader streamReader = new StreamReader(new MemoryStream(array));
			string text = streamReader.ReadLine();
			if (text == null)
			{
				break;
			}
			string[] array2 = text.Split(new string[1] { " " }, StringSplitOptions.RemoveEmptyEntries);
			if (array2.Length != 0)
			{
				try
				{
					string text2 = array2[1];
					TargetServer = text2.Split(':')[0];
					TargetPort = ushort.Parse(text2.Split(':')[1]);
					_isConnectCommand = true;
					_isDomainNameType = true;
					Client.SendProxyCommand(new ReverseProxyConnect
					{
						ConnectionId = ConnectionId,
						Target = TargetServer,
						Port = TargetPort
					}, ReverseProxyCommands.CONNECT);
					Server.CallonConnectionEstablished(this);
					return;
				}
				catch
				{
					Disconnect();
				}
			}
			goto IL_01cf;
		}
		case 1L:
			{
				int num2 = 6;
				if (array.Length < num2)
				{
					break;
				}
				if (!CheckProxyVersion(array))
				{
					return;
				}
				_isConnectCommand = array[1] == 1;
				_isBindCommand = array[1] == 2;
				_isUdpCommand = array[1] == 3;
				_isIpType = array[3] == 1;
				_isDomainNameType = array[3] == 3;
				_isIPv6NameType = array[3] == 4;
				Array.Reverse(array, array.Length - 2, 2);
				TargetPort = BitConverter.ToUInt16(array, array.Length - 2);
				if (_isConnectCommand)
				{
					if (_isIpType)
					{
						TargetServer = array[4] + "." + array[5] + "." + array[6] + "." + array[7];
					}
					else if (_isDomainNameType)
					{
						int num3 = array[4];
						if (num2 + num3 < array.Length)
						{
							TargetServer = Encoding.ASCII.GetString(array, 5, num3);
						}
					}
					if (TargetServer.Length > 0)
					{
						Client.SendProxyCommand(new ReverseProxyConnect
						{
							ConnectionId = ConnectionId,
							Target = TargetServer,
							Port = TargetPort
						}, ReverseProxyCommands.CONNECT);
					}
					Server.CallonUpdateConnection(this);
				}
				else
				{
					SendFailToClient();
				}
				return;
			}
			IL_01cf:
			if (CheckProxyVersion(array))
			{
				SendSuccessToClient();
				PacketsReceived++;
				_handshakeStream.SetLength(0L);
				Server.CallonConnectionEstablished(this);
			}
			break;
		}
		try
		{
			Handle.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, AsyncReceive, null);
		}
		catch
		{
			Disconnect();
		}
	}

	public void Disconnect()
	{
		if (!_disconnectIsSend)
		{
			_disconnectIsSend = true;
			Client.SendProxyCommand(new ReverseProxyDisconnect
			{
				ConnectionId = ConnectionId
			}, ReverseProxyCommands.DISCONNECT);
		}
		try
		{
			Handle.Close();
		}
		catch
		{
		}
		IsConnected = false;
		Server.CallonUpdateConnection(this);
	}

	public void SendToClient(byte[] payload)
	{
		lock (Handle)
		{
			try
			{
				LengthSent += payload.Length;
				Handle.Send(payload);
			}
			catch
			{
				Disconnect();
			}
		}
		Server.CallonUpdateConnection(this);
	}

	private void SendFailToClient()
	{
		if (Type == ProxyType.HTTPS)
		{
			Disconnect();
		}
		if (Type == ProxyType.SOCKS5)
		{
			SendToClient(new byte[2] { 5, 255 });
			Disconnect();
		}
	}

	private void SendSuccessToClient()
	{
		if (Type == ProxyType.SOCKS5)
		{
			SendToClient(new byte[2] { 5, 0 });
		}
	}

	private bool CheckProxyVersion(byte[] payload)
	{
		if (Type == ProxyType.HTTPS)
		{
			return true;
		}
		if (payload.Length != 0 && payload[0] != 5)
		{
			SendFailToClient();
			Disconnect();
			return false;
		}
		return true;
	}

	public void HandleCommandResponse(ReverseProxyConnectResponse response)
	{
		if (_receivedConnResponse)
		{
			return;
		}
		_receivedConnResponse = true;
		if (response.IsConnected)
		{
			HostName = response.HostName;
			if (Type == ProxyType.HTTPS)
			{
				SendToClient(Encoding.ASCII.GetBytes("HTTP/1.0 200 Connection established\r\n\r\n"));
			}
			else if (Type == ProxyType.SOCKS5)
			{
				try
				{
					List<byte> list = new List<byte>();
					list.Add(5);
					list.Add(0);
					list.Add(0);
					list.Add(1);
					list.AddRange(response.LocalAddress);
					list.Add((byte)Math.Floor((decimal)response.LocalPort / 256m));
					list.Add((byte)(response.LocalPort % 256));
					SendToClient(list.ToArray());
				}
				catch
				{
					SendToClient(new byte[10] { 5, 0, 0, 1, 0, 0, 0, 0, 0, 0 });
				}
			}
			_handshakeStream.Close();
			ProxySuccessful = true;
			try
			{
				Handle.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, AsyncReceiveProxy, null);
			}
			catch
			{
				Disconnect();
			}
		}
		else if (Type == ProxyType.HTTPS)
		{
			Disconnect();
		}
		else if (Type == ProxyType.SOCKS5)
		{
			SendToClient(new byte[10] { 5, 5, 0, 1, 0, 0, 0, 0, 0, 0 });
		}
		Server.CallonUpdateConnection(this);
	}

	private void AsyncReceiveProxy(IAsyncResult ar)
	{
		try
		{
			int num = Handle.EndReceive(ar);
			if (num <= 0)
			{
				Disconnect();
				return;
			}
			LengthReceived += num;
			ReverseProxyData reverseProxyData = new ReverseProxyData
			{
				ConnectionId = ConnectionId
			};
			reverseProxyData.Data = new byte[num];
			Array.Copy(_buffer, reverseProxyData.Data, num);
			Client.SendProxyCommand(reverseProxyData, ReverseProxyCommands.DATA);
			LengthSent += num;
			PacketsSended++;
		}
		catch
		{
			Disconnect();
			return;
		}
		PacketsReceived++;
		Server.CallonUpdateConnection(this);
		try
		{
			Handle.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, AsyncReceiveProxy, null);
		}
		catch
		{
		}
	}
}
