Refactored using IntelliJ

This commit is contained in:
UnlegitDqrk
2026-02-06 18:03:32 +01:00
parent 6c513c9436
commit 8829737d30
16 changed files with 880 additions and 823 deletions

View File

@@ -1,7 +1,5 @@
package org.openautonomousconnection.webserver;
import jdk.dynalink.linker.LinkerServices;
import java.util.Map;
public final class ContentTypeResolver {

View File

@@ -17,7 +17,8 @@ import java.util.Scanner;
public class Main {
public static final CommandPermission PERMISSION_ALL = new CommandPermission("all", 1);
private static final CommandExecutor commandExecutor = new CommandExecutor("WebServer", PERMISSION_ALL) {};
private static final CommandExecutor commandExecutor = new CommandExecutor("WebServer", PERMISSION_ALL) {
};
@Getter
private static CommandManager commandManager;

View File

@@ -18,7 +18,8 @@ import org.openautonomousconnection.webserver.utils.WebHasher;
import java.io.*;
import java.nio.file.Files;
import java.util.*;
import java.util.Arrays;
import java.util.Map;
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.WEB)
public final class WebServer extends ProtocolWebServer {
@@ -35,7 +36,7 @@ public final class WebServer extends ProtocolWebServer {
int sessionExpire,
int maxUpload
) 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).
@@ -80,8 +81,10 @@ public final class WebServer extends ProtocolWebServer {
File root = getContentFolder().getCanonicalFile();
File file = new File(root, path).getCanonicalFile();
if (!file.getPath().startsWith(root.getPath())) return new WebResponsePacket(403, "text/plain; charset=utf-8", Map.of(), "Forbidden".getBytes());
if (!file.exists() || !file.isFile()) return new WebResponsePacket(404, "text/plain; charset=utf-8", Map.of(), "Not found".getBytes());
if (!file.getPath().startsWith(root.getPath()))
return new WebResponsePacket(403, "text/plain; charset=utf-8", Map.of(), "Forbidden".getBytes());
if (!file.exists() || !file.isFile())
return new WebResponsePacket(404, "text/plain; charset=utf-8", Map.of(), "Not found".getBytes());
String contentType = ContentTypeResolver.resolve(file.getName());
long size = file.length();

View File

@@ -1,6 +1,9 @@
package org.openautonomousconnection.webserver.api;
import java.lang.annotation.*;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Declares a route path for a server-side Java WebPage.

View File

@@ -41,7 +41,15 @@ public final class SessionContext {
return new SessionContext(sessionId, user, true);
}
public boolean isValid() { return valid; }
public String getSessionId() { return sessionId; }
public String getUser() { return user; }
public boolean isValid() {
return valid;
}
public String getSessionId() {
return sessionId;
}
public String getUser() {
return user;
}
}

View File

@@ -3,8 +3,8 @@ package org.openautonomousconnection.webserver.api;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebRequestPacket;
import org.openautonomousconnection.protocol.side.server.CustomConnectedClient;
import org.openautonomousconnection.protocol.side.web.ProtocolWebServer;
import org.openautonomousconnection.webserver.utils.WebHasher;
import org.openautonomousconnection.webserver.utils.RequestParams;
import org.openautonomousconnection.webserver.utils.WebHasher;
/**
* Context passed to Java WebPages (client, request, session, params, hasher).

View File

@@ -8,16 +8,6 @@ import java.util.concurrent.ConcurrentHashMap;
*/
public final class JavaPageCache {
private static final class Entry {
final long lastModified;
final Class<?> clazz;
Entry(long lastModified, Class<?> clazz) {
this.lastModified = lastModified;
this.clazz = clazz;
}
}
private final ConcurrentHashMap<String, Entry> cache = new ConcurrentHashMap<>();
public Class<?> getOrCompile(File javaFile) throws Exception {
@@ -33,4 +23,7 @@ public final class JavaPageCache {
cache.put(key, new Entry(lm, compiled));
return compiled;
}
private record Entry(long lastModified, Class<?> clazz) {
}
}

View File

@@ -1,6 +1,9 @@
package org.openautonomousconnection.webserver.runtime;
import javax.tools.*;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
@@ -16,7 +19,8 @@ import java.util.List;
*/
public final class JavaPageCompiler {
private JavaPageCompiler() {}
private JavaPageCompiler() {
}
public static Class<?> compile(File javaFile) throws Exception {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
@@ -35,7 +39,7 @@ public final class JavaPageCompiler {
String fqcn = deriveFqcn(javaFile);
// Root must be the folder containing the package root (javaFile parent is fine if classes output there).
URLClassLoader cl = new URLClassLoader(new URL[]{ javaFile.getParentFile().toURI().toURL() });
URLClassLoader cl = new URLClassLoader(new URL[]{javaFile.getParentFile().toURI().toURL()});
return cl.loadClass(fqcn);
}

View File

@@ -7,8 +7,8 @@ import org.openautonomousconnection.protocol.side.web.ProtocolWebServer;
import org.openautonomousconnection.webserver.WebServer;
import org.openautonomousconnection.webserver.api.WebPage;
import org.openautonomousconnection.webserver.api.WebPageContext;
import org.openautonomousconnection.webserver.utils.WebHasher;
import org.openautonomousconnection.webserver.utils.RequestParams;
import org.openautonomousconnection.webserver.utils.WebHasher;
import java.io.File;
import java.nio.charset.StandardCharsets;
@@ -21,7 +21,8 @@ public final class JavaPageDispatcher {
private static final JavaRouteRegistry ROUTES = new JavaRouteRegistry();
private JavaPageDispatcher() {}
private JavaPageDispatcher() {
}
public static WebResponsePacket dispatch(
CustomConnectedClient client,

View File

@@ -4,7 +4,10 @@ import org.openautonomousconnection.webserver.api.Route;
import org.openautonomousconnection.webserver.api.WebPage;
import java.io.File;
import java.util.*;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
/**
@@ -18,24 +21,50 @@ import java.util.concurrent.ConcurrentHashMap;
*/
public final class JavaRouteRegistry {
private static final class RouteEntry {
final File sourceFile;
final long lastModified;
final String fqcn;
final boolean routable;
RouteEntry(File sourceFile, long lastModified, String fqcn, boolean routable) {
this.sourceFile = sourceFile;
this.lastModified = lastModified;
this.fqcn = fqcn;
this.routable = routable;
}
}
private final JavaPageCache cache = new JavaPageCache();
private final ConcurrentHashMap<String, RouteEntry> routes = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, Long> scanState = new ConcurrentHashMap<>();
private static String normalizeRoute(String value) {
if (value == null) return "/";
String v = value.trim();
if (v.isEmpty()) return "/";
if (!v.startsWith("/")) v = "/" + v;
return v;
}
private static List<File> listJavaFiles(File root) {
ArrayList<File> out = new ArrayList<>();
Deque<File> stack = new ArrayDeque<>();
stack.push(root);
while (!stack.isEmpty()) {
File cur = stack.pop();
File[] children = cur.listFiles();
if (children == null) continue;
for (File c : children) {
if (c.isDirectory()) {
stack.push(c);
} else if (c.isFile() && c.getName().endsWith(".java")) {
out.add(c);
}
}
}
return out;
}
private static long folderLastModified(File folder) {
long lm = folder.lastModified();
File[] children = folder.listFiles();
if (children == null) return lm;
for (File c : children) {
lm = Math.max(lm, c.isDirectory() ? folderLastModified(c) : c.lastModified());
}
return lm;
}
/**
* Refreshes registry when content folder changed.
*
@@ -88,52 +117,16 @@ public final class JavaRouteRegistry {
}
}
private static String normalizeRoute(String value) {
if (value == null) return "/";
String v = value.trim();
if (v.isEmpty()) return "/";
if (!v.startsWith("/")) v = "/" + v;
return v;
}
private static List<File> listJavaFiles(File root) {
ArrayList<File> out = new ArrayList<>();
Deque<File> stack = new ArrayDeque<>();
stack.push(root);
while (!stack.isEmpty()) {
File cur = stack.pop();
File[] children = cur.listFiles();
if (children == null) continue;
for (File c : children) {
if (c.isDirectory()) {
stack.push(c);
} else if (c.isFile() && c.getName().endsWith(".java")) {
out.add(c);
}
}
}
return out;
}
private static long folderLastModified(File folder) {
long lm = folder.lastModified();
File[] children = folder.listFiles();
if (children == null) return lm;
for (File c : children) {
lm = Math.max(lm, c.isDirectory() ? folderLastModified(c) : c.lastModified());
}
return lm;
private record RouteEntry(File sourceFile, long lastModified, String fqcn, boolean routable) {
}
/**
* Lookup result for a route.
*
* @param sourceFile Java source file
* @param fqcn fully qualified class name
* @param routable true if @Route + implements WebPage
* @param fqcn fully qualified class name
* @param routable true if @Route + implements WebPage
*/
public record RouteLookupResult(File sourceFile, String fqcn, boolean routable) {}
public record RouteLookupResult(File sourceFile, String fqcn, boolean routable) {
}
}

View File

@@ -7,7 +7,8 @@ import java.nio.charset.StandardCharsets;
*/
public final class Html {
private Html() {}
private Html() {
}
/**
* Escapes text for HTML.
@@ -37,7 +38,7 @@ public final class Html {
* Simple page wrapper.
*
* @param title title
* @param body body html
* @param body body html
* @return full html
*/
public static String page(String title, String body) {

View File

@@ -88,7 +88,7 @@ public final class RequestParams {
* Returns SHA-256 hex of a header value.
*
* @param hasher hasher
* @param key header key
* @param key header key
* @return sha256 hex (or null if missing)
*/
public String getSha256Hex(WebHasher hasher, String key) {

View File

@@ -26,8 +26,8 @@ public final class WebHasher {
* Creates a hasher.
*
* @param pbkdf2Iterations iterations (recommended >= 100k)
* @param pbkdf2SaltBytes salt bytes
* @param pbkdf2KeyBytes derived key bytes
* @param pbkdf2SaltBytes salt bytes
* @param pbkdf2KeyBytes derived key bytes
*/
public WebHasher(int pbkdf2Iterations, int pbkdf2SaltBytes, int pbkdf2KeyBytes) {
if (pbkdf2Iterations < 10_000) throw new IllegalArgumentException("pbkdf2Iterations too low");
@@ -38,6 +38,35 @@ public final class WebHasher {
this.pbkdf2KeyBytes = pbkdf2KeyBytes;
}
private static byte[] derive(char[] password, byte[] salt, int iterations, int keyBytes) {
try {
PBEKeySpec spec = new PBEKeySpec(password, salt, iterations, keyBytes * 8);
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
return skf.generateSecret(spec).getEncoded();
} catch (Exception e) {
throw new IllegalStateException("PBKDF2 not available", e);
}
}
private static boolean constantTimeEquals(byte[] a, byte[] b) {
if (a == null || b == null) return false;
if (a.length != b.length) return false;
int r = 0;
for (int i = 0; i < a.length; i++) r |= (a[i] ^ b[i]);
return r == 0;
}
private static String toHexLower(byte[] data) {
final char[] hex = "0123456789abcdef".toCharArray();
char[] out = new char[data.length * 2];
int i = 0;
for (byte b : data) {
out[i++] = hex[(b >>> 4) & 0x0F];
out[i++] = hex[b & 0x0F];
}
return new String(out);
}
/**
* SHA-256 hashes text (lowercase hex).
*
@@ -77,7 +106,7 @@ public final class WebHasher {
* Verifies a raw password against a stored PBKDF2 string.
*
* @param password raw password
* @param stored stored format
* @param stored stored format
* @return true if valid
*/
public boolean pbkdf2Verify(String password, String stored) {
@@ -107,33 +136,4 @@ public final class WebHasher {
byte[] actual = derive(password.toCharArray(), salt, it, expected.length);
return constantTimeEquals(expected, actual);
}
private static byte[] derive(char[] password, byte[] salt, int iterations, int keyBytes) {
try {
PBEKeySpec spec = new PBEKeySpec(password, salt, iterations, keyBytes * 8);
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
return skf.generateSecret(spec).getEncoded();
} catch (Exception e) {
throw new IllegalStateException("PBKDF2 not available", e);
}
}
private static boolean constantTimeEquals(byte[] a, byte[] b) {
if (a == null || b == null) return false;
if (a.length != b.length) return false;
int r = 0;
for (int i = 0; i < a.length; i++) r |= (a[i] ^ b[i]);
return r == 0;
}
private static String toHexLower(byte[] data) {
final char[] hex = "0123456789abcdef".toCharArray();
char[] out = new char[data.length * 2];
int i = 0;
for (byte b : data) {
out[i++] = hex[(b >>> 4) & 0x0F];
out[i++] = hex[b & 0x0F];
}
return new String(out);
}
}