This commit is contained in:
2025-12-12 22:00:48 +01:00
commit 04d4a94b50
37 changed files with 1336 additions and 0 deletions
@@ -0,0 +1,93 @@
package erfinderlabyrinth.panel.website;
import erfinderlabyrinth.panel.panel.Panel;
import erfinderlabyrinth.panel.website.api.APIManager;
import erfinderlabyrinth.panel.website.page.FilePage;
import erfinderlabyrinth.panel.website.page.Page;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
public class Connection {
Socket s;
Panel panel;
public Connection(Socket s, Panel panel) {
this.s = s;
this.panel = panel;
}
public void start() throws Exception {
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
String line;
ConnectionType type = null;
String path = "";
String host = "";
int post_length = 0;
System.out.println("start reading");
while (!(line = reader.readLine()).isEmpty()) {
System.out.println("Input Line: " + line);
if (line.startsWith("GET")) {
type = ConnectionType.GET;
path = line.split(" ")[1];
}else if (line.startsWith("POST")) {
type = ConnectionType.POST;
path = line.split(" ")[1];
}else if (line.startsWith("OPTIONS")) {
type = ConnectionType.OPTIONS;
path = line.split(" ")[1];
} else if (line.startsWith("Host: ")) {
host = line.substring(6);
} else if (line.startsWith("Content-Length: ")) {
post_length = Integer.parseInt(line.substring(16));
}
}
System.out.println("Finished reading");
if (type == null) {
s.close();
return;
}
byte[] data = null;
if (type == ConnectionType.POST) {
System.out.println("Read Data: " + post_length + " Bytes");
data = new byte[post_length];
for (int i = 0; i < post_length; i++) {
data[i] = (byte)reader.read();
}
}
if (path.startsWith("/")) {
path = path.substring(1);
}
OutputStream output = s.getOutputStream();
if (!path.startsWith("api/")) {
Page page;
page = FilePage.load(path);
if (page == null) {
output.write(("HTTP/1.0 404 UNKNOWN_PAGE\n\n").getBytes());
}else {
output.write(("HTTP/1.0 200 OK\n\n").getBytes());
output.write(page.getPageData());
}
}else {
path = path.substring("api/".length());
APIManager.call(path, type, data, output, panel);
}
output.flush();
s.close();
System.out.println("Written");
}
public enum ConnectionType {
GET, POST, OPTIONS
}
}
@@ -0,0 +1,44 @@
package erfinderlabyrinth.panel.website;
import org.json.JSONObject;
public class Response {
int code;
JSONObject json;
public Response(int code, JSONObject json) {
this.code = code;
this.json = json;
}
public static Response ok(JSONObject json) {
return new Response(200, json);
}
public static Response missing(JSONObject json) {
return new Response(404, json);
}
public static Response unauthorized(JSONObject json) {
return new Response(401, json);
}
public static Response wrongConnectionType(JSONObject json) {
return new Response(405, json);
}
public static Response wrongData(JSONObject json) {
return new Response(400, json);
}
public static Response noContent(JSONObject json) {
return new Response(204, json);
}
public int getResponseCode() {
return code;
}
public JSONObject getJson() {
return json;
}
}
@@ -0,0 +1,100 @@
package erfinderlabyrinth.panel.website;
import erfinderlabyrinth.panel.panel.Panel;
import javax.net.ssl.*;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.security.SecureRandom;
public class Webserver {
int port;
Thread thread;
boolean running = true;
SSLServerSocket serverSocket;
Panel panel;
public Webserver(int port, Panel panel) {
this.port = port;
this.panel = panel;
}
public void start() throws IOException {
byte[] passBytes;
try(FileInputStream in = new FileInputStream("pw.txt")) {
passBytes = in.readAllBytes();
} catch (IOException | NullPointerException e) {
System.err.println("Missing Password. Couldn't find the server keystore pass");
e.printStackTrace();
return;
}
ByteBuffer byteBuf = ByteBuffer.wrap(passBytes);
CharBuffer charBuf = StandardCharsets.UTF_8.decode(byteBuf);
char[] pass = charBuf.array();
try {
KeyStore store = KeyStore.getInstance("jks");
store.load(new FileInputStream("cert.jks"), pass);
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
keyManagerFactory.init(store, pass);
KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();
SSLContext context = SSLContext.getInstance("SSL");
context.init(keyManagers, null, new SecureRandom());
SSLServerSocketFactory factory = context.getServerSocketFactory();
serverSocket = (SSLServerSocket) factory.createServerSocket(port);
serverSocket.setNeedClientAuth(false);
serverSocket.setWantClientAuth(false);
} catch (IllegalArgumentException e) {
System.out.println("Port " + port + "is invalid.");
} catch (IOException e) {
System.out.println("An error occured while opening the ServerSocket:");
throw new RuntimeException(e);
} catch (Exception e) {
throw new RuntimeException(e);
}
if (serverSocket != null) {
thread = new Thread(() -> {
while(running) {
System.out.println("Server is running");
try {
Socket s = serverSocket.accept();
System.out.println("accepted");
Connection connection = new Connection(s, panel);
new Thread(() -> {
try {
connection.start();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
} catch (Exception e) {
e.printStackTrace();
}
}
});
thread.start();
}
}
public void stop() {
running = false;
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
thread.join();
} catch (InterruptedException ignored) {
}
}
}
@@ -0,0 +1,9 @@
package erfinderlabyrinth.panel.website.api;
import erfinderlabyrinth.panel.panel.Panel;
import erfinderlabyrinth.panel.website.Connection;
import erfinderlabyrinth.panel.website.Response;
public interface APIInterface {
Response onCall(String path, Connection.ConnectionType connectionType, byte[] data, Panel panel);
}
@@ -0,0 +1,62 @@
package erfinderlabyrinth.panel.website.api;
import erfinderlabyrinth.panel.panel.Panel;
import erfinderlabyrinth.panel.website.Connection;
import erfinderlabyrinth.panel.website.Response;
import erfinderlabyrinth.panel.website.api.interfaces.LoginInterface;
import org.json.JSONObject;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
public class APIManager {
public static HashMap<String, LoginInterface.Session> sessions = new HashMap<>();
static HashMap<String, APIInterface> interfaces = new HashMap<>();
public static void call(String path, Connection.ConnectionType connectionType, byte[] data, OutputStream output, Panel panel) throws IOException {
String mainCall = path.split("/")[0];
Response response = null;
if (connectionType == Connection.ConnectionType.OPTIONS) {
response = Response.noContent(new JSONObject());
}else {
APIInterface apiInterface = interfaces.get(mainCall);
if (apiInterface != null) {
response = apiInterface.onCall(path, connectionType, data, panel);
}
if (response == null) {
response = Response.missing(null);
}
}
output.write(("HTTP/1.0 " + response.getResponseCode() + " " + getDescription(response.getResponseCode()) + "\n").getBytes());
output.write(("""
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: *
""").getBytes());
JSONObject json = response.getJson();
if (json == null) {
json = new JSONObject();
}
json.put("success", response.getJson() != null);
output.write(json.toString().getBytes());
output.flush();
output.close();
}
public static void registerInterface(String path, APIInterface apiInterface) {
interfaces.put(path, apiInterface);
}
private static String getDescription(int responseCode) {
return switch (responseCode) {
case 200 -> "OK";
case 204 -> "NO CONTENT";
case 404 -> "UNKNOWN_API_CALL";
case 401 -> "UNAUTHORIZED";
case 405 -> "WRONG_METHODE";
case 400 -> "WRONG_DATA";
default -> "MISSING_DESCRIPTION";
};
}
}
@@ -0,0 +1,32 @@
package erfinderlabyrinth.panel.website.api;
import com.fasterxml.jackson.core.JsonParser;
import erfinderlabyrinth.panel.panel.Panel;
import erfinderlabyrinth.panel.website.Connection;
import erfinderlabyrinth.panel.website.Response;
import erfinderlabyrinth.panel.website.api.interfaces.LoginInterface;
import org.json.JSONObject;
public interface LoginRequireAPIInterface extends APIInterface{
@Override
default Response onCall(String path, Connection.ConnectionType connectionType, byte[] data, Panel panel) {
try {
JSONObject json = new JSONObject(new String(data));
String token = json.getString("token");
LoginInterface.Session session = APIManager.sessions.get(token);
if (session != null) {
return onLoggedinCall(path, connectionType, data, session, panel);
}
return onNotLoggedinCall(path, connectionType, data, panel);
}catch (IllegalStateException|NullPointerException e) {
return Response.wrongData(null);
}
}
Response onLoggedinCall(String path, Connection.ConnectionType connectionType, byte[] data, LoginInterface.Session session, Panel panel);
default Response onNotLoggedinCall(String path, Connection.ConnectionType connectionType, byte[] data, Panel panel) {
return Response.unauthorized(null);
}
}
@@ -0,0 +1,92 @@
package erfinderlabyrinth.panel.website.api.interfaces;
import erfinderlabyrinth.panel.database.UserDatabase;
import erfinderlabyrinth.panel.panel.Panel;
import erfinderlabyrinth.panel.website.Connection;
import erfinderlabyrinth.panel.website.Response;
import erfinderlabyrinth.panel.website.api.APIInterface;
import erfinderlabyrinth.panel.website.api.APIManager;
import org.json.JSONObject;
import java.security.SecureRandom;
public class LoginInterface implements APIInterface {
@Override
public Response onCall(String path, Connection.ConnectionType connectionType, byte[] data, Panel panel) {
if (path.equals("login/") || path.equals("login")) {
if (connectionType == Connection.ConnectionType.POST) {
try {
JSONObject json = new JSONObject(new String(data));
String name = json.getString("name");
String password = json.getString("password");
User user = UserDatabase.getUser(name, password);
//if (name.equals("root") && UserDatabase.getSHA256(password).equals("2a7044b477960dd525b7f5990a3746a097cd189769603e670b7a5040ec797ffd")) {
// user = new User("root");
//}
if (user == null) {
return Response.ok(null);
}
Session session = new Session(user);
APIManager.sessions.put(session.getToken(), session);
JSONObject response = new JSONObject();
response.put("token", session.getToken());
response.put("user", session.getUser().getName());
return Response.ok(response);
}catch (IllegalStateException|NullPointerException e) {
return Response.wrongData(null);
}
}
return Response.wrongConnectionType(null);
}
return Response.missing(null);
}
public static class Session{
User user;
long sessionCreated;
String token;
Session(User user) {
this.user = user;
this.sessionCreated = System.currentTimeMillis();
this.token = generateToken();
}
public String getToken() {
return token;
}
public User getUser() {
return user;
}
private String generateToken() {
SecureRandom random = new SecureRandom();
long randomLong1 = random.nextLong();
long randomLong2 = random.nextLong();
return Long.toHexString(randomLong1) + Long.toHexString(randomLong2);
}
}
public static class User{
String name;
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
}
@@ -0,0 +1,25 @@
package erfinderlabyrinth.panel.website.api.interfaces;
import erfinderlabyrinth.panel.panel.Panel;
import erfinderlabyrinth.panel.panel.server.Server;
import erfinderlabyrinth.panel.website.Connection;
import erfinderlabyrinth.panel.website.Response;
import erfinderlabyrinth.panel.website.api.LoginRequireAPIInterface;
import org.json.JSONArray;
import org.json.JSONObject;
public class RequestServerListInterface implements LoginRequireAPIInterface {
@Override
public Response onLoggedinCall(String path, Connection.ConnectionType connectionType, byte[] data, LoginInterface.Session session, Panel panel) {
JSONObject object = new JSONObject();
JSONArray serverList = new JSONArray();
object.append("servers", serverList);
for (Server server : panel.getServers()) {
}
return Response.ok(object);
}
}
@@ -0,0 +1,24 @@
package erfinderlabyrinth.panel.website.api.interfaces;
import erfinderlabyrinth.panel.website.Connection;
import erfinderlabyrinth.panel.website.Response;
import erfinderlabyrinth.panel.website.api.APIInterface;
import java.util.HashMap;
public class SubInterface implements APIInterface {
static HashMap<String, APIInterface> subInterfaces = new HashMap<>();
@Override
public Response onCall(String path, Connection.ConnectionType connectionType, byte[] data) {
String secondPath = path.split("/")[1];
if (subInterfaces.containsKey(secondPath)) {
return subInterfaces.get(secondPath).onCall(path, connectionType, data);
}else {
return Response.missing(null);
}
}
public void registerSubInterface(String path, APIInterface apiInterface) {
subInterfaces.put(path, apiInterface);
}
}
@@ -0,0 +1,5 @@
package erfinderlabyrinth.panel.website.api.interfaces;
public class SubInterfaces {
public static final SubInterface PLAYER = new SubInterface();
}
@@ -0,0 +1,22 @@
package erfinderlabyrinth.panel.website.api.interfaces;
import erfinderlabyrinth.panel.panel.Panel;
import erfinderlabyrinth.panel.website.Connection;
import erfinderlabyrinth.panel.website.Response;
import erfinderlabyrinth.panel.website.api.LoginRequireAPIInterface;
import org.json.JSONObject;
public class TokenCheckInterface implements LoginRequireAPIInterface {
@Override
public Response onLoggedinCall(String path, Connection.ConnectionType connectionType, byte[] data, LoginInterface.Session session, Panel panel) {
JSONObject object = new JSONObject();
object.put("username", session.getUser().getName());
return Response.ok(object);
}
@Override
public Response onNotLoggedinCall(String path, Connection.ConnectionType connectionType, byte[] data, Panel panel) {
return Response.ok(null);
}
}
@@ -0,0 +1,35 @@
package erfinderlabyrinth.panel.website.command;
import erfinderlabyrinth.panel.website.page.PageParser;
import erfinderlabyrinth.panel.website.page.RequestContext;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
public class FileCommand implements PageParser.PageCommand {
public static final String FILE_PATH_START = "insert/";
@Override
public String apply(String args, RequestContext context) {
Path path = Path.of(FILE_PATH_START, args);
path = path.normalize();
if(path.startsWith(FILE_PATH_START)) {
try (InputStream in = getClass().getClassLoader().getResourceAsStream(FILE_PATH_START + args.replace("..", ""))) {
if (in != null) {
return new String(in.readAllBytes(), StandardCharsets.UTF_8);
}
} catch (IOException e) {
e.printStackTrace();
}
}
return "";
}
@Override
public boolean needsEscaping() {
return false;
}
}
@@ -0,0 +1,37 @@
package erfinderlabyrinth.panel.website.page;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
public class FilePage implements Page{
byte[] data;
public FilePage(byte[] data) {
this.data = data;
}
public static FilePage load(String path) {
try (InputStream input = FilePage.class.getResourceAsStream("/website/" + path.replace("..", ""))) {
if (input != null) {
byte[] bytes = input.readAllBytes();
if(path.endsWith(".html")) {
String text = new String(bytes, StandardCharsets.UTF_8);
String parsed = PageParser.parsePage(text, new RequestContext());
bytes = parsed.getBytes(StandardCharsets.UTF_8);
}
return new FilePage(bytes);
}
} catch (IOException ignored) {
}
return null;
}
@Override
public byte[] getPageData() {
return data;
}
}
@@ -0,0 +1,5 @@
package erfinderlabyrinth.panel.website.page;
public interface Page {
byte[] getPageData();
}
@@ -0,0 +1,94 @@
package erfinderlabyrinth.panel.website.page;
import erfinderlabyrinth.panel.website.command.FileCommand;
import org.apache.commons.text.StringEscapeUtils;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
public class PageParser {
private static final Map<String, PageCommand> commands = new HashMap<>(
Map.of(
"file", new FileCommand()
)
);
public static String parsePage(String original, RequestContext context) {
int exclamationCount = 0;
StringBuilder output = new StringBuilder();
StringBuilder command = null;
boolean wasInTag = false;
boolean inTag = false;
for(int i = 0; i < original.length(); i++) {
char c = original.charAt(i);
if(c == '!') {
exclamationCount++;
if(exclamationCount == 3) {
inTag = !inTag;
if(inTag) {
command = new StringBuilder();
} else {
output.append(interpretCommand(command.toString(), context));
}
}
} else {
if(!inTag && !wasInTag && exclamationCount > 0) {
for(int x = 0; x < exclamationCount; x++) {
output.append('!');
}
}
exclamationCount = 0;
if(inTag) {
command.append(c);
} else {
output.append(c);
}
wasInTag = inTag;
}
}
return output.toString();
}
public static String interpretCommand(String command, RequestContext context) {
int firstSpace = command.indexOf(' ');
String name = command.substring(0, firstSpace);
String cmdArgs = command.substring(firstSpace + 1, command.length() - 1);
PageCommand pageCommand = commands.get(name);
if(pageCommand == null) {
throw new RuntimeException("Command not found " + name);
}
String out = pageCommand.apply(cmdArgs, context);
if(pageCommand.needsEscaping()) {
return StringEscapeUtils.escapeHtml4(out);
} else {
return out;
}
}
public interface PageCommand {
String apply(String args, RequestContext context);
default boolean needsEscaping() {
return true;
}
}
public static void main(String[] args) {
System.out.println(parsePage("fjjalöfjjwm!!f!kwsöqwlsd2,!!!p95!!!fjdqkpk47 ßz8dkß2kqla4qoj9cf69p576aop! ah faöohfaöh !!!testcommand1!!!", new RequestContext()));
}
}
@@ -0,0 +1,4 @@
package erfinderlabyrinth.panel.website.page;
public class RequestContext {
}