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 +}