Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/core/config/Categories.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
"Show Base64 offsets",
"To Base92",
"From Base92",
"To Base91",
"From Base91",
"To Base85",
"From Base85",
"To Base",
Expand Down
117 changes: 117 additions & 0 deletions src/core/lib/Base91.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/**
* 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
*/

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
*/
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
*/
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);
}
39 changes: 39 additions & 0 deletions src/core/operations/FromBase91.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* @author CyberChef Base91 Implementation
* @copyright Crown Copyright 2024
* @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;
39 changes: 39 additions & 0 deletions src/core/operations/ToBase91.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* @author CyberChef Base91 Implementation
* @copyright Crown Copyright 2024
* @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;
2 changes: 1 addition & 1 deletion tests/node/tests/nodeApi.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down
Loading