diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a71bb41..d9b8eff0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ All major and minor version changes will be documented in this file. Details of patch-level version changes can be found in [commit messages](https://github.com/gchq/CyberChef/commits/master). +### [8.20.0] - 2019-01-09 +- 'Generate Lorem Ipsum' operation added [@klaxon1] | [#455] + ### [8.19.0] - 2018-12-30 - UI test suite added to confirm that the app loads correctly in a reasonable time and that various operations from each module can be run [@n1474335] | [#458] @@ -88,6 +91,7 @@ All major and minor version changes will be documented in this file. Details of +[8.20.0]: https://github.com/gchq/CyberChef/releases/tag/v8.20.0 [8.19.0]: https://github.com/gchq/CyberChef/releases/tag/v8.19.0 [8.18.0]: https://github.com/gchq/CyberChef/releases/tag/v8.18.0 [8.17.0]: https://github.com/gchq/CyberChef/releases/tag/v8.17.0 @@ -159,4 +163,5 @@ All major and minor version changes will be documented in this file. Details of [#446]: https://github.com/gchq/CyberChef/pull/446 [#448]: https://github.com/gchq/CyberChef/pull/448 [#449]: https://github.com/gchq/CyberChef/pull/449 +[#455]: https://github.com/gchq/CyberChef/pull/455 [#458]: https://github.com/gchq/CyberChef/pull/458 diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 686c9842..3e792b99 100755 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -374,6 +374,7 @@ "Generate QR Code", "Parse QR Code", "Haversine distance", + "Generate Lorem Ipsum", "Numberwang", "XKCD Random Number" ] diff --git a/src/core/lib/LoremIpsum.mjs b/src/core/lib/LoremIpsum.mjs new file mode 100644 index 00000000..d7fff69b --- /dev/null +++ b/src/core/lib/LoremIpsum.mjs @@ -0,0 +1,230 @@ +/** + * Lorem Ipsum generator. + * + * @author Klaxon [klaxon@veyr.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +/** + * Generate lorem ipsum paragraphs. + * + * @param {number} length + * @returns {string} + */ +export function GenerateParagraphs(length=3) { + const paragraphs = []; + while (paragraphs.length < length) { + const paragraphLength = getRandomLength(PARAGRAPH_LENGTH_MEAN, PARAGRAPH_LENGTH_STD_DEV); + const sentences = []; + while (sentences.length < paragraphLength) { + const sentenceLength = getRandomLength(SENTENCE_LENGTH_MEAN, SENTENCE_LENGTH_STD_DEV); + const sentence = getWords(sentenceLength); + sentences.push(formatSentence(sentence)); + } + paragraphs.push(formatParagraph(sentences)); + } + paragraphs[paragraphs.length-1] = paragraphs[paragraphs.length-1].slice(0, -2); + paragraphs[0] = replaceStart(paragraphs[0]); + return paragraphs.join(""); +} + + +/** + * Generate lorem ipsum sentences. + * + * @param {number} length + * @returns {string} + */ +export function GenerateSentences(length=3) { + const sentences = []; + while (sentences.length < length) { + const sentenceLength = getRandomLength(SENTENCE_LENGTH_MEAN, SENTENCE_LENGTH_STD_DEV); + const sentence = getWords(sentenceLength); + sentences.push(formatSentence(sentence)); + } + const paragraphs = sentencesToParagraphs(sentences); + return paragraphs.join(""); +} + + +/** + * Generate lorem ipsum words. + * + * @param {number} length + * @returns {string} + */ +export function GenerateWords(length=3) { + const words = getWords(length); + const sentences = wordsToSentences(words); + const paragraphs = sentencesToParagraphs(sentences); + return paragraphs.join(""); +} + + +/** + * Generate lorem ipsum bytes. + * + * @param {number} length + * @returns {string} + */ +export function GenerateBytes(length=3) { + const str = GenerateWords(length/3); + return str.slice(0, length); +} + + +/** + * Get array of randomly selected words from the lorem ipsum wordList. + * + * @param {number} length + * @returns {string[]} + * @private + */ +function getWords(length=3) { + const words = []; + let word; + let previousWord; + while (words.length < length){ + do { + word = wordList[Math.floor(Math.random() * wordList.length)]; + } while (previousWord === word); + words.push(word); + previousWord = word; + } + return words; +} + + +/** + * Convert an array of words into an array of sentences + * + * @param {string[]} words + * @returns {string[]} + * @private + */ +function wordsToSentences(words) { + const sentences = []; + while (words.length > 0) { + const sentenceLength = getRandomLength(SENTENCE_LENGTH_MEAN, SENTENCE_LENGTH_STD_DEV); + if (sentenceLength <= words.length) { + sentences.push(formatSentence(words.splice(0, sentenceLength))); + } else { + sentences.push(formatSentence(words.splice(0, words.length))); + } + } + return sentences; +} + + +/** + * Convert an array of sentences into an array of paragraphs + * + * @param {string[]} sentences + * @returns {string[]} + * @private + */ +function sentencesToParagraphs(sentences) { + const paragraphs = []; + while (sentences.length > 0) { + const paragraphLength = getRandomLength(PARAGRAPH_LENGTH_MEAN, PARAGRAPH_LENGTH_STD_DEV); + paragraphs.push(formatParagraph(sentences.splice(0, paragraphLength))); + } + paragraphs[paragraphs.length-1] = paragraphs[paragraphs.length-1].slice(0, -1); + paragraphs[0] = replaceStart(paragraphs[0]); + return paragraphs; +} + + +/** + * Format an array of words into a sentence. + * + * @param {string[]} words + * @returns {string} + * @private + */ +function formatSentence(words) { + // 0.35 chance of a comma being added randomly to the sentence. + if (Math.random() < PROBABILITY_OF_A_COMMA) { + const pos = Math.round(Math.random()*(words.length-1)); + words[pos] +=","; + } + let sentence = words.join(" "); + sentence = sentence.charAt(0).toUpperCase() + sentence.slice(1); + sentence += "."; + return sentence; +} + + +/** + * Format an array of sentences into a paragraph. + * + * @param {string[]} sentences + * @returns {string} + * @private + */ +function formatParagraph(sentences) { + let paragraph = sentences.join(" "); + paragraph += "\n\n"; + return paragraph; +} + + +/** + * Get a random number based on a mean and standard deviation. + * + * @param {number} mean + * @param {number} stdDev + * @returns {number} + * @private + */ +function getRandomLength(mean, stdDev) { + let length; + do { + length = Math.round((Math.random()*2-1)+(Math.random()*2-1)+(Math.random()*2-1)*stdDev+mean); + } while (length <= 0); + return length; +} + + +/** + * Replace first 5 words with "Lorem ipsum dolor sit amet" + * + * @param {string[]} str + * @returns {string[]} + * @private + */ +function replaceStart(str) { + let words = str.split(" "); + if (words.length > 5) { + words.splice(0, 5, "Lorem", "ipsum", "dolor", "sit", "amet"); + return words.join(" "); + } else { + const lorem = ["Lorem", "ipsum", "dolor", "sit", "amet"]; + words = lorem.slice(0, words.length); + str = words.join(" "); + str += "."; + return str; + } +} + + +const SENTENCE_LENGTH_MEAN = 15; +const SENTENCE_LENGTH_STD_DEV = 9; +const PARAGRAPH_LENGTH_MEAN = 5; +const PARAGRAPH_LENGTH_STD_DEV = 2; +const PROBABILITY_OF_A_COMMA = 0.35; + +const wordList = [ + "ad", "adipisicing", "aliqua", "aliquip", "amet", "anim", + "aute", "cillum", "commodo", "consectetur", "consequat", "culpa", + "cupidatat", "deserunt", "do", "dolor", "dolore", "duis", + "ea", "eiusmod", "elit", "enim", "esse", "est", + "et", "eu", "ex", "excepteur", "exercitation", "fugiat", + "id", "in", "incididunt", "ipsum", "irure", "labore", + "laboris", "laborum", "Lorem", "magna", "minim", "mollit", + "nisi", "non", "nostrud", "nulla", "occaecat", "officia", + "pariatur", "proident", "qui", "quis", "reprehenderit", "sint", + "sit", "sunt", "tempor", "ullamco", "ut", "velit", + "veniam", "voluptate", +]; diff --git a/src/core/operations/GenerateLoremIpsum.mjs b/src/core/operations/GenerateLoremIpsum.mjs new file mode 100644 index 00000000..fb5ecd17 --- /dev/null +++ b/src/core/operations/GenerateLoremIpsum.mjs @@ -0,0 +1,70 @@ +/** + * @author klaxon [klaxon@veyr.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; +import { GenerateParagraphs, GenerateSentences, GenerateWords, GenerateBytes } from "../lib/LoremIpsum"; + +/** + * Generate Lorem Ipsum operation + */ +class GenerateLoremIpsum extends Operation { + + /** + * GenerateLoremIpsum constructor + */ + constructor() { + super(); + + this.name = "Generate Lorem Ipsum"; + this.module = "Default"; + this.description = "Generate varying length lorem ipsum placeholder text."; + this.infoURL = "https://wikipedia.org/wiki/Lorem_ipsum"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Length", + "type": "number", + "value": "3" + }, + { + "name": "Length in", + "type": "option", + "value": ["Paragraphs", "Sentences", "Words", "Bytes"] + } + + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [length, lengthType] = args; + if (length < 1){ + throw new OperationError("Length must be greater than 0"); + } + switch (lengthType) { + case "Paragraphs": + return GenerateParagraphs(length); + case "Sentences": + return GenerateSentences(length); + case "Words": + return GenerateWords(length); + case "Bytes": + return GenerateBytes(length); + default: + throw new OperationError("Invalid length type"); + + } + } + +} + +export default GenerateLoremIpsum;