diff --git a/mclib/src/repos/CurseForgeRepository.ts b/mclib/src/repos/CurseForgeRepository.ts index 7f661d8..4083066 100644 --- a/mclib/src/repos/CurseForgeRepository.ts +++ b/mclib/src/repos/CurseForgeRepository.ts @@ -2,6 +2,7 @@ import type { IRepository } from "./IRepository"; import { RawModRepoRelease, ModRepositoryName, ModLoader, ModRepoMetadata, MCVersion } from ".."; import { cf_fingerprint } from 'cf-fingerprint'; import { logger } from "../logger"; +import { validateParam } from "../utils"; // Translation map for Curseforge modloader IDs // Source: https://docs.curseforge.com/rest-api/#tocS_ModLoaderType @@ -25,6 +26,8 @@ export class CurseForgeRepository implements IRepository { } async getModReleases(modId: string): Promise { + validateParam(modId); + const modInfo = await this.fetchModInfo(Number(modId)); if (!modInfo || !modInfo.latestFilesIndexes) return []; @@ -47,6 +50,9 @@ export class CurseForgeRepository implements IRepository { } async searchMods(query: string, maxResults: number): Promise { + validateParam(query); + validateParam(maxResults.toString()); + type Data = { data: { id: number; @@ -70,7 +76,7 @@ export class CurseForgeRepository implements IRepository { pageSize: maxResults.toString(), sortField: "1", sortOrder: "desc", - searchFilter: query + searchFilter: query, })); if (!resp.ok) throw new Error("Failed to fetch search results from CurseForge"); const jsonResp: Data = (await resp.json()); @@ -87,12 +93,16 @@ export class CurseForgeRepository implements IRepository { } private async fetchModInfo(modId: number): Promise { + validateParam(modId.toString()); + const modResp = await this.fetchClient(`${CurseForgeRepository.BASE_URL}/mods/${modId}`); if (!modResp.ok) return null; return (await modResp.json()).data as ModInfoData; } async getByDataHash(hash: string): Promise { + validateParam(hash); + // Use the CurseForge API to get file info by fingerprint const resp = await this.fetchClient(`${CurseForgeRepository.BASE_URL}/fingerprints`, { method: 'POST', diff --git a/mclib/src/repos/ModrinthRepository.ts b/mclib/src/repos/ModrinthRepository.ts index 5554cce..743d3a2 100644 --- a/mclib/src/repos/ModrinthRepository.ts +++ b/mclib/src/repos/ModrinthRepository.ts @@ -1,13 +1,14 @@ import type { IRepository } from "./IRepository"; import { ModRepositoryName, ModRepoMetadata, ModLoaderUtil, RawModRepoRelease } from ".."; +import { validateParam } from "../utils"; // https://devblogs.microsoft.com/typescript/announcing-typescript-5-9-rc/#notable-behavioral-changes function toArrayBuffer(buf: ArrayBufferLike): ArrayBuffer { - if (buf instanceof ArrayBuffer) return buf; - // SharedArrayBuffer → copy into a new ArrayBuffer - const copy = new Uint8Array(buf.byteLength); - copy.set(new Uint8Array(buf)); - return copy.buffer; + if (buf instanceof ArrayBuffer) return buf; + // SharedArrayBuffer → copy into a new ArrayBuffer + const copy = new Uint8Array(buf.byteLength); + copy.set(new Uint8Array(buf)); + return copy.buffer; } @@ -20,6 +21,8 @@ export class ModrinthRepository implements IRepository { } async getModReleases(modId: string): Promise { + validateParam(modId); + type Data = Array<{ game_versions: string[]; version_number: string; @@ -49,6 +52,9 @@ export class ModrinthRepository implements IRepository { } async searchMods(query: string, maxResults: number): Promise { + validateParam(query); + validateParam(maxResults.toString()); + type Data = { hits: Array<{ slug: string; @@ -74,6 +80,8 @@ export class ModrinthRepository implements IRepository { } async getByDataHash(hash: string): Promise { + validateParam(hash); + // Get version info using the hash const versionResp = await this.fetchClient(`https://api.modrinth.com/v2/version_file/${hash}`); if (!versionResp.ok) return null; diff --git a/mclib/src/utils.ts b/mclib/src/utils.ts new file mode 100644 index 0000000..caf3f3d --- /dev/null +++ b/mclib/src/utils.ts @@ -0,0 +1,5 @@ +export function validateParam(s: string) { + if (!/^[a-zA-Z0-9]{0,64}$/.test(s)) { + throw new Error('Invalid parameter: must be 0-64 alphanumeric characters'); + } +}