diff --git a/src/main/java/me/jonasjones/mcwebserver/commands/McWebCommand.java b/src/main/java/me/jonasjones/mcwebserver/commands/McWebCommand.java index f31d569..b4a292c 100644 --- a/src/main/java/me/jonasjones/mcwebserver/commands/McWebCommand.java +++ b/src/main/java/me/jonasjones/mcwebserver/commands/McWebCommand.java @@ -30,40 +30,28 @@ public class McWebCommand { .executes(context -> { String name = StringArgumentType.getString(context, "Name"); String expires = StringArgumentType.getString(context, "Expiration Time (example: 1y3d4h -> 1 Year, 3 Days, 4 Hours)"); - if (context.getSource().isExecutedByPlayer()) { String result = registerToken(name, expires); - if (result.equals("exists")) { - context.getSource().sendFeedback(() -> Text.of("A token with that name already exists"), false); - return 0; - } else if (result.equals("failed")) { - context.getSource().sendFeedback(() -> Text.of("Failed to create token (Unknown Error)"), false); - return 0; - } else { - context.getSource().sendFeedback(() -> Text.of("Token Created! - Expires " + convertToHumanReadable(convertExpirationDate(expires))), true); - if (MC_SERVER != null) { - if (context.getSource().isExecutedByPlayer()) { - // get the player name - String playerName = Objects.requireNonNull(context.getSource().getPlayer()).getName().getString(); - MC_SERVER.getCommandManager().executeWithPrefix(MC_SERVER.getCommandSource(), "tellraw " + playerName + " [\"\",\"Token (will only show once): \",\"\\n\",\"[\",{\"text\":\"" + result + "\",\"color\":\"green\",\"clickEvent\":{\"action\":\"copy_to_clipboard\",\"value\":\"\"},\"hoverEvent\":{\"action\":\"show_text\",\"contents\":[\"Click to Copy to Clipboard\"]}},\"]\"]"); - } - return 1; - } - context.getSource().sendFeedback(() -> Text.of("Failed to create token (Unknown Error)"), false); - return 0; - } + if (result.equals("exists")) { + context.getSource().sendFeedback(() -> Text.of("A token with that name already exists"), false); + return 0; + } else if (result.equals("failed")) { + context.getSource().sendFeedback(() -> Text.of("Failed to create token (Unknown Error)"), false); + return 0; } else { - context.getSource().sendFeedback(() -> Text.of("ERROR: Due to Security concerns it is not possible to generate tokens from the server console as they are saved in server logs. Tokens must be created by a player! (more info at: https://wiki.jonasjones.dev/McWebserver/Tokens)"), false); + context.getSource().sendFeedback(() -> Text.of("Token Created!\nExpires " + convertToHumanReadable(convertExpirationDate(expires))), true); + if (MC_SERVER != null) { + // get the player name + String playerName = Objects.requireNonNull(context.getSource().getPlayer()).getName().getString(); + MC_SERVER.getCommandManager().executeWithPrefix(MC_SERVER.getCommandSource(), "tellraw " + playerName + " [\"\",\"Token (will only show once): \",\"\\n\",\"[\",{\"text\":\"" + result + "\",\"color\":\"green\",\"clickEvent\":{\"action\":\"copy_to_clipboard\",\"value\":\"\"},\"hoverEvent\":{\"action\":\"show_text\",\"contents\":[\"Click to Copy to Clipboard\"]}},\"]\"]"); + return 1; + } + context.getSource().sendFeedback(() -> Text.of("Failed to create token (Unknown Error)"), false); return 0; } })))) .then(literal("list") .executes(context -> { - try { - context.getSource().sendFeedback(() -> Text.of(listTokens()), false); - return 1; - } catch (Exception e) { - e.printStackTrace(); - } + context.getSource().sendFeedback(() -> Text.of(listTokens()), false); return 1; })) .then(literal("delete") diff --git a/src/main/java/me/jonasjones/mcwebserver/config/ModConfigs.java b/src/main/java/me/jonasjones/mcwebserver/config/ModConfigs.java index 2ec1e6b..09c270d 100644 --- a/src/main/java/me/jonasjones/mcwebserver/config/ModConfigs.java +++ b/src/main/java/me/jonasjones/mcwebserver/config/ModConfigs.java @@ -19,6 +19,7 @@ public class ModConfigs { public static Boolean SERVER_API_ENABLED; public static Boolean ADV_API_ENABLED; public static Boolean API_INGAME_COMMAND_ENABLED; + public static String WEB_FILE_NOSUPPORT; public static Boolean PHP_ENABLED; public static Boolean VERBOSE = false; //needs to be set to false since the verbose logger is called before config file is fully loaded @@ -49,6 +50,7 @@ public class ModConfigs { config.addKeyValuePair(new Pair<>("web.api", true), "whether or not the webserver api should be enabled or not"); config.addKeyValuePair(new Pair<>("web.api.adv", true), "whether or not the api should expose information such as player coordinates and inventory"); config.addKeyValuePair(new Pair<>("web.api.cmd", true), "whether or not the ingame command to manage tokens should be enabled or not"); + config.addKeyValuePair(new Pair<>("web.file.notSupported", "not_supported.html"), "the name of the html file for 'not supported' page"); config.addKeyValuePair(new Pair<>("logger.verbose", false), "whether or not to log verbose output"); config.addKeyValuePair(new Pair<>("web.php", false), "enable php"); } @@ -63,6 +65,7 @@ public class ModConfigs { SERVER_API_ENABLED = CONFIG.getOrDefault("web.api", true); ADV_API_ENABLED = CONFIG.getOrDefault("web.api.adv", true); API_INGAME_COMMAND_ENABLED = CONFIG.getOrDefault("web.api.cmd", true); + WEB_FILE_NOSUPPORT = CONFIG.getOrDefault("web.file.notSupported", "not_supported.html"); VERBOSE = CONFIG.getOrDefault("logger.verbose", true); PHP_ENABLED = CONFIG.getOrDefault("web.php", false); } diff --git a/src/main/java/me/jonasjones/mcwebserver/web/HttpServer.java b/src/main/java/me/jonasjones/mcwebserver/web/HttpServer.java index 62711c0..a6a951e 100644 --- a/src/main/java/me/jonasjones/mcwebserver/web/HttpServer.java +++ b/src/main/java/me/jonasjones/mcwebserver/web/HttpServer.java @@ -25,7 +25,6 @@ import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.security.NoSuchAlgorithmException; import java.time.Instant; -import java.util.Objects; import java.util.StringTokenizer; import static me.jonasjones.mcwebserver.web.ServerHandler.mcserveractive; @@ -36,8 +35,11 @@ public class HttpServer implements Runnable { static Path WEB_ROOT; static final String DEFAULT_FILE = ModConfigs.WEB_FILE_ROOT; static final String FILE_NOT_FOUND = ModConfigs.WEB_FILE_404; + static final String METHOD_NOT_SUPPORTED = ModConfigs.WEB_FILE_NOSUPPORT; // port to listen connection static final int PORT = ModConfigs.WEB_PORT; + + private static final byte[] NOT_IMPLEMENTED = "HTTP/1.1 405 Method Not Allowed\r\n".getBytes(StandardCharsets.UTF_8); private static final byte[] OK = "HTTP/1.1 200 OK\r\n".getBytes(StandardCharsets.UTF_8); private static final byte[] NOT_FOUND = "HTTP/1.1 404 Not Found\r\n".getBytes(StandardCharsets.UTF_8); private static final byte[] HEADERS = String.join( @@ -129,17 +131,24 @@ public class HttpServer implements Runnable { // we support only GET and HEAD methods, we check if (!method.equals("GET") && !method.equals("HEAD")) { isApiv1Request = false; - VerboseLogger.error("501 Not Implemented : " + method + " method."); + VerboseLogger.info("501 Not Implemented : " + method + " method."); - dataOut.write("HTTP/1.1 501 Not Implemented\r\n".getBytes(StandardCharsets.UTF_8)); + // we return the not supported file to the client + Path file = WEB_ROOT.resolve(METHOD_NOT_SUPPORTED); + long fileLength = Files.size(file); + String contentMimeType = "text/html"; + //read content to return to client + byte[] fileData = readFileData(file); + + // we send HTTP Headers with data to client + dataOut.write(NOT_IMPLEMENTED); + dataOut.write(HEADERS); //hopefully enough credits dataOut.write("Date: %s\r\n".formatted(Instant.now()).getBytes(StandardCharsets.UTF_8)); - dataOut.write("Content-Type: application/json\r\n".getBytes(StandardCharsets.UTF_8)); - String jsonString = ErrorHandler.notImplementedErrorString(); - byte[] jsonBytes = jsonString.getBytes(StandardCharsets.UTF_8); - int contentLength = jsonBytes.length; - dataOut.write(("Content-Length: " + contentLength + "\r\n").getBytes(StandardCharsets.UTF_8)); - dataOut.write("\r\n".getBytes(StandardCharsets.UTF_8)); // Blank line before content - dataOut.write(jsonBytes, 0, contentLength); + dataOut.write("Content-Type: %s\r\n".formatted(contentMimeType).getBytes(StandardCharsets.UTF_8)); + dataOut.write("Content-Length: %s\r\n".formatted(fileLength).getBytes(StandardCharsets.UTF_8)); + dataOut.write(CRLF); // blank line between headers and content, very important ! + // file + dataOut.write(fileData, 0, fileData.length); dataOut.flush(); } else if (isApiV1Request(fileRequested)) { isApiv1Request = true; @@ -350,41 +359,22 @@ public class HttpServer implements Runnable { } private void fileNotFound(PrintWriter out, OutputStream dataOut, String fileRequested) throws IOException { - try { - Path file = WEB_ROOT.resolve(FILE_NOT_FOUND); - int fileLength = (int) Files.size(file); - String contentType = "text/html"; - byte[] fileData = readFileData(file); + Path file = WEB_ROOT.resolve(FILE_NOT_FOUND); + int fileLength = (int) Files.size(file); + String contentType = "text/html"; + byte[] fileData = readFileData(file); - dataOut.write(NOT_FOUND); - dataOut.write(HEADERS); - dataOut.write("Date: %s\r\n".formatted(Instant.now()).getBytes(StandardCharsets.UTF_8)); - dataOut.write("Content-Type: %s\r\n".formatted(contentType).getBytes(StandardCharsets.UTF_8)); - dataOut.write("Content-Length: %s\r\n".formatted(fileLength).getBytes(StandardCharsets.UTF_8)); - dataOut.write(CRLF); // blank line between headers and content, very important ! - out.flush(); // flush character output stream buffer + dataOut.write(NOT_FOUND); + dataOut.write(HEADERS); + dataOut.write("Date: %s\r\n".formatted(Instant.now()).getBytes(StandardCharsets.UTF_8)); + dataOut.write("Content-Type: %s\r\n".formatted(contentType).getBytes(StandardCharsets.UTF_8)); + dataOut.write("Content-Length: %s\r\n".formatted(fileLength).getBytes(StandardCharsets.UTF_8)); + dataOut.write(CRLF); // blank line between headers and content, very important ! + out.flush(); // flush character output stream buffer - dataOut.write(fileData, 0, fileLength); - dataOut.flush(); + dataOut.write(fileData, 0, fileLength); + dataOut.flush(); - VerboseLogger.error("File " + fileRequested + " not found"); - } catch (Exception e) { - VerboseLogger.error("Error with file not found exception : " + e.getMessage()); - if (Objects.equals(e.getMessage(), WEB_ROOT.resolve(FILE_NOT_FOUND).toString())) { - VerboseLogger.error("Couldn't find fallback 404 file. Sending JSON 404 error."); - } - dataOut.write("HTTP/1.1 404 Not Found\r\n".getBytes(StandardCharsets.UTF_8)); - dataOut.write("Date: %s\r\n".formatted(Instant.now()).getBytes(StandardCharsets.UTF_8)); - dataOut.write("Content-Type: application/json\r\n".getBytes(StandardCharsets.UTF_8)); - String jsonString = ErrorHandler.notFoundErrorString(); - byte[] jsonBytes = jsonString.getBytes(StandardCharsets.UTF_8); - int contentLength = jsonBytes.length; - dataOut.write(("Content-Length: " + contentLength + "\r\n").getBytes(StandardCharsets.UTF_8)); - dataOut.write("\r\n".getBytes(StandardCharsets.UTF_8)); // Blank line before content - dataOut.write(jsonBytes, 0, contentLength); - dataOut.flush(); - - VerboseLogger.error("File " + fileRequested + " not found"); - } + VerboseLogger.error("File " + fileRequested + " not found"); } } diff --git a/src/main/java/me/jonasjones/mcwebserver/web/ServerHandler.java b/src/main/java/me/jonasjones/mcwebserver/web/ServerHandler.java index d01ca61..ed15b2b 100644 --- a/src/main/java/me/jonasjones/mcwebserver/web/ServerHandler.java +++ b/src/main/java/me/jonasjones/mcwebserver/web/ServerHandler.java @@ -101,8 +101,10 @@ public class ServerHandler implements Runnable { Path path = FabricLoader.getInstance().getGameDir(); Path webroot = path.resolve(ModConfigs.WEB_ROOT); Path index = webroot.resolve(ModConfigs.WEB_FILE_ROOT); + Path notsupported = webroot.resolve(ModConfigs.WEB_FILE_NOSUPPORT); Path notfound = webroot.resolve(ModConfigs.WEB_FILE_404); index.toFile().mkdirs(); + notsupported.toFile().mkdirs(); notfound.toFile().mkdirs(); } } diff --git a/src/main/java/me/jonasjones/mcwebserver/web/api/ErrorHandler.java b/src/main/java/me/jonasjones/mcwebserver/web/api/ErrorHandler.java index e0e5415..0f3d1ae 100644 --- a/src/main/java/me/jonasjones/mcwebserver/web/api/ErrorHandler.java +++ b/src/main/java/me/jonasjones/mcwebserver/web/api/ErrorHandler.java @@ -45,14 +45,6 @@ public class ErrorHandler { return gson.toJsonTree(notFoundError()).getAsJsonObject().toString(); } - public static Error notImplementedError() { - return new Error(501, "Not Implemented"); - } - - public static String notImplementedErrorString() { - return gson.toJsonTree(notImplementedError()).getAsJsonObject().toString(); - } - public static Error customError(int status, String message) { return new Error(status, message); } diff --git a/src/main/java/me/jonasjones/mcwebserver/web/api/v2/tokenmgr/TokenManager.java b/src/main/java/me/jonasjones/mcwebserver/web/api/v2/tokenmgr/TokenManager.java index 96a02c8..91bdf3d 100644 --- a/src/main/java/me/jonasjones/mcwebserver/web/api/v2/tokenmgr/TokenManager.java +++ b/src/main/java/me/jonasjones/mcwebserver/web/api/v2/tokenmgr/TokenManager.java @@ -140,34 +140,10 @@ public class TokenManager { if (tokens.size() == 0) { return "No active tokens."; } - int longestName = 0; - int longestExpires = 0; - for (Token token : tokens) { - if (token.getName().length() > longestName) { - longestName = token.getName().length(); - } - if (convertToHumanReadable(token.getExpires()).length() > longestExpires) { - longestExpires = convertToHumanReadable(token.getExpires()).length(); - } - } sb.append("Active Tokens:\n"); - sb.append("Name") - .append(" ".repeat(Math.max(longestName - 4, 0))) - .append(" | ").append("Expires") - .append(" ".repeat(Math.max(longestExpires - 6, 0))) - .append(" | TokenStart\n"); - sb.append("-".repeat(Math.max(longestName, 4))) - .append(" | ") - .append("-".repeat(Math.max(longestExpires, 7))) - .append(" | ") - .append("-".repeat(10)) - .append("\n"); + sb.append("Name | Expiration Date | Beginning of Token value\n"); for (Token token : tokens) { - String humanExpires = convertToHumanReadable(token.getExpires()); - sb.append(token.getName()) - .append(" ".repeat(Math.max(longestName - token.getName().length(), 4 - token.getName().length()))) - .append(" | ").append(humanExpires).append(" ".repeat(Math.max(longestExpires - humanExpires.length(), 7 - humanExpires.length()))) - .append(" | ").append(token.getTokenStart()).append("...").append("\n"); + sb.append(token.getName()).append(" | ").append(convertToHumanReadable(token.getExpires())).append(" | ").append(token.getTokenStart()).append("...").append("\n"); } return sb.toString(); }