Merge branch 'master' into master

This commit is contained in:
GCHQ Developer 85297 2026-02-06 00:32:56 +00:00 committed by GitHub
commit 09c8cdd8d6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 1915 additions and 264 deletions

View file

@ -177,7 +177,7 @@ class Utils {
*/
static printable(str, preserveWs=false, onlyAscii=false) {
if (onlyAscii) {
return str.replace(/[^\x20-\x7f]/g, ".");
return str.replace(/[^\x20-\x7e]/g, ".");
}
// eslint-disable-next-line no-misleading-character-class

View file

@ -26,6 +26,8 @@
"From Base45",
"To Base58",
"From Base58",
"To Bech32",
"From Bech32",
"To Base62",
"From Base62",
"To Base64",

371
src/core/lib/Bech32.mjs Normal file
View file

@ -0,0 +1,371 @@
/**
* Pure JavaScript implementation of Bech32 and Bech32m encoding.
*
* Bech32 is defined in BIP-0173: https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki
* Bech32m is defined in BIP-0350: https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki
*
* @author Medjedtxm
* @copyright Crown Copyright 2025
* @license Apache-2.0
*/
import OperationError from "../errors/OperationError.mjs";
/** Bech32 character set (32 characters, excludes 1, b, i, o) */
const CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
/** Reverse lookup table for decoding */
const CHARSET_REV = {};
for (let i = 0; i < CHARSET.length; i++) {
CHARSET_REV[CHARSET[i]] = i;
}
/** Checksum constant for Bech32 (BIP-0173) */
const BECH32_CONST = 1;
/** Checksum constant for Bech32m (BIP-0350) */
const BECH32M_CONST = 0x2bc830a3;
/** Generator polynomial coefficients for checksum */
const GENERATOR = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3];
/**
* Compute the polymod checksum
* @param {number[]} values - Array of 5-bit values
* @returns {number} - Checksum value
*/
function polymod(values) {
let chk = 1;
for (const v of values) {
const top = chk >> 25;
chk = ((chk & 0x1ffffff) << 5) ^ v;
for (let i = 0; i < 5; i++) {
if ((top >> i) & 1) {
chk ^= GENERATOR[i];
}
}
}
return chk;
}
/**
* Expand HRP for checksum computation
* @param {string} hrp - Human-readable part (lowercase)
* @returns {number[]} - Expanded values
*/
function hrpExpand(hrp) {
const result = [];
for (let i = 0; i < hrp.length; i++) {
result.push(hrp.charCodeAt(i) >> 5);
}
result.push(0);
for (let i = 0; i < hrp.length; i++) {
result.push(hrp.charCodeAt(i) & 31);
}
return result;
}
/**
* Verify checksum of a Bech32/Bech32m string
* @param {string} hrp - Human-readable part (lowercase)
* @param {number[]} data - Data including checksum (5-bit values)
* @param {string} encoding - "Bech32" or "Bech32m"
* @returns {boolean} - True if checksum is valid
*/
function verifyChecksum(hrp, data, encoding) {
const constant = encoding === "Bech32m" ? BECH32M_CONST : BECH32_CONST;
return polymod(hrpExpand(hrp).concat(data)) === constant;
}
/**
* Create checksum for Bech32/Bech32m encoding
* @param {string} hrp - Human-readable part (lowercase)
* @param {number[]} data - Data values (5-bit)
* @param {string} encoding - "Bech32" or "Bech32m"
* @returns {number[]} - 6 checksum values
*/
function createChecksum(hrp, data, encoding) {
const constant = encoding === "Bech32m" ? BECH32M_CONST : BECH32_CONST;
const values = hrpExpand(hrp).concat(data).concat([0, 0, 0, 0, 0, 0]);
const mod = polymod(values) ^ constant;
const result = [];
for (let i = 0; i < 6; i++) {
result.push((mod >> (5 * (5 - i))) & 31);
}
return result;
}
/**
* Convert 8-bit bytes to 5-bit words
* @param {number[]|Uint8Array} data - Input bytes
* @returns {number[]} - 5-bit words
*/
export function toWords(data) {
let value = 0;
let bits = 0;
const result = [];
for (let i = 0; i < data.length; i++) {
value = (value << 8) | data[i];
bits += 8;
while (bits >= 5) {
bits -= 5;
result.push((value >> bits) & 31);
}
}
// Pad remaining bits
if (bits > 0) {
result.push((value << (5 - bits)) & 31);
}
return result;
}
/**
* Convert 5-bit words to 8-bit bytes
* @param {number[]} words - 5-bit words
* @returns {number[]} - Output bytes
*/
export function fromWords(words) {
let value = 0;
let bits = 0;
const result = [];
for (let i = 0; i < words.length; i++) {
value = (value << 5) | words[i];
bits += 5;
while (bits >= 8) {
bits -= 8;
result.push((value >> bits) & 255);
}
}
// Check for invalid padding per BIP-0173
// Condition 1: Cannot have 5+ bits remaining (would indicate incomplete byte)
if (bits >= 5) {
throw new OperationError("Invalid padding: too many bits remaining");
}
// Condition 2: Remaining padding bits must all be zero
if (bits > 0) {
const paddingValue = (value << (8 - bits)) & 255;
if (paddingValue !== 0) {
throw new OperationError("Invalid padding: non-zero bits in padding");
}
}
return result;
}
/**
* Encode data to Bech32/Bech32m string
*
* @param {string} hrp - Human-readable part
* @param {number[]|Uint8Array} data - Data bytes to encode
* @param {string} encoding - "Bech32" or "Bech32m"
* @param {boolean} segwit - If true, treat first byte as witness version (for Bitcoin SegWit)
* @returns {string} - Encoded Bech32/Bech32m string
*/
export function encode(hrp, data, encoding = "Bech32", segwit = false) {
// Validate HRP
if (!hrp || hrp.length === 0) {
throw new OperationError("Human-Readable Part (HRP) cannot be empty.");
}
// Check HRP characters (ASCII 33-126)
for (let i = 0; i < hrp.length; i++) {
const c = hrp.charCodeAt(i);
if (c < 33 || c > 126) {
throw new OperationError(`HRP contains invalid character at position ${i}. Only printable ASCII characters (33-126) are allowed.`);
}
}
// Convert HRP to lowercase
const hrpLower = hrp.toLowerCase();
let words;
if (segwit && data.length >= 2) {
// SegWit encoding: first byte is witness version (0-16), rest is witness program
const witnessVersion = data[0];
if (witnessVersion > 16) {
throw new OperationError(`Invalid witness version: ${witnessVersion}. Must be 0-16.`);
}
const witnessProgram = Array.prototype.slice.call(data, 1);
// Validate witness program length per BIP-0141
if (witnessProgram.length < 2 || witnessProgram.length > 40) {
throw new OperationError(`Invalid witness program length: ${witnessProgram.length}. Must be 2-40 bytes.`);
}
if (witnessVersion === 0 && witnessProgram.length !== 20 && witnessProgram.length !== 32) {
throw new OperationError(`Invalid witness program length for v0: ${witnessProgram.length}. Must be 20 or 32 bytes.`);
}
// Witness version is kept as single 5-bit value, program is converted
words = [witnessVersion].concat(toWords(witnessProgram));
} else {
// Generic encoding: convert all bytes to 5-bit words
words = toWords(data);
}
// Create checksum
const checksum = createChecksum(hrpLower, words, encoding);
// Build result string
let result = hrpLower + "1";
for (const w of words.concat(checksum)) {
result += CHARSET[w];
}
// Check maximum length (90 characters)
if (result.length > 90) {
throw new OperationError(`Encoded string exceeds maximum length of 90 characters (got ${result.length}). Consider using smaller input data.`);
}
return result;
}
/**
* Decode a Bech32/Bech32m string
*
* @param {string} str - Bech32/Bech32m encoded string
* @param {string} encoding - "Bech32", "Bech32m", or "Auto-detect"
* @returns {{hrp: string, data: number[]}} - Decoded HRP and data bytes
*/
export function decode(str, encoding = "Auto-detect") {
// Check for empty input
if (!str || str.length === 0) {
throw new OperationError("Input cannot be empty.");
}
// Check maximum length
if (str.length > 90) {
throw new OperationError(`Invalid Bech32 string: exceeds maximum length of 90 characters (got ${str.length}).`);
}
// Check for mixed case
const hasUpper = /[A-Z]/.test(str);
const hasLower = /[a-z]/.test(str);
if (hasUpper && hasLower) {
throw new OperationError("Invalid Bech32 string: mixed case is not allowed. Use all uppercase or all lowercase.");
}
// Convert to lowercase for processing
str = str.toLowerCase();
// Find separator (last occurrence of '1')
const sepIndex = str.lastIndexOf("1");
if (sepIndex === -1) {
throw new OperationError("Invalid Bech32 string: no separator '1' found.");
}
if (sepIndex === 0) {
throw new OperationError("Invalid Bech32 string: Human-Readable Part (HRP) cannot be empty.");
}
if (sepIndex + 7 > str.length) {
throw new OperationError("Invalid Bech32 string: data part is too short (minimum 6 characters for checksum).");
}
// Extract HRP and data part
const hrp = str.substring(0, sepIndex);
const dataPart = str.substring(sepIndex + 1);
// Validate HRP characters
for (let i = 0; i < hrp.length; i++) {
const c = hrp.charCodeAt(i);
if (c < 33 || c > 126) {
throw new OperationError(`HRP contains invalid character at position ${i}.`);
}
}
// Decode data characters to 5-bit values
const data = [];
for (let i = 0; i < dataPart.length; i++) {
const c = dataPart[i];
if (CHARSET_REV[c] === undefined) {
throw new OperationError(`Invalid character '${c}' at position ${sepIndex + 1 + i}.`);
}
data.push(CHARSET_REV[c]);
}
// Verify checksum
let usedEncoding;
if (encoding === "Bech32") {
if (!verifyChecksum(hrp, data, "Bech32")) {
throw new OperationError("Invalid Bech32 checksum.");
}
usedEncoding = "Bech32";
} else if (encoding === "Bech32m") {
if (!verifyChecksum(hrp, data, "Bech32m")) {
throw new OperationError("Invalid Bech32m checksum.");
}
usedEncoding = "Bech32m";
} else {
// Auto-detect: try Bech32 first, then Bech32m
if (verifyChecksum(hrp, data, "Bech32")) {
usedEncoding = "Bech32";
} else if (verifyChecksum(hrp, data, "Bech32m")) {
usedEncoding = "Bech32m";
} else {
throw new OperationError("Invalid Bech32/Bech32m string: checksum verification failed.");
}
}
// Remove checksum (last 6 values)
const words = data.slice(0, data.length - 6);
// Check if this is likely a SegWit address (Bitcoin, Litecoin, etc.)
// For SegWit, the first 5-bit word is the witness version (0-16)
// and should be extracted separately, not bit-converted with the rest
const segwitHrps = ["bc", "tb", "ltc", "tltc", "bcrt"];
const couldBeSegWit = segwitHrps.includes(hrp) && words.length > 0 && words[0] <= 16;
let bytes;
let witnessVersion = null;
if (couldBeSegWit) {
// Try SegWit decode first
try {
witnessVersion = words[0];
const programWords = words.slice(1);
const programBytes = fromWords(programWords);
// Validate SegWit witness program length (20 or 32 bytes for v0, 2-40 for others)
const validV0 = witnessVersion === 0 && (programBytes.length === 20 || programBytes.length === 32);
const validOther = witnessVersion !== 0 && programBytes.length >= 2 && programBytes.length <= 40;
if (validV0 || validOther) {
// Valid SegWit address
bytes = [witnessVersion, ...programBytes];
} else {
// Not valid SegWit, fall back to generic decode
witnessVersion = null;
bytes = fromWords(words);
}
} catch (e) {
// SegWit decode failed, try generic decode
witnessVersion = null;
try {
bytes = fromWords(words);
} catch (e2) {
throw new OperationError(`Failed to decode data: ${e2.message}`);
}
}
} else {
// Generic Bech32: convert all words
try {
bytes = fromWords(words);
} catch (e) {
throw new OperationError(`Failed to decode data: ${e.message}`);
}
}
return {
hrp: hrp,
data: bytes,
encoding: usedEncoding,
witnessVersion: witnessVersion
};
}

View file

@ -91,9 +91,7 @@ export function toJA4(bytes) {
let alpn = "00";
for (const ext of tlsr.handshake.value.extensions.value) {
if (ext.type.value === "application_layer_protocol_negotiation") {
alpn = parseFirstALPNValue(ext.value.data);
alpn = alpn.charAt(0) + alpn.charAt(alpn.length - 1);
if (alpn.charCodeAt(0) > 127) alpn = "99";
alpn = alpnFingerprint(parseFirstALPNValue(ext.value.data));
break;
}
}
@ -212,9 +210,7 @@ export function toJA4S(bytes) {
let alpn = "00";
for (const ext of tlsr.handshake.value.extensions.value) {
if (ext.type.value === "application_layer_protocol_negotiation") {
alpn = parseFirstALPNValue(ext.value.data);
alpn = alpn.charAt(0) + alpn.charAt(alpn.length - 1);
if (alpn.charCodeAt(0) > 127) alpn = "99";
alpn = alpnFingerprint(parseFirstALPNValue(ext.value.data));
break;
}
}
@ -262,3 +258,33 @@ function tlsVersionMapper(version) {
default: return "00"; // Unknown
}
}
/**
* Checks if a byte is ASCII alphanumeric (0-9, A-Z, a-z).
* @param {number} byte
* @returns {boolean}
*/
function isAlphanumeric(byte) {
return (byte >= 0x30 && byte <= 0x39) ||
(byte >= 0x41 && byte <= 0x5A) ||
(byte >= 0x61 && byte <= 0x7A);
}
/**
* Computes the 2-character ALPN fingerprint from raw ALPN bytes.
* If both first and last bytes are ASCII alphanumeric, returns their characters.
* Otherwise, returns first hex digit of first byte + last hex digit of last byte.
* @param {Uint8Array|null} rawBytes
* @returns {string}
*/
function alpnFingerprint(rawBytes) {
if (!rawBytes || rawBytes.length === 0) return "00";
const firstByte = rawBytes[0];
const lastByte = rawBytes[rawBytes.length - 1];
if (isAlphanumeric(firstByte) && isAlphanumeric(lastByte)) {
return String.fromCharCode(firstByte) + String.fromCharCode(lastByte);
}
const firstHex = firstByte.toString(16).padStart(2, "0");
const lastHex = lastByte.toString(16).padStart(2, "0");
return firstHex[0] + lastHex[1];
}

View file

@ -863,15 +863,15 @@ export function parseHighestSupportedVersion(bytes) {
}
/**
* Parses the application_layer_protocol_negotiation extension and returns the first value.
* Parses the application_layer_protocol_negotiation extension and returns the first value as raw bytes.
* @param {Uint8Array} bytes
* @returns {number}
* @returns {Uint8Array|null}
*/
export function parseFirstALPNValue(bytes) {
const s = new Stream(bytes);
const alpnExtLen = s.readInt(2);
if (alpnExtLen < 3) return "00";
if (alpnExtLen < 2) return null;
const strLen = s.readInt(1);
if (strLen < 2) return "00";
return s.readString(strLen);
if (strLen < 1) return null;
return s.getBytes(strLen);
}

View file

@ -0,0 +1,149 @@
/**
* @author Medjedtxm
* @copyright Crown Copyright 2025
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import { decode } from "../lib/Bech32.mjs";
import { toHex } from "../lib/Hex.mjs";
/**
* From Bech32 operation
*/
class FromBech32 extends Operation {
/**
* FromBech32 constructor
*/
constructor() {
super();
this.name = "From Bech32";
this.module = "Default";
this.description = "Bech32 is an encoding scheme primarily used for Bitcoin SegWit addresses (BIP-0173). It uses a 32-character alphabet that excludes easily confused characters (1, b, i, o) and includes a checksum for error detection.<br><br>Bech32m (BIP-0350) is an updated version used for Bitcoin Taproot addresses.<br><br>Auto-detect will attempt Bech32 first, then Bech32m if the checksum fails.<br><br>Output format options allow you to see the Human-Readable Part (HRP) along with the decoded data.";
this.infoURL = "https://wikipedia.org/wiki/Bech32";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
"name": "Encoding",
"type": "option",
"value": ["Auto-detect", "Bech32", "Bech32m"]
},
{
"name": "Output Format",
"type": "option",
"value": ["Raw", "Hex", "Bitcoin scriptPubKey", "HRP: Hex", "JSON"]
}
];
this.checks = [
{
// Bitcoin mainnet SegWit/Taproot addresses
pattern: "^bc1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{6,87}$",
flags: "i",
args: ["Auto-detect", "Hex"]
},
{
// Bitcoin testnet addresses
pattern: "^tb1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{6,87}$",
flags: "i",
args: ["Auto-detect", "Hex"]
},
{
// AGE public keys
pattern: "^age1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{6,87}$",
flags: "i",
args: ["Auto-detect", "HRP: Hex"]
},
{
// AGE secret keys
pattern: "^AGE-SECRET-KEY-1[QPZRY9X8GF2TVDW0S3JN54KHCE6MUA7L]{6,87}$",
flags: "",
args: ["Auto-detect", "HRP: Hex"]
},
{
// Litecoin mainnet addresses
pattern: "^ltc1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{6,87}$",
flags: "i",
args: ["Auto-detect", "Hex"]
},
{
// Generic bech32 pattern
pattern: "^[a-z]{1,83}1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{6,}$",
flags: "i",
args: ["Auto-detect", "Hex"]
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const encoding = args[0];
const outputFormat = args[1];
input = input.trim();
if (input.length === 0) {
return "";
}
const decoded = decode(input, encoding);
// Format output based on selected option
switch (outputFormat) {
case "Raw":
return decoded.data.map(b => String.fromCharCode(b)).join("");
case "Hex":
return toHex(decoded.data, "");
case "Bitcoin scriptPubKey": {
// Convert to Bitcoin scriptPubKey format as shown in BIP-0173/BIP-0350
// Format: [OP_version][length][witness_program]
// OP_0 = 0x00, OP_1-OP_16 = 0x51-0x60
if (decoded.witnessVersion === null || decoded.data.length < 2) {
// Not a SegWit address, fall back to hex
return toHex(decoded.data, "");
}
const witnessVersion = decoded.data[0];
const witnessProgram = decoded.data.slice(1);
// Convert witness version to OP code
let opCode;
if (witnessVersion === 0) {
opCode = 0x00; // OP_0
} else if (witnessVersion >= 1 && witnessVersion <= 16) {
opCode = 0x50 + witnessVersion; // OP_1 = 0x51, ..., OP_16 = 0x60
} else {
// Invalid witness version, fall back to hex
return toHex(decoded.data, "");
}
// Build scriptPubKey: [OP_version][length][program]
const scriptPubKey = [opCode, witnessProgram.length, ...witnessProgram];
return toHex(scriptPubKey, "");
}
case "HRP: Hex":
return `${decoded.hrp}: ${toHex(decoded.data, "")}`;
case "JSON":
return JSON.stringify({
hrp: decoded.hrp,
encoding: decoded.encoding,
data: toHex(decoded.data, "")
}, null, 2);
default:
return toHex(decoded.data, "");
}
}
}
export default FromBech32;

View file

@ -43,7 +43,7 @@ class FromHexdump extends Operation {
*/
run(input, args) {
const output = [],
regex = /^\s*(?:[\dA-F]{4,16}h?:?)?[ \t]+((?:[\dA-F]{2} ){1,8}(?:[ \t]|[\dA-F]{2}-)(?:[\dA-F]{2} ){1,8}|(?:[\dA-F]{4} )*[\dA-F]{4}|(?:[\dA-F]{2} )*[\dA-F]{2})/igm;
regex = /^\s*(?:[\dA-F]{4,16}h?:?)?[ \t]+((?:[\dA-F]{2} ){1,8}(?:[ \t]|[\dA-F]{2}-)(?:[\dA-F]{2} ){1,8}|(?:[\dA-F]{4} )+(?:[\dA-F]{2})?|(?:[\dA-F]{2} )*[\dA-F]{2})/igm;
let block, line;
while ((block = regex.exec(input))) {

View file

@ -33,7 +33,7 @@ class ToBase85 extends Operation {
value: ALPHABET_OPTIONS
},
{
name: "Include delimeter",
name: "Include delimiter",
type: "boolean",
value: false
}

View file

@ -0,0 +1,92 @@
/**
* @author Medjedtxm
* @copyright Crown Copyright 2025
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import { encode } from "../lib/Bech32.mjs";
import { fromHex } from "../lib/Hex.mjs";
/**
* To Bech32 operation
*/
class ToBech32 extends Operation {
/**
* ToBech32 constructor
*/
constructor() {
super();
this.name = "To Bech32";
this.module = "Default";
this.description = "Bech32 is an encoding scheme primarily used for Bitcoin SegWit addresses (BIP-0173). It uses a 32-character alphabet that excludes easily confused characters (1, b, i, o) and includes a checksum for error detection.<br><br>Bech32m (BIP-0350) is an updated version that fixes a weakness in the original Bech32 checksum and is used for Bitcoin Taproot addresses.<br><br>The Human-Readable Part (HRP) identifies the network or purpose (e.g., 'bc' for Bitcoin mainnet, 'tb' for testnet, 'age' for AGE encryption keys).<br><br>Maximum output length is 90 characters as per specification.";
this.infoURL = "https://wikipedia.org/wiki/Bech32";
this.inputType = "ArrayBuffer";
this.outputType = "string";
this.args = [
{
"name": "Human-Readable Part (HRP)",
"type": "string",
"value": "bc"
},
{
"name": "Encoding",
"type": "option",
"value": ["Bech32", "Bech32m"]
},
{
"name": "Input Format",
"type": "option",
"value": ["Raw bytes", "Hex"]
},
{
"name": "Mode",
"type": "option",
"value": ["Generic", "Bitcoin SegWit"]
},
{
"name": "Witness Version",
"type": "number",
"value": 0,
"hint": "SegWit witness version (0-16). Only used in Bitcoin SegWit mode."
}
];
}
/**
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const hrp = args[0];
const encoding = args[1];
const inputFormat = args[2];
const mode = args[3];
const witnessVersion = args[4];
let inputArray;
if (inputFormat === "Hex") {
// Convert hex string to bytes
const hexStr = new TextDecoder().decode(new Uint8Array(input)).replace(/\s/g, "");
inputArray = fromHex(hexStr);
} else {
inputArray = new Uint8Array(input);
}
if (mode === "Bitcoin SegWit") {
// Prepend witness version to the input data
const withVersion = new Uint8Array(inputArray.length + 1);
withVersion[0] = witnessVersion;
withVersion.set(inputArray, 1);
return encode(hrp, withVersion, encoding, true);
}
return encode(hrp, inputArray, encoding, false);
}
}
export default ToBech32;

View file

@ -66,7 +66,7 @@ export function removeSubheadingsFromArray(array) {
* @param str
*/
export function sanitise(str) {
return str.replace(/ /g, "").toLowerCase();
return str.replace(/[/\s.-]/g, "").toLowerCase();
}

View file

@ -650,7 +650,7 @@ class App {
// const compareURL = `https://github.com/gchq/CyberChef/compare/v${prev.join(".")}...v${PKG_VERSION}`;
let compileInfo = `<a href='https://github.com/gchq/CyberChef/blob/master/CHANGELOG.md'>Last build: ${timeSinceCompile.substr(0, 1).toUpperCase() + timeSinceCompile.substr(1)} ago</a>`;
let compileInfo = `<a href='https://github.com/gchq/CyberChef/blob/master/CHANGELOG.md'>Last build: ${timeSinceCompile.substring(0, 1).toUpperCase() + timeSinceCompile.substring(1)} ago</a>`;
if (window.compileMessage !== "") {
compileInfo += " - " + window.compileMessage;

View file

@ -1,6 +1,5 @@
import sm from "sitemap";
import OperationConfig from "../../core/config/OperationConfig.json" assert {type: "json"};
import OperationConfig from "../../core/config/OperationConfig.json" assert { type: "json" };
/**
* Generates an XML sitemap for all CyberChef operations and a number of recipes.
@ -10,25 +9,25 @@ import OperationConfig from "../../core/config/OperationConfig.json" assert {typ
* @license Apache-2.0
*/
const smStream = new sm.SitemapStream({
hostname: "https://gchq.github.io/CyberChef",
});
const baseUrl = "https://gchq.github.io/CyberChef/";
const smStream = new sm.SitemapStream({});
smStream.write({
url: "/",
url: baseUrl,
changefreq: "weekly",
priority: 1.0
priority: 1.0,
});
for (const op in OperationConfig) {
smStream.write({
url: `/?op=${encodeURIComponent(op)}`,
url: `${baseUrl}?op=${encodeURIComponent(op)}`,
changeFreq: "yearly",
priority: 0.5
priority: 0.5,
});
}
smStream.end();
sm.streamToPromise(smStream).then(
buffer => console.log(buffer.toString()) // eslint-disable-line no-console
(buffer) => console.log(buffer.toString()), // eslint-disable-line no-console
);