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:
+ *
+ * - Does not rewrite HTML/CSS URLs. If you need full offline/proxied subresources,
+ * implement URL rewriting and route subresource paths through this proxy as well.
+ *
+ */
+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();
+ }
+}