Bug fixes (im frustrated)

This commit is contained in:
UnlegitDqrk
2026-02-08 21:40:23 +01:00
parent 68802e4317
commit e3fee17a73
7 changed files with 127 additions and 33 deletions

View File

@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>org.openautonomousconnection</groupId> <groupId>org.openautonomousconnection</groupId>
<artifactId>WebServer</artifactId> <artifactId>WebServer</artifactId>
<version>1.0.0-BETA.1.8</version> <version>1.0.0-BETA.1.9</version>
<description>The default DNS-Server</description> <description>The default DNS-Server</description>
<url>https://open-autonomous-connection.org/</url> <url>https://open-autonomous-connection.org/</url>
<issueManagement> <issueManagement>

View File

@@ -112,7 +112,7 @@
<dependency> <dependency>
<groupId>org.openautonomousconnection</groupId> <groupId>org.openautonomousconnection</groupId>
<artifactId>Protocol</artifactId> <artifactId>Protocol</artifactId>
<version>1.0.0-BETA.7.6</version> <version>1.0.0-BETA.7.7</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>

View File

@@ -3,17 +3,26 @@ package org.openautonomousconnection.webserver;
import dev.unlegitdqrk.unlegitlibrary.command.CommandExecutor; import dev.unlegitdqrk.unlegitlibrary.command.CommandExecutor;
import dev.unlegitdqrk.unlegitlibrary.command.CommandManager; import dev.unlegitdqrk.unlegitlibrary.command.CommandManager;
import dev.unlegitdqrk.unlegitlibrary.command.CommandPermission; import dev.unlegitdqrk.unlegitlibrary.command.CommandPermission;
import dev.unlegitdqrk.unlegitlibrary.event.EventListener;
import dev.unlegitdqrk.unlegitlibrary.event.EventManager; import dev.unlegitdqrk.unlegitlibrary.event.EventManager;
import dev.unlegitdqrk.unlegitlibrary.event.Listener;
import dev.unlegitdqrk.unlegitlibrary.file.ConfigurationManager; import dev.unlegitdqrk.unlegitlibrary.file.ConfigurationManager;
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.state.ClientConnectedEvent;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler; import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.NetworkServer;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.packets.S_PacketSendEvent;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.state.ServerStoppedEvent;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.ClientAuthMode; import dev.unlegitdqrk.unlegitlibrary.network.system.utils.ClientAuthMode;
import lombok.Getter; import lombok.Getter;
import org.openautonomousconnection.protocol.ProtocolBridge; import org.openautonomousconnection.protocol.ProtocolBridge;
import org.openautonomousconnection.protocol.ProtocolValues; import org.openautonomousconnection.protocol.ProtocolValues;
import org.openautonomousconnection.protocol.side.server.events.S_CustomClientConnectedEvent;
import org.openautonomousconnection.protocol.versions.ProtocolVersion; import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import org.openautonomousconnection.webserver.commands.StopCommand; import org.openautonomousconnection.webserver.commands.StopCommand;
import java.io.File; import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Scanner; import java.util.Scanner;
public class Main { public class Main {
@@ -35,6 +44,8 @@ public class Main {
values.packetHandler = new PacketHandler(); values.packetHandler = new PacketHandler();
values.eventManager = new EventManager(); values.eventManager = new EventManager();
if (!Files.exists(new File("config.properties").toPath())) Files.createFile(new File("config.properties").toPath());
ConfigurationManager config = new ConfigurationManager(new File("config.properties")); ConfigurationManager config = new ConfigurationManager(new File("config.properties"));
config.loadProperties(); config.loadProperties();
@@ -85,11 +96,12 @@ public class Main {
Scanner scanner = new Scanner(System.in); Scanner scanner = new Scanner(System.in);
while (true) { // while (protocolBridge.getProtocolServer().getNetwork().isServerOnline()) {
System.out.print(commandExecutor.getName() + "> "); // System.out.print(commandExecutor.getName() + "> ");
String line = scanner.nextLine(); // String line = scanner.nextLine();
commandManager.execute(commandExecutor, line); // commandManager.execute(commandExecutor, line);
} // }
//
// System.exit(0);
} }
} }

View File

