nodeApi.mjs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  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 SyncDish from "../../../src/node/SyncDish";
  16. import { toBase32, Dish } from "../../../src/node/index";
  17. import TestRegister from "../../TestRegister";
  18. TestRegister.addApiTests([
  19. it("should have some operations", () => {
  20. assert(chef);
  21. assert(chef.toBase32);
  22. assert(chef.setUnion);
  23. assert(!chef.randomFunction);
  24. }),
  25. it("should export other functions at top level", () => {
  26. assert(toBase32);
  27. }),
  28. it("should be synchronous", () => {
  29. try {
  30. const result = chef.toBase32("input");
  31. assert.notEqual("something", result);
  32. } catch (e) {
  33. // shouldnt reach here
  34. assert(false);
  35. }
  36. try {
  37. const fail = chef.setUnion("1");
  38. // shouldnt get here
  39. assert(!fail || false);
  40. } catch (e) {
  41. assert(true);
  42. }
  43. }),
  44. it("should not catch Errors", () => {
  45. try {
  46. chef.setUnion("1");
  47. assert(false);
  48. } catch (e) {
  49. assert(e instanceof OperationError);
  50. }
  51. }),
  52. it("should accept arguments in object format for operations", () => {
  53. const result = chef.setUnion("1 2 3 4:3 4 5 6", {
  54. itemDelimiter: " ",
  55. sampleDelimiter: ":"
  56. });
  57. assert.equal(result.value, "1 2 3 4 5 6");
  58. }),
  59. it("should accept just some of the optional arguments being overriden", () => {
  60. const result = chef.setIntersection("1 2 3 4 5\\n\\n3 4 5", {
  61. itemDelimiter: " ",
  62. });
  63. assert.equal(result.value, "3 4 5");
  64. }),
  65. it("should accept no override arguments and just use the default values", () => {
  66. const result = chef.powerSet("1,2,3");
  67. assert.equal(result.value, "\n3\n2\n1\n2,3\n1,3\n1,2\n1,2,3\n");
  68. }),
  69. it("should return an object with a .to method", () => {
  70. const result = chef.toBase32("input");
  71. assert(result.to);
  72. assert.equal(result.to("string"), "NFXHA5LU");
  73. }),
  74. it("should return an object with a .get method", () => {
  75. const result = chef.toBase32("input");
  76. assert(result.get);
  77. assert.equal(result.get("string"), "NFXHA5LU");
  78. }),
  79. it("should return a SyncDish", () => {
  80. const result = chef.toBase32("input");
  81. assert(result instanceof SyncDish);
  82. }),
  83. it("should coerce to a string as you expect", () => {
  84. const result = chef.fromBase32(chef.toBase32("something"));
  85. assert.equal(String(result), "something");
  86. // This kind of coercion uses toValue
  87. assert.equal(""+result, "NaN");
  88. }),
  89. it("should coerce to a number as you expect", () => {
  90. const result = chef.fromBase32(chef.toBase32("32"));
  91. assert.equal(3 + result, 35);
  92. }),
  93. it("chef.help: should exist", () => {
  94. assert(chef.help);
  95. }),
  96. it("chef.help: should describe a operation", () => {
  97. const result = chef.help("tripleDESDecrypt");
  98. assert.strictEqual(result.name, "Triple DES Decrypt");
  99. assert.strictEqual(result.module, "Ciphers");
  100. assert.strictEqual(result.inputType, "string");
  101. assert.strictEqual(result.outputType, "string");
  102. assert.strictEqual(result.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.");
  103. assert.strictEqual(result.args.length, 5);
  104. }),
  105. it("chef.help: null for invalid operation", () => {
  106. const result = chef.help("some invalid function name");
  107. assert.strictEqual(result, null);
  108. }),
  109. it("chef.help: takes a wrapped operation as input", () => {
  110. const result = chef.help(chef.toBase32);
  111. assert.strictEqual(result.name, "To Base32");
  112. assert.strictEqual(result.module, "Default");
  113. }),
  114. it("chef.bake: should exist", () => {
  115. assert(chef.bake);
  116. }),
  117. it("chef.bake: should return SyncDish", () => {
  118. const result = chef.bake("input", "to base 64");
  119. assert(result instanceof SyncDish);
  120. }),
  121. it("chef.bake: should take an input and an op name and perform it", () => {
  122. const result = chef.bake("some input", "to base 32");
  123. assert.strictEqual(result.toString(), "ONXW2ZJANFXHA5LU");
  124. }),
  125. it("chef.bake: should complain if recipe isnt a valid object", () => {
  126. try {
  127. chef.bake("some input", 3264);
  128. } catch (e) {
  129. assert.strictEqual(e.name, "TypeError");
  130. assert.strictEqual(e.message, "Recipe can only contain function names or functions");
  131. }
  132. }),
  133. it("chef.bake: Should complain if string op is invalid", () => {
  134. try {
  135. chef.bake("some input", "not a valid operation");
  136. assert.fail("Shouldn't be hit");
  137. } catch (e) {
  138. assert.strictEqual(e.name, "TypeError");
  139. assert.strictEqual(e.message, "Couldn't find an operation with name 'not a valid operation'.");
  140. }
  141. }),
  142. it("chef.bake: Should take an input and an operation and perform it", () => {
  143. const result = chef.bake("https://google.com/search?q=help", chef.parseURI);
  144. assert.strictEqual(result.toString(), "Protocol:\thttps:\nHostname:\tgoogle.com\nPath name:\t/search\nArguments:\n\tq = help\n");
  145. }),
  146. it("chef.bake: Should complain if an invalid operation is inputted", () => {
  147. try {
  148. chef.bake("https://google.com/search?q=help", () => {});
  149. assert.fail("Shouldn't be hit");
  150. } catch (e) {
  151. assert.strictEqual(e.name, "TypeError");
  152. assert.strictEqual(e.message, "Inputted function not a Chef operation.");
  153. }
  154. }),
  155. it("chef.bake: accepts an array of operation names and performs them all in order", () => {
  156. const result = chef.bake("https://google.com/search?q=that's a complicated question", ["URL encode", "URL decode", "Parse URI"]);
  157. assert.strictEqual(result.toString(), "Protocol:\thttps:\nHostname:\tgoogle.com\nPath name:\t/search\nArguments:\n\tq = that's a complicated question\n");
  158. }),
  159. it("chef.bake: forgiving with operation names", () =>{
  160. const result = chef.bake("https://google.com/search?q=that's a complicated question", ["urlencode", "url decode", "parseURI"]);
  161. assert.strictEqual(result.toString(), "Protocol:\thttps:\nHostname:\tgoogle.com\nPath name:\t/search\nArguments:\n\tq = that's a complicated question\n");
  162. }),
  163. it("chef.bake: forgiving with operation names", () =>{
  164. const result = chef.bake("hello", ["to base 64"]);
  165. assert.strictEqual(result.toString(), "aGVsbG8=");
  166. }),
  167. it("chef.bake: if recipe is empty array, return input as dish", () => {
  168. const result = chef.bake("some input", []);
  169. assert.strictEqual(result.toString(), "some input");
  170. assert(result instanceof SyncDish, "Result is not instance of SyncDish");
  171. }),
  172. it("chef.bake: accepts an array of operations as recipe", () => {
  173. const result = chef.bake("https://google.com/search?q=that's a complicated question", [chef.URLEncode, chef.URLDecode, chef.parseURI]);
  174. assert.strictEqual(result.toString(), "Protocol:\thttps:\nHostname:\tgoogle.com\nPath name:\t/search\nArguments:\n\tq = that's a complicated question\n");
  175. }),
  176. it("should complain if an invalid operation is inputted as part of array", () => {
  177. try {
  178. chef.bake("something", [() => {}]);
  179. } catch (e) {
  180. assert.strictEqual(e.name, "TypeError");
  181. assert.strictEqual(e.message, "Inputted function not a Chef operation.");
  182. }
  183. }),
  184. it("chef.bake: should take single JSON object describing op and args OBJ", () => {
  185. const result = chef.bake("some input", {
  186. op: chef.toHex,
  187. args: {
  188. Delimiter: "Colon"
  189. }
  190. });
  191. assert.strictEqual(result.toString(), "73:6f:6d:65:20:69:6e:70:75:74");
  192. }),
  193. it("chef.bake: should take single JSON object describing op and args ARRAY", () => {
  194. const result = chef.bake("some input", {
  195. op: chef.toHex,
  196. args: ["Colon"]
  197. });
  198. assert.strictEqual(result.toString(), "73:6f:6d:65:20:69:6e:70:75:74");
  199. }),
  200. it("chef.bake: should error if op in JSON is not chef op", () => {
  201. try {
  202. chef.bake("some input", {
  203. op: () => {},
  204. args: ["Colon"],
  205. });
  206. } catch (e) {
  207. assert.strictEqual(e.name, "TypeError");
  208. assert.strictEqual(e.message, "Inputted function not a Chef operation.");
  209. }
  210. }),
  211. it("chef.bake: should take multiple ops in JSON object form, some ops by string", () => {
  212. const result = chef.bake("some input", [
  213. {
  214. op: chef.toHex,
  215. args: ["Colon"]
  216. },
  217. {
  218. op: "to octal",
  219. args: {
  220. delimiter: "Semi-colon",
  221. }
  222. }
  223. ]);
  224. 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");
  225. }),
  226. it("chef.bake: should handle op with multiple args", () => {
  227. const result = chef.bake("some input", {
  228. op: "to morse code",
  229. args: {
  230. formatOptions: "Dash/Dot",
  231. wordDelimiter: "Comma",
  232. letterDelimiter: "Backslash",
  233. }
  234. });
  235. assert.strictEqual(result.toString(), "DotDotDot\\DashDashDash\\DashDash\\Dot,DotDot\\DashDot\\DotDashDashDot\\DotDotDash\\Dash");
  236. }),
  237. it("chef.bake: should take compact JSON format from Chef Website as recipe", () => {
  238. 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]}]);
  239. assert.strictEqual(result.toString(), "begin_something_anananaaaaak_da_aaak_da_aaaaananaaaaaaan_da_aaaaaaanan_da_aaak_end_something");
  240. }),
  241. it("chef.bake: should accept Clean JSON format from Chef website as recipe", () => {
  242. const result = chef.bake("some input", [
  243. { "op": "To Morse Code",
  244. "args": ["Dash/Dot", "Backslash", "Comma"] },
  245. { "op": "Hex to PEM",
  246. "args": ["SOMETHING"] },
  247. { "op": "To Snake case",
  248. "args": [false] }
  249. ]);
  250. assert.strictEqual(result.toString(), "begin_something_anananaaaaak_da_aaak_da_aaaaananaaaaaaan_da_aaaaaaanan_da_aaak_end_something");
  251. }),
  252. it("Composable Dish: Should have top level Dish object", () => {
  253. assert.ok(Dish);
  254. }),
  255. it("Composable Dish: Should construct empty dish object", () => {
  256. const dish = new Dish();
  257. assert.deepEqual(dish.value, []);
  258. assert.strictEqual(dish.type, 0);
  259. }),
  260. it("Composable Dish: constructed dish should have operation prototype functions", () => {
  261. const dish = new Dish();
  262. assert.ok(dish.translateDateTimeFormat);
  263. assert.ok(dish.stripHTTPHeaders);
  264. assert.throws(() => dish.someInvalidFunction());
  265. }),
  266. it("Composable Dish: composed function returns another dish", () => {
  267. const result = new Dish("some input").toBase32();
  268. assert.ok(result instanceof SyncDish);
  269. }),
  270. it("Composable dish: infers type from input if needed", () => {
  271. const dish = new Dish("string input");
  272. assert.strictEqual(dish.type, 1);
  273. const numberDish = new Dish(333);
  274. assert.strictEqual(numberDish.type, 2);
  275. const arrayBufferDish = new Dish(Buffer.from("some buffer input").buffer);
  276. assert.strictEqual(arrayBufferDish.type, 4);
  277. const JSONDish = new Dish({key: "value"});
  278. assert.strictEqual(JSONDish.type, 6);
  279. }),
  280. it("Excluded operations: throw a sensible error when you try and call one", () => {
  281. try {
  282. chef.fork();
  283. } catch (e) {
  284. assert.strictEqual(e.type, "ExcludedOperationError");
  285. assert.strictEqual(e.message, "Sorry, the Fork operation is not available in the Node.js version of CyberChef.");
  286. }
  287. }),
  288. it("Excluded operations: throw a sensible error when you try and call one", () => {
  289. try {
  290. chef.renderImage();
  291. } catch (e) {
  292. assert.strictEqual(e.type, "ExcludedOperationError");
  293. assert.strictEqual(e.message, "Sorry, the RenderImage operation is not available in the Node.js version of CyberChef.");
  294. }
  295. })
  296. ]);