diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 434c8bb619..61b6d64d27 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -33,6 +33,8 @@ "Show Base64 offsets", "To Base92", "From Base92", + "To Base91", + "From Base91", "To Base85", "From Base85", "To Base", diff --git a/src/core/lib/Base91.mjs b/src/core/lib/Base91.mjs new file mode 100644 index 0000000000..da9e296f4f --- /dev/null +++ b/src/core/lib/Base91.mjs @@ -0,0 +1,125 @@ +/** + * Base91 resources. + * + * Based on the original basE91 algorithm by Joachim Henke + * http://base91.sourceforge.net/ + * + * @author CyberChef Base91 Implementation + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + * @modified-by Izai Alejandro Zalles Merino (ialejandrozalles) + * @modified-date 2025-10-01 + * © 2025 Izai Alejandro Zalles Merino + + */ + +import OperationError from "../errors/OperationError.mjs"; + +/** + * Base91 alphabet - 91 printable ASCII characters + */ +const BASE91_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!#$%&()*+,./:;<=>?@[]^_`{|}~\""; + +/** + * Decode table for Base91 + */ +const BASE91_DECODE_TABLE = new Array(256).fill(-1); +for (let i = 0; i < BASE91_ALPHABET.length; i++) { + BASE91_DECODE_TABLE[BASE91_ALPHABET.charCodeAt(i)] = i; +} + +/** + * Encode bytes to Base91 + * + * @param {Uint8Array} data - Input byte array + * @returns {string} Base91 encoded string + * @modified-by Izai Alejandro Zalles Merino (ialejandrozalles) + + */ +export function encodeBase91(data) { + let accumulator = 0; + let accumulatorBits = 0; + let output = ""; + + for (let i = 0; i < data.length; i++) { + accumulator |= data[i] << accumulatorBits; + accumulatorBits += 8; + + if (accumulatorBits > 13) { + let value = accumulator & 8191; + + if (value > 88) { + accumulator >>= 13; + accumulatorBits -= 13; + } else { + value = accumulator & 16383; + accumulator >>= 14; + accumulatorBits -= 14; + } + + output += BASE91_ALPHABET[value % 91] + BASE91_ALPHABET[Math.floor(value / 91)]; + } + } + + if (accumulatorBits > 0) { + output += BASE91_ALPHABET[accumulator % 91]; + + if (accumulatorBits > 7 || accumulator > 90) { + output += BASE91_ALPHABET[Math.floor(accumulator / 91)]; + } + } + + return output; +} + +/** + * Decode Base91 string to bytes + * + * @param {string} str - Base91 encoded string + * @returns {Uint8Array} Decoded byte array + * @modified-by Izai Alejandro Zalles Merino (ialejandrozalles) + + */ +export function decodeBase91(str) { + let accumulator = 0; + let accumulatorBits = 0; + let value = -1; + const output = []; + + for (let i = 0; i < str.length; i++) { + const charCode = str.charCodeAt(i); + const decodeValue = BASE91_DECODE_TABLE[charCode]; + + if (decodeValue === -1) { + throw new OperationError(`Invalid Base91 character: ${str[i]}`); + } + + if (value === -1) { + value = decodeValue; + } else { + value += decodeValue * 91; + accumulator |= (value << accumulatorBits); + + if (value > 88) { + accumulatorBits += 13; + } else { + accumulatorBits += 14; + } + + value = -1; + + while (accumulatorBits > 7) { + output.push(accumulator & 255); + accumulator >>= 8; + accumulatorBits -= 8; + } + } + } + + if (value !== -1) { + accumulator |= value << accumulatorBits; + output.push(accumulator & 255); + } + + return new Uint8Array(output); +} diff --git a/src/core/operations/FromBase91.mjs b/src/core/operations/FromBase91.mjs new file mode 100644 index 0000000000..921434dd76 --- /dev/null +++ b/src/core/operations/FromBase91.mjs @@ -0,0 +1,39 @@ +/** + * @author Izai Alejandro Zalles Merino (ialejandrozalles) + * @copyright © 2025 Izai Alejandro Zalles Merino + * @license Apache-2.0 + */ + +import { decodeBase91 } from "../lib/Base91.mjs"; +import Operation from "../Operation.mjs"; + +/** + * From Base91 operation + */ +class FromBase91 extends Operation { + /** + * FromBase91 constructor + */ + constructor() { + super(); + + this.name = "From Base91"; + this.module = "Default"; + this.description = "Base91 is a binary-to-text encoding scheme that uses 91 printable ASCII characters. It provides better space efficiency than Base64 while maintaining readability. This operation decodes Base91-encoded text back to its original binary data."; + this.infoURL = "https://en.wikipedia.org/wiki/Binary-to-text_encoding#Encoding_standards"; + this.inputType = "string"; + this.outputType = "ArrayBuffer"; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + const decoded = decodeBase91(input); + return decoded.buffer.slice(decoded.byteOffset, decoded.byteOffset + decoded.byteLength); + } +} + +export default FromBase91; diff --git a/src/core/operations/ToBase91.mjs b/src/core/operations/ToBase91.mjs new file mode 100644 index 0000000000..88fa5cfb5b --- /dev/null +++ b/src/core/operations/ToBase91.mjs @@ -0,0 +1,39 @@ +/** + * @author Izai Alejandro Zalles Merino (ialejandrozalles) + * @copyright © 2025 Izai Alejandro Zalles Merino + * @license Apache-2.0 + */ + +import { encodeBase91 } from "../lib/Base91.mjs"; +import Operation from "../Operation.mjs"; + +/** + * To Base91 operation + */ +class ToBase91 extends Operation { + /** + * ToBase91 constructor + */ + constructor() { + super(); + + this.name = "To Base91"; + this.module = "Default"; + this.description = "Base91 is a binary-to-text encoding scheme that uses 91 printable ASCII characters. It provides better space efficiency than Base64 while maintaining readability. Base91 encodes arbitrary binary data using characters A-Z, a-z, 0-9, and various symbols (excluding hyphen, backslash, and single quote)."; + this.infoURL = "https://en.wikipedia.org/wiki/Binary-to-text_encoding#Encoding_standards"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const data = new Uint8Array(input); + return encodeBase91(data); + } +} + +export default ToBase91; diff --git a/tests/node/tests/nodeApi.mjs b/tests/node/tests/nodeApi.mjs index 29a47ffc8d..2aa1c5fec0 100644 --- a/tests/node/tests/nodeApi.mjs +++ b/tests/node/tests/nodeApi.mjs @@ -136,7 +136,7 @@ TestRegister.addApiTests([ it("chef.help: returns multiple results", () => { const result = chef.help("base 64"); - assert.strictEqual(result.length, 13); + assert.strictEqual(result.length, 15); }), it("chef.help: looks in description for matches too", () => {