From 05ef426614045ce31d84d4363cce9595b8e067e5 Mon Sep 17 00:00:00 2001 From: Mikhail Zuenko Date: Fri, 24 Apr 2026 00:29:21 +0700 Subject: [PATCH] Add support HTTP-proxies and encryption with SSL --- .../gui/screens/ProxiesImportScreen.java | 36 +++++----- .../gui/screens/ProxiesScreen.java | 2 +- .../meteorclient/mixin/ConnectionMixin.java | 39 ++++++++-- .../meteorclient/systems/proxies/Proxies.java | 14 ++-- .../meteorclient/systems/proxies/Proxy.java | 71 ++++++++++++++++--- .../systems/proxies/ProxyType.java | 5 +- 6 files changed, 127 insertions(+), 40 deletions(-) diff --git a/src/main/java/meteordevelopment/meteorclient/gui/screens/ProxiesImportScreen.java b/src/main/java/meteordevelopment/meteorclient/gui/screens/ProxiesImportScreen.java index e3a069eb43..876396a192 100644 --- a/src/main/java/meteordevelopment/meteorclient/gui/screens/ProxiesImportScreen.java +++ b/src/main/java/meteordevelopment/meteorclient/gui/screens/ProxiesImportScreen.java @@ -49,52 +49,54 @@ public void initWidgets() { matcher = Proxies.PROXY_PATTERN.matcher(line); if (matcher.matches()) { - String address = matcher.group(2).replaceAll("\\b0+\\B", ""); - int port = Integer.parseInt(matcher.group(3)); + String address = matcher.group("address").replaceAll("\\b0+\\B", ""); + int port = Integer.parseInt(matcher.group("port")); proxy = new Proxy.Builder() .address(address) .port(port) - .name(matcher.group(1) != null ? matcher.group(1) : address + ":" + port) - .type(matcher.group(4) != null ? ProxyType.parse(matcher.group(4)) : ProxyType.Socks4) + .name(matcher.group("name") != null ? matcher.group("name") : address + ":" + port) + .type(matcher.group("type") != null ? ProxyType.parse(matcher.group("type")) : ProxyType.SOCKS4) + .secure(matcher.group("secure") != null) .build(); } matcher = Proxies.PROXY_PATTERN_WEBSHARE.matcher(line); if (proxy == null && matcher.matches()) { - String address = matcher.group(1).replaceAll("\\b0+\\B", ""); - int port = Integer.parseInt(matcher.group(2)); + String address = matcher.group("address").replaceAll("\\b0+\\B", ""); + int port = Integer.parseInt(matcher.group("port")); proxy = new Proxy.Builder() .address(address) .port(port) .name(address + ":" + port) - .username(matcher.group(3) != null ? matcher.group(3) : "") - .password(matcher.group(4) != null ? matcher.group(4) : "") - .type(ProxyType.Socks5) + .username(matcher.group("username") != null ? matcher.group("username") : "") + .password(matcher.group("password") != null ? matcher.group("password") : "") + .type(ProxyType.HTTP) + .secure(true) .build(); } matcher = Proxies.PROXY_PATTERN_URI.matcher(line); if (proxy == null && matcher.matches()) { - String address = matcher.group("addr").replaceAll("\\b0+\\B", ""); + String address = matcher.group("address").replaceAll("\\b0+\\B", ""); int port = Integer.parseInt(matcher.group("port")); - ProxyType type = ProxyType.parse(matcher.group(1)); + ProxyType type = ProxyType.parse(matcher.group("type")); + boolean secure = matcher.group("secure") != null; if (type == null) { - if (matcher.group(1) != null && matcher.group(1).equals("socks")) type = ProxyType.Socks5; - // if it has a password it's a socks5 proxy - else if (matcher.group("pass") != null) type = ProxyType.Socks5; - else type = ProxyType.Socks4; + type = ProxyType.HTTP; + secure = true; } proxy = new Proxy.Builder() .address(address) .port(port) .name(address + ":" + port) - .username(matcher.group("user") != null ? matcher.group("user") : "") - .password(matcher.group("pass") != null ? matcher.group("pass") : "") + .username(matcher.group("username") != null ? matcher.group("username") : "") + .password(matcher.group("password") != null ? matcher.group("password") : "") .type(type) + .secure(secure) .build(); } diff --git a/src/main/java/meteordevelopment/meteorclient/gui/screens/ProxiesScreen.java b/src/main/java/meteordevelopment/meteorclient/gui/screens/ProxiesScreen.java index 1812d47292..4cc4ff0f5e 100644 --- a/src/main/java/meteordevelopment/meteorclient/gui/screens/ProxiesScreen.java +++ b/src/main/java/meteordevelopment/meteorclient/gui/screens/ProxiesScreen.java @@ -110,7 +110,7 @@ private void initTable(WTable table) { WLabel name = table.add(theme.label(proxy.name.get())).widget(); name.color = theme.textColor(); - WLabel type = table.add(theme.label("(" + proxy.type.get() + ")")).widget(); + WLabel type = table.add(theme.label("(" + proxy.type.get() + (proxy.secure.get() ? "S" : "") + ")")).widget(); type.color = theme.textSecondaryColor(); WHorizontalList ipList = table.add(theme.horizontalList()).expandCellX().widget(); diff --git a/src/main/java/meteordevelopment/meteorclient/mixin/ConnectionMixin.java b/src/main/java/meteordevelopment/meteorclient/mixin/ConnectionMixin.java index 3b4f09150f..20e47ca128 100644 --- a/src/main/java/meteordevelopment/meteorclient/mixin/ConnectionMixin.java +++ b/src/main/java/meteordevelopment/meteorclient/mixin/ConnectionMixin.java @@ -9,8 +9,13 @@ import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPipeline; +import io.netty.handler.proxy.HttpProxyHandler; import io.netty.handler.proxy.Socks4ProxyHandler; import io.netty.handler.proxy.Socks5ProxyHandler; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslContextBuilder; +import io.netty.handler.ssl.SslHandler; +import io.netty.handler.ssl.util.InsecureTrustManagerFactory; import io.netty.handler.timeout.TimeoutException; import meteordevelopment.meteorclient.MeteorClient; import meteordevelopment.meteorclient.events.packets.PacketEvent; @@ -40,6 +45,8 @@ import java.net.InetSocketAddress; import java.util.Iterator; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLException; @Mixin(Connection.class) public abstract class ConnectionMixin { @@ -99,10 +106,34 @@ private static void onAddHandlers(ChannelPipeline pipeline, PacketFlow inboundDi if (proxy == null) return; switch (proxy.type.get()) { - case Socks4 -> - pipeline.addFirst(new Socks4ProxyHandler(new InetSocketAddress(proxy.address.get(), proxy.port.get()), proxy.username.get())); - case Socks5 -> - pipeline.addFirst(new Socks5ProxyHandler(new InetSocketAddress(proxy.address.get(), proxy.port.get()), proxy.username.get(), proxy.password.get())); + case HTTP -> pipeline.addFirst(new HttpProxyHandler( + new InetSocketAddress(proxy.address.get(), proxy.port.get()), + proxy.username.get(), proxy.password.get() + )); + case SOCKS5 -> pipeline.addFirst(new Socks5ProxyHandler( + new InetSocketAddress(proxy.address.get(), proxy.port.get()), + proxy.username.get(), proxy.password.get() + )); + case SOCKS4 -> pipeline.addFirst(new Socks4ProxyHandler( + new InetSocketAddress(proxy.address.get(), proxy.port.get()), + proxy.username.get() + )); + } + + if (proxy.secure.get()) { + try { + SslContext sslContext = SslContextBuilder.forClient() + .trustManager(InsecureTrustManagerFactory.INSTANCE) + .build(); + SSLEngine engine = sslContext.newEngine( + pipeline.channel().alloc(), + proxy.address.get(), + proxy.port.get() + ); + pipeline.addFirst(new SslHandler(engine)); + } catch (SSLException e) { + throw new RuntimeException("Failed to create SSL context for proxy", e); + } } } } diff --git a/src/main/java/meteordevelopment/meteorclient/systems/proxies/Proxies.java b/src/main/java/meteordevelopment/meteorclient/systems/proxies/Proxies.java index 95f55075fa..ad30e2b692 100644 --- a/src/main/java/meteordevelopment/meteorclient/systems/proxies/Proxies.java +++ b/src/main/java/meteordevelopment/meteorclient/systems/proxies/Proxies.java @@ -80,12 +80,12 @@ public class Proxies extends System implements Iterable { .build() ); - // https://regex101.com/r/gRHjnd/latest - public static final Pattern PROXY_PATTERN = Pattern.compile("^(?:([\\w\\s]+)=)?((?:0*(?:\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])(?:\\.(?!:)|)){4}):(?!0)(\\d{1,4}|[1-5]\\d{4}|6[0-4]\\d{3}|65[0-4]\\d{2}|655[0-2]\\d|6553[0-5])(?i:@(socks[45]))?$", Pattern.MULTILINE); - // https://regex101.com/r/QXATIS/1 - public static final Pattern PROXY_PATTERN_WEBSHARE = Pattern.compile("^((?:0*(?:\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])(?:\\.(?!:)|)){4}):(?!0)(\\d{1,4}|[1-5]\\d{4}|6[0-4]\\d{3}|65[0-4]\\d{2}|655[0-2]\\d|6553[0-5]):([^:]+)(?::(.+))?$", Pattern.MULTILINE); - // https://regex101.com/r/7M2LFx/1 - public static final Pattern PROXY_PATTERN_URI = Pattern.compile("^(?:(socks|socks4|socks5)://)?(?:(?[\\w~-]+)(:(?[\\w~-]+))?@)?(?(?:0*(?:\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])(?:\\.(?!:)|)){4}):(?!0)(?\\d{1,4}|[1-5]\\d{4}|6[0-4]\\d{3}|65[0-4]\\d{2}|655[0-2]\\d|6553[0-5])$", Pattern.MULTILINE); + // https://regex101.com/r/swN1Ya/1 + public static final Pattern PROXY_PATTERN = Pattern.compile("^(?:(?[\\w\\s]+)=)?(?
(?:0*(?:\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])(?:\\.(?!\\:)|)){4})\\:(?!0)(?\\d{1,4}|[1-5]\\d{4}|6[0-4]\\d{3}|65[0-4]\\d{2}|655[0-2]\\d|6553[0-5])(@(?http|socks[45])(?s)?)?$", Pattern.CASE_INSENSITIVE); + // https://regex101.com/r/rPqeik/1 + public static final Pattern PROXY_PATTERN_WEBSHARE = Pattern.compile("^(?
(?:0*(?:\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])(?:\\.(?!\\:)|)){4})\\:(?!0)(?\\d{1,4}|[1-5]\\d{4}|6[0-4]\\d{3}|65[0-4]\\d{2}|655[0-2]\\d|6553[0-5])(?:\\:(?[^:]+)(?:\\:(?.+))?)$", Pattern.CASE_INSENSITIVE); + // https://regex101.com/r/bGQd0X/1 + public static final Pattern PROXY_PATTERN_URI = Pattern.compile("^(?:(?http|socks[45])(?s)?\\:\\/\\/)?(?:(?[^:]+)(\\:(?[^:]+))?\\@)?(?
(?:0*(?:\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])(?:\\.(?!\\:)|)){4})\\:(?!0)(?\\d{1,4}|[1-5]\\d{4}|6[0-4]\\d{3}|65[0-4]\\d{2}|655[0-2]\\d|6553[0-5])$", Pattern.CASE_INSENSITIVE); private List proxies = new ArrayList<>(); public boolean refreshing; @@ -100,7 +100,7 @@ public static Proxies get() { public boolean add(Proxy proxy) { for (Proxy p : proxies) { - if (p.type.get().equals(proxy.type.get()) && p.address.get().equals(proxy.address.get()) && Objects.equals(p.port.get(), proxy.port.get())) + if (p.type.get().equals(proxy.type.get()) && p.secure.get().equals(proxy.secure.get()) && p.address.get().equals(proxy.address.get()) && Objects.equals(p.port.get(), proxy.port.get())) return false; } diff --git a/src/main/java/meteordevelopment/meteorclient/systems/proxies/Proxy.java b/src/main/java/meteordevelopment/meteorclient/systems/proxies/Proxy.java index d0c4c4765d..8df211fc02 100644 --- a/src/main/java/meteordevelopment/meteorclient/systems/proxies/Proxy.java +++ b/src/main/java/meteordevelopment/meteorclient/systems/proxies/Proxy.java @@ -20,9 +20,12 @@ import java.net.Socket; import java.net.SocketTimeoutException; import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; import java.time.Duration; import java.time.Instant; import java.util.Objects; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; public class Proxy implements ISerializable { public final Settings settings = new Settings(); @@ -39,7 +42,14 @@ public class Proxy implements ISerializable { public Setting type = sgGeneral.add(new EnumSetting.Builder() .name("type") .description("The type of proxy.") - .defaultValue(ProxyType.Socks5) + .defaultValue(ProxyType.SOCKS5) + .build() + ); + + public Setting secure = sgGeneral.add(new BoolSetting.Builder() + .name("secure") + .description("Whether the proxy is secure.") + .defaultValue(false) .build() ); @@ -78,7 +88,7 @@ public class Proxy implements ISerializable { public Setting password = sgOptional.add(new StringSetting.Builder() .name("password") .description("The password of the proxy.") - .visible(() -> type.get().equals(ProxyType.Socks5)) + .visible(() -> !type.get().equals(ProxyType.SOCKS4)) .build() ); @@ -138,10 +148,32 @@ public int checkStatus() { } catch (IOException _) { } + try { + Instant before = Instant.now(); + if (isHttp()) { + status = Status.ALIVE; + latency = Duration.between(before, Instant.now()).toMillis(); + return 1; + } + } + catch (SocketTimeoutException e) { + timeout = true; + } + catch (IOException ignored) {} + status = Status.DEAD; return timeout ? 3 : 2; } + private boolean isHttp() throws IOException { + String request = "CONNECT 0.0.0.0:80 HTTP/1.1\r\nHost: 0.0.0.0:80\r\n\r\n"; + + byte[] data = sendData(request.getBytes(StandardCharsets.UTF_8), 12); + + if (data.length < 12) return false; + return data[0] == 'H' && data[1] == 'T' && data[2] == 'T' && data[3] == 'P' && data[4] == '/'; + } + private boolean isSocks4() throws IOException { ByteBuffer bb; byte[] u = username.get().getBytes(); @@ -191,19 +223,34 @@ private boolean isSocks5() throws IOException { } private byte[] sendData(byte[] data, int read) throws IOException { - try (Socket s = new Socket()) { - s.setSoTimeout(Proxies.get().timeout.get()); - s.connect(new InetSocketAddress(address.get(), port.get()), Proxies.get().timeout.get()); - OutputStream out = s.getOutputStream(); + if (secure.get()) { + SSLSocketFactory factory = (SSLSocketFactory)SSLSocketFactory.getDefault(); + try (SSLSocket s = (SSLSocket)factory.createSocket()) { + s.setSoTimeout(Proxies.get().timeout.get()); + s.connect(new InetSocketAddress(address.get(), port.get()), Proxies.get().timeout.get()); + s.startHandshake(); + + OutputStream out = s.getOutputStream(); + out.write(data); - out.write(data); + return s.getInputStream().readNBytes(read); + } + } else { + try (Socket s = new Socket()) { + s.setSoTimeout(Proxies.get().timeout.get()); + s.connect(new InetSocketAddress(address.get(), port.get()), Proxies.get().timeout.get()); + + OutputStream out = s.getOutputStream(); + out.write(data); - return s.getInputStream().readNBytes(read); + return s.getInputStream().readNBytes(read); + } } } public static class Builder { - protected ProxyType type = ProxyType.Socks5; + protected ProxyType type = ProxyType.SOCKS5; + protected boolean secure = false; protected String address = ""; protected int port = 0; protected String name = ""; @@ -216,6 +263,11 @@ public Builder type(ProxyType type) { return this; } + public Builder secure(boolean secure) { + this.secure = secure; + return this; + } + public Builder address(String address) { this.address = address; return this; @@ -250,6 +302,7 @@ public Proxy build() { Proxy proxy = new Proxy(); if (!type.equals(proxy.type.getDefaultValue())) proxy.type.set(type); + if (secure != proxy.secure.getDefaultValue()) proxy.secure.set(secure); if (!address.equals(proxy.address.getDefaultValue())) proxy.address.set(address); if (port != proxy.port.getDefaultValue()) proxy.port.set(port); if (!name.equals(proxy.name.getDefaultValue())) proxy.name.set(name); diff --git a/src/main/java/meteordevelopment/meteorclient/systems/proxies/ProxyType.java b/src/main/java/meteordevelopment/meteorclient/systems/proxies/ProxyType.java index 1d09080467..c416146a08 100644 --- a/src/main/java/meteordevelopment/meteorclient/systems/proxies/ProxyType.java +++ b/src/main/java/meteordevelopment/meteorclient/systems/proxies/ProxyType.java @@ -8,8 +8,9 @@ import org.jetbrains.annotations.Nullable; public enum ProxyType { - Socks4, - Socks5; + HTTP, + SOCKS5, + SOCKS4; @Nullable public static ProxyType parse(String group) {