Skip to content

Commit c53841d

Browse files
committed
Add support HTTP-proxies and encryption with SSL
1 parent c2bb1c3 commit c53841d

6 files changed

Lines changed: 127 additions & 38 deletions

File tree

src/main/java/meteordevelopment/meteorclient/gui/screens/ProxiesImportScreen.java

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -48,52 +48,54 @@ public void initWidgets() {
4848

4949
matcher = Proxies.PROXY_PATTERN.matcher(line);
5050
if (matcher.matches()) {
51-
String address = matcher.group(2).replaceAll("\\b0+\\B", "");
52-
int port = Integer.parseInt(matcher.group(3));
51+
String address = matcher.group("address").replaceAll("\\b0+\\B", "");
52+
int port = Integer.parseInt(matcher.group("port"));
5353

5454
proxy = new Proxy.Builder()
5555
.address(address)
5656
.port(port)
57-
.name(matcher.group(1) != null ? matcher.group(1) : address + ":" + port)
58-
.type(matcher.group(4) != null ? ProxyType.parse(matcher.group(4)) : ProxyType.Socks4)
57+
.name(matcher.group("name") != null ? matcher.group("name") : address + ":" + port)
58+
.type(matcher.group("type") != null ? ProxyType.parse(matcher.group("type")) : ProxyType.SOCKS4)
59+
.secure(matcher.group("secure") != null)
5960
.build();
6061
}
6162

6263
matcher = Proxies.PROXY_PATTERN_WEBSHARE.matcher(line);
6364
if (proxy == null && matcher.matches()) {
64-
String address = matcher.group(1).replaceAll("\\b0+\\B", "");
65-
int port = Integer.parseInt(matcher.group(2));
65+
String address = matcher.group("address").replaceAll("\\b0+\\B", "");
66+
int port = Integer.parseInt(matcher.group("port"));
6667

6768
proxy = new Proxy.Builder()
6869
.address(address)
6970
.port(port)
7071
.name(address + ":" + port)
71-
.username(matcher.group(3) != null ? matcher.group(3) : "")
72-
.password(matcher.group(4) != null ? matcher.group(4) : "")
73-
.type(ProxyType.Socks5)
72+
.username(matcher.group("username") != null ? matcher.group("username") : "")
73+
.password(matcher.group("password") != null ? matcher.group("password") : "")
74+
.type(ProxyType.HTTP)
75+
.secure(true)
7476
.build();
7577
}
7678

7779
matcher = Proxies.PROXY_PATTERN_URI.matcher(line);
7880
if (proxy == null && matcher.matches()) {
79-
String address = matcher.group("addr").replaceAll("\\b0+\\B", "");
81+
String address = matcher.group("address").replaceAll("\\b0+\\B", "");
8082
int port = Integer.parseInt(matcher.group("port"));
8183

82-
ProxyType type = ProxyType.parse(matcher.group(1));
84+
ProxyType type = ProxyType.parse(matcher.group("type"));
85+
boolean secure = matcher.group("secure") != null;
8386
if (type == null) {
84-
if (matcher.group(1) != null && matcher.group(1).equals("socks")) type = ProxyType.Socks5;
85-
// if it has a password it's a socks5 proxy
86-
else if (matcher.group("pass") != null) type = ProxyType.Socks5;
87-
else type = ProxyType.Socks4;
87+
type = ProxyType.HTTP;
88+
secure = true;
8889
}
8990

9091
proxy = new Proxy.Builder()
9192
.address(address)
9293
.port(port)
9394
.name(address + ":" + port)
94-
.username(matcher.group("user") != null ? matcher.group("user") : "")
95-
.password(matcher.group("pass") != null ? matcher.group("pass") : "")
95+
.username(matcher.group("username") != null ? matcher.group("username") : "")
96+
.password(matcher.group("password") != null ? matcher.group("password") : "")
9697
.type(type)
98+
.secure(secure)
9799
.build();
98100
}
99101

src/main/java/meteordevelopment/meteorclient/gui/screens/ProxiesScreen.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ private void initTable(WTable table) {
110110
WLabel name = table.add(theme.label(proxy.name.get())).widget();
111111
name.color = theme.textColor();
112112

113-
WLabel type = table.add(theme.label("(" + proxy.type.get() + ")")).widget();
113+
WLabel type = table.add(theme.label("(" + proxy.type.get() + (proxy.secure.get() ? "S" : "") + ")")).widget();
114114
type.color = theme.textSecondaryColor();
115115

116116
WHorizontalList ipList = table.add(theme.horizontalList()).expandCellX().widget();

src/main/java/meteordevelopment/meteorclient/mixin/ClientConnectionMixin.java

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,13 @@
99
import io.netty.channel.ChannelFutureListener;
1010
import io.netty.channel.ChannelHandlerContext;
1111
import io.netty.channel.ChannelPipeline;
12+
import io.netty.handler.proxy.HttpProxyHandler;
1213
import io.netty.handler.proxy.Socks4ProxyHandler;
1314
import io.netty.handler.proxy.Socks5ProxyHandler;
15+
import io.netty.handler.ssl.SslContext;
16+
import io.netty.handler.ssl.SslContextBuilder;
17+
import io.netty.handler.ssl.SslHandler;
18+
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
1419
import io.netty.handler.timeout.TimeoutException;
1520
import meteordevelopment.meteorclient.MeteorClient;
1621
import meteordevelopment.meteorclient.events.packets.PacketEvent;
@@ -40,6 +45,8 @@
4045

4146
import java.net.InetSocketAddress;
4247
import java.util.Iterator;
48+
import javax.net.ssl.SSLEngine;
49+
import javax.net.ssl.SSLException;
4350

4451
@Mixin(ClientConnection.class)
4552
public abstract class ClientConnectionMixin {
@@ -97,8 +104,34 @@ private static void onAddHandlers(ChannelPipeline pipeline, NetworkSide side, bo
97104
if (proxy == null) return;
98105

99106
switch (proxy.type.get()) {
100-
case Socks4 -> pipeline.addFirst(new Socks4ProxyHandler(new InetSocketAddress(proxy.address.get(), proxy.port.get()), proxy.username.get()));
101-
case Socks5 -> pipeline.addFirst(new Socks5ProxyHandler(new InetSocketAddress(proxy.address.get(), proxy.port.get()), proxy.username.get(), proxy.password.get()));
107+
case HTTP -> pipeline.addFirst(new HttpProxyHandler(
108+
new InetSocketAddress(proxy.address.get(), proxy.port.get()),
109+
proxy.username.get(), proxy.password.get()
110+
));
111+
case SOCKS5 -> pipeline.addFirst(new Socks5ProxyHandler(
112+
new InetSocketAddress(proxy.address.get(), proxy.port.get()),
113+
proxy.username.get(), proxy.password.get()
114+
));
115+
case SOCKS4 -> pipeline.addFirst(new Socks4ProxyHandler(
116+
new InetSocketAddress(proxy.address.get(), proxy.port.get()),
117+
proxy.username.get()
118+
));
119+
}
120+
121+
if (proxy.secure.get()) {
122+
try {
123+
SslContext sslContext = SslContextBuilder.forClient()
124+
.trustManager(InsecureTrustManagerFactory.INSTANCE)
125+
.build();
126+
SSLEngine engine = sslContext.newEngine(
127+
pipeline.channel().alloc(),
128+
proxy.address.get(),
129+
proxy.port.get()
130+
);
131+
pipeline.addFirst(new SslHandler(engine));
132+
} catch (SSLException e) {
133+
throw new RuntimeException("Failed to create SSL context for proxy", e);
134+
}
102135
}
103136
}
104137
}

src/main/java/meteordevelopment/meteorclient/systems/proxies/Proxies.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,12 @@ public class Proxies extends System<Proxies> implements Iterable<Proxy> {
8080
.build()
8181
);
8282

83-
// https://regex101.com/r/gRHjnd/latest
84-
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);
85-
// https://regex101.com/r/QXATIS/1
86-
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);
87-
// https://regex101.com/r/7M2LFx/1
88-
public static final Pattern PROXY_PATTERN_URI = Pattern.compile("^(?:(socks|socks4|socks5)://)?(?:(?<user>[\\w~-]+)(:(?<pass>[\\w~-]+))?@)?(?<addr>(?:0*(?:\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])(?:\\.(?!:)|)){4}):(?!0)(?<port>\\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);
83+
// https://regex101.com/r/swN1Ya/1
84+
public static final Pattern PROXY_PATTERN = Pattern.compile("^(?:(?<name>[\\w\\s]+)=)?(?<address>(?:0*(?:\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])(?:\\.(?!\\:)|)){4})\\:(?!0)(?<port>\\d{1,4}|[1-5]\\d{4}|6[0-4]\\d{3}|65[0-4]\\d{2}|655[0-2]\\d|6553[0-5])(@(?<type>http|socks[45])(?<secure>s)?)?$", Pattern.CASE_INSENSITIVE);
85+
// https://regex101.com/r/rPqeik/1
86+
public static final Pattern PROXY_PATTERN_WEBSHARE = Pattern.compile("^(?<address>(?:0*(?:\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])(?:\\.(?!\\:)|)){4})\\:(?!0)(?<port>\\d{1,4}|[1-5]\\d{4}|6[0-4]\\d{3}|65[0-4]\\d{2}|655[0-2]\\d|6553[0-5])(?:\\:(?<username>[^:]+)(?:\\:(?<password>.+))?)$", Pattern.CASE_INSENSITIVE);
87+
// https://regex101.com/r/bGQd0X/1
88+
public static final Pattern PROXY_PATTERN_URI = Pattern.compile("^(?:(?<type>http|socks[45])(?<secure>s)?\\:\\/\\/)?(?:(?<username>[^:]+)(\\:(?<password>[^:]+))?\\@)?(?<address>(?:0*(?:\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])(?:\\.(?!\\:)|)){4})\\:(?!0)(?<port>\\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);
8989

9090
private List<Proxy> proxies = new ArrayList<>();
9191
public boolean refreshing;
@@ -100,7 +100,7 @@ public static Proxies get() {
100100

101101
public boolean add(Proxy proxy) {
102102
for (Proxy p : proxies) {
103-
if (p.type.get().equals(proxy.type.get()) && p.address.get().equals(proxy.address.get()) && Objects.equals(p.port.get(), proxy.port.get())) return false;
103+
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;
104104
}
105105

106106
if (proxies.isEmpty()) proxy.enabled.set(true);

src/main/java/meteordevelopment/meteorclient/systems/proxies/Proxy.java

Lines changed: 62 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,12 @@
2020
import java.net.Socket;
2121
import java.net.SocketTimeoutException;
2222
import java.nio.ByteBuffer;
23+
import java.nio.charset.StandardCharsets;
2324
import java.time.Duration;
2425
import java.time.Instant;
2526
import java.util.Objects;
27+
import javax.net.ssl.SSLSocket;
28+
import javax.net.ssl.SSLSocketFactory;
2629

2730
public class Proxy implements ISerializable<Proxy> {
2831
public final Settings settings = new Settings();
@@ -39,7 +42,14 @@ public class Proxy implements ISerializable<Proxy> {
3942
public Setting<ProxyType> type = sgGeneral.add(new EnumSetting.Builder<ProxyType>()
4043
.name("type")
4144
.description("The type of proxy.")
42-
.defaultValue(ProxyType.Socks5)
45+
.defaultValue(ProxyType.SOCKS5)
46+
.build()
47+
);
48+
49+
public Setting<Boolean> secure = sgGeneral.add(new BoolSetting.Builder()
50+
.name("secure")
51+
.description("Whether the proxy is secure.")
52+
.defaultValue(false)
4353
.build()
4454
);
4555

@@ -78,7 +88,7 @@ public class Proxy implements ISerializable<Proxy> {
7888
public Setting<String> password = sgOptional.add(new StringSetting.Builder()
7989
.name("password")
8090
.description("The password of the proxy.")
81-
.visible(() -> type.get().equals(ProxyType.Socks5))
91+
.visible(() -> !type.get().equals(ProxyType.SOCKS4))
8292
.build()
8393
);
8494

@@ -138,10 +148,32 @@ public int checkStatus() {
138148
}
139149
catch (IOException ignored) {}
140150

151+
try {
152+
Instant before = Instant.now();
153+
if (isHttp()) {
154+
status = Status.ALIVE;
155+
latency = Duration.between(before, Instant.now()).toMillis();
156+
return 1;
157+
}
158+
}
159+
catch (SocketTimeoutException e) {
160+
timeout = true;
161+
}
162+
catch (IOException ignored) {}
163+
141164
status = Status.DEAD;
142165
return timeout ? 3 : 2;
143166
}
144167

168+
private boolean isHttp() throws IOException {
169+
String request = "CONNECT 0.0.0.0:80 HTTP/1.1\r\nHost: 0.0.0.0:80\r\n\r\n";
170+
171+
byte[] data = sendData(request.getBytes(StandardCharsets.UTF_8), 12);
172+
173+
if (data.length < 12) return false;
174+
return data[0] == 'H' && data[1] == 'T' && data[2] == 'T' && data[3] == 'P' && data[4] == '/';
175+
}
176+
145177
private boolean isSocks4() throws IOException {
146178
ByteBuffer bb;
147179
byte[] u = username.get().getBytes();
@@ -191,19 +223,34 @@ private boolean isSocks5() throws IOException {
191223
}
192224

193225
private byte[] sendData(byte[] data, int read) throws IOException {
194-
try (Socket s = new Socket()) {
195-
s.setSoTimeout(Proxies.get().timeout.get());
196-
s.connect(new InetSocketAddress(address.get(), port.get()), Proxies.get().timeout.get());
197-
OutputStream out = s.getOutputStream();
226+
if (secure.get()) {
227+
SSLSocketFactory factory = (SSLSocketFactory)SSLSocketFactory.getDefault();
228+
try (SSLSocket s = (SSLSocket)factory.createSocket()) {
229+
s.setSoTimeout(Proxies.get().timeout.get());
230+
s.connect(new InetSocketAddress(address.get(), port.get()), Proxies.get().timeout.get());
231+
s.startHandshake();
198232

199-
out.write(data);
233+
OutputStream out = s.getOutputStream();
234+
out.write(data);
200235

201-
return s.getInputStream().readNBytes(read);
236+
return s.getInputStream().readNBytes(read);
237+
}
238+
} else {
239+
try (Socket s = new Socket()) {
240+
s.setSoTimeout(Proxies.get().timeout.get());
241+
s.connect(new InetSocketAddress(address.get(), port.get()), Proxies.get().timeout.get());
242+
243+
OutputStream out = s.getOutputStream();
244+
out.write(data);
245+
246+
return s.getInputStream().readNBytes(read);
247+
}
202248
}
203249
}
204250

205251
public static class Builder {
206-
protected ProxyType type = ProxyType.Socks5;
252+
protected ProxyType type = ProxyType.SOCKS5;
253+
protected boolean secure = false;
207254
protected String address = "";
208255
protected int port = 0;
209256
protected String name = "";
@@ -216,6 +263,11 @@ public Builder type(ProxyType type) {
216263
return this;
217264
}
218265

266+
public Builder secure(boolean secure) {
267+
this.secure = secure;
268+
return this;
269+
}
270+
219271
public Builder address(String address) {
220272
this.address = address;
221273
return this;
@@ -250,6 +302,7 @@ public Proxy build() {
250302
Proxy proxy = new Proxy();
251303

252304
if (!type.equals(proxy.type.getDefaultValue())) proxy.type.set(type);
305+
if (secure != proxy.secure.getDefaultValue()) proxy.secure.set(secure);
253306
if (!address.equals(proxy.address.getDefaultValue())) proxy.address.set(address);
254307
if (port != proxy.port.getDefaultValue()) proxy.port.set(port);
255308
if (!name.equals(proxy.name.getDefaultValue())) proxy.name.set(name);

src/main/java/meteordevelopment/meteorclient/systems/proxies/ProxyType.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@
88
import org.jetbrains.annotations.Nullable;
99

1010
public enum ProxyType {
11-
Socks4,
12-
Socks5;
11+
HTTP,
12+
SOCKS5,
13+
SOCKS4;
1314

1415
@Nullable
1516
public static ProxyType parse(String group) {

0 commit comments

Comments
 (0)