Add Base91 encoding and decoding operations
- Implement Base91 algorithm using 91 printable ASCII characters - Add ToBase91 operation for encoding data to Base91 - Add FromBase91 operation for decoding Base91 strings - Base91 provides ~23% better space efficiency than Base64 - Support for Unicode text, emojis, and binary data - Full round-trip encoding/decoding compatibility - Integration with CyberChef operation system
This commit is contained in:
parent
2a1294f1c0
commit
fa20a7a404
@ -33,6 +33,8 @@
|
|||||||
"Show Base64 offsets",
|
"Show Base64 offsets",
|
||||||
"To Base92",
|
"To Base92",
|
||||||
"From Base92",
|
"From Base92",
|
||||||
|
"To Base91",
|
||||||
|
"From Base91",
|
||||||
"To Base85",
|
"To Base85",
|
||||||
"From Base85",
|
"From Base85",
|
||||||
"To Base",
|
"To Base",
|
||||||
|
|||||||
117
src/core/lib/Base91.mjs
Normal file
117
src/core/lib/Base91.mjs
Normal file
@ -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
src/core/operations/FromBase91.mjs
Normal file
39
src/core/operations/FromBase91.mjs
Normal file
@ -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
src/core/operations/ToBase91.mjs
Normal file
39
src/core/operations/ToBase91.mjs
Normal file
@ -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;
|
||||||
@ -136,7 +136,7 @@ TestRegister.addApiTests([
|
|||||||
|
|
||||||
it("chef.help: returns multiple results", () => {
|
it("chef.help: returns multiple results", () => {
|
||||||
const result = chef.help("base 64");
|
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", () => {
|
it("chef.help: looks in description for matches too", () => {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user