diff --git a/src/RemoteFileAPI/MessageHandlers.ts b/src/RemoteFileAPI/MessageHandlers.ts index 2aecf10cd..966b7adaf 100644 --- a/src/RemoteFileAPI/MessageHandlers.ts +++ b/src/RemoteFileAPI/MessageHandlers.ts @@ -4,110 +4,170 @@ import { hasScriptExtension } from "../Paths/ScriptFilePath"; import { GetServer, GetAllServers } from "../Server/AllServers"; import { RFAMessage, - type FileData, - type FileContent, isFileServer, isFileLocation, - type FileLocation, isFileData, - type FileMetadata, + type FileData, + type FileLocation, + type FileServer, } from "./MessageDefinitions"; import libSource from "../ScriptEditor/NetscriptDefinitions.d.ts?raw"; import { saveObject } from "../SaveObject"; import { Player } from "@player"; +import type { BaseServer } from "../Server/BaseServer"; +import type { ContentFilePath } from "../Paths/ContentFile"; -function error(errorMsg: string, { id }: RFAMessage): RFAMessage { - return new RFAMessage({ error: errorMsg, id: id }); +type SuccessResult = { success: true; params: T }; +type FailureResult = { success: false; errorResponse: RFAMessage }; +export type ValidationResult = SuccessResult | FailureResult; + +function getErrorResponse(errorMsg: string, { id }: RFAMessage): RFAMessage { + return new RFAMessage({ error: errorMsg, id }); +} + +function validateParams(validationFunction: (p: unknown) => p is T, request: RFAMessage): ValidationResult { + if (!request.params) { + return { success: false, errorResponse: getErrorResponse("Missing params", request) }; + } + if (!validationFunction(request.params)) { + return { + success: false, + errorResponse: getErrorResponse(`Invalid params: ${JSON.stringify(request.params)}`, request), + }; + } + return { success: true, params: request.params }; +} + +function validateFilePathAndServerParams( + validationFunction: (p: unknown) => p is T, + request: RFAMessage, +): { success: true; data: { filePath: ContentFilePath; server: BaseServer; params: T } } | FailureResult { + const validationResult = validateParams(validationFunction, request); + if (!validationResult.success) { + return validationResult; + } + const params = validationResult.params; + const filePath = resolveFilePath(params.filename); + if (!filePath) { + return { success: false, errorResponse: getErrorResponse(`Invalid file path: ${params.filename}`, request) }; + } + + if (!hasTextExtension(filePath) && !hasScriptExtension(filePath)) { + return { + success: false, + errorResponse: getErrorResponse(`Invalid file extension. Filename: ${params.filename}`, request), + }; + } + + const server = GetServer(params.server); + if (!server) { + return { success: false, errorResponse: getErrorResponse(`Invalid hostname: ${params.server}`, request) }; + } + + return { success: true, data: { filePath, server, params } }; +} + +function validateServerParams( + request: RFAMessage, +): { success: true; data: { server: BaseServer; params: FileServer } } | FailureResult { + const validationResult = validateParams(isFileServer, request); + if (!validationResult.success) { + return validationResult; + } + const fileServer = validationResult.params; + + const server = GetServer(fileServer.server); + if (!server) { + return { success: false, errorResponse: getErrorResponse(`Invalid hostname: ${fileServer.server}`, request) }; + } + + return { success: true, data: { server, params: fileServer } }; } export const RFARequestHandler: Record RFAMessage | Promise> = { pushFile: function (msg: RFAMessage): RFAMessage { - if (!isFileData(msg.params)) return error("Misses parameters", msg); - - const fileData: FileData = msg.params; - const filePath = resolveFilePath(fileData.filename); - if (!filePath) return error("Invalid file path", msg); - - const server = GetServer(fileData.server); - if (!server) return error("Server hostname invalid", msg); - - if (hasTextExtension(filePath) || hasScriptExtension(filePath)) { - server.writeToContentFile(filePath, fileData.content); - return new RFAMessage({ result: "OK", id: msg.id }); + const validationResult = validateFilePathAndServerParams(isFileData, msg); + if (!validationResult.success) { + return validationResult.errorResponse; } - return error("Invalid file extension", msg); + const validationData = validationResult.data; + + validationData.server.writeToContentFile(validationData.filePath, validationData.params.content); + return new RFAMessage({ result: "OK", id: msg.id }); }, + getFile: function (msg: RFAMessage): RFAMessage { - if (!isFileLocation(msg.params)) return error("Message misses parameters", msg); + const validationResult = validateFilePathAndServerParams(isFileLocation, msg); + if (!validationResult.success) { + return validationResult.errorResponse; + } + const validationData = validationResult.data; - const fileData: FileLocation = msg.params; - const filePath = resolveFilePath(fileData.filename); - if (!filePath) return error("Invalid file path", msg); + const file = validationData.server.getContentFile(validationData.filePath); + if (!file) { + return getErrorResponse(`File does not exist. Filename: ${validationData.params.filename}`, msg); + } - const server = GetServer(fileData.server); - if (!server) return error("Server hostname invalid", msg); - - if (!hasTextExtension(filePath) && !hasScriptExtension(filePath)) return error("Invalid file extension", msg); - const file = server.getContentFile(filePath); - if (!file) return error("File doesn't exist", msg); return new RFAMessage({ result: file.content, id: msg.id }); }, getFileMetadata: function (msg: RFAMessage): RFAMessage { - if (!isFileLocation(msg.params)) return error("Message misses parameters", msg); + const validationResult = validateFilePathAndServerParams(isFileLocation, msg); + if (!validationResult.success) { + return validationResult.errorResponse; + } + const validationData = validationResult.data; - const fileData: FileLocation = msg.params; - const filePath = resolveFilePath(fileData.filename); - if (!filePath) return error("Invalid file path", msg); + const file = validationData.server.getContentFile(validationData.filePath); + if (!file) { + return getErrorResponse(`File does not exist. Filename: ${validationData.params.filename}`, msg); + } - const server = GetServer(fileData.server); - if (!server) return error("Server hostname invalid", msg); - - if (!hasTextExtension(filePath) && !hasScriptExtension(filePath)) return error("Invalid file extension", msg); - const file = server.getContentFile(filePath); - if (!file) return error("File doesn't exist", msg); - - const result: FileMetadata = { - filename: file.filename, - ...file.metadata.plain(), - }; - return new RFAMessage({ result: result, id: msg.id }); + return new RFAMessage({ + result: { + filename: file.filename, + ...file.metadata.plain(), + }, + id: msg.id, + }); }, deleteFile: function (msg: RFAMessage): RFAMessage { - if (!isFileLocation(msg.params)) return error("Message misses parameters", msg); + const validationResult = validateFilePathAndServerParams(isFileLocation, msg); + if (!validationResult.success) { + return validationResult.errorResponse; + } + const validationData = validationResult.data; - const fileData: FileLocation = msg.params; - const filePath = resolveFilePath(fileData.filename); - if (!filePath) return error("Invalid filename", msg); + const resultOfRemovingFile = validationData.server.removeFile(validationData.filePath); + if (!resultOfRemovingFile.res) { + return getErrorResponse(resultOfRemovingFile.msg ?? "Failed", msg); + } - const server = GetServer(fileData.server); - if (!server) return error("Server hostname invalid", msg); - - const result = server.removeFile(filePath); - if (result.res) return new RFAMessage({ result: "OK", id: msg.id }); - return error(result.msg ?? "Failed", msg); + return new RFAMessage({ result: "OK", id: msg.id }); }, getFileNames: function (msg: RFAMessage): RFAMessage { - if (!isFileServer(msg.params)) return error("Message misses parameters", msg); + const validationResult = validateServerParams(msg); + if (!validationResult.success) { + return validationResult.errorResponse; + } + const validationData = validationResult.data; - const server = GetServer(msg.params.server); - if (!server) return error("Server hostname invalid", msg); - - const fileNameList: string[] = [...server.textFiles.keys(), ...server.scripts.keys()]; + const fileNameList = [...validationData.server.textFiles.keys(), ...validationData.server.scripts.keys()]; return new RFAMessage({ result: fileNameList, id: msg.id }); }, getAllFiles: function (msg: RFAMessage): RFAMessage { - if (!isFileServer(msg.params)) return error("Message misses parameters", msg); + const validationResult = validateServerParams(msg); + if (!validationResult.success) { + return validationResult.errorResponse; + } + const validationData = validationResult.data; - const server = GetServer(msg.params.server); - if (!server) return error("Server hostname invalid", msg); - - const fileList: FileContent[] = [...server.scripts, ...server.textFiles].map(([filename, file]) => ({ + const fileList = [...validationData.server.scripts, ...validationData.server.textFiles].map(([filename, file]) => ({ filename, content: file.content, })); @@ -115,12 +175,13 @@ export const RFARequestHandler: Record RFAMessa }, getAllFileMetadata: function (msg: RFAMessage): RFAMessage { - if (!isFileServer(msg.params)) return error("Message misses parameters", msg); + const validationResult = validateServerParams(msg); + if (!validationResult.success) { + return validationResult.errorResponse; + } + const validationData = validationResult.data; - const server = GetServer(msg.params.server); - if (!server) return error("Server hostname invalid", msg); - - const fileList: FileMetadata[] = [...server.scripts, ...server.textFiles].map(([filename, file]) => ({ + const fileList = [...validationData.server.scripts, ...validationData.server.textFiles].map(([filename, file]) => ({ filename: filename, ...file.metadata.plain(), })); @@ -128,19 +189,30 @@ export const RFARequestHandler: Record RFAMessa }, calculateRam: function (msg: RFAMessage): RFAMessage { - if (!isFileLocation(msg.params)) return error("Message misses parameters", msg); - const fileData: FileLocation = msg.params; - const filePath = resolveFilePath(fileData.filename); - if (!filePath) return error("Invalid filename", msg); + const validationResult = validateFilePathAndServerParams(isFileLocation, msg); + if (!validationResult.success) { + return validationResult.errorResponse; + } + const validationData = validationResult.data; - const server = GetServer(fileData.server); - if (!server) return error("Server hostname invalid", msg); + // Validate filePath again. validateFilePathAndServerParams only checks if filePath is a text file or a script. + if (!hasScriptExtension(validationData.filePath)) { + return getErrorResponse(`File is not a script. Filename: ${validationData.params.filename}`, msg); + } + + const script = validationData.server.scripts.get(validationData.filePath); + if (!script) { + return getErrorResponse(`File does not exist. Filename: ${validationData.params.filename}`, msg); + } + + const ramUsage = script.getRamUsage(validationData.server.scripts); + if (!ramUsage) { + return getErrorResponse( + `Cannot calculate RAM usage of an invalid script. Filename: ${validationData.params.filename}`, + msg, + ); + } - if (!hasScriptExtension(filePath)) return error("Filename isn't a script filename", msg); - const script = server.scripts.get(filePath); - if (!script) return error("File doesn't exist", msg); - const ramUsage = script.getRamUsage(server.scripts); - if (!ramUsage) return error("Ram cost could not be calculated", msg); return new RFAMessage({ result: ramUsage, id: msg.id }); },