- Started with Web Protocol

This commit is contained in:
2025-09-25 23:40:24 +02:00
parent f30a2a1046
commit 1dbfad7947
30 changed files with 1389 additions and 141 deletions

View File

@@ -1,8 +1,12 @@
package org.openautonomousconnection.protocol.side.client;
import org.openautonomousconnection.protocol.ProtocolBridge;
import org.openautonomousconnection.protocol.exceptions.UnsupportedProtocolException;
import org.openautonomousconnection.protocol.packets.OACPacket;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.GetDestinationPacket;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.ValidateDomainPacket;
import org.openautonomousconnection.protocol.packets.v1_0_0.classic.Classic_DomainPacket;
import org.openautonomousconnection.protocol.packets.v1_0_0.classic.Classic_PingPacket;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.DNSResponseCode;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.Domain;
@@ -11,10 +15,14 @@ import dev.unlegitdqrk.unlegitlibrary.network.system.client.NetworkClient;
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.ClientDisconnectedEvent;
import dev.unlegitdqrk.unlegitlibrary.network.utils.NetworkUtils;
import dev.unlegitdqrk.unlegitlibrary.utils.DefaultMethodsOverrider;
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.ClassicConverter;
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.Classic_Domain;
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.Classic_RequestDomain;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.ConnectException;
import java.security.cert.CertificateException;
public abstract class ProtocolClient extends DefaultMethodsOverrider {
@@ -55,22 +63,41 @@ public abstract class ProtocolClient extends DefaultMethodsOverrider {
}
}
@Getter
private final NetworkClient networkClient;
private final NetworkClient clientToDNS; // Handles everything with DNS-Connection
private WebClient webClient;
private ProtocolVersion serverVersion = null;
@Getter
private final ClientCertificateFolderStructure folderStructure;
public final NetworkClient getClientDNSConnection() {
return clientToDNS;
}
public ProtocolClient() throws CertificateException, IOException {
folderStructure = new ClientCertificateFolderStructure();
networkClient = new NetworkClient.ClientBuilder().setLogger(ProtocolBridge.getInstance().getLogger()).
clientToDNS = new NetworkClient.ClientBuilder().setLogger(ProtocolBridge.getInstance().getLogger()).
setHost(ProtocolBridge.getInstance().getProtocolSettings().host).setPort(ProtocolBridge.getInstance().getProtocolSettings().port).
setPacketHandler(ProtocolBridge.getInstance().getProtocolSettings().packetHandler).setEventManager(ProtocolBridge.getInstance().getProtocolSettings().eventManager).
setRootCAFolder(folderStructure.publicCAFolder).setClientCertificatesFolder(folderStructure.publicClientFolder, folderStructure.privateClientFolder).
build();
}
public final void createWebConnection(Domain domain, int pipelinePort, int webPort) throws Exception {
if (!ProtocolBridge.getInstance().isProtocolSupported(ProtocolVersion.Protocol.OAC)) throw new UnsupportedProtocolException();
if (webClient != null) {
try {
webClient.closeConnection();
} catch (IOException e) {
ProtocolBridge.getInstance().getLogger().exception("Failed to close connection to web server", e);
}
}
webClient = new WebClient(domain, pipelinePort, webPort);
}
private final void checkFileExists(File folder, String prefix, String extension) throws CertificateException, IOException {
boolean found = false;
if (folder == null) throw new FileNotFoundException("Folder does not exist");
@@ -94,15 +121,22 @@ public abstract class ProtocolClient extends DefaultMethodsOverrider {
if (serverVersion == null) this.serverVersion = serverVersion;
}
public final void onDisconnect(ClientDisconnectedEvent event) {
public final void onDNSDisconnect(ClientDisconnectedEvent event) {
serverVersion = null;
if (webClient != null) {
try {
webClient.closeConnection();
} catch (IOException e) {
ProtocolBridge.getInstance().getLogger().exception("Failed to close connection to web server", e);
}
}
}
public final boolean isStableServer() {
return !isBetaServer() && !isClassicServer();
}
public final boolean serverSupportStable() {
public final boolean supportServerStable() {
boolean yes = false;
for (ProtocolVersion compatibleVersion : getServerVersion().getCompatibleVersions()) {
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.STABLE;
@@ -116,7 +150,7 @@ public abstract class ProtocolClient extends DefaultMethodsOverrider {
return getServerVersion().getProtocolType() == ProtocolVersion.ProtocolType.BETA;
}
public final boolean serverSupportBeta() {
public final boolean supportServerBeta() {
boolean yes = false;
for (ProtocolVersion compatibleVersion : getServerVersion().getCompatibleVersions()) {
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.BETA;
@@ -130,7 +164,7 @@ public abstract class ProtocolClient extends DefaultMethodsOverrider {
return getServerVersion().getProtocolType() == ProtocolVersion.ProtocolType.CLASSIC;
}
public final boolean serverSupportClassic() {
public final boolean supportServerClassic() {
boolean yes = false;
for (ProtocolVersion compatibleVersion : getServerVersion().getCompatibleVersions()) {
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.CLASSIC;
@@ -140,16 +174,34 @@ public abstract class ProtocolClient extends DefaultMethodsOverrider {
return isClassicServer() || yes;
}
public final boolean isPacketSupported(OACPacket packet) {
return isVersionSupported(packet.getProtocolVersion());
public final boolean supportServerPacket(OACPacket packet) {
return supportServerVersion(packet.getProtocolVersion());
}
public final boolean isVersionSupported(ProtocolVersion targetVersion) {
public final boolean supportServerVersion(ProtocolVersion targetVersion) {
return getServerVersion() == targetVersion || getServerVersion().getCompatibleVersions().contains(targetVersion);
}
public final boolean supportServerProtocol(ProtocolVersion.Protocol protocol) {
boolean yes = false;
for (ProtocolVersion compatibleVersion : getServerVersion().getCompatibleVersions()) {
yes = compatibleVersion.getSupportedProtocols().contains(protocol);
if (yes) break;
}
return getServerVersion().getSupportedProtocols().contains(protocol) || yes;
}
public final void validateDomain(Domain domain) throws IOException, ClassNotFoundException {
networkClient.sendPacket(new ValidateDomainPacket(domain));
Classic_PingPacket cPingPacket = new Classic_PingPacket(new Classic_RequestDomain(domain.getName(), domain.getTopLevelName(), domain.getPath()), null, false);
if (ProtocolBridge.getInstance().isPacketSupported(cPingPacket)) clientToDNS.sendPacket(cPingPacket);
clientToDNS.sendPacket(new ValidateDomainPacket(domain));
}
public final void requestDestination(Domain domain, DNSResponseCode responseCode) throws IOException, ClassNotFoundException {
Classic_DomainPacket cDomainPacket = new Classic_DomainPacket(0, new Classic_RequestDomain(domain.getName(), domain.getTopLevelName(), domain.getPath()), null);
if (ProtocolBridge.getInstance().isPacketSupported(cDomainPacket)) clientToDNS.sendPacket(cDomainPacket);
clientToDNS.sendPacket(new GetDestinationPacket(domain, responseCode));
}
public abstract void validationCompleted(Domain domain, DNSResponseCode responseCode);

View File

@@ -0,0 +1,136 @@
package org.openautonomousconnection.protocol.side.client;
import dev.unlegitdqrk.unlegitlibrary.network.system.client.NetworkClient;
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.*;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
import lombok.Getter;
import org.openautonomousconnection.protocol.ProtocolBridge;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.Domain;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.Socket;
public final class WebClient {
private final NetworkClient clientToWebPipeline; // Handles everything with Pipeline-Connection
private SSLSocket clientToWebServer; // Handles everything with Web-Connection
private final Thread receiveThread = new Thread(this::receive);
private ObjectOutputStream outputStream;
private ObjectInputStream inputStream;
public final NetworkClient getClientPipelineConnection() {
return clientToWebPipeline;
}
public final SSLSocket getClientWebConnection() {
return clientToWebServer;
}
public WebClient(Domain domain, int pipelinePort, int webPort) throws Exception {
clientToWebPipeline = new NetworkClient.ClientBuilder().setLogger(ProtocolBridge.getInstance().getLogger()).
setHost(domain.getDestination()).setPort(pipelinePort).
setPacketHandler(ProtocolBridge.getInstance().getProtocolSettings().packetHandler).setEventManager(ProtocolBridge.getInstance().getProtocolSettings().eventManager).
setRootCAFolder(ProtocolBridge.getInstance().getProtocolClient().getFolderStructure().publicCAFolder).setClientCertificatesFolder(ProtocolBridge.getInstance().getProtocolClient().getFolderStructure().publicClientFolder, ProtocolBridge.getInstance().getProtocolClient().getFolderStructure().privateClientFolder).
build();
clientToWebPipeline.connect();
while (!clientToWebPipeline.isConnected()) if (clientToWebPipeline.isConnected()) break;
SSLSocketFactory sslSocketFactory = NetworkClient.ClientBuilder.createSSLSocketFactory(ProtocolBridge.getInstance().getProtocolClient().getFolderStructure().publicCAFolder, ProtocolBridge.getInstance().getProtocolClient().getFolderStructure().publicClientFolder, ProtocolBridge.getInstance().getProtocolClient().getFolderStructure().privateClientFolder);
Proxy proxy = clientToWebPipeline.getProxy();
SSLSocket tempSocket;
if (sslSocketFactory == null) {
throw new ConnectException("SSL socket factory not set. Client certificate required!");
} else {
if (proxy != null) {
Socket rawSocket = new Socket(proxy);
rawSocket.connect(new InetSocketAddress(domain.getDestination(), webPort), 0);
tempSocket = (SSLSocket)sslSocketFactory.createSocket(rawSocket, domain.getDestination(), webPort, true);
} else {
tempSocket = (SSLSocket)sslSocketFactory.createSocket(domain.getDestination(), webPort);
}
clientToWebServer = tempSocket;
SSLParameters sslParameters = clientToWebPipeline.getSocket().getSSLParameters();
if (sslParameters != null) {
clientToWebServer.setSSLParameters(sslParameters);
} else {
SSLParameters defaultParams = clientToWebServer.getSSLParameters();
defaultParams.setProtocols(new String[]{"TLSv1.3"});
clientToWebServer.setSSLParameters(defaultParams);
}
clientToWebServer.setTcpNoDelay(true);
clientToWebServer.setSoTimeout(0);
try {
this.clientToWebServer.startHandshake();
} catch (Exception handshakeEx) {
throw new ConnectException("Handshake failed: " + handshakeEx.getMessage());
}
this.outputStream = new ObjectOutputStream(clientToWebServer.getOutputStream());
this.inputStream = new ObjectInputStream(clientToWebServer.getInputStream());
this.receiveThread.start();
}
}
public final boolean isConnected() {
return this.clientToWebServer != null && this.clientToWebServer.isConnected() && !this.clientToWebServer.isClosed()
&& this.receiveThread.isAlive() && !this.receiveThread.isInterrupted() &&
ProtocolBridge.getInstance().getProtocolClient().getClientDNSConnection().isConnected() && clientToWebPipeline.isConnected();
}
private void receive() {
try {
while(this.isConnected()) {
Object received = this.inputStream.readObject();
}
} catch (Exception var2) {
try {
this.closeConnection();
} catch (IOException exception) {
ProtocolBridge.getInstance().getLogger().exception("Failed to close connection to web server", var2);
}
}
}
public synchronized final boolean closeConnection() throws IOException {
if (!this.isConnected()) {
return false;
} else {
clientToWebPipeline.disconnect();
try {
this.receiveThread.interrupt();
if (this.outputStream != null) {
this.outputStream.close();
}
if (this.inputStream != null) {
this.inputStream.close();
}
if (this.clientToWebServer != null) {
this.clientToWebServer.close();
}
} finally {
this.clientToWebServer = null;
this.outputStream = null;
this.inputStream = null;
}
return true;
}
}
}

View File

@@ -1,11 +1,11 @@
package org.openautonomousconnection.protocol.side.server;
package org.openautonomousconnection.protocol.side.dns;
import org.openautonomousconnection.protocol.packets.OACPacket;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import lombok.Getter;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.ConnectionHandler;
public class ConnectedProtocolClient {
public final class ConnectedProtocolClient {
@Getter
private final ConnectionHandler connectionHandler;
@@ -20,7 +20,7 @@ public class ConnectedProtocolClient {
return clientVersion == null ? ProtocolVersion.PV_1_0_0_CLASSIC : clientVersion;
}
public void setClientVersion(ProtocolVersion clientVersion) {
public final void setClientVersion(ProtocolVersion clientVersion) {
if (clientVersion == null) this.clientVersion = clientVersion;
}
@@ -28,7 +28,7 @@ public class ConnectedProtocolClient {
return !isBetaClient() && !isClassicClient();
}
public boolean clientSupportStable() {
public boolean supportClientStable() {
boolean yes = false;
for (ProtocolVersion compatibleVersion : getClientVersion().getCompatibleVersions()) {
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.STABLE;
@@ -42,7 +42,7 @@ public class ConnectedProtocolClient {
return getClientVersion().getProtocolType() == ProtocolVersion.ProtocolType.BETA;
}
public boolean clientSupportBeta() {
public boolean supportClientBeta() {
boolean yes = false;
for (ProtocolVersion compatibleVersion : getClientVersion().getCompatibleVersions()) {
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.BETA;
@@ -56,7 +56,7 @@ public class ConnectedProtocolClient {
return getClientVersion().getProtocolType() == ProtocolVersion.ProtocolType.CLASSIC;
}
public boolean clientSupportClassic() {
public boolean supportClientClassic() {
boolean yes = false;
for (ProtocolVersion compatibleVersion : getClientVersion().getCompatibleVersions()) {
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.CLASSIC;
@@ -66,11 +66,21 @@ public class ConnectedProtocolClient {
return isClassicClient() || yes;
}
public boolean isPacketSupported(OACPacket packet) {
return isVersionSupported(packet.getProtocolVersion());
public boolean supportClientPacket(OACPacket packet) {
return supportClientVersion(packet.getProtocolVersion());
}
public boolean isVersionSupported(ProtocolVersion targetVersion) {
public boolean supportClientVersion(ProtocolVersion targetVersion) {
return getClientVersion() == targetVersion || getClientVersion().getCompatibleVersions().contains(targetVersion);
}
public final boolean supportClientProtocol(ProtocolVersion.Protocol protocol) {
boolean yes = false;
for (ProtocolVersion compatibleVersion : getClientVersion().getCompatibleVersions()) {
yes = compatibleVersion.getSupportedProtocols().contains(protocol);
if (yes) break;
}
return getClientVersion().getSupportedProtocols().contains(protocol) || yes;
}
}

View File

@@ -1,4 +1,4 @@
package org.openautonomousconnection.protocol.side.server;
package org.openautonomousconnection.protocol.side.dns;
import org.openautonomousconnection.protocol.ProtocolBridge;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.DNSResponseCode;
@@ -16,7 +16,7 @@ import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.List;
public abstract class ProtocolServer extends DefaultMethodsOverrider {
public abstract class ProtocolDNSServer extends DefaultMethodsOverrider {
public final class ServerCertificateFolderStructure {
public final File certificatesFolder;
@@ -68,7 +68,7 @@ public abstract class ProtocolServer extends DefaultMethodsOverrider {
private final ConfigurationManager configurationManager;
public ProtocolServer(File configFile) throws IOException, CertificateException {
public ProtocolDNSServer(File configFile) throws IOException, CertificateException {
if (!configFile.exists()) configFile.createNewFile();
configurationManager = new ConfigurationManager(configFile);
@@ -146,7 +146,7 @@ public abstract class ProtocolServer extends DefaultMethodsOverrider {
public abstract DNSResponseCode validateDomain(Domain requestedDomain);
public abstract void validationFailed(Domain domain, ConnectedProtocolClient client, Exception exception);
public abstract void validationPacketSendFailed(Domain domain, ConnectedProtocolClient client, Exception exception);
public abstract void getDomainDestinationFailed(ConnectedProtocolClient client, Domain domain, DNSResponseCode validationResponse, Exception exception);
public abstract void domainDestinationPacketFailedSend(ConnectedProtocolClient client, Domain domain, DNSResponseCode validationResponse, Exception exception);
}

View File

@@ -0,0 +1,15 @@
package org.openautonomousconnection.protocol.side.dns.events;
import org.openautonomousconnection.protocol.side.dns.ConnectedProtocolClient;
import lombok.Getter;
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
public final class ConnectedProtocolClientEvent extends Event {
@Getter
private final ConnectedProtocolClient protocolClient;
public ConnectedProtocolClientEvent(ConnectedProtocolClient protocolClient) {
this.protocolClient = protocolClient;
}
}

View File

@@ -1,15 +0,0 @@
package org.openautonomousconnection.protocol.side.server.events;
import org.openautonomousconnection.protocol.side.server.ConnectedProtocolClient;
import lombok.Getter;
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
public class ProtocolClientConnected extends Event {
@Getter
private final ConnectedProtocolClient protocolClient;
public ProtocolClientConnected(ConnectedProtocolClient protocolClient) {
this.protocolClient = protocolClient;
}
}

View File

@@ -0,0 +1,464 @@
package org.openautonomousconnection.protocol.side.web;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.ConnectionHandler;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.NetworkServer;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.ConnectionHandlerDisconnectedEvent;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.S_PacketReceivedEvent;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.S_PacketReceivedFailedEvent;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.S_UnknownObjectReceivedEvent;
import lombok.Getter;
import org.openautonomousconnection.protocol.ProtocolBridge;
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.versions.ProtocolVersion;
import javax.net.ssl.SSLSocket;
import java.io.*;
import java.net.SocketException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.Map;
public final class ConnectedWebClient {
@Getter
private final ConnectionHandler pipelineConnection;
@Getter
private SSLSocket webSocket;
private ObjectOutputStream outputStream;
private ObjectInputStream inputStream;
private final Thread receiveThread = new Thread(this::receive);
private ProtocolVersion clientVersion = null;
public ConnectedWebClient(ConnectionHandler pipelineConnection) {
this.pipelineConnection = pipelineConnection;
}
public void setWebSocket(SSLSocket webSocket) {
if (webSocket != null) this.webSocket = webSocket;
this.receiveThread.start();
}
public boolean isConnected() {
return this.webSocket != null && this.webSocket.isConnected() && !this.webSocket.isClosed() && this.receiveThread.isAlive() && pipelineConnection.isConnected();
}
public synchronized boolean disconnect() {
if (!this.isConnected()) {
return false;
} else {
pipelineConnection.disconnect();
if (this.receiveThread.isAlive()) {
this.receiveThread.interrupt();
}
try {
this.outputStream.close();
this.inputStream.close();
this.webSocket.close();
} catch (IOException var2) {
}
this.webSocket = null;
this.outputStream = null;
this.inputStream = null;
return true;
}
}
@Getter
private boolean clientVersionLoaded = false;
public ProtocolVersion getClientVersion() {
return clientVersion == null ? ProtocolVersion.PV_1_0_0_CLASSIC : clientVersion;
}
public final void setClientVersion(ProtocolVersion clientVersion) {
if (!clientVersionLoaded) clientVersionLoaded = true;
if (clientVersion == null) this.clientVersion = clientVersion;
}
public boolean isStableClient() {
return !isBetaClient() && !isClassicClient();
}
public boolean supportClientStable() {
boolean yes = false;
for (ProtocolVersion compatibleVersion : getClientVersion().getCompatibleVersions()) {
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.STABLE;
if (yes) break;
}
return isStableClient() || yes;
}
public boolean isBetaClient() {
return getClientVersion().getProtocolType() == ProtocolVersion.ProtocolType.BETA;
}
public boolean supportClientBeta() {
boolean yes = false;
for (ProtocolVersion compatibleVersion : getClientVersion().getCompatibleVersions()) {
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.BETA;
if (yes) break;
}
return isBetaClient() || yes;
}
public boolean isClassicClient() {
return getClientVersion().getProtocolType() == ProtocolVersion.ProtocolType.CLASSIC;
}
public boolean supportClientClassic() {
boolean yes = false;
for (ProtocolVersion compatibleVersion : getClientVersion().getCompatibleVersions()) {
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.CLASSIC;
if (yes) break;
}
return isClassicClient() || yes;
}
public boolean supportClientPacket(OACPacket packet) {
return supportClientVersion(packet.getProtocolVersion());
}
public boolean supportClientVersion(ProtocolVersion targetVersion) {
return getClientVersion() == targetVersion || getClientVersion().getCompatibleVersions().contains(targetVersion);
}
public final boolean supportClientProtocol(ProtocolVersion.Protocol protocol) {
boolean yes = false;
for (ProtocolVersion compatibleVersion : getClientVersion().getCompatibleVersions()) {
yes = compatibleVersion.getSupportedProtocols().contains(protocol);
if (yes) break;
}
return getClientVersion().getSupportedProtocols().contains(protocol) || yes;
}
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(ProtocolBridge.getInstance().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(ProtocolBridge.getInstance().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);
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);
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(ProtocolBridge.getInstance().getProtocolWebServer().getContentFolder(), "uploads"));
}
if (RuleManager.requiresAuth(path) && !loggedIn) {
PHPResponse phpResp = renderPHPWithCookies(new File(ProtocolBridge.getInstance().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(ProtocolBridge.getInstance().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();
}
}
private static void sendRedirect(OutputStream out, String location, Map<String,String> cookies) throws IOException {
out.write(("HTTP/1.1 302 Found\r\n").getBytes());
out.write(("Location: " + location + "\r\n").getBytes());
if (cookies != null) {
for (var entry : cookies.entrySet()) {
out.write((entry.getKey() + ": " + entry.getValue() + "\r\n").getBytes());
}
}
out.write("\r\n".getBytes());
out.flush();
}
private static Map<String,String> parsePostParams(InputStream in) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder();
while (reader.ready()) {
sb.append((char) reader.read());
}
Map<String,String> map = new HashMap<>();
String[] pairs = sb.toString().split("&");
for (String p : pairs) {
String[] kv = p.split("=",2);
if (kv.length == 2) map.put(URLDecoder.decode(kv[0], StandardCharsets.UTF_8), URLDecoder.decode(kv[1], StandardCharsets.UTF_8));
}
return map;
}
private static String normalizePath(String path) {
path = path.replace("/", File.separator).replace("\\","/");
while (path.contains("..")) path = path.replace("..", "");
if (path.startsWith("/")) path = path.substring(1);
return path;
}
private static Map<String, String> parseQueryParams(String rawPath) {
Map<String, String> map = new HashMap<>();
if (rawPath.contains("?")) {
String[] params = rawPath.substring(rawPath.indexOf("?") + 1).split("&");
for (String p : params) {
String[] kv = p.split("=");
if (kv.length == 2) map.put(kv[0], kv[1]);
}
}
return map;
}
private static boolean isMultipart(Map<String,String> headers) {
String contentType = headers.get("content-type");
return contentType != null && contentType.startsWith("multipart/form-data");
}
private static void handleMultipart(InputStream in, Map<String, String> headers, File uploadDir) throws IOException {
if (!uploadDir.exists()) uploadDir.mkdirs();
String contentType = headers.get("content-type");
String boundary = "--" + contentType.split("boundary=")[1];
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
}
String data = buffer.toString(StandardCharsets.UTF_8);
String[] parts = data.split(boundary);
for (String part : parts) {
if (part.contains("Content-Disposition")) {
String name = null;
String filename = null;
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);
}
}
}
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);
}
}
}
}
private static String renderPHP(File file) throws IOException, InterruptedException {
ProcessBuilder pb = new ProcessBuilder("php", file.getAbsolutePath());
pb.redirectErrorStream(true);
Process p = pb.start();
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();
return output.toString(StandardCharsets.UTF_8);
}
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);
}
private static void sendResponse(OutputStream out, int code, File file) throws IOException {
sendResponse(out, code, Files.readString(file.toPath()), "text/html");
}
private static void sendResponse(OutputStream out, int code, String body, String contentType) throws IOException {
sendResponse(out, code, body.getBytes(StandardCharsets.UTF_8), contentType, null);
}
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);
}
private static void sendResponse(OutputStream out, int code, byte[] body, String contentType, Map<String, String> headers) throws IOException {
out.write(("HTTP/1.1 " + code + " " + getStatusText(code) + "\r\n").getBytes());
out.write(("Content-Type: " + contentType + "\r\n").getBytes());
out.write(("Content-Length: " + body.length + "\r\n").getBytes());
if (headers != null) headers.forEach((k, v) -> {
try { out.write((k + ": " + v + "\r\n").getBytes()); } catch (IOException ignored) {}
});
out.write("\r\n".getBytes());
out.write(body);
out.flush();
}
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";
};
}
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";
};
}
private static PHPResponse renderPHPWithCookies(File file) throws IOException, InterruptedException {
ProcessBuilder pb = new ProcessBuilder("php", file.getAbsolutePath());
pb.redirectErrorStream(true);
Process p = pb.start();
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();
String fullOutput = output.toString(StandardCharsets.UTF_8);
Map<String, String> cookies = new HashMap<>();
String[] parts = fullOutput.split("\r\n\r\n", 2);
String body;
if (parts.length == 2) {
String headers = parts[0];
body = parts[1];
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 {
body = fullOutput;
}
return new PHPResponse(body, 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

@@ -0,0 +1,235 @@
package org.openautonomousconnection.protocol.side.web;
import dev.unlegitdqrk.unlegitlibrary.file.ConfigurationManager;
import dev.unlegitdqrk.unlegitlibrary.file.FileUtils;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.NetworkServer;
import dev.unlegitdqrk.unlegitlibrary.network.utils.NetworkUtils;
import lombok.Getter;
import org.openautonomousconnection.protocol.ProtocolBridge;
import org.openautonomousconnection.protocol.side.web.managers.AuthManager;
import org.openautonomousconnection.protocol.side.web.managers.RuleManager;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLSocket;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ProtocolWebServer {
public final class ServerCertificateFolderStructure {
public final File certificatesFolder;
public final File publicFolder;
public final File privateFolder;
public final File privateCAFolder;
public final File privateServerFolder;
public final File publicCAFolder;
public final File publicServerFolder;
public ServerCertificateFolderStructure() {
certificatesFolder = new File("certificates");
publicFolder = new File(certificatesFolder, "public");
privateFolder = new File(certificatesFolder, "private");
privateCAFolder = new File(privateFolder, "ca");
privateServerFolder = new File(privateFolder, "server");
publicCAFolder = new File(publicFolder, "ca");
publicServerFolder = new File(publicFolder, "server");
if (!certificatesFolder.exists()) certificatesFolder.mkdirs();
if (!publicFolder.exists()) publicFolder.mkdirs();
if (!privateFolder.exists()) privateFolder.mkdirs();
if (!privateCAFolder.exists()) privateCAFolder.mkdirs();
if (!privateServerFolder.exists()) privateServerFolder.mkdirs();
if (!publicCAFolder.exists()) publicCAFolder.mkdirs();
if (!publicServerFolder.exists()) publicServerFolder.mkdirs();
}
public final String caPrefix = "ca_server_";
public final String certPrefix = "cert_server_";
}
@Getter
private NetworkServer pipelineServer;
@Getter
private SSLServerSocket webServer;
@Getter
private final File contentFolder;
@Getter
private final File errorsFolder;
@Getter
private final ServerCertificateFolderStructure folderStructure;
private final ConfigurationManager configurationManager;
private final File certFile;
private final File keyFile;
@Getter
private List<ConnectedWebClient> clients;
public ProtocolWebServer(File configFile, File authFile, File rulesFile) throws Exception {
this.clients = new ArrayList<>();
folderStructure = new ServerCertificateFolderStructure();
checkFileExists(folderStructure.publicServerFolder, folderStructure.certPrefix, ".crt");
checkFileExists(folderStructure.privateServerFolder, folderStructure.certPrefix, ".key");
this.configurationManager = getConfigurationManager(configFile);
contentFolder = new File("content");
errorsFolder = new File("errors");
if (!contentFolder.exists()) contentFolder.mkdir();
if (!errorsFolder.exists()) errorsFolder.mkdir();
this.certFile = new File(folderStructure.publicServerFolder, folderStructure.certPrefix + NetworkUtils.getPublicIPAddress() + ".crt");
this.keyFile = new File(folderStructure.privateServerFolder, folderStructure.certPrefix + NetworkUtils.getPublicIPAddress() + ".key");
if (!authFile.exists()) {
authFile.createNewFile();
FileUtils.writeFile(authFile, """
admin:5e884898da28047151d0e56f8dc6292773603d0d6aabbddab8f91d8e5f99f6c7
user:e99a18c428cb38d5f260853678922e03abd8335f
""");
}
if (!rulesFile.exists()) {
rulesFile.createNewFile();
FileUtils.writeFile(rulesFile, """
{
"allow": [
"index.html",
"css/*",
"private/info.php"
],
"deny": [
"private/*"
],
"auth": [
"private/*",
"admin/*"
]
}
""");
}
AuthManager.loadAuthFile(authFile);
RuleManager.loadRules(rulesFile);
pipelineServer = new NetworkServer.ServerBuilder().
setPort(configurationManager.getInt("port.pipeline")).setTimeout(0).
setPacketHandler(ProtocolBridge.getInstance().getProtocolSettings().packetHandler).setEventManager(ProtocolBridge.getInstance().getProtocolSettings().eventManager).
setLogger(ProtocolBridge.getInstance().getLogger()).
setServerCertificate(certFile, keyFile).setRootCAFolder(folderStructure.publicCAFolder).
build();
}
public final ConnectedWebClient getClientByID(int clientID) {
for (ConnectedWebClient client : clients)
if (client.getPipelineConnection().getClientID() == clientID) return client;
return null;
}
public final void startWebServer() throws Exception {
webServer = (SSLServerSocket)NetworkServer.ServerBuilder.
createSSLServerSocketFactory(folderStructure.publicCAFolder, certFile,keyFile).
createServerSocket(configurationManager.getInt("port"));
webServer.setSoTimeout(0);
webServer.setEnabledProtocols(pipelineServer.getServerSocket().getEnabledProtocols());
// Stop WebServer if pipelineServer dies
new Thread(() -> {
while (true) {
if (pipelineServer == null || !pipelineServer.getServerSocket().isBound()) {
try {
onPipelineStop();
} catch (IOException e) {
pipelineServer.getLogger().exception("Failed to stop WebServer", e);
}
Thread.currentThread().interrupt();
break;
}
}
}).start();
// Handle every client
new Thread(() -> {
while (true) {
try {
SSLSocket client = (SSLSocket) webServer.accept();
for (ConnectedWebClient connectedWebClient : clients) {
if (connectedWebClient.getPipelineConnection().getClientID() != -1 && connectedWebClient.isClientVersionLoaded()) {
connectedWebClient.setWebSocket(client);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
private void onPipelineStop() throws IOException {
webServer.close();
}
private void checkFileExists(File folder, String prefix, String extension) throws CertificateException, IOException {
boolean found = false;
if (folder == null) throw new FileNotFoundException("Folder does not exist");
File[] files = folder.listFiles();
if (files == null || files.length == 0) throw new FileNotFoundException("Folder " + folder.getAbsolutePath() + " is empty");
for (File file : files) {
if (!file.getName().startsWith(prefix) || !file.getName().endsWith(extension)) throw new CertificateException(file.getAbsolutePath() + " is not valid");
if (!found) found = file.getName().equalsIgnoreCase(prefix + NetworkUtils.getPublicIPAddress() + extension);
}
if (!found) throw new CertificateException("Missing " + prefix + NetworkUtils.getPublicIPAddress() + extension);
}
private ConfigurationManager getConfigurationManager(File configFile) throws IOException {
if (!configFile.exists()) configFile.createNewFile();
ConfigurationManager configurationManager = new ConfigurationManager(configFile);
configurationManager.loadProperties();
if (!configurationManager.isSet("port.webserver")) {
configurationManager.set("port.webserver", 9824);
configurationManager.saveProperties();
}
if (!configurationManager.isSet("port.pipeline")) {
configurationManager.set("port.pipeline", 9389);
configurationManager.saveProperties();
}
if (!configurationManager.isSet("filemaxuploadmb")) {
configurationManager.set("filemaxuploadmb", 1000);
configurationManager.saveProperties();
}
if (!configurationManager.isSet("sessionexpireminutes")) {
configurationManager.set("sessionexpireminutes", 60);
configurationManager.saveProperties();
}
return configurationManager;
}
}

View File

@@ -0,0 +1,15 @@
package org.openautonomousconnection.protocol.side.web.events;
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
import lombok.Getter;
import org.openautonomousconnection.protocol.side.web.ConnectedWebClient;
public final class ConnectedWebClientEvent extends Event {
@Getter
private final ConnectedWebClient webClient;
public ConnectedWebClientEvent(ConnectedWebClient webClient) {
this.webClient = webClient;
}
}

View File

@@ -0,0 +1,48 @@
package org.openautonomousconnection.protocol.side.web.managers;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.security.MessageDigest;
import java.util.HashMap;
import java.util.Map;
public class AuthManager {
private static Map<String, String> users = new HashMap<>();
public static void loadAuthFile(File authFile) throws IOException {
if (!authFile.exists()) authFile.createNewFile();
for (String line : Files.readAllLines(authFile.toPath(), StandardCharsets.UTF_8)) {
line = line.trim();
if (line.isEmpty() || line.startsWith("#")) continue;
String[] parts = line.split(":", 2);
if (parts.length == 2) {
users.put(parts[0], parts[1]);
}
}
}
public static boolean checkAuth(String login, String password) {
String storedHash = users.get(login);
if (storedHash == null) return false;
String hash = sha256(password);
return storedHash.equalsIgnoreCase(hash);
}
private static String sha256(String input) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] digest = md.digest(input.getBytes(StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder();
for (byte b : digest) sb.append(String.format("%02x", b));
return sb.toString();
} catch (Exception e) {
return "";
}
}
}

View File

@@ -0,0 +1,40 @@
package org.openautonomousconnection.protocol.side.web.managers;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.io.File;
import java.nio.file.Files;
import java.util.List;
import java.util.Map;
public class RuleManager {
private static List<String> allow;
private static List<String> deny;
private static List<String> auth;
public static void loadRules(File rulesFile) throws Exception {
String json = new String(Files.readAllBytes(rulesFile.toPath()));
Map<String, List<String>> map = new Gson().fromJson(json, new TypeToken<Map<String, List<String>>>(){}.getType());
allow = map.getOrDefault("allow", List.of());
deny = map.getOrDefault("deny", List.of());
auth = map.getOrDefault("auth", List.of());
}
public static boolean isAllowed(String path) {
return allow.stream().anyMatch(p -> pathMatches(path, p));
}
public static boolean isDenied(String path) {
return deny.stream().anyMatch(p -> pathMatches(path, p));
}
public static boolean requiresAuth(String path) {
return auth.stream().anyMatch(p -> pathMatches(path, p));
}
private static boolean pathMatches(String path, String pattern) {
pattern = pattern.replace("/", File.separator).replace("*", ".*");
return path.matches(pattern);
}
}

View File

@@ -0,0 +1,79 @@
package org.openautonomousconnection.protocol.side.web.managers;
import lombok.Getter;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class SessionManager {
private static final Map<String, Session> sessions = new ConcurrentHashMap<>();
private static final SecureRandom secureRandom = new SecureRandom();
private static class Session {
@Getter
String login;
String ip;
String userAgent;
long expiresAt;
Session(String login, String ip, String userAgent) throws IOException {
this.login = login;
this.ip = ip;
this.userAgent = userAgent;
this.expiresAt = System.currentTimeMillis() + (long) Main.getConfigurationManager().getInt("sessionexpireminutes") * 60 * 1000;;;
}
boolean isExpired() {
return System.currentTimeMillis() > expiresAt;
}
boolean matches(String ip, String userAgent) {
return this.ip.equals(ip) && this.userAgent.equals(userAgent);
}
void refresh() throws IOException {
this.expiresAt = System.currentTimeMillis() + (long) Main.getConfigurationManager().getInt("sessionexpireminutes") * 60 * 1000;;;
}
}
public static String create(String login, String ip, String userAgent) throws IOException {
byte[] bytes = new byte[32];
secureRandom.nextBytes(bytes);
String sessionId = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
sessions.put(sessionId, new Session(login, ip, userAgent));
return sessionId;
}
public static boolean isValid(String sessionId, String ip, String userAgent) throws IOException {
Session session = sessions.get(sessionId);
if (session == null || session.isExpired() || !session.matches(ip, userAgent)) {
sessions.remove(sessionId);
return false;
}
session.refresh();
return true;
}
public static void invalidate(String sessionId) {
sessions.remove(sessionId);
}
public static String getUser(String sessionId) {
Session session = sessions.get(sessionId);
if (session == null || session.isExpired()) {
sessions.remove(sessionId);
return null;
}
return session.getLogin();
}
public static void cleanupExpiredSessions() {
long now = System.currentTimeMillis();
sessions.entrySet().removeIf(entry -> entry.getValue().isExpired());
}
}