cyberchef 9.38.0 → 9.38.4

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cyberchef",
3
- "version": "9.38.0",
3
+ "version": "9.38.4",
4
4
  "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.",
5
5
  "author": "n1474335 <n1474335@gmail.com>",
6
6
  "homepage": "https://gchq.github.io/CyberChef",
@@ -69,6 +69,7 @@
69
69
  "html-webpack-plugin": "^5.5.0",
70
70
  "imports-loader": "^3.1.1",
71
71
  "mini-css-extract-plugin": "2.6.0",
72
+ "modify-source-webpack-plugin": "^3.0.0",
72
73
  "nightwatch": "^2.0.10",
73
74
  "postcss": "^8.4.12",
74
75
  "postcss-css-variables": "^0.18.0",
@@ -174,6 +175,7 @@
174
175
  "lint": "npx grunt lint",
175
176
  "postinstall": "npx grunt exec:fixCryptoApiImports",
176
177
  "newop": "node --experimental-modules --experimental-json-modules src/core/config/scripts/newOperation.mjs",
178
+ "minor": "node --experimental-modules --experimental-json-modules src/core/config/scripts/newMinorVersion.mjs",
177
179
  "getheapsize": "node -e 'console.log(`node heap limit = ${require(\"v8\").getHeapStatistics().heap_size_limit / (1024 * 1024)} Mb`)'",
178
180
  "setheapsize": "export NODE_OPTIONS=--max_old_space_size=2048"
179
181
  }
@@ -5791,6 +5791,11 @@
5791
5791
  "name": "Remove non-alphabet chars",
5792
5792
  "type": "boolean",
5793
5793
  "value": true
5794
+ },
5795
+ {
5796
+ "name": "Strict mode",
5797
+ "type": "boolean",
5798
+ "value": false
5794
5799
  }
5795
5800
  ],
5796
5801
  "checks": [
@@ -5799,7 +5804,8 @@
5799
5804
  "flags": "i",
5800
5805
  "args": [
5801
5806
  "A-Za-z0-9+/=",
5802
- true
5807
+ true,
5808
+ false
5803
5809
  ]
5804
5810
  },
5805
5811
  {
@@ -5807,7 +5813,8 @@
5807
5813
  "flags": "i",
5808
5814
  "args": [
5809
5815
  "A-Za-z0-9-_",
5810
- true
5816
+ true,
5817
+ false
5811
5818
  ]
5812
5819
  },
5813
5820
  {
@@ -5815,7 +5822,8 @@
5815
5822
  "flags": "i",
5816
5823
  "args": [
5817
5824
  "A-Za-z0-9+\\-=",
5818
- true
5825
+ true,
5826
+ false
5819
5827
  ]
5820
5828
  },
5821
5829
  {
@@ -5823,7 +5831,8 @@
5823
5831
  "flags": "i",
5824
5832
  "args": [
5825
5833
  "./0-9A-Za-z=",
5826
- true
5834
+ true,
5835
+ false
5827
5836
  ]
5828
5837
  },
5829
5838
  {
@@ -5831,7 +5840,8 @@
5831
5840
  "flags": "i",
5832
5841
  "args": [
5833
5842
  "A-Za-z0-9_.",
5834
- true
5843
+ true,
5844
+ false
5835
5845
  ]
5836
5846
  },
5837
5847
  {
@@ -5839,7 +5849,8 @@
5839
5849
  "flags": "i",
5840
5850
  "args": [
5841
5851
  "A-Za-z0-9._-",
5842
- true
5852
+ true,
5853
+ false
5843
5854
  ]
5844
5855
  },
5845
5856
  {
@@ -5847,7 +5858,8 @@
5847
5858
  "flags": "i",
5848
5859
  "args": [
5849
5860
  "0-9a-zA-Z+/=",
5850
- true
5861
+ true,
5862
+ false
5851
5863
  ]
5852
5864
  },
5853
5865
  {
@@ -5855,7 +5867,8 @@
5855
5867
  "flags": "i",
5856
5868
  "args": [
5857
5869
  "0-9A-Za-z+/=",
5858
- true
5870
+ true,
5871
+ false
5859
5872
  ]
5860
5873
  },
5861
5874
  {
@@ -5863,6 +5876,7 @@
5863
5876
  "flags": "",
5864
5877
  "args": [
5865
5878
  " -_",
5879
+ false,
5866
5880
  false
5867
5881
  ]
5868
5882
  },
@@ -5871,7 +5885,8 @@
5871
5885
  "flags": "i",
5872
5886
  "args": [
5873
5887
  "+\\-0-9A-Za-z",
5874
- true
5888
+ true,
5889
+ false
5875
5890
  ]
5876
5891
  },
5877
5892
  {
@@ -5879,7 +5894,8 @@
5879
5894
  "flags": "",
5880
5895
  "args": [
5881
5896
  "!-,-0-689@A-NP-VX-Z[`a-fh-mp-r",
5882
- true
5897
+ true,
5898
+ false
5883
5899
  ]
5884
5900
  },
5885
5901
  {
@@ -5887,7 +5903,8 @@
5887
5903
  "flags": "i",
5888
5904
  "args": [
5889
5905
  "N-ZA-Mn-za-m0-9+/=",
5890
- true
5906
+ true,
5907
+ false
5891
5908
  ]
5892
5909
  },
5893
5910
  {
@@ -5895,7 +5912,8 @@
5895
5912
  "flags": "i",
5896
5913
  "args": [
5897
5914
  "./0-9A-Za-z",
5898
- true
5915
+ true,
5916
+ false
5899
5917
  ]
5900
5918
  },
5901
5919
  {
@@ -5903,7 +5921,8 @@
5903
5921
  "flags": "i",
5904
5922
  "args": [
5905
5923
  "/128GhIoPQROSTeUbADfgHijKLM+n0pFWXY456xyzB7=39VaqrstJklmNuZvwcdEC",
5906
- true
5924
+ true,
5925
+ false
5907
5926
  ]
5908
5927
  },
5909
5928
  {
@@ -5911,7 +5930,8 @@
5911
5930
  "flags": "i",
5912
5931
  "args": [
5913
5932
  "3GHIJKLMNOPQRSTUb=cdefghijklmnopWXYZ/12+406789VaqrstuvwxyzABCDEF5",
5914
- true
5933
+ true,
5934
+ false
5915
5935
  ]
5916
5936
  },
5917
5937
  {
@@ -5919,7 +5939,8 @@
5919
5939
  "flags": "i",
5920
5940
  "args": [
5921
5941
  "ZKj9n+yf0wDVX1s/5YbdxSo=ILaUpPBCHg8uvNO4klm6iJGhQ7eFrWczAMEq3RTt2",
5922
- true
5942
+ true,
5943
+ false
5923
5944
  ]
5924
5945
  },
5925
5946
  {
@@ -5927,7 +5948,8 @@
5927
5948
  "flags": "i",
5928
5949
  "args": [
5929
5950
  "HNO4klm6ij9n+J2hyf0gzA8uvwDEq3X1Q7ZKeFrWcVTts/MRGYbdxSo=ILaUpPBC5",
5930
- true
5951
+ true,
5952
+ false
5931
5953
  ]
5932
5954
  }
5933
5955
  ]
@@ -0,0 +1,144 @@
1
+ /**
2
+ * This script updates the CHANGELOG when a new minor version is created.
3
+ *
4
+ * @author n1474335 [n1474335@gmail.com]
5
+ * @copyright Crown Copyright 2022
6
+ * @license Apache-2.0
7
+ */
8
+
9
+ /* eslint no-console: ["off"] */
10
+
11
+ import prompt from "prompt";
12
+ import colors from "colors";
13
+ import path from "path";
14
+ import fs from "fs";
15
+ import process from "process";
16
+
17
+ const dir = path.join(process.cwd() + "/src/core/config/");
18
+ if (!fs.existsSync(dir)) {
19
+ console.log("\nCWD: " + process.cwd());
20
+ console.log("Error: newMinorVersion.mjs should be run from the project root");
21
+ console.log("Example> node --experimental-modules src/core/config/scripts/newMinorVersion.mjs");
22
+ process.exit(1);
23
+ }
24
+
25
+ let changelogData = fs.readFileSync(path.join(process.cwd(), "CHANGELOG.md"), "utf8");
26
+ const lastVersion = changelogData.match(/## Details\s+### \[(\d+)\.(\d+)\.(\d+)\]/);
27
+ const newVersion = [
28
+ parseInt(lastVersion[1], 10),
29
+ parseInt(lastVersion[2], 10) + 1,
30
+ 0
31
+ ];
32
+
33
+ let knownContributors = changelogData.match(/^\[@([^\]]+)\]/gm);
34
+ knownContributors = knownContributors.map(c => c.slice(2, -1));
35
+
36
+ const date = (new Date()).toISOString().split("T")[0];
37
+
38
+ const schema = {
39
+ properties: {
40
+ message: {
41
+ description: "A short but descriptive summary of a feature in this version",
42
+ example: "Added 'Op name' operation",
43
+ prompt: "Feature description",
44
+ type: "string",
45
+ required: true,
46
+ },
47
+ author: {
48
+ description: "The author of the feature (only one supported, edit manually to add more)",
49
+ example: "n1474335",
50
+ prompt: "Author",
51
+ type: "string",
52
+ default: "n1474335"
53
+ },
54
+ id: {
55
+ description: "The PR number or full commit hash for this feature.",
56
+ example: "1200",
57
+ prompt: "Pull request or commit ID",
58
+ type: "string"
59
+ },
60
+ another: {
61
+ description: "y/n",
62
+ example: "y",
63
+ prompt: "Add another feature?",
64
+ type: "string",
65
+ pattern: /^[yn]$/,
66
+ }
67
+ }
68
+ };
69
+
70
+ // Build schema
71
+ for (const prop in schema.properties) {
72
+ const p = schema.properties[prop];
73
+ p.description = "\n" + colors.white(p.description) + colors.cyan("\nExample: " + p.example) + "\n" + colors.green(p.prompt);
74
+ }
75
+
76
+ prompt.message = "";
77
+ prompt.delimiter = ":".green;
78
+
79
+ const features = [];
80
+ const authors = [];
81
+ const prIDs = [];
82
+ const commitIDs = [];
83
+
84
+ prompt.start();
85
+
86
+ const getFeature = function() {
87
+ prompt.get(schema, (err, result) => {
88
+ if (err) {
89
+ console.log("\nExiting script.");
90
+ process.exit(0);
91
+ }
92
+
93
+ features.push(result);
94
+
95
+ if (result.another === "y") {
96
+ getFeature();
97
+ } else {
98
+ let message = `### [${newVersion[0]}.${newVersion[1]}.${newVersion[2]}] - ${date}\n`;
99
+
100
+ features.forEach(feature => {
101
+ const id = feature.id.length > 10 ? feature.id.slice(0, 7) : "#" + feature.id;
102
+ message += `- ${feature.message} [@${feature.author}] | [${id}]\n`;
103
+
104
+ if (!knownContributors.includes(feature.author)) {
105
+ authors.push(`[@${feature.author}]: https://github.com/${feature.author}`);
106
+ }
107
+
108
+ if (feature.id.length > 10) {
109
+ commitIDs.push(`[${id}]: https://github.com/gchq/CyberChef/commit/${feature.id}`);
110
+ } else {
111
+ prIDs.push(`[#${feature.id}]: https://github.com/gchq/CyberChef/pull/${feature.id}`);
112
+ }
113
+ });
114
+
115
+ // Message
116
+ changelogData = changelogData.replace(/## Details\n\n/, "## Details\n\n" + message + "\n");
117
+
118
+ // Tag
119
+ const newTag = `[${newVersion[0]}.${newVersion[1]}.${newVersion[2]}]: https://github.com/gchq/CyberChef/releases/tag/v${newVersion[0]}.${newVersion[1]}.${newVersion[2]}\n`;
120
+ changelogData = changelogData.replace(/\n\n(\[\d+\.\d+\.\d+\]: https)/, "\n\n" + newTag + "$1");
121
+
122
+ // Author
123
+ authors.forEach(author => {
124
+ changelogData = changelogData.replace(/(\n\[@[^\]]+\]: https:\/\/github\.com\/[^\n]+\n)\n/, "$1" + author + "\n\n");
125
+ });
126
+
127
+ // Commit IDs
128
+ commitIDs.forEach(commitID => {
129
+ changelogData = changelogData.replace(/(\n\[[^\].]+\]: https:\/\/github.com\/gchq\/CyberChef\/commit\/[^\n]+\n)\n/, "$1" + commitID + "\n\n");
130
+ });
131
+
132
+ // PR IDs
133
+ prIDs.forEach(prID => {
134
+ changelogData = changelogData.replace(/(\n\[#[^\]]+\]: https:\/\/github.com\/gchq\/CyberChef\/pull\/[^\n]+\n)\n/, "$1" + prID + "\n\n");
135
+ });
136
+
137
+ fs.writeFileSync(path.join(process.cwd(), "CHANGELOG.md"), changelogData);
138
+
139
+ console.log("Written CHANGELOG.md");
140
+ }
141
+ });
142
+ };
143
+
144
+ getFeature();
@@ -82,47 +82,74 @@ export function toBase64(data, alphabet="A-Za-z0-9+/=") {
82
82
  * // returns [72, 101, 108, 108, 111]
83
83
  * fromBase64("SGVsbG8=", null, "byteArray");
84
84
  */
85
- export function fromBase64(data, alphabet="A-Za-z0-9+/=", returnType="string", removeNonAlphChars=true) {
85
+ export function fromBase64(data, alphabet="A-Za-z0-9+/=", returnType="string", removeNonAlphChars=true, strictMode=false) {
86
86
  if (!data) {
87
87
  return returnType === "string" ? "" : [];
88
88
  }
89
89
 
90
90
  alphabet = alphabet || "A-Za-z0-9+/=";
91
91
  alphabet = Utils.expandAlphRange(alphabet).join("");
92
+
93
+ // Confirm alphabet is a valid length
92
94
  if (alphabet.length !== 64 && alphabet.length !== 65) { // Allow for padding
93
- throw new OperationError(`Invalid Base64 alphabet length (${alphabet.length}): ${alphabet}`);
95
+ throw new OperationError(`Error: Base64 alphabet should be 64 characters long, or 65 with a padding character. Found ${alphabet.length}: ${alphabet}`);
94
96
  }
95
97
 
96
- const output = [];
97
- let chr1, chr2, chr3,
98
- enc1, enc2, enc3, enc4,
99
- i = 0;
100
-
98
+ // Remove non-alphabet characters
101
99
  if (removeNonAlphChars) {
102
100
  const re = new RegExp("[^" + alphabet.replace(/[[\]\\\-^$]/g, "\\$&") + "]", "g");
103
101
  data = data.replace(re, "");
104
102
  }
105
103
 
104
+ if (strictMode) {
105
+ // Check for incorrect lengths (even without padding)
106
+ if (data.length % 4 === 1) {
107
+ throw new OperationError(`Error: Invalid Base64 input length (${data.length}). Cannot be 4n+1, even without padding chars.`);
108
+ }
109
+
110
+ if (alphabet.length === 65) { // Padding character included
111
+ const pad = alphabet.charAt(64);
112
+ const padPos = data.indexOf(pad);
113
+ if (padPos >= 0) {
114
+ // Check that the padding character is only used at the end and maximum of twice
115
+ if (padPos < data.length - 2 || data.charAt(data.length - 1) !== pad) {
116
+ throw new OperationError(`Error: Base64 padding character (${pad}) not used in the correct place.`);
117
+ }
118
+
119
+ // Check that input is padded to the correct length
120
+ if (data.length % 4 !== 0) {
121
+ throw new OperationError("Error: Base64 not padded to a multiple of 4.");
122
+ }
123
+ }
124
+ }
125
+ }
126
+
127
+ const output = [];
128
+ let chr1, chr2, chr3,
129
+ enc1, enc2, enc3, enc4,
130
+ i = 0;
131
+
106
132
  while (i < data.length) {
107
133
  enc1 = alphabet.indexOf(data.charAt(i++));
108
- enc2 = alphabet.indexOf(data.charAt(i++) || "=");
109
- enc3 = alphabet.indexOf(data.charAt(i++) || "=");
110
- enc4 = alphabet.indexOf(data.charAt(i++) || "=");
134
+ enc2 = alphabet.indexOf(data.charAt(i++));
135
+ enc3 = alphabet.indexOf(data.charAt(i++));
136
+ enc4 = alphabet.indexOf(data.charAt(i++));
111
137
 
112
- enc2 = enc2 === -1 ? 64 : enc2;
113
- enc3 = enc3 === -1 ? 64 : enc3;
114
- enc4 = enc4 === -1 ? 64 : enc4;
138
+ if (strictMode && (enc1 < 0 || enc2 < 0 || enc3 < 0 || enc4 < 0)) {
139
+ throw new OperationError("Error: Base64 input contains non-alphabet char(s)");
140
+ }
115
141
 
116
142
  chr1 = (enc1 << 2) | (enc2 >> 4);
117
143
  chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
118
144
  chr3 = ((enc3 & 3) << 6) | enc4;
119
145
 
120
- output.push(chr1);
121
-
122
- if (enc3 !== 64) {
146
+ if (chr1 < 256) {
147
+ output.push(chr1);
148
+ }
149
+ if (chr2 < 256 && enc3 !== 64) {
123
150
  output.push(chr2);
124
151
  }
125
- if (enc4 !== 64) {
152
+ if (chr3 < 256 && enc4 !== 64) {
126
153
  output.push(chr3);
127
154
  }
128
155
  }
@@ -19,31 +19,33 @@ import OperationError from "../errors/OperationError.mjs";
19
19
  * @returns {string}
20
20
  *
21
21
  * @example
22
- * // returns "00010000 00100000 00110000"
22
+ * // returns "00001010 00010100 00011110"
23
23
  * toBinary([10,20,30]);
24
24
  *
25
- * // returns "00010000 00100000 00110000"
26
- * toBinary([10,20,30], ":");
25
+ * // returns "00001010:00010100:00011110"
26
+ * toBinary([10,20,30], "Colon");
27
+ *
28
+ * // returns "1010:10100:11110"
29
+ * toBinary([10,20,30], "Colon", 0);
27
30
  */
28
31
  export function toBinary(data, delim="Space", padding=8) {
32
+ if (data === undefined || data === null)
33
+ throw new OperationError("Unable to convert to binary: Empty input data enocuntered");
34
+
29
35
  delim = Utils.charRep(delim);
30
36
  let output = "";
31
37
 
32
38
  if (data.length) { // array
33
39
  for (let i = 0; i < data.length; i++) {
34
- output += data[i].toString(2).padStart(padding, "0") + delim;
40
+ output += data[i].toString(2).padStart(padding, "0");
41
+ if (i !== data.length - 1) output += delim;
35
42
  }
36
43
  } else if (typeof data === "number") { // Single value
37
44
  return data.toString(2).padStart(padding, "0");
38
45
  } else {
39
46
  return "";
40
47
  }
41
-
42
- if (delim.length) {
43
- return output.slice(0, -delim.length);
44
- } else {
45
- return output;
46
- }
48
+ return output;
47
49
  }
48
50
 
49
51
 
@@ -57,10 +59,10 @@ export function toBinary(data, delim="Space", padding=8) {
57
59
  *
58
60
  * @example
59
61
  * // returns [10,20,30]
60
- * fromBinary("00010000 00100000 00110000");
62
+ * fromBinary("00001010 00010100 00011110");
61
63
  *
62
64
  * // returns [10,20,30]
63
- * fromBinary("00010000:00100000:00110000", "Colon");
65
+ * fromBinary("00001010:00010100:00011110", "Colon");
64
66
  */
65
67
  export function fromBinary(data, delim="Space", byteLen=8) {
66
68
  if (byteLen < 1 || Math.round(byteLen) !== byteLen)
@@ -34,93 +34,98 @@ class FromBase64 extends Operation {
34
34
  name: "Remove non-alphabet chars",
35
35
  type: "boolean",
36
36
  value: true
37
+ },
38
+ {
39
+ name: "Strict mode",
40
+ type: "boolean",
41
+ value: false
37
42
  }
38
43
  ];
39
44
  this.checks = [
40
45
  {
41
46
  pattern: "^\\s*(?:[A-Z\\d+/]{4})+(?:[A-Z\\d+/]{2}==|[A-Z\\d+/]{3}=)?\\s*$",
42
47
  flags: "i",
43
- args: ["A-Za-z0-9+/=", true]
48
+ args: ["A-Za-z0-9+/=", true, false]
44
49
  },
45
50
  {
46
51
  pattern: "^\\s*[A-Z\\d\\-_]{20,}\\s*$",
47
52
  flags: "i",
48
- args: ["A-Za-z0-9-_", true]
53
+ args: ["A-Za-z0-9-_", true, false]
49
54
  },
50
55
  {
51
56
  pattern: "^\\s*(?:[A-Z\\d+\\-]{4}){5,}(?:[A-Z\\d+\\-]{2}==|[A-Z\\d+\\-]{3}=)?\\s*$",
52
57
  flags: "i",
53
- args: ["A-Za-z0-9+\\-=", true]
58
+ args: ["A-Za-z0-9+\\-=", true, false]
54
59
  },
55
60
  {
56
61
  pattern: "^\\s*(?:[A-Z\\d./]{4}){5,}(?:[A-Z\\d./]{2}==|[A-Z\\d./]{3}=)?\\s*$",
57
62
  flags: "i",
58
- args: ["./0-9A-Za-z=", true]
63
+ args: ["./0-9A-Za-z=", true, false]
59
64
  },
60
65
  {
61
66
  pattern: "^\\s*[A-Z\\d_.]{20,}\\s*$",
62
67
  flags: "i",
63
- args: ["A-Za-z0-9_.", true]
68
+ args: ["A-Za-z0-9_.", true, false]
64
69
  },
65
70
  {
66
71
  pattern: "^\\s*(?:[A-Z\\d._]{4}){5,}(?:[A-Z\\d._]{2}--|[A-Z\\d._]{3}-)?\\s*$",
67
72
  flags: "i",
68
- args: ["A-Za-z0-9._-", true]
73
+ args: ["A-Za-z0-9._-", true, false]
69
74
  },
70
75
  {
71
76
  pattern: "^\\s*(?:[A-Z\\d+/]{4}){5,}(?:[A-Z\\d+/]{2}==|[A-Z\\d+/]{3}=)?\\s*$",
72
77
  flags: "i",
73
- args: ["0-9a-zA-Z+/=", true]
78
+ args: ["0-9a-zA-Z+/=", true, false]
74
79
  },
75
80
  {
76
81
  pattern: "^\\s*(?:[A-Z\\d+/]{4}){5,}(?:[A-Z\\d+/]{2}==|[A-Z\\d+/]{3}=)?\\s*$",
77
82
  flags: "i",
78
- args: ["0-9A-Za-z+/=", true]
83
+ args: ["0-9A-Za-z+/=", true, false]
79
84
  },
80
85
  {
81
86
  pattern: "^[ !\"#$%&'()*+,\\-./\\d:;<=>?@A-Z[\\\\\\]^_]{20,}$",
82
87
  flags: "",
83
- args: [" -_", false]
88
+ args: [" -_", false, false]
84
89
  },
85
90
  {
86
91
  pattern: "^\\s*[A-Z\\d+\\-]{20,}\\s*$",
87
92
  flags: "i",
88
- args: ["+\\-0-9A-Za-z", true]
93
+ args: ["+\\-0-9A-Za-z", true, false]
89
94
  },
90
95
  {
91
96
  pattern: "^\\s*[!\"#$%&'()*+,\\-0-689@A-NP-VX-Z[`a-fh-mp-r]{20,}\\s*$",
92
97
  flags: "",
93
- args: ["!-,-0-689@A-NP-VX-Z[`a-fh-mp-r", true]
98
+ args: ["!-,-0-689@A-NP-VX-Z[`a-fh-mp-r", true, false]
94
99
  },
95
100
  {
96
101
  pattern: "^\\s*(?:[N-ZA-M\\d+/]{4}){5,}(?:[N-ZA-M\\d+/]{2}==|[N-ZA-M\\d+/]{3}=)?\\s*$",
97
102
  flags: "i",
98
- args: ["N-ZA-Mn-za-m0-9+/=", true]
103
+ args: ["N-ZA-Mn-za-m0-9+/=", true, false]
99
104
  },
100
105
  {
101
106
  pattern: "^\\s*[A-Z\\d./]{20,}\\s*$",
102
107
  flags: "i",
103
- args: ["./0-9A-Za-z", true]
108
+ args: ["./0-9A-Za-z", true, false]
104
109
  },
105
110
  {
106
111
  pattern: "^\\s*(?:[A-Z=\\d\\+/]{4}){5,}(?:[A-Z=\\d\\+/]{2}CC|[A-Z=\\d\\+/]{3}C)?\\s*$",
107
112
  flags: "i",
108
- args: ["/128GhIoPQROSTeUbADfgHijKLM+n0pFWXY456xyzB7=39VaqrstJklmNuZvwcdEC", true]
113
+ args: ["/128GhIoPQROSTeUbADfgHijKLM+n0pFWXY456xyzB7=39VaqrstJklmNuZvwcdEC", true, false]
109
114
  },
110
115
  {
111
116
  pattern: "^\\s*(?:[A-Z=\\d\\+/]{4}){5,}(?:[A-Z=\\d\\+/]{2}55|[A-Z=\\d\\+/]{3}5)?\\s*$",
112
117
  flags: "i",
113
- args: ["3GHIJKLMNOPQRSTUb=cdefghijklmnopWXYZ/12+406789VaqrstuvwxyzABCDEF5", true]
118
+ args: ["3GHIJKLMNOPQRSTUb=cdefghijklmnopWXYZ/12+406789VaqrstuvwxyzABCDEF5", true, false]
114
119
  },
115
120
  {
116
121
  pattern: "^\\s*(?:[A-Z=\\d\\+/]{4}){5,}(?:[A-Z=\\d\\+/]{2}22|[A-Z=\\d\\+/]{3}2)?\\s*$",
117
122
  flags: "i",
118
- args: ["ZKj9n+yf0wDVX1s/5YbdxSo=ILaUpPBCHg8uvNO4klm6iJGhQ7eFrWczAMEq3RTt2", true]
123
+ args: ["ZKj9n+yf0wDVX1s/5YbdxSo=ILaUpPBCHg8uvNO4klm6iJGhQ7eFrWczAMEq3RTt2", true, false]
119
124
  },
120
125
  {
121
126
  pattern: "^\\s*(?:[A-Z=\\d\\+/]{4}){5,}(?:[A-Z=\\d\\+/]{2}55|[A-Z=\\d\\+/]{3}5)?\\s*$",
122
127
  flags: "i",
123
- args: ["HNO4klm6ij9n+J2hyf0gzA8uvwDEq3X1Q7ZKeFrWcVTts/MRGYbdxSo=ILaUpPBC5", true]
128
+ args: ["HNO4klm6ij9n+J2hyf0gzA8uvwDEq3X1Q7ZKeFrWcVTts/MRGYbdxSo=ILaUpPBC5", true, false]
124
129
  }
125
130
  ];
126
131
  }
@@ -131,9 +136,9 @@ class FromBase64 extends Operation {
131
136
  * @returns {byteArray}
132
137
  */
133
138
  run(input, args) {
134
- const [alphabet, removeNonAlphChars] = args;
139
+ const [alphabet, removeNonAlphChars, strictMode] = args;
135
140
 
136
- return fromBase64(input, alphabet, "byteArray", removeNonAlphChars);
141
+ return fromBase64(input, alphabet, "byteArray", removeNonAlphChars, strictMode);
137
142
  }
138
143
 
139
144
  /**
@@ -44,8 +44,8 @@ class Substitute extends Operation {
44
44
  * @returns {string}
45
45
  */
46
46
  run(input, args) {
47
- const plaintext = Utils.expandAlphRange(args[0]).join(""),
48
- ciphertext = Utils.expandAlphRange(args[1]).join("");
47
+ const plaintext = Utils.expandAlphRange([...args[0]]),
48
+ ciphertext = Utils.expandAlphRange([...args[1]]);
49
49
  let output = "",
50
50
  index = -1;
51
51
 
@@ -53,9 +53,9 @@ class Substitute extends Operation {
53
53
  output = "Warning: Plaintext and Ciphertext lengths differ\n\n";
54
54
  }
55
55
 
56
- for (let i = 0; i < input.length; i++) {
57
- index = plaintext.indexOf(input[i]);
58
- output += index > -1 && index < ciphertext.length ? ciphertext[index] : input[i];
56
+ for (const character of input) {
57
+ index = plaintext.indexOf(character);
58
+ output += index > -1 && index < ciphertext.length ? ciphertext[index] : character;
59
59
  }
60
60
 
61
61
  return output;
@@ -65,6 +65,10 @@ class ToBase45 extends Operation {
65
65
 
66
66
  if (chars < 2) {
67
67
  res.push("0");
68
+ chars++;
69
+ }
70
+ if (pair.length > 1 && chars < 3) {
71
+ res.push("0");
68
72
  }
69
73
  }
70
74
 
@@ -67,7 +67,7 @@ class ToHex extends Operation {
67
67
  * @returns {Object[]} pos
68
68
  */
69
69
  highlight(pos, args) {
70
- let delim, commaLen;
70
+ let delim, commaLen = 0;
71
71
  if (args[0] === "0x with comma") {
72
72
  delim = "0x";
73
73
  commaLen = 1;
@@ -86,7 +86,7 @@ class ToHex extends Operation {
86
86
  pos[0].start = pos[0].start * (2 + len) + countLF(pos[0].start);
87
87
  pos[0].end = pos[0].end * (2 + len) + countLF(pos[0].end);
88
88
 
89
- // if the deliminators are not prepended, trim the trailing deliminator
89
+ // if the delimiters are not prepended, trim the trailing delimiter
90
90
  if (!(delim === "0x" || delim === "\\x")) {
91
91
  pos[0].end -= delim.length;
92
92
  }
@@ -5,6 +5,7 @@
5
5
  */
6
6
 
7
7
  import Operation from "../Operation.mjs";
8
+ import OperationError from "../errors/OperationError.mjs";
8
9
 
9
10
  /**
10
11
  * To Upper case operation
@@ -37,25 +38,30 @@ class ToUpperCase extends Operation {
37
38
  * @returns {string}
38
39
  */
39
40
  run(input, args) {
41
+ if (!args || args.length === 0) {
42
+ throw new OperationError("No capitalization scope was provided.");
43
+ }
44
+
40
45
  const scope = args[0];
41
46
 
42
- switch (scope) {
43
- case "Word":
44
- return input.replace(/(\b\w)/gi, function(m) {
45
- return m.toUpperCase();
46
- });
47
- case "Sentence":
48
- return input.replace(/(?:\.|^)\s*(\b\w)/gi, function(m) {
49
- return m.toUpperCase();
50
- });
51
- case "Paragraph":
52
- return input.replace(/(?:\n|^)\s*(\b\w)/gi, function(m) {
53
- return m.toUpperCase();
54
- });
55
- case "All": /* falls through */
56
- default:
57
- return input.toUpperCase();
47
+ if (scope === "All") {
48
+ return input.toUpperCase();
49
+ }
50
+
51
+ const scopeRegex = {
52
+ "Word": /(\b\w)/gi,
53
+ "Sentence": /(?:\.|^)\s*(\b\w)/gi,
54
+ "Paragraph": /(?:\n|^)\s*(\b\w)/gi
55
+ }[scope];
56
+
57
+ if (scopeRegex === undefined) {
58
+ throw new OperationError("Unrecognized capitalization scope");
58
59
  }
60
+
61
+ // Use the regex to capitalize the input
62
+ return input.replace(scopeRegex, function(m) {
63
+ return m.toUpperCase();
64
+ });
59
65
  }
60
66
 
61
67
  /**
package/src/web/App.mjs CHANGED
@@ -57,7 +57,7 @@ class App {
57
57
  this.populateOperationsList();
58
58
  this.manager.setup();
59
59
  this.manager.output.saveBombe();
60
- this.resetLayout();
60
+ this.adjustComponentSizes();
61
61
  this.setCompileMessage();
62
62
 
63
63
  log.debug("App loaded");
@@ -295,9 +295,7 @@ class App {
295
295
  gutterSize: 4,
296
296
  expandToMin: true,
297
297
  onDrag: debounce(function() {
298
- this.manager.recipe.adjustWidth();
299
- this.manager.input.calcMaxTabs();
300
- this.manager.output.calcMaxTabs();
298
+ this.adjustComponentSizes();
301
299
  }, 50, "dragSplitter", this, [])
302
300
  });
303
301
 
@@ -307,7 +305,7 @@ class App {
307
305
  minSize: minimise ? [0, 0] : [100, 100]
308
306
  });
309
307
 
310
- this.resetLayout();
308
+ this.adjustComponentSizes();
311
309
  }
312
310
 
313
311
 
@@ -581,6 +579,13 @@ class App {
581
579
  resetLayout() {
582
580
  this.columnSplitter.setSizes([20, 30, 50]);
583
581
  this.ioSplitter.setSizes([50, 50]);
582
+ this.adjustComponentSizes();
583
+ }
584
+
585
+ /**
586
+ * Adjust components to fit their containers.
587
+ */
588
+ adjustComponentSizes() {
584
589
  this.manager.recipe.adjustWidth();
585
590
  this.manager.input.calcMaxTabs();
586
591
  this.manager.output.calcMaxTabs();
@@ -176,7 +176,7 @@
176
176
  <div id="recipe" class="split split-horizontal no-select">
177
177
  <div class="title no-select">
178
178
  Recipe
179
- <span class="float-right">
179
+ <span class="pane-controls hide-on-maximised-output">
180
180
  <button type="button" class="btn btn-primary bmd-btn-icon" id="save" data-toggle="tooltip" title="Save recipe">
181
181
  <i class="material-icons">save</i>
182
182
  </button>
@@ -190,7 +190,7 @@
190
190
  </div>
191
191
  <ul id="rec-list" class="list-area no-select"></ul>
192
192
 
193
- <div id="controls" class="no-select">
193
+ <div id="controls" class="no-select hide-on-maximised-output">
194
194
  <div id="controls-content" class="d-flex align-items-center">
195
195
  <button type="button" class="mx-2 btn btn-lg btn-secondary" id="step" data-toggle="tooltip" title="Step through the recipe">
196
196
  Step
@@ -217,7 +217,10 @@
217
217
  <div id="input" class="split no-select">
218
218
  <div class="title no-select">
219
219
  <label for="input-text">Input</label>
220
- <span class="float-right">
220
+ <span class="pane-controls">
221
+ <div class="io-info" id="input-files-info"></div>
222
+ <div class="io-info" id="input-selection-info"></div>
223
+ <div class="io-info" id="input-info"></div>
221
224
  <button type="button" class="btn btn-primary bmd-btn-icon" id="btn-new-tab" data-toggle="tooltip" title="Add a new input tab">
222
225
  <i class="material-icons">add</i>
223
226
  </button>
@@ -236,9 +239,7 @@
236
239
  <i class="material-icons">view_compact</i>
237
240
  </button>
238
241
  </span>
239
- <div class="io-info" id="input-files-info"></div>
240
- <div class="io-info" id="input-info"></div>
241
- <div class="io-info" id="input-selection-info"></div>
242
+
242
243
  </div>
243
244
  <div id="input-tabs-wrapper" style="display: none;" class="no-select">
244
245
  <span id="btn-previous-input-tab" class="input-tab-buttons">
@@ -288,7 +289,10 @@
288
289
  <div id="output" class="split">
289
290
  <div class="title no-select">
290
291
  <label for="output-text">Output</label>
291
- <span class="float-right">
292
+ <span class="pane-controls">
293
+ <div class="io-info" id="bake-info"></div>
294
+ <div class="io-info" id="output-selection-info"></div>
295
+ <div class="io-info" id="output-info"></div>
292
296
  <button type="button" class="btn btn-primary bmd-btn-icon" id="save-all-to-file" data-toggle="tooltip" title="Save all outputs to a zip file" style="display: none">
293
297
  <i class="material-icons">archive</i>
294
298
  </button>
@@ -308,9 +312,7 @@
308
312
  <i class="material-icons">fullscreen</i>
309
313
  </button>
310
314
  </span>
311
- <div class="io-info" id="bake-info"></div>
312
- <div class="io-info" id="output-info"></div>
313
- <div class="io-info" id="output-selection-info"></div>
315
+
314
316
  <button type="button" class="btn btn-primary bmd-btn-icon hidden" id="magic" data-toggle="tooltip" title="Magic!" data-html="true">
315
317
  <svg width="22" height="22" viewBox="0 0 24 24">
316
318
  <path d="M7.5,5.6L5,7L6.4,4.5L5,2L7.5,3.4L10,2L8.6,4.5L10,7L7.5,5.6M19.5,15.4L22,14L20.6,16.5L22,19L19.5,17.6L17,19L18.4,16.5L17,14L19.5,15.4M22,2L20.6,4.5L22,7L19.5,5.6L17,7L18.4,4.5L17,2L19.5,3.4L22,2M13.34,12.78L15.78,10.34L13.66,8.22L11.22,10.66L13.34,12.78M14.37,7.29L16.71,9.63C17.1,10 17.1,10.65 16.71,11.04L5.04,22.71C4.65,23.1 4,23.1 3.63,22.71L1.29,20.37C0.9,20 0.9,19.35 1.29,18.96L12.96,7.29C13.35,6.9 14,6.9 14.37,7.29Z" />
@@ -24,9 +24,16 @@
24
24
  line-height: calc(var(--title-height) - 14px);
25
25
  }
26
26
 
27
- .title>span,
28
- .title>.btn {
29
- margin-top: -4px;
27
+ .pane-controls {
28
+ position: absolute;
29
+ right: 8px;
30
+ top: 8px;
31
+ display: flex;
32
+ flex-direction: row;
33
+ }
34
+
35
+ .pane-controls .btn {
36
+ margin-left: 2px;
30
37
  }
31
38
 
32
39
  .list-area {
@@ -107,4 +114,4 @@
107
114
 
108
115
  #files .card-header .float-right a:hover {
109
116
  text-decoration: none;
110
- }
117
+ }
@@ -58,6 +58,10 @@
58
58
  border-radius: 30px;
59
59
  }
60
60
 
61
+ .output-maximised .hide-on-maximised-output {
62
+ display: none !important;
63
+ }
64
+
61
65
  .spin {
62
66
  animation-name: spin;
63
67
  animation-duration: 3s;
@@ -280,9 +280,8 @@
280
280
  }
281
281
 
282
282
  .io-info {
283
- margin-right: 20px;
283
+ margin-right: 18px;
284
284
  margin-top: 1px;
285
- float: right;
286
285
  height: 30px;
287
286
  text-align: right;
288
287
  line-height: 12px;
@@ -39,8 +39,8 @@ div#output {
39
39
 
40
40
  .split {
41
41
  box-sizing: border-box;
42
- /* overflow: auto;
43
- Removed to enable Background Magic button pulse to overflow.
42
+ /* overflow: auto; */
43
+ /* Removed to enable Background Magic button pulse to overflow.
44
44
  Replace this rule if it seems to be causing problems. */
45
45
  position: relative;
46
46
  }
@@ -1373,6 +1373,7 @@ class OutputWaiter {
1373
1373
  const el = e.target.id === "maximise-output" ? e.target : e.target.parentNode;
1374
1374
 
1375
1375
  if (el.getAttribute("data-original-title").indexOf("Maximise") === 0) {
1376
+ document.body.classList.add("output-maximised");
1376
1377
  this.app.initialiseSplitter(true);
1377
1378
  this.app.columnSplitter.collapse(0);
1378
1379
  this.app.columnSplitter.collapse(1);
@@ -1381,6 +1382,7 @@ class OutputWaiter {
1381
1382
  $(el).attr("data-original-title", "Restore output pane");
1382
1383
  el.querySelector("i").innerHTML = "fullscreen_exit";
1383
1384
  } else {
1385
+ document.body.classList.remove("output-maximised");
1384
1386
  $(el).attr("data-original-title", "Maximise output pane");
1385
1387
  el.querySelector("i").innerHTML = "fullscreen";
1386
1388
  this.app.initialiseSplitter(false);
@@ -23,11 +23,11 @@ class WindowWaiter {
23
23
 
24
24
  /**
25
25
  * Handler for window resize events.
26
- * Resets the layout of CyberChef's panes after 200ms (so that continuous resizing doesn't cause
26
+ * Resets adjustable component sizes after 200ms (so that continuous resizing doesn't cause
27
27
  * continuous resetting).
28
28
  */
29
29
  windowResize() {
30
- debounce(this.app.resetLayout, 200, "windowResize", this.app, [])();
30
+ debounce(this.app.adjustComponentSizes, 200, "windowResize", this.app, [])();
31
31
  }
32
32
 
33
33
 
@@ -68,7 +68,7 @@ TestRegister.addTests([
68
68
  {
69
69
  name: "Magic Chain: Base64",
70
70
  input: "WkVkV2VtUkRRbnBrU0Vwd1ltMWpQUT09",
71
- expectedMatch: /From_Base64\('A-Za-z0-9\+\/=',true\)\nFrom_Base64\('A-Za-z0-9\+\/=',true\)\nFrom_Base64\('A-Za-z0-9\+\/=',true\)/,
71
+ expectedMatch: /From_Base64\('A-Za-z0-9\+\/=',true,false\)\nFrom_Base64\('A-Za-z0-9\+\/=',true,false\)\nFrom_Base64\('A-Za-z0-9\+\/=',true,false\)/,
72
72
  recipeConfig: [
73
73
  {
74
74
  op: "Magic",
@@ -79,7 +79,7 @@ TestRegister.addTests([
79
79
  {
80
80
  name: "Magic Chain: Hex -> Hexdump -> Base64",
81
81
  input: "MDAwMDAwMDAgIDM3IDM0IDIwIDM2IDM1IDIwIDM3IDMzIDIwIDM3IDM0IDIwIDMyIDMwIDIwIDM3ICB8NzQgNjUgNzMgNzQgMjAgN3wKMDAwMDAwMTAgIDMzIDIwIDM3IDM0IDIwIDM3IDMyIDIwIDM2IDM5IDIwIDM2IDY1IDIwIDM2IDM3ICB8MyA3NCA3MiA2OSA2ZSA2N3w=",
82
- expectedMatch: /From_Base64\('A-Za-z0-9\+\/=',true\)\nFrom_Hexdump\(\)\nFrom_Hex\('Space'\)/,
82
+ expectedMatch: /From_Base64\('A-Za-z0-9\+\/=',true,false\)\nFrom_Hexdump\(\)\nFrom_Hex\('Space'\)/,
83
83
  recipeConfig: [
84
84
  {
85
85
  op: "Magic",
package/webpack.config.js CHANGED
@@ -1,6 +1,7 @@
1
1
  const webpack = require("webpack");
2
2
  const MiniCssExtractPlugin = require("mini-css-extract-plugin");
3
3
  const CopyWebpackPlugin = require("copy-webpack-plugin");
4
+ const { ModifySourcePlugin } = require("modify-source-webpack-plugin");
4
5
  const path = require("path");
5
6
 
6
7
  /**
@@ -82,6 +83,16 @@ module.exports = {
82
83
  to: "assets/forge/"
83
84
  }
84
85
  ]
86
+ }),
87
+ new ModifySourcePlugin({
88
+ rules: [
89
+ {
90
+ // Fix toSpare(0) bug in Split.js by avoiding gutter accomodation
91
+ test: /split\.es\.js$/,
92
+ modify: (src, path) =>
93
+ src.replace("if (pixelSize < elementMinSize)", "if (false)")
94
+ }
95
+ ]
85
96
  })
86
97
  ],
87
98
  resolve: {