mirror of
https://github.com/gchq/CyberChef.git
synced 2026-03-17 20:31:37 -07:00
Add To Base91 and From Base91 operations (Resolves #1213)
This commit is contained in:
parent
d195a51e2e
commit
1f8a0d6323
4 changed files with 191 additions and 1 deletions
|
|
@ -37,6 +37,8 @@
|
|||
"From Base92",
|
||||
"To Base85",
|
||||
"From Base85",
|
||||
"To Base91",
|
||||
"From Base91",
|
||||
"To Base",
|
||||
"From Base",
|
||||
"To BCD",
|
||||
|
|
|
|||
94
src/core/operations/FromBase91.mjs
Normal file
94
src/core/operations/FromBase91.mjs
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
/**
|
||||
* @author rayane-ara []
|
||||
* @copyright Crown Copyright 2026
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
|
||||
/**
|
||||
* From Base91 operation
|
||||
*/
|
||||
class FromBase91 extends Operation {
|
||||
|
||||
/**
|
||||
* FromBase91 constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "From Base91";
|
||||
this.module = "Default";
|
||||
this.description = "Decodes Base91 encoded data back into its original binary format.<br><br>Example:<br><code>fPNKd</code> becomes <code>test</code>";
|
||||
this.infoURL = "https://en.wikipedia.org/wiki/Binary-to-text_encoding";
|
||||
this.inputType = "string";
|
||||
this.outputType = "byteArray";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
run(input, args) {
|
||||
const TABLE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!#$%&()*+,./:;<=>?@[]^_`{|}~"';
|
||||
|
||||
if (TABLE.length !== 91) {
|
||||
throw new OperationError("Base91 table is invalid (must contain exactly 91 characters).");
|
||||
}
|
||||
|
||||
const DECODE_TABLE = {};
|
||||
for (let i = 0; i < TABLE.length; i++) {
|
||||
DECODE_TABLE[TABLE[i]] = i;
|
||||
}
|
||||
|
||||
let b = 0; // Bit accumulator (buffer)
|
||||
let n = 0; // Number of bits currently in the buffer
|
||||
let v = -1; // Pending value waiting for its second character (-1 = none)
|
||||
const o = []; // Output byte array
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
const c = input[i];
|
||||
|
||||
// Skip characters that are not part of the Base91 alphabet
|
||||
if (!(c in DECODE_TABLE)) continue;
|
||||
|
||||
const p = DECODE_TABLE[c];
|
||||
|
||||
if (v < 0) {
|
||||
// First character of a pair: store the value and wait for the second
|
||||
v = p;
|
||||
} else {
|
||||
// Second character of a pair: reconstruct the encoded value
|
||||
v += p * 91;
|
||||
|
||||
// Push the lower 13 bits into the bit buffer
|
||||
b |= v << n;
|
||||
n += (v & 8191) > 88 ? 13 : 14;
|
||||
|
||||
// Reset v for the next pair
|
||||
v = -1;
|
||||
|
||||
// Extract all complete bytes from the buffer
|
||||
do {
|
||||
o.push(b & 255); // Extract the lowest 8 bits as a byte
|
||||
b >>= 8;
|
||||
n -= 8;
|
||||
} while (n > 7);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the final leftover character (if the input had an odd length)
|
||||
if (v > -1) {
|
||||
o.push((b | v << n) & 255);
|
||||
}
|
||||
|
||||
return o;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default FromBase91;
|
||||
|
||||
94
src/core/operations/ToBase91.mjs
Normal file
94
src/core/operations/ToBase91.mjs
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
/**
|
||||
* @author rayane-ara []
|
||||
* @copyright Crown Copyright 2026
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
|
||||
/**
|
||||
* To Base91 operation
|
||||
*/
|
||||
class ToBase91 extends Operation {
|
||||
|
||||
/**
|
||||
* ToBase91 constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "To Base91";
|
||||
this.module = "Default";
|
||||
this.description = "Encodes binary data into Base91 format. Base91 is an advanced method for encoding binary data as ASCII characters, resulting in a more compact string than Base64.<br><br>Example:<br><code>test</code> becomes <code>fPNKd</code>";
|
||||
this.infoURL = "https://en.wikipedia.org/wiki/Binary-to-text_encoding";
|
||||
this.inputType = "byteArray";
|
||||
this.outputType = "string";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
// Base91 alphabet: 91 printable ASCII characters
|
||||
const TABLE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!#$%&()*+,./:;<=>?@[]^_`{|}~"';
|
||||
|
||||
if (TABLE.length !== 91) {
|
||||
throw new OperationError("Base91 table is invalid (must contain exactly 91 characters).");
|
||||
}
|
||||
|
||||
let b = 0; // Bit accumulator (buffer)
|
||||
let n = 0; // Number of bits currently in the buffer
|
||||
let o = ""; // Output string
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
// Append the current byte to the bit buffer (LSB first)
|
||||
b |= input[i] << n;
|
||||
n += 8;
|
||||
|
||||
// We need at least 13 bits to attempt encoding
|
||||
if (n > 13) {
|
||||
// Extract the lower 13 bits
|
||||
let v = b & 8191; // 8191 = 0x1FFF = 2^13 - 1
|
||||
|
||||
if (v > 88) {
|
||||
// 13 bits are sufficient to encode this value:
|
||||
// consume 13 bits from the buffer
|
||||
b >>= 13;
|
||||
n -= 13;
|
||||
} else {
|
||||
// Value is too small (=< 88): a 14-bit encoding avoids
|
||||
// wasting range, so we take one extra bit instead
|
||||
v = b & 16383; // 16383 = 0x3FFF = 2^14 - 1
|
||||
b >>= 14;
|
||||
n -= 14;
|
||||
}
|
||||
|
||||
// Each value is encoded as exactly 2 characters
|
||||
o += TABLE[v % 91] + TABLE[Math.floor(v / 91)];
|
||||
}
|
||||
}
|
||||
|
||||
// Handle remaining bits in the buffer after the main loop
|
||||
if (n) {
|
||||
// Always emit at least one character for the leftover bits
|
||||
o += TABLE[b % 91];
|
||||
|
||||
// Emit a second character only if needed:
|
||||
// more than 7 leftover bits (i.e. a full byte was split), OR
|
||||
// the remaining value exceeds the single-character range (> 90)
|
||||
if (n > 7 || b > 90) {
|
||||
o += TABLE[Math.floor(b / 91)];
|
||||
}
|
||||
}
|
||||
|
||||
return o;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default ToBase91;
|
||||
|
||||
|
|
@ -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, 14);
|
||||
}),
|
||||
|
||||
it("chef.help: looks in description for matches too", () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue