Chef.mjs 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. /**
  2. * @author n1474335 [n1474335@gmail.com]
  3. * @copyright Crown Copyright 2016
  4. * @license Apache-2.0
  5. */
  6. import Dish from "./Dish";
  7. import Recipe from "./Recipe";
  8. import log from "loglevel";
  9. /**
  10. * The main controller for CyberChef.
  11. */
  12. class Chef {
  13. /**
  14. * Chef constructor
  15. */
  16. constructor() {
  17. this.dish = new Dish();
  18. }
  19. /**
  20. * Runs the recipe over the input.
  21. *
  22. * @param {string|ArrayBuffer} input - The input data as a string or ArrayBuffer
  23. * @param {Object[]} recipeConfig - The recipe configuration object
  24. * @param {Object} options - The options object storing various user choices
  25. * @param {boolean} options.attempHighlight - Whether or not to attempt highlighting
  26. * @param {number} progress - The position in the recipe to start from
  27. * @param {number} [step] - Whether to only execute one operation in the recipe
  28. *
  29. * @returns {Object} response
  30. * @returns {string} response.result - The output of the recipe
  31. * @returns {string} response.type - The data type of the result
  32. * @returns {number} response.progress - The position that we have got to in the recipe
  33. * @returns {number} response.duration - The number of ms it took to execute the recipe
  34. * @returns {number} response.error - The error object thrown by a failed operation (false if no error)
  35. */
  36. async bake(input, recipeConfig, options, progress, step) {
  37. log.debug("Chef baking");
  38. const startTime = new Date().getTime(),
  39. recipe = new Recipe(recipeConfig),
  40. containsFc = recipe.containsFlowControl(),
  41. notUTF8 = options && options.hasOwnProperty("treatAsUtf8") && !options.treatAsUtf8;
  42. let error = false;
  43. if (containsFc && ENVIRONMENT_IS_WORKER()) self.setOption("attemptHighlight", false);
  44. // Clean up progress
  45. if (progress >= recipeConfig.length) {
  46. progress = 0;
  47. }
  48. if (step) {
  49. // Unset breakpoint on this step
  50. recipe.setBreakpoint(progress, false);
  51. // Set breakpoint on next step
  52. recipe.setBreakpoint(progress + 1, true);
  53. }
  54. // If the previously run operation presented a different value to its
  55. // normal output, we need to recalculate it.
  56. if (recipe.lastOpPresented(progress)) {
  57. progress = 0;
  58. }
  59. // If stepping with flow control, we have to start from the beginning
  60. // but still want to skip all previous breakpoints
  61. if (progress > 0 && containsFc) {
  62. recipe.removeBreaksUpTo(progress);
  63. progress = 0;
  64. }
  65. // If starting from scratch, load data
  66. if (progress === 0) {
  67. const type = input instanceof ArrayBuffer ? Dish.ARRAY_BUFFER : Dish.STRING;
  68. this.dish.set(input, type);
  69. }
  70. try {
  71. progress = await recipe.execute(this.dish, progress);
  72. } catch (err) {
  73. log.error(err);
  74. error = {
  75. displayStr: err.displayStr,
  76. };
  77. progress = err.progress;
  78. }
  79. // Depending on the size of the output, we may send it back as a string or an ArrayBuffer.
  80. // This can prevent unnecessary casting as an ArrayBuffer can be easily downloaded as a file.
  81. // The threshold is specified in KiB.
  82. const threshold = (options.ioDisplayThreshold || 1024) * 1024;
  83. const returnType = this.dish.size > threshold ? Dish.ARRAY_BUFFER : Dish.STRING;
  84. // Create a raw version of the dish, unpresented
  85. const rawDish = new Dish(this.dish);
  86. // Present the raw result
  87. await recipe.present(this.dish);
  88. return {
  89. dish: rawDish,
  90. result: this.dish.type === Dish.HTML ?
  91. await this.dish.get(Dish.HTML, notUTF8) :
  92. await this.dish.get(returnType, notUTF8),
  93. type: Dish.enumLookup(this.dish.type),
  94. progress: progress,
  95. duration: new Date().getTime() - startTime,
  96. error: error
  97. };
  98. }
  99. /**
  100. * When a browser tab is unfocused and the browser has to run lots of dynamic content in other tabs,
  101. * it swaps out the memory for that tab. If the CyberChef tab has been unfocused for more than a
  102. * minute, we run a silent bake which will force the browser to load and cache all the relevant
  103. * JavaScript code needed to do a real bake.
  104. *
  105. * This will stop baking taking a long time when the CyberChef browser tab has been unfocused for a
  106. * long time and the browser has swapped out all its memory.
  107. *
  108. * The output will not be modified (hence "silent" bake).
  109. *
  110. * This will only actually execute the recipe if auto-bake is enabled, otherwise it will just load
  111. * the recipe, ingredients and dish.
  112. *
  113. * @param {Object[]} recipeConfig - The recipe configuration object
  114. * @returns {number} The time it took to run the silent bake in milliseconds.
  115. */
  116. silentBake(recipeConfig) {
  117. log.debug("Running silent bake");
  118. const startTime = new Date().getTime(),
  119. recipe = new Recipe(recipeConfig),
  120. dish = new Dish();
  121. try {
  122. recipe.execute(dish);
  123. } catch (err) {
  124. // Suppress all errors
  125. }
  126. return new Date().getTime() - startTime;
  127. }
  128. /**
  129. * Calculates highlight offsets if possible.
  130. *
  131. * @param {Object[]} recipeConfig
  132. * @param {string} direction
  133. * @param {Object} pos - The position object for the highlight.
  134. * @param {number} pos.start - The start offset.
  135. * @param {number} pos.end - The end offset.
  136. * @returns {Object}
  137. */
  138. calculateHighlights(recipeConfig, direction, pos) {
  139. const recipe = new Recipe(recipeConfig);
  140. const highlights = recipe.generateHighlightList();
  141. if (!highlights) return false;
  142. for (let i = 0; i < highlights.length; i++) {
  143. // Remove multiple highlights before processing again
  144. pos = [pos[0]];
  145. const func = direction === "forward" ? highlights[i].f : highlights[i].b;
  146. if (typeof func == "function") {
  147. pos = func(pos, highlights[i].args);
  148. }
  149. }
  150. return {
  151. pos: pos,
  152. direction: direction
  153. };
  154. }
  155. /**
  156. * Translates the dish to a specified type and returns it.
  157. *
  158. * @param {Dish} dish
  159. * @param {string} type
  160. * @returns {Dish}
  161. */
  162. async getDishAs(dish, type) {
  163. const newDish = new Dish(dish);
  164. return await newDish.get(type);
  165. }
  166. }
  167. export default Chef;