mirror of
https://github.com/gchq/CyberChef.git
synced 2026-04-28 00:10:53 -07:00
Add Text/Integer Converter operation (#2213)
Some checks are pending
Master Build, Test & Deploy / main (push) Waiting to run
Some checks are pending
Master Build, Test & Deploy / main (push) Waiting to run
Co-authored-by: GCHQDeveloper581 <63102987+GCHQDeveloper581@users.noreply.github.com> - Additional test case added.
This commit is contained in:
parent
81b3e9abd4
commit
f759f4c43b
4 changed files with 324 additions and 0 deletions
|
|
@ -41,6 +41,7 @@
|
|||
"From Base",
|
||||
"To BCD",
|
||||
"From BCD",
|
||||
"Text-Integer Conversion",
|
||||
"To HTML Entity",
|
||||
"From HTML Entity",
|
||||
"URL Encode",
|
||||
|
|
|
|||
123
src/core/operations/TextIntegerConverter.mjs
Normal file
123
src/core/operations/TextIntegerConverter.mjs
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
/**
|
||||
* @author p-leriche [philip.leriche@cantab.net]
|
||||
* @copyright Crown Copyright 2025
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
|
||||
/* ---------- helper functions ---------- */
|
||||
|
||||
/**
|
||||
* Convert text to BigInt (big-endian byte interpretation)
|
||||
*/
|
||||
function textToBigInt(text) {
|
||||
if (text.length === 0) return 0n;
|
||||
|
||||
let result = 0n;
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
const charCode = BigInt(text.charCodeAt(i));
|
||||
if (charCode > 255n) {
|
||||
throw new OperationError(
|
||||
`Character at position ${i} exceeds Latin-1 range (0-255).\n` +
|
||||
"Only ASCII and Latin-1 characters are supported.");
|
||||
}
|
||||
result = (result << 8n) | charCode;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert BigInt to text (big-endian byte interpretation)
|
||||
*/
|
||||
function bigIntToText(value) {
|
||||
if (value === 0n) return "";
|
||||
|
||||
const bytes = [];
|
||||
let num = value;
|
||||
|
||||
while (num > 0n) {
|
||||
bytes.unshift(Number(num & 0xFFn));
|
||||
num >>= 8n;
|
||||
}
|
||||
|
||||
return String.fromCharCode(...bytes);
|
||||
}
|
||||
|
||||
/* ---------- operation class ---------- */
|
||||
|
||||
/**
|
||||
* Text/Integer Converter operation
|
||||
*/
|
||||
class TextIntegerConverter extends Operation {
|
||||
/**
|
||||
* TextIntegerConverter constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.description =
|
||||
"Converts between text strings and large integers (decimal or hexadecimal).<br><br>" +
|
||||
"Text is interpreted as a big-endian sequence of character codes. For example:<br>" +
|
||||
"ABC is 0x414243 (hex) is 4276803 (decimal)<br>" +
|
||||
"<b>Input format detection:</b><br>" +
|
||||
"Decimal: digits 0-9 only<br>" +
|
||||
"Hexadecimal: 0x... prefix<br>" +
|
||||
"Quoted or unquoted text: treated as string<br><br>" +
|
||||
"<b>Character limitations:</b><br>" +
|
||||
"Text input may only contain ASCII and Latin-1 characters (code point < 256).<br>" +
|
||||
"Multi-byte Unicode characters will generate an error.<br><br>." ;
|
||||
this.infoURL = "https://wikipedia.org/wiki/Endianness";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Output format",
|
||||
type: "option",
|
||||
value: ["String", "Decimal", "Hexadecimal"]
|
||||
}
|
||||
];
|
||||
this.name = "Text-Integer Conversion";
|
||||
this.module = "Default";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const outputFormat = args[0];
|
||||
const trimmed = input.trim();
|
||||
|
||||
let bigIntValue;
|
||||
|
||||
if (!trimmed) {
|
||||
// Null input - treat as zero
|
||||
bigIntValue = 0;
|
||||
} else if (/^0x[0-9a-f]+$/i.test(trimmed) ||
|
||||
/^[+-]?[0-9]+$/.test(trimmed)) {
|
||||
// Hex or decimal integer
|
||||
bigIntValue = BigInt(trimmed);
|
||||
} else if (/^["'].*["']$/.test(trimmed)) {
|
||||
// Quoted string: Remove quotes and convert text to BigInt
|
||||
const text = trimmed.slice(1, -1);
|
||||
bigIntValue = textToBigInt(text);
|
||||
} else {
|
||||
// Assume it's unquoted text
|
||||
bigIntValue = textToBigInt(trimmed);
|
||||
}
|
||||
|
||||
// Convert to output format
|
||||
if (outputFormat === "String") {
|
||||
return bigIntToText(bigIntValue);
|
||||
} else if (outputFormat === "Decimal") {
|
||||
return bigIntValue.toString();
|
||||
} else { // Hexadecimal
|
||||
return "0x" + bigIntValue.toString(16);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default TextIntegerConverter;
|
||||
|
|
@ -165,6 +165,7 @@ import "./tests/SymmetricDifference.mjs";
|
|||
import "./tests/TakeNthBytes.mjs";
|
||||
import "./tests/Template.mjs";
|
||||
import "./tests/TextEncodingBruteForce.mjs";
|
||||
import "./tests/TextIntegerConverter.mjs";
|
||||
import "./tests/ToFromInsensitiveRegex.mjs";
|
||||
import "./tests/TranslateDateTimeFormat.mjs";
|
||||
import "./tests/Typex.mjs";
|
||||
|
|
|
|||
199
tests/operations/tests/TextIntegerConverter.mjs
Normal file
199
tests/operations/tests/TextIntegerConverter.mjs
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
/**
|
||||
* Text-Integer Conversion tests.
|
||||
*
|
||||
* @author p-leriche [philip.leriche@cantab.net]
|
||||
*
|
||||
* @copyright Crown Copyright 2025
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
import TestRegister from "../../lib/TestRegister.mjs";
|
||||
|
||||
TestRegister.addTests([
|
||||
{
|
||||
name: "Text-Integer Conversion quoted string to decimal",
|
||||
input: "\"ABC\"",
|
||||
expectedOutput: "4276803",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Text-Integer Conversion",
|
||||
args: ["Decimal"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Text-Integer Conversion quoted string to hexadecimal",
|
||||
input: "\"ABC\"",
|
||||
expectedOutput: "0x414243",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Text-Integer Conversion",
|
||||
args: ["Hexadecimal"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Text-Integer Conversion single quoted string to decimal",
|
||||
input: "'Hello'",
|
||||
expectedOutput: "310939249775",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Text-Integer Conversion",
|
||||
args: ["Decimal"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Text-Integer Conversion decimal to string",
|
||||
input: "4276803",
|
||||
expectedOutput: "ABC",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Text-Integer Conversion",
|
||||
args: ["String"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Text-Integer Conversion hexadecimal to string",
|
||||
input: "0x48656C6C6F",
|
||||
expectedOutput: "Hello",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Text-Integer Conversion",
|
||||
args: ["String"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Text-Integer Conversion round-trip string.decimal.string",
|
||||
input: "\"Test\"",
|
||||
expectedOutput: "Test",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Text-Integer Conversion",
|
||||
args: ["Decimal"],
|
||||
},
|
||||
{
|
||||
op: "Text-Integer Conversion",
|
||||
args: ["String"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Text-Integer Conversion round-trip string.hex.string",
|
||||
input: "\"CyberChef\"",
|
||||
expectedOutput: "CyberChef",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Text-Integer Conversion",
|
||||
args: ["Hexadecimal"],
|
||||
},
|
||||
{
|
||||
op: "Text-Integer Conversion",
|
||||
args: ["String"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Text-Integer Conversion implicit round trip string-string Latin-1",
|
||||
input: "U+00FF",
|
||||
expectedOutput: "U+00FF", // U+00FF (Latin small letter y with diaeresis)
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Unescape Unicode Characters",
|
||||
args: ["U+"],
|
||||
},
|
||||
{
|
||||
op: "Text-Integer Conversion",
|
||||
args: ["String"],
|
||||
},
|
||||
{
|
||||
op: "Escape Unicode Characters",
|
||||
args: ["U+", false, 4, true],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Text-Integer Conversion unquoted text to decimal",
|
||||
input: "Hi",
|
||||
expectedOutput: "18537",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Text-Integer Conversion",
|
||||
args: ["Decimal"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Text-Integer Conversion single character",
|
||||
input: "\"A\"",
|
||||
expectedOutput: "65",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Text-Integer Conversion",
|
||||
args: ["Decimal"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Text-Integer Conversion hex to decimal conversion",
|
||||
input: "0xFF",
|
||||
expectedOutput: "255",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Text-Integer Conversion",
|
||||
args: ["Decimal"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Text-Integer Conversion decimal to hex conversion",
|
||||
input: "255",
|
||||
expectedOutput: "0xff",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Text-Integer Conversion",
|
||||
args: ["Hexadecimal"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Text-Integer Conversion large number to string",
|
||||
input: "113091951015816448506195587157728348242683688608116",
|
||||
expectedOutput: "Mary had a little cat",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Text-Integer Conversion",
|
||||
args: ["String"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Text-Integer Conversion whitespace handling (quoted)",
|
||||
input: "\" test \"",
|
||||
expectedOutput: "2314978187545944096",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Text-Integer Conversion",
|
||||
args: ["Decimal"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Text-Integer Conversion non-Latin1 character in input",
|
||||
input: "61 ce 93 61",
|
||||
expectedOutput:
|
||||
`Character at position 1 exceeds Latin-1 range (0-255).
|
||||
Only ASCII and Latin-1 characters are supported.`,
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "From Hex",
|
||||
"args": ["Auto"]
|
||||
},
|
||||
{
|
||||
op: "Text-Integer Conversion",
|
||||
args: ["Decimal"],
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
Loading…
Add table
Add a link
Reference in a new issue