From 86c6a3ad35c7865e2db932b5c73cb230be5f190d Mon Sep 17 00:00:00 2001 From: LJNeon <23249107+LJNeon@users.noreply.github.com> Date: Sun, 2 Oct 2022 16:28:58 -0700 Subject: [PATCH 1/2] Fortify message handler --- src/index.ts | 5 +-- src/interfaces.ts | 31 +++++++++++++++++-- src/networking/messageGenerators.ts | 8 ++--- src/networking/messageHandler.ts | 48 +++++++++++++++++++++++------ src/networking/messageTracker.ts | 6 ++-- src/networking/webSocket.ts | 1 - 6 files changed, 77 insertions(+), 22 deletions(-) diff --git a/src/index.ts b/src/index.ts index 36f5902..c256948 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,7 @@ import { setupWatch } from "./fileWatch"; import { config, loadConfig } from "./config"; import { setupSocket } from "./networking/webSocket"; import signal from "signal-js"; +import { RawData } from "ws"; import { fileChangeEventToMsg, fileRemovalEventToMsg, @@ -10,7 +11,7 @@ import { } from "./networking/messageGenerators"; import { EventType } from "./eventTypes"; import { messageHandler } from "./networking/messageHandler"; -import { FileEvent, Message } from "./interfaces"; +import { FileEvent } from "./interfaces"; export async function start() { loadConfig(); @@ -18,7 +19,7 @@ export async function start() { const socket = setupSocket(signal); // Add a handler for received messages. - signal.on(EventType.MessageReceived, (msg: Message) => messageHandler(signal, msg, watch.paths)); + signal.on(EventType.MessageReceived, (msg: RawData) => messageHandler(signal, msg, watch.paths)); // Add a handler for when a connection to a game is made. signal.on(EventType.ConnectionMade, () => { diff --git a/src/interfaces.ts b/src/interfaces.ts index 0ac2630..3805267 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -1,10 +1,35 @@ import type { Stats } from "fs"; export interface Message { - id: string; + jsonrpc: "2.0"; method?: string; - jsonrpc: string; - params?: object; + result?: ResultType; + params?: FileMetadata; + error?: string; + id?: number; +} + +type ResultType = string | number | string[] | FileContent[]; +type FileMetadata = FileData | FileContent | FileLocation | FileServer; + +export interface FileData { + filename: string; + content: string; + server: string; +} + +export interface FileContent { + filename: string; + content: string; +} + +export interface FileLocation { + filename: string; + server: string; +} + +export interface FileServer { + server: string; } export interface FileEvent { diff --git a/src/networking/messageGenerators.ts b/src/networking/messageGenerators.ts index 6d951ae..e8db805 100644 --- a/src/networking/messageGenerators.ts +++ b/src/networking/messageGenerators.ts @@ -14,7 +14,7 @@ export function fileChangeEventToMsg({ path }: FileEvent): Message { filename: addLeadingSlash(path), content: readFileSync(join(config.get("scriptsFolder"), path)).toString(), }, - id: (messageCounter++).toString(), + id: messageCounter++, }; } @@ -26,7 +26,7 @@ export function fileRemovalEventToMsg({ path }: FileEvent): Message { server: "home", filename: addLeadingSlash(path), }, - id: (messageCounter++).toString(), + id: messageCounter++, }; } @@ -34,7 +34,7 @@ export function requestDefinitionFile(): Message { return { jsonrpc: "2.0", method: "getDefinitionFile", - id: (messageCounter++).toString(), + id: messageCounter++, }; } @@ -45,7 +45,7 @@ export function requestFilenames(): Message { params: { server: "home", }, - id: (messageCounter++).toString(), + id: messageCounter++, }; } diff --git a/src/networking/messageHandler.ts b/src/networking/messageHandler.ts index 0c50b27..ca94b76 100644 --- a/src/networking/messageHandler.ts +++ b/src/networking/messageHandler.ts @@ -1,31 +1,59 @@ import { messageTracker } from "./messageTracker"; import { Stats, writeFile } from "fs"; +import { RawData } from "ws"; import { config } from "../config"; import { EventType } from "../eventTypes"; import { fileChangeEventToMsg } from "./messageGenerators"; import type { Signal } from "signal-js"; import { Message } from "../interfaces"; -export function messageHandler(signaller: Signal, msg: Message, paths: Map) { - let incoming; +function deserialize(data: RawData): Message | void { + let msg; try { - incoming = JSON.parse(msg.toString()); + msg = JSON.parse(data.toString()); } catch (err) { return console.log(err); } - console.log(incoming); - if (incoming.id == undefined) return; - if (incoming.result) { - const request = messageTracker.get(incoming.id); - if (request?.method && request.method == "getDefinitionFile" && incoming.result) { + if (typeof msg.jsonrpc !== "string" || msg.jsonrpc !== "2.0" || typeof msg.id !== "number") return; + + const id: number = msg.id; + const request = messageTracker.get(id); + + if (typeof request?.method !== "string") return; + else if (msg.error != null) return { jsonrpc: "2.0", error: msg.error, id }; + else if (msg.result == null) return; + + return { jsonrpc: "2.0", method: request.method, result: msg.result, id }; +} + +function isStringArray(s: Array): s is string[] { + return s.every((s) => typeof s === "string"); +} + +export function messageHandler(signaller: Signal, data: RawData, paths: Map) { + const incoming = deserialize(data); + + if (incoming == null) { + return console.log("Malformed data received."); + } else if (incoming.error) { + return console.log(incoming.error); + } + + switch (incoming.method) { + case "getDefinitionFile": + if (typeof incoming.result !== "string") return console.log("Malformed data received."); + writeFile(config.get("definitionFile").location, incoming.result, (err) => { if (err) return console.log(err); }); - } - if (request?.method && request.method == "getFileNames" && incoming.result) { + break; + case "getFileNames": { + if (!Array.isArray(incoming.result) || !isStringArray(incoming.result)) + return console.log("Malformed data received."); + const gameFiles = incoming.result.map((file: string) => removeLeadingSlash(file)); paths.forEach((stats, fileName) => { diff --git a/src/networking/messageTracker.ts b/src/networking/messageTracker.ts index 91c0cee..67eb528 100644 --- a/src/networking/messageTracker.ts +++ b/src/networking/messageTracker.ts @@ -1,10 +1,12 @@ import type { Message } from "../interfaces"; class MessageTracker { - data = new Map(); + data = new Map(); #maxLength = 200; push(msg: Message) { + if (typeof msg.id !== "number") return; + this.data.set(msg.id, msg); if (this.data.size > this.#maxLength) { @@ -13,7 +15,7 @@ class MessageTracker { } } - get(index: string) { + get(index: number) { return this.data.get(index); } } diff --git a/src/networking/webSocket.ts b/src/networking/webSocket.ts index 01c2ae7..044e3bd 100644 --- a/src/networking/webSocket.ts +++ b/src/networking/webSocket.ts @@ -3,7 +3,6 @@ import { WebSocketServer } from "ws"; import { config } from "../config"; import { EventType } from "../eventTypes"; import { Message } from "../interfaces"; -import { requestDefinitionFile } from "./messageGenerators"; import { messageTracker } from "./messageTracker"; export function setupSocket(signaller: Signal) { From 0657fac98d97d5db020289ba14d86bb4682bea19 Mon Sep 17 00:00:00 2001 From: LJNeon <23249107+LJNeon@users.noreply.github.com> Date: Sun, 2 Oct 2022 18:52:38 -0700 Subject: [PATCH 2/2] Better error handling --- src/networking/messageHandler.ts | 35 ++++++++++++++++---------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/networking/messageHandler.ts b/src/networking/messageHandler.ts index ca94b76..48d122d 100644 --- a/src/networking/messageHandler.ts +++ b/src/networking/messageHandler.ts @@ -7,38 +7,39 @@ import { fileChangeEventToMsg } from "./messageGenerators"; import type { Signal } from "signal-js"; import { Message } from "../interfaces"; -function deserialize(data: RawData): Message | void { - let msg; +function deserialize(data: RawData): Message { + const msg = JSON.parse(data.toString()); - try { - msg = JSON.parse(data.toString()); - } catch (err) { - return console.log(err); + if (typeof msg.jsonrpc !== "string" || msg.jsonrpc !== "2.0" || typeof msg.id !== "number") { + throw Error("Malformed data received."); } - if (typeof msg.jsonrpc !== "string" || msg.jsonrpc !== "2.0" || typeof msg.id !== "number") return; - const id: number = msg.id; const request = messageTracker.get(id); - if (typeof request?.method !== "string") return; - else if (msg.error != null) return { jsonrpc: "2.0", error: msg.error, id }; - else if (msg.result == null) return; + if (typeof request?.method !== "string") { + throw Error("Malformed JSON received."); + } else if (msg.error != null) { + throw Error(msg.error); + } else if (msg.result == null) { + throw Error("Malformed JSON received."); + } return { jsonrpc: "2.0", method: request.method, result: msg.result, id }; } -function isStringArray(s: Array): s is string[] { +export function isStringArray(s: Array): s is string[] { return s.every((s) => typeof s === "string"); } export function messageHandler(signaller: Signal, data: RawData, paths: Map) { - const incoming = deserialize(data); + let incoming; - if (incoming == null) { - return console.log("Malformed data received."); - } else if (incoming.error) { - return console.log(incoming.error); + try { + incoming = deserialize(data); + } catch (err) { + if (err instanceof Error) return console.log(err.message); + else throw err; } switch (incoming.method) {