From a671a3f5fd8ad6d7a637c3fd1ed76ed55e199591 Mon Sep 17 00:00:00 2001 From: Finn Date: Sun, 18 Jan 2026 23:06:39 +0100 Subject: [PATCH] Added HttpsProxy for resolving Websites from the WWW --- pom.xml | 2 +- .../webserver/api/HttpsProxy.java | 121 ++++++++++++++++++ 2 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/openautonomousconnection/webserver/api/HttpsProxy.java diff --git a/pom.xml b/pom.xml index 32a9c1f..89b9667 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.openautonomousconnection WebServer - 1.0.0-BETA.1.3 + 1.0.0-BETA.1.4 Open Autonomous Connection https://open-autonomous-connection.org/ diff --git a/src/main/java/org/openautonomousconnection/webserver/api/HttpsProxy.java b/src/main/java/org/openautonomousconnection/webserver/api/HttpsProxy.java new file mode 100644 index 0000000..02f4485 --- /dev/null +++ b/src/main/java/org/openautonomousconnection/webserver/api/HttpsProxy.java @@ -0,0 +1,121 @@ +package org.openautonomousconnection.webserver.api; + +import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebResponsePacket; +import org.openautonomousconnection.webserver.api.WebPageContext; + +import javax.net.ssl.HttpsURLConnection; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +/** + * Simple HTTPS -> OAC proxy helper. + * + *

Limitations: + *

+ */ +public final class HttpsProxy { + + private static final int CONNECT_TIMEOUT_MS = 10_000; + private static final int READ_TIMEOUT_MS = 25_000; + private static final int MAX_REDIRECTS = 8; + + private HttpsProxy() { + } + + /** + * Fetches an HTTPS URL and returns it as a WebResponsePacket. + * + * @param ctx the current web page context (for optional user-agent forwarding) + * @param url the target HTTPS URL + * @return proxied response + */ + public static WebResponsePacket proxyGet(WebPageContext ctx, String url) { + try { + return proxyGetInternal(ctx, url, 0); + } catch (Exception e) { + return new WebResponsePacket( + 502, + "text/plain; charset=utf-8", + Map.of(), + ("Bad Gateway: " + e.getClass().getName() + ": " + e.getMessage()).getBytes(StandardCharsets.UTF_8) + ); + } + } + + private static WebResponsePacket proxyGetInternal(WebPageContext ctx, String url, int depth) throws Exception { + if (depth > MAX_REDIRECTS) { + return new WebResponsePacket( + 508, + "text/plain; charset=utf-8", + Map.of(), + "Too many redirects".getBytes(StandardCharsets.UTF_8) + ); + } + + URL target = new URL(url); + HttpsURLConnection con = (HttpsURLConnection) target.openConnection(); + con.setInstanceFollowRedirects(false); + con.setRequestMethod("GET"); + con.setConnectTimeout(CONNECT_TIMEOUT_MS); + con.setReadTimeout(READ_TIMEOUT_MS); + + // Forward a user-agent if you have one (optional). + String ua = null; + if (ctx != null && ctx.request != null && ctx.request.getHeaders() != null) { + ua = ctx.request.getHeaders().get("user-agent"); + } + con.setRequestProperty("User-Agent", ua != null ? ua : "OAC-HttpsProxy/1.0"); + + int code = con.getResponseCode(); + + // Handle redirects manually to preserve content and avoid silent issues + if (code == 301 || code == 302 || code == 303 || code == 307 || code == 308) { + String location = con.getHeaderField("Location"); + if (location == null || location.isBlank()) { + return new WebResponsePacket( + 502, + "text/plain; charset=utf-8", + Map.of(), + ("Bad Gateway: redirect without Location (code=" + code + ")").getBytes(StandardCharsets.UTF_8) + ); + } + // Resolve relative redirects + URL resolved = new URL(target, location); + con.disconnect(); + return proxyGetInternal(ctx, resolved.toString(), depth + 1); + } + + String contentType = con.getContentType(); + if (contentType == null || contentType.isBlank()) { + contentType = "application/octet-stream"; + } + + byte[] body; + try (InputStream in = (code >= 400 ? con.getErrorStream() : con.getInputStream())) { + body = readAllBytes(in); + } finally { + con.disconnect(); + } + + Map headers = new HashMap<>(); + return new WebResponsePacket(code, contentType, headers, body); + } + + private static byte[] readAllBytes(InputStream in) throws Exception { + if (in == null) return new byte[0]; + ByteArrayOutputStream out = new ByteArrayOutputStream(Math.max(1024, in.available())); + byte[] buf = new byte[32 * 1024]; + int r; + while ((r = in.read(buf)) != -1) { + out.write(buf, 0, r); + } + return out.toByteArray(); + } +}