Compare commits
3489 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
c4e7c41a6e | ||
![]() |
210186e754 | ||
![]() |
b4c14219b6 | ||
![]() |
299a3c48a1 | ||
![]() |
cd0aee7626 | ||
![]() |
bc82f590d4 | ||
![]() |
bebb216df2 | ||
![]() |
61d587a4a5 | ||
![]() |
85da5f83b5 | ||
![]() |
6c0c53d00f | ||
![]() |
196bce04cc | ||
![]() |
ba82941cef | ||
![]() |
63449872da | ||
![]() |
14ee3f0f4b | ||
![]() |
774828823c | ||
![]() |
9e73e2555b | ||
![]() |
dc68b7d9bf | ||
![]() |
7a38504015 | ||
![]() |
26fa5f3d1d | ||
![]() |
112d52cb99 | ||
![]() |
47f1f4c549 | ||
![]() |
4b9d5a7685 | ||
![]() |
fc7c6312e6 | ||
![]() |
8b5b17b8e0 | ||
![]() |
c7377da37f | ||
![]() |
7cfb5e0b2a | ||
![]() |
62dfa8f9dd | ||
![]() |
7582abfa27 | ||
![]() |
3f89a94df2 | ||
![]() |
c5e880628a | ||
![]() |
abd9024097 | ||
![]() |
314b925ec9 | ||
![]() |
d700d1d459 | ||
![]() |
1a2207a045 | ||
![]() |
2b85336c71 | ||
![]() |
4b95ab2477 | ||
![]() |
a0729304d1 | ||
![]() |
dbdcb460e5 | ||
![]() |
7588e50f9f | ||
![]() |
40a4872f70 | ||
![]() |
3b265322e0 | ||
![]() |
0da30813da | ||
![]() |
e973ea6f08 | ||
![]() |
a942fe92fd | ||
![]() |
9829b419b9 | ||
![]() |
19194a7eb0 | ||
![]() |
c13997bdb1 | ||
![]() |
35c2d437fa | ||
![]() |
a54522f796 | ||
![]() |
6c971876de | ||
![]() |
d8be3dfa27 | ||
![]() |
2b57f94ccd | ||
![]() |
a3944fe1d1 | ||
![]() |
cfc8a506f7 | ||
![]() |
afcf46561a | ||
![]() |
59b97bfccb | ||
![]() |
20db43c0a8 | ||
![]() |
dc7760247b | ||
![]() |
56a8e02bb8 | ||
![]() |
e532248701 | ||
![]() |
73100896d4 | ||
![]() |
a95be3b4c5 | ||
![]() |
75c4e196fa | ||
![]() |
0359a2eccf | ||
![]() |
b118932451 | ||
![]() |
fd77152343 | ||
![]() |
5afecdb11a | ||
![]() |
1916137c3c | ||
![]() |
c3b89efd9a | ||
![]() |
0f3cd72dd3 | ||
![]() |
ed59f6a67a | ||
![]() |
592745f380 | ||
![]() |
10b0d91bdc | ||
![]() |
dea2b3a2c0 | ||
![]() |
df151eabf9 | ||
![]() |
22a873c73e | ||
![]() |
c6da0c623d | ||
![]() |
44b566789f | ||
![]() |
940f78a8a7 | ||
![]() |
06c912be72 | ||
![]() |
91639ee836 | ||
![]() |
b78533bb02 | ||
![]() |
b5e3a6c5a3 | ||
![]() |
a045c4ffec | ||
![]() |
856ba1cf50 | ||
![]() |
6510773789 | ||
![]() |
7b280b3369 | ||
![]() |
1618e112e1 | ||
![]() |
c08a7dc6ce | ||
![]() |
57731706b3 | ||
![]() |
e9e926d054 | ||
![]() |
5fa3d691cf | ||
![]() |
d022dbc406 | ||
![]() |
f96607c81b | ||
![]() |
24cd4033c4 | ||
![]() |
0bf7852e83 | ||
![]() |
aaff2e687d | ||
![]() |
77042abc23 | ||
![]() |
ac18b74e66 | ||
![]() |
76ba630d59 | ||
![]() |
362755b22f | ||
![]() |
6c63302d62 | ||
![]() |
efda16b039 | ||
![]() |
6ed9d4554a | ||
![]() |
2dcd345349 | ||
![]() |
81924b4a7e | ||
![]() |
5a0c3a3b47 | ||
![]() |
7b599fe7f7 | ||
![]() |
4faaa07188 | ||
![]() |
fa228b2571 | ||
![]() |
9a216dc1bf | ||
![]() |
0e0bafdeb6 | ||
![]() |
5f0f037c46 | ||
![]() |
6b01cf0c1a | ||
![]() |
30f9286ce9 | ||
![]() |
e9ff8707ed | ||
![]() |
12082ba3cc | ||
![]() |
6d3ca3f56c | ||
![]() |
4262e6f6f7 | ||
![]() |
1bc88728f0 | ||
![]() |
7bb0649b27 | ||
![]() |
e46a7448d9 | ||
![]() |
d102e1b15c | ||
![]() |
0a0217cb66 | ||
![]() |
3faa9d3a1e | ||
![]() |
d902c7e30c | ||
![]() |
25fe7bba67 | ||
![]() |
ca340cdd7b | ||
![]() |
266fbab8fd | ||
![]() |
e57fd2a408 | ||
![]() |
3328ae8afd | ||
![]() |
1caecf70a2 | ||
![]() |
6e347742d9 | ||
![]() |
2e2dcfa416 | ||
![]() |
4bb10a34e1 | ||
![]() |
1632e23c78 | ||
![]() |
11fca557af | ||
![]() |
1cf14c6f01 | ||
![]() |
1bba10e7c2 | ||
![]() |
8f3096af0a | ||
![]() |
6fdf37e2f2 | ||
![]() |
a4d6e1f92c | ||
![]() |
4a356476b1 | ||
![]() |
c2171a08f2 | ||
![]() |
84aad167d5 | ||
![]() |
493b1eee1e | ||
![]() |
6c5b260ece | ||
![]() |
7394bb45d4 | ||
![]() |
5d3302f6d7 | ||
![]() |
92dada8a80 | ||
![]() |
c6569b7c47 | ||
![]() |
ea56efae47 | ||
![]() |
7419009745 | ||
![]() |
e58744c63a | ||
![]() |
4c7fe147bc | ||
![]() |
7605d48f0b | ||
![]() |
d6f8e0a520 | ||
![]() |
ab283fc801 | ||
![]() |
d9d6b7aa37 | ||
![]() |
b0c9a1850d | ||
![]() |
764bd4b9ae | ||
![]() |
a24fdf4250 | ||
![]() |
13e3dba784 | ||
![]() |
ccea5cdf88 | ||
![]() |
51e2b97595 | ||
![]() |
61501a7cbc | ||
![]() |
bca4c34b3a | ||
![]() |
2fab1028c5 | ||
![]() |
5d3c66f615 | ||
![]() |
cab83cae35 | ||
![]() |
bd16378e23 | ||
![]() |
65e431bd9e | ||
![]() |
b9f2bddffc | ||
![]() |
80e8b2339d | ||
![]() |
7eda2fd4a6 | ||
![]() |
36aafb9246 | ||
![]() |
acc1df2031 | ||
![]() |
c5766c89f6 | ||
![]() |
73f5069971 | ||
![]() |
819e4a574c | ||
![]() |
8c0e23e196 | ||
![]() |
05160227a3 | ||
![]() |
5cfc1abf41 | ||
![]() |
046a0917e7 | ||
![]() |
9cbf217d42 | ||
![]() |
bf949c0320 | ||
![]() |
9e679f411c | ||
![]() |
dd6eae52ef | ||
![]() |
cdde7166cf | ||
![]() |
251bd86ce5 | ||
![]() |
dee2dc3777 | ||
![]() |
533047a3a2 | ||
![]() |
659325c85a | ||
![]() |
7a2517fd61 | ||
![]() |
0b2cb7e68c | ||
![]() |
4e747b3697 | ||
![]() |
84f0750525 | ||
![]() |
fa21768931 | ||
![]() |
934efcc5a0 | ||
![]() |
91f1be8c70 | ||
![]() |
56d1a016da | ||
![]() |
d6159cc154 | ||
![]() |
e9d7a8363c | ||
![]() |
4e512a9a7b | ||
![]() |
c1394e299a | ||
![]() |
17c349973d | ||
![]() |
f2bd838596 | ||
![]() |
d5b72548fc | ||
![]() |
2c822314df | ||
![]() |
1b3d55f051 | ||
![]() |
ff45f61b68 | ||
![]() |
771a013c9f | ||
![]() |
b354f61502 | ||
![]() |
2e201c747a | ||
![]() |
c1d2970f1e | ||
![]() |
2efd075803 | ||
![]() |
4b4d3c6845 | ||
![]() |
760eff49b5 | ||
![]() |
ba12ad8e7c | ||
![]() |
249af2ded1 | ||
![]() |
7403666a11 | ||
![]() |
1cc8b150f3 | ||
![]() |
faa25ee662 | ||
![]() |
aa981ccd4a | ||
![]() |
04378c1f91 | ||
![]() |
53179a158e | ||
![]() |
7ecde9fd2a | ||
![]() |
320f79fe0d | ||
![]() |
157346b055 | ||
![]() |
904c08c436 | ||
![]() |
fa238545a0 | ||
![]() |
3f85c32c7c | ||
![]() |
55809b2e87 | ||
![]() |
5c32c1bdaa | ||
![]() |
f84c11f63d | ||
![]() |
2bcfb693b7 | ||
![]() |
897d2bef6e | ||
![]() |
0a3d219ad5 | ||
![]() |
ba0dfe91c3 | ||
![]() |
bf4e62b4b7 | ||
![]() |
657f8940fa | ||
![]() |
74d629c491 | ||
![]() |
130e9d8192 | ||
![]() |
0fb3743b6d | ||
![]() |
a19aea1516 | ||
![]() |
137f8d9471 | ||
![]() |
1f57f1f000 | ||
![]() |
468071ee98 | ||
![]() |
23403f55a5 | ||
![]() |
c25cf44d92 | ||
![]() |
e5644b5712 | ||
![]() |
9321b79d81 | ||
![]() |
e97d535ff8 | ||
![]() |
7589361e58 | ||
![]() |
f15ef81693 | ||
![]() |
d5f4968664 | ||
![]() |
74e9edbccf | ||
![]() |
51229d85cb | ||
![]() |
b979b051cb | ||
![]() |
17cf154bc2 | ||
![]() |
c7f6954b97 | ||
![]() |
9ccc1613cf | ||
![]() |
9730ce1f6a | ||
![]() |
55a7981547 | ||
![]() |
2d99c365dd | ||
![]() |
59d8be511a | ||
![]() |
8349ffc001 | ||
![]() |
c1368c4ecb | ||
![]() |
c6935e040d | ||
![]() |
f79c3ae91a | ||
![]() |
bc27cd2772 | ||
![]() |
2b02c44ca4 | ||
![]() |
59fe8d1c4b | ||
![]() |
9a5d62c4c3 | ||
![]() |
9fa82150ee | ||
![]() |
d7561ec208 | ||
![]() |
743b834f6d | ||
![]() |
0658836f87 | ||
![]() |
a4e20c7059 | ||
![]() |
d77f8ba747 | ||
![]() |
78d35ecec3 | ||
![]() |
c79bf5caaf | ||
![]() |
66cbc6908a | ||
![]() |
c04f409d23 | ||
![]() |
1a9833132d | ||
![]() |
9c3ddca269 | ||
![]() |
72889d1c20 | ||
![]() |
6c5433b226 | ||
![]() |
31a7f83b82 | ||
![]() |
39143fa6a1 | ||
![]() |
a116a2a423 | ||
![]() |
61d4c0ea63 | ||
![]() |
44c7a1e92d | ||
![]() |
09fd333997 | ||
![]() |
ebe2a29543 | ||
![]() |
1e83e0e935 | ||
![]() |
fe9eb08648 | ||
![]() |
2255c5b360 | ||
![]() |
c046cf5695 | ||
![]() |
3086c25079 | ||
![]() |
3700780d14 | ||
![]() |
5b134d7e9e | ||
![]() |
58b1fb8de5 | ||
![]() |
c0bd6645ce | ||
![]() |
c6b79cd1c6 | ||
![]() |
cb023089bb | ||
![]() |
5a507aa1ba | ||
![]() |
d23b88e2b8 | ||
![]() |
3ac2ed20d2 | ||
![]() |
d5ffbbb14c | ||
![]() |
b92501ee35 | ||
![]() |
570206af77 | ||
![]() |
f6ae89587c | ||
![]() |
fa30f597ad | ||
![]() |
bdb8c02d5a | ||
![]() |
5efd125d9b | ||
![]() |
01508a2459 | ||
![]() |
ed8bd34915 | ||
![]() |
5c72791279 | ||
![]() |
142f91425c | ||
![]() |
d6344760ec | ||
![]() |
64c009f266 | ||
![]() |
a73decc792 | ||
![]() |
f332ca4617 | ||
![]() |
937791d33d | ||
![]() |
a63a130723 | ||
![]() |
0f1175bf15 | ||
![]() |
e4db23f857 | ||
![]() |
32e7dd030e | ||
![]() |
e33950961e | ||
![]() |
5d65cb419f | ||
![]() |
536053d5f9 | ||
![]() |
04ef095b88 | ||
![]() |
66277cd71f | ||
![]() |
58f01d0464 | ||
![]() |
9ba9c56361 | ||
![]() |
11902e3220 | ||
![]() |
c3f79c4b2c | ||
![]() |
576905e8b8 | ||
![]() |
77a3b91afe | ||
![]() |
40b58aa144 | ||
![]() |
d5bcdc8eed | ||
![]() |
8e57354307 | ||
![]() |
126debf44e | ||
![]() |
674649ca7f | ||
![]() |
1a9a070c3b | ||
![]() |
32bee35f85 | ||
![]() |
a68ce5a5af | ||
![]() |
026e9ca9c3 | ||
![]() |
f1ce67d79b | ||
![]() |
312be4772c | ||
![]() |
be97a0062e | ||
![]() |
00f0101723 | ||
![]() |
f450240094 | ||
![]() |
a7b8378736 | ||
![]() |
98a70c2dd2 | ||
![]() |
d502dd9857 | ||
![]() |
1ec7033d46 | ||
![]() |
28ec56a27f | ||
![]() |
bf2afcd2ef | ||
![]() |
8f710461da | ||
![]() |
a07b8f693b | ||
![]() |
a141873db8 | ||
![]() |
08b91fd7ff | ||
![]() |
cd7156dc55 | ||
![]() |
c2cf535f88 | ||
![]() |
ced9ab68fa | ||
![]() |
cdb197a9c3 | ||
![]() |
c8eacb9942 | ||
![]() |
1c8e37cb64 | ||
![]() |
1b0ced9f9b | ||
![]() |
7b245b084a | ||
![]() |
b00f64518f | ||
![]() |
c3434e894d | ||
![]() |
dd66f728b3 | ||
![]() |
e40142b8c5 | ||
![]() |
1dd1b839b8 | ||
![]() |
d90d845f27 | ||
![]() |
8c9ad81039 | ||
![]() |
cef7a7b27d | ||
![]() |
3e715ef21a | ||
![]() |
86b43b4ffa | ||
![]() |
3893c22275 | ||
![]() |
65d883496b | ||
![]() |
406da9fa2c | ||
![]() |
16b79e32f6 | ||
![]() |
e93aa42697 | ||
![]() |
69e59916e2 | ||
![]() |
475282984b | ||
![]() |
2f89130f41 | ||
![]() |
7c8a185a3d | ||
![]() |
e9dd7eceb8 | ||
![]() |
0dc2322269 | ||
![]() |
5c8aac5572 | ||
![]() |
157dacb3a5 | ||
![]() |
890f645eeb | ||
![]() |
2785459257 | ||
![]() |
037590f831 | ||
![]() |
85496684d8 | ||
![]() |
4200ed4eb9 | ||
![]() |
6b16f11d3b | ||
![]() |
683bd3e5db | ||
![]() |
6a10e94bfd | ||
![]() |
25086386c6 | ||
![]() |
d99ee32cc4 | ||
![]() |
f1d318f229 | ||
![]() |
a7fc455e05 | ||
![]() |
c02c4a72e4 | ||
![]() |
f97ce18ff9 | ||
![]() |
dfd9afc2c4 | ||
![]() |
eb5663a1ed | ||
![]() |
418a7962a5 | ||
![]() |
2ffce23c67 | ||
![]() |
b828b50ccc | ||
![]() |
a6aa40db97 | ||
![]() |
45ede4beaf | ||
![]() |
98a95c8bbf | ||
![]() |
74bb8d92dc | ||
![]() |
6cccc2c786 | ||
![]() |
99a0a05947 | ||
![]() |
94700dab89 | ||
![]() |
c9d29c89bb | ||
![]() |
7d4e554571 | ||
![]() |
2858a74cbf | ||
![]() |
28e599a835 | ||
![]() |
1fb1d9cbb7 | ||
![]() |
2f097e5dfc | ||
![]() |
b71e3241be | ||
![]() |
4b018bf421 | ||
![]() |
f751de896f | ||
![]() |
65aeae9c1e | ||
![]() |
80943b0c26 | ||
![]() |
a9657ac5c7 | ||
![]() |
6fa2e49f3a | ||
![]() |
50f0f70805 | ||
![]() |
fc95d82c49 | ||
![]() |
bb6c1c54ff | ||
![]() |
c4414bd910 | ||
![]() |
68733c74cc | ||
![]() |
bc949b47d9 | ||
![]() |
85ffe48743 | ||
![]() |
42c911838d | ||
![]() |
8917eabfd1 | ||
![]() |
fc91469807 | ||
![]() |
1735d9c091 | ||
![]() |
00d754d466 | ||
![]() |
906727f133 | ||
![]() |
191d7f11f7 | ||
![]() |
54fdc05e3a | ||
![]() |
2267569c8d | ||
![]() |
2f53ee3974 | ||
![]() |
a3b846638f | ||
![]() |
cc3033266c | ||
![]() |
23b168515c | ||
![]() |
049690fea2 | ||
![]() |
d3de91de85 | ||
![]() |
64eae37788 | ||
![]() |
8c71b0b8df | ||
![]() |
2bf1ac6b9c | ||
![]() |
7197a434c2 | ||
![]() |
5349115b94 | ||
![]() |
4274e8f3a2 | ||
![]() |
7610e159a3 | ||
![]() |
9ec94434bb | ||
![]() |
1ab444bda2 | ||
![]() |
3990ba774f | ||
![]() |
1fea9a25a5 | ||
![]() |
3f57711c39 | ||
![]() |
dc46018757 | ||
![]() |
1464e5d5e4 | ||
![]() |
95f7ed0de4 | ||
![]() |
6e7240026a | ||
![]() |
8bae7bf809 | ||
![]() |
b78bb2d3d6 | ||
![]() |
f9a6402825 | ||
![]() |
8ec5f3cb18 | ||
![]() |
c330394ff2 | ||
![]() |
36e66ad5b4 | ||
![]() |
a5a89efc06 | ||
![]() |
1078c37043 | ||
![]() |
535c7188a8 | ||
![]() |
d6f9e216a6 | ||
![]() |
7d6a879a67 | ||
![]() |
668eac1f9e | ||
![]() |
ff99436ce6 | ||
![]() |
ec577fc075 | ||
![]() |
cc9d51b7be | ||
![]() |
cf2b54e8c0 | ||
![]() |
a895d1d82a | ||
![]() |
11da4188ee | ||
![]() |
5b68bad185 | ||
![]() |
477e4a7421 | ||
![]() |
9a982f05ac | ||
![]() |
6959e2cf01 | ||
![]() |
f5fe79326a | ||
![]() |
f77633cee9 | ||
![]() |
c858970573 | ||
![]() |
fb3eceaee0 | ||
![]() |
8e37fec8f8 | ||
![]() |
ccaabfaee8 | ||
![]() |
e712af33b7 | ||
![]() |
c431fb30c5 | ||
![]() |
1c04848480 | ||
![]() |
ca1a0797fb | ||
![]() |
7b497181fd | ||
![]() |
92767b1078 | ||
![]() |
7c66dacc40 | ||
![]() |
632277f9bd | ||
![]() |
3c23ae03f5 | ||
![]() |
8117926ca3 | ||
![]() |
31e9d27f1a | ||
![]() |
0c067d60d8 | ||
![]() |
1171d6b165 | ||
![]() |
18022a2a48 | ||
![]() |
1400b5cca4 | ||
![]() |
98b5f9cc29 | ||
![]() |
e766cb009e | ||
![]() |
993e276858 | ||
![]() |
a762fb4df4 | ||
![]() |
00781fa459 | ||
![]() |
c6e7367a4e | ||
![]() |
3749bb1222 | ||
![]() |
f3c83b2009 | ||
![]() |
d3583c31bc | ||
![]() |
2f638d8c0c | ||
![]() |
74e43ff65a | ||
![]() |
4f0b160ed3 | ||
![]() |
709b8696fc | ||
![]() |
e080c5d72e | ||
![]() |
9cc177a9ad | ||
![]() |
9ad4e2525e | ||
![]() |
60b5fe0e76 | ||
![]() |
9273f97d88 | ||
![]() |
e2b7ac68ef | ||
![]() |
98a6baf55a | ||
![]() |
b5677387f5 | ||
![]() |
cd3eaa4762 | ||
![]() |
9733bf65de | ||
![]() |
bc433f0234 | ||
![]() |
ad7283ee6f | ||
![]() |
5b941358a9 | ||
![]() |
f19e898c57 | ||
![]() |
590ffa184d | ||
![]() |
46de51512f | ||
![]() |
a13f2f26e4 | ||
![]() |
c9c26f6f9f | ||
![]() |
787c29e42b | ||
![]() |
1c0b83d833 | ||
![]() |
5c767d09b0 | ||
![]() |
75dba51f56 | ||
![]() |
78a1827af8 | ||
![]() |
9e3733b33b | ||
![]() |
4ef65589e8 | ||
![]() |
cf9e670309 | ||
![]() |
b09f98fbb4 | ||
![]() |
e43e010163 | ||
![]() |
2a5cee0bd3 | ||
![]() |
c962bb79f5 | ||
![]() |
add745551b | ||
![]() |
7b8213e1f6 | ||
![]() |
2e23a33dfc | ||
![]() |
bca296ee37 | ||
![]() |
2dbd647868 | ||
![]() |
2991e7d1fe | ||
![]() |
f6f12fc193 | ||
![]() |
1fac8c1cea | ||
![]() |
590462e2e4 | ||
![]() |
578a61d331 | ||
![]() |
ed542582f9 | ||
![]() |
2574a63975 | ||
![]() |
291c55befd | ||
![]() |
b7a978505f | ||
![]() |
7db1f39473 | ||
![]() |
6017578964 | ||
![]() |
1dbcd2ac84 | ||
![]() |
671ae6558f | ||
![]() |
d2174725a9 | ||
![]() |
f630c499d5 | ||
![]() |
e8f91316ff | ||
![]() |
a7cdb095d2 | ||
![]() |
cfc29ef821 | ||
![]() |
83c6775038 | ||
![]() |
ae1b12c120 | ||
![]() |
c423de545f | ||
![]() |
84011371b7 | ||
![]() |
f831ec6b7e | ||
![]() |
05bfa9158d | ||
![]() |
649016bc85 | ||
![]() |
7492b874cf | ||
![]() |
9ea21af61f | ||
![]() |
dd18e52993 | ||
![]() |
7712ee7f35 | ||
![]() |
a4a13666e6 | ||
![]() |
07ef4da892 | ||
![]() |
e9ca4dc9ca | ||
![]() |
57bb8fbc45 | ||
![]() |
9175624210 | ||
![]() |
289a417dfb | ||
![]() |
8379a9b275 | ||
![]() |
5b1fad118f | ||
![]() |
5e8985810e | ||
![]() |
d2568e2a29 | ||
![]() |
6dfc21ef06 | ||
![]() |
1f19f2f58c | ||
![]() |
1728cc7a85 | ||
![]() |
fa2fc2ba33 | ||
![]() |
9a33498fed | ||
![]() |
a3b873fd96 | ||
![]() |
97bd03799e | ||
![]() |
ffaaaae2b4 | ||
![]() |
ff88d30d2f | ||
![]() |
88e3c2ccb2 | ||
![]() |
6155634d3b | ||
![]() |
5029356514 | ||
![]() |
e57d5a7e75 | ||
![]() |
2bbe54cdcd | ||
![]() |
0e2423c390 | ||
![]() |
8fadad5891 | ||
![]() |
32455cd20f | ||
![]() |
1e0e7f16a7 | ||
![]() |
95884d77cf | ||
![]() |
b69373f5e7 | ||
![]() |
61e85474d3 | ||
![]() |
3a9bdc58af | ||
![]() |
59c1c45d78 | ||
![]() |
b5f6cedd30 | ||
![]() |
c879af6860 | ||
![]() |
22fe5a6ae7 | ||
![]() |
57714c86a6 | ||
![]() |
70cd375049 | ||
![]() |
e27e1dd42f | ||
![]() |
8ad18bc7db | ||
![]() |
5893ac1a37 | ||
![]() |
83c3ab97f9 | ||
![]() |
9b6be140fa | ||
![]() |
a6a60392c2 | ||
![]() |
ccfa0b991e | ||
![]() |
73b0e68993 | ||
![]() |
31a4eef001 | ||
![]() |
d6e2c9a6b9 | ||
![]() |
e069f5db13 | ||
![]() |
96b59cf0df | ||
![]() |
c1e1d4b7e3 | ||
![]() |
32d869231e | ||
![]() |
6f95f01dda | ||
![]() |
61a1c44f26 | ||
![]() |
e6c7899569 | ||
![]() |
a74a14145e | ||
![]() |
04022b22be | ||
![]() |
4f3010691c | ||
![]() |
672b477751 | ||
![]() |
19360391a6 | ||
![]() |
447be8af34 | ||
![]() |
0989550e5c | ||
![]() |
4d9b48b4d8 | ||
![]() |
979652387d | ||
![]() |
de84fbdd1c | ||
![]() |
170e564319 | ||
![]() |
530836876f | ||
![]() |
1abc46058c | ||
![]() |
892a3716ed | ||
![]() |
5d982a9c8d | ||
![]() |
6206224f1e | ||
![]() |
766310e2c7 | ||
![]() |
02a397d2ae | ||
![]() |
4563c86acd | ||
![]() |
0a59f8068e | ||
![]() |
24548e3a48 | ||
![]() |
f4784d49e7 | ||
![]() |
14d5069c6e | ||
![]() |
9fdd55c5c6 | ||
![]() |
5bc523aeff | ||
![]() |
3ae2e2e2c8 | ||
![]() |
83e49da7f6 | ||
![]() |
fe6df8778f | ||
![]() |
69073c9d99 | ||
![]() |
d5a0adea0c | ||
![]() |
1bcb8e433d | ||
![]() |
fa05cf1d78 | ||
![]() |
63dff0d34d | ||
![]() |
e228b197f9 | ||
![]() |
4bbeb6caa3 | ||
![]() |
139d25dff9 | ||
![]() |
6984258404 | ||
![]() |
ba66fd6546 | ||
![]() |
47bbefd81f | ||
![]() |
50f796049c | ||
![]() |
618da545b1 | ||
![]() |
21236f1938 | ||
![]() |
4169a15066 | ||
![]() |
6b10f61e11 | ||
![]() |
83f119f7e4 | ||
![]() |
041c899a35 | ||
![]() |
5412fc01b3 | ||
![]() |
76926d9252 | ||
![]() |
3270961574 | ||
![]() |
9a1ef71aec | ||
![]() |
ba878925ad | ||
![]() |
8d6b71bfaa | ||
![]() |
b6845aa03c | ||
![]() |
4a673bd92a | ||
![]() |
fdffabfdd4 | ||
![]() |
ba8591293b | ||
![]() |
9b3aae10cf | ||
![]() |
5c85c4df63 | ||
![]() |
8ece2603fb | ||
![]() |
1b54584820 | ||
![]() |
3ce3866000 | ||
![]() |
becc258b6c | ||
![]() |
16c9e7119d | ||
![]() |
f5888fea9c | ||
![]() |
b5162c7549 | ||
![]() |
1baea1da3d | ||
![]() |
40899a6fe4 | ||
![]() |
66c533431d | ||
![]() |
74ae77f17a | ||
![]() |
99eb1cced5 | ||
![]() |
c880ecf3c4 | ||
![]() |
c54c34d88e | ||
![]() |
60b3c597a7 | ||
![]() |
372ab32539 | ||
![]() |
e14745a973 | ||
![]() |
afffe584cf | ||
![]() |
cf532f1e30 | ||
![]() |
46425ba552 | ||
![]() |
9f65fac4e6 | ||
![]() |
af98feff51 | ||
![]() |
339c741a2c | ||
![]() |
d1bde23f00 | ||
![]() |
be544faf0f | ||
![]() |
eff77fd3bb | ||
![]() |
3df57ba3dd | ||
![]() |
4bae662357 | ||
![]() |
357c90546e | ||
![]() |
54769a90ac | ||
![]() |
0550aedd54 | ||
![]() |
5947ed21fc | ||
![]() |
0a0949246f | ||
![]() |
09c6e181fb | ||
![]() |
02cf394bcd | ||
![]() |
46afbf9888 | ||
![]() |
7cf19d22a8 | ||
![]() |
46d708360f | ||
![]() |
7ec91e2366 | ||
![]() |
a74ee47bf0 | ||
![]() |
7b68b92498 | ||
![]() |
274f3acd45 | ||
![]() |
53dff8b30f | ||
![]() |
9892ee273e | ||
![]() |
7dccecb336 | ||
![]() |
aa09da0403 | ||
![]() |
98d7f1481c | ||
![]() |
7d8bdbcf7e | ||
![]() |
db009d3689 | ||
![]() |
d7bc529a95 | ||
![]() |
3f035294a6 | ||
![]() |
36282e362f | ||
![]() |
223353cf4d | ||
![]() |
ded32da632 | ||
![]() |
d6fc21cc34 | ||
![]() |
b86e960456 | ||
![]() |
7747bfe0f2 | ||
![]() |
fabea8cc61 | ||
![]() |
de4cd2eebc | ||
![]() |
e16ce1d9c2 | ||
![]() |
345ad741b3 | ||
![]() |
e53108c493 | ||
![]() |
6129378854 | ||
![]() |
11a1416dcc | ||
![]() |
9025538544 | ||
![]() |
46929e1844 | ||
![]() |
bf023cad48 | ||
![]() |
f649236bad | ||
![]() |
54b1454c0a | ||
![]() |
a41b1c2f5e | ||
![]() |
0327d7cb7a | ||
![]() |
621d7c3683 | ||
![]() |
ae7c3fca31 | ||
![]() |
1b3295ff59 | ||
![]() |
e40e7a0e4e | ||
![]() |
cf5fd7cbf2 | ||
![]() |
ec37a676a8 | ||
![]() |
f33193e122 | ||
![]() |
7c40204e4f | ||
![]() |
2b2ffb3346 | ||
![]() |
39b7e4ff9e | ||
![]() |
a1109c43f6 | ||
![]() |
887ea0cf06 | ||
![]() |
3e0525ee9e | ||
![]() |
e6eafc2843 | ||
![]() |
3e8dea73d2 | ||
![]() |
bbf19ee944 | ||
![]() |
f6c8c9e76c | ||
![]() |
9dba1232b7 | ||
![]() |
bf14c8983f | ||
![]() |
3ab95384df | ||
![]() |
953a581a94 | ||
![]() |
3bfddd708c | ||
![]() |
13a54ec318 | ||
![]() |
2781640a2a | ||
![]() |
7989f119d3 | ||
![]() |
2e0aa7ae87 | ||
![]() |
667dfd820e | ||
![]() |
3e3c526a62 | ||
![]() |
c01ce90e06 | ||
![]() |
d68c8cb845 | ||
![]() |
0b5ee7c79f | ||
![]() |
23cbe1c426 | ||
![]() |
de727bcddc | ||
![]() |
c9d9730726 | ||
![]() |
a380aed878 | ||
![]() |
4dafa50799 | ||
![]() |
f5a7db03cd | ||
![]() |
d4ae241758 | ||
![]() |
88947b9d42 | ||
![]() |
3c68ad1302 | ||
![]() |
e2b3389da6 | ||
![]() |
938385c18b | ||
![]() |
5d01b06877 | ||
![]() |
f007c093eb | ||
![]() |
53e69835ff | ||
![]() |
939208903a | ||
![]() |
7526f4d7b1 | ||
![]() |
616b38c6fb | ||
![]() |
a302df8f91 | ||
![]() |
093512a55a | ||
![]() |
007224c92e | ||
![]() |
738ee33959 | ||
![]() |
d720a6b250 | ||
![]() |
5ce3cc17bb | ||
![]() |
5c35205315 | ||
![]() |
10751934e4 | ||
![]() |
d658f91106 | ||
![]() |
ee408f7add | ||
![]() |
1294d764e2 | ||
![]() |
eab1be0e2c | ||
![]() |
15dd9d4c93 | ||
![]() |
103ecff6a7 | ||
![]() |
0182cdda69 | ||
![]() |
fae96af17d | ||
![]() |
57c1a03c4f | ||
![]() |
cb8fe42c66 | ||
![]() |
7f4b2574b0 | ||
![]() |
fad163e0eb | ||
![]() |
7ad3992bd1 | ||
![]() |
e7b5c0e37c | ||
![]() |
cc35127459 | ||
![]() |
1c0ecd29c2 | ||
![]() |
1f0fddd0e9 | ||
![]() |
18c6b9bc09 | ||
![]() |
2233b9a094 | ||
![]() |
e0f000b913 | ||
![]() |
73864e0809 | ||
![]() |
cd8a85975c | ||
![]() |
2f94ec20b0 | ||
![]() |
09d9deae43 | ||
![]() |
209fc07eac | ||
![]() |
ae70cb89ed | ||
![]() |
4c3324aea1 | ||
![]() |
ac2fcee90f | ||
![]() |
94e00115fe | ||
![]() |
29255d2338 | ||
![]() |
bda36e508a | ||
![]() |
d2ea1273da | ||
![]() |
39278cfce7 | ||
![]() |
46cc48cfb9 | ||
![]() |
eb4009949d | ||
![]() |
57c48a4bd2 | ||
![]() |
45011de494 | ||
![]() |
5e51ed0a5f | ||
![]() |
875802ef2a | ||
![]() |
bbc255ef83 | ||
![]() |
fc155ec3fc | ||
![]() |
3a0c8a199a | ||
![]() |
9c729c4490 | ||
![]() |
19bdbd66e5 | ||
![]() |
ea090f79ee | ||
![]() |
1be6c54be2 | ||
![]() |
a4eeb226b1 | ||
![]() |
d136717636 | ||
![]() |
ad0a2e6f58 | ||
![]() |
0212bfb46e | ||
![]() |
5e0d661542 | ||
![]() |
4c5f529ef4 | ||
![]() |
b765534b8b | ||
![]() |
7c672c5ee9 | ||
![]() |
090bf3f8ec | ||
![]() |
9f4ef9cdad | ||
![]() |
26fa66ef64 | ||
![]() |
b69e4567c0 | ||
![]() |
26b19350f2 | ||
![]() |
2018b7e247 | ||
![]() |
cbff4161a1 | ||
![]() |
130bdfb7f2 | ||
![]() |
d0c43f5aa9 | ||
![]() |
f864a5f31e | ||
![]() |
ff585584f6 | ||
![]() |
2e0af64ac3 | ||
![]() |
8a029e5147 | ||
![]() |
4251089687 | ||
![]() |
dbcd670ca8 | ||
![]() |
cecae671d8 | ||
![]() |
b4e23ac454 | ||
![]() |
e7209ca085 | ||
![]() |
022ef71d2c | ||
![]() |
0fad891a3a | ||
![]() |
47f608b502 | ||
![]() |
4ab745f730 | ||
![]() |
1f89ac11d2 | ||
![]() |
1a5dae76c2 | ||
![]() |
032c7f529a | ||
![]() |
707818abcc | ||
![]() |
58e8b4c618 | ||
![]() |
9c0c2867dd | ||
![]() |
4308c717c3 | ||
![]() |
30bc8dfbe9 | ||
![]() |
342b67581b | ||
![]() |
75da5b650c | ||
![]() |
5b6a53be3e | ||
![]() |
5b5105c864 | ||
![]() |
9d09146f68 | ||
![]() |
1b19d20d0c | ||
![]() |
0eacab5ddc | ||
![]() |
0d7874bac1 | ||
![]() |
5cddfafbd0 | ||
![]() |
5ce133c47e | ||
![]() |
53a579028c | ||
![]() |
570a84b67a | ||
![]() |
a68bfd7223 | ||
![]() |
fd7176a445 | ||
![]() |
0a06472639 | ||
![]() |
3f3a7cd4f6 | ||
![]() |
99415359d0 | ||
![]() |
54cb2d268b | ||
![]() |
0e40daecb6 | ||
![]() |
21a822b082 | ||
![]() |
a770d09687 | ||
![]() |
27b81c4e11 | ||
![]() |
8826c80e07 | ||
![]() |
02e3ce7fc1 | ||
![]() |
673e6aede5 | ||
![]() |
5809dea0fc | ||
![]() |
165ca9ebc1 | ||
![]() |
154b9386f7 | ||
![]() |
62dd7c3dbc | ||
![]() |
82d098fc1a | ||
![]() |
c69ac7f0f2 | ||
![]() |
5578f4577d | ||
![]() |
28bd8a32e3 | ||
![]() |
24fd35e6af | ||
![]() |
c4493d15b6 | ||
![]() |
f9ef9739b0 | ||
![]() |
d50b9103c2 | ||
![]() |
196a611c9b | ||
![]() |
7c057ad254 | ||
![]() |
cced384e0a | ||
![]() |
cce7abecd5 | ||
![]() |
8013b3b5e9 | ||
![]() |
64b979e25e | ||
![]() |
5e7b004925 | ||
![]() |
1e791d7f1b | ||
![]() |
a255d1912c | ||
![]() |
3d1266f815 | ||
![]() |
ede78c540f | ||
![]() |
c0f003b450 | ||
![]() |
14190fc533 | ||
![]() |
9f34009244 | ||
![]() |
bc2fefd1b2 | ||
![]() |
940b56ba5f | ||
![]() |
8f2a1f5b2c | ||
![]() |
20d0ae5304 | ||
![]() |
2ba37af109 | ||
![]() |
728f8e65d6 | ||
![]() |
d78730edc0 | ||
![]() |
b0d2593968 | ||
![]() |
38a033f1a9 | ||
![]() |
846ad1796a | ||
![]() |
355a6d6b76 | ||
![]() |
e91e993fb5 | ||
![]() |
e71794d362 | ||
![]() |
6090842372 | ||
![]() |
0a3bd6456c | ||
![]() |
ab6576d739 | ||
![]() |
d5615b90bb | ||
![]() |
cf1349ccb2 | ||
![]() |
36190f1967 | ||
![]() |
3ecbe22d99 | ||
![]() |
b045dc37f5 | ||
![]() |
015d0f065f | ||
![]() |
d7cc6c7363 | ||
![]() |
a5bc1c6f9e | ||
![]() |
c2212f9ab3 | ||
![]() |
3fb5bf14a6 | ||
![]() |
f32b7d5de5 | ||
![]() |
7a58567659 | ||
![]() |
4be0a436f2 | ||
![]() |
1e71fc91a1 | ||
![]() |
0ab96dd4ca | ||
![]() |
1509b2b96c | ||
![]() |
6fd929160d | ||
![]() |
5cdd062ed9 | ||
![]() |
4430ea55c4 | ||
![]() |
4b6cebc068 | ||
![]() |
0259ed8314 | ||
![]() |
a06303c2fd | ||
![]() |
115b064081 | ||
![]() |
3a2580fbc2 | ||
![]() |
1197859865 | ||
![]() |
293a95e938 | ||
![]() |
9a3464a5ec | ||
![]() |
55dddd3ef9 | ||
![]() |
23956480b7 | ||
![]() |
6dbaf6a36c | ||
![]() |
1d8c7dcb97 | ||
![]() |
41c8a5aff0 | ||
![]() |
597fba2fd0 | ||
![]() |
0f0674daf6 | ||
![]() |
d9b7fe2bb9 | ||
![]() |
ace8121d0e | ||
![]() |
f7be8d720b | ||
![]() |
0a5038e533 | ||
![]() |
5feba30956 | ||
![]() |
23a228bbd9 | ||
![]() |
598813ff88 | ||
![]() |
118f0130d8 | ||
![]() |
e77d3a4b7d | ||
![]() |
bf0bd620f1 | ||
![]() |
62edd76d7e | ||
![]() |
cef6585cab | ||
![]() |
481241b88d | ||
![]() |
93c0c7cc10 | ||
![]() |
01d9536bbd | ||
![]() |
24a7c75926 | ||
![]() |
c2eea7a9f7 | ||
![]() |
e2812cadb5 | ||
![]() |
cde958af16 | ||
![]() |
bf70589b3c | ||
![]() |
78d1114869 | ||
![]() |
ab83caa77b | ||
![]() |
42dd03bb84 | ||
![]() |
cb09949fb9 | ||
![]() |
0c6eac3b21 | ||
![]() |
5e771c521c | ||
![]() |
b8afbf7458 | ||
![]() |
be59efbd6b | ||
![]() |
99ccd06f23 | ||
![]() |
f4d75f88a9 | ||
![]() |
9112bd4936 | ||
![]() |
3e513efd59 | ||
![]() |
4100a22c7f | ||
![]() |
71078d9332 | ||
![]() |
72ba579e1e | ||
![]() |
5fd2512a9b | ||
![]() |
3a1a6a94d2 | ||
![]() |
928178716a | ||
![]() |
08419a20c0 | ||
![]() |
60506ee2d1 | ||
![]() |
6e411c9dd9 | ||
![]() |
86db43e6dd | ||
![]() |
252ac0bdaa | ||
![]() |
4d8b1721bc | ||
![]() |
fd390bc61b | ||
![]() |
813a151524 | ||
![]() |
c06502cd76 | ||
![]() |
974ce1fd12 | ||
![]() |
d2dc50fe8e | ||
![]() |
ec50105e34 | ||
![]() |
86ebed132d | ||
![]() |
47ccafcbb2 | ||
![]() |
9f901188af | ||
![]() |
ccdd2af8be | ||
![]() |
a387db6109 | ||
![]() |
b88a35cd14 | ||
![]() |
bcafaebf77 | ||
![]() |
798f013219 | ||
![]() |
61e6423d95 | ||
![]() |
c32fec6b53 | ||
![]() |
4c0d944992 | ||
![]() |
57ee3f305d | ||
![]() |
61ab9a904f | ||
![]() |
820bd2f867 | ||
![]() |
44c2b71e6c | ||
![]() |
b806be3f49 | ||
![]() |
2750284eea | ||
![]() |
5366f1a2eb | ||
![]() |
32625dc0b0 | ||
![]() |
dfc8f517f2 | ||
![]() |
b459c15d74 | ||
![]() |
8a02b35d7d | ||
![]() |
d4441823aa | ||
![]() |
13e9a4f0da | ||
![]() |
33471a33d6 | ||
![]() |
a6fa0628f2 | ||
![]() |
8e5aa2c393 | ||
![]() |
1118ff598d | ||
![]() |
09e93b4639 | ||
![]() |
d8405e5f81 | ||
![]() |
0295d0c9b4 | ||
![]() |
d16bbe1e7e | ||
![]() |
8e1e1d56ca | ||
![]() |
4814922e67 | ||
![]() |
81d1007bb7 | ||
![]() |
63bb19d48d | ||
![]() |
610d46a1a4 | ||
![]() |
f7acef4642 | ||
![]() |
fd5b6c5243 | ||
![]() |
e92ed13864 | ||
![]() |
a8917e4713 | ||
![]() |
04036e001e | ||
![]() |
725b0d42f8 | ||
![]() |
071c1bdea6 | ||
![]() |
7386c145ef | ||
![]() |
25ca8d85a6 | ||
![]() |
c60ed2c403 | ||
![]() |
7d41d4d030 | ||
![]() |
6d77fe6eb3 | ||
![]() |
40d3c8b071 | ||
![]() |
02ec4a3bfd | ||
![]() |
7a4ebbf47e | ||
![]() |
2e7ce477d7 | ||
![]() |
c1a22ef639 | ||
![]() |
0a7b78b7ee | ||
![]() |
e1cb62848c | ||
![]() |
acf5c733c2 | ||
![]() |
7c25e29515 | ||
![]() |
7c72871c02 | ||
![]() |
ddb77c6ab3 | ||
![]() |
8502fd246d | ||
![]() |
30c6917914 | ||
![]() |
33464b3388 | ||
![]() |
2205637ad6 | ||
![]() |
149198ef1c | ||
![]() |
2c40353180 | ||
![]() |
a62a6c2aa4 | ||
![]() |
4be06f6779 | ||
![]() |
03f4740968 | ||
![]() |
ea6d80edfb | ||
![]() |
68e8a221ff | ||
![]() |
cce84c3782 | ||
![]() |
c1878ca28b | ||
![]() |
e9b7a43b9a | ||
![]() |
3921b4f445 | ||
![]() |
dfd4cca43f | ||
![]() |
69c6c3e790 | ||
![]() |
4541d75f49 | ||
![]() |
9eda670026 | ||
![]() |
d3c13b118d | ||
![]() |
8e2345cf9e | ||
![]() |
d240d65c5f | ||
![]() |
d3473a7462 | ||
![]() |
6bfe4ee238 | ||
![]() |
e61b7d598e | ||
![]() |
eb81b9217e | ||
![]() |
4d9bfcad20 | ||
![]() |
2387452a56 | ||
![]() |
a4772941a7 | ||
![]() |
6318f78e29 | ||
![]() |
5e6f3cc5b4 | ||
![]() |
04f1fa06ad | ||
![]() |
8d660e53b2 | ||
![]() |
f3864b00fe | ||
![]() |
51cc94bf2a | ||
![]() |
f63d1354ba | ||
![]() |
80362cfa84 | ||
![]() |
447a6d7524 | ||
![]() |
f022440b4a | ||
![]() |
4f5e0c007d | ||
![]() |
b83f6591bb | ||
![]() |
77a9481cf9 | ||
![]() |
a8f029309d | ||
![]() |
b0df8b7dca | ||
![]() |
2cc05717e6 | ||
![]() |
b96394131f | ||
![]() |
875c1019b2 | ||
![]() |
d1a0a39efa | ||
![]() |
fdfbf7ddf8 | ||
![]() |
414f8b5ba9 | ||
![]() |
03a1c566fc | ||
![]() |
0fc1c37e65 | ||
![]() |
6b4efb420e | ||
![]() |
9ed2b26933 | ||
![]() |
70665534b8 | ||
![]() |
7244d4d343 | ||
![]() |
980c1e8681 | ||
![]() |
e1378860d6 | ||
![]() |
462f619f43 | ||
![]() |
b9571db9f1 | ||
![]() |
734962ac22 | ||
![]() |
35103bf155 | ||
![]() |
4c4d7b5d26 | ||
![]() |
daad633195 | ||
![]() |
d5cfe9f262 | ||
![]() |
a2c46b3f66 | ||
![]() |
6a1d11b9b5 | ||
![]() |
0630c094e0 | ||
![]() |
ace71f20b3 | ||
![]() |
c0e02451a1 | ||
![]() |
2d12a16771 | ||
![]() |
9108b3923b | ||
![]() |
726e117656 | ||
![]() |
ab524fff15 | ||
![]() |
55eae9910f | ||
![]() |
d3138a7fdf | ||
![]() |
05e65a74ce | ||
![]() |
6d138f345f | ||
![]() |
3c165dd7e8 | ||
![]() |
04561d29b5 | ||
![]() |
e5e6c1a2dd | ||
![]() |
882efea314 | ||
![]() |
89d979d92e | ||
![]() |
383aab5f85 | ||
![]() |
6659174f88 | ||
![]() |
3ca29b8744 | ||
![]() |
726bf3345e | ||
![]() |
b2d61482d5 | ||
![]() |
88d8e9a7f9 | ||
![]() |
0b0ddd3140 | ||
![]() |
3d4f74945c | ||
![]() |
4387038351 | ||
![]() |
49f444dfe9 | ||
![]() |
061533bb57 | ||
![]() |
6e2fb67d76 | ||
![]() |
60f5093c6c | ||
![]() |
665f91ec37 | ||
![]() |
0805a011b9 | ||
![]() |
3e3322e1f0 | ||
![]() |
252b1b65c4 | ||
![]() |
e8b4536ec2 | ||
![]() |
61d40b5a0b | ||
![]() |
d175aa958c | ||
![]() |
ac3c220789 | ||
![]() |
add65e121a | ||
![]() |
de2e757691 | ||
![]() |
eb34ab4f6a | ||
![]() |
08e4232166 | ||
![]() |
adf9772928 | ||
![]() |
562171ec86 | ||
![]() |
e9e162319f | ||
![]() |
17c9ffe107 | ||
![]() |
1831c84a29 | ||
![]() |
7e27449204 | ||
![]() |
282476d530 | ||
![]() |
fce0728d5d | ||
![]() |
0e9ac90607 | ||
![]() |
5383f56b26 | ||
![]() |
a02484c6cd | ||
![]() |
be365f66ef | ||
![]() |
011dc09d5e | ||
![]() |
fc4d6d2d2e | ||
![]() |
9d73127cae | ||
![]() |
223743e3b5 | ||
![]() |
44ed372f21 | ||
![]() |
4d1f970105 | ||
![]() |
b28a891a40 | ||
![]() |
834ff95702 | ||
![]() |
3e93580aa4 | ||
![]() |
ef61735f64 | ||
![]() |
a2780ca056 | ||
![]() |
d025c8bd9a | ||
![]() |
7a3ca027bb | ||
![]() |
3c021919dd | ||
![]() |
2106e8ddb0 | ||
![]() |
acf38e47ba | ||
![]() |
4122d4207d | ||
![]() |
d550ae7d93 | ||
![]() |
815a542cc1 | ||
![]() |
3472484601 | ||
![]() |
826a8c8a74 | ||
![]() |
c66703f0ca | ||
![]() |
874e7d8d54 | ||
![]() |
4e2b85b8c8 | ||
![]() |
5314a456cb | ||
![]() |
ba2a5b195c | ||
![]() |
f8115671ee | ||
![]() |
494279edd8 | ||
![]() |
bd6673afed | ||
![]() |
210daf7324 | ||
![]() |
d60d595254 | ||
![]() |
06708949a1 | ||
![]() |
da901e20d9 | ||
![]() |
c28999ec6f | ||
![]() |
9872578d51 | ||
![]() |
3014696fcd | ||
![]() |
6b70f77dcd | ||
![]() |
7f6d8bffe3 | ||
![]() |
458307f5ed | ||
![]() |
294aa826f1 | ||
![]() |
6810f38808 | ||
![]() |
087cc6b8fd | ||
![]() |
10c8101476 | ||
![]() |
22028b074a | ||
![]() |
3d086beed2 | ||
![]() |
777d814e70 | ||
![]() |
15c26a95c5 | ||
![]() |
f87d3bd1cb | ||
![]() |
b9ea1e8c71 | ||
![]() |
123a0ccd70 | ||
![]() |
4c737475d4 | ||
![]() |
4e0d97f2c1 | ||
![]() |
85906cafbb | ||
![]() |
a8dc691033 | ||
![]() |
4d7988b78e | ||
![]() |
841e760b04 | ||
![]() |
db232f4ff2 | ||
![]() |
70f705afbc | ||
![]() |
912d63067c | ||
![]() |
880df212d5 | ||
![]() |
b06acd99ec | ||
![]() |
a59de80d18 | ||
![]() |
1e8dee9935 | ||
![]() |
928f1c3e4b | ||
![]() |
014e70a7b1 | ||
![]() |
5148b16246 | ||
![]() |
b4ae4c5a00 | ||
![]() |
70346bce35 | ||
![]() |
3539e065fa | ||
![]() |
8ffc58b340 | ||
![]() |
503e733c81 | ||
![]() |
7eabaf0de6 | ||
![]() |
a8ad10757c | ||
![]() |
111546ad1a | ||
![]() |
a8fbd5164e | ||
![]() |
8dde732514 | ||
![]() |
f1659af5e4 | ||
![]() |
e68fb51f44 | ||
![]() |
95453131c8 | ||
![]() |
c60d5c8e85 | ||
![]() |
b31f32a7e7 | ||
![]() |
f0b3bd0ede | ||
![]() |
de5243ec67 | ||
![]() |
c6de3eb2ae | ||
![]() |
d7b6f29c81 | ||
![]() |
5bebd71a44 | ||
![]() |
eb769c7fb4 | ||
![]() |
5bc5c0df90 | ||
![]() |
cfc3684a16 | ||
![]() |
0590020130 | ||
![]() |
2a91af152d | ||
![]() |
d8120d4e13 | ||
![]() |
0ac211ce77 | ||
![]() |
32c0d6f253 | ||
![]() |
de762847e9 | ||
![]() |
6248e32148 | ||
![]() |
52f88ee32d | ||
![]() |
f8d1cf2f60 | ||
![]() |
e129425d8d | ||
![]() |
ae20d82e1d | ||
![]() |
463b2ce040 | ||
![]() |
412b47abba | ||
![]() |
ed216eee73 | ||
![]() |
9dd5234962 | ||
![]() |
018532016b | ||
![]() |
7dfecc38f6 | ||
![]() |
aa5afadcce | ||
![]() |
d23a584b9e | ||
![]() |
572f035877 | ||
![]() |
b94eb6adb0 | ||
![]() |
45fccb94e1 | ||
![]() |
2628f17fae | ||
![]() |
69fb6e77fc | ||
![]() |
6992858e67 | ||
![]() |
59917cca45 | ||
![]() |
2f0b959aa4 | ||
![]() |
4f2749e0dc | ||
![]() |
c9deaae744 | ||
![]() |
950a12360e | ||
![]() |
686ca284fe | ||
![]() |
b21643f485 | ||
![]() |
4c28627459 | ||
![]() |
6a6fe0f56d | ||
![]() |
48831225ac | ||
![]() |
4e8a79d8f1 | ||
![]() |
570a4c7fca | ||
![]() |
094d352e5f | ||
![]() |
44b90be7d6 | ||
![]() |
e7980a8886 | ||
![]() |
1c9c0a48be | ||
![]() |
c541eebe3e | ||
![]() |
c51e6efe74 | ||
![]() |
6c9ce15b26 | ||
![]() |
8e1bd36b4c | ||
![]() |
d3e3e6e6fc | ||
![]() |
f1794a2dfe | ||
![]() |
1efccff730 | ||
![]() |
0031345383 | ||
![]() |
46fa7475cf | ||
![]() |
afc7c40975 | ||
![]() |
dc99797f7b | ||
![]() |
4624266a5c | ||
![]() |
05bfd99318 | ||
![]() |
db3faf16b0 | ||
![]() |
9fc451ece8 | ||
![]() |
9e1079027b | ||
![]() |
9774a4bd26 | ||
![]() |
ce9e864757 | ||
![]() |
737ea19c9e | ||
![]() |
82b5e97a2b | ||
![]() |
c43f829854 | ||
![]() |
f43a868607 | ||
![]() |
9f2d1453ed | ||
![]() |
082d939f7d | ||
![]() |
8d628cf0ed | ||
![]() |
19553dcfed | ||
![]() |
a7938526aa | ||
![]() |
863551ee1d | ||
![]() |
772c6bbba5 | ||
![]() |
148dcbb0c5 | ||
![]() |
82abdb50b1 | ||
![]() |
b8dbb11136 | ||
![]() |
b14cb99587 | ||
![]() |
1d32a5939c | ||
![]() |
ae1cd8ba3e | ||
![]() |
1fb6bffe1c | ||
![]() |
59864e3781 | ||
![]() |
e2c7d8c678 | ||
![]() |
62f82c5d12 | ||
![]() |
ec70d8a3a2 | ||
![]() |
1b4471a946 | ||
![]() |
43472394c7 | ||
![]() |
a4e9025b8e | ||
![]() |
6b9e93e310 | ||
![]() |
06b385563c | ||
![]() |
d90a23bfd5 | ||
![]() |
91cdd50ba7 | ||
![]() |
4bc4db8232 | ||
![]() |
863675e636 | ||
![]() |
1cdcaebb4d | ||
![]() |
cdb30d86b0 | ||
![]() |
138e3c4239 | ||
![]() |
66b82598e3 | ||
![]() |
780eecf35b | ||
![]() |
8548d39318 | ||
![]() |
6f26ff0a89 | ||
![]() |
2ffd2c6b7a | ||
![]() |
0e95ad8ed6 | ||
![]() |
3a8b362dfd | ||
![]() |
82b94fad5d | ||
![]() |
42cfed5fa8 | ||
![]() |
e4452b906e | ||
![]() |
36abaeb6fb | ||
![]() |
507c951f28 | ||
![]() |
8a67103808 | ||
![]() |
b3a10d4f9e | ||
![]() |
d4c4e2599d | ||
![]() |
67ead1c250 | ||
![]() |
e4d98eba6b | ||
![]() |
9c6ceaa58a | ||
![]() |
7f168d49a6 | ||
![]() |
897dc0fb97 | ||
![]() |
a08419a4ce | ||
![]() |
710223d7de | ||
![]() |
4c40c6fa74 | ||
![]() |
fe6d86622b | ||
![]() |
d1ad2d0845 | ||
![]() |
29cb273d08 | ||
![]() |
566517d20f | ||
![]() |
ac1c93d29b | ||
![]() |
944842d4eb | ||
![]() |
d56ff0825a | ||
![]() |
d7d50337f9 | ||
![]() |
0de89f3145 | ||
![]() |
aa362ba18b | ||
![]() |
b272d3b32f | ||
![]() |
e4e32a9c56 | ||
![]() |
4bae718f40 | ||
![]() |
5eb3979504 | ||
![]() |
e03ff22ba8 | ||
![]() |
368f508b17 | ||
![]() |
a1cf20bd67 | ||
![]() |
d2ff2ec9c6 | ||
![]() |
1c24c05647 | ||
![]() |
c70f14419a | ||
![]() |
80386f1c86 | ||
![]() |
4f70a79638 | ||
![]() |
080886a2d1 | ||
![]() |
5f9112b565 | ||
![]() |
2007cfd3a0 | ||
![]() |
e49974beaa | ||
![]() |
e863c8c549 | ||
![]() |
e4b4e8afca | ||
![]() |
4d4ebf7279 | ||
![]() |
892632a814 | ||
![]() |
2ba6a3fd8d | ||
![]() |
09bea6e231 | ||
![]() |
99f4091c1a | ||
![]() |
6964bbc232 | ||
![]() |
367d30e421 | ||
![]() |
30a66f1441 | ||
![]() |
666c447e36 | ||
![]() |
a43dff558a | ||
![]() |
f39006ea1d | ||
![]() |
2355ef9d31 | ||
![]() |
13b0ab73d0 | ||
![]() |
3766982734 | ||
![]() |
54a864e18c | ||
![]() |
9a1f4e40dd | ||
![]() |
5a83ef4f48 | ||
![]() |
b3fa1eaae2 | ||
![]() |
6d95cb01c8 | ||
![]() |
a7eefa88fa | ||
![]() |
a7d4e37851 | ||
![]() |
57e71f0e1a | ||
![]() |
30be3cab53 | ||
![]() |
0e6626bf39 | ||
![]() |
bb0eaf4597 | ||
![]() |
8ada3bae0c | ||
![]() |
db72cad610 | ||
![]() |
455b42aa85 | ||
![]() |
2d95092479 | ||
![]() |
0f87c70cee | ||
![]() |
4312d39624 | ||
![]() |
cf500b7296 | ||
![]() |
7a0180213d | ||
![]() |
160e4a0479 | ||
![]() |
bed66298d1 | ||
![]() |
44643c151a | ||
![]() |
bc1bd2427d | ||
![]() |
6726994ad5 | ||
![]() |
5abc5279f5 | ||
![]() |
1135ca5fb4 | ||
![]() |
be08a62f52 | ||
![]() |
f473807459 | ||
![]() |
3a197379d5 | ||
![]() |
a9f8dac656 | ||
![]() |
3dc57c4a4a | ||
![]() |
4c9a61f5a7 | ||
![]() |
ba4740a245 | ||
![]() |
86c37bab3f | ||
![]() |
dd5038c15b | ||
![]() |
550ab403f6 | ||
![]() |
bac2e8c014 | ||
![]() |
d148cae814 | ||
![]() |
f22211ce8c | ||
![]() |
f9c24f2528 | ||
![]() |
6f6786d79e | ||
![]() |
a5ea7f7d58 | ||
![]() |
d637ac7633 | ||
![]() |
c1ad2386ef | ||
![]() |
b0b6de116d | ||
![]() |
1b161f997b | ||
![]() |
b99af58636 | ||
![]() |
c46928aefe | ||
![]() |
d4edbb3c3e | ||
![]() |
1b765605ca | ||
![]() |
8286dc26ad | ||
![]() |
19a438c15b | ||
![]() |
12898a1a8e | ||
![]() |
4c27a46d35 | ||
![]() |
541f2a2988 | ||
![]() |
d0277dde3f | ||
![]() |
d184e40116 | ||
![]() |
596db07647 | ||
![]() |
c233c5c67e | ||
![]() |
db788b57e7 | ||
![]() |
c0983654d8 | ||
![]() |
30bf95f2c1 | ||
![]() |
da178107f9 | ||
![]() |
4dda1d9e49 | ||
![]() |
e11aec64cd | ||
![]() |
71575e49d7 | ||
![]() |
393d070b05 | ||
![]() |
d7e4c28cd0 | ||
![]() |
ccf2348cd6 | ||
![]() |
cde3eb2c39 | ||
![]() |
f29d8eeda8 | ||
![]() |
daee7ac761 | ||
![]() |
313d1a580e | ||
![]() |
824d8e929a | ||
![]() |
862d2adf1d | ||
![]() |
906ece0c02 | ||
![]() |
0bcf57e89c | ||
![]() |
c529a406cd | ||
![]() |
05d81eb734 | ||
![]() |
ca9bab5d4c | ||
![]() |
e35ef8f39b | ||
![]() |
370cae3c8d | ||
![]() |
5731f5c75c | ||
![]() |
34d03973f9 | ||
![]() |
84204c1d12 | ||
![]() |
6ee76e3bf0 | ||
![]() |
f497dc3170 | ||
![]() |
749ffdd5d1 | ||
![]() |
e709582062 | ||
![]() |
713f10e63a | ||
![]() |
9d60ef5f72 | ||
![]() |
5a52e5e9b3 | ||
![]() |
d99cf9d499 | ||
![]() |
be0e12589d | ||
![]() |
a6732ba815 | ||
![]() |
466d872d30 | ||
![]() |
ad982746dd | ||
![]() |
8c2cc5b6d2 | ||
![]() |
3cf7238106 | ||
![]() |
f8d08cc5db | ||
![]() |
97f86af6b9 | ||
![]() |
c2087f6d5f | ||
![]() |
98af95a6ce | ||
![]() |
39101610d5 | ||
![]() |
b77239fc15 | ||
![]() |
31a3af1f84 | ||
![]() |
0e5944e9c6 | ||
![]() |
99e9df0211 | ||
![]() |
c1bdca8df3 | ||
![]() |
15b5cf7c20 | ||
![]() |
f55102716e | ||
![]() |
17b95f1609 | ||
![]() |
2575a762e1 | ||
![]() |
f5cf7bb1f2 | ||
![]() |
d3e8616e90 | ||
![]() |
87dc325932 | ||
![]() |
eb91dd7a7d | ||
![]() |
89c4f54ce8 | ||
![]() |
b3cb800f35 | ||
![]() |
4a0f62b636 | ||
![]() |
bcaefe39aa | ||
![]() |
54bb17c109 | ||
![]() |
dc74a389d8 | ||
![]() |
784b3b58ca | ||
![]() |
34b7c47533 | ||
![]() |
d6a80b4865 | ||
![]() |
1d20fe13f3 | ||
![]() |
7586c3ef8a | ||
![]() |
b232069631 | ||
![]() |
11d2a96ecb | ||
![]() |
586e5a3db8 | ||
![]() |
d94a431537 | ||
![]() |
9b2f44efb1 | ||
![]() |
12e4fe4120 | ||
![]() |
476d9c8034 | ||
![]() |
85809efcc0 | ||
![]() |
722edcc274 | ||
![]() |
919c3fd999 | ||
![]() |
6c711ec211 | ||
![]() |
7528065d4d | ||
![]() |
79e2ca6284 | ||
![]() |
d22d22d24c | ||
![]() |
1a9c9a5ddf | ||
![]() |
0bced35931 | ||
![]() |
4dcd60adc0 | ||
![]() |
d51e8ec378 | ||
![]() |
59cdd259ac | ||
![]() |
2be2c83f67 | ||
![]() |
a271eaabd0 | ||
![]() |
e95f92cdeb | ||
![]() |
cb86cb1882 | ||
![]() |
1d130c88a8 | ||
![]() |
c50f5769db | ||
![]() |
6df9b649a1 | ||
![]() |
6f5018d45e | ||
![]() |
f51ee76c72 | ||
![]() |
a750b0f08a | ||
![]() |
0e850b2a85 | ||
![]() |
d326cad611 | ||
![]() |
2ddb9b8f7c | ||
![]() |
6237db9ba6 | ||
![]() |
0cff7bdd04 | ||
![]() |
413e80ed3e | ||
![]() |
5d52f4a760 | ||
![]() |
0855dc617f | ||
![]() |
04587a8a47 | ||
![]() |
392b96dc34 | ||
![]() |
219469f24f | ||
![]() |
84cc3d16ed | ||
![]() |
e34d521dcc | ||
![]() |
2c02900edf | ||
![]() |
f5442c307a | ||
![]() |
802879ced2 | ||
![]() |
0c9e8fe050 | ||
![]() |
8f41571e47 | ||
![]() |
6d14368e2f | ||
![]() |
f90ad48906 | ||
![]() |
e95dac82c2 | ||
![]() |
144601ffd4 | ||
![]() |
cbcc2aa731 | ||
![]() |
f9354c8cd1 | ||
![]() |
aff0416405 | ||
![]() |
6d9a14feed | ||
![]() |
3c2e5c143a | ||
![]() |
cbf1d2feb0 | ||
![]() |
65bb7f32e9 | ||
![]() |
3b8c365f7e | ||
![]() |
43747accd5 | ||
![]() |
50f9bbeac3 | ||
![]() |
3cffd9334f | ||
![]() |
f72749304d | ||
![]() |
689d08416b | ||
![]() |
28182713bf | ||
![]() |
8190c7aac7 | ||
![]() |
6268f091b8 | ||
![]() |
cbfde7ddb1 | ||
![]() |
35d6c7d79e | ||
![]() |
f1a2cf7efc | ||
![]() |
ec4eb22a72 | ||
![]() |
97c218cb4c | ||
![]() |
07021b8dd5 | ||
![]() |
aa9bf08157 | ||
![]() |
90dd7037c8 | ||
![]() |
6084759907 | ||
![]() |
0a3d87dfe3 | ||
![]() |
65a3897f87 | ||
![]() |
df20196201 | ||
![]() |
e9d60f73f4 | ||
![]() |
8e9717906f | ||
![]() |
82183bf204 | ||
![]() |
b2406b0465 | ||
![]() |
1bdef97dc7 | ||
![]() |
ff9c68db56 | ||
![]() |
1cedc94652 | ||
![]() |
02bda3250b | ||
![]() |
f6f60e8203 | ||
![]() |
55beb433f3 | ||
![]() |
2fff18cb61 | ||
![]() |
6d8bf3a08a | ||
![]() |
13ee79a06b | ||
![]() |
b4208ede3b | ||
![]() |
a96cb3f99f | ||
![]() |
1f39a922d1 | ||
![]() |
145fba5949 | ||
![]() |
7a4f418e75 | ||
![]() |
8fa8e34027 | ||
![]() |
5225874498 | ||
![]() |
8cb86c364d | ||
![]() |
6d98014ad2 | ||
![]() |
1cf83c2485 | ||
![]() |
f638bd4ded | ||
![]() |
7b169fb798 | ||
![]() |
406cbb0f41 | ||
![]() |
802493fec4 | ||
![]() |
aeb08caf77 | ||
![]() |
6d8daac5ca | ||
![]() |
82186c8592 | ||
![]() |
f099a352b3 | ||
![]() |
01f0625d6a | ||
![]() |
38ff7ec89f | ||
![]() |
7163a0802d | ||
![]() |
8d0fcf37c5 | ||
![]() |
3da5a8bb34 | ||
![]() |
fad33b583b | ||
![]() |
8f450501cc | ||
![]() |
b3ae0e577a | ||
![]() |
aedac94e40 | ||
![]() |
08c5dbce09 | ||
![]() |
482d658de7 | ||
![]() |
39e34081fc | ||
![]() |
5797786a75 | ||
![]() |
f6977ea264 | ||
![]() |
de8c99eceb | ||
![]() |
cdc15c0f20 | ||
![]() |
18408901be | ||
![]() |
982c915931 | ||
![]() |
a339eacd45 | ||
![]() |
8fc0e012e3 | ||
![]() |
b7fb9635e5 | ||
![]() |
f988a958bb | ||
![]() |
c80cb57b07 | ||
![]() |
e764ddf0b8 | ||
![]() |
a210f951da | ||
![]() |
367532cf8d | ||
![]() |
d3e6200385 | ||
![]() |
af504891e4 | ||
![]() |
dec28e16d4 | ||
![]() |
bd3777f9de | ||
![]() |
e4ee0fc397 | ||
![]() |
9690ccd8a0 | ||
![]() |
0804647441 | ||
![]() |
76da287cca | ||
![]() |
8739dd97d6 | ||
![]() |
a7874cc351 | ||
![]() |
de21f14fd8 | ||
![]() |
b90cca77a9 | ||
![]() |
1eadc08098 | ||
![]() |
9df26b8c84 | ||
![]() |
525cb0689f | ||
![]() |
7796c473ae | ||
![]() |
e0c9aba25e | ||
![]() |
2cb33bfec4 | ||
![]() |
2aad7647ef | ||
![]() |
6430129b2a | ||
![]() |
8445165491 | ||
![]() |
c5698fcd65 | ||
![]() |
786e50c3c3 | ||
![]() |
7d03be3a77 | ||
![]() |
4496d7762b | ||
![]() |
8b12caad78 | ||
![]() |
e1492c3bb1 | ||
![]() |
3cc66e9db9 | ||
![]() |
37389a62c1 | ||
![]() |
aef65620da | ||
![]() |
cd094d1475 | ||
![]() |
99bef09e0e | ||
![]() |
c97e77c765 | ||
![]() |
8ece8ebec2 | ||
![]() |
e44a22e143 | ||
![]() |
c2496fe63e | ||
![]() |
7bf8467637 | ||
![]() |
27677adbe8 | ||
![]() |
6fa06a4f8b | ||
![]() |
a3be4d2945 | ||
![]() |
3dc5b5c31a | ||
![]() |
342e11f83e | ||
![]() |
b6d78b4001 | ||
![]() |
2aaa6db538 | ||
![]() |
fc909d8199 | ||
![]() |
262136393b | ||
![]() |
1640859542 | ||
![]() |
f0b48acaf9 | ||
![]() |
bb8c305fc8 | ||
![]() |
3950dba2c5 | ||
![]() |
903ea45228 | ||
![]() |
b116b8ba1e | ||
![]() |
908043fb7f | ||
![]() |
2d7e3f180e | ||
![]() |
c813d17595 | ||
![]() |
7d16265c4e | ||
![]() |
4fb4764d3f | ||
![]() |
2385f1cbf8 | ||
![]() |
4d39c3bbd2 | ||
![]() |
23ddb87c9f | ||
![]() |
db662a7662 | ||
![]() |
eb5809fd17 | ||
![]() |
f6ab82faaf | ||
![]() |
0d8584f499 | ||
![]() |
c289e1beef | ||
![]() |
db491cbb09 | ||
![]() |
3d80d66925 | ||
![]() |
ce208b69fe | ||
![]() |
a44418c6a1 | ||
![]() |
5ac84491c1 | ||
![]() |
58769eb06e | ||
![]() |
59ae9c6437 | ||
![]() |
81d698c091 | ||
![]() |
bce0895392 | ||
![]() |
39f3383709 | ||
![]() |
4f8fc8d65e | ||
![]() |
a2cc7a84db | ||
![]() |
0397ba857f | ||
![]() |
90dd2b5ac2 | ||
![]() |
3597aacce9 | ||
![]() |
61bc5436a2 | ||
![]() |
37428fbe3b | ||
![]() |
444fb4555b | ||
![]() |
294f890a92 | ||
![]() |
578502187d | ||
![]() |
786082a9d0 | ||
![]() |
b6eb851a13 | ||
![]() |
93c41f044c | ||
![]() |
a7f61397f7 | ||
![]() |
0f5f20247a | ||
![]() |
e909eea82a | ||
![]() |
ef4b977bef | ||
![]() |
fbe1e2c2cc | ||
![]() |
9b86ed7c45 | ||
![]() |
c0c83c5cdc | ||
![]() |
638093d40e | ||
![]() |
8707287349 | ||
![]() |
37218c1e81 | ||
![]() |
b98cab7d62 | ||
![]() |
656cf32753 | ||
![]() |
9e2aac0696 | ||
![]() |
8c48d73b03 | ||
![]() |
b48a55bd74 | ||
![]() |
a1b116d2f5 | ||
![]() |
aad1bc898e | ||
![]() |
4add484d2a | ||
![]() |
8eed2232ee | ||
![]() |
6a01e40394 | ||
![]() |
757176727d | ||
![]() |
d9fdae9175 | ||
![]() |
4a84cf3ac7 | ||
![]() |
8f5f3e56cb | ||
![]() |
e03e51c450 | ||
![]() |
f61e33aa71 | ||
![]() |
4bc3fc274b | ||
![]() |
e3cdf6361e | ||
![]() |
674f879fb8 | ||
![]() |
b195fc69dd | ||
![]() |
2049e4e1d5 | ||
![]() |
d09ab4a153 | ||
![]() |
328c0ade22 | ||
![]() |
b312e17904 | ||
![]() |
2cd3e9cacd | ||
![]() |
ce72acdd61 | ||
![]() |
b3d92b04cb | ||
![]() |
8e74acbf3e | ||
![]() |
a5703cb4f1 | ||
![]() |
76cc7f1169 | ||
![]() |
b8cb7e9ba8 | ||
![]() |
2019ae43d7 | ||
![]() |
bb5b92571e | ||
![]() |
3ad5f889a0 | ||
![]() |
3ff10bfeae | ||
![]() |
c8a2a8b003 | ||
![]() |
6acb3a7ca5 | ||
![]() |
ef38897a01 | ||
![]() |
33db0e666a | ||
![]() |
cf32372a57 | ||
![]() |
b98cf9538d | ||
![]() |
768fef502d | ||
![]() |
d080c5dd14 | ||
![]() |
4fafa39e54 | ||
![]() |
bb7487c476 | ||
![]() |
308195279c | ||
![]() |
1a707eab86 | ||
![]() |
e2efc3e8e8 | ||
![]() |
fa6d5e60ce | ||
![]() |
e95b7075b9 | ||
![]() |
dfbc1beccd | ||
![]() |
0a7a0ac681 | ||
![]() |
981d4be44d | ||
![]() |
80bdf7410b | ||
![]() |
6f45d33c8a | ||
![]() |
cd22985f11 | ||
![]() |
66c0425080 | ||
![]() |
fd7fd9ca35 | ||
![]() |
7b3efa746e | ||
![]() |
90ddc2bfa7 | ||
![]() |
8e3425ed6d | ||
![]() |
c1bb42fe65 | ||
![]() |
b99f73919f | ||
![]() |
978bf75765 | ||
![]() |
14d591caa1 | ||
![]() |
432d5b43a1 | ||
![]() |
3412372d1e | ||
![]() |
ca6d472e5d | ||
![]() |
c2e130f369 | ||
![]() |
6501454424 | ||
![]() |
0019a4e1db | ||
![]() |
f8874fc586 | ||
![]() |
4ae875601a | ||
![]() |
e8880f068f | ||
![]() |
45c1c23e09 | ||
![]() |
e10d4bf45c | ||
![]() |
369b640408 | ||
![]() |
d2d30bf668 | ||
![]() |
dcb59edb79 | ||
![]() |
84d31c1d59 | ||
![]() |
58d41f4458 | ||
![]() |
360effb839 | ||
![]() |
3e428c044a | ||
![]() |
d923c99975 | ||
![]() |
2b538061e9 | ||
![]() |
11451ac6b9 | ||
![]() |
21a8d03201 | ||
![]() |
0c9db5afe9 | ||
![]() |
1031429550 | ||
![]() |
4a7ea469d4 | ||
![]() |
833c1cd98f | ||
![]() |
662922be6f | ||
![]() |
370ae323f6 | ||
![]() |
514eef50de | ||
![]() |
ec1fd7b923 | ||
![]() |
737ce99398 | ||
![]() |
4f1a897e18 | ||
![]() |
588a8b2a3a | ||
![]() |
f281a32a4e | ||
![]() |
d09e6089ca | ||
![]() |
7b6062a4a2 | ||
![]() |
7975fadfe9 | ||
![]() |
ad571e6019 | ||
![]() |
55cac17456 | ||
![]() |
846e84d3a4 | ||
![]() |
14d924f6c7 | ||
![]() |
282f02f4d5 | ||
![]() |
d36cede0c7 | ||
![]() |
a262d70b88 | ||
![]() |
9fa7edffbf | ||
![]() |
24a47445f6 | ||
![]() |
77b098c5fe | ||
![]() |
e4b688a2c3 | ||
![]() |
6d219ade2d | ||
![]() |
b48c16b4db | ||
![]() |
0a1ca18de5 | ||
![]() |
9a0b784153 | ||
![]() |
a446ec31c7 | ||
![]() |
9323737d1d | ||
![]() |
d288acdd4e | ||
![]() |
765aded208 | ||
![]() |
1f9fd92b01 | ||
![]() |
2be642e4c9 | ||
![]() |
934356dea8 | ||
![]() |
a037bf6f9d | ||
![]() |
c005c86c27 | ||
![]() |
68278267e1 | ||
![]() |
da2d5674a5 | ||
![]() |
5bb8eb22ec | ||
![]() |
0d86a7e427 | ||
![]() |
9f4aa0a123 | ||
![]() |
da838e266e | ||
![]() |
fd160e87e8 | ||
![]() |
0dd4304902 | ||
![]() |
a0b94bba4e | ||
![]() |
74c2a2b5cb | ||
![]() |
b691c30677 | ||
![]() |
01acefe4cf | ||
![]() |
1a2c5a95c7 | ||
![]() |
eb8725a0db | ||
![]() |
57e1061063 | ||
![]() |
91f4681a3c | ||
![]() |
822a4fab86 | ||
![]() |
44a164ed28 | ||
![]() |
ff2521aa9f | ||
![]() |
9094e8bde9 | ||
![]() |
573a292e16 | ||
![]() |
04b7f2fa8c | ||
![]() |
1f09c03d48 | ||
![]() |
aafde8986d | ||
![]() |
03e3ab5599 | ||
![]() |
8148c1a8a8 | ||
![]() |
ce61bcc078 | ||
![]() |
49e2b05a11 | ||
![]() |
c76322c40d | ||
![]() |
39c68ff26e | ||
![]() |
481f2a4717 | ||
![]() |
c01c076561 | ||
![]() |
2391e08ac1 | ||
![]() |
a7a2fe243a | ||
![]() |
b5c655dd70 | ||
![]() |
d84a61d108 | ||
![]() |
13abbd2c5d | ||
![]() |
d22eac9f35 | ||
![]() |
d71aa8d7e2 | ||
![]() |
4db6199fd9 | ||
![]() |
dd9cbbac77 | ||
![]() |
069d4956aa | ||
![]() |
61fee3122a | ||
![]() |
5a8255a9f4 | ||
![]() |
5a2a8b4c8e | ||
![]() |
1079080f5c | ||
![]() |
1e914c4cf2 | ||
![]() |
53226c1050 | ||
![]() |
7a4e0301d2 | ||
![]() |
42826e542d | ||
![]() |
42d1c9403c | ||
![]() |
ab43635583 | ||
![]() |
b8ecd83bfd | ||
![]() |
0db0ced1ab | ||
![]() |
3d20833d42 | ||
![]() |
7d09ba5669 | ||
![]() |
310ff30278 | ||
![]() |
821bc9405c | ||
![]() |
d54d66cffc | ||
![]() |
d2b4c40357 | ||
![]() |
f48af97ddc | ||
![]() |
f1264d6310 | ||
![]() |
4dc5a1499a | ||
![]() |
dd51b675b0 | ||
![]() |
7679705369 | ||
![]() |
58a8af20a6 | ||
![]() |
75a58f465c | ||
![]() |
220053c044 | ||
![]() |
613cbaa556 | ||
![]() |
0d0a634255 | ||
![]() |
105090db60 | ||
![]() |
3e9c75f735 | ||
![]() |
d42075072b | ||
![]() |
6a099f0813 | ||
![]() |
9af5e40071 | ||
![]() |
4bf2a29070 | ||
![]() |
069d0e48c1 | ||
![]() |
c8cb2692dd | ||
![]() |
74a22bcf9c | ||
![]() |
8b44927cb6 | ||
![]() |
781ff956e3 | ||
![]() |
7522e5de33 | ||
![]() |
3209c94622 | ||
![]() |
dd4a7f9fac | ||
![]() |
6f8a5ea1be | ||
![]() |
69837837b0 | ||
![]() |
03d8bf2836 | ||
![]() |
eca2c142f3 | ||
![]() |
715f7bbbc2 | ||
![]() |
291ebd5c12 | ||
![]() |
ba04cac7ac | ||
![]() |
acb8c0b5af | ||
![]() |
0c14bacea7 | ||
![]() |
4cabb849f3 | ||
![]() |
445a85798b | ||
![]() |
55775f48e9 | ||
![]() |
b491b9d77d | ||
![]() |
237f792fb4 | ||
![]() |
4bd923dc06 | ||
![]() |
439654ed7f | ||
![]() |
87e956fe7d | ||
![]() |
02b9dbdee9 | ||
![]() |
69797e58cb | ||
![]() |
d1961ca3fa | ||
![]() |
2e9b1e079c | ||
![]() |
3dfaaf4c25 | ||
![]() |
fcc39a0397 | ||
![]() |
0602f457ce | ||
![]() |
d00b0f4c0e | ||
![]() |
5e68959c03 | ||
![]() |
ad4451a757 | ||
![]() |
4d8127a7d9 | ||
![]() |
cd2c8078c8 | ||
![]() |
ffc4b0a0a8 | ||
![]() |
ee360521bb | ||
![]() |
04b0b8c723 | ||
![]() |
b3ac8d0835 | ||
![]() |
1a88a0164c | ||
![]() |
8b77ad7748 | ||
![]() |
8d1f668fc5 | ||
![]() |
68fbbb64db | ||
![]() |
eee92aa1aa | ||
![]() |
74eb4cca86 | ||
![]() |
49f5c94a75 | ||
![]() |
8bba4b2973 | ||
![]() |
d94e8c8187 | ||
![]() |
2307325af8 | ||
![]() |
4e57b4be88 | ||
![]() |
21335e7d05 | ||
![]() |
abdd70c6fa | ||
![]() |
78768e00d4 | ||
![]() |
3eb44708e5 | ||
![]() |
c077b22410 | ||
![]() |
9787ab04cd | ||
![]() |
79d3c90026 | ||
![]() |
c2068b343b | ||
![]() |
9e63e40dab | ||
![]() |
6424839731 | ||
![]() |
863a525625 | ||
![]() |
f82a727e24 | ||
![]() |
995fcab071 | ||
![]() |
c5270d75a1 | ||
![]() |
8c757d1e03 | ||
![]() |
324c409ff1 | ||
![]() |
1db8e6dddc | ||
![]() |
c49a770c59 | ||
![]() |
dd9ba4d250 | ||
![]() |
0e601d5b5f | ||
![]() |
ebb632e888 | ||
![]() |
4db2335107 | ||
![]() |
26a2fb6662 | ||
![]() |
4c1521a98e | ||
![]() |
df8abb099c | ||
![]() |
e74c86842b | ||
![]() |
fe1332f18e | ||
![]() |
cb9ab7a2c9 | ||
![]() |
ad6e30f3d4 | ||
![]() |
3a6b2875d5 | ||
![]() |
766de7e6fa | ||
![]() |
13439e100e | ||
![]() |
5ac469b174 | ||
![]() |
8ac5b48493 | ||
![]() |
1a827ef44f | ||
![]() |
0f0e346a02 | ||
![]() |
b6eb4e84e4 | ||
![]() |
2a6db47aeb | ||
![]() |
f7d425189a | ||
![]() |
6ca78ce8cb | ||
![]() |
4b7c927ca4 | ||
![]() |
19b7957523 | ||
![]() |
1b1a3c261d | ||
![]() |
9d674ce5a7 | ||
![]() |
f22e9ceec6 | ||
![]() |
0d2cb02f97 | ||
![]() |
a4de937eb6 | ||
![]() |
7d8d80ca2c | ||
![]() |
b4e130234c | ||
![]() |
53cc413eae | ||
![]() |
0449c46b38 | ||
![]() |
f8450183c1 | ||
![]() |
eb1f8b4bc6 | ||
![]() |
088864fd9c | ||
![]() |
cd0c86e0d6 | ||
![]() |
a56f92cdee | ||
![]() |
c82971f8db | ||
![]() |
cb2c376c63 | ||
![]() |
bc00fa0694 | ||
![]() |
c86007da71 | ||
![]() |
1bf513ca74 | ||
![]() |
29411c903f | ||
![]() |
017dde364c | ||
![]() |
c123d7370a | ||
![]() |
76f1e5e8f3 | ||
![]() |
4e466c7886 | ||
![]() |
d469fb9c58 | ||
![]() |
4c285bce57 | ||
![]() |
050ab03448 | ||
![]() |
40acf751a8 | ||
![]() |
126ad585c0 | ||
![]() |
1d04b649e0 | ||
![]() |
b750006cf0 | ||
![]() |
3c16b839b6 | ||
![]() |
32aea6b86c | ||
![]() |
688c2d0df5 | ||
![]() |
ede75530d0 | ||
![]() |
3ae225ac59 | ||
![]() |
fd07b89028 | ||
![]() |
0cea56dc62 | ||
![]() |
bb44268c30 | ||
![]() |
19b3dcf1c2 | ||
![]() |
71e0a4e0ce | ||
![]() |
7f2e879e24 | ||
![]() |
840e44deac | ||
![]() |
f7707faece | ||
![]() |
b631e3fef6 | ||
![]() |
b0fb9db4b8 | ||
![]() |
c7e9115994 | ||
![]() |
f2d115ee4d | ||
![]() |
0198f05112 | ||
![]() |
729307336e | ||
![]() |
f4f9b5c91c | ||
![]() |
f355fe3447 | ||
![]() |
321718d43a | ||
![]() |
a1b161493c | ||
![]() |
5acee80463 | ||
![]() |
e6932401ad | ||
![]() |
7a4eff0f5c | ||
![]() |
8b533e9893 | ||
![]() |
02b92c7977 | ||
![]() |
e386863bdb | ||
![]() |
16be7cb28a | ||
![]() |
f6d97c19d9 | ||
![]() |
8fef01d961 | ||
![]() |
43dcd544f2 | ||
![]() |
b29bb6fdd7 | ||
![]() |
d2325306db | ||
![]() |
dfe31980b7 | ||
![]() |
9734b78aeb | ||
![]() |
4ee0800990 | ||
![]() |
387008bd9c | ||
![]() |
bf24547202 | ||
![]() |
454ef0076b | ||
![]() |
18693d2471 | ||
![]() |
5a9583c970 | ||
![]() |
0046f7e3d7 | ||
![]() |
8d3836cb16 | ||
![]() |
c2f6b8df66 | ||
![]() |
d0e428b728 | ||
![]() |
ae5128a33a | ||
![]() |
83fa50bf13 | ||
![]() |
3efe9309ef | ||
![]() |
456b85ed03 | ||
![]() |
6bc5f87cb7 | ||
![]() |
a010bba047 | ||
![]() |
ed25017e2d | ||
![]() |
e5b2b84073 | ||
![]() |
1953d9a4c8 | ||
![]() |
b3113c109b | ||
![]() |
9829491c4c | ||
![]() |
8c3569ea63 | ||
![]() |
ae28d476de | ||
![]() |
01c4cfdc8d | ||
![]() |
8c6c3a1c01 | ||
![]() |
f367c1f78b | ||
![]() |
13d87d397d | ||
![]() |
ed2c886359 | ||
![]() |
6337e84708 | ||
![]() |
ae20a951be | ||
![]() |
866dd546c8 | ||
![]() |
2070e1a96b | ||
![]() |
952f49e2e1 | ||
![]() |
f600571c6d | ||
![]() |
e6fb0be1d0 | ||
![]() |
5d4c7244e1 | ||
![]() |
d02124550b | ||
![]() |
b6bdcaa71f | ||
![]() |
c8eab5d218 | ||
![]() |
8ab56a29ac | ||
![]() |
973b5f3f5c | ||
![]() |
076a1f97c2 | ||
![]() |
d96ef37d81 | ||
![]() |
56f8302402 | ||
![]() |
97e6a7cbd8 | ||
![]() |
d89d79116c | ||
![]() |
367d79e820 | ||
![]() |
283d7f2159 | ||
![]() |
dacb3ef6c3 | ||
![]() |
22454ae842 | ||
![]() |
79b9b63982 | ||
![]() |
2f68bf30a4 | ||
![]() |
50f078cc45 | ||
![]() |
47a410d6ab | ||
![]() |
31cbf8cccc | ||
![]() |
88bd321e3e | ||
![]() |
b4a586c0b9 | ||
![]() |
63593f1b6c | ||
![]() |
dcff8971e8 | ||
![]() |
6aa9d2b492 | ||
![]() |
3f7059a235 | ||
![]() |
71c743ff5a | ||
![]() |
19366e3624 | ||
![]() |
18f6ab451d | ||
![]() |
15fbe5a459 | ||
![]() |
cd47394709 | ||
![]() |
6f4ee8b7b6 | ||
![]() |
8c5d05b611 | ||
![]() |
3bdcf4d851 | ||
![]() |
1d1c69ca51 | ||
![]() |
c4c679021d | ||
![]() |
c16d13e2c9 | ||
![]() |
97613eb3c7 | ||
![]() |
a946d04a72 | ||
![]() |
cc35ec82eb | ||
![]() |
d6604e0008 | ||
![]() |
b9e63efc37 | ||
![]() |
b458707921 | ||
![]() |
6ec52c6cd2 | ||
![]() |
79d7a5dd87 | ||
![]() |
1653d0212a | ||
![]() |
c378bcb00b | ||
![]() |
215e7a5f5d | ||
![]() |
6b686681d5 | ||
![]() |
20ea050728 | ||
![]() |
4b6132a2d7 | ||
![]() |
4cf80e3ebb | ||
![]() |
045747f543 | ||
![]() |
b10c5e3256 | ||
![]() |
69df2e4183 | ||
![]() |
12ebd35c4d | ||
![]() |
30c5f76cf0 | ||
![]() |
3a979b6cda | ||
![]() |
863bdffa84 | ||
![]() |
42b956e402 | ||
![]() |
4acf7b4e4f | ||
![]() |
42e881326f | ||
![]() |
b19f8f65a4 | ||
![]() |
97255bbb33 | ||
![]() |
3c4893d7c7 | ||
![]() |
027aca4ab2 | ||
![]() |
2d471f551f | ||
![]() |
5c598b69b0 | ||
![]() |
037300de79 | ||
![]() |
6990dcae89 | ||
![]() |
6e81d6dfcd | ||
![]() |
cea30465d8 | ||
![]() |
b301d16cb2 | ||
![]() |
19c002fcdd | ||
![]() |
ab3a73fe58 | ||
![]() |
91fc2c28dc | ||
![]() |
ca47ba3c7c | ||
![]() |
e1b456c01c | ||
![]() |
5eb7e00eac | ||
![]() |
8bcf68c8a1 | ||
![]() |
520eaedd9a | ||
![]() |
4c5e664ce0 | ||
![]() |
53c500eb1b | ||
![]() |
253346a201 | ||
![]() |
c5d82a76ab | ||
![]() |
18a9dfffc7 | ||
![]() |
38838e4dca | ||
![]() |
5c151d727b | ||
![]() |
2d5b157c91 | ||
![]() |
10d3d27a33 | ||
![]() |
1614442bd7 | ||
![]() |
a3c5b1e107 | ||
![]() |
3f0af9cdea | ||
![]() |
f4de4de8c1 | ||
![]() |
69033a7343 | ||
![]() |
5a22106731 | ||
![]() |
5155d0ed56 | ||
![]() |
9be674103f | ||
![]() |
ba24e12454 | ||
![]() |
b76aa16143 | ||
![]() |
8f7bb3a7c9 | ||
![]() |
be2b466376 | ||
![]() |
f957925aac | ||
![]() |
1bf8d63d1a | ||
![]() |
214d6c4405 | ||
![]() |
8875144307 | ||
![]() |
fa87fc8325 | ||
![]() |
d5c01f387a | ||
![]() |
32709cd60f | ||
![]() |
aaf0a91975 | ||
![]() |
6cc6230b91 | ||
![]() |
dd630f20f8 | ||
![]() |
0c6efd95fa | ||
![]() |
a276378887 | ||
![]() |
98d861a639 | ||
![]() |
3b3c27072f | ||
![]() |
3089c39369 | ||
![]() |
0cbb17f7ce | ||
![]() |
54793f2b78 | ||
![]() |
f1ffe19ec8 | ||
![]() |
e638fb69b5 | ||
![]() |
718a94b5e0 | ||
![]() |
3079059ce3 | ||
![]() |
d6c6981bc0 | ||
![]() |
8aeb7b60a7 | ||
![]() |
9197ac6510 | ||
![]() |
b67ad3073c | ||
![]() |
4a4f37f888 | ||
![]() |
c55331f220 | ||
![]() |
757ec98554 | ||
![]() |
14309f2069 | ||
![]() |
e6b89d571e | ||
![]() |
d957198fd6 | ||
![]() |
903bd22999 | ||
![]() |
48f3a3b18f | ||
![]() |
25e0585742 | ||
![]() |
04ee2fb3e4 | ||
![]() |
ac2466a304 | ||
![]() |
ab4c9ef0d6 | ||
![]() |
a69063de9b | ||
![]() |
62b76777c0 | ||
![]() |
431f1d4be0 | ||
![]() |
32a91bda0a | ||
![]() |
cc840c050b | ||
![]() |
e19d3d0d06 | ||
![]() |
749a587eb2 | ||
![]() |
5047619e54 | ||
![]() |
63a23e4dcc | ||
![]() |
a15af602e0 | ||
![]() |
3466f4e452 | ||
![]() |
ec9dfd2918 | ||
![]() |
b36e3adf7c | ||
![]() |
016086ef4e | ||
![]() |
2e5ea968ee | ||
![]() |
5dde1c1c04 | ||
![]() |
be14d56eae | ||
![]() |
c6a292bccb | ||
![]() |
f51d924ec9 | ||
![]() |
96d5930f05 | ||
![]() |
7c9ebafd00 | ||
![]() |
100b097ace | ||
![]() |
3833c5f9fe | ||
![]() |
f6c4b5073c | ||
![]() |
f73452e01f | ||
![]() |
55f7cac526 | ||
![]() |
3470dd9f3b | ||
![]() |
31e758ca45 | ||
![]() |
f81ca3ba60 | ||
![]() |
c5e8649284 | ||
![]() |
a95f43aa4d | ||
![]() |
0420aa8edb | ||
![]() |
806b43dfec | ||
![]() |
98f4fe4c2b | ||
![]() |
0d63b3cbae | ||
![]() |
7061c05f77 | ||
![]() |
9b9a182f9f | ||
![]() |
2d9e7fcc6d | ||
![]() |
56946a66aa | ||
![]() |
c9242e32fe | ||
![]() |
22e8883934 | ||
![]() |
552a18d89a | ||
![]() |
6b725e9114 | ||
![]() |
415beaa0b0 | ||
![]() |
e9fe227ed7 | ||
![]() |
c1be109592 | ||
![]() |
88e603bbf1 | ||
![]() |
c7b2095bb4 | ||
![]() |
7396117d89 | ||
![]() |
fd96bf345b | ||
![]() |
2820660264 | ||
![]() |
86145dbf67 | ||
![]() |
135b17186e | ||
![]() |
ce494339ef | ||
![]() |
dd5af7eb10 | ||
![]() |
3abe99078e | ||
![]() |
edbd540c68 | ||
![]() |
06d9302d96 | ||
![]() |
032b4bed7f | ||
![]() |
8559f5c8ea | ||
![]() |
91133172d5 | ||
![]() |
001f3f30cd | ||
![]() |
a1b1059ad1 | ||
![]() |
69a0122fea | ||
![]() |
3d505b4248 | ||
![]() |
70d4e3394c | ||
![]() |
3905c01a0d | ||
![]() |
2a49af1ec3 | ||
![]() |
3d4f54e8bc | ||
![]() |
61f2f2d2e3 | ||
![]() |
00058bd5c7 | ||
![]() |
383fe50fc9 | ||
![]() |
e4fdadc573 | ||
![]() |
032f8808ef | ||
![]() |
10cf0d13c2 | ||
![]() |
6c9fea97ef | ||
![]() |
4c2d612bdd | ||
![]() |
8e9fece77d | ||
![]() |
c818370123 | ||
![]() |
bf2643802a | ||
![]() |
1ad079fbd4 | ||
![]() |
2d9e8773f5 | ||
![]() |
5aa13f2428 | ||
![]() |
38f5fcde86 | ||
![]() |
f99ab87ca1 | ||
![]() |
d3d230a76d | ||
![]() |
e7c6a05e9f | ||
![]() |
17ec5c2683 | ||
![]() |
8d1839b9e2 | ||
![]() |
23213a4ac5 | ||
![]() |
e81c81351d | ||
![]() |
36e16d9925 | ||
![]() |
84eaaf4819 | ||
![]() |
757096d97a | ||
![]() |
2393eac218 | ||
![]() |
745d21d1bc | ||
![]() |
cb34518a89 | ||
![]() |
821dd9c48c | ||
![]() |
740f3924df | ||
![]() |
f052ed9b00 | ||
![]() |
2507362741 | ||
![]() |
481a4266b0 | ||
![]() |
92d9abf43a | ||
![]() |
5bcdd99870 | ||
![]() |
e11bb38625 | ||
![]() |
affe057cab | ||
![]() |
20bb104006 | ||
![]() |
c1b2fc9400 | ||
![]() |
782e0f3475 | ||
![]() |
6be7ac89d4 | ||
![]() |
ad2424cfdd | ||
![]() |
b9703e2bb6 | ||
![]() |
198b67104f | ||
![]() |
8dab0eac58 | ||
![]() |
1f771a9e01 | ||
![]() |
223e2e0b73 | ||
![]() |
fbcb626fd0 | ||
![]() |
8f85e57fc5 | ||
![]() |
7557f43f77 | ||
![]() |
a6c0db47ab | ||
![]() |
8b78383732 | ||
![]() |
f1c31ea966 | ||
![]() |
1a1fad7433 | ||
![]() |
8675d1a666 | ||
![]() |
1f634ed341 | ||
![]() |
ab248a0209 | ||
![]() |
50c916216c | ||
![]() |
6155dbb6e9 | ||
![]() |
f100356078 | ||
![]() |
a2f4e34866 | ||
![]() |
7c1ac4392e | ||
![]() |
20452fe3cb | ||
![]() |
f7310369e6 | ||
![]() |
9b7f4e824a | ||
![]() |
0c06e64051 | ||
![]() |
2b6c280858 | ||
![]() |
6de74b8211 | ||
![]() |
3e5b3900bc | ||
![]() |
0453bd098b | ||
![]() |
4dc6dac885 | ||
![]() |
8da9af8989 | ||
![]() |
bd34f6bb6d | ||
![]() |
ff59a9d130 | ||
![]() |
63e0e4eaeb | ||
![]() |
c9635bee06 | ||
![]() |
1b2d09c1e5 | ||
![]() |
a947e06140 | ||
![]() |
53b92ae8ef | ||
![]() |
87b77215e4 | ||
![]() |
2a55f1317a | ||
![]() |
c3a353837f | ||
![]() |
035b6dfa95 | ||
![]() |
33b2fd8d69 | ||
![]() |
541e4ff8cd | ||
![]() |
ba5c503c48 | ||
![]() |
91eb692d99 | ||
![]() |
9abaadf1b6 | ||
![]() |
651ca6cf5d | ||
![]() |
ab44100312 | ||
![]() |
a39e2c165d | ||
![]() |
d182261ff3 | ||
![]() |
052c32e2ce | ||
![]() |
2480dca473 | ||
![]() |
f101eefc56 | ||
![]() |
fa8736b1a4 | ||
![]() |
db7b52d84c | ||
![]() |
2027b643b1 | ||
![]() |
a4d7065e42 | ||
![]() |
2ff7b5902c | ||
![]() |
dcf3152dee | ||
![]() |
4338e2626b | ||
![]() |
e04f66f599 | ||
![]() |
27ee7f8592 | ||
![]() |
f95f01b895 | ||
![]() |
866b36729c | ||
![]() |
67dffbec32 | ||
![]() |
7504f89666 | ||
![]() |
ba79144036 | ||
![]() |
471009bc17 | ||
![]() |
c90f30a7a1 | ||
![]() |
ffb65d8ea1 | ||
![]() |
974ab29e36 | ||
![]() |
fa523e0d4a | ||
![]() |
ab9bc7e390 | ||
![]() |
4147ec719b | ||
![]() |
a5d58071cd | ||
![]() |
4ca14ed0ce | ||
![]() |
7d410fcdc9 | ||
![]() |
2bacd6424d | ||
![]() |
27b0505ede | ||
![]() |
1ce6e32086 | ||
![]() |
fc1b8917a8 | ||
![]() |
ec00413a4b | ||
![]() |
33f2c89716 | ||
![]() |
45d2fbc5fc | ||
![]() |
d00b9e0928 | ||
![]() |
58a7f7b739 | ||
![]() |
40a1da10e3 | ||
![]() |
1ef4f71d8b | ||
![]() |
a0f034b843 | ||
![]() |
dcf6564d0a | ||
![]() |
6db9521419 | ||
![]() |
336810cbf4 | ||
![]() |
9d5e533e32 | ||
![]() |
37c8d1b7ea | ||
![]() |
6a4bc3c9df | ||
![]() |
78c1c4d680 | ||
![]() |
b45fc630ae | ||
![]() |
60340fc8ae | ||
![]() |
be0fc7591d | ||
![]() |
7f9a2eeb6b | ||
![]() |
40b29d770a | ||
![]() |
3f3a3e0016 | ||
![]() |
6a561185df | ||
![]() |
049656ec6b | ||
![]() |
709630f39b | ||
![]() |
eb3a2502f5 | ||
![]() |
ade056e881 | ||
![]() |
6768038a2f | ||
![]() |
905bc6699e | ||
![]() |
9a471d2a26 | ||
![]() |
0a0240e520 | ||
![]() |
0977e82170 | ||
![]() |
ed84614389 | ||
![]() |
67bd2605c0 | ||
![]() |
83c145c2ac | ||
![]() |
53bf52c989 | ||
![]() |
176e83a79f | ||
![]() |
95f81ad740 | ||
![]() |
9ffab374db | ||
![]() |
46b8b2fa7e | ||
![]() |
189e077247 | ||
![]() |
c29ea53405 | ||
![]() |
0d1e5311dc | ||
![]() |
cefe3fc542 | ||
![]() |
749b0510e7 | ||
![]() |
eed28f67d5 | ||
![]() |
739e06d7d3 | ||
![]() |
28b24b725f | ||
![]() |
1472f82205 | ||
![]() |
4990a1f9f1 | ||
![]() |
10556f528f | ||
![]() |
8ff6596657 | ||
![]() |
046e1ebad9 | ||
![]() |
9f52689fde | ||
![]() |
093a7c8c8a | ||
![]() |
ee519c7352 | ||
![]() |
ec0ecf5151 | ||
![]() |
bfb405c4a6 | ||
![]() |
bca73b496f | ||
![]() |
72d943aca2 | ||
![]() |
0f6ee68731 | ||
![]() |
3f08fa3b23 | ||
![]() |
1dddcb4345 | ||
![]() |
3fd1f4e6d9 | ||
![]() |
3ba12ae9ac | ||
![]() |
ea36687205 | ||
![]() |
c17e897674 | ||
![]() |
b7bdd4eed6 | ||
![]() |
792218df9c | ||
![]() |
161f89c038 | ||
![]() |
1920e9c7fb | ||
![]() |
5362508a99 | ||
![]() |
f26d175cad | ||
![]() |
84df055888 | ||
![]() |
ebcc5bd9c8 | ||
![]() |
b760c2f1a0 | ||
![]() |
acb8a342a7 | ||
![]() |
654596ea79 | ||
![]() |
8d2adfaae7 | ||
![]() |
5b6d57fd47 | ||
![]() |
39ba83eefb | ||
![]() |
03f435915b | ||
![]() |
07715bd167 | ||
![]() |
2e4f5b7070 | ||
![]() |
b3ee251ee3 | ||
![]() |
c90acd24f5 | ||
![]() |
e41eb3d8a2 | ||
![]() |
3c214ce17c | ||
![]() |
285e512483 | ||
![]() |
b8d3b33963 | ||
![]() |
3bbfc130d4 | ||
![]() |
6ddc1b1c9c | ||
![]() |
f79dd29ed3 | ||
![]() |
1198094d3b | ||
![]() |
2716be397c | ||
![]() |
9de93022d6 | ||
![]() |
bf28410812 | ||
![]() |
30288c6237 | ||
![]() |
2b0c327001 | ||
![]() |
4fe34a4839 | ||
![]() |
b8d39f49b2 | ||
![]() |
c9e9499106 | ||
![]() |
a7d763287e | ||
![]() |
61832a9e2a | ||
![]() |
cbb5e7c611 | ||
![]() |
bad45f19d6 | ||
![]() |
e6f19c3dfd | ||
![]() |
24e4e268dc | ||
![]() |
10005ce104 | ||
![]() |
4008dbf38a | ||
![]() |
cefe5bbaa8 | ||
![]() |
d327dd47b2 | ||
![]() |
66c768fe31 | ||
![]() |
037e2f3771 | ||
![]() |
a98d37e61c | ||
![]() |
77a0238406 | ||
![]() |
0a0316a000 | ||
![]() |
7e8f78acaa | ||
![]() |
2d6a56343b | ||
![]() |
615cb561e4 | ||
![]() |
c96a1df8e1 | ||
![]() |
350d10d98b | ||
![]() |
dcc28438ff | ||
![]() |
df7c1721f5 | ||
![]() |
6bec68021c | ||
![]() |
789ec94eff | ||
![]() |
f87666f659 | ||
![]() |
6987e6b1b9 | ||
![]() |
0ca0e7427d | ||
![]() |
be61419b80 | ||
![]() |
1f877817f4 | ||
![]() |
c7f13ff67f | ||
![]() |
403296cc59 | ||
![]() |
addb2b4448 | ||
![]() |
8556bdcdeb | ||
![]() |
5efc3f15f2 | ||
![]() |
5fb50a1759 | ||
![]() |
e50758f0a6 | ||
![]() |
8fc5f59647 | ||
![]() |
f7729c0fd2 | ||
![]() |
e2376c7c71 | ||
![]() |
2cefd3b941 | ||
![]() |
4cff1c8fa9 | ||
![]() |
0dbc7d4fd1 | ||
![]() |
68ab2da866 | ||
![]() |
72f7f0b70c | ||
![]() |
d18a7df3bc | ||
![]() |
f81012ef6e | ||
![]() |
3d672802a3 | ||
![]() |
c238862efe | ||
![]() |
7e7e5e7466 | ||
![]() |
cc20ad9567 | ||
![]() |
b0a8ca56bc | ||
![]() |
fbec0a1c7d | ||
![]() |
d5b5443a84 | ||
![]() |
411bba53a8 | ||
![]() |
e2af3c78e7 | ||
![]() |
a8aa1bc5e8 | ||
![]() |
76a066ab74 | ||
![]() |
b8b98358d0 | ||
![]() |
fca4ed7013 | ||
![]() |
59877b5138 | ||
![]() |
30aa4e05ef | ||
![]() |
4e38b09e18 | ||
![]() |
23f69bd21d | ||
![]() |
dc5ce31087 | ||
![]() |
e99331f305 | ||
![]() |
955a082614 | ||
![]() |
bbc580e71b | ||
![]() |
76f27dbcdb | ||
![]() |
543dce5721 | ||
![]() |
adc4f78e99 | ||
![]() |
852c95a994 | ||
![]() |
b7ed1becba | ||
![]() |
03ecaa81f7 | ||
![]() |
5f93c667a2 | ||
![]() |
cb66508b8a | ||
![]() |
ae55fde591 | ||
![]() |
fad4713a90 | ||
![]() |
c56038a1e2 | ||
![]() |
f491461a57 | ||
![]() |
7ce1bf1048 | ||
![]() |
af51090ebb | ||
![]() |
7e86f02e4e | ||
![]() |
4988ead918 | ||
![]() |
083d2d1cc4 | ||
![]() |
fbb3a02315 | ||
![]() |
eeb1d0a891 | ||
![]() |
0011e9caa8 | ||
![]() |
041cd9fb8e | ||
![]() |
81e62a6c22 | ||
![]() |
c501720cdd | ||
![]() |
92b5aa08f3 | ||
![]() |
1097170a68 | ||
![]() |
e6e2b169dc | ||
![]() |
e41145427e | ||
![]() |
caadf8e762 | ||
![]() |
1143c23ad9 | ||
![]() |
9b4fc3d3aa | ||
![]() |
c1bb93eec1 | ||
![]() |
1ae9c82e29 | ||
![]() |
715ca1c292 | ||
![]() |
e403adb9a1 | ||
![]() |
f3610e7c95 | ||
![]() |
951568ce22 | ||
![]() |
e8bb9e264d | ||
![]() |
208cb05c74 | ||
![]() |
2c68be3193 | ||
![]() |
2f5b0533d8 | ||
![]() |
5ece79c74d | ||
![]() |
12c226f874 | ||
![]() |
20e54a8ecf | ||
![]() |
7d15bfe58a | ||
![]() |
567474ce00 | ||
![]() |
209a5b7274 | ||
![]() |
e962c9594b | ||
![]() |
242bad09ea | ||
![]() |
8dd223f1cb | ||
![]() |
d15662dea4 | ||
![]() |
f47a408755 | ||
![]() |
bcc8804495 | ||
![]() |
5a7bb092c7 | ||
![]() |
d166dda229 | ||
![]() |
8360c9e9f9 | ||
![]() |
328142dac7 | ||
![]() |
372b2378a8 | ||
![]() |
574207c626 | ||
![]() |
ead64dcb47 | ||
![]() |
da473de9f0 | ||
![]() |
a177e2ab7e | ||
![]() |
8cef6db482 | ||
![]() |
79a3128491 | ||
![]() |
cb1fe80214 | ||
![]() |
19f2e6dae0 | ||
![]() |
74394a773d | ||
![]() |
d491e95d1c | ||
![]() |
881523ce54 | ||
![]() |
502a9fbb92 | ||
![]() |
56d33ea487 | ||
![]() |
cc44fe6557 | ||
![]() |
5b1ac3de18 | ||
![]() |
559741fd07 | ||
![]() |
b3c52a8601 | ||
![]() |
5a2a649d8e | ||
![]() |
1aef193b40 | ||
![]() |
27ec4aa923 | ||
![]() |
1760ab2305 | ||
![]() |
99ade42e9a | ||
![]() |
544d78f461 | ||
![]() |
750fa45c04 | ||
![]() |
50f2819699 | ||
![]() |
ab55b91da1 | ||
![]() |
59f7774964 | ||
![]() |
b5eb44af9f | ||
![]() |
8518fa67f2 | ||
![]() |
f6b68f9880 | ||
![]() |
2bc563b693 | ||
![]() |
23bdfd04a2 | ||
![]() |
6624f25a64 | ||
![]() |
f6b2783f8b | ||
![]() |
078849041f | ||
![]() |
1c711f5e03 | ||
![]() |
614af0602a | ||
![]() |
e55cfe0bc1 | ||
![]() |
2b703b2b9b | ||
![]() |
170feaaff2 | ||
![]() |
870c2b6d8b | ||
![]() |
eee8b7db56 | ||
![]() |
3c669a075e | ||
![]() |
f528930ad2 | ||
![]() |
231322eddf | ||
![]() |
8e6763c165 | ||
![]() |
f091918575 | ||
![]() |
bb077c87b3 | ||
![]() |
865ee6a720 | ||
![]() |
6947d2a7f3 | ||
![]() |
28abd00d82 | ||
![]() |
57314b77e5 | ||
![]() |
fe8f8bc712 | ||
![]() |
b035f6c410 | ||
![]() |
abe87830cd | ||
![]() |
615a020469 | ||
![]() |
7490651a06 | ||
![]() |
6220128a74 | ||
![]() |
ec205f4f7d | ||
![]() |
512487328d | ||
![]() |
6fbb2f26d1 | ||
![]() |
c91bfeaa81 | ||
![]() |
aa2b3b2843 | ||
![]() |
90d8be48d4 | ||
![]() |
48f8ca693d | ||
![]() |
a1624a9215 | ||
![]() |
fc2828fee3 | ||
![]() |
aa3f781e18 | ||
![]() |
aa6890432c | ||
![]() |
192d0ed8a6 | ||
![]() |
fff188eb30 | ||
![]() |
71067939e3 | ||
![]() |
b07c014b48 | ||
![]() |
f2c073798b | ||
![]() |
4cc38db895 | ||
![]() |
2762319dbb | ||
![]() |
8e5d43dfa8 | ||
![]() |
82ad8cc444 | ||
![]() |
50a3cc57ad | ||
![]() |
1d6bf39548 | ||
![]() |
7554cbda72 | ||
![]() |
6a67fe09de | ||
![]() |
f07263ca2a | ||
![]() |
bfbefb7318 | ||
![]() |
ec02b7deda | ||
![]() |
fa938f832f | ||
![]() |
6f59d9217c | ||
![]() |
7176e5ca6e | ||
![]() |
429829471f | ||
![]() |
4760e539b7 | ||
![]() |
f53e7ad617 | ||
![]() |
28c83fa921 | ||
![]() |
4588cd151c | ||
![]() |
2d9f87abef | ||
![]() |
491e6f5f5f | ||
![]() |
ab7c05284d | ||
![]() |
0586fa0e01 | ||
![]() |
53eba2337c | ||
![]() |
283d3e1e7b | ||
![]() |
7992a540ae | ||
![]() |
3f3e7a78eb | ||
![]() |
8d3d39acd3 | ||
![]() |
7b20aba2ff | ||
![]() |
bbfb732d8f | ||
![]() |
566adbcda5 | ||
![]() |
1bff490fa4 | ||
![]() |
d38375a08c | ||
![]() |
56551712d6 | ||
![]() |
c241d2f90b | ||
![]() |
0ba28dc891 | ||
![]() |
ac9af6d2ba | ||
![]() |
90d9e087f7 | ||
![]() |
50b24d9a56 | ||
![]() |
10f42e9a7f | ||
![]() |
987bd303a0 | ||
![]() |
a3f58fb831 | ||
![]() |
f52f5a0edb | ||
![]() |
b58942f69a | ||
![]() |
68e52d1645 | ||
![]() |
9fc7e6cd98 | ||
![]() |
87f346d88c | ||
![]() |
e423ff2639 | ||
![]() |
fa6905ef00 | ||
![]() |
8684bc0158 | ||
![]() |
a96eb450de | ||
![]() |
d079420d46 | ||
![]() |
124ff83206 | ||
![]() |
2c9e67ad1d | ||
![]() |
75a554e215 | ||
![]() |
849d41ee56 | ||
![]() |
e18ec5f2b2 | ||
![]() |
50e4daeaf2 | ||
![]() |
f7f07f2cb5 | ||
![]() |
e2161ec934 | ||
![]() |
1e4d2ba90d | ||
![]() |
caf794b01d | ||
![]() |
e81122739b | ||
![]() |
a0aa363203 | ||
![]() |
bad0816115 | ||
![]() |
53a3f3d452 | ||
![]() |
ff94172b3c | ||
![]() |
af71ca6a25 | ||
![]() |
0e7989111f | ||
![]() |
db8955d90d | ||
![]() |
5a5ce1101b | ||
![]() |
8ee3742216 | ||
![]() |
194eb184f3 | ||
![]() |
98f59ace3a | ||
![]() |
c1fb6d9776 | ||
![]() |
670566b7eb | ||
![]() |
fc7d2c2f52 | ||
![]() |
0fea84ed7a | ||
![]() |
5e7f8e3976 | ||
![]() |
fe4c5f5899 | ||
![]() |
d47b7b9242 | ||
![]() |
09b6661e35 | ||
![]() |
57b1667b69 | ||
![]() |
5e9380b550 | ||
![]() |
fc8a0480fb | ||
![]() |
317327d097 | ||
![]() |
ecd0ac2521 | ||
![]() |
f9eaf67db2 | ||
![]() |
99e0c8d5e3 | ||
![]() |
7a951d86d8 | ||
![]() |
d9dfaec84c | ||
![]() |
80719ae368 | ||
![]() |
9407809356 | ||
![]() |
f7e958e7a1 | ||
![]() |
5d75f8636a | ||
![]() |
22aaeb3ff5 | ||
![]() |
8c29ce95e2 | ||
![]() |
049fc66785 | ||
![]() |
029c55fd53 | ||
![]() |
7b433b9bd6 | ||
![]() |
5ec210990b | ||
![]() |
a7a0cacddb | ||
![]() |
e61ced93d6 | ||
![]() |
df122da1d2 | ||
![]() |
31d90939fe | ||
![]() |
67b0fdf73e | ||
![]() |
12fc8c22dd | ||
![]() |
4e00ac9300 | ||
![]() |
4ca2a30249 | ||
![]() |
08a31523b2 | ||
![]() |
4b29a61065 | ||
![]() |
06c83cb44c | ||
![]() |
75a5fc0ddc | ||
![]() |
946d165aa0 | ||
![]() |
224d79be05 | ||
![]() |
435ed587a5 | ||
![]() |
81082ea001 | ||
![]() |
1d3229a729 | ||
![]() |
a9e60d3450 | ||
![]() |
f9ddee7d80 | ||
![]() |
ef0d3b73b0 | ||
![]() |
5368040e83 | ||
![]() |
b9b4147c2f | ||
![]() |
caae0ec5ca | ||
![]() |
2b47631f4d | ||
![]() |
298e8e8491 | ||
![]() |
6ad3728314 | ||
![]() |
772f9a806e | ||
![]() |
ae8d1f2178 | ||
![]() |
9bc6c46dc3 | ||
![]() |
b48e940f2d | ||
![]() |
dcd8f98e8c | ||
![]() |
60c8da7bbb | ||
![]() |
e500cfae75 | ||
![]() |
f01c0adee2 | ||
![]() |
021cae1a95 | ||
![]() |
f66cd8f983 | ||
![]() |
fe8049199a | ||
![]() |
cfb6dd9471 | ||
![]() |
7abda44fd6 | ||
![]() |
47ce240e70 | ||
![]() |
c23d7fd79c | ||
![]() |
555fed2d51 | ||
![]() |
7a2f68e14a | ||
![]() |
8fd08cb2bf | ||
![]() |
7a2b75c861 | ||
![]() |
00074f914f | ||
![]() |
ea352e05f0 | ||
![]() |
305956cbe3 | ||
![]() |
5399d27875 | ||
![]() |
29047c2481 | ||
![]() |
50a32e90d9 | ||
![]() |
f596fe8404 | ||
![]() |
4be7f89fd8 | ||
![]() |
8b30fdf7f1 | ||
![]() |
2cd4256ece | ||
![]() |
c6a65c4686 | ||
![]() |
1553b5f54b | ||
![]() |
2ddd2e0a60 | ||
![]() |
d924ede9cf | ||
![]() |
638e03856b | ||
![]() |
8afd77b32d | ||
![]() |
599fefb39b | ||
![]() |
ec7294d734 | ||
![]() |
c0d03db9e8 | ||
![]() |
0365f96678 | ||
![]() |
40d8b42478 | ||
![]() |
e5a32ac57d | ||
![]() |
390d5927a4 | ||
![]() |
b61a1b4edb | ||
![]() |
1d78578fc2 | ||
![]() |
0e3751407b | ||
![]() |
7feafbf0e7 | ||
![]() |
cfd9b16f8b | ||
![]() |
1bbc73ec50 | ||
![]() |
dc9ba583d5 | ||
![]() |
6b5ff83927 | ||
![]() |
a5ffa406a8 | ||
![]() |
6234afc119 | ||
![]() |
ef2ead262b | ||
![]() |
cd5265fad4 | ||
![]() |
0c4ef1cc94 | ||
![]() |
4fee6cd5d8 | ||
![]() |
da63420d67 | ||
![]() |
72c79c684b | ||
![]() |
a997ec0695 | ||
![]() |
e3be2a9531 | ||
![]() |
bd1790b692 | ||
![]() |
90763dee6a | ||
![]() |
16879a9190 | ||
![]() |
2afd23fcf7 | ||
![]() |
39fd0f9b34 | ||
![]() |
ee57a92daa | ||
![]() |
ab7cc878c3 | ||
![]() |
877ab57f0a | ||
![]() |
e2ac297102 | ||
![]() |
b86dceb3c6 | ||
![]() |
35c9dca955 | ||
![]() |
d54d7011d9 | ||
![]() |
392652ed8e | ||
![]() |
7557e1e9e5 | ||
![]() |
d924da2f25 | ||
![]() |
be4d1eabaa | ||
![]() |
db98e56e72 | ||
![]() |
e2a35ea844 | ||
![]() |
7966b2bde6 | ||
![]() |
9391b947c6 | ||
![]() |
e61e3bcf9b | ||
![]() |
89ca2cc631 | ||
![]() |
e2cae035f2 | ||
![]() |
d79a0e737a | ||
![]() |
c60ec7c170 | ||
![]() |
e42b19d324 | ||
![]() |
3be370aefa | ||
![]() |
b13917fbdc | ||
![]() |
9028761821 | ||
![]() |
f6b52b7c82 | ||
![]() |
13f07abb8a | ||
![]() |
8c960f0661 | ||
![]() |
77203a4363 | ||
![]() |
d3246b7c8b | ||
![]() |
c93edec55b | ||
![]() |
83d258c396 | ||
![]() |
81acb5bdab | ||
![]() |
0b79019e0f | ||
![]() |
be07298741 | ||
![]() |
2d779fdcd0 | ||
![]() |
35382faf28 | ||
![]() |
dba0b104ab | ||
![]() |
174cabdc74 | ||
![]() |
3c52a9faab | ||
![]() |
73561993a7 | ||
![]() |
6e875393d9 | ||
![]() |
bbd85a491b | ||
![]() |
a736be7ca8 | ||
![]() |
f1fe0b944f | ||
![]() |
c1e40dd109 | ||
![]() |
73823e3eb9 | ||
![]() |
bf833991bf | ||
![]() |
615357c977 | ||
![]() |
19f23127b9 | ||
![]() |
2ea6d9437f | ||
![]() |
833b6d67a6 | ||
![]() |
04c1adc5f0 | ||
![]() |
8e8f6a0284 | ||
![]() |
68bf1d123e | ||
![]() |
2c03689195 | ||
![]() |
45f41b1140 | ||
![]() |
8fa6f3f45c | ||
![]() |
78a842deb7 | ||
![]() |
3657ff4f79 | ||
![]() |
d4d12c3db0 | ||
![]() |
1b628ac213 | ||
![]() |
5fcc259efb | ||
![]() |
9545205f19 | ||
![]() |
cb6708c02e | ||
![]() |
4f403d4450 | ||
![]() |
f2d8f930fb | ||
![]() |
4a86340d50 | ||
![]() |
f8e9e9ba85 | ||
![]() |
934ed1af09 | ||
![]() |
0fc2a219a7 | ||
![]() |
aa5939c051 | ||
![]() |
9c5f06101e | ||
![]() |
2a7c0252a0 | ||
![]() |
414726ecd4 | ||
![]() |
fdc8a15595 | ||
![]() |
d012fd3a65 | ||
![]() |
a5ed824674 | ||
![]() |
d6705c682f | ||
![]() |
d68523a54e | ||
![]() |
6af82680f1 | ||
![]() |
a4aee761c2 | ||
![]() |
33b606d48f | ||
![]() |
cc44be7ef9 | ||
![]() |
e0eb972a54 | ||
![]() |
19c54a99cd | ||
![]() |
9d60ec22ee | ||
![]() |
cb4eeccfd3 | ||
![]() |
62e50caa99 | ||
![]() |
0192566d19 | ||
![]() |
8a8b70f2ab | ||
![]() |
af311001cf | ||
![]() |
040229418e | ||
![]() |
c259963542 | ||
![]() |
ca75f7fa0b | ||
![]() |
4b22a409e7 | ||
![]() |
55806db00f | ||
![]() |
83c757ebd4 | ||
![]() |
a19b02aa8c | ||
![]() |
cf1ba60a10 | ||
![]() |
2a4c9afdf2 | ||
![]() |
ab76933158 | ||
![]() |
d4d7bcab7a | ||
![]() |
a61cf6a68a | ||
![]() |
146a307b59 | ||
![]() |
875946b4e8 | ||
![]() |
81f2a460ed | ||
![]() |
6698a2ac13 | ||
![]() |
9161cc693d | ||
![]() |
be689e293d | ||
![]() |
53c22d5e29 | ||
![]() |
5c0c80829e | ||
![]() |
55aedfe901 | ||
![]() |
4b87d66131 | ||
![]() |
fc9497f067 | ||
![]() |
3186335f47 | ||
![]() |
31bfd8664a | ||
![]() |
ab1c9e27dc | ||
![]() |
c07cc913c8 | ||
![]() |
b8fb5493c5 | ||
![]() |
e8e5eb9c53 | ||
![]() |
a15034b03e | ||
![]() |
1435acdc28 | ||
![]() |
37f164f11c | ||
![]() |
7a2f071269 | ||
![]() |
9f19afc943 | ||
![]() |
6742bef289 | ||
![]() |
9ee0964d0e | ||
![]() |
33ecbfa95b | ||
![]() |
e977a1006c | ||
![]() |
559c13a003 | ||
![]() |
8f00345430 | ||
![]() |
f1ebab0c2d | ||
![]() |
98884d851a | ||
![]() |
78d0369e71 | ||
![]() |
7a56af8ffa | ||
![]() |
ed2bfbd27c | ||
![]() |
e0905255ba | ||
![]() |
de80db73f2 | ||
![]() |
90ed62add2 | ||
![]() |
d46e279933 | ||
![]() |
77e074efc2 | ||
![]() |
bd000d2d2c | ||
![]() |
5f1c88104d | ||
![]() |
a61df0832f | ||
![]() |
58361e58f8 | ||
![]() |
96b4361b31 | ||
![]() |
c773edceb9 | ||
![]() |
bcaef8ba54 | ||
![]() |
c7d0b0ccc5 | ||
![]() |
38792a0f02 | ||
![]() |
cda557e1b9 | ||
![]() |
8d09f7c7eb | ||
![]() |
4845a56435 | ||
![]() |
f164dcdd70 | ||
![]() |
cc3aad17e1 | ||
![]() |
6c8da6b070 | ||
![]() |
09df753ca9 | ||
![]() |
72ec9df1b1 | ||
![]() |
086d5272ac | ||
![]() |
2555de7712 | ||
![]() |
645e540c66 | ||
![]() |
d16e1a4451 | ||
![]() |
7c5dd2bd78 | ||
![]() |
2f0121f0e4 | ||
![]() |
7e310a8de7 | ||
![]() |
c460c2bf6b | ||
![]() |
760ab688b2 | ||
![]() |
ff78c72d54 | ||
![]() |
2400de337b | ||
![]() |
85d41085de | ||
![]() |
48d45d026e | ||
![]() |
183c57643b | ||
![]() |
e7cea889ab | ||
![]() |
61c799447b | ||
![]() |
ad25daf206 | ||
![]() |
4143bba89f | ||
![]() |
8eb7d65b74 | ||
![]() |
51798553e1 | ||
![]() |
323928ff86 | ||
![]() |
fe3aeabd0a | ||
![]() |
ec7a55dba6 | ||
![]() |
b4fe708d70 | ||
![]() |
c3469bd545 | ||
![]() |
92018b761d | ||
![]() |
bb45ff0515 | ||
![]() |
df1405e998 | ||
![]() |
62ec018bb2 | ||
![]() |
14b7c4bf23 | ||
![]() |
5c774a3ce2 | ||
![]() |
246480daef | ||
![]() |
91c6f682e7 | ||
![]() |
6784a1c027 | ||
![]() |
a417a6469c | ||
![]() |
2821bdd52b | ||
![]() |
d37300be39 | ||
![]() |
15b83072bb | ||
![]() |
6d2e2259db | ||
![]() |
5b70614212 | ||
![]() |
4363da534d | ||
![]() |
685f7a4f00 | ||
![]() |
6e26f25418 | ||
![]() |
d8051ce9a2 | ||
![]() |
00e7d8a390 | ||
![]() |
213ec028b8 | ||
![]() |
61951e76ac | ||
![]() |
47cf763b3f | ||
![]() |
04aac03d6e | ||
![]() |
3faef2c9c9 | ||
![]() |
eda17d1671 | ||
![]() |
fbd6ead6b7 | ||
![]() |
bf2950abdd | ||
![]() |
3eacc325a3 | ||
![]() |
15decd9cd9 | ||
![]() |
c0142adba9 | ||
![]() |
ec01333c83 | ||
![]() |
8110384ea2 | ||
![]() |
0c2c0d7b8b | ||
![]() |
ae38bb0927 | ||
![]() |
003e076b00 | ||
![]() |
2c2a0eb7d9 | ||
![]() |
e7f5b17184 | ||
![]() |
69e12b1067 | ||
![]() |
fef446687a | ||
![]() |
3affce8f98 | ||
![]() |
0b91468edc | ||
![]() |
127364e8a4 | ||
![]() |
a144f65dcf | ||
![]() |
96ec3a869b | ||
![]() |
52426bc1a4 | ||
![]() |
cbab995c6d | ||
![]() |
d27fa43120 | ||
![]() |
45a9da5b30 | ||
![]() |
aed22aebb2 | ||
![]() |
369d213da5 | ||
![]() |
e92775eec2 | ||
![]() |
2c0f48f4e5 | ||
![]() |
a5f1c430a3 | ||
![]() |
e4a91b5397 | ||
![]() |
cbcd45cd70 | ||
![]() |
0968912954 | ||
![]() |
3bfe22c0f7 | ||
![]() |
39ab600887 | ||
![]() |
49ea532cdc | ||
![]() |
247e9bfbde | ||
![]() |
5944568565 | ||
![]() |
1c87707a76 | ||
![]() |
6cf64d794f | ||
![]() |
6741ba0783 | ||
![]() |
f1e7bc3363 | ||
![]() |
2dbe2d044e | ||
![]() |
ea3630e018 | ||
![]() |
b4188db671 | ||
![]() |
dc642be1f5 | ||
![]() |
6cdc7d3966 | ||
![]() |
281d558111 | ||
![]() |
fa89713f19 | ||
![]() |
c6391d958d | ||
![]() |
71aa4033a4 | ||
![]() |
57dcd961d5 | ||
![]() |
83878d6b05 | ||
![]() |
9055fc72d2 | ||
![]() |
fb4ab56b47 | ||
![]() |
51e195ed17 | ||
![]() |
9947c574d2 | ||
![]() |
693abdacf6 | ||
![]() |
dd3b42fb53 | ||
![]() |
8e72d7d0d6 | ||
![]() |
a6317212d9 | ||
![]() |
347adf688a | ||
![]() |
3ee67927a5 | ||
![]() |
7ecd36efcf | ||
![]() |
714ce8a8a3 | ||
![]() |
7b18a2f46f | ||
![]() |
19103a64e5 | ||
![]() |
a13f1d27e2 | ||
![]() |
9a6e4b1e85 | ||
![]() |
67b78fc230 | ||
![]() |
5e79187176 | ||
![]() |
491a82cd67 | ||
![]() |
87c2ec678f | ||
![]() |
70135ab3ea | ||
![]() |
ad18d84f14 | ||
![]() |
77e47e3fa4 | ||
![]() |
04432385b3 | ||
![]() |
cbdc24d869 | ||
![]() |
2b3e471f96 | ||
![]() |
488d54493a | ||
![]() |
a418f63a44 | ||
![]() |
0e285151f3 | ||
![]() |
e4ad7768d5 | ||
![]() |
f800fab1a3 | ||
![]() |
dc61aeeeb8 | ||
![]() |
4b1d0fd011 | ||
![]() |
2b7ba594fc | ||
![]() |
d87b14af13 | ||
![]() |
83623d23cf | ||
![]() |
07fba53b73 | ||
![]() |
823b276ef5 | ||
![]() |
0e72d78731 | ||
![]() |
768609e357 | ||
![]() |
526a157421 | ||
![]() |
62154309fb | ||
![]() |
ad74e6c475 | ||
![]() |
82d28242cc | ||
![]() |
60fddf837d | ||
![]() |
ee25df0c28 | ||
![]() |
478af40359 | ||
![]() |
6bf06a9629 | ||
![]() |
3c15bd9e29 | ||
![]() |
71796e3dbf | ||
![]() |
280f1ee2df | ||
![]() |
0dc72d8301 | ||
![]() |
c43b67ea90 | ||
![]() |
244421b69e | ||
![]() |
1adedff61a | ||
![]() |
6abd10f9e2 | ||
![]() |
a85096ea11 | ||
![]() |
f67157f0ad | ||
![]() |
5efe9bd91d | ||
![]() |
dc7a7267c9 | ||
![]() |
17188b1e38 | ||
![]() |
e9c3bebfff | ||
![]() |
03fc22d3da | ||
![]() |
5d52c49c31 | ||
![]() |
d53da4cfb5 | ||
![]() |
76204f5f47 | ||
![]() |
b68adbd9a8 | ||
![]() |
951b168f3a | ||
![]() |
53d89af459 | ||
![]() |
4f844ea837 | ||
![]() |
59a36e77bd | ||
![]() |
61d8d4473c | ||
![]() |
508a371175 | ||
![]() |
b010fd88e8 | ||
![]() |
66a93b81c6 | ||
![]() |
274e1139fa | ||
![]() |
6122e33f4f | ||
![]() |
b365ce3195 | ||
![]() |
0a3233d289 | ||
![]() |
15aea9e9ea | ||
![]() |
5b03a84be8 | ||
![]() |
80cdf0c014 | ||
![]() |
d41d56e670 | ||
![]() |
7e4e1f1a0d | ||
![]() |
463b06f508 | ||
![]() |
d3377d56b4 | ||
![]() |
c7611fbc05 | ||
![]() |
67ee218f69 | ||
![]() |
d1d394eec7 | ||
![]() |
98bc68c2bf | ||
![]() |
116c0680a2 | ||
![]() |
1b8a25ec88 | ||
![]() |
d5def01a9d | ||
![]() |
ee07b72415 | ||
![]() |
f7547db272 | ||
![]() |
5d271687ec | ||
![]() |
d05543db30 | ||
![]() |
b33f73ac9a | ||
![]() |
31e5d785fe | ||
![]() |
8096fd20a7 | ||
![]() |
addd45ae8e | ||
![]() |
d6895537ac | ||
![]() |
4ac40fee5e | ||
![]() |
05b8d0b45c | ||
![]() |
446f539803 | ||
![]() |
6ef2e46aa3 | ||
![]() |
e4c5c4a901 | ||
![]() |
be231f3a91 | ||
![]() |
39f36c9184 | ||
![]() |
2e4076bb75 | ||
![]() |
d71ac2e894 | ||
![]() |
7e7da26f29 | ||
![]() |
4375a151dd | ||
![]() |
0f02fb5d05 | ||
![]() |
05edc1f9c4 | ||
![]() |
50784f2600 | ||
![]() |
2c0f0d9a20 | ||
![]() |
21c0fed833 | ||
![]() |
d081ff745d | ||
![]() |
e3f41fea9c | ||
![]() |
dea214bd2e | ||
![]() |
a13e2468db | ||
![]() |
3fb660d816 | ||
![]() |
9f60dc3dd6 | ||
![]() |
02f855ff09 | ||
![]() |
c39622ed1e | ||
![]() |
07bb095e73 | ||
![]() |
e409029bb3 | ||
![]() |
a4d93f23d6 | ||
![]() |
6201176852 | ||
![]() |
7d958cc20e | ||
![]() |
e0e5670d0e | ||
![]() |
68e855e3d2 | ||
![]() |
bf91352fce | ||
![]() |
a840504b3d | ||
![]() |
e120422b05 | ||
![]() |
c60e99288f | ||
![]() |
d9006d6e37 | ||
![]() |
9121b83bd1 | ||
![]() |
fad3db1556 | ||
![]() |
9d4862b5c8 | ||
![]() |
2a07999db6 | ||
![]() |
ce2ffcafd3 | ||
![]() |
3093794f72 | ||
![]() |
abd05f5105 | ||
![]() |
dcb2ad9d2d | ||
![]() |
44d2e100bd | ||
![]() |
8d9c114acd | ||
![]() |
bce0950498 | ||
![]() |
dcac64fb9a | ||
![]() |
03ad87d651 | ||
![]() |
fadd7158ed |
902 changed files with 139734 additions and 49165 deletions
12
.babelrc
12
.babelrc
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"presets": [
|
||||
["env", {
|
||||
"targets": {
|
||||
"chrome": 40,
|
||||
"firefox": 35,
|
||||
"edge": 14
|
||||
},
|
||||
"modules": false
|
||||
}]
|
||||
]
|
||||
}
|
41
.devcontainer/devcontainer.json
Normal file
41
.devcontainer/devcontainer.json
Normal file
|
@ -0,0 +1,41 @@
|
|||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
||||
// README at: https://github.com/devcontainers/templates/tree/main/src/javascript-node
|
||||
{
|
||||
"name": "CyberChef",
|
||||
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
||||
"image": "mcr.microsoft.com/devcontainers/javascript-node:1-18-bookworm",
|
||||
|
||||
// Features to add to the dev container. More info: https://containers.dev/features.
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/github-cli": "latest"
|
||||
},
|
||||
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
"forwardPorts": [8080],
|
||||
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
"postCreateCommand": {
|
||||
"npm": "bash -c \"sudo chown node node_modules && npm install\""
|
||||
},
|
||||
|
||||
"containerEnv": {
|
||||
"DISPLAY": ":99"
|
||||
},
|
||||
|
||||
"mounts": [
|
||||
"source=${localWorkspaceFolderBasename}-node_modules,target=${containerWorkspaceFolder}/node_modules,type=volume"
|
||||
],
|
||||
|
||||
// Configure tool-specific properties.
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"GitHub.vscode-github-actions"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
||||
// "remoteUser": "root"
|
||||
}
|
2
.dockerignore
Normal file
2
.dockerignore
Normal file
|
@ -0,0 +1,2 @@
|
|||
node_modules
|
||||
build
|
14
.editorconfig
Normal file
14
.editorconfig
Normal file
|
@ -0,0 +1,14 @@
|
|||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[{package.json,.travis.yml,nightwatch.json}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
1
.eslintignore
Normal file
1
.eslintignore
Normal file
|
@ -0,0 +1 @@
|
|||
src/core/vendor/**
|
|
@ -1,10 +1,12 @@
|
|||
{
|
||||
"parser": "@babel/eslint-parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 6,
|
||||
"ecmaVersion": 2022,
|
||||
"ecmaFeatures": {
|
||||
"impliedStrict": true
|
||||
},
|
||||
"sourceType": "module"
|
||||
"sourceType": "module",
|
||||
"allowImportExportEverywhere": true
|
||||
},
|
||||
"env": {
|
||||
"browser": true,
|
||||
|
@ -28,37 +30,41 @@
|
|||
// modify rules from base configurations
|
||||
"no-unused-vars": ["error", {
|
||||
"args": "none",
|
||||
"vars": "local",
|
||||
// Allow vars that start with a capital letter to be unused.
|
||||
// This is mainly for exported module names which are useful to indicate
|
||||
// the name of the module and may be used to refer to itself in future.
|
||||
"varsIgnorePattern": "^[A-Z]"
|
||||
"vars": "all"
|
||||
}],
|
||||
"no-empty": ["error", {
|
||||
"allowEmptyCatch": true
|
||||
}],
|
||||
|
||||
// disable rules from base configurations
|
||||
"no-console": "off",
|
||||
"no-control-regex": "off",
|
||||
"require-atomic-updates": "off",
|
||||
"no-async-promise-executor": "off",
|
||||
|
||||
// stylistic conventions
|
||||
"brace-style": ["error", "1tbs"],
|
||||
"space-before-blocks": ["error", "always"],
|
||||
"block-spacing": "error",
|
||||
"array-bracket-spacing": "error",
|
||||
"comma-spacing": "error",
|
||||
"spaced-comment": ["error", "always", { "exceptions": ["/"] } ],
|
||||
"comma-style": "error",
|
||||
"computed-property-spacing": "error",
|
||||
"no-trailing-spaces": "warn",
|
||||
"eol-last": "error",
|
||||
"func-call-spacing": "error",
|
||||
"key-spacing": ["warn", {
|
||||
"mode": "minimum"
|
||||
}],
|
||||
"indent": ["error", 4, {
|
||||
"ignoreComments": true,
|
||||
"ArrayExpression": "first",
|
||||
"SwitchCase": 1
|
||||
}],
|
||||
"linebreak-style": ["error", "unix"],
|
||||
"quotes": ["error", "double", {
|
||||
"avoidEscape": true
|
||||
"avoidEscape": true,
|
||||
"allowTemplateLiterals": true
|
||||
}],
|
||||
"camelcase": ["error", {
|
||||
"properties": "always"
|
||||
|
@ -84,14 +90,27 @@
|
|||
}],
|
||||
"no-whitespace-before-property": "error",
|
||||
"operator-linebreak": ["error", "after"],
|
||||
"space-in-parens": "error"
|
||||
"space-in-parens": "error",
|
||||
"no-var": "error",
|
||||
"prefer-const": "error"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": "tests/**/*",
|
||||
"rules": {
|
||||
"no-unused-expressions": "off",
|
||||
"no-console": "off"
|
||||
}
|
||||
}
|
||||
],
|
||||
"globals": {
|
||||
"$": false,
|
||||
"jQuery": false,
|
||||
"moment": false,
|
||||
"log": false,
|
||||
"app": false,
|
||||
|
||||
"COMPILE_TIME": false,
|
||||
"COMPILE_MSG": false
|
||||
"COMPILE_MSG": false,
|
||||
"PKG_VERSION": false
|
||||
}
|
||||
}
|
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
* text=auto eol=lf
|
12
.github/CONTRIBUTING.md
vendored
12
.github/CONTRIBUTING.md
vendored
|
@ -6,7 +6,7 @@ There are lots of opportunities to contribute to CyberChef. If you want ideas, t
|
|||
|
||||
Before your contributions can be accepted, you must:
|
||||
|
||||
- Sign the [GCHQ Contributor Licence Agreement](https://github.com/gchq/Gaffer/wiki/GCHQ-OSS-Contributor-License-Agreement-V1.0)
|
||||
- Sign the [GCHQ Contributor Licence Agreement](https://cla-assistant.io/gchq/CyberChef)
|
||||
- Push your changes to your fork.
|
||||
- Submit a pull request.
|
||||
|
||||
|
@ -22,15 +22,15 @@ Before your contributions can be accepted, you must:
|
|||
* Line endings: UNIX style (\n)
|
||||
|
||||
|
||||
## Design Principals
|
||||
## Design Principles
|
||||
|
||||
1. If at all possible, all operations and features should be client-side and not rely on connections to an external server. This increases the utility of CyberChef on closed networks and in virtual machines that are not connected to the Internet. Calls to external APIs may be accepted if there is no other option, but not for critical components.
|
||||
2. Latency should be kept to a minimum to enhance the user experience. This means that all operation code should sit on the client, rather than being loaded dynamically from a server.
|
||||
3. Use Vanilla JS if at all possible to reduce the number of libraries required and relied upon. Frameworks like jQuery, although included, should not be used unless absolutely necessary.
|
||||
4. Minimise the use of large libraries, especially for niche operations that won't be used very often - these will be downloaded by everyone using the app, whether they use that operation or not (due to principal 2).
|
||||
2. Latency should be kept to a minimum to enhance the user experience. This means that operation code should sit on the client and be executed there. However, as a trade-off between latency and bandwidth, operation code with large dependencies can be loaded in discrete modules in order to reduce the size of the initial download. The downloading of additional modules must remain entirely transparent so that the user is not inconvenienced.
|
||||
3. Large libraries should be kept in separate modules so that they are not downloaded by everyone who uses the app, just those who specifically require the relevant operations.
|
||||
4. Use Vanilla JS if at all possible to reduce the number of libraries required and relied upon. Frameworks like jQuery, although included, should not be used unless absolutely necessary.
|
||||
|
||||
|
||||
With these principals in mind, any changes or additions to CyberChef should keep it:
|
||||
With these principles in mind, any changes or additions to CyberChef should keep it:
|
||||
|
||||
- Standalone
|
||||
- Efficient
|
||||
|
|
26
.github/ISSUE_TEMPLATE.md
vendored
26
.github/ISSUE_TEMPLATE.md
vendored
|
@ -1,25 +1 @@
|
|||
<!-- Prefix the title above with one of the following: -->
|
||||
<!-- Bug report: -->
|
||||
<!-- Operation request: -->
|
||||
<!-- Feature request: -->
|
||||
<!-- Misc: -->
|
||||
|
||||
### Summary
|
||||
<!-- If you're describing a bug, tell us what's wrong -->
|
||||
<!-- If you're suggesting a change/improvement, tell us what it is and how it should work -->
|
||||
|
||||
### Example
|
||||
<!-- If describing a bug, tell us what happens instead of the expected behavior -->
|
||||
<!-- Include a link that triggers the bug if possible -->
|
||||
<!-- If you are requesting a new operation, include example input and output -->
|
||||
|
||||
### Possible solutions
|
||||
<!-- Not obligatory, but suggest a fix/reason for the bug, or ideas for how to -->
|
||||
<!-- implement the addition or change, including links to relevant resources -->
|
||||
|
||||
### Environment
|
||||
<!-- Include any relevant details about the environment you experienced the bug in -->
|
||||
<!-- This information is displayed in the About/Support pane -->
|
||||
* CyberChef compile time:
|
||||
* User-Agent:
|
||||
* [Link to reproduce]()
|
||||
<!-- Prefix the title above with 'Misc:' -->
|
||||
|
|
33
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
33
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: 'Bug report: <Insert title here>'
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behaviour or a link to the recipe / input used to cause the bug:
|
||||
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behaviour**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (if relevant, please complete the following information):**
|
||||
- OS: [e.g. Windows]
|
||||
- Browser: [e.g. chrome 72, firefox 60]
|
||||
- CyberChef version: [e.g. 9.7.14]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
20
.github/ISSUE_TEMPLATE/feature-request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature-request.md
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for the project
|
||||
title: 'Feature request: <Insert title here>'
|
||||
labels: feature
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. E.g. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
14
.github/ISSUE_TEMPLATE/operation-request.md
vendored
Normal file
14
.github/ISSUE_TEMPLATE/operation-request.md
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
name: Operation request
|
||||
about: Suggest a new operation
|
||||
title: 'Operation request: <Insert title here>'
|
||||
labels: operation
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
### Example Input
|
||||
|
||||
### Example Output
|
40
.github/workflows/codeql.yml
vendored
Normal file
40
.github/workflows/codeql.yml
vendored
Normal file
|
@ -0,0 +1,40 @@
|
|||
name: "CodeQL Analysis"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ master ]
|
||||
types: [synchronize, opened, reopened]
|
||||
schedule:
|
||||
- cron: '22 17 * * 5'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'javascript' ]
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
57
.github/workflows/master.yml
vendored
Normal file
57
.github/workflows/master.yml
vendored
Normal file
|
@ -0,0 +1,57 @@
|
|||
name: "Master Build, Test & Deploy"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
main:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set node version
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '18.x'
|
||||
|
||||
- name: Install
|
||||
run: |
|
||||
npm install
|
||||
npm run setheapsize
|
||||
|
||||
- name: Lint
|
||||
run: npx grunt lint
|
||||
|
||||
- name: Unit Tests
|
||||
run: |
|
||||
npm test
|
||||
npm run testnodeconsumer
|
||||
|
||||
- name: Production Build
|
||||
if: success()
|
||||
run: npx grunt prod --msg="Version 10 is here! Read about the new features <a href='https://github.com/gchq/CyberChef/wiki/Character-encoding,-EOL-separators,-and-editor-features'>here</a>"
|
||||
|
||||
- name: Generate sitemap
|
||||
run: npx grunt exec:sitemap
|
||||
|
||||
- name: UI Tests
|
||||
if: success()
|
||||
run: |
|
||||
sudo apt-get install xvfb
|
||||
xvfb-run --server-args="-screen 0 1200x800x24" npx grunt testui
|
||||
|
||||
- name: Prepare for GitHub Pages
|
||||
if: success()
|
||||
run: npx grunt copy:ghPages
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
if: success() && github.ref == 'refs/heads/master'
|
||||
uses: crazy-max/ghaction-github-pages@v3
|
||||
with:
|
||||
target_branch: gh-pages
|
||||
build_dir: ./build/prod
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
54
.github/workflows/pull_requests.yml
vendored
Normal file
54
.github/workflows/pull_requests.yml
vendored
Normal file
|
@ -0,0 +1,54 @@
|
|||
name: "Pull Requests"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
types: [synchronize, opened, reopened]
|
||||
|
||||
jobs:
|
||||
main:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set node version
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '18.x'
|
||||
|
||||
- name: Install
|
||||
run: |
|
||||
npm install
|
||||
npm run setheapsize
|
||||
|
||||
- name: Lint
|
||||
run: npx grunt lint
|
||||
|
||||
- name: Unit Tests
|
||||
run: |
|
||||
npm test
|
||||
npm run testnodeconsumer
|
||||
|
||||
- name: Production Build
|
||||
if: success()
|
||||
run: npx grunt prod
|
||||
|
||||
- name: Production Image Build
|
||||
if: success()
|
||||
id: build-image
|
||||
uses: redhat-actions/buildah-build@v2
|
||||
with:
|
||||
# Not being uploaded to any registry, use a simple name to allow Buildah to build correctly.
|
||||
image: cyberchef
|
||||
containerfiles: ./Dockerfile
|
||||
platforms: linux/amd64
|
||||
oci: true
|
||||
# Webpack seems to use a lot of open files, increase the max open file limit to accomodate.
|
||||
extra-args: |
|
||||
--ulimit nofile=10000
|
||||
|
||||
- name: UI Tests
|
||||
if: success()
|
||||
run: |
|
||||
sudo apt-get install xvfb
|
||||
xvfb-run --server-args="-screen 0 1200x800x24" npx grunt testui
|
93
.github/workflows/releases.yml
vendored
Normal file
93
.github/workflows/releases.yml
vendored
Normal file
|
@ -0,0 +1,93 @@
|
|||
name: "Releases"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
REGISTRY_USER: ${{ github.actor }}
|
||||
REGISTRY_PASSWORD: ${{ github.token }}
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
main:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set node version
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '18.x'
|
||||
|
||||
- name: Install
|
||||
run: |
|
||||
npm ci
|
||||
npm run setheapsize
|
||||
|
||||
- name: Lint
|
||||
run: npx grunt lint
|
||||
|
||||
- name: Unit Tests
|
||||
run: |
|
||||
npm test
|
||||
npm run testnodeconsumer
|
||||
|
||||
- name: Production Build
|
||||
run: npx grunt prod
|
||||
|
||||
- name: UI Tests
|
||||
run: |
|
||||
sudo apt-get install xvfb
|
||||
xvfb-run --server-args="-screen 0 1200x800x24" npx grunt testui
|
||||
|
||||
- name: Image Metadata
|
||||
id: image-metadata
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=semver,pattern={{major}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{version}}
|
||||
|
||||
- name: Production Image Build
|
||||
id: build-image
|
||||
uses: redhat-actions/buildah-build@v2
|
||||
with:
|
||||
tags: ${{ steps.image-metadata.outputs.tags }}
|
||||
labels: ${{ steps.image-metadata.outputs.labels }}
|
||||
containerfiles: ./Dockerfile
|
||||
platforms: linux/amd64
|
||||
oci: true
|
||||
# Webpack seems to use a lot of open files, increase the max open file limit to accomodate.
|
||||
extra-args: |
|
||||
--ulimit nofile=10000
|
||||
|
||||
|
||||
- name: Upload Release Assets
|
||||
id: upload-release-assets
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: build/prod/*.zip
|
||||
tag: ${{ github.ref }}
|
||||
overwrite: true
|
||||
file_glob: true
|
||||
body: "See the [CHANGELOG](https://github.com/gchq/CyberChef/blob/master/CHANGELOG.md) and [commit messages](https://github.com/gchq/CyberChef/commits/master) for details."
|
||||
|
||||
- name: Publish to NPM
|
||||
uses: JS-DevTools/npm-publish@v1
|
||||
with:
|
||||
token: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
- name: Publish to GHCR
|
||||
uses: redhat-actions/push-to-registry@v2
|
||||
with:
|
||||
tags: ${{ steps.build-image.outputs.tags }}
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ env.REGISTRY_USER }}
|
||||
password: ${{ env.REGISTRY_PASSWORD }}
|
14
.gitignore
vendored
14
.gitignore
vendored
|
@ -1,7 +1,15 @@
|
|||
node_modules
|
||||
npm-debug.log
|
||||
travis.log
|
||||
build
|
||||
docs/*
|
||||
!docs/*.conf.json
|
||||
!docs/*.ico
|
||||
.vscode
|
||||
.idea
|
||||
.*.swp
|
||||
src/core/config/modules/*
|
||||
src/core/config/OperationConfig.json
|
||||
src/core/operations/index.mjs
|
||||
src/node/config/OperationConfig.json
|
||||
src/node/index.mjs
|
||||
**/*.DS_Store
|
||||
tests/browser/output/*
|
||||
.node-version
|
||||
|
|
7
.npmignore
Executable file
7
.npmignore
Executable file
|
@ -0,0 +1,7 @@
|
|||
node_modules
|
||||
npm-debug.log
|
||||
travis.log
|
||||
build/*
|
||||
!build/node
|
||||
.vscode
|
||||
.github
|
1
.nvmrc
Normal file
1
.nvmrc
Normal file
|
@ -0,0 +1 @@
|
|||
18
|
32
.travis.yml
32
.travis.yml
|
@ -1,32 +0,0 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- node
|
||||
install: npm install
|
||||
before_script:
|
||||
- npm install -g grunt
|
||||
script:
|
||||
- grunt lint
|
||||
- grunt test
|
||||
- grunt docs
|
||||
- grunt node
|
||||
- grunt prod
|
||||
before_deploy:
|
||||
- grunt copy:ghPages
|
||||
deploy:
|
||||
- provider: pages
|
||||
skip_cleanup: true
|
||||
github_token: $GITHUB_TOKEN
|
||||
local_dir: build/prod/
|
||||
target_branch: gh-pages
|
||||
on:
|
||||
branch: master
|
||||
- provider: releases
|
||||
skip_cleaup: true
|
||||
api_key:
|
||||
secure: "HV1WSKv4l/0Y2bKKs1iBJocBcmLj08PCRUeEM/jTwA4jqJ8EiLHWiXtER/D5sEg2iibRVKd2OQjfrmS6bo4AiwdeVgAKmv0FtS2Jw+391N8Nd5AkEANHa5Om/IpHLTL2YRAjpJTsDpY72bMUTJIwjQA3TFJkgrpOw6KYfohOcgbxLpZ4XuNJRU3VL4Hsxdv5V9aOVmfFOmMOVPQlakXy7NgtW5POp1f2WJwgcZxylkR1CjwaqMyXmSoVl46pyH3tr5+dptsQoKSGdi6sIHGA60oDotFPcm+0ifa47wZw+vapuuDi4tdNxhrHGaDMG8xiE0WFDHwQUDlk2/+W7j9SEX0H3Em7us371JXRp56EDwEcDa34VpVkC6i8HGcHK55hnxVbMZXGf3qhOFD8wY7qMbjMRvIpucrMHBi86OfkDfv0vDj2LyvIl5APj/AX50BrE0tfH1MZbH26Jkx4NdlkcxQ14GumarmUqfmVvbX/fsoA6oUuAAE9ZgRRi3KHO4wci6KUcRfdm+XOeUkaBFsL86G3EEYIvrtBTuaypdz+Cx7nd1iPZyWMx5Y1gXnVzha4nBdV4+7l9JIsFggD8QVpw2uHXQiS1KXFjOeqA3DBD8tjMB7q26Fl2fD3jkOo4BTbQ2NrRIZUu/iL+fOmMPsyMt2qulB0yaSBCfkbEq8xrUA="
|
||||
file:
|
||||
- build/prod/cyberchef.htm
|
||||
- build/node/CyberChef.js
|
||||
on:
|
||||
repo: gchq/CyberChef
|
||||
tags: true
|
680
CHANGELOG.md
Normal file
680
CHANGELOG.md
Normal file
|
@ -0,0 +1,680 @@
|
|||
# Changelog
|
||||
|
||||
## Versioning
|
||||
|
||||
CyberChef uses the [semver](https://semver.org/) system to manage versioning: `<MAJOR>.<MINOR>.<PATCH>`.
|
||||
|
||||
- MAJOR version changes represent a significant change to the fundamental architecture of CyberChef and may (but don't always) make breaking changes that are not backwards compatible.
|
||||
- MINOR version changes usually mean the addition of new operations or reasonably significant new features.
|
||||
- PATCH versions are used for bug fixes and any other small tweaks that modify or improve existing capabilities.
|
||||
|
||||
All major and minor version changes will be documented in this file. Details of patch-level version changes can be found in [commit messages](https://github.com/gchq/CyberChef/commits/master).
|
||||
|
||||
|
||||
## Details
|
||||
|
||||
### [10.8.0] - 2024-02-13
|
||||
- Add official Docker images [@AshCorr] | [#1699]
|
||||
|
||||
### [10.7.0] - 2024-02-09
|
||||
- Added 'File Tree' operation [@sw5678] | [#1667]
|
||||
- Added 'RISON' operation [@sg5506844] | [#1555]
|
||||
- Added 'MurmurHash3' operation [@AliceGrey] | [#1694]
|
||||
|
||||
### [10.6.0] - 2024-02-03
|
||||
- Updated 'Forensics Wiki' URLs to new domain [@a3957273] | [#1703]
|
||||
- Added 'LZNT1 Decompress' operation [@0xThiebaut] | [#1675]
|
||||
- Updated 'Regex Expression' UUID matcher [@cnotin] | [#1678]
|
||||
- Removed duplicate 'hover' message within baking info [@KevinSJ] | [#1541]
|
||||
|
||||
### [10.5.0] - 2023-07-14
|
||||
- Added GOST Encrypt, Decrypt, Sign, Verify, Key Wrap, and Key Unwrap operations [@n1474335] | [#592]
|
||||
|
||||
### [10.4.0] - 2023-03-24
|
||||
- Added 'Generate De Bruijn Sequence' operation [@gchq77703] | [#493]
|
||||
|
||||
### [10.3.0] - 2023-03-24
|
||||
- Added 'Argon2' and 'Argon2 compare' operations [@Xenonym] | [#661]
|
||||
|
||||
### [10.2.0] - 2023-03-23
|
||||
- Added 'Derive HKDF key' operation [@mikecat] | [#1528]
|
||||
|
||||
### [10.1.0] - 2023-03-23
|
||||
- Added 'Levenshtein Distance' operation [@mikecat] | [#1498]
|
||||
- Added 'Swap case' operation [@mikecat] | [#1499]
|
||||
|
||||
## [10.0.0] - 2023-03-22
|
||||
- [Full details explained here](https://github.com/gchq/CyberChef/wiki/Character-encoding,-EOL-separators,-and-editor-features)
|
||||
- Status bars added to the Input and Output [@n1474335] | [#1405]
|
||||
- Character encoding selection added to the Input and Output [@n1474335] | [#1405]
|
||||
- End of line separator selection added to the Input and Output [@n1474335] | [#1405]
|
||||
- Non-printable characters are rendered as control character pictures [@n1474335] | [#1405]
|
||||
- Loaded files can now be edited in the Input [@n1474335] | [#1405]
|
||||
- Various editor features added such as multiple selections and bracket matching [@n1474335] | [#1405]
|
||||
- Contextual help added, activated by pressing F1 while hovering over features [@n1474335] | [#1405]
|
||||
- Many, many UI tests added for I/O features and operations [@n1474335] | [#1405]
|
||||
|
||||
<details>
|
||||
<summary>Click to expand v9 minor versions</summary>
|
||||
|
||||
### [9.55.0] - 2022-12-09
|
||||
- Added 'AMF Encode' and 'AMF Decode' operations [@n1474335] | [760eff4]
|
||||
|
||||
### [9.54.0] - 2022-11-25
|
||||
- Added 'Rabbit' operation [@mikecat] | [#1450]
|
||||
|
||||
### [9.53.0] - 2022-11-25
|
||||
- Added 'AES Key Wrap' and 'AES Key Unwrap' operations [@mikecat] | [#1456]
|
||||
|
||||
### [9.52.0] - 2022-11-25
|
||||
- Added 'ChaCha' operation [@joostrijneveld] | [#1466]
|
||||
|
||||
### [9.51.0] - 2022-11-25
|
||||
- Added 'CMAC' operation [@mikecat] | [#1457]
|
||||
|
||||
### [9.50.0] - 2022-11-25
|
||||
- Added 'Shuffle' operation [@mikecat] | [#1472]
|
||||
|
||||
### [9.49.0] - 2022-11-11
|
||||
- Added 'LZ4 Compress' and 'LZ4 Decompress' operations [@n1474335] | [31a7f83]
|
||||
|
||||
### [9.48.0] - 2022-10-14
|
||||
- Added 'LM Hash' and 'NT Hash' operations [@n1474335] [@brun0ne] | [#1427]
|
||||
|
||||
### [9.47.0] - 2022-10-14
|
||||
- Added 'LZMA Decompress' and 'LZMA Compress' operations [@mattnotmitt] | [#1421]
|
||||
|
||||
### [9.46.0] - 2022-07-08
|
||||
- Added 'Cetacean Cipher Encode' and 'Cetacean Cipher Decode' operations [@valdelaseras] | [#1308]
|
||||
|
||||
### [9.45.0] - 2022-07-08
|
||||
- Added 'ROT8000' operation [@thomasleplus] | [#1250]
|
||||
|
||||
### [9.44.0] - 2022-07-08
|
||||
- Added 'LZString Compress' and 'LZString Decompress' operations [@crespyl] | [#1266]
|
||||
|
||||
### [9.43.0] - 2022-07-08
|
||||
- Added 'ROT13 Brute Force' and 'ROT47 Brute Force' operations [@mikecat] | [#1264]
|
||||
|
||||
### [9.42.0] - 2022-07-08
|
||||
- Added 'LS47 Encrypt' and 'LS47 Decrypt' operations [@n1073645] | [#951]
|
||||
|
||||
### [9.41.0] - 2022-07-08
|
||||
- Added 'Caesar Box Cipher' operation [@n1073645] | [#1066]
|
||||
|
||||
### [9.40.0] - 2022-07-08
|
||||
- Added 'P-list Viewer' operation [@n1073645] | [#906]
|
||||
|
||||
### [9.39.0] - 2022-06-09
|
||||
- Added 'ELF Info' operation [@n1073645] | [#1364]
|
||||
|
||||
### [9.38.0] - 2022-05-30
|
||||
- Added 'Parse TCP' operation [@n1474335] | [a895d1d]
|
||||
|
||||
### [9.37.0] - 2022-03-29
|
||||
- 'SM4 Encrypt' and 'SM4 Decrypt' operations added [@swesven] | [#1189]
|
||||
- NoPadding options added for CBC and ECB modes in AES, DES and Triple DES Decrypt operations [@swesven] | [#1189]
|
||||
|
||||
### [9.36.0] - 2022-03-29
|
||||
- 'SIGABA' operation added [@hettysymes] | [#934]
|
||||
|
||||
### [9.35.0] - 2022-03-28
|
||||
- 'To Base45' and 'From Base45' operations added [@t-8ch] | [#1242]
|
||||
|
||||
### [9.34.0] - 2022-03-28
|
||||
- 'Get All Casings' operation added [@n1073645] | [#1065]
|
||||
|
||||
### [9.33.0] - 2022-03-25
|
||||
- Updated to support Node 17 [@n1474335] [@john19696] [@t-8ch] | [[#1326] [#1313] [#1244]
|
||||
- Improved CJS and ESM module support [@d98762625] | [#1037]
|
||||
|
||||
### [9.32.0] - 2021-08-18
|
||||
- 'Protobuf Encode' operation added and decode operation modified to allow decoding with full and partial schemas [@n1474335] | [dd18e52]
|
||||
|
||||
### [9.31.0] - 2021-08-10
|
||||
- 'HASSH Client Fingerprint' and 'HASSH Server Fingerprint' operations added [@n1474335] | [e9ca4dc]
|
||||
|
||||
### [9.30.0] - 2021-08-10
|
||||
- 'JA3S Fingerprint' operation added [@n1474335] | [289a417]
|
||||
|
||||
### [9.29.0] - 2021-07-28
|
||||
- 'JA3 Fingerprint' operation added [@n1474335] | [9a33498]
|
||||
|
||||
### [9.28.0] - 2021-03-26
|
||||
- 'CBOR Encode' and 'CBOR Decode' operations added [@Danh4] | [#999]
|
||||
|
||||
### [9.27.0] - 2021-02-12
|
||||
- 'Fuzzy Match' operation added [@n1474335] | [8ad18b]
|
||||
|
||||
### [9.26.0] - 2021-02-11
|
||||
- 'Get Time' operation added [@n1073645] [@n1474335] | [#1045]
|
||||
|
||||
### [9.25.0] - 2021-02-11
|
||||
- 'Extract ID3' operation added [@n1073645] [@n1474335] | [#1006]
|
||||
|
||||
### [9.24.0] - 2021-02-02
|
||||
- 'SM3' hashing function added along with more configuration options for other hashing operations [@n1073645] [@n1474335] | [#1022]
|
||||
|
||||
### [9.23.0] - 2021-02-01
|
||||
- Various RSA operations added to encrypt, decrypt, sign, verify and generate keys [@mattnotmitt] [@GCHQ77703] | [#652]
|
||||
|
||||
### [9.22.0] - 2021-02-01
|
||||
- 'Unicode Text Format' operation added [@mattnotmitt] | [#1083]
|
||||
|
||||
### [9.21.0] - 2020-06-12
|
||||
- Node API now exports `magic` operation [@d98762625] | [#1049]
|
||||
|
||||
### [9.20.0] - 2020-03-27
|
||||
- 'Parse ObjectID Timestamp' operation added [@dmfj] | [#987]
|
||||
|
||||
### [9.19.0] - 2020-03-24
|
||||
- Improvements to the 'Magic' operation, allowing it to recognise more data formats and provide more accurate results [@n1073645] [@n1474335] | [#966] [b765534b](https://github.com/gchq/CyberChef/commit/b765534b8b2a0454a5132a0a52d1d8844bcbdaaa)
|
||||
|
||||
### [9.18.0] - 2020-03-13
|
||||
- 'Convert to NATO alphabet' operation added [@MarvinJWendt] | [#674]
|
||||
|
||||
### [9.17.0] - 2020-03-13
|
||||
- 'Generate Image' operation added [@pointhi] | [#683]
|
||||
|
||||
### [9.16.0] - 2020-03-06
|
||||
- 'Colossus' operation added [@VirtualColossus] | [#917]
|
||||
|
||||
### [9.15.0] - 2020-03-05
|
||||
- 'CipherSaber2 Encrypt' and 'CipherSaber2 Decrypt' operations added [@n1073645] | [#952]
|
||||
|
||||
### [9.14.0] - 2020-03-05
|
||||
- 'Luhn Checksum' operation added [@n1073645] | [#965]
|
||||
|
||||
### [9.13.0] - 2020-02-13
|
||||
- 'Rail Fence Cipher Encode' and 'Rail Fence Cipher Decode' operations added [@Flavsditz] | [#948]
|
||||
|
||||
### [9.12.0] - 2019-12-20
|
||||
- 'Normalise Unicode' operation added [@matthieuxyz] | [#912]
|
||||
|
||||
### [9.11.0] - 2019-11-06
|
||||
- Implemented CFB, OFB, and CTR modes for Blowfish operations [@cbeuw] | [#653]
|
||||
|
||||
### [9.10.0] - 2019-11-06
|
||||
- 'Lorenz' operation added [@VirtualColossus] | [#528]
|
||||
|
||||
### [9.9.0] - 2019-11-01
|
||||
- Added support for 109 more character encodings [@n1474335]
|
||||
|
||||
### [9.8.0] - 2019-10-31
|
||||
- 'Avro to JSON' operation added [@jarrodconnolly] | [#865]
|
||||
|
||||
### [9.7.0] - 2019-09-13
|
||||
- 'Optical Character Recognition' operation added [@MShwed] [@n1474335] | [#632]
|
||||
|
||||
### [9.6.0] - 2019-09-04
|
||||
- 'Bacon Cipher Encode' and 'Bacon Cipher Decode' operations added [@kassi] | [#500]
|
||||
|
||||
### [9.5.0] - 2019-09-04
|
||||
- Various Steganography operations added: 'Extract LSB', 'Extract RGBA', 'Randomize Colour Palette', and 'View Bit Plane' [@Ge0rg3] | [#625]
|
||||
|
||||
### [9.4.0] - 2019-08-30
|
||||
- 'Render Markdown' operation added [@j433866] | [#627]
|
||||
|
||||
### [9.3.0] - 2019-08-30
|
||||
- 'Show on map' operation added [@j433866] | [#477]
|
||||
|
||||
### [9.2.0] - 2019-08-23
|
||||
- 'Parse UDP' operation added [@h345983745] | [#614]
|
||||
|
||||
### [9.1.0] - 2019-08-22
|
||||
- 'Parse SSH Host Key' operation added [@j433866] | [#595]
|
||||
- 'Defang IP Addresses' operation added [@h345983745] | [#556]
|
||||
|
||||
</details>
|
||||
|
||||
## [9.0.0] - 2019-07-09
|
||||
- [Multiple inputs](https://github.com/gchq/CyberChef/wiki/Multiple-Inputs) are now supported in the main web UI, allowing you to upload and process multiple files at once [@j433866] | [#566]
|
||||
- A [Node.js API](https://github.com/gchq/CyberChef/wiki/Node-API) has been implemented, meaning that CyberChef can now be used as a library, either to provide specific operations, or an entire baking environment [@d98762625] | [#291]
|
||||
- A [read-eval-print loop (REPL)](https://github.com/gchq/CyberChef/wiki/Node-API#repl) is also included to enable prototyping and experimentation with the API [@d98762625] | [#291]
|
||||
- Light and dark Solarized themes added [@j433866] | [#566]
|
||||
|
||||
<details>
|
||||
<summary>Click to expand v8 minor versions</summary>
|
||||
|
||||
### [8.38.0] - 2019-07-03
|
||||
- 'Streebog' and 'GOST hash' operations added [@MShwed] [@n1474335] | [#530]
|
||||
|
||||
### [8.37.0] - 2019-07-03
|
||||
- 'CRC-8 Checksum' operation added [@MShwed] | [#591]
|
||||
|
||||
### [8.36.0] - 2019-07-03
|
||||
- 'PGP Verify' operation added [@artemisbot] | [#585]
|
||||
|
||||
### [8.35.0] - 2019-07-03
|
||||
- 'Sharpen Image', 'Convert Image Format' and 'Add Text To Image' operations added [@j433866] | [#515]
|
||||
|
||||
### [8.34.0] - 2019-06-28
|
||||
- Various new visualisations added to the 'Entropy' operation [@MShwed] | [#535]
|
||||
- Efficiency improvements made to the 'Entropy' operation for large file support [@n1474335]
|
||||
|
||||
### [8.33.0] - 2019-06-27
|
||||
- 'Bzip2 Compress' operation added and 'Bzip2 Decompress' operation greatly improved [@artemisbot] | [#531]
|
||||
|
||||
### [8.32.0] - 2019-06-27
|
||||
- 'Index of Coincidence' operation added [@Ge0rg3] | [#571]
|
||||
|
||||
### [8.31.0] - 2019-04-12
|
||||
- The downloadable version of CyberChef is now a .zip file containing separate modules rather than a single .htm file. It is still completely standalone and will not make any external network requests. This change reduces the complexity of the build process significantly. [@n1474335]
|
||||
|
||||
### [8.30.0] - 2019-04-12
|
||||
- 'Decode Protobuf' operation added [@n1474335] | [#533]
|
||||
|
||||
### [8.29.0] - 2019-03-31
|
||||
- 'BLAKE2s' and 'BLAKE2b' hashing operations added [@h345983745] | [#525]
|
||||
|
||||
### [8.28.0] - 2019-03-31
|
||||
- 'Heatmap Chart', 'Hex Density Chart', 'Scatter Chart' and 'Series Chart' operation added [@artemisbot] [@tlwr] | [#496] [#143]
|
||||
|
||||
### [8.27.0] - 2019-03-14
|
||||
- 'Enigma', 'Typex', 'Bombe' and 'Multiple Bombe' operations added [@s2224834] | [#516]
|
||||
- See [this wiki article](https://github.com/gchq/CyberChef/wiki/Enigma,-the-Bombe,-and-Typex) for a full explanation of these operations.
|
||||
- New Bombe-style loading animation added for long-running operations [@n1474335]
|
||||
- New operation argument types added: `populateMultiOption` and `argSelector` [@n1474335]
|
||||
|
||||
### [8.26.0] - 2019-03-09
|
||||
- Various image manipulation operations added [@j433866] | [#506]
|
||||
|
||||
### [8.25.0] - 2019-03-09
|
||||
- 'Extract Files' operation added and more file formats supported [@n1474335] | [#440]
|
||||
|
||||
### [8.24.0] - 2019-02-08
|
||||
- 'DNS over HTTPS' operation added [@h345983745] | [#489]
|
||||
|
||||
### [8.23.1] - 2019-01-18
|
||||
- 'Convert co-ordinate format' operation added [@j433866] | [#476]
|
||||
|
||||
### [8.23.0] - 2019-01-18
|
||||
- 'YARA Rules' operation added [@artemisbot] | [#468]
|
||||
|
||||
### [8.22.0] - 2019-01-10
|
||||
- 'Subsection' operation added [@j433866] | [#467]
|
||||
|
||||
### [8.21.0] - 2019-01-10
|
||||
- 'To Case Insensitive Regex' and 'From Case Insensitive Regex' operations added [@masq] | [#461]
|
||||
|
||||
### [8.20.0] - 2019-01-09
|
||||
- 'Generate Lorem Ipsum' operation added [@klaxon1] | [#455]
|
||||
|
||||
### [8.19.0] - 2018-12-30
|
||||
- UI test suite added to confirm that the app loads correctly in a reasonable time and that various operations from each module can be run [@n1474335] | [#458]
|
||||
|
||||
### [8.18.0] - 2018-12-26
|
||||
- 'Split Colour Channels' operation added [@artemisbot] | [#449]
|
||||
|
||||
### [8.17.0] - 2018-12-25
|
||||
- 'Generate QR Code' and 'Parse QR Code' operations added [@j433866] | [#448]
|
||||
|
||||
### [8.16.0] - 2018-12-19
|
||||
- 'Play Media' operation added [@anthony-arnold] | [#446]
|
||||
|
||||
### [8.15.0] - 2018-12-18
|
||||
- 'Text Encoding Brute Force' operation added [@Cynser] | [#439]
|
||||
|
||||
### [8.14.0] - 2018-12-18
|
||||
- 'To Base62' and 'From Base62' operations added [@tcode2k16] | [#443]
|
||||
|
||||
### [8.13.0] - 2018-12-15
|
||||
- 'A1Z26 Cipher Encode' and 'A1Z26 Cipher Decode' operations added [@jarmovanlenthe] | [#441]
|
||||
|
||||
### [8.12.0] - 2018-11-21
|
||||
- 'Citrix CTX1 Encode' and 'Citrix CTX1 Decode' operations added [@bwhitn] | [#428]
|
||||
|
||||
### [8.11.0] - 2018-11-13
|
||||
- 'CSV to JSON' and 'JSON to CSV' operations added [@n1474335] | [#277]
|
||||
|
||||
### [8.10.0] - 2018-11-07
|
||||
- 'Remove Diacritics' operation added [@klaxon1] | [#387]
|
||||
|
||||
### [8.9.0] - 2018-11-07
|
||||
- 'Defang URL' operation added [@arnydo] | [#394]
|
||||
|
||||
### [8.8.0] - 2018-10-10
|
||||
- 'Parse TLV' operation added [@GCHQ77703] | [#351]
|
||||
|
||||
### [8.7.0] - 2018-08-31
|
||||
- 'JWT Sign', 'JWT Verify' and 'JWT Decode' operations added [@GCHQ77703] | [#348]
|
||||
|
||||
### [8.6.0] - 2018-08-29
|
||||
- 'To Geohash' and 'From Geohash' operations added [@GCHQ77703] | [#344]
|
||||
|
||||
### [8.5.0] - 2018-08-23
|
||||
- 'To Braille' and 'From Braille' operations added [@n1474335] | [#255]
|
||||
|
||||
### [8.4.0] - 2018-08-23
|
||||
- 'To Base85' and 'From Base85' operations added [@PenguinGeorge] | [#340]
|
||||
|
||||
### [8.3.0] - 2018-08-21
|
||||
- 'To MessagePack' and 'From MessagePack' operations added [@artemisbot] | [#338]
|
||||
|
||||
### [8.2.0] - 2018-08-21
|
||||
- Information links added to most operations, accessible in the description popover [@PenguinGeorge] | [#298]
|
||||
|
||||
### [8.1.0] - 2018-08-19
|
||||
- 'Dechunk HTTP response' operation added [@sevzero] | [#311]
|
||||
|
||||
</details>
|
||||
|
||||
## [8.0.0] - 2018-08-05
|
||||
- Codebase rewritten using [ES modules](https://hacks.mozilla.org/2018/03/es-modules-a-cartoon-deep-dive/) and [classes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) [@n1474335] [@d98762625] [@artemisbot] [@picapi] | [#284]
|
||||
- Operation architecture restructured to make adding new operations a lot simpler [@n1474335] | [#284]
|
||||
- A script has been added to aid in the creation of new operations by running `npm run newop` [@n1474335] | [#284]
|
||||
- 'Magic' operation added - [automated detection of encoded data](https://github.com/gchq/CyberChef/wiki/Automatic-detection-of-encoded-data-using-CyberChef-Magic) [@n1474335] | [#239]
|
||||
- UI updated to use [Bootstrap Material Design](https://fezvrasta.github.io/bootstrap-material-design/) [@n1474335] | [#248]
|
||||
- `JSON`, `File` and `List<File>` Dish types added [@n1474335] | [#284]
|
||||
- `OperationError` type added for better handling of errors thrown by operations [@d98762625] | [#296]
|
||||
- A `present()` method has been added, allowing operations to pass machine-friendly data to subsequent operations whilst presenting human-friendly data to the user [@n1474335] | [#284]
|
||||
- Set operations added [@d98762625] | [#281]
|
||||
- 'To Table' operation added [@JustAnotherMark] | [#294]
|
||||
- 'Haversine distance' operation added [@Dachande663] | [#325]
|
||||
- Started keeping a changelog [@n1474335]
|
||||
|
||||
## [7.0.0] - 2017-12-28
|
||||
- Added support for loading, processing and downloading files up to 500MB [@n1474335] | [#224]
|
||||
|
||||
## [6.0.0] - 2017-09-19
|
||||
- Threading support added. All recipe processing moved into a [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers) to increase performance and to allow long-running operations to be cancelled [@n1474335] | [#173]
|
||||
- Module system created so that operations relying on large libraries can be downloaded separately as required, reducing the initial loading time for the app [@n1474335] | [#173]
|
||||
|
||||
## [5.0.0] - 2017-03-30
|
||||
- Webpack build process configured with Babel transpilation and ES6 imports and exports [@n1474335] | [#95]
|
||||
|
||||
## [4.0.0] - 2016-11-28
|
||||
- Initial open source commit [@n1474335] | [b1d73a72](https://github.com/gchq/CyberChef/commit/b1d73a725dc7ab9fb7eb789296efd2b7e4b08306)
|
||||
|
||||
[10.8.0]: https://github.com/gchq/CyberChef/releases/tag/v10.7.0
|
||||
[10.7.0]: https://github.com/gchq/CyberChef/releases/tag/v10.7.0
|
||||
[10.6.0]: https://github.com/gchq/CyberChef/releases/tag/v10.6.0
|
||||
[10.5.0]: https://github.com/gchq/CyberChef/releases/tag/v10.5.0
|
||||
[10.4.0]: https://github.com/gchq/CyberChef/releases/tag/v10.4.0
|
||||
[10.3.0]: https://github.com/gchq/CyberChef/releases/tag/v10.3.0
|
||||
[10.2.0]: https://github.com/gchq/CyberChef/releases/tag/v10.2.0
|
||||
[10.1.0]: https://github.com/gchq/CyberChef/releases/tag/v10.1.0
|
||||
[10.0.0]: https://github.com/gchq/CyberChef/releases/tag/v10.0.0
|
||||
[9.55.0]: https://github.com/gchq/CyberChef/releases/tag/v9.55.0
|
||||
[9.54.0]: https://github.com/gchq/CyberChef/releases/tag/v9.54.0
|
||||
[9.53.0]: https://github.com/gchq/CyberChef/releases/tag/v9.53.0
|
||||
[9.52.0]: https://github.com/gchq/CyberChef/releases/tag/v9.52.0
|
||||
[9.51.0]: https://github.com/gchq/CyberChef/releases/tag/v9.51.0
|
||||
[9.50.0]: https://github.com/gchq/CyberChef/releases/tag/v9.50.0
|
||||
[9.49.0]: https://github.com/gchq/CyberChef/releases/tag/v9.49.0
|
||||
[9.48.0]: https://github.com/gchq/CyberChef/releases/tag/v9.48.0
|
||||
[9.47.0]: https://github.com/gchq/CyberChef/releases/tag/v9.47.0
|
||||
[9.46.0]: https://github.com/gchq/CyberChef/releases/tag/v9.46.0
|
||||
[9.45.0]: https://github.com/gchq/CyberChef/releases/tag/v9.45.0
|
||||
[9.44.0]: https://github.com/gchq/CyberChef/releases/tag/v9.44.0
|
||||
[9.43.0]: https://github.com/gchq/CyberChef/releases/tag/v9.43.0
|
||||
[9.42.0]: https://github.com/gchq/CyberChef/releases/tag/v9.42.0
|
||||
[9.41.0]: https://github.com/gchq/CyberChef/releases/tag/v9.41.0
|
||||
[9.40.0]: https://github.com/gchq/CyberChef/releases/tag/v9.40.0
|
||||
[9.39.0]: https://github.com/gchq/CyberChef/releases/tag/v9.39.0
|
||||
[9.38.0]: https://github.com/gchq/CyberChef/releases/tag/v9.38.0
|
||||
[9.37.0]: https://github.com/gchq/CyberChef/releases/tag/v9.37.0
|
||||
[9.36.0]: https://github.com/gchq/CyberChef/releases/tag/v9.36.0
|
||||
[9.35.0]: https://github.com/gchq/CyberChef/releases/tag/v9.35.0
|
||||
[9.34.0]: https://github.com/gchq/CyberChef/releases/tag/v9.34.0
|
||||
[9.33.0]: https://github.com/gchq/CyberChef/releases/tag/v9.33.0
|
||||
[9.32.0]: https://github.com/gchq/CyberChef/releases/tag/v9.32.0
|
||||
[9.31.0]: https://github.com/gchq/CyberChef/releases/tag/v9.31.0
|
||||
[9.30.0]: https://github.com/gchq/CyberChef/releases/tag/v9.30.0
|
||||
[9.29.0]: https://github.com/gchq/CyberChef/releases/tag/v9.29.0
|
||||
[9.28.0]: https://github.com/gchq/CyberChef/releases/tag/v9.28.0
|
||||
[9.27.0]: https://github.com/gchq/CyberChef/releases/tag/v9.27.0
|
||||
[9.26.0]: https://github.com/gchq/CyberChef/releases/tag/v9.26.0
|
||||
[9.25.0]: https://github.com/gchq/CyberChef/releases/tag/v9.25.0
|
||||
[9.24.0]: https://github.com/gchq/CyberChef/releases/tag/v9.24.0
|
||||
[9.23.0]: https://github.com/gchq/CyberChef/releases/tag/v9.23.0
|
||||
[9.22.0]: https://github.com/gchq/CyberChef/releases/tag/v9.22.0
|
||||
[9.21.0]: https://github.com/gchq/CyberChef/releases/tag/v9.21.0
|
||||
[9.20.0]: https://github.com/gchq/CyberChef/releases/tag/v9.20.0
|
||||
[9.19.0]: https://github.com/gchq/CyberChef/releases/tag/v9.19.0
|
||||
[9.18.0]: https://github.com/gchq/CyberChef/releases/tag/v9.18.0
|
||||
[9.17.0]: https://github.com/gchq/CyberChef/releases/tag/v9.17.0
|
||||
[9.16.0]: https://github.com/gchq/CyberChef/releases/tag/v9.16.0
|
||||
[9.15.0]: https://github.com/gchq/CyberChef/releases/tag/v9.15.0
|
||||
[9.14.0]: https://github.com/gchq/CyberChef/releases/tag/v9.14.0
|
||||
[9.13.0]: https://github.com/gchq/CyberChef/releases/tag/v9.13.0
|
||||
[9.12.0]: https://github.com/gchq/CyberChef/releases/tag/v9.12.0
|
||||
[9.11.0]: https://github.com/gchq/CyberChef/releases/tag/v9.11.0
|
||||
[9.10.0]: https://github.com/gchq/CyberChef/releases/tag/v9.10.0
|
||||
[9.9.0]: https://github.com/gchq/CyberChef/releases/tag/v9.9.0
|
||||
[9.8.0]: https://github.com/gchq/CyberChef/releases/tag/v9.8.0
|
||||
[9.7.0]: https://github.com/gchq/CyberChef/releases/tag/v9.7.0
|
||||
[9.6.0]: https://github.com/gchq/CyberChef/releases/tag/v9.6.0
|
||||
[9.5.0]: https://github.com/gchq/CyberChef/releases/tag/v9.5.0
|
||||
[9.4.0]: https://github.com/gchq/CyberChef/releases/tag/v9.4.0
|
||||
[9.3.0]: https://github.com/gchq/CyberChef/releases/tag/v9.3.0
|
||||
[9.2.0]: https://github.com/gchq/CyberChef/releases/tag/v9.2.0
|
||||
[9.1.0]: https://github.com/gchq/CyberChef/releases/tag/v9.1.0
|
||||
[9.0.0]: https://github.com/gchq/CyberChef/releases/tag/v9.0.0
|
||||
[8.38.0]: https://github.com/gchq/CyberChef/releases/tag/v8.38.0
|
||||
[8.37.0]: https://github.com/gchq/CyberChef/releases/tag/v8.37.0
|
||||
[8.36.0]: https://github.com/gchq/CyberChef/releases/tag/v8.36.0
|
||||
[8.35.0]: https://github.com/gchq/CyberChef/releases/tag/v8.35.0
|
||||
[8.34.0]: https://github.com/gchq/CyberChef/releases/tag/v8.34.0
|
||||
[8.33.0]: https://github.com/gchq/CyberChef/releases/tag/v8.33.0
|
||||
[8.32.0]: https://github.com/gchq/CyberChef/releases/tag/v8.32.0
|
||||
[8.31.0]: https://github.com/gchq/CyberChef/releases/tag/v8.31.0
|
||||
[8.30.0]: https://github.com/gchq/CyberChef/releases/tag/v8.30.0
|
||||
[8.29.0]: https://github.com/gchq/CyberChef/releases/tag/v8.29.0
|
||||
[8.28.0]: https://github.com/gchq/CyberChef/releases/tag/v8.28.0
|
||||
[8.27.0]: https://github.com/gchq/CyberChef/releases/tag/v8.27.0
|
||||
[8.26.0]: https://github.com/gchq/CyberChef/releases/tag/v8.26.0
|
||||
[8.25.0]: https://github.com/gchq/CyberChef/releases/tag/v8.25.0
|
||||
[8.24.0]: https://github.com/gchq/CyberChef/releases/tag/v8.24.0
|
||||
[8.23.1]: https://github.com/gchq/CyberChef/releases/tag/v8.23.1
|
||||
[8.23.0]: https://github.com/gchq/CyberChef/releases/tag/v8.23.0
|
||||
[8.22.0]: https://github.com/gchq/CyberChef/releases/tag/v8.22.0
|
||||
[8.21.0]: https://github.com/gchq/CyberChef/releases/tag/v8.21.0
|
||||
[8.20.0]: https://github.com/gchq/CyberChef/releases/tag/v8.20.0
|
||||
[8.19.0]: https://github.com/gchq/CyberChef/releases/tag/v8.19.0
|
||||
[8.18.0]: https://github.com/gchq/CyberChef/releases/tag/v8.18.0
|
||||
[8.17.0]: https://github.com/gchq/CyberChef/releases/tag/v8.17.0
|
||||
[8.16.0]: https://github.com/gchq/CyberChef/releases/tag/v8.16.0
|
||||
[8.15.0]: https://github.com/gchq/CyberChef/releases/tag/v8.15.0
|
||||
[8.14.0]: https://github.com/gchq/CyberChef/releases/tag/v8.14.0
|
||||
[8.13.0]: https://github.com/gchq/CyberChef/releases/tag/v8.13.0
|
||||
[8.12.0]: https://github.com/gchq/CyberChef/releases/tag/v8.12.0
|
||||
[8.11.0]: https://github.com/gchq/CyberChef/releases/tag/v8.11.0
|
||||
[8.10.0]: https://github.com/gchq/CyberChef/releases/tag/v8.10.0
|
||||
[8.9.0]: https://github.com/gchq/CyberChef/releases/tag/v8.9.0
|
||||
[8.8.0]: https://github.com/gchq/CyberChef/releases/tag/v8.8.0
|
||||
[8.7.0]: https://github.com/gchq/CyberChef/releases/tag/v8.7.0
|
||||
[8.6.0]: https://github.com/gchq/CyberChef/releases/tag/v8.6.0
|
||||
[8.5.0]: https://github.com/gchq/CyberChef/releases/tag/v8.5.0
|
||||
[8.4.0]: https://github.com/gchq/CyberChef/releases/tag/v8.4.0
|
||||
[8.3.0]: https://github.com/gchq/CyberChef/releases/tag/v8.3.0
|
||||
[8.2.0]: https://github.com/gchq/CyberChef/releases/tag/v8.2.0
|
||||
[8.1.0]: https://github.com/gchq/CyberChef/releases/tag/v8.1.0
|
||||
[8.0.0]: https://github.com/gchq/CyberChef/releases/tag/v8.0.0
|
||||
[7.0.0]: https://github.com/gchq/CyberChef/releases/tag/v7.0.0
|
||||
[6.0.0]: https://github.com/gchq/CyberChef/releases/tag/v6.0.0
|
||||
[5.0.0]: https://github.com/gchq/CyberChef/releases/tag/v5.0.0
|
||||
[4.0.0]: https://github.com/gchq/CyberChef/commit/b1d73a725dc7ab9fb7eb789296efd2b7e4b08306
|
||||
|
||||
[@n1474335]: https://github.com/n1474335
|
||||
[@d98762625]: https://github.com/d98762625
|
||||
[@j433866]: https://github.com/j433866
|
||||
[@n1073645]: https://github.com/n1073645
|
||||
[@GCHQ77703]: https://github.com/GCHQ77703
|
||||
[@h345983745]: https://github.com/h345983745
|
||||
[@s2224834]: https://github.com/s2224834
|
||||
[@artemisbot]: https://github.com/artemisbot
|
||||
[@tlwr]: https://github.com/tlwr
|
||||
[@picapi]: https://github.com/picapi
|
||||
[@Dachande663]: https://github.com/Dachande663
|
||||
[@JustAnotherMark]: https://github.com/JustAnotherMark
|
||||
[@sevzero]: https://github.com/sevzero
|
||||
[@PenguinGeorge]: https://github.com/PenguinGeorge
|
||||
[@arnydo]: https://github.com/arnydo
|
||||
[@klaxon1]: https://github.com/klaxon1
|
||||
[@bwhitn]: https://github.com/bwhitn
|
||||
[@jarmovanlenthe]: https://github.com/jarmovanlenthe
|
||||
[@tcode2k16]: https://github.com/tcode2k16
|
||||
[@Cynser]: https://github.com/Cynser
|
||||
[@anthony-arnold]: https://github.com/anthony-arnold
|
||||
[@masq]: https://github.com/masq
|
||||
[@Ge0rg3]: https://github.com/Ge0rg3
|
||||
[@MShwed]: https://github.com/MShwed
|
||||
[@kassi]: https://github.com/kassi
|
||||
[@jarrodconnolly]: https://github.com/jarrodconnolly
|
||||
[@VirtualColossus]: https://github.com/VirtualColossus
|
||||
[@cbeuw]: https://github.com/cbeuw
|
||||
[@matthieuxyz]: https://github.com/matthieuxyz
|
||||
[@Flavsditz]: https://github.com/Flavsditz
|
||||
[@pointhi]: https://github.com/pointhi
|
||||
[@MarvinJWendt]: https://github.com/MarvinJWendt
|
||||
[@dmfj]: https://github.com/dmfj
|
||||
[@mattnotmitt]: https://github.com/mattnotmitt
|
||||
[@Danh4]: https://github.com/Danh4
|
||||
[@john19696]: https://github.com/john19696
|
||||
[@t-8ch]: https://github.com/t-8ch
|
||||
[@hettysymes]: https://github.com/hettysymes
|
||||
[@swesven]: https://github.com/swesven
|
||||
[@mikecat]: https://github.com/mikecat
|
||||
[@crespyl]: https://github.com/crespyl
|
||||
[@thomasleplus]: https://github.com/thomasleplus
|
||||
[@valdelaseras]: https://github.com/valdelaseras
|
||||
[@brun0ne]: https://github.com/brun0ne
|
||||
[@joostrijneveld]: https://github.com/joostrijneveld
|
||||
[@Xenonym]: https://github.com/Xenonym
|
||||
[@gchq77703]: https://github.com/gchq77703
|
||||
[@a3957273]: https://github.com/a3957273
|
||||
[@0xThiebaut]: https://github.com/0xThiebaut
|
||||
[@cnotin]: https://github.com/cnotin
|
||||
[@KevinSJ]: https://github.com/KevinSJ
|
||||
[@sw5678]: https://github.com/sw5678
|
||||
[@sg5506844]: https://github.com/sg5506844
|
||||
[@AliceGrey]: https://github.com/AliceGrey
|
||||
[@AshCorr]: https://github.com/AshCorr
|
||||
|
||||
|
||||
[8ad18b]: https://github.com/gchq/CyberChef/commit/8ad18bc7db6d9ff184ba3518686293a7685bf7b7
|
||||
[9a33498]: https://github.com/gchq/CyberChef/commit/9a33498fed26a8df9c9f35f39a78a174bf50a513
|
||||
[289a417]: https://github.com/gchq/CyberChef/commit/289a417dfb5923de5e1694354ec42a08d9395bfe
|
||||
[e9ca4dc]: https://github.com/gchq/CyberChef/commit/e9ca4dc9caf98f33fd986431cd400c88082a42b8
|
||||
[dd18e52]: https://github.com/gchq/CyberChef/commit/dd18e529939078b89867297b181a584e8b2cc7da
|
||||
[a895d1d]: https://github.com/gchq/CyberChef/commit/a895d1d82a2f92d440a0c5eca2bc7c898107b737
|
||||
[31a7f83]: https://github.com/gchq/CyberChef/commit/31a7f83b82e78927f89689f323fcb9185144d6ff
|
||||
[760eff4]: https://github.com/gchq/CyberChef/commit/760eff49b5307aaa3104c5e5b437ffe62299acd1
|
||||
|
||||
[#95]: https://github.com/gchq/CyberChef/pull/299
|
||||
[#173]: https://github.com/gchq/CyberChef/pull/173
|
||||
[#143]: https://github.com/gchq/CyberChef/pull/143
|
||||
[#224]: https://github.com/gchq/CyberChef/pull/224
|
||||
[#239]: https://github.com/gchq/CyberChef/pull/239
|
||||
[#248]: https://github.com/gchq/CyberChef/pull/248
|
||||
[#255]: https://github.com/gchq/CyberChef/issues/255
|
||||
[#277]: https://github.com/gchq/CyberChef/issues/277
|
||||
[#281]: https://github.com/gchq/CyberChef/pull/281
|
||||
[#284]: https://github.com/gchq/CyberChef/pull/284
|
||||
[#291]: https://github.com/gchq/CyberChef/pull/291
|
||||
[#294]: https://github.com/gchq/CyberChef/pull/294
|
||||
[#296]: https://github.com/gchq/CyberChef/pull/296
|
||||
[#298]: https://github.com/gchq/CyberChef/pull/298
|
||||
[#311]: https://github.com/gchq/CyberChef/pull/311
|
||||
[#325]: https://github.com/gchq/CyberChef/pull/325
|
||||
[#338]: https://github.com/gchq/CyberChef/pull/338
|
||||
[#340]: https://github.com/gchq/CyberChef/pull/340
|
||||
[#344]: https://github.com/gchq/CyberChef/pull/344
|
||||
[#348]: https://github.com/gchq/CyberChef/pull/348
|
||||
[#351]: https://github.com/gchq/CyberChef/pull/351
|
||||
[#387]: https://github.com/gchq/CyberChef/pull/387
|
||||
[#394]: https://github.com/gchq/CyberChef/pull/394
|
||||
[#428]: https://github.com/gchq/CyberChef/pull/428
|
||||
[#439]: https://github.com/gchq/CyberChef/pull/439
|
||||
[#440]: https://github.com/gchq/CyberChef/pull/440
|
||||
[#441]: https://github.com/gchq/CyberChef/pull/441
|
||||
[#443]: https://github.com/gchq/CyberChef/pull/443
|
||||
[#446]: https://github.com/gchq/CyberChef/pull/446
|
||||
[#448]: https://github.com/gchq/CyberChef/pull/448
|
||||
[#449]: https://github.com/gchq/CyberChef/pull/449
|
||||
[#455]: https://github.com/gchq/CyberChef/pull/455
|
||||
[#458]: https://github.com/gchq/CyberChef/pull/458
|
||||
[#461]: https://github.com/gchq/CyberChef/pull/461
|
||||
[#467]: https://github.com/gchq/CyberChef/pull/467
|
||||
[#468]: https://github.com/gchq/CyberChef/pull/468
|
||||
[#476]: https://github.com/gchq/CyberChef/pull/476
|
||||
[#477]: https://github.com/gchq/CyberChef/pull/477
|
||||
[#489]: https://github.com/gchq/CyberChef/pull/489
|
||||
[#496]: https://github.com/gchq/CyberChef/pull/496
|
||||
[#500]: https://github.com/gchq/CyberChef/pull/500
|
||||
[#506]: https://github.com/gchq/CyberChef/pull/506
|
||||
[#515]: https://github.com/gchq/CyberChef/pull/515
|
||||
[#516]: https://github.com/gchq/CyberChef/pull/516
|
||||
[#525]: https://github.com/gchq/CyberChef/pull/525
|
||||
[#528]: https://github.com/gchq/CyberChef/pull/528
|
||||
[#530]: https://github.com/gchq/CyberChef/pull/530
|
||||
[#531]: https://github.com/gchq/CyberChef/pull/531
|
||||
[#533]: https://github.com/gchq/CyberChef/pull/533
|
||||
[#535]: https://github.com/gchq/CyberChef/pull/535
|
||||
[#556]: https://github.com/gchq/CyberChef/pull/556
|
||||
[#566]: https://github.com/gchq/CyberChef/pull/566
|
||||
[#571]: https://github.com/gchq/CyberChef/pull/571
|
||||
[#585]: https://github.com/gchq/CyberChef/pull/585
|
||||
[#591]: https://github.com/gchq/CyberChef/pull/591
|
||||
[#595]: https://github.com/gchq/CyberChef/pull/595
|
||||
[#614]: https://github.com/gchq/CyberChef/pull/614
|
||||
[#625]: https://github.com/gchq/CyberChef/pull/625
|
||||
[#627]: https://github.com/gchq/CyberChef/pull/627
|
||||
[#632]: https://github.com/gchq/CyberChef/pull/632
|
||||
[#652]: https://github.com/gchq/CyberChef/pull/652
|
||||
[#653]: https://github.com/gchq/CyberChef/pull/653
|
||||
[#674]: https://github.com/gchq/CyberChef/pull/674
|
||||
[#683]: https://github.com/gchq/CyberChef/pull/683
|
||||
[#865]: https://github.com/gchq/CyberChef/pull/865
|
||||
[#906]: https://github.com/gchq/CyberChef/pull/906
|
||||
[#912]: https://github.com/gchq/CyberChef/pull/912
|
||||
[#917]: https://github.com/gchq/CyberChef/pull/917
|
||||
[#934]: https://github.com/gchq/CyberChef/pull/934
|
||||
[#948]: https://github.com/gchq/CyberChef/pull/948
|
||||
[#951]: https://github.com/gchq/CyberChef/pull/951
|
||||
[#952]: https://github.com/gchq/CyberChef/pull/952
|
||||
[#965]: https://github.com/gchq/CyberChef/pull/965
|
||||
[#966]: https://github.com/gchq/CyberChef/pull/966
|
||||
[#987]: https://github.com/gchq/CyberChef/pull/987
|
||||
[#999]: https://github.com/gchq/CyberChef/pull/999
|
||||
[#1006]: https://github.com/gchq/CyberChef/pull/1006
|
||||
[#1022]: https://github.com/gchq/CyberChef/pull/1022
|
||||
[#1037]: https://github.com/gchq/CyberChef/pull/1037
|
||||
[#1045]: https://github.com/gchq/CyberChef/pull/1045
|
||||
[#1049]: https://github.com/gchq/CyberChef/pull/1049
|
||||
[#1065]: https://github.com/gchq/CyberChef/pull/1065
|
||||
[#1066]: https://github.com/gchq/CyberChef/pull/1066
|
||||
[#1083]: https://github.com/gchq/CyberChef/pull/1083
|
||||
[#1189]: https://github.com/gchq/CyberChef/pull/1189
|
||||
[#1242]: https://github.com/gchq/CyberChef/pull/1242
|
||||
[#1244]: https://github.com/gchq/CyberChef/pull/1244
|
||||
[#1313]: https://github.com/gchq/CyberChef/pull/1313
|
||||
[#1326]: https://github.com/gchq/CyberChef/pull/1326
|
||||
[#1364]: https://github.com/gchq/CyberChef/pull/1364
|
||||
[#1264]: https://github.com/gchq/CyberChef/pull/1264
|
||||
[#1266]: https://github.com/gchq/CyberChef/pull/1266
|
||||
[#1250]: https://github.com/gchq/CyberChef/pull/1250
|
||||
[#1308]: https://github.com/gchq/CyberChef/pull/1308
|
||||
[#1405]: https://github.com/gchq/CyberChef/pull/1405
|
||||
[#1421]: https://github.com/gchq/CyberChef/pull/1421
|
||||
[#1427]: https://github.com/gchq/CyberChef/pull/1427
|
||||
[#1472]: https://github.com/gchq/CyberChef/pull/1472
|
||||
[#1457]: https://github.com/gchq/CyberChef/pull/1457
|
||||
[#1466]: https://github.com/gchq/CyberChef/pull/1466
|
||||
[#1456]: https://github.com/gchq/CyberChef/pull/1456
|
||||
[#1450]: https://github.com/gchq/CyberChef/pull/1450
|
||||
[#1498]: https://github.com/gchq/CyberChef/pull/1498
|
||||
[#1499]: https://github.com/gchq/CyberChef/pull/1499
|
||||
[#1528]: https://github.com/gchq/CyberChef/pull/1528
|
||||
[#661]: https://github.com/gchq/CyberChef/pull/661
|
||||
[#493]: https://github.com/gchq/CyberChef/pull/493
|
||||
[#592]: https://github.com/gchq/CyberChef/issues/592
|
||||
[#1703]: https://github.com/gchq/CyberChef/issues/1703
|
||||
[#1675]: https://github.com/gchq/CyberChef/issues/1675
|
||||
[#1678]: https://github.com/gchq/CyberChef/issues/1678
|
||||
[#1541]: https://github.com/gchq/CyberChef/issues/1541
|
||||
[#1667]: https://github.com/gchq/CyberChef/issues/1667
|
||||
[#1555]: https://github.com/gchq/CyberChef/issues/1555
|
||||
[#1694]: https://github.com/gchq/CyberChef/issues/1694
|
||||
[#1699]: https://github.com/gchq/CyberChef/issues/1694
|
46
CODE_OF_CONDUCT.md
Normal file
46
CODE_OF_CONDUCT.md
Normal file
|
@ -0,0 +1,46 @@
|
|||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at oss@gchq.gov.uk. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
9
Dockerfile
Normal file
9
Dockerfile
Normal file
|
@ -0,0 +1,9 @@
|
|||
FROM node:18-alpine AS build
|
||||
|
||||
COPY . .
|
||||
RUN npm ci
|
||||
RUN npm run build
|
||||
|
||||
FROM nginx:1.25-alpine3.18 AS cyberchef
|
||||
|
||||
COPY --from=build ./build/prod /usr/share/nginx/html/
|
594
Gruntfile.js
594
Gruntfile.js
|
@ -1,7 +1,20 @@
|
|||
var webpack = require("webpack"),
|
||||
ExtractTextPlugin = require("extract-text-webpack-plugin"),
|
||||
HtmlWebpackPlugin = require("html-webpack-plugin"),
|
||||
Inliner = require("web-resource-inliner");
|
||||
"use strict";
|
||||
|
||||
const webpack = require("webpack");
|
||||
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
|
||||
const glob = require("glob");
|
||||
const path = require("path");
|
||||
|
||||
const nodeFlags = "--experimental-modules --experimental-json-modules --experimental-specifier-resolution=node --no-warnings --no-deprecation";
|
||||
|
||||
/**
|
||||
* Grunt configuration for building the app in various formats.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2017
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
module.exports = function (grunt) {
|
||||
grunt.file.defaultEncoding = "utf8";
|
||||
|
@ -10,278 +23,232 @@ module.exports = function (grunt) {
|
|||
// Tasks
|
||||
grunt.registerTask("dev",
|
||||
"A persistent task which creates a development build whenever source files are modified.",
|
||||
["clean:dev", "webpack:webDev"]);
|
||||
|
||||
grunt.registerTask("node",
|
||||
"Compiles CyberChef into a single NodeJS module.",
|
||||
["clean:node", "webpack:node", "chmod:build"]);
|
||||
|
||||
grunt.registerTask("test",
|
||||
"A task which runs all the tests in test/tests.",
|
||||
["clean:test", "webpack:tests", "execute:test"]);
|
||||
|
||||
grunt.registerTask("docs",
|
||||
"Compiles documentation in the /docs directory.",
|
||||
["clean:docs", "jsdoc", "chmod:docs"]);
|
||||
["clean:dev", "clean:config", "exec:generateConfig", "concurrent:dev"]);
|
||||
|
||||
grunt.registerTask("prod",
|
||||
"Creates a production-ready build. Use the --msg flag to add a compile message.",
|
||||
["eslint", "clean:prod", "webpack:webProd", "inline", "chmod"]);
|
||||
[
|
||||
"eslint", "clean:prod", "clean:config", "exec:generateConfig", "findModules", "webpack:web",
|
||||
"copy:standalone", "zip:standalone", "clean:standalone", "exec:calcDownloadHash", "chmod"
|
||||
]);
|
||||
|
||||
grunt.registerTask("node",
|
||||
"Compiles CyberChef into a single NodeJS module.",
|
||||
[
|
||||
"clean:node", "clean:config", "clean:nodeConfig", "exec:generateConfig", "exec:generateNodeIndex"
|
||||
]);
|
||||
|
||||
grunt.registerTask("configTests",
|
||||
"A task which configures config files in preparation for tests to be run. Use `npm test` to run tests.",
|
||||
[
|
||||
"clean:config", "clean:nodeConfig", "exec:generateConfig", "exec:generateNodeIndex"
|
||||
]);
|
||||
|
||||
grunt.registerTask("testui",
|
||||
"A task which runs all the UI tests in the tests directory. The prod task must already have been run.",
|
||||
["connect:prod", "exec:browserTests"]);
|
||||
|
||||
grunt.registerTask("testnodeconsumer",
|
||||
"A task which checks whether consuming CJS and ESM apps work with the CyberChef build",
|
||||
["exec:setupNodeConsumers", "exec:testCJSNodeConsumer", "exec:testESMNodeConsumer", "exec:teardownNodeConsumers"]);
|
||||
|
||||
grunt.registerTask("default",
|
||||
"Lints the code base",
|
||||
["eslint", "exec:repoSize"]);
|
||||
|
||||
grunt.registerTask("inline",
|
||||
"Compiles a production build of CyberChef into a single, portable web page.",
|
||||
runInliner);
|
||||
|
||||
grunt.registerTask("doc", "docs");
|
||||
grunt.registerTask("tests", "test");
|
||||
grunt.registerTask("lint", "eslint");
|
||||
|
||||
grunt.registerTask("findModules",
|
||||
"Finds all generated modules and updates the entry point list for Webpack",
|
||||
function(arg1, arg2) {
|
||||
const moduleEntryPoints = listEntryModules();
|
||||
|
||||
grunt.log.writeln(`Found ${Object.keys(moduleEntryPoints).length} modules.`);
|
||||
|
||||
grunt.config.set("webpack.web.entry",
|
||||
Object.assign({
|
||||
main: "./src/web/index.js"
|
||||
}, moduleEntryPoints));
|
||||
});
|
||||
|
||||
|
||||
// Load tasks provided by each plugin
|
||||
grunt.loadNpmTasks("grunt-eslint");
|
||||
grunt.loadNpmTasks("grunt-webpack");
|
||||
grunt.loadNpmTasks("grunt-jsdoc");
|
||||
grunt.loadNpmTasks("grunt-contrib-clean");
|
||||
grunt.loadNpmTasks("grunt-contrib-copy");
|
||||
grunt.loadNpmTasks("grunt-contrib-watch");
|
||||
grunt.loadNpmTasks("grunt-chmod");
|
||||
grunt.loadNpmTasks("grunt-exec");
|
||||
grunt.loadNpmTasks("grunt-execute");
|
||||
grunt.loadNpmTasks("grunt-concurrent");
|
||||
grunt.loadNpmTasks("grunt-contrib-connect");
|
||||
grunt.loadNpmTasks("grunt-zip");
|
||||
|
||||
|
||||
// Project configuration
|
||||
var compileTime = grunt.template.today("dd/mm/yyyy HH:MM:ss") + " UTC",
|
||||
banner = "/**\n" +
|
||||
"* CyberChef - The Cyber Swiss Army Knife\n" +
|
||||
"*\n" +
|
||||
"* @copyright Crown Copyright 2016\n" +
|
||||
"* @license Apache-2.0\n" +
|
||||
"*\n" +
|
||||
"* Copyright 2016 Crown Copyright\n" +
|
||||
"*\n" +
|
||||
'* Licensed under the Apache License, Version 2.0 (the "License");\n' +
|
||||
"* you may not use this file except in compliance with the License.\n" +
|
||||
"* You may obtain a copy of the License at\n" +
|
||||
"*\n" +
|
||||
"* http://www.apache.org/licenses/LICENSE-2.0\n" +
|
||||
"*\n" +
|
||||
"* Unless required by applicable law or agreed to in writing, software\n" +
|
||||
'* distributed under the License is distributed on an "AS IS" BASIS,\n' +
|
||||
"* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" +
|
||||
"* See the License for the specific language governing permissions and\n" +
|
||||
"* limitations under the License.\n" +
|
||||
"*/\n";
|
||||
const compileTime = grunt.template.today("UTC:dd/mm/yyyy HH:MM:ss") + " UTC",
|
||||
pkg = grunt.file.readJSON("package.json"),
|
||||
webpackConfig = require("./webpack.config.js"),
|
||||
BUILD_CONSTANTS = {
|
||||
COMPILE_TIME: JSON.stringify(compileTime),
|
||||
COMPILE_MSG: JSON.stringify(grunt.option("compile-msg") || grunt.option("msg") || ""),
|
||||
PKG_VERSION: JSON.stringify(pkg.version),
|
||||
},
|
||||
moduleEntryPoints = listEntryModules(),
|
||||
nodeConsumerTestPath = "~/tmp-cyberchef",
|
||||
/**
|
||||
* Configuration for Webpack production build. Defined as a function so that it
|
||||
* can be recalculated when new modules are generated.
|
||||
*/
|
||||
webpackProdConf = () => {
|
||||
return {
|
||||
mode: "production",
|
||||
target: "web",
|
||||
entry: Object.assign({
|
||||
main: "./src/web/index.js"
|
||||
}, moduleEntryPoints),
|
||||
output: {
|
||||
path: __dirname + "/build/prod",
|
||||
filename: chunkData => {
|
||||
return chunkData.chunk.name === "main" ? "assets/[name].js": "[name].js";
|
||||
},
|
||||
globalObject: "this"
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
"./config/modules/OpModules.mjs": "./config/modules/Default.mjs"
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin(BUILD_CONSTANTS),
|
||||
new HtmlWebpackPlugin({
|
||||
filename: "index.html",
|
||||
template: "./src/web/html/index.html",
|
||||
chunks: ["main"],
|
||||
compileTime: compileTime,
|
||||
version: pkg.version,
|
||||
minify: {
|
||||
removeComments: true,
|
||||
collapseWhitespace: true,
|
||||
minifyJS: true,
|
||||
minifyCSS: true
|
||||
}
|
||||
}),
|
||||
new BundleAnalyzerPlugin({
|
||||
analyzerMode: "static",
|
||||
reportFilename: "BundleAnalyzerReport.html",
|
||||
openAnalyzer: false
|
||||
}),
|
||||
]
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Compiles a production build of CyberChef into a single, portable web page.
|
||||
* Generates an entry list for all the modules.
|
||||
*/
|
||||
function runInliner() {
|
||||
var inlinerError = false;
|
||||
Inliner.html({
|
||||
relativeTo: "build/prod/",
|
||||
fileContent: grunt.file.read("build/prod/cyberchef.htm"),
|
||||
images: true,
|
||||
svgs: true,
|
||||
scripts: true,
|
||||
links: true,
|
||||
strict: true
|
||||
}, function(error, result) {
|
||||
if (error) {
|
||||
console.log(error);
|
||||
inlinerError = true;
|
||||
return false;
|
||||
}
|
||||
grunt.file.write("build/prod/cyberchef.htm", result);
|
||||
function listEntryModules() {
|
||||
const entryModules = {};
|
||||
|
||||
glob.sync("./src/core/config/modules/*.mjs").forEach(file => {
|
||||
const basename = path.basename(file);
|
||||
if (basename !== "Default.mjs" && basename !== "OpModules.mjs")
|
||||
entryModules["modules/" + basename.split(".mjs")[0]] = path.resolve(file);
|
||||
});
|
||||
|
||||
return !inlinerError;
|
||||
return entryModules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects the correct delimiter to use to chain shell commands together
|
||||
* based on the current OS.
|
||||
*
|
||||
* @param {string[]} cmds
|
||||
* @returns {string}
|
||||
*/
|
||||
function chainCommands(cmds) {
|
||||
const win = process.platform === "win32";
|
||||
if (!win) {
|
||||
return cmds.join(";");
|
||||
}
|
||||
return cmds
|
||||
// && means that subsequent commands will not be executed if the
|
||||
// previous one fails. & would coninue on a fail
|
||||
.join("&&")
|
||||
// Windows does not support \n properly
|
||||
.replace(/\n/g, "\\n");
|
||||
}
|
||||
|
||||
grunt.initConfig({
|
||||
clean: {
|
||||
dev: ["build/dev/*"],
|
||||
prod: ["build/prod/*"],
|
||||
test: ["build/test/*"],
|
||||
node: ["build/node/*"],
|
||||
docs: ["docs/*", "!docs/*.conf.json", "!docs/*.ico"],
|
||||
config: ["src/core/config/OperationConfig.json", "src/core/config/modules/*", "src/code/operations/index.mjs"],
|
||||
nodeConfig: ["src/node/index.mjs", "src/node/config/OperationConfig.json"],
|
||||
standalone: ["build/prod/CyberChef*.html"]
|
||||
},
|
||||
eslint: {
|
||||
options: {
|
||||
configFile: "src/.eslintrc.json"
|
||||
},
|
||||
configs: ["Gruntfile.js"],
|
||||
core: ["src/core/**/*.js", "!src/core/lib/**/*"],
|
||||
web: ["src/web/**/*.js"],
|
||||
node: ["src/node/**/*.js"],
|
||||
tests: ["test/**/*.js"],
|
||||
},
|
||||
jsdoc: {
|
||||
options: {
|
||||
destination: "docs",
|
||||
template: "node_modules/ink-docstrap/template",
|
||||
recurse: true,
|
||||
readme: "./README.md",
|
||||
configure: "docs/jsdoc.conf.json"
|
||||
},
|
||||
all: {
|
||||
src: [
|
||||
"src/**/*.js",
|
||||
"!src/core/lib/**/*",
|
||||
],
|
||||
}
|
||||
configs: ["*.{js,mjs}"],
|
||||
core: ["src/core/**/*.{js,mjs}", "!src/core/vendor/**/*", "!src/core/operations/legacy/**/*"],
|
||||
web: ["src/web/**/*.{js,mjs}", "!src/web/static/**/*"],
|
||||
node: ["src/node/**/*.{js,mjs}"],
|
||||
tests: ["tests/**/*.{js,mjs}"],
|
||||
},
|
||||
webpack: {
|
||||
options: {
|
||||
plugins: [
|
||||
new webpack.ProvidePlugin({
|
||||
$: "jquery",
|
||||
jQuery: "jquery",
|
||||
moment: "moment-timezone"
|
||||
}),
|
||||
new webpack.BannerPlugin({
|
||||
banner: banner,
|
||||
raw: true,
|
||||
entryOnly: true
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
COMPILE_TIME: JSON.stringify(compileTime),
|
||||
COMPILE_MSG: JSON.stringify(grunt.option("compile-msg") || grunt.option("msg") || "")
|
||||
}),
|
||||
new ExtractTextPlugin("styles.css"),
|
||||
],
|
||||
options: webpackConfig,
|
||||
myConfig: webpackConfig,
|
||||
web: webpackProdConf(),
|
||||
},
|
||||
"webpack-dev-server": {
|
||||
options: webpackConfig,
|
||||
start: {
|
||||
mode: "development",
|
||||
target: "web",
|
||||
entry: Object.assign({
|
||||
main: "./src/web/index.js"
|
||||
}, moduleEntryPoints),
|
||||
resolve: {
|
||||
alias: {
|
||||
jquery: "jquery/src/jquery"
|
||||
"./config/modules/OpModules.mjs": "./config/modules/Default.mjs"
|
||||
}
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
loader: "babel-loader?compact=false"
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: ExtractTextPlugin.extract({
|
||||
use: "css-loader?minimize"
|
||||
})
|
||||
},
|
||||
{
|
||||
test: /\.less$/,
|
||||
use: ExtractTextPlugin.extract({
|
||||
use: [
|
||||
{ loader: "css-loader?minimize" },
|
||||
{ loader: "less-loader" }
|
||||
]
|
||||
})
|
||||
},
|
||||
{
|
||||
test: /\.(ico|eot|ttf|woff|woff2)$/,
|
||||
loader: "url-loader",
|
||||
options: {
|
||||
limit: 10000
|
||||
}
|
||||
},
|
||||
{ // First party images are saved as files to be cached
|
||||
test: /\.(png|jpg|gif|svg)$/,
|
||||
exclude: /node_modules/,
|
||||
loader: "file-loader",
|
||||
options: {
|
||||
name: "images/[name].[ext]"
|
||||
}
|
||||
},
|
||||
{ // Third party images are inlined
|
||||
test: /\.(png|jpg|gif|svg)$/,
|
||||
exclude: /web\/static/,
|
||||
loader: "url-loader",
|
||||
options: {
|
||||
limit: 10000
|
||||
}
|
||||
},
|
||||
]
|
||||
},
|
||||
stats: {
|
||||
children: false
|
||||
}
|
||||
},
|
||||
webDev: {
|
||||
target: "web",
|
||||
entry: "./src/web/index.js",
|
||||
output: {
|
||||
filename: "scripts.js",
|
||||
path: __dirname + "/build/dev"
|
||||
devServer: {
|
||||
port: grunt.option("port") || 8080,
|
||||
client: {
|
||||
logging: "error",
|
||||
overlay: true
|
||||
},
|
||||
hot: "only"
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin(BUILD_CONSTANTS),
|
||||
new HtmlWebpackPlugin({
|
||||
filename: "index.html",
|
||||
template: "./src/web/html/index.html",
|
||||
compileTime: compileTime
|
||||
chunks: ["main"],
|
||||
compileTime: compileTime,
|
||||
version: pkg.version,
|
||||
})
|
||||
],
|
||||
watch: true
|
||||
},
|
||||
webProd: {
|
||||
target: "web",
|
||||
entry: "./src/web/index.js",
|
||||
output: {
|
||||
filename: "scripts.js",
|
||||
path: __dirname + "/build/prod"
|
||||
},
|
||||
plugins: [
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
compress: {
|
||||
"screw_ie8": true,
|
||||
"dead_code": true,
|
||||
"unused": true,
|
||||
"warnings": false
|
||||
},
|
||||
comments: false,
|
||||
}),
|
||||
new HtmlWebpackPlugin({ // Main version
|
||||
filename: "index.html",
|
||||
template: "./src/web/html/index.html",
|
||||
compileTime: compileTime,
|
||||
minify: {
|
||||
removeComments: true,
|
||||
collapseWhitespace: true,
|
||||
minifyJS: true,
|
||||
minifyCSS: true
|
||||
}
|
||||
}),
|
||||
new HtmlWebpackPlugin({ // Inline version
|
||||
filename: "cyberchef.htm",
|
||||
template: "./src/web/html/index.html",
|
||||
compileTime: compileTime,
|
||||
inline: true,
|
||||
minify: {
|
||||
removeComments: true,
|
||||
collapseWhitespace: true,
|
||||
minifyJS: true,
|
||||
minifyCSS: true
|
||||
}
|
||||
}),
|
||||
]
|
||||
},
|
||||
tests: {
|
||||
target: "node",
|
||||
entry: "./test/index.js",
|
||||
output: {
|
||||
filename: "index.js",
|
||||
path: __dirname + "/build/test"
|
||||
}
|
||||
},
|
||||
node: {
|
||||
target: "node",
|
||||
entry: "./src/node/index.js",
|
||||
output: {
|
||||
filename: "CyberChef.js",
|
||||
path: __dirname + "/build/node",
|
||||
library: "CyberChef",
|
||||
libraryTarget: "commonjs2"
|
||||
}
|
||||
},
|
||||
zip: {
|
||||
standalone: {
|
||||
cwd: "build/prod/",
|
||||
src: [
|
||||
"build/prod/**/*",
|
||||
"!build/prod/index.html",
|
||||
"!build/prod/BundleAnalyzerReport.html",
|
||||
],
|
||||
dest: `build/prod/CyberChef_v${pkg.version}.zip`
|
||||
}
|
||||
},
|
||||
connect: {
|
||||
prod: {
|
||||
options: {
|
||||
port: grunt.option("port") || 8000,
|
||||
base: "build/prod/"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -289,14 +256,51 @@ module.exports = function (grunt) {
|
|||
ghPages: {
|
||||
options: {
|
||||
process: function (content, srcpath) {
|
||||
// Add Google Analytics code to index.html
|
||||
content = content.replace("</body></html>",
|
||||
grunt.file.read("src/web/static/ga.html") + "</body></html>");
|
||||
return grunt.template.process(content);
|
||||
}
|
||||
if (srcpath.indexOf("index.html") >= 0) {
|
||||
// Add Google Analytics code to index.html
|
||||
content = content.replace("</body></html>",
|
||||
grunt.file.read("src/web/static/ga.html") + "</body></html>");
|
||||
|
||||
// Add Structured Data for SEO
|
||||
content = content.replace("</head>",
|
||||
"<script type='application/ld+json'>" +
|
||||
JSON.stringify(JSON.parse(grunt.file.read("src/web/static/structuredData.json"))) +
|
||||
"</script></head>");
|
||||
return grunt.template.process(content, srcpath);
|
||||
} else {
|
||||
return content;
|
||||
}
|
||||
},
|
||||
noProcess: ["**", "!**/*.html"]
|
||||
},
|
||||
src: "build/prod/index.html",
|
||||
dest: "build/prod/index.html"
|
||||
files: [
|
||||
{
|
||||
src: ["build/prod/index.html"],
|
||||
dest: "build/prod/index.html"
|
||||
}
|
||||
]
|
||||
},
|
||||
standalone: {
|
||||
options: {
|
||||
process: function (content, srcpath) {
|
||||
if (srcpath.indexOf("index.html") >= 0) {
|
||||
// Replace download link with version number
|
||||
content = content.replace(/<a [^>]+>Download CyberChef.+?<\/a>/,
|
||||
`<span>Version ${pkg.version}</span>`);
|
||||
|
||||
return grunt.template.process(content, srcpath);
|
||||
} else {
|
||||
return content;
|
||||
}
|
||||
},
|
||||
noProcess: ["**", "!**/*.html"]
|
||||
},
|
||||
files: [
|
||||
{
|
||||
src: ["build/prod/index.html"],
|
||||
dest: `build/prod/CyberChef_v${pkg.version}.html`
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
chmod: {
|
||||
|
@ -305,29 +309,125 @@ module.exports = function (grunt) {
|
|||
mode: "755",
|
||||
},
|
||||
src: ["build/**/*", "build/"]
|
||||
},
|
||||
docs: {
|
||||
options: {
|
||||
mode: "755",
|
||||
},
|
||||
src: ["docs/**/*", "docs/"]
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
config: {
|
||||
files: ["src/core/operations/**/*", "!src/core/operations/index.mjs"],
|
||||
tasks: ["exec:generateNodeIndex", "exec:generateConfig"]
|
||||
}
|
||||
},
|
||||
concurrent: {
|
||||
dev: ["watch:config", "webpack-dev-server:start"],
|
||||
options: {
|
||||
logConcurrentOutput: true
|
||||
}
|
||||
},
|
||||
exec: {
|
||||
calcDownloadHash: {
|
||||
command: function () {
|
||||
switch (process.platform) {
|
||||
case "darwin":
|
||||
return chainCommands([
|
||||
`shasum -a 256 build/prod/CyberChef_v${pkg.version}.zip | awk '{print $1;}' > build/prod/sha256digest.txt`,
|
||||
`sed -i '' -e "s/DOWNLOAD_HASH_PLACEHOLDER/$(cat build/prod/sha256digest.txt)/" build/prod/index.html`
|
||||
]);
|
||||
default:
|
||||
return chainCommands([
|
||||
`sha256sum build/prod/CyberChef_v${pkg.version}.zip | awk '{print $1;}' > build/prod/sha256digest.txt`,
|
||||
`sed -i -e "s/DOWNLOAD_HASH_PLACEHOLDER/$(cat build/prod/sha256digest.txt)/" build/prod/index.html`
|
||||
]);
|
||||
}
|
||||
},
|
||||
},
|
||||
repoSize: {
|
||||
command: [
|
||||
command: chainCommands([
|
||||
"git ls-files | wc -l | xargs printf '\n%b\ttracked files\n'",
|
||||
"du -hs | egrep -o '^[^\t]*' | xargs printf '%b\trepository size\n'"
|
||||
].join(";"),
|
||||
]),
|
||||
stderr: false
|
||||
},
|
||||
cleanGit: {
|
||||
command: "git gc --prune=now --aggressive"
|
||||
},
|
||||
},
|
||||
execute: {
|
||||
test: "build/test/index.js"
|
||||
sitemap: {
|
||||
command: `node ${nodeFlags} src/web/static/sitemap.mjs > build/prod/sitemap.xml`,
|
||||
sync: true
|
||||
},
|
||||
generateConfig: {
|
||||
command: chainCommands([
|
||||
"echo '\n--- Regenerating config files. ---'",
|
||||
"echo [] > src/core/config/OperationConfig.json",
|
||||
`node ${nodeFlags} src/core/config/scripts/generateOpsIndex.mjs`,
|
||||
`node ${nodeFlags} src/core/config/scripts/generateConfig.mjs`,
|
||||
"echo '--- Config scripts finished. ---\n'"
|
||||
]),
|
||||
sync: true
|
||||
},
|
||||
generateNodeIndex: {
|
||||
command: chainCommands([
|
||||
"echo '\n--- Regenerating node index ---'",
|
||||
`node ${nodeFlags} src/node/config/scripts/generateNodeIndex.mjs`,
|
||||
"echo '--- Node index generated. ---\n'"
|
||||
]),
|
||||
sync: true
|
||||
},
|
||||
browserTests: {
|
||||
command: "./node_modules/.bin/nightwatch --env prod"
|
||||
},
|
||||
setupNodeConsumers: {
|
||||
command: chainCommands([
|
||||
"echo '\n--- Testing node consumers ---'",
|
||||
"npm link",
|
||||
`mkdir ${nodeConsumerTestPath}`,
|
||||
`cp tests/node/consumers/* ${nodeConsumerTestPath}`,
|
||||
`cd ${nodeConsumerTestPath}`,
|
||||
"npm link cyberchef"
|
||||
]),
|
||||
sync: true
|
||||
},
|
||||
teardownNodeConsumers: {
|
||||
command: chainCommands([
|
||||
`rm -rf ${nodeConsumerTestPath}`,
|
||||
"echo '\n--- Node consumer tests complete ---'"
|
||||
]),
|
||||
},
|
||||
testCJSNodeConsumer: {
|
||||
command: chainCommands([
|
||||
`cd ${nodeConsumerTestPath}`,
|
||||
`node ${nodeFlags} cjs-consumer.js`,
|
||||
]),
|
||||
stdout: false,
|
||||
},
|
||||
testESMNodeConsumer: {
|
||||
command: chainCommands([
|
||||
`cd ${nodeConsumerTestPath}`,
|
||||
`node ${nodeFlags} esm-consumer.mjs`,
|
||||
]),
|
||||
stdout: false,
|
||||
},
|
||||
fixCryptoApiImports: {
|
||||
command: function () {
|
||||
switch (process.platform) {
|
||||
case "darwin":
|
||||
return `find ./node_modules/crypto-api/src/ \\( -type d -name .git -prune \\) -o -type f -print0 | xargs -0 sed -i '' -e '/\\.mjs/!s/\\(from "\\.[^"]*\\)";/\\1.mjs";/g'`;
|
||||
default:
|
||||
return `find ./node_modules/crypto-api/src/ \\( -type d -name .git -prune \\) -o -type f -print0 | xargs -0 sed -i -e '/\\.mjs/!s/\\(from "\\.[^"]*\\)";/\\1.mjs";/g'`;
|
||||
}
|
||||
},
|
||||
stdout: false
|
||||
},
|
||||
fixSnackbarMarkup: {
|
||||
command: function () {
|
||||
switch (process.platform) {
|
||||
case "darwin":
|
||||
return `sed -i '' 's/<div id=snackbar-container\\/>/<div id=snackbar-container>/g' ./node_modules/snackbarjs/src/snackbar.js`;
|
||||
default:
|
||||
return `sed -i 's/<div id=snackbar-container\\/>/<div id=snackbar-container>/g' ./node_modules/snackbarjs/src/snackbar.js`;
|
||||
}
|
||||
},
|
||||
stdout: false
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
};
|
||||
|
|
104
README.md
104
README.md
|
@ -1,10 +1,16 @@
|
|||
# CyberChef
|
||||
|
||||
[](https://github.com/gchq/CyberChef/actions?query=workflow%3A%22Master+Build%2C+Test+%26+Deploy%22)
|
||||
[](https://www.npmjs.com/package/cyberchef)
|
||||
[](https://github.com/gchq/CyberChef/blob/master/LICENSE)
|
||||
[](https://gitter.im/gchq/CyberChef?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||
|
||||
|
||||
#### *The Cyber Swiss Army Knife*
|
||||
|
||||
CyberChef is a simple, intuitive web app for carrying out all manner of "cyber" operations within a web browser. These operations include creating hexdumps, simple encoding like XOR or Base64, more complex encryption like AES, DES and Blowfish, data compression and decompression, calculating hashes and checksums, IPv6 and X.509 parsing, and much more.
|
||||
CyberChef is a simple, intuitive web app for carrying out all manner of "cyber" operations within a web browser. These operations include simple encoding like XOR and Base64, more complex encryption like AES, DES and Blowfish, creating binary and hexdumps, compression and decompression of data, calculating hashes and checksums, IPv6 and X.509 parsing, changing character encodings, and much more.
|
||||
|
||||
The tool is designed to enable both technical and non-technical analysts to manipulate data in complex ways without having to deal with complex tools or algorithms. It was conceived, designed, built and incrementally improved by an analyst in their 10% innovation time over several years. Every effort has been made to structure the code in a readable and extendable format, however it should be noted that the analyst is not a professional developer and the code has not been peer-reviewed for compliance with a formal specification.
|
||||
The tool is designed to enable both technical and non-technical analysts to manipulate data in complex ways without having to deal with complex tools or algorithms. It was conceived, designed, built and incrementally improved by an analyst in their 10% innovation time over several years.
|
||||
|
||||
## Live demo
|
||||
|
||||
|
@ -14,15 +20,31 @@ Cryptographic operations in CyberChef should not be relied upon to provide secur
|
|||
|
||||
[A live demo can be found here][1] - have fun!
|
||||
|
||||
## Containers
|
||||
|
||||
If you would like to try out CyberChef locally you can either build it yourself:
|
||||
|
||||
```bash
|
||||
docker build --tag cyberchef --ulimit nofile=10000 .
|
||||
docker run -it -p 8080:80 cyberchef
|
||||
```
|
||||
|
||||
Or you can use our image directly:
|
||||
|
||||
```bash
|
||||
docker run -it -p 8080:80 ghcr.io/gchq/cyberchef:latest
|
||||
```
|
||||
|
||||
This image is built and published through our [GitHub Workflows](.github/workflows/releases.yml)
|
||||
|
||||
## How it works
|
||||
|
||||
There are four main areas in CyberChef:
|
||||
|
||||
1. The **input** box in the top right, where you can paste, type or drag the data you want to operate on.
|
||||
2. The **output** box in the bottom right, where the outcome of the specified processing will be displayed.
|
||||
1. The **input** box in the top right, where you can paste, type or drag the text or file you want to operate on.
|
||||
2. The **output** box in the bottom right, where the outcome of your processing will be displayed.
|
||||
3. The **operations** list on the far left, where you can find all the operations that CyberChef is capable of in categorised lists, or by searching.
|
||||
4. The **recipe** area in the middle, where you drag the operations that you want to use and specify arguments and options.
|
||||
4. The **recipe** area in the middle, where you can drag the operations that you want to use and specify arguments and options.
|
||||
|
||||
You can use as many operations as you like in simple or complex ways. Some examples are as follows:
|
||||
|
||||
|
@ -30,60 +52,86 @@ You can use as many operations as you like in simple or complex ways. Some examp
|
|||
- [Convert a date and time to a different time zone][3]
|
||||
- [Parse a Teredo IPv6 address][4]
|
||||
- [Convert data from a hexdump, then decompress][5]
|
||||
- [Display multiple timestamps as full dates][6]
|
||||
- [Carry out different operations on data of different types][7]
|
||||
- [Decrypt and disassemble shellcode][6]
|
||||
- [Display multiple timestamps as full dates][7]
|
||||
- [Carry out different operations on data of different types][8]
|
||||
- [Use parts of the input as arguments to operations][9]
|
||||
- [Perform AES decryption, extracting the IV from the beginning of the cipher stream][10]
|
||||
- [Automagically detect several layers of nested encoding][12]
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- Drag and drop
|
||||
- Operations can be dragged in and out of the recipe list, or reorganised.
|
||||
- Files can be dragged over the input box to load them directly.
|
||||
- Files up to 2GB can be dragged over the input box to load them directly into the browser.
|
||||
- Auto Bake
|
||||
- Whenever you modify the input or the recipe, CyberChef will automatically “bake” for you and produce the output immediately.
|
||||
- Whenever you modify the input or the recipe, CyberChef will automatically "bake" for you and produce the output immediately.
|
||||
- This can be turned off and operated manually if it is affecting performance (if the input is very large, for instance).
|
||||
- If any bake takes longer than 200 milliseconds, auto bake will be switched off automatically to prevent further performance issues.
|
||||
- Automated encoding detection
|
||||
- CyberChef uses [a number of techniques](https://github.com/gchq/CyberChef/wiki/Automatic-detection-of-encoded-data-using-CyberChef-Magic) to attempt to automatically detect which encodings your data is under. If it finds a suitable operation that make sense of your data, it displays the 'magic' icon in the Output field which you can click to decode your data.
|
||||
- Breakpoints
|
||||
- You can set breakpoints on any operation in your recipe to pause execution before running it.
|
||||
- You can also step through the recipe one operation at a time to see what the data looks like at each stage.
|
||||
- Save and load recipes
|
||||
- If you come up with an awesome recipe that you know you’ll want to use again, just click save and add it to your local storage. It'll be waiting for you next time you visit CyberChef.
|
||||
- You can also copy a URL which includes your recipe and input which can be shared with others.
|
||||
- If you come up with an awesome recipe that you know you’ll want to use again, just click "Save recipe" and add it to your local storage. It'll be waiting for you next time you visit CyberChef.
|
||||
- You can also copy the URL, which includes your recipe and input, to easily share it with others.
|
||||
- Search
|
||||
- If you know the name of the operation you want or a word associated with it, start typing it into the search field and any matching operations will immediately be shown.
|
||||
- Highlighting
|
||||
- When you highlight text in the input or output, the offset and length values will be displayed and, if possible, the corresponding data will be highlighted in the output or input respectively (example: [highlight the word 'question' in the input to see where it appears in the output][8]).
|
||||
- When you highlight text in the input or output, the offset and length values will be displayed and, if possible, the corresponding data will be highlighted in the output or input respectively (example: [highlight the word 'question' in the input to see where it appears in the output][11]).
|
||||
- Save to file and load from file
|
||||
- You can save the output to a file at any time or load a file by dragging and dropping it into the input field (note that files larger than about 500kb may cause your browser to hang or even crash due to the way that browsers handle large amounts of textual data).
|
||||
- You can save the output to a file at any time or load a file by dragging and dropping it into the input field. Files up to around 2GB are supported (depending on your browser), however, some operations may take a very long time to run over this much data.
|
||||
- CyberChef is entirely client-side
|
||||
- It should be noted that none of your input or recipe configuration is ever sent to the CyberChef web server - all processing is carried out within your browser, on your own computer.
|
||||
- Due to this feature, CyberChef can be compiled into a single HTML file. You can download this file and drop it into a virtual machine, share it with other people, or use it independently on your desktop.
|
||||
- It should be noted that none of your recipe configuration or input (either text or files) is ever sent to the CyberChef web server - all processing is carried out within your browser, on your own computer.
|
||||
- Due to this feature, CyberChef can be downloaded and run locally. You can use the link in the top left corner of the app to download a full copy of CyberChef and drop it into a virtual machine, share it with other people, or host it in a closed network.
|
||||
|
||||
|
||||
## Deep linking
|
||||
|
||||
By manipulating CyberChef's URL hash, you can change the initial settings with which the page opens.
|
||||
The format is `https://gchq.github.io/CyberChef/#recipe=Operation()&input=...`
|
||||
|
||||
Supported arguments are `recipe`, `input` (encoded in Base64), and `theme`.
|
||||
|
||||
|
||||
## Browser support
|
||||
|
||||
CyberChef is built to support Google Chrome 40+, Mozilla Firefox 35+ and Microsoft Edge 14+.
|
||||
CyberChef is built to support
|
||||
|
||||
- Google Chrome 50+
|
||||
- Mozilla Firefox 38+
|
||||
|
||||
|
||||
## Node.js support
|
||||
|
||||
CyberChef is built to fully support Node.js `v16`. For more information, see the ["Node API" wiki page](https://github.com/gchq/CyberChef/wiki/Node-API)
|
||||
|
||||
|
||||
## Contributing
|
||||
|
||||
An installation walkthrough, how-to guides for adding new operations, descriptions of the repository structure, available data types and coding conventions can all be found in the project [wiki pages](https://github.com/gchq/CyberChef/wiki).
|
||||
Contributing a new operation to CyberChef is super easy! The quickstart script will walk you through the process. If you can write basic JavaScript, you can write a CyberChef operation.
|
||||
|
||||
An installation walkthrough, how-to guides for adding new operations and themes, descriptions of the repository structure, available data types and coding conventions can all be found in the ["Contributing" wiki page](https://github.com/gchq/CyberChef/wiki/Contributing).
|
||||
|
||||
- Sign the [GCHQ Contributor Licence Agreement](https://github.com/gchq/Gaffer/wiki/GCHQ-OSS-Contributor-License-Agreement-V1.0)
|
||||
- Push your changes to your fork.
|
||||
- Submit a pull request.
|
||||
- Submit a pull request. If you are doing this for the first time, you will be prompted to sign the [GCHQ Contributor Licence Agreement](https://cla-assistant.io/gchq/CyberChef) via the CLA assistant on the pull request. This will also ask whether you are happy for GCHQ to contact you about a token of thanks for your contribution, or about job opportunities at GCHQ.
|
||||
|
||||
|
||||
## Licencing
|
||||
|
||||
CyberChef is released under the [Apache 2.0 Licence](https://www.apache.org/licenses/LICENSE-2.0) and is covered by [Crown Copyright](https://www.nationalarchives.gov.uk/information-management/re-using-public-sector-information/copyright-and-re-use/crown-copyright/).
|
||||
CyberChef is released under the [Apache 2.0 Licence](https://www.apache.org/licenses/LICENSE-2.0) and is covered by [Crown Copyright](https://www.nationalarchives.gov.uk/information-management/re-using-public-sector-information/uk-government-licensing-framework/crown-copyright/).
|
||||
|
||||
|
||||
[1]: https://gchq.github.io/CyberChef
|
||||
[2]: https://gchq.github.io/CyberChef/?recipe=%5B%7B%22op%22%3A%22From%20Base64%22%2C%22args%22%3A%5B%22A-Za-z0-9%2B%2F%3D%22%2Ctrue%5D%7D%5D&input=VTI4Z2JHOXVaeUJoYm1RZ2RHaGhibXR6SUdadmNpQmhiR3dnZEdobElHWnBjMmd1
|
||||
[3]: https://gchq.github.io/CyberChef/?recipe=%5B%7B%22op%22%3A%22Translate%20DateTime%20Format%22%2C%22args%22%3A%5B%22Standard%20date%20and%20time%22%2C%22DD%2FMM%2FYYYY%20HH%3Amm%3Ass%22%2C%22UTC%22%2C%22dddd%20Do%20MMMM%20YYYY%20HH%3Amm%3Ass%20Z%20z%22%2C%22Australia%2FQueensland%22%5D%7D%5D&input=MTUvMDYvMjAxNSAyMDo0NTowMA
|
||||
[4]: https://gchq.github.io/CyberChef/?recipe=%5B%7B%22op%22%3A%22Parse%20IPv6%20address%22%2C%22args%22%3A%5B%5D%7D%5D&input=MjAwMTowMDAwOjQxMzY6ZTM3ODo4MDAwOjYzYmY6M2ZmZjpmZGQy
|
||||
[5]: https://gchq.github.io/CyberChef/?recipe=%5B%7B%22op%22%3A%22From%20Hexdump%22%2C%22args%22%3A%5B%5D%7D%2C%7B%22op%22%3A%22Gunzip%22%2C%22args%22%3A%5B%5D%7D%5D&input=MDAwMDAwMDAgIDFmIDhiIDA4IDAwIDEyIGJjIGYzIDU3IDAwIGZmIDBkIGM3IGMxIDA5IDAwIDIwICB8Li4uLi6881cu%2Fy7HwS4uIHwKMDAwMDAwMTAgIDA4IDA1IGQwIDU1IGZlIDA0IDJkIGQzIDA0IDFmIGNhIDhjIDQ0IDIxIDViIGZmICB8Li7QVf4uLdMuLsouRCFb%2F3wKMDAwMDAwMjAgIDYwIGM3IGQ3IDAzIDE2IGJlIDQwIDFmIDc4IDRhIDNmIDA5IDg5IDBiIDlhIDdkICB8YMfXLi6%2BQC54Sj8uLi4ufXwKMDAwMDAwMzAgIDRlIGM4IDRlIDZkIDA1IDFlIDAxIDhiIDRjIDI0IDAwIDAwIDAwICAgICAgICAgICB8TshObS4uLi5MJC4uLnw
|
||||
[6]: https://gchq.github.io/CyberChef/?recipe=%5B%7B%22op%22%3A%22Fork%22%2C%22args%22%3A%5B%22%5C%5Cn%22%2C%22%5C%5Cn%22%5D%7D%2C%7B%22op%22%3A%22From%20UNIX%20Timestamp%22%2C%22args%22%3A%5B%22Seconds%20(s)%22%5D%7D%5D&input=OTc4MzQ2ODAwCjEwMTI2NTEyMDAKMTA0NjY5NjQwMAoxMDgxMDg3MjAwCjExMTUzMDUyMDAKMTE0OTYwOTYwMA
|
||||
[7]: https://gchq.github.io/CyberChef/?recipe=%5B%7B%22op%22%3A%22Fork%22%2C%22args%22%3A%5B%22%5C%5Cn%22%2C%22%5C%5Cn%22%5D%7D%2C%7B%22op%22%3A%22Conditional%20Jump%22%2C%22args%22%3A%5B%221%22%2C%222%22%2C%2210%22%5D%7D%2C%7B%22op%22%3A%22To%20Hex%22%2C%22args%22%3A%5B%22Space%22%5D%7D%2C%7B%22op%22%3A%22Return%22%2C%22args%22%3A%5B%5D%7D%2C%7B%22op%22%3A%22To%20Base64%22%2C%22args%22%3A%5B%22A-Za-z0-9%2B%2F%3D%22%5D%7D%5D&input=U29tZSBkYXRhIHdpdGggYSAxIGluIGl0ClNvbWUgZGF0YSB3aXRoIGEgMiBpbiBpdA
|
||||
[8]: https://gchq.github.io/CyberChef/?recipe=%5B%7B%22op%22%3A%22XOR%22%2C%22args%22%3A%5B%7B%22option%22%3A%22Hex%22%2C%22string%22%3A%223a%22%7D%2Cfalse%2Cfalse%5D%7D%2C%7B%22op%22%3A%22To%20Hexdump%22%2C%22args%22%3A%5B%2216%22%2Cfalse%2Cfalse%5D%7D%5D&input=VGhlIGFuc3dlciB0byB0aGUgdWx0aW1hdGUgcXVlc3Rpb24gb2YgbGlmZSwgdGhlIFVuaXZlcnNlLCBhbmQgZXZlcnl0aGluZyBpcyA0Mi4
|
||||
[2]: https://gchq.github.io/CyberChef/#recipe=From_Base64('A-Za-z0-9%2B/%3D',true)&input=VTI4Z2JHOXVaeUJoYm1RZ2RHaGhibXR6SUdadmNpQmhiR3dnZEdobElHWnBjMmd1
|
||||
[3]: https://gchq.github.io/CyberChef/#recipe=Translate_DateTime_Format('Standard%20date%20and%20time','DD/MM/YYYY%20HH:mm:ss','UTC','dddd%20Do%20MMMM%20YYYY%20HH:mm:ss%20Z%20z','Australia/Queensland')&input=MTUvMDYvMjAxNSAyMDo0NTowMA
|
||||
[4]: https://gchq.github.io/CyberChef/#recipe=Parse_IPv6_address()&input=MjAwMTowMDAwOjQxMzY6ZTM3ODo4MDAwOjYzYmY6M2ZmZjpmZGQy
|
||||
[5]: https://gchq.github.io/CyberChef/#recipe=From_Hexdump()Gunzip()&input=MDAwMDAwMDAgIDFmIDhiIDA4IDAwIDEyIGJjIGYzIDU3IDAwIGZmIDBkIGM3IGMxIDA5IDAwIDIwICB8Li4uLi6881cu/y7HwS4uIHwKMDAwMDAwMTAgIDA4IDA1IGQwIDU1IGZlIDA0IDJkIGQzIDA0IDFmIGNhIDhjIDQ0IDIxIDViIGZmICB8Li7QVf4uLdMuLsouRCFb/3wKMDAwMDAwMjAgIDYwIGM3IGQ3IDAzIDE2IGJlIDQwIDFmIDc4IDRhIDNmIDA5IDg5IDBiIDlhIDdkICB8YMfXLi6%2BQC54Sj8uLi4ufXwKMDAwMDAwMzAgIDRlIGM4IDRlIDZkIDA1IDFlIDAxIDhiIDRjIDI0IDAwIDAwIDAwICAgICAgICAgICB8TshObS4uLi5MJC4uLnw
|
||||
[6]: https://gchq.github.io/CyberChef/#recipe=RC4(%7B'option':'UTF8','string':'secret'%7D,'Hex','Hex')Disassemble_x86('64','Full%20x86%20architecture',16,0,true,true)&input=MjFkZGQyNTQwMTYwZWU2NWZlMDc3NzEwM2YyYTM5ZmJlNWJjYjZhYTBhYWJkNDE0ZjkwYzZjYWY1MzEyNzU0YWY3NzRiNzZiM2JiY2QxOTNjYjNkZGZkYmM1YTI2NTMzYTY4NmI1OWI4ZmVkNGQzODBkNDc0NDIwMWFlYzIwNDA1MDcxMzhlMmZlMmIzOTUwNDQ2ZGIzMWQyYmM2MjliZTRkM2YyZWIwMDQzYzI5M2Q3YTVkMjk2MmMwMGZlNmRhMzAwNzJkOGM1YTZiNGZlN2Q4NTlhMDQwZWVhZjI5OTczMzYzMDJmNWEwZWMxOQ
|
||||
[7]: https://gchq.github.io/CyberChef/#recipe=Fork('%5C%5Cn','%5C%5Cn',false)From_UNIX_Timestamp('Seconds%20(s)')&input=OTc4MzQ2ODAwCjEwMTI2NTEyMDAKMTA0NjY5NjQwMAoxMDgxMDg3MjAwCjExMTUzMDUyMDAKMTE0OTYwOTYwMA
|
||||
[8]: https://gchq.github.io/CyberChef/#recipe=Fork('%5C%5Cn','%5C%5Cn',false)Conditional_Jump('1',false,'base64',10)To_Hex('Space')Return()Label('base64')To_Base64('A-Za-z0-9%2B/%3D')&input=U29tZSBkYXRhIHdpdGggYSAxIGluIGl0ClNvbWUgZGF0YSB3aXRoIGEgMiBpbiBpdA
|
||||
[9]: https://gchq.github.io/CyberChef/#recipe=Register('key%3D(%5B%5C%5Cda-f%5D*)',true,false)Find_/_Replace(%7B'option':'Regex','string':'.*data%3D(.*)'%7D,'$1',true,false,true)RC4(%7B'option':'Hex','string':'$R0'%7D,'Hex','Latin1')&input=aHR0cDovL21hbHdhcmV6LmJpei9iZWFjb24ucGhwP2tleT0wZTkzMmE1YyZkYXRhPThkYjdkNWViZTM4NjYzYTU0ZWNiYjMzNGUzZGIxMQ
|
||||
[10]: https://gchq.github.io/CyberChef/#recipe=Register('(.%7B32%7D)',true,false)Drop_bytes(0,32,false)AES_Decrypt(%7B'option':'Hex','string':'1748e7179bd56570d51fa4ba287cc3e5'%7D,%7B'option':'Hex','string':'$R0'%7D,'CTR','Hex','Raw',%7B'option':'Hex','string':''%7D)&input=NTFlMjAxZDQ2MzY5OGVmNWY3MTdmNzFmNWI0NzEyYWYyMGJlNjc0YjNiZmY1M2QzODU0NjM5NmVlNjFkYWFjNDkwOGUzMTljYTNmY2Y3MDg5YmZiNmIzOGVhOTllNzgxZDI2ZTU3N2JhOWRkNmYzMTFhMzk0MjBiODk3OGU5MzAxNGIwNDJkNDQ3MjZjYWVkZjU0MzZlYWY2NTI0MjljMGRmOTRiNTIxNjc2YzdjMmNlODEyMDk3YzI3NzI3M2M3YzcyY2Q4OWFlYzhkOWZiNGEyNzU4NmNjZjZhYTBhZWUyMjRjMzRiYTNiZmRmN2FlYjFkZGQ0Nzc2MjJiOTFlNzJjOWU3MDlhYjYwZjhkYWY3MzFlYzBjYzg1Y2UwZjc0NmZmMTU1NGE1YTNlYzI5MWNhNDBmOWU2MjlhODcyNTkyZDk4OGZkZDgzNDUzNGFiYTc5YzFhZDE2NzY3NjlhN2MwMTBiZjA0NzM5ZWNkYjY1ZDk1MzAyMzcxZDYyOWQ5ZTM3ZTdiNGEzNjFkYTQ2OGYxZWQ1MzU4OTIyZDJlYTc1MmRkMTFjMzY2ZjMwMTdiMTRhYTAxMWQyYWYwM2M0NGY5NTU3OTA5OGExNWUzY2Y5YjQ0ODZmOGZmZTljMjM5ZjM0ZGU3MTUxZjZjYTY1MDBmZTRiODUwYzNmMWMwMmU4MDFjYWYzYTI0NDY0NjE0ZTQyODAxNjE1YjhmZmFhMDdhYzgyNTE0OTNmZmRhN2RlNWRkZjMzNjg4ODBjMmI5NWIwMzBmNDFmOGYxNTA2NmFkZDA3MWE2NmNmNjBlNWY0NmYzYTIzMGQzOTdiNjUyOTYzYTIxYTUzZg
|
||||
[11]: https://gchq.github.io/CyberChef/#recipe=XOR(%7B'option':'Hex','string':'3a'%7D,'Standard',false)To_Hexdump(16,false,false)&input=VGhlIGFuc3dlciB0byB0aGUgdWx0aW1hdGUgcXVlc3Rpb24gb2YgbGlmZSwgdGhlIFVuaXZlcnNlLCBhbmQgZXZlcnl0aGluZyBpcyA0Mi4
|
||||
[12]: https://gchq.github.io/CyberChef/#recipe=Magic(3,false,false)&input=V1VhZ3dzaWFlNm1QOGdOdENDTFVGcENwQ0IyNlJtQkRvREQ4UGFjZEFtekF6QlZqa0syUXN0RlhhS2hwQzZpVVM3UkhxWHJKdEZpc29SU2dvSjR3aGptMWFybTg2NHFhTnE0UmNmVW1MSHJjc0FhWmM1VFhDWWlmTmRnUzgzZ0RlZWpHWDQ2Z2FpTXl1QlY2RXNrSHQxc2NnSjg4eDJ0TlNvdFFEd2JHWTFtbUNvYjJBUkdGdkNLWU5xaU45aXBNcTFaVTFtZ2tkYk51R2NiNzZhUnRZV2hDR1VjOGc5M1VKdWRoYjhodHNoZVpud1RwZ3FoeDgzU1ZKU1pYTVhVakpUMnptcEM3dVhXdHVtcW9rYmRTaTg4WXRrV0RBYzFUb291aDJvSDRENGRkbU5LSldVRHBNd21uZ1VtSzE0eHdtb21jY1BRRTloTTE3MkFQblNxd3hkS1ExNzJSa2NBc3lzbm1qNWdHdFJtVk5OaDJzMzU5d3I2bVMyUVJQ
|
||||
|
|
26
SECURITY.md
Normal file
26
SECURITY.md
Normal file
|
@ -0,0 +1,26 @@
|
|||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
CyberChef is supported on a best endeavours basis. Patches will be applied to
|
||||
the latest version rather than retroactively to older versions. To ensure you
|
||||
are using the most secure version of CyberChef, please make sure you have the
|
||||
[latest release](https://github.com/gchq/CyberChef/releases/latest). The
|
||||
official [live demo](https://gchq.github.io/CyberChef/) is always up to date.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
In most scenarios, the most appropriate way to report a vulnerability is to
|
||||
[raise a new issue](https://github.com/gchq/CyberChef/issues/new/choose)
|
||||
describing the problem in as much detail as possible, ideally with examples.
|
||||
This will obviously be public. If you feel that the vulnerability is
|
||||
significant enough to warrant a private disclosure, please email
|
||||
[oss@gchq.gov.uk](mailto:oss@gchq.gov.uk) and
|
||||
[n1474335@gmail.com](mailto:n1474335@gmail.com).
|
||||
|
||||
Disclosures of vulnerabilities in CyberChef are always welcomed. Whilst we aim
|
||||
to write clean and secure code free from bugs, we recognise that this is an open
|
||||
source project written by analysts in their spare time, relying on dozens of
|
||||
open source libraries that are modified and updated on a regular basis. We hope
|
||||
that the community will continue to support us as we endeavour to maintain and
|
||||
develop this tool together.
|
27
babel.config.js
Normal file
27
babel.config.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
module.exports = function(api) {
|
||||
api.cache.forever();
|
||||
|
||||
return {
|
||||
"presets": [
|
||||
["@babel/preset-env", {
|
||||
"modules": false,
|
||||
"useBuiltIns": "entry",
|
||||
"corejs": 3
|
||||
}]
|
||||
],
|
||||
"plugins": [
|
||||
"dynamic-import-node",
|
||||
"@babel/plugin-syntax-import-assertions",
|
||||
[
|
||||
"babel-plugin-transform-builtin-extend", {
|
||||
"globals": ["Error"]
|
||||
}
|
||||
],
|
||||
[
|
||||
"@babel/plugin-transform-runtime", {
|
||||
"regenerator": true
|
||||
}
|
||||
]
|
||||
]
|
||||
};
|
||||
};
|
BIN
docs/favicon.ico
BIN
docs/favicon.ico
Binary file not shown.
Before Width: | Height: | Size: 1.1 KiB |
|
@ -1,29 +0,0 @@
|
|||
{
|
||||
"tags": {
|
||||
"allowUnknownTags": true
|
||||
},
|
||||
"plugins": ["plugins/markdown"],
|
||||
"templates": {
|
||||
"systemName": "CyberChef",
|
||||
"footer": "",
|
||||
"copyright": "© Crown Copyright 2016",
|
||||
"navType": "inline",
|
||||
"theme": "cerulean",
|
||||
"linenums": true,
|
||||
"collapseSymbols": false,
|
||||
"inverseNav": true,
|
||||
"outputSourceFiles": true,
|
||||
"outputSourcePath": true,
|
||||
"dateFormat": "ddd MMM Do YYYY",
|
||||
"sort": false,
|
||||
"logoFile": "../build/prod/images/cyberchef-32x32.png",
|
||||
"cleverLinks": false,
|
||||
"monospaceLinks": false,
|
||||
"protocol": "html://",
|
||||
"methodHeadingReturns": false
|
||||
},
|
||||
"markdown": {
|
||||
"parser": "gfm",
|
||||
"hardwrap": true
|
||||
}
|
||||
}
|
32
nightwatch.json
Normal file
32
nightwatch.json
Normal file
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"src_folders": ["tests/browser"],
|
||||
"exclude": ["tests/browser/browserUtils.js"],
|
||||
"output_folder": "tests/browser/output",
|
||||
|
||||
"test_settings": {
|
||||
|
||||
"default": {
|
||||
"launch_url": "http://localhost:8080",
|
||||
"webdriver": {
|
||||
"start_process": true,
|
||||
"server_path": "./node_modules/.bin/chromedriver",
|
||||
"port": 9515,
|
||||
"log_path": "tests/browser/output"
|
||||
},
|
||||
"desiredCapabilities": {
|
||||
"browserName": "chrome"
|
||||
},
|
||||
"enable_fail_fast": true
|
||||
},
|
||||
|
||||
"dev": {
|
||||
"launch_url": "http://localhost:8080"
|
||||
},
|
||||
|
||||
"prod": {
|
||||
"launch_url": "http://localhost:8000/index.html"
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
15017
package-lock.json
generated
Normal file
15017
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
217
package.json
217
package.json
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "cyberchef",
|
||||
"version": "5.1.0",
|
||||
"description": "CyberChef is a simple, intuitive web app for analysing and decoding data within a web browser.",
|
||||
"version": "10.8.2",
|
||||
"description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.",
|
||||
"author": "n1474335 <n1474335@gmail.com>",
|
||||
"homepage": "https://gchq.github.io/CyberChef",
|
||||
"copyright": "Crown copyright 2016",
|
||||
|
@ -19,67 +19,180 @@
|
|||
"hex",
|
||||
"encoding",
|
||||
"format",
|
||||
"cybersecurity"
|
||||
"cybersecurity",
|
||||
"data manipulation",
|
||||
"data analysis"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/gchq/CyberChef/"
|
||||
},
|
||||
"main": "src/node/wrapper.js",
|
||||
"exports": {
|
||||
"import": "./src/node/index.mjs",
|
||||
"require": "./src/node/wrapper.js"
|
||||
},
|
||||
"bugs": "https://github.com/gchq/CyberChef/issues",
|
||||
"browserslist": [
|
||||
"Chrome >= 50",
|
||||
"Firefox >= 38",
|
||||
"node >= 16"
|
||||
],
|
||||
"devDependencies": {
|
||||
"babel-core": "^6.24.0",
|
||||
"babel-loader": "^6.4.0",
|
||||
"babel-polyfill": "^6.23.0",
|
||||
"babel-preset-env": "^1.2.2",
|
||||
"css-loader": "^0.27.3",
|
||||
"exports-loader": "^0.6.4",
|
||||
"extract-text-webpack-plugin": "^2.1.0",
|
||||
"file-loader": "^0.10.1",
|
||||
"grunt": ">=0.4.5",
|
||||
"@babel/core": "^7.23.9",
|
||||
"@babel/eslint-parser": "^7.23.10",
|
||||
"@babel/plugin-syntax-import-assertions": "^7.23.3",
|
||||
"@babel/plugin-transform-runtime": "^7.23.9",
|
||||
"@babel/preset-env": "^7.23.9",
|
||||
"@babel/runtime": "^7.23.9",
|
||||
"@codemirror/commands": "^6.3.3",
|
||||
"@codemirror/language": "^6.10.1",
|
||||
"@codemirror/search": "^6.5.5",
|
||||
"@codemirror/state": "^6.4.0",
|
||||
"@codemirror/view": "^6.23.1",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"babel-loader": "^9.1.3",
|
||||
"babel-plugin-dynamic-import-node": "^2.3.3",
|
||||
"babel-plugin-transform-builtin-extend": "1.1.2",
|
||||
"base64-loader": "^1.0.0",
|
||||
"chromedriver": "^121.0.0",
|
||||
"cli-progress": "^3.12.0",
|
||||
"colors": "^1.4.0",
|
||||
"copy-webpack-plugin": "^12.0.2",
|
||||
"core-js": "^3.35.1",
|
||||
"css-loader": "6.10.0",
|
||||
"eslint": "^8.56.0",
|
||||
"grunt": "^1.6.1",
|
||||
"grunt-chmod": "~1.1.1",
|
||||
"grunt-contrib-clean": "~1.0.0",
|
||||
"grunt-concurrent": "^3.0.0",
|
||||
"grunt-contrib-clean": "~2.0.1",
|
||||
"grunt-contrib-connect": "^4.0.0",
|
||||
"grunt-contrib-copy": "~1.0.0",
|
||||
"grunt-eslint": "^19.0.0",
|
||||
"grunt-exec": "~1.0.1",
|
||||
"grunt-execute": "^0.2.2",
|
||||
"grunt-jsdoc": "^2.1.0",
|
||||
"grunt-webpack": "^2.0.1",
|
||||
"html-webpack-plugin": "^2.28.0",
|
||||
"imports-loader": "^0.7.1",
|
||||
"ink-docstrap": "^1.1.4",
|
||||
"less": "^2.7.2",
|
||||
"less-loader": "^4.0.2",
|
||||
"style-loader": "^0.15.0",
|
||||
"url-loader": "^0.5.8",
|
||||
"web-resource-inliner": "^4.1.0",
|
||||
"webpack": "^2.2.1"
|
||||
"grunt-contrib-watch": "^1.1.0",
|
||||
"grunt-eslint": "^24.3.0",
|
||||
"grunt-exec": "~3.0.0",
|
||||
"grunt-webpack": "^6.0.0",
|
||||
"grunt-zip": "^1.0.0",
|
||||
"html-webpack-plugin": "^5.6.0",
|
||||
"imports-loader": "^5.0.0",
|
||||
"mini-css-extract-plugin": "2.8.0",
|
||||
"modify-source-webpack-plugin": "^3.0.0",
|
||||
"nightwatch": "^3.4.0",
|
||||
"postcss": "^8.4.33",
|
||||
"postcss-css-variables": "^0.19.0",
|
||||
"postcss-import": "^16.0.0",
|
||||
"postcss-loader": "^8.1.0",
|
||||
"prompt": "^1.3.0",
|
||||
"sitemap": "^7.1.1",
|
||||
"terser": "^5.27.0",
|
||||
"webpack": "^5.90.1",
|
||||
"webpack-bundle-analyzer": "^4.10.1",
|
||||
"webpack-dev-server": "4.15.1",
|
||||
"webpack-node-externals": "^3.0.0",
|
||||
"worker-loader": "^3.0.8"
|
||||
},
|
||||
"dependencies": {
|
||||
"bootstrap": "^3.3.7",
|
||||
"bootstrap-colorpicker": "^2.5.1",
|
||||
"bootstrap-switch": "^3.3.4",
|
||||
"crypto-api": "^0.6.2",
|
||||
"crypto-js": "^3.1.9-1",
|
||||
"diff": "^3.2.0",
|
||||
"escodegen": "^1.8.1",
|
||||
"esmangle": "^1.0.1",
|
||||
"esprima": "^3.1.3",
|
||||
"google-code-prettify": "^1.0.5",
|
||||
"jquery": "^3.1.1",
|
||||
"jsbn": "^1.1.0",
|
||||
"jsrsasign": "^7.1.0",
|
||||
"moment": "^2.17.1",
|
||||
"moment-timezone": "^0.5.11",
|
||||
"sladex-blowfish": "^0.8.1",
|
||||
"sortablejs": "^1.5.1",
|
||||
"split.js": "^1.2.0",
|
||||
"vkbeautify": "^0.99.1",
|
||||
"xmldom": "^0.1.27",
|
||||
"xpath": "0.0.24",
|
||||
"zlibjs": "^0.2.0"
|
||||
"@astronautlabs/amf": "^0.0.6",
|
||||
"@babel/polyfill": "^7.12.1",
|
||||
"@blu3r4y/lzma": "^2.3.3",
|
||||
"@wavesenterprise/crypto-gost-js": "^2.1.0-RC1",
|
||||
"argon2-browser": "^1.18.0",
|
||||
"arrive": "^2.4.1",
|
||||
"avsc": "^5.7.7",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"bignumber.js": "^9.1.2",
|
||||
"blakejs": "^1.2.1",
|
||||
"bootstrap": "4.6.2",
|
||||
"bootstrap-colorpicker": "^3.4.0",
|
||||
"bootstrap-material-design": "^4.1.3",
|
||||
"browserify-zlib": "^0.2.0",
|
||||
"bson": "^4.7.2",
|
||||
"buffer": "^6.0.3",
|
||||
"cbor": "9.0.2",
|
||||
"chi-squared": "^1.1.0",
|
||||
"codepage": "^1.15.0",
|
||||
"crypto-api": "^0.8.5",
|
||||
"crypto-browserify": "^3.12.0",
|
||||
"crypto-js": "^4.2.0",
|
||||
"ctph.js": "0.0.5",
|
||||
"d3": "7.8.5",
|
||||
"d3-hexbin": "^0.2.2",
|
||||
"diff": "^5.1.0",
|
||||
"es6-promisify": "^7.0.0",
|
||||
"escodegen": "^2.1.0",
|
||||
"esprima": "^4.0.1",
|
||||
"exif-parser": "^0.1.12",
|
||||
"fernet": "^0.3.2",
|
||||
"file-saver": "^2.0.5",
|
||||
"flat": "^6.0.1",
|
||||
"geodesy": "1.1.3",
|
||||
"highlight.js": "^11.9.0",
|
||||
"jimp": "^0.16.13",
|
||||
"jquery": "3.7.1",
|
||||
"js-crc": "^0.2.0",
|
||||
"js-sha3": "^0.9.3",
|
||||
"jsesc": "^3.0.2",
|
||||
"json5": "^2.2.3",
|
||||
"jsonpath-plus": "^8.0.0",
|
||||
"jsonwebtoken": "8.5.1",
|
||||
"jsqr": "^1.4.0",
|
||||
"jsrsasign": "^11.1.0",
|
||||
"kbpgp": "2.1.15",
|
||||
"libbzip2-wasm": "0.0.4",
|
||||
"libyara-wasm": "^1.2.1",
|
||||
"lodash": "^4.17.21",
|
||||
"loglevel": "^1.9.1",
|
||||
"loglevel-message-prefix": "^3.0.0",
|
||||
"lz-string": "^1.5.0",
|
||||
"lz4js": "^0.2.0",
|
||||
"markdown-it": "^14.0.0",
|
||||
"moment": "^2.30.1",
|
||||
"moment-timezone": "^0.5.44",
|
||||
"ngeohash": "^0.6.3",
|
||||
"node-forge": "^1.3.1",
|
||||
"node-md6": "^0.1.0",
|
||||
"nodom": "^2.4.0",
|
||||
"notepack.io": "^3.0.1",
|
||||
"ntlm": "^0.1.3",
|
||||
"nwmatcher": "^1.4.4",
|
||||
"otp": "0.1.3",
|
||||
"path": "^0.12.7",
|
||||
"popper.js": "^1.16.1",
|
||||
"process": "^0.11.10",
|
||||
"protobufjs": "^7.2.6",
|
||||
"qr-image": "^3.2.0",
|
||||
"reflect-metadata": "^0.2.1",
|
||||
"rison": "^0.1.1",
|
||||
"scryptsy": "^2.1.0",
|
||||
"snackbarjs": "^1.1.0",
|
||||
"sortablejs": "^1.15.2",
|
||||
"split.js": "^1.6.5",
|
||||
"ssdeep.js": "0.0.3",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"tesseract.js": "5.0.4",
|
||||
"ua-parser-js": "^1.0.37",
|
||||
"unorm": "^1.6.0",
|
||||
"utf8": "^3.0.0",
|
||||
"vkbeautify": "^0.99.3",
|
||||
"xmldom": "^0.6.0",
|
||||
"xpath": "0.0.34",
|
||||
"xregexp": "^5.1.1",
|
||||
"zlibjs": "^0.3.1"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "grunt prod",
|
||||
"test": "grunt test",
|
||||
"docs": "grunt docs"
|
||||
"start": "npx grunt dev",
|
||||
"build": "npx grunt prod",
|
||||
"node": "npx grunt node",
|
||||
"repl": "node --experimental-modules --experimental-json-modules --experimental-specifier-resolution=node --no-experimental-fetch --no-warnings src/node/repl.mjs",
|
||||
"test": "npx grunt configTests && node --experimental-modules --experimental-json-modules --no-warnings --no-deprecation --openssl-legacy-provider --no-experimental-fetch tests/node/index.mjs && node --experimental-modules --experimental-json-modules --no-warnings --no-deprecation --openssl-legacy-provider --no-experimental-fetch --trace-uncaught tests/operations/index.mjs",
|
||||
"testnodeconsumer": "npx grunt testnodeconsumer",
|
||||
"testui": "npx grunt testui",
|
||||
"testuidev": "npx nightwatch --env=dev",
|
||||
"lint": "npx grunt lint",
|
||||
"postinstall": "npx grunt exec:fixCryptoApiImports && npx grunt exec:fixSnackbarMarkup",
|
||||
"newop": "node --experimental-modules --experimental-json-modules src/core/config/scripts/newOperation.mjs",
|
||||
"minor": "node --experimental-modules --experimental-json-modules src/core/config/scripts/newMinorVersion.mjs",
|
||||
"getheapsize": "node -e 'console.log(`node heap limit = ${require(\"v8\").getHeapStatistics().heap_size_limit / (1024 * 1024)} Mb`)'",
|
||||
"setheapsize": "export NODE_OPTIONS=--max_old_space_size=2048"
|
||||
}
|
||||
}
|
||||
|
|
9
postcss.config.js
Normal file
9
postcss.config.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
module.exports = {
|
||||
plugins: [
|
||||
require("postcss-import"),
|
||||
require("autoprefixer"),
|
||||
require("postcss-css-variables")({
|
||||
preserve: true
|
||||
}),
|
||||
]
|
||||
};
|
126
src/core/Chef.js
126
src/core/Chef.js
|
@ -1,126 +0,0 @@
|
|||
import Dish from "./Dish.js";
|
||||
import Recipe from "./Recipe.js";
|
||||
|
||||
|
||||
/**
|
||||
* The main controller for CyberChef.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
var Chef = function() {
|
||||
this.dish = new Dish();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Runs the recipe over the input.
|
||||
*
|
||||
* @param {string} inputText - The input data as a string
|
||||
* @param {Object[]} recipeConfig - The recipe configuration object
|
||||
* @param {Object} options - The options object storing various user choices
|
||||
* @param {boolean} options.attempHighlight - Whether or not to attempt highlighting
|
||||
* @param {number} progress - The position in the recipe to start from
|
||||
* @param {number} [step] - Whether to only execute one operation in the recipe
|
||||
*
|
||||
* @returns {Object} response
|
||||
* @returns {string} response.result - The output of the recipe
|
||||
* @returns {string} response.type - The data type of the result
|
||||
* @returns {number} response.progress - The position that we have got to in the recipe
|
||||
* @returns {number} response.options - The app options object (which may have been changed)
|
||||
* @returns {number} response.duration - The number of ms it took to execute the recipe
|
||||
* @returns {number} response.error - The error object thrown by a failed operation (false if no error)
|
||||
*/
|
||||
Chef.prototype.bake = function(inputText, recipeConfig, options, progress, step) {
|
||||
var startTime = new Date().getTime(),
|
||||
recipe = new Recipe(recipeConfig),
|
||||
containsFc = recipe.containsFlowControl(),
|
||||
error = false;
|
||||
|
||||
// Reset attemptHighlight flag
|
||||
if (options.hasOwnProperty("attemptHighlight")) {
|
||||
options.attemptHighlight = true;
|
||||
}
|
||||
|
||||
if (containsFc) options.attemptHighlight = false;
|
||||
|
||||
// Clean up progress
|
||||
if (progress >= recipeConfig.length) {
|
||||
progress = 0;
|
||||
}
|
||||
|
||||
if (step) {
|
||||
// Unset breakpoint on this step
|
||||
recipe.setBreakpoint(progress, false);
|
||||
// Set breakpoint on next step
|
||||
recipe.setBreakpoint(progress + 1, true);
|
||||
}
|
||||
|
||||
// If stepping with flow control, we have to start from the beginning
|
||||
// but still want to skip all previous breakpoints
|
||||
if (progress > 0 && containsFc) {
|
||||
recipe.removeBreaksUpTo(progress);
|
||||
progress = 0;
|
||||
}
|
||||
|
||||
// If starting from scratch, load data
|
||||
if (progress === 0) {
|
||||
this.dish.set(inputText, Dish.STRING);
|
||||
}
|
||||
|
||||
try {
|
||||
progress = recipe.execute(this.dish, progress);
|
||||
} catch (err) {
|
||||
// Return the error in the result so that everything else gets correctly updated
|
||||
// rather than throwing it here and losing state info.
|
||||
error = err;
|
||||
progress = err.progress;
|
||||
}
|
||||
|
||||
return {
|
||||
result: this.dish.type === Dish.HTML ?
|
||||
this.dish.get(Dish.HTML) :
|
||||
this.dish.get(Dish.STRING),
|
||||
type: Dish.enumLookup(this.dish.type),
|
||||
progress: progress,
|
||||
options: options,
|
||||
duration: new Date().getTime() - startTime,
|
||||
error: error
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* When a browser tab is unfocused and the browser has to run lots of dynamic content in other tabs,
|
||||
* it swaps out the memory for that tab. If the CyberChef tab has been unfocused for more than a
|
||||
* minute, we run a silent bake which will force the browser to load and cache all the relevant
|
||||
* JavaScript code needed to do a real bake.
|
||||
*
|
||||
* This will stop baking taking a long time when the CyberChef browser tab has been unfocused for a
|
||||
* long time and the browser has swapped out all its memory.
|
||||
*
|
||||
* The output will not be modified (hence "silent" bake).
|
||||
*
|
||||
* This will only actually execute the recipe if auto-bake is enabled, otherwise it will just load
|
||||
* the recipe, ingredients and dish.
|
||||
*
|
||||
* @param {Object[]} recipeConfig - The recipe configuration object
|
||||
* @returns {number} The time it took to run the silent bake in milliseconds.
|
||||
*/
|
||||
Chef.prototype.silentBake = function(recipeConfig) {
|
||||
var startTime = new Date().getTime(),
|
||||
recipe = new Recipe(recipeConfig),
|
||||
dish = new Dish("", Dish.STRING);
|
||||
|
||||
try {
|
||||
recipe.execute(dish);
|
||||
} catch (err) {
|
||||
// Suppress all errors
|
||||
}
|
||||
return new Date().getTime() - startTime;
|
||||
};
|
||||
|
||||
export default Chef;
|
183
src/core/Chef.mjs
Executable file
183
src/core/Chef.mjs
Executable file
|
@ -0,0 +1,183 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Dish from "./Dish.mjs";
|
||||
import Recipe from "./Recipe.mjs";
|
||||
import log from "loglevel";
|
||||
import { isWorkerEnvironment } from "./Utils.mjs";
|
||||
|
||||
/**
|
||||
* The main controller for CyberChef.
|
||||
*/
|
||||
class Chef {
|
||||
|
||||
/**
|
||||
* Chef constructor
|
||||
*/
|
||||
constructor() {
|
||||
this.dish = new Dish();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Runs the recipe over the input.
|
||||
*
|
||||
* @param {string|ArrayBuffer} input - The input data as a string or ArrayBuffer
|
||||
* @param {Object[]} recipeConfig - The recipe configuration object
|
||||
* @param {Object} [options={}] - The options object storing various user choices
|
||||
* @param {string} [options.returnType] - What type to return the result as
|
||||
*
|
||||
* @returns {Object} response
|
||||
* @returns {string} response.result - The output of the recipe
|
||||
* @returns {string} response.type - The data type of the result
|
||||
* @returns {number} response.progress - The position that we have got to in the recipe
|
||||
* @returns {number} response.duration - The number of ms it took to execute the recipe
|
||||
* @returns {number} response.error - The error object thrown by a failed operation (false if no error)
|
||||
*/
|
||||
async bake(input, recipeConfig, options={}) {
|
||||
log.debug("Chef baking");
|
||||
const startTime = Date.now(),
|
||||
recipe = new Recipe(recipeConfig),
|
||||
containsFc = recipe.containsFlowControl();
|
||||
let error = false,
|
||||
progress = 0;
|
||||
|
||||
if (containsFc && isWorkerEnvironment()) self.setOption("attemptHighlight", false);
|
||||
|
||||
// Load data
|
||||
const type = input instanceof ArrayBuffer ? Dish.ARRAY_BUFFER : Dish.STRING;
|
||||
this.dish.set(input, type);
|
||||
|
||||
try {
|
||||
progress = await recipe.execute(this.dish, progress);
|
||||
} catch (err) {
|
||||
log.error(err);
|
||||
error = {
|
||||
displayStr: err.displayStr,
|
||||
};
|
||||
progress = err.progress;
|
||||
}
|
||||
|
||||
// Create a raw version of the dish, unpresented
|
||||
const rawDish = this.dish.clone();
|
||||
|
||||
// Present the raw result
|
||||
await recipe.present(this.dish);
|
||||
|
||||
const returnType =
|
||||
this.dish.type === Dish.HTML ? Dish.HTML :
|
||||
options?.returnType ? options.returnType : Dish.ARRAY_BUFFER;
|
||||
|
||||
return {
|
||||
dish: rawDish,
|
||||
result: await this.dish.get(returnType),
|
||||
type: Dish.enumLookup(this.dish.type),
|
||||
progress: progress,
|
||||
duration: Date.now() - startTime,
|
||||
error: error
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* When a browser tab is unfocused and the browser has to run lots of dynamic content in other tabs,
|
||||
* it swaps out the memory for that tab. If the CyberChef tab has been unfocused for more than a
|
||||
* minute, we run a silent bake which will force the browser to load and cache all the relevant
|
||||
* JavaScript code needed to do a real bake.
|
||||
*
|
||||
* This will stop baking taking a long time when the CyberChef browser tab has been unfocused for a
|
||||
* long time and the browser has swapped out all its memory.
|
||||
*
|
||||
* The output will not be modified (hence "silent" bake).
|
||||
*
|
||||
* This will only actually execute the recipe if auto-bake is enabled, otherwise it will just load
|
||||
* the recipe, ingredients and dish.
|
||||
*
|
||||
* @param {Object[]} recipeConfig - The recipe configuration object
|
||||
* @returns {number} The time it took to run the silent bake in milliseconds.
|
||||
*/
|
||||
silentBake(recipeConfig) {
|
||||
log.debug("Running silent bake");
|
||||
|
||||
const startTime = Date.now(),
|
||||
recipe = new Recipe(recipeConfig),
|
||||
dish = new Dish();
|
||||
|
||||
try {
|
||||
recipe.execute(dish);
|
||||
} catch (err) {
|
||||
// Suppress all errors
|
||||
}
|
||||
return Date.now() - startTime;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculates highlight offsets if possible.
|
||||
*
|
||||
* @param {Object[]} recipeConfig
|
||||
* @param {string} direction
|
||||
* @param {Object} pos - The position object for the highlight.
|
||||
* @param {number} pos.start - The start offset.
|
||||
* @param {number} pos.end - The end offset.
|
||||
* @returns {Object}
|
||||
*/
|
||||
async calculateHighlights(recipeConfig, direction, pos) {
|
||||
const recipe = new Recipe(recipeConfig);
|
||||
const highlights = await recipe.generateHighlightList();
|
||||
|
||||
if (!highlights) return false;
|
||||
|
||||
for (let i = 0; i < highlights.length; i++) {
|
||||
// Remove multiple highlights before processing again
|
||||
pos = [pos[0]];
|
||||
|
||||
const func = direction === "forward" ? highlights[i].f : highlights[i].b;
|
||||
|
||||
if (typeof func == "function") {
|
||||
try {
|
||||
pos = func(pos, highlights[i].args);
|
||||
} catch (err) {
|
||||
// Throw away highlighting errors
|
||||
pos = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
pos: pos,
|
||||
direction: direction
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Translates the dish to a specified type and returns it.
|
||||
*
|
||||
* @param {Dish} dish
|
||||
* @param {string} type
|
||||
* @returns {Dish}
|
||||
*/
|
||||
async getDishAs(dish, type) {
|
||||
const newDish = new Dish(dish);
|
||||
return await newDish.get(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the title of a dish and returns it
|
||||
*
|
||||
* @param {Dish} dish
|
||||
* @param {number} [maxLength=100]
|
||||
* @returns {string}
|
||||
*/
|
||||
async getDishTitle(dish, maxLength=100) {
|
||||
const newDish = new Dish(dish);
|
||||
return await newDish.getTitle(maxLength);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default Chef;
|
290
src/core/ChefWorker.js
Normal file
290
src/core/ChefWorker.js
Normal file
|
@ -0,0 +1,290 @@
|
|||
/**
|
||||
* Web Worker to handle communications between the front-end and the core.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2017
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Chef from "./Chef.mjs";
|
||||
import OperationConfig from "./config/OperationConfig.json" assert {type: "json"};
|
||||
import OpModules from "./config/modules/OpModules.mjs";
|
||||
import loglevelMessagePrefix from "loglevel-message-prefix";
|
||||
|
||||
|
||||
// Set up Chef instance
|
||||
self.chef = new Chef();
|
||||
|
||||
self.OpModules = OpModules;
|
||||
self.OperationConfig = OperationConfig;
|
||||
self.inputNum = -1;
|
||||
|
||||
|
||||
// Tell the app that the worker has loaded and is ready to operate
|
||||
self.postMessage({
|
||||
action: "workerLoaded",
|
||||
data: {}
|
||||
});
|
||||
|
||||
/**
|
||||
* Respond to message from parent thread.
|
||||
*
|
||||
* inputNum is optional and only used for baking multiple inputs.
|
||||
* Defaults to -1 when one isn't sent with the bake message.
|
||||
*
|
||||
* Messages should have the following format:
|
||||
* {
|
||||
* action: "bake" | "silentBake",
|
||||
* data: {
|
||||
* input: {string},
|
||||
* recipeConfig: {[Object]},
|
||||
* options: {Object},
|
||||
* progress: {number},
|
||||
* step: {boolean},
|
||||
* [inputNum=-1]: {number}
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
self.addEventListener("message", function(e) {
|
||||
// Handle message
|
||||
const r = e.data;
|
||||
log.debug(`Receiving command '${r.action}'`);
|
||||
|
||||
switch (r.action) {
|
||||
case "bake":
|
||||
bake(r.data);
|
||||
break;
|
||||
case "silentBake":
|
||||
silentBake(r.data);
|
||||
break;
|
||||
case "getDishAs":
|
||||
getDishAs(r.data);
|
||||
break;
|
||||
case "getDishTitle":
|
||||
getDishTitle(r.data);
|
||||
break;
|
||||
case "docURL":
|
||||
// Used to set the URL of the current document so that scripts can be
|
||||
// imported into an inline worker.
|
||||
self.docURL = r.data;
|
||||
break;
|
||||
case "highlight":
|
||||
calculateHighlights(
|
||||
r.data.recipeConfig,
|
||||
r.data.direction,
|
||||
r.data.pos
|
||||
);
|
||||
break;
|
||||
case "setLogLevel":
|
||||
log.setLevel(r.data, false);
|
||||
break;
|
||||
case "setLogPrefix":
|
||||
loglevelMessagePrefix(log, {
|
||||
prefixes: [],
|
||||
staticPrefixes: [r.data]
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Baking handler
|
||||
*
|
||||
* @param {Object} data
|
||||
*/
|
||||
async function bake(data) {
|
||||
// Ensure the relevant modules are loaded
|
||||
self.loadRequiredModules(data.recipeConfig);
|
||||
try {
|
||||
self.inputNum = data.inputNum === undefined ? -1 : data.inputNum;
|
||||
const response = await self.chef.bake(
|
||||
data.input, // The user's input
|
||||
data.recipeConfig, // The configuration of the recipe
|
||||
data.options // Options set by the user
|
||||
);
|
||||
|
||||
const transferable = (response.dish.value instanceof ArrayBuffer) ?
|
||||
[response.dish.value] :
|
||||
undefined;
|
||||
|
||||
self.postMessage({
|
||||
action: "bakeComplete",
|
||||
data: Object.assign(response, {
|
||||
id: data.id,
|
||||
inputNum: data.inputNum,
|
||||
bakeId: data.bakeId
|
||||
})
|
||||
}, transferable);
|
||||
|
||||
} catch (err) {
|
||||
self.postMessage({
|
||||
action: "bakeError",
|
||||
data: {
|
||||
error: err.message || err,
|
||||
id: data.id,
|
||||
inputNum: data.inputNum
|
||||
}
|
||||
});
|
||||
}
|
||||
self.inputNum = -1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Silent baking handler
|
||||
*/
|
||||
function silentBake(data) {
|
||||
const duration = self.chef.silentBake(data.recipeConfig);
|
||||
|
||||
self.postMessage({
|
||||
action: "silentBakeComplete",
|
||||
data: duration
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Translates the dish to a given type.
|
||||
*/
|
||||
async function getDishAs(data) {
|
||||
const value = await self.chef.getDishAs(data.dish, data.type);
|
||||
const transferable = (data.type === "ArrayBuffer") ? [value] : undefined;
|
||||
self.postMessage({
|
||||
action: "dishReturned",
|
||||
data: {
|
||||
value: value,
|
||||
id: data.id
|
||||
}
|
||||
}, transferable);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the dish title
|
||||
*
|
||||
* @param {object} data
|
||||
* @param {Dish} data.dish
|
||||
* @param {number} data.maxLength
|
||||
* @param {number} data.id
|
||||
*/
|
||||
async function getDishTitle(data) {
|
||||
const title = await self.chef.getDishTitle(data.dish, data.maxLength);
|
||||
self.postMessage({
|
||||
action: "dishReturned",
|
||||
data: {
|
||||
value: title,
|
||||
id: data.id
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculates highlight offsets if possible.
|
||||
*
|
||||
* @param {Object[]} recipeConfig
|
||||
* @param {string} direction
|
||||
* @param {Object[]} pos - The position object for the highlight.
|
||||
* @param {number} pos.start - The start offset.
|
||||
* @param {number} pos.end - The end offset.
|
||||
*/
|
||||
async function calculateHighlights(recipeConfig, direction, pos) {
|
||||
pos = await self.chef.calculateHighlights(recipeConfig, direction, pos);
|
||||
|
||||
self.postMessage({
|
||||
action: "highlightsCalculated",
|
||||
data: pos
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks that all required modules are loaded and loads them if not.
|
||||
*
|
||||
* @param {Object} recipeConfig
|
||||
*/
|
||||
self.loadRequiredModules = function(recipeConfig) {
|
||||
recipeConfig.forEach(op => {
|
||||
const module = self.OperationConfig[op.op].module;
|
||||
|
||||
if (!(module in OpModules)) {
|
||||
log.info(`Loading ${module} module`);
|
||||
self.sendStatusMessage(`Loading ${module} module`);
|
||||
self.importScripts(`${self.docURL}/modules/${module}.js`); // lgtm [js/client-side-unvalidated-url-redirection]
|
||||
self.sendStatusMessage("");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Send status update to the app.
|
||||
*
|
||||
* @param {string} msg
|
||||
*/
|
||||
self.sendStatusMessage = function(msg) {
|
||||
self.postMessage({
|
||||
action: "statusMessage",
|
||||
data: {
|
||||
message: msg,
|
||||
inputNum: self.inputNum
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Send progress update to the app.
|
||||
*
|
||||
* @param {number} progress
|
||||
* @param {number} total
|
||||
*/
|
||||
self.sendProgressMessage = function(progress, total) {
|
||||
self.postMessage({
|
||||
action: "progressMessage",
|
||||
data: {
|
||||
progress: progress,
|
||||
total: total,
|
||||
inputNum: self.inputNum
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Send an option value update to the app.
|
||||
*
|
||||
* @param {string} option
|
||||
* @param {*} value
|
||||
*/
|
||||
self.setOption = function(option, value) {
|
||||
self.postMessage({
|
||||
action: "optionUpdate",
|
||||
data: {
|
||||
option: option,
|
||||
value: value
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Send register values back to the app.
|
||||
*
|
||||
* @param {number} opIndex
|
||||
* @param {number} numPrevRegisters
|
||||
* @param {string[]} registers
|
||||
*/
|
||||
self.setRegisters = function(opIndex, numPrevRegisters, registers) {
|
||||
self.postMessage({
|
||||
action: "setRegisters",
|
||||
data: {
|
||||
opIndex: opIndex,
|
||||
numPrevRegisters: numPrevRegisters,
|
||||
registers: registers
|
||||
}
|
||||
});
|
||||
};
|
207
src/core/Dish.js
207
src/core/Dish.js
|
@ -1,207 +0,0 @@
|
|||
import Utils from "./Utils.js";
|
||||
|
||||
|
||||
/**
|
||||
* The data being operated on by each operation.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* @class
|
||||
* @param {byteArray|string|number} value - The value of the input data.
|
||||
* @param {number} type - The data type of value, see Dish enums.
|
||||
*/
|
||||
var Dish = function(value, type) {
|
||||
this.value = value || typeof value == "string" ? value : null;
|
||||
this.type = type || Dish.BYTE_ARRAY;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Dish data type enum for byte arrays.
|
||||
* @readonly
|
||||
* @enum
|
||||
*/
|
||||
Dish.BYTE_ARRAY = 0;
|
||||
/**
|
||||
* Dish data type enum for strings.
|
||||
* @readonly
|
||||
* @enum
|
||||
*/
|
||||
Dish.STRING = 1;
|
||||
/**
|
||||
* Dish data type enum for numbers.
|
||||
* @readonly
|
||||
* @enum
|
||||
*/
|
||||
Dish.NUMBER = 2;
|
||||
/**
|
||||
* Dish data type enum for HTML.
|
||||
* @readonly
|
||||
* @enum
|
||||
*/
|
||||
Dish.HTML = 3;
|
||||
|
||||
|
||||
/**
|
||||
* Returns the data type enum for the given type string.
|
||||
*
|
||||
* @static
|
||||
* @param {string} typeStr - The name of the data type.
|
||||
* @returns {number} The data type enum value.
|
||||
*/
|
||||
Dish.typeEnum = function(typeStr) {
|
||||
switch (typeStr) {
|
||||
case "byteArray":
|
||||
case "Byte array":
|
||||
return Dish.BYTE_ARRAY;
|
||||
case "string":
|
||||
case "String":
|
||||
return Dish.STRING;
|
||||
case "number":
|
||||
case "Number":
|
||||
return Dish.NUMBER;
|
||||
case "html":
|
||||
case "HTML":
|
||||
return Dish.HTML;
|
||||
default:
|
||||
throw "Invalid data type string. No matching enum.";
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the data type string for the given type enum.
|
||||
*
|
||||
* @static
|
||||
* @param {string} typeEnum - The enum value of the data type.
|
||||
* @returns {number} The data type as a string.
|
||||
*/
|
||||
Dish.enumLookup = function(typeEnum) {
|
||||
switch (typeEnum) {
|
||||
case Dish.BYTE_ARRAY:
|
||||
return "byteArray";
|
||||
case Dish.STRING:
|
||||
return "string";
|
||||
case Dish.NUMBER:
|
||||
return "number";
|
||||
case Dish.HTML:
|
||||
return "html";
|
||||
default:
|
||||
throw "Invalid data type enum. No matching type.";
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the data value and type and then validates them.
|
||||
*
|
||||
* @param {byteArray|string|number} value - The value of the input data.
|
||||
* @param {number} type - The data type of value, see Dish enums.
|
||||
*/
|
||||
Dish.prototype.set = function(value, type) {
|
||||
this.value = value;
|
||||
this.type = type;
|
||||
|
||||
if (!this.valid()) {
|
||||
var sample = Utils.truncate(JSON.stringify(this.value), 13);
|
||||
throw "Data is not a valid " + Dish.enumLookup(type) + ": " + sample;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the value of the data in the type format specified.
|
||||
*
|
||||
* @param {number} type - The data type of value, see Dish enums.
|
||||
* @returns {byteArray|string|number} The value of the output data.
|
||||
*/
|
||||
Dish.prototype.get = function(type) {
|
||||
if (this.type !== type) {
|
||||
this.translate(type);
|
||||
}
|
||||
return this.value;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Translates the data to the given type format.
|
||||
*
|
||||
* @param {number} toType - The data type of value, see Dish enums.
|
||||
*/
|
||||
Dish.prototype.translate = function(toType) {
|
||||
// Convert data to intermediate byteArray type
|
||||
switch (this.type) {
|
||||
case Dish.STRING:
|
||||
this.value = this.value ? Utils.strToByteArray(this.value) : [];
|
||||
this.type = Dish.BYTE_ARRAY;
|
||||
break;
|
||||
case Dish.NUMBER:
|
||||
this.value = typeof this.value == "number" ? Utils.strToByteArray(this.value.toString()) : [];
|
||||
this.type = Dish.BYTE_ARRAY;
|
||||
break;
|
||||
case Dish.HTML:
|
||||
this.value = this.value ? Utils.strToByteArray(Utils.unescapeHtml(Utils.stripHtmlTags(this.value, true))) : [];
|
||||
this.type = Dish.BYTE_ARRAY;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Convert from byteArray to toType
|
||||
switch (toType) {
|
||||
case Dish.STRING:
|
||||
case Dish.HTML:
|
||||
this.value = this.value ? Utils.byteArrayToUtf8(this.value) : "";
|
||||
this.type = Dish.STRING;
|
||||
break;
|
||||
case Dish.NUMBER:
|
||||
this.value = this.value ? parseFloat(Utils.byteArrayToUtf8(this.value)) : 0;
|
||||
this.type = Dish.NUMBER;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Validates that the value is the type that has been specified.
|
||||
* May have to disable parts of BYTE_ARRAY validation if it effects performance.
|
||||
*
|
||||
* @returns {boolean} Whether the data is valid or not.
|
||||
*/
|
||||
Dish.prototype.valid = function() {
|
||||
switch (this.type) {
|
||||
case Dish.BYTE_ARRAY:
|
||||
if (!(this.value instanceof Array)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that every value is a number between 0 - 255
|
||||
for (var i = 0; i < this.value.length; i++) {
|
||||
if (typeof this.value[i] != "number" ||
|
||||
this.value[i] < 0 ||
|
||||
this.value[i] > 255) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
case Dish.STRING:
|
||||
case Dish.HTML:
|
||||
if (typeof this.value == "string") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case Dish.NUMBER:
|
||||
if (typeof this.value == "number") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export default Dish;
|
569
src/core/Dish.mjs
Executable file
569
src/core/Dish.mjs
Executable file
|
@ -0,0 +1,569 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @author Matt C [matt@artemisbot.uk]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Utils, { isNodeEnvironment } from "./Utils.mjs";
|
||||
import DishError from "./errors/DishError.mjs";
|
||||
import BigNumber from "bignumber.js";
|
||||
import { detectFileType } from "./lib/FileType.mjs";
|
||||
import log from "loglevel";
|
||||
|
||||
import DishByteArray from "./dishTypes/DishByteArray.mjs";
|
||||
import DishBigNumber from "./dishTypes/DishBigNumber.mjs";
|
||||
import DishFile from "./dishTypes/DishFile.mjs";
|
||||
import DishHTML from "./dishTypes/DishHTML.mjs";
|
||||
import DishJSON from "./dishTypes/DishJSON.mjs";
|
||||
import DishListFile from "./dishTypes/DishListFile.mjs";
|
||||
import DishNumber from "./dishTypes/DishNumber.mjs";
|
||||
import DishString from "./dishTypes/DishString.mjs";
|
||||
|
||||
|
||||
/**
|
||||
* The data being operated on by each operation.
|
||||
*/
|
||||
class Dish {
|
||||
|
||||
/**
|
||||
* Dish constructor
|
||||
*
|
||||
* @param {Dish || *} [dishOrInput=null] - A dish to clone OR an object
|
||||
* literal to make into a dish
|
||||
* @param {Enum} [type=null] (optional) - A type to accompany object
|
||||
* literal input
|
||||
*/
|
||||
constructor(dishOrInput=null, type = null) {
|
||||
this.value = new ArrayBuffer(0);
|
||||
this.type = Dish.ARRAY_BUFFER;
|
||||
|
||||
// Case: dishOrInput is dish object
|
||||
if (dishOrInput &&
|
||||
Object.prototype.hasOwnProperty.call(dishOrInput, "value") &&
|
||||
Object.prototype.hasOwnProperty.call(dishOrInput, "type")) {
|
||||
this.set(dishOrInput.value, dishOrInput.type);
|
||||
// input and type defined separately
|
||||
} else if (dishOrInput && type !== null) {
|
||||
this.set(dishOrInput, type);
|
||||
// No type declared, so infer it.
|
||||
} else if (dishOrInput) {
|
||||
const inferredType = Dish.typeEnum(dishOrInput.constructor.name);
|
||||
this.set(dishOrInput, inferredType);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the data type enum for the given type string.
|
||||
*
|
||||
* @param {string} typeStr - The name of the data type.
|
||||
* @returns {number} The data type enum value.
|
||||
*/
|
||||
static typeEnum(typeStr) {
|
||||
switch (typeStr.toLowerCase()) {
|
||||
case "bytearray":
|
||||
case "byte array":
|
||||
return Dish.BYTE_ARRAY;
|
||||
case "string":
|
||||
return Dish.STRING;
|
||||
case "number":
|
||||
return Dish.NUMBER;
|
||||
case "html":
|
||||
return Dish.HTML;
|
||||
case "arraybuffer":
|
||||
case "array buffer":
|
||||
return Dish.ARRAY_BUFFER;
|
||||
case "bignumber":
|
||||
case "big number":
|
||||
return Dish.BIG_NUMBER;
|
||||
case "json":
|
||||
case "object": // object constructor name. To allow JSON input in node.
|
||||
return Dish.JSON;
|
||||
case "file":
|
||||
return Dish.FILE;
|
||||
case "list<file>":
|
||||
return Dish.LIST_FILE;
|
||||
default:
|
||||
throw new DishError("Invalid data type string. No matching enum.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the data type string for the given type enum.
|
||||
*
|
||||
* @param {number} typeEnum - The enum value of the data type.
|
||||
* @returns {string} The data type as a string.
|
||||
*/
|
||||
static enumLookup(typeEnum) {
|
||||
switch (typeEnum) {
|
||||
case Dish.BYTE_ARRAY:
|
||||
return "byteArray";
|
||||
case Dish.STRING:
|
||||
return "string";
|
||||
case Dish.NUMBER:
|
||||
return "number";
|
||||
case Dish.HTML:
|
||||
return "html";
|
||||
case Dish.ARRAY_BUFFER:
|
||||
return "ArrayBuffer";
|
||||
case Dish.BIG_NUMBER:
|
||||
return "BigNumber";
|
||||
case Dish.JSON:
|
||||
return "JSON";
|
||||
case Dish.FILE:
|
||||
return "File";
|
||||
case Dish.LIST_FILE:
|
||||
return "List<File>";
|
||||
default:
|
||||
throw new DishError("Invalid data type enum. No matching type.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the value of the data in the type format specified.
|
||||
*
|
||||
* If running in a browser, get is asynchronous.
|
||||
*
|
||||
* @param {number} type - The data type of value, see Dish enums.
|
||||
* @returns {* | Promise} - (Browser) A promise | (Node) value of dish in given type
|
||||
*/
|
||||
get(type) {
|
||||
if (typeof type === "string") {
|
||||
type = Dish.typeEnum(type);
|
||||
}
|
||||
|
||||
if (this.type !== type) {
|
||||
|
||||
// Node environment => _translate is sync
|
||||
if (isNodeEnvironment()) {
|
||||
this._translate(type);
|
||||
return this.value;
|
||||
|
||||
// Browser environment => _translate is async
|
||||
} else {
|
||||
return new Promise((resolve, reject) => {
|
||||
this._translate(type)
|
||||
.then(() => {
|
||||
resolve(this.value);
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return this.value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the data value and type and then validates them.
|
||||
*
|
||||
* @param {*} value
|
||||
* - The value of the input data.
|
||||
* @param {number} type
|
||||
* - The data type of value, see Dish enums.
|
||||
*/
|
||||
set(value, type) {
|
||||
if (typeof type === "string") {
|
||||
type = Dish.typeEnum(type);
|
||||
}
|
||||
|
||||
log.debug("Dish type: " + Dish.enumLookup(type));
|
||||
this.value = value;
|
||||
this.type = type;
|
||||
|
||||
if (!this.valid()) {
|
||||
const sample = Utils.truncate(JSON.stringify(this.value), 25);
|
||||
throw new DishError(`Data is not a valid ${Dish.enumLookup(type)}: ${sample}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Dish as the given type, without mutating the original dish.
|
||||
*
|
||||
* If running in a browser, get is asynchronous.
|
||||
*
|
||||
* @Node
|
||||
*
|
||||
* @param {number} type - The data type of value, see Dish enums.
|
||||
* @returns {Dish | Promise} - (Browser) A promise | (Node) value of dish in given type
|
||||
*/
|
||||
presentAs(type) {
|
||||
const clone = this.clone();
|
||||
return clone.get(type);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Detects the MIME type of the current dish
|
||||
* @returns {string}
|
||||
*/
|
||||
detectDishType() {
|
||||
const data = new Uint8Array(this.value.slice(0, 2048)),
|
||||
types = detectFileType(data);
|
||||
|
||||
if (!types.length || !types[0].mime || !(types[0].mime === "text/plain")) {
|
||||
return null;
|
||||
} else {
|
||||
return types[0].mime;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the title of the data up to the specified length
|
||||
*
|
||||
* @param {number} maxLength - The maximum title length
|
||||
* @returns {string}
|
||||
*/
|
||||
async getTitle(maxLength) {
|
||||
let title = "";
|
||||
let cloned;
|
||||
|
||||
switch (this.type) {
|
||||
case Dish.FILE:
|
||||
title = this.value.name;
|
||||
break;
|
||||
case Dish.LIST_FILE:
|
||||
title = `${this.value.length} file(s)`;
|
||||
break;
|
||||
case Dish.JSON:
|
||||
title = "application/json";
|
||||
break;
|
||||
case Dish.NUMBER:
|
||||
case Dish.BIG_NUMBER:
|
||||
title = this.value.toString();
|
||||
break;
|
||||
case Dish.ARRAY_BUFFER:
|
||||
case Dish.BYTE_ARRAY:
|
||||
title = this.detectDishType();
|
||||
if (title !== null) break;
|
||||
// fall through if no mime type was detected
|
||||
default:
|
||||
try {
|
||||
cloned = this.clone();
|
||||
cloned.value = cloned.value.slice(0, 256);
|
||||
title = await cloned.get(Dish.STRING);
|
||||
} catch (err) {
|
||||
log.error(`${Dish.enumLookup(this.type)} cannot be sliced. ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
return title.slice(0, maxLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the value is the type that has been specified.
|
||||
* May have to disable parts of BYTE_ARRAY validation if it effects performance.
|
||||
*
|
||||
* @returns {boolean} Whether the data is valid or not.
|
||||
*/
|
||||
valid() {
|
||||
switch (this.type) {
|
||||
case Dish.BYTE_ARRAY:
|
||||
if (!(this.value instanceof Uint8Array) && !(this.value instanceof Array)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that every value is a number between 0 - 255
|
||||
for (let i = 0; i < this.value.length; i++) {
|
||||
if (typeof this.value[i] !== "number" ||
|
||||
this.value[i] < 0 ||
|
||||
this.value[i] > 255) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
case Dish.STRING:
|
||||
case Dish.HTML:
|
||||
return typeof this.value === "string";
|
||||
case Dish.NUMBER:
|
||||
return typeof this.value === "number";
|
||||
case Dish.ARRAY_BUFFER:
|
||||
return this.value instanceof ArrayBuffer;
|
||||
case Dish.BIG_NUMBER:
|
||||
if (BigNumber.isBigNumber(this.value)) return true;
|
||||
/*
|
||||
If a BigNumber is passed between WebWorkers it is serialised as a JSON
|
||||
object with a coefficient (c), exponent (e) and sign (s). We detect this
|
||||
and reinitialise it as a BigNumber object.
|
||||
*/
|
||||
if (Object.keys(this.value).sort().equals(["c", "e", "s"])) {
|
||||
const temp = new BigNumber();
|
||||
temp.c = this.value.c;
|
||||
temp.e = this.value.e;
|
||||
temp.s = this.value.s;
|
||||
this.value = temp;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case Dish.JSON:
|
||||
// All values can be serialised in some manner, so we return true in all cases
|
||||
return true;
|
||||
case Dish.FILE:
|
||||
return this.value instanceof File;
|
||||
case Dish.LIST_FILE:
|
||||
return this.value instanceof Array &&
|
||||
this.value.reduce((acc, curr) => acc && curr instanceof File, true);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determines how much space the Dish takes up.
|
||||
* Numbers in JavaScript are 64-bit floating point, however for the purposes of the Dish,
|
||||
* we measure how many bytes are taken up when the number is written as a string.
|
||||
*
|
||||
* @returns {number}
|
||||
*/
|
||||
get size() {
|
||||
switch (this.type) {
|
||||
case Dish.BYTE_ARRAY:
|
||||
case Dish.STRING:
|
||||
case Dish.HTML:
|
||||
return this.value.length;
|
||||
case Dish.NUMBER:
|
||||
case Dish.BIG_NUMBER:
|
||||
return this.value.toString().length;
|
||||
case Dish.ARRAY_BUFFER:
|
||||
return this.value.byteLength;
|
||||
case Dish.JSON:
|
||||
return JSON.stringify(this.value).length;
|
||||
case Dish.FILE:
|
||||
return this.value.size;
|
||||
case Dish.LIST_FILE:
|
||||
return this.value.reduce((acc, curr) => acc + curr.size, 0);
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a deep clone of the current Dish.
|
||||
*
|
||||
* @returns {Dish}
|
||||
*/
|
||||
clone() {
|
||||
const newDish = new Dish();
|
||||
|
||||
switch (this.type) {
|
||||
case Dish.STRING:
|
||||
case Dish.HTML:
|
||||
case Dish.NUMBER:
|
||||
case Dish.BIG_NUMBER:
|
||||
// These data types are immutable so it is acceptable to copy them by reference
|
||||
newDish.set(
|
||||
this.value,
|
||||
this.type
|
||||
);
|
||||
break;
|
||||
case Dish.BYTE_ARRAY:
|
||||
case Dish.JSON:
|
||||
// These data types are mutable so they need to be copied by value
|
||||
newDish.set(
|
||||
JSON.parse(JSON.stringify(this.value)),
|
||||
this.type
|
||||
);
|
||||
break;
|
||||
case Dish.ARRAY_BUFFER:
|
||||
// Slicing an ArrayBuffer returns a new ArrayBuffer with a copy its contents
|
||||
newDish.set(
|
||||
this.value.slice(0),
|
||||
this.type
|
||||
);
|
||||
break;
|
||||
case Dish.FILE:
|
||||
// A new file can be created by copying over all the values from the original
|
||||
newDish.set(
|
||||
new File([this.value], this.value.name, {
|
||||
"type": this.value.type,
|
||||
"lastModified": this.value.lastModified
|
||||
}),
|
||||
this.type
|
||||
);
|
||||
break;
|
||||
case Dish.LIST_FILE:
|
||||
newDish.set(
|
||||
this.value.map(f =>
|
||||
new File([f], f.name, {
|
||||
"type": f.type,
|
||||
"lastModified": f.lastModified
|
||||
})
|
||||
),
|
||||
this.type
|
||||
);
|
||||
break;
|
||||
default:
|
||||
throw new DishError("Cannot clone Dish, unknown type");
|
||||
}
|
||||
|
||||
return newDish;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates the data to the given type format.
|
||||
*
|
||||
* If running in the browser, _translate is asynchronous.
|
||||
*
|
||||
* @param {number} toType - The data type of value, see Dish enums.
|
||||
* @returns {Promise || undefined}
|
||||
*/
|
||||
_translate(toType) {
|
||||
log.debug(`Translating Dish from ${Dish.enumLookup(this.type)} to ${Dish.enumLookup(toType)}`);
|
||||
|
||||
// Node environment => translate is sync
|
||||
if (isNodeEnvironment()) {
|
||||
this._toArrayBuffer();
|
||||
this.type = Dish.ARRAY_BUFFER;
|
||||
this._fromArrayBuffer(toType);
|
||||
|
||||
// Browser environment => translate is async
|
||||
} else {
|
||||
return new Promise((resolve, reject) => {
|
||||
this._toArrayBuffer()
|
||||
.then(() => this.type = Dish.ARRAY_BUFFER)
|
||||
.then(() => {
|
||||
this._fromArrayBuffer(toType);
|
||||
resolve();
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert this.value to an ArrayBuffer
|
||||
*
|
||||
* If running in a browser, _toByteArray is asynchronous.
|
||||
*
|
||||
* @returns {Promise || undefined}
|
||||
*/
|
||||
_toArrayBuffer() {
|
||||
// Using 'bind' here to allow this.value to be mutated within translation functions
|
||||
const toByteArrayFuncs = {
|
||||
browser: {
|
||||
[Dish.STRING]: () => Promise.resolve(DishString.toArrayBuffer.bind(this)()),
|
||||
[Dish.NUMBER]: () => Promise.resolve(DishNumber.toArrayBuffer.bind(this)()),
|
||||
[Dish.HTML]: () => Promise.resolve(DishHTML.toArrayBuffer.bind(this)()),
|
||||
[Dish.ARRAY_BUFFER]: () => Promise.resolve(),
|
||||
[Dish.BIG_NUMBER]: () => Promise.resolve(DishBigNumber.toArrayBuffer.bind(this)()),
|
||||
[Dish.JSON]: () => Promise.resolve(DishJSON.toArrayBuffer.bind(this)()),
|
||||
[Dish.FILE]: () => DishFile.toArrayBuffer.bind(this)(),
|
||||
[Dish.LIST_FILE]: () => Promise.resolve(DishListFile.toArrayBuffer.bind(this)()),
|
||||
[Dish.BYTE_ARRAY]: () => Promise.resolve(DishByteArray.toArrayBuffer.bind(this)()),
|
||||
},
|
||||
node: {
|
||||
[Dish.STRING]: () => DishString.toArrayBuffer.bind(this)(),
|
||||
[Dish.NUMBER]: () => DishNumber.toArrayBuffer.bind(this)(),
|
||||
[Dish.HTML]: () => DishHTML.toArrayBuffer.bind(this)(),
|
||||
[Dish.ARRAY_BUFFER]: () => {},
|
||||
[Dish.BIG_NUMBER]: () => DishBigNumber.toArrayBuffer.bind(this)(),
|
||||
[Dish.JSON]: () => DishJSON.toArrayBuffer.bind(this)(),
|
||||
[Dish.FILE]: () => DishFile.toArrayBuffer.bind(this)(),
|
||||
[Dish.LIST_FILE]: () => DishListFile.toArrayBuffer.bind(this)(),
|
||||
[Dish.BYTE_ARRAY]: () => DishByteArray.toArrayBuffer.bind(this)(),
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
return toByteArrayFuncs[isNodeEnvironment() && "node" || "browser"][this.type]();
|
||||
} catch (err) {
|
||||
throw new DishError(`Error translating from ${Dish.enumLookup(this.type)} to ArrayBuffer: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert this.value to the given type from ArrayBuffer
|
||||
*
|
||||
* @param {number} toType - the Dish enum to convert to
|
||||
*/
|
||||
_fromArrayBuffer(toType) {
|
||||
|
||||
// Using 'bind' here to allow this.value to be mutated within translation functions
|
||||
const toTypeFunctions = {
|
||||
[Dish.STRING]: () => DishString.fromArrayBuffer.bind(this)(),
|
||||
[Dish.NUMBER]: () => DishNumber.fromArrayBuffer.bind(this)(),
|
||||
[Dish.HTML]: () => DishHTML.fromArrayBuffer.bind(this)(),
|
||||
[Dish.ARRAY_BUFFER]: () => {},
|
||||
[Dish.BIG_NUMBER]: () => DishBigNumber.fromArrayBuffer.bind(this)(),
|
||||
[Dish.JSON]: () => DishJSON.fromArrayBuffer.bind(this)(),
|
||||
[Dish.FILE]: () => DishFile.fromArrayBuffer.bind(this)(),
|
||||
[Dish.LIST_FILE]: () => DishListFile.fromArrayBuffer.bind(this)(),
|
||||
[Dish.BYTE_ARRAY]: () => DishByteArray.fromArrayBuffer.bind(this)(),
|
||||
};
|
||||
|
||||
try {
|
||||
toTypeFunctions[toType]();
|
||||
this.type = toType;
|
||||
} catch (err) {
|
||||
throw new DishError(`Error translating from ArrayBuffer to ${Dish.enumLookup(toType)}: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Dish data type enum for byte arrays.
|
||||
* @readonly
|
||||
* @enum
|
||||
*/
|
||||
Dish.BYTE_ARRAY = 0;
|
||||
/**
|
||||
* Dish data type enum for strings.
|
||||
* @readonly
|
||||
* @enum
|
||||
*/
|
||||
Dish.STRING = 1;
|
||||
/**
|
||||
* Dish data type enum for numbers.
|
||||
* @readonly
|
||||
* @enum
|
||||
*/
|
||||
Dish.NUMBER = 2;
|
||||
/**
|
||||
* Dish data type enum for HTML.
|
||||
* @readonly
|
||||
* @enum
|
||||
*/
|
||||
Dish.HTML = 3;
|
||||
/**
|
||||
* Dish data type enum for ArrayBuffers.
|
||||
* @readonly
|
||||
* @enum
|
||||
*/
|
||||
Dish.ARRAY_BUFFER = 4;
|
||||
/**
|
||||
* Dish data type enum for BigNumbers.
|
||||
* @readonly
|
||||
* @enum
|
||||
*/
|
||||
Dish.BIG_NUMBER = 5;
|
||||
/**
|
||||
* Dish data type enum for JSON.
|
||||
* @readonly
|
||||
* @enum
|
||||
*/
|
||||
Dish.JSON = 6;
|
||||
/**
|
||||
* Dish data type enum for lists of files.
|
||||
* @readonly
|
||||
* @enum
|
||||
*/
|
||||
Dish.FILE = 7;
|
||||
/**
|
||||
* Dish data type enum for lists of files.
|
||||
* @readonly
|
||||
* @enum
|
||||
*/
|
||||
Dish.LIST_FILE = 8;
|
||||
|
||||
|
||||
export default Dish;
|
|
@ -1,198 +0,0 @@
|
|||
import Recipe from "./Recipe.js";
|
||||
import Dish from "./Dish.js";
|
||||
|
||||
|
||||
/**
|
||||
* Flow Control operations.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* @namespace
|
||||
*/
|
||||
const FlowControl = {
|
||||
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
FORK_DELIM: "\\n",
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
MERGE_DELIM: "\\n",
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
FORK_IGNORE_ERRORS: false,
|
||||
|
||||
/**
|
||||
* Fork operation.
|
||||
*
|
||||
* @param {Object} state - The current state of the recipe.
|
||||
* @param {number} state.progress - The current position in the recipe.
|
||||
* @param {Dish} state.dish - The Dish being operated on.
|
||||
* @param {Operation[]} state.opList - The list of operations in the recipe.
|
||||
* @returns {Object} The updated state of the recipe.
|
||||
*/
|
||||
runFork: function(state) {
|
||||
var opList = state.opList,
|
||||
inputType = opList[state.progress].inputType,
|
||||
outputType = opList[state.progress].outputType,
|
||||
input = state.dish.get(inputType),
|
||||
ings = opList[state.progress].getIngValues(),
|
||||
splitDelim = ings[0],
|
||||
mergeDelim = ings[1],
|
||||
ignoreErrors = ings[2],
|
||||
subOpList = [],
|
||||
inputs = [];
|
||||
|
||||
if (input)
|
||||
inputs = input.split(splitDelim);
|
||||
|
||||
// Create subOpList for each tranche to operate on
|
||||
// (all remaining operations unless we encounter a Merge)
|
||||
for (var i = state.progress + 1; i < opList.length; i++) {
|
||||
if (opList[i].name === "Merge" && !opList[i].isDisabled()) {
|
||||
break;
|
||||
} else {
|
||||
subOpList.push(opList[i]);
|
||||
}
|
||||
}
|
||||
|
||||
var recipe = new Recipe(),
|
||||
output = "",
|
||||
progress = 0;
|
||||
|
||||
recipe.addOperations(subOpList);
|
||||
|
||||
// Run recipe over each tranche
|
||||
for (i = 0; i < inputs.length; i++) {
|
||||
var dish = new Dish(inputs[i], inputType);
|
||||
try {
|
||||
progress = recipe.execute(dish, 0);
|
||||
} catch (err) {
|
||||
if (!ignoreErrors) {
|
||||
throw err;
|
||||
}
|
||||
progress = err.progress + 1;
|
||||
}
|
||||
output += dish.get(outputType) + mergeDelim;
|
||||
}
|
||||
|
||||
state.dish.set(output, outputType);
|
||||
state.progress += progress;
|
||||
return state;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Merge operation.
|
||||
*
|
||||
* @param {Object} state - The current state of the recipe.
|
||||
* @param {number} state.progress - The current position in the recipe.
|
||||
* @param {Dish} state.dish - The Dish being operated on.
|
||||
* @param {Operation[]} state.opList - The list of operations in the recipe.
|
||||
* @returns {Object} The updated state of the recipe.
|
||||
*/
|
||||
runMerge: function(state) {
|
||||
// No need to actually do anything here. The fork operation will
|
||||
// merge when it sees this operation.
|
||||
return state;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
JUMP_NUM: 0,
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
MAX_JUMPS: 10,
|
||||
|
||||
/**
|
||||
* Jump operation.
|
||||
*
|
||||
* @param {Object} state - The current state of the recipe.
|
||||
* @param {number} state.progress - The current position in the recipe.
|
||||
* @param {Dish} state.dish - The Dish being operated on.
|
||||
* @param {Operation[]} state.opList - The list of operations in the recipe.
|
||||
* @param {number} state.numJumps - The number of jumps taken so far.
|
||||
* @returns {Object} The updated state of the recipe.
|
||||
*/
|
||||
runJump: function(state) {
|
||||
var ings = state.opList[state.progress].getIngValues(),
|
||||
jumpNum = ings[0],
|
||||
maxJumps = ings[1];
|
||||
|
||||
if (jumpNum < 0) {
|
||||
jumpNum--;
|
||||
}
|
||||
|
||||
if (state.numJumps >= maxJumps) {
|
||||
return state;
|
||||
}
|
||||
|
||||
state.progress += jumpNum;
|
||||
state.numJumps++;
|
||||
return state;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Conditional Jump operation.
|
||||
*
|
||||
* @param {Object} state - The current state of the recipe.
|
||||
* @param {number} state.progress - The current position in the recipe.
|
||||
* @param {Dish} state.dish - The Dish being operated on.
|
||||
* @param {Operation[]} state.opList - The list of operations in the recipe.
|
||||
* @param {number} state.numJumps - The number of jumps taken so far.
|
||||
* @returns {Object} The updated state of the recipe.
|
||||
*/
|
||||
runCondJump: function(state) {
|
||||
var ings = state.opList[state.progress].getIngValues(),
|
||||
dish = state.dish,
|
||||
regexStr = ings[0],
|
||||
jumpNum = ings[1],
|
||||
maxJumps = ings[2];
|
||||
|
||||
if (jumpNum < 0) {
|
||||
jumpNum--;
|
||||
}
|
||||
|
||||
if (state.numJumps >= maxJumps) {
|
||||
return state;
|
||||
}
|
||||
|
||||
if (regexStr !== "" && dish.get(Dish.STRING).search(regexStr) > -1) {
|
||||
state.progress += jumpNum;
|
||||
state.numJumps++;
|
||||
}
|
||||
|
||||
return state;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Return operation.
|
||||
*
|
||||
* @param {Object} state - The current state of the recipe.
|
||||
* @param {number} state.progress - The current position in the recipe.
|
||||
* @param {Dish} state.dish - The Dish being operated on.
|
||||
* @param {Operation[]} state.opList - The list of operations in the recipe.
|
||||
* @returns {Object} The updated state of the recipe.
|
||||
*/
|
||||
runReturn: function(state) {
|
||||
state.progress = state.opList.length;
|
||||
return state;
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
export default FlowControl;
|
|
@ -1,90 +0,0 @@
|
|||
import Utils from "./Utils.js";
|
||||
|
||||
|
||||
/**
|
||||
* The arguments to operations.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* @class
|
||||
* @param {Object} ingredientConfig
|
||||
*/
|
||||
var Ingredient = function(ingredientConfig) {
|
||||
this.name = "";
|
||||
this.type = "";
|
||||
this.value = null;
|
||||
|
||||
if (ingredientConfig) {
|
||||
this._parseConfig(ingredientConfig);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Reads and parses the given config.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} ingredientConfig
|
||||
*/
|
||||
Ingredient.prototype._parseConfig = function(ingredientConfig) {
|
||||
this.name = ingredientConfig.name;
|
||||
this.type = ingredientConfig.type;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the value of the Ingredient as it should be displayed in a recipe config.
|
||||
*
|
||||
* @returns {*}
|
||||
*/
|
||||
Ingredient.prototype.getConfig = function() {
|
||||
return this.value;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the value of the Ingredient.
|
||||
*
|
||||
* @param {*} value
|
||||
*/
|
||||
Ingredient.prototype.setValue = function(value) {
|
||||
this.value = Ingredient.prepare(value, this.type);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Most values will be strings when they are entered. This function converts them to the correct
|
||||
* type.
|
||||
*
|
||||
* @static
|
||||
* @param {*} data
|
||||
* @param {string} type - The name of the data type.
|
||||
*/
|
||||
Ingredient.prepare = function(data, type) {
|
||||
switch (type) {
|
||||
case "binaryString":
|
||||
case "binaryShortString":
|
||||
case "editableOption":
|
||||
return Utils.parseEscapedChars(data);
|
||||
case "byteArray":
|
||||
if (typeof data == "string") {
|
||||
data = data.replace(/\s+/g, "");
|
||||
return Utils.hexToByteArray(data);
|
||||
} else {
|
||||
return data;
|
||||
}
|
||||
case "number":
|
||||
var number = parseFloat(data);
|
||||
if (isNaN(number)) {
|
||||
var sample = Utils.truncate(data.toString(), 10);
|
||||
throw "Invalid ingredient value. Not a number: " + sample;
|
||||
}
|
||||
return number;
|
||||
default:
|
||||
return data;
|
||||
}
|
||||
};
|
||||
|
||||
export default Ingredient;
|
132
src/core/Ingredient.mjs
Executable file
132
src/core/Ingredient.mjs
Executable file
|
@ -0,0 +1,132 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Utils from "./Utils.mjs";
|
||||
import {fromHex} from "./lib/Hex.mjs";
|
||||
|
||||
/**
|
||||
* The arguments to operations.
|
||||
*/
|
||||
class Ingredient {
|
||||
|
||||
/**
|
||||
* Ingredient constructor
|
||||
*
|
||||
* @param {Object} ingredientConfig
|
||||
*/
|
||||
constructor(ingredientConfig) {
|
||||
this.name = "";
|
||||
this.type = "";
|
||||
this._value = null;
|
||||
this.disabled = false;
|
||||
this.hint = "";
|
||||
this.rows = 0;
|
||||
this.toggleValues = [];
|
||||
this.target = null;
|
||||
this.defaultIndex = 0;
|
||||
this.maxLength = null;
|
||||
this.min = null;
|
||||
this.max = null;
|
||||
this.step = 1;
|
||||
|
||||
if (ingredientConfig) {
|
||||
this._parseConfig(ingredientConfig);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reads and parses the given config.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} ingredientConfig
|
||||
*/
|
||||
_parseConfig(ingredientConfig) {
|
||||
this.name = ingredientConfig.name;
|
||||
this.type = ingredientConfig.type;
|
||||
this.defaultValue = ingredientConfig.value;
|
||||
this.disabled = !!ingredientConfig.disabled;
|
||||
this.hint = ingredientConfig.hint || false;
|
||||
this.rows = ingredientConfig.rows || false;
|
||||
this.toggleValues = ingredientConfig.toggleValues;
|
||||
this.target = typeof ingredientConfig.target !== "undefined" ? ingredientConfig.target : null;
|
||||
this.defaultIndex = typeof ingredientConfig.defaultIndex !== "undefined" ? ingredientConfig.defaultIndex : 0;
|
||||
this.maxLength = ingredientConfig.maxLength || null;
|
||||
this.min = ingredientConfig.min;
|
||||
this.max = ingredientConfig.max;
|
||||
this.step = ingredientConfig.step;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the value of the Ingredient as it should be displayed in a recipe config.
|
||||
*
|
||||
* @returns {*}
|
||||
*/
|
||||
get config() {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the value of the Ingredient.
|
||||
*
|
||||
* @param {*} value
|
||||
*/
|
||||
set value(value) {
|
||||
this._value = Ingredient.prepare(value, this.type);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the value of the Ingredient.
|
||||
*
|
||||
* @returns {*}
|
||||
*/
|
||||
get value() {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Most values will be strings when they are entered. This function converts them to the correct
|
||||
* type.
|
||||
*
|
||||
* @param {*} data
|
||||
* @param {string} type - The name of the data type.
|
||||
*/
|
||||
static prepare(data, type) {
|
||||
let number;
|
||||
|
||||
switch (type) {
|
||||
case "binaryString":
|
||||
case "binaryShortString":
|
||||
case "editableOption":
|
||||
case "editableOptionShort":
|
||||
return Utils.parseEscapedChars(data);
|
||||
case "byteArray":
|
||||
if (typeof data == "string") {
|
||||
data = data.replace(/\s+/g, "");
|
||||
return fromHex(data);
|
||||
} else {
|
||||
return data;
|
||||
}
|
||||
case "number":
|
||||
if (data === null) return data;
|
||||
number = parseFloat(data);
|
||||
if (isNaN(number)) {
|
||||
const sample = Utils.truncate(data.toString(), 10);
|
||||
throw "Invalid ingredient value. Not a number: " + sample;
|
||||
}
|
||||
return number;
|
||||
default:
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default Ingredient;
|
|
@ -1,163 +0,0 @@
|
|||
import Dish from "./Dish.js";
|
||||
import Ingredient from "./Ingredient.js";
|
||||
|
||||
|
||||
/**
|
||||
* The Operation specified by the user to be run.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* @class
|
||||
* @param {string} operationName
|
||||
* @param {Object} operationConfig
|
||||
*/
|
||||
var Operation = function(operationName, operationConfig) {
|
||||
this.name = operationName;
|
||||
this.description = "";
|
||||
this.inputType = -1;
|
||||
this.outputType = -1;
|
||||
this.run = null;
|
||||
this.highlight = null;
|
||||
this.highlightReverse = null;
|
||||
this.breakpoint = false;
|
||||
this.disabled = false;
|
||||
this.ingList = [];
|
||||
|
||||
if (operationConfig) {
|
||||
this._parseConfig(operationConfig);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Reads and parses the given config.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} operationConfig
|
||||
*/
|
||||
Operation.prototype._parseConfig = function(operationConfig) {
|
||||
this.description = operationConfig.description;
|
||||
this.inputType = Dish.typeEnum(operationConfig.inputType);
|
||||
this.outputType = Dish.typeEnum(operationConfig.outputType);
|
||||
this.run = operationConfig.run;
|
||||
this.highlight = operationConfig.highlight;
|
||||
this.highlightReverse = operationConfig.highlightReverse;
|
||||
this.flowControl = operationConfig.flowControl;
|
||||
|
||||
for (var a = 0; a < operationConfig.args.length; a++) {
|
||||
var ingredientConfig = operationConfig.args[a];
|
||||
var ingredient = new Ingredient(ingredientConfig);
|
||||
this.addIngredient(ingredient);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the value of the Operation as it should be displayed in a recipe config.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
Operation.prototype.getConfig = function() {
|
||||
var ingredientConfig = [];
|
||||
|
||||
for (var o = 0; o < this.ingList.length; o++) {
|
||||
ingredientConfig.push(this.ingList[o].getConfig());
|
||||
}
|
||||
|
||||
var operationConfig = {
|
||||
"op": this.name,
|
||||
"args": ingredientConfig
|
||||
};
|
||||
|
||||
return operationConfig;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a new Ingredient to this Operation.
|
||||
*
|
||||
* @param {Ingredient} ingredient
|
||||
*/
|
||||
Operation.prototype.addIngredient = function(ingredient) {
|
||||
this.ingList.push(ingredient);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set the Ingredient values for this Operation.
|
||||
*
|
||||
* @param {Object[]} ingValues
|
||||
*/
|
||||
Operation.prototype.setIngValues = function(ingValues) {
|
||||
for (var i = 0; i < ingValues.length; i++) {
|
||||
this.ingList[i].setValue(ingValues[i]);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the Ingredient values for this Operation.
|
||||
*
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
Operation.prototype.getIngValues = function() {
|
||||
var ingValues = [];
|
||||
for (var i = 0; i < this.ingList.length; i++) {
|
||||
ingValues.push(this.ingList[i].value);
|
||||
}
|
||||
return ingValues;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set whether this Operation has a breakpoint.
|
||||
*
|
||||
* @param {boolean} value
|
||||
*/
|
||||
Operation.prototype.setBreakpoint = function(value) {
|
||||
this.breakpoint = !!value;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if this Operation has a breakpoint set.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
Operation.prototype.isBreakpoint = function() {
|
||||
return this.breakpoint;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set whether this Operation is disabled.
|
||||
*
|
||||
* @param {boolean} value
|
||||
*/
|
||||
Operation.prototype.setDisabled = function(value) {
|
||||
this.disabled = !!value;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if this Operation is disabled.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
Operation.prototype.isDisabled = function() {
|
||||
return this.disabled;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if this Operation is a flow control.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
Operation.prototype.isFlowControl = function() {
|
||||
return this.flowControl;
|
||||
};
|
||||
|
||||
export default Operation;
|
322
src/core/Operation.mjs
Executable file
322
src/core/Operation.mjs
Executable file
|
@ -0,0 +1,322 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Dish from "./Dish.mjs";
|
||||
import Ingredient from "./Ingredient.mjs";
|
||||
|
||||
/**
|
||||
* The Operation specified by the user to be run.
|
||||
*/
|
||||
class Operation {
|
||||
|
||||
/**
|
||||
* Operation constructor
|
||||
*/
|
||||
constructor() {
|
||||
// Private fields
|
||||
this._inputType = -1;
|
||||
this._outputType = -1;
|
||||
this._presentType = -1;
|
||||
this._breakpoint = false;
|
||||
this._disabled = false;
|
||||
this._flowControl = false;
|
||||
this._manualBake = false;
|
||||
this._ingList = [];
|
||||
|
||||
// Public fields
|
||||
this.name = "";
|
||||
this.module = "";
|
||||
this.description = "";
|
||||
this.infoURL = null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Interface for operation runner
|
||||
*
|
||||
* @param {*} input
|
||||
* @param {Object[]} args
|
||||
* @returns {*}
|
||||
*/
|
||||
run(input, args) {
|
||||
return input;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Interface for forward highlighter
|
||||
*
|
||||
* @param {Object[]} pos
|
||||
* @param {number} pos[].start
|
||||
* @param {number} pos[].end
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} pos
|
||||
*/
|
||||
highlight(pos, args) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Interface for reverse highlighter
|
||||
*
|
||||
* @param {Object[]} pos
|
||||
* @param {number} pos[].start
|
||||
* @param {number} pos[].end
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} pos
|
||||
*/
|
||||
highlightReverse(pos, args) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Method to be called when displaying the result of an operation in a human-readable
|
||||
* format. This allows operations to return usable data from their run() method and
|
||||
* only format them when this method is called.
|
||||
*
|
||||
* The default action is to return the data unchanged, but child classes can override
|
||||
* this behaviour.
|
||||
*
|
||||
* @param {*} data - The result of the run() function
|
||||
* @param {Object[]} args - The operation's arguments
|
||||
* @returns {*} - A human-readable version of the data
|
||||
*/
|
||||
present(data, args) {
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the input type as a Dish enum.
|
||||
*
|
||||
* @param {string} typeStr
|
||||
*/
|
||||
set inputType(typeStr) {
|
||||
this._inputType = Dish.typeEnum(typeStr);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the input type as a readable string.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
get inputType() {
|
||||
return Dish.enumLookup(this._inputType);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the output type as a Dish enum.
|
||||
*
|
||||
* @param {string} typeStr
|
||||
*/
|
||||
set outputType(typeStr) {
|
||||
this._outputType = Dish.typeEnum(typeStr);
|
||||
if (this._presentType < 0) this._presentType = this._outputType;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the output type as a readable string.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
get outputType() {
|
||||
return Dish.enumLookup(this._outputType);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the presentation type as a Dish enum.
|
||||
*
|
||||
* @param {string} typeStr
|
||||
*/
|
||||
set presentType(typeStr) {
|
||||
this._presentType = Dish.typeEnum(typeStr);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the presentation type as a readable string.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
get presentType() {
|
||||
return Dish.enumLookup(this._presentType);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the args for the current operation.
|
||||
*
|
||||
* @param {Object[]} conf
|
||||
*/
|
||||
set args(conf) {
|
||||
conf.forEach(arg => {
|
||||
const ingredient = new Ingredient(arg);
|
||||
this.addIngredient(ingredient);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the args for the current operation.
|
||||
*
|
||||
* @param {Object[]} conf
|
||||
*/
|
||||
get args() {
|
||||
return this._ingList.map(ing => {
|
||||
const conf = {
|
||||
name: ing.name,
|
||||
type: ing.type,
|
||||
value: ing.defaultValue
|
||||
};
|
||||
|
||||
if (ing.toggleValues) conf.toggleValues = ing.toggleValues;
|
||||
if (ing.hint) conf.hint = ing.hint;
|
||||
if (ing.rows) conf.rows = ing.rows;
|
||||
if (ing.disabled) conf.disabled = ing.disabled;
|
||||
if (ing.target) conf.target = ing.target;
|
||||
if (ing.defaultIndex) conf.defaultIndex = ing.defaultIndex;
|
||||
if (ing.maxLength) conf.maxLength = ing.maxLength;
|
||||
if (typeof ing.min === "number") conf.min = ing.min;
|
||||
if (typeof ing.max === "number") conf.max = ing.max;
|
||||
if (ing.step) conf.step = ing.step;
|
||||
return conf;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the value of the Operation as it should be displayed in a recipe config.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
get config() {
|
||||
return {
|
||||
"op": this.name,
|
||||
"args": this._ingList.map(ing => ing.config)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a new Ingredient to this Operation.
|
||||
*
|
||||
* @param {Ingredient} ingredient
|
||||
*/
|
||||
addIngredient(ingredient) {
|
||||
this._ingList.push(ingredient);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the Ingredient values for this Operation.
|
||||
*
|
||||
* @param {Object[]} ingValues
|
||||
*/
|
||||
set ingValues(ingValues) {
|
||||
ingValues.forEach((val, i) => {
|
||||
this._ingList[i].value = val;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the Ingredient values for this Operation.
|
||||
*
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
get ingValues() {
|
||||
return this._ingList.map(ing => ing.value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set whether this Operation has a breakpoint.
|
||||
*
|
||||
* @param {boolean} value
|
||||
*/
|
||||
set breakpoint(value) {
|
||||
this._breakpoint = !!value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if this Operation has a breakpoint set.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get breakpoint() {
|
||||
return this._breakpoint;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set whether this Operation is disabled.
|
||||
*
|
||||
* @param {boolean} value
|
||||
*/
|
||||
set disabled(value) {
|
||||
this._disabled = !!value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if this Operation is disabled.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get disabled() {
|
||||
return this._disabled;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if this Operation is a flow control.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get flowControl() {
|
||||
return this._flowControl;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set whether this Operation is a flowcontrol op.
|
||||
*
|
||||
* @param {boolean} value
|
||||
*/
|
||||
set flowControl(value) {
|
||||
this._flowControl = !!value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if this Operation should not trigger AutoBake.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get manualBake() {
|
||||
return this._manualBake;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set whether this Operation should trigger AutoBake.
|
||||
*
|
||||
* @param {boolean} value
|
||||
*/
|
||||
set manualBake(value) {
|
||||
this._manualBake = !!value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default Operation;
|
|
@ -1,220 +0,0 @@
|
|||
import Operation from "./Operation.js";
|
||||
import OperationConfig from "./config/OperationConfig.js";
|
||||
|
||||
|
||||
/**
|
||||
* The Recipe controls a list of Operations and the Dish they operate on.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* @class
|
||||
* @param {Object} recipeConfig
|
||||
*/
|
||||
var Recipe = function(recipeConfig) {
|
||||
this.opList = [];
|
||||
|
||||
if (recipeConfig) {
|
||||
this._parseConfig(recipeConfig);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Reads and parses the given config.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} recipeConfig
|
||||
*/
|
||||
Recipe.prototype._parseConfig = function(recipeConfig) {
|
||||
for (var c = 0; c < recipeConfig.length; c++) {
|
||||
var operationName = recipeConfig[c].op;
|
||||
var operationConfig = OperationConfig[operationName];
|
||||
var operation = new Operation(operationName, operationConfig);
|
||||
operation.setIngValues(recipeConfig[c].args);
|
||||
operation.setBreakpoint(recipeConfig[c].breakpoint);
|
||||
operation.setDisabled(recipeConfig[c].disabled);
|
||||
this.addOperation(operation);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the value of the Recipe as it should be displayed in a recipe config.
|
||||
*
|
||||
* @returns {*}
|
||||
*/
|
||||
Recipe.prototype.getConfig = function() {
|
||||
var recipeConfig = [];
|
||||
|
||||
for (var o = 0; o < this.opList.length; o++) {
|
||||
recipeConfig.push(this.opList[o].getConfig());
|
||||
}
|
||||
|
||||
return recipeConfig;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a new Operation to this Recipe.
|
||||
*
|
||||
* @param {Operation} operation
|
||||
*/
|
||||
Recipe.prototype.addOperation = function(operation) {
|
||||
this.opList.push(operation);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a list of Operations to this Recipe.
|
||||
*
|
||||
* @param {Operation[]} operations
|
||||
*/
|
||||
Recipe.prototype.addOperations = function(operations) {
|
||||
this.opList = this.opList.concat(operations);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set a breakpoint on a specified Operation.
|
||||
*
|
||||
* @param {number} position - The index of the Operation
|
||||
* @param {boolean} value
|
||||
*/
|
||||
Recipe.prototype.setBreakpoint = function(position, value) {
|
||||
try {
|
||||
this.opList[position].setBreakpoint(value);
|
||||
} catch (err) {
|
||||
// Ignore index error
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Remove breakpoints on all Operations in the Recipe up to the specified position. Used by Flow
|
||||
* Control Fork operation.
|
||||
*
|
||||
* @param {number} pos
|
||||
*/
|
||||
Recipe.prototype.removeBreaksUpTo = function(pos) {
|
||||
for (var i = 0; i < pos; i++) {
|
||||
this.opList[i].setBreakpoint(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if there is an Flow Control Operation in this Recipe.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
Recipe.prototype.containsFlowControl = function() {
|
||||
for (var i = 0; i < this.opList.length; i++) {
|
||||
if (this.opList[i].isFlowControl()) return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the index of the last Operation index that will be executed, taking into account disabled
|
||||
* Operations and breakpoints.
|
||||
*
|
||||
* @param {number} [startIndex=0] - The index to start searching from
|
||||
* @returns (number}
|
||||
*/
|
||||
Recipe.prototype.lastOpIndex = function(startIndex) {
|
||||
var i = startIndex + 1 || 0,
|
||||
op;
|
||||
|
||||
for (; i < this.opList.length; i++) {
|
||||
op = this.opList[i];
|
||||
if (op.isDisabled()) return i-1;
|
||||
if (op.isBreakpoint()) return i-1;
|
||||
}
|
||||
|
||||
return i-1;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Executes each operation in the recipe over the given Dish.
|
||||
*
|
||||
* @param {Dish} dish
|
||||
* @param {number} [startFrom=0] - The index of the Operation to start executing from
|
||||
* @returns {number} - The final progress through the recipe
|
||||
*/
|
||||
Recipe.prototype.execute = function(dish, startFrom) {
|
||||
startFrom = startFrom || 0;
|
||||
var op, input, output, numJumps = 0;
|
||||
|
||||
for (var i = startFrom; i < this.opList.length; i++) {
|
||||
op = this.opList[i];
|
||||
if (op.isDisabled()) {
|
||||
continue;
|
||||
}
|
||||
if (op.isBreakpoint()) {
|
||||
return i;
|
||||
}
|
||||
|
||||
try {
|
||||
input = dish.get(op.inputType);
|
||||
|
||||
if (op.isFlowControl()) {
|
||||
// Package up the current state
|
||||
var state = {
|
||||
"progress" : i,
|
||||
"dish" : dish,
|
||||
"opList" : this.opList,
|
||||
"numJumps" : numJumps
|
||||
};
|
||||
|
||||
state = op.run(state);
|
||||
i = state.progress;
|
||||
numJumps = state.numJumps;
|
||||
} else {
|
||||
output = op.run(input, op.getIngValues());
|
||||
dish.set(output, op.outputType);
|
||||
}
|
||||
} catch (err) {
|
||||
var e = typeof err == "string" ? { message: err } : err;
|
||||
|
||||
e.progress = i;
|
||||
if (e.fileName) {
|
||||
e.displayStr = op.name + " - " + e.name + " in " +
|
||||
e.fileName + " on line " + e.lineNumber +
|
||||
".<br><br>Message: " + (e.displayStr || e.message);
|
||||
} else {
|
||||
e.displayStr = op.name + " - " + (e.displayStr || e.message);
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
return this.opList.length;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the recipe configuration in string format.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
Recipe.prototype.toString = function() {
|
||||
return JSON.stringify(this.getConfig());
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a Recipe from a given configuration string.
|
||||
*
|
||||
* @param {string} recipeStr
|
||||
*/
|
||||
Recipe.prototype.fromString = function(recipeStr) {
|
||||
var recipeConfig = JSON.parse(recipeStr);
|
||||
this._parseConfig(recipeConfig);
|
||||
};
|
||||
|
||||
export default Recipe;
|
346
src/core/Recipe.mjs
Executable file
346
src/core/Recipe.mjs
Executable file
|
@ -0,0 +1,346 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import OperationConfig from "./config/OperationConfig.json" assert {type: "json"};
|
||||
import OperationError from "./errors/OperationError.mjs";
|
||||
import Operation from "./Operation.mjs";
|
||||
import DishError from "./errors/DishError.mjs";
|
||||
import log from "loglevel";
|
||||
import { isWorkerEnvironment } from "./Utils.mjs";
|
||||
|
||||
// Cache container for modules
|
||||
let modules = null;
|
||||
|
||||
/**
|
||||
* The Recipe controls a list of Operations and the Dish they operate on.
|
||||
*/
|
||||
class Recipe {
|
||||
|
||||
/**
|
||||
* Recipe constructor
|
||||
*
|
||||
* @param {Object} recipeConfig
|
||||
*/
|
||||
constructor(recipeConfig) {
|
||||
this.opList = [];
|
||||
|
||||
if (recipeConfig) {
|
||||
this._parseConfig(recipeConfig);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reads and parses the given config.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} recipeConfig
|
||||
*/
|
||||
_parseConfig(recipeConfig) {
|
||||
recipeConfig.forEach(c => {
|
||||
this.opList.push({
|
||||
name: c.op,
|
||||
module: OperationConfig[c.op].module,
|
||||
ingValues: c.args,
|
||||
breakpoint: c.breakpoint,
|
||||
disabled: c.disabled || c.op === "Comment",
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Populate elements of opList with operation instances.
|
||||
* Dynamic import here removes top-level cyclic dependency issue.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
async _hydrateOpList() {
|
||||
if (!modules) {
|
||||
// Using Webpack Magic Comments to force the dynamic import to be included in the main chunk
|
||||
// https://webpack.js.org/api/module-methods/
|
||||
modules = await import(/* webpackMode: "eager" */ "./config/modules/OpModules.mjs");
|
||||
modules = modules.default;
|
||||
}
|
||||
|
||||
this.opList = this.opList.map(o => {
|
||||
if (o instanceof Operation) {
|
||||
return o;
|
||||
} else {
|
||||
const op = new modules[o.module][o.name]();
|
||||
op.ingValues = o.ingValues;
|
||||
op.breakpoint = o.breakpoint;
|
||||
op.disabled = o.disabled;
|
||||
return op;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the value of the Recipe as it should be displayed in a recipe config.
|
||||
*
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
get config() {
|
||||
return this.opList.map(op => ({
|
||||
op: op.name,
|
||||
args: op.ingValues,
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a new Operation to this Recipe.
|
||||
*
|
||||
* @param {Operation} operation
|
||||
*/
|
||||
addOperation(operation) {
|
||||
this.opList.push(operation);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a list of Operations to this Recipe.
|
||||
*
|
||||
* @param {Operation[]} operations
|
||||
*/
|
||||
addOperations(operations) {
|
||||
operations.forEach(o => {
|
||||
if (o instanceof Operation) {
|
||||
this.opList.push(o);
|
||||
} else {
|
||||
this.opList.push({
|
||||
name: o.name,
|
||||
module: o.module,
|
||||
ingValues: o.args,
|
||||
breakpoint: o.breakpoint,
|
||||
disabled: o.disabled,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set a breakpoint on a specified Operation.
|
||||
*
|
||||
* @param {number} position - The index of the Operation
|
||||
* @param {boolean} value
|
||||
*/
|
||||
setBreakpoint(position, value) {
|
||||
try {
|
||||
this.opList[position].breakpoint = value;
|
||||
} catch (err) {
|
||||
// Ignore index error
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove breakpoints on all Operations in the Recipe up to the specified position. Used by Flow
|
||||
* Control Fork operation.
|
||||
*
|
||||
* @param {number} pos
|
||||
*/
|
||||
removeBreaksUpTo(pos) {
|
||||
for (let i = 0; i < pos; i++) {
|
||||
this.opList[i].breakpoint = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if there is a Flow Control Operation in this Recipe.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
containsFlowControl() {
|
||||
return this.opList.reduce((acc, curr) => {
|
||||
return acc || curr.flowControl;
|
||||
}, false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Executes each operation in the recipe over the given Dish.
|
||||
*
|
||||
* @param {Dish} dish
|
||||
* @param {number} [startFrom=0]
|
||||
* - The index of the Operation to start executing from
|
||||
* @param {number} [forkState={}]
|
||||
* - If this is a forked recipe, the state of the recipe up to this point
|
||||
* @returns {number}
|
||||
* - The final progress through the recipe
|
||||
*/
|
||||
async execute(dish, startFrom=0, forkState={}) {
|
||||
let op, input, output,
|
||||
numJumps = 0,
|
||||
numRegisters = forkState.numRegisters || 0;
|
||||
|
||||
if (startFrom === 0) this.lastRunOp = null;
|
||||
|
||||
await this._hydrateOpList();
|
||||
|
||||
log.debug(`[*] Executing recipe of ${this.opList.length} operations, starting at ${startFrom}`);
|
||||
|
||||
for (let i = startFrom; i < this.opList.length; i++) {
|
||||
op = this.opList[i];
|
||||
log.debug(`[${i}] ${op.name} ${JSON.stringify(op.ingValues)}`);
|
||||
if (op.disabled) {
|
||||
log.debug("Operation is disabled, skipping");
|
||||
continue;
|
||||
}
|
||||
if (op.breakpoint) {
|
||||
log.debug("Pausing at breakpoint");
|
||||
return i;
|
||||
}
|
||||
|
||||
try {
|
||||
input = await dish.get(op.inputType);
|
||||
log.debug(`Executing operation '${op.name}'`);
|
||||
|
||||
if (isWorkerEnvironment()) {
|
||||
self.sendStatusMessage(`Baking... (${i+1}/${this.opList.length})`);
|
||||
self.sendProgressMessage(i + 1, this.opList.length);
|
||||
}
|
||||
|
||||
if (op.flowControl) {
|
||||
// Package up the current state
|
||||
let state = {
|
||||
"progress": i,
|
||||
"dish": dish,
|
||||
"opList": this.opList,
|
||||
"numJumps": numJumps,
|
||||
"numRegisters": numRegisters,
|
||||
"forkOffset": forkState.forkOffset || 0
|
||||
};
|
||||
|
||||
state = await op.run(state);
|
||||
i = state.progress;
|
||||
numJumps = state.numJumps;
|
||||
numRegisters = state.numRegisters;
|
||||
} else {
|
||||
output = await op.run(input, op.ingValues);
|
||||
dish.set(output, op.outputType);
|
||||
}
|
||||
this.lastRunOp = op;
|
||||
} catch (err) {
|
||||
// Return expected errors as output
|
||||
if (err instanceof OperationError || err?.type === "OperationError") {
|
||||
// Cannot rely on `err instanceof OperationError` here as extending
|
||||
// native types is not fully supported yet.
|
||||
dish.set(err.message, "string");
|
||||
return i;
|
||||
} else if (err instanceof DishError || err?.type === "DishError") {
|
||||
dish.set(err.message, "string");
|
||||
return i;
|
||||
} else {
|
||||
const e = typeof err == "string" ? { message: err } : err;
|
||||
|
||||
e.progress = i;
|
||||
if (e.fileName) {
|
||||
e.displayStr = `${op.name} - ${e.name} in ${e.fileName} on line ` +
|
||||
`${e.lineNumber}.<br><br>Message: ${e.displayStr || e.message}`;
|
||||
} else {
|
||||
e.displayStr = `${op.name} - ${e.displayStr || e.message}`;
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("Recipe complete");
|
||||
return this.opList.length;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Present the results of the final operation.
|
||||
*
|
||||
* @param {Dish} dish
|
||||
*/
|
||||
async present(dish) {
|
||||
if (!this.lastRunOp) return;
|
||||
|
||||
const output = await this.lastRunOp.present(
|
||||
await dish.get(this.lastRunOp.outputType),
|
||||
this.lastRunOp.ingValues
|
||||
);
|
||||
dish.set(output, this.lastRunOp.presentType);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the recipe configuration in string format.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
toString() {
|
||||
return JSON.stringify(this.config);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a Recipe from a given configuration string.
|
||||
*
|
||||
* @param {string} recipeStr
|
||||
*/
|
||||
fromString(recipeStr) {
|
||||
const recipeConfig = JSON.parse(recipeStr);
|
||||
this._parseConfig(recipeConfig);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates a list of all the highlight functions assigned to operations in the recipe, if the
|
||||
* entire recipe supports highlighting.
|
||||
*
|
||||
* @returns {Object[]} highlights
|
||||
* @returns {function} highlights[].f
|
||||
* @returns {function} highlights[].b
|
||||
* @returns {Object[]} highlights[].args
|
||||
*/
|
||||
async generateHighlightList() {
|
||||
await this._hydrateOpList();
|
||||
const highlights = [];
|
||||
|
||||
for (let i = 0; i < this.opList.length; i++) {
|
||||
const op = this.opList[i];
|
||||
if (op.disabled) continue;
|
||||
|
||||
// If any breakpoints are set, do not attempt to highlight
|
||||
if (op.breakpoint) return false;
|
||||
|
||||
// If any of the operations do not support highlighting, fail immediately.
|
||||
if (op.highlight === false || op.highlight === undefined) return false;
|
||||
|
||||
highlights.push({
|
||||
f: op.highlight,
|
||||
b: op.highlightReverse,
|
||||
args: op.ingValues
|
||||
});
|
||||
}
|
||||
|
||||
return highlights;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether the previous operation has a different presentation type to its normal output.
|
||||
*
|
||||
* @param {number} progress
|
||||
* @returns {boolean}
|
||||
*/
|
||||
lastOpPresented(progress) {
|
||||
if (progress < 1) return false;
|
||||
return this.opList[progress-1].presentType !== this.opList[progress-1].outputType;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default Recipe;
|
1326
src/core/Utils.js
1326
src/core/Utils.js
File diff suppressed because it is too large
Load diff
1575
src/core/Utils.mjs
Executable file
1575
src/core/Utils.mjs
Executable file
File diff suppressed because it is too large
Load diff
|
@ -1,297 +0,0 @@
|
|||
/**
|
||||
* Type definition for a CatConf.
|
||||
*
|
||||
* @typedef {Object} CatConf
|
||||
* @property {string} name - The display name for the category
|
||||
* @property {string[]} ops - A list of the operations to be included in this category
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Categories of operations.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* @constant
|
||||
* @type {CatConf[]}
|
||||
*/
|
||||
const Categories = [
|
||||
{
|
||||
name: "Favourites",
|
||||
ops: []
|
||||
},
|
||||
{
|
||||
name: "Data format",
|
||||
ops: [
|
||||
"To Hexdump",
|
||||
"From Hexdump",
|
||||
"To Hex",
|
||||
"From Hex",
|
||||
"To Charcode",
|
||||
"From Charcode",
|
||||
"To Decimal",
|
||||
"From Decimal",
|
||||
"To Binary",
|
||||
"From Binary",
|
||||
"To Octal",
|
||||
"From Octal",
|
||||
"To Base64",
|
||||
"From Base64",
|
||||
"Show Base64 offsets",
|
||||
"To Base32",
|
||||
"From Base32",
|
||||
"To Base58",
|
||||
"From Base58",
|
||||
"To Base",
|
||||
"From Base",
|
||||
"To HTML Entity",
|
||||
"From HTML Entity",
|
||||
"URL Encode",
|
||||
"URL Decode",
|
||||
"Unescape Unicode Characters",
|
||||
"To Quoted Printable",
|
||||
"From Quoted Printable",
|
||||
"To Punycode",
|
||||
"From Punycode",
|
||||
"To Hex Content",
|
||||
"From Hex Content",
|
||||
"PEM to Hex",
|
||||
"Hex to PEM",
|
||||
"Parse ASN.1 hex string",
|
||||
"Change IP format",
|
||||
"Text encoding",
|
||||
"Swap endianness",
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Encryption / Encoding",
|
||||
ops: [
|
||||
"AES Encrypt",
|
||||
"AES Decrypt",
|
||||
"Blowfish Encrypt",
|
||||
"Blowfish Decrypt",
|
||||
"DES Encrypt",
|
||||
"DES Decrypt",
|
||||
"Triple DES Encrypt",
|
||||
"Triple DES Decrypt",
|
||||
"Rabbit Encrypt",
|
||||
"Rabbit Decrypt",
|
||||
"RC4",
|
||||
"RC4 Drop",
|
||||
"ROT13",
|
||||
"ROT47",
|
||||
"XOR",
|
||||
"XOR Brute Force",
|
||||
"Vigenère Encode",
|
||||
"Vigenère Decode",
|
||||
"To Morse Code",
|
||||
"From Morse Code",
|
||||
"Affine Cipher Encode",
|
||||
"Affine Cipher Decode",
|
||||
"Atbash Cipher",
|
||||
"Substitute",
|
||||
"Derive PBKDF2 key",
|
||||
"Derive EVP key",
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Public Key",
|
||||
ops: [
|
||||
"Parse X.509 certificate",
|
||||
"Parse ASN.1 hex string",
|
||||
"PEM to Hex",
|
||||
"Hex to PEM",
|
||||
"Hex to Object Identifier",
|
||||
"Object Identifier to Hex",
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Logical operations",
|
||||
ops: [
|
||||
"XOR",
|
||||
"XOR Brute Force",
|
||||
"OR",
|
||||
"NOT",
|
||||
"AND",
|
||||
"ADD",
|
||||
"SUB",
|
||||
"Rotate left",
|
||||
"Rotate right",
|
||||
"ROT13",
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Networking",
|
||||
ops: [
|
||||
"Strip HTTP headers",
|
||||
"Parse User Agent",
|
||||
"Parse IP range",
|
||||
"Parse IPv6 address",
|
||||
"Parse IPv4 header",
|
||||
"Parse URI",
|
||||
"URL Encode",
|
||||
"URL Decode",
|
||||
"Format MAC addresses",
|
||||
"Change IP format",
|
||||
"Group IP addresses",
|
||||
"Encode NetBIOS Name",
|
||||
"Decode NetBIOS Name",
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Language",
|
||||
ops: [
|
||||
"Text encoding",
|
||||
"Unescape Unicode Characters",
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Utils",
|
||||
ops: [
|
||||
"Diff",
|
||||
"Remove whitespace",
|
||||
"Remove null bytes",
|
||||
"To Upper case",
|
||||
"To Lower case",
|
||||
"Add line numbers",
|
||||
"Remove line numbers",
|
||||
"Reverse",
|
||||
"Sort",
|
||||
"Unique",
|
||||
"Split",
|
||||
"Filter",
|
||||
"Count occurrences",
|
||||
"Expand alphabet range",
|
||||
"Parse escaped string",
|
||||
"Drop bytes",
|
||||
"Take bytes",
|
||||
"Pad lines",
|
||||
"Find / Replace",
|
||||
"Regular expression",
|
||||
"Offset checker",
|
||||
"Convert distance",
|
||||
"Convert area",
|
||||
"Convert mass",
|
||||
"Convert speed",
|
||||
"Convert data units",
|
||||
"Parse UNIX file permissions",
|
||||
"Swap endianness",
|
||||
"Parse colour code",
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Date / Time",
|
||||
ops: [
|
||||
"Parse DateTime",
|
||||
"Translate DateTime Format",
|
||||
"From UNIX Timestamp",
|
||||
"To UNIX Timestamp",
|
||||
"Extract dates",
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Extractors",
|
||||
ops: [
|
||||
"Strings",
|
||||
"Extract IP addresses",
|
||||
"Extract email addresses",
|
||||
"Extract MAC addresses",
|
||||
"Extract URLs",
|
||||
"Extract domains",
|
||||
"Extract file paths",
|
||||
"Extract dates",
|
||||
"Regular expression",
|
||||
"XPath expression",
|
||||
"CSS selector",
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Compression",
|
||||
ops: [
|
||||
"Raw Deflate",
|
||||
"Raw Inflate",
|
||||
"Zlib Deflate",
|
||||
"Zlib Inflate",
|
||||
"Gzip",
|
||||
"Gunzip",
|
||||
"Zip",
|
||||
"Unzip",
|
||||
"Bzip2 Decompress",
|
||||
"Tar",
|
||||
"Untar",
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Hashing",
|
||||
ops: [
|
||||
"Analyse hash",
|
||||
"Generate all hashes",
|
||||
"MD2",
|
||||
"MD4",
|
||||
"MD5",
|
||||
"SHA0",
|
||||
"SHA1",
|
||||
"SHA224",
|
||||
"SHA256",
|
||||
"SHA384",
|
||||
"SHA512",
|
||||
"SHA3",
|
||||
"RIPEMD-160",
|
||||
"HMAC",
|
||||
"Fletcher-8 Checksum",
|
||||
"Fletcher-16 Checksum",
|
||||
"Fletcher-32 Checksum",
|
||||
"Fletcher-64 Checksum",
|
||||
"Adler-32 Checksum",
|
||||
"CRC-32 Checksum",
|
||||
"TCP/IP Checksum",
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Code tidy",
|
||||
ops: [
|
||||
"Syntax highlighter",
|
||||
"Generic Code Beautify",
|
||||
"JavaScript Parser",
|
||||
"JavaScript Beautify",
|
||||
"JavaScript Minify",
|
||||
"JSON Beautify",
|
||||
"JSON Minify",
|
||||
"XML Beautify",
|
||||
"XML Minify",
|
||||
"SQL Beautify",
|
||||
"SQL Minify",
|
||||
"CSS Beautify",
|
||||
"CSS Minify",
|
||||
"XPath expression",
|
||||
"CSS selector",
|
||||
"Strip HTML tags",
|
||||
"Diff",
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Other",
|
||||
ops: [
|
||||
"Entropy",
|
||||
"Frequency distribution",
|
||||
"Detect File Type",
|
||||
"Scan for Embedded Files",
|
||||
"Generate UUID",
|
||||
"Numberwang",
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Flow control",
|
||||
ops: [
|
||||
"Fork",
|
||||
"Merge",
|
||||
"Jump",
|
||||
"Conditional Jump",
|
||||
"Return",
|
||||
]
|
||||
},
|
||||
];
|
||||
|
||||
export default Categories;
|
538
src/core/config/Categories.json
Normal file
538
src/core/config/Categories.json
Normal file
|
@ -0,0 +1,538 @@
|
|||
[
|
||||
{
|
||||
"name": "Favourites",
|
||||
"ops": []
|
||||
},
|
||||
{
|
||||
"name": "Data format",
|
||||
"ops": [
|
||||
"To Hexdump",
|
||||
"From Hexdump",
|
||||
"To Hex",
|
||||
"From Hex",
|
||||
"To Charcode",
|
||||
"From Charcode",
|
||||
"To Decimal",
|
||||
"From Decimal",
|
||||
"To Binary",
|
||||
"From Binary",
|
||||
"To Octal",
|
||||
"From Octal",
|
||||
"To Base32",
|
||||
"From Base32",
|
||||
"To Base45",
|
||||
"From Base45",
|
||||
"To Base58",
|
||||
"From Base58",
|
||||
"To Base62",
|
||||
"From Base62",
|
||||
"To Base64",
|
||||
"From Base64",
|
||||
"Show Base64 offsets",
|
||||
"To Base92",
|
||||
"From Base92",
|
||||
"To Base85",
|
||||
"From Base85",
|
||||
"To Base",
|
||||
"From Base",
|
||||
"To BCD",
|
||||
"From BCD",
|
||||
"To HTML Entity",
|
||||
"From HTML Entity",
|
||||
"URL Encode",
|
||||
"URL Decode",
|
||||
"Escape Unicode Characters",
|
||||
"Unescape Unicode Characters",
|
||||
"Normalise Unicode",
|
||||
"To Quoted Printable",
|
||||
"From Quoted Printable",
|
||||
"To Punycode",
|
||||
"From Punycode",
|
||||
"AMF Encode",
|
||||
"AMF Decode",
|
||||
"To Hex Content",
|
||||
"From Hex Content",
|
||||
"PEM to Hex",
|
||||
"Hex to PEM",
|
||||
"Parse ASN.1 hex string",
|
||||
"Change IP format",
|
||||
"Encode text",
|
||||
"Decode text",
|
||||
"Text Encoding Brute Force",
|
||||
"Swap endianness",
|
||||
"To MessagePack",
|
||||
"From MessagePack",
|
||||
"To Braille",
|
||||
"From Braille",
|
||||
"Parse TLV",
|
||||
"CSV to JSON",
|
||||
"JSON to CSV",
|
||||
"Avro to JSON",
|
||||
"CBOR Encode",
|
||||
"CBOR Decode",
|
||||
"Caret/M-decode",
|
||||
"Rison Encode",
|
||||
"Rison Decode"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Encryption / Encoding",
|
||||
"ops": [
|
||||
"AES Encrypt",
|
||||
"AES Decrypt",
|
||||
"Blowfish Encrypt",
|
||||
"Blowfish Decrypt",
|
||||
"DES Encrypt",
|
||||
"DES Decrypt",
|
||||
"Triple DES Encrypt",
|
||||
"Triple DES Decrypt",
|
||||
"Fernet Encrypt",
|
||||
"Fernet Decrypt",
|
||||
"LS47 Encrypt",
|
||||
"LS47 Decrypt",
|
||||
"RC2 Encrypt",
|
||||
"RC2 Decrypt",
|
||||
"RC4",
|
||||
"RC4 Drop",
|
||||
"ChaCha",
|
||||
"Rabbit",
|
||||
"SM4 Encrypt",
|
||||
"SM4 Decrypt",
|
||||
"GOST Encrypt",
|
||||
"GOST Decrypt",
|
||||
"GOST Sign",
|
||||
"GOST Verify",
|
||||
"GOST Key Wrap",
|
||||
"GOST Key Unwrap",
|
||||
"ROT13",
|
||||
"ROT13 Brute Force",
|
||||
"ROT47",
|
||||
"ROT47 Brute Force",
|
||||
"ROT8000",
|
||||
"XOR",
|
||||
"XOR Brute Force",
|
||||
"Vigenère Encode",
|
||||
"Vigenère Decode",
|
||||
"To Morse Code",
|
||||
"From Morse Code",
|
||||
"Bacon Cipher Encode",
|
||||
"Bacon Cipher Decode",
|
||||
"Bifid Cipher Encode",
|
||||
"Bifid Cipher Decode",
|
||||
"Caesar Box Cipher",
|
||||
"Affine Cipher Encode",
|
||||
"Affine Cipher Decode",
|
||||
"A1Z26 Cipher Encode",
|
||||
"A1Z26 Cipher Decode",
|
||||
"Rail Fence Cipher Encode",
|
||||
"Rail Fence Cipher Decode",
|
||||
"Atbash Cipher",
|
||||
"CipherSaber2 Encrypt",
|
||||
"CipherSaber2 Decrypt",
|
||||
"Cetacean Cipher Encode",
|
||||
"Cetacean Cipher Decode",
|
||||
"Substitute",
|
||||
"Derive PBKDF2 key",
|
||||
"Derive EVP key",
|
||||
"Derive HKDF key",
|
||||
"Bcrypt",
|
||||
"Scrypt",
|
||||
"JWT Sign",
|
||||
"JWT Verify",
|
||||
"JWT Decode",
|
||||
"Citrix CTX1 Encode",
|
||||
"Citrix CTX1 Decode",
|
||||
"AES Key Wrap",
|
||||
"AES Key Unwrap",
|
||||
"Pseudo-Random Number Generator",
|
||||
"Enigma",
|
||||
"Bombe",
|
||||
"Multiple Bombe",
|
||||
"Typex",
|
||||
"Lorenz",
|
||||
"Colossus",
|
||||
"SIGABA"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Public Key",
|
||||
"ops": [
|
||||
"Parse X.509 certificate",
|
||||
"Parse ASN.1 hex string",
|
||||
"PEM to Hex",
|
||||
"Hex to PEM",
|
||||
"Hex to Object Identifier",
|
||||
"Object Identifier to Hex",
|
||||
"Generate PGP Key Pair",
|
||||
"PGP Encrypt",
|
||||
"PGP Decrypt",
|
||||
"PGP Verify",
|
||||
"PGP Encrypt and Sign",
|
||||
"PGP Decrypt and Verify",
|
||||
"Generate RSA Key Pair",
|
||||
"RSA Sign",
|
||||
"RSA Verify",
|
||||
"RSA Encrypt",
|
||||
"RSA Decrypt",
|
||||
"Parse SSH Host Key"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Arithmetic / Logic",
|
||||
"ops": [
|
||||
"Set Union",
|
||||
"Set Intersection",
|
||||
"Set Difference",
|
||||
"Symmetric Difference",
|
||||
"Cartesian Product",
|
||||
"Power Set",
|
||||
"XOR",
|
||||
"XOR Brute Force",
|
||||
"OR",
|
||||
"NOT",
|
||||
"AND",
|
||||
"ADD",
|
||||
"SUB",
|
||||
"Sum",
|
||||
"Subtract",
|
||||
"Multiply",
|
||||
"Divide",
|
||||
"Mean",
|
||||
"Median",
|
||||
"Standard Deviation",
|
||||
"Bit shift left",
|
||||
"Bit shift right",
|
||||
"Rotate left",
|
||||
"Rotate right",
|
||||
"ROT13",
|
||||
"ROT8000"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Networking",
|
||||
"ops": [
|
||||
"HTTP request",
|
||||
"DNS over HTTPS",
|
||||
"Strip HTTP headers",
|
||||
"Dechunk HTTP response",
|
||||
"Parse User Agent",
|
||||
"Parse IP range",
|
||||
"Parse IPv6 address",
|
||||
"Parse IPv4 header",
|
||||
"Parse TCP",
|
||||
"Parse UDP",
|
||||
"Parse SSH Host Key",
|
||||
"Parse URI",
|
||||
"URL Encode",
|
||||
"URL Decode",
|
||||
"Protobuf Decode",
|
||||
"Protobuf Encode",
|
||||
"VarInt Encode",
|
||||
"VarInt Decode",
|
||||
"JA3 Fingerprint",
|
||||
"JA3S Fingerprint",
|
||||
"HASSH Client Fingerprint",
|
||||
"HASSH Server Fingerprint",
|
||||
"Format MAC addresses",
|
||||
"Change IP format",
|
||||
"Group IP addresses",
|
||||
"Encode NetBIOS Name",
|
||||
"Decode NetBIOS Name",
|
||||
"Defang URL",
|
||||
"Defang IP Addresses"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Language",
|
||||
"ops": [
|
||||
"Encode text",
|
||||
"Decode text",
|
||||
"Unicode Text Format",
|
||||
"Remove Diacritics",
|
||||
"Unescape Unicode Characters",
|
||||
"Convert to NATO alphabet"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Utils",
|
||||
"ops": [
|
||||
"Diff",
|
||||
"Remove whitespace",
|
||||
"Remove null bytes",
|
||||
"To Upper case",
|
||||
"To Lower case",
|
||||
"Swap case",
|
||||
"To Case Insensitive Regex",
|
||||
"From Case Insensitive Regex",
|
||||
"Add line numbers",
|
||||
"Remove line numbers",
|
||||
"Get All Casings",
|
||||
"To Table",
|
||||
"Reverse",
|
||||
"Sort",
|
||||
"Shuffle",
|
||||
"Unique",
|
||||
"Split",
|
||||
"Filter",
|
||||
"Head",
|
||||
"Tail",
|
||||
"Count occurrences",
|
||||
"Expand alphabet range",
|
||||
"Drop bytes",
|
||||
"Take bytes",
|
||||
"Pad lines",
|
||||
"Find / Replace",
|
||||
"Regular expression",
|
||||
"Fuzzy Match",
|
||||
"Offset checker",
|
||||
"Hamming Distance",
|
||||
"Levenshtein Distance",
|
||||
"Convert distance",
|
||||
"Convert area",
|
||||
"Convert mass",
|
||||
"Convert speed",
|
||||
"Convert data units",
|
||||
"Convert co-ordinate format",
|
||||
"Show on map",
|
||||
"Parse UNIX file permissions",
|
||||
"Parse ObjectID timestamp",
|
||||
"Swap endianness",
|
||||
"Parse colour code",
|
||||
"Escape string",
|
||||
"Unescape string",
|
||||
"Pseudo-Random Number Generator",
|
||||
"Sleep",
|
||||
"File Tree"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Date / Time",
|
||||
"ops": [
|
||||
"Parse DateTime",
|
||||
"Translate DateTime Format",
|
||||
"From UNIX Timestamp",
|
||||
"To UNIX Timestamp",
|
||||
"Windows Filetime to UNIX Timestamp",
|
||||
"UNIX Timestamp to Windows Filetime",
|
||||
"Extract dates",
|
||||
"Get Time",
|
||||
"Sleep"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Extractors",
|
||||
"ops": [
|
||||
"Strings",
|
||||
"Extract IP addresses",
|
||||
"Extract email addresses",
|
||||
"Extract MAC addresses",
|
||||
"Extract URLs",
|
||||
"Extract domains",
|
||||
"Extract file paths",
|
||||
"Extract dates",
|
||||
"Regular expression",
|
||||
"XPath expression",
|
||||
"JPath expression",
|
||||
"CSS selector",
|
||||
"Extract EXIF",
|
||||
"Extract ID3",
|
||||
"Extract Files",
|
||||
"RAKE"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Compression",
|
||||
"ops": [
|
||||
"Raw Deflate",
|
||||
"Raw Inflate",
|
||||
"Zlib Deflate",
|
||||
"Zlib Inflate",
|
||||
"Gzip",
|
||||
"Gunzip",
|
||||
"Zip",
|
||||
"Unzip",
|
||||
"Bzip2 Decompress",
|
||||
"Bzip2 Compress",
|
||||
"Tar",
|
||||
"Untar",
|
||||
"LZString Decompress",
|
||||
"LZString Compress",
|
||||
"LZMA Decompress",
|
||||
"LZMA Compress",
|
||||
"LZ4 Decompress",
|
||||
"LZ4 Compress",
|
||||
"LZNT1 Decompress"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Hashing",
|
||||
"ops": [
|
||||
"Analyse hash",
|
||||
"Generate all hashes",
|
||||
"MD2",
|
||||
"MD4",
|
||||
"MD5",
|
||||
"MD6",
|
||||
"SHA0",
|
||||
"SHA1",
|
||||
"SHA2",
|
||||
"SHA3",
|
||||
"SM3",
|
||||
"MurmurHash3",
|
||||
"Keccak",
|
||||
"Shake",
|
||||
"RIPEMD",
|
||||
"HAS-160",
|
||||
"Whirlpool",
|
||||
"Snefru",
|
||||
"BLAKE2b",
|
||||
"BLAKE2s",
|
||||
"GOST Hash",
|
||||
"Streebog",
|
||||
"SSDEEP",
|
||||
"CTPH",
|
||||
"Compare SSDEEP hashes",
|
||||
"Compare CTPH hashes",
|
||||
"HMAC",
|
||||
"CMAC",
|
||||
"Bcrypt",
|
||||
"Bcrypt compare",
|
||||
"Bcrypt parse",
|
||||
"Argon2",
|
||||
"Argon2 compare",
|
||||
"Scrypt",
|
||||
"NT Hash",
|
||||
"LM Hash",
|
||||
"Fletcher-8 Checksum",
|
||||
"Fletcher-16 Checksum",
|
||||
"Fletcher-32 Checksum",
|
||||
"Fletcher-64 Checksum",
|
||||
"Adler-32 Checksum",
|
||||
"Luhn Checksum",
|
||||
"CRC-8 Checksum",
|
||||
"CRC-16 Checksum",
|
||||
"CRC-32 Checksum",
|
||||
"TCP/IP Checksum"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Code tidy",
|
||||
"ops": [
|
||||
"Syntax highlighter",
|
||||
"Generic Code Beautify",
|
||||
"JavaScript Parser",
|
||||
"JavaScript Beautify",
|
||||
"JavaScript Minify",
|
||||
"JSON Beautify",
|
||||
"JSON Minify",
|
||||
"XML Beautify",
|
||||
"XML Minify",
|
||||
"SQL Beautify",
|
||||
"SQL Minify",
|
||||
"CSS Beautify",
|
||||
"CSS Minify",
|
||||
"XPath expression",
|
||||
"JPath expression",
|
||||
"CSS selector",
|
||||
"PHP Deserialize",
|
||||
"Microsoft Script Decoder",
|
||||
"Strip HTML tags",
|
||||
"Diff",
|
||||
"To Snake case",
|
||||
"To Camel case",
|
||||
"To Kebab case",
|
||||
"BSON serialise",
|
||||
"BSON deserialise",
|
||||
"To MessagePack",
|
||||
"From MessagePack",
|
||||
"Render Markdown"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Forensics",
|
||||
"ops": [
|
||||
"Detect File Type",
|
||||
"Scan for Embedded Files",
|
||||
"Extract Files",
|
||||
"YARA Rules",
|
||||
"Remove EXIF",
|
||||
"Extract EXIF",
|
||||
"Extract RGBA",
|
||||
"View Bit Plane",
|
||||
"Randomize Colour Palette",
|
||||
"Extract LSB",
|
||||
"ELF Info"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Multimedia",
|
||||
"ops": [
|
||||
"Render Image",
|
||||
"Play Media",
|
||||
"Generate Image",
|
||||
"Optical Character Recognition",
|
||||
"Remove EXIF",
|
||||
"Extract EXIF",
|
||||
"Split Colour Channels",
|
||||
"Rotate Image",
|
||||
"Resize Image",
|
||||
"Blur Image",
|
||||
"Dither Image",
|
||||
"Invert Image",
|
||||
"Flip Image",
|
||||
"Crop Image",
|
||||
"Image Brightness / Contrast",
|
||||
"Image Opacity",
|
||||
"Image Filter",
|
||||
"Contain Image",
|
||||
"Cover Image",
|
||||
"Image Hue/Saturation/Lightness",
|
||||
"Sharpen Image",
|
||||
"Normalise Image",
|
||||
"Convert Image Format",
|
||||
"Add Text To Image",
|
||||
"Hex Density chart",
|
||||
"Scatter chart",
|
||||
"Series chart",
|
||||
"Heatmap chart"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Other",
|
||||
"ops": [
|
||||
"Entropy",
|
||||
"Frequency distribution",
|
||||
"Index of Coincidence",
|
||||
"Chi Square",
|
||||
"P-list Viewer",
|
||||
"Disassemble x86",
|
||||
"Pseudo-Random Number Generator",
|
||||
"Generate De Bruijn Sequence",
|
||||
"Generate UUID",
|
||||
"Generate TOTP",
|
||||
"Generate HOTP",
|
||||
"Generate QR Code",
|
||||
"Parse QR Code",
|
||||
"Haversine distance",
|
||||
"HTML To Text",
|
||||
"Generate Lorem Ipsum",
|
||||
"Numberwang",
|
||||
"XKCD Random Number"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Flow control",
|
||||
"ops": [
|
||||
"Magic",
|
||||
"Fork",
|
||||
"Subsection",
|
||||
"Merge",
|
||||
"Register",
|
||||
"Label",
|
||||
"Jump",
|
||||
"Conditional Jump",
|
||||
"Return",
|
||||
"Comment"
|
||||
]
|
||||
}
|
||||
]
|
File diff suppressed because it is too large
Load diff
147
src/core/config/scripts/generateConfig.mjs
Normal file
147
src/core/config/scripts/generateConfig.mjs
Normal file
|
@ -0,0 +1,147 @@
|
|||
/**
|
||||
* This script automatically generates OperationConfig.json, containing metadata
|
||||
* for each operation in the src/core/operations directory.
|
||||
* It also generates modules in the src/core/config/modules directory to separate
|
||||
* out operations into logical collections.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
/* eslint no-console: ["off"] */
|
||||
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import process from "process";
|
||||
import * as Ops from "../../operations/index.mjs";
|
||||
|
||||
const dir = path.join(process.cwd() + "/src/core/config/");
|
||||
if (!fs.existsSync(dir)) {
|
||||
console.log("\nCWD: " + process.cwd());
|
||||
console.log("Error: generateConfig.mjs should be run from the project root");
|
||||
console.log("Example> node --experimental-modules src/core/config/scripts/generateConfig.mjs");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
|
||||
const operationConfig = {},
|
||||
modules = {};
|
||||
|
||||
/**
|
||||
* Generate operation config and module lists.
|
||||
*/
|
||||
for (const opObj in Ops) {
|
||||
const op = new Ops[opObj]();
|
||||
|
||||
operationConfig[op.name] = {
|
||||
module: op.module,
|
||||
description: op.description,
|
||||
infoURL: op.infoURL,
|
||||
inputType: op.inputType,
|
||||
outputType: op.presentType,
|
||||
flowControl: op.flowControl,
|
||||
manualBake: op.manualBake,
|
||||
args: op.args,
|
||||
checks: op.checks
|
||||
};
|
||||
|
||||
if (!(op.module in modules))
|
||||
modules[op.module] = {};
|
||||
modules[op.module][op.name] = opObj;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Write OperationConfig.
|
||||
*/
|
||||
fs.writeFileSync(
|
||||
path.join(dir, "OperationConfig.json"),
|
||||
JSON.stringify(operationConfig, null, 4)
|
||||
);
|
||||
console.log("Written OperationConfig.json");
|
||||
|
||||
|
||||
/**
|
||||
* Write modules.
|
||||
*/
|
||||
if (!fs.existsSync(path.join(dir, "modules/"))) {
|
||||
fs.mkdirSync(path.join(dir, "modules/"));
|
||||
}
|
||||
|
||||
for (const module in modules) {
|
||||
let code = `/**
|
||||
* THIS FILE IS AUTOMATICALLY GENERATED BY src/core/config/scripts/generateConfig.mjs
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright ${new Date().getUTCFullYear()}
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
`;
|
||||
|
||||
for (const opName in modules[module]) {
|
||||
const objName = modules[module][opName];
|
||||
code += `import ${objName} from "../../operations/${objName}.mjs";\n`;
|
||||
}
|
||||
|
||||
code += `
|
||||
const OpModules = typeof self === "undefined" ? {} : self.OpModules || {};
|
||||
|
||||
OpModules.${module} = {
|
||||
`;
|
||||
for (const opName in modules[module]) {
|
||||
const objName = modules[module][opName];
|
||||
code += ` "${opName}": ${objName},\n`;
|
||||
}
|
||||
|
||||
code += `};
|
||||
|
||||
export default OpModules;
|
||||
`;
|
||||
fs.writeFileSync(
|
||||
path.join(dir, `modules/${module}.mjs`),
|
||||
code
|
||||
);
|
||||
console.log(`Written ${module} module`);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Write OpModules wrapper.
|
||||
*/
|
||||
let opModulesCode = `/**
|
||||
* THIS FILE IS AUTOMATICALLY GENERATED BY src/core/config/scripts/generateConfig.mjs
|
||||
*
|
||||
* Imports all modules for builds which do not load modules separately.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright ${new Date().getUTCFullYear()}
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
`;
|
||||
|
||||
for (const module in modules) {
|
||||
opModulesCode += `import ${module}Module from "./${module}.mjs";\n`;
|
||||
}
|
||||
|
||||
opModulesCode += `
|
||||
const OpModules = {};
|
||||
|
||||
Object.assign(
|
||||
OpModules,
|
||||
`;
|
||||
|
||||
for (const module in modules) {
|
||||
opModulesCode += ` ${module}Module,\n`;
|
||||
}
|
||||
|
||||
opModulesCode += `);
|
||||
|
||||
export default OpModules;
|
||||
`;
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(dir, "modules/OpModules.mjs"),
|
||||
opModulesCode
|
||||
);
|
||||
console.log("Written OpModules.mjs");
|
60
src/core/config/scripts/generateOpsIndex.mjs
Normal file
60
src/core/config/scripts/generateOpsIndex.mjs
Normal file
|
@ -0,0 +1,60 @@
|
|||
/**
|
||||
* This script automatically generates src/core/operations/index.mjs, containing
|
||||
* imports for all operations in src/core/operations.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
/* eslint no-console: ["off"] */
|
||||
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import process from "process";
|
||||
|
||||
const dir = path.join(process.cwd() + "/src/core/config/");
|
||||
if (!fs.existsSync(dir)) {
|
||||
console.log("\nCWD: " + process.cwd());
|
||||
console.log("Error: generateOpsIndex.mjs should be run from the project root");
|
||||
console.log("Example> node --experimental-modules src/core/config/scripts/generateOpsIndex.mjs");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Find all operation files
|
||||
const opObjs = [];
|
||||
fs.readdirSync(path.join(dir, "../operations")).forEach(file => {
|
||||
if (!file.endsWith(".mjs") || file === "index.mjs") return;
|
||||
opObjs.push(file.split(".mjs")[0]);
|
||||
});
|
||||
|
||||
// Construct index file
|
||||
let code = `/**
|
||||
* THIS FILE IS AUTOMATICALLY GENERATED BY src/core/config/scripts/generateOpsIndex.mjs
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright ${new Date().getUTCFullYear()}
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
`;
|
||||
|
||||
opObjs.forEach(obj => {
|
||||
code += `import ${obj} from "./${obj}.mjs";\n`;
|
||||
});
|
||||
|
||||
code += `
|
||||
export {
|
||||
`;
|
||||
|
||||
opObjs.forEach(obj => {
|
||||
code += ` ${obj},\n`;
|
||||
});
|
||||
|
||||
code += "};\n";
|
||||
|
||||
// Write file
|
||||
fs.writeFileSync(
|
||||
path.join(dir, "../operations/index.mjs"),
|
||||
code
|
||||
);
|
||||
console.log("Written operation index.");
|
144
src/core/config/scripts/newMinorVersion.mjs
Normal file
144
src/core/config/scripts/newMinorVersion.mjs
Normal file
|
@ -0,0 +1,144 @@
|
|||
/**
|
||||
* This script updates the CHANGELOG when a new minor version is created.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2022
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
/* eslint no-console: ["off"] */
|
||||
|
||||
import prompt from "prompt";
|
||||
import colors from "colors";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import process from "process";
|
||||
|
||||
const dir = path.join(process.cwd() + "/src/core/config/");
|
||||
if (!fs.existsSync(dir)) {
|
||||
console.log("\nCWD: " + process.cwd());
|
||||
console.log("Error: newMinorVersion.mjs should be run from the project root");
|
||||
console.log("Example> node --experimental-modules src/core/config/scripts/newMinorVersion.mjs");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let changelogData = fs.readFileSync(path.join(process.cwd(), "CHANGELOG.md"), "utf8");
|
||||
const lastVersion = changelogData.match(/## Details\s+### \[(\d+)\.(\d+)\.(\d+)\]/);
|
||||
const newVersion = [
|
||||
parseInt(lastVersion[1], 10),
|
||||
parseInt(lastVersion[2], 10) + 1,
|
||||
0
|
||||
];
|
||||
|
||||
let knownContributors = changelogData.match(/^\[@([^\]]+)\]/gm);
|
||||
knownContributors = knownContributors.map(c => c.slice(2, -1));
|
||||
|
||||
const date = (new Date()).toISOString().split("T")[0];
|
||||
|
||||
const schema = {
|
||||
properties: {
|
||||
message: {
|
||||
description: "A short but descriptive summary of a feature in this version",
|
||||
example: "Added 'Op name' operation",
|
||||
prompt: "Feature description",
|
||||
type: "string",
|
||||
required: true,
|
||||
},
|
||||
author: {
|
||||
description: "The author of the feature (only one supported, edit manually to add more)",
|
||||
example: "n1474335",
|
||||
prompt: "Author",
|
||||
type: "string",
|
||||
default: "n1474335"
|
||||
},
|
||||
id: {
|
||||
description: "The PR number or full commit hash for this feature.",
|
||||
example: "1200",
|
||||
prompt: "Pull request or commit ID",
|
||||
type: "string"
|
||||
},
|
||||
another: {
|
||||
description: "y/n",
|
||||
example: "y",
|
||||
prompt: "Add another feature?",
|
||||
type: "string",
|
||||
pattern: /^[yn]$/,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Build schema
|
||||
for (const prop in schema.properties) {
|
||||
const p = schema.properties[prop];
|
||||
p.description = "\n" + colors.white(p.description) + colors.cyan("\nExample: " + p.example) + "\n" + colors.green(p.prompt);
|
||||
}
|
||||
|
||||
prompt.message = "";
|
||||
prompt.delimiter = ":".green;
|
||||
|
||||
const features = [];
|
||||
const authors = [];
|
||||
const prIDs = [];
|
||||
const commitIDs = [];
|
||||
|
||||
prompt.start();
|
||||
|
||||
const getFeature = function() {
|
||||
prompt.get(schema, (err, result) => {
|
||||
if (err) {
|
||||
console.log("\nExiting script.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
features.push(result);
|
||||
|
||||
if (result.another === "y") {
|
||||
getFeature();
|
||||
} else {
|
||||
let message = `### [${newVersion[0]}.${newVersion[1]}.${newVersion[2]}] - ${date}\n`;
|
||||
|
||||
features.forEach(feature => {
|
||||
const id = feature.id.length > 10 ? feature.id.slice(0, 7) : "#" + feature.id;
|
||||
message += `- ${feature.message} [@${feature.author}] | [${id}]\n`;
|
||||
|
||||
if (!knownContributors.includes(feature.author)) {
|
||||
authors.push(`[@${feature.author}]: https://github.com/${feature.author}`);
|
||||
}
|
||||
|
||||
if (feature.id.length > 10) {
|
||||
commitIDs.push(`[${id}]: https://github.com/gchq/CyberChef/commit/${feature.id}`);
|
||||
} else {
|
||||
prIDs.push(`[#${feature.id}]: https://github.com/gchq/CyberChef/pull/${feature.id}`);
|
||||
}
|
||||
});
|
||||
|
||||
// Message
|
||||
changelogData = changelogData.replace(/## Details\n\n/, "## Details\n\n" + message + "\n");
|
||||
|
||||
// Tag
|
||||
const newTag = `[${newVersion[0]}.${newVersion[1]}.${newVersion[2]}]: https://github.com/gchq/CyberChef/releases/tag/v${newVersion[0]}.${newVersion[1]}.${newVersion[2]}\n`;
|
||||
changelogData = changelogData.replace(/\n\n(\[\d+\.\d+\.\d+\]: https)/, "\n\n" + newTag + "$1");
|
||||
|
||||
// Author
|
||||
authors.forEach(author => {
|
||||
changelogData = changelogData.replace(/(\n\[@[^\]]+\]: https:\/\/github\.com\/[^\n]+\n)\n/, "$1" + author + "\n\n");
|
||||
});
|
||||
|
||||
// Commit IDs
|
||||
commitIDs.forEach(commitID => {
|
||||
changelogData = changelogData.replace(/(\n\[[^\].]+\]: https:\/\/github.com\/gchq\/CyberChef\/commit\/[^\n]+\n)\n/, "$1" + commitID + "\n\n");
|
||||
});
|
||||
|
||||
// PR IDs
|
||||
prIDs.forEach(prID => {
|
||||
changelogData = changelogData.replace(/(\n\[#[^\]]+\]: https:\/\/github.com\/gchq\/CyberChef\/pull\/[^\n]+\n)\n*$/, "$1" + prID + "\n\n");
|
||||
});
|
||||
|
||||
fs.writeFileSync(path.join(process.cwd(), "CHANGELOG.md"), changelogData);
|
||||
|
||||
console.log("Written CHANGELOG.md\nCommit changes and then run `npm version minor`.");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
getFeature();
|
230
src/core/config/scripts/newOperation.mjs
Normal file
230
src/core/config/scripts/newOperation.mjs
Normal file
|
@ -0,0 +1,230 @@
|
|||
/**
|
||||
* Interactive script for generating a new operation template.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
/* eslint no-console: ["off"] */
|
||||
|
||||
import prompt from "prompt";
|
||||
import colors from "colors";
|
||||
import process from "process";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import EscapeString from "../../operations/EscapeString.mjs";
|
||||
|
||||
|
||||
const dir = path.join(process.cwd() + "/src/core/operations/");
|
||||
if (!fs.existsSync(dir)) {
|
||||
console.log("\nCWD: " + process.cwd());
|
||||
console.log("Error: newOperation.mjs should be run from the project root");
|
||||
console.log("Example> node --experimental-modules src/core/config/scripts/newOperation.mjs");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const ioTypes = ["string", "byteArray", "number", "html", "ArrayBuffer", "BigNumber", "JSON", "File", "List<File>"];
|
||||
|
||||
const schema = {
|
||||
properties: {
|
||||
opName: {
|
||||
description: "The operation name should be short but descriptive.",
|
||||
example: "URL Decode",
|
||||
prompt: "Operation name",
|
||||
type: "string",
|
||||
pattern: /^[\w\s-/().]+$/,
|
||||
required: true,
|
||||
message: "Operation names should consist of letters, numbers or the following symbols: _-/()."
|
||||
},
|
||||
module: {
|
||||
description: `Modules are used to group operations that rely on large libraries. Any operation that is not in the Default module will be loaded in dynamically when it is first called. All operations in the same module will also be loaded at this time. This system prevents the CyberChef web app from getting too bloated and taking a long time to load initially.
|
||||
If your operation does not rely on a library, just leave this blank and it will be added to the Default module. If it relies on the same library as other operations, enter the name of the module those operations are in. If it relies on a new large library, enter a new module name (capitalise the first letter).`,
|
||||
example: "Crypto",
|
||||
prompt: "Module",
|
||||
type: "string",
|
||||
pattern: /^[A-Z][A-Za-z\d]+$/,
|
||||
message: "Module names should start with a capital letter and not contain any spaces or symbols.",
|
||||
default: "Default"
|
||||
},
|
||||
description: {
|
||||
description: "The description should explain what the operation is and how it works. It can describe how the arguments should be entered and give examples of expected input and output. HTML markup is supported. Use <code> tags for examples. The description is scanned during searches, so include terms that are likely to be searched for when someone is looking for your operation.",
|
||||
example: "Converts URI/URL percent-encoded characters back to their raw values.<br><br>e.g. <code>%3d</code> becomes <code>=</code>",
|
||||
prompt: "Description",
|
||||
type: "string"
|
||||
},
|
||||
infoURL: {
|
||||
description: "An optional URL for an external site can be added to give more information about the operation. Wikipedia links are often suitable. If linking to Wikipedia, use an international link (e.g. https://wikipedia.org/...) rather than a localised link (e.g. https://en.wikipedia.org/...).",
|
||||
example: "https://wikipedia.org/wiki/Percent-encoding",
|
||||
prompt: "Information URL",
|
||||
type: "string",
|
||||
},
|
||||
inputType: {
|
||||
description: `The input type defines how the input data will be presented to your operation. Check the project wiki for a full description of each type. The options are: ${ioTypes.join(", ")}.`,
|
||||
example: "string",
|
||||
prompt: "Input type",
|
||||
type: "string",
|
||||
pattern: new RegExp(`^(${ioTypes.join("|")})$`),
|
||||
required: true,
|
||||
message: `The input type should be one of: ${ioTypes.join(", ")}.`
|
||||
},
|
||||
outputType: {
|
||||
description: `The output type tells CyberChef what sort of data you are returning from your operation. Check the project wiki for a full description of each type. The options are: ${ioTypes.join(", ")}.`,
|
||||
example: "string",
|
||||
prompt: "Output type",
|
||||
type: "string",
|
||||
pattern: new RegExp(`^(${ioTypes.join("|")})$`),
|
||||
required: true,
|
||||
message: `The output type should be one of: ${ioTypes.join(", ")}.`
|
||||
},
|
||||
highlight: {
|
||||
description: "If your operation does not change the length of the input in any way, we can enable highlighting. If it does change the length in a predictable way, we may still be able to enable highlighting and calculate the correct offsets. If this is not possible, we will disable highlighting for this operation.",
|
||||
example: "true/false",
|
||||
prompt: "Enable highlighting",
|
||||
type: "boolean",
|
||||
default: "false",
|
||||
message: "Enter true or false to specify if highlighting should be enabled."
|
||||
},
|
||||
authorName: {
|
||||
description: "Your name or username will be added to the @author tag for this operation.",
|
||||
example: "n1474335",
|
||||
prompt: "Username",
|
||||
type: "string"
|
||||
},
|
||||
authorEmail: {
|
||||
description: "Your email address will also be added to the @author tag for this operation.",
|
||||
example: "n1474335@gmail.com",
|
||||
prompt: "Email",
|
||||
type: "string"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Build schema
|
||||
for (const prop in schema.properties) {
|
||||
const p = schema.properties[prop];
|
||||
p.description = "\n" + colors.white(p.description) + colors.cyan("\nExample: " + p.example) + "\n" + colors.green(p.prompt);
|
||||
}
|
||||
|
||||
console.log("\n\nThis script will generate a new operation template based on the information you provide. These values can be changed manually later.".yellow);
|
||||
|
||||
prompt.message = "";
|
||||
prompt.delimiter = ":".green;
|
||||
|
||||
prompt.start();
|
||||
|
||||
prompt.get(schema, (err, result) => {
|
||||
if (err) {
|
||||
console.log("\nExiting build script.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const moduleName = result.opName.replace(/\w\S*/g, txt => {
|
||||
return txt.charAt(0).toUpperCase() + txt.substr(1);
|
||||
}).replace(/[\s-()./]/g, "");
|
||||
|
||||
|
||||
const template = `/**
|
||||
* @author ${result.authorName} [${result.authorEmail}]
|
||||
* @copyright Crown Copyright ${(new Date()).getFullYear()}
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
|
||||
/**
|
||||
* ${result.opName} operation
|
||||
*/
|
||||
class ${moduleName} extends Operation {
|
||||
|
||||
/**
|
||||
* ${moduleName} constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "${result.opName}";
|
||||
this.module = "${result.module}";
|
||||
this.description = "${(new EscapeString).run(result.description, ["Special chars", "Double"])}";
|
||||
this.infoURL = "${result.infoURL}";
|
||||
this.inputType = "${result.inputType}";
|
||||
this.outputType = "${result.outputType}";
|
||||
this.args = [
|
||||
/* Example arguments. See the project wiki for full details.
|
||||
{
|
||||
name: "First arg",
|
||||
type: "string",
|
||||
value: "Don't Panic"
|
||||
},
|
||||
{
|
||||
name: "Second arg",
|
||||
type: "number",
|
||||
value: 42
|
||||
}
|
||||
*/
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {${result.inputType}} input
|
||||
* @param {Object[]} args
|
||||
* @returns {${result.outputType}}
|
||||
*/
|
||||
run(input, args) {
|
||||
// const [firstArg, secondArg] = args;
|
||||
|
||||
throw new OperationError("Test");
|
||||
}
|
||||
${result.highlight ? `
|
||||
/**
|
||||
* Highlight ${result.opName}
|
||||
*
|
||||
* @param {Object[]} pos
|
||||
* @param {number} pos[].start
|
||||
* @param {number} pos[].end
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} pos
|
||||
*/
|
||||
highlight(pos, args) {
|
||||
return pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight ${result.opName} in reverse
|
||||
*
|
||||
* @param {Object[]} pos
|
||||
* @param {number} pos[].start
|
||||
* @param {number} pos[].end
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} pos
|
||||
*/
|
||||
highlightReverse(pos, args) {
|
||||
return pos;
|
||||
}
|
||||
` : ""}
|
||||
}
|
||||
|
||||
export default ${moduleName};
|
||||
`;
|
||||
|
||||
// console.log(template);
|
||||
|
||||
const filename = path.join(dir, `./${moduleName}.mjs`);
|
||||
if (fs.existsSync(filename)) {
|
||||
console.log(`${filename} already exists. It has NOT been overwritten.`.red);
|
||||
console.log("Choose a different operation name to avoid conflicts.");
|
||||
process.exit(0);
|
||||
}
|
||||
fs.writeFileSync(filename, template);
|
||||
|
||||
console.log(`\nOperation template written to ${colors.green(filename)}`);
|
||||
console.log(`\nNext steps:
|
||||
1. Add your operation to ${colors.green("src/core/config/Categories.json")}
|
||||
2. Write your operation code.
|
||||
3. Write tests in ${colors.green("tests/operations/tests/")}
|
||||
4. Run ${colors.cyan("npm run lint")} and ${colors.cyan("npm run test")}
|
||||
5. Submit a Pull Request to get your operation added to the official CyberChef repository.`);
|
||||
|
||||
});
|
||||
|
38
src/core/dishTypes/DishBigNumber.mjs
Normal file
38
src/core/dishTypes/DishBigNumber.mjs
Normal file
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* @author d98762625 [d98762625@gmail.com]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import DishType from "./DishType.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import BigNumber from "bignumber.js";
|
||||
|
||||
/**
|
||||
* translation methods for BigNumber Dishes
|
||||
*/
|
||||
class DishBigNumber extends DishType {
|
||||
|
||||
/**
|
||||
* convert the given value to a ArrayBuffer
|
||||
* @param {BigNumber} value
|
||||
*/
|
||||
static toArrayBuffer() {
|
||||
DishBigNumber.checkForValue(this.value);
|
||||
this.value = BigNumber.isBigNumber(this.value) ? Utils.strToArrayBuffer(this.value.toFixed()) : new ArrayBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* convert the given value from a ArrayBuffer
|
||||
*/
|
||||
static fromArrayBuffer() {
|
||||
DishBigNumber.checkForValue(this.value);
|
||||
try {
|
||||
this.value = new BigNumber(Utils.arrayBufferToStr(this.value));
|
||||
} catch (err) {
|
||||
this.value = new BigNumber(NaN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default DishBigNumber;
|
31
src/core/dishTypes/DishByteArray.mjs
Normal file
31
src/core/dishTypes/DishByteArray.mjs
Normal file
|
@ -0,0 +1,31 @@
|
|||
/**
|
||||
* @author d98762625 [d98762625@gmail.com]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import DishType from "./DishType.mjs";
|
||||
|
||||
/**
|
||||
* Translation methods for ArrayBuffer Dishes
|
||||
*/
|
||||
class DishByteArray extends DishType {
|
||||
|
||||
/**
|
||||
* convert the given value to a ArrayBuffer
|
||||
*/
|
||||
static toArrayBuffer() {
|
||||
DishByteArray.checkForValue(this.value);
|
||||
this.value = new Uint8Array(this.value).buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* convert the given value from a ArrayBuffer
|
||||
*/
|
||||
static fromArrayBuffer() {
|
||||
DishByteArray.checkForValue(this.value);
|
||||
this.value = Array.prototype.slice.call(new Uint8Array(this.value));
|
||||
}
|
||||
}
|
||||
|
||||
export default DishByteArray;
|
42
src/core/dishTypes/DishFile.mjs
Normal file
42
src/core/dishTypes/DishFile.mjs
Normal file
|
@ -0,0 +1,42 @@
|
|||
/**
|
||||
* @author d98762625 [d98762625@gmail.com]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import DishType from "./DishType.mjs";
|
||||
import Utils, { isNodeEnvironment } from "../Utils.mjs";
|
||||
|
||||
/**
|
||||
* Translation methods for file Dishes
|
||||
*/
|
||||
class DishFile extends DishType {
|
||||
|
||||
/**
|
||||
* convert the given value to an ArrayBuffer
|
||||
* @param {File} value
|
||||
*/
|
||||
static toArrayBuffer() {
|
||||
DishFile.checkForValue(this.value);
|
||||
if (isNodeEnvironment()) {
|
||||
this.value = Utils.readFileSync(this.value);
|
||||
} else {
|
||||
return new Promise((resolve, reject) => {
|
||||
Utils.readFile(this.value)
|
||||
.then(v => this.value = v.buffer)
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* convert the given value from an ArrayBuffer
|
||||
*/
|
||||
static fromArrayBuffer() {
|
||||
DishFile.checkForValue(this.value);
|
||||
this.value = new File(this.value, "unknown");
|
||||
}
|
||||
}
|
||||
|
||||
export default DishFile;
|
26
src/core/dishTypes/DishHTML.mjs
Normal file
26
src/core/dishTypes/DishHTML.mjs
Normal file
|
@ -0,0 +1,26 @@
|
|||
/**
|
||||
* @author d98762625 [d98762625@gmail.com]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import DishString from "./DishString.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
|
||||
/**
|
||||
* Translation methods for HTML Dishes
|
||||
*/
|
||||
class DishHTML extends DishString {
|
||||
|
||||
/**
|
||||
* convert the given value to a ArrayBuffer
|
||||
* @param {String} value
|
||||
*/
|
||||
static toArrayBuffer() {
|
||||
DishHTML.checkForValue(this.value);
|
||||
this.value = this.value ? Utils.strToArrayBuffer(Utils.unescapeHtml(Utils.stripHtmlTags(this.value, true))) : new ArrayBuffer;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default DishHTML;
|
32
src/core/dishTypes/DishJSON.mjs
Normal file
32
src/core/dishTypes/DishJSON.mjs
Normal file
|
@ -0,0 +1,32 @@
|
|||
/**
|
||||
* @author d98762625 [d98762625@gmail.com]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import DishType from "./DishType.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
|
||||
/**
|
||||
* Translation methods for JSON dishes
|
||||
*/
|
||||
class DishJSON extends DishType {
|
||||
|
||||
/**
|
||||
* convert the given value to a ArrayBuffer
|
||||
*/
|
||||
static toArrayBuffer() {
|
||||
DishJSON.checkForValue(this.value);
|
||||
this.value = this.value !== undefined ? Utils.strToArrayBuffer(JSON.stringify(this.value, null, 4)) : new ArrayBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* convert the given value from a ArrayBuffer
|
||||
*/
|
||||
static fromArrayBuffer() {
|
||||
DishJSON.checkForValue(this.value);
|
||||
this.value = JSON.parse(Utils.arrayBufferToStr(this.value));
|
||||
}
|
||||
}
|
||||
|
||||
export default DishJSON;
|
80
src/core/dishTypes/DishListFile.mjs
Normal file
80
src/core/dishTypes/DishListFile.mjs
Normal file
|
@ -0,0 +1,80 @@
|
|||
/**
|
||||
* @author d98762625 [d98762625@gmail.com]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import DishType from "./DishType.mjs";
|
||||
import Utils, { isNodeEnvironment } from "../Utils.mjs";
|
||||
|
||||
|
||||
/**
|
||||
* Translation methods for ListFile Dishes
|
||||
*/
|
||||
class DishListFile extends DishType {
|
||||
|
||||
/**
|
||||
* convert the given value to a ArrayBuffer
|
||||
*/
|
||||
static async toArrayBuffer() {
|
||||
DishListFile.checkForValue(this.value);
|
||||
|
||||
if (isNodeEnvironment()) {
|
||||
this.value = this.value.map(file => Uint8Array.from(file.data));
|
||||
} else {
|
||||
this.value = await DishListFile.concatenateTypedArraysWithTypedElements(...this.value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* convert the given value from a ArrayBuffer
|
||||
*/
|
||||
static fromArrayBuffer() {
|
||||
DishListFile.checkForValue(this.value);
|
||||
this.value = [new File(this.value, "unknown")];
|
||||
}
|
||||
|
||||
/**
|
||||
* Concatenates a list of typed elements together.
|
||||
*
|
||||
* @param {Uint8Array[]} arrays
|
||||
* @returns {Uint8Array}
|
||||
*/
|
||||
static async concatenateTypedArraysWithTypedElements(...arrays) {
|
||||
let totalLength = 0;
|
||||
for (const arr of arrays) {
|
||||
totalLength += arr.size;
|
||||
}
|
||||
const myArray = new Uint8Array(totalLength);
|
||||
|
||||
let offset = 0;
|
||||
for (const arr of arrays) {
|
||||
const data = await Utils.readFile(arr);
|
||||
myArray.set(data, offset);
|
||||
offset += data.length;
|
||||
}
|
||||
return myArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Concatenates a list of Uint8Arrays together
|
||||
*
|
||||
* @param {Uint8Array[]} arrays
|
||||
* @returns {Uint8Array}
|
||||
*/
|
||||
static concatenateTypedArrays(...arrays) {
|
||||
let totalLength = 0;
|
||||
for (const arr of arrays) {
|
||||
totalLength += arr.length;
|
||||
}
|
||||
const result = new Uint8Array(totalLength);
|
||||
let offset = 0;
|
||||
for (const arr of arrays) {
|
||||
result.set(arr, offset);
|
||||
offset += arr.length;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export default DishListFile;
|
33
src/core/dishTypes/DishNumber.mjs
Normal file
33
src/core/dishTypes/DishNumber.mjs
Normal file
|
@ -0,0 +1,33 @@
|
|||
/**
|
||||
* @author d98762625 [d98762625@gmail.com]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
|
||||
import DishType from "./DishType.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
|
||||
/**
|
||||
* Translation methods for number dishes
|
||||
*/
|
||||
class DishNumber extends DishType {
|
||||
|
||||
/**
|
||||
* convert the given value to a ArrayBuffer
|
||||
*/
|
||||
static toArrayBuffer() {
|
||||
DishNumber.checkForValue(this.value);
|
||||
this.value = typeof this.value === "number" ? Utils.strToArrayBuffer(this.value.toString()) : new ArrayBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* convert the given value from a ArrayBuffer
|
||||
*/
|
||||
static fromArrayBuffer() {
|
||||
DishNumber.checkForValue(this.value);
|
||||
this.value = this.value ? parseFloat(Utils.arrayBufferToStr(this.value)) : 0;
|
||||
}
|
||||
}
|
||||
|
||||
export default DishNumber;
|
33
src/core/dishTypes/DishString.mjs
Normal file
33
src/core/dishTypes/DishString.mjs
Normal file
|
@ -0,0 +1,33 @@
|
|||
/**
|
||||
* @author d98762625 [d98762625@gmail.com]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
|
||||
import DishType from "./DishType.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
|
||||
/**
|
||||
* Translation methods for string dishes
|
||||
*/
|
||||
class DishString extends DishType {
|
||||
|
||||
/**
|
||||
* convert the given value to a ArrayBuffer
|
||||
*/
|
||||
static toArrayBuffer() {
|
||||
DishString.checkForValue(this.value);
|
||||
this.value = this.value ? Utils.strToArrayBuffer(this.value) : new ArrayBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* convert the given value from a ArrayBuffer
|
||||
*/
|
||||
static fromArrayBuffer() {
|
||||
DishString.checkForValue(this.value);
|
||||
this.value = this.value ? Utils.arrayBufferToStr(this.value) : "";
|
||||
}
|
||||
}
|
||||
|
||||
export default DishString;
|
38
src/core/dishTypes/DishType.mjs
Normal file
38
src/core/dishTypes/DishType.mjs
Normal file
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* @author d98762625 [d98762625@gmail.com]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Abstract class for dish translation methods
|
||||
*/
|
||||
class DishType {
|
||||
|
||||
/**
|
||||
* Warn translations dont work without value from bind
|
||||
*/
|
||||
static checkForValue(value) {
|
||||
if (value === undefined) {
|
||||
throw new Error("only use translation methods with .bind");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* convert the given value to a ArrayBuffer
|
||||
* @param {*} value
|
||||
*/
|
||||
static toArrayBuffer() {
|
||||
throw new Error("toArrayBuffer has not been implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* convert the given value from a ArrayBuffer
|
||||
*/
|
||||
static fromArrayBuffer() {
|
||||
throw new Error("fromArrayBuffer has not been implemented");
|
||||
}
|
||||
}
|
||||
|
||||
export default DishType;
|
26
src/core/dishTypes/index.mjs
Normal file
26
src/core/dishTypes/index.mjs
Normal file
|
@ -0,0 +1,26 @@
|
|||
/**
|
||||
* @author d98762625 [d98762625@gmail.com]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
|
||||
import DishByteArray from "./DishByteArray.mjs";
|
||||
import DishBigNumber from "./DishBigNumber.mjs";
|
||||
import DishFile from "./DishFile.mjs";
|
||||
import DishHTML from "./DishHTML.mjs";
|
||||
import DishJSON from "./DishJSON.mjs";
|
||||
import DishListFile from "./DishListFile.mjs";
|
||||
import DishNumber from "./DishNumber.mjs";
|
||||
import DishString from "./DishString.mjs";
|
||||
|
||||
export {
|
||||
DishByteArray,
|
||||
DishBigNumber,
|
||||
DishFile,
|
||||
DishHTML,
|
||||
DishJSON,
|
||||
DishListFile,
|
||||
DishNumber,
|
||||
DishString,
|
||||
};
|
26
src/core/errors/DishError.mjs
Normal file
26
src/core/errors/DishError.mjs
Normal file
|
@ -0,0 +1,26 @@
|
|||
/**
|
||||
* Custom error type for handling Dish type errors.
|
||||
* i.e. where the Dish cannot be successfully translated between types
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
class DishError extends Error {
|
||||
/**
|
||||
* Standard error constructor. Adds no new behaviour.
|
||||
*
|
||||
* @param args - Standard error args
|
||||
*/
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
this.type = "DishError";
|
||||
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this, DishError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default DishError;
|
25
src/core/errors/ExcludedOperationError.mjs
Normal file
25
src/core/errors/ExcludedOperationError.mjs
Normal file
|
@ -0,0 +1,25 @@
|
|||
/**
|
||||
* Custom error type for handling operation that isnt included in node.js API
|
||||
*
|
||||
* @author d98762625 [d98762625@gmail.com]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
class ExcludedOperationError extends Error {
|
||||
/**
|
||||
* Standard error constructor. Adds no new behaviour.
|
||||
*
|
||||
* @param args - Standard error args
|
||||
*/
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
this.type = "ExcludedOperationError";
|
||||
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this, ExcludedOperationError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ExcludedOperationError;
|
26
src/core/errors/OperationError.mjs
Normal file
26
src/core/errors/OperationError.mjs
Normal file
|
@ -0,0 +1,26 @@
|
|||
/**
|
||||
* Custom error type for handling operation input errors.
|
||||
* i.e. where the operation can handle the error and print a message to the screen.
|
||||
*
|
||||
* @author d98762625 [d98762625@gmail.com]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
class OperationError extends Error {
|
||||
/**
|
||||
* Standard error constructor. Adds no new behaviour.
|
||||
*
|
||||
* @param args - Standard error args
|
||||
*/
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
this.type = "OperationError";
|
||||
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this, OperationError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default OperationError;
|
9
src/core/errors/index.mjs
Normal file
9
src/core/errors/index.mjs
Normal file
|
@ -0,0 +1,9 @@
|
|||
import OperationError from "./OperationError.mjs";
|
||||
import DishError from "./DishError.mjs";
|
||||
import ExcludedOperationError from "./ExcludedOperationError.mjs";
|
||||
|
||||
export {
|
||||
OperationError,
|
||||
DishError,
|
||||
ExcludedOperationError,
|
||||
};
|
139
src/core/lib/Arithmetic.mjs
Normal file
139
src/core/lib/Arithmetic.mjs
Normal file
|
@ -0,0 +1,139 @@
|
|||
/**
|
||||
* @author bwhitn [brian.m.whitney@outlook.com]
|
||||
* @author d98762625 [d98762625@gmailcom]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Utils from "../Utils.mjs";
|
||||
import BigNumber from "bignumber.js";
|
||||
|
||||
|
||||
/**
|
||||
* Converts a string array to a number array.
|
||||
*
|
||||
* @param {string[]} input
|
||||
* @param {string} delim
|
||||
* @returns {BigNumber[]}
|
||||
*/
|
||||
export function createNumArray(input, delim) {
|
||||
delim = Utils.charRep(delim || "Space");
|
||||
const splitNumbers = input.split(delim);
|
||||
const numbers = [];
|
||||
let num;
|
||||
|
||||
splitNumbers.map((number) => {
|
||||
try {
|
||||
num = BigNumber(number.trim());
|
||||
if (!num.isNaN()) {
|
||||
numbers.push(num);
|
||||
}
|
||||
} catch (err) {
|
||||
// This line is not a valid number
|
||||
}
|
||||
});
|
||||
return numbers;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds an array of numbers and returns the value.
|
||||
*
|
||||
* @param {BigNumber[]} data
|
||||
* @returns {BigNumber}
|
||||
*/
|
||||
export function sum(data) {
|
||||
if (data.length > 0) {
|
||||
return data.reduce((acc, curr) => acc.plus(curr));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Subtracts an array of numbers and returns the value.
|
||||
*
|
||||
* @param {BigNumber[]} data
|
||||
* @returns {BigNumber}
|
||||
*/
|
||||
export function sub(data) {
|
||||
if (data.length > 0) {
|
||||
return data.reduce((acc, curr) => acc.minus(curr));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Multiplies an array of numbers and returns the value.
|
||||
*
|
||||
* @param {BigNumber[]} data
|
||||
* @returns {BigNumber}
|
||||
*/
|
||||
export function multi(data) {
|
||||
if (data.length > 0) {
|
||||
return data.reduce((acc, curr) => acc.times(curr));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Divides an array of numbers and returns the value.
|
||||
*
|
||||
* @param {BigNumber[]} data
|
||||
* @returns {BigNumber}
|
||||
*/
|
||||
export function div(data) {
|
||||
if (data.length > 0) {
|
||||
return data.reduce((acc, curr) => acc.div(curr));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Computes mean of a number array and returns the value.
|
||||
*
|
||||
* @param {BigNumber[]} data
|
||||
* @returns {BigNumber}
|
||||
*/
|
||||
export function mean(data) {
|
||||
if (data.length > 0) {
|
||||
return sum(data).div(data.length);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Computes median of a number array and returns the value.
|
||||
*
|
||||
* @param {BigNumber[]} data
|
||||
* @returns {BigNumber}
|
||||
*/
|
||||
export function median(data) {
|
||||
if ((data.length % 2) === 0 && data.length > 0) {
|
||||
data.sort(function(a, b) {
|
||||
return a.minus(b);
|
||||
});
|
||||
const first = data[Math.floor(data.length / 2)];
|
||||
const second = data[Math.floor(data.length / 2) - 1];
|
||||
return mean([first, second]);
|
||||
} else {
|
||||
return data[Math.floor(data.length / 2)];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Computes standard deviation of a number array and returns the value.
|
||||
*
|
||||
* @param {BigNumber[]} data
|
||||
* @returns {BigNumber}
|
||||
*/
|
||||
export function stdDev(data) {
|
||||
if (data.length > 0) {
|
||||
const avg = mean(data);
|
||||
let devSum = new BigNumber(0);
|
||||
data.map((datum) => {
|
||||
devSum = devSum.plus(datum.minus(avg).pow(2));
|
||||
});
|
||||
return devSum.div(data.length).sqrt();
|
||||
}
|
||||
}
|
48
src/core/lib/BCD.mjs
Normal file
48
src/core/lib/BCD.mjs
Normal file
|
@ -0,0 +1,48 @@
|
|||
/**
|
||||
* Binary Code Decimal resources.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2017
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* BCD encoding schemes.
|
||||
*/
|
||||
export const ENCODING_SCHEME = [
|
||||
"8 4 2 1",
|
||||
"7 4 2 1",
|
||||
"4 2 2 1",
|
||||
"2 4 2 1",
|
||||
"8 4 -2 -1",
|
||||
"Excess-3",
|
||||
"IBM 8 4 2 1",
|
||||
];
|
||||
|
||||
/**
|
||||
* Lookup table for the binary value of each digit representation.
|
||||
*
|
||||
* I wrote a very nice algorithm to generate 8 4 2 1 encoding programmatically,
|
||||
* but unfortunately it's much easier (if less elegant) to use lookup tables
|
||||
* when supporting multiple encoding schemes.
|
||||
*
|
||||
* "Practicality beats purity" - PEP 20
|
||||
*
|
||||
* In some schemes it is possible to represent the same value in multiple ways.
|
||||
* For instance, in 4 2 2 1 encoding, 0100 and 0010 both represent 2. Support
|
||||
* has not yet been added for this.
|
||||
*/
|
||||
export const ENCODING_LOOKUP = {
|
||||
"8 4 2 1": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
|
||||
"7 4 2 1": [0, 1, 2, 3, 4, 5, 6, 8, 9, 10],
|
||||
"4 2 2 1": [0, 1, 4, 5, 8, 9, 12, 13, 14, 15],
|
||||
"2 4 2 1": [0, 1, 2, 3, 4, 11, 12, 13, 14, 15],
|
||||
"8 4 -2 -1": [0, 7, 6, 5, 4, 11, 10, 9, 8, 15],
|
||||
"Excess-3": [3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
|
||||
"IBM 8 4 2 1": [10, 1, 2, 3, 4, 5, 6, 7, 8, 9],
|
||||
};
|
||||
|
||||
/**
|
||||
* BCD formats.
|
||||
*/
|
||||
export const FORMAT = ["Nibbles", "Bytes", "Raw"];
|
66
src/core/lib/Bacon.mjs
Normal file
66
src/core/lib/Bacon.mjs
Normal file
|
@ -0,0 +1,66 @@
|
|||
/**
|
||||
* Bacon Cipher resources.
|
||||
*
|
||||
* @author Karsten Silkenbäumer [github.com/kassi]
|
||||
* @copyright Karsten Silkenbäumer 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Bacon definitions.
|
||||
*/
|
||||
export const BACON_ALPHABETS = {
|
||||
"Standard (I=J and U=V)": {
|
||||
alphabet: "ABCDEFGHIKLMNOPQRSTUWXYZ",
|
||||
codes: [0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 23]
|
||||
},
|
||||
"Complete": {
|
||||
alphabet: "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
}
|
||||
};
|
||||
export const BACON_TRANSLATION_01 = "0/1";
|
||||
export const BACON_TRANSLATION_AB = "A/B";
|
||||
export const BACON_TRANSLATION_CASE = "Case";
|
||||
export const BACON_TRANSLATION_AMNZ = "A-M/N-Z first letter";
|
||||
export const BACON_TRANSLATIONS = [
|
||||
BACON_TRANSLATION_01,
|
||||
BACON_TRANSLATION_AB,
|
||||
BACON_TRANSLATION_CASE,
|
||||
BACON_TRANSLATION_AMNZ,
|
||||
];
|
||||
export const BACON_TRANSLATIONS_FOR_ENCODING = [
|
||||
BACON_TRANSLATION_01,
|
||||
BACON_TRANSLATION_AB
|
||||
];
|
||||
export const BACON_CLEARER_MAP = {
|
||||
[BACON_TRANSLATION_01]: /[^01]/g,
|
||||
[BACON_TRANSLATION_AB]: /[^ABab]/g,
|
||||
[BACON_TRANSLATION_CASE]: /[^A-Za-z]/g,
|
||||
};
|
||||
export const BACON_NORMALIZE_MAP = {
|
||||
[BACON_TRANSLATION_AB]: {
|
||||
"A": "0",
|
||||
"B": "1",
|
||||
"a": "0",
|
||||
"b": "1"
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Swaps zeros to ones and ones to zeros.
|
||||
*
|
||||
* @param {string} data
|
||||
* @returns {string}
|
||||
*
|
||||
* @example
|
||||
* // returns "11001 01010"
|
||||
* swapZeroAndOne("00110 10101");
|
||||
*/
|
||||
export function swapZeroAndOne(string) {
|
||||
return string.replace(/[01]/g, function (c) {
|
||||
return {
|
||||
"0": "1",
|
||||
"1": "0"
|
||||
}[c];
|
||||
});
|
||||
}
|
27
src/core/lib/Base45.mjs
Normal file
27
src/core/lib/Base45.mjs
Normal file
|
@ -0,0 +1,27 @@
|
|||
/**
|
||||
* Base45 resources.
|
||||
*
|
||||
* @author Thomas Weißschuh [thomas@t-8ch.de]
|
||||
* @copyright Crown Copyright 2021
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Highlight to Base45
|
||||
*/
|
||||
export function highlightToBase45(pos, args) {
|
||||
pos[0].start = Math.floor(pos[0].start / 2) * 3;
|
||||
pos[0].end = Math.ceil(pos[0].end / 2) * 3;
|
||||
return pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight from Base45
|
||||
*/
|
||||
export function highlightFromBase45(pos, args) {
|
||||
pos[0].start = Math.floor(pos[0].start / 3) * 2;
|
||||
pos[0].end = Math.ceil(pos[0].end / 3) * 2;
|
||||
return pos;
|
||||
}
|
||||
|
||||
export const ALPHABET = "0-9A-Z $%*+\\-./:";
|
22
src/core/lib/Base58.mjs
Normal file
22
src/core/lib/Base58.mjs
Normal file
|
@ -0,0 +1,22 @@
|
|||
/**
|
||||
* Base58 resources.
|
||||
*
|
||||
* @author tlwr [toby@toby.codes]
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2017
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Base58 alphabet options.
|
||||
*/
|
||||
export const ALPHABET_OPTIONS = [
|
||||
{
|
||||
name: "Bitcoin",
|
||||
value: "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz",
|
||||
},
|
||||
{
|
||||
name: "Ripple",
|
||||
value: "rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz",
|
||||
},
|
||||
];
|
183
src/core/lib/Base64.mjs
Normal file
183
src/core/lib/Base64.mjs
Normal file
|
@ -0,0 +1,183 @@
|
|||
/**
|
||||
* Base64 functions.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Utils from "../Utils.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
|
||||
/**
|
||||
* Base64's the input byte array using the given alphabet, returning a string.
|
||||
*
|
||||
* @param {byteArray|Uint8Array|ArrayBuffer|string} data
|
||||
* @param {string} [alphabet="A-Za-z0-9+/="]
|
||||
* @returns {string}
|
||||
*
|
||||
* @example
|
||||
* // returns "SGVsbG8="
|
||||
* toBase64([72, 101, 108, 108, 111]);
|
||||
*
|
||||
* // returns "SGVsbG8="
|
||||
* toBase64("Hello");
|
||||
*/
|
||||
export function toBase64(data, alphabet="A-Za-z0-9+/=") {
|
||||
if (!data) return "";
|
||||
if (typeof data == "string") {
|
||||
data = Utils.strToArrayBuffer(data);
|
||||
}
|
||||
if (data instanceof ArrayBuffer) {
|
||||
data = new Uint8Array(data);
|
||||
}
|
||||
|
||||
alphabet = Utils.expandAlphRange(alphabet).join("");
|
||||
if (alphabet.length !== 64 && alphabet.length !== 65) { // Allow for padding
|
||||
throw new OperationError(`Invalid Base64 alphabet length (${alphabet.length}): ${alphabet}`);
|
||||
}
|
||||
|
||||
let output = "",
|
||||
chr1, chr2, chr3,
|
||||
enc1, enc2, enc3, enc4,
|
||||
i = 0;
|
||||
|
||||
while (i < data.length) {
|
||||
chr1 = data[i++];
|
||||
chr2 = data[i++];
|
||||
chr3 = data[i++];
|
||||
|
||||
enc1 = chr1 >> 2;
|
||||
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
|
||||
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
|
||||
enc4 = chr3 & 63;
|
||||
|
||||
if (isNaN(chr2)) {
|
||||
enc3 = enc4 = 64;
|
||||
} else if (isNaN(chr3)) {
|
||||
enc4 = 64;
|
||||
}
|
||||
|
||||
output += alphabet.charAt(enc1) + alphabet.charAt(enc2) +
|
||||
alphabet.charAt(enc3) + alphabet.charAt(enc4);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* UnBase64's the input string using the given alphabet, returning a byte array.
|
||||
*
|
||||
* @param {string} data
|
||||
* @param {string} [alphabet="A-Za-z0-9+/="]
|
||||
* @param {string} [returnType="string"] - Either "string" or "byteArray"
|
||||
* @param {boolean} [removeNonAlphChars=true]
|
||||
* @returns {byteArray}
|
||||
*
|
||||
* @example
|
||||
* // returns "Hello"
|
||||
* fromBase64("SGVsbG8=");
|
||||
*
|
||||
* // returns [72, 101, 108, 108, 111]
|
||||
* fromBase64("SGVsbG8=", null, "byteArray");
|
||||
*/
|
||||
export function fromBase64(data, alphabet="A-Za-z0-9+/=", returnType="string", removeNonAlphChars=true, strictMode=false) {
|
||||
if (!data) {
|
||||
return returnType === "string" ? "" : [];
|
||||
}
|
||||
|
||||
alphabet = alphabet || "A-Za-z0-9+/=";
|
||||
alphabet = Utils.expandAlphRange(alphabet).join("");
|
||||
|
||||
// Confirm alphabet is a valid length
|
||||
if (alphabet.length !== 64 && alphabet.length !== 65) { // Allow for padding
|
||||
throw new OperationError(`Error: Base64 alphabet should be 64 characters long, or 65 with a padding character. Found ${alphabet.length}: ${alphabet}`);
|
||||
}
|
||||
|
||||
// Remove non-alphabet characters
|
||||
if (removeNonAlphChars) {
|
||||
const re = new RegExp("[^" + alphabet.replace(/[[\]\\\-^$]/g, "\\$&") + "]", "g");
|
||||
data = data.replace(re, "");
|
||||
}
|
||||
|
||||
if (strictMode) {
|
||||
// Check for incorrect lengths (even without padding)
|
||||
if (data.length % 4 === 1) {
|
||||
throw new OperationError(`Error: Invalid Base64 input length (${data.length}). Cannot be 4n+1, even without padding chars.`);
|
||||
}
|
||||
|
||||
if (alphabet.length === 65) { // Padding character included
|
||||
const pad = alphabet.charAt(64);
|
||||
const padPos = data.indexOf(pad);
|
||||
if (padPos >= 0) {
|
||||
// Check that the padding character is only used at the end and maximum of twice
|
||||
if (padPos < data.length - 2 || data.charAt(data.length - 1) !== pad) {
|
||||
throw new OperationError(`Error: Base64 padding character (${pad}) not used in the correct place.`);
|
||||
}
|
||||
|
||||
// Check that input is padded to the correct length
|
||||
if (data.length % 4 !== 0) {
|
||||
throw new OperationError("Error: Base64 not padded to a multiple of 4.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const output = [];
|
||||
let chr1, chr2, chr3,
|
||||
enc1, enc2, enc3, enc4,
|
||||
i = 0;
|
||||
|
||||
while (i < data.length) {
|
||||
// Including `|| null` forces empty strings to null so that indexOf returns -1 instead of 0
|
||||
enc1 = alphabet.indexOf(data.charAt(i++) || null);
|
||||
enc2 = alphabet.indexOf(data.charAt(i++) || null);
|
||||
enc3 = alphabet.indexOf(data.charAt(i++) || null);
|
||||
enc4 = alphabet.indexOf(data.charAt(i++) || null);
|
||||
|
||||
if (strictMode && (enc1 < 0 || enc2 < 0 || enc3 < 0 || enc4 < 0)) {
|
||||
throw new OperationError("Error: Base64 input contains non-alphabet char(s)");
|
||||
}
|
||||
|
||||
chr1 = (enc1 << 2) | (enc2 >> 4);
|
||||
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
|
||||
chr3 = ((enc3 & 3) << 6) | enc4;
|
||||
|
||||
if (chr1 >= 0 && chr1 < 256) {
|
||||
output.push(chr1);
|
||||
}
|
||||
if (chr2 >= 0 && chr2 < 256 && enc3 !== 64) {
|
||||
output.push(chr2);
|
||||
}
|
||||
if (chr3 >= 0 && chr3 < 256 && enc4 !== 64) {
|
||||
output.push(chr3);
|
||||
}
|
||||
}
|
||||
|
||||
return returnType === "string" ? Utils.byteArrayToUtf8(output) : output;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Base64 alphabets.
|
||||
*/
|
||||
export const ALPHABET_OPTIONS = [
|
||||
{name: "Standard (RFC 4648): A-Za-z0-9+/=", value: "A-Za-z0-9+/="},
|
||||
{name: "URL safe (RFC 4648 \u00A75): A-Za-z0-9-_", value: "A-Za-z0-9-_"},
|
||||
{name: "Filename safe: A-Za-z0-9+-=", value: "A-Za-z0-9+\\-="},
|
||||
{name: "itoa64: ./0-9A-Za-z=", value: "./0-9A-Za-z="},
|
||||
{name: "XML: A-Za-z0-9_.", value: "A-Za-z0-9_."},
|
||||
{name: "y64: A-Za-z0-9._-", value: "A-Za-z0-9._-"},
|
||||
{name: "z64: 0-9a-zA-Z+/=", value: "0-9a-zA-Z+/="},
|
||||
{name: "Radix-64 (RFC 4880): 0-9A-Za-z+/=", value: "0-9A-Za-z+/="},
|
||||
{name: "Uuencoding: [space]-_", value: " -_"},
|
||||
{name: "Xxencoding: +-0-9A-Za-z", value: "+\\-0-9A-Za-z"},
|
||||
{name: "BinHex: !-,-0-689@A-NP-VX-Z[`a-fh-mp-r", value: "!-,-0-689@A-NP-VX-Z[`a-fh-mp-r"},
|
||||
{name: "ROT13: N-ZA-Mn-za-m0-9+/=", value: "N-ZA-Mn-za-m0-9+/="},
|
||||
{name: "UNIX crypt: ./0-9A-Za-z", value: "./0-9A-Za-z"},
|
||||
{name: "Atom128: /128GhIoPQROSTeUbADfgHijKLM+n0pFWXY456xyzB7=39VaqrstJklmNuZvwcdEC", value: "/128GhIoPQROSTeUbADfgHijKLM+n0pFWXY456xyzB7=39VaqrstJklmNuZvwcdEC"},
|
||||
{name: "Megan35: 3GHIJKLMNOPQRSTUb=cdefghijklmnopWXYZ/12+406789VaqrstuvwxyzABCDEF5", value: "3GHIJKLMNOPQRSTUb=cdefghijklmnopWXYZ/12+406789VaqrstuvwxyzABCDEF5"},
|
||||
{name: "Zong22: ZKj9n+yf0wDVX1s/5YbdxSo=ILaUpPBCHg8uvNO4klm6iJGhQ7eFrWczAMEq3RTt2", value: "ZKj9n+yf0wDVX1s/5YbdxSo=ILaUpPBCHg8uvNO4klm6iJGhQ7eFrWczAMEq3RTt2"},
|
||||
{name: "Hazz15: HNO4klm6ij9n+J2hyf0gzA8uvwDEq3X1Q7ZKeFrWcVTts/MRGYbdxSo=ILaUpPBC5", value: "HNO4klm6ij9n+J2hyf0gzA8uvwDEq3X1Q7ZKeFrWcVTts/MRGYbdxSo=ILaUpPBC5"}
|
||||
];
|
46
src/core/lib/Base85.mjs
Normal file
46
src/core/lib/Base85.mjs
Normal file
|
@ -0,0 +1,46 @@
|
|||
import Utils from "../Utils.mjs";
|
||||
|
||||
/**
|
||||
* Base85 resources.
|
||||
*
|
||||
* @author PenguinGeorge [george@penguingeorge.com]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Base85 alphabet options.
|
||||
*/
|
||||
export const ALPHABET_OPTIONS = [
|
||||
{
|
||||
name: "Standard",
|
||||
value: "!-u",
|
||||
},
|
||||
{
|
||||
name: "Z85 (ZeroMQ)",
|
||||
value: "0-9a-zA-Z.\\-:+=^!/*?&<>()[]{}@%$#",
|
||||
},
|
||||
{
|
||||
name: "IPv6",
|
||||
value: "0-9A-Za-z!#$%&()*+\\-;<=>?@^_`{|}~",
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Returns the name of the alphabet, when given the alphabet.
|
||||
*
|
||||
* @param {string} alphabet
|
||||
* @returns {string}
|
||||
*/
|
||||
export function alphabetName(alphabet) {
|
||||
alphabet = escape(alphabet);
|
||||
let name;
|
||||
|
||||
ALPHABET_OPTIONS.forEach(function(a) {
|
||||
const expanded = Utils.expandAlphRange(a.value).join("");
|
||||
if (alphabet === escape(expanded)) name = a.name;
|
||||
});
|
||||
|
||||
return name;
|
||||
}
|
44
src/core/lib/Base92.mjs
Normal file
44
src/core/lib/Base92.mjs
Normal file
|
@ -0,0 +1,44 @@
|
|||
/**
|
||||
* Base92 resources.
|
||||
*
|
||||
* @author sg5506844 [sg5506844@gmail.com]
|
||||
* @copyright Crown Copyright 2021
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
|
||||
/**
|
||||
* Base92 alphabet char
|
||||
*
|
||||
* @param {number} val
|
||||
* @returns {number}
|
||||
*/
|
||||
export function base92Chr(val) {
|
||||
if (val < 0 || val >= 91) {
|
||||
throw new OperationError("Invalid value");
|
||||
}
|
||||
if (val === 0)
|
||||
return "!".charCodeAt(0);
|
||||
else if (val <= 61)
|
||||
return "#".charCodeAt(0) + val - 1;
|
||||
else
|
||||
return "a".charCodeAt(0) + val - 62;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base92 alphabet ord
|
||||
*
|
||||
* @param {string} val
|
||||
* @returns {number}
|
||||
*/
|
||||
export function base92Ord(val) {
|
||||
if (val === "!")
|
||||
return 0;
|
||||
else if ("#" <= val && val <= "_")
|
||||
return val.charCodeAt(0) - "#".charCodeAt(0) + 1;
|
||||
else if ("a" <= val && val <= "}")
|
||||
return val.charCodeAt(0) - "a".charCodeAt(0) + 62;
|
||||
throw new OperationError(`${val} is not a base92 character`);
|
||||
}
|
||||
|
80
src/core/lib/Binary.mjs
Normal file
80
src/core/lib/Binary.mjs
Normal file
|
@ -0,0 +1,80 @@
|
|||
/**
|
||||
* Binary functions.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Utils from "../Utils.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
|
||||
|
||||
/**
|
||||
* Convert a byte array into a binary string.
|
||||
*
|
||||
* @param {Uint8Array|byteArray|number} data
|
||||
* @param {string} [delim="Space"]
|
||||
* @param {number} [padding=8]
|
||||
* @returns {string}
|
||||
*
|
||||
* @example
|
||||
* // returns "00001010 00010100 00011110"
|
||||
* toBinary([10,20,30]);
|
||||
*
|
||||
* // returns "00001010:00010100:00011110"
|
||||
* toBinary([10,20,30], "Colon");
|
||||
*
|
||||
* // returns "1010:10100:11110"
|
||||
* toBinary([10,20,30], "Colon", 0);
|
||||
*/
|
||||
export function toBinary(data, delim="Space", padding=8) {
|
||||
if (data === undefined || data === null)
|
||||
throw new OperationError("Unable to convert to binary: Empty input data enocuntered");
|
||||
|
||||
delim = Utils.charRep(delim);
|
||||
let output = "";
|
||||
|
||||
if (data.length) { // array
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
output += data[i].toString(2).padStart(padding, "0");
|
||||
if (i !== data.length - 1) output += delim;
|
||||
}
|
||||
} else if (typeof data === "number") { // Single value
|
||||
return data.toString(2).padStart(padding, "0");
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert a binary string into a byte array.
|
||||
*
|
||||
* @param {string} data
|
||||
* @param {string} [delim]
|
||||
* @param {number} [byteLen=8]
|
||||
* @returns {byteArray}
|
||||
*
|
||||
* @example
|
||||
* // returns [10,20,30]
|
||||
* fromBinary("00001010 00010100 00011110");
|
||||
*
|
||||
* // returns [10,20,30]
|
||||
* fromBinary("00001010:00010100:00011110", "Colon");
|
||||
*/
|
||||
export function fromBinary(data, delim="Space", byteLen=8) {
|
||||
if (byteLen < 1 || Math.round(byteLen) !== byteLen)
|
||||
throw new OperationError("Byte length must be a positive integer");
|
||||
|
||||
const delimRegex = Utils.regexRep(delim);
|
||||
data = data.replace(delimRegex, "");
|
||||
|
||||
const output = [];
|
||||
for (let i = 0; i < data.length; i += byteLen) {
|
||||
output.push(parseInt(data.substr(i, byteLen), 2));
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
124
src/core/lib/BitwiseOp.mjs
Normal file
124
src/core/lib/BitwiseOp.mjs
Normal file
|
@ -0,0 +1,124 @@
|
|||
/**
|
||||
* Bitwise operation resources.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Runs bitwise operations across the input data.
|
||||
*
|
||||
* @param {byteArray|Uint8Array} input
|
||||
* @param {byteArray} key
|
||||
* @param {function} func - The bitwise calculation to carry out
|
||||
* @param {boolean} nullPreserving
|
||||
* @param {string} scheme
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
export function bitOp (input, key, func, nullPreserving, scheme) {
|
||||
if (!key || !key.length) key = [0];
|
||||
const result = [];
|
||||
let x = null,
|
||||
k = null,
|
||||
o = null;
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
k = key[i % key.length];
|
||||
if (scheme === "Cascade") k = input[i + 1] || 0;
|
||||
o = input[i];
|
||||
x = nullPreserving && (o === 0 || o === k) ? o : func(o, k);
|
||||
result.push(x);
|
||||
if (scheme &&
|
||||
scheme !== "Standard" &&
|
||||
!(nullPreserving && (o === 0 || o === k))) {
|
||||
switch (scheme) {
|
||||
case "Input differential":
|
||||
key[i % key.length] = o;
|
||||
break;
|
||||
case "Output differential":
|
||||
key[i % key.length] = x;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* XOR bitwise calculation.
|
||||
*
|
||||
* @param {number} operand
|
||||
* @param {number} key
|
||||
* @returns {number}
|
||||
*/
|
||||
export function xor(operand, key) {
|
||||
return operand ^ key;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* NOT bitwise calculation.
|
||||
*
|
||||
* @param {number} operand
|
||||
* @returns {number}
|
||||
*/
|
||||
export function not(operand, _) {
|
||||
return ~operand & 0xff;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* AND bitwise calculation.
|
||||
*
|
||||
* @param {number} operand
|
||||
* @param {number} key
|
||||
* @returns {number}
|
||||
*/
|
||||
export function and(operand, key) {
|
||||
return operand & key;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* OR bitwise calculation.
|
||||
*
|
||||
* @param {number} operand
|
||||
* @param {number} key
|
||||
* @returns {number}
|
||||
*/
|
||||
export function or(operand, key) {
|
||||
return operand | key;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ADD bitwise calculation.
|
||||
*
|
||||
* @param {number} operand
|
||||
* @param {number} key
|
||||
* @returns {number}
|
||||
*/
|
||||
export function add(operand, key) {
|
||||
return (operand + key) % 256;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* SUB bitwise calculation.
|
||||
*
|
||||
* @param {number} operand
|
||||
* @param {number} key
|
||||
* @returns {number}
|
||||
*/
|
||||
export function sub(operand, key) {
|
||||
const result = operand - key;
|
||||
return (result < 0) ? 256 + result : result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Delimiter options for bitwise operations
|
||||
*/
|
||||
export const BITWISE_OP_DELIMS = ["Hex", "Decimal", "Binary", "Base64", "UTF8", "Latin1"];
|
436
src/core/lib/Blowfish.mjs
Normal file
436
src/core/lib/Blowfish.mjs
Normal file
|
@ -0,0 +1,436 @@
|
|||
/**
|
||||
Blowfish.js from Dojo Toolkit 1.8.1 (https://github.com/dojo/dojox/tree/1.8/encoding)
|
||||
Extracted by Sladex (xslade@gmail.com)
|
||||
Shoehorned into working with mjs for CyberChef by Matt C (matt@artemisbot.uk)
|
||||
Refactored and implemented modes support by cbeuw (cbeuw.andy@gmail.com)
|
||||
|
||||
@license BSD
|
||||
========================================================================
|
||||
The "New" BSD License:
|
||||
**********************
|
||||
|
||||
Copyright (c) 2005-2016, The Dojo Foundation
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
* Neither the name of the Dojo Foundation nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
const crypto = {};
|
||||
|
||||
import forge from "node-forge";
|
||||
|
||||
|
||||
/* dojo-release-1.8.1/dojo/_base/lang.js.uncompressed.js */
|
||||
|
||||
const lang = {};
|
||||
lang.isString = function(it) {
|
||||
// summary:
|
||||
// Return true if it is a String
|
||||
// it: anything
|
||||
// Item to test.
|
||||
return (typeof it == "string" || it instanceof String); // Boolean
|
||||
};
|
||||
|
||||
|
||||
/* dojo-release-1.8.1/dojo/_base/array.js.uncompressed.js */
|
||||
|
||||
const arrayUtil = {};
|
||||
arrayUtil.map = function(arr, callback, thisObject, Ctr) {
|
||||
// summary:
|
||||
// applies callback to each element of arr and returns
|
||||
// an Array with the results
|
||||
// arr: Array|String
|
||||
// the array to iterate on. If a string, operates on
|
||||
// individual characters.
|
||||
// callback: Function
|
||||
// a function is invoked with three arguments, (item, index,
|
||||
// array), and returns a value
|
||||
// thisObject: Object?
|
||||
// may be used to scope the call to callback
|
||||
// returns: Array
|
||||
// description:
|
||||
// This function corresponds to the JavaScript 1.6 Array.map() method, with one difference: when
|
||||
// run over sparse arrays, this implementation passes the "holes" in the sparse array to
|
||||
// the callback function with a value of undefined. JavaScript 1.6's map skips the holes in the sparse array.
|
||||
// For more details, see:
|
||||
// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/map
|
||||
// example:
|
||||
// | // returns [2, 3, 4, 5]
|
||||
// | array.map([1, 2, 3, 4], function(item){ return item+1 });
|
||||
|
||||
// TODO: why do we have a non-standard signature here? do we need "Ctr"?
|
||||
let i = 0;
|
||||
const l = arr && arr.length || 0, out = new (Ctr || Array)(l);
|
||||
if (l && typeof arr == "string") arr = arr.split("");
|
||||
if (thisObject) {
|
||||
for (; i < l; ++i) {
|
||||
out[i] = callback.call(thisObject, arr[i], i, arr);
|
||||
}
|
||||
} else {
|
||||
for (; i < l; ++i) {
|
||||
out[i] = callback(arr[i], i, arr);
|
||||
}
|
||||
}
|
||||
return out; // Array
|
||||
};
|
||||
|
||||
/* dojo-release-1.8.1/dojox/encoding/crypto/Blowfish.js.uncompressed.js */
|
||||
|
||||
/* Blowfish
|
||||
* Created based on the C# implementation by Marcus Hahn (http://www.hotpixel.net/)
|
||||
* Unsigned math based on Paul Johnstone and Peter Wood patches.
|
||||
* 2005-12-08
|
||||
*/
|
||||
const boxes={
|
||||
p: [
|
||||
0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89,
|
||||
0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917,
|
||||
0x9216d5d9, 0x8979fb1b
|
||||
],
|
||||
s0: [
|
||||
0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99,
|
||||
0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e,
|
||||
0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013,
|
||||
0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e,
|
||||
0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440,
|
||||
0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce, 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a,
|
||||
0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677,
|
||||
0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032,
|
||||
0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239,
|
||||
0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0,
|
||||
0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98,
|
||||
0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88, 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe,
|
||||
0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d,
|
||||
0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7,
|
||||
0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba, 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463,
|
||||
0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09,
|
||||
0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb,
|
||||
0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8,
|
||||
0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82,
|
||||
0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573,
|
||||
0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0, 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b,
|
||||
0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8,
|
||||
0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0,
|
||||
0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7, 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c,
|
||||
0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1,
|
||||
0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9,
|
||||
0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf,
|
||||
0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af,
|
||||
0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5,
|
||||
0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915,
|
||||
0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915,
|
||||
0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a
|
||||
],
|
||||
s1: [
|
||||
0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266,
|
||||
0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e,
|
||||
0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1,
|
||||
0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1,
|
||||
0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8,
|
||||
0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff, 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd,
|
||||
0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7,
|
||||
0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331,
|
||||
0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf, 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af,
|
||||
0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87,
|
||||
0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2,
|
||||
0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16, 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd,
|
||||
0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509,
|
||||
0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3,
|
||||
0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a,
|
||||
0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960,
|
||||
0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28,
|
||||
0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802, 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84,
|
||||
0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf,
|
||||
0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e,
|
||||
0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50, 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7,
|
||||
0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281,
|
||||
0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696,
|
||||
0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128, 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73,
|
||||
0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0,
|
||||
0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250,
|
||||
0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3, 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285,
|
||||
0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061,
|
||||
0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e,
|
||||
0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc,
|
||||
0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340,
|
||||
0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7
|
||||
],
|
||||
s2: [
|
||||
0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068,
|
||||
0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840,
|
||||
0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504,
|
||||
0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb,
|
||||
0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6,
|
||||
0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42, 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b,
|
||||
0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb,
|
||||
0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b,
|
||||
0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c,
|
||||
0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc,
|
||||
0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564,
|
||||
0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b, 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115,
|
||||
0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728,
|
||||
0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e,
|
||||
0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37, 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d,
|
||||
0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b,
|
||||
0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb,
|
||||
0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c,
|
||||
0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9,
|
||||
0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe,
|
||||
0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d, 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc,
|
||||
0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61,
|
||||
0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9,
|
||||
0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2, 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c,
|
||||
0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633,
|
||||
0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169,
|
||||
0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027,
|
||||
0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62,
|
||||
0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76,
|
||||
0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc,
|
||||
0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c,
|
||||
0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0
|
||||
],
|
||||
s3: [
|
||||
0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe,
|
||||
0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4,
|
||||
0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6,
|
||||
0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22,
|
||||
0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6,
|
||||
0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9, 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59,
|
||||
0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51,
|
||||
0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c,
|
||||
0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b, 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28,
|
||||
0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd,
|
||||
0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319,
|
||||
0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb, 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f,
|
||||
0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32,
|
||||
0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166,
|
||||
0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae, 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb,
|
||||
0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47,
|
||||
0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d,
|
||||
0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84, 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048,
|
||||
0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd,
|
||||
0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7,
|
||||
0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38, 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f,
|
||||
0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525,
|
||||
0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442,
|
||||
0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964, 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e,
|
||||
0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d,
|
||||
0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299,
|
||||
0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02, 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc,
|
||||
0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a,
|
||||
0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b,
|
||||
0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060,
|
||||
0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9,
|
||||
0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// fixes based on patch submitted by Peter Wood (#5791)
|
||||
const xor = function(x, y) {
|
||||
return (((x>>0x10)^(y>>0x10))<<0x10)|(((x&0xffff)^(y&0xffff))&0xffff);
|
||||
};
|
||||
|
||||
|
||||
const f = function(v, box) {
|
||||
const d=box.s3[v&0xff]; v>>=8;
|
||||
const c=box.s2[v&0xff]; v>>=8;
|
||||
const b=box.s1[v&0xff]; v>>=8;
|
||||
const a=box.s0[v&0xff];
|
||||
|
||||
let r = (((a>>0x10)+(b>>0x10)+(((a&0xffff)+(b&0xffff))>>0x10))<<0x10)|(((a&0xffff)+(b&0xffff))&0xffff);
|
||||
r = (((r>>0x10)^(c>>0x10))<<0x10)|(((r&0xffff)^(c&0xffff))&0xffff);
|
||||
return (((r>>0x10)+(d>>0x10)+(((r&0xffff)+(d&0xffff))>>0x10))<<0x10)|(((r&0xffff)+(d&0xffff))&0xffff);
|
||||
};
|
||||
|
||||
|
||||
const eb = function(o, box) {
|
||||
// TODO: see if this can't be made more efficient
|
||||
let l=o.left;
|
||||
let r=o.right;
|
||||
l=xor(l, box.p[0]);
|
||||
r=xor(r, xor(f(l, box), box.p[1]));
|
||||
l=xor(l, xor(f(r, box), box.p[2]));
|
||||
r=xor(r, xor(f(l, box), box.p[3]));
|
||||
l=xor(l, xor(f(r, box), box.p[4]));
|
||||
r=xor(r, xor(f(l, box), box.p[5]));
|
||||
l=xor(l, xor(f(r, box), box.p[6]));
|
||||
r=xor(r, xor(f(l, box), box.p[7]));
|
||||
l=xor(l, xor(f(r, box), box.p[8]));
|
||||
r=xor(r, xor(f(l, box), box.p[9]));
|
||||
l=xor(l, xor(f(r, box), box.p[10]));
|
||||
r=xor(r, xor(f(l, box), box.p[11]));
|
||||
l=xor(l, xor(f(r, box), box.p[12]));
|
||||
r=xor(r, xor(f(l, box), box.p[13]));
|
||||
l=xor(l, xor(f(r, box), box.p[14]));
|
||||
r=xor(r, xor(f(l, box), box.p[15]));
|
||||
l=xor(l, xor(f(r, box), box.p[16]));
|
||||
o.right=l;
|
||||
o.left=xor(r, box.p[17]);
|
||||
};
|
||||
|
||||
const db = function(o, box) {
|
||||
let l=o.left;
|
||||
let r=o.right;
|
||||
l=xor(l, box.p[17]);
|
||||
r=xor(r, xor(f(l, box), box.p[16]));
|
||||
l=xor(l, xor(f(r, box), box.p[15]));
|
||||
r=xor(r, xor(f(l, box), box.p[14]));
|
||||
l=xor(l, xor(f(r, box), box.p[13]));
|
||||
r=xor(r, xor(f(l, box), box.p[12]));
|
||||
l=xor(l, xor(f(r, box), box.p[11]));
|
||||
r=xor(r, xor(f(l, box), box.p[10]));
|
||||
l=xor(l, xor(f(r, box), box.p[9]));
|
||||
r=xor(r, xor(f(l, box), box.p[8]));
|
||||
l=xor(l, xor(f(r, box), box.p[7]));
|
||||
r=xor(r, xor(f(l, box), box.p[6]));
|
||||
l=xor(l, xor(f(r, box), box.p[5]));
|
||||
r=xor(r, xor(f(l, box), box.p[4]));
|
||||
l=xor(l, xor(f(r, box), box.p[3]));
|
||||
r=xor(r, xor(f(l, box), box.p[2]));
|
||||
l=xor(l, xor(f(r, box), box.p[1]));
|
||||
o.right=l;
|
||||
o.left=xor(r, box.p[0]);
|
||||
};
|
||||
|
||||
const encryptBlock=function(inblock, outblock, box) {
|
||||
const o = {};
|
||||
o.left=inblock[0];
|
||||
o.right=inblock[1];
|
||||
eb(o, box);
|
||||
outblock[0] = o.left;
|
||||
outblock[1] = o.right;
|
||||
};
|
||||
|
||||
const decryptBlock=function(inblock, outblock, box) {
|
||||
const o= {};
|
||||
o.left=inblock[0];
|
||||
o.right=inblock[1];
|
||||
db(o, box);
|
||||
outblock[0] = o.left;
|
||||
outblock[1] = o.right;
|
||||
};
|
||||
|
||||
crypto.Blowfish = new function() {
|
||||
this.createCipher=function(key, modeName) {
|
||||
return new forge.cipher.BlockCipher({
|
||||
algorithm: new Blowfish.Algorithm(key, modeName),
|
||||
key: key,
|
||||
decrypt: false
|
||||
});
|
||||
};
|
||||
|
||||
this.createDecipher=function(key, modeName) {
|
||||
return new forge.cipher.BlockCipher({
|
||||
algorithm: new Blowfish.Algorithm(key, modeName),
|
||||
key: key,
|
||||
decrypt: true
|
||||
});
|
||||
};
|
||||
}();
|
||||
|
||||
|
||||
crypto.Blowfish.Algorithm=function(key, modeName) {
|
||||
this.initialize({key: key});
|
||||
const _box = this.box;
|
||||
const modeOption = {
|
||||
blockSize: 8,
|
||||
cipher: {
|
||||
encrypt: function(inblock, outblock) {
|
||||
encryptBlock(inblock, outblock, _box);
|
||||
},
|
||||
decrypt: function(inblock, outblock) {
|
||||
decryptBlock(inblock, outblock, _box);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
switch (modeName.toLowerCase()) {
|
||||
case "ecb":
|
||||
this.mode=new forge.cipher.modes.ecb(modeOption);
|
||||
break;
|
||||
case "cbc":
|
||||
this.mode=new forge.cipher.modes.cbc(modeOption);
|
||||
break;
|
||||
case "cfb":
|
||||
this.mode=new forge.cipher.modes.cfb(modeOption);
|
||||
break;
|
||||
case "ofb":
|
||||
this.mode=new forge.cipher.modes.ofb(modeOption);
|
||||
break;
|
||||
case "ctr":
|
||||
this.mode=new forge.cipher.modes.ctr(modeOption);
|
||||
break;
|
||||
default:
|
||||
this.mode=new forge.cipher.modes.ecb(modeOption);
|
||||
break;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
crypto.Blowfish.Algorithm.prototype.initialize=function(options) {
|
||||
const POW8=Math.pow(2, 8);
|
||||
|
||||
let k=options.key;
|
||||
if (lang.isString(k)) {
|
||||
k = arrayUtil.map(k.split(""), function(item) {
|
||||
return item.charCodeAt(0) & 0xff;
|
||||
});
|
||||
}
|
||||
|
||||
// init the boxes
|
||||
let pos=0, data=0;
|
||||
const res={ left: 0, right: 0 };
|
||||
const box = {
|
||||
p: arrayUtil.map(boxes.p.slice(0), function(item) {
|
||||
const l=k.length;
|
||||
for (let j=0; j<4; j++) {
|
||||
data=(data*POW8)|k[pos++ % l];
|
||||
}
|
||||
return (((item>>0x10)^(data>>0x10))<<0x10)|(((item&0xffff)^(data&0xffff))&0xffff);
|
||||
}),
|
||||
s0: boxes.s0.slice(0),
|
||||
s1: boxes.s1.slice(0),
|
||||
s2: boxes.s2.slice(0),
|
||||
s3: boxes.s3.slice(0)
|
||||
};
|
||||
|
||||
// encrypt p and the s boxes
|
||||
for (let i=0, l=box.p.length; i<l;) {
|
||||
eb(res, box);
|
||||
box.p[i++]=res.left;
|
||||
box.p[i++]=res.right;
|
||||
}
|
||||
for (let i=0; i<4; i++) {
|
||||
for (let j=0, l=box["s"+i].length; j<l;) {
|
||||
eb(res, box);
|
||||
box["s"+i][j++]=res.left;
|
||||
box["s"+i][j++]=res.right;
|
||||
}
|
||||
}
|
||||
this.box = box;
|
||||
};
|
||||
|
||||
export const Blowfish = crypto.Blowfish;
|
756
src/core/lib/Bombe.mjs
Normal file
756
src/core/lib/Bombe.mjs
Normal file
|
@ -0,0 +1,756 @@
|
|||
/**
|
||||
* Emulation of the Bombe machine.
|
||||
*
|
||||
* @author s2224834
|
||||
* @author The National Museum of Computing - Bombe Rebuild Project
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import {Rotor, Plugboard, a2i, i2a} from "./Enigma.mjs";
|
||||
|
||||
/**
|
||||
* Convenience/optimisation subclass of Rotor
|
||||
*
|
||||
* This allows creating multiple Rotors which share backing maps, to avoid repeatedly parsing the
|
||||
* rotor spec strings and duplicating the maps in memory.
|
||||
*/
|
||||
class CopyRotor extends Rotor {
|
||||
/**
|
||||
* Return a copy of this Rotor.
|
||||
* @returns {Object}
|
||||
*/
|
||||
copy() {
|
||||
const clone = {
|
||||
map: this.map,
|
||||
revMap: this.revMap,
|
||||
pos: this.pos,
|
||||
step: this.step,
|
||||
transform: this.transform,
|
||||
revTransform: this.revTransform,
|
||||
};
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Node in the menu graph
|
||||
*
|
||||
* A node represents a cipher/plaintext letter.
|
||||
*/
|
||||
class Node {
|
||||
/**
|
||||
* Node constructor.
|
||||
* @param {number} letter - The plain/ciphertext letter this node represents (as a number).
|
||||
*/
|
||||
constructor(letter) {
|
||||
this.letter = letter;
|
||||
this.edges = new Set();
|
||||
this.visited = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Edge in the menu graph
|
||||
*
|
||||
* An edge represents an Enigma machine transformation between two letters.
|
||||
*/
|
||||
class Edge {
|
||||
/**
|
||||
* Edge constructor - an Enigma machine mapping between letters
|
||||
* @param {number} pos - The rotor position, relative to the beginning of the crib, at this edge
|
||||
* @param {number} node1 - Letter at one end (as a number)
|
||||
* @param {number} node2 - Letter at the other end
|
||||
*/
|
||||
constructor(pos, node1, node2) {
|
||||
this.pos = pos;
|
||||
this.node1 = node1;
|
||||
this.node2 = node2;
|
||||
node1.edges.add(this);
|
||||
node2.edges.add(this);
|
||||
this.visited = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the node at one end of this edge, return the other end.
|
||||
* @param node {number} - The node we have
|
||||
* @returns {number}
|
||||
*/
|
||||
getOther(node) {
|
||||
return this.node1 === node ? this.node2 : this.node1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* As all the Bombe's rotors move in step, at any given point the vast majority of the scramblers
|
||||
* in the machine share the majority of their state, which is hosted in this class.
|
||||
*/
|
||||
class SharedScrambler {
|
||||
/**
|
||||
* SharedScrambler constructor.
|
||||
* @param {Object[]} rotors - List of rotors in the shared state _only_.
|
||||
* @param {Object} reflector - The reflector in use.
|
||||
*/
|
||||
constructor(rotors, reflector) {
|
||||
this.lowerCache = new Array(26);
|
||||
this.higherCache = new Array(26);
|
||||
for (let i=0; i<26; i++) {
|
||||
this.higherCache[i] = new Array(26);
|
||||
}
|
||||
this.changeRotors(rotors, reflector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the rotors and reflector in this SharedScrambler.
|
||||
* This takes care of flushing caches as well.
|
||||
* @param {Object[]} rotors - List of rotors in the shared state _only_.
|
||||
* @param {Object} reflector - The reflector in use.
|
||||
*/
|
||||
changeRotors(rotors, reflector) {
|
||||
this.reflector = reflector;
|
||||
this.rotors = rotors;
|
||||
this.rotorsRev = [].concat(rotors).reverse();
|
||||
this.cacheGen();
|
||||
}
|
||||
|
||||
/**
|
||||
* Step the rotors forward.
|
||||
* @param {number} n - How many rotors to step. This includes the rotors which are not part of
|
||||
* the shared state, so should be 2 or more.
|
||||
*/
|
||||
step(n) {
|
||||
for (let i=0; i<n-1; i++) {
|
||||
this.rotors[i].step();
|
||||
}
|
||||
this.cacheGen();
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimisation: We pregenerate all routes through the machine with the top rotor removed,
|
||||
* as these rarely change. This saves a lot of lookups. This function generates this route
|
||||
* table.
|
||||
* We also just-in-time cache the full routes through the scramblers, because after stepping
|
||||
* the fast rotor some scramblers will be in states occupied by other scrambles on previous
|
||||
* iterations.
|
||||
*/
|
||||
cacheGen() {
|
||||
for (let i=0; i<26; i++) {
|
||||
this.lowerCache[i] = undefined;
|
||||
for (let j=0; j<26; j++) {
|
||||
this.higherCache[i][j] = undefined;
|
||||
}
|
||||
}
|
||||
for (let i=0; i<26; i++) {
|
||||
if (this.lowerCache[i] !== undefined) {
|
||||
continue;
|
||||
}
|
||||
let letter = i;
|
||||
for (const rotor of this.rotors) {
|
||||
letter = rotor.transform(letter);
|
||||
}
|
||||
letter = this.reflector.transform(letter);
|
||||
for (const rotor of this.rotorsRev) {
|
||||
letter = rotor.revTransform(letter);
|
||||
}
|
||||
// By symmetry
|
||||
this.lowerCache[i] = letter;
|
||||
this.lowerCache[letter] = i;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a letter through this (partial) scrambler.
|
||||
* @param {number} i - The letter
|
||||
* @returns {number}
|
||||
*/
|
||||
transform(i) {
|
||||
return this.lowerCache[i];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrambler.
|
||||
*
|
||||
* This is effectively just an Enigma machine, but it only operates on one character at a time and
|
||||
* the stepping mechanism is different.
|
||||
*/
|
||||
class Scrambler {
|
||||
/** Scrambler constructor.
|
||||
* @param {Object} base - The SharedScrambler whose state this scrambler uses
|
||||
* @param {Object} rotor - The non-shared fast rotor in this scrambler
|
||||
* @param {number} pos - Position offset from start of crib
|
||||
* @param {number} end1 - Letter in menu this scrambler is attached to
|
||||
* @param {number} end2 - Other letter in menu this scrambler is attached to
|
||||
*/
|
||||
constructor(base, rotor, pos, end1, end2) {
|
||||
this.baseScrambler = base;
|
||||
this.initialPos = pos;
|
||||
this.changeRotor(rotor);
|
||||
this.end1 = end1;
|
||||
this.end2 = end2;
|
||||
// For efficiency reasons, we pull the relevant shared cache from the baseScrambler into
|
||||
// this object - this saves us a few pointer dereferences
|
||||
this.cache = this.baseScrambler.higherCache[pos];
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the rotor in this scrambler.
|
||||
* The position is reset automatically.
|
||||
* @param {Object} rotor - New rotor
|
||||
*/
|
||||
changeRotor(rotor) {
|
||||
this.rotor = rotor;
|
||||
this.rotor.pos += this.initialPos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Step the rotor forward.
|
||||
*
|
||||
* The base SharedScrambler needs to be instructed to step separately.
|
||||
*/
|
||||
step() {
|
||||
// The Bombe steps the slowest rotor on an actual Enigma fastest, for reasons.
|
||||
// ...but for optimisation reasons I'm going to cheat and not do that, as this vastly
|
||||
// simplifies caching the state of the majority of the scramblers. The results are the
|
||||
// same, just in a slightly different order.
|
||||
this.rotor.step();
|
||||
this.cache = this.baseScrambler.higherCache[this.rotor.pos];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Run a letter through the scrambler.
|
||||
* @param {number} i - The letter to transform (as a number)
|
||||
* @returns {number}
|
||||
*/
|
||||
transform(i) {
|
||||
let letter = i;
|
||||
const cached = this.cache[i];
|
||||
if (cached !== undefined) {
|
||||
return cached;
|
||||
}
|
||||
letter = this.rotor.transform(letter);
|
||||
letter = this.baseScrambler.transform(letter);
|
||||
letter = this.rotor.revTransform(letter);
|
||||
this.cache[i] = letter;
|
||||
this.cache[letter] = i;
|
||||
return letter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given one letter in the menu this scrambler maps to, return the other.
|
||||
* @param end {number} - The node we have
|
||||
* @returns {number}
|
||||
*/
|
||||
getOtherEnd(end) {
|
||||
return this.end1 === end ? this.end2 : this.end1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the position this scrambler is set to.
|
||||
* Note that because of Enigma's stepping, you need to set an actual Enigma to the previous
|
||||
* position in order to get it to make a certain set of electrical connections when a button
|
||||
* is pressed - this function *does* take this into account.
|
||||
* However, as with the rest of the Bombe, it does not take stepping into account - the middle
|
||||
* and slow rotors are treated as static.
|
||||
* @return {string}
|
||||
*/
|
||||
getPos() {
|
||||
let result = "";
|
||||
// Roll back the fast rotor by one step
|
||||
let pos = Utils.mod(this.rotor.pos - 1, 26);
|
||||
result += i2a(pos);
|
||||
for (let i=0; i<this.baseScrambler.rotors.length; i++) {
|
||||
pos = this.baseScrambler.rotors[i].pos;
|
||||
result += i2a(pos);
|
||||
}
|
||||
return result.split("").reverse().join("");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bombe simulator class.
|
||||
*/
|
||||
export class BombeMachine {
|
||||
/**
|
||||
* Construct a Bombe.
|
||||
*
|
||||
* Note that there is no handling of offsets here: the crib specified must exactly match the
|
||||
* ciphertext. It will check that the crib is sane (length is vaguely sensible and there's no
|
||||
* matching characters between crib and ciphertext) but cannot check further - if it's wrong
|
||||
* your results will be wrong!
|
||||
*
|
||||
* There is also no handling of rotor stepping - if the target Enigma stepped in the middle of
|
||||
* your crib, you're out of luck. TODO: Allow specifying a step point - this is fairly easy to
|
||||
* configure on a real Bombe, but we're not clear on whether it was ever actually done for
|
||||
* real (there would almost certainly have been better ways of attacking in most situations
|
||||
* than attempting to exhaust options for the stepping point, but in some circumstances, e.g.
|
||||
* via Banburismus, the stepping point might have been known).
|
||||
*
|
||||
* @param {string[]} rotors - list of rotor spec strings (without step points!)
|
||||
* @param {Object} reflector - Reflector object
|
||||
* @param {string} ciphertext - The ciphertext to attack
|
||||
* @param {string} crib - Known plaintext for this ciphertext
|
||||
* @param {boolean} check - Whether to use the checking machine
|
||||
* @param {function} update - Function to call to send status updates (optional)
|
||||
*/
|
||||
constructor(rotors, reflector, ciphertext, crib, check, update=undefined) {
|
||||
if (ciphertext.length < crib.length) {
|
||||
throw new OperationError("Crib overruns supplied ciphertext");
|
||||
}
|
||||
if (crib.length < 2) {
|
||||
// This is the absolute bare minimum to be sane, and even then it's likely too short to
|
||||
// be useful
|
||||
throw new OperationError("Crib is too short");
|
||||
}
|
||||
if (crib.length > 25) {
|
||||
// A crib longer than this will definitely cause the middle rotor to step somewhere
|
||||
// A shorter crib is preferable to reduce this chance, of course
|
||||
throw new OperationError("Crib is too long");
|
||||
}
|
||||
for (let i=0; i<crib.length; i++) {
|
||||
if (ciphertext[i] === crib[i]) {
|
||||
throw new OperationError(`Invalid crib: character ${ciphertext[i]} at pos ${i} in both ciphertext and crib`);
|
||||
}
|
||||
}
|
||||
this.ciphertext = ciphertext;
|
||||
this.crib = crib;
|
||||
this.initRotors(rotors);
|
||||
this.check = check;
|
||||
this.updateFn = update;
|
||||
|
||||
const [mostConnected, edges] = this.makeMenu();
|
||||
|
||||
// This is the bundle of wires corresponding to the 26 letters within each of the 26
|
||||
// possible nodes in the menu
|
||||
this.wires = new Array(26*26);
|
||||
|
||||
// These are the pseudo-Engima devices corresponding to each edge in the menu, and the
|
||||
// nodes in the menu they each connect to
|
||||
this.scramblers = new Array();
|
||||
for (let i=0; i<26; i++) {
|
||||
this.scramblers.push(new Array());
|
||||
}
|
||||
this.sharedScrambler = new SharedScrambler(this.baseRotors.slice(1), reflector);
|
||||
this.allScramblers = new Array();
|
||||
this.indicator = undefined;
|
||||
for (const edge of edges) {
|
||||
const cRotor = this.baseRotors[0].copy();
|
||||
const end1 = a2i(edge.node1.letter);
|
||||
const end2 = a2i(edge.node2.letter);
|
||||
const scrambler = new Scrambler(this.sharedScrambler, cRotor, edge.pos, end1, end2);
|
||||
if (edge.pos === 0) {
|
||||
this.indicator = scrambler;
|
||||
}
|
||||
this.scramblers[end1].push(scrambler);
|
||||
this.scramblers[end2].push(scrambler);
|
||||
this.allScramblers.push(scrambler);
|
||||
}
|
||||
// The Bombe uses a set of rotors to keep track of what settings it's testing. We cheat and
|
||||
// use one of the actual scramblers if there's one in the right position, but if not we'll
|
||||
// just create one.
|
||||
if (this.indicator === undefined) {
|
||||
this.indicator = new Scrambler(this.sharedScrambler, this.baseRotors[0].copy(), 0, undefined, undefined);
|
||||
this.allScramblers.push(this.indicator);
|
||||
}
|
||||
|
||||
this.testRegister = a2i(mostConnected.letter);
|
||||
// This is an arbitrary letter other than the most connected letter
|
||||
for (const edge of mostConnected.edges) {
|
||||
this.testInput = [this.testRegister, a2i(edge.getOther(mostConnected).letter)];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build Rotor objects from list of rotor wiring strings.
|
||||
* @param {string[]} rotors - List of rotor wiring strings
|
||||
*/
|
||||
initRotors(rotors) {
|
||||
// This is ordered from the Enigma fast rotor to the slow, so bottom to top for the Bombe
|
||||
this.baseRotors = [];
|
||||
for (const rstr of rotors) {
|
||||
const rotor = new CopyRotor(rstr, "", "A", "A");
|
||||
this.baseRotors.push(rotor);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the rotors and reflector in all components of this Bombe.
|
||||
* @param {string[]} rotors - List of rotor wiring strings
|
||||
* @param {Object} reflector - Reflector object
|
||||
*/
|
||||
changeRotors(rotors, reflector) {
|
||||
// At the end of the run, the rotors are all back in the same position they started
|
||||
this.initRotors(rotors);
|
||||
this.sharedScrambler.changeRotors(this.baseRotors.slice(1), reflector);
|
||||
for (const scrambler of this.allScramblers) {
|
||||
scrambler.changeRotor(this.baseRotors[0].copy());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If we have a way of sending status messages, do so.
|
||||
* @param {...*} msg - Message to send.
|
||||
*/
|
||||
update(...msg) {
|
||||
if (this.updateFn !== undefined) {
|
||||
this.updateFn(...msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive depth-first search on the menu graph.
|
||||
* This is used to a) isolate unconnected sub-graphs, and b) count the number of loops in each
|
||||
* of those graphs.
|
||||
* @param {Object} node - Node object to start the search from
|
||||
* @returns {[number, number, Object, number, Object[]} - loop count, node count, most connected
|
||||
* node, order of most connected node, list of edges in this sub-graph
|
||||
*/
|
||||
dfs(node) {
|
||||
let loops = 0;
|
||||
let nNodes = 1;
|
||||
let mostConnected = node;
|
||||
let nConnections = mostConnected.edges.size;
|
||||
let edges = new Set();
|
||||
node.visited = true;
|
||||
for (const edge of node.edges) {
|
||||
if (edge.visited) {
|
||||
// Already been here from the other end.
|
||||
continue;
|
||||
}
|
||||
edge.visited = true;
|
||||
edges.add(edge);
|
||||
const other = edge.getOther(node);
|
||||
if (other.visited) {
|
||||
// We have a loop, record that and continue
|
||||
loops += 1;
|
||||
continue;
|
||||
}
|
||||
// This is a newly visited node
|
||||
const [oLoops, oNNodes, oMostConnected, oNConnections, oEdges] = this.dfs(other);
|
||||
loops += oLoops;
|
||||
nNodes += oNNodes;
|
||||
edges = new Set([...edges, ...oEdges]);
|
||||
if (oNConnections > nConnections) {
|
||||
mostConnected = oMostConnected;
|
||||
nConnections = oNConnections;
|
||||
}
|
||||
}
|
||||
return [loops, nNodes, mostConnected, nConnections, edges];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a menu from the ciphertext and crib.
|
||||
* A menu is just a graph where letters in either the ciphertext or crib (Enigma is symmetric,
|
||||
* so there's no difference mathematically) are nodes and states of the Enigma machine itself
|
||||
* are the edges.
|
||||
* Additionally, we want a single connected graph, and of the subgraphs available, we want the
|
||||
* one with the most loops (since these generate feedback cycles which efficiently close off
|
||||
* disallowed states).
|
||||
* Finally, we want to identify the most connected node in that graph (as it's the best choice
|
||||
* of measurement point).
|
||||
* @returns [Object, Object[]] - the most connected node, and the list of edges in the subgraph
|
||||
*/
|
||||
makeMenu() {
|
||||
// First, we make a graph of all of the mappings given by the crib
|
||||
// Make all nodes first
|
||||
const nodes = new Map();
|
||||
for (const c of this.ciphertext + this.crib) {
|
||||
if (!nodes.has(c)) {
|
||||
const node = new Node(c);
|
||||
nodes.set(c, node);
|
||||
}
|
||||
}
|
||||
// Then all edges
|
||||
for (let i=0; i<this.crib.length; i++) {
|
||||
const a = this.crib[i];
|
||||
const b = this.ciphertext[i];
|
||||
new Edge(i, nodes.get(a), nodes.get(b));
|
||||
}
|
||||
// list of [loop_count, node_count, most_connected_node, connections_on_most_connected, edges]
|
||||
const graphs = [];
|
||||
// Then, for each unconnected subgraph, we count the number of loops and nodes
|
||||
for (const start of nodes.keys()) {
|
||||
if (nodes.get(start).visited) {
|
||||
continue;
|
||||
}
|
||||
const subgraph = this.dfs(nodes.get(start));
|
||||
graphs.push(subgraph);
|
||||
}
|
||||
// Return the subgraph with the most loops (ties broken by node count)
|
||||
graphs.sort((a, b) => {
|
||||
let result = b[0] - a[0];
|
||||
if (result === 0) {
|
||||
result = b[1] - a[1];
|
||||
}
|
||||
return result;
|
||||
});
|
||||
this.nLoops = graphs[0][0];
|
||||
return [graphs[0][2], graphs[0][4]];
|
||||
}
|
||||
|
||||
/**
|
||||
* Bombe electrical simulation. Energise a wire. For all connected wires (both via the diagonal
|
||||
* board and via the scramblers), energise them too, recursively.
|
||||
* @param {number} i - Bombe wire bundle
|
||||
* @param {number} j - Bombe stecker hypothesis wire within bundle
|
||||
*/
|
||||
energise(i, j) {
|
||||
const idx = 26*i + j;
|
||||
if (this.wires[idx]) {
|
||||
return;
|
||||
}
|
||||
this.wires[idx] = true;
|
||||
// Welchman's diagonal board: if A steckers to B, that implies B steckers to A. Handle
|
||||
// both.
|
||||
const idxPair = 26*j + i;
|
||||
this.wires[idxPair] = true;
|
||||
if (i === this.testRegister || j === this.testRegister) {
|
||||
this.energiseCount++;
|
||||
if (this.energiseCount === 26) {
|
||||
// no point continuing, bail out
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (let k=0; k<this.scramblers[i].length; k++) {
|
||||
const scrambler = this.scramblers[i][k];
|
||||
const out = scrambler.transform(j);
|
||||
const other = scrambler.getOtherEnd(i);
|
||||
// Lift the pre-check before the call, to save some function call overhead
|
||||
const otherIdx = 26*other + out;
|
||||
if (!this.wires[otherIdx]) {
|
||||
this.energise(other, out);
|
||||
if (this.energiseCount === 26) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (i === j) {
|
||||
return;
|
||||
}
|
||||
for (let k=0; k<this.scramblers[j].length; k++) {
|
||||
const scrambler = this.scramblers[j][k];
|
||||
const out = scrambler.transform(i);
|
||||
const other = scrambler.getOtherEnd(j);
|
||||
const otherIdx = 26*other + out;
|
||||
if (!this.wires[otherIdx]) {
|
||||
this.energise(other, out);
|
||||
if (this.energiseCount === 26) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trial decryption at the current setting.
|
||||
* Used after we get a stop.
|
||||
* This applies the detected stecker pair if we have one. It does not handle the other
|
||||
* steckering or stepping (which is why we limit it to 26 characters, since it's guaranteed to
|
||||
* be wrong after that anyway).
|
||||
* @param {string} stecker - Known stecker spec string.
|
||||
* @returns {string}
|
||||
*/
|
||||
tryDecrypt(stecker) {
|
||||
const fastRotor = this.indicator.rotor;
|
||||
const initialPos = fastRotor.pos;
|
||||
const res = [];
|
||||
const plugboard = new Plugboard(stecker);
|
||||
// The indicator scrambler starts in the right place for the beginning of the ciphertext.
|
||||
for (let i=0; i<Math.min(26, this.ciphertext.length); i++) {
|
||||
const t = this.indicator.transform(plugboard.transform(a2i(this.ciphertext[i])));
|
||||
res.push(i2a(plugboard.transform(t)));
|
||||
this.indicator.step(1);
|
||||
}
|
||||
fastRotor.pos = initialPos;
|
||||
return res.join("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a steckered pair, in sorted order to allow uniquing.
|
||||
* @param {number} a - A letter
|
||||
* @param {number} b - Its stecker pair
|
||||
* @returns {string}
|
||||
*/
|
||||
formatPair(a, b) {
|
||||
if (a < b) {
|
||||
return `${i2a(a)}${i2a(b)}`;
|
||||
}
|
||||
return `${i2a(b)}${i2a(a)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* The checking machine was used to manually verify Bombe stops. Using a device which was
|
||||
* effectively a non-stepping Enigma, the user would walk through each of the links in the
|
||||
* menu at the rotor positions determined by the Bombe. By starting with the stecker pair the
|
||||
* Bombe gives us, we find the stecker pair of each connected letter in the graph, and so on.
|
||||
* If a contradiction is reached, the stop is invalid. If not, we have most (but not
|
||||
* necessarily all) of the plugboard connections.
|
||||
* You will notice that this procedure is exactly the same as what the Bombe itself does, only
|
||||
* we start with an assumed good hypothesis and read out the stecker pair for every letter.
|
||||
* On the real hardware that wasn't practical, but fortunately we're not the real hardware, so
|
||||
* we don't need to implement the manual checking machine procedure.
|
||||
* @param {number} pair - The stecker pair of the test register.
|
||||
* @returns {string} - The empty string for invalid stops, or a plugboard configuration string
|
||||
* containing all known pairs.
|
||||
*/
|
||||
checkingMachine(pair) {
|
||||
if (pair !== this.testInput[1]) {
|
||||
// We have a new hypothesis for this stop - apply the new one.
|
||||
// De-energise the board
|
||||
for (let i=0; i<this.wires.length; i++) {
|
||||
this.wires[i] = false;
|
||||
}
|
||||
this.energiseCount = 0;
|
||||
// Re-energise with the corrected hypothesis
|
||||
this.energise(this.testRegister, pair);
|
||||
}
|
||||
|
||||
const results = new Set();
|
||||
results.add(this.formatPair(this.testRegister, pair));
|
||||
for (let i=0; i<26; i++) {
|
||||
let count = 0;
|
||||
let other;
|
||||
for (let j=0; j<26; j++) {
|
||||
if (this.wires[i*26 + j]) {
|
||||
count++;
|
||||
other = j;
|
||||
}
|
||||
}
|
||||
if (count > 1) {
|
||||
// This is an invalid stop.
|
||||
return "";
|
||||
} else if (count === 0) {
|
||||
// No information about steckering from this wire
|
||||
continue;
|
||||
}
|
||||
results.add(this.formatPair(i, other));
|
||||
}
|
||||
return [...results].join(" ");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if the Bombe has stopped. If so, process the stop.
|
||||
* @returns {(undefined|string[3])} - Undefined for no stop, or [rotor settings, plugboard settings, decryption preview]
|
||||
*/
|
||||
checkStop() {
|
||||
// Count the energised outputs
|
||||
const count = this.energiseCount;
|
||||
if (count === 26) {
|
||||
return undefined;
|
||||
}
|
||||
// If it's not all of them, we have a stop
|
||||
let steckerPair;
|
||||
// The Bombe tells us one stecker pair as well. The input wire and test register we
|
||||
// started with are hypothesised to be a stecker pair.
|
||||
if (count === 25) {
|
||||
// Our steckering hypothesis is wrong. Correct value is the un-energised wire.
|
||||
for (let j=0; j<26; j++) {
|
||||
if (!this.wires[26*this.testRegister + j]) {
|
||||
steckerPair = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (count === 1) {
|
||||
// This means our hypothesis for the steckering is correct.
|
||||
steckerPair = this.testInput[1];
|
||||
} else {
|
||||
// This was known as a "boxing stop" - we have a stop but not a single hypothesis.
|
||||
// If this happens a lot it implies the menu isn't good enough.
|
||||
// If we have the checking machine enabled, we're going to just check each wire in
|
||||
// turn. If we get 0 or 1 hit, great.
|
||||
// If we get multiple hits, or the checking machine is off, the user will just have to
|
||||
// deal with it.
|
||||
if (!this.check) {
|
||||
// We can't draw any conclusions about the steckering (one could maybe suggest
|
||||
// options in some cases, but too hard to present clearly).
|
||||
return [this.indicator.getPos(), "??", this.tryDecrypt("")];
|
||||
}
|
||||
let stecker = undefined;
|
||||
for (let i = 0; i < 26; i++) {
|
||||
const newStecker = this.checkingMachine(i);
|
||||
if (newStecker !== "") {
|
||||
if (stecker !== undefined) {
|
||||
// Multiple hypotheses can't be ruled out.
|
||||
return [this.indicator.getPos(), "??", this.tryDecrypt("")];
|
||||
}
|
||||
stecker = newStecker;
|
||||
}
|
||||
}
|
||||
if (stecker === undefined) {
|
||||
// Checking machine ruled all possibilities out.
|
||||
return undefined;
|
||||
}
|
||||
// If we got here, there was just one possibility allowed by the checking machine. Success.
|
||||
return [this.indicator.getPos(), stecker, this.tryDecrypt(stecker)];
|
||||
}
|
||||
let stecker;
|
||||
if (this.check) {
|
||||
stecker = this.checkingMachine(steckerPair);
|
||||
if (stecker === "") {
|
||||
// Invalid stop - don't count it, don't return it
|
||||
return undefined;
|
||||
}
|
||||
} else {
|
||||
stecker = `${i2a(this.testRegister)}${i2a(steckerPair)}`;
|
||||
}
|
||||
const testDecrypt = this.tryDecrypt(stecker);
|
||||
return [this.indicator.getPos(), stecker, testDecrypt];
|
||||
}
|
||||
|
||||
/**
|
||||
* Having set up the Bombe, do the actual attack run. This tries every possible rotor setting
|
||||
* and attempts to logically invalidate them. If it can't, it's added to the list of candidate
|
||||
* solutions.
|
||||
* @returns {string[][3]} - list of 3-tuples of candidate rotor setting, plugboard settings, and decryption preview
|
||||
*/
|
||||
run() {
|
||||
let stops = 0;
|
||||
const result = [];
|
||||
// For each possible rotor setting
|
||||
const nChecks = Math.pow(26, this.baseRotors.length);
|
||||
for (let i=1; i<=nChecks; i++) {
|
||||
// Benchmarking suggests this is faster than using .fill()
|
||||
for (let i=0; i<this.wires.length; i++) {
|
||||
this.wires[i] = false;
|
||||
}
|
||||
this.energiseCount = 0;
|
||||
// Energise the test input, follow the current through each scrambler
|
||||
// (and the diagonal board)
|
||||
this.energise(...this.testInput);
|
||||
|
||||
const stop = this.checkStop();
|
||||
if (stop !== undefined) {
|
||||
stops++;
|
||||
result.push(stop);
|
||||
}
|
||||
// Step all the scramblers
|
||||
// This loop counts how many rotors have reached their starting position (meaning the
|
||||
// next one needs to step as well)
|
||||
let n = 1;
|
||||
for (let j=1; j<this.baseRotors.length; j++) {
|
||||
if ((i % Math.pow(26, j)) === 0) {
|
||||
n++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (n > 1) {
|
||||
this.sharedScrambler.step(n);
|
||||
}
|
||||
for (const scrambler of this.allScramblers) {
|
||||
scrambler.step();
|
||||
}
|
||||
// Send status messages at what seems to be a reasonably sensible frequency
|
||||
// (note this won't be triggered on 3-rotor runs - they run fast enough it doesn't seem necessary)
|
||||
if (n > 3) {
|
||||
this.update(this.nLoops, stops, i/nChecks);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
15
src/core/lib/Braille.mjs
Normal file
15
src/core/lib/Braille.mjs
Normal file
|
@ -0,0 +1,15 @@
|
|||
/**
|
||||
* Braille resources.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Braille lookup table.
|
||||
*/
|
||||
export const BRAILLE_LOOKUP = {
|
||||
ascii: " A1B'K2L@CIF/MSP\"E3H9O6R^DJG>NTQ,*5<-U8V.%[$+X!&;:4\\0Z7(_?W]#Y)=",
|
||||
dot6: "⠀⠁⠂⠃⠄⠅⠆⠇⠈⠉⠊⠋⠌⠍⠎⠏⠐⠑⠒⠓⠔⠕⠖⠗⠘⠙⠚⠛⠜⠝⠞⠟⠠⠡⠢⠣⠤⠥⠦⠧⠨⠩⠪⠫⠬⠭⠮⠯⠰⠱⠲⠳⠴⠵⠶⠷⠸⠹⠺⠻⠼⠽⠾⠿"
|
||||
};
|
204
src/core/lib/CanvasComponents.mjs
Normal file
204
src/core/lib/CanvasComponents.mjs
Normal file
|
@ -0,0 +1,204 @@
|
|||
/**
|
||||
* Various components for drawing diagrams on an HTML5 canvas.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Draws a line from one point to another
|
||||
*
|
||||
* @param ctx
|
||||
* @param startX
|
||||
* @param startY
|
||||
* @param endX
|
||||
* @param endY
|
||||
*/
|
||||
export function drawLine(ctx, startX, startY, endX, endY) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(startX, startY);
|
||||
ctx.lineTo(endX, endY);
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a bar chart on the canvas.
|
||||
*
|
||||
* @param canvas
|
||||
* @param scores
|
||||
* @param xAxisLabel
|
||||
* @param yAxisLabel
|
||||
* @param numXLabels
|
||||
* @param numYLabels
|
||||
* @param fontSize
|
||||
*/
|
||||
export function drawBarChart(canvas, scores, xAxisLabel, yAxisLabel, numXLabels, numYLabels, fontSize) {
|
||||
fontSize = fontSize || 15;
|
||||
if (!numXLabels || numXLabels > Math.round(canvas.width / 50)) {
|
||||
numXLabels = Math.round(canvas.width / 50);
|
||||
}
|
||||
if (!numYLabels || numYLabels > Math.round(canvas.width / 50)) {
|
||||
numYLabels = Math.round(canvas.height / 50);
|
||||
}
|
||||
|
||||
// Graph properties
|
||||
const ctx = canvas.getContext("2d"),
|
||||
leftPadding = canvas.width * 0.08,
|
||||
rightPadding = canvas.width * 0.03,
|
||||
topPadding = canvas.height * 0.08,
|
||||
bottomPadding = canvas.height * 0.2,
|
||||
graphHeight = canvas.height - topPadding - bottomPadding,
|
||||
graphWidth = canvas.width - leftPadding - rightPadding,
|
||||
base = topPadding + graphHeight,
|
||||
ceil = topPadding;
|
||||
|
||||
ctx.font = fontSize + "px Arial";
|
||||
|
||||
// Draw axis
|
||||
ctx.lineWidth = "1.0";
|
||||
ctx.strokeStyle = "#444";
|
||||
drawLine(ctx, leftPadding, base, graphWidth + leftPadding, base); // x
|
||||
drawLine(ctx, leftPadding, base, leftPadding, ceil); // y
|
||||
|
||||
// Bar properties
|
||||
const barPadding = graphWidth * 0.003,
|
||||
barWidth = (graphWidth - (barPadding * scores.length)) / scores.length,
|
||||
max = Math.max.apply(Math, scores);
|
||||
let currX = leftPadding + barPadding;
|
||||
|
||||
// Draw bars
|
||||
ctx.fillStyle = "green";
|
||||
for (let i = 0; i < scores.length; i++) {
|
||||
const h = scores[i] / max * graphHeight;
|
||||
ctx.fillRect(currX, base - h, barWidth, h);
|
||||
currX += barWidth + barPadding;
|
||||
}
|
||||
|
||||
// Mark x axis
|
||||
ctx.fillStyle = "black";
|
||||
ctx.textAlign = "center";
|
||||
currX = leftPadding + barPadding;
|
||||
if (numXLabels >= scores.length) {
|
||||
// Mark every score
|
||||
for (let i = 0; i <= scores.length; i++) {
|
||||
ctx.fillText(i, currX, base + (bottomPadding * 0.3));
|
||||
currX += barWidth + barPadding;
|
||||
}
|
||||
} else {
|
||||
// Mark some scores
|
||||
for (let i = 0; i <= numXLabels; i++) {
|
||||
const val = Math.ceil((scores.length / numXLabels) * i);
|
||||
currX = (graphWidth / numXLabels) * i + leftPadding;
|
||||
ctx.fillText(val, currX, base + (bottomPadding * 0.3));
|
||||
}
|
||||
}
|
||||
|
||||
// Mark y axis
|
||||
ctx.textAlign = "right";
|
||||
let currY;
|
||||
if (numYLabels >= max) {
|
||||
// Mark every increment
|
||||
for (let i = 0; i <= max; i++) {
|
||||
currY = base - (i / max * graphHeight) + fontSize / 3;
|
||||
ctx.fillText(i, leftPadding * 0.8, currY);
|
||||
}
|
||||
} else {
|
||||
// Mark some increments
|
||||
for (let i = 0; i <= numYLabels; i++) {
|
||||
const val = Math.ceil((max / numYLabels) * i);
|
||||
currY = base - (val / max * graphHeight) + fontSize / 3;
|
||||
ctx.fillText(val, leftPadding * 0.8, currY);
|
||||
}
|
||||
}
|
||||
|
||||
// Label x axis
|
||||
if (xAxisLabel) {
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillText(xAxisLabel, graphWidth / 2 + leftPadding, base + bottomPadding * 0.8);
|
||||
}
|
||||
|
||||
// Label y axis
|
||||
if (yAxisLabel) {
|
||||
ctx.save();
|
||||
const x = leftPadding * 0.3,
|
||||
y = graphHeight / 2 + topPadding;
|
||||
ctx.translate(x, y);
|
||||
ctx.rotate(-Math.PI / 2);
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillText(yAxisLabel, 0, 0);
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a scale bar on the canvas.
|
||||
*
|
||||
* @param canvas
|
||||
* @param score
|
||||
* @param max
|
||||
* @param markings
|
||||
*/
|
||||
export function drawScaleBar(canvas, score, max, markings) {
|
||||
// Bar properties
|
||||
const ctx = canvas.getContext("2d"),
|
||||
leftPadding = canvas.width * 0.01,
|
||||
rightPadding = canvas.width * 0.01,
|
||||
topPadding = canvas.height * 0.1,
|
||||
bottomPadding = canvas.height * 0.35,
|
||||
barHeight = canvas.height - topPadding - bottomPadding,
|
||||
barWidth = canvas.width - leftPadding - rightPadding;
|
||||
|
||||
// Scale properties
|
||||
const proportion = score / max;
|
||||
|
||||
// Draw bar outline
|
||||
ctx.strokeRect(leftPadding, topPadding, barWidth, barHeight);
|
||||
|
||||
// Shade in up to proportion
|
||||
const grad = ctx.createLinearGradient(leftPadding, 0, barWidth + leftPadding, 0);
|
||||
grad.addColorStop(0, "green");
|
||||
grad.addColorStop(0.5, "gold");
|
||||
grad.addColorStop(1, "red");
|
||||
ctx.fillStyle = grad;
|
||||
ctx.fillRect(leftPadding, topPadding, barWidth * proportion, barHeight);
|
||||
|
||||
// Add markings
|
||||
let x0, y0, x1, y1;
|
||||
ctx.fillStyle = "black";
|
||||
ctx.textAlign = "center";
|
||||
ctx.font = "13px Arial";
|
||||
for (let i = 0; i < markings.length; i++) {
|
||||
// Draw min line down
|
||||
x0 = barWidth / max * markings[i].min + leftPadding;
|
||||
y0 = topPadding + barHeight + (bottomPadding * 0.1);
|
||||
x1 = x0;
|
||||
y1 = topPadding + barHeight + (bottomPadding * 0.3);
|
||||
drawLine(ctx, x0, y0, x1, y1);
|
||||
|
||||
// Draw max line down
|
||||
x0 = barWidth / max * markings[i].max + leftPadding;
|
||||
x1 = x0;
|
||||
drawLine(ctx, x0, y0, x1, y1);
|
||||
|
||||
// Join min and max lines
|
||||
x0 = barWidth / max * markings[i].min + leftPadding;
|
||||
y0 = topPadding + barHeight + (bottomPadding * 0.3);
|
||||
x1 = barWidth / max * markings[i].max + leftPadding;
|
||||
y1 = y0;
|
||||
drawLine(ctx, x0, y0, x1, y1);
|
||||
|
||||
// Add label
|
||||
if (markings[i].max >= max * 0.9) {
|
||||
ctx.textAlign = "right";
|
||||
x0 = x1;
|
||||
} else if (markings[i].max <= max * 0.1) {
|
||||
ctx.textAlign = "left";
|
||||
} else {
|
||||
x0 = x0 + (x1 - x0) / 2;
|
||||
}
|
||||
y0 = topPadding + barHeight + (bottomPadding * 0.8);
|
||||
ctx.fillText(markings[i].label, x0, y0);
|
||||
}
|
||||
}
|
179
src/core/lib/Charts.mjs
Normal file
179
src/core/lib/Charts.mjs
Normal file
|
@ -0,0 +1,179 @@
|
|||
/**
|
||||
* @author tlwr [toby@toby.codes]
|
||||
* @author Matt C [me@mitt.dev]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
export const RECORD_DELIMITER_OPTIONS = ["Line feed", "CRLF"];
|
||||
|
||||
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
export const FIELD_DELIMITER_OPTIONS = ["Space", "Comma", "Semi-colon", "Colon", "Tab"];
|
||||
|
||||
|
||||
/**
|
||||
* Default from colour
|
||||
*
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
export const COLOURS = {
|
||||
min: "white",
|
||||
max: "black"
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets values from input for a plot.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {string} recordDelimiter
|
||||
* @param {string} fieldDelimiter
|
||||
* @param {boolean} columnHeadingsAreIncluded - whether we should skip the first record
|
||||
* @param {number} length
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
export function getValues(input, recordDelimiter, fieldDelimiter, columnHeadingsAreIncluded, length) {
|
||||
let headings;
|
||||
const values = [];
|
||||
|
||||
input
|
||||
.split(recordDelimiter)
|
||||
.forEach((row, rowIndex) => {
|
||||
const split = row.split(fieldDelimiter);
|
||||
if (split.length !== length) throw new OperationError(`Each row must have length ${length}.`);
|
||||
|
||||
if (columnHeadingsAreIncluded && rowIndex === 0) {
|
||||
headings = split;
|
||||
} else {
|
||||
values.push(split);
|
||||
}
|
||||
});
|
||||
return { headings, values };
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets values from input for a scatter plot.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {string} recordDelimiter
|
||||
* @param {string} fieldDelimiter
|
||||
* @param {boolean} columnHeadingsAreIncluded - whether we should skip the first record
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
export function getScatterValues(input, recordDelimiter, fieldDelimiter, columnHeadingsAreIncluded) {
|
||||
let { headings, values } = getValues(
|
||||
input,
|
||||
recordDelimiter,
|
||||
fieldDelimiter,
|
||||
columnHeadingsAreIncluded,
|
||||
2
|
||||
);
|
||||
|
||||
if (headings) {
|
||||
headings = {x: headings[0], y: headings[1]};
|
||||
}
|
||||
|
||||
values = values.map(row => {
|
||||
const x = parseFloat(row[0]),
|
||||
y = parseFloat(row[1]);
|
||||
|
||||
if (Number.isNaN(x)) throw new OperationError("Values must be numbers in base 10.");
|
||||
if (Number.isNaN(y)) throw new OperationError("Values must be numbers in base 10.");
|
||||
|
||||
return [x, y];
|
||||
});
|
||||
|
||||
return { headings, values };
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets values from input for a scatter plot with colour from the third column.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {string} recordDelimiter
|
||||
* @param {string} fieldDelimiter
|
||||
* @param {boolean} columnHeadingsAreIncluded - whether we should skip the first record
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
export function getScatterValuesWithColour(input, recordDelimiter, fieldDelimiter, columnHeadingsAreIncluded) {
|
||||
let { headings, values } = getValues(
|
||||
input,
|
||||
recordDelimiter, fieldDelimiter,
|
||||
columnHeadingsAreIncluded,
|
||||
3
|
||||
);
|
||||
|
||||
if (headings) {
|
||||
headings = {x: headings[0], y: headings[1]};
|
||||
}
|
||||
|
||||
values = values.map(row => {
|
||||
const x = parseFloat(row[0]),
|
||||
y = parseFloat(row[1]),
|
||||
colour = row[2];
|
||||
|
||||
if (Number.isNaN(x)) throw new OperationError("Values must be numbers in base 10.");
|
||||
if (Number.isNaN(y)) throw new OperationError("Values must be numbers in base 10.");
|
||||
|
||||
return [x, y, Utils.escapeHtml(colour)];
|
||||
});
|
||||
|
||||
return { headings, values };
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets values from input for a time series plot.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {string} recordDelimiter
|
||||
* @param {string} fieldDelimiter
|
||||
* @param {boolean} columnHeadingsAreIncluded - whether we should skip the first record
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
export function getSeriesValues(input, recordDelimiter, fieldDelimiter, columnHeadingsAreIncluded) {
|
||||
const { values } = getValues(
|
||||
input,
|
||||
recordDelimiter, fieldDelimiter,
|
||||
false,
|
||||
3
|
||||
);
|
||||
|
||||
let xValues = new Set();
|
||||
const series = {};
|
||||
|
||||
values.forEach(row => {
|
||||
const serie = row[0],
|
||||
xVal = row[1],
|
||||
val = parseFloat(row[2]);
|
||||
|
||||
if (Number.isNaN(val)) throw new OperationError("Values must be numbers in base 10.");
|
||||
|
||||
xValues.add(xVal);
|
||||
if (typeof series[serie] === "undefined") series[serie] = {};
|
||||
series[serie][xVal] = val;
|
||||
});
|
||||
|
||||
xValues = new Array(...xValues);
|
||||
|
||||
const seriesList = [];
|
||||
for (const seriesName in series) {
|
||||
const serie = series[seriesName];
|
||||
seriesList.push({name: seriesName, data: serie});
|
||||
}
|
||||
|
||||
return { xValues, series: seriesList };
|
||||
}
|
231
src/core/lib/ChrEnc.mjs
Normal file
231
src/core/lib/ChrEnc.mjs
Normal file
|
@ -0,0 +1,231 @@
|
|||
/**
|
||||
* Character encoding resources.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import cptable from "codepage";
|
||||
|
||||
/**
|
||||
* Character encoding format mappings.
|
||||
*/
|
||||
export const CHR_ENC_CODE_PAGES = {
|
||||
"UTF-8 (65001)": 65001,
|
||||
"UTF-7 (65000)": 65000,
|
||||
"UTF-16LE (1200)": 1200,
|
||||
"UTF-16BE (1201)": 1201,
|
||||
"UTF-32LE (12000)": 12000,
|
||||
"UTF-32BE (12001)": 12001,
|
||||
"IBM EBCDIC International (500)": 500,
|
||||
"IBM EBCDIC US-Canada (37)": 37,
|
||||
"IBM EBCDIC Multilingual/ROECE (Latin 2) (870)": 870,
|
||||
"IBM EBCDIC Greek Modern (875)": 875,
|
||||
"IBM EBCDIC French (1010)": 1010,
|
||||
"IBM EBCDIC Turkish (Latin 5) (1026)": 1026,
|
||||
"IBM EBCDIC Latin 1/Open System (1047)": 1047,
|
||||
"IBM EBCDIC Lao (1132/1133/1341)": 1132,
|
||||
"IBM EBCDIC US-Canada (037 + Euro symbol) (1140)": 1140,
|
||||
"IBM EBCDIC Germany (20273 + Euro symbol) (1141)": 1141,
|
||||
"IBM EBCDIC Denmark-Norway (20277 + Euro symbol) (1142)": 1142,
|
||||
"IBM EBCDIC Finland-Sweden (20278 + Euro symbol) (1143)": 1143,
|
||||
"IBM EBCDIC Italy (20280 + Euro symbol) (1144)": 1144,
|
||||
"IBM EBCDIC Latin America-Spain (20284 + Euro symbol) (1145)": 1145,
|
||||
"IBM EBCDIC United Kingdom (20285 + Euro symbol) (1146)": 1146,
|
||||
"IBM EBCDIC France (20297 + Euro symbol) (1147)": 1147,
|
||||
"IBM EBCDIC International (500 + Euro symbol) (1148)": 1148,
|
||||
"IBM EBCDIC Icelandic (20871 + Euro symbol) (1149)": 1149,
|
||||
"IBM EBCDIC Germany (20273)": 20273,
|
||||
"IBM EBCDIC Denmark-Norway (20277)": 20277,
|
||||
"IBM EBCDIC Finland-Sweden (20278)": 20278,
|
||||
"IBM EBCDIC Italy (20280)": 20280,
|
||||
"IBM EBCDIC Latin America-Spain (20284)": 20284,
|
||||
"IBM EBCDIC United Kingdom (20285)": 20285,
|
||||
"IBM EBCDIC Japanese Katakana Extended (20290)": 20290,
|
||||
"IBM EBCDIC France (20297)": 20297,
|
||||
"IBM EBCDIC Arabic (20420)": 20420,
|
||||
"IBM EBCDIC Greek (20423)": 20423,
|
||||
"IBM EBCDIC Hebrew (20424)": 20424,
|
||||
"IBM EBCDIC Korean Extended (20833)": 20833,
|
||||
"IBM EBCDIC Thai (20838)": 20838,
|
||||
"IBM EBCDIC Icelandic (20871)": 20871,
|
||||
"IBM EBCDIC Cyrillic Russian (20880)": 20880,
|
||||
"IBM EBCDIC Turkish (20905)": 20905,
|
||||
"IBM EBCDIC Latin 1/Open System (1047 + Euro symbol) (20924)": 20924,
|
||||
"IBM EBCDIC Cyrillic Serbian-Bulgarian (21025)": 21025,
|
||||
"OEM United States (437)": 437,
|
||||
"OEM Greek (formerly 437G); Greek (DOS) (737)": 737,
|
||||
"OEM Baltic; Baltic (DOS) (775)": 775,
|
||||
"OEM Russian; Cyrillic + Euro symbol (808)": 808,
|
||||
"OEM Multilingual Latin 1; Western European (DOS) (850)": 850,
|
||||
"OEM Latin 2; Central European (DOS) (852)": 852,
|
||||
"OEM Cyrillic (primarily Russian) (855)": 855,
|
||||
"OEM Turkish; Turkish (DOS) (857)": 857,
|
||||
"OEM Multilingual Latin 1 + Euro symbol (858)": 858,
|
||||
"OEM Portuguese; Portuguese (DOS) (860)": 860,
|
||||
"OEM Icelandic; Icelandic (DOS) (861)": 861,
|
||||
"OEM Hebrew; Hebrew (DOS) (862)": 862,
|
||||
"OEM French Canadian; French Canadian (DOS) (863)": 863,
|
||||
"OEM Arabic; Arabic (864) (864)": 864,
|
||||
"OEM Nordic; Nordic (DOS) (865)": 865,
|
||||
"OEM Russian; Cyrillic (DOS) (866)": 866,
|
||||
"OEM Modern Greek; Greek, Modern (DOS) (869)": 869,
|
||||
"OEM Cyrillic (primarily Russian) + Euro Symbol (872)": 872,
|
||||
"Windows-874 Thai (874)": 874,
|
||||
"Windows-1250 Central European (1250)": 1250,
|
||||
"Windows-1251 Cyrillic (1251)": 1251,
|
||||
"Windows-1252 Latin (1252)": 1252,
|
||||
"Windows-1253 Greek (1253)": 1253,
|
||||
"Windows-1254 Turkish (1254)": 1254,
|
||||
"Windows-1255 Hebrew (1255)": 1255,
|
||||
"Windows-1256 Arabic (1256)": 1256,
|
||||
"Windows-1257 Baltic (1257)": 1257,
|
||||
"Windows-1258 Vietnam (1258)": 1258,
|
||||
"ISO-8859-1 Latin 1 Western European (28591)": 28591,
|
||||
"ISO-8859-2 Latin 2 Central European (28592)": 28592,
|
||||
"ISO-8859-3 Latin 3 South European (28593)": 28593,
|
||||
"ISO-8859-4 Latin 4 North European (28594)": 28594,
|
||||
"ISO-8859-5 Latin/Cyrillic (28595)": 28595,
|
||||
"ISO-8859-6 Latin/Arabic (28596)": 28596,
|
||||
"ISO-8859-7 Latin/Greek (28597)": 28597,
|
||||
"ISO-8859-8 Latin/Hebrew (28598)": 28598,
|
||||
"ISO 8859-8 Hebrew (ISO-Logical) (38598)": 38598,
|
||||
"ISO-8859-9 Latin 5 Turkish (28599)": 28599,
|
||||
"ISO-8859-10 Latin 6 Nordic (28600)": 28600,
|
||||
"ISO-8859-11 Latin/Thai (28601)": 28601,
|
||||
"ISO-8859-13 Latin 7 Baltic Rim (28603)": 28603,
|
||||
"ISO-8859-14 Latin 8 Celtic (28604)": 28604,
|
||||
"ISO-8859-15 Latin 9 (28605)": 28605,
|
||||
"ISO-8859-16 Latin 10 (28606)": 28606,
|
||||
"ISO 2022 JIS Japanese with no halfwidth Katakana (50220)": 50220,
|
||||
"ISO 2022 JIS Japanese with halfwidth Katakana (50221)": 50221,
|
||||
"ISO 2022 Japanese JIS X 0201-1989 (1 byte Kana-SO/SI) (50222)": 50222,
|
||||
"ISO 2022 Korean (50225)": 50225,
|
||||
"ISO 2022 Simplified Chinese (50227)": 50227,
|
||||
"ISO 6937 Non-Spacing Accent (20269)": 20269,
|
||||
"EUC Japanese (51932)": 51932,
|
||||
"EUC Simplified Chinese (51936)": 51936,
|
||||
"EUC Korean (51949)": 51949,
|
||||
"ISCII Devanagari (57002)": 57002,
|
||||
"ISCII Bengali (57003)": 57003,
|
||||
"ISCII Tamil (57004)": 57004,
|
||||
"ISCII Telugu (57005)": 57005,
|
||||
"ISCII Assamese (57006)": 57006,
|
||||
"ISCII Oriya (57007)": 57007,
|
||||
"ISCII Kannada (57008)": 57008,
|
||||
"ISCII Malayalam (57009)": 57009,
|
||||
"ISCII Gujarati (57010)": 57010,
|
||||
"ISCII Punjabi (57011)": 57011,
|
||||
"Japanese Shift-JIS (932)": 932,
|
||||
"Simplified Chinese GBK (936)": 936,
|
||||
"Korean (949)": 949,
|
||||
"Traditional Chinese Big5 (950)": 950,
|
||||
"US-ASCII (7-bit) (20127)": 20127,
|
||||
"Simplified Chinese GB2312 (20936)": 20936,
|
||||
"KOI8-R Russian Cyrillic (20866)": 20866,
|
||||
"KOI8-U Ukrainian Cyrillic (21866)": 21866,
|
||||
"Mazovia (Polish) MS-DOS (620)": 620,
|
||||
"Arabic (ASMO 708) (708)": 708,
|
||||
"Arabic (Transparent ASMO); Arabic (DOS) (720)": 720,
|
||||
"Kamenický (Czech) MS-DOS (895)": 895,
|
||||
"Korean (Johab) (1361)": 1361,
|
||||
"MAC Roman (10000)": 10000,
|
||||
"Japanese (Mac) (10001)": 10001,
|
||||
"MAC Traditional Chinese (Big5) (10002)": 10002,
|
||||
"Korean (Mac) (10003)": 10003,
|
||||
"Arabic (Mac) (10004)": 10004,
|
||||
"Hebrew (Mac) (10005)": 10005,
|
||||
"Greek (Mac) (10006)": 10006,
|
||||
"Cyrillic (Mac) (10007)": 10007,
|
||||
"MAC Simplified Chinese (GB 2312) (10008)": 10008,
|
||||
"Romanian (Mac) (10010)": 10010,
|
||||
"Ukrainian (Mac) (10017)": 10017,
|
||||
"Thai (Mac) (10021)": 10021,
|
||||
"MAC Latin 2 (Central European) (10029)": 10029,
|
||||
"Icelandic (Mac) (10079)": 10079,
|
||||
"Turkish (Mac) (10081)": 10081,
|
||||
"Croatian (Mac) (10082)": 10082,
|
||||
"CNS Taiwan (Chinese Traditional) (20000)": 20000,
|
||||
"TCA Taiwan (20001)": 20001,
|
||||
"ETEN Taiwan (Chinese Traditional) (20002)": 20002,
|
||||
"IBM5550 Taiwan (20003)": 20003,
|
||||
"TeleText Taiwan (20004)": 20004,
|
||||
"Wang Taiwan (20005)": 20005,
|
||||
"Western European IA5 (IRV International Alphabet 5) (20105)": 20105,
|
||||
"IA5 German (7-bit) (20106)": 20106,
|
||||
"IA5 Swedish (7-bit) (20107)": 20107,
|
||||
"IA5 Norwegian (7-bit) (20108)": 20108,
|
||||
"T.61 (20261)": 20261,
|
||||
"Japanese (JIS 0208-1990 and 0212-1990) (20932)": 20932,
|
||||
"Korean Wansung (20949)": 20949,
|
||||
"Extended/Ext Alpha Lowercase (21027)": 21027,
|
||||
"Europa 3 (29001)": 29001,
|
||||
"Atari ST/TT (47451)": 47451,
|
||||
"HZ-GB2312 Simplified Chinese (52936)": 52936,
|
||||
"Simplified Chinese GB18030 (54936)": 54936,
|
||||
};
|
||||
|
||||
|
||||
export const CHR_ENC_SIMPLE_LOOKUP = {};
|
||||
export const CHR_ENC_SIMPLE_REVERSE_LOOKUP = {};
|
||||
|
||||
for (const name in CHR_ENC_CODE_PAGES) {
|
||||
const simpleName = name.match(/(^.+)\([\d/]+\)$/)[1];
|
||||
|
||||
CHR_ENC_SIMPLE_LOOKUP[simpleName] = CHR_ENC_CODE_PAGES[name];
|
||||
CHR_ENC_SIMPLE_REVERSE_LOOKUP[CHR_ENC_CODE_PAGES[name]] = simpleName;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the width of the character set for the given codepage.
|
||||
* For example, UTF-8 is a Single Byte Character Set, whereas
|
||||
* UTF-16 is a Double Byte Character Set.
|
||||
*
|
||||
* @param {number} page - The codepage number
|
||||
* @returns {number}
|
||||
*/
|
||||
export function chrEncWidth(page) {
|
||||
if (typeof page !== "number") return 0;
|
||||
|
||||
// Raw Bytes have a width of 1
|
||||
if (page === 0) return 1;
|
||||
|
||||
const pageStr = page.toString();
|
||||
// Confirm this page is legitimate
|
||||
if (!Object.prototype.hasOwnProperty.call(CHR_ENC_SIMPLE_REVERSE_LOOKUP, pageStr))
|
||||
return 0;
|
||||
|
||||
// Statically defined code pages
|
||||
if (Object.prototype.hasOwnProperty.call(cptable, pageStr))
|
||||
return cptable[pageStr].dec.length > 256 ? 2 : 1;
|
||||
|
||||
// Cached code pages
|
||||
if (cptable.utils.cache.sbcs.includes(pageStr))
|
||||
return 1;
|
||||
if (cptable.utils.cache.dbcs.includes(pageStr))
|
||||
return 2;
|
||||
|
||||
// Dynamically generated code pages
|
||||
if (Object.prototype.hasOwnProperty.call(cptable.utils.magic, pageStr)) {
|
||||
// Generate a single character and measure it
|
||||
const a = cptable.utils.encode(page, "a");
|
||||
return a.length;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unicode Normalisation Forms
|
||||
*
|
||||
* @author Matthieu [m@tthieu.xyz]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Character encoding format mappings.
|
||||
*/
|
||||
export const UNICODE_NORMALISATION_FORMS = ["NFD", "NFC", "NFKD", "NFKC"];
|
34
src/core/lib/CipherSaber2.mjs
Normal file
34
src/core/lib/CipherSaber2.mjs
Normal file
|
@ -0,0 +1,34 @@
|
|||
/**
|
||||
* @author n1073645 [n1073645@gmail.com]
|
||||
* @copyright Crown Copyright 2020
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
export function encode(tempIVP, key, rounds, input) {
|
||||
const ivp = new Uint8Array(key.concat(tempIVP));
|
||||
const state = new Array(256).fill(0);
|
||||
let j = 0, i = 0;
|
||||
const result = [];
|
||||
|
||||
// Mixing states based off of IV.
|
||||
for (let i = 0; i < 256; i++)
|
||||
state[i] = i;
|
||||
const ivpLength = ivp.length;
|
||||
for (let r = 0; r < rounds; r ++) {
|
||||
for (let k = 0; k < 256; k++) {
|
||||
j = (j + state[k] + ivp[k % ivpLength]) % 256;
|
||||
[state[k], state[j]] = [state[j], state[k]];
|
||||
}
|
||||
}
|
||||
j = 0;
|
||||
i = 0;
|
||||
|
||||
// XOR cipher with key.
|
||||
for (let x = 0; x < input.length; x++) {
|
||||
i = (++i) % 256;
|
||||
j = (j + state[i]) % 256;
|
||||
[state[i], state[j]] = [state[j], state[i]];
|
||||
const n = (state[i] + state[j]) % 256;
|
||||
result.push(state[n] ^ input[x]);
|
||||
}
|
||||
return result;
|
||||
}
|
82
src/core/lib/Ciphers.mjs
Normal file
82
src/core/lib/Ciphers.mjs
Normal file
|
@ -0,0 +1,82 @@
|
|||
/**
|
||||
* Cipher functions.
|
||||
*
|
||||
* @author Matt C [matt@artemisbot.uk]
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
*
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*
|
||||
*/
|
||||
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import CryptoJS from "crypto-js";
|
||||
|
||||
/**
|
||||
* Affine Cipher Encode operation.
|
||||
*
|
||||
* @author Matt C [matt@artemisbot.uk]
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
export function affineEncode(input, args) {
|
||||
const alphabet = "abcdefghijklmnopqrstuvwxyz",
|
||||
a = args[0],
|
||||
b = args[1];
|
||||
let output = "";
|
||||
|
||||
if (!/^\+?(0|[1-9]\d*)$/.test(a) || !/^\+?(0|[1-9]\d*)$/.test(b)) {
|
||||
throw new OperationError("The values of a and b can only be integers.");
|
||||
}
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
if (alphabet.indexOf(input[i]) >= 0) {
|
||||
// Uses the affine function ax+b % m = y (where m is length of the alphabet)
|
||||
output += alphabet[((a * alphabet.indexOf(input[i])) + b) % 26];
|
||||
} else if (alphabet.indexOf(input[i].toLowerCase()) >= 0) {
|
||||
// Same as above, accounting for uppercase
|
||||
output += alphabet[((a * alphabet.indexOf(input[i].toLowerCase())) + b) % 26].toUpperCase();
|
||||
} else {
|
||||
// Non-alphabetic characters
|
||||
output += input[i];
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a polybius square for the given keyword
|
||||
*
|
||||
* @private
|
||||
* @author Matt C [matt@artemisbot.uk]
|
||||
* @param {string} keyword - Must be upper case
|
||||
* @returns {string}
|
||||
*/
|
||||
export function genPolybiusSquare (keyword) {
|
||||
const alpha = "ABCDEFGHIKLMNOPQRSTUVWXYZ",
|
||||
polArray = `${keyword}${alpha}`.split("").unique(),
|
||||
polybius = [];
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
polybius[i] = polArray.slice(i*5, i*5 + 5);
|
||||
}
|
||||
|
||||
return polybius;
|
||||
}
|
||||
|
||||
/**
|
||||
* A mapping of string formats to their classes in the CryptoJS library.
|
||||
*
|
||||
* @private
|
||||
* @constant
|
||||
*/
|
||||
export const format = {
|
||||
"Hex": CryptoJS.enc.Hex,
|
||||
"Base64": CryptoJS.enc.Base64,
|
||||
"UTF8": CryptoJS.enc.Utf8,
|
||||
"UTF16": CryptoJS.enc.Utf16,
|
||||
"UTF16LE": CryptoJS.enc.Utf16LE,
|
||||
"UTF16BE": CryptoJS.enc.Utf16BE,
|
||||
"Latin1": CryptoJS.enc.Latin1,
|
||||
};
|
29
src/core/lib/Code.mjs
Normal file
29
src/core/lib/Code.mjs
Normal file
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* Code resources.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* This tries to rename variable names in a code snippet according to a function.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {function} replacer - This function will be fed the token which should be renamed.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function replaceVariableNames(input, replacer) {
|
||||
const tokenRegex = /\\"|"(?:\\"|[^"])*"|(\b[a-z0-9\-_]+\b)/ig;
|
||||
|
||||
return input.replace(tokenRegex, (...args) => {
|
||||
const match = args[0],
|
||||
quotes = args[1];
|
||||
|
||||
if (!quotes) {
|
||||
return match;
|
||||
} else {
|
||||
return replacer(match);
|
||||
}
|
||||
});
|
||||
}
|
417
src/core/lib/Colossus.mjs
Normal file
417
src/core/lib/Colossus.mjs
Normal file
|
@ -0,0 +1,417 @@
|
|||
/**
|
||||
* Colossus - an emulation of the world's first electronic computer
|
||||
*
|
||||
* @author VirtualColossus [martin@virtualcolossus.co.uk]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
import {INIT_PATTERNS, ITA2_TABLE, ROTOR_SIZES} from "../lib/Lorenz.mjs";
|
||||
|
||||
/**
|
||||
* Colossus simulator class.
|
||||
*/
|
||||
export class ColossusComputer {
|
||||
/**
|
||||
* Construct a Colossus.
|
||||
*
|
||||
* @param {string} ciphertext
|
||||
* @param {string} pattern - named pattern of Chi, Mu and Psi wheels
|
||||
* @param {Object} qbusin - which data inputs are being sent to q bus - each can be null, plain or delta
|
||||
* @param {Object[]} qbusswitches - Q bus calculation switches, multiple rows
|
||||
* @param {Object} control - control switches which specify stepping modes
|
||||
* @param {Object} starts - rotor start positions
|
||||
*/
|
||||
constructor(ciphertext, pattern, qbusin, qbusswitches, control, starts, settotal, limit) {
|
||||
this.ITAlookup = ITA2_TABLE;
|
||||
this.ReverseITAlookup = {};
|
||||
for (const letter in this.ITAlookup) {
|
||||
const code = this.ITAlookup[letter];
|
||||
this.ReverseITAlookup[code] = letter;
|
||||
}
|
||||
|
||||
this.initThyratrons(pattern);
|
||||
|
||||
this.ciphertext = ciphertext;
|
||||
this.qbusin = qbusin;
|
||||
this.qbusswitches = qbusswitches;
|
||||
this.control = control;
|
||||
this.starts = starts;
|
||||
this.settotal = settotal;
|
||||
this.limitations = limit;
|
||||
|
||||
|
||||
this.allCounters = [0, 0, 0, 0, 0];
|
||||
|
||||
this.Zbits = [0, 0, 0, 0, 0]; // Z input is the cipher tape
|
||||
this.ZbitsOneBack = [0, 0, 0, 0, 0]; // for delta
|
||||
this.Qbits = [0, 0, 0, 0, 0]; // input generated for placing onto the Q-bus (the logic processor)
|
||||
|
||||
this.Xbits = [0, 0, 0, 0, 0]; // X is the Chi wheel bits
|
||||
this.Xptr = [0, 0, 0, 0, 0]; // pointers to the current X bits (Chi wheels)
|
||||
this.XbitsOneBack = [0, 0, 0, 0, 0]; // the X bits one back (for delta)
|
||||
|
||||
this.Sbits = [0, 0, 0, 0, 0]; // S is the Psi wheel bits
|
||||
this.Sptr = [0, 0, 0, 0, 0]; // pointers to the current S bits (Psi wheels)
|
||||
this.SbitsOneBack = [0, 0, 0, 0, 0]; // the S bits one back (for delta)
|
||||
|
||||
this.Mptr = [0, 0];
|
||||
|
||||
this.rotorPtrs = {};
|
||||
|
||||
this.totalmotor = 0;
|
||||
this.P5Zbit = [0, 0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin a run
|
||||
*
|
||||
* @returns {object}
|
||||
*/
|
||||
run() {
|
||||
const result = {
|
||||
printout: ""
|
||||
};
|
||||
|
||||
// loop until our start positions are back to the beginning
|
||||
this.rotorPtrs = {X1: this.starts.X1, X2: this.starts.X2, X3: this.starts.X3, X4: this.starts.X4, X5: this.starts.X5, M61: this.starts.M61, M37: this.starts.M37, S1: this.starts.S1, S2: this.starts.S2, S3: this.starts.S3, S4: this.starts.S4, S5: this.starts.S5};
|
||||
// this.rotorPtrs = this.starts;
|
||||
let runcount = 1;
|
||||
|
||||
const fast = this.control.fast;
|
||||
const slow = this.control.slow;
|
||||
|
||||
// Print Headers
|
||||
result.printout += fast + " " + slow + "\n";
|
||||
|
||||
do {
|
||||
this.allCounters = [0, 0, 0, 0, 0];
|
||||
this.ZbitsOneBack = [0, 0, 0, 0, 0];
|
||||
this.XbitsOneBack = [0, 0, 0, 0, 0];
|
||||
|
||||
// Run full tape loop and process counters
|
||||
this.runTape();
|
||||
|
||||
// Only print result if larger than set total
|
||||
let fastRef = "00";
|
||||
let slowRef = "00";
|
||||
if (fast !== "") fastRef = this.rotorPtrs[fast].toString().padStart(2, "0");
|
||||
if (slow !== "") slowRef = this.rotorPtrs[slow].toString().padStart(2, "0");
|
||||
let printline = "";
|
||||
for (let c=0;c<5;c++) {
|
||||
if (this.allCounters[c] > this.settotal) {
|
||||
printline += String.fromCharCode(c+97) + this.allCounters[c]+" ";
|
||||
}
|
||||
}
|
||||
if (printline !== "") {
|
||||
result.printout += fastRef + " " + slowRef + " : ";
|
||||
result.printout += printline;
|
||||
result.printout += "\n";
|
||||
}
|
||||
|
||||
// Step fast rotor if required
|
||||
if (fast !== "") {
|
||||
this.rotorPtrs[fast]++;
|
||||
if (this.rotorPtrs[fast] > ROTOR_SIZES[fast]) this.rotorPtrs[fast] = 1;
|
||||
}
|
||||
|
||||
// Step slow rotor if fast rotor has returned to initial start position
|
||||
if (slow !== "" && this.rotorPtrs[fast] === this.starts[fast]) {
|
||||
this.rotorPtrs[slow]++;
|
||||
if (this.rotorPtrs[slow] > ROTOR_SIZES[slow]) this.rotorPtrs[slow] = 1;
|
||||
}
|
||||
|
||||
runcount++;
|
||||
} while (JSON.stringify(this.rotorPtrs) !== JSON.stringify(this.starts));
|
||||
|
||||
result.counters = this.allCounters;
|
||||
result.runcount = runcount;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run tape loop
|
||||
*/
|
||||
runTape() {
|
||||
let charZin = "";
|
||||
|
||||
this.Xptr = [this.rotorPtrs.X1, this.rotorPtrs.X2, this.rotorPtrs.X3, this.rotorPtrs.X4, this.rotorPtrs.X5];
|
||||
this.Mptr = [this.rotorPtrs.M37, this.rotorPtrs.M61];
|
||||
this.Sptr = [this.rotorPtrs.S1, this.rotorPtrs.S2, this.rotorPtrs.S3, this.rotorPtrs.S4, this.rotorPtrs.S5];
|
||||
|
||||
// Run full loop of all character on the input tape (Z)
|
||||
for (let i=0; i<this.ciphertext.length; i++) {
|
||||
charZin = this.ciphertext.charAt(i);
|
||||
|
||||
// Firstly, we check what inputs are specified on the Q-bus input switches
|
||||
this.getQbusInputs(charZin);
|
||||
|
||||
/*
|
||||
* Pattern conditions on individual impulses. Matching patterns of bits on the Q bus.
|
||||
* This is the top section on Colussus K rack - the Q bus programming switches
|
||||
*/
|
||||
const tmpcnt = this.runQbusProcessingConditional();
|
||||
|
||||
/*
|
||||
* Addition of impulses.
|
||||
* This is the bottom section of Colossus K rack.
|
||||
*/
|
||||
this.runQbusProcessingAddition(tmpcnt);
|
||||
|
||||
// Store Z bit impulse 5 two back required for P5 limitation
|
||||
this.P5Zbit[1] = this.P5Zbit[0];
|
||||
this.P5Zbit[0] = this.ITAlookup[charZin].split("")[4];
|
||||
|
||||
// Step rotors
|
||||
this.stepThyratrons();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Step thyratron rings to simulate movement of Lorenz rotors
|
||||
* Chi rotors all step one per character
|
||||
* Motor M61 rotor steps one per character, M37 steps dependant on M61 setting
|
||||
* Psi rotors only step dependant on M37 setting + limitation
|
||||
*/
|
||||
stepThyratrons() {
|
||||
let X2bPtr = this.Xptr[1]-1;
|
||||
if (X2bPtr===0) X2bPtr = ROTOR_SIZES.X2;
|
||||
let S1bPtr = this.Sptr[0]-1;
|
||||
if (S1bPtr===0) S1bPtr = ROTOR_SIZES.S1;
|
||||
|
||||
// Get Chi rotor 5 two back to calculate plaintext (Z+Chi+Psi=Plain)
|
||||
let X5bPtr=this.Xptr[4]-1;
|
||||
if (X5bPtr===0) X5bPtr=ROTOR_SIZES.X5;
|
||||
X5bPtr=X5bPtr-1;
|
||||
if (X5bPtr===0) X5bPtr=ROTOR_SIZES.X5;
|
||||
// Get Psi rotor 5 two back to calculate plaintext (Z+Chi+Psi=Plain)
|
||||
let S5bPtr=this.Sptr[4]-1;
|
||||
if (S5bPtr===0) S5bPtr=ROTOR_SIZES.S5;
|
||||
S5bPtr=S5bPtr-1;
|
||||
if (S5bPtr===0) S5bPtr=ROTOR_SIZES.S5;
|
||||
|
||||
const x2sw = this.limitations.X2;
|
||||
const s1sw = this.limitations.S1;
|
||||
const p5sw = this.limitations.P5;
|
||||
|
||||
// Limitation calculations
|
||||
let lim=1;
|
||||
if (x2sw) lim = this.rings.X[2][X2bPtr-1];
|
||||
if (s1sw) lim = lim ^ this.rings.S[1][S1bPtr-1];
|
||||
|
||||
// P5
|
||||
if (p5sw) {
|
||||
let p5lim = this.P5Zbit[1];
|
||||
p5lim = p5lim ^ this.rings.X[5][X5bPtr-1];
|
||||
p5lim = p5lim ^ this.rings.S[5][S5bPtr-1];
|
||||
lim = lim ^ p5lim;
|
||||
}
|
||||
|
||||
const basicmotor = this.rings.M[2][this.Mptr[0]-1];
|
||||
this.totalmotor = basicmotor;
|
||||
|
||||
if (x2sw || s1sw) {
|
||||
if (basicmotor===0 && lim===1) {
|
||||
this.totalmotor = 0;
|
||||
} else {
|
||||
this.totalmotor = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Step Chi rotors
|
||||
for (let r=0; r<5; r++) {
|
||||
this.Xptr[r]++;
|
||||
if (this.Xptr[r] > ROTOR_SIZES["X"+(r+1)]) this.Xptr[r] = 1;
|
||||
}
|
||||
|
||||
if (this.totalmotor) {
|
||||
// Step Psi rotors
|
||||
for (let r=0; r<5; r++) {
|
||||
this.Sptr[r]++;
|
||||
if (this.Sptr[r] > ROTOR_SIZES["S"+(r+1)]) this.Sptr[r] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Move M37 rotor if M61 set
|
||||
if (this.rings.M[1][this.Mptr[1]-1]===1) this.Mptr[0]++;
|
||||
if (this.Mptr[0] > ROTOR_SIZES.M37) this.Mptr[0]=1;
|
||||
|
||||
// Always move M61 rotor
|
||||
this.Mptr[1]++;
|
||||
if (this.Mptr[1] > ROTOR_SIZES.M61) this.Mptr[1]=1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Q bus inputs
|
||||
*/
|
||||
getQbusInputs(charZin) {
|
||||
// Zbits - the bits from the current character from the cipher tape.
|
||||
this.Zbits = this.ITAlookup[charZin].split("");
|
||||
if (this.qbusin.Z === "Z") {
|
||||
// direct Z
|
||||
this.Qbits = this.Zbits;
|
||||
} else if (this.qbusin.Z === "ΔZ") {
|
||||
// delta Z, the Bitwise XOR of this character Zbits + last character Zbits
|
||||
for (let b=0;b<5;b++) {
|
||||
this.Qbits[b] = this.Zbits[b] ^ this.ZbitsOneBack[b];
|
||||
}
|
||||
}
|
||||
this.ZbitsOneBack = this.Zbits.slice(); // copy value of object, not reference
|
||||
|
||||
// Xbits - the current Chi wheel bits
|
||||
for (let b=0;b<5;b++) {
|
||||
this.Xbits[b] = this.rings.X[b+1][this.Xptr[b]-1];
|
||||
}
|
||||
if (this.qbusin.Chi !== "") {
|
||||
if (this.qbusin.Chi === "Χ") {
|
||||
// direct X added to Qbits
|
||||
for (let b=0;b<5;b++) {
|
||||
this.Qbits[b] = this.Qbits[b] ^ this.Xbits[b];
|
||||
}
|
||||
} else if (this.qbusin.Chi === "ΔΧ") {
|
||||
// delta X
|
||||
for (let b=0;b<5;b++) {
|
||||
this.Qbits[b] = this.Qbits[b] ^ this.Xbits[b];
|
||||
this.Qbits[b] = this.Qbits[b] ^ this.XbitsOneBack[b];
|
||||
}
|
||||
}
|
||||
}
|
||||
this.XbitsOneBack = this.Xbits.slice();
|
||||
|
||||
// Sbits - the current Psi wheel bits
|
||||
for (let b=0;b<5;b++) {
|
||||
this.Sbits[b] = this.rings.S[b+1][this.Sptr[b]-1];
|
||||
}
|
||||
if (this.qbusin.Psi !== "") {
|
||||
if (this.qbusin.Psi === "Ψ") {
|
||||
// direct S added to Qbits
|
||||
for (let b=0;b<5;b++) {
|
||||
this.Qbits[b] = this.Qbits[b] ^ this.Sbits[b];
|
||||
}
|
||||
} else if (this.qbusin.Psi === "ΔΨ") {
|
||||
// delta S
|
||||
for (let b=0;b<5;b++) {
|
||||
this.Qbits[b] = this.Qbits[b] ^ this.Sbits[b];
|
||||
this.Qbits[b] = this.Qbits[b] ^ this.SbitsOneBack[b];
|
||||
}
|
||||
}
|
||||
}
|
||||
this.SbitsOneBack = this.Sbits.slice();
|
||||
}
|
||||
|
||||
/**
|
||||
* Conditional impulse Q bus section
|
||||
*/
|
||||
runQbusProcessingConditional() {
|
||||
const cnt = [-1, -1, -1, -1, -1];
|
||||
const numrows = this.qbusswitches.condition.length;
|
||||
|
||||
for (let r=0;r<numrows;r++) {
|
||||
const row = this.qbusswitches.condition[r];
|
||||
if (row.Counter !== "") {
|
||||
let result = true;
|
||||
const cPnt = row.Counter-1;
|
||||
const Qswitch = this.readBusSwitches(row.Qswitches);
|
||||
// Match switches to bit pattern
|
||||
for (let s=0;s<5;s++) {
|
||||
if (Qswitch[s] >= 0 && Qswitch[s] !== parseInt(this.Qbits[s], 10)) result = false;
|
||||
}
|
||||
// Check for NOT switch
|
||||
if (row.Negate) result = !result;
|
||||
|
||||
// AND each row to get final result
|
||||
if (cnt[cPnt] === -1) {
|
||||
cnt[cPnt] = result;
|
||||
} else if (!result) {
|
||||
cnt[cPnt] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Negate the whole column, this allows A OR B by doing NOT(NOT A AND NOT B)
|
||||
for (let c=0;c<5;c++) {
|
||||
if (this.qbusswitches.condNegateAll && cnt[c] !== -1) cnt[c] = !cnt[c];
|
||||
}
|
||||
|
||||
return cnt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Addition of impulses Q bus section
|
||||
*/
|
||||
runQbusProcessingAddition(cnt) {
|
||||
const row = this.qbusswitches.addition[0];
|
||||
const Qswitch = row.Qswitches.slice();
|
||||
|
||||
// To save making the arguments of this operation any larger, limiting addition counter to first one only
|
||||
// Colossus could actually add into any of the five counters.
|
||||
if (row.C1) {
|
||||
let addition = 0;
|
||||
for (let s=0;s<5;s++) {
|
||||
// XOR addition
|
||||
if (Qswitch[s]) {
|
||||
addition = addition ^ this.Qbits[s];
|
||||
}
|
||||
}
|
||||
const equals = (row.Equals===""?-1:(row.Equals==="."?0:1));
|
||||
if (addition === equals) {
|
||||
// AND with conditional rows to get final result
|
||||
if (cnt[0] === -1) cnt[0] = true;
|
||||
} else {
|
||||
cnt[0] = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Final check, check for addition section negate
|
||||
// then, if any column set, from top to bottom of rack, add to counter.
|
||||
for (let c=0;c<5;c++) {
|
||||
if (this.qbusswitches.addNegateAll && cnt[c] !== -1) cnt[c] = !cnt[c];
|
||||
|
||||
if (this.qbusswitches.totalMotor === "" || (this.qbusswitches.totalMotor === "x" && this.totalmotor === 0) || (this.qbusswitches.totalMotor === "." && this.totalmotor === 1)) {
|
||||
if (cnt[c] === true) this.allCounters[c]++;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise thyratron rings
|
||||
* These hold the pattern of 1s & 0s for each rotor on banks of thyraton GT1C valves which act as a one-bit store.
|
||||
*/
|
||||
initThyratrons(pattern) {
|
||||
this.rings = {
|
||||
X: {
|
||||
1: INIT_PATTERNS[pattern].X[1].slice().reverse(),
|
||||
2: INIT_PATTERNS[pattern].X[2].slice().reverse(),
|
||||
3: INIT_PATTERNS[pattern].X[3].slice().reverse(),
|
||||
4: INIT_PATTERNS[pattern].X[4].slice().reverse(),
|
||||
5: INIT_PATTERNS[pattern].X[5].slice().reverse()
|
||||
},
|
||||
M: {
|
||||
1: INIT_PATTERNS[pattern].M[1].slice().reverse(),
|
||||
2: INIT_PATTERNS[pattern].M[2].slice().reverse(),
|
||||
},
|
||||
S: {
|
||||
1: INIT_PATTERNS[pattern].S[1].slice().reverse(),
|
||||
2: INIT_PATTERNS[pattern].S[2].slice().reverse(),
|
||||
3: INIT_PATTERNS[pattern].S[3].slice().reverse(),
|
||||
4: INIT_PATTERNS[pattern].S[4].slice().reverse(),
|
||||
5: INIT_PATTERNS[pattern].S[5].slice().reverse()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Read argument bus switches X & . and convert to 1 & 0
|
||||
*/
|
||||
readBusSwitches(row) {
|
||||
const output = [-1, -1, -1, -1, -1];
|
||||
for (let c=0;c<5;c++) {
|
||||
if (row[c]===".") output[c] = 0;
|
||||
if (row[c]==="x") output[c] = 1;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
}
|
668
src/core/lib/ConvertCoordinates.mjs
Normal file
668
src/core/lib/ConvertCoordinates.mjs
Normal file
|
@ -0,0 +1,668 @@
|
|||
/**
|
||||
* Co-ordinate conversion resources.
|
||||
*
|
||||
* @author j433866 [j433866@gmail.com]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import geohash from "ngeohash";
|
||||
/*
|
||||
Currently unable to update to geodesy v2 as we cannot load .js modules into a .mjs file.
|
||||
When we do update, imports will look like this:
|
||||
|
||||
import LatLonEllipsoidal from "geodesy/latlon-ellipsoidal.js";
|
||||
import Mgrs from "geodesy/mgrs.js";
|
||||
import OsGridRef from "geodesy/osgridref.js";
|
||||
import Utm from "geodesy/utm.js";
|
||||
*/
|
||||
import geodesy from "geodesy";
|
||||
const LatLonEllipsoidal = geodesy.LatLonEllipsoidal,
|
||||
Mgrs = geodesy.Mgrs,
|
||||
OsGridRef = geodesy.OsGridRef,
|
||||
Utm = geodesy.Utm;
|
||||
|
||||
/**
|
||||
* Co-ordinate formats
|
||||
*/
|
||||
export const FORMATS = [
|
||||
"Degrees Minutes Seconds",
|
||||
"Degrees Decimal Minutes",
|
||||
"Decimal Degrees",
|
||||
"Geohash",
|
||||
"Military Grid Reference System",
|
||||
"Ordnance Survey National Grid",
|
||||
"Universal Transverse Mercator"
|
||||
];
|
||||
|
||||
/**
|
||||
* Formats that should be passed to the conversion module as-is
|
||||
*/
|
||||
const NO_CHANGE = [
|
||||
"Geohash",
|
||||
"Military Grid Reference System",
|
||||
"Ordnance Survey National Grid",
|
||||
"Universal Transverse Mercator",
|
||||
];
|
||||
|
||||
/**
|
||||
* Convert a given latitude and longitude into a different format.
|
||||
*
|
||||
* @param {string} input - Input string to be converted
|
||||
* @param {string} inFormat - Format of the input coordinates
|
||||
* @param {string} inDelim - The delimiter splitting the lat/long of the input
|
||||
* @param {string} outFormat - Format to convert to
|
||||
* @param {string} outDelim - The delimiter to separate the output with
|
||||
* @param {string} includeDir - Whether or not to include the compass direction in the output
|
||||
* @param {number} precision - Precision of the result
|
||||
* @returns {string} A formatted string of the converted co-ordinates
|
||||
*/
|
||||
export function convertCoordinates (input, inFormat, inDelim, outFormat, outDelim, includeDir, precision) {
|
||||
let isPair = false,
|
||||
split,
|
||||
latlon,
|
||||
convLat,
|
||||
convLon,
|
||||
conv,
|
||||
hash,
|
||||
utm,
|
||||
mgrs,
|
||||
osng,
|
||||
splitLat,
|
||||
splitLong,
|
||||
lat,
|
||||
lon;
|
||||
|
||||
// Can't have a precision less than 0!
|
||||
if (precision < 0) {
|
||||
precision = 0;
|
||||
}
|
||||
|
||||
if (inDelim === "Auto") {
|
||||
// Try to detect a delimiter in the input.
|
||||
inDelim = findDelim(input);
|
||||
if (inDelim === null) {
|
||||
throw new OperationError("Unable to detect the input delimiter automatically.");
|
||||
}
|
||||
} else if (!inDelim.includes("Direction")) {
|
||||
// Convert the delimiter argument value to the actual character
|
||||
inDelim = realDelim(inDelim);
|
||||
}
|
||||
|
||||
if (inFormat === "Auto") {
|
||||
// Try to detect the format of the input data
|
||||
inFormat = findFormat(input, inDelim);
|
||||
if (inFormat === null) {
|
||||
throw new OperationError("Unable to detect the input format automatically.");
|
||||
}
|
||||
}
|
||||
|
||||
// Convert the output delimiter argument to the real character
|
||||
outDelim = realDelim(outDelim);
|
||||
|
||||
if (!NO_CHANGE.includes(inFormat)) {
|
||||
if (inDelim.includes("Direction")) {
|
||||
// Split on directions
|
||||
split = input.split(/[NnEeSsWw]/g);
|
||||
if (split[0] === "") {
|
||||
// Remove first element if direction preceding
|
||||
split = split.slice(1);
|
||||
}
|
||||
} else {
|
||||
split = input.split(inDelim);
|
||||
}
|
||||
// Replace any co-ordinate symbols with spaces so we can split on them later
|
||||
for (let i = 0; i < split.length; i++) {
|
||||
split[i] = split[i].replace(/[°˝´'"]/g, " ");
|
||||
}
|
||||
if (split.length > 1) {
|
||||
isPair = true;
|
||||
}
|
||||
} else {
|
||||
// Remove any delimiters from the input
|
||||
input = input.replace(inDelim, "");
|
||||
isPair = true;
|
||||
}
|
||||
|
||||
// Conversions from the input format into a geodesy latlon object
|
||||
switch (inFormat) {
|
||||
case "Geohash":
|
||||
hash = geohash.decode(input.replace(/[^A-Za-z0-9]/g, ""));
|
||||
latlon = new LatLonEllipsoidal(hash.latitude, hash.longitude);
|
||||
break;
|
||||
case "Military Grid Reference System":
|
||||
utm = Mgrs.parse(input.replace(/[^A-Za-z0-9]/g, "")).toUtm();
|
||||
latlon = utm.toLatLonE();
|
||||
break;
|
||||
case "Ordnance Survey National Grid":
|
||||
osng = OsGridRef.parse(input.replace(/[^A-Za-z0-9]/g, ""));
|
||||
latlon = OsGridRef.osGridToLatLon(osng);
|
||||
break;
|
||||
case "Universal Transverse Mercator":
|
||||
// Geodesy needs a space between the first 2 digits and the next letter
|
||||
if (/^[\d]{2}[A-Za-z]/.test(input)) {
|
||||
input = input.slice(0, 2) + " " + input.slice(2);
|
||||
}
|
||||
utm = Utm.parse(input);
|
||||
latlon = utm.toLatLonE();
|
||||
break;
|
||||
case "Degrees Minutes Seconds":
|
||||
if (isPair) {
|
||||
// Split up the lat/long into degrees / minutes / seconds values
|
||||
splitLat = splitInput(split[0]);
|
||||
splitLong = splitInput(split[1]);
|
||||
|
||||
if (splitLat.length >= 3 && splitLong.length >= 3) {
|
||||
lat = convDMSToDD(splitLat[0], splitLat[1], splitLat[2], 10);
|
||||
lon = convDMSToDD(splitLong[0], splitLong[1], splitLong[2], 10);
|
||||
latlon = new LatLonEllipsoidal(lat.degrees, lon.degrees);
|
||||
} else {
|
||||
throw new OperationError("Invalid co-ordinate format for Degrees Minutes Seconds");
|
||||
}
|
||||
} else {
|
||||
// Not a pair, so only try to convert one set of co-ordinates
|
||||
splitLat = splitInput(split[0]);
|
||||
if (splitLat.length >= 3) {
|
||||
lat = convDMSToDD(splitLat[0], splitLat[1], splitLat[2]);
|
||||
latlon = new LatLonEllipsoidal(lat.degrees, lat.degrees);
|
||||
} else {
|
||||
throw new OperationError("Invalid co-ordinate format for Degrees Minutes Seconds");
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "Degrees Decimal Minutes":
|
||||
if (isPair) {
|
||||
splitLat = splitInput(split[0]);
|
||||
splitLong = splitInput(split[1]);
|
||||
if (splitLat.length !== 2 || splitLong.length !== 2) {
|
||||
throw new OperationError("Invalid co-ordinate format for Degrees Decimal Minutes.");
|
||||
}
|
||||
// Convert to decimal degrees, and then convert to a geodesy object
|
||||
lat = convDDMToDD(splitLat[0], splitLat[1], 10);
|
||||
lon = convDDMToDD(splitLong[0], splitLong[1], 10);
|
||||
latlon = new LatLonEllipsoidal(lat.degrees, lon.degrees);
|
||||
} else {
|
||||
// Not a pair, so only try to convert one set of co-ordinates
|
||||
splitLat = splitInput(input);
|
||||
if (splitLat.length !== 2) {
|
||||
throw new OperationError("Invalid co-ordinate format for Degrees Decimal Minutes.");
|
||||
}
|
||||
lat = convDDMToDD(splitLat[0], splitLat[1], 10);
|
||||
latlon = new LatLonEllipsoidal(lat.degrees, lat.degrees);
|
||||
}
|
||||
break;
|
||||
case "Decimal Degrees":
|
||||
if (isPair) {
|
||||
splitLat = splitInput(split[0]);
|
||||
splitLong = splitInput(split[1]);
|
||||
if (splitLat.length !== 1 || splitLong.length !== 1) {
|
||||
throw new OperationError("Invalid co-ordinate format for Decimal Degrees.");
|
||||
}
|
||||
latlon = new LatLonEllipsoidal(splitLat[0], splitLong[0]);
|
||||
} else {
|
||||
// Not a pair, so only try to convert one set of co-ordinates
|
||||
splitLat = splitInput(split[0]);
|
||||
if (splitLat.length !== 1) {
|
||||
throw new OperationError("Invalid co-ordinate format for Decimal Degrees.");
|
||||
}
|
||||
latlon = new LatLonEllipsoidal(splitLat[0], splitLat[0]);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new OperationError(`Unknown input format '${inFormat}'`);
|
||||
}
|
||||
|
||||
// Everything is now a geodesy latlon object
|
||||
// These store the latitude and longitude as decimal
|
||||
if (inFormat.includes("Degrees")) {
|
||||
// If the input string contains directions, we need to check if they're S or W.
|
||||
// If either of the directions are, we should make the decimal value negative
|
||||
const dirs = input.toUpperCase().match(/[NESW]/g);
|
||||
if (dirs && dirs.length >= 1) {
|
||||
// Make positive lat/lon values with S/W directions into negative values
|
||||
if (dirs[0] === "S" || dirs[0] === "W" && latlon.lat > 0) {
|
||||
latlon.lat = -latlon.lat;
|
||||
}
|
||||
if (dirs.length >= 2) {
|
||||
if (dirs[1] === "S" || dirs[1] === "W" && latlon.lon > 0) {
|
||||
latlon.lon = -latlon.lon;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try to find the compass directions of the lat and long
|
||||
const [latDir, longDir] = findDirs(latlon.lat + "," + latlon.lon, ",");
|
||||
|
||||
// Output conversions for each output format
|
||||
switch (outFormat) {
|
||||
case "Decimal Degrees":
|
||||
// We could use the built in latlon.toString(),
|
||||
// but this makes adjusting the output harder
|
||||
lat = convDDToDD(latlon.lat, precision);
|
||||
lon = convDDToDD(latlon.lon, precision);
|
||||
convLat = lat.string;
|
||||
convLon = lon.string;
|
||||
break;
|
||||
case "Degrees Decimal Minutes":
|
||||
lat = convDDToDDM(latlon.lat, precision);
|
||||
lon = convDDToDDM(latlon.lon, precision);
|
||||
convLat = lat.string;
|
||||
convLon = lon.string;
|
||||
break;
|
||||
case "Degrees Minutes Seconds":
|
||||
lat = convDDToDMS(latlon.lat, precision);
|
||||
lon = convDDToDMS(latlon.lon, precision);
|
||||
convLat = lat.string;
|
||||
convLon = lon.string;
|
||||
break;
|
||||
case "Geohash":
|
||||
convLat = geohash.encode(latlon.lat, latlon.lon, precision);
|
||||
break;
|
||||
case "Military Grid Reference System":
|
||||
utm = latlon.toUtm();
|
||||
mgrs = utm.toMgrs();
|
||||
// MGRS wants a precision that's an even number between 2 and 10
|
||||
if (precision % 2 !== 0) {
|
||||
precision = precision + 1;
|
||||
}
|
||||
if (precision > 10) {
|
||||
precision = 10;
|
||||
}
|
||||
convLat = mgrs.toString(precision);
|
||||
break;
|
||||
case "Ordnance Survey National Grid":
|
||||
osng = OsGridRef.latLonToOsGrid(latlon);
|
||||
if (osng.toString() === "") {
|
||||
throw new OperationError("Could not convert co-ordinates to OS National Grid. Are the co-ordinates in range?");
|
||||
}
|
||||
// OSNG wants a precision that's an even number between 2 and 10
|
||||
if (precision % 2 !== 0) {
|
||||
precision = precision + 1;
|
||||
}
|
||||
if (precision > 10) {
|
||||
precision = 10;
|
||||
}
|
||||
convLat = osng.toString(precision);
|
||||
break;
|
||||
case "Universal Transverse Mercator":
|
||||
utm = latlon.toUtm();
|
||||
convLat = utm.toString(precision);
|
||||
break;
|
||||
}
|
||||
|
||||
if (convLat === undefined) {
|
||||
throw new OperationError("Error converting co-ordinates.");
|
||||
}
|
||||
|
||||
if (outFormat.includes("Degrees")) {
|
||||
// Format DD/DDM/DMS for output
|
||||
// If we're outputting a compass direction, remove the negative sign
|
||||
if (latDir === "S" && includeDir !== "None") {
|
||||
convLat = convLat.replace("-", "");
|
||||
}
|
||||
if (longDir === "W" && includeDir !== "None") {
|
||||
convLon = convLon.replace("-", "");
|
||||
}
|
||||
|
||||
let outConv = "";
|
||||
if (includeDir === "Before") {
|
||||
outConv += latDir + " ";
|
||||
}
|
||||
|
||||
outConv += convLat;
|
||||
if (includeDir === "After") {
|
||||
outConv += " " + latDir;
|
||||
}
|
||||
outConv += outDelim;
|
||||
if (isPair) {
|
||||
if (includeDir === "Before") {
|
||||
outConv += longDir + " ";
|
||||
}
|
||||
outConv += convLon;
|
||||
if (includeDir === "After") {
|
||||
outConv += " " + longDir;
|
||||
}
|
||||
outConv += outDelim;
|
||||
}
|
||||
conv = outConv;
|
||||
} else {
|
||||
conv = convLat + outDelim;
|
||||
}
|
||||
|
||||
return conv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Split up the input using a space or degrees signs, and sanitise the result
|
||||
*
|
||||
* @param {string} input - The input data to be split
|
||||
* @returns {number[]} An array of the different items in the string, stored as floats
|
||||
*/
|
||||
function splitInput (input) {
|
||||
const split = [];
|
||||
|
||||
input.split(/\s+/).forEach(item => {
|
||||
// Remove any character that isn't a digit, decimal point or negative sign
|
||||
item = item.replace(/[^0-9.-]/g, "");
|
||||
if (item.length > 0) {
|
||||
// Turn the item into a float
|
||||
split.push(parseFloat(item));
|
||||
}
|
||||
});
|
||||
return split;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Degrees Minutes Seconds to Decimal Degrees
|
||||
*
|
||||
* @param {number} degrees - The degrees of the input co-ordinates
|
||||
* @param {number} minutes - The minutes of the input co-ordinates
|
||||
* @param {number} seconds - The seconds of the input co-ordinates
|
||||
* @param {number} precision - The precision the result should be rounded to
|
||||
* @returns {{string: string, degrees: number}} An object containing the raw converted value (obj.degrees), and a formatted string version (obj.string)
|
||||
*/
|
||||
function convDMSToDD (degrees, minutes, seconds, precision) {
|
||||
const absDegrees = Math.abs(degrees);
|
||||
let conv = absDegrees + (minutes / 60) + (seconds / 3600);
|
||||
let outString = round(conv, precision) + "°";
|
||||
if (isNegativeZero(degrees) || degrees < 0) {
|
||||
conv = -conv;
|
||||
outString = "-" + outString;
|
||||
}
|
||||
return {
|
||||
"degrees": conv,
|
||||
"string": outString
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Decimal Degrees Minutes to Decimal Degrees
|
||||
*
|
||||
* @param {number} degrees - The input degrees to be converted
|
||||
* @param {number} minutes - The input minutes to be converted
|
||||
* @param {number} precision - The precision which the result should be rounded to
|
||||
* @returns {{string: string, degrees: number}} An object containing the raw converted value (obj.degrees), and a formatted string version (obj.string)
|
||||
*/
|
||||
function convDDMToDD (degrees, minutes, precision) {
|
||||
const absDegrees = Math.abs(degrees);
|
||||
let conv = absDegrees + minutes / 60;
|
||||
let outString = round(conv, precision) + "°";
|
||||
if (isNegativeZero(degrees) || degrees < 0) {
|
||||
conv = -conv;
|
||||
outString = "-" + outString;
|
||||
}
|
||||
return {
|
||||
"degrees": conv,
|
||||
"string": outString
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Decimal Degrees to Decimal Degrees
|
||||
*
|
||||
* Doesn't affect the input, just puts it into an object
|
||||
* @param {number} degrees - The input degrees to be converted
|
||||
* @param {number} precision - The precision which the result should be rounded to
|
||||
* @returns {{string: string, degrees: number}} An object containing the raw converted value (obj.degrees), and a formatted string version (obj.string)
|
||||
*/
|
||||
function convDDToDD (degrees, precision) {
|
||||
return {
|
||||
"degrees": degrees,
|
||||
"string": round(degrees, precision) + "°"
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Decimal Degrees to Degrees Minutes Seconds
|
||||
*
|
||||
* @param {number} decDegrees - The input data to be converted
|
||||
* @param {number} precision - The precision which the result should be rounded to
|
||||
* @returns {{string: string, degrees: number, minutes: number, seconds: number}} An object containing the raw converted value as separate numbers (.degrees, .minutes, .seconds), and a formatted string version (obj.string)
|
||||
*/
|
||||
function convDDToDMS (decDegrees, precision) {
|
||||
const absDegrees = Math.abs(decDegrees);
|
||||
let degrees = Math.floor(absDegrees);
|
||||
const minutes = Math.floor(60 * (absDegrees - degrees)),
|
||||
seconds = round(3600 * (absDegrees - degrees) - 60 * minutes, precision);
|
||||
let outString = degrees + "° " + minutes + "' " + seconds + "\"";
|
||||
if (isNegativeZero(decDegrees) || decDegrees < 0) {
|
||||
degrees = -degrees;
|
||||
outString = "-" + outString;
|
||||
}
|
||||
return {
|
||||
"degrees": degrees,
|
||||
"minutes": minutes,
|
||||
"seconds": seconds,
|
||||
"string": outString
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Decimal Degrees to Degrees Decimal Minutes
|
||||
*
|
||||
* @param {number} decDegrees - The input degrees to be converted
|
||||
* @param {number} precision - The precision the input data should be rounded to
|
||||
* @returns {{string: string, degrees: number, minutes: number}} An object containing the raw converted value as separate numbers (.degrees, .minutes), and a formatted string version (obj.string)
|
||||
*/
|
||||
function convDDToDDM (decDegrees, precision) {
|
||||
const absDegrees = Math.abs(decDegrees);
|
||||
let degrees = Math.floor(absDegrees);
|
||||
const minutes = absDegrees - degrees,
|
||||
decMinutes = round(minutes * 60, precision);
|
||||
let outString = degrees + "° " + decMinutes + "'";
|
||||
if (decDegrees < 0 || isNegativeZero(decDegrees)) {
|
||||
degrees = -degrees;
|
||||
outString = "-" + outString;
|
||||
}
|
||||
|
||||
return {
|
||||
"degrees": degrees,
|
||||
"minutes": decMinutes,
|
||||
"string": outString,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and returns the compass directions in an input string
|
||||
*
|
||||
* @param {string} input - The input co-ordinates containing the direction
|
||||
* @param {string} delim - The delimiter separating latitide and longitude
|
||||
* @returns {string[]} String array containing the latitude and longitude directions
|
||||
*/
|
||||
export function findDirs(input, delim) {
|
||||
const upperInput = input.toUpperCase();
|
||||
const dirExp = new RegExp(/[NESW]/g);
|
||||
|
||||
const dirs = upperInput.match(dirExp);
|
||||
|
||||
if (dirs) {
|
||||
// If there's actually compass directions
|
||||
// in the input, use these to work out the direction
|
||||
if (dirs.length <= 2 && dirs.length >= 1) {
|
||||
return dirs.length === 2 ? [dirs[0], dirs[1]] : [dirs[0], ""];
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing was returned, so guess the directions
|
||||
let lat = upperInput,
|
||||
long,
|
||||
latDir = "",
|
||||
longDir = "";
|
||||
if (!delim.includes("Direction")) {
|
||||
if (upperInput.includes(delim)) {
|
||||
const split = upperInput.split(delim);
|
||||
if (split.length >= 1) {
|
||||
if (split[0] !== "") {
|
||||
lat = split[0];
|
||||
}
|
||||
if (split.length >= 2 && split[1] !== "") {
|
||||
long = split[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const split = upperInput.split(dirExp);
|
||||
if (split.length > 1) {
|
||||
lat = split[0] === "" ? split[1] : split[0];
|
||||
if (split.length > 2 && split[2] !== "") {
|
||||
long = split[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lat) {
|
||||
lat = parseFloat(lat);
|
||||
latDir = lat < 0 ? "S" : "N";
|
||||
}
|
||||
|
||||
if (long) {
|
||||
long = parseFloat(long);
|
||||
longDir = long < 0 ? "W" : "E";
|
||||
}
|
||||
|
||||
return [latDir, longDir];
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects the co-ordinate format of the input data
|
||||
*
|
||||
* @param {string} input - The input data whose format we need to detect
|
||||
* @param {string} delim - The delimiter separating the data in input
|
||||
* @returns {string} The input format
|
||||
*/
|
||||
export function findFormat (input, delim) {
|
||||
let testData;
|
||||
const mgrsPattern = new RegExp(/^[0-9]{2}\s?[C-HJ-NP-X]{1}\s?[A-HJ-NP-Z][A-HJ-NP-V]\s?[0-9\s]+/),
|
||||
osngPattern = new RegExp(/^[A-HJ-Z]{2}\s+[0-9\s]+$/),
|
||||
geohashPattern = new RegExp(/^[0123456789BCDEFGHJKMNPQRSTUVWXYZ]+$/),
|
||||
utmPattern = new RegExp(/^[0-9]{2}\s?[C-HJ-NP-X]\s[0-9.]+\s?[0-9.]+$/),
|
||||
degPattern = new RegExp(/[°'"]/g);
|
||||
|
||||
input = input.trim();
|
||||
|
||||
if (delim !== null && delim.includes("Direction")) {
|
||||
const split = input.split(/[NnEeSsWw]/);
|
||||
if (split.length > 1) {
|
||||
testData = split[0] === "" ? split[1] : split[0];
|
||||
}
|
||||
} else if (delim !== null && delim !== "") {
|
||||
if (input.includes(delim)) {
|
||||
const split = input.split(delim);
|
||||
if (split.length > 1) {
|
||||
testData = split[0] === "" ? split[1] : split[0];
|
||||
}
|
||||
} else {
|
||||
testData = input;
|
||||
}
|
||||
}
|
||||
|
||||
// Test non-degrees formats
|
||||
if (!degPattern.test(input)) {
|
||||
const filteredInput = input.toUpperCase().replace(delim, "");
|
||||
|
||||
if (utmPattern.test(filteredInput)) {
|
||||
return "Universal Transverse Mercator";
|
||||
}
|
||||
if (mgrsPattern.test(filteredInput)) {
|
||||
return "Military Grid Reference System";
|
||||
}
|
||||
if (osngPattern.test(filteredInput)) {
|
||||
return "Ordnance Survey National Grid";
|
||||
}
|
||||
if (geohashPattern.test(filteredInput)) {
|
||||
return "Geohash";
|
||||
}
|
||||
}
|
||||
|
||||
// Test DMS/DDM/DD formats
|
||||
if (testData !== undefined) {
|
||||
const split = splitInput(testData);
|
||||
switch (split.length) {
|
||||
case 3:
|
||||
return "Degrees Minutes Seconds";
|
||||
case 2:
|
||||
return "Degrees Decimal Minutes";
|
||||
case 1:
|
||||
return "Decimal Degrees";
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically find the delimeter type from the given input
|
||||
*
|
||||
* @param {string} input
|
||||
* @returns {string} Delimiter type
|
||||
*/
|
||||
export function findDelim (input) {
|
||||
input = input.trim();
|
||||
const delims = [",", ";", ":"];
|
||||
const testDir = input.match(/[NnEeSsWw]/g);
|
||||
if (testDir !== null && testDir.length > 0 && testDir.length < 3) {
|
||||
// Possibly contains a direction
|
||||
const splitInput = input.split(/[NnEeSsWw]/);
|
||||
if (splitInput.length <= 3 && splitInput.length > 0) {
|
||||
// If there's 3 splits (one should be empty), then assume we have directions
|
||||
if (splitInput[0] === "") {
|
||||
return "Direction Preceding";
|
||||
} else if (splitInput[splitInput.length - 1] === "") {
|
||||
return "Direction Following";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Loop through the standard delimiters, and try to find them in the input
|
||||
for (let i = 0; i < delims.length; i++) {
|
||||
const delim = delims[i];
|
||||
if (input.includes(delim)) {
|
||||
const splitInput = input.split(delim);
|
||||
if (splitInput.length <= 3 && splitInput.length > 0) {
|
||||
// Don't want to try and convert more than 2 co-ordinates
|
||||
return delim;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the real string for a delimiter name.
|
||||
*
|
||||
* @param {string} delim The delimiter to be matched
|
||||
* @returns {string}
|
||||
*/
|
||||
export function realDelim (delim) {
|
||||
return {
|
||||
"Auto": "Auto",
|
||||
"Space": " ",
|
||||
"\\n": "\n",
|
||||
"Comma": ",",
|
||||
"Semi-colon": ";",
|
||||
"Colon": ":"
|
||||
}[delim];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a zero is negative
|
||||
*
|
||||
* @param {number} zero
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isNegativeZero(zero) {
|
||||
return zero === 0 && (1/zero < 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rounds a number to a specified number of decimal places
|
||||
*
|
||||
* @param {number} input - The number to be rounded
|
||||
* @param {precision} precision - The number of decimal places the number should be rounded to
|
||||
* @returns {number}
|
||||
*/
|
||||
function round(input, precision) {
|
||||
precision = Math.pow(10, precision);
|
||||
return Math.round(input * precision) / precision;
|
||||
}
|
9
src/core/lib/Crypt.mjs
Normal file
9
src/core/lib/Crypt.mjs
Normal file
|
@ -0,0 +1,9 @@
|
|||
/**
|
||||
* Crypt resources.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2021
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
export const cryptNotice = "WARNING: Cryptographic operations in CyberChef should not be relied upon to provide security in any situation. No guarantee is offered for their correctness. We advise you not to use keys generated from CyberChef in operational contexts.";
|
313
src/core/lib/DateTime.mjs
Normal file
313
src/core/lib/DateTime.mjs
Normal file
|
@ -0,0 +1,313 @@
|
|||
/**
|
||||
* DateTime resources.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* DateTime units.
|
||||
*/
|
||||
export const UNITS = ["Seconds (s)", "Milliseconds (ms)", "Microseconds (μs)", "Nanoseconds (ns)"];
|
||||
|
||||
/**
|
||||
* DateTime formats.
|
||||
*/
|
||||
export const DATETIME_FORMATS = [
|
||||
{
|
||||
name: "Standard date and time",
|
||||
value: "DD/MM/YYYY HH:mm:ss"
|
||||
},
|
||||
{
|
||||
name: "American-style date and time",
|
||||
value: "MM/DD/YYYY HH:mm:ss"
|
||||
},
|
||||
{
|
||||
name: "International date and time",
|
||||
value: "YYYY-MM-DD HH:mm:ss"
|
||||
},
|
||||
{
|
||||
name: "Verbose date and time",
|
||||
value: "dddd Do MMMM YYYY HH:mm:ss Z z"
|
||||
},
|
||||
{
|
||||
name: "UNIX timestamp (seconds)",
|
||||
value: "X"
|
||||
},
|
||||
{
|
||||
name: "UNIX timestamp offset (milliseconds)",
|
||||
value: "x"
|
||||
},
|
||||
{
|
||||
name: "Automatic",
|
||||
value: ""
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* MomentJS DateTime formatting examples.
|
||||
*/
|
||||
export const FORMAT_EXAMPLES = `Format string tokens:
|
||||
<table class="table table-striped table-hover table-sm table-bordered" style="font-family: sans-serif">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th>Category</th>
|
||||
<th>Token</th>
|
||||
<th>Output</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><b>Month</b></td>
|
||||
<td>M</td>
|
||||
<td>1 2 ... 11 12</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>Mo</td>
|
||||
<td>1st 2nd ... 11th 12th</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>MM</td>
|
||||
<td>01 02 ... 11 12</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>MMM</td>
|
||||
<td>Jan Feb ... Nov Dec</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>MMMM</td>
|
||||
<td>January February ... November December</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Quarter</b></td>
|
||||
<td>Q</td>
|
||||
<td>1 2 3 4</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Day of Month</b></td>
|
||||
<td>D</td>
|
||||
<td>1 2 ... 30 31</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>Do</td>
|
||||
<td>1st 2nd ... 30th 31st</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>DD</td>
|
||||
<td>01 02 ... 30 31</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Day of Year</b></td>
|
||||
<td>DDD</td>
|
||||
<td>1 2 ... 364 365</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>DDDo</td>
|
||||
<td>1st 2nd ... 364th 365th</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>DDDD</td>
|
||||
<td>001 002 ... 364 365</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Day of Week</b></td>
|
||||
<td>d</td>
|
||||
<td>0 1 ... 5 6</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>do</td>
|
||||
<td>0th 1st ... 5th 6th</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>dd</td>
|
||||
<td>Su Mo ... Fr Sa</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>ddd</td>
|
||||
<td>Sun Mon ... Fri Sat</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>dddd</td>
|
||||
<td>Sunday Monday ... Friday Saturday</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Day of Week (Locale)</b></td>
|
||||
<td>e</td>
|
||||
<td>0 1 ... 5 6</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Day of Week (ISO)</b></td>
|
||||
<td>E</td>
|
||||
<td>1 2 ... 6 7</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Week of Year</b></td>
|
||||
<td>w</td>
|
||||
<td>1 2 ... 52 53</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>wo</td>
|
||||
<td>1st 2nd ... 52nd 53rd</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>ww</td>
|
||||
<td>01 02 ... 52 53</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Week of Year (ISO)</b></td>
|
||||
<td>W</td>
|
||||
<td>1 2 ... 52 53</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>Wo</td>
|
||||
<td>1st 2nd ... 52nd 53rd</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>WW</td>
|
||||
<td>01 02 ... 52 53</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Year</b></td>
|
||||
<td>YY</td>
|
||||
<td>70 71 ... 29 30</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>YYYY</td>
|
||||
<td>1970 1971 ... 2029 2030</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Week Year</b></td>
|
||||
<td>gg</td>
|
||||
<td>70 71 ... 29 30</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>gggg</td>
|
||||
<td>1970 1971 ... 2029 2030</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Week Year (ISO)</b></td>
|
||||
<td>GG</td>
|
||||
<td>70 71 ... 29 30</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>GGGG</td>
|
||||
<td>1970 1971 ... 2029 2030</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>AM/PM</b></td>
|
||||
<td>A</td>
|
||||
<td>AM PM</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>a</td>
|
||||
<td>am pm</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Hour</b></td>
|
||||
<td>H</td>
|
||||
<td>0 1 ... 22 23</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>HH</td>
|
||||
<td>00 01 ... 22 23</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>h</td>
|
||||
<td>1 2 ... 11 12</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>hh</td>
|
||||
<td>01 02 ... 11 12</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Minute</b></td>
|
||||
<td>m</td>
|
||||
<td>0 1 ... 58 59</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>mm</td>
|
||||
<td>00 01 ... 58 59</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Second</b></td>
|
||||
<td>s</td>
|
||||
<td>0 1 ... 58 59</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>ss</td>
|
||||
<td>00 01 ... 58 59</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Fractional Second</b></td>
|
||||
<td>S</td>
|
||||
<td>0 1 ... 8 9</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>SS</td>
|
||||
<td>00 01 ... 98 99</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>SSS</td>
|
||||
<td>000 001 ... 998 999</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>SSSS ... SSSSSSSSS</td>
|
||||
<td>000[0..] 001[0..] ... 998[0..] 999[0..]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Timezone</b></td>
|
||||
<td>z or zz</td>
|
||||
<td>EST CST ... MST PST</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>Z</td>
|
||||
<td>-07:00 -06:00 ... +06:00 +07:00</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>ZZ</td>
|
||||
<td>-0700 -0600 ... +0600 +0700</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Unix Timestamp</b></td>
|
||||
<td>X</td>
|
||||
<td>1360013296</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Unix Millisecond Timestamp</b></td>
|
||||
<td>x</td>
|
||||
<td>1360013296123</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>`;
|
||||
|
37
src/core/lib/Decimal.mjs
Normal file
37
src/core/lib/Decimal.mjs
Normal file
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
* Decimal functions.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Utils from "../Utils.mjs";
|
||||
|
||||
|
||||
/**
|
||||
* Convert a string of decimal values into a byte array.
|
||||
*
|
||||
* @param {string} data
|
||||
* @param {string} [delim]
|
||||
* @returns {byteArray}
|
||||
*
|
||||
* @example
|
||||
* // returns [10,20,30]
|
||||
* fromDecimal("10 20 30");
|
||||
*
|
||||
* // returns [10,20,30]
|
||||
* fromDecimal("10:20:30", "Colon");
|
||||
*/
|
||||
export function fromDecimal(data, delim="Auto") {
|
||||
delim = Utils.charRep(delim);
|
||||
const output = [];
|
||||
let byteStr = data.split(delim);
|
||||
if (byteStr[byteStr.length-1] === "")
|
||||
byteStr = byteStr.slice(0, byteStr.length-1);
|
||||
|
||||
for (let i = 0; i < byteStr.length; i++) {
|
||||
output[i] = parseInt(byteStr[i], 10);
|
||||
}
|
||||
return output;
|
||||
}
|
83
src/core/lib/Delim.mjs
Normal file
83
src/core/lib/Delim.mjs
Normal file
|
@ -0,0 +1,83 @@
|
|||
/**
|
||||
* Various delimiters
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Generic sequence delimiters.
|
||||
*/
|
||||
export const DELIM_OPTIONS = ["Space", "Comma", "Semi-colon", "Colon", "Line feed", "CRLF"];
|
||||
|
||||
/**
|
||||
* Binary sequence delimiters.
|
||||
*/
|
||||
export const BIN_DELIM_OPTIONS = ["Space", "Comma", "Semi-colon", "Colon", "Line feed", "CRLF", "None"];
|
||||
|
||||
/**
|
||||
* Letter sequence delimiters.
|
||||
*/
|
||||
export const LETTER_DELIM_OPTIONS = ["Space", "Line feed", "CRLF", "Forward slash", "Backslash", "Comma", "Semi-colon", "Colon"];
|
||||
|
||||
/**
|
||||
* Word sequence delimiters.
|
||||
*/
|
||||
export const WORD_DELIM_OPTIONS = ["Line feed", "CRLF", "Forward slash", "Backslash", "Comma", "Semi-colon", "Colon"];
|
||||
|
||||
/**
|
||||
* Input sequence delimiters.
|
||||
*/
|
||||
export const INPUT_DELIM_OPTIONS = ["Line feed", "CRLF", "Space", "Comma", "Semi-colon", "Colon", "Nothing (separate chars)"];
|
||||
|
||||
/**
|
||||
* Arithmetic sequence delimiters
|
||||
*/
|
||||
export const ARITHMETIC_DELIM_OPTIONS = ["Line feed", "Space", "Comma", "Semi-colon", "Colon", "CRLF"];
|
||||
|
||||
/**
|
||||
* Hash delimiters
|
||||
*/
|
||||
export const HASH_DELIM_OPTIONS = ["Line feed", "CRLF", "Space", "Comma"];
|
||||
|
||||
/**
|
||||
* IP delimiters
|
||||
*/
|
||||
export const IP_DELIM_OPTIONS = ["Line feed", "CRLF", "Space", "Comma", "Semi-colon"];
|
||||
|
||||
/**
|
||||
* Split delimiters.
|
||||
*/
|
||||
export const SPLIT_DELIM_OPTIONS = [
|
||||
{name: "Comma", value: ","},
|
||||
{name: "Space", value: " "},
|
||||
{name: "Line feed", value: "\\n"},
|
||||
{name: "CRLF", value: "\\r\\n"},
|
||||
{name: "Semi-colon", value: ";"},
|
||||
{name: "Colon", value: ":"},
|
||||
{name: "Nothing (separate chars)", value: ""}
|
||||
];
|
||||
|
||||
/**
|
||||
* Join delimiters.
|
||||
*/
|
||||
export const JOIN_DELIM_OPTIONS = [
|
||||
{name: "Line feed", value: "\\n"},
|
||||
{name: "CRLF", value: "\\r\\n"},
|
||||
{name: "Space", value: " "},
|
||||
{name: "Comma", value: ","},
|
||||
{name: "Semi-colon", value: ";"},
|
||||
{name: "Colon", value: ":"},
|
||||
{name: "Nothing (join chars)", value: ""}
|
||||
];
|
||||
|
||||
/**
|
||||
* RGBA list delimiters.
|
||||
*/
|
||||
export const RGBA_DELIM_OPTIONS = [
|
||||
{name: "Comma", value: ","},
|
||||
{name: "Space", value: " "},
|
||||
{name: "CRLF", value: "\\r\\n"},
|
||||
{name: "Line Feed", value: "\n"}
|
||||
];
|
369
src/core/lib/Enigma.mjs
Normal file
369
src/core/lib/Enigma.mjs
Normal file
|
@ -0,0 +1,369 @@
|
|||
/**
|
||||
* Emulation of the Enigma machine.
|
||||
*
|
||||
* @author s2224834
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
|
||||
/**
|
||||
* Provided default Enigma rotor set.
|
||||
* These are specified as a list of mappings from the letters A through Z in order, optionally
|
||||
* followed by < and a list of letters at which the rotor steps.
|
||||
*/
|
||||
export const ROTORS = [
|
||||
{name: "I", value: "EKMFLGDQVZNTOWYHXUSPAIBRCJ<R"},
|
||||
{name: "II", value: "AJDKSIRUXBLHWTMCQGZNPYFVOE<F"},
|
||||
{name: "III", value: "BDFHJLCPRTXVZNYEIWGAKMUSQO<W"},
|
||||
{name: "IV", value: "ESOVPZJAYQUIRHXLNFTGKDCMWB<K"},
|
||||
{name: "V", value: "VZBRGITYUPSDNHLXAWMJQOFECK<A"},
|
||||
{name: "VI", value: "JPGVOUMFYQBENHZRDKASXLICTW<AN"},
|
||||
{name: "VII", value: "NZJHGRCXMYSWBOUFAIVLPEKQDT<AN"},
|
||||
{name: "VIII", value: "FKQHTLXOCBJSPDZRAMEWNIUYGV<AN"},
|
||||
];
|
||||
|
||||
export const ROTORS_FOURTH = [
|
||||
{name: "Beta", value: "LEYJVCNIXWPBQMDRTAKZGFUHOS"},
|
||||
{name: "Gamma", value: "FSOKANUERHMBTIYCWLQPZXVGJD"},
|
||||
];
|
||||
|
||||
/**
|
||||
* Provided default Enigma reflector set.
|
||||
* These are specified as 13 space-separated transposed pairs covering every letter.
|
||||
*/
|
||||
export const REFLECTORS = [
|
||||
{name: "B", value: "AY BR CU DH EQ FS GL IP JX KN MO TZ VW"},
|
||||
{name: "C", value: "AF BV CP DJ EI GO HY KR LZ MX NW TQ SU"},
|
||||
{name: "B Thin", value: "AE BN CK DQ FU GY HW IJ LO MP RX SZ TV"},
|
||||
{name: "C Thin", value: "AR BD CO EJ FN GT HK IV LM PW QZ SX UY"},
|
||||
];
|
||||
|
||||
export const LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");
|
||||
|
||||
/**
|
||||
* Map a letter to a number in 0..25.
|
||||
*
|
||||
* @param {char} c
|
||||
* @param {boolean} permissive - Case insensitive; don't throw errors on other chars.
|
||||
* @returns {number}
|
||||
*/
|
||||
export function a2i(c, permissive=false) {
|
||||
const i = Utils.ord(c);
|
||||
if (i >= 65 && i <= 90) {
|
||||
return i - 65;
|
||||
}
|
||||
if (permissive) {
|
||||
// Allow case insensitivity
|
||||
if (i >= 97 && i <= 122) {
|
||||
return i - 97;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
throw new OperationError("a2i called on non-uppercase ASCII character");
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a number in 0..25 to a letter.
|
||||
*
|
||||
* @param {number} i
|
||||
* @returns {char}
|
||||
*/
|
||||
export function i2a(i) {
|
||||
if (i >= 0 && i < 26) {
|
||||
return Utils.chr(i+65);
|
||||
}
|
||||
throw new OperationError("i2a called on value outside 0..25");
|
||||
}
|
||||
|
||||
/**
|
||||
* A rotor in the Enigma machine.
|
||||
*/
|
||||
export class Rotor {
|
||||
/**
|
||||
* Rotor constructor.
|
||||
*
|
||||
* @param {string} wiring - A 26 character string of the wiring order.
|
||||
* @param {string} steps - A 0..26 character string of stepping points.
|
||||
* @param {char} ringSetting - The ring setting.
|
||||
* @param {char} initialPosition - The initial position of the rotor.
|
||||
*/
|
||||
constructor(wiring, steps, ringSetting, initialPosition) {
|
||||
if (!/^[A-Z]{26}$/.test(wiring)) {
|
||||
throw new OperationError("Rotor wiring must be 26 unique uppercase letters");
|
||||
}
|
||||
if (!/^[A-Z]{0,26}$/.test(steps)) {
|
||||
throw new OperationError("Rotor steps must be 0-26 unique uppercase letters");
|
||||
}
|
||||
if (!/^[A-Z]$/.test(ringSetting)) {
|
||||
throw new OperationError("Rotor ring setting must be exactly one uppercase letter");
|
||||
}
|
||||
if (!/^[A-Z]$/.test(initialPosition)) {
|
||||
throw new OperationError("Rotor initial position must be exactly one uppercase letter");
|
||||
}
|
||||
this.map = new Array(26);
|
||||
this.revMap = new Array(26);
|
||||
const uniq = {};
|
||||
for (let i=0; i<LETTERS.length; i++) {
|
||||
const a = a2i(LETTERS[i]);
|
||||
const b = a2i(wiring[i]);
|
||||
this.map[a] = b;
|
||||
this.revMap[b] = a;
|
||||
uniq[b] = true;
|
||||
}
|
||||
if (Object.keys(uniq).length !== LETTERS.length) {
|
||||
throw new OperationError("Rotor wiring must have each letter exactly once");
|
||||
}
|
||||
const rs = a2i(ringSetting);
|
||||
this.steps = new Set();
|
||||
for (const x of steps) {
|
||||
this.steps.add(Utils.mod(a2i(x) - rs, 26));
|
||||
}
|
||||
if (this.steps.size !== steps.length) {
|
||||
// This isn't strictly fatal, but it's probably a mistake
|
||||
throw new OperationError("Rotor steps must be unique");
|
||||
}
|
||||
this.pos = Utils.mod(a2i(initialPosition) - rs, 26);
|
||||
}
|
||||
|
||||
/**
|
||||
* Step the rotor forward by one.
|
||||
*/
|
||||
step() {
|
||||
this.pos = Utils.mod(this.pos + 1, 26);
|
||||
return this.pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a character through this rotor forwards.
|
||||
*
|
||||
* @param {number} c - The character.
|
||||
* @returns {number}
|
||||
*/
|
||||
transform(c) {
|
||||
return Utils.mod(this.map[Utils.mod(c + this.pos, 26)] - this.pos, 26);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a character through this rotor backwards.
|
||||
*
|
||||
* @param {number} c - The character.
|
||||
* @returns {number}
|
||||
*/
|
||||
revTransform(c) {
|
||||
return Utils.mod(this.revMap[Utils.mod(c + this.pos, 26)] - this.pos, 26);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for plugboard and reflector (since these do effectively the same
|
||||
* thing).
|
||||
*/
|
||||
class PairMapBase {
|
||||
/**
|
||||
* PairMapBase constructor.
|
||||
*
|
||||
* @param {string} pairs - A whitespace separated string of letter pairs to swap.
|
||||
* @param {string} [name='PairMapBase'] - For errors, the name of this object.
|
||||
*/
|
||||
constructor(pairs, name="PairMapBase") {
|
||||
// I've chosen to make whitespace significant here to make a) code and
|
||||
// b) inputs easier to read
|
||||
this.pairs = pairs;
|
||||
this.map = {};
|
||||
if (pairs === "") {
|
||||
return;
|
||||
}
|
||||
pairs.split(/\s+/).forEach(pair => {
|
||||
if (!/^[A-Z]{2}$/.test(pair)) {
|
||||
throw new OperationError(name + " must be a whitespace-separated list of uppercase letter pairs");
|
||||
}
|
||||
const a = a2i(pair[0]), b = a2i(pair[1]);
|
||||
if (a === b) {
|
||||
// self-stecker
|
||||
return;
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(this.map, a)) {
|
||||
throw new OperationError(`${name} connects ${pair[0]} more than once`);
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(this.map, b)) {
|
||||
throw new OperationError(`${name} connects ${pair[1]} more than once`);
|
||||
}
|
||||
this.map[a] = b;
|
||||
this.map[b] = a;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a character through this object.
|
||||
* Returns other characters unchanged.
|
||||
*
|
||||
* @param {number} c - The character.
|
||||
* @returns {number}
|
||||
*/
|
||||
transform(c) {
|
||||
if (!Object.prototype.hasOwnProperty.call(this.map, c)) {
|
||||
return c;
|
||||
}
|
||||
return this.map[c];
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias for transform, to allow interchangeable use with rotors.
|
||||
*
|
||||
* @param {number} c - The character.
|
||||
* @returns {number}
|
||||
*/
|
||||
revTransform(c) {
|
||||
return this.transform(c);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reflector. PairMapBase but requires that all characters are accounted for.
|
||||
*
|
||||
* Includes a couple of optimisations on that basis.
|
||||
*/
|
||||
export class Reflector extends PairMapBase {
|
||||
/**
|
||||
* Reflector constructor. See PairMapBase.
|
||||
* Additional restriction: every character must be accounted for.
|
||||
*/
|
||||
constructor(pairs) {
|
||||
super(pairs, "Reflector");
|
||||
const s = Object.keys(this.map).length;
|
||||
if (s !== 26) {
|
||||
throw new OperationError("Reflector must have exactly 13 pairs covering every letter");
|
||||
}
|
||||
const optMap = new Array(26);
|
||||
for (const x of Object.keys(this.map)) {
|
||||
optMap[x] = this.map[x];
|
||||
}
|
||||
this.map = optMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a character through this object.
|
||||
*
|
||||
* @param {number} c - The character.
|
||||
* @returns {number}
|
||||
*/
|
||||
transform(c) {
|
||||
return this.map[c];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugboard. Unmodified PairMapBase.
|
||||
*/
|
||||
export class Plugboard extends PairMapBase {
|
||||
/**
|
||||
* Plugboard constructor. See PairMapbase.
|
||||
*/
|
||||
constructor(pairs) {
|
||||
super(pairs, "Plugboard");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for the Enigma machine itself. Holds rotors, a reflector, and a plugboard.
|
||||
*/
|
||||
export class EnigmaBase {
|
||||
/**
|
||||
* EnigmaBase constructor.
|
||||
*
|
||||
* @param {Object[]} rotors - List of Rotors.
|
||||
* @param {Object} reflector - A Reflector.
|
||||
* @param {Plugboard} plugboard - A Plugboard.
|
||||
*/
|
||||
constructor(rotors, reflector, plugboard) {
|
||||
this.rotors = rotors;
|
||||
this.rotorsRev = [].concat(rotors).reverse();
|
||||
this.reflector = reflector;
|
||||
this.plugboard = plugboard;
|
||||
}
|
||||
|
||||
/**
|
||||
* Step the rotors forward by one.
|
||||
*
|
||||
* This happens before the output character is generated.
|
||||
*
|
||||
* Note that rotor 4, if it's there, never steps.
|
||||
*
|
||||
* Why is all the logic in EnigmaBase and not a nice neat method on
|
||||
* Rotor that knows when it should advance the next item?
|
||||
* Because the double stepping anomaly is a thing. tl;dr if the left rotor
|
||||
* should step the next time the middle rotor steps, the middle rotor will
|
||||
* immediately step.
|
||||
*/
|
||||
step() {
|
||||
const r0 = this.rotors[0];
|
||||
const r1 = this.rotors[1];
|
||||
r0.step();
|
||||
// The second test here is the double-stepping anomaly
|
||||
if (r0.steps.has(r0.pos) || r1.steps.has(Utils.mod(r1.pos + 1, 26))) {
|
||||
r1.step();
|
||||
if (r1.steps.has(r1.pos)) {
|
||||
const r2 = this.rotors[2];
|
||||
r2.step();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt (or decrypt) some data.
|
||||
* Takes an arbitrary string and runs the Engima machine on that data from
|
||||
* *its current state*, and outputs the result. Non-alphabetic characters
|
||||
* are returned unchanged.
|
||||
*
|
||||
* @param {string} input - Data to encrypt.
|
||||
* @returns {string}
|
||||
*/
|
||||
crypt(input) {
|
||||
let result = "";
|
||||
for (const c of input) {
|
||||
let letter = a2i(c, true);
|
||||
if (letter === -1) {
|
||||
result += c;
|
||||
continue;
|
||||
}
|
||||
// First, step the rotors forward.
|
||||
this.step();
|
||||
// Now, run through the plugboard.
|
||||
letter = this.plugboard.transform(letter);
|
||||
// Then through each wheel in sequence, through the reflector, and
|
||||
// backwards through the wheels again.
|
||||
for (const rotor of this.rotors) {
|
||||
letter = rotor.transform(letter);
|
||||
}
|
||||
letter = this.reflector.transform(letter);
|
||||
for (const rotor of this.rotorsRev) {
|
||||
letter = rotor.revTransform(letter);
|
||||
}
|
||||
// Finally, back through the plugboard.
|
||||
letter = this.plugboard.revTransform(letter);
|
||||
result += i2a(letter);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The Enigma machine itself. Holds 3-4 rotors, a reflector, and a plugboard.
|
||||
*/
|
||||
export class EnigmaMachine extends EnigmaBase {
|
||||
/**
|
||||
* EnigmaMachine constructor.
|
||||
*
|
||||
* @param {Object[]} rotors - List of Rotors.
|
||||
* @param {Object} reflector - A Reflector.
|
||||
* @param {Plugboard} plugboard - A Plugboard.
|
||||
*/
|
||||
constructor(rotors, reflector, plugboard) {
|
||||
super(rotors, reflector, plugboard);
|
||||
if (rotors.length !== 3 && rotors.length !== 4) {
|
||||
throw new OperationError("Enigma must have 3 or 4 rotors");
|
||||
}
|
||||
}
|
||||
}
|
64
src/core/lib/Extract.mjs
Normal file
64
src/core/lib/Extract.mjs
Normal file
|
@ -0,0 +1,64 @@
|
|||
/**
|
||||
* Identifier extraction functions
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Runs search operations across the input data using regular expressions.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {RegExp} searchRegex
|
||||
* @param {RegExp} [removeRegex=null] - A regular expression defining results to remove from the
|
||||
* final list
|
||||
* @param {Function} [sortBy=null] - The sorting comparison function to apply
|
||||
* @param {boolean} [unique=false] - Whether to unique the results
|
||||
* @returns {string}
|
||||
*/
|
||||
export function search(input, searchRegex, removeRegex=null, sortBy=null, unique=false) {
|
||||
let results = [];
|
||||
let match;
|
||||
|
||||
while ((match = searchRegex.exec(input))) {
|
||||
// Moves pointer when an empty string is matched (prevents infinite loop)
|
||||
if (match.index === searchRegex.lastIndex) {
|
||||
searchRegex.lastIndex++;
|
||||
}
|
||||
|
||||
if (removeRegex && removeRegex.test(match[0]))
|
||||
continue;
|
||||
|
||||
results.push(match[0]);
|
||||
}
|
||||
|
||||
if (sortBy) {
|
||||
results = results.sort(sortBy);
|
||||
}
|
||||
|
||||
if (unique) {
|
||||
results = results.unique();
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* URL regular expression
|
||||
*/
|
||||
const protocol = "[A-Z]+://",
|
||||
hostname = "[-\\w]+(?:\\.\\w[-\\w]*)+",
|
||||
port = ":\\d+",
|
||||
path = "/[^.!,?\"<>\\[\\]{}\\s\\x7F-\\xFF]*" +
|
||||
"(?:[.!,?]+[^.!,?\"<>\\[\\]{}\\s\\x7F-\\xFF]+)*";
|
||||
|
||||
export const URL_REGEX = new RegExp(protocol + hostname + "(?:" + port + ")?(?:" + path + ")?", "ig");
|
||||
|
||||
|
||||
/**
|
||||
* Domain name regular expression
|
||||
*/
|
||||
export const DOMAIN_REGEX = /\b((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}\b/ig;
|
4191
src/core/lib/FileSignatures.mjs
Normal file
4191
src/core/lib/FileSignatures.mjs
Normal file
File diff suppressed because it is too large
Load diff
267
src/core/lib/FileType.mjs
Normal file
267
src/core/lib/FileType.mjs
Normal file
|
@ -0,0 +1,267 @@
|
|||
/**
|
||||
* File type functions
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*
|
||||
*/
|
||||
import {FILE_SIGNATURES} from "./FileSignatures.mjs";
|
||||
import {sendStatusMessage} from "../Utils.mjs";
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether a signature matches a buffer.
|
||||
*
|
||||
* @param {Object|Object[]} sig - A dictionary of offsets with values assigned to them.
|
||||
* These values can be numbers for static checks, arrays of potential valid matches,
|
||||
* or bespoke functions to check the validity of the buffer value at that offset.
|
||||
* @param {Uint8Array} buf
|
||||
* @param {number} [offset=0] Where in the buffer to start searching from
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function signatureMatches(sig, buf, offset=0) {
|
||||
// Using a length check seems to be more performant than `sig instanceof Array`
|
||||
if (sig.length) {
|
||||
// sig is an Array - return true if any of them match
|
||||
// The following `reduce` method is nice, but performance matters here, so we
|
||||
// opt for a faster, if less elegant, for loop.
|
||||
// return sig.reduce((acc, s) => acc || bytesMatch(s, buf, offset), false);
|
||||
for (let i = 0; i < sig.length; i++) {
|
||||
if (bytesMatch(sig[i], buf, offset)) return true;
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return bytesMatch(sig, buf, offset);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether a set of bytes match the given buffer.
|
||||
*
|
||||
* @param {Object} sig - A dictionary of offsets with values assigned to them.
|
||||
* These values can be numbers for static checks, arrays of potential valid matches,
|
||||
* or bespoke functions to check the validity of the buffer value at that offset.
|
||||
* @param {Uint8Array} buf
|
||||
* @param {number} [offset=0] Where in the buffer to start searching from
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function bytesMatch(sig, buf, offset=0) {
|
||||
for (const sigoffset in sig) {
|
||||
const pos = parseInt(sigoffset, 10) + offset;
|
||||
switch (typeof sig[sigoffset]) {
|
||||
case "number": // Static check
|
||||
if (buf[pos] !== sig[sigoffset])
|
||||
return false;
|
||||
break;
|
||||
case "object": // Array of options
|
||||
if (sig[sigoffset].indexOf(buf[pos]) < 0)
|
||||
return false;
|
||||
break;
|
||||
case "function": // More complex calculation
|
||||
if (!sig[sigoffset](buf[pos]))
|
||||
return false;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unrecognised signature type at offset ${sigoffset}`);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Given a buffer, detects magic byte sequences at specific positions and returns the
|
||||
* extension and mime type.
|
||||
*
|
||||
* @param {Uint8Array|ArrayBuffer} buf
|
||||
* @param {string[]} [categories=All] - Which categories of file to look for
|
||||
* @returns {Object[]} types
|
||||
* @returns {string} type.name - Name of file type
|
||||
* @returns {string} type.ext - File extension
|
||||
* @returns {string} type.mime - Mime type
|
||||
* @returns {string} [type.desc] - Description
|
||||
*/
|
||||
export function detectFileType(buf, categories=Object.keys(FILE_SIGNATURES)) {
|
||||
if (buf instanceof ArrayBuffer) {
|
||||
buf = new Uint8Array(buf);
|
||||
}
|
||||
|
||||
if (!(buf && buf.length > 1)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const matchingFiles = [];
|
||||
const signatures = {};
|
||||
|
||||
for (const cat in FILE_SIGNATURES) {
|
||||
if (categories.includes(cat)) {
|
||||
signatures[cat] = FILE_SIGNATURES[cat];
|
||||
}
|
||||
}
|
||||
|
||||
for (const cat in signatures) {
|
||||
const category = signatures[cat];
|
||||
|
||||
category.forEach(filetype => {
|
||||
if (signatureMatches(filetype.signature, buf)) {
|
||||
matchingFiles.push(filetype);
|
||||
}
|
||||
});
|
||||
}
|
||||
return matchingFiles;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Given a buffer, searches for magic byte sequences at all possible positions and returns
|
||||
* the extensions and mime types.
|
||||
*
|
||||
* @param {Uint8Array} buf
|
||||
* @param {string[]} [categories=All] - Which categories of file to look for
|
||||
* @returns {Object[]} foundFiles
|
||||
* @returns {number} foundFiles.offset - The position in the buffer at which this file was found
|
||||
* @returns {Object} foundFiles.fileDetails
|
||||
* @returns {string} foundFiles.fileDetails.name - Name of file type
|
||||
* @returns {string} foundFiles.fileDetails.ext - File extension
|
||||
* @returns {string} foundFiles.fileDetails.mime - Mime type
|
||||
* @returns {string} [foundFiles.fileDetails.desc] - Description
|
||||
*/
|
||||
export function scanForFileTypes(buf, categories=Object.keys(FILE_SIGNATURES)) {
|
||||
if (!(buf && buf.length > 1)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const foundFiles = [];
|
||||
const signatures = {};
|
||||
|
||||
for (const cat in FILE_SIGNATURES) {
|
||||
if (categories.includes(cat)) {
|
||||
signatures[cat] = FILE_SIGNATURES[cat];
|
||||
}
|
||||
}
|
||||
|
||||
for (const cat in signatures) {
|
||||
const category = signatures[cat];
|
||||
|
||||
for (let i = 0; i < category.length; i++) {
|
||||
const filetype = category[i];
|
||||
const sigs = filetype.signature.length ? filetype.signature : [filetype.signature];
|
||||
|
||||
sigs.forEach(sig => {
|
||||
let pos = 0;
|
||||
while ((pos = locatePotentialSig(buf, sig, pos)) >= 0) {
|
||||
if (bytesMatch(sig, buf, pos)) {
|
||||
sendStatusMessage(`Found potential signature for ${filetype.name} at pos ${pos}`);
|
||||
foundFiles.push({
|
||||
offset: pos,
|
||||
fileDetails: filetype
|
||||
});
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Return found files in order of increasing offset
|
||||
return foundFiles.sort((a, b) => {
|
||||
return a.offset - b.offset;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fastcheck function to quickly scan the buffer for the first byte in a signature.
|
||||
*
|
||||
* @param {Uint8Array} buf - The buffer to search
|
||||
* @param {Object} sig - A single signature object (Not an array of signatures)
|
||||
* @param {number} offset - Where to start search from
|
||||
* @returns {number} The position of the match or -1 if one cannot be found.
|
||||
*/
|
||||
function locatePotentialSig(buf, sig, offset) {
|
||||
// Find values for first key and value in sig
|
||||
const k = parseInt(Object.keys(sig)[0], 10);
|
||||
const v = Object.values(sig)[0];
|
||||
switch (typeof v) {
|
||||
case "number":
|
||||
return buf.indexOf(v, offset + k) - k;
|
||||
case "object":
|
||||
for (let i = offset + k; i < buf.length; i++) {
|
||||
if (v.indexOf(buf[i]) >= 0) return i - k;
|
||||
}
|
||||
return -1;
|
||||
case "function":
|
||||
for (let i = offset + k; i < buf.length; i++) {
|
||||
if (v(buf[i])) return i - k;
|
||||
}
|
||||
return -1;
|
||||
default:
|
||||
throw new Error("Unrecognised signature type");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Detects whether the given buffer is a file of the type specified.
|
||||
*
|
||||
* @param {string|RegExp} type
|
||||
* @param {Uint8Array|ArrayBuffer} buf
|
||||
* @returns {string|false} The mime type or false if the type does not match
|
||||
*/
|
||||
export function isType(type, buf) {
|
||||
const types = detectFileType(buf);
|
||||
|
||||
if (!types.length) return false;
|
||||
|
||||
if (typeof type === "string") {
|
||||
return types.reduce((acc, t) => {
|
||||
const mime = t.mime.startsWith(type) ? t.mime : false;
|
||||
return acc || mime;
|
||||
}, false);
|
||||
} else if (type instanceof RegExp) {
|
||||
return types.reduce((acc, t) => {
|
||||
const mime = type.test(t.mime) ? t.mime : false;
|
||||
return acc || mime;
|
||||
}, false);
|
||||
} else {
|
||||
throw new Error("Invalid type input.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Detects whether the given buffer contains an image file.
|
||||
*
|
||||
* @param {Uint8Array|ArrayBuffer} buf
|
||||
* @returns {string|false} The mime type or false if the type does not match
|
||||
*/
|
||||
export function isImage(buf) {
|
||||
return isType("image", buf);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Attempts to extract a file from a data stream given its offset and extractor function.
|
||||
*
|
||||
* @param {Uint8Array} bytes
|
||||
* @param {Object} fileDetail
|
||||
* @param {string} fileDetail.mime
|
||||
* @param {string} fileDetail.extension
|
||||
* @param {Function} fileDetail.extractor
|
||||
* @param {number} offset
|
||||
* @returns {File}
|
||||
*/
|
||||
export function extractFile(bytes, fileDetail, offset) {
|
||||
if (fileDetail.extractor) {
|
||||
sendStatusMessage(`Attempting to extract ${fileDetail.name} at pos ${offset}...`);
|
||||
const fileData = fileDetail.extractor(bytes, offset);
|
||||
const ext = fileDetail.extension.split(",")[0];
|
||||
return new File([fileData], `extracted_at_0x${offset.toString(16)}.${ext}`, {
|
||||
type: fileDetail.mime
|
||||
});
|
||||
}
|
||||
|
||||
throw new Error(`No extraction algorithm available for "${fileDetail.mime}" files`);
|
||||
}
|
20
src/core/lib/FlowControl.mjs
Normal file
20
src/core/lib/FlowControl.mjs
Normal file
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* Flow control functions
|
||||
*
|
||||
* @author d98762625 [d98762625@gmail.com]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns the index of a label.
|
||||
*
|
||||
* @param {Object} state - The current state of the recipe.
|
||||
* @param {string} name - The label name to look for.
|
||||
* @returns {number}
|
||||
*/
|
||||
export function getLabelIndex(name, state) {
|
||||
return state.opList.findIndex((operation) => {
|
||||
return (operation.name === "Label") && (name === operation.ingValues[0]);
|
||||
});
|
||||
}
|
253
src/core/lib/FuzzyMatch.mjs
Normal file
253
src/core/lib/FuzzyMatch.mjs
Normal file
|
@ -0,0 +1,253 @@
|
|||
/**
|
||||
* LICENSE
|
||||
*
|
||||
* This software is dual-licensed to the public domain and under the following
|
||||
* license: you are granted a perpetual, irrevocable license to copy, modify,
|
||||
* publish, and distribute this file as you see fit.
|
||||
*
|
||||
* VERSION
|
||||
* 0.1.0 (2016-03-28) Initial release
|
||||
*
|
||||
* AUTHOR
|
||||
* Forrest Smith
|
||||
*
|
||||
* CONTRIBUTORS
|
||||
* J<EFBFBD>rgen Tjern<EFBFBD> - async helper
|
||||
* Anurag Awasthi - updated to 0.2.0
|
||||
*/
|
||||
|
||||
export const DEFAULT_WEIGHTS = {
|
||||
sequentialBonus: 15, // bonus for adjacent matches
|
||||
separatorBonus: 30, // bonus if match occurs after a separator
|
||||
camelBonus: 30, // bonus if match is uppercase and prev is lower
|
||||
firstLetterBonus: 15, // bonus if the first letter is matched
|
||||
|
||||
leadingLetterPenalty: -5, // penalty applied for every letter in str before the first match
|
||||
maxLeadingLetterPenalty: -15, // maximum penalty for leading letters
|
||||
unmatchedLetterPenalty: -1
|
||||
};
|
||||
|
||||
/**
|
||||
* Does a fuzzy search to find pattern inside a string.
|
||||
* @param {string} pattern pattern to search for
|
||||
* @param {string} str string which is being searched
|
||||
* @param {boolean} global whether to search for all matches or just one
|
||||
* @returns [boolean, number] a boolean which tells if pattern was
|
||||
* found or not and a search score
|
||||
*/
|
||||
export function fuzzyMatch(pattern, str, global=false, weights=DEFAULT_WEIGHTS) {
|
||||
const recursionCount = 0;
|
||||
const recursionLimit = 10;
|
||||
const matches = [];
|
||||
const maxMatches = 256;
|
||||
|
||||
if (!global) {
|
||||
return fuzzyMatchRecursive(
|
||||
pattern,
|
||||
str,
|
||||
0 /* patternCurIndex */,
|
||||
0 /* strCurrIndex */,
|
||||
null /* srcMatches */,
|
||||
matches,
|
||||
maxMatches,
|
||||
0 /* nextMatch */,
|
||||
recursionCount,
|
||||
recursionLimit,
|
||||
weights
|
||||
);
|
||||
}
|
||||
|
||||
// Return all matches
|
||||
let foundMatch = true,
|
||||
score,
|
||||
idxs,
|
||||
strCurrIndex = 0;
|
||||
const results = [];
|
||||
|
||||
while (foundMatch) {
|
||||
[foundMatch, score, idxs] = fuzzyMatchRecursive(
|
||||
pattern,
|
||||
str,
|
||||
0 /* patternCurIndex */,
|
||||
strCurrIndex,
|
||||
null /* srcMatches */,
|
||||
matches,
|
||||
maxMatches,
|
||||
0 /* nextMatch */,
|
||||
recursionCount,
|
||||
recursionLimit,
|
||||
weights
|
||||
);
|
||||
if (foundMatch) results.push([foundMatch, score, [...idxs]]);
|
||||
strCurrIndex = idxs[idxs.length - 1] + 1;
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive helper function
|
||||
*/
|
||||
function fuzzyMatchRecursive(
|
||||
pattern,
|
||||
str,
|
||||
patternCurIndex,
|
||||
strCurrIndex,
|
||||
srcMatches,
|
||||
matches,
|
||||
maxMatches,
|
||||
nextMatch,
|
||||
recursionCount,
|
||||
recursionLimit,
|
||||
weights
|
||||
) {
|
||||
let outScore = 0;
|
||||
|
||||
// Return if recursion limit is reached.
|
||||
if (++recursionCount >= recursionLimit) {
|
||||
return [false, outScore, []];
|
||||
}
|
||||
|
||||
// Return if we reached ends of strings.
|
||||
if (patternCurIndex === pattern.length || strCurrIndex === str.length) {
|
||||
return [false, outScore, []];
|
||||
}
|
||||
|
||||
// Recursion params
|
||||
let recursiveMatch = false;
|
||||
let bestRecursiveMatches = [];
|
||||
let bestRecursiveScore = 0;
|
||||
|
||||
// Loop through pattern and str looking for a match.
|
||||
let firstMatch = true;
|
||||
while (patternCurIndex < pattern.length && strCurrIndex < str.length) {
|
||||
// Match found.
|
||||
if (
|
||||
pattern[patternCurIndex].toLowerCase() === str[strCurrIndex].toLowerCase()
|
||||
) {
|
||||
if (nextMatch >= maxMatches) {
|
||||
return [false, outScore, []];
|
||||
}
|
||||
|
||||
if (firstMatch && srcMatches) {
|
||||
matches = [...srcMatches];
|
||||
firstMatch = false;
|
||||
}
|
||||
|
||||
const [matched, recursiveScore, recursiveMatches] = fuzzyMatchRecursive(
|
||||
pattern,
|
||||
str,
|
||||
patternCurIndex,
|
||||
strCurrIndex + 1,
|
||||
matches,
|
||||
recursiveMatches,
|
||||
maxMatches,
|
||||
nextMatch,
|
||||
recursionCount,
|
||||
recursionLimit,
|
||||
weights
|
||||
);
|
||||
|
||||
if (matched) {
|
||||
// Pick best recursive score.
|
||||
if (!recursiveMatch || recursiveScore > bestRecursiveScore) {
|
||||
bestRecursiveMatches = [...recursiveMatches];
|
||||
bestRecursiveScore = recursiveScore;
|
||||
}
|
||||
recursiveMatch = true;
|
||||
}
|
||||
|
||||
matches[nextMatch++] = strCurrIndex;
|
||||
++patternCurIndex;
|
||||
}
|
||||
++strCurrIndex;
|
||||
}
|
||||
|
||||
const matched = patternCurIndex === pattern.length;
|
||||
|
||||
if (matched) {
|
||||
outScore = 100;
|
||||
|
||||
// Apply leading letter penalty
|
||||
let penalty = weights.leadingLetterPenalty * matches[0];
|
||||
penalty =
|
||||
penalty < weights.maxLeadingLetterPenalty ?
|
||||
weights.maxLeadingLetterPenalty :
|
||||
penalty;
|
||||
outScore += penalty;
|
||||
|
||||
// Apply unmatched penalty
|
||||
const unmatched = str.length - nextMatch;
|
||||
outScore += weights.unmatchedLetterPenalty * unmatched;
|
||||
|
||||
// Apply ordering bonuses
|
||||
for (let i = 0; i < nextMatch; i++) {
|
||||
const currIdx = matches[i];
|
||||
|
||||
if (i > 0) {
|
||||
const prevIdx = matches[i - 1];
|
||||
if (currIdx === prevIdx + 1) {
|
||||
outScore += weights.sequentialBonus;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for bonuses based on neighbor character value.
|
||||
if (currIdx > 0) {
|
||||
// Camel case
|
||||
const neighbor = str[currIdx - 1];
|
||||
const curr = str[currIdx];
|
||||
if (
|
||||
neighbor !== neighbor.toUpperCase() &&
|
||||
curr !== curr.toLowerCase()
|
||||
) {
|
||||
outScore += weights.camelBonus;
|
||||
}
|
||||
const isNeighbourSeparator = neighbor === "_" || neighbor === " ";
|
||||
if (isNeighbourSeparator) {
|
||||
outScore += weights.separatorBonus;
|
||||
}
|
||||
} else {
|
||||
// First letter
|
||||
outScore += weights.firstLetterBonus;
|
||||
}
|
||||
}
|
||||
|
||||
// Return best result
|
||||
if (recursiveMatch && (!matched || bestRecursiveScore > outScore)) {
|
||||
// Recursive score is better than "this"
|
||||
matches = bestRecursiveMatches;
|
||||
outScore = bestRecursiveScore;
|
||||
return [true, outScore, matches];
|
||||
} else if (matched) {
|
||||
// "this" score is better than recursive
|
||||
return [true, outScore, matches];
|
||||
} else {
|
||||
return [false, outScore, matches];
|
||||
}
|
||||
}
|
||||
return [false, outScore, matches];
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns a list of match indexes into a list of match ranges
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @param [number] matches
|
||||
* @returns [[number]]
|
||||
*/
|
||||
export function calcMatchRanges(matches) {
|
||||
const ranges = [];
|
||||
let start = matches[0],
|
||||
curr = start;
|
||||
|
||||
matches.forEach(m => {
|
||||
if (m === curr || m === curr + 1) curr = m;
|
||||
else {
|
||||
ranges.push([start, curr - start + 1]);
|
||||
start = m;
|
||||
curr = m;
|
||||
}
|
||||
});
|
||||
|
||||
ranges.push([start, curr - start + 1]);
|
||||
return ranges;
|
||||
}
|
28
src/core/lib/Hash.mjs
Normal file
28
src/core/lib/Hash.mjs
Normal file
|
@ -0,0 +1,28 @@
|
|||
/**
|
||||
* Hashing resources.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
*
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Utils from "../Utils.mjs";
|
||||
import CryptoApi from "crypto-api/src/crypto-api.mjs";
|
||||
|
||||
|
||||
/**
|
||||
* Generic hash function.
|
||||
*
|
||||
* @param {string} name
|
||||
* @param {ArrayBuffer} input
|
||||
* @param {Object} [options={}]
|
||||
* @returns {string}
|
||||
*/
|
||||
export function runHash(name, input, options={}) {
|
||||
const msg = Utils.arrayBufferToStr(input, false),
|
||||
hasher = CryptoApi.getHasher(name, options);
|
||||
hasher.update(msg);
|
||||
return CryptoApi.encoder.toHex(hasher.finalize());
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue