HTMLIngredient.mjs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. /**
  2. * @author n1474335 [n1474335@gmail.com]
  3. * @copyright Crown Copyright 2016
  4. * @license Apache-2.0
  5. */
  6. import Utils from "../core/Utils";
  7. /**
  8. * Object to handle the creation of operation ingredients.
  9. */
  10. class HTMLIngredient {
  11. /**
  12. * HTMLIngredient constructor.
  13. *
  14. * @param {Object} config - The configuration object for this ingredient.
  15. * @param {App} app - The main view object for CyberChef.
  16. * @param {Manager} manager - The CyberChef event manager.
  17. */
  18. constructor(config, app, manager) {
  19. this.app = app;
  20. this.manager = manager;
  21. this.name = config.name;
  22. this.type = config.type;
  23. this.value = config.value;
  24. this.disabled = config.disabled || false;
  25. this.hint = config.hint || false;
  26. this.rows = config.rows || false;
  27. this.target = config.target;
  28. this.defaultIndex = config.defaultIndex || 0;
  29. this.toggleValues = config.toggleValues;
  30. this.id = "ing-" + this.app.nextIngId();
  31. }
  32. /**
  33. * Renders the ingredient in HTML.
  34. *
  35. * @returns {string}
  36. */
  37. toHtml() {
  38. let html = "",
  39. i, m, eventFn;
  40. switch (this.type) {
  41. case "string":
  42. case "binaryString":
  43. case "byteArray":
  44. html += `<div class="form-group">
  45. <label for="${this.id}" class="bmd-label-floating">${this.name}</label>
  46. <input type="text"
  47. class="form-control arg"
  48. id="${this.id}"
  49. arg-name="${this.name}"
  50. value="${this.value}"
  51. ${this.disabled ? "disabled" : ""}>
  52. ${this.hint ? "<span class='bmd-help'>" + this.hint + "</span>" : ""}
  53. </div>`;
  54. break;
  55. case "shortString":
  56. case "binaryShortString":
  57. html += `<div class="form-group inline">
  58. <label for="${this.id}" class="bmd-label-floating inline">${this.name}</label>
  59. <input type="text"
  60. class="form-control arg inline"
  61. id="${this.id}"
  62. arg-name="${this.name}"
  63. value="${this.value}"
  64. ${this.disabled ? "disabled" : ""}>
  65. ${this.hint ? "<span class='bmd-help'>" + this.hint + "</span>" : ""}
  66. </div>`;
  67. break;
  68. case "toggleString":
  69. html += `<div class="form-group input-group">
  70. <div class="toggle-string">
  71. <label for="${this.id}" class="bmd-label-floating toggle-string">${this.name}</label>
  72. <input type="text"
  73. class="form-control arg toggle-string"
  74. id="${this.id}"
  75. arg-name="${this.name}"
  76. value="${this.value}"
  77. ${this.disabled ? "disabled" : ""}>
  78. ${this.hint ? "<span class='bmd-help'>" + this.hint + "</span>" : ""}
  79. </div>
  80. <div class="input-group-append">
  81. <button class="btn btn-secondary dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">${this.toggleValues[0]}</button>
  82. <div class="dropdown-menu toggle-dropdown">`;
  83. for (i = 0; i < this.toggleValues.length; i++) {
  84. html += `<a class="dropdown-item" href="#">${this.toggleValues[i]}</a>`;
  85. }
  86. html += `</div>
  87. </div>
  88. </div>`;
  89. break;
  90. case "number":
  91. html += `<div class="form-group inline">
  92. <label for="${this.id}" class="bmd-label-floating inline">${this.name}</label>
  93. <input type="number"
  94. class="form-control arg inline"
  95. id="${this.id}"
  96. arg-name="${this.name}"
  97. value="${this.value}"
  98. ${this.disabled ? "disabled" : ""}>
  99. ${this.hint ? "<span class='bmd-help'>" + this.hint + "</span>" : ""}
  100. </div>`;
  101. break;
  102. case "boolean":
  103. html += `<div class="form-group inline boolean-arg">
  104. <div class="checkbox">
  105. <label>
  106. <input type="checkbox"
  107. class="arg"
  108. id="${this.id}"
  109. arg-name="${this.name}"
  110. ${this.value ? " checked" : ""}
  111. ${this.disabled ? " disabled" : ""}
  112. value="${this.name}"> ${this.name}
  113. </label>
  114. ${this.hint ? "<span class='bmd-help'>" + this.hint + "</span>" : ""}
  115. </div>
  116. </div>`;
  117. break;
  118. case "option":
  119. html += `<div class="form-group inline">
  120. <label for="${this.id}" class="bmd-label-floating inline">${this.name}</label>
  121. <select
  122. class="form-control arg inline"
  123. id="${this.id}"
  124. arg-name="${this.name}"
  125. ${this.disabled ? "disabled" : ""}>`;
  126. for (i = 0; i < this.value.length; i++) {
  127. if ((m = this.value[i].match(/\[([a-z0-9 -()^]+)\]/i))) {
  128. html += `<optgroup label="${m[1]}">`;
  129. } else if ((m = this.value[i].match(/\[\/([a-z0-9 -()^]+)\]/i))) {
  130. html += "</optgroup>";
  131. } else {
  132. html += `<option ${this.defaultIndex === i ? "selected" : ""}>${this.value[i]}</option>`;
  133. }
  134. }
  135. html += `</select>
  136. ${this.hint ? "<span class='bmd-help'>" + this.hint + "</span>" : ""}
  137. </div>`;
  138. break;
  139. case "populateOption":
  140. case "populateMultiOption":
  141. html += `<div class="form-group">
  142. <label for="${this.id}" class="bmd-label-floating">${this.name}</label>
  143. <select
  144. class="form-control arg no-state-change populate-option"
  145. id="${this.id}"
  146. arg-name="${this.name}"
  147. ${this.disabled ? "disabled" : ""}>`;
  148. for (i = 0; i < this.value.length; i++) {
  149. if ((m = this.value[i].name.match(/\[([a-z0-9 -()^]+)\]/i))) {
  150. html += `<optgroup label="${m[1]}">`;
  151. } else if ((m = this.value[i].name.match(/\[\/([a-z0-9 -()^]+)\]/i))) {
  152. html += "</optgroup>";
  153. } else {
  154. const val = this.type === "populateMultiOption" ?
  155. JSON.stringify(this.value[i].value) :
  156. this.value[i].value;
  157. html += `<option populate-value='${Utils.escapeHtml(val)}'>${this.value[i].name}</option>`;
  158. }
  159. }
  160. html += `</select>
  161. ${this.hint ? "<span class='bmd-help'>" + this.hint + "</span>" : ""}
  162. </div>`;
  163. eventFn = this.type === "populateMultiOption" ?
  164. this.populateMultiOptionChange :
  165. this.populateOptionChange;
  166. this.manager.addDynamicListener("#" + this.id, "change", eventFn, this);
  167. break;
  168. case "editableOption":
  169. html += `<div class="form-group input-group">
  170. <label for="${this.id}" class="bmd-label-floating">${this.name}</label>
  171. <input type="text"
  172. class="form-control arg"
  173. id="${this.id}"
  174. arg-name="${this.name}"
  175. value="${this.value[this.defaultIndex].value}"
  176. ${this.disabled ? "disabled" : ""}>
  177. ${this.hint ? "<span class='bmd-help'>" + this.hint + "</span>" : ""}
  178. <div class="input-group-append">
  179. <button type="button"
  180. class="btn btn-secondary dropdown-toggle dropdown-toggle-split"
  181. data-toggle="dropdown"
  182. data-boundary="scrollParent"
  183. aria-haspopup="true"
  184. aria-expanded="false">
  185. <span class="sr-only">Toggle Dropdown</span>
  186. </button>
  187. <div class="dropdown-menu editable-option-menu">`;
  188. for (i = 0; i < this.value.length; i++) {
  189. html += `<a class="dropdown-item" href="#" value="${this.value[i].value}">${this.value[i].name}</a>`;
  190. }
  191. html += `</div>
  192. </div>
  193. </div>`;
  194. this.manager.addDynamicListener(".editable-option-menu a", "click", this.editableOptionClick, this);
  195. break;
  196. case "editableOptionShort":
  197. html += `<div class="form-group input-group inline">
  198. <label for="${this.id}" class="bmd-label-floating inline">${this.name}</label>
  199. <input type="text"
  200. class="form-control arg inline"
  201. id="${this.id}"
  202. arg-name="${this.name}"
  203. value="${this.value[this.defaultIndex].value}"
  204. ${this.disabled ? "disabled" : ""}>
  205. ${this.hint ? "<span class='bmd-help'>" + this.hint + "</span>" : ""}
  206. <div class="input-group-append inline">
  207. <button type="button"
  208. class="btn btn-secondary dropdown-toggle dropdown-toggle-split"
  209. data-toggle="dropdown"
  210. data-boundary="scrollParent"
  211. aria-haspopup="true"
  212. aria-expanded="false">
  213. <span class="sr-only">Toggle Dropdown</span>
  214. </button>
  215. <div class="dropdown-menu editable-option-menu">`;
  216. for (i = 0; i < this.value.length; i++) {
  217. html += `<a class="dropdown-item" href="#" value="${this.value[i].value}">${this.value[i].name}</a>`;
  218. }
  219. html += `</div>
  220. </div>
  221. </div>`;
  222. this.manager.addDynamicListener(".editable-option-menu a", "click", this.editableOptionClick, this);
  223. break;
  224. case "text":
  225. html += `<div class="form-group">
  226. <label for="${this.id}" class="bmd-label-floating">${this.name}</label>
  227. <textarea
  228. class="form-control arg"
  229. id="${this.id}"
  230. arg-name="${this.name}"
  231. rows="${this.rows ? this.rows : 3}"
  232. ${this.disabled ? "disabled" : ""}>${this.value}</textarea>
  233. ${this.hint ? "<span class='bmd-help'>" + this.hint + "</span>" : ""}
  234. </div>`;
  235. break;
  236. case "argSelector":
  237. html += `<div class="form-group inline">
  238. <label for="${this.id}" class="bmd-label-floating inline">${this.name}</label>
  239. <select
  240. class="form-control arg inline arg-selector"
  241. id="${this.id}"
  242. arg-name="${this.name}"
  243. ${this.disabled ? "disabled" : ""}>`;
  244. for (i = 0; i < this.value.length; i++) {
  245. html += `<option ${this.defaultIndex === i ? "selected" : ""}
  246. turnon="${JSON.stringify(this.value[i].on || [])}"
  247. turnoff="${JSON.stringify(this.value[i].off || [])}">
  248. ${this.value[i].name}
  249. </option>`;
  250. }
  251. html += `</select>
  252. ${this.hint ? "<span class='bmd-help'>" + this.hint + "</span>" : ""}
  253. </div>`;
  254. this.manager.addDynamicListener(".arg-selector", "change", this.argSelectorChange, this);
  255. break;
  256. default:
  257. break;
  258. }
  259. return html;
  260. }
  261. /**
  262. * Handler for populate option changes.
  263. * Populates the relevant argument with the specified value.
  264. *
  265. * @param {event} e
  266. */
  267. populateOptionChange(e) {
  268. e.preventDefault();
  269. e.stopPropagation();
  270. const el = e.target;
  271. const op = el.parentNode.parentNode;
  272. const target = op.querySelectorAll(".arg")[this.target];
  273. target.value = el.childNodes[el.selectedIndex].getAttribute("populate-value");
  274. const evt = new Event("change");
  275. target.dispatchEvent(evt);
  276. this.manager.recipe.ingChange();
  277. }
  278. /**
  279. * Handler for populate multi option changes.
  280. * Populates the relevant arguments with the specified values.
  281. *
  282. * @param {event} e
  283. */
  284. populateMultiOptionChange(e) {
  285. e.preventDefault();
  286. e.stopPropagation();
  287. const el = e.target;
  288. const op = el.parentNode.parentNode;
  289. const args = op.querySelectorAll(".arg");
  290. const targets = this.target.map(i => args[i]);
  291. const vals = JSON.parse(el.childNodes[el.selectedIndex].getAttribute("populate-value"));
  292. const evt = new Event("change");
  293. for (let i = 0; i < targets.length; i++) {
  294. targets[i].value = vals[i];
  295. }
  296. // Fire change event after all targets have been assigned
  297. this.manager.recipe.ingChange();
  298. // Send change event for each target once all have been assigned, to update the label placement.
  299. for (const target of targets) {
  300. target.dispatchEvent(evt);
  301. }
  302. }
  303. /**
  304. * Handler for editable option clicks.
  305. * Populates the input box with the selected value.
  306. *
  307. * @param {event} e
  308. */
  309. editableOptionClick(e) {
  310. e.preventDefault();
  311. e.stopPropagation();
  312. const link = e.target,
  313. input = link.parentNode.parentNode.parentNode.querySelector("input");
  314. input.value = link.getAttribute("value");
  315. const evt = new Event("change");
  316. input.dispatchEvent(evt);
  317. this.manager.recipe.ingChange();
  318. }
  319. /**
  320. * Handler for argument selector changes.
  321. * Shows or hides the relevant arguments for this operation.
  322. *
  323. * @param {event} e
  324. */
  325. argSelectorChange(e) {
  326. e.preventDefault();
  327. e.stopPropagation();
  328. const option = e.target.options[e.target.selectedIndex];
  329. const op = e.target.closest(".operation");
  330. const args = op.querySelectorAll(".ingredients .form-group");
  331. const turnon = JSON.parse(option.getAttribute("turnon"));
  332. const turnoff = JSON.parse(option.getAttribute("turnoff"));
  333. args.forEach((arg, i) => {
  334. if (turnon.includes(i)) {
  335. arg.classList.remove("d-none");
  336. }
  337. if (turnoff.includes(i)) {
  338. arg.classList.add("d-none");
  339. }
  340. });
  341. }
  342. }
  343. export default HTMLIngredient;