cyberchef 10.23.0 → 11.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/.devcontainer/devcontainer.json +1 -1
  2. package/.nvmrc +1 -1
  3. package/CHANGELOG.md +79 -0
  4. package/CONTRIBUTING.md +4 -1
  5. package/Dockerfile +2 -2
  6. package/Gruntfile.js +3 -17
  7. package/README.md +3 -3
  8. package/babel.config.js +4 -1
  9. package/package.json +31 -27
  10. package/src/core/ChefWorker.js +1 -1
  11. package/src/core/Recipe.mjs +1 -1
  12. package/src/core/config/Categories.json +2 -1
  13. package/src/core/config/OperationConfig.json +54 -3
  14. package/src/core/config/modules/Default.mjs +2 -0
  15. package/src/core/config/scripts/fixCryptoApiImports.mjs +54 -0
  16. package/src/core/config/scripts/fixSnackBarMarkup.mjs +28 -0
  17. package/src/core/lib/Extract.mjs +5 -0
  18. package/src/core/lib/Magic.mjs +1 -1
  19. package/src/core/lib/ParityBit.mjs +50 -0
  20. package/src/core/operations/AnalyseUUID.mjs +109 -6
  21. package/src/core/operations/ExtractEmailAddresses.mjs +2 -3
  22. package/src/core/operations/FromPunycode.mjs +1 -1
  23. package/src/core/operations/ParityBit.mjs +128 -0
  24. package/src/core/operations/RegularExpression.mjs +2 -1
  25. package/src/core/operations/RenderMarkdown.mjs +35 -4
  26. package/src/core/operations/ShowBase64Offsets.mjs +28 -28
  27. package/src/core/operations/ToPunycode.mjs +1 -1
  28. package/src/core/operations/index.mjs +2 -0
  29. package/src/node/NodeRecipe.mjs +8 -7
  30. package/src/node/api.mjs +4 -4
  31. package/src/node/index.mjs +5 -0
  32. package/src/web/HTMLOperation.mjs +8 -2
  33. package/src/web/html/index.html +1 -1
  34. package/src/web/index.js +2 -2
  35. package/src/web/static/sitemap.mjs +1 -1
  36. package/src/web/waiters/OperationsWaiter.mjs +30 -13
  37. package/tests/lib/wasmFetchPolyfill.mjs +31 -0
  38. package/tests/node/consumers/cjs-consumer.js +2 -2
  39. package/tests/node/consumers/esm-consumer.mjs +2 -2
  40. package/tests/node/tests/Categories.mjs +2 -2
  41. package/tests/node/tests/nodeApi.mjs +55 -58
  42. package/tests/node/tests/operations.mjs +2 -3
  43. package/tests/operations/index.mjs +6 -0
  44. package/tests/operations/tests/AnalyseUUID.mjs +66 -0
  45. package/tests/operations/tests/Base64.mjs +11 -0
  46. package/tests/operations/tests/ExtractEmailAddresses.mjs +38 -12
  47. package/tests/operations/tests/Fernet.mjs +18 -3
  48. package/tests/operations/tests/ParityBit.mjs +147 -0
  49. package/tests/operations/tests/RegularExpression.mjs +75 -0
  50. package/tests/operations/tests/RenderMarkdown.mjs +110 -0
