diff --git a/package-lock.json b/package-lock.json index 8dd38b7a..974f9810 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "arrive": "^2.4.1", "avsc": "^5.7.7", "bcryptjs": "^2.4.3", + "bencodec": "^3.0.1", "bignumber.js": "^9.1.2", "blakejs": "^1.2.1", "bootstrap": "4.6.2", @@ -5403,6 +5404,15 @@ "dev": true, "license": "MIT" }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -5473,6 +5483,24 @@ "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==", "license": "MIT" }, + "node_modules/bencode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/bencode/-/bencode-4.0.0.tgz", + "integrity": "sha512-AERXw18df0pF3ziGOCyUjqKZBVNH8HV3lBxnx5w0qtgMIk4a1wb9BkcCQbkp9Zstfrn/dzRwl7MmUHHocX3sRQ==", + "license": "MIT", + "dependencies": { + "uint8-util": "^2.2.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/bencodec": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/bencodec/-/bencodec-3.0.1.tgz", + "integrity": "sha512-5Ntc3E7R1vSnBcOddG65L9kObEmZGI0Vool6z/7apwO5Hc9OlziwK0LyxvaTK5Il+nSWNxlVSuh2zJM+TN9O3g==", + "license": "MIT" + }, "node_modules/big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -17850,6 +17878,15 @@ "integrity": "sha512-w+VZSp8hSZ/xWZfZNMppWNF6iqY+dcMYtG5CpwRDgxi94HIE6ematSdkzHGzVC4SDEaTsG65zrajN+oKoWG6ew==", "license": "MIT" }, + "node_modules/uint8-util": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/uint8-util/-/uint8-util-2.2.5.tgz", + "integrity": "sha512-/QxVQD7CttWpVUKVPz9znO+3Dd4BdTSnFQ7pv/4drVhC9m4BaL2LFHTkJn6EsYoxT79VDq/2Gg8L0H22PrzyMw==", + "license": "MIT", + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, "node_modules/ultron": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", diff --git a/package.json b/package.json index feb18bc8..4b116619 100644 --- a/package.json +++ b/package.json @@ -105,6 +105,7 @@ "arrive": "^2.4.1", "avsc": "^5.7.7", "bcryptjs": "^2.4.3", + "bencodec": "^3.0.1", "bignumber.js": "^9.1.2", "blakejs": "^1.2.1", "bootstrap": "4.6.2", diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 7f9591e0..a44b65a7 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -162,7 +162,9 @@ "Typex", "Lorenz", "Colossus", - "SIGABA" + "SIGABA", + "Bencode Encode", + "Bencode Decode" ] }, { diff --git a/src/core/operations/BencodeDecode.mjs b/src/core/operations/BencodeDecode.mjs new file mode 100644 index 00000000..a4a680bf --- /dev/null +++ b/src/core/operations/BencodeDecode.mjs @@ -0,0 +1,63 @@ +/** + * @author jg42526 + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import bencodec from "bencodec"; + +/** + * URL Decode operation + */ +class BencodeDecode extends Operation { + + /** + * URLDecode constructor + */ + constructor() { + super(); + + this.name = "Bencode Decode"; + this.module = "Encodings"; + this.description = "Decodes a Bencoded string.

e.g. 7:bencode becomes bencode"; + this.infoURL = "https://en.wikipedia.org/wiki/Bencode"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + if (input) return toStringRepresentation(bencodec.decode(input, { stringify: true })); + return ""; + } + +} + +export default BencodeDecode; + +/** + * Returns string representation of object + */ +function toStringRepresentation(value) { + if (typeof value === "string") { + return value; + } + + if (typeof value === "number" || typeof value === "boolean") { + return String(value); + } + + if (Array.isArray(value) || (value !== null && typeof value === "object")) { + // For arrays and objects, output JSON string + return JSON.stringify(value); + } + + // For other types (undefined, null), handle as you see fit, e.g.: + return String(value); +} diff --git a/src/core/operations/BencodeEncode.mjs b/src/core/operations/BencodeEncode.mjs new file mode 100644 index 00000000..91d519ed --- /dev/null +++ b/src/core/operations/BencodeEncode.mjs @@ -0,0 +1,55 @@ +/** + * @author jg42526 + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import bencodec from "bencodec"; + +/** + * URL Decode operation + */ +class BencodeEncode extends Operation { + + /** + * URLDecode constructor + */ + constructor() { + super(); + + this.name = "Bencode Encode"; + this.module = "Encodings"; + this.description = "Bencodes a string.

e.g. bencode becomes 7:bencode"; + this.infoURL = "https://en.wikipedia.org/wiki/Bencode"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return bencodec.encode(parseValue(input), { stringify: true }); + } + +} + +export default BencodeEncode; + +/** + * Parses string, returns appropraite data structure + */ +function parseValue(str) { + const trimmed = str.trim(); + try { + // Attempt to parse with JSON.parse + return JSON.parse(trimmed); + } catch (e) { + // If JSON.parse fails, treat input as a plain string (assuming it's unquoted) + return trimmed; + } +} diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index ab1ceb8f..8d195553 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -26,6 +26,8 @@ import "./tests/Base64.mjs"; import "./tests/Base85.mjs"; import "./tests/Base92.mjs"; import "./tests/BCD.mjs"; +import "./tests/BencodeEncode.mjs"; +import "./tests/BencodeDecode.mjs"; import "./tests/BitwiseOp.mjs"; import "./tests/BLAKE2b.mjs"; import "./tests/BLAKE2s.mjs"; diff --git a/tests/operations/tests/BencodeDecode.mjs b/tests/operations/tests/BencodeDecode.mjs new file mode 100644 index 00000000..c0297d8b --- /dev/null +++ b/tests/operations/tests/BencodeDecode.mjs @@ -0,0 +1,66 @@ +/** + * Bencode Encode tests. + * + * @author jg42526 + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Bencode Decode: nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + "op": "Bencode Decode", + "args": [] + } + ] + }, + { + name: "Bencode Decode: integer", + input: "i42e", + expectedOutput: "42", + recipeConfig: [ + { + "op": "Bencode Decode", + "args": [] + } + ] + }, + { + name: "Bencode Decode: byte string", + input: "7:bencode", + expectedOutput: "bencode", + recipeConfig: [ + { + "op": "Bencode Decode", + "args": [] + } + ] + }, + { + name: "Bencode Decode: list", + input: "l7:bencodei-20ee", + expectedOutput: `["bencode",-20]`, + recipeConfig: [ + { + "op": "Bencode Decode", + "args": [] + } + ] + }, + { + name: "Bencode Decode: dictionary", + input: "d7:meaningi42e4:wiki7:bencodee", + expectedOutput: `{"meaning":42,"wiki":"bencode"}`, + recipeConfig: [ + { + "op": "Bencode Decode", + "args": [] + } + ] + }, +]); diff --git a/tests/operations/tests/BencodeEncode.mjs b/tests/operations/tests/BencodeEncode.mjs new file mode 100644 index 00000000..51b96826 --- /dev/null +++ b/tests/operations/tests/BencodeEncode.mjs @@ -0,0 +1,72 @@ +/** + * Bencode Encode tests. + * + * @author jg42526 + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Bencode Encode: nothing", + input: "", + expectedOutput: "0:", + recipeConfig: [ + { + "op": "Bencode Encode", + "args": [] + } + ] + }, + { + name: "Bencode Encode: integer", + input: "42", + expectedOutput: "i42e", + recipeConfig: [ + { + "op": "Bencode Encode", + "args": [] + } + ] + }, + { + name: "Bencode Encode: byte string", + input: "bencode", + expectedOutput: "7:bencode", + recipeConfig: [ + { + "op": "Bencode Encode", + "args": [] + } + ] + }, + { + name: "Bencode Encode: list", + input: `[ + "bencode", + -20 + ]`, + expectedOutput: "l7:bencodei-20ee", + recipeConfig: [ + { + "op": "Bencode Encode", + "args": [] + } + ] + }, + { + name: "Bencode Encode: dictionary", + input: `{ + "meaning": 42, + "wiki": "bencode" + }`, + expectedOutput: "d7:meaningi42e4:wiki7:bencodee", + recipeConfig: [ + { + "op": "Bencode Encode", + "args": [] + } + ] + }, +]);