nodeApi.mjs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  1. /* eslint no-console: 0 */
  2. /**
  3. * nodeApi.js
  4. *
  5. * Test node api utilities
  6. *
  7. * @author d98762625 [d98762625@gmail.com]
  8. * @copyright Crown Copyright 2018
  9. * @license Apache-2.0
  10. */
  11. import assert from "assert";
  12. import it from "../assertionHandler";
  13. import chef from "../../../src/node/index";
  14. import OperationError from "../../../src/core/errors/OperationError";
  15. import NodeDish from "../../../src/node/NodeDish";
  16. import fs from "fs";
  17. import { toBase32, Dish, SHA3 } from "../../../src/node/index";
  18. import TestRegister from "../../lib/TestRegister";
  19. TestRegister.addApiTests([
  20. it("should have some operations", () => {
  21. assert(chef);
  22. assert(chef.toBase32);
  23. assert(chef.setUnion);
  24. assert(!chef.randomFunction);
  25. }),
  26. it("should export other functions at top level", () => {
  27. assert(toBase32);
  28. }),
  29. it("should be synchronous", () => {
  30. try {
  31. const result = chef.toBase32("input");
  32. assert.notEqual("something", result);
  33. } catch (e) {
  34. // shouldnt reach here
  35. assert(false);
  36. }
  37. try {
  38. const fail = chef.setUnion("1");
  39. // shouldnt get here
  40. assert(!fail || false);
  41. } catch (e) {
  42. assert(true);
  43. }
  44. }),
  45. it("should not catch Errors", () => {
  46. try {
  47. chef.setUnion("1");
  48. assert(false);
  49. } catch (e) {
  50. assert(e instanceof OperationError);
  51. }
  52. }),
  53. it("should accept arguments in object format for operations", () => {
  54. const result = chef.setUnion("1 2 3 4:3 4 5 6", {
  55. itemDelimiter: " ",
  56. sampleDelimiter: ":"
  57. });
  58. assert.equal(result.value, "1 2 3 4 5 6");
  59. }),
  60. it("should accept just some of the optional arguments being overriden", () => {
  61. const result = chef.setIntersection("1 2 3 4 5\\n\\n3 4 5", {
  62. itemDelimiter: " ",
  63. });
  64. assert.equal(result.value, "3 4 5");
  65. }),
  66. it("should accept no override arguments and just use the default values", () => {
  67. const result = chef.powerSet("1,2,3");
  68. assert.equal(result.value, "\n3\n2\n1\n2,3\n1,3\n1,2\n1,2,3\n");
  69. }),
  70. it("should return an object with a .to method", () => {
  71. const result = chef.toBase32("input");
  72. assert(result.to);
  73. assert.equal(result.to("string"), "NFXHA5LU");
  74. }),
  75. it("should return an object with a .get method", () => {
  76. const result = chef.toBase32("input");
  77. assert(result.get);
  78. assert.equal(result.get("string"), "NFXHA5LU");
  79. }),
  80. it("should return a NodeDish", async () => {
  81. const result = chef.toBase32("input");
  82. assert(result instanceof NodeDish);
  83. }),
  84. it("should coerce to a string as you expect", () => {
  85. const result = chef.fromBase32(chef.toBase32("something"));
  86. assert.equal(String(result), "something");
  87. // This kind of coercion uses toValue
  88. assert.equal(""+result, "NaN");
  89. }),
  90. it("should coerce to a number as you expect", () => {
  91. const result = chef.fromBase32(chef.toBase32("32"));
  92. assert.equal(3 + result, 35);
  93. }),
  94. it("chef.help: should exist", () => {
  95. assert(chef.help);
  96. }),
  97. it("chef.help: should describe a operation", () => {
  98. const result = chef.help("tripleDESDecrypt");
  99. assert.strictEqual(result[0].name, "Triple DES Decrypt");
  100. assert.strictEqual(result[0].module, "Ciphers");
  101. assert.strictEqual(result[0].inputType, "string");
  102. assert.strictEqual(result[0].outputType, "string");
  103. assert.strictEqual(result[0].description, "Triple DES applies DES three times to each block to increase key size.<br><br><b>Key:</b> Triple DES uses a key length of 24 bytes (192 bits).<br>DES uses a key length of 8 bytes (64 bits).<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used.");
  104. assert.strictEqual(result[0].args.length, 5);
  105. }),
  106. it("chef.help: null for invalid operation", () => {
  107. const result = chef.help("some invalid function name");
  108. assert.strictEqual(result, null);
  109. }),
  110. it("chef.help: takes a wrapped operation as input", () => {
  111. const result = chef.help(chef.toBase32);
  112. assert.strictEqual(result[0].name, "To Base32");
  113. assert.strictEqual(result[0].module, "Default");
  114. }),
  115. it("chef.help: returns multiple results", () => {
  116. const result = chef.help("base 64");
  117. assert.strictEqual(result.length, 10);
  118. }),
  119. it("chef.help: looks in description for matches too", () => {
  120. // string only in one operation's description.
  121. const result = chef.help("Converts a unit of data to another format.");
  122. assert.strictEqual(result.length, 1);
  123. assert.strictEqual(result[0].name, "Convert data units");
  124. }),
  125. it("chef.help: lists name matches before desc matches", () => {
  126. const result = chef.help("Checksum");
  127. assert.ok(result[0].name.includes("Checksum"));
  128. assert.ok(result[1].name.includes("Checksum"));
  129. assert.strictEqual(result[result.length - 1].name.includes("Checksum"), false);
  130. assert.ok(result[result.length - 1].description.includes("checksum"));
  131. }),
  132. it("chef.help: exact name match only returns one result", () => {
  133. const result = chef.help("MD5");
  134. assert.strictEqual(result.length, 1);
  135. assert.strictEqual(result[0].name, "MD5");
  136. }),
  137. it("chef.help: exact match ignores whitespace", () => {
  138. const result = chef.help("tobase64");
  139. assert.strictEqual(result.length, 1);
  140. assert.strictEqual(result[0].name, "To Base64");
  141. }),
  142. it("chef.bake: should exist", () => {
  143. assert(chef.bake);
  144. }),
  145. it("chef.bake: should return NodeDish", () => {
  146. const result = chef.bake("input", "to base 64");
  147. assert(result instanceof NodeDish);
  148. }),
  149. it("chef.bake: should take an input and an op name and perform it", () => {
  150. const result = chef.bake("some input", "to base 32");
  151. assert.strictEqual(result.toString(), "ONXW2ZJANFXHA5LU");
  152. }),
  153. it("chef.bake: should complain if recipe isnt a valid object", () => {
  154. try {
  155. chef.bake("some input", 3264);
  156. } catch (e) {
  157. assert.strictEqual(e.name, "TypeError");
  158. assert.strictEqual(e.message, "Recipe can only contain function names or functions");
  159. }
  160. }),
  161. it("chef.bake: Should complain if string op is invalid", () => {
  162. try {
  163. chef.bake("some input", "not a valid operation");
  164. assert.fail("Shouldn't be hit");
  165. } catch (e) {
  166. assert.strictEqual(e.name, "TypeError");
  167. assert.strictEqual(e.message, "Couldn't find an operation with name 'not a valid operation'.");
  168. }
  169. }),
  170. it("chef.bake: Should take an input and an operation and perform it", () => {
  171. const result = chef.bake("https://google.com/search?q=help", chef.parseURI);
  172. assert.strictEqual(result.toString(), "Protocol:\thttps:\nHostname:\tgoogle.com\nPath name:\t/search\nArguments:\n\tq = help\n");
  173. }),
  174. it("chef.bake: Should complain if an invalid operation is inputted", () => {
  175. try {
  176. chef.bake("https://google.com/search?q=help", () => {});
  177. assert.fail("Shouldn't be hit");
  178. } catch (e) {
  179. assert.strictEqual(e.name, "TypeError");
  180. assert.strictEqual(e.message, "Inputted function not a Chef operation.");
  181. }
  182. }),
  183. it("chef.bake: accepts an array of operation names and performs them all in order", () => {
  184. const result = chef.bake("https://google.com/search?q=that's a complicated question", ["URL encode", "URL decode", "Parse URI"]);
  185. assert.strictEqual(result.toString(), "Protocol:\thttps:\nHostname:\tgoogle.com\nPath name:\t/search\nArguments:\n\tq = that's a complicated question\n");
  186. }),
  187. it("chef.bake: forgiving with operation names", () =>{
  188. const result = chef.bake("https://google.com/search?q=that's a complicated question", ["urlencode", "url decode", "parseURI"]);
  189. assert.strictEqual(result.toString(), "Protocol:\thttps:\nHostname:\tgoogle.com\nPath name:\t/search\nArguments:\n\tq = that's a complicated question\n");
  190. }),
  191. it("chef.bake: forgiving with operation names", () =>{
  192. const result = chef.bake("hello", ["to base 64"]);
  193. assert.strictEqual(result.toString(), "aGVsbG8=");
  194. }),
  195. it("chef.bake: if recipe is empty array, return input as dish", () => {
  196. const result = chef.bake("some input", []);
  197. assert.strictEqual(result.toString(), "some input");
  198. assert(result instanceof NodeDish, "Result is not instance of NodeDish");
  199. }),
  200. it("chef.bake: accepts an array of operations as recipe", () => {
  201. const result = chef.bake("https://google.com/search?q=that's a complicated question", [chef.URLEncode, chef.URLDecode, chef.parseURI]);
  202. assert.strictEqual(result.toString(), "Protocol:\thttps:\nHostname:\tgoogle.com\nPath name:\t/search\nArguments:\n\tq = that's a complicated question\n");
  203. }),
  204. it("should complain if an invalid operation is inputted as part of array", () => {
  205. try {
  206. chef.bake("something", [() => {}]);
  207. } catch (e) {
  208. assert.strictEqual(e.name, "TypeError");
  209. assert.strictEqual(e.message, "Inputted function not a Chef operation.");
  210. }
  211. }),
  212. it("chef.bake: should take single JSON object describing op and args OBJ", () => {
  213. const result = chef.bake("some input", {
  214. op: chef.toHex,
  215. args: {
  216. Delimiter: "Colon"
  217. }
  218. });
  219. assert.strictEqual(result.toString(), "73:6f:6d:65:20:69:6e:70:75:74");
  220. }),
  221. it("chef.bake: should take single JSON object describing op and args ARRAY", () => {
  222. const result = chef.bake("some input", {
  223. op: chef.toHex,
  224. args: ["Colon"]
  225. });
  226. assert.strictEqual(result.toString(), "73:6f:6d:65:20:69:6e:70:75:74");
  227. }),
  228. it("chef.bake: should error if op in JSON is not chef op", () => {
  229. try {
  230. chef.bake("some input", {
  231. op: () => {},
  232. args: ["Colon"],
  233. });
  234. } catch (e) {
  235. assert.strictEqual(e.name, "TypeError");
  236. assert.strictEqual(e.message, "Inputted function not a Chef operation.");
  237. }
  238. }),
  239. it("chef.bake: should take multiple ops in JSON object form, some ops by string", () => {
  240. const result = chef.bake("some input", [
  241. {
  242. op: chef.toHex,
  243. args: ["Colon"]
  244. },
  245. {
  246. op: "to octal",
  247. args: {
  248. delimiter: "Semi-colon",
  249. }
  250. }
  251. ]);
  252. assert.strictEqual(result.toString(), "67;63;72;66;146;72;66;144;72;66;65;72;62;60;72;66;71;72;66;145;72;67;60;72;67;65;72;67;64");
  253. }),
  254. it("chef.bake: should handle op with multiple args", () => {
  255. const result = chef.bake("some input", {
  256. op: "to morse code",
  257. args: {
  258. formatOptions: "Dash/Dot",
  259. wordDelimiter: "Comma",
  260. letterDelimiter: "Backslash",
  261. }
  262. });
  263. assert.strictEqual(result.toString(), "DotDotDot\\DashDashDash\\DashDash\\Dot,DotDot\\DashDot\\DotDashDashDot\\DotDotDash\\Dash");
  264. }),
  265. it("chef.bake: should take compact JSON format from Chef Website as recipe", () => {
  266. const result = chef.bake("some input", [{"op": "To Morse Code", "args": ["Dash/Dot", "Backslash", "Comma"]}, {"op": "Hex to PEM", "args": ["SOMETHING"]}, {"op": "To Snake case", "args": [false]}]);
  267. assert.strictEqual(result.toString(), "begin_something_anananaaaaak_da_aaak_da_aaaaananaaaaaaan_da_aaaaaaanan_da_aaak_end_something");
  268. }),
  269. it("chef.bake: should accept Clean JSON format from Chef website as recipe", () => {
  270. const result = chef.bake("some input", [
  271. { "op": "To Morse Code",
  272. "args": ["Dash/Dot", "Backslash", "Comma"] },
  273. { "op": "Hex to PEM",
  274. "args": ["SOMETHING"] },
  275. { "op": "To Snake case",
  276. "args": [false] }
  277. ]);
  278. assert.strictEqual(result.toString(), "begin_something_anananaaaaak_da_aaak_da_aaaaananaaaaaaan_da_aaaaaaanan_da_aaak_end_something");
  279. }),
  280. it("Composable Dish: Should have top level Dish object", () => {
  281. assert.ok(Dish);
  282. }),
  283. it("Composable Dish: Should construct empty dish object", () => {
  284. const dish = new Dish();
  285. assert.strictEqual(dish.value.byteLength, new ArrayBuffer(0).byteLength);
  286. assert.strictEqual(dish.type, 4);
  287. }),
  288. it("Composable Dish: constructed dish should have apply prototype functions", () => {
  289. const dish = new Dish();
  290. assert.ok(dish.apply);
  291. assert.throws(() => dish.someInvalidFunction());
  292. }),
  293. it("Composable Dish: composed function returns another dish", () => {
  294. const result = new Dish("some input").apply(toBase32);
  295. assert.ok(result instanceof NodeDish);
  296. }),
  297. it("Composable dish: infers type from input if needed", () => {
  298. const dish = new Dish("string input");
  299. assert.strictEqual(dish.type, 1);
  300. const numberDish = new Dish(333);
  301. assert.strictEqual(numberDish.type, 2);
  302. const arrayBufferDish = new Dish(Buffer.from("some buffer input").buffer);
  303. assert.strictEqual(arrayBufferDish.type, 4);
  304. const byteArrayDish = new Dish(Buffer.from("some buffer input"));
  305. assert.strictEqual(byteArrayDish.type, 0);
  306. const JSONDish = new Dish({key: "value"});
  307. assert.strictEqual(JSONDish.type, 6);
  308. }),
  309. it("Composable dish: Buffer type dishes should be converted to strings", () => {
  310. fs.writeFileSync("test.txt", "abc");
  311. const dish = new Dish(fs.readFileSync("test.txt"));
  312. assert.strictEqual(dish.type, 0);
  313. fs.unlinkSync("test.txt");
  314. }),
  315. it("Composable Dish: apply should allow set of arguments for operation", () => {
  316. const result = new Dish("input").apply(SHA3, {size: "256"});
  317. assert.strictEqual(result.toString(), "7640cc9b7e3662b2250a43d1757e318bb29fb4860276ac4373b67b1650d6d3e3");
  318. }),
  319. it("Composable Dish: apply functions can be chained", () => {
  320. const result = new Dish("input").apply(toBase32).apply(SHA3, {size: "224"});
  321. assert.strictEqual(result.toString(), "493e8136b759370a415ef2cf2f7a69690441ff86592aba082bc2e2e0");
  322. }),
  323. it("Excluded operations: throw a sensible error when you try and call one", () => {
  324. try {
  325. chef.fork();
  326. } catch (e) {
  327. assert.strictEqual(e.type, "ExcludedOperationError");
  328. assert.strictEqual(e.message, "Sorry, the Fork operation is not available in the Node.js version of CyberChef.");
  329. }
  330. }),
  331. it("Excluded operations: throw a sensible error when you try and call one", () => {
  332. try {
  333. chef.renderImage();
  334. } catch (e) {
  335. assert.strictEqual(e.type, "ExcludedOperationError");
  336. assert.strictEqual(e.message, "Sorry, the RenderImage operation is not available in the Node.js version of CyberChef.");
  337. }
  338. }),
  339. it("Operation arguments: should be accessible from operation object if op has array arg", () => {
  340. assert.ok(chef.toCharcode.args);
  341. assert.deepEqual(chef.unzip.args, {
  342. password: {
  343. type: "binaryString",
  344. value: "",
  345. },
  346. verifyResult: {
  347. type: "boolean",
  348. value: false,
  349. }
  350. });
  351. }),
  352. it("Operation arguments: should have key for each argument in operation", () => {
  353. assert.ok(chef.convertDistance.args.inputUnits);
  354. assert.ok(chef.convertDistance.args.outputUnits);
  355. assert.strictEqual(chef.bitShiftRight.args.amount.type, "number");
  356. assert.strictEqual(chef.bitShiftRight.args.amount.value, 1);
  357. assert.strictEqual(chef.bitShiftRight.args.type.type, "option");
  358. assert.ok(Array.isArray(chef.bitShiftRight.args.type.options));
  359. }),
  360. it("Operation arguments: should list all options excluding subheadings", () => {
  361. // First element (subheading) removed
  362. assert.equal(chef.convertDistance.args.inputUnits.options[0], "Nanometres (nm)");
  363. assert.equal(chef.defangURL.args.process.options[1], "Only full URLs");
  364. })
  365. ]);