@@ -43,17 +43,23 @@ class HTMLOperation {
43
43
  /**
44
44
  * Renders the operation in HTML as a stub operation with no ingredients.
45
45
  *
46
+ * @param {boolean} removeIcon - show icon for removing operation
47
+ * @param {string} elementId - element ID for aria usage
46
48
  * @returns {string}
47
49
  */
48
- toStubHtml(removeIcon) {
50
+ toStubHtml(removeIcon = false, elementId = null) {
49
51
  let html = "<li class='operation'";
50
52
 
53
+ if (elementId) {
54
+ html += ` id='${elementId}'`;
55
+ }
56
+
51
57
  if (this.description) {
52
58
  const infoLink = this.infoURL ? `<hr>${titleFromWikiLink(this.infoURL)}` : "";
53
59
 
54
60
  html += ` data-container='body' data-toggle='popover' data-placement='right'
55
61
  data-content="${this.description}${infoLink}" data-html='true' data-trigger='hover'
56
- data-boundary='viewport'`;
62
+ data-boundary='viewport' role='button'`;
57
63
  }
58
64
 
59
65
  html += ">" + this.name;
@@ -173,7 +173,7 @@
173
173
  Operations
174
174
  <span class="op-count"></span>
175
175
  </div>
176
- <input id="search" type="search" class="form-control" placeholder="Search..." autocomplete="off" tabindex="0" data-help-title="Searching for operations" data-help="<p>Use the search box to find useful operations.</p><p>Both operation names and descriptions are queried using a fuzzy matching algorithm.</p>">
176
+ <input id="search" type="search" class="form-control" placeholder="Search..." autocomplete="off" tabindex="0" data-help-title="Searching for operations" data-help="<p>Use the search box to find useful operations.</p><p>Both operation names and descriptions are queried using a fuzzy matching algorithm.</p>" aria-owns="search-results">
177
177
  <ul id="search-results" class="op-list"></ul>
178
178
  <div id="categories" class="panel-group no-select"></div>
179
179
  </div>
package/src/web/index.js CHANGED
@@ -17,8 +17,8 @@ import * as CanvasComponents from "../core/lib/CanvasComponents.mjs";
17
17
 
18
18
  // CyberChef
19
19
  import App from "./App.mjs";
20
- import Categories from "../core/config/Categories.json" assert {type: "json"};
21
- import OperationConfig from "../core/config/OperationConfig.json" assert {type: "json"};
20
+ import Categories from "../core/config/Categories.json" with { type: "json" };
21
+ import OperationConfig from "../core/config/OperationConfig.json" with { type: "json" };
22
22
 
23
23
 
24
24
  /**
@@ -1,5 +1,5 @@
1
1
  import sm from "sitemap";
2
- import OperationConfig from "../../core/config/OperationConfig.json" assert { type: "json" };
2
+ import OperationConfig from "../../core/config/OperationConfig.json" with { type: "json" };
3
3
 
4
4
  /**
5
5
  * Generates an XML sitemap for all CyberChef operations and a number of recipes.
@@ -28,17 +28,16 @@ class OperationsWaiter {
28
28
  this.removeIntent = false;
29
29
  }
30
30
 
31
-
32
31
  /**
33
32
  * Handler for search events.
34
33
  * Finds operations which match the given search term and displays them under the search box.
35
34
  *
36
- * @param {event} e
35
+ * @param {KeyboardEvent | ClipboardEvent | Event} e
37
36
  */
38
37
  searchOperations(e) {
39
38
  let ops, selected;
40
39
 
41
- if (e.type === "search" || e.keyCode === 13) { // Search or Return
40
+ if ((e.type === "search" && e.target.value !== "") || e.keyCode === 13) { // Search (non-empty) or Return
42
41
  e.preventDefault();
43
42
  ops = document.querySelectorAll("#search-results li");
44
43
  if (ops.length) {
@@ -49,27 +48,43 @@ class OperationsWaiter {
49
48
  }
50
49
  }
51
50
 
51
+ /**
52
+ * Sets up the operation element with the correct attributes when selected
53
+ * @param {HTMLElement} element
54
+ */
55
+ const _selectOperation = (element) => {
56
+ element.classList.add("selected-op");
57
+ element.scrollIntoView({block: "nearest"});
58
+ $(element).popover("show");
59
+ e.target.setAttribute("aria-activedescendant", element.id);
60
+ };
61
+
62
+ /**
63
+ * Sets up the operation element with the correct attributes when deselected
64
+ * @param {HTMLElement} element
65
+ */
66
+ const _deselectOperation = (element) => {
67
+ element.classList.remove("selected-op");
68
+ $(element).popover("hide");
69
+ };
70
+
52
71
  if (e.keyCode === 40) { // Down
53
72
  e.preventDefault();
54
73
  ops = document.querySelectorAll("#search-results li");
55
74
  if (ops.length) {
56
75
  selected = this.getSelectedOp(ops);
57
- if (selected > -1) {
58
- ops[selected].classList.remove("selected-op");
59
- }
76
+ if (selected > -1) _deselectOperation(ops[selected]);
60
77
  if (selected === ops.length-1) selected = -1;
61
- ops[selected+1].classList.add("selected-op");
78
+ _selectOperation(ops[selected+1]);
62
79
  }
63
80
  } else if (e.keyCode === 38) { // Up
64
81
  e.preventDefault();
65
82
  ops = document.querySelectorAll("#search-results li");
66
83
  if (ops.length) {
67
84
  selected = this.getSelectedOp(ops);
68
- if (selected > -1) {
69
- ops[selected].classList.remove("selected-op");
70
- }
85
+ if (selected > -1) _deselectOperation(ops[selected]);
71
86
  if (selected === 0) selected = ops.length;
72
- ops[selected-1].classList.add("selected-op");
87
+ _selectOperation(ops[selected-1]);
73
88
  }
74
89
  } else {
75
90
  const searchResultsEl = document.getElementById("search-results");
@@ -83,11 +98,13 @@ class OperationsWaiter {
83
98
  searchResultsEl.removeChild(searchResultsEl.firstChild);
84
99
  }
85
100
 
101
+ document.querySelector("#search").removeAttribute("aria-activedescendant");
102
+
86
103
  $("#categories .show").collapse("hide");
87
104
  if (str) {
88
105
  const matchedOps = this.filterOperations(str, true);
89
106
  const matchedOpsHtml = matchedOps
90
- .map(v => v.toStubHtml())
107
+ .map((operation, idx) => operation.toStubHtml(false, `search-result-${idx}`))
91
108
  .join("");
92
109
 
93
110
  searchResultsEl.innerHTML = matchedOpsHtml;
@@ -103,7 +120,7 @@ class OperationsWaiter {
103
120
  * @param {string} searchStr
104
121
  * @param {boolean} highlight - Whether or not to highlight the matching string in the operation
105
122
  * name and description
106
- * @returns {string[]}
123
+ * @returns {HTMLOperation[]}
107
124
  */
108
125
  filterOperations(inStr, highlight) {
109
126
  const matchedOps = [];
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Polyfill for Node.js 22+ where globalThis.fetch is built-in but rejects
3
+ * bare filesystem paths. WASM libraries like argon2-browser call fetch() with
4
+ * an absolute path (e.g. "/path/to/argon2.wasm") expecting a browser-style
5
+ * fallback, but Node.js 22's fetch throws synchronously for non-URL strings.
6
+ *
7
+ * This wrapper intercepts such calls and serves the file via Node's fs module,
8
+ * returning a synthetic Response so the WASM module loads correctly.
9
+ */
10
+
11
+ import { readFile } from "fs/promises";
12
+
13
+ if (globalThis.fetch) {
14
+ const originalFetch = globalThis.fetch;
15
+ globalThis.fetch = async function patchedFetch(url, options) {
16
+ const urlStr = typeof url === "string" ?
17
+ url :
18
+ url instanceof URL ?
19
+ url.href :
20
+ String(url);
21
+ // Intercept bare filesystem paths (absolute POSIX or Windows)
22
+ if (urlStr.startsWith("/") || /^[A-Za-z]:[/\\]/.test(urlStr)) {
23
+ const buffer = await readFile(urlStr);
24
+ return new Response(buffer, {
25
+ status: 200,
26
+ headers: { "Content-Type": "application/wasm" },
27
+ });
28
+ }
29
+ return originalFetch(url, options);
30
+ };
31
+ }
@@ -8,9 +8,9 @@
8
8
 
9
9
  const assert = require("assert");
10
10
 
11
- require("cyberchef").then(chef => {
11
+ require("cyberchef").then(async chef => {
12
12
 
13
- const d = chef.bake("Testing, 1 2 3", [
13
+ const d = await chef.bake("Testing, 1 2 3", [
14
14
  chef.toHex,
15
15
  chef.reverse,
16
16
  {
@@ -9,7 +9,7 @@ import assert from "assert";
9
9
  import chef from "cyberchef";
10
10
  import { bake, toHex, reverse, unique, multiply } from "cyberchef";
11
11
 
12
- const a = bake("Testing, 1 2 3", [
12
+ const a = await bake("Testing, 1 2 3", [
13
13
  toHex,
14
14
  reverse,
15
15
  {
@@ -28,7 +28,7 @@ const a = bake("Testing, 1 2 3", [
28
28
 
29
29
  assert.equal(a.value, "630957449041920");
30
30
 
31
- const b = chef.bake("Testing, 1 2 3", [
31
+ const b = await chef.bake("Testing, 1 2 3", [
32
32
  chef.toHex,
33
33
  chef.reverse,
34
34
  {
@@ -1,6 +1,6 @@
1
1
  import TestRegister from "../../lib/TestRegister.mjs";
2
- import Categories from "../../../src/core/config/Categories.json" assert {type: "json"};
3
- import OperationConfig from "../../../src/core/config/OperationConfig.json" assert {type: "json"};
2
+ import Categories from "../../../src/core/config/Categories.json" with { type: "json" };
3
+ import OperationConfig from "../../../src/core/config/OperationConfig.json" with { type: "json" };
4
4
  import it from "../assertionHandler.mjs";
5
5
  import assert from "assert";
6
6
 
@@ -170,77 +170,77 @@ TestRegister.addApiTests([
170
170
  assert(chef.bake);
171
171
  }),
172
172
 
173
- it("chef.bake: should return NodeDish", () => {
174
- const result = chef.bake("input", "to base 64");
173
+ it("chef.bake: should return NodeDish", async () => {
174
+ const result = await chef.bake("input", "to base 64");
175
175
  assert(result instanceof NodeDish);
176
176
  }),
177
177
 
178
- it("chef.bake: should take an input and an op name and perform it", () => {
179
- const result = chef.bake("some input", "to base 32");
178
+ it("chef.bake: should take an input and an op name and perform it", async () => {
179
+ const result = await chef.bake("some input", "to base 32");
180
180
  assert.strictEqual(result.toString(), "ONXW2ZJANFXHA5LU");
181
181
  }),
182
182
 
183
- it("chef.bake: should complain if recipe isnt a valid object", () => {
184
- assert.throws(() => chef.bake("some input", 3264), {
183
+ it("chef.bake: should complain if recipe isnt a valid object", async () => {
184
+ await assert.rejects(() => chef.bake("some input", 3264), {
185
185
  name: "TypeError",
186
186
  message: "Recipe can only contain function names or functions"
187
187
  });
188
188
  }),
189
189
 
190
- it("chef.bake: Should complain if string op is invalid", () => {
191
- assert.throws(() => chef.bake("some input", "not a valid operation"), {
190
+ it("chef.bake: Should complain if string op is invalid", async () => {
191
+ await assert.rejects(() => chef.bake("some input", "not a valid operation"), {
192
192
  name: "TypeError",
193
193
  message: "Couldn't find an operation with name 'not a valid operation'."
194
194
  });
195
195
  }),
196
196
 
197
- it("chef.bake: Should take an input and an operation and perform it", () => {
198
- const result = chef.bake("https://google.com/search?q=help", chef.parseURI);
197
+ it("chef.bake: Should take an input and an operation and perform it", async () => {
198
+ const result = await chef.bake("https://google.com/search?q=help", chef.parseURI);
199
199
  assert.strictEqual(result.toString(), "Protocol:\thttps:\nHostname:\tgoogle.com\nPath name:\t/search\nArguments:\n\tq = help\n");
200
200
  }),
201
201
 
202
- it("chef.bake: Should complain if an invalid operation is inputted", () => {
203
- assert.throws(() => chef.bake("https://google.com/search?q=help", () => {}), {
202
+ it("chef.bake: Should complain if an invalid operation is inputted", async () => {
203
+ await assert.rejects(() => chef.bake("https://google.com/search?q=help", () => {}), {
204
204
  name: "TypeError",
205
205
  message: "Inputted function not a Chef operation."
206
206
  });
207
207
  }),
208
208
 
209
- it("chef.bake: accepts an array of operation names and performs them all in order", () => {
210
- const result = chef.bake("https://google.com/search?q=that's a complicated question", ["URL encode", "URL decode", "Parse URI"]);
209
+ it("chef.bake: accepts an array of operation names and performs them all in order", async () => {
210
+ const result = await chef.bake("https://google.com/search?q=that's a complicated question", ["URL encode", "URL decode", "Parse URI"]);
211
211
  assert.strictEqual(result.toString(), "Protocol:\thttps:\nHostname:\tgoogle.com\nPath name:\t/search\nArguments:\n\tq = that's a complicated question\n");
212
212
  }),
213
213
 
214
- it("chef.bake: forgiving with operation names", () =>{
215
- const result = chef.bake("https://google.com/search?q=that's a complicated question", ["urlencode", "url decode", "parseURI"]);
214
+ it("chef.bake: forgiving with operation names", async () =>{
215
+ const result = await chef.bake("https://google.com/search?q=that's a complicated question", ["urlencode", "url decode", "parseURI"]);
216
216
  assert.strictEqual(result.toString(), "Protocol:\thttps:\nHostname:\tgoogle.com\nPath name:\t/search\nArguments:\n\tq = that's a complicated question\n");
217
217
  }),
218
218
 
219
- it("chef.bake: forgiving with operation names", () =>{
220
- const result = chef.bake("hello", ["to base 64"]);
219
+ it("chef.bake: forgiving with operation names", async () =>{
220
+ const result = await chef.bake("hello", ["to base 64"]);
221
221
  assert.strictEqual(result.toString(), "aGVsbG8=");
222
222
  }),
223
223
 
224
- it("chef.bake: if recipe is empty array, return input as dish", () => {
225
- const result = chef.bake("some input", []);
224
+ it("chef.bake: if recipe is empty array, return input as dish", async () => {
225
+ const result = await chef.bake("some input", []);
226
226
  assert.strictEqual(result.toString(), "some input");
227
227
  assert(result instanceof NodeDish, "Result is not instance of NodeDish");
228
228
  }),
229
229
 
230
- it("chef.bake: accepts an array of operations as recipe", () => {
231
- const result = chef.bake("https://google.com/search?q=that's a complicated question", [chef.URLEncode, chef.URLDecode, chef.parseURI]);
230
+ it("chef.bake: accepts an array of operations as recipe", async () => {
231
+ const result = await chef.bake("https://google.com/search?q=that's a complicated question", [chef.URLEncode, chef.URLDecode, chef.parseURI]);
232
232
  assert.strictEqual(result.toString(), "Protocol:\thttps:\nHostname:\tgoogle.com\nPath name:\t/search\nArguments:\n\tq = that's a complicated question\n");
233
233
  }),
234
234
 
235
- it("should complain if an invalid operation is inputted as part of array", () => {
236
- assert.throws(() => chef.bake("something", [() => {}]), {
235
+ it("should complain if an invalid operation is inputted as part of array", async () => {
236
+ await assert.rejects(() => chef.bake("something", [() => {}]), {
237
237
  name: "TypeError",
238
238
  message: "Inputted function not a Chef operation."
239
239
  });
240
240
  }),
241
241
 
242
- it("chef.bake: should take single JSON object describing op and args OBJ", () => {
243
- const result = chef.bake("some input", {
242
+ it("chef.bake: should take single JSON object describing op and args OBJ", async () => {
243
+ const result = await chef.bake("some input", {
244
244
  op: chef.toHex,
245
245
  args: {
246
246
  Delimiter: "Colon"
@@ -249,23 +249,23 @@ TestRegister.addApiTests([
249
249
  assert.strictEqual(result.toString(), "73:6f:6d:65:20:69:6e:70:75:74");
250
250
  }),
251
251
 
252
- it("chef.bake: should take single JSON object desribing op with optional args", () => {
253
- const result = chef.bake("some input", {
252
+ it("chef.bake: should take single JSON object desribing op with optional args", async () => {
253
+ const result = await chef.bake("some input", {
254
254
  op: chef.toHex,
255
255
  });
256
256
  assert.strictEqual(result.toString(), "73 6f 6d 65 20 69 6e 70 75 74");
257
257
  }),
258
258
 
259
- it("chef.bake: should take single JSON object describing op and args ARRAY", () => {
260
- const result = chef.bake("some input", {
259
+ it("chef.bake: should take single JSON object describing op and args ARRAY", async () => {
260
+ const result = await chef.bake("some input", {
261
261
  op: chef.toHex,
262
262
  args: ["Colon"]
263
263
  });
264
264
  assert.strictEqual(result.toString(), "73:6f:6d:65:20:69:6e:70:75:74");
265
265
  }),
266
266
 
267
- it("chef.bake: should error if op in JSON is not chef op", () => {
268
- assert.throws(() => chef.bake("some input", {
267
+ it("chef.bake: should error if op in JSON is not chef op", async () => {
268
+ await assert.rejects(() => chef.bake("some input", {
269
269
  op: () => {},
270
270
  args: ["Colon"],
271
271
  }), {
@@ -274,8 +274,8 @@ TestRegister.addApiTests([
274
274
  });
275
275
  }),
276
276
 
277
- it("chef.bake: should take multiple ops in JSON object form, some ops by string", () => {
278
- const result = chef.bake("some input", [
277
+ it("chef.bake: should take multiple ops in JSON object form, some ops by string", async () => {
278
+ const result = await chef.bake("some input", [
279
279
  {
280
280
  op: chef.toHex,
281
281
  args: ["Colon"]
@@ -290,8 +290,8 @@ TestRegister.addApiTests([
290
290
  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");
291
291
  }),
292
292
 
293
- it("chef.bake: should take multiple ops in JSON object form, some without args", () => {
294
- const result = chef.bake("some input", [
293
+ it("chef.bake: should take multiple ops in JSON object form, some without args", async () => {
294
+ const result = await chef.bake("some input", [
295
295
  {
296
296
  op: chef.toHex,
297
297
  },
@@ -305,8 +305,8 @@ TestRegister.addApiTests([
305
305
  assert.strictEqual(result.toString(), "67;63;40;66;146;40;66;144;40;66;65;40;62;60;40;66;71;40;66;145;40;67;60;40;67;65;40;67;64");
306
306
  }),
307
307
 
308
- it("chef.bake: should handle op with multiple args", () => {
309
- const result = chef.bake("some input", {
308
+ it("chef.bake: should handle op with multiple args", async () => {
309
+ const result = await chef.bake("some input", {
310
310
  op: "to morse code",
311
311
  args: {
312
312
  formatOptions: "Dash/Dot",
@@ -317,13 +317,13 @@ TestRegister.addApiTests([
317
317
  assert.strictEqual(result.toString(), "DotDotDot\\DashDashDash\\DashDash\\Dot,DotDot\\DashDot\\DotDashDashDot\\DotDotDash\\Dash");
318
318
  }),
319
319
 
320
- it("chef.bake: should take compact JSON format from Chef Website as recipe", () => {
321
- 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]}]);
320
+ it("chef.bake: should take compact JSON format from Chef Website as recipe", async () => {
321
+ const result = await 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]}]);
322
322
  assert.strictEqual(result.toString(), "begin_something_anananaaaaak_da_aaak_da_aaaaananaaaaaaan_da_aaaaaaanan_da_aaak_end_something");
323
323
  }),
324
324
 
325
- it("chef.bake: should accept Clean JSON format from Chef website as recipe", () => {
326
- const result = chef.bake("some input", [
325
+ it("chef.bake: should accept Clean JSON format from Chef website as recipe", async () => {
326
+ const result = await chef.bake("some input", [
327
327
  { "op": "To Morse Code",
328
328
  "args": ["Dash/Dot", "Backslash", "Comma"] },
329
329
  { "op": "Hex to PEM",
@@ -334,8 +334,8 @@ TestRegister.addApiTests([
334
334
  assert.strictEqual(result.toString(), "begin_something_anananaaaaak_da_aaak_da_aaaaananaaaaaaan_da_aaaaaaanan_da_aaak_end_something");
335
335
  }),
336
336
 
337
- it("chef.bake: should accept Clean JSON format from Chef website - args optional", () => {
338
- const result = chef.bake("some input", [
337
+ it("chef.bake: should accept Clean JSON format from Chef website - args optional", async () => {
338
+ const result = await chef.bake("some input", [
339
339
  { "op": "To Morse Code" },
340
340
  { "op": "Hex to PEM",
341
341
  "args": ["SOMETHING"] },
@@ -345,31 +345,28 @@ TestRegister.addApiTests([
345
345
  assert.strictEqual(result.toString(), "begin_something_aaaaaaaaaaaaaa_end_something");
346
346
  }),
347
347
 
348
- it("chef.bake: should accept operation names from Chef Website which contain forward slash", () => {
349
- const result = chef.bake("I'll have the test salmon", [
348
+ it("chef.bake: should accept operation names from Chef Website which contain forward slash", async () => {
349
+ const result = await chef.bake("I'll have the test salmon", [
350
350
  { "op": "Find / Replace",
351
351
  "args": [{ "option": "Regex", "string": "test" }, "good", true, false, true, false]}
352
352
  ]);
353
353
  assert.strictEqual(result.toString(), "I'll have the good salmon");
354
354
  }),
355
355
 
356
- it("chef.bake: should accept operation names from Chef Website which contain a hyphen", () => {
357
- const result = chef.bake("I'll have the test salmon", [
356
+ it("chef.bake: should accept operation names from Chef Website which contain a hyphen", async () => {
357
+ const result = await chef.bake("I'll have the test salmon", [
358
358
  { "op": "Adler-32 Checksum",
359
359
  "args": [] }
360
360
  ]);
361
361
  assert.strictEqual(result.toString(), "6e4208f8");
362
362
  }),
363
363
 
364
- it("chef.bake: should accept operation names from Chef Website which contain a period", () => {
365
- const result = chef.bake("30 13 02 01 05 16 0e 41 6e 79 62 6f 64 79 20 74 68 65 72 65 3f", [
364
+ it("chef.bake: should accept operation names from Chef Website which contain a period", async () => {
365
+ const result = await chef.bake("30 13 02 01 05 16 0e 41 6e 79 62 6f 64 79 20 74 68 65 72 65 3f", [
366
366
  { "op": "Parse ASN.1 hex string",
367
367
  "args": [0, 32] }
368
368
  ]);
369
- assert.strictEqual(result.toString(), `SEQUENCE
370
- INTEGER 05
371
- IA5String 'Anybody there?'
372
- `);
369
+ assert.strictEqual(result.toString(), `SEQUENCE\n INTEGER 05\n IA5String 'Anybody there?'\n`);
373
370
  }),
374
371
 
375
372
  it("Excluded operations: throw a sensible error when you try and call one", () => {
@@ -381,16 +378,16 @@ TestRegister.addApiTests([
381
378
  }
382
379
  }),
383
380
 
384
- it("chef.bake: cannot accept flowControl operations in recipe", () => {
385
- assert.throws(() => chef.bake("some input", "magic"), {
381
+ it("chef.bake: cannot accept flowControl operations in recipe", async () => {
382
+ await assert.rejects(() => chef.bake("some input", "magic"), {
386
383
  name: "TypeError",
387
384
  message: "flowControl operations like Magic are not currently allowed in recipes for chef.bake in the Node API"
388
385
  });
389
- assert.throws(() => chef.bake("some input", magic), {
386
+ await assert.rejects(() => chef.bake("some input", magic), {
390
387
  name: "TypeError",
391
388
  message: "flowControl operations like Magic are not currently allowed in recipes for chef.bake in the Node API"
392
389
  });
393
- assert.throws(() => chef.bake("some input", ["to base 64", "magic"]), {
390
+ await assert.rejects(() => chef.bake("some input", ["to base 64", "magic"]), {
394
391
  name: "TypeError",
395
392
  message: "flowControl operations like Magic are not currently allowed in recipes for chef.bake in the Node API"
396
393
  });
@@ -589,8 +589,7 @@ Password: 282760`;
589
589
  ...[1, 3, 4, 5, 6, 7].map(version => it(`Analyze UUID v${version}`, () => {
590
590
  const uuid = chef.generateUUID("", { "version": `v${version}` }).toString();
591
591
  const result = chef.analyseUUID(uuid).toString();
592
- const expected = `UUID version: ${version}`;
593
- assert.strictEqual(result, expected);
592
+ assert.ok(result.startsWith(`Version:\n${version}\n`), `Expected output to start with "Version:\\n${version}\\n", got: ${result}`);
594
593
  })),
595
594
 
596
595
  it("Generate UUID using defaults", () => {
@@ -598,7 +597,7 @@ Password: 282760`;
598
597
  assert.ok(uuid);
599
598
 
600
599
  const analysis = chef.analyseUUID(uuid).toString();
601
- assert.strictEqual(analysis, "UUID version: 4");
600
+ assert.ok(analysis.startsWith("Version:\n4\n"), `Expected output to start with "Version:\\n4\\n", got: ${analysis}`);
602
601
  }),
603
602
 
604
603
  it("Gzip, Gunzip", () => {
@@ -11,11 +11,13 @@
11
11
  * @license Apache-2.0
12
12
  */
13
13
 
14
+ import "../lib/wasmFetchPolyfill.mjs";
14
15
  import { setLongTestFailure, logTestReport } from "../lib/utils.mjs";
15
16
 
16
17
  import TestRegister from "../lib/TestRegister.mjs";
17
18
  import "./tests/A1Z26CipherDecode.mjs";
18
19
  import "./tests/AESKeyWrap.mjs";
20
+ import "./tests/AnalyseUUID.mjs";
19
21
  import "./tests/AlternatingCaps.mjs";
20
22
  import "./tests/AvroToJSON.mjs";
21
23
  import "./tests/BaconCipher.mjs";
@@ -71,6 +73,7 @@ import "./tests/ExtractAudioMetadata.mjs";
71
73
  import "./tests/ExtractEmailAddresses.mjs";
72
74
  import "./tests/ExtractHashes.mjs";
73
75
  import "./tests/ExtractIPAddresses.mjs";
76
+ import "./tests/Fernet.mjs";
74
77
  import "./tests/Float.mjs";
75
78
  import "./tests/FileTree.mjs";
76
79
  import "./tests/FletcherChecksum.mjs";
@@ -134,6 +137,7 @@ import "./tests/ParseUDP.mjs";
134
137
  import "./tests/PEMtoHex.mjs";
135
138
  import "./tests/PGP.mjs";
136
139
  import "./tests/PHP.mjs";
140
+ import "./tests/ParityBit.mjs";
137
141
  import "./tests/PHPSerialize.mjs";
138
142
  import "./tests/PowerSet.mjs";
139
143
  import "./tests/Protobuf.mjs";
@@ -143,6 +147,8 @@ import "./tests/Rabbit.mjs";
143
147
  import "./tests/RAKE.mjs";
144
148
  import "./tests/Regex.mjs";
145
149
  import "./tests/Register.mjs";
150
+ import "./tests/RegularExpression.mjs";
151
+ import "./tests/RenderMarkdown.mjs";
146
152
  import "./tests/RisonEncodeDecode.mjs";
147
153
  import "./tests/Rotate.mjs";
148
154
  import "./tests/RSA.mjs";
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Analyse UUID tests
3
+ *
4
+ * @author ko80240 [csk.dev@proton.me]
5
+ * @copyright Crown Copyright 2023
6
+ * @license Apache-2.0
7
+ */
8
+ import TestRegister from "../../lib/TestRegister.mjs";
9
+
10
+ TestRegister.addTests([
11
+ {
12
+ "name": "Analyse UUID: v1 UUID extracts timestamp, clock, and node",
13
+ "input": "cefa1760-28ee-11f1-9f95-1fb76af3e239",
14
+ "expectedOutput": "Version:\n1\n\nTimestamp:\n1774514156502\n\nTimestamp (ISO):\n2026-03-26T08:35:56.502Z\n\nNode:\n1F:B7:6A:F3:E2:39\n\nClock:\n8085\n\nUUID Integer:\n275119515460318071558429785403790975545",
15
+ "recipeConfig": [
16
+ {
17
+ "op": "Analyse UUID",
18
+ "args": [true]
19
+ }
20
+ ]
21
+ },
22
+ {
23
+ "name": "Analyse UUID: v7 UUID extracts timestamp, randA, and randB",
24
+ "input": "019d294a-af64-7728-9524-26da08f50708",
25
+ "expectedOutput": "Version:\n7\n\nTimestamp:\n1774514253668\n\nTimestamp (ISO):\n2026-03-26T08:37:33.668Z\n\nRand A:\n1832\n\nRand B:\n952426DA08F50708\n\nUUID Integer:\n2145256098533991595556290452700595976",
26
+ "recipeConfig": [
27
+ {
28
+ "op": "Analyse UUID",
29
+ "args": [true]
30
+ }
31
+ ]
32
+ },
33
+ {
34
+ "name": "Analyse UUID: v4 UUID should show no metadata - not possible",
35
+ "input": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
36
+ "expectedOutput": "Version:\n4\n\nNo metadata available. Only versions 1, 6, 7 are supported.\n\nUUID Integer:\n324969006592305634633390616021200786553",
37
+ "recipeConfig": [
38
+ {
39
+ "op": "Analyse UUID",
40
+ "args": [true]
41
+ }
42
+ ]
43
+ },
44
+ {
45
+ "name": "Analyse UUID: if the 'Include Metadata' option is false it should return not metadata",
46
+ "input": "cefa1760-28ee-11f1-9f95-1fb76af3e239",
47
+ "expectedOutput": "Version:\n1\n\nUUID Integer:\n275119515460318071558429785403790975545",
48
+ "recipeConfig": [
49
+ {
50
+ "op": "Analyse UUID",
51
+ "args": [false]
52
+ }
53
+ ]
54
+ },
55
+ {
56
+ "name": "Analyse UUID: invalid UUID should return error message",
57
+ "input": "not-a-uuid",
58
+ "expectedOutput": "Invalid UUID",
59
+ "recipeConfig": [
60
+ {
61
+ "op": "Analyse UUID",
62
+ "args": [true]
63
+ }
64
+ ]
65
+ }
66
+ ]);
@@ -116,4 +116,15 @@ TestRegister.addTests([
116
116
  },
117
117
  ],
118
118
  },
119
+ {
120
+ name: "Show Base64 offsets: escapes static output",
121
+ input: "\x00\x10\x83\x10\x51\x87",
122
+ expectedOutput: "&lt;script&gt;\n&lt;AQmsBRk66\n&lt;ia1AEIM6",
123
+ recipeConfig: [
124
+ {
125
+ op: "Show Base64 offsets",
126
+ args: ["<script>ale(1)/.ABCDEFGHIJKLMNOPQRSTUVWXYZbdfghjkmnoquvwxyz023456", false, "Raw"],
127
+ },
128
+ ],
129
+ },
119
130
  ]);