Finished up WebServer-Protocol

This commit is contained in:
Finn
2025-12-12 18:19:56 +01:00
parent 4108b04582
commit e37a76af56
9 changed files with 213 additions and 563 deletions

View File

@@ -6,7 +6,7 @@
<groupId>org.openautonomousconnection</groupId>
<artifactId>protocol</artifactId>
<version>1.0.0-BETA.1</version>
<version>1.0.0-BETA.2</version>
<organization>
<name>Open Autonomous Connection</name>
<url>https://open-autonomous-connection.org/</url>

View File

@@ -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);
}
/**

View File

@@ -41,7 +41,7 @@ public final class INSResponsePacket extends OACPacket {
* @param bridge Protocol runtime context.
*/
public INSResponsePacket(INSResponseStatus status, List<INSRecord> 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;
}

View File

@@ -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);
}
/**

View File

@@ -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<String,String> headers;
private byte[] body;
public WebRequestPacket() {
super(8, ProtocolVersion.PV_1_0_0_BETA);
}
public WebRequestPacket(String path, WebRequestMethod method, Map<String, String> 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<String, String>) in.readObject();
int len = in.readInt();
if (len < 0) {
throw new IOException("Negative body length in WebRequestPacket");
}
this.body = in.readNBytes(len);
}
}

View File

@@ -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<String,String> headers;
private byte[] body;
public WebResponsePacket() {
super(9, ProtocolVersion.PV_1_0_0_BETA);
}
public WebResponsePacket(int statusCode, String contentType, Map<String, String> 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<String, String>) in.readObject();
int len = in.readInt();
if (len < 0) {
throw new IOException("Negative body length in WebResponsePacket");
}
this.body = in.readNBytes(len);
}
}

View File

@@ -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<String, String> 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<String, String> 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<String, String> 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<String, String> parseQueryParams(String rawPath) {
// Extract query parameters from the URL path
Map<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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.
*/
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();
}
}
public boolean isConnected() {
return this.webSocket != null && this.webSocket.isConnected() && !this.webSocket.isClosed() && this.receiveThread.isAlive() && pipelineConnection.isConnected();
return webSocket != null &&
webSocket.isConnected() &&
!webSocket.isClosed() &&
pipelineConnection.isConnected();
}
/**
* 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
pipelineConnection.disconnect();
// Interrupt the receive thread if it's still alive
if (this.receiveThread.isAlive()) {
this.receiveThread.interrupt();
}
public synchronized void disconnect() {
try {
server.onDisconnect(this);
clientVersionLoaded = false;
clientVersion = null;
} catch (Exception ignored) {}
try {
// Close streams and the socket
this.outputStream.close();
this.inputStream.close();
this.webSocket.close();
} catch (IOException var2) {
}
pipelineConnection.disconnect();
} catch (Exception ignored) {}
// Nullify references
this.webSocket = null;
this.outputStream = null;
this.inputStream = null;
try {
if (webSocket != null) webSocket.close();
} catch (IOException ignored) {}
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<String, String> 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<String, String> 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<String, String> 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<String, String> cookies;
public PHPResponse(String body, Map<String, String> cookies) {
this.body = body;
this.cookies = cookies;
}
}
}

View File

@@ -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);
}

View File

@@ -0,0 +1,5 @@
package org.openautonomousconnection.protocol.versions.v1_0_0.beta;
public enum WebRequestMethod {
GET, POST, EXECUTE, SCRIPT
}