@@ -1,5 +1,7 @@
package org.openautonomousconnection.webserver; package org.openautonomousconnection.webserver;
import dev.unlegitdqrk.unlegitlibrary.event.Listener;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.packets.S_PacketReadEvent;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol; import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
import lombok.Getter; import lombok.Getter;
import org.openautonomousconnection.protocol.annotations.ProtocolInfo; import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
@@ -14,19 +16,34 @@ import org.openautonomousconnection.protocol.side.web.managers.RuleManager;
import org.openautonomousconnection.protocol.versions.ProtocolVersion; import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import org.openautonomousconnection.webserver.api.SessionContext; import org.openautonomousconnection.webserver.api.SessionContext;
import org.openautonomousconnection.webserver.runtime.JavaPageDispatcher; import org.openautonomousconnection.webserver.runtime.JavaPageDispatcher;
import org.openautonomousconnection.webserver.utils.HeaderMaps;
import org.openautonomousconnection.webserver.utils.WebHasher; import org.openautonomousconnection.webserver.utils.WebHasher;
import java.io.*; import java.io.*;
import java.nio.file.Files; import java.nio.file.Files;
import java.util.Arrays; import java.util.Arrays;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* OAC WebServer implementation.
*/
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.WEB) @ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.WEB)
public final class WebServer extends ProtocolWebServer { public final class WebServer extends ProtocolWebServer {
private static final int STREAM_CHUNK_SIZE = 64 * 1024; private static final int STREAM_CHUNK_SIZE = 64 * 1024;
private static final long STREAM_THRESHOLD = 2L * 1024 * 1024; private static final long STREAM_THRESHOLD = 2L * 1024 * 1024;
/**
* Dedicated executor for streaming to avoid blocking network receive thread.
*/
private static final ExecutorService STREAM_EXECUTOR = Executors.newCachedThreadPool(r -> {
Thread t = new Thread(r, "oac-web-stream");
t.setDaemon(true);
return t;
});
@Getter @Getter
private final WebHasher hasher; private final WebHasher hasher;
@@ -38,28 +55,27 @@ public final class WebServer extends ProtocolWebServer {
) throws Exception { ) throws Exception {
super(authFile, rulesFile, sessionExpire, maxUpload); super(authFile, rulesFile, sessionExpire, maxUpload);
// NOTE: Values chosen as safe defaults.
// move them to Main and pass them in here (no hidden assumptions).
this.hasher = new WebHasher( this.hasher = new WebHasher(
120_000, // PBKDF2 iterations 120_000,
16, // salt bytes 16,
32 // key bytes 32
); );
} }
@Override @Override
public WebResponsePacket onWebRequest(CustomConnectedClient client, WebRequestPacket request) { public WebResponsePacket onWebRequest(CustomConnectedClient client, WebRequestPacket request) {
try { try {
String path = request.getPath() == null ? "/" : request.getPath(); String path = request.getPath() == null ? "/" : request.getPath();
if (RuleManager.isDenied(path)) { if (RuleManager.isDenied(path)) {
return new WebResponsePacket(403, "text/plain; charset=utf-8", Map.of(), "Forbidden".getBytes()); return new WebResponsePacket(403, "text/plain; charset=utf-8", HeaderMaps.mutable(), "Forbidden".getBytes());
} }
if (RuleManager.requiresAuth(path)) { if (RuleManager.requiresAuth(path)) {
SessionContext ctx = SessionContext.from(client, this, request.getHeaders()); SessionContext ctx = SessionContext.from(client, this, request.getHeaders());
if (!ctx.isValid()) { if (!ctx.isValid()) {
return new WebResponsePacket(401, "text/plain; charset=utf-8", Map.of(), "Authentication required".getBytes()); return new WebResponsePacket(401, "text/plain; charset=utf-8", HeaderMaps.mutable(), "Authentication required".getBytes());
} }
} }
@@ -69,8 +85,12 @@ public final class WebServer extends ProtocolWebServer {
return serveFile(client, path); return serveFile(client, path);
} catch (Exception e) { } catch (Exception e) {
return new WebResponsePacket(500, "text/plain; charset=utf-8", Map.of(), return new WebResponsePacket(
("Internal Error: " + e.getMessage()).getBytes()); 500,
"text/plain; charset=utf-8",
HeaderMaps.mutable(),
("Internal Error: " + e.getClass().getName() + ": " + e.getMessage()).getBytes()
);
} }
} }
@@ -81,34 +101,64 @@ public final class WebServer extends ProtocolWebServer {
File root = getContentFolder().getCanonicalFile(); File root = getContentFolder().getCanonicalFile();
File file = new File(root, path).getCanonicalFile(); File file = new File(root, path).getCanonicalFile();
if (!file.getPath().startsWith(root.getPath())) if (!file.getPath().startsWith(root.getPath())) {
return new WebResponsePacket(403, "text/plain; charset=utf-8", Map.of(), "Forbidden".getBytes()); return new WebResponsePacket(403, "text/plain; charset=utf-8", HeaderMaps.mutable(), "Forbidden".getBytes());
if (!file.exists() || !file.isFile()) }
return new WebResponsePacket(404, "text/plain; charset=utf-8", Map.of(), "Not found".getBytes()); if (!file.exists() || !file.isFile()) {
return new WebResponsePacket(404, "text/plain; charset=utf-8", HeaderMaps.mutable(), "Not found".getBytes());
}
String contentType = ContentTypeResolver.resolve(file.getName()); String contentType = ContentTypeResolver.resolve(file.getName());
long size = file.length(); long size = file.length();
if (size >= STREAM_THRESHOLD) { if (size >= STREAM_THRESHOLD) {
streamFile(client, file, contentType); startStreamingAsync(client, file, contentType);
return null;
// IMPORTANT: Never return null. If your client expects a normal response, this keeps the pipeline stable.
// The actual bytes are delivered via WebStream* packets.
return new WebResponsePacket(
202,
"text/plain; charset=utf-8",
HeaderMaps.mutable(Map.of("x-oac-stream", "1")),
"Streaming started".getBytes()
);
} }
byte[] data = Files.readAllBytes(file.toPath()); byte[] data = Files.readAllBytes(file.toPath());
return new WebResponsePacket(200, contentType, Map.of(), data); return new WebResponsePacket(200, contentType, HeaderMaps.mutable(), data);
}
private void startStreamingAsync(CustomConnectedClient client, File file, String contentType) {
STREAM_EXECUTOR.execute(() -> {
try {
streamFile(client, file, contentType);
} catch (Exception e) {
// Never let streaming errors kill your server threads.
try {
client.getConnection().sendPacket(new WebStreamEndPacket(false), TransportProtocol.TCP);
} catch (Exception ignored) {
// ignore: client may already be gone
}
}
});
} }
private void streamFile(CustomConnectedClient client, File file, String contentType) throws IOException, ClassNotFoundException { private void streamFile(CustomConnectedClient client, File file, String contentType) throws IOException, ClassNotFoundException {
long total = file.length(); long total = file.length();
client.getConnection().sendPacket(new WebStreamStartPacket(200, contentType, Map.of("name", file.getName()), total), TransportProtocol.TCP); client.getConnection().sendPacket(
new WebStreamStartPacket(200, contentType, Map.of("name", file.getName()), total),
TransportProtocol.TCP
);
try (InputStream in = new BufferedInputStream(new FileInputStream(file))) { try (InputStream in = new BufferedInputStream(new FileInputStream(file))) {
byte[] buf = new byte[STREAM_CHUNK_SIZE]; byte[] buf = new byte[STREAM_CHUNK_SIZE];
int seq = 0; int seq = 0;
int r; int r;
while ((r = in.read(buf)) != -1) { while ((r = in.read(buf)) != -1) {
byte[] chunk = (r == buf.length) ? buf : Arrays.copyOf(buf, r); // Always copy: never hand out the reusable buffer reference.
byte[] chunk = Arrays.copyOf(buf, r);
client.getConnection().sendPacket( client.getConnection().sendPacket(
new WebStreamChunkPacket(seq++, chunk), new WebStreamChunkPacket(seq++, chunk),
ContentTypeResolver.isVideoFile(file.getName()) ? TransportProtocol.UDP : TransportProtocol.TCP ContentTypeResolver.isVideoFile(file.getName()) ? TransportProtocol.UDP : TransportProtocol.TCP
@@ -118,4 +168,4 @@ public final class WebServer extends ProtocolWebServer {
client.getConnection().sendPacket(new WebStreamEndPacket(true), TransportProtocol.TCP); client.getConnection().sendPacket(new WebStreamEndPacket(true), TransportProtocol.TCP);
} }
} }

View File

@@ -7,6 +7,7 @@ import org.openautonomousconnection.protocol.side.web.ProtocolWebServer;
import org.openautonomousconnection.webserver.WebServer; import org.openautonomousconnection.webserver.WebServer;
import org.openautonomousconnection.webserver.api.WebPage; import org.openautonomousconnection.webserver.api.WebPage;
import org.openautonomousconnection.webserver.api.WebPageContext; import org.openautonomousconnection.webserver.api.WebPageContext;
import org.openautonomousconnection.webserver.utils.HeaderMaps;
import org.openautonomousconnection.webserver.utils.RequestParams; import org.openautonomousconnection.webserver.utils.RequestParams;
import org.openautonomousconnection.webserver.utils.WebHasher; import org.openautonomousconnection.webserver.utils.WebHasher;
@@ -77,7 +78,7 @@ public final class JavaPageDispatcher {
return new WebResponsePacket( return new WebResponsePacket(
code, code,
"text/plain; charset=utf-8", "text/plain; charset=utf-8",
Map.of(), HeaderMaps.mutable(),
msg.getBytes(StandardCharsets.UTF_8) msg.getBytes(StandardCharsets.UTF_8)
); );
} }

View File

@@ -0,0 +1,32 @@
package org.openautonomousconnection.webserver.utils;
import java.util.HashMap;
import java.util.Map;
/**
* Small helper utilities for mutable header maps.
*/
public final class HeaderMaps {
private HeaderMaps() {
}
/**
* Creates an empty mutable header map.
*
* @return mutable map
*/
public static Map<String, String> mutable() {
return new HashMap<>();
}
/**
* Creates a mutable header map initialized with the given entries.
*
* @param initial initial entries (may be null)
* @return mutable map
*/
public static Map<String, String> mutable(Map<String, String> initial) {
return (initial == null || initial.isEmpty()) ? new HashMap<>() : new HashMap<>(initial);
}
}

View File

@@ -43,7 +43,7 @@ public final class HttpsProxy {
return new WebResponsePacket( return new WebResponsePacket(
502, 502,
"text/plain; charset=utf-8", "text/plain; charset=utf-8",
Map.of(), HeaderMaps.mutable(),
("Bad Gateway: " + e.getClass().getName() + ": " + e.getMessage()).getBytes(StandardCharsets.UTF_8) ("Bad Gateway: " + e.getClass().getName() + ": " + e.getMessage()).getBytes(StandardCharsets.UTF_8)
); );
} }
@@ -54,7 +54,7 @@ public final class HttpsProxy {
return new WebResponsePacket( return new WebResponsePacket(
508, 508,
"text/plain; charset=utf-8", "text/plain; charset=utf-8",
Map.of(), HeaderMaps.mutable(),
"Too many redirects".getBytes(StandardCharsets.UTF_8) "Too many redirects".getBytes(StandardCharsets.UTF_8)
); );
} }
@@ -82,7 +82,7 @@ public final class HttpsProxy {
return new WebResponsePacket( return new WebResponsePacket(
502, 502,
"text/plain; charset=utf-8", "text/plain; charset=utf-8",
Map.of(), HeaderMaps.mutable(),
("Bad Gateway: redirect without Location (code=" + code + ")").getBytes(StandardCharsets.UTF_8) ("Bad Gateway: redirect without Location (code=" + code + ")").getBytes(StandardCharsets.UTF_8)
); );
} }
@@ -104,8 +104,7 @@ public final class HttpsProxy {
con.disconnect(); con.disconnect();
} }
Map<String, String> headers = new HashMap<>(); return new WebResponsePacket(code, contentType, HeaderMaps.mutable(), body);
return new WebResponsePacket(code, contentType, headers, body);
} }
private static byte[] readAllBytes(InputStream in) throws Exception { private static byte[] readAllBytes(InputStream in) throws Exception {