diff --git a/pom.xml b/pom.xml
index c303e26..9f5395b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
org.openautonomousconnection
protocol
- 1.0.0-BETA.1
+ 1.0.0-BETA.2
Open Autonomous Connection
https://open-autonomous-connection.org/
diff --git a/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/INSQueryPacket.java b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/INSQueryPacket.java
index 013cd5d..77b27f1 100644
--- a/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/INSQueryPacket.java
+++ b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/INSQueryPacket.java
@@ -39,7 +39,7 @@ public final class INSQueryPacket extends OACPacket {
* @param clientId Sender client ID for routing.
*/
public INSQueryPacket(String tln, String name, String sub, INSRecordType type, int clientId) {
- super(6, ProtocolVersion.PV_1_0_0_BETA);
+ super(5, ProtocolVersion.PV_1_0_0_BETA);
this.tln = tln;
this.name = name;
this.sub = sub;
@@ -51,7 +51,7 @@ public final class INSQueryPacket extends OACPacket {
* Registration constructor
*/
public INSQueryPacket() {
- super(6, ProtocolVersion.PV_1_0_0_BETA);
+ super(5, ProtocolVersion.PV_1_0_0_BETA);
}
/**
diff --git a/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/INSResponsePacket.java b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/INSResponsePacket.java
index 7a410ef..c9f0f68 100644
--- a/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/INSResponsePacket.java
+++ b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/INSResponsePacket.java
@@ -41,7 +41,7 @@ public final class INSResponsePacket extends OACPacket {
* @param bridge Protocol runtime context.
*/
public INSResponsePacket(INSResponseStatus status, List records, int clientId, ProtocolBridge bridge) {
- super(7, ProtocolVersion.PV_1_0_0_BETA);
+ super(6, ProtocolVersion.PV_1_0_0_BETA);
this.status = status;
this.records = records;
this.clientId = clientId;
@@ -54,7 +54,7 @@ public final class INSResponsePacket extends OACPacket {
* @param bridge Protocol runtime context.
*/
public INSResponsePacket(ProtocolBridge bridge) {
- super(7, ProtocolVersion.PV_1_0_0_BETA);
+ super(6, ProtocolVersion.PV_1_0_0_BETA);
this.bridge = bridge;
}
diff --git a/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/UnsupportedClassicPacket.java b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/UnsupportedClassicPacket.java
index 3ce71da..2a1370e 100644
--- a/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/UnsupportedClassicPacket.java
+++ b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/UnsupportedClassicPacket.java
@@ -45,7 +45,7 @@ public final class UnsupportedClassicPacket extends OACPacket {
* Registration Constructor
*/
public UnsupportedClassicPacket() {
- super(5, ProtocolVersion.PV_1_0_0_BETA);
+ super(7, ProtocolVersion.PV_1_0_0_BETA);
}
/**
diff --git a/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/WebRequestPacket.java b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/WebRequestPacket.java
new file mode 100644
index 0000000..e4b6def
--- /dev/null
+++ b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/WebRequestPacket.java
@@ -0,0 +1,56 @@
+package org.openautonomousconnection.protocol.packets.v1_0_0.beta;
+
+import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
+import org.openautonomousconnection.protocol.packets.OACPacket;
+import org.openautonomousconnection.protocol.versions.ProtocolVersion;
+import org.openautonomousconnection.protocol.versions.v1_0_0.beta.WebRequestMethod;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.Map;
+
+public final class WebRequestPacket extends OACPacket {
+
+ private String path;
+ private WebRequestMethod method;
+ private Map headers;
+ private byte[] body;
+
+ public WebRequestPacket() {
+ super(8, ProtocolVersion.PV_1_0_0_BETA);
+ }
+
+ public WebRequestPacket(String path, WebRequestMethod method, Map headers, byte[] body) {
+ super(8, ProtocolVersion.PV_1_0_0_BETA);
+ this.path = path;
+ this.method = method;
+ this.headers = headers;
+ this.body = (body != null) ? body : new byte[0];
+ }
+
+ @Override
+ public void onWrite(PacketHandler handler, ObjectOutputStream out) throws IOException {
+ out.writeUTF(path != null ? path : "/");
+ out.writeUTF(method != null ? method.name() : WebRequestMethod.GET.name());
+ out.writeObject(headers);
+
+ if (body == null) body = new byte[0];
+ out.writeInt(body.length);
+ out.write(body);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void onRead(PacketHandler handler, ObjectInputStream in) throws IOException, ClassNotFoundException {
+ this.path = in.readUTF();
+ this.method = WebRequestMethod.valueOf(in.readUTF());
+ this.headers = (Map) in.readObject();
+
+ int len = in.readInt();
+ if (len < 0) {
+ throw new IOException("Negative body length in WebRequestPacket");
+ }
+ this.body = in.readNBytes(len);
+ }
+}
diff --git a/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/WebResponsePacket.java b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/WebResponsePacket.java
new file mode 100644
index 0000000..37e64ce
--- /dev/null
+++ b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/WebResponsePacket.java
@@ -0,0 +1,54 @@
+package org.openautonomousconnection.protocol.packets.v1_0_0.beta;
+
+import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
+import org.openautonomousconnection.protocol.packets.OACPacket;
+import org.openautonomousconnection.protocol.versions.ProtocolVersion;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.Map;
+
+public final class WebResponsePacket extends OACPacket {
+
+ private int statusCode; // 200, 404, 500 ...
+ private String contentType; // text/ohtml, text/plain, application/json, text/py
+ private Map headers;
+ private byte[] body;
+
+ public WebResponsePacket() {
+ super(9, ProtocolVersion.PV_1_0_0_BETA);
+ }
+
+ public WebResponsePacket(int statusCode, String contentType, Map headers, byte[] body) {
+ super(9, ProtocolVersion.PV_1_0_0_BETA);
+ this.statusCode = statusCode;
+ this.contentType = contentType;
+ this.headers = headers;
+ this.body = (body != null) ? body : new byte[0];
+ }
+
+ @Override
+ public void onWrite(PacketHandler handler, ObjectOutputStream out) throws IOException {
+ out.writeInt(statusCode);
+ out.writeUTF(contentType != null ? contentType : "text/plain");
+ out.writeObject(headers);
+
+ if (body == null) body = new byte[0];
+ out.writeInt(body.length);
+ out.write(body);
+ }
+
+ @Override
+ public void onRead(PacketHandler handler, ObjectInputStream in) throws IOException, ClassNotFoundException {
+ this.statusCode = in.readInt();
+ this.contentType = in.readUTF();
+ this.headers = (Map) in.readObject();
+
+ int len = in.readInt();
+ if (len < 0) {
+ throw new IOException("Negative body length in WebResponsePacket");
+ }
+ this.body = in.readNBytes(len);
+ }
+}
diff --git a/src/main/java/org/openautonomousconnection/protocol/side/web/ConnectedWebClient.java b/src/main/java/org/openautonomousconnection/protocol/side/web/ConnectedWebClient.java
index 48a36a9..87839c9 100644
--- a/src/main/java/org/openautonomousconnection/protocol/side/web/ConnectedWebClient.java
+++ b/src/main/java/org/openautonomousconnection/protocol/side/web/ConnectedWebClient.java
@@ -1,50 +1,32 @@
package org.openautonomousconnection.protocol.side.web;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.ConnectionHandler;
+import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
import lombok.Getter;
-import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
import org.openautonomousconnection.protocol.packets.OACPacket;
-import org.openautonomousconnection.protocol.side.web.managers.AuthManager;
-import org.openautonomousconnection.protocol.side.web.managers.RuleManager;
-import org.openautonomousconnection.protocol.side.web.managers.SessionManager;
+import org.openautonomousconnection.protocol.packets.v1_0_0.beta.WebRequestPacket;
+import org.openautonomousconnection.protocol.packets.v1_0_0.beta.WebResponsePacket;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import javax.net.ssl.SSLSocket;
-import java.io.*;
-import java.net.URLDecoder;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.util.HashMap;
-import java.util.Map;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
/**
- * Represents a connected web client.
- * Manages the connection, handles HTTP requests, and serves files.
+ * A connected web client using pure OAC packets.
+ * No HTTP, no GET/POST, no header parsing.
*/
-@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.WEB)
public final class ConnectedWebClient {
- /**
- * The connection handler associated with this web client.
- */
@Getter
private final ConnectionHandler pipelineConnection;
- /**
- * The SSL socket for the web client connection.
- */
@Getter
private SSLSocket webSocket;
- /**
- * The output stream for sending data to the client.
- */
- private ObjectOutputStream outputStream;
-
- /**
- * The input stream for receiving data from the client.
- */
- private ObjectInputStream inputStream;
+ @Getter
+ private ProtocolWebServer server;
/**
* The protocol version of the connected client.
@@ -55,418 +37,76 @@ public final class ConnectedWebClient {
*/
@Getter
private boolean clientVersionLoaded = false;
- /**
- * The reference to the ProtocolWebServer Object
- */
- @Getter
- private ProtocolWebServer protocolWebServer;
- /**
- * Constructs a ConnectedWebClient with the given connection handler.
- *
- * @param pipelineConnection The connection handler for the web client.
- */
+ private Thread receiveThread;
+
public ConnectedWebClient(ConnectionHandler pipelineConnection) {
this.pipelineConnection = pipelineConnection;
}
- /**
- * Sends an HTTP redirect response to the client.
- *
- * @param out The output stream to send the response to.
- * @param location The URL to redirect to.
- * @param cookies Optional cookies to set in the response.
- * @throws IOException If an I/O error occurs.
- */
- private static void sendRedirect(OutputStream out, String location, Map cookies) throws IOException {
- // Send HTTP 302 Found response with Location header
- out.write(("OAC 302 Found\r\n").getBytes());
- out.write(("Location: " + location + "\r\n").getBytes());
+ public void attachWebServer(SSLSocket socket, ProtocolWebServer server) {
+ if (this.webSocket != null) return;
- // Set cookies if provided
- if (cookies != null) {
- for (var entry : cookies.entrySet()) {
- out.write((entry.getKey() + ": " + entry.getValue() + "\r\n").getBytes());
- }
- }
+ this.webSocket = socket;
+ this.server = server;
- // End of headers
- out.write("\r\n".getBytes());
- out.flush();
- } /**
- * Thread for receiving data from the client.
- */
- private final Thread receiveThread = new Thread(this::receive);
-
- /**
- * Parses POST parameters from the input stream.
- *
- * @param in The input stream to read from.
- * @return A map of POST parameter names to values.
- * @throws IOException If an I/O error occurs.
- */
- private static Map parsePostParams(InputStream in) throws IOException {
- // Read the entire input stream into a string
- BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
- StringBuilder sb = new StringBuilder();
- while (reader.ready()) {
- sb.append((char) reader.read());
- }
-
- // Split the string into key-value pairs and decode them
- Map map = new HashMap<>();
- String[] pairs = sb.toString().split("&");
- for (String p : pairs) {
- // Split each pair into key and value
- String[] kv = p.split("=", 2);
- if (kv.length == 2)
- // Decode and store in the map
- map.put(URLDecoder.decode(kv[0], StandardCharsets.UTF_8), URLDecoder.decode(kv[1], StandardCharsets.UTF_8));
- }
- return map;
- }
-
- /**
- * Normalizes a file path to prevent directory traversal attacks.
- *
- * @param path The raw file path.
- * @return The normalized file path.
- */
- private static String normalizePath(String path) {
- // Replace backslashes with forward slashes and remove ".." segments
- path = path.replace("/", File.separator).replace("\\", "/");
- // Remove any ".." segments to prevent directory traversal
- while (path.contains("..")) path = path.replace("..", "");
-
- // Remove leading slashes
- if (path.startsWith("/")) path = path.substring(1);
-
- return path;
- }
-
- /**
- * Parses query parameters from a raw URL path.
- *
- * @param rawPath The raw URL path containing query parameters.
- * @return A map of query parameter names to values.
- */
- private static Map parseQueryParams(String rawPath) {
- // Extract query parameters from the URL path
- Map map = new HashMap<>();
- if (rawPath.contains("?")) {
- // Split the query string into key-value pairs
- String[] params = rawPath.substring(rawPath.indexOf("?") + 1).split("&");
- for (String p : params) {
- // Split each pair into key and value
- String[] kv = p.split("=");
- if (kv.length == 2) map.put(kv[0], kv[1]);
- }
- }
- return map;
- }
-
- /**
- * Checks if the request is a multipart/form-data request.
- *
- * @param headers The HTTP headers of the request.
- * @return True if the request is multipart/form-data, false otherwise.
- */
- private static boolean isMultipart(Map headers) {
- String contentType = headers.get("content-type");
- return contentType != null && contentType.startsWith("multipart/form-data");
- }
-
- /**
- * Handles a multipart/form-data request, saving uploaded files to the specified directory.
- *
- * @param in The input stream to read the request body from.
- * @param headers The HTTP headers of the request.
- * @param uploadDir The directory to save uploaded files to.
- * @throws IOException If an I/O error occurs.
- */
- private static void handleMultipart(InputStream in, Map headers, File uploadDir) throws IOException {
- // Ensure the upload directory exists
- if (!uploadDir.exists()) uploadDir.mkdirs();
-
- // Extract the boundary from the Content-Type header
- String contentType = headers.get("content-type");
- String boundary = "--" + contentType.split("boundary=")[1];
-
- // Read the entire request body into a buffer
- ByteArrayOutputStream buffer = new ByteArrayOutputStream();
- byte[] lineBuffer = new byte[8192];
- int read;
- while ((read = in.read(lineBuffer)) != -1) {
- buffer.write(lineBuffer, 0, read);
- if (buffer.size() > 10 * 1024 * 1024) break; // 10 MB max
- }
-
- // Parse the multipart data
- String data = buffer.toString(StandardCharsets.UTF_8);
- String[] parts = data.split(boundary);
-
- // Process each part
- for (String part : parts) {
- if (part.contains("Content-Disposition")) {
- String name = null;
- String filename = null;
-
- // Extract headers from the part
- for (String headerLine : part.split("\r\n")) {
- if (headerLine.startsWith("Content-Disposition")) {
- if (headerLine.contains("filename=\"")) {
- int start = headerLine.indexOf("filename=\"") + 10;
- int end = headerLine.indexOf("\"", start);
- filename = headerLine.substring(start, end);
- }
- if (headerLine.contains("name=\"")) {
- int start = headerLine.indexOf("name=\"") + 6;
- int end = headerLine.indexOf("\"", start);
- name = headerLine.substring(start, end);
- }
- }
- }
-
- // Save the file if a filename is provided
- if (filename != null && !filename.isEmpty()) {
- int headerEnd = part.indexOf("\r\n\r\n");
- byte[] fileData = part.substring(headerEnd + 4).getBytes(StandardCharsets.UTF_8);
- File outFile = new File(uploadDir, filename);
- Files.write(outFile.toPath(), fileData);
- }
- }
- }
- }
-
- /**
- * Sends an response to the client.
- *
- * @param out
- * @param code
- * @param file
- * @param headers
- * @throws IOException
- */
- private static void sendResponse(OutputStream out, int code, File file, Map headers) throws IOException {
- byte[] body = Files.readAllBytes(file.toPath());
- sendResponse(out, code, body, "text/html", headers);
- }
-
- /**
- * Sends an response to the client.
- *
- * @param out The output stream to send the response to.
- * @param code The HTTP status code.
- * @param file The file to read the response body from.
- * @throws IOException If an I/O error occurs.
- */
- private static void sendResponse(OutputStream out, int code, File file) throws IOException {
- sendResponse(out, code, Files.readString(file.toPath()), "text/html");
- }
-
- /**
- * Sends an response to the client.
- *
- * @param out The output stream to send the response to.
- * @param code The HTTP status code.
- * @param body The response body as a string.
- * @param contentType The content type of the response.
- * @throws IOException If an I/O error occurs.
- */
- private static void sendResponse(OutputStream out, int code, String body, String contentType) throws IOException {
- sendResponse(out, code, body.getBytes(StandardCharsets.UTF_8), contentType, null);
- }
-
- /**
- * Sends an response to the client.
- *
- * @param out The output stream to send the response to.
- * @param code The HTTP status code.
- * @param file The file to read the response body from.
- * @param contentType The content type of the response.
- * @throws IOException If an I/O error occurs.
- */
- private static void sendResponse(OutputStream out, int code, File file, String contentType) throws IOException {
- byte[] bytes = Files.readAllBytes(file.toPath());
- sendResponse(out, code, bytes, contentType, null);
- }
-
- /**
- * Sends an response to the client.
- *
- * @param out The output stream to send the response to.
- * @param code The HTTP status code.
- * @param body The response body as a byte array.
- * @param contentType The content type of the response.
- * @param headers Additional headers to include in the response.
- * @throws IOException If an I/O error occurs.
- */
- private static void sendResponse(OutputStream out, int code, byte[] body, String contentType, Map headers) throws IOException {
- // Send response status line and headers
- out.write(("OAC " + code + " " + getStatusText(code) + "\r\n").getBytes());
- out.write(("Content-Type: " + contentType + "\r\n").getBytes());
- out.write(("Content-Length: " + body.length + "\r\n").getBytes());
-
- // Write additional headers if provided
- if (headers != null) headers.forEach((k, v) -> {
- try {
- out.write((k + ": " + v + "\r\n").getBytes());
- } catch (IOException ignored) {
- }
- });
-
- // End of headers
- out.write("\r\n".getBytes());
- out.write(body);
- out.flush();
- }
-
- /**
- * Returns the standard status text for a given status code.
- *
- * @param code The status code.
- * @return The corresponding status text.
- */
- private static String getStatusText(int code) {
- return switch (code) {
- case 200 -> "OK";
- case 301 -> "Moved Permanently";
- case 302 -> "Found";
- case 400 -> "Bad Request";
- case 401 -> "Unauthorized";
- case 403 -> "Forbidden";
- case 404 -> "Not Found";
- case 500 -> "Internal Server Error";
- default -> "Unknown";
- };
- }
-
- /**
- * Returns the content type based on the file extension.
- *
- * @param name The file name.
- * @return The corresponding content type.
- */
- private static String getContentType(String name) {
- return switch (name.substring(name.lastIndexOf('.') + 1).toLowerCase()) {
- case "html", "php" -> "text/html";
- case "js" -> "text/javascript";
- case "css" -> "text/css";
- case "json" -> "application/json";
- case "png" -> "image/png";
- case "jpg", "jpeg" -> "image/jpeg";
- case "mp4" -> "video/mp4";
- case "mp3" -> "audio/mpeg3";
- case "wav" -> "audio/wav";
- case "pdf" -> "application/pdf";
- default -> "text/plain";
- };
- }
-
- /**
- * Renders a PHP file by executing it with the PHP interpreter and captures cookies.
- *
- * @param file The PHP file to render.
- * @return A PHPResponse containing the output and cookies.
- * @throws IOException If an I/O error occurs.
- * @throws InterruptedException If the process is interrupted.
- */
- private static PHPResponse renderPHPWithCookies(File file) throws IOException, InterruptedException {
- // Execute the PHP file using the PHP interpreter
- ProcessBuilder pb = new ProcessBuilder("php", file.getAbsolutePath());
- pb.redirectErrorStream(true);
- Process p = pb.start();
-
- // Capture the output of the PHP process
- ByteArrayOutputStream output = new ByteArrayOutputStream();
- InputStream processIn = p.getInputStream();
- byte[] buf = new byte[8192];
- int read;
- while ((read = processIn.read(buf)) != -1) {
- output.write(buf, 0, read);
- }
- p.waitFor();
-
- // Parse the output to separate headers and body, and extract cookies
- String fullOutput = output.toString(StandardCharsets.UTF_8);
- Map cookies = new HashMap<>();
-
- // Split headers and body
- String[] parts = fullOutput.split("\r\n\r\n", 2);
- String body;
- if (parts.length == 2) {
- // Get headers and body
- String headers = parts[0];
- body = parts[1];
-
- // Extract cookies from headers
- for (String headerLine : headers.split("\r\n")) {
- if (headerLine.toLowerCase().startsWith("set-cookie:")) {
- String cookie = headerLine.substring("set-cookie:".length()).trim();
- String[] kv = cookie.split(";", 2);
- String[] pair = kv[0].split("=", 2);
- if (pair.length == 2) cookies.put(pair[0], pair[1]);
- }
- }
- } else {
- // No headers, only body
- body = fullOutput;
- }
-
- return new PHPResponse(body, cookies);
- }
-
- /**
- * Sets the SSL socket for the web client and starts the receive thread.
- *
- * @param webSocket The SSL socket to set.
- */
- public void setWebSocket(SSLSocket webSocket) {
- if (webSocket != null) this.webSocket = webSocket;
+ this.receiveThread = new Thread(this::receiveLoop, "OAC-WebClient-Receiver");
this.receiveThread.start();
}
- /**
- * Checks if the web client is currently connected.
- *
- * @return True if connected, false otherwise.
- */
- public boolean isConnected() {
- return this.webSocket != null && this.webSocket.isConnected() && !this.webSocket.isClosed() && this.receiveThread.isAlive() && pipelineConnection.isConnected();
+ private void receiveLoop() {
+ try {
+ ObjectInputStream in = new ObjectInputStream(webSocket.getInputStream());
+ ObjectOutputStream out = new ObjectOutputStream(webSocket.getOutputStream());
+
+ PacketHandler handler = server.getProtocolBridge().getProtocolSettings().packetHandler;
+
+ while (!webSocket.isClosed() && pipelineConnection.isConnected()) {
+
+ Object obj = in.readObject();
+ if (!(obj instanceof OACPacket packet)) continue;
+
+ if (packet instanceof WebRequestPacket requestPacket) {
+
+ WebResponsePacket response =
+ server.onWebRequest(this, requestPacket);
+
+ if (response != null) {
+ out.writeObject(response);
+ out.flush();
+ }
+ }
+ }
+
+ } catch (Exception ignored) {
+ } finally {
+ disconnect();
+ }
}
- /**
- * Disconnects the web client, closing streams and the socket.
- *
- * @return True if disconnection was successful, false if already disconnected.
- */
- public synchronized boolean disconnect() {
- if (!this.isConnected()) {
- return false;
- } else {
- // Disconnect the underlying connection handler
+ public boolean isConnected() {
+ return webSocket != null &&
+ webSocket.isConnected() &&
+ !webSocket.isClosed() &&
+ pipelineConnection.isConnected();
+ }
+
+ public synchronized void disconnect() {
+ try {
+ server.onDisconnect(this);
+ clientVersionLoaded = false;
+ clientVersion = null;
+ } catch (Exception ignored) {}
+
+ try {
pipelineConnection.disconnect();
+ } catch (Exception ignored) {}
- // Interrupt the receive thread if it's still alive
- if (this.receiveThread.isAlive()) {
- this.receiveThread.interrupt();
- }
+ try {
+ if (webSocket != null) webSocket.close();
+ } catch (IOException ignored) {}
- try {
- // Close streams and the socket
- this.outputStream.close();
- this.inputStream.close();
- this.webSocket.close();
- } catch (IOException var2) {
- }
-
- // Nullify references
- this.webSocket = null;
- this.outputStream = null;
- this.inputStream = null;
-
- return true;
- }
+ webSocket = null;
}
/**
@@ -484,8 +124,11 @@ public final class ConnectedWebClient {
* @param clientVersion The protocol version to set.
*/
public void setClientVersion(ProtocolVersion clientVersion) {
- if (!clientVersionLoaded) clientVersionLoaded = true;
- if (clientVersion == null) this.clientVersion = clientVersion;
+ if (clientVersionLoaded) return;
+ if (clientVersion == null) return;
+
+ this.clientVersion = clientVersion;
+ this.clientVersionLoaded = true;
}
/**
@@ -607,128 +250,4 @@ public final class ConnectedWebClient {
return getClientVersion().getSupportedProtocols().contains(protocol) || yes;
}
- /**
- * Receives and processes requests from the client.
- * Handles authentication, file serving, and PHP rendering.
- */
- private void receive() {
- try {
- while (this.isConnected()) {
- Object received = this.inputStream.readObject();
-
- try (InputStream in = webSocket.getInputStream(); OutputStream out = webSocket.getOutputStream()) {
- BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
- String line;
- String path = "/main.html";
- Map headers = new HashMap<>();
- while ((line = reader.readLine()) != null && !line.isEmpty()) {
- if (line.toLowerCase().startsWith("get") || line.toLowerCase().startsWith("post")) {
- path = line.split(" ")[1];
- }
- if (line.contains(":")) {
- String[] parts = line.split(":", 2);
- headers.put(parts[0].trim().toLowerCase(), parts[1].trim());
- }
- }
-
- path = URLDecoder.decode(path, StandardCharsets.UTF_8);
- path = normalizePath(path);
-
- File file = new File(protocolWebServer.getProtocolBridge().getProtocolWebServer().getContentFolder(), path);
-
- String sessionId = null;
- if (headers.containsKey("cookie")) {
- for (String cookie : headers.get("cookie").split(";")) {
- cookie = cookie.trim();
- if (cookie.startsWith("SESSIONID=")) {
- sessionId = cookie.substring("SESSIONID=".length());
- }
- }
- }
-
- if (!file.exists() || !file.isFile()) {
- sendResponse(out, 404, new File(protocolWebServer.getProtocolBridge().getProtocolWebServer().getErrorsFolder(), "404.html"));
- return;
- }
-
- String clientIp = webSocket.getInetAddress().getHostAddress();
- String userAgent = headers.getOrDefault("user-agent", null);
-
- boolean loggedIn = sessionId != null && SessionManager.isValid(sessionId, clientIp, userAgent, protocolWebServer);
-
- if (path.equals("/403-login") && headers.getOrDefault("content-type", "").startsWith("application/x-www-form-urlencoded")) {
- Map postParams = parsePostParams(in);
- String login = postParams.get("login");
- String password = postParams.get("password");
-
- if (AuthManager.checkAuth(login, password)) {
- String newSessionId = SessionManager.create(login, clientIp, userAgent, protocolWebServer);
- Map cookies = Map.of("Set-Cookie", "SESSIONID=" + newSessionId + "; HttpOnly; Path=/");
- sendRedirect(out, "/main.html", cookies);
- return;
- } else {
- sendRedirect(out, "/403.php", null);
- return;
- }
- }
-
- if (isMultipart(headers)) {
- handleMultipart(in, headers, new File(protocolWebServer.getProtocolBridge().getProtocolWebServer().getContentFolder(), "uploads"));
- }
-
- if (RuleManager.requiresAuth(path) && !loggedIn) {
- PHPResponse phpResp = renderPHPWithCookies(new File(protocolWebServer.getProtocolBridge().getProtocolWebServer().getContentFolder(), "403.php"));
- sendResponse(out, 200, phpResp.body.getBytes(StandardCharsets.UTF_8), "text/html", phpResp.cookies);
- return;
- }
-
-
- if (RuleManager.isDenied(path) && !RuleManager.isAllowed(path)) {
- sendResponse(out, 403, new File(protocolWebServer.getProtocolBridge().getProtocolWebServer().getErrorsFolder(), "403.php"));
- return;
- }
-
- if (path.endsWith(".php")) {
- PHPResponse phpResp = renderPHPWithCookies(file);
- sendResponse(out, 200, phpResp.body.getBytes(StandardCharsets.UTF_8), "text/html", phpResp.cookies);
- } else {
- sendResponse(out, 200, Files.readAllBytes(file.toPath()), getContentType(path), null);
- }
-
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- disconnect();
- }
- }
- } catch (Exception var2) {
- this.disconnect();
- }
- }
-
- /**
- * Set protocol bridge
- *
- * @param protocolWebServer The ProtocolWebServer object
- */
- public void setProtocolWebServer(ProtocolWebServer protocolWebServer) {
- if (this.protocolWebServer == null) this.protocolWebServer = protocolWebServer;
- }
-
- /**
- * Represents the response from a PHP script, including body and cookies.
- */
- private static class PHPResponse {
- String body;
- Map cookies;
-
- public PHPResponse(String body, Map cookies) {
- this.body = body;
- this.cookies = cookies;
- }
- }
-
-
-
-
}
diff --git a/src/main/java/org/openautonomousconnection/protocol/side/web/ProtocolWebServer.java b/src/main/java/org/openautonomousconnection/protocol/side/web/ProtocolWebServer.java
index b02df46..a50d39b 100644
--- a/src/main/java/org/openautonomousconnection/protocol/side/web/ProtocolWebServer.java
+++ b/src/main/java/org/openautonomousconnection/protocol/side/web/ProtocolWebServer.java
@@ -8,6 +8,8 @@ import dev.unlegitdqrk.unlegitlibrary.string.RandomString;
import lombok.Getter;
import org.openautonomousconnection.protocol.ProtocolBridge;
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
+import org.openautonomousconnection.protocol.packets.v1_0_0.beta.WebRequestPacket;
+import org.openautonomousconnection.protocol.packets.v1_0_0.beta.WebResponsePacket;
import org.openautonomousconnection.protocol.side.web.managers.AuthManager;
import org.openautonomousconnection.protocol.side.web.managers.RuleManager;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
@@ -26,7 +28,7 @@ import java.util.Random;
* Represents the web server for the protocol.
*/
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.WEB)
-public final class ProtocolWebServer {
+public abstract class ProtocolWebServer {
/**
* Folder for web content.
*/
@@ -193,7 +195,7 @@ public final class ProtocolWebServer {
* @param clientID The client ID to search for.
* @return The connected web client with the specified ID, or null if not found.
*/
- public ConnectedWebClient getClientByID(int clientID) {
+ public final ConnectedWebClient getClientByID(int clientID) {
for (ConnectedWebClient client : clients)
if (client.getPipelineConnection().getClientID() == clientID) return client;
return null;
@@ -204,7 +206,7 @@ public final class ProtocolWebServer {
*
* @throws Exception If an error occurs while starting the server.
*/
- public void startWebServer() throws Exception {
+ public final void startWebServer() throws Exception {
// Start the pipeline server
pipelineServer.start();
@@ -249,8 +251,7 @@ public final class ProtocolWebServer {
for (ConnectedWebClient connectedWebClient : clients) {
if (connectedWebClient.getPipelineConnection().getClientID() != -1 && connectedWebClient.isClientVersionLoaded()) {
// Assign socket to an existing connected client
- connectedWebClient.setWebSocket(client);
- connectedWebClient.setProtocolWebServer(this);
+ connectedWebClient.attachWebServer(client, this);
}
}
} catch (IOException e) {
@@ -260,6 +261,21 @@ public final class ProtocolWebServer {
}).start();
}
+ /**
+ * Optional callback when the connection closes.
+ */
+ public void onDisconnect(ConnectedWebClient client) {}
+
+ /**
+ * Called when the server receives a WebRequestPacket from the client.
+ *
+ * @param client The connected web client (pipeline + web socket).
+ * @param request The full decoded request packet.
+ * @return The response packet that should be sent back to the client.
+ */
+ public abstract WebResponsePacket onWebRequest(ConnectedWebClient client, WebRequestPacket request);
+
+
/**
* Handles the shutdown of the web server when the pipeline server stops.
*
@@ -308,7 +324,7 @@ public final class ProtocolWebServer {
* @return The configuration manager.
* @throws IOException If an I/O error occurs while loading or saving the configuration.
*/
- public ConfigurationManager getConfigurationManager() throws IOException {
+ public final ConfigurationManager getConfigurationManager() throws IOException {
return getConfigurationManager(configFile);
}
diff --git a/src/main/java/org/openautonomousconnection/protocol/versions/v1_0_0/beta/WebRequestMethod.java b/src/main/java/org/openautonomousconnection/protocol/versions/v1_0_0/beta/WebRequestMethod.java
new file mode 100644
index 0000000..131c5f3
--- /dev/null
+++ b/src/main/java/org/openautonomousconnection/protocol/versions/v1_0_0/beta/WebRequestMethod.java
@@ -0,0 +1,5 @@
+package org.openautonomousconnection.protocol.versions.v1_0_0.beta;
+
+public enum WebRequestMethod {
+ GET, POST, EXECUTE, SCRIPT
+}