mirror of
https://github.com/sirjonasxx/G-Earth.git
synced 2024-11-27 02:40:51 +01:00
Add cloudflare cookies support for Nitro WebSocket connections
This commit is contained in:
parent
0cdb5e6465
commit
c4c2d2bfe1
@ -25,7 +25,7 @@ public class NitroProxyProvider implements ProxyProvider, NitroHttpProxyServerCa
|
|||||||
private final AtomicBoolean abortLock;
|
private final AtomicBoolean abortLock;
|
||||||
|
|
||||||
private String originalWebsocketUrl;
|
private String originalWebsocketUrl;
|
||||||
private String originalOriginUrl;
|
private String originalCookies;
|
||||||
|
|
||||||
public NitroProxyProvider(HProxySetter proxySetter, HStateSetter stateSetter, HConnection connection) {
|
public NitroProxyProvider(HProxySetter proxySetter, HStateSetter stateSetter, HConnection connection) {
|
||||||
this.proxySetter = proxySetter;
|
this.proxySetter = proxySetter;
|
||||||
@ -40,12 +40,15 @@ public class NitroProxyProvider implements ProxyProvider, NitroHttpProxyServerCa
|
|||||||
return originalWebsocketUrl;
|
return originalWebsocketUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getOriginalOriginUrl() {
|
public String getOriginalCookies() {
|
||||||
return originalOriginUrl;
|
return originalCookies;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start() throws IOException {
|
public void start() throws IOException {
|
||||||
|
originalWebsocketUrl = null;
|
||||||
|
originalCookies = null;
|
||||||
|
|
||||||
connection.getStateObservable().addListener(this);
|
connection.getStateObservable().addListener(this);
|
||||||
|
|
||||||
if (!nitroHttpProxy.start()) {
|
if (!nitroHttpProxy.start()) {
|
||||||
@ -97,11 +100,15 @@ public class NitroProxyProvider implements ProxyProvider, NitroHttpProxyServerCa
|
|||||||
@Override
|
@Override
|
||||||
public String replaceWebsocketServer(String configUrl, String websocketUrl) {
|
public String replaceWebsocketServer(String configUrl, String websocketUrl) {
|
||||||
originalWebsocketUrl = websocketUrl;
|
originalWebsocketUrl = websocketUrl;
|
||||||
originalOriginUrl = extractOriginUrl(configUrl);
|
|
||||||
|
|
||||||
return String.format("ws://127.0.0.1:%d", NitroConstants.WEBSOCKET_PORT);
|
return String.format("ws://127.0.0.1:%d", NitroConstants.WEBSOCKET_PORT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOriginCookies(String cookieHeaderValue) {
|
||||||
|
originalCookies = cookieHeaderValue;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stateChanged(HState oldState, HState newState) {
|
public void stateChanged(HState oldState, HState newState) {
|
||||||
if (oldState == HState.WAITING_FOR_CLIENT && newState == HState.CONNECTED) {
|
if (oldState == HState.WAITING_FOR_CLIENT && newState == HState.CONNECTED) {
|
||||||
@ -115,15 +122,4 @@ public class NitroProxyProvider implements ProxyProvider, NitroHttpProxyServerCa
|
|||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String extractOriginUrl(String url) {
|
|
||||||
try {
|
|
||||||
final URI uri = new URI(url);
|
|
||||||
return String.format("%s://%s/", uri.getScheme(), uri.getHost());
|
|
||||||
} catch (URISyntaxException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package gearth.protocol.connection.proxy.nitro.http;
|
package gearth.protocol.connection.proxy.nitro.http;
|
||||||
|
|
||||||
import gearth.GEarth;
|
|
||||||
import gearth.misc.ConfirmationDialog;
|
import gearth.misc.ConfirmationDialog;
|
||||||
import gearth.protocol.connection.proxy.nitro.NitroConstants;
|
import gearth.protocol.connection.proxy.nitro.NitroConstants;
|
||||||
import gearth.protocol.connection.proxy.nitro.os.NitroOsFunctions;
|
import gearth.protocol.connection.proxy.nitro.os.NitroOsFunctions;
|
||||||
@ -10,8 +9,6 @@ import javafx.application.Platform;
|
|||||||
import javafx.scene.control.Alert;
|
import javafx.scene.control.Alert;
|
||||||
import javafx.scene.control.ButtonType;
|
import javafx.scene.control.ButtonType;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.image.Image;
|
|
||||||
import javafx.stage.Stage;
|
|
||||||
import org.littleshoot.proxy.HttpProxyServer;
|
import org.littleshoot.proxy.HttpProxyServer;
|
||||||
import org.littleshoot.proxy.impl.DefaultHttpProxyServer;
|
import org.littleshoot.proxy.impl.DefaultHttpProxyServer;
|
||||||
import org.littleshoot.proxy.mitm.Authority;
|
import org.littleshoot.proxy.mitm.Authority;
|
||||||
|
@ -7,6 +7,10 @@ import io.netty.util.CharsetUtil;
|
|||||||
import org.littleshoot.proxy.HttpFiltersAdapter;
|
import org.littleshoot.proxy.HttpFiltersAdapter;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@ -16,6 +20,17 @@ public class NitroHttpProxyFilter extends HttpFiltersAdapter {
|
|||||||
private static final String NitroClientSearch = "configurationUrls:";
|
private static final String NitroClientSearch = "configurationUrls:";
|
||||||
private static final Pattern NitroConfigPattern = Pattern.compile("\"socket\\.url\":.?\"(wss?://.*?)\"", Pattern.MULTILINE);
|
private static final Pattern NitroConfigPattern = Pattern.compile("\"socket\\.url\":.?\"(wss?://.*?)\"", Pattern.MULTILINE);
|
||||||
|
|
||||||
|
// https://developers.cloudflare.com/fundamentals/get-started/reference/cloudflare-cookies/
|
||||||
|
private static final HashSet<String> CloudflareCookies = new HashSet<>(Arrays.asList(
|
||||||
|
"__cflb",
|
||||||
|
"__cf_bm",
|
||||||
|
"cf_ob_info",
|
||||||
|
"cf_use_ob",
|
||||||
|
"__cfwaitingroom",
|
||||||
|
"__cfruid",
|
||||||
|
"cf_clearance"
|
||||||
|
));
|
||||||
|
|
||||||
private static final String HeaderAcceptEncoding = "Accept-Encoding";
|
private static final String HeaderAcceptEncoding = "Accept-Encoding";
|
||||||
private static final String HeaderAge = "Age";
|
private static final String HeaderAge = "Age";
|
||||||
private static final String HeaderCacheControl = "Cache-Control";
|
private static final String HeaderCacheControl = "Cache-Control";
|
||||||
@ -27,6 +42,7 @@ public class NitroHttpProxyFilter extends HttpFiltersAdapter {
|
|||||||
|
|
||||||
private final NitroHttpProxyServerCallback callback;
|
private final NitroHttpProxyServerCallback callback;
|
||||||
private final String url;
|
private final String url;
|
||||||
|
private String cookies;
|
||||||
|
|
||||||
public NitroHttpProxyFilter(HttpRequest originalRequest, ChannelHandlerContext ctx, NitroHttpProxyServerCallback callback, String url) {
|
public NitroHttpProxyFilter(HttpRequest originalRequest, ChannelHandlerContext ctx, NitroHttpProxyServerCallback callback, String url) {
|
||||||
super(originalRequest, ctx);
|
super(originalRequest, ctx);
|
||||||
@ -58,6 +74,9 @@ public class NitroHttpProxyFilter extends HttpFiltersAdapter {
|
|||||||
|
|
||||||
// Disable caching.
|
// Disable caching.
|
||||||
stripCacheHeaders(headers);
|
stripCacheHeaders(headers);
|
||||||
|
|
||||||
|
// Find relevant cookies for the WebSocket connection.
|
||||||
|
this.cookies = parseCookies(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.clientToProxyRequest(httpObject);
|
return super.clientToProxyRequest(httpObject);
|
||||||
@ -77,13 +96,15 @@ public class NitroHttpProxyFilter extends HttpFiltersAdapter {
|
|||||||
|
|
||||||
if (matcher.find()) {
|
if (matcher.find()) {
|
||||||
final String originalWebsocket = matcher.group(1);
|
final String originalWebsocket = matcher.group(1);
|
||||||
final String replacementWebsocket = callback.replaceWebsocketServer(url, originalWebsocket);
|
final String replacementWebsocket = callback.replaceWebsocketServer(this.url, originalWebsocket);
|
||||||
|
|
||||||
if (replacementWebsocket != null) {
|
if (replacementWebsocket != null) {
|
||||||
responseBody = responseBody.replace(originalWebsocket, replacementWebsocket);
|
responseBody = responseBody.replace(originalWebsocket, replacementWebsocket);
|
||||||
responseModified = true;
|
responseModified = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
callback.setOriginCookies(this.cookies);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply changes.
|
// Apply changes.
|
||||||
@ -100,6 +121,32 @@ public class NitroHttpProxyFilter extends HttpFiltersAdapter {
|
|||||||
return httpObject;
|
return httpObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if cookies from the request need to be recorded for the websocket connection to the origin server.
|
||||||
|
*/
|
||||||
|
private String parseCookies(final HttpRequest request) {
|
||||||
|
final List<String> result = new ArrayList<>();
|
||||||
|
final List<String> cookieHeaders = request.headers().getAll("Cookie");
|
||||||
|
|
||||||
|
for (final String cookieHeader : cookieHeaders) {
|
||||||
|
final String[] cookies = cookieHeader.split(";");
|
||||||
|
|
||||||
|
for (final String cookie : cookies) {
|
||||||
|
final String[] parts = cookie.trim().split("=");
|
||||||
|
|
||||||
|
if (CloudflareCookies.contains(parts[0])) {
|
||||||
|
result.add(cookie.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.size() == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return String.join("; ", result);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Modify Content-Security-Policy header, which could prevent Nitro from connecting with G-Earth.
|
* Modify Content-Security-Policy header, which could prevent Nitro from connecting with G-Earth.
|
||||||
*/
|
*/
|
||||||
@ -148,5 +195,4 @@ public class NitroHttpProxyFilter extends HttpFiltersAdapter {
|
|||||||
headers.remove(HeaderIfModifiedSince);
|
headers.remove(HeaderIfModifiedSince);
|
||||||
headers.remove(HeaderLastModified);
|
headers.remove(HeaderLastModified);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -11,4 +11,9 @@ public interface NitroHttpProxyServerCallback {
|
|||||||
*/
|
*/
|
||||||
String replaceWebsocketServer(String configUrl, String websocketUrl);
|
String replaceWebsocketServer(String configUrl, String websocketUrl);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the parsed cookies for the origin WebSocket connection.
|
||||||
|
*/
|
||||||
|
void setOriginCookies(String cookieHeaderValue);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -6,10 +6,18 @@ import gearth.protocol.connection.*;
|
|||||||
import gearth.protocol.connection.proxy.nitro.NitroConstants;
|
import gearth.protocol.connection.proxy.nitro.NitroConstants;
|
||||||
import gearth.protocol.connection.proxy.nitro.NitroProxyProvider;
|
import gearth.protocol.connection.proxy.nitro.NitroProxyProvider;
|
||||||
import gearth.protocol.packethandler.nitro.NitroPacketHandler;
|
import gearth.protocol.packethandler.nitro.NitroPacketHandler;
|
||||||
|
import org.eclipse.jetty.websocket.common.WebSocketSession;
|
||||||
|
import org.eclipse.jetty.websocket.jsr356.JsrSession;
|
||||||
|
import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
|
||||||
|
|
||||||
import javax.websocket.*;
|
import javax.websocket.*;
|
||||||
import javax.websocket.server.ServerEndpoint;
|
import javax.websocket.server.ServerEndpoint;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
@ServerEndpoint(value = "/")
|
@ServerEndpoint(value = "/")
|
||||||
@ -23,7 +31,7 @@ public class NitroWebsocketClient implements NitroSession {
|
|||||||
private final NitroPacketHandler packetHandler;
|
private final NitroPacketHandler packetHandler;
|
||||||
private final AtomicBoolean shutdownLock;
|
private final AtomicBoolean shutdownLock;
|
||||||
|
|
||||||
private Session activeSession = null;
|
private JsrSession activeSession = null;
|
||||||
|
|
||||||
public NitroWebsocketClient(HProxySetter proxySetter, HStateSetter stateSetter, HConnection connection, NitroProxyProvider proxyProvider) {
|
public NitroWebsocketClient(HProxySetter proxySetter, HStateSetter stateSetter, HConnection connection, NitroProxyProvider proxyProvider) {
|
||||||
this.proxySetter = proxySetter;
|
this.proxySetter = proxySetter;
|
||||||
@ -36,11 +44,19 @@ public class NitroWebsocketClient implements NitroSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@OnOpen
|
@OnOpen
|
||||||
public void onOpen(Session session) throws IOException {
|
public void onOpen(Session session) throws Exception {
|
||||||
activeSession = session;
|
activeSession = (JsrSession) session;
|
||||||
activeSession.setMaxBinaryMessageBufferSize(NitroConstants.WEBSOCKET_BUFFER_SIZE);
|
activeSession.setMaxBinaryMessageBufferSize(NitroConstants.WEBSOCKET_BUFFER_SIZE);
|
||||||
|
|
||||||
server.connect(proxyProvider.getOriginalWebsocketUrl(), proxyProvider.getOriginalOriginUrl());
|
// Set proper headers to spoof being a real client.
|
||||||
|
final Map<String, List<String>> headers = new HashMap<>(activeSession.getUpgradeRequest().getHeaders());
|
||||||
|
|
||||||
|
if (proxyProvider.getOriginalCookies() != null) {
|
||||||
|
headers.put("Cookie", Collections.singletonList(proxyProvider.getOriginalCookies()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect to origin server.
|
||||||
|
server.connect(proxyProvider.getOriginalWebsocketUrl(), headers);
|
||||||
|
|
||||||
final HProxy proxy = new HProxy(HClient.NITRO, "", "", -1, -1, "");
|
final HProxy proxy = new HProxy(HClient.NITRO, "", "", -1, -1, "");
|
||||||
|
|
||||||
@ -89,7 +105,7 @@ public class NitroWebsocketClient implements NitroSession {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
activeSession.close();
|
activeSession.close();
|
||||||
} catch (IOException e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
} finally {
|
} finally {
|
||||||
activeSession = null;
|
activeSession = null;
|
||||||
|
@ -5,16 +5,26 @@ import gearth.protocol.HMessage;
|
|||||||
import gearth.protocol.connection.proxy.nitro.NitroConstants;
|
import gearth.protocol.connection.proxy.nitro.NitroConstants;
|
||||||
import gearth.protocol.packethandler.PacketHandler;
|
import gearth.protocol.packethandler.PacketHandler;
|
||||||
import gearth.protocol.packethandler.nitro.NitroPacketHandler;
|
import gearth.protocol.packethandler.nitro.NitroPacketHandler;
|
||||||
|
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
|
||||||
|
import org.eclipse.jetty.websocket.common.extensions.compress.PerMessageDeflateExtension;
|
||||||
|
import org.eclipse.jetty.websocket.jsr356.JsrExtension;
|
||||||
|
|
||||||
import javax.websocket.*;
|
import javax.websocket.*;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.Collections;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class NitroWebsocketServer extends Endpoint implements NitroSession {
|
public class NitroWebsocketServer extends Endpoint implements NitroSession {
|
||||||
|
|
||||||
|
private static final HashSet<String> SKIP_HEADERS = new HashSet<>(Arrays.asList(
|
||||||
|
"Sec-WebSocket-Extensions",
|
||||||
|
"Sec-WebSocket-Key",
|
||||||
|
"Sec-WebSocket-Version",
|
||||||
|
"Host",
|
||||||
|
"Connection",
|
||||||
|
"Upgrade"
|
||||||
|
));
|
||||||
|
|
||||||
private final PacketHandler packetHandler;
|
private final PacketHandler packetHandler;
|
||||||
private final NitroWebsocketClient client;
|
private final NitroWebsocketClient client;
|
||||||
private Session activeSession = null;
|
private Session activeSession = null;
|
||||||
@ -24,18 +34,25 @@ public class NitroWebsocketServer extends Endpoint implements NitroSession {
|
|||||||
this.packetHandler = new NitroPacketHandler(HMessage.Direction.TOCLIENT, client, connection.getExtensionHandler(), connection.getTrafficObservables());
|
this.packetHandler = new NitroPacketHandler(HMessage.Direction.TOCLIENT, client, connection.getExtensionHandler(), connection.getTrafficObservables());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void connect(String websocketUrl, String originUrl) throws IOException {
|
public void connect(String websocketUrl, Map<String, List<String>> clientHeaders) throws IOException {
|
||||||
try {
|
try {
|
||||||
ClientEndpointConfig.Builder builder = ClientEndpointConfig.Builder.create();
|
ClientEndpointConfig.Builder builder = ClientEndpointConfig.Builder.create();
|
||||||
|
|
||||||
if (originUrl != null) {
|
builder.extensions(Collections.singletonList(new JsrExtension(new ExtensionConfig("permessage-deflate;client_max_window_bits"))));
|
||||||
|
|
||||||
builder.configurator(new ClientEndpointConfig.Configurator() {
|
builder.configurator(new ClientEndpointConfig.Configurator() {
|
||||||
@Override
|
@Override
|
||||||
public void beforeRequest(Map<String, List<String>> headers) {
|
public void beforeRequest(Map<String, List<String>> headers) {
|
||||||
headers.put("Origin", Collections.singletonList(originUrl));
|
clientHeaders.forEach((key, value) -> {
|
||||||
|
if (SKIP_HEADERS.contains(key)) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
headers.remove(key);
|
||||||
|
headers.put(key, value);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
ClientEndpointConfig config = builder.build();
|
ClientEndpointConfig config = builder.build();
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user