From bfe70507bc2c467d2a603c00551f210c00966b7d Mon Sep 17 00:00:00 2001
From: RaphiMC <50594595+RaphiMC@users.noreply.github.com>
Date: Sat, 18 Mar 2023 17:26:13 +0100
Subject: [PATCH] Added support for connecting through proxies

---
 .../raphimc/viaproxy/cli/options/Options.java | 15 +++++++-
 .../Proxy2ServerChannelInitializer.java       | 34 +++++++++++++++++++
 2 files changed, 48 insertions(+), 1 deletion(-)

diff --git a/src/main/java/net/raphimc/viaproxy/cli/options/Options.java b/src/main/java/net/raphimc/viaproxy/cli/options/Options.java
index 4f88ab4..2e206e0 100644
--- a/src/main/java/net/raphimc/viaproxy/cli/options/Options.java
+++ b/src/main/java/net/raphimc/viaproxy/cli/options/Options.java
@@ -29,6 +29,8 @@ import net.raphimc.viaproxy.saves.impl.accounts.Account;
 import net.raphimc.viaproxy.util.logging.Logger;
 
 import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
 
 import static java.util.Arrays.asList;
 
@@ -51,7 +53,8 @@ public class Options {
     public static boolean SRV_MODE; // Example: lenni0451.net_25565_1.8.x.viaproxy.127.0.0.1.nip.io
     public static boolean INTERNAL_SRV_MODE; // Example: ip\7port\7version\7mppass
     public static boolean LOCAL_SOCKET_AUTH;
-    public static String RESOURCE_PACK_URL;
+    public static String RESOURCE_PACK_URL; // Example: http://example.com/resourcepack.zip
+    public static URI PROXY_URL; // Example: type://address:port or type://username:password@address:port
 
     public static void parse(final String[] args) throws IOException {
         final OptionParser parser = new OptionParser();
@@ -70,6 +73,7 @@ public class Options {
         final OptionSpec<Void> localSocketAuth = parser.accepts("local_socket_auth", "Enable authentication over a local socket");
         final OptionSpec<Void> betaCraftAuth = parser.accepts("betacraft_auth", "Use BetaCraft authentication servers for classic");
         final OptionSpec<String> resourcePackUrl = parser.acceptsAll(asList("resource_pack_url", "resource_pack", "rpu", "rp"), "URL of a resource pack which all connecting clients can optionally download").withRequiredArg().ofType(String.class);
+        final OptionSpec<String> proxyUrl = parser.acceptsAll(asList("proxy_url", "proxy"), "URL of a SOCKS(4/5)/HTTP(S) proxy which will be used for TCP connections").withRequiredArg().ofType(String.class);
         PluginManager.EVENT_MANAGER.call(new PreOptionsParseEvent(parser));
 
         final OptionSet options = parser.parse(args);
@@ -98,6 +102,15 @@ public class Options {
         if (options.has(resourcePackUrl)) {
             RESOURCE_PACK_URL = options.valueOf(resourcePackUrl);
         }
+        if (options.has(proxyUrl)) {
+            try {
+                PROXY_URL = new URI(options.valueOf(proxyUrl));
+            } catch (URISyntaxException e) {
+                Logger.LOGGER.error("Invalid proxy url: " + options.valueOf(proxyUrl));
+                Logger.LOGGER.error("Proxy url format: type://address:port or type://username:password@address:port");
+                System.exit(1);
+            }
+        }
         PluginManager.EVENT_MANAGER.call(new PostOptionsParseEvent(options));
     }
 
diff --git a/src/main/java/net/raphimc/viaproxy/proxy/proxy2server/Proxy2ServerChannelInitializer.java b/src/main/java/net/raphimc/viaproxy/proxy/proxy2server/Proxy2ServerChannelInitializer.java
index 15f4843..41ac9e8 100644
--- a/src/main/java/net/raphimc/viaproxy/proxy/proxy2server/Proxy2ServerChannelInitializer.java
+++ b/src/main/java/net/raphimc/viaproxy/proxy/proxy2server/Proxy2ServerChannelInitializer.java
@@ -22,6 +22,10 @@ import com.viaversion.viaversion.connection.UserConnectionImpl;
 import com.viaversion.viaversion.protocol.ProtocolPipelineImpl;
 import io.netty.channel.Channel;
 import io.netty.channel.ChannelHandler;
+import io.netty.handler.proxy.HttpProxyHandler;
+import io.netty.handler.proxy.ProxyHandler;
+import io.netty.handler.proxy.Socks4ProxyHandler;
+import io.netty.handler.proxy.Socks5ProxyHandler;
 import net.raphimc.netminecraft.constants.MCPipeline;
 import net.raphimc.netminecraft.netty.connection.MinecraftChannelInitializer;
 import net.raphimc.netminecraft.packet.registry.PacketRegistryUtil;
@@ -31,12 +35,16 @@ import net.raphimc.vialegacy.protocols.release.protocol1_7_2_5to1_6_4.baseprotoc
 import net.raphimc.viaprotocolhack.netty.VPHEncodeHandler;
 import net.raphimc.viaprotocolhack.netty.VPHPipeline;
 import net.raphimc.viaprotocolhack.util.VersionEnum;
+import net.raphimc.viaproxy.cli.options.Options;
 import net.raphimc.viaproxy.plugins.PluginManager;
 import net.raphimc.viaproxy.plugins.events.Proxy2ServerChannelInitializeEvent;
 import net.raphimc.viaproxy.plugins.events.types.ITyped;
 import net.raphimc.viaproxy.protocolhack.impl.ViaProxyViaDecodeHandler;
 import net.raphimc.viaproxy.proxy.ProxyConnection;
 
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.util.Locale;
 import java.util.function.Supplier;
 
 public class Proxy2ServerChannelInitializer extends MinecraftChannelInitializer {
@@ -67,9 +75,35 @@ public class Proxy2ServerChannelInitializer extends MinecraftChannelInitializer
             channel.pipeline().addBefore(MCPipeline.SIZER_HANDLER_NAME, VPHPipeline.PRE_NETTY_DECODER_HANDLER_NAME, new PreNettyDecoder(user));
         }
 
+        if (Options.PROXY_URL != null) {
+            channel.pipeline().addFirst("viaproxy-proxy-handler", this.getProxyHandler());
+        }
+
         if (PluginManager.EVENT_MANAGER.call(new Proxy2ServerChannelInitializeEvent(ITyped.Type.POST, channel)).isCancelled()) {
             channel.close();
         }
     }
 
+    protected ProxyHandler getProxyHandler() {
+        final URI proxyUrl = Options.PROXY_URL;
+        final InetSocketAddress proxyAddress = new InetSocketAddress(proxyUrl.getHost(), proxyUrl.getPort());
+        final String username = proxyUrl.getUserInfo() != null ? proxyUrl.getUserInfo().split(":")[0] : null;
+        final String password = proxyUrl.getUserInfo() != null && proxyUrl.getUserInfo().contains(":") ? proxyUrl.getUserInfo().split(":")[1] : null;
+
+        switch (proxyUrl.getScheme().toUpperCase(Locale.ROOT)) {
+            case "HTTP":
+            case "HTTPS":
+                if (username != null && password != null) return new HttpProxyHandler(proxyAddress, username, password);
+                else return new HttpProxyHandler(proxyAddress);
+            case "SOCKS4":
+                if (username != null) return new Socks4ProxyHandler(proxyAddress, username);
+                else return new Socks4ProxyHandler(proxyAddress);
+            case "SOCKS5":
+                if (username != null && password != null) return new Socks5ProxyHandler(proxyAddress, username, password);
+                else return new Socks5ProxyHandler(proxyAddress);
+        }
+
+        throw new IllegalArgumentException("Unknown proxy type: " + proxyUrl.getScheme());
+    }
+
 }