diff --git a/src/core/operations/GenerateLoremIpsum.mjs b/src/core/operations/GenerateLoremIpsum.mjs index 7bc636ac..5065f331 100644 --- a/src/core/operations/GenerateLoremIpsum.mjs +++ b/src/core/operations/GenerateLoremIpsum.mjs @@ -8,6 +8,10 @@ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import { GenerateParagraphs, GenerateSentences, GenerateWords, GenerateBytes } from "../lib/LoremIpsum.mjs"; +// arbitrary limits set to avoid DoS by requesting ridiculous amounts of data +const maxLoremWords = 100_000; // same limit also used for paragraphs/sentences +const maxLoremCharacters = 1_000_000; + /** * Generate Lorem Ipsum operation */ @@ -47,9 +51,7 @@ class GenerateLoremIpsum extends Operation { */ run(input, args) { const [length, lengthType] = args; - if (length < 1) { - throw new OperationError("Length must be greater than 0"); - } + checkLimits(lengthType, length); switch (lengthType) { case "Paragraphs": return GenerateParagraphs(length); @@ -68,3 +70,32 @@ class GenerateLoremIpsum extends Operation { } export default GenerateLoremIpsum; + +/** + * check combined validity of lengthType and length arguments + * @param {string} lengthType + * @param {number} length + * @throws {OperationError} + */ +function checkLimits(lengthType, length) { + if (length < 1) { + throw new OperationError("Length must be greater than 0"); + } + + switch (lengthType) { + case "Paragraphs": + case "Sentences": + case "Words": + if (length > maxLoremWords) { + throw new OperationError("Length must be less than " + maxLoremWords); + } + break; + case "Bytes": + if (length > maxLoremCharacters) { + throw new OperationError("Length must be less than " + maxLoremCharacters); + } + break; + default: + throw new OperationError("Invalid length type"); + } +} diff --git a/tests/operations/tests/GenerateLoremIpsum.mjs b/tests/operations/tests/GenerateLoremIpsum.mjs new file mode 100644 index 00000000..c42bf8da --- /dev/null +++ b/tests/operations/tests/GenerateLoremIpsum.mjs @@ -0,0 +1,80 @@ +/** + * Generate Lorem Ipsum tests + * + * @author GCHQDeveloper581 + * @copyright Crown Copyright 2025 + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Generate Lorem Ipsum: Exceeds Word Limit", + input: "", + expectedOutput: "Length must be less than 100000", + recipeConfig: [ + { + "op": "Generate Lorem Ipsum", + "args": [999_999, "Words"] + }, + ], + }, + { + name: "Generate Lorem Ipsum: Within Word Limit", + input: "", + // each word is >= 3 characters long, so expect at least 3000 characters + expectedMatch: /.{3000,}/s, + recipeConfig: [ + { + "op": "Generate Lorem Ipsum", + "args": [1000, "Words"] + }, + ], + }, + { + name: "Generate Lorem Ipsum: Exceeds Byte Limit", + input: "", + expectedOutput: "Length must be less than 1000000", + recipeConfig: [ + { + "op": "Generate Lorem Ipsum", + "args": [1_000_001, "Bytes"] + }, + ], + }, + { + name: "Generate Lorem Ipsum: Exceeds Sentence Limit", + input: "", + expectedOutput: "Length must be less than 100000", + recipeConfig: [ + { + "op": "Generate Lorem Ipsum", + "args": [999_999, "Sentences"] + }, + ], + }, + { + name: "Generate Lorem Ipsum: Exceeds Paragraph Limit", + input: "", + expectedOutput: "Length must be less than 100000", + recipeConfig: [ + { + "op": "Generate Lorem Ipsum", + "args": [999_999, "Paragraphs"] + }, + ], + }, + { + name: "Generate Lorem Ipsum: Incorrect lengthType", + input: "", + expectedOutput: "Invalid length type", + recipeConfig: [ + { + "op": "Generate Lorem Ipsum", + "args": [999_999, "Novels"] + }, + ], + }, + + +]);