diff --git a/plugins/srktoolbox/.cspell.json b/plugins/srktoolbox/.cspell.json new file mode 100644 index 00000000..66fca29b --- /dev/null +++ b/plugins/srktoolbox/.cspell.json @@ -0,0 +1,17 @@ +{ + "version": "0.2", + "language": "en,en-gb", + "words": [], + "dictionaries": [ + "npm", + "softwareTerms", + "node", + "html", + "css", + "bash", + "en-gb", + "misc" + ], + "ignorePaths": ["package.json", "package-lock.json", "node_modules"] + } + \ No newline at end of file diff --git a/plugins/srktoolbox/.editorconfig b/plugins/srktoolbox/.editorconfig new file mode 100644 index 00000000..cef4cab0 --- /dev/null +++ b/plugins/srktoolbox/.editorconfig @@ -0,0 +1,18 @@ +# 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 + +[.github/**.yml] +indent_style = space +indent_size = 2 diff --git a/plugins/srktoolbox/.gitattributes b/plugins/srktoolbox/.gitattributes new file mode 100644 index 00000000..6313b56c --- /dev/null +++ b/plugins/srktoolbox/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/plugins/srktoolbox/.gitignore b/plugins/srktoolbox/.gitignore new file mode 100644 index 00000000..42923f5d --- /dev/null +++ b/plugins/srktoolbox/.gitignore @@ -0,0 +1,15 @@ +node_modules +npm-debug.log +travis.log +build +.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 diff --git a/plugins/srktoolbox/.npmignore b/plugins/srktoolbox/.npmignore new file mode 100644 index 00000000..05ab5f52 --- /dev/null +++ b/plugins/srktoolbox/.npmignore @@ -0,0 +1,7 @@ +node_modules +npm-debug.log +travis.log +build/* +!build/node +.vscode +.github diff --git a/plugins/srktoolbox/.nvmrc b/plugins/srktoolbox/.nvmrc new file mode 100644 index 00000000..3c032078 --- /dev/null +++ b/plugins/srktoolbox/.nvmrc @@ -0,0 +1 @@ +18 diff --git a/plugins/srktoolbox/CHANGELOG.md b/plugins/srktoolbox/CHANGELOG.md new file mode 100644 index 00000000..960e922b --- /dev/null +++ b/plugins/srktoolbox/CHANGELOG.md @@ -0,0 +1,1013 @@ +# Changelog + +## Versioning + +CyberChef uses the [semver](https://semver.org/) system to manage versioning: `..`. + +- 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.22.0] - 2026-02-11 +- Separate npm publish out into separate job and run with Node 24.5 [@GCHQDeveloper581] | [#2188] +- Fixed Percent delimiter for hex encoding [@beneri] [@C85297] | [#2137] +- Added the ability to paste one or more Images from the Clipboard [@t-martine] [@a3957273] [@C85297] | [#1876] +- Quoted Printable - consistent reference to 'email' [@wesinator] | [#2186] +- Fix freeze when output text decoding fails [@Raka-loah] | [#1573] +- Update Browserslist DB [@C85297] | [#2183] +- Add contents write permission to releases workflow [@C85297] | [#2182] +- Fix release workflow permissions [@C85297] | [#2181] + +### [10.21.0] - 2026-02-05 +- Fix import operations with special chars in them [@d98762625] [@jg42526] | [#1040] +- Remove custom CodeQL workflow [@C85297] | [#2176] +- Fix code scanning warnings in workflows [@GCHQDeveloper581] | [#2177] +- Use NPM trusted publishing [@C85297] [@GCHQDeveloper581] | [#2174] +- Fix: Correctly parse xxd odd byte hexdumps [@ThomasNotTom] [@GCHQDeveloper581] | [#2058] +- Update Sitemap URLs to Use Valid Paths in sitemap.mjs [@rbpi] [@C85297] | [#1861] +- Use recommended GitHub Actions to build image [@AlexGustafsson] [@C85297] | [#2055] +- Remove version 10 message from banner [@C85297] | [#2169] +- Bump form-data from 4.0.1 to 4.0.5 | [#2175] +- Bump node-forge from 1.3.1 to 1.3.3 | [#2173] +- Update crypto browserify [@C85297] | [#2172] +- Update kbpgp package (resolves #2135) [@GCHQDeveloper581] | [#2136] +- Fix the processing of ALPNs for JA4 to align with new specification update [@tuliperis] | [#2165] +- Add Bech32 and Bech32m encoding/decoding operations [@thomasxm] | [#2159] +- Exclude Delete character from hex dump output [@mikecat] [@C85297] | [#2086] +- Tiny typo fix in "To Base85" operation [@twostraws] | [#2118] +- Bump jsonpath-plus [@C85297] | [#2166] + +### [10.20.0] - 2026-01-28 +- Fixed Optical Character Recognition and added tests [@n1474335] | [ab37c1e] +- Fixed JA4 version fallback value [@n1474335] | [7a5225c] +- Updated chromedriver [@n1474335] | [0e82e4b] +- Fixed RSA Sign and Verify character encodings [@n1474335] | [895a929] +- Updated chromedriver [@n1474335] | [d3adfc7] +- Added message format arg to RSA Verify operation [@n1474335] | [47c85a1] +- Add operation for parsing X.509 CRLs [@robinsandhu] | [#1887] +- Fix typo in description of JWT Sign recipe [@GuilhermoReadonly] | [#1961] +- Corrected path to generateNodeIndex.mjs [@simonarnell] | [#1959] +- Add 'header' ingredient to JWT Sign operation [@RandomByte] | [#1957] +- Add Parse TLS record operation [@c65722] | [#1936] +- Automatically detect chrome driver version [@gchq] | [#1972] +- Add Strip UDP header operation [@c65722] | [#1900] +- Add Strip TCP header operation [@c65722] | [#1898] +- Webpack compress with gzip and brotli [@max0x53] | [#1955] +- add offset field to 'Add Line Numbers' operation [@Adamkadaban] | [#1866] +- Disable flakey URL test [@a3957273] | [#1973] +- Add Strip IPv4 header operation [@c65722] | [#1899] +- IPv6 Transition Operation [@jb30795] | [#1780] +- fix: Blowfish - ignore IV length in ECB mode [@FranciscoPombal] | [#1902] +- Add 'Drop nth bytes' operation [@Oshawk] | [#1914] +- Add 'Take nth bytes' operation [@Oshawk] | [#1915] +- Add Leet Speak [@bartblaze] | [#1971] +- Fix Generate TOTP & HOPT [@exactlyaron] | [#1966] +- Updated luhn checksum operation to work with different bases [@k3ach] | [#1933] +- automatically theme mode based on user preference [@vs4vijay] | [#1921] +- fix: DES/Triple DES - misleading error messages [@FranciscoPombal] | [#1904] +- fix: ROT13 - shifting numbers by negative amounts [@FranciscoPombal] | [#1903] +- Introduce Yubico's Modhex for Conversion [@linuxgemini] | [#1105] +- Feature: MIME RFC2047 Decoding [@MShwed] | [#630] +- CC-1889 add _ option [@depperm] | [#1977] +- chore(root): add cspell [@evenstensberg] | [#1976] +- Preserve uppercase for Leet Speak [@bartblaze] | [#1981] +- Load the user's preferred color scheme if the URL contains an invalid theme [@0xh3xa] | [#2007] +- Add SM2 Encrypt and Decrypt Operations [@flakjacket95] | [#1909] +- Support jq as an operation. [@zhzy0077] | [#1604] +- Add fingerprints to the 'Parse X.509 certificate' operation [@JSCU-CNI] | [#1863] +- Added a JSON to YAML and a YAML to JSON operation [@ccarpo] | [#1286] +- Add CRC Operation [@r4mos] | [#1993] +- Bug Fix: selected theme not loading when refreshing [@0xh3xa] | [#2006] +- Fix(RecipeWaiter): sanitize user input in addOperation to prevent XSS [@0xh3xa] | [#2014] +- Docker multiplatform build support [@PathToLife] | [#1974] +- Add Base32 Hex Extended Alphabet and Base32 Tests. [@peterc-s] | [#1991] +- Add ECB/NoPadding and CBC/NoPadding support to AES encryption [@plvie] | [#2013] +- Add new operation: PHP Serialize [@brun0ne] | [#1548] +- Push input through postmessage [@kenduguay1] | [#1992] +- Add jsonata query operation [@jonking-ajar] | [#1587] +- Re-enable Npm Release in github workflows [@PathToLife] | [#2031] +- Add to ECDSA Verify the message format [@r4mos] | [#2027] +- Added alternating caps functionality [@sw5678] | [#1897] +- XOR Checksum operation added [@jg42526] | [#2035] +- Add GenerateAllChecksums operation * Remove checksums from GenerateAllHashes operation [@es45411] | [66d445c] +- Update GenerateAllChecksums infoURL [@es45411] | [#2037] +- Add toggle "+" character to URLDecode operation [@es45411] | [#2040] +- Workaround for Safari load bug [@GCHQDeveloper94872] | [#2038] +- Updated Dockerfile to correctly build on ARM64 platforms [@Sma-Das] | [#2042] +- Addresses bug report #2008 Added explicit support for octal IP addresses. Changed approach to IPv4 regex to be string manipulation generated. Added some unit tests for IP address parsing - probably not full coverage. Added lookahead and lookbehind tricks to resolve warned issue that 1.2.3.256 would still be extracted as 1.2.3.25. Now only accepts valid IP addresses. Warning replaced with clause about infinite length dotted decimal forms. [@gchqdev364] | [#2041] +- Remove trim from rail fence [@Odyhibit] | [#1986] +- Fix email regex [@ericli-splunk] | [#2025] +- Add Blake3 hashing [@xumptex] | [#2023] +- Use defaultIndex instead of 0 in transformArgs [@bartvanandel] | [#2015] +- Add "Generate UUID" and "Analyse UUID" operations [@bartvanandel] | [#2011] +- Add new operation: Template [@kendallgoto] | [#2021] +- Add more clear build instructions [@remingtr] | [#1873] +- Show On Map updated to use leaflet over WikiMedia [@0xff1ce] | [#1884] +- Fixed ToDecimal signed logic [@starplanet] | [#1545] +- Use BigInt for encoding/decoding VarInt [@mikecat] | [#1978] + +### [10.19.0] - 2024-06-21 +- Add support for ECDSA and DSA in 'Parse CSR' [@robinsandhu] | [#1828] +- Fix typos in SIGABA.mjs [@eltociear] | [#1834] + +### [10.18.0] - 2024-04-24 +- Added 'XXTEA Encrypt' and 'XXTEA Decrypt' operations [@n1474335] | [0a353ee] + +### [10.17.0] - 2024-04-13 +- Fix unit test 'expectOutput' implementation [@zb3] | [#1783] +- Add accessibility labels for icons [@e218736] | [#1743] +- Add focus styling for keyboard navigation [@e218736] | [#1739] +- Add support for operation option hiding [@TheZ3ro] | [#541] +- Improve efficiency of RAKE implementation [@sw5678] | [#1751] +- Require (a, 26) to be coprime in 'Affine Encode' [@EvieHarv] | [#1788] +- Added 'JWK to PEM' operation [@cplussharp] | [#1277] +- Added 'PEM to JWK' operation [@cplussharp] | [#1277] +- Added 'Public Key from Certificate' operation [@cplussharp] | [#1642] +- Added 'Public Key from Private Key' operation [@cplussharp] | [#1642] + +### [10.16.0] - 2024-04-12 +- Added 'JA4Server Fingerprint' operation [@n1474335] | [#1789] + +### [10.15.0] - 2024-04-02 +- Fix Ciphersaber2 key concatenation [@zb3] | [#1765] +- Fix DeriveEVPKey's array parsing [@zb3] | [#1767] +- Fix JWT operations [@a3957273] | [#1769] +- Added 'Parse Certificate Signing Request' operation [@jkataja] | [#1504] +- Added 'Extract Hash Values' operation [@MShwed] | [#512] +- Added 'DateTime Delta' operation [@tomgond] | [#1732] + +### [10.14.0] - 2024-03-31 +- Added 'To Float' and 'From Float' operations [@tcode2k16] | [#1762] +- Fix ChaCha raw export option [@joostrijneveld] | [#1606] +- Update x86 disassembler vendor library [@evanreichard] | [#1197] +- Allow variable Blowfish key sizes [@cbeuw] | [#933] +- Added 'XXTEA' operation [@devcydo] | [#1361] + +### [10.13.0] - 2024-03-30 +- Added 'FangURL' operation [@breakersall] [@arnydo] | [#1591] [#654] + +### [10.12.0] - 2024-03-29 +- Added 'Salsa20' and 'XSalsa20' operation [@joostrijneveld] | [#1750] + +### [10.11.0] - 2024-03-29 +- Add HEIC/HEIF file signatures [@simonw] | [#1757] +- Update xmldom to fix medium security vulnerability [@chriswhite199] | [#1752] +- Update JSONWebToken to fix medium security vulnerability [@chriswhite199] | [#1753] + +### [10.10.0] - 2024-03-27 +- Added 'JA4 Fingerprint' operation [@n1474335] | [#1759] + +### [10.9.0] - 2024-03-26 +- Line ending sequences and UTF-8 character encoding are now detected automatically [@n1474335] | [65ffd8d] + +### [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] + +
+ Click to expand v9 minor versions + +### [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] + +
+ +## [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] + +
+ Click to expand v8 minor versions + +### [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] + +
+ +## [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` 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.22.0]: https://github.com/gchq/CyberChef/releases/tag/v10.22.0 +[10.21.0]: https://github.com/gchq/CyberChef/releases/tag/v10.21.0 +[10.20.0]: https://github.com/gchq/CyberChef/releases/tag/v10.20.0 +[10.19.0]: https://github.com/gchq/CyberChef/releases/tag/v10.19.0 +[10.18.0]: https://github.com/gchq/CyberChef/releases/tag/v10.18.0 +[10.17.0]: https://github.com/gchq/CyberChef/releases/tag/v10.17.0 +[10.16.0]: https://github.com/gchq/CyberChef/releases/tag/v10.16.0 +[10.15.0]: https://github.com/gchq/CyberChef/releases/tag/v10.15.0 +[10.14.0]: https://github.com/gchq/CyberChef/releases/tag/v10.14.0 +[10.13.0]: https://github.com/gchq/CyberChef/releases/tag/v10.13.0 +[10.12.0]: https://github.com/gchq/CyberChef/releases/tag/v10.12.0 +[10.11.0]: https://github.com/gchq/CyberChef/releases/tag/v10.11.0 +[10.10.0]: https://github.com/gchq/CyberChef/releases/tag/v10.10.0 +[10.9.0]: https://github.com/gchq/CyberChef/releases/tag/v10.9.0 +[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 +[@simonw]: https://github.com/simonw +[@chriswhite199]: https://github.com/chriswhite199 +[@breakersall]: https://github.com/breakersall +[@evanreichard]: https://github.com/evanreichard +[@devcydo]: https://github.com/devcydo +[@zb3]: https://github.com/zb3 +[@jkataja]: https://github.com/jkataja +[@tomgond]: https://github.com/tomgond +[@e218736]: https://github.com/e218736 +[@TheZ3ro]: https://github.com/TheZ3ro +[@EvieHarv]: https://github.com/EvieHarv +[@cplussharp]: https://github.com/cplussharp +[@robinsandhu]: https://github.com/robinsandhu +[@eltociear]: https://github.com/eltociear +[@GuilhermoReadonly]: https://github.com/GuilhermoReadonly +[@simonarnell]: https://github.com/simonarnell +[@RandomByte]: https://github.com/RandomByte +[@c65722]: https://github.com/c65722 +[@c65722]: https://github.com/c65722 +[@c65722]: https://github.com/c65722 +[@max0x53]: https://github.com/max0x53 +[@Adamkadaban]: https://github.com/Adamkadaban +[@c65722]: https://github.com/c65722 +[@jb30795]: https://github.com/jb30795 +[@FranciscoPombal]: https://github.com/FranciscoPombal +[@Oshawk]: https://github.com/Oshawk +[@Oshawk]: https://github.com/Oshawk +[@bartblaze]: https://github.com/bartblaze +[@exactlyaron]: https://github.com/exactlyaron +[@k3ach]: https://github.com/k3ach +[@vs4vijay]: https://github.com/vs4vijay +[@FranciscoPombal]: https://github.com/FranciscoPombal +[@FranciscoPombal]: https://github.com/FranciscoPombal +[@linuxgemini]: https://github.com/linuxgemini +[@depperm]: https://github.com/depperm +[@evenstensberg]: https://github.com/evenstensberg +[@bartblaze]: https://github.com/bartblaze +[@0xh3xa]: https://github.com/0xh3xa +[@flakjacket95]: https://github.com/flakjacket95 +[@zhzy0077]: https://github.com/zhzy0077 +[@JSCU-CNI]: https://github.com/JSCU-CNI +[@ccarpo]: https://github.com/ccarpo +[@r4mos]: https://github.com/r4mos +[@0xh3xa]: https://github.com/0xh3xa +[@0xh3xa]: https://github.com/0xh3xa +[@PathToLife]: https://github.com/PathToLife +[@peterc-s]: https://github.com/peterc-s +[@plvie]: https://github.com/plvie +[@kenduguay1]: https://github.com/kenduguay1 +[@jonking-ajar]: https://github.com/jonking-ajar +[@PathToLife]: https://github.com/PathToLife +[@r4mos]: https://github.com/r4mos +[@jg42526]: https://github.com/jg42526 +[@es45411]: https://github.com/es45411 +[@gchq]: https://github.com/gchq +[@gchqdev364]: https://github.com/gchqdev364 +[@GCHQDeveloper94872]: https://github.com/GCHQDeveloper94872 +[@Sma-Das]: https://github.com/Sma-Das +[@gchq]: https://github.com/gchq +[@Odyhibit]: https://github.com/Odyhibit +[@ericli-splunk]: https://github.com/ericli-splunk +[@xumptex]: https://github.com/xumptex +[@bartvanandel]: https://github.com/bartvanandel +[@bartvanandel]: https://github.com/bartvanandel +[@kendallgoto]: https://github.com/kendallgoto +[@remingtr]: https://github.com/remingtr +[@0xff1ce]: https://github.com/0xff1ce +[@starplanet]: https://github.com/starplanet +[@C85297]: https://github.com/C85297 +[@GCHQDeveloper581]: https://github.com/GCHQDeveloper581 +[@ThomasNotTom]: https://github.com/ThomasNotTom +[@rbpi]: https://github.com/rbpi +[@AlexGustafsson]: https://github.com/AlexGustafsson +[@tuliperis]: https://github.com/tuliperis +[@thomasxm]: https://github.com/thomasxm +[@twostraws]: https://github.com/twostraws +[@beneri]: https://github.com/beneri +[@t-martine]: https://github.com/t-martine +[@wesinator]: https://github.com/wesinator +[@Raka-loah]: https://github.com/Raka-loah + + +[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 +[65ffd8d]: https://github.com/gchq/CyberChef/commit/65ffd8d65d88eb369f6f61a5d1d0f807179bffb7 +[0a353ee]: https://github.com/gchq/CyberChef/commit/0a353eeb378b9ca5d49e23c7dfc175ae07107b08 +[66d445c]: https://github.com/gchq/CyberChef/commit/66d445c5ef4e8bd896fd15396e3ce2d660d8ace1 +[ab37c1e]: https://github.com/gchq/CyberChef/commit/ab37c1e562dbee0495ed32876ecbb8225282af25 +[965570d]: https://github.com/gchq/CyberChef/commit/965570d2504c17ee1f96211a1dc10ed40cd2b332 +[a477f47]: https://github.com/gchq/CyberChef/commit/a477f47aecd01d78b11fe186ed4b20d9c487cfac +[7a5225c]: https://github.com/gchq/CyberChef/commit/7a5225c961a5e0d192b03152117cd10a761f73d6 +[5f88ae4]: https://github.com/gchq/CyberChef/commit/5f88ae44ec77228d9bed8f11e8cc8e7dcfb36914 +[0e82e4b]: https://github.com/gchq/CyberChef/commit/0e82e4b7c6c77cadb8be61cb145e081d6ecfdc88 +[d635cca]: https://github.com/gchq/CyberChef/commit/d635cca2106aae2a59caf0e5d7e3633ee1ea3155 +[895a929]: https://github.com/gchq/CyberChef/commit/895a9299255525cb57886deb9d9fd4ba17ae9548 +[270a333]: https://github.com/gchq/CyberChef/commit/270a33317944612d27ea1cc15275ad6b0ed097e5 +[d3adfc7]: https://github.com/gchq/CyberChef/commit/d3adfc7c3e5719279524356bce5261bd8350c0f8 +[47c85a1]: https://github.com/gchq/CyberChef/commit/47c85a105ddbdd4cabfa44ddddbc56e3907a8c33 +[3822c6c]: https://github.com/gchq/CyberChef/commit/3822c6c520a0b4200abc675c33f46082f5b9efc6 +[66d445c]: https://github.com/gchq/CyberChef/commit/66d445c5ef4e8bd896fd15396e3ce2d660d8ace1 +[ab37c1e]: https://github.com/gchq/CyberChef/commit/ab37c1e562dbee0495ed32876ecbb8225282af25 +[965570d]: https://github.com/gchq/CyberChef/commit/965570d2504c17ee1f96211a1dc10ed40cd2b332 +[a477f47]: https://github.com/gchq/CyberChef/commit/a477f47aecd01d78b11fe186ed4b20d9c487cfac +[7a5225c]: https://github.com/gchq/CyberChef/commit/7a5225c961a5e0d192b03152117cd10a761f73d6 +[5f88ae4]: https://github.com/gchq/CyberChef/commit/5f88ae44ec77228d9bed8f11e8cc8e7dcfb36914 +[0e82e4b]: https://github.com/gchq/CyberChef/commit/0e82e4b7c6c77cadb8be61cb145e081d6ecfdc88 +[d635cca]: https://github.com/gchq/CyberChef/commit/d635cca2106aae2a59caf0e5d7e3633ee1ea3155 +[895a929]: https://github.com/gchq/CyberChef/commit/895a9299255525cb57886deb9d9fd4ba17ae9548 +[270a333]: https://github.com/gchq/CyberChef/commit/270a33317944612d27ea1cc15275ad6b0ed097e5 +[d3adfc7]: https://github.com/gchq/CyberChef/commit/d3adfc7c3e5719279524356bce5261bd8350c0f8 +[47c85a1]: https://github.com/gchq/CyberChef/commit/47c85a105ddbdd4cabfa44ddddbc56e3907a8c33 +[3822c6c]: https://github.com/gchq/CyberChef/commit/3822c6c520a0b4200abc675c33f46082f5b9efc6 +[66d445c]: https://github.com/gchq/CyberChef/commit/66d445c5ef4e8bd896fd15396e3ce2d660d8ace1 +[ab37c1e]: https://github.com/gchq/CyberChef/commit/ab37c1e562dbee0495ed32876ecbb8225282af25 +[965570d]: https://github.com/gchq/CyberChef/commit/965570d2504c17ee1f96211a1dc10ed40cd2b332 +[a477f47]: https://github.com/gchq/CyberChef/commit/a477f47aecd01d78b11fe186ed4b20d9c487cfac +[7a5225c]: https://github.com/gchq/CyberChef/commit/7a5225c961a5e0d192b03152117cd10a761f73d6 +[5f88ae4]: https://github.com/gchq/CyberChef/commit/5f88ae44ec77228d9bed8f11e8cc8e7dcfb36914 +[0e82e4b]: https://github.com/gchq/CyberChef/commit/0e82e4b7c6c77cadb8be61cb145e081d6ecfdc88 +[d635cca]: https://github.com/gchq/CyberChef/commit/d635cca2106aae2a59caf0e5d7e3633ee1ea3155 +[895a929]: https://github.com/gchq/CyberChef/commit/895a9299255525cb57886deb9d9fd4ba17ae9548 +[270a333]: https://github.com/gchq/CyberChef/commit/270a33317944612d27ea1cc15275ad6b0ed097e5 +[d3adfc7]: https://github.com/gchq/CyberChef/commit/d3adfc7c3e5719279524356bce5261bd8350c0f8 +[47c85a1]: https://github.com/gchq/CyberChef/commit/47c85a105ddbdd4cabfa44ddddbc56e3907a8c33 +[3822c6c]: https://github.com/gchq/CyberChef/commit/3822c6c520a0b4200abc675c33f46082f5b9efc6 +[66d445c]: https://github.com/gchq/CyberChef/commit/66d445c5ef4e8bd896fd15396e3ce2d660d8ace1 + +[#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/1699 +[#1757]: https://github.com/gchq/CyberChef/issues/1757 +[#1752]: https://github.com/gchq/CyberChef/issues/1752 +[#1753]: https://github.com/gchq/CyberChef/issues/1753 +[#1750]: https://github.com/gchq/CyberChef/issues/1750 +[#1591]: https://github.com/gchq/CyberChef/issues/1591 +[#654]: https://github.com/gchq/CyberChef/issues/654 +[#1762]: https://github.com/gchq/CyberChef/issues/1762 +[#1606]: https://github.com/gchq/CyberChef/issues/1606 +[#1197]: https://github.com/gchq/CyberChef/issues/1197 +[#933]: https://github.com/gchq/CyberChef/issues/933 +[#1361]: https://github.com/gchq/CyberChef/issues/1361 +[#1765]: https://github.com/gchq/CyberChef/issues/1765 +[#1767]: https://github.com/gchq/CyberChef/issues/1767 +[#1769]: https://github.com/gchq/CyberChef/issues/1769 +[#1759]: https://github.com/gchq/CyberChef/issues/1759 +[#1504]: https://github.com/gchq/CyberChef/issues/1504 +[#512]: https://github.com/gchq/CyberChef/issues/512 +[#1732]: https://github.com/gchq/CyberChef/issues/1732 +[#1789]: https://github.com/gchq/CyberChef/issues/1789 +[#1040]: https://github.com/gchq/CyberChef/pull/1040 +[#2176]: https://github.com/gchq/CyberChef/pull/2176 +[#2177]: https://github.com/gchq/CyberChef/pull/2177 +[#2174]: https://github.com/gchq/CyberChef/pull/2174 +[#2058]: https://github.com/gchq/CyberChef/pull/2058 +[#1861]: https://github.com/gchq/CyberChef/pull/1861 +[#2055]: https://github.com/gchq/CyberChef/pull/2055 +[#2169]: https://github.com/gchq/CyberChef/pull/2169 +[#2175]: https://github.com/gchq/CyberChef/pull/2175 +[#2173]: https://github.com/gchq/CyberChef/pull/2173 +[#2172]: https://github.com/gchq/CyberChef/pull/2172 +[#2136]: https://github.com/gchq/CyberChef/pull/2136 +[#2165]: https://github.com/gchq/CyberChef/pull/2165 +[#2159]: https://github.com/gchq/CyberChef/pull/2159 +[#2086]: https://github.com/gchq/CyberChef/pull/2086 +[#2118]: https://github.com/gchq/CyberChef/pull/2118 +[#2166]: https://github.com/gchq/CyberChef/pull/2166 +[#2188]: https://github.com/gchq/CyberChef/pull/2188 +[#2137]: https://github.com/gchq/CyberChef/pull/2137 +[#1876]: https://github.com/gchq/CyberChef/pull/1876 +[#2186]: https://github.com/gchq/CyberChef/pull/2186 +[#1573]: https://github.com/gchq/CyberChef/pull/1573 +[#2183]: https://github.com/gchq/CyberChef/pull/2183 +[#2182]: https://github.com/gchq/CyberChef/pull/2182 +[#2181]: https://github.com/gchq/CyberChef/pull/2181 + diff --git a/plugins/srktoolbox/CHANGELOG_SRKTOOLBOX.md b/plugins/srktoolbox/CHANGELOG_SRKTOOLBOX.md new file mode 100644 index 00000000..1aec8663 --- /dev/null +++ b/plugins/srktoolbox/CHANGELOG_SRKTOOLBOX.md @@ -0,0 +1,33 @@ +# SRK Toolbox 改动记录 + +## 版本命名 + +SRK Toolbox沿用CyberChef版本命名方式,此处记录和官方版本不同的版本号与改动记录,未记载此处的版本内容改动同[CyberChef原版改动](https://github.com/gchq/CyberChef/blob/master/CHANGELOG.md)。 + +## 详细信息 +### [10.19.5] - 2026-01-15 +- 前一个版本和官方版本同步发生问题导致版本内容不一致,此版本暂时追至原版最新代码以修复bug。 + +### [10.15.2] - 2024-04-08 +- 大意了,合并冲突的时候不小心删了一些东西得补回来。 +- 因为官方加入了自动检测输出字符编码的功能,所以删除“输出默认使用UTF8编码”的选项。 + +### [10.4.1] - 2023-05-09 +- 汉化之前漏掉的一小部分说明文字(主要是F1帮助)。 +- 增加“输出默认使用UTF8编码”的选项,因为“显示原始字节”不能显示中文,导致汉化后的模块输出看起来是乱码,默认关闭,需要在设置里手动打开。 +- 修复选择GB18030解码有时会卡死的问题。 + +### [9.54.1] - 2022-12-05 +- 彻底汉化单元测试并修复对应操作的bug,此后发布的版本均进行全部单元测试。(备注:经核对前期版本中少量操作存在数据部分丢失或汉化疏漏,但不存在数据错误,可放心使用) +- 将Package名称修改为`srktoolbox`与原版进行区分。 +- Toastr的一处显示bug修复。 + +### [9.49.1] - 2022-11-23 +- 启用Github Actions作为CI,添加docker镜像。 +- 替换SnackbarJS为Toastr。 + +[10.19.5]: https://github.com/Raka-loah/SRK-Toolbox/releases/tag/v10.15.2 +[10.15.2]: https://github.com/Raka-loah/SRK-Toolbox/releases/tag/v10.15.2 +[10.4.1]: https://github.com/Raka-loah/SRK-Toolbox/releases/tag/v10.4.1 +[9.54.1]: https://github.com/Raka-loah/SRK-Toolbox/releases/tag/v9.54.1 +[9.49.1]: https://github.com/Raka-loah/SRK-Toolbox/releases/tag/v9.49.1 \ No newline at end of file diff --git a/plugins/srktoolbox/CODE_OF_CONDUCT.md b/plugins/srktoolbox/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..1fadd9c4 --- /dev/null +++ b/plugins/srktoolbox/CODE_OF_CONDUCT.md @@ -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/ diff --git a/plugins/srktoolbox/Gruntfile.js b/plugins/srktoolbox/Gruntfile.js new file mode 100644 index 00000000..978e6dcb --- /dev/null +++ b/plugins/srktoolbox/Gruntfile.js @@ -0,0 +1,471 @@ +"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 + * + * Modified by Raka-loah@github + */ + +module.exports = function (grunt) { + grunt.file.defaultEncoding = "utf8"; + grunt.file.preserveBOM = false; + + // Tasks + grunt.registerTask("dev", + "A persistent task which creates a development build whenever source files are modified.", + ["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", "clean:config", "exec:generateConfig", "findModules", "webpack:web", + "copy:standalone", "zip:standalone", "clean:standalone", "exec:calcDownloadHash", "chmod" + ]); + + grunt.registerTask("ztools", + "Creates a ZTools plugin build in dist.", + [ + "eslint", "clean:ztools", "clean:prod", "clean:config", "exec:generateConfig", "findModules", "webpack:web", + "copy:ztools", "zip:ztools", "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("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-contrib-clean"); + grunt.loadNpmTasks("grunt-contrib-copy"); + grunt.loadNpmTasks("grunt-contrib-watch"); + grunt.loadNpmTasks("grunt-chmod"); + grunt.loadNpmTasks("grunt-exec"); + grunt.loadNpmTasks("grunt-concurrent"); + grunt.loadNpmTasks("grunt-contrib-connect"); + grunt.loadNpmTasks("grunt-zip"); + + + // Project configuration + const compileYear = grunt.template.today("UTC:yyyy"), + 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_YEAR: JSON.stringify(compileYear), + 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-srktoolbox", + /** + * 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), + optimization: { + minimize: false + }, + 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"], + compileYear: compileYear, + compileTime: compileTime, + version: pkg.version, + minify: { + removeComments: true, + collapseWhitespace: true, + minifyJS: true, + minifyCSS: true + } + }), + new BundleAnalyzerPlugin({ + analyzerMode: "static", + reportFilename: "BundleAnalyzerReport.html", + openAnalyzer: false + }), + ] + }; + }; + + + /** + * Generates an entry list for all the modules. + */ + 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 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/*"], + ztools: ["dist/*", "build/SRK_Toolbox_ZTools_v*.zip"], + node: ["build/node/*"], + 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: { + 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: 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: { + "./config/modules/OpModules.mjs": "./config/modules/Default.mjs" + } + }, + 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", + chunks: ["main"], + compileYear: compileYear, + compileTime: compileTime, + version: pkg.version, + }) + ] + } + }, + zip: { + standalone: { + cwd: "build/prod/", + src: [ + "build/prod/**/*", + "!build/prod/index.html", + "!build/prod/BundleAnalyzerReport.html", + ], + dest: `build/prod/SRK_Toolbox_v${pkg.version}.zip`, + compression: "DEFLATE" + }, + ztools: { + cwd: "dist/", + src: [ + "dist/**/*" + ], + dest: `build/SRK_Toolbox_ZTools_v${pkg.version}.zip`, + compression: "DEFLATE" + } + }, + connect: { + prod: { + options: { + port: grunt.option("port") || 8000, + base: "build/prod/" + } + } + }, + copy: { + ghPages: { + options: { + process: function (content, srcpath) { + if (srcpath.indexOf("index.html") >= 0) { + // Add Google Analytics code to index.html + content = content.replace("", + grunt.file.read("src/web/static/ga.html") + ""); + + // Add Structured Data for SEO + content = content.replace("", + ""); + return grunt.template.process(content, srcpath); + } else { + return content; + } + }, + noProcess: ["**", "!**/*.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(/]+>.+下载离线版SRK Toolbox.+?<\/a>/, + `版本 ${pkg.version}`); + + return grunt.template.process(content, srcpath); + } else { + return content; + } + }, + noProcess: ["**", "!**/*.html"] + }, + files: [ + { + src: ["build/prod/index.html"], + dest: `build/prod/SRK_Toolbox_v${pkg.version}.html` + } + ] + }, + ztools: { + files: [ + { + expand: true, + cwd: "build/prod/", + src: [ + "**/*", + "!*.gz", + "!*.br", + "!**/*.gz", + "!**/*.br", + "!BundleAnalyzerReport.html", + "!SRK_Toolbox_v*.html", + "!SRK_Toolbox_v*.zip", + "!sha256digest.txt" + ], + dest: "dist/" + }, + { + src: ["plugin.json", "public/logo.png"], + dest: "dist/", + flatten: true, + expand: true + } + ] + } + }, + chmod: { + build: { + options: { + mode: "755", + }, + src: ["build/**/*", "build/", "dist/**/*", "dist/"] + } + }, + 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/SRK_Toolbox_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/SRK_Toolbox_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: chainCommands([ + "git ls-files | wc -l | xargs printf '\n%b\ttracked files\n'", + "du -hs | egrep -o '^[^\t]*' | xargs printf '%b\trepository size\n'" + ]), + stderr: false + }, + cleanGit: { + command: "git gc --prune=now --aggressive" + }, + 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 srktoolbox" + ]), + 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: { + cmd: "node scripts/fixCryptoApiImports.cjs", + stdout: false + }, + fixSnackbarMarkup: { + cmd: "node scripts/fixSnackbarMarkup.cjs", + stdout: false + }, + }, + }); +}; diff --git a/plugins/srktoolbox/LICENSE b/plugins/srktoolbox/LICENSE new file mode 100644 index 00000000..7a4a3ea2 --- /dev/null +++ b/plugins/srktoolbox/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/plugins/srktoolbox/README.md b/plugins/srktoolbox/README.md new file mode 100644 index 00000000..4f08c173 --- /dev/null +++ b/plugins/srktoolbox/README.md @@ -0,0 +1,85 @@ +# SRK Toolbox for ZTools +[源地址](https://github.com/Raka-loah/SRK-Toolbox) +SRK Toolbox for ZTools 是一个可导入 ZTools 的中文安全工具箱插件,基于 CyberChef 适配而来,提供编码转换、加密解密、压缩处理和数据分析等常用能力。 + +## 项目特性 + +- 可直接作为 ZTools 插件导入 +- 保留 SRK Toolbox 的核心操作体验 +- 适配 ZTools 窗口展示 +- 支持本地开发与生产打包 + +## 插件信息 + +插件清单位于 `public/plugin.json`,ZTools 会根据这里识别插件名称、描述、图标、命令入口和窗口高度等信息。 + +## 安装 + +```bash +npm install +``` + +## 开发 + +启动本地开发环境后,将 `public/plugin.json` 中的 `development.main` 指向本地服务地址,即可在 ZTools 中进行调试。 + +```bash +npm run dev +``` + +如果你的本地开发端口与清单中的地址不同,请同步修改: + +```json +"development": { + "main": "http://localhost:54333" +} +``` + +## 构建 + +生产构建会生成 ZTools 可用的插件包和普通离线包: + +```bash +npm run build:ztools +``` + +构建完成后,产物位于: + +- `build/prod/SRK_Toolbox_ztools_v10.22.1.zip` +- `build/prod/SRK_Toolbox_v10.22.1.zip` + +其中 `SRK_Toolbox_ztools_v10.22.1.zip` 是用于导入 ZTools 的插件包。 + +## 导入到 ZTools + +1. 打开 ZTools +2. 导入 `build/prod/SRK_Toolbox_ztools_v10.22.1.zip` +3. 安装后即可通过“SRK Toolbox”或“srk”等命令打开插件 + +## 命令入口 + +插件清单中的快捷命令包括: + +- `SRK Toolbox` +- `srk` +- `toolbox` +- `工具箱` +- `密码工具` +- `编码转换` +- `CyberChef` + +## 窗口适配 + +由于 ZTools 插件窗口宽度受宿主限制,本项目已针对主界面做了最小宽度和滚动适配,避免操作面板在较窄窗口中被过度挤压。 + +## 目录说明 + +- `src/`:核心源码 +- `public/`:插件清单和图标资源 +- `build/prod/`:生产构建输出 +- `webpack.config.js`:Webpack 构建配置 +- `Gruntfile.js`:构建与打包任务 + +## 许可 + +本项目基于 CyberChef 相关代码演化而来,具体许可请参考原项目与仓库中的许可证文件。 diff --git a/plugins/srktoolbox/SECURITY.md b/plugins/srktoolbox/SECURITY.md new file mode 100644 index 00000000..c934c934 --- /dev/null +++ b/plugins/srktoolbox/SECURITY.md @@ -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. diff --git a/plugins/srktoolbox/babel.config.js b/plugins/srktoolbox/babel.config.js new file mode 100644 index 00000000..deab9108 --- /dev/null +++ b/plugins/srktoolbox/babel.config.js @@ -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 + } + ] + ] + }; +}; diff --git a/plugins/srktoolbox/eslint.config.mjs b/plugins/srktoolbox/eslint.config.mjs new file mode 100644 index 00000000..c0a7222c --- /dev/null +++ b/plugins/srktoolbox/eslint.config.mjs @@ -0,0 +1,129 @@ +import babelParser from "@babel/eslint-parser"; +import jsdoc from "eslint-plugin-jsdoc"; +import js from "@eslint/js"; +import globals from "globals"; + +export default [ + js.configs.recommended, + { + languageOptions: { + ecmaVersion: 2022, + parser: babelParser, + parserOptions: { + ecmaVersion: 2022, + ecmaFeatures: { + impliedStrict: true + }, + sourceType: "module", + allowImportExportEverywhere: true + }, + globals: { + ...globals.browser, + ...globals.node, + ...globals.es6, + "$": false, + "jQuery": false, + "log": false, + "app": false, + + "COMPILE_TIME": false, + "COMPILE_MSG": false, + "PKG_VERSION": false + }, + }, + ignores: ["src/core/vendor/**"], + plugins: { + jsdoc + }, + rules: { + // enable additional rules + "no-eval": "error", + "no-implied-eval": "error", + "dot-notation": "error", + "eqeqeq": ["error", "smart"], + "no-caller": "error", + "no-extra-bind": "error", + "no-unused-expressions": "error", + "no-useless-call": "error", + "no-useless-return": "error", + "radix": "warn", + + // modify rules from base configurations + "no-unused-vars": ["error", { + "args": "none", + "vars": "all", + "caughtErrors": "none" + }], + "no-empty": ["error", { + "allowEmptyCatch": true + }], + + // disable rules from base configurations + "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, + "allowTemplateLiterals": true + }], + "camelcase": ["error", { + "properties": "always" + }], + "semi": ["error", "always"], + "unicode-bom": "error", + "jsdoc/require-jsdoc": ["error", { + "require": { + "FunctionDeclaration": true, + "MethodDefinition": true, + "ClassDeclaration": true, + "ArrowFunctionExpression": false + } + }], + "keyword-spacing": ["error", { + "before": true, + "after": true + }], + "no-multiple-empty-lines": ["warn", { + "max": 2, + "maxEOF": 1, + "maxBOF": 0 + }], + "no-whitespace-before-property": "error", + "operator-linebreak": ["error", "after"], + "space-in-parens": "error", + "no-var": "error", + "prefer-const": "error", + "no-console": "error" + }, + }, + // File-pattern specific overrides + { + files: ["tests/**/*"], + rules: { + "no-unused-expressions": "off", + "no-console": "off" + } + }, +]; diff --git a/plugins/srktoolbox/nightwatch.json b/plugins/srktoolbox/nightwatch.json new file mode 100644 index 00000000..95359f44 --- /dev/null +++ b/plugins/srktoolbox/nightwatch.json @@ -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" + } + + } +} + diff --git a/plugins/srktoolbox/package-lock.json b/plugins/srktoolbox/package-lock.json new file mode 100644 index 00000000..a5503ad4 --- /dev/null +++ b/plugins/srktoolbox/package-lock.json @@ -0,0 +1,24241 @@ +{ + "name": "srktoolbox", + "version": "10.22.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "srktoolbox", + "version": "10.22.1", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "14": "^3.1.6", + "@astronautlabs/amf": "^0.0.6", + "@babel/polyfill": "^7.12.1", + "@blu3r4y/lzma": "^2.3.3", + "@wavesenterprise/crypto-gost-js": "^2.1.0-RC1", + "@xmldom/xmldom": "^0.8.11", + "argon2-browser": "^1.18.0", + "arrive": "^2.5.2", + "assert": "^2.1.0", + "avsc": "^5.7.9", + "bcryptjs": "^2.4.3", + "bignumber.js": "^9.3.1", + "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.1", + "crypto-js": "^4.2.0", + "ctph.js": "0.0.5", + "d3": "7.9.0", + "d3-hexbin": "^0.2.2", + "diff": "^5.2.2", + "dompurify": "^3.3.1", + "es6-promisify": "^7.0.0", + "escodegen": "^2.1.0", + "esprima": "^4.0.1", + "events": "^3.3.0", + "exif-parser": "^0.1.12", + "fernet": "^0.3.3", + "file-saver": "^2.0.5", + "flat": "^6.0.1", + "geodesy": "1.1.3", + "handlebars": "^4.7.8", + "hash-wasm": "^4.12.0", + "highlight.js": "^11.11.1", + "ieee754": "^1.2.1", + "jimp": "^1.6.0", + "jq-web": "^0.6.2", + "jquery": "3.7.1", + "js-sha3": "^0.9.3", + "jsesc": "^3.1.0", + "json5": "^2.2.3", + "jsonata": "^2.1.0", + "jsonpath-plus": "^10.4.0", + "jsonwebtoken": "8.5.1", + "jsqr": "^1.4.0", + "jsrsasign": "^11.1.1", + "kbpgp": "^2.1.17", + "libbzip2-wasm": "0.0.4", + "libyara-wasm": "^1.2.1", + "lodash": "^4.17.23", + "loglevel": "^1.9.2", + "loglevel-message-prefix": "^3.0.0", + "lz-string": "^1.5.0", + "lz4js": "^0.2.0", + "markdown-it": "^14.1.1", + "moment": "^2.30.1", + "moment-timezone": "^0.6.0", + "ngeohash": "^0.6.3", + "node-forge": "^1.3.3", + "node-md6": "^0.1.0", + "nodom": "^2.4.0", + "notepack.io": "^3.0.1", + "ntlm": "^0.1.3", + "nwmatcher": "^1.4.4", + "otpauth": "9.3.6", + "path": "^0.12.7", + "popper.js": "^1.16.1", + "process": "^0.11.10", + "protobufjs": "^7.5.4", + "qr-image": "^3.2.0", + "reflect-metadata": "^0.2.2", + "rison": "^0.1.1", + "scryptsy": "^2.1.0", + "snackbarjs": "^1.1.0", + "sortablejs": "^1.15.7", + "split.js": "^1.6.5", + "ssdeep.js": "0.0.3", + "stream-browserify": "^3.0.0", + "tesseract.js": "5.1.1", + "toastr": "^2.1.4", + "ua-parser-js": "^1.0.41", + "unorm": "^1.6.0", + "url": "^0.11.4", + "utf8": "^3.0.0", + "uuid": "^13.0.0", + "vkbeautify": "^0.99.3", + "xpath": "0.0.34", + "xregexp": "^5.1.2", + "zlibjs": "^0.3.1" + }, + "devDependencies": { + "@babel/core": "^7.29.0", + "@babel/eslint-parser": "^7.28.6", + "@babel/plugin-syntax-import-assertions": "^7.28.6", + "@babel/plugin-transform-runtime": "^7.29.0", + "@babel/preset-env": "^7.29.0", + "@babel/runtime": "^7.28.6", + "@codemirror/commands": "^6.10.2", + "@codemirror/language": "^6.12.1", + "@codemirror/search": "^6.6.0", + "@codemirror/state": "^6.5.4", + "@codemirror/view": "^6.39.15", + "autoprefixer": "^10.4.24", + "babel-loader": "^10.0.0", + "babel-plugin-dynamic-import-node": "^2.3.3", + "babel-plugin-transform-builtin-extend": "1.1.2", + "base64-loader": "^1.0.0", + "chromedriver": "^143.0.4", + "cli-progress": "^3.12.0", + "colors": "^1.4.0", + "compression-webpack-plugin": "^11.1.0", + "copy-webpack-plugin": "^13.0.1", + "core-js": "^3.48.0", + "cspell": "^8.19.4", + "css-loader": "7.1.4", + "eslint": "^9.39.3", + "eslint-plugin-jsdoc": "^48.11.0", + "globals": "^15.15.0", + "grunt": "^1.6.1", + "grunt-chmod": "~1.1.1", + "grunt-concurrent": "^3.0.0", + "grunt-contrib-clean": "~2.0.1", + "grunt-contrib-connect": "^5.0.1", + "grunt-contrib-copy": "~1.0.0", + "grunt-contrib-watch": "^1.1.0", + "grunt-eslint": "^25.0.0", + "grunt-exec": "~3.0.0", + "grunt-webpack": "^6.0.0", + "grunt-zip": "^1.0.0", + "html-webpack-plugin": "^5.6.6", + "imports-loader": "^5.0.0", + "mini-css-extract-plugin": "2.10.0", + "modify-source-webpack-plugin": "^4.1.0", + "nightwatch": "^3.15.0", + "postcss": "^8.5.6", + "postcss-css-variables": "^0.19.0", + "postcss-import": "^16.1.1", + "postcss-loader": "^8.2.1", + "prompt": "^1.3.0", + "sitemap": "^8.0.2", + "terser": "^5.46.0", + "webpack": "^5.105.2", + "webpack-bundle-analyzer": "^4.10.2", + "webpack-dev-server": "5.0.4", + "webpack-node-externals": "^3.0.0", + "worker-loader": "^3.0.8" + } + }, + "node_modules/@asamuzakjp/css-color": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-2.8.3.tgz", + "integrity": "sha512-GIc76d9UI1hCvOATjZPyHFmE5qhRccp3/zGfMPapK3jBi+yocEzp6BBB0UnfRYP9NP4FANqUZYb0hnfs3TM3hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.1", + "@csstools/css-color-parser": "^3.0.7", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" + } + }, + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@astronautlabs/amf": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@astronautlabs/amf/-/amf-0.0.6.tgz", + "integrity": "sha512-cJgbXW45TIDLQf2hiHqDoRfmeRy5u9Z4npr7sZfBThvbp5cbqDieTWaJTu91cUAj35/u87OHZijLTbMO18ZIow==", + "license": "MIT", + "dependencies": { + "@astronautlabs/bitstream": "^4.0.0" + }, + "engines": { + "node": "^14" + } + }, + "node_modules/@astronautlabs/amf/node_modules/@astronautlabs/bitstream": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@astronautlabs/bitstream/-/bitstream-4.2.2.tgz", + "integrity": "sha512-/D18Aua0Er95TkulkVBXK5oweh55tXlgpug7g2cKdUvVBT91k2YD9n1RYnbrfaaHIN+KVDaBIrf3j9dS3DhzXw==", + "license": "MIT", + "peerDependencies": { + "reflect-metadata": "^0.1.13" + } + }, + "node_modules/@astronautlabs/amf/node_modules/reflect-metadata": { + "version": "0.1.14", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.14.tgz", + "integrity": "sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==", + "license": "Apache-2.0", + "peer": true + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/eslint-parser": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.28.6.tgz", + "integrity": "sha512-QGmsKi2PBO/MHSQk+AAgA9R6OHQr+VqnniFE0eMWZcVcfBZoA2dKn2hUsl3Csg/Plt9opRUWdY7//VXsrIlEiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", + "eslint-visitor-keys": "^2.1.0", + "semver": "^6.3.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || >=14.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0", + "eslint": "^7.5.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", + "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.6", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz", + "integrity": "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "regexpu-core": "^6.3.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.6.tgz", + "integrity": "sha512-mOAsxeeKkUKayvZR3HeTYD/fICpCPLJrU5ZjelT/PA6WHtNDBOE436YiaEUvHN454bRM3CebhDsIpieCc4texA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "debug": "^4.4.3", + "lodash.debounce": "^4.0.8", + "resolve": "^1.22.11" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", + "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", + "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-wrap-function": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", + "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.6.tgz", + "integrity": "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz", + "integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", + "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", + "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", + "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.6.tgz", + "integrity": "sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.28.6.tgz", + "integrity": "sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", + "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.0.tgz", + "integrity": "sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-remap-async-to-generator": "^7.27.1", + "@babel/traverse": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.28.6.tgz", + "integrity": "sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-remap-async-to-generator": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", + "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz", + "integrity": "sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz", + "integrity": "sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz", + "integrity": "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz", + "integrity": "sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-globals": "^7.28.0", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz", + "integrity": "sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/template": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz", + "integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.28.6.tgz", + "integrity": "sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", + "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", + "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-explicit-resource-management": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.6.tgz", + "integrity": "sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.6.tgz", + "integrity": "sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", + "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", + "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", + "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.28.6.tgz", + "integrity": "sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", + "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz", + "integrity": "sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", + "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", + "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", + "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.0.tgz", + "integrity": "sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", + "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", + "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz", + "integrity": "sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz", + "integrity": "sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz", + "integrity": "sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", + "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz", + "integrity": "sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz", + "integrity": "sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", + "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz", + "integrity": "sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz", + "integrity": "sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", + "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.0.tgz", + "integrity": "sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.28.6.tgz", + "integrity": "sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", + "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.29.0.tgz", + "integrity": "sha512-jlaRT5dJtMaMCV6fAuLbsQMSwz/QkvaHOHOSXRitGGwSpR1blCY4KUKoyP2tYO8vJcqYe8cEj96cqSztv3uF9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "babel-plugin-polyfill-corejs2": "^0.4.14", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", + "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz", + "integrity": "sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", + "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", + "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", + "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", + "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.28.6.tgz", + "integrity": "sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", + "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.28.6.tgz", + "integrity": "sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/polyfill": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/polyfill/-/polyfill-7.12.1.tgz", + "integrity": "sha512-X0pi0V6gxLi6lFZpGmeNa4zxtwEmCs42isWLNjZZDE0Y8yVfgu0T2OAHlzBbdYlqbW/YXVvoBHpATEM+goCj8g==", + "deprecated": "🚨 This package has been deprecated in favor of separate inclusion of a polyfill and regenerator-runtime (when needed). See the @babel/polyfill docs (https://babeljs.io/docs/en/babel-polyfill) for more information.", + "license": "MIT", + "dependencies": { + "core-js": "^2.6.5", + "regenerator-runtime": "^0.13.4" + } + }, + "node_modules/@babel/polyfill/node_modules/core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", + "hasInstallScript": true, + "license": "MIT" + }, + "node_modules/@babel/preset-env": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.0.tgz", + "integrity": "sha512-fNEdfc0yi16lt6IZo2Qxk3knHVdfMYX33czNb4v8yWhemoBhibCpQK/uYHtSKIiO+p/zd3+8fYVXhQdOVV608w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.6", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.28.6", + "@babel/plugin-syntax-import-attributes": "^7.28.6", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.29.0", + "@babel/plugin-transform-async-to-generator": "^7.28.6", + "@babel/plugin-transform-block-scoped-functions": "^7.27.1", + "@babel/plugin-transform-block-scoping": "^7.28.6", + "@babel/plugin-transform-class-properties": "^7.28.6", + "@babel/plugin-transform-class-static-block": "^7.28.6", + "@babel/plugin-transform-classes": "^7.28.6", + "@babel/plugin-transform-computed-properties": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5", + "@babel/plugin-transform-dotall-regex": "^7.28.6", + "@babel/plugin-transform-duplicate-keys": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.0", + "@babel/plugin-transform-dynamic-import": "^7.27.1", + "@babel/plugin-transform-explicit-resource-management": "^7.28.6", + "@babel/plugin-transform-exponentiation-operator": "^7.28.6", + "@babel/plugin-transform-export-namespace-from": "^7.27.1", + "@babel/plugin-transform-for-of": "^7.27.1", + "@babel/plugin-transform-function-name": "^7.27.1", + "@babel/plugin-transform-json-strings": "^7.28.6", + "@babel/plugin-transform-literals": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.28.6", + "@babel/plugin-transform-member-expression-literals": "^7.27.1", + "@babel/plugin-transform-modules-amd": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.28.6", + "@babel/plugin-transform-modules-systemjs": "^7.29.0", + "@babel/plugin-transform-modules-umd": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.0", + "@babel/plugin-transform-new-target": "^7.27.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", + "@babel/plugin-transform-numeric-separator": "^7.28.6", + "@babel/plugin-transform-object-rest-spread": "^7.28.6", + "@babel/plugin-transform-object-super": "^7.27.1", + "@babel/plugin-transform-optional-catch-binding": "^7.28.6", + "@babel/plugin-transform-optional-chaining": "^7.28.6", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/plugin-transform-private-methods": "^7.28.6", + "@babel/plugin-transform-private-property-in-object": "^7.28.6", + "@babel/plugin-transform-property-literals": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.29.0", + "@babel/plugin-transform-regexp-modifiers": "^7.28.6", + "@babel/plugin-transform-reserved-words": "^7.27.1", + "@babel/plugin-transform-shorthand-properties": "^7.27.1", + "@babel/plugin-transform-spread": "^7.28.6", + "@babel/plugin-transform-sticky-regex": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-typeof-symbol": "^7.27.1", + "@babel/plugin-transform-unicode-escapes": "^7.27.1", + "@babel/plugin-transform-unicode-property-regex": "^7.28.6", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.28.6", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.15", + "babel-plugin-polyfill-corejs3": "^0.14.0", + "babel-plugin-polyfill-regenerator": "^0.6.6", + "core-js-compat": "^3.48.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.0.tgz", + "integrity": "sha512-AvDcMxJ34W4Wgy4KBIIePQTAOP1Ie2WFwkQp3dB7FQ/f0lI5+nM96zUnYEOE1P9sEg0es5VCP0HxiWu5fUHZAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.6", + "core-js-compat": "^3.48.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/runtime-corejs3": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.29.0.tgz", + "integrity": "sha512-TgUkdp71C9pIbBcHudc+gXZnihEDOjUAmXO1VO4HHGES7QLZcShR0stfKIxLSNIYx2fqhmJChOjm/wkF8wv4gA==", + "license": "MIT", + "dependencies": { + "core-js-pure": "^3.48.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bazel/runfiles": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@bazel/runfiles/-/runfiles-6.3.1.tgz", + "integrity": "sha512-1uLNT5NZsUVIGS4syuHwTzZ8HycMPyr6POA3FCE4GbMtc4rhoJk8aZKtNIRthJYfL+iioppi+rTfH3olMPr9nA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@blu3r4y/lzma": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/@blu3r4y/lzma/-/lzma-2.3.3.tgz", + "integrity": "sha512-2ckRSsYewLAgq/s8tUW3o5gurtCNYga1f9l0egV4QlT8hgVEilQHRt18s+behmPL2M/BPBxUINaOz67u++r0wA==", + "license": "MIT", + "bin": { + "lzma.js": "bin/lzma.js" + } + }, + "node_modules/@borewit/text-codec": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.2.tgz", + "integrity": "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@codemirror/commands": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.2.tgz", + "integrity": "sha512-vvX1fsih9HledO1c9zdotZYUZnE4xV0m6i3m25s5DIfXofuprk6cRcLUZvSk3CASUbwjQX21tOGbkY2BH8TpnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.4.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.1.tgz", + "integrity": "sha512-Fa6xkSiuGKc8XC8Cn96T+TQHYj4ZZ7RdFmXA3i9xe/3hLHfwPZdM+dqfX0Cp0zQklBKhVD8Yzc8LS45rkqcwpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.5.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/search": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.6.0.tgz", + "integrity": "sha512-koFuNXcDvyyotWcgOnZGmY7LZqEOXZaaxD/j6n18TCLx2/9HieZJ5H6hs1g8FiRxBD0DNfs0nXn17g872RmYdw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.37.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/state": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.4.tgz", + "integrity": "sha512-8y7xqG/hpB53l25CIoit9/ngxdfoG+fx+V3SHBrinnhOtLvKHRyAJJuHzkWrR4YXXLX8eXBsejgAAxHUOdW1yw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "node_modules/@codemirror/view": { + "version": "6.39.15", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.39.15.tgz", + "integrity": "sha512-aCWjgweIIXLBHh7bY6cACvXuyrZ0xGafjQ2VInjp4RM4gMfscK5uESiNdrH0pE+e1lZr2B4ONGsjchl2KsKZzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.5.0", + "crelt": "^1.0.6", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@cspell/cspell-bundled-dicts": { + "version": "8.19.4", + "resolved": "https://registry.npmjs.org/@cspell/cspell-bundled-dicts/-/cspell-bundled-dicts-8.19.4.tgz", + "integrity": "sha512-2ZRcZP/ncJ5q953o8i+R0fb8+14PDt5UefUNMrFZZHvfTI0jukAASOQeLY+WT6ASZv6CgbPrApAdbppy9FaXYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/dict-ada": "^4.1.0", + "@cspell/dict-al": "^1.1.0", + "@cspell/dict-aws": "^4.0.10", + "@cspell/dict-bash": "^4.2.0", + "@cspell/dict-companies": "^3.1.15", + "@cspell/dict-cpp": "^6.0.8", + "@cspell/dict-cryptocurrencies": "^5.0.4", + "@cspell/dict-csharp": "^4.0.6", + "@cspell/dict-css": "^4.0.17", + "@cspell/dict-dart": "^2.3.0", + "@cspell/dict-data-science": "^2.0.8", + "@cspell/dict-django": "^4.1.4", + "@cspell/dict-docker": "^1.1.13", + "@cspell/dict-dotnet": "^5.0.9", + "@cspell/dict-elixir": "^4.0.7", + "@cspell/dict-en_us": "^4.4.3", + "@cspell/dict-en-common-misspellings": "^2.0.10", + "@cspell/dict-en-gb": "1.1.33", + "@cspell/dict-filetypes": "^3.0.11", + "@cspell/dict-flutter": "^1.1.0", + "@cspell/dict-fonts": "^4.0.4", + "@cspell/dict-fsharp": "^1.1.0", + "@cspell/dict-fullstack": "^3.2.6", + "@cspell/dict-gaming-terms": "^1.1.1", + "@cspell/dict-git": "^3.0.4", + "@cspell/dict-golang": "^6.0.20", + "@cspell/dict-google": "^1.0.8", + "@cspell/dict-haskell": "^4.0.5", + "@cspell/dict-html": "^4.0.11", + "@cspell/dict-html-symbol-entities": "^4.0.3", + "@cspell/dict-java": "^5.0.11", + "@cspell/dict-julia": "^1.1.0", + "@cspell/dict-k8s": "^1.0.10", + "@cspell/dict-kotlin": "^1.1.0", + "@cspell/dict-latex": "^4.0.3", + "@cspell/dict-lorem-ipsum": "^4.0.4", + "@cspell/dict-lua": "^4.0.7", + "@cspell/dict-makefile": "^1.0.4", + "@cspell/dict-markdown": "^2.0.10", + "@cspell/dict-monkeyc": "^1.0.10", + "@cspell/dict-node": "^5.0.7", + "@cspell/dict-npm": "^5.2.1", + "@cspell/dict-php": "^4.0.14", + "@cspell/dict-powershell": "^5.0.14", + "@cspell/dict-public-licenses": "^2.0.13", + "@cspell/dict-python": "^4.2.17", + "@cspell/dict-r": "^2.1.0", + "@cspell/dict-ruby": "^5.0.8", + "@cspell/dict-rust": "^4.0.11", + "@cspell/dict-scala": "^5.0.7", + "@cspell/dict-shell": "^1.1.0", + "@cspell/dict-software-terms": "^5.0.5", + "@cspell/dict-sql": "^2.2.0", + "@cspell/dict-svelte": "^1.0.6", + "@cspell/dict-swift": "^2.0.5", + "@cspell/dict-terraform": "^1.1.1", + "@cspell/dict-typescript": "^3.2.1", + "@cspell/dict-vue": "^3.0.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@cspell/cspell-json-reporter": { + "version": "8.19.4", + "resolved": "https://registry.npmjs.org/@cspell/cspell-json-reporter/-/cspell-json-reporter-8.19.4.tgz", + "integrity": "sha512-pOlUtLUmuDdTIOhDTvWxxta0Wm8RCD/p1V0qUqeP6/Ups1ajBI4FWEpRFd7yMBTUHeGeSNicJX5XeX7wNbAbLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/cspell-types": "8.19.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@cspell/cspell-pipe": { + "version": "8.19.4", + "resolved": "https://registry.npmjs.org/@cspell/cspell-pipe/-/cspell-pipe-8.19.4.tgz", + "integrity": "sha512-GNAyk+7ZLEcL2fCMT5KKZprcdsq3L1eYy3e38/tIeXfbZS7Sd1R5FXUe6CHXphVWTItV39TvtLiDwN/2jBts9A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@cspell/cspell-resolver": { + "version": "8.19.4", + "resolved": "https://registry.npmjs.org/@cspell/cspell-resolver/-/cspell-resolver-8.19.4.tgz", + "integrity": "sha512-S8vJMYlsx0S1D60glX8H2Jbj4mD8519VjyY8lu3fnhjxfsl2bDFZvF3ZHKsLEhBE+Wh87uLqJDUJQiYmevHjDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "global-directory": "^4.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@cspell/cspell-service-bus": { + "version": "8.19.4", + "resolved": "https://registry.npmjs.org/@cspell/cspell-service-bus/-/cspell-service-bus-8.19.4.tgz", + "integrity": "sha512-uhY+v8z5JiUogizXW2Ft/gQf3eWrh5P9036jN2Dm0UiwEopG/PLshHcDjRDUiPdlihvA0RovrF0wDh4ptcrjuQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@cspell/cspell-types": { + "version": "8.19.4", + "resolved": "https://registry.npmjs.org/@cspell/cspell-types/-/cspell-types-8.19.4.tgz", + "integrity": "sha512-ekMWuNlFiVGfsKhfj4nmc8JCA+1ZltwJgxiKgDuwYtR09ie340RfXFF6YRd2VTW5zN7l4F1PfaAaPklVz6utSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@cspell/dict-ada": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@cspell/dict-ada/-/dict-ada-4.1.1.tgz", + "integrity": "sha512-E+0YW9RhZod/9Qy2gxfNZiHJjCYFlCdI69br1eviQQWB8yOTJX0JHXLs79kOYhSW0kINPVUdvddEBe6Lu6CjGQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-al": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@cspell/dict-al/-/dict-al-1.1.1.tgz", + "integrity": "sha512-sD8GCaZetgQL4+MaJLXqbzWcRjfKVp8x+px3HuCaaiATAAtvjwUQ5/Iubiqwfd1boIh2Y1/3EgM3TLQ7Q8e0wQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-aws": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@cspell/dict-aws/-/dict-aws-4.0.17.tgz", + "integrity": "sha512-ORcblTWcdlGjIbWrgKF+8CNEBQiLVKdUOFoTn0KPNkAYnFcdPP0muT4892h7H4Xafh3j72wqB4/loQ6Nti9E/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-bash": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@cspell/dict-bash/-/dict-bash-4.2.2.tgz", + "integrity": "sha512-kyWbwtX3TsCf5l49gGQIZkRLaB/P8g73GDRm41Zu8Mv51kjl2H7Au0TsEvHv7jzcsRLS6aUYaZv6Zsvk1fOz+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/dict-shell": "1.1.2" + } + }, + "node_modules/@cspell/dict-companies": { + "version": "3.2.10", + "resolved": "https://registry.npmjs.org/@cspell/dict-companies/-/dict-companies-3.2.10.tgz", + "integrity": "sha512-bJ1qnO1DkTn7JYGXvxp8FRQc4yq6tRXnrII+jbP8hHmq5TX5o1Wu+rdfpoUQaMWTl6balRvcMYiINDesnpR9Bw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-cpp": { + "version": "6.0.15", + "resolved": "https://registry.npmjs.org/@cspell/dict-cpp/-/dict-cpp-6.0.15.tgz", + "integrity": "sha512-N7MKK3llRNoBncygvrnLaGvmjo4xzVr5FbtAc9+MFGHK6/LeSySBupr1FM72XDaVSIsmBEe7sDYCHHwlI9Jb2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-cryptocurrencies": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@cspell/dict-cryptocurrencies/-/dict-cryptocurrencies-5.0.5.tgz", + "integrity": "sha512-R68hYYF/rtlE6T/dsObStzN5QZw+0aQBinAXuWCVqwdS7YZo0X33vGMfChkHaiCo3Z2+bkegqHlqxZF4TD3rUA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-csharp": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@cspell/dict-csharp/-/dict-csharp-4.0.8.tgz", + "integrity": "sha512-qmk45pKFHSxckl5mSlbHxmDitSsGMlk/XzFgt7emeTJWLNSTUK//MbYAkBNRtfzB4uD7pAFiKgpKgtJrTMRnrQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-css": { + "version": "4.0.19", + "resolved": "https://registry.npmjs.org/@cspell/dict-css/-/dict-css-4.0.19.tgz", + "integrity": "sha512-VYHtPnZt/Zd/ATbW3rtexWpBnHUohUrQOHff/2JBhsVgxOrksAxJnLAO43Q1ayLJBJUUwNVo+RU0sx0aaysZfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-dart": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@cspell/dict-dart/-/dict-dart-2.3.2.tgz", + "integrity": "sha512-sUiLW56t9gfZcu8iR/5EUg+KYyRD83Cjl3yjDEA2ApVuJvK1HhX+vn4e4k4YfjpUQMag8XO2AaRhARE09+/rqw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-data-science": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@cspell/dict-data-science/-/dict-data-science-2.0.13.tgz", + "integrity": "sha512-l1HMEhBJkPmw4I2YGVu2eBSKM89K9pVF+N6qIr5Uo5H3O979jVodtuwP8I7LyPrJnC6nz28oxeGRCLh9xC5CVA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-django": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@cspell/dict-django/-/dict-django-4.1.6.tgz", + "integrity": "sha512-SdbSFDGy9ulETqNz15oWv2+kpWLlk8DJYd573xhIkeRdcXOjskRuxjSZPKfW7O3NxN/KEf3gm3IevVOiNuFS+w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-docker": { + "version": "1.1.17", + "resolved": "https://registry.npmjs.org/@cspell/dict-docker/-/dict-docker-1.1.17.tgz", + "integrity": "sha512-OcnVTIpHIYYKhztNTyK8ShAnXTfnqs43hVH6p0py0wlcwRIXe5uj4f12n7zPf2CeBI7JAlPjEsV0Rlf4hbz/xQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-dotnet": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/@cspell/dict-dotnet/-/dict-dotnet-5.0.12.tgz", + "integrity": "sha512-FiV934kNieIjGTkiApu/WKvLYi/KBpvfWB2TSqpDQtmXZlt3uSa5blwblO1ZC8OvjH8RCq/31H5IdEYmTaZS7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-elixir": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@cspell/dict-elixir/-/dict-elixir-4.0.8.tgz", + "integrity": "sha512-CyfphrbMyl4Ms55Vzuj+mNmd693HjBFr9hvU+B2YbFEZprE5AG+EXLYTMRWrXbpds4AuZcvN3deM2XVB80BN/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-en_us": { + "version": "4.4.29", + "resolved": "https://registry.npmjs.org/@cspell/dict-en_us/-/dict-en_us-4.4.29.tgz", + "integrity": "sha512-G3B27++9ziRdgbrY/G/QZdFAnMzzx17u8nCb2Xyd4q6luLpzViRM/CW3jA+Mb/cGT5zR/9N+Yz9SrGu1s0bq7g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-en-common-misspellings": { + "version": "2.1.12", + "resolved": "https://registry.npmjs.org/@cspell/dict-en-common-misspellings/-/dict-en-common-misspellings-2.1.12.tgz", + "integrity": "sha512-14Eu6QGqyksqOd4fYPuRb58lK1Va7FQK9XxFsRKnZU8LhL3N+kj7YKDW+7aIaAN/0WGEqslGP6lGbQzNti8Akw==", + "dev": true, + "license": "CC BY-SA 4.0" + }, + "node_modules/@cspell/dict-en-gb": { + "version": "1.1.33", + "resolved": "https://registry.npmjs.org/@cspell/dict-en-gb/-/dict-en-gb-1.1.33.tgz", + "integrity": "sha512-tKSSUf9BJEV+GJQAYGw5e+ouhEe2ZXE620S7BLKe3ZmpnjlNG9JqlnaBhkIMxKnNFkLY2BP/EARzw31AZnOv4g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-filetypes": { + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@cspell/dict-filetypes/-/dict-filetypes-3.0.15.tgz", + "integrity": "sha512-uDMeqYlLlK476w/muEFQGBy9BdQWS0mQ7BJiy/iQv5XUWZxE2O54ZQd9nW8GyQMzAgoyg5SG4hf9l039Qt66oA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-flutter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@cspell/dict-flutter/-/dict-flutter-1.1.1.tgz", + "integrity": "sha512-UlOzRcH2tNbFhZmHJN48Za/2/MEdRHl2BMkCWZBYs+30b91mWvBfzaN4IJQU7dUZtowKayVIF9FzvLZtZokc5A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-fonts": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@cspell/dict-fonts/-/dict-fonts-4.0.5.tgz", + "integrity": "sha512-BbpkX10DUX/xzHs6lb7yzDf/LPjwYIBJHJlUXSBXDtK/1HaeS+Wqol4Mlm2+NAgZ7ikIE5DQMViTgBUY3ezNoQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-fsharp": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@cspell/dict-fsharp/-/dict-fsharp-1.1.1.tgz", + "integrity": "sha512-imhs0u87wEA4/cYjgzS0tAyaJpwG7vwtC8UyMFbwpmtw+/bgss+osNfyqhYRyS/ehVCWL17Ewx2UPkexjKyaBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-fullstack": { + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@cspell/dict-fullstack/-/dict-fullstack-3.2.8.tgz", + "integrity": "sha512-J6EeoeThvx/DFrcA2rJiCA6vfqwJMbkG0IcXhlsmRZmasIpanmxgt90OEaUazbZahFiuJT8wrhgQ1QgD1MsqBw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-gaming-terms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@cspell/dict-gaming-terms/-/dict-gaming-terms-1.1.2.tgz", + "integrity": "sha512-9XnOvaoTBscq0xuD6KTEIkk9hhdfBkkvJAIsvw3JMcnp1214OCGW8+kako5RqQ2vTZR3Tnf3pc57o7VgkM0q1Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-git": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-git/-/dict-git-3.1.0.tgz", + "integrity": "sha512-KEt9zGkxqGy2q1nwH4CbyqTSv5nadpn8BAlDnzlRcnL0Xb3LX9xTgSGShKvzb0bw35lHoYyLWN2ZKAqbC4pgGQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-golang": { + "version": "6.0.26", + "resolved": "https://registry.npmjs.org/@cspell/dict-golang/-/dict-golang-6.0.26.tgz", + "integrity": "sha512-YKA7Xm5KeOd14v5SQ4ll6afe9VSy3a2DWM7L9uBq4u3lXToRBQ1W5PRa+/Q9udd+DTURyVVnQ+7b9cnOlNxaRg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-google": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@cspell/dict-google/-/dict-google-1.0.9.tgz", + "integrity": "sha512-biL65POqialY0i4g6crj7pR6JnBkbsPovB2WDYkj3H4TuC/QXv7Pu5pdPxeUJA6TSCHI7T5twsO4VSVyRxD9CA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-haskell": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@cspell/dict-haskell/-/dict-haskell-4.0.6.tgz", + "integrity": "sha512-ib8SA5qgftExpYNjWhpYIgvDsZ/0wvKKxSP+kuSkkak520iPvTJumEpIE+qPcmJQo4NzdKMN8nEfaeci4OcFAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-html": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@cspell/dict-html/-/dict-html-4.0.14.tgz", + "integrity": "sha512-2bf7n+kS92g+cMKV0wr9o/Oq9n8JzU7CcrB96gIh2GHgnF+0xDOqO2W/1KeFAqOfqosoOVE48t+4dnEMkkoJ2Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-html-symbol-entities": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@cspell/dict-html-symbol-entities/-/dict-html-symbol-entities-4.0.5.tgz", + "integrity": "sha512-429alTD4cE0FIwpMucvSN35Ld87HCyuM8mF731KU5Rm4Je2SG6hmVx7nkBsLyrmH3sQukTcr1GaiZsiEg8svPA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-java": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/@cspell/dict-java/-/dict-java-5.0.12.tgz", + "integrity": "sha512-qPSNhTcl7LGJ5Qp6VN71H8zqvRQK04S08T67knMq9hTA8U7G1sTKzLmBaDOFhq17vNX/+rT+rbRYp+B5Nwza1A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-julia": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@cspell/dict-julia/-/dict-julia-1.1.1.tgz", + "integrity": "sha512-WylJR9TQ2cgwd5BWEOfdO3zvDB+L7kYFm0I9u0s9jKHWQ6yKmfKeMjU9oXxTBxIufhCXm92SKwwVNAC7gjv+yA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-k8s": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@cspell/dict-k8s/-/dict-k8s-1.0.12.tgz", + "integrity": "sha512-2LcllTWgaTfYC7DmkMPOn9GsBWsA4DZdlun4po8s2ysTP7CPEnZc1ZfK6pZ2eI4TsZemlUQQ+NZxMe9/QutQxg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-kotlin": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@cspell/dict-kotlin/-/dict-kotlin-1.1.1.tgz", + "integrity": "sha512-J3NzzfgmxRvEeOe3qUXnSJQCd38i/dpF9/t3quuWh6gXM+krsAXP75dY1CzDmS8mrJAlBdVBeAW5eAZTD8g86Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-latex": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@cspell/dict-latex/-/dict-latex-4.0.4.tgz", + "integrity": "sha512-YdTQhnTINEEm/LZgTzr9Voz4mzdOXH7YX+bSFs3hnkUHCUUtX/mhKgf1CFvZ0YNM2afjhQcmLaR9bDQVyYBvpA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-lorem-ipsum": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@cspell/dict-lorem-ipsum/-/dict-lorem-ipsum-4.0.5.tgz", + "integrity": "sha512-9a4TJYRcPWPBKkQAJ/whCu4uCAEgv/O2xAaZEI0n4y1/l18Yyx8pBKoIX5QuVXjjmKEkK7hi5SxyIsH7pFEK9Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-lua": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@cspell/dict-lua/-/dict-lua-4.0.8.tgz", + "integrity": "sha512-N4PkgNDMu9JVsRu7JBS/3E/dvfItRgk9w5ga2dKq+JupP2Y3lojNaAVFhXISh4Y0a6qXDn2clA6nvnavQ/jjLA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-makefile": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@cspell/dict-makefile/-/dict-makefile-1.0.5.tgz", + "integrity": "sha512-4vrVt7bGiK8Rx98tfRbYo42Xo2IstJkAF4tLLDMNQLkQ86msDlYSKG1ZCk8Abg+EdNcFAjNhXIiNO+w4KflGAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-markdown": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/@cspell/dict-markdown/-/dict-markdown-2.0.14.tgz", + "integrity": "sha512-uLKPNJsUcumMQTsZZgAK9RgDLyQhUz/uvbQTEkvF/Q4XfC1i/BnA8XrOrd0+Vp6+tPOKyA+omI5LRWfMu5K/Lw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@cspell/dict-css": "^4.0.19", + "@cspell/dict-html": "^4.0.14", + "@cspell/dict-html-symbol-entities": "^4.0.5", + "@cspell/dict-typescript": "^3.2.3" + } + }, + "node_modules/@cspell/dict-monkeyc": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@cspell/dict-monkeyc/-/dict-monkeyc-1.0.12.tgz", + "integrity": "sha512-MN7Vs11TdP5mbdNFQP5x2Ac8zOBm97ARg6zM5Sb53YQt/eMvXOMvrep7+/+8NJXs0jkp70bBzjqU4APcqBFNAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-node": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@cspell/dict-node/-/dict-node-5.0.9.tgz", + "integrity": "sha512-hO+ga+uYZ/WA4OtiMEyKt5rDUlUyu3nXMf8KVEeqq2msYvAPdldKBGH7lGONg6R/rPhv53Rb+0Y1SLdoK1+7wQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-npm": { + "version": "5.2.34", + "resolved": "https://registry.npmjs.org/@cspell/dict-npm/-/dict-npm-5.2.34.tgz", + "integrity": "sha512-M2MtfmYeHIPBuC8esMU4JQXHKma7Xt7VyBWUk67B62KDu61sxebQ2HeizdqmN2sLEJsTkq3bZT5PGzHpZ0LEWQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-php": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@cspell/dict-php/-/dict-php-4.1.1.tgz", + "integrity": "sha512-EXelI+4AftmdIGtA8HL8kr4WlUE11OqCSVlnIgZekmTkEGSZdYnkFdiJ5IANSALtlQ1mghKjz+OFqVs6yowgWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-powershell": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/@cspell/dict-powershell/-/dict-powershell-5.0.15.tgz", + "integrity": "sha512-l4S5PAcvCFcVDMJShrYD0X6Huv9dcsQPlsVsBGbH38wvuN7gS7+GxZFAjTNxDmTY1wrNi1cCatSg6Pu2BW4rgg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-public-licenses": { + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@cspell/dict-public-licenses/-/dict-public-licenses-2.0.15.tgz", + "integrity": "sha512-cJEOs901H13Pfy0fl4dCD1U+xpWIMaEPq8MeYU83FfDZvellAuSo4GqWCripfIqlhns/L6+UZEIJSOZnjgy7Wg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-python": { + "version": "4.2.25", + "resolved": "https://registry.npmjs.org/@cspell/dict-python/-/dict-python-4.2.25.tgz", + "integrity": "sha512-hDdN0YhKgpbtZVRjQ2c8jk+n0wQdidAKj1Fk8w7KEHb3YlY5uPJ0mAKJk7AJKPNLOlILoUmN+HAVJz+cfSbWYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/dict-data-science": "^2.0.13" + } + }, + "node_modules/@cspell/dict-r": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@cspell/dict-r/-/dict-r-2.1.1.tgz", + "integrity": "sha512-71Ka+yKfG4ZHEMEmDxc6+blFkeTTvgKbKAbwiwQAuKl3zpqs1Y0vUtwW2N4b3LgmSPhV3ODVY0y4m5ofqDuKMw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-ruby": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-ruby/-/dict-ruby-5.1.0.tgz", + "integrity": "sha512-9PJQB3cfkBULrMLp5kSAcFPpzf8oz9vFN+QYZABhQwWkGbuzCIXSorHrmWSASlx4yejt3brjaWS57zZ/YL5ZQQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-rust": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@cspell/dict-rust/-/dict-rust-4.1.2.tgz", + "integrity": "sha512-O1FHrumYcO+HZti3dHfBPUdnDFkI+nbYK3pxYmiM1sr+G0ebOd6qchmswS0Wsc6ZdEVNiPYJY/gZQR6jfW3uOg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-scala": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@cspell/dict-scala/-/dict-scala-5.0.9.tgz", + "integrity": "sha512-AjVcVAELgllybr1zk93CJ5wSUNu/Zb5kIubymR/GAYkMyBdYFCZ3Zbwn4Zz8GJlFFAbazABGOu0JPVbeY59vGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-shell": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@cspell/dict-shell/-/dict-shell-1.1.2.tgz", + "integrity": "sha512-WqOUvnwcHK1X61wAfwyXq04cn7KYyskg90j4lLg3sGGKMW9Sq13hs91pqrjC44Q+lQLgCobrTkMDw9Wyl9nRFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-software-terms": { + "version": "5.1.21", + "resolved": "https://registry.npmjs.org/@cspell/dict-software-terms/-/dict-software-terms-5.1.21.tgz", + "integrity": "sha512-3lAB4OXsf6rs5zbwe4/nKmwyAJAvjs5KTRrPckzHx7q9dYpviW+UxDyhevCCsRfmcu24OhYP7BVQWXxLvYk4xA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-sql": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@cspell/dict-sql/-/dict-sql-2.2.1.tgz", + "integrity": "sha512-qDHF8MpAYCf4pWU8NKbnVGzkoxMNrFqBHyG/dgrlic5EQiKANCLELYtGlX5auIMDLmTf1inA0eNtv74tyRJ/vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-svelte": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@cspell/dict-svelte/-/dict-svelte-1.0.7.tgz", + "integrity": "sha512-hGZsGqP0WdzKkdpeVLBivRuSNzOTvN036EBmpOwxH+FTY2DuUH7ecW+cSaMwOgmq5JFSdTcbTNFlNC8HN8lhaQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-swift": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@cspell/dict-swift/-/dict-swift-2.0.6.tgz", + "integrity": "sha512-PnpNbrIbex2aqU1kMgwEKvCzgbkHtj3dlFLPMqW1vSniop7YxaDTtvTUO4zA++ugYAEL+UK8vYrBwDPTjjvSnA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-terraform": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@cspell/dict-terraform/-/dict-terraform-1.1.3.tgz", + "integrity": "sha512-gr6wxCydwSFyyBKhBA2xkENXtVFToheqYYGFvlMZXWjviynXmh+NK/JTvTCk/VHk3+lzbO9EEQKee6VjrAUSbA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-typescript": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@cspell/dict-typescript/-/dict-typescript-3.2.3.tgz", + "integrity": "sha512-zXh1wYsNljQZfWWdSPYwQhpwiuW0KPW1dSd8idjMRvSD0aSvWWHoWlrMsmZeRl4qM4QCEAjua8+cjflm41cQBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-vue": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@cspell/dict-vue/-/dict-vue-3.0.5.tgz", + "integrity": "sha512-Mqutb8jbM+kIcywuPQCCaK5qQHTdaByoEO2J9LKFy3sqAdiBogNkrplqUK0HyyRFgCfbJUgjz3N85iCMcWH0JA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dynamic-import": { + "version": "8.19.4", + "resolved": "https://registry.npmjs.org/@cspell/dynamic-import/-/dynamic-import-8.19.4.tgz", + "integrity": "sha512-0LLghC64+SiwQS20Sa0VfFUBPVia1rNyo0bYeIDoB34AA3qwguDBVJJkthkpmaP1R2JeR/VmxmJowuARc4ZUxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/url": "8.19.4", + "import-meta-resolve": "^4.1.0" + }, + "engines": { + "node": ">=18.0" + } + }, + "node_modules/@cspell/filetypes": { + "version": "8.19.4", + "resolved": "https://registry.npmjs.org/@cspell/filetypes/-/filetypes-8.19.4.tgz", + "integrity": "sha512-D9hOCMyfKtKjjqQJB8F80PWsjCZhVGCGUMiDoQpcta0e+Zl8vHgzwaC0Ai4QUGBhwYEawHGiWUd7Y05u/WXiNQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@cspell/strong-weak-map": { + "version": "8.19.4", + "resolved": "https://registry.npmjs.org/@cspell/strong-weak-map/-/strong-weak-map-8.19.4.tgz", + "integrity": "sha512-MUfFaYD8YqVe32SQaYLI24/bNzaoyhdBIFY5pVrvMo1ZCvMl8AlfI2OcBXvcGb5aS5z7sCNCJm11UuoYbLI1zw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@cspell/url": { + "version": "8.19.4", + "resolved": "https://registry.npmjs.org/@cspell/url/-/url-8.19.4.tgz", + "integrity": "sha512-Pa474iBxS+lxsAL4XkETPGIq3EgMLCEb9agj3hAd2VGMTCApaiUvamR4b+uGXIPybN70piFxvzrfoxsG2uIP6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.1.tgz", + "integrity": "sha512-MKtmkA0BX87PKaO1NFRTFH+UnkgnmySQOvNxJubsadusqPEC2aJ9MOQiMceZJJ6oitUl/i0L6u0M1IrmAOmgBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.1.tgz", + "integrity": "sha512-rL7kaUnTkL9K+Cvo2pnCieqNpTKgQzy5f+N+5Iuko9HAoasP+xgprVh7KN/MaJVvVL1l0EzQq2MoqBHKSrDrag==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.7.tgz", + "integrity": "sha512-nkMp2mTICw32uE5NN+EsJ4f5N+IGFeCFu4bGpiKgb2Pq/7J/MpyLBeQ5ry4KKtRFZaYs6sTmcMYrSRIyj5DFKA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.0.1", + "@csstools/css-calc": "^2.1.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.4.tgz", + "integrity": "sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.3" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.3.tgz", + "integrity": "sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@es-joy/jsdoccomment": { + "version": "0.46.0", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.46.0.tgz", + "integrity": "sha512-C3Axuq1xd/9VqFZpW4YAzOx5O9q/LP46uIQy/iNDpHG3fmPa6TBtvfglMCs3RBiBxAIi0Go97r8+jvTt55XMyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "comment-parser": "1.4.1", + "esquery": "^1.6.0", + "jsdoc-type-pratt-parser": "~4.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.3", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.3.tgz", + "integrity": "sha512-1B1VkCq6FuUNlQvlBYb+1jDu/gV297TIs/OeiaSR9l1H27SVW55ONE1e1Vp16NqP683+xEGzxYtv4XCiDPaQiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@gulpjs/messages": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@gulpjs/messages/-/messages-1.1.0.tgz", + "integrity": "sha512-Ys9sazDatyTgZVb4xPlDufLweJ/Os2uHWOv+Caxvy2O85JcnT4M3vc73bi8pdLWlv3fdWQz3pdI9tVwo8rQQSg==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@gulpjs/to-absolute-glob": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@gulpjs/to-absolute-glob/-/to-absolute-glob-4.0.0.tgz", + "integrity": "sha512-kjotm7XJrJ6v+7knhPaRgaT6q8F8K2jiafwYdNHLzmV0uGLuZY43FK6smNSHUPrhq5kX2slCUy+RGG/xGqmIKA==", + "license": "MIT", + "dependencies": { + "is-negated-glob": "^1.0.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@jimp/core": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/core/-/core-1.6.0.tgz", + "integrity": "sha512-EQQlKU3s9QfdJqiSrZWNTxBs3rKXgO2W+GxNXDtwchF3a4IqxDheFX1ti+Env9hdJXDiYLp2jTRjlxhPthsk8w==", + "license": "MIT", + "dependencies": { + "@jimp/file-ops": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "await-to-js": "^3.0.0", + "exif-parser": "^0.1.12", + "file-type": "^16.0.0", + "mime": "3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/core/node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@jimp/diff": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/diff/-/diff-1.6.0.tgz", + "integrity": "sha512-+yUAQ5gvRC5D1WHYxjBHZI7JBRusGGSLf8AmPRPCenTzh4PA+wZ1xv2+cYqQwTfQHU5tXYOhA0xDytfHUf1Zyw==", + "license": "MIT", + "dependencies": { + "@jimp/plugin-resize": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "pixelmatch": "^5.3.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/file-ops": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/file-ops/-/file-ops-1.6.0.tgz", + "integrity": "sha512-Dx/bVDmgnRe1AlniRpCKrGRm5YvGmUwbDzt+MAkgmLGf+jvBT75hmMEZ003n9HQI/aPnm/YKnXjg/hOpzNCpHQ==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/js-bmp": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/js-bmp/-/js-bmp-1.6.0.tgz", + "integrity": "sha512-FU6Q5PC/e3yzLyBDXupR3SnL3htU7S3KEs4e6rjDP6gNEOXRFsWs6YD3hXuXd50jd8ummy+q2WSwuGkr8wi+Gw==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "bmp-ts": "^1.0.9" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/js-gif": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/js-gif/-/js-gif-1.6.0.tgz", + "integrity": "sha512-N9CZPHOrJTsAUoWkWZstLPpwT5AwJ0wge+47+ix3++SdSL/H2QzyMqxbcDYNFe4MoI5MIhATfb0/dl/wmX221g==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "gifwrap": "^0.10.1", + "omggif": "^1.0.10" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/js-jpeg": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/js-jpeg/-/js-jpeg-1.6.0.tgz", + "integrity": "sha512-6vgFDqeusblf5Pok6B2DUiMXplH8RhIKAryj1yn+007SIAQ0khM1Uptxmpku/0MfbClx2r7pnJv9gWpAEJdMVA==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "jpeg-js": "^0.4.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/js-png": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/js-png/-/js-png-1.6.0.tgz", + "integrity": "sha512-AbQHScy3hDDgMRNfG0tPjL88AV6qKAILGReIa3ATpW5QFjBKpisvUaOqhzJ7Reic1oawx3Riyv152gaPfqsBVg==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "pngjs": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/js-tiff": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/js-tiff/-/js-tiff-1.6.0.tgz", + "integrity": "sha512-zhReR8/7KO+adijj3h0ZQUOiun3mXUv79zYEAKvE0O+rP7EhgtKvWJOZfRzdZSNv0Pu1rKtgM72qgtwe2tFvyw==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "utif2": "^4.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-blit": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-1.6.0.tgz", + "integrity": "sha512-M+uRWl1csi7qilnSK8uxK4RJMSuVeBiO1AY0+7APnfUbQNZm6hCe0CCFv1Iyw1D/Dhb8ph8fQgm5mwM0eSxgVA==", + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-blur": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-1.6.0.tgz", + "integrity": "sha512-zrM7iic1OTwUCb0g/rN5y+UnmdEsT3IfuCXCJJNs8SZzP0MkZ1eTvuwK9ZidCuMo4+J3xkzCidRwYXB5CyGZTw==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/utils": "1.6.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-circle": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-1.6.0.tgz", + "integrity": "sha512-xt1Gp+LtdMKAXfDp3HNaG30SPZW6AQ7dtAtTnoRKorRi+5yCJjKqXRgkewS5bvj8DEh87Ko1ydJfzqS3P2tdWw==", + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-color": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-1.6.0.tgz", + "integrity": "sha512-J5q8IVCpkBsxIXM+45XOXTrsyfblyMZg3a9eAo0P7VPH4+CrvyNQwaYatbAIamSIN1YzxmO3DkIZXzRjFSz1SA==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "tinycolor2": "^1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-contain": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-1.6.0.tgz", + "integrity": "sha512-oN/n+Vdq/Qg9bB4yOBOxtY9IPAtEfES8J1n9Ddx+XhGBYT1/QTU/JYkGaAkIGoPnyYvmLEDqMz2SGihqlpqfzQ==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/plugin-blit": "1.6.0", + "@jimp/plugin-resize": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-cover": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-1.6.0.tgz", + "integrity": "sha512-Iow0h6yqSC269YUJ8HC3Q/MpCi2V55sMlbkkTTx4zPvd8mWZlC0ykrNDeAy9IJegrQ7v5E99rJwmQu25lygKLA==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/plugin-crop": "1.6.0", + "@jimp/plugin-resize": "1.6.0", + "@jimp/types": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-crop": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-1.6.0.tgz", + "integrity": "sha512-KqZkEhvs+21USdySCUDI+GFa393eDIzbi1smBqkUPTE+pRwSWMAf01D5OC3ZWB+xZsNla93BDS9iCkLHA8wang==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-displace": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-1.6.0.tgz", + "integrity": "sha512-4Y10X9qwr5F+Bo5ME356XSACEF55485j5nGdiyJ9hYzjQP9nGgxNJaZ4SAOqpd+k5sFaIeD7SQ0Occ26uIng5Q==", + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-dither": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-1.6.0.tgz", + "integrity": "sha512-600d1RxY0pKwgyU0tgMahLNKsqEcxGdbgXadCiVCoGd6V6glyCvkNrnnwC0n5aJ56Htkj88PToSdF88tNVZEEQ==", + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-fisheye": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-1.6.0.tgz", + "integrity": "sha512-E5QHKWSCBFtpgZarlmN3Q6+rTQxjirFqo44ohoTjzYVrDI6B6beXNnPIThJgPr0Y9GwfzgyarKvQuQuqCnnfbA==", + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-flip": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-1.6.0.tgz", + "integrity": "sha512-/+rJVDuBIVOgwoyVkBjUFHtP+wmW0r+r5OQ2GpatQofToPVbJw1DdYWXlwviSx7hvixTWLKVgRWQ5Dw862emDg==", + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-hash": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-hash/-/plugin-hash-1.6.0.tgz", + "integrity": "sha512-wWzl0kTpDJgYVbZdajTf+4NBSKvmI3bRI8q6EH9CVeIHps9VWVsUvEyb7rpbcwVLWYuzDtP2R0lTT6WeBNQH9Q==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/js-bmp": "1.6.0", + "@jimp/js-jpeg": "1.6.0", + "@jimp/js-png": "1.6.0", + "@jimp/js-tiff": "1.6.0", + "@jimp/plugin-color": "1.6.0", + "@jimp/plugin-resize": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "any-base": "^1.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-mask": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-1.6.0.tgz", + "integrity": "sha512-Cwy7ExSJMZszvkad8NV8o/Z92X2kFUFM8mcDAhNVxU0Q6tA0op2UKRJY51eoK8r6eds/qak3FQkXakvNabdLnA==", + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-print": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-1.6.0.tgz", + "integrity": "sha512-zarTIJi8fjoGMSI/M3Xh5yY9T65p03XJmPsuNet19K/Q7mwRU6EV2pfj+28++2PV2NJ+htDF5uecAlnGyxFN2A==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/js-jpeg": "1.6.0", + "@jimp/js-png": "1.6.0", + "@jimp/plugin-blit": "1.6.0", + "@jimp/types": "1.6.0", + "parse-bmfont-ascii": "^1.0.6", + "parse-bmfont-binary": "^1.0.6", + "parse-bmfont-xml": "^1.1.6", + "simple-xml-to-json": "^1.2.2", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-quantize": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-quantize/-/plugin-quantize-1.6.0.tgz", + "integrity": "sha512-EmzZ/s9StYQwbpG6rUGBCisc3f64JIhSH+ncTJd+iFGtGo0YvSeMdAd+zqgiHpfZoOL54dNavZNjF4otK+mvlg==", + "license": "MIT", + "dependencies": { + "image-q": "^4.0.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-resize": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-1.6.0.tgz", + "integrity": "sha512-uSUD1mqXN9i1SGSz5ov3keRZ7S9L32/mAQG08wUwZiEi5FpbV0K8A8l1zkazAIZi9IJzLlTauRNU41Mi8IF9fA==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-rotate": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-1.6.0.tgz", + "integrity": "sha512-JagdjBLnUZGSG4xjCLkIpQOZZ3Mjbg8aGCCi4G69qR+OjNpOeGI7N2EQlfK/WE8BEHOW5vdjSyglNqcYbQBWRw==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/plugin-crop": "1.6.0", + "@jimp/plugin-resize": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-threshold": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-1.6.0.tgz", + "integrity": "sha512-M59m5dzLoHOVWdM41O8z9SyySzcDn43xHseOH0HavjsfQsT56GGCC4QzU1banJidbUrePhzoEdS42uFE8Fei8w==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/plugin-color": "1.6.0", + "@jimp/plugin-hash": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/types": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/types/-/types-1.6.0.tgz", + "integrity": "sha512-7UfRsiKo5GZTAATxm2qQ7jqmUXP0DxTArztllTcYdyw6Xi5oT4RaoXynVtCD4UyLK5gJgkZJcwonoijrhYFKfg==", + "license": "MIT", + "dependencies": { + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/utils": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-1.6.0.tgz", + "integrity": "sha512-gqFTGEosKbOkYF/WFj26jMHOI5OH2jeP1MmC/zbK6BF6VJBf8rIC5898dPfSzZEbSA0wbbV5slbntWVc5PKLFA==", + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.0", + "tinycolor2": "^1.6.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jsep-plugin/assignment": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@jsep-plugin/assignment/-/assignment-1.3.0.tgz", + "integrity": "sha512-VVgV+CXrhbMI3aSusQyclHkenWSAm95WaiKrMxRFam3JSUiIaQjoMIw2sEs/OX4XifnqeQUN4DYbJjlA8EfktQ==", + "license": "MIT", + "engines": { + "node": ">= 10.16.0" + }, + "peerDependencies": { + "jsep": "^0.4.0||^1.0.0" + } + }, + "node_modules/@jsep-plugin/regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@jsep-plugin/regex/-/regex-1.0.4.tgz", + "integrity": "sha512-q7qL4Mgjs1vByCaTnDFcBnV9HS7GVPJX5vyVoCgZHNSC9rjwIlmbXG5sUuorR5ndfHAIlJ8pVStxvjXHbNvtUg==", + "license": "MIT", + "engines": { + "node": ">= 10.16.0" + }, + "peerDependencies": { + "jsep": "^0.4.0||^1.0.0" + } + }, + "node_modules/@jsonjoy.com/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.1.1.tgz", + "integrity": "sha512-osjeBqMJ2lb/j/M8NCPjs1ylqWIcTRTycIhVB5pt6LgzgeRSb0YRZ7j9RfA8wIUrsr/medIuhVyonXRZWLyfdw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/base64": "^1.1.1", + "@jsonjoy.com/util": "^1.1.2", + "hyperdyperid": "^1.2.0", + "thingies": "^1.20.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.5.0.tgz", + "integrity": "sha512-ojoNsrIuPI9g6o8UxhraZQSyF2ByJanAY4cTFbc8Mf2AXEF4aQRGY1dJxyJpuyav8r9FGflEt/Ff3u5Nt6YMPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@lezer/common": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.1.tgz", + "integrity": "sha512-6YRVG9vBkaY7p1IVxL4s44n5nUnaNnGM2/AckNgYOnxTG2kWh1vR8BMxPseWPjRNpb5VtXnMpeYAEAADoRV1Iw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@lezer/highlight": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz", + "integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz", + "integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", + "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@napi-rs/nice": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice/-/nice-1.0.1.tgz", + "integrity": "sha512-zM0mVWSXE0a0h9aKACLwKmD6nHcRiKrPpCfvaKqG1CqDEyjEawId0ocXxVzPMCAm6kkWr2P025msfxXEnt8UGQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "optionalDependencies": { + "@napi-rs/nice-android-arm-eabi": "1.0.1", + "@napi-rs/nice-android-arm64": "1.0.1", + "@napi-rs/nice-darwin-arm64": "1.0.1", + "@napi-rs/nice-darwin-x64": "1.0.1", + "@napi-rs/nice-freebsd-x64": "1.0.1", + "@napi-rs/nice-linux-arm-gnueabihf": "1.0.1", + "@napi-rs/nice-linux-arm64-gnu": "1.0.1", + "@napi-rs/nice-linux-arm64-musl": "1.0.1", + "@napi-rs/nice-linux-ppc64-gnu": "1.0.1", + "@napi-rs/nice-linux-riscv64-gnu": "1.0.1", + "@napi-rs/nice-linux-s390x-gnu": "1.0.1", + "@napi-rs/nice-linux-x64-gnu": "1.0.1", + "@napi-rs/nice-linux-x64-musl": "1.0.1", + "@napi-rs/nice-win32-arm64-msvc": "1.0.1", + "@napi-rs/nice-win32-ia32-msvc": "1.0.1", + "@napi-rs/nice-win32-x64-msvc": "1.0.1" + } + }, + "node_modules/@napi-rs/nice-android-arm-eabi": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm-eabi/-/nice-android-arm-eabi-1.0.1.tgz", + "integrity": "sha512-5qpvOu5IGwDo7MEKVqqyAxF90I6aLj4n07OzpARdgDRfz8UbBztTByBp0RC59r3J1Ij8uzYi6jI7r5Lws7nn6w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-android-arm64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm64/-/nice-android-arm64-1.0.1.tgz", + "integrity": "sha512-GqvXL0P8fZ+mQqG1g0o4AO9hJjQaeYG84FRfZaYjyJtZZZcMjXW5TwkL8Y8UApheJgyE13TQ4YNUssQaTgTyvA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-darwin-arm64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-arm64/-/nice-darwin-arm64-1.0.1.tgz", + "integrity": "sha512-91k3HEqUl2fsrz/sKkuEkscj6EAj3/eZNCLqzD2AA0TtVbkQi8nqxZCZDMkfklULmxLkMxuUdKe7RvG/T6s2AA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-darwin-x64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-x64/-/nice-darwin-x64-1.0.1.tgz", + "integrity": "sha512-jXnMleYSIR/+TAN/p5u+NkCA7yidgswx5ftqzXdD5wgy/hNR92oerTXHc0jrlBisbd7DpzoaGY4cFD7Sm5GlgQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-freebsd-x64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-freebsd-x64/-/nice-freebsd-x64-1.0.1.tgz", + "integrity": "sha512-j+iJ/ezONXRQsVIB/FJfwjeQXX7A2tf3gEXs4WUGFrJjpe/z2KB7sOv6zpkm08PofF36C9S7wTNuzHZ/Iiccfw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm-gnueabihf": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm-gnueabihf/-/nice-linux-arm-gnueabihf-1.0.1.tgz", + "integrity": "sha512-G8RgJ8FYXYkkSGQwywAUh84m946UTn6l03/vmEXBYNJxQJcD+I3B3k5jmjFG/OPiU8DfvxutOP8bi+F89MCV7Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm64-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-gnu/-/nice-linux-arm64-gnu-1.0.1.tgz", + "integrity": "sha512-IMDak59/W5JSab1oZvmNbrms3mHqcreaCeClUjwlwDr0m3BoR09ZiN8cKFBzuSlXgRdZ4PNqCYNeGQv7YMTjuA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm64-musl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-musl/-/nice-linux-arm64-musl-1.0.1.tgz", + "integrity": "sha512-wG8fa2VKuWM4CfjOjjRX9YLIbysSVV1S3Kgm2Fnc67ap/soHBeYZa6AGMeR5BJAylYRjnoVOzV19Cmkco3QEPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-ppc64-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-ppc64-gnu/-/nice-linux-ppc64-gnu-1.0.1.tgz", + "integrity": "sha512-lxQ9WrBf0IlNTCA9oS2jg/iAjQyTI6JHzABV664LLrLA/SIdD+I1i3Mjf7TsnoUbgopBcCuDztVLfJ0q9ubf6Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-riscv64-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-riscv64-gnu/-/nice-linux-riscv64-gnu-1.0.1.tgz", + "integrity": "sha512-3xs69dO8WSWBb13KBVex+yvxmUeEsdWexxibqskzoKaWx9AIqkMbWmE2npkazJoopPKX2ULKd8Fm9veEn0g4Ig==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-s390x-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-s390x-gnu/-/nice-linux-s390x-gnu-1.0.1.tgz", + "integrity": "sha512-lMFI3i9rlW7hgToyAzTaEybQYGbQHDrpRkg+1gJWEpH0PLAQoZ8jiY0IzakLfNWnVda1eTYYlxxFYzW8Rqczkg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-x64-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-gnu/-/nice-linux-x64-gnu-1.0.1.tgz", + "integrity": "sha512-XQAJs7DRN2GpLN6Fb+ZdGFeYZDdGl2Fn3TmFlqEL5JorgWKrQGRUrpGKbgZ25UeZPILuTKJ+OowG2avN8mThBA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-x64-musl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-musl/-/nice-linux-x64-musl-1.0.1.tgz", + "integrity": "sha512-/rodHpRSgiI9o1faq9SZOp/o2QkKQg7T+DK0R5AkbnI/YxvAIEHf2cngjYzLMQSQgUhxym+LFr+UGZx4vK4QdQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-arm64-msvc": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-arm64-msvc/-/nice-win32-arm64-msvc-1.0.1.tgz", + "integrity": "sha512-rEcz9vZymaCB3OqEXoHnp9YViLct8ugF+6uO5McifTedjq4QMQs3DHz35xBEGhH3gJWEsXMUbzazkz5KNM5YUg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-ia32-msvc": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-ia32-msvc/-/nice-win32-ia32-msvc-1.0.1.tgz", + "integrity": "sha512-t7eBAyPUrWL8su3gDxw9xxxqNwZzAqKo0Szv3IjVQd1GpXXVkb6vBBQUuxfIYaXMzZLwlxRQ7uzM2vdUE9ULGw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-x64-msvc": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-x64-msvc/-/nice-win32-x64-msvc-1.0.1.tgz", + "integrity": "sha512-JlF+uDcatt3St2ntBG8H02F1mM45i5SF9W+bIKiReVE6wiy3o16oBP/yxt+RZ+N6LbCImJXJ6bXNO2kn9AXicg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { + "version": "5.1.1-v1", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", + "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-scope": "5.1.1" + } + }, + "node_modules/@nightwatch/chai": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@nightwatch/chai/-/chai-5.0.3.tgz", + "integrity": "sha512-1OIkOf/7jswOC3/t+Add/HVQO8ib75kz6BVYSNeWGghTlmHUqYEfNJ6vcACbXrn/4v3+9iRlWixuhFkxXkU/RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "1.1.0", + "check-error": "1.0.2", + "deep-eql": "4.0.1", + "loupe": "^2.3.7", + "pathval": "1.1.1", + "type-detect": "4.0.8" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@nightwatch/html-reporter-template": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@nightwatch/html-reporter-template/-/html-reporter-template-0.3.0.tgz", + "integrity": "sha512-Mze1z6pmUz2O8N9w1/h3QWz1lzMig45PGyh8PrL9ERs3FxVnIX0RCn37vjZUYiV4wgjZOg41JjdcpriZ3dJxkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nightwatch/nightwatch-inspector": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@nightwatch/nightwatch-inspector/-/nightwatch-inspector-1.0.1.tgz", + "integrity": "sha512-/ax11EOB4eJXT5VioMztcalbCtsNeuFn6icfT75qPLBmkxLvThePSfyGTys+t9AULUR0ug0wMDMiLV1Oy586Fg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "archiver": "^5.3.1" + } + }, + "node_modules/@noble/hashes": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.6.1.tgz", + "integrity": "sha512-pq5D8h10hHBjyqX+cfBm0i8JUXJ0UhczFc4r74zbuT9XgewFo2E3J1cOaGtdZynILNmQ685YWGzGE1Zv6io50w==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.28", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz", + "integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" + }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "license": "MIT" + }, + "node_modules/@sindresorhus/is": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-6.3.1.tgz", + "integrity": "sha512-FX4MfcifwJyFOI2lPoX7PQxCqx8BG1HCho7WdiXwpEQx1Ycij0JxkfYtGK7yqNScrZGSlt6RE6sw8QYoH7eKnQ==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@testim/chrome-version": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@testim/chrome-version/-/chrome-version-1.1.4.tgz", + "integrity": "sha512-kIhULpw9TrGYnHp/8VfdcneIcxKnLixmADtukQRtJUmsVlMg0niMkwV0xZmi8hqa57xqilIHjWFA0GKvEjVU5g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "license": "MIT" + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/chai": { + "version": "4.3.20", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.20.tgz", + "integrity": "sha512-/pC9HAB5I/xMlc5FP77qjCnI16ChlJfW0tGa0IUcFn38VJrTV6DeZ60NU5KZBtaOZqjdpwTWohz5HU1RrhiYxQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/expect": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/@types/expect/-/expect-1.20.4.tgz", + "integrity": "sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg==", + "license": "MIT" + }, + "node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.5.tgz", + "integrity": "sha512-GLZPrd9ckqEBFMcVM/qRFAP0Hg3qiVEojgEFsx/N/zKXsBzbGF6z5FBDpZ0+Xhp1xr+qRZYjfGr1cWHB9oFHSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/express/node_modules/@types/express-serve-static-core": { + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/http-proxy": { + "version": "1.17.15", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.15.tgz", + "integrity": "sha512-25g5atgiVNTIv0LBDTg1H74Hvayx0ajtJPLLcYE3whFv75J0pWNtOBzaXJQgDTmrX1bx5U9YC2w/n65BN1HwRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.10.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.9.tgz", + "integrity": "sha512-Ir6hwgsKyNESl/gLOcEz3krR4CBGgliDqBQ2ma4wIhEx0w+xnoeTq3tdrNw15kU3SxogDjOgv9sqdtLW8mIHaw==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/@types/node-forge": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", + "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/qs": { + "version": "6.9.18", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz", + "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/retry": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", + "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/sax": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz", + "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/selenium-webdriver": { + "version": "4.1.28", + "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-4.1.28.tgz", + "integrity": "sha512-Au7CXegiS7oapbB16zxPToY4Cjzi9UQQMf3W2ZZM8PigMLTGR3iUAHjPUTddyE5g1SBjT/qpmvlsAQLBfNAdKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/ws": "*" + } + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-index": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/sockjs": { + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, + "node_modules/@types/vinyl": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/@types/vinyl/-/vinyl-2.0.12.tgz", + "integrity": "sha512-Sr2fYMBUVGYq8kj3UthXFAu5UN6ZW+rYr4NACjZQJvHvj+c8lYv0CahmZ2P/r7iUkN44gGUBwqxZkrKXYPb7cw==", + "license": "MIT", + "dependencies": { + "@types/expect": "^1.20.4", + "@types/node": "*" + } + }, + "node_modules/@types/ws": { + "version": "8.5.13", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz", + "integrity": "sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@wavesenterprise/crypto-gost-js": { + "version": "2.1.0-RC1", + "resolved": "https://registry.npmjs.org/@wavesenterprise/crypto-gost-js/-/crypto-gost-js-2.1.0-RC1.tgz", + "integrity": "sha512-liAR3/T/vxnEgNUE00Llt+sDvKYqo+sm/L7tqkJorg2ha3SsplOSXAqpH0t4Ya0gRj8qN8zXqO+WwLCxXXuQcw==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.11", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz", + "integrity": "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/14": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/14/-/14-3.1.6.tgz", + "integrity": "sha512-Dy9rp+KQshffwe5NjDudvNNhdvNlgTqwv1crwBIuK0Dq/RrAoY3HmbiTZpuuoE6TX32HEDMgZ93q1mjr76UYRA==", + "license": "ISC", + "dependencies": { + "gulp": "*", + "gulp-concat": "*", + "gulp-imagemin": "*", + "gulp-minify-css": "*", + "gulp-minify-html": "*", + "gulp-rename": "*", + "gulp-uglify": "*" + } + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "acorn": "^8.14.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==", + "license": "BSD-3-Clause OR MIT", + "engines": { + "node": ">=0.4.2" + } + }, + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.1.0" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-gray": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", + "integrity": "sha512-HrgGIZUl8h2EHuZaU9hTR/cU5nhKxpVE1V6kdGsQ8e4zirElJ5fvtfc8N7Q1oq1aatO275i8pUFUCpNWCAnVWw==", + "license": "MIT", + "dependencies": { + "ansi-wrap": "0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "dev": true, + "engines": [ + "node >= 0.8.0" + ], + "license": "Apache-2.0", + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-to-html": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/ansi-to-html/-/ansi-to-html-0.7.2.tgz", + "integrity": "sha512-v6MqmEpNlxF+POuyhKkidusCHWWkaLcGRURzivcU3I9tv7k4JVhFcnukrM5Rlk2rUywdZuzYAZ+kbZqWCnfN3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^2.2.0" + }, + "bin": { + "ansi-to-html": "bin/ansi-to-html" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/ansi-to-html/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/ansi-wrap": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", + "integrity": "sha512-ZyznvL8k/FZeQHr2T6LzcJ/+vBApDnMNZvfVFy3At0knswWd6rJ3/0Hhmpu8oqa6C92npmozs890sX9Dl6q+Qw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/any-base": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz", + "integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==", + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", + "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/archive-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/archive-type/-/archive-type-4.0.0.tgz", + "integrity": "sha512-zV4Ky0v1F8dBrdYElwTvQhweQ0P7Kwc1aluqJsYtOBP01jXcWCyW2IEfI1YiqsG+Iy7ZR+o5LF1N+PGECBxHWA==", + "license": "MIT", + "optional": true, + "dependencies": { + "file-type": "^4.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/archive-type/node_modules/file-type": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-4.4.0.tgz", + "integrity": "sha512-f2UbFQEk7LXgWpi5ntcO86OeA/cC80fuDDDaX/fZ2ZGel+AF7leRQqBBW1eJNiiQkrZlAoM6P+VYP5P6bOlDEQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/archiver": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", + "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "archiver-utils": "^2.1.0", + "async": "^3.2.4", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archiver/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/are-docs-informative": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", + "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/argh": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/argh/-/argh-0.1.4.tgz", + "integrity": "sha512-sQN85FUGbEUBLyQiSJp4v8yAHTST2ao1WVXb/L8jkVqQTsypZuJQD0gMVeOLoSZBz21p22izF6HsBQP16QKQtg==", + "license": "MIT" + }, + "node_modules/argon2-browser": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/argon2-browser/-/argon2-browser-1.18.0.tgz", + "integrity": "sha512-ImVAGIItnFnvET1exhsQB7apRztcoC5TnlSqernMJDUjbc/DLq3UEYeXFrLPrlaIl8cVfwnXb6wX2KpFf2zxHw==", + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", + "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "deep-equal": "^2.0.5" + } + }, + "node_modules/aria-query/node_modules/deep-equal": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", + "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.5", + "es-get-iterator": "^1.1.3", + "get-intrinsic": "^1.2.2", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.2", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.1", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/aria-query/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-differ": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", + "integrity": "sha512-LeZY+DZDRnvP7eMuQ6LHfCzUGxAAIViUBliK24P3hWXL6y4SortgR6Nim6xrkfSLlmH0+k+9NYNwVC2s53ZrYQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", + "integrity": "sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/array-slice": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", + "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-timsort": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz", + "integrity": "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/arrive": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/arrive/-/arrive-2.5.2.tgz", + "integrity": "sha512-A9asXhuUR6pgHtwUciZw4FRZDR09ZwVsz1ckEGE6W9gXWrYM9cAkuKzHplpYN0bd/iobg0xqQ9fpRr/GHPsXUw==", + "license": "MIT" + }, + "node_modules/asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/asn1.js/node_modules/bn.js": { + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", + "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", + "license": "MIT" + }, + "node_modules/assert": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", + "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "is-nan": "^1.3.2", + "object-is": "^1.1.5", + "object.assign": "^4.1.4", + "util": "^0.12.5" + } + }, + "node_modules/assert/node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-done": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/async-done/-/async-done-2.0.0.tgz", + "integrity": "sha512-j0s3bzYq9yKIVLKGE/tWlCpa3PfFLcrDZLTSVdnnCTGagXuXBJO4SsY9Xdk/fQBirCkH4evW5xOeJXqlAQFdsw==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.4.4", + "once": "^1.4.0", + "stream-exhaust": "^1.0.2" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/async-settle": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-2.0.0.tgz", + "integrity": "sha512-Obu/KE8FurfQRN6ODdHN9LuXqwC+JFIM9NRyZqJJ4ZfLJmIYN9Rg0/kb+wF70VV5+fJusTMQlJ1t5rF7J/ETdg==", + "license": "MIT", + "dependencies": { + "async-done": "^2.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/autoprefixer": { + "version": "10.4.24", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.24.tgz", + "integrity": "sha512-uHZg7N9ULTVbutaIsDRoUkoS8/h3bdsmVJYZ5l3wv8Cp/6UIIoRDm90hZ+BwxUj/hGBEzLxdHNSKuFpn8WOyZw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001766", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/avsc": { + "version": "5.7.9", + "resolved": "https://registry.npmjs.org/avsc/-/avsc-5.7.9.tgz", + "integrity": "sha512-yOA4wFeI7ET3v32Di/sUybQ+ttP20JHSW3mxLuNGeO0uD6PPcvLrIQXSvy/rhJOWU5JrYh7U4OHplWMmtAtjMg==", + "license": "MIT", + "engines": { + "node": ">=0.11" + } + }, + "node_modules/await-to-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/await-to-js/-/await-to-js-3.0.0.tgz", + "integrity": "sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/axe-core": { + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.2.tgz", + "integrity": "sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/axios": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", + "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/b4a": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.0.tgz", + "integrity": "sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==", + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, + "node_modules/babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha512-XqYMR2dfdGMW+hd0IUZ2PwK+fGeFkOxZJ0wY+JaQAHzt1Zx8LcvpiZD2NiGkEG8qx0CfkAOr5xt76d1e8vG90g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + } + }, + "node_modules/babel-code-frame/node_modules/js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha512-RjTcuD4xjtthQkaWH7dFlH85L+QaVtSoOyGdZ3g6HFhS9dFNDfLyqgm2NFe2X6cQpeFmt0452FJjFG5UameExg==", + "dev": true, + "license": "MIT" + }, + "node_modules/babel-loader": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-10.0.0.tgz", + "integrity": "sha512-z8jt+EdS61AMw22nSfoNJAZ0vrtmhPRVi6ghL3rCeRZI8cdNYFiV5xeV3HbE7rlZZNmGH8BVccwWt8/ED0QOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^5.0.0" + }, + "engines": { + "node": "^18.20.0 || ^20.10.0 || >=22.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0", + "webpack": ">=5.61.0" + } + }, + "node_modules/babel-messages": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "integrity": "sha512-Bl3ZiA+LjqaMtNYopA9TYE9HP1tQ+E5dLxE0XrAzcIJeK2UqF0/EaqXwBn9esd4UmTfEab+P+UYQ1GnioFIb/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-runtime": "^6.22.0" + } + }, + "node_modules/babel-plugin-dynamic-import-node": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "object.assign": "^4.1.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.15.tgz", + "integrity": "sha512-hR3GwrRwHUfYwGfrisXPIDP3JcYfBrW7wKE7+Au6wDYl7fm/ka1NEII6kORzxNU556JjfidZeBsO10kYvtV1aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-define-polyfill-provider": "^0.6.6", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", + "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.5", + "core-js-compat": "^3.43.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.6.tgz", + "integrity": "sha512-hYm+XLYRMvupxiQzrvXUj7YyvFFVfv5gI0R71AJzudg1g2AI2vyCPPIFEBjk162/wFzti3inBHo7isWFuEVS/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.6" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-transform-builtin-extend": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-builtin-extend/-/babel-plugin-transform-builtin-extend-1.1.2.tgz", + "integrity": "sha512-foUQxHjMiLNynzJaBKrIoZfoRY22S620MLafGC5UfnBEwqcBODIFcgwqqzHE8Hj590lbUJCQ2uBo9y08+sYIkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-runtime": "^6.2.0", + "babel-template": "^6.3.0" + } + }, + "node_modules/babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "node_modules/babel-runtime/node_modules/core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", + "dev": true, + "hasInstallScript": true, + "license": "MIT" + }, + "node_modules/babel-runtime/node_modules/regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/babel-template": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", + "integrity": "sha512-PCOcLFW7/eazGUKIoqH97sO9A2UYMahsn/yRQ7uOk37iutwjq7ODtcTNF+iFDSHNfkctqsLRjLP7URnOx0T1fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-runtime": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "lodash": "^4.17.4" + } + }, + "node_modules/babel-traverse": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", + "integrity": "sha512-iSxeXx7apsjCHe9c7n8VtRXGzI2Bk1rBSOJgCCjfyXb6v1aCqE1KSEpq/8SXuVN8Ka/Rh1WDTF0MDzkvTA4MIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-code-frame": "^6.26.0", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" + } + }, + "node_modules/babel-traverse/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/babel-traverse/node_modules/globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/babel-traverse/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha512-zhe3V/26rCWsEZK8kZN+HaQj5yQ1CilTObixFzKW1UWjqG7618Twz6YEsCnjfg5gBcJh02DrpCkS9h98ZqDY+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "node_modules/babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true, + "license": "MIT", + "bin": { + "babylon": "bin/babylon.js" + } + }, + "node_modules/bach": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/bach/-/bach-2.0.1.tgz", + "integrity": "sha512-A7bvGMGiTOxGMpNupYl9HQTf0FFDNF4VCmks4PJpFyN1AX2pdKuxuwdvUz2Hu388wcgp+OvGFNsumBfFNkR7eg==", + "license": "MIT", + "dependencies": { + "async-done": "^2.0.0", + "async-settle": "^2.0.0", + "now-and-later": "^3.0.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/bare-events": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", + "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", + "license": "Apache-2.0", + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/base64-loader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base64-loader/-/base64-loader-1.0.0.tgz", + "integrity": "sha512-p32+F8dg+ANGx7s8QsZS74ZPHfIycmC2yZcoerzFgbersIYWitPbbF39G6SBx3gyvzyLH5nt1ooocxr0IHuWKA==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", + "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/basic-auth/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/basic-ftp": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "dev": true, + "license": "MIT" + }, + "node_modules/bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==", + "license": "MIT" + }, + "node_modules/beeper": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/beeper/-/beeper-1.1.1.tgz", + "integrity": "sha512-3vqtKL1N45I5dV0RdssXZG7X6pCqQrWPNOlBPZPrd+QkE2HEhR57Z04m0KtpbsZH73j+a3F8UD1TQnn+ExTvIA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/bin-build": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bin-build/-/bin-build-3.0.0.tgz", + "integrity": "sha512-jcUOof71/TNAI2uM5uoUaDq2ePcVBQ3R/qhxAz1rX7UfvduAL/RXD3jXzvn8cVcDJdGVkiR1shal3OH0ImpuhA==", + "license": "MIT", + "optional": true, + "dependencies": { + "decompress": "^4.0.0", + "download": "^6.2.2", + "execa": "^0.7.0", + "p-map-series": "^1.0.0", + "tempfile": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-build/node_modules/cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==", + "license": "MIT", + "optional": true, + "dependencies": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "node_modules/bin-build/node_modules/execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha512-RztN09XglpYI7aBBrJCPW95jEH7YF1UEPOoX9yDhUTPdp7mK+CQvnLTuD10BNXZ3byLTu2uehZ8EcKT/4CGiFw==", + "license": "MIT", + "optional": true, + "dependencies": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-build/node_modules/get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-build/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/bin-build/node_modules/lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "license": "ISC", + "optional": true, + "dependencies": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "node_modules/bin-build/node_modules/npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", + "license": "MIT", + "optional": true, + "dependencies": { + "path-key": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-build/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-build/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "license": "MIT", + "optional": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/bin-build/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/bin-build/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/bin-build/node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "license": "ISC", + "optional": true + }, + "node_modules/bin-check": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bin-check/-/bin-check-4.1.0.tgz", + "integrity": "sha512-b6weQyEUKsDGFlACWSIOfveEnImkJyK/FGW6FAG42loyoquvjdtOIqO6yBFzHyqyVVhNgNkQxxx09SFLK28YnA==", + "license": "MIT", + "optional": true, + "dependencies": { + "execa": "^0.7.0", + "executable": "^4.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-check/node_modules/cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==", + "license": "MIT", + "optional": true, + "dependencies": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "node_modules/bin-check/node_modules/execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha512-RztN09XglpYI7aBBrJCPW95jEH7YF1UEPOoX9yDhUTPdp7mK+CQvnLTuD10BNXZ3byLTu2uehZ8EcKT/4CGiFw==", + "license": "MIT", + "optional": true, + "dependencies": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-check/node_modules/get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-check/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/bin-check/node_modules/lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "license": "ISC", + "optional": true, + "dependencies": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "node_modules/bin-check/node_modules/npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", + "license": "MIT", + "optional": true, + "dependencies": { + "path-key": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-check/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-check/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "license": "MIT", + "optional": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/bin-check/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/bin-check/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/bin-check/node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "license": "ISC", + "optional": true + }, + "node_modules/bin-version": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bin-version/-/bin-version-3.1.0.tgz", + "integrity": "sha512-Mkfm4iE1VFt4xd4vH+gx+0/71esbfus2LsnCGe8Pi4mndSPyT+NGES/Eg99jx8/lUGWfu3z2yuB/bt5UB+iVbQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "execa": "^1.0.0", + "find-versions": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/bin-version-check": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/bin-version-check/-/bin-version-check-4.0.0.tgz", + "integrity": "sha512-sR631OrhC+1f8Cvs8WyVWOA33Y8tgwjETNPyyD/myRBXLkfS/vl74FmH/lFcRl9KY3zwGh7jFhvyk9vV3/3ilQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "bin-version": "^3.0.0", + "semver": "^5.6.0", + "semver-truncate": "^1.1.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/bin-version-check/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/bin-version/node_modules/cross-spawn": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", + "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", + "license": "MIT", + "optional": true, + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/bin-version/node_modules/execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "license": "MIT", + "optional": true, + "dependencies": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/bin-version/node_modules/get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "license": "MIT", + "optional": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/bin-version/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/bin-version/node_modules/npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", + "license": "MIT", + "optional": true, + "dependencies": { + "path-key": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-version/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-version/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/bin-version/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "license": "MIT", + "optional": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/bin-version/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/bin-version/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/bin-wrapper": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bin-wrapper/-/bin-wrapper-4.1.0.tgz", + "integrity": "sha512-hfRmo7hWIXPkbpi0ZltboCMVrU+0ClXR/JgbCKKjlDjQf6igXa7OwdqNcFWQZPZTgiY7ZpzE3+LjjkLiTN2T7Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "bin-check": "^4.1.0", + "bin-version-check": "^4.0.0", + "download": "^7.1.0", + "import-lazy": "^3.1.0", + "os-filter-obj": "^2.0.0", + "pify": "^4.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/bin-wrapper/node_modules/@sindresorhus/is": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz", + "integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-wrapper/node_modules/download": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/download/-/download-7.1.0.tgz", + "integrity": "sha512-xqnBTVd/E+GxJVrX5/eUJiLYjCGPwMpdL+jGhGU57BvtcA7wwhtHVbXBeUk51kOpW3S7Jn3BQbN9Q1R1Km2qDQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "archive-type": "^4.0.0", + "caw": "^2.0.1", + "content-disposition": "^0.5.2", + "decompress": "^4.2.0", + "ext-name": "^5.0.0", + "file-type": "^8.1.0", + "filenamify": "^2.0.0", + "get-stream": "^3.0.0", + "got": "^8.3.1", + "make-dir": "^1.2.0", + "p-event": "^2.1.0", + "pify": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/bin-wrapper/node_modules/download/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-wrapper/node_modules/file-type": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-8.1.0.tgz", + "integrity": "sha512-qyQ0pzAy78gVoJsmYeNgl8uH8yKhr1lVhW7JbzJmnlRi0I4R2eEDEJZVKG8agpDnLpacwNbDhLNG/LMdxHD2YQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/bin-wrapper/node_modules/get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-wrapper/node_modules/got": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/got/-/got-8.3.2.tgz", + "integrity": "sha512-qjUJ5U/hawxosMryILofZCkm3C84PLJS/0grRIpjAwu+Lkxxj5cxeCU25BG0/3mDSpXKTyZr8oh8wIgLaH0QCw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@sindresorhus/is": "^0.7.0", + "cacheable-request": "^2.1.1", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "into-stream": "^3.1.0", + "is-retry-allowed": "^1.1.0", + "isurl": "^1.0.0-alpha5", + "lowercase-keys": "^1.0.0", + "mimic-response": "^1.0.0", + "p-cancelable": "^0.4.0", + "p-timeout": "^2.0.1", + "pify": "^3.0.0", + "safe-buffer": "^5.1.1", + "timed-out": "^4.0.1", + "url-parse-lax": "^3.0.0", + "url-to-options": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-wrapper/node_modules/got/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-wrapper/node_modules/p-cancelable": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz", + "integrity": "sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-wrapper/node_modules/p-event": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-2.3.1.tgz", + "integrity": "sha512-NQCqOFhbpVTMX4qMe8PF8lbGtzZ+LCiN7pcNrb/413Na7+TRoe1xkKUzuWa/YEJdGQ0FvKtj35EEbDoVPO2kbA==", + "license": "MIT", + "optional": true, + "dependencies": { + "p-timeout": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/bin-wrapper/node_modules/p-timeout": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-2.0.1.tgz", + "integrity": "sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==", + "license": "MIT", + "optional": true, + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-wrapper/node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/bin-wrapper/node_modules/prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-wrapper/node_modules/url-parse-lax": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "prepend-http": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/blakejs": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz", + "integrity": "sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==", + "license": "MIT" + }, + "node_modules/bmp-js": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz", + "integrity": "sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==", + "license": "MIT" + }, + "node_modules/bmp-ts": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/bmp-ts/-/bmp-ts-1.0.9.tgz", + "integrity": "sha512-cTEHk2jLrPyi+12M3dhpEbnnPOsaZuq7C45ylbbQIiWgDFZq4UVYPEY5mlqjvsj/6gJv9qX5sa+ebDzLXT28Vw==", + "license": "MIT" + }, + "node_modules/bn": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/bn/-/bn-1.0.5.tgz", + "integrity": "sha512-7TvGbqbZb6lDzsBtNz1VkdXXV0BVmZKPPViPmo2IpvwaryF7P+QKYKACyVkwo2mZPr2CpFiz7EtgPEcc3o/JFQ==", + "license": "BSD-3-Clause" + }, + "node_modules/bn.js": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.3.tgz", + "integrity": "sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w==", + "license": "MIT" + }, + "node_modules/body": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/body/-/body-5.1.0.tgz", + "integrity": "sha512-chUsBxGRtuElD6fmw1gHLpvnKdVLK302peeFa9ZqAEk8TyzZ3fygLyUEDDPTJvL9+Bor0dIwn6ePOsRM2y0zQQ==", + "dev": true, + "dependencies": { + "continuable-cache": "^0.3.1", + "error": "^7.0.0", + "raw-body": "~1.1.0", + "safe-json-parse": "~1.0.1" + } + }, + "node_modules/body-parser": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/body-parser/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/body-parser/node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, + "license": "ISC" + }, + "node_modules/body-parser/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/bonjour-service": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", + "integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/bootstrap": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.2.tgz", + "integrity": "sha512-51Bbp/Uxr9aTuy6ca/8FbFloBUJZLHwnhTcnjIeRn2suQWsWzcuJhGjKDB5eppVte/8oCdOL3VuwxvZDUggwGQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "license": "MIT", + "peerDependencies": { + "jquery": "1.9.1 - 3", + "popper.js": "^1.16.1" + } + }, + "node_modules/bootstrap-colorpicker": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/bootstrap-colorpicker/-/bootstrap-colorpicker-3.4.0.tgz", + "integrity": "sha512-7vA0hvLrat3ptobEKlT9+6amzBUJcDAoh6hJRQY/AD+5dVZYXXf1ivRfrTwmuwiVLJo9rZwM8YB4lYzp6agzqg==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "license": "MIT", + "dependencies": { + "bootstrap": ">=4.0", + "jquery": ">=2.2", + "popper.js": ">=1.10" + } + }, + "node_modules/bootstrap-material-design": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/bootstrap-material-design/-/bootstrap-material-design-4.1.3.tgz", + "integrity": "sha512-jOB9io76BKLxwF+IAgObFH9f88ityqOiYsQe9Aa8m88h7sSP3eFL1K8ygb0FsYyIiVm194iodg9i4GMOSlLeRA==", + "license": "MIT" + }, + "node_modules/boxen": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", + "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-align": "^3.0.0", + "camelcase": "^6.2.0", + "chalk": "^4.1.0", + "cli-boxes": "^2.2.1", + "string-width": "^4.2.2", + "type-fest": "^0.20.2", + "widest-line": "^3.1.0", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/boxen/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/boxen/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "license": "MIT" + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true, + "license": "ISC" + }, + "node_modules/browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "license": "MIT", + "dependencies": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "license": "MIT", + "dependencies": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "node_modules/browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/browserify-rsa": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.1.tgz", + "integrity": "sha512-YBjSAiTqM04ZVei6sXighu679a3SqWORA3qZTEqZImnlkDIFtKc6pNutpjyZ8RJTjQtuYfeetkxM11GwoYXMIQ==", + "license": "MIT", + "dependencies": { + "bn.js": "^5.2.1", + "randombytes": "^2.1.0", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/browserify-sign": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.5.tgz", + "integrity": "sha512-C2AUdAJg6rlM2W5QMp2Q4KGQMVBwR1lIimTsUnutJ8bMpW5B52pGpR2gEnNBNwijumDo5FojQ0L9JrXA8m4YEw==", + "license": "ISC", + "dependencies": { + "bn.js": "^5.2.2", + "browserify-rsa": "^4.1.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.6.1", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.9", + "readable-stream": "^2.3.8", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "license": "MIT", + "dependencies": { + "pako": "~1.0.5" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bson": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/bson/-/bson-4.7.2.tgz", + "integrity": "sha512-Ry9wCtIZ5kGqkJoi6aD8KjxFZEx78guTQDnpXWiNthsxzrxAK/i8E6pCHAIZTbaEFWcOCvbecMukfK7XUvyLpQ==", + "license": "Apache-2.0", + "dependencies": { + "buffer": "^5.6.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/bson/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "license": "MIT", + "optional": true, + "dependencies": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "node_modules/buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", + "license": "MIT", + "optional": true + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==", + "license": "MIT", + "optional": true + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", + "license": "MIT" + }, + "node_modules/bufferstreams": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bufferstreams/-/bufferstreams-1.0.1.tgz", + "integrity": "sha512-LZmiIfQprMLS6/k42w/PTc7awhU8AdNNcUerxTgr01WlP9agR2SgMv0wjlYYFD6eDOi8WvofrTX8RayjR/AeUQ==", + "dependencies": { + "readable-stream": "^1.0.33" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/bufferstreams/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "license": "MIT" + }, + "node_modules/bufferstreams/node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/bufferstreams/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", + "license": "MIT" + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz", + "integrity": "sha512-/x68VkHLeTl3/Ll8IvxdwzhrT+IyKc52e/oyHhA2RwqPqswSnjVbSddfPRwAsJtbilMAPSRWwAlpxdYsSWOTKQ==", + "dev": true + }, + "node_modules/bzip-deflate": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/bzip-deflate/-/bzip-deflate-1.0.0.tgz", + "integrity": "sha512-9RMnpiJqMYMJcLdr4pxwowZ8Zh3P+tVswE/bnX6tZ14UGKNcdV5WVK2P+lGp2As+RCjl+i3SFJ117HyCaaHNDA==", + "license": "CC-SA 3.0" + }, + "node_modules/cacheable-request": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-2.1.4.tgz", + "integrity": "sha512-vag0O2LKZ/najSoUwDbVlnlCFvhBE/7mGTY2B5FgCBDcRD+oVV1HYTOwM6JZfMg/hIcM6IwnTZ1uQQL5/X3xIQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "clone-response": "1.0.2", + "get-stream": "3.0.0", + "http-cache-semantics": "3.8.1", + "keyv": "3.0.0", + "lowercase-keys": "1.0.0", + "normalize-url": "2.0.1", + "responselike": "1.0.2" + } + }, + "node_modules/cacheable-request/node_modules/get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/cacheable-request/node_modules/json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==", + "license": "MIT", + "optional": true + }, + "node_modules/cacheable-request/node_modules/keyv": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.0.0.tgz", + "integrity": "sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA==", + "license": "MIT", + "optional": true, + "dependencies": { + "json-buffer": "3.0.0" + } + }, + "node_modules/cacheable-request/node_modules/lowercase-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz", + "integrity": "sha512-RPlX0+PHuvxVDZ7xX+EBVAp4RsVxP/TdDSN2mJYdiq1Lc4Hz7EUSjUI7RZrKKlmrIzVhf6Jo2stj7++gVarS0A==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001769", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz", + "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/caw": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/caw/-/caw-2.0.1.tgz", + "integrity": "sha512-Cg8/ZSBEa8ZVY9HspcGUYaK63d/bN7rqS3CYCzEGUxuYv6UlmcjzDUz2fCFFHyTvUW5Pk0I+3hkA3iXlIj6guA==", + "license": "MIT", + "optional": true, + "dependencies": { + "get-proxy": "^2.0.0", + "isurl": "^1.0.0-alpha5", + "tunnel-agent": "^0.6.0", + "url-to-options": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cbor": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/cbor/-/cbor-9.0.2.tgz", + "integrity": "sha512-JPypkxsB10s9QOWwa6zwPzqE1Md3vqpPc+cai4sAecuCsRyAtAl/pMyhPlMbT/xtPnm2dznJZYRLui57qiRhaQ==", + "license": "MIT", + "dependencies": { + "nofilter": "^3.1.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/chai-nightwatch": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/chai-nightwatch/-/chai-nightwatch-0.5.3.tgz", + "integrity": "sha512-38ixH/mqpY6IwnZkz6xPqx8aB5/KVR+j6VPugcir3EGOsphnWXrPH/mUt8Jp+ninL6ghY0AaJDQ10hSfCPGy/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "1.1.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/chalk-template": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-1.1.0.tgz", + "integrity": "sha512-T2VJbcDuZQ0Tb2EWwSotMPJjgpy1/tGee1BTpUNsGZ/qgNjV2t7Mvu+d4600U564nbLesN1x2dPL+xii174Ekg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.2.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/chalk/chalk-template?sponsor=1" + } + }, + "node_modules/chalk-template/node_modules/chalk": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/change-file-extension": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/change-file-extension/-/change-file-extension-0.1.1.tgz", + "integrity": "sha512-lB0j9teu8JtDPDHRfU8pNH33w4wMu5bOaKoT4PxH+AKugBrIfpiJMTTKIm0TErNeJPkeQEgvH31YpccTwOKPRg==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/chi-squared": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/chi-squared/-/chi-squared-1.1.0.tgz", + "integrity": "sha512-IFJA5igW44wzi7VHSsBcuHQ1+sF6noKRK6eFb+so4v9A/dH23RElM+8UBCi6+kWSINB/hDPNH+KDBCazPuKVwg==", + "license": "MIT", + "dependencies": { + "gamma": "^1.0.0" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/chromedriver": { + "version": "143.0.4", + "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-143.0.4.tgz", + "integrity": "sha512-mE++40DprY2n4d3OPxzW7ujIFRY9eLYwJf4uBgQtMaJQkapSVXRzUrLzSMcRaybrt47Y1t8xW5AKoaUIL3aYZw==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@testim/chrome-version": "^1.1.4", + "axios": "^1.12.0", + "compare-versions": "^6.1.0", + "extract-zip": "^2.0.1", + "proxy-agent": "^6.4.0", + "proxy-from-env": "^1.1.0", + "tcp-port-used": "^1.0.2" + }, + "bin": { + "chromedriver": "bin/chromedriver" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/ci-info": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.0.tgz", + "integrity": "sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw==", + "dev": true, + "license": "MIT" + }, + "node_modules/cipher-base": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.6.tgz", + "integrity": "sha512-3Ek9H3X6pj5TgenXYtNWdaBon1tgYCaebd+XPg0keyjEbEfkD4KkmAxkQ/i1vYvxdcT5nscLBfq9VJRmCBcFSw==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/clean-css": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", + "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 10.0" + } + }, + "node_modules/clear-module": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/clear-module/-/clear-module-4.1.2.tgz", + "integrity": "sha512-LWAxzHqdHsAZlPlEyJ2Poz6AIs384mPeqLVCru2p0BrP9G/kVGuhNyZYClLO6cXlnuJjzC8xtsJIuMjKqLXoAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^2.0.0", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clear-module/node_modules/parent-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-2.0.0.tgz", + "integrity": "sha512-uo0Z9JJeWzv8BG+tRcapBKNJ0dro9cLyczGzulS6EfeyAdeC9sbojtW6XwvYxJkEne9En+J2XEl4zyglVeIwFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/clear-module/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-boxes": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", + "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-color": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-1.1.0.tgz", + "integrity": "sha512-SzsTUTopL62kJOMbLqBUkaLVbkyw0qKB3uMRFxgy9LrEQ5tdFO9dT8oUhqszpJB9FMpVTIQnZMjb6zn0abilvQ==", + "license": "MIT", + "dependencies": { + "ansi-regex": "2", + "d": "^0.1.1", + "es5-ext": "^0.10.8", + "es6-iterator": "2", + "memoizee": "^0.3.9", + "timers-ext": "0.1" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-progress": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz", + "integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.3" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", + "integrity": "sha512-KLLTJWrvwIP+OPfMn0x2PheDEP20RPUcGXj/ERegTgdmPEZylALQldygiqrPPu8P45uNuPs7ckmReLY6v/iA5g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha512-yjLXh88P599UOyPTFX0POsd7WxnbsVsGohcwzHOLspIhhpalPw1BcqED8NblyZLKcGrL8dTgMlcaZxV2jAD41Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "mimic-response": "^1.0.0" + } + }, + "node_modules/clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha512-au6ydSpg6nsrigcZ4m8Bc9hxjeW+GJ8xh5G3BJCMt4WXe1H10UNaVOamqQTmrx1kjVuxAHIQSNU6hY4Nsn9/ag==", + "license": "MIT" + }, + "node_modules/cloneable-readable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", + "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "process-nextick-args": "^2.0.0", + "readable-stream": "^2.3.5" + } + }, + "node_modules/codepage": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz", + "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/color": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/color/-/color-0.8.0.tgz", + "integrity": "sha512-tKmPx2t+2N4pxZT+P4jXaT3qHMkYqE1ZHe5z6TpRVR/wSONGwHDracgkv//oRsFZ3T1QO6ZBxAxjpDIeNqEQyw==", + "license": "MIT", + "dependencies": { + "color-convert": "^0.5.0", + "color-string": "^0.3.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-0.3.0.tgz", + "integrity": "sha512-sz29j1bmSDfoAxKIEU6zwoIZXN6BrFbAMIhfYCNyiZXBDuU/aiHlN84lp/xDzL2ubyFhLDobHIlU1X70XRrMDA==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0" + } + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "license": "ISC", + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/color/node_modules/color-convert": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-0.5.3.tgz", + "integrity": "sha512-RwBeO/B/vZR3dfKL1ye/vx8MHZ40ugzpyfeVG5GsiuGnrlMWe2o8wxBbLCpw9CsxV+wHuzYlCiWnybrIA0ling==" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/colornames": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/colornames/-/colornames-0.0.2.tgz", + "integrity": "sha512-aeaoTql364CeoC6VHeRJd8uUiOVZDDtCyTP2dwXPD3WIt8UuPcXzmBB5gEhLDLaJS3MW152O7DfYm1a2HQv11g==", + "license": "MIT" + }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/colorspace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.0.1.tgz", + "integrity": "sha512-rCnzSo6lkArg8rgeLdPQgxC5avqkyFGSpg3Roqn+rGRZfaHSBKgeDMr1YJZ9XTNZAeVoR4KxLjq9SUQ6hMvFlQ==", + "license": "MIT", + "dependencies": { + "color": "0.8.x", + "text-hex": "0.0.x" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/comment-json": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.5.1.tgz", + "integrity": "sha512-taEtr3ozUmOB7it68Jll7s0Pwm+aoiHyXKrEC8SEodL4rNpdfDLqa7PfBlrgFoCNNdR8ImL+muti5IGvktJAAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-timsort": "^1.0.3", + "core-util-is": "^1.0.3", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/comment-parser": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz", + "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/compare-versions": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz", + "integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==", + "dev": true, + "license": "MIT" + }, + "node_modules/compress-commons": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", + "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/compress-commons/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression-webpack-plugin": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/compression-webpack-plugin/-/compression-webpack-plugin-11.1.0.tgz", + "integrity": "sha512-zDOQYp10+upzLxW+VRSjEpRRwBXJdsb5lBMlRxx1g8hckIFBpe3DTI0en2w7h+beuq89576RVzfiXrkdPGrHhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "schema-utils": "^4.2.0", + "serialize-javascript": "^6.0.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/compression/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/compression/node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/concat-with-sourcemaps": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz", + "integrity": "sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==", + "license": "ISC", + "dependencies": { + "source-map": "^0.6.1" + } + }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/connect-livereload": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/connect-livereload/-/connect-livereload-0.6.1.tgz", + "integrity": "sha512-3R0kMOdL7CjJpU66fzAkCe6HNtd3AavCS4m+uW4KtJjrdGPT0SQEZieAYd+cm+lJoBznNQ4lqipYWkhBMgk00g==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/connect/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/connect/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/continuable-cache": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/continuable-cache/-/continuable-cache-0.3.1.tgz", + "integrity": "sha512-TF30kpKhTH8AGCG3dut0rdd/19B7Z+qCnrMoBLpyQu/2drZdNrrpcjPEoJeSVsQM+8KmWG5O56oPDjSSUsuTyA==", + "dev": true + }, + "node_modules/convert-hrtime": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/convert-hrtime/-/convert-hrtime-5.0.0.tgz", + "integrity": "sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-props": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-4.0.0.tgz", + "integrity": "sha512-bVWtw1wQLzzKiYROtvNlbJgxgBYt2bMJpkCbKmXM3xyijvcjjWXEk5nyrrT3bgJ7ODb19ZohE2T0Y3FgNPyoTw==", + "license": "MIT", + "dependencies": { + "each-props": "^3.0.0", + "is-plain-object": "^5.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/copy-props/node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/copy-webpack-plugin": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-13.0.1.tgz", + "integrity": "sha512-J+YV3WfhY6W/Xf9h+J1znYuqTye2xkBUIGyTPWuBAT27qajBa5mR4f8WBmfDY3YjRftT2kqZZiLi1qf0H+UOFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob-parent": "^6.0.1", + "normalize-path": "^3.0.0", + "schema-utils": "^4.2.0", + "serialize-javascript": "^6.0.2", + "tinyglobby": "^0.2.12" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/core-js": { + "version": "3.48.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.48.0.tgz", + "integrity": "sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-compat": { + "version": "3.48.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.48.0.tgz", + "integrity": "sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-pure": { + "version": "3.48.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.48.0.tgz", + "integrity": "sha512-1slJgk89tWC51HQ1AEqG+s2VuwpTRr8ocu4n20QUcH1v9lAN0RXen0Q0AABa/DK1I7RrNWLucplOHMx8hfTGTw==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz", + "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/crc32-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/create-ecdh": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + } + }, + "node_modules/create-ecdh/node_modules/bn.js": { + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", + "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", + "license": "MIT" + }, + "node_modules/create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "node_modules/create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-api": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/crypto-api/-/crypto-api-0.8.5.tgz", + "integrity": "sha512-kcif7fCeYZpUsA3Y1VidFrK4HRf2Lsx9X4cnl7pauTXjgnXfEjaTyUGxzIBJ6DZwEPgX/VyKkhAeBV+vXHwX2Q==", + "license": "MIT" + }, + "node_modules/crypto-browserify": { + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.1.tgz", + "integrity": "sha512-r4ESw/IlusD17lgQi1O20Fa3qNnsckR126TdUuBgAu7GBYSIPvdNyONd3Zrxh0xCwA4+6w/TDArBPsMvhur+KQ==", + "license": "MIT", + "dependencies": { + "browserify-cipher": "^1.0.1", + "browserify-sign": "^4.2.3", + "create-ecdh": "^4.0.4", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "diffie-hellman": "^5.0.3", + "hash-base": "~3.0.4", + "inherits": "^2.0.4", + "pbkdf2": "^3.1.2", + "public-encrypt": "^4.0.3", + "randombytes": "^2.1.0", + "randomfill": "^1.0.4" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "license": "MIT" + }, + "node_modules/cspell": { + "version": "8.19.4", + "resolved": "https://registry.npmjs.org/cspell/-/cspell-8.19.4.tgz", + "integrity": "sha512-toaLrLj3usWY0Bvdi661zMmpKW2DVLAG3tcwkAv4JBTisdIRn15kN/qZDrhSieUEhVgJgZJDH4UKRiq29mIFxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/cspell-json-reporter": "8.19.4", + "@cspell/cspell-pipe": "8.19.4", + "@cspell/cspell-types": "8.19.4", + "@cspell/dynamic-import": "8.19.4", + "@cspell/url": "8.19.4", + "chalk": "^5.4.1", + "chalk-template": "^1.1.0", + "commander": "^13.1.0", + "cspell-dictionary": "8.19.4", + "cspell-gitignore": "8.19.4", + "cspell-glob": "8.19.4", + "cspell-io": "8.19.4", + "cspell-lib": "8.19.4", + "fast-json-stable-stringify": "^2.1.0", + "file-entry-cache": "^9.1.0", + "semver": "^7.7.1", + "tinyglobby": "^0.2.13" + }, + "bin": { + "cspell": "bin.mjs", + "cspell-esm": "bin.mjs" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/streetsidesoftware/cspell?sponsor=1" + } + }, + "node_modules/cspell-config-lib": { + "version": "8.19.4", + "resolved": "https://registry.npmjs.org/cspell-config-lib/-/cspell-config-lib-8.19.4.tgz", + "integrity": "sha512-LtFNZEWVrnpjiTNgEDsVN05UqhhJ1iA0HnTv4jsascPehlaUYVoyucgNbFeRs6UMaClJnqR0qT9lnPX+KO1OLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/cspell-types": "8.19.4", + "comment-json": "^4.2.5", + "yaml": "^2.7.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cspell-dictionary": { + "version": "8.19.4", + "resolved": "https://registry.npmjs.org/cspell-dictionary/-/cspell-dictionary-8.19.4.tgz", + "integrity": "sha512-lr8uIm7Wub8ToRXO9f6f7in429P1Egm3I+Ps3ZGfWpwLTCUBnHvJdNF/kQqF7PL0Lw6acXcjVWFYT7l2Wdst2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/cspell-pipe": "8.19.4", + "@cspell/cspell-types": "8.19.4", + "cspell-trie-lib": "8.19.4", + "fast-equals": "^5.2.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cspell-gitignore": { + "version": "8.19.4", + "resolved": "https://registry.npmjs.org/cspell-gitignore/-/cspell-gitignore-8.19.4.tgz", + "integrity": "sha512-KrViypPilNUHWZkMV0SM8P9EQVIyH8HvUqFscI7+cyzWnlglvzqDdV4N5f+Ax5mK+IqR6rTEX8JZbCwIWWV7og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/url": "8.19.4", + "cspell-glob": "8.19.4", + "cspell-io": "8.19.4" + }, + "bin": { + "cspell-gitignore": "bin.mjs" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cspell-glob": { + "version": "8.19.4", + "resolved": "https://registry.npmjs.org/cspell-glob/-/cspell-glob-8.19.4.tgz", + "integrity": "sha512-042uDU+RjAz882w+DXKuYxI2rrgVPfRQDYvIQvUrY1hexH4sHbne78+OMlFjjzOCEAgyjnm1ktWUCCmh08pQUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/url": "8.19.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cspell-glob/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/cspell-grammar": { + "version": "8.19.4", + "resolved": "https://registry.npmjs.org/cspell-grammar/-/cspell-grammar-8.19.4.tgz", + "integrity": "sha512-lzWgZYTu/L7DNOHjxuKf8H7DCXvraHMKxtFObf8bAzgT+aBmey5fW2LviXUkZ2Lb2R0qQY+TJ5VIGoEjNf55ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/cspell-pipe": "8.19.4", + "@cspell/cspell-types": "8.19.4" + }, + "bin": { + "cspell-grammar": "bin.mjs" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cspell-io": { + "version": "8.19.4", + "resolved": "https://registry.npmjs.org/cspell-io/-/cspell-io-8.19.4.tgz", + "integrity": "sha512-W48egJqZ2saEhPWf5ftyighvm4mztxEOi45ILsKgFikXcWFs0H0/hLwqVFeDurgELSzprr12b6dXsr67dV8amg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/cspell-service-bus": "8.19.4", + "@cspell/url": "8.19.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cspell-lib": { + "version": "8.19.4", + "resolved": "https://registry.npmjs.org/cspell-lib/-/cspell-lib-8.19.4.tgz", + "integrity": "sha512-NwfdCCYtIBNQuZcoMlMmL3HSv2olXNErMi/aOTI9BBAjvCHjhgX5hbHySMZ0NFNynnN+Mlbu5kooJ5asZeB3KA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/cspell-bundled-dicts": "8.19.4", + "@cspell/cspell-pipe": "8.19.4", + "@cspell/cspell-resolver": "8.19.4", + "@cspell/cspell-types": "8.19.4", + "@cspell/dynamic-import": "8.19.4", + "@cspell/filetypes": "8.19.4", + "@cspell/strong-weak-map": "8.19.4", + "@cspell/url": "8.19.4", + "clear-module": "^4.1.2", + "comment-json": "^4.2.5", + "cspell-config-lib": "8.19.4", + "cspell-dictionary": "8.19.4", + "cspell-glob": "8.19.4", + "cspell-grammar": "8.19.4", + "cspell-io": "8.19.4", + "cspell-trie-lib": "8.19.4", + "env-paths": "^3.0.0", + "fast-equals": "^5.2.2", + "gensequence": "^7.0.0", + "import-fresh": "^3.3.1", + "resolve-from": "^5.0.0", + "vscode-languageserver-textdocument": "^1.0.12", + "vscode-uri": "^3.1.0", + "xdg-basedir": "^5.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cspell-lib/node_modules/env-paths": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", + "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cspell-lib/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cspell-trie-lib": { + "version": "8.19.4", + "resolved": "https://registry.npmjs.org/cspell-trie-lib/-/cspell-trie-lib-8.19.4.tgz", + "integrity": "sha512-yIPlmGSP3tT3j8Nmu+7CNpkPh/gBO2ovdnqNmZV+LNtQmVxqFd2fH7XvR1TKjQyctSH1ip0P5uIdJmzY1uhaYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/cspell-pipe": "8.19.4", + "@cspell/cspell-types": "8.19.4", + "gensequence": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cspell/node_modules/chalk": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cspell/node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/cspell/node_modules/file-entry-cache": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-9.1.0.tgz", + "integrity": "sha512-/pqPFG+FdxWQj+/WSuzXSDaNzxgTLr/OrR1QuqfEZzDakpdYE70PwUxL7BPUa8hpjbvY1+qvCl8k+8Tq34xJgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cspell/node_modules/flat-cache": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-5.0.0.tgz", + "integrity": "sha512-JrqFmyUl2PnPi1OvLyTVHnQvwQ0S+e6lGSwu8OkAZlSaNIZciTY2H/cOOROxsBA1m/LZNHDsqAgDZt6akWcjsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.3.1", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cspell/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/css-loader": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.4.tgz", + "integrity": "sha512-vv3J9tlOl04WjiMvHQI/9tmIrCxVrj6PFbHemBB1iihpeRbi/I4h033eoFIhwxBBqLhI0KYFS7yvynBFhIZfTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.40", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.6.3" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || ^1.0.0 || ^2.0.0-0", + "webpack": "^5.27.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/css-loader/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "devOptional": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "devOptional": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csso": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", + "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "license": "MIT", + "optional": true, + "dependencies": { + "css-tree": "^1.1.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/cssstyle": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.2.1.tgz", + "integrity": "sha512-9+vem03dMXG7gDmZ62uqmRiMRNtinIZ9ZyuF6BdxzfOD+FdN5hretzynkn0ReS2DO2GSw76RWHs0UmJPI2zUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^2.8.2", + "rrweb-cssom": "^0.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cssstyle/node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ctph.js": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/ctph.js/-/ctph.js-0.0.5.tgz", + "integrity": "sha512-xcgQ6zzamT6ESHBhFPZHjkcpjzv4rtWw9GLoeNwvQwQQdXNR49xKBF5/Nt1oLsE3X1JM8QHktbGknp5cH20PTA==" + }, + "node_modules/cycle": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", + "integrity": "sha512-TVF6svNzeQCOpjCqsy0/CSy8VgObG3wXusJ73xW2GbG5rGx7lC8zxDSURicsXI2UsGdi2L0QNRCi745/wUDvsA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/d": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/d/-/d-0.1.1.tgz", + "integrity": "sha512-0SdM9V9pd/OXJHoWmTfNPTAeD+lw6ZqHg+isPyBFuJsZLSE0Ygg1cYZ/0l6DrKQXMOqGOu1oWupMoOfoRfMZrQ==", + "license": "MIT", + "dependencies": { + "es5-ext": "~0.10.2" + } + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "license": "ISC", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hexbin": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/d3-hexbin/-/d3-hexbin-0.2.2.tgz", + "integrity": "sha512-KS3fUT2ReD4RlGCjvCEm1RgMtp2NFZumdMu4DBzQK8AZv3fXRM6Xm8I4fSU07UXvH4xxg03NwWKWdvxfS/yc4w==", + "license": "BSD-3-Clause" + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/decompress": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress/-/decompress-4.2.1.tgz", + "integrity": "sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "decompress-tar": "^4.0.0", + "decompress-tarbz2": "^4.0.0", + "decompress-targz": "^4.0.0", + "decompress-unzip": "^4.0.1", + "graceful-fs": "^4.1.10", + "make-dir": "^1.0.0", + "pify": "^2.3.0", + "strip-dirs": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==", + "license": "MIT", + "optional": true, + "dependencies": { + "mimic-response": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tar": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-4.1.1.tgz", + "integrity": "sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "file-type": "^5.2.0", + "is-stream": "^1.1.0", + "tar-stream": "^1.5.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tar/node_modules/bl": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz", + "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", + "license": "MIT", + "optional": true, + "dependencies": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/decompress-tar/node_modules/file-type": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", + "integrity": "sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tar/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress-tar/node_modules/tar-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", + "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", + "license": "MIT", + "optional": true, + "dependencies": { + "bl": "^1.0.0", + "buffer-alloc": "^1.2.0", + "end-of-stream": "^1.0.0", + "fs-constants": "^1.0.0", + "readable-stream": "^2.3.0", + "to-buffer": "^1.1.1", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/decompress-tarbz2": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz", + "integrity": "sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==", + "license": "MIT", + "optional": true, + "dependencies": { + "decompress-tar": "^4.1.0", + "file-type": "^6.1.0", + "is-stream": "^1.1.0", + "seek-bzip": "^1.0.5", + "unbzip2-stream": "^1.0.9" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tarbz2/node_modules/file-type": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-6.2.0.tgz", + "integrity": "sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tarbz2/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress-targz": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-4.1.1.tgz", + "integrity": "sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==", + "license": "MIT", + "optional": true, + "dependencies": { + "decompress-tar": "^4.1.1", + "file-type": "^5.2.0", + "is-stream": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-targz/node_modules/file-type": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", + "integrity": "sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-targz/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress-unzip": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-4.0.1.tgz", + "integrity": "sha512-1fqeluvxgnn86MOh66u8FjbtJpAFv5wgCT9Iw8rcBqQcCo5tO8eiJw7NNTrvt9n4CRBVq7CstiS922oPgyGLrw==", + "license": "MIT", + "optional": true, + "dependencies": { + "file-type": "^3.8.0", + "get-stream": "^2.2.0", + "pify": "^2.3.0", + "yauzl": "^2.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-unzip/node_modules/file-type": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", + "integrity": "sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress-unzip/node_modules/get-stream": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz", + "integrity": "sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA==", + "license": "MIT", + "optional": true, + "dependencies": { + "object-assign": "^4.0.1", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deep-eql": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.0.1.tgz", + "integrity": "sha512-D/Oxqobjr+kxaHsgiQBZq9b6iAWdEj5W/JdJm8deNduAPc9CwXQ3BJJCuEqlrPXcy45iOMkGPZ0T81Dnz7UDCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-equal": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.2.tgz", + "integrity": "sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg==", + "license": "MIT", + "dependencies": { + "is-arguments": "^1.1.1", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "regexp.prototype.flags": "^1.5.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/deep-for-each": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/deep-for-each/-/deep-for-each-3.0.0.tgz", + "integrity": "sha512-pPN+0f8jlnNP+z90qqOdxGghJU5XM6oBDhvAR+qdQzjCg5pk/7VPPvKK1GqoXEFkHza6ZS+Otzzvmr0g3VUaKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash.isplainobject": "^4.0.6" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/default-browser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", + "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", + "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-gateway": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", + "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "execa": "^5.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/delaunator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/des.js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", + "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true, + "license": "MIT" + }, + "node_modules/devtools-protocol": { + "version": "0.0.1140464", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1140464.tgz", + "integrity": "sha512-I1jXnjpQh/6TBFyQ0A9dB2kXXk6DprpPFZoI8pUsxHtlNuOTQEdv9fUqYBsFtf8tOJCbdsZZyQrWeXu6GfK+Bw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/diagnostics": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/diagnostics/-/diagnostics-1.0.1.tgz", + "integrity": "sha512-CRx2wYrfE/5+CpLdQY0Oat5A14C/ntU7BCVeczr4S8WtCDAkhiNAgf7sDy19eIg2byEEJ8UIOPo8frUdQdO/0Q==", + "license": "MIT", + "dependencies": { + "colorspace": "1.0.x", + "enabled": "1.0.x", + "kuler": "0.0.x" + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/diff": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.2.tgz", + "integrity": "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "node_modules/diffie-hellman/node_modules/bn.js": { + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", + "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", + "license": "MIT" + }, + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/dom-converter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", + "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "utila": "~0.4" + } + }, + "node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "devOptional": true, + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "devOptional": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/dompurify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.1.tgz", + "integrity": "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "devOptional": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/dot-prop": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-8.0.2.tgz", + "integrity": "sha512-xaBe6ZT4DHPkg0k4Ytbvn5xoxgpG0jOS1dYxSOwAHPuNLjP3/OzN0gH55SrLqpx8cBfSaVt91lXYkApjb+nYdQ==", + "license": "MIT", + "dependencies": { + "type-fest": "^3.8.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dot-prop/node_modules/type-fest": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", + "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dotenv": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" + } + }, + "node_modules/download": { + "version": "6.2.5", + "resolved": "https://registry.npmjs.org/download/-/download-6.2.5.tgz", + "integrity": "sha512-DpO9K1sXAST8Cpzb7kmEhogJxymyVUd5qz/vCOSyvwtp2Klj2XcDt5YUuasgxka44SxF0q5RriKIwJmQHG2AuA==", + "license": "MIT", + "optional": true, + "dependencies": { + "caw": "^2.0.0", + "content-disposition": "^0.5.2", + "decompress": "^4.0.0", + "ext-name": "^5.0.0", + "file-type": "5.2.0", + "filenamify": "^2.0.0", + "get-stream": "^3.0.0", + "got": "^7.0.0", + "make-dir": "^1.0.0", + "p-event": "^1.0.0", + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/download/node_modules/file-type": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", + "integrity": "sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/download/node_modules/get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/download/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true, + "license": "MIT" + }, + "node_modules/duplexer2": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", + "integrity": "sha512-+AWBwjGadtksxjOQSFDhPNQbed7icNXApT4+2BNpsXzcCBiInq2H9XW0O8sfHFaPmnQRs7cg/P0fAr2IWQSW0g==", + "license": "BSD", + "dependencies": { + "readable-stream": "~1.1.9" + } + }, + "node_modules/duplexer2/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "license": "MIT" + }, + "node_modules/duplexer2/node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/duplexer2/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", + "license": "MIT" + }, + "node_modules/duplexer3": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.5.tgz", + "integrity": "sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==", + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "node_modules/each-props": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/each-props/-/each-props-3.0.0.tgz", + "integrity": "sha512-IYf1hpuWrdzse/s/YJOrFmU15lyhSzxelNVAHTEG3DtP4QsLTWZUzcUL3HMXmKQxXpa4EIrBPpwRgj0aehdvAw==", + "license": "MIT", + "dependencies": { + "is-plain-object": "^5.0.0", + "object.defaults": "^1.1.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/each-props/node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/easy-transform-stream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/easy-transform-stream/-/easy-transform-stream-1.0.1.tgz", + "integrity": "sha512-ktkaa6XR7COAR3oj02CF3IOgz2m1hCaY3SfzvKT4Svt2MhHw9XCt+ncJNWfe2TGz31iqzNGZ8spdKQflj+Rlog==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true, + "license": "MIT" + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.302", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.302.tgz", + "integrity": "sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/elliptic": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", + "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", + "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", + "license": "MIT" + }, + "node_modules/emits": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emits/-/emits-3.0.0.tgz", + "integrity": "sha512-WJSCMaN/qjIkzWy5Ayu0MDENFltcu4zTPPnWqdFPOVBtsENVTN+A3d76G61yuiVALsMK+76MejdPrwmccv/wag==", + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/enabled": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-1.0.2.tgz", + "integrity": "sha512-nnzgVSpB35qKrUN8358SjO1bYAmxoThECTWw9s3J0x5G8A9hokKHVDFzBjVpCoSryo6MhN8woVyascN5jheaNA==", + "license": "MIT", + "dependencies": { + "env-variable": "0.0.x" + } + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", + "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/env-variable": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/env-variable/-/env-variable-0.0.6.tgz", + "integrity": "sha512-bHz59NlBbtS0NhftmR8+ExBEekE7br0e01jw+kk0NDro7TtZzBYZ5ScGPs3OmwnpyfHTHOtr1Y6uedCdrIldtg==", + "license": "MIT" + }, + "node_modules/envinfo": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.0.tgz", + "integrity": "sha512-G9/6xF1FPbIw0TtalAMaVPpiq2aDEuKLXM314jPVAO9r2fo2a4BLqMNkmRS7O/xPPZ+COAhGIz3ETvHEV3eUcg==", + "dev": true, + "license": "MIT", + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/error": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/error/-/error-7.2.1.tgz", + "integrity": "sha512-fo9HBvWnx3NGUKMvMwB/CBCMMrfEJgbDTVDEkPygA3Bdd3lM1OyCd+rbQ8BwnpF6GdVeOLDNmyL4N5Bg80ZvdA==", + "dev": true, + "dependencies": { + "string-template": "~0.2.1" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-get-iterator": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", + "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-get-iterator/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-module-lexer": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", + "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es5-ext": { + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-iterator/node_modules/d": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", + "license": "ISC", + "dependencies": { + "es5-ext": "^0.10.64", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/es6-object-assign": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", + "integrity": "sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw==", + "license": "MIT" + }, + "node_modules/es6-polyfills": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es6-polyfills/-/es6-polyfills-2.0.0.tgz", + "integrity": "sha512-daIt/MHqdYmxnuo5KcwAU9EqSxvaDRyajYOUU9fy+CLuU5+RFhpNCnL3oPsq7n+g673F3z/Vb+FXo/EmQjlkbw==", + "deprecated": "Use @natlibfi/es6-polyfills instead", + "license": " LGPL-3.0+", + "dependencies": { + "es6-object-assign": "^1.0.3", + "es6-promise-polyfill": "^1.2.0" + } + }, + "node_modules/es6-promise-polyfill": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es6-promise-polyfill/-/es6-promise-polyfill-1.2.0.tgz", + "integrity": "sha512-HHb0vydCpoclpd0ySPkRXMmBw80MRt1wM4RBJBlXkux97K7gleabZdsR0gvE1nNPM9mgOZIBTzjjXiPxf4lIqQ==", + "license": "MIT" + }, + "node_modules/es6-promisify": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-7.0.0.tgz", + "integrity": "sha512-ginqzK3J90Rd4/Yz7qRrqUeIpe3TwSXTPPZtPne7tGBPeAaQiU8qt4fpKApnxHcq1AwtUdHVg5P77x/yrggG8Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", + "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", + "license": "ISC", + "dependencies": { + "d": "^1.0.2", + "ext": "^1.7.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/es6-symbol/node_modules/d": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", + "license": "ISC", + "dependencies": { + "es5-ext": "^0.10.64", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/es6-weak-map": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-0.1.4.tgz", + "integrity": "sha512-P+N5Cd2TXeb7G59euFiM7snORspgbInS29Nbf3KNO2JQp/DyhvMCDWd58nsVAXwYJ6W3Bx7qDdy6QQ3PCJ7jKQ==", + "license": "MIT", + "dependencies": { + "d": "~0.1.1", + "es5-ext": "~0.10.6", + "es6-iterator": "~0.1.3", + "es6-symbol": "~2.0.1" + } + }, + "node_modules/es6-weak-map/node_modules/es6-iterator": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-0.1.3.tgz", + "integrity": "sha512-6TOmbFM6OPWkTe+bQ3ZuUkvqcWUjAnYjKUCLdbvRsAUz2Pr+fYIibwNXNkLNtIK9PPFbNMZZddaRNkyJhlGJhA==", + "license": "MIT", + "dependencies": { + "d": "~0.1.1", + "es5-ext": "~0.10.5", + "es6-symbol": "~2.0.1" + } + }, + "node_modules/es6-weak-map/node_modules/es6-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-2.0.1.tgz", + "integrity": "sha512-wjobO4zO8726HVU7mI2OA/B6QszqwHJuKab7gKHVx+uRfVVYGcWJkCIFxV2Madqb9/RUSrhJ/r6hPfG7FsWtow==", + "license": "MIT", + "dependencies": { + "d": "~0.1.1", + "es5-ext": "~0.10.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/eslint": { + "version": "9.39.3", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.3.tgz", + "integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.3", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-jsdoc": { + "version": "48.11.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.11.0.tgz", + "integrity": "sha512-d12JHJDPNo7IFwTOAItCeJY1hcqoIxE0lHA8infQByLilQ9xkqrRa6laWCnsuCrf+8rUnvxXY1XuTbibRBNylA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@es-joy/jsdoccomment": "~0.46.0", + "are-docs-informative": "^0.0.2", + "comment-parser": "1.4.1", + "debug": "^4.3.5", + "escape-string-regexp": "^4.0.0", + "espree": "^10.1.0", + "esquery": "^1.6.0", + "parse-imports": "^2.1.1", + "semver": "^7.6.3", + "spdx-expression-parse": "^4.0.0", + "synckit": "^0.9.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-jsdoc/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-plugin-jsdoc/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-scope/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "license": "ISC", + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esniff/node_modules/d": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", + "license": "ISC", + "dependencies": { + "es5-ext": "^0.10.64", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "node_modules/event-emitter/node_modules/d": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", + "license": "ISC", + "dependencies": { + "es5-ext": "^0.10.64", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/eventemitter2": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", + "integrity": "sha512-K7J4xq5xAD5jHsGM5ReWXRTFa3JRGofHiMcVgQ8PRwgWxzjHpMWCIzsmyf60+mh8KLsqYPcjUMa0AC4hd6lPyQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true, + "license": "MIT" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.7.0" + } + }, + "node_modules/evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "license": "MIT", + "dependencies": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/exec-buffer": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/exec-buffer/-/exec-buffer-3.2.0.tgz", + "integrity": "sha512-wsiD+2Tp6BWHoVv3B+5Dcx6E7u5zky+hUwOHjuH2hKSLR3dvRmX8fk8UD8uqQixHs4Wk6eDmiegVrMPjKj7wpA==", + "license": "MIT", + "optional": true, + "dependencies": { + "execa": "^0.7.0", + "p-finally": "^1.0.0", + "pify": "^3.0.0", + "rimraf": "^2.5.4", + "tempfile": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/exec-buffer/node_modules/cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==", + "license": "MIT", + "optional": true, + "dependencies": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "node_modules/exec-buffer/node_modules/execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha512-RztN09XglpYI7aBBrJCPW95jEH7YF1UEPOoX9yDhUTPdp7mK+CQvnLTuD10BNXZ3byLTu2uehZ8EcKT/4CGiFw==", + "license": "MIT", + "optional": true, + "dependencies": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/exec-buffer/node_modules/get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/exec-buffer/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/exec-buffer/node_modules/lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "license": "ISC", + "optional": true, + "dependencies": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "node_modules/exec-buffer/node_modules/npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", + "license": "MIT", + "optional": true, + "dependencies": { + "path-key": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/exec-buffer/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/exec-buffer/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/exec-buffer/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "license": "MIT", + "optional": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/exec-buffer/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/exec-buffer/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/exec-buffer/node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "license": "ISC", + "optional": true + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/executable": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", + "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", + "license": "MIT", + "optional": true, + "dependencies": { + "pify": "^2.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/exif-parser": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz", + "integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==" + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==", + "license": "MIT", + "dependencies": { + "homedir-polyfill": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/express": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.14.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/express/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/qs": { + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/express/node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, + "license": "ISC" + }, + "node_modules/express/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "license": "ISC", + "dependencies": { + "type": "^2.7.2" + } + }, + "node_modules/ext-list": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz", + "integrity": "sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==", + "license": "MIT", + "optional": true, + "dependencies": { + "mime-db": "^1.28.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ext-name": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ext-name/-/ext-name-5.0.0.tgz", + "integrity": "sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "ext-list": "^2.0.0", + "sort-keys-length": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "license": "MIT", + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/eyes": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", + "integrity": "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==", + "dev": true, + "engines": { + "node": "> 0.1.90" + } + }, + "node_modules/fancy-log": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", + "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==", + "license": "MIT", + "dependencies": { + "ansi-gray": "^0.1.1", + "color-support": "^1.1.3", + "parse-node-version": "^1.0.0", + "time-stamp": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-equals": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.0.tgz", + "integrity": "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", + "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fast-xml-parser": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.5.tgz", + "integrity": "sha512-cK9c5I/DwIOI7/Q7AlGN3DuTdwN61gwSfL8rvuVPK+0mcCNHHGxRrpiFtaZZRfRMJL3Gl8B2AFlBG6qXf03w9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/faye-websocket": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", + "integrity": "sha512-Xhj93RXbMSq8urNCUq4p9l0P6hnySJ/7YNRhYNug0bLOuii7pKO7xQFb5mx9xZXWCar88pLPb805PvUkwrLZpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fernet": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/fernet/-/fernet-0.3.3.tgz", + "integrity": "sha512-DvvqouVhv3VCor83wkQbSycekYUKDRQ1IKqcInaF5n5BSKgWBVfYLbSf7RRxojwQO0DZySiz5MlM2vO4MG3SUg==", + "license": "MIT", + "dependencies": { + "crypto-js": "~4.2.0", + "urlsafe-base64": "1.0.0" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/file-saver": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", + "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==", + "license": "MIT" + }, + "node_modules/file-sync-cmp": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/file-sync-cmp/-/file-sync-cmp-0.1.1.tgz", + "integrity": "sha512-0k45oWBokCqh2MOexeYKpyqmGKG+8mQ2Wd8iawx+uWd/weWJQAZ6SoPybagdCI4xFisag8iAR77WPm4h3pTfxA==", + "dev": true, + "license": "MIT" + }, + "node_modules/file-type": { + "version": "16.5.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz", + "integrity": "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==", + "license": "MIT", + "dependencies": { + "readable-web-to-node-stream": "^3.0.0", + "strtok3": "^6.2.4", + "token-types": "^4.1.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/filename-reserved-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", + "integrity": "sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/filenamify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-2.1.0.tgz", + "integrity": "sha512-ICw7NTT6RsDp2rnYKVd8Fu4cr6ITzGy3+u4vUujPkabyaz+03F24NWEX7fs5fp+kBonlaqPH8fAO2NM+SXt/JA==", + "license": "MIT", + "optional": true, + "dependencies": { + "filename-reserved-regex": "^2.0.0", + "strip-outer": "^1.0.0", + "trim-repeated": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-versions": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-3.2.0.tgz", + "integrity": "sha512-P8WRou2S+oe222TOCHitLy8zj+SIsVJh52VP4lvXkaFVnOFFdoWv1H1Jjvel1aI6NCFOAaeAVm8qrI0odiLcww==", + "license": "MIT", + "optional": true, + "dependencies": { + "semver-regex": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/findup-sync": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-5.0.0.tgz", + "integrity": "sha512-MzwXju70AuyflbgeOhzvQWAvvQdo1XL0A9bVvlXsYcFEBM87WR4OakL4OfZq+QRmr+duJubio+UtNQCPsVESzQ==", + "license": "MIT", + "dependencies": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.3", + "micromatch": "^4.0.4", + "resolve-dir": "^1.0.1" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/fined": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", + "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "expand-tilde": "^2.0.2", + "is-plain-object": "^2.0.3", + "object.defaults": "^1.1.0", + "object.pick": "^1.2.0", + "parse-filepath": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/flagged-respawn": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", + "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/flat": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/flat/-/flat-6.0.1.tgz", + "integrity": "sha512-/3FfIa8mbrg3xE7+wAhWeV+bd7L2Mof+xtZb5dRDKZ+wDvYJK4WDYeIOuOhre5Yv5aQObZrlbRmk3RTSiuQBtw==", + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", + "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg==", + "license": "MIT", + "dependencies": { + "for-in": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", + "license": "MIT", + "optional": true, + "dependencies": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/fs-mkdirp-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-2.0.1.tgz", + "integrity": "sha512-UTOY+59K6IA94tec8Wjqm0FSh5OVudGNB0NL/P6fB3HiE3bYOY3VYBGijsnOHNkQSwC1FKkU77pmq7xp9CskLw==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.8", + "streamx": "^2.12.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function-timeout": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/function-timeout/-/function-timeout-1.0.2.tgz", + "integrity": "sha512-939eZS4gJ3htTHAldmyyuzlrD58P03fHG49v2JfFXbV6OhvZKRC9j2yAtdHw/zrp2zXHuv05zMIy40F0ge7spA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gamma": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gamma/-/gamma-1.0.0.tgz", + "integrity": "sha512-bPI4QJKNe/PfuO6PZU7ckMkBk0wEe3iQWx3DadEp5ceiAkJwc8CRukwncOZ5Hk5rc6nNTbIfByZKGz7Vv+xXdw==", + "license": "MIT" + }, + "node_modules/gaze": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", + "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "globule": "^1.0.0" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gensequence": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/gensequence/-/gensequence-7.0.0.tgz", + "integrity": "sha512-47Frx13aZh01afHJTB3zTtKIlFI6vWY+MYCN9Qpew6i52rfKjnhCF/l1YlC8UmEMvvntZZ6z4PiCcmyuedR2aQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/geodesy": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/geodesy/-/geodesy-1.1.3.tgz", + "integrity": "sha512-H/0XSd1KjKZGZ2YGZcOYzRyY/foYAawwTEumNSo+YUwf+u5d4CfvBRg2i2Qimrx9yUEjWR8hLvMnhghuVFN0Zg==", + "license": "MIT" + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-proxy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/get-proxy/-/get-proxy-2.1.0.tgz", + "integrity": "sha512-zmZIaQTWnNQb4R4fJUEp/FC51eZsc6EkErspy3xtIYStaq8EB/hDIWipxsal+E8rz0qD7f2sL/NA9Xee4RInJw==", + "license": "MIT", + "optional": true, + "dependencies": { + "npm-conf": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-uri": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.4.tgz", + "integrity": "sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/getobject": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/getobject/-/getobject-1.0.2.tgz", + "integrity": "sha512-2zblDBaFcb3rB4rF77XVnuINOE2h2k/OnqXAiy0IrTxUfV1iFp3la33oAQVY9pCpWU268WFYVt2t71hlMuLsOg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/gifsicle": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/gifsicle/-/gifsicle-5.3.0.tgz", + "integrity": "sha512-FJTpgdj1Ow/FITB7SVza5HlzXa+/lqEY0tHQazAJbuAdvyJtkH4wIdsR2K414oaTwRXHFLLF+tYbipj+OpYg+Q==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "bin-build": "^3.0.0", + "bin-wrapper": "^4.0.0", + "execa": "^5.0.0" + }, + "bin": { + "gifsicle": "cli.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/imagemin/gisicle-bin?sponsor=1" + } + }, + "node_modules/gifwrap": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/gifwrap/-/gifwrap-0.10.1.tgz", + "integrity": "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw==", + "license": "MIT", + "dependencies": { + "image-q": "^4.0.0", + "omggif": "^1.0.10" + } + }, + "node_modules/glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "devOptional": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-stream": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-8.0.3.tgz", + "integrity": "sha512-fqZVj22LtFJkHODT+M4N1RJQ3TjnnQhfE9GwZI8qXscYarnhpip70poMldRnP8ipQ/w0B621kOhfc53/J9bd/A==", + "license": "MIT", + "dependencies": { + "@gulpjs/to-absolute-glob": "^4.0.0", + "anymatch": "^3.1.3", + "fastq": "^1.13.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "is-negated-glob": "^1.0.0", + "normalize-path": "^3.0.0", + "streamx": "^2.12.5" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/glob-watcher": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-6.0.0.tgz", + "integrity": "sha512-wGM28Ehmcnk2NqRORXFOTOR064L4imSw3EeOqU5bIwUf62eXGwg89WivH6VMahL8zlQHeodzvHpXplrqzrz3Nw==", + "license": "MIT", + "dependencies": { + "async-done": "^2.0.0", + "chokidar": "^3.5.3" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/global-directory": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz", + "integrity": "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "4.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/global-directory/node_modules/ini": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", + "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "license": "MIT", + "dependencies": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==", + "license": "MIT", + "dependencies": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/globals": { + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz", + "integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==", + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.3", + "ignore": "^7.0.3", + "path-type": "^6.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/globule": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.4.tgz", + "integrity": "sha512-OPTIfhMBh7JbBYDpa5b+Q5ptmMWKwcNcFSR/0c6t8V4f3ZAVBEsKNY37QdVqmLRYSMhOUGYrY0QhSoEpzGr/Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob": "~7.1.1", + "lodash": "^4.17.21", + "minimatch": "~3.0.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/globule/node_modules/minimatch": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", + "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/glogg": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/glogg/-/glogg-2.2.0.tgz", + "integrity": "sha512-eWv1ds/zAlz+M1ioHsyKJomfY7jbDDPpwSkv14KQj89bycx1nvK5/2Cj/T9g7kzJcX5Bc7Yv22FjfBZS/jl94A==", + "license": "MIT", + "dependencies": { + "sparkles": "^2.1.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/got/-/got-7.1.0.tgz", + "integrity": "sha512-Y5WMo7xKKq1muPsxD+KmrR8DH5auG7fBdDVueZwETwV6VytKyU9OX/ddpq2/1hp1vIPvVb4T81dKQz3BivkNLw==", + "license": "MIT", + "optional": true, + "dependencies": { + "decompress-response": "^3.2.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "is-plain-obj": "^1.1.0", + "is-retry-allowed": "^1.0.0", + "is-stream": "^1.0.0", + "isurl": "^1.0.0-alpha5", + "lowercase-keys": "^1.0.0", + "p-cancelable": "^0.3.0", + "p-timeout": "^1.1.1", + "safe-buffer": "^5.0.1", + "timed-out": "^4.0.0", + "url-parse-lax": "^1.0.0", + "url-to-options": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/got/node_modules/get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/got/node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/got/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/graceful-readlink": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha512-8tLu60LgxF6XpdbK8OW3FA+IfTNBn1ZHGHKF4KQbEeSkajYw5PlYJcKluntgegDPTg8UkHjpet1T82vk6TQ68w==", + "license": "MIT" + }, + "node_modules/grunt": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/grunt/-/grunt-1.6.1.tgz", + "integrity": "sha512-/ABUy3gYWu5iBmrUSRBP97JLpQUm0GgVveDCp6t3yRNIoltIYw7rEj3g5y1o2PGPR2vfTRGa7WC/LZHLTXnEzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "dateformat": "~4.6.2", + "eventemitter2": "~0.4.13", + "exit": "~0.1.2", + "findup-sync": "~5.0.0", + "glob": "~7.1.6", + "grunt-cli": "~1.4.3", + "grunt-known-options": "~2.0.0", + "grunt-legacy-log": "~3.0.0", + "grunt-legacy-util": "~2.0.1", + "iconv-lite": "~0.6.3", + "js-yaml": "~3.14.0", + "minimatch": "~3.0.4", + "nopt": "~3.0.6" + }, + "bin": { + "grunt": "bin/grunt" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/grunt-chmod": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/grunt-chmod/-/grunt-chmod-1.1.1.tgz", + "integrity": "sha512-f807W/VOIhhaOW85JyeRd4DgB0RcbsGQV/4IvtcKctOWGvPJns4AqN7xW73PG9+RwDnSGxApS+6Xov5L2LeNXg==", + "dev": true, + "dependencies": { + "shelljs": "^0.5.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/grunt-cli": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/grunt-cli/-/grunt-cli-1.4.3.tgz", + "integrity": "sha512-9Dtx/AhVeB4LYzsViCjUQkd0Kw0McN2gYpdmGYKtE2a5Yt7v1Q+HYZVWhqXc/kGnxlMtqKDxSwotiGeFmkrCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "grunt-known-options": "~2.0.0", + "interpret": "~1.1.0", + "liftup": "~3.0.1", + "nopt": "~4.0.1", + "v8flags": "~3.2.0" + }, + "bin": { + "grunt": "bin/grunt" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/grunt-cli/node_modules/nopt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", + "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "1", + "osenv": "^0.1.4" + }, + "bin": { + "nopt": "bin/nopt.js" + } + }, + "node_modules/grunt-concurrent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/grunt-concurrent/-/grunt-concurrent-3.0.0.tgz", + "integrity": "sha512-AgXtjUJESHEGeGX8neL3nmXBTHSj1QC48ABQ3ng2/vjuSBpDD8gKcVHSlXP71pFkIR8TQHf+eomOx6OSYSgfrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "arrify": "^2.0.1", + "async": "^3.1.0", + "indent-string": "^4.0.0", + "pad-stream": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "grunt": ">=1" + } + }, + "node_modules/grunt-contrib-clean": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/grunt-contrib-clean/-/grunt-contrib-clean-2.0.1.tgz", + "integrity": "sha512-uRvnXfhiZt8akb/ZRDHJpQQtkkVkqc/opWO4Po/9ehC2hPxgptB9S6JHDC/Nxswo4CJSM0iFPT/Iym3cEMWzKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "async": "^3.2.3", + "rimraf": "^2.6.2" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "grunt": ">=0.4.5" + } + }, + "node_modules/grunt-contrib-connect": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/grunt-contrib-connect/-/grunt-contrib-connect-5.0.1.tgz", + "integrity": "sha512-Hfq/0QJl3ddD2N/a/1cDJHkKEOGk6m7W6uxNe0AmYwtf6v0F/4+8q9rvPJ1tl+mrI90lU/89I9T/h48qqeMfQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "async": "^3.2.5", + "connect": "^3.7.0", + "connect-livereload": "^0.6.1", + "http2-wrapper": "^2.2.1", + "morgan": "^1.10.0", + "open": "^8.0.0", + "portscanner": "^2.2.0", + "serve-index": "^1.9.1", + "serve-static": "^1.15.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/grunt-contrib-copy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-copy/-/grunt-contrib-copy-1.0.0.tgz", + "integrity": "sha512-gFRFUB0ZbLcjKb67Magz1yOHGBkyU6uL29hiEW1tdQ9gQt72NuMKIy/kS6dsCbV0cZ0maNCb0s6y+uT1FKU7jA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^1.1.1", + "file-sync-cmp": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/grunt-contrib-watch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-watch/-/grunt-contrib-watch-1.1.0.tgz", + "integrity": "sha512-yGweN+0DW5yM+oo58fRu/XIRrPcn3r4tQx+nL7eMRwjpvk+rQY6R8o94BPK0i2UhTg9FN21hS+m8vR8v9vXfeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "async": "^2.6.0", + "gaze": "^1.1.0", + "lodash": "^4.17.10", + "tiny-lr": "^1.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/grunt-contrib-watch/node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/grunt-eslint": { + "version": "25.0.0", + "resolved": "https://registry.npmjs.org/grunt-eslint/-/grunt-eslint-25.0.0.tgz", + "integrity": "sha512-JIV5IPgOuacorFLmYtUTq0n+0qGIL9FSQJ4KVnNfCg/8Fm+K1t6OWrzXXI8TxWTwq2K9E3parFVXCpn1sGLbKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "eslint": "^9.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + }, + "peerDependencies": { + "grunt": ">=1" + } + }, + "node_modules/grunt-eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/grunt-eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/grunt-eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/grunt-exec": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/grunt-exec/-/grunt-exec-3.0.0.tgz", + "integrity": "sha512-cgAlreXf3muSYS5LzW0Cc4xHK03BjFOYk0MqCQ/MZ3k1Xz2GU7D+IAJg4UKicxpO+XdONJdx/NJ6kpy2wI+uHg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + }, + "peerDependencies": { + "grunt": ">=0.4" + } + }, + "node_modules/grunt-known-options": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/grunt-known-options/-/grunt-known-options-2.0.0.tgz", + "integrity": "sha512-GD7cTz0I4SAede1/+pAbmJRG44zFLPipVtdL9o3vqx9IEyb7b4/Y3s7r6ofI3CchR5GvYJ+8buCSioDv5dQLiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/grunt-legacy-log": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-3.0.0.tgz", + "integrity": "sha512-GHZQzZmhyq0u3hr7aHW4qUH0xDzwp2YXldLPZTCjlOeGscAOWWPftZG3XioW8MasGp+OBRIu39LFx14SLjXRcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "colors": "~1.1.2", + "grunt-legacy-log-utils": "~2.1.0", + "hooker": "~0.2.3", + "lodash": "~4.17.19" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/grunt-legacy-log-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-2.1.0.tgz", + "integrity": "sha512-lwquaPXJtKQk0rUM1IQAop5noEpwFqOXasVoedLeNzaibf/OPWjKYvvdqnEHNmU+0T0CaReAXIbGo747ZD+Aaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "~4.1.0", + "lodash": "~4.17.19" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/grunt-legacy-log-utils/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/grunt-legacy-log-utils/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/grunt-legacy-log-utils/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/grunt-legacy-log/node_modules/colors": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", + "integrity": "sha512-ENwblkFQpqqia6b++zLD/KUWafYlVY/UNnAp7oz7LY7E924wmpye416wBOmvv/HMWzl8gL1kJlfvId/1Dg176w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/grunt-legacy-util": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-2.0.1.tgz", + "integrity": "sha512-2bQiD4fzXqX8rhNdXkAywCadeqiPiay0oQny77wA2F3WF4grPJXCvAcyoWUJV+po/b15glGkxuSiQCK299UC2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "async": "~3.2.0", + "exit": "~0.1.2", + "getobject": "~1.0.0", + "hooker": "~0.2.3", + "lodash": "~4.17.21", + "underscore.string": "~3.3.5", + "which": "~2.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/grunt-retro": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/grunt-retro/-/grunt-retro-0.6.4.tgz", + "integrity": "sha512-kqnvNUAngOhkDckEQPYFDqNcRlculVp/Sy+gCe4ey7utM4BCaENVf2JfDeK488mj/0cgmAZyXwpW6w9l1OAxMg==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/grunt-webpack": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/grunt-webpack/-/grunt-webpack-6.0.0.tgz", + "integrity": "sha512-FtRVTGJGuV9Ic/OrCR80p5u601e0ekvTyHo7vnwVo3XlvRh5wR1ATAVT9FnnobHqZnQ/DeF84W97si5+roUWEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-for-each": "^3.0.0", + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=16.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/grunt-zip": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/grunt-zip/-/grunt-zip-1.0.0.tgz", + "integrity": "sha512-e5HOzf+BLFR6rXM67oGrFcVgfVbLDPBnv29YdWTupthwNg/1y91B0xXx667E6dKin8YvrwDShtQy48D1NRzn7g==", + "dev": true, + "dependencies": { + "grunt-retro": "~0.6.0", + "jszip": "^3.8.0" + }, + "bin": { + "grunt-zip": "bin/grunt-zip" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/grunt/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/grunt/node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/grunt/node_modules/minimatch": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", + "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/grunt/node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/gulp": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/gulp/-/gulp-5.0.1.tgz", + "integrity": "sha512-PErok3DZSA5WGMd6XXV3IRNO0mlB+wW3OzhFJLEec1jSERg2j1bxJ6e5Fh6N6fn3FH2T9AP4UYNb/pYlADB9sA==", + "license": "MIT", + "dependencies": { + "glob-watcher": "^6.0.0", + "gulp-cli": "^3.1.0", + "undertaker": "^2.0.0", + "vinyl-fs": "^4.0.2" + }, + "bin": { + "gulp": "bin/gulp.js" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/gulp-cli": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-3.1.0.tgz", + "integrity": "sha512-zZzwlmEsTfXcxRKiCHsdyjZZnFvXWM4v1NqBJSYbuApkvVKivjcmOS2qruAJ+PkEHLFavcDKH40DPc1+t12a9Q==", + "license": "MIT", + "dependencies": { + "@gulpjs/messages": "^1.1.0", + "chalk": "^4.1.2", + "copy-props": "^4.0.0", + "gulplog": "^2.2.0", + "interpret": "^3.1.1", + "liftoff": "^5.0.1", + "mute-stdout": "^2.0.0", + "replace-homedir": "^2.0.0", + "semver-greatest-satisfied-range": "^2.0.0", + "string-width": "^4.2.3", + "v8flags": "^4.0.0", + "yargs": "^16.2.0" + }, + "bin": { + "gulp": "bin/gulp.js" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/gulp-cli/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/gulp-cli/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/gulp-cli/node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/gulp-cli/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/gulp-cli/node_modules/v8flags": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-4.0.1.tgz", + "integrity": "sha512-fcRLaS4H/hrZk9hYwbdRM35D0U8IYMfEClhXxCivOojl+yTRAZH3Zy2sSy6qVCiGbV9YAtPssP6jaChqC9vPCg==", + "license": "MIT", + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/gulp-concat": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/gulp-concat/-/gulp-concat-2.6.1.tgz", + "integrity": "sha512-a2scActrQrDBpBbR3WUZGyGS1JEPLg5PZJdIa7/Bi3GuKAmPYDK6SFhy/NZq5R8KsKKFvtfR0fakbUCcKGCCjg==", + "license": "MIT", + "dependencies": { + "concat-with-sourcemaps": "^1.0.0", + "through2": "^2.0.0", + "vinyl": "^2.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/gulp-imagemin": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/gulp-imagemin/-/gulp-imagemin-9.2.0.tgz", + "integrity": "sha512-y7yAHTUzfHHq6njHY2OIvLOcffF1I/ULSlfzunnIvPeNNpYDArwrlqCzPvnN4Le0p3H/1ERAAQJJPMhbnTRnFQ==", + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "gulp-plugin-extras": "^1.0.0", + "imagemin": "^9.0.0", + "plur": "^5.1.0", + "pretty-bytes": "^6.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + }, + "optionalDependencies": { + "imagemin-gifsicle": "^7.0.0", + "imagemin-mozjpeg": "^10.0.0", + "imagemin-optipng": "^8.0.0", + "imagemin-svgo": "^10.0.1" + }, + "peerDependencies": { + "gulp": ">=4" + }, + "peerDependenciesMeta": { + "gulp": { + "optional": true + } + } + }, + "node_modules/gulp-imagemin/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/gulp-minify-css": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/gulp-minify-css/-/gulp-minify-css-1.2.4.tgz", + "integrity": "sha512-byBqFQM/HrZoUVYihu/03iYH4m7U5TjSGhr6/7JvpMHh9+woewsCtEp6Noif2VXB+idDoM4ECd9sw+St+KFqsg==", + "deprecated": "Please use gulp-clean-css", + "license": "MIT", + "dependencies": { + "clean-css": "^3.3.3", + "gulp-util": "^3.0.5", + "object-assign": "^4.0.1", + "readable-stream": "^2.0.0", + "vinyl-bufferstream": "^1.0.1", + "vinyl-sourcemaps-apply": "^0.2.0" + } + }, + "node_modules/gulp-minify-css/node_modules/clean-css": { + "version": "3.4.28", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-3.4.28.tgz", + "integrity": "sha512-aTWyttSdI2mYi07kWqHi24NUU9YlELFKGOAgFzZjDN1064DMAOy2FBuoyGmkKRlXkbpXd0EVHmiVkbKhKoirTw==", + "license": "MIT", + "dependencies": { + "commander": "2.8.x", + "source-map": "0.4.x" + }, + "bin": { + "cleancss": "bin/cleancss" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-minify-css/node_modules/commander": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz", + "integrity": "sha512-+pJLBFVk+9ZZdlAOB5WuIElVPPth47hILFkmGym57aq8kwxsowvByvB0DHs1vQAhyMZzdcpTtF0VDKGkSDR4ZQ==", + "license": "MIT", + "dependencies": { + "graceful-readlink": ">= 1.0.0" + }, + "engines": { + "node": ">= 0.6.x" + } + }, + "node_modules/gulp-minify-css/node_modules/source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha512-Y8nIfcb1s/7DcobUz1yOO1GSp7gyL+D9zLHDehT7iRESqGSxjJ448Sg7rvfgsRJCnKLdSl11uGf0s9X80cH0/A==", + "license": "BSD-3-Clause", + "dependencies": { + "amdefine": ">=0.0.4" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/gulp-minify-html": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/gulp-minify-html/-/gulp-minify-html-1.0.6.tgz", + "integrity": "sha512-toazuTI8XG4BNOY7L/4Da258tgWbtFlfPAyZi0QwrDc+ba11LMMT8dLhGo48SMAil6Wu8PuTcICfkJ6d/dCjLQ==", + "license": "MIT", + "dependencies": { + "gulp-util": "^3.0.3", + "minimize": "^1.5.0", + "through2": "^0.6.1" + } + }, + "node_modules/gulp-minify-html/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "license": "MIT" + }, + "node_modules/gulp-minify-html/node_modules/readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/gulp-minify-html/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", + "license": "MIT" + }, + "node_modules/gulp-minify-html/node_modules/through2": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", + "integrity": "sha512-RkK/CCESdTKQZHdmKICijdKKsCRVHs5KsLZ6pACAmF/1GPUQhonHSXWNERctxEp7RmvjdNbZTL5z9V7nSCXKcg==", + "license": "MIT", + "dependencies": { + "readable-stream": ">=1.0.33-1 <1.1.0-0", + "xtend": ">=4.0.0 <4.1.0-0" + } + }, + "node_modules/gulp-plugin-extras": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/gulp-plugin-extras/-/gulp-plugin-extras-1.1.0.tgz", + "integrity": "sha512-T0AXOEVoKYzLIBlwEZ7LtAx2w4ExIozIoxVeYEVLFbdxI7i0sWvFDq0F8mm47djixDF3vAqDPoyGwh3Sg/PWtQ==", + "license": "MIT", + "dependencies": { + "@types/vinyl": "^2.0.12", + "chalk": "^5.3.0", + "easy-transform-stream": "^1.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gulp-plugin-extras/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/gulp-rename": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/gulp-rename/-/gulp-rename-2.1.0.tgz", + "integrity": "sha512-dGuzuH8jQGqCMqC544IEPhs5+O2l+IkdoSZsgd4kY97M1CxQeI3qrmweQBIrxLBbjbe/8uEWK8HHcNBc3OCy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/gulp-uglify": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gulp-uglify/-/gulp-uglify-3.0.2.tgz", + "integrity": "sha512-gk1dhB74AkV2kzqPMQBLA3jPoIAPd/nlNzP2XMDSG8XZrqnlCiDGAqC+rZOumzFvB5zOphlFh6yr3lgcAb/OOg==", + "license": "MIT", + "dependencies": { + "array-each": "^1.0.1", + "extend-shallow": "^3.0.2", + "gulplog": "^1.0.0", + "has-gulplog": "^0.1.0", + "isobject": "^3.0.1", + "make-error-cause": "^1.1.1", + "safe-buffer": "^5.1.2", + "through2": "^2.0.0", + "uglify-js": "^3.0.5", + "vinyl-sourcemaps-apply": "^0.2.0" + } + }, + "node_modules/gulp-uglify/node_modules/glogg": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz", + "integrity": "sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==", + "license": "MIT", + "dependencies": { + "sparkles": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/gulp-uglify/node_modules/gulplog": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", + "integrity": "sha512-hm6N8nrm3Y08jXie48jsC55eCZz9mnb4OirAStEk2deqeyhXU3C1otDVh+ccttMuc1sBi6RX6ZJ720hs9RCvgw==", + "license": "MIT", + "dependencies": { + "glogg": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/gulp-uglify/node_modules/sparkles": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", + "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/gulp-util": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.8.tgz", + "integrity": "sha512-q5oWPc12lwSFS9h/4VIjG+1NuNDlJ48ywV2JKItY4Ycc/n1fXJeYPVQsfu5ZrhQi7FGSDBalwUCLar/GyHXKGw==", + "deprecated": "gulp-util is deprecated - replace it, following the guidelines at https://medium.com/gulpjs/gulp-util-ca3b1f9f9ac5", + "license": "MIT", + "dependencies": { + "array-differ": "^1.0.0", + "array-uniq": "^1.0.2", + "beeper": "^1.0.0", + "chalk": "^1.0.0", + "dateformat": "^2.0.0", + "fancy-log": "^1.1.0", + "gulplog": "^1.0.0", + "has-gulplog": "^0.1.0", + "lodash._reescape": "^3.0.0", + "lodash._reevaluate": "^3.0.0", + "lodash._reinterpolate": "^3.0.0", + "lodash.template": "^3.0.0", + "minimist": "^1.1.0", + "multipipe": "^0.1.2", + "object-assign": "^3.0.0", + "replace-ext": "0.0.1", + "through2": "^2.0.0", + "vinyl": "^0.5.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/gulp-util/node_modules/clone-stats": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", + "integrity": "sha512-dhUqc57gSMCo6TX85FLfe51eC/s+Im2MLkAgJwfaRRexR2tA4dd3eLEW4L6efzHc2iNorrRRXITifnDLlRrhaA==", + "license": "MIT" + }, + "node_modules/gulp-util/node_modules/dateformat": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz", + "integrity": "sha512-GODcnWq3YGoTnygPfi02ygEiRxqUxpJwuRHjdhJYuxpcZmDq4rjBiXYmbCCzStxo176ixfLT6i4NPwQooRySnw==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/gulp-util/node_modules/glogg": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz", + "integrity": "sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==", + "license": "MIT", + "dependencies": { + "sparkles": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/gulp-util/node_modules/gulplog": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", + "integrity": "sha512-hm6N8nrm3Y08jXie48jsC55eCZz9mnb4OirAStEk2deqeyhXU3C1otDVh+ccttMuc1sBi6RX6ZJ720hs9RCvgw==", + "license": "MIT", + "dependencies": { + "glogg": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/gulp-util/node_modules/object-assign": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", + "integrity": "sha512-jHP15vXVGeVh1HuaA2wY6lxk+whK/x4KBG88VXeRma7CCun7iGD5qPc4eYykQ9sdQvg8jkwFKsSxHln2ybW3xQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-util/node_modules/sparkles": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", + "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/gulp-util/node_modules/vinyl": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz", + "integrity": "sha512-P5zdf3WB9uzr7IFoVQ2wZTmUwHL8cMZWJGzLBNCHNZ3NB6HTMsYABtt7z8tAGIINLXyAob9B9a1yzVGMFOYKEA==", + "license": "MIT", + "dependencies": { + "clone": "^1.0.0", + "clone-stats": "^0.0.1", + "replace-ext": "0.0.1" + }, + "engines": { + "node": ">= 0.9" + } + }, + "node_modules/gulplog": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-2.2.0.tgz", + "integrity": "sha512-V2FaKiOhpR3DRXZuYdRLn/qiY0yI5XmqbTKrYbdemJ+xOh2d2MOweI/XFgMzd/9+1twdvMwllnZbWZNJ+BOm4A==", + "license": "MIT", + "dependencies": { + "glogg": "^2.2.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/gzip-size": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", + "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "duplexer": "^0.1.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "dev": true, + "license": "MIT" + }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-gulplog": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz", + "integrity": "sha512-+F4GzLjwHNNDEAJW2DC1xXfEoPkRDmUdJ7CBYw4MpqtDwOnqdImJl7GWlpqx+Wko6//J8uKTnIe4wZSv7yCqmw==", + "license": "MIT", + "dependencies": { + "sparkles": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/has-gulplog/node_modules/sparkles": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", + "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbol-support-x": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz", + "integrity": "sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==", + "license": "MIT", + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-to-string-tag-x": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz", + "integrity": "sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==", + "license": "MIT", + "optional": true, + "dependencies": { + "has-symbol-support-x": "^1.4.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hash-base": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.5.tgz", + "integrity": "sha512-vXm0l45VbcHEVlTCzs8M+s0VeYsB2lnlAaThoLKGXr3bE/VWDOelNUnycUPEhKEaXARL2TEFjBOyUiM6+55KBg==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/hash-wasm": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/hash-wasm/-/hash-wasm-4.12.0.tgz", + "integrity": "sha512-+/2B2rYLb48I/evdOIhP+K/DD2ca2fgBjp6O+GBEnCDk2e4rpeXIK8GvIyRPjTezgmWn9gmKwkQjjx6BtqDHVQ==" + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/highlight.js": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", + "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "license": "MIT", + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "license": "MIT", + "dependencies": { + "parse-passwd": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hooker": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", + "integrity": "sha512-t+UerCsQviSymAInD01Pw+Dn/usmz1sRO+3Zk1+lx8eg+WKpD2ulcwWqHHL0+aseRBr+3+vIhiG1K1JTwaIcTA==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/html-entities": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz", + "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT" + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "camel-case": "^4.1.2", + "clean-css": "^5.2.2", + "commander": "^8.3.0", + "he": "^1.2.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.10.0" + }, + "bin": { + "html-minifier-terser": "cli.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/html-minifier-terser/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/html-webpack-plugin": { + "version": "5.6.6", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.6.tgz", + "integrity": "sha512-bLjW01UTrvoWTJQL5LsMRo1SypHW80FTm12OJRSnr3v6YHNhfe+1r0MYUZJMACxnCHURVnBWRwAsWs2yPU9Ezw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/html-minifier-terser": "^6.0.0", + "html-minifier-terser": "^6.0.2", + "lodash": "^4.17.21", + "pretty-error": "^4.0.0", + "tapable": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/html-webpack-plugin" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.20.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/htmlparser2": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/http-cache-semantics": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz", + "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==", + "license": "BSD-2-Clause", + "optional": true + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-errors/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-errors/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true, + "license": "ISC" + }, + "node_modules/http-parser-js": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.9.tgz", + "integrity": "sha512-n1XsPy3rXVxlqxVioEWdC+0+M+SQw0DpJynwtOPo1X+ZlvdzTLtDBIJJlDQTnwZIFJrZSzSGmIOUdP8tu+SgLw==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-proxy-middleware": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", + "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/http2-wrapper": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", + "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "devOptional": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/hyperdyperid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", + "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.18" + } + }, + "node_modules/iced-error": { + "version": "0.0.13", + "resolved": "https://registry.npmjs.org/iced-error/-/iced-error-0.0.13.tgz", + "integrity": "sha512-yEEaG8QfyyRL0SsbNNDw3rVgTyqwHFMCuV6jDvD43f/2shmdaFXkqvFLGhDlsYNSolzYHwVLM/CrXt9GygYopA==" + }, + "node_modules/iced-lock": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/iced-lock/-/iced-lock-1.1.0.tgz", + "integrity": "sha512-J9UMVitgTMYrkUil5EB9/Q4BPWiMpFH156yjDlmMoMRKs3s3PnXj/6G0UlzIOGnNi5JVNk/zVYLXVnuo+1QnqQ==", + "dependencies": { + "iced-runtime": "^1.0.0" + } + }, + "node_modules/iced-runtime": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/iced-runtime/-/iced-runtime-1.0.4.tgz", + "integrity": "sha512-rgiJXNF6ZgF2Clh/TKUlBDW3q51YPDJUXmxGQXx1b8tbZpVpTn+1RX9q1sjNkujXIIaVxZByQzPHHORg7KV51g==" + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/idb-keyval": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.1.tgz", + "integrity": "sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg==", + "license": "Apache-2.0" + }, + "node_modules/identifier-regex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/identifier-regex/-/identifier-regex-1.0.1.tgz", + "integrity": "sha512-ZrYyM0sozNPZlvBvE7Oq9Bn44n0qKGrYu5sQ0JzMUnjIhpgWYE2JB6aBoFwEYdPjqj7jPyxXTMJiHDOxDfd8yw==", + "license": "MIT", + "dependencies": { + "reserved-identifiers": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/image-dimensions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/image-dimensions/-/image-dimensions-2.5.0.tgz", + "integrity": "sha512-CKZPHjAEtSg9lBV9eER0bhNn/yrY7cFEQEhkwjLhqLY+Na8lcP1pEyWsaGMGc8t2qbKWA/tuqbhFQpOKGN72Yw==", + "license": "MIT", + "bin": { + "image-dimensions": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/image-q": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/image-q/-/image-q-4.0.0.tgz", + "integrity": "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==", + "license": "MIT", + "dependencies": { + "@types/node": "16.9.1" + } + }, + "node_modules/image-q/node_modules/@types/node": { + "version": "16.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz", + "integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==", + "license": "MIT" + }, + "node_modules/imagemin": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/imagemin/-/imagemin-9.0.1.tgz", + "integrity": "sha512-UoHOfynN8QeqRoUGunn6ilMnLpJ+utbmleP2ufcFqaGal8mY/PeOpV43N31uqtb+CBMFqQ7hxgKzIaAAnmcrdA==", + "license": "MIT", + "dependencies": { + "change-file-extension": "^0.1.1", + "environment": "^1.0.0", + "file-type": "^19.0.0", + "globby": "^14.0.1", + "image-dimensions": "^2.3.0", + "junk": "^4.0.1", + "ow": "^2.0.0", + "p-pipe": "^4.0.0", + "slash": "^5.1.0", + "uint8array-extras": "^1.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imagemin-gifsicle": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/imagemin-gifsicle/-/imagemin-gifsicle-7.0.0.tgz", + "integrity": "sha512-LaP38xhxAwS3W8PFh4y5iQ6feoTSF+dTAXFRUEYQWYst6Xd+9L/iPk34QGgK/VO/objmIlmq9TStGfVY2IcHIA==", + "license": "MIT", + "optional": true, + "dependencies": { + "execa": "^1.0.0", + "gifsicle": "^5.0.0", + "is-gif": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/imagemin/imagemin-gifsicle?sponsor=1" + } + }, + "node_modules/imagemin-gifsicle/node_modules/cross-spawn": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", + "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", + "license": "MIT", + "optional": true, + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/imagemin-gifsicle/node_modules/execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "license": "MIT", + "optional": true, + "dependencies": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/imagemin-gifsicle/node_modules/get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "license": "MIT", + "optional": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/imagemin-gifsicle/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/imagemin-gifsicle/node_modules/npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", + "license": "MIT", + "optional": true, + "dependencies": { + "path-key": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/imagemin-gifsicle/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/imagemin-gifsicle/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/imagemin-gifsicle/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "license": "MIT", + "optional": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/imagemin-gifsicle/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/imagemin-gifsicle/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/imagemin-mozjpeg": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/imagemin-mozjpeg/-/imagemin-mozjpeg-10.0.0.tgz", + "integrity": "sha512-DK85QNOjS3/GzWYfNB3CACMZD10sIQgFDv1+WTOnZljgltQTEyATjdyUVyjKu5q4sCESQdwvwq7WEZzJ5fFjlg==", + "license": "MIT", + "optional": true, + "dependencies": { + "execa": "^6.0.0", + "is-jpg": "^3.0.0", + "mozjpeg": "^8.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/imagemin-mozjpeg/node_modules/execa": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-6.1.0.tgz", + "integrity": "sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA==", + "license": "MIT", + "optional": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.1", + "human-signals": "^3.0.1", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^3.0.7", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/imagemin-mozjpeg/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imagemin-mozjpeg/node_modules/human-signals": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-3.0.1.tgz", + "integrity": "sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/imagemin-mozjpeg/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "license": "MIT", + "optional": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imagemin-mozjpeg/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imagemin-mozjpeg/node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imagemin-mozjpeg/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imagemin-mozjpeg/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imagemin-mozjpeg/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imagemin-optipng": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/imagemin-optipng/-/imagemin-optipng-8.0.0.tgz", + "integrity": "sha512-CUGfhfwqlPjAC0rm8Fy+R2DJDBGjzy2SkfyT09L8rasnF9jSoHFqJ1xxSZWK6HVPZBMhGPMxCTL70OgTHlLF5A==", + "license": "MIT", + "optional": true, + "dependencies": { + "exec-buffer": "^3.0.0", + "is-png": "^2.0.0", + "optipng-bin": "^7.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/imagemin-svgo": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/imagemin-svgo/-/imagemin-svgo-10.0.1.tgz", + "integrity": "sha512-v27/UTGkb3vrm5jvjsMGQ2oxaDfSOTBfJOgmFO2fYepx05bY1IqWCK13aDytVR+l9w9eOlq0NMCLbxJlghYb2g==", + "license": "MIT", + "optional": true, + "dependencies": { + "is-svg": "^4.3.1", + "svgo": "^2.5.0" + }, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sindresorhus/imagemin-svgo?sponsor=1" + } + }, + "node_modules/imagemin/node_modules/file-type": { + "version": "19.6.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-19.6.0.tgz", + "integrity": "sha512-VZR5I7k5wkD0HgFnMsq5hOsSc710MJMu5Nc5QYsbe38NN5iPV/XTObYLc/cpttRTf6lX538+5uO1ZQRhYibiZQ==", + "license": "MIT", + "dependencies": { + "get-stream": "^9.0.1", + "strtok3": "^9.0.1", + "token-types": "^6.0.0", + "uint8array-extras": "^1.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/imagemin/node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imagemin/node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imagemin/node_modules/peek-readable": { + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-5.4.2.tgz", + "integrity": "sha512-peBp3qZyuS6cNIJ2akRNG1uo1WJ1d0wTxg/fxMdZ0BqCVhx242bSFHM9eNqflfJVS9SsgkzgT/1UgnsurBOTMg==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/imagemin/node_modules/strtok3": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-9.1.1.tgz", + "integrity": "sha512-FhwotcEqjr241ZbjFzjlIYg6c5/L/s4yBGWSMvJ9UoExiSqL+FnFA/CaeZx17WGaZMS/4SOZp8wH18jSS4R4lw==", + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "peek-readable": "^5.3.1" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/imagemin/node_modules/token-types": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", + "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", + "license": "MIT", + "dependencies": { + "@borewit/text-codec": "^0.2.1", + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-lazy": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-3.1.0.tgz", + "integrity": "sha512-8/gvXvX2JMn0F+CDlSC4l6kOmVaLOO3XLkksI7CI3Ud95KDYJuYur2b9P/PUt/i/pDAMd/DulQsNbbbmRRsDIQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/imports-loader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/imports-loader/-/imports-loader-5.0.0.tgz", + "integrity": "sha512-tXgL8xxZFjOjQLLiE7my00UUQfktg4G8fdpXcZphL0bJWbk9eCxKKFaCwmFRcwyRJQl95GXBL1DoE1rCS/tcPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "source-map-js": "^1.0.2", + "strip-comments": "^2.0.1" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "devOptional": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/interpret": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", + "integrity": "sha512-CLM8SNMDu7C5psFCn6Wg/tgpj/bKAg7hc2gWqcuR9OD5Ft9PhBpIu8PLicPeis+xDd6YX2ncI8MCA64I9tftIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/into-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-3.1.0.tgz", + "integrity": "sha512-TcdjPibTksa1NQximqep2r17ISRiNE9fwlfbg3F8ANdvP5/yrFTew86VcO//jk4QTaMlbjypPBq76HN2zaKfZQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "from2": "^2.1.1", + "p-is-promise": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ip-regex": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", + "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ipaddr.js": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/irregular-plurals": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-3.5.0.tgz", + "integrity": "sha512-1ANGLZ+Nkv1ptFb2pa8oG8Lem4krflKuX/gINiHJHjJUKaJHk/SXk5x6K3J+39/p0h1RQ2saROclJJ+QLvETCQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "license": "MIT", + "dependencies": { + "is-relative": "^1.0.0", + "is-windows": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-arguments": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.1.tgz", + "integrity": "sha512-l9qO6eFlUETHtuihLcYOaLKByJ1f+N4kthcU9YjHy3N+B3hWv0y/2Nd0mu/7lTFnRQHTrSdXF50HQ3bl5fEnng==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-electron": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.2.tgz", + "integrity": "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==", + "license": "MIT" + }, + "node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-gif": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-gif/-/is-gif-3.0.0.tgz", + "integrity": "sha512-IqJ/jlbw5WJSNfwQ/lHEDXF8rxhRgF6ythk2oiEvhpG29F704eX9NO6TvPfMiq9DrbwgcEDnETYNcZDPewQoVw==", + "license": "MIT", + "optional": true, + "dependencies": { + "file-type": "^10.4.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-gif/node_modules/file-type": { + "version": "10.11.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-10.11.0.tgz", + "integrity": "sha512-uzk64HRpUZyTGZtVuvrjP0FYxzQrBf4rojot6J65YMEbwBLB0CWm0CLojVpwpmFmxcE/lkvYICgfcGozbBq6rw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-identifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-identifier/-/is-identifier-1.0.1.tgz", + "integrity": "sha512-HQ5v4rEJ7REUV54bCd2l5FaD299SGDEn2UPoVXaTHAyGviLq2menVUD2udi3trQ32uvB6LdAh/0ck2EuizrtpA==", + "license": "MIT", + "dependencies": { + "identifier-regex": "^1.0.0", + "super-regex": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container/node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-jpg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-jpg/-/is-jpg-3.0.0.tgz", + "integrity": "sha512-Vcd67KWHZblEKEBrtP25qLZ8wN9ICoAhl1pKUqD7SM7hf2qtuRl7loDgP5Zigh2oN/+7uj+KVyC0eRJvgOEFeQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-nan": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", + "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-natural-number": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz", + "integrity": "sha512-Y4LTamMe0DDQIIAlaer9eKebAlDSV6huy+TWhJVPlzZh2o4tRP5SQWFlLn5N0To4mDD22/qdOq+veo1cSISLgQ==", + "license": "MIT", + "optional": true + }, + "node_modules/is-negated-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", + "integrity": "sha512-czXVVn/QEmgvej1f50BZ648vUI+em0xqMq2Sn+QncCLN4zj1UAxlT+kw/6ggQTOaZPd1HqKQGEqbpQVtJucWug==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-network-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.1.0.tgz", + "integrity": "sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-like": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/is-number-like/-/is-number-like-1.0.8.tgz", + "integrity": "sha512-6rZi3ezCyFcn5L71ywzz2bS5b2Igl1En3eTlZlvKjpz1n3IZLAYMbKYAIQgFmEu0GENg92ziU/faEOA/aixjbA==", + "dev": true, + "license": "ISC", + "dependencies": { + "lodash.isfinite": "^3.3.2" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.2.tgz", + "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", + "license": "MIT", + "optional": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-png": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-png/-/is-png-2.0.0.tgz", + "integrity": "sha512-4KPGizaVGj2LK7xwJIz8o5B2ubu1D/vcQsgOGFEDlpcvgZHto4gBnyd0ig7Ws+67ixmwKoNmu0hYnpo6AaKb5g==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-relative": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "license": "MIT", + "dependencies": { + "is-unc-path": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-retry-allowed": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", + "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-svg": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-4.4.0.tgz", + "integrity": "sha512-v+AgVwiK5DsGtT9ng+m4mClp6zDAmwrW8nZi6Gg15qzvBnRWWdfWA1TGaXyCDnWq5g5asofIgMVl3PjKxvk1ug==", + "license": "MIT", + "optional": true, + "dependencies": { + "fast-xml-parser": "^4.1.3" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-unc-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "license": "MIT", + "dependencies": { + "unc-path-regex": "^0.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", + "license": "MIT" + }, + "node_modules/is-valid-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", + "integrity": "sha512-AhiROmoEFDSsjx8hW+5sGwgKVIORcXnrlAx/R0ZSeaPw70Vw0CqkGBBhHGL58Uox2eXnU1AnvXJl1XlyedO5bA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is2": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/is2/-/is2-2.0.9.tgz", + "integrity": "sha512-rZkHeBn9Zzq52sd9IUIV3a5mfwBY+o2HePMh0wkGBM4z4qjvy2GwVxQ6nNXSfw6MmVP6gf1QIlWjiOavhM3x5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "ip-regex": "^4.1.0", + "is-url": "^1.2.4" + }, + "engines": { + "node": ">=v0.10.0" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "dev": true, + "license": "MIT" + }, + "node_modules/isurl": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz", + "integrity": "sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==", + "license": "MIT", + "optional": true, + "dependencies": { + "has-to-string-tag-x": "^1.2.0", + "is-object": "^1.0.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jimp": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/jimp/-/jimp-1.6.0.tgz", + "integrity": "sha512-YcwCHw1kiqEeI5xRpDlPPBGL2EOpBKLwO4yIBJcXWHPj5PnA5urGq0jbyhM5KoNpypQ6VboSoxc9D8HyfvngSg==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/diff": "1.6.0", + "@jimp/js-bmp": "1.6.0", + "@jimp/js-gif": "1.6.0", + "@jimp/js-jpeg": "1.6.0", + "@jimp/js-png": "1.6.0", + "@jimp/js-tiff": "1.6.0", + "@jimp/plugin-blit": "1.6.0", + "@jimp/plugin-blur": "1.6.0", + "@jimp/plugin-circle": "1.6.0", + "@jimp/plugin-color": "1.6.0", + "@jimp/plugin-contain": "1.6.0", + "@jimp/plugin-cover": "1.6.0", + "@jimp/plugin-crop": "1.6.0", + "@jimp/plugin-displace": "1.6.0", + "@jimp/plugin-dither": "1.6.0", + "@jimp/plugin-fisheye": "1.6.0", + "@jimp/plugin-flip": "1.6.0", + "@jimp/plugin-hash": "1.6.0", + "@jimp/plugin-mask": "1.6.0", + "@jimp/plugin-print": "1.6.0", + "@jimp/plugin-quantize": "1.6.0", + "@jimp/plugin-resize": "1.6.0", + "@jimp/plugin-rotate": "1.6.0", + "@jimp/plugin-threshold": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/jpeg-js": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz", + "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==", + "license": "BSD-3-Clause" + }, + "node_modules/jq-web": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/jq-web/-/jq-web-0.6.2.tgz", + "integrity": "sha512-+7XvjBYwTx4vP5PYkf6Q6orubO/v+UgMU6By1GritrmShr9QpT3UKa4ANzXWQfhdqtBnQYXsm7ZNbdIHT6tYpQ==", + "license": "ISC" + }, + "node_modules/jquery": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", + "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==", + "license": "MIT" + }, + "node_modules/js-sha3": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.9.3.tgz", + "integrity": "sha512-BcJPCQeLg6WjEx3FE591wVAevlli8lxsxm9/FzV4HXkV49TmBH38Yvrpce6fjbADGMKFrBMGTqrVz3qPIZ88Gg==", + "license": "MIT" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsdoc-type-pratt-parser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.0.0.tgz", + "integrity": "sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/jsdom": { + "version": "24.1.3", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-24.1.3.tgz", + "integrity": "sha512-MyL55p3Ut3cXbeBEG7Hcv0mVM8pp8PBNWxRqchZnSfAiES1v1mRnMeFfaHWIPULpwsYfvO+ZmMZz5tGCnjzDUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssstyle": "^4.0.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.4.3", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.12", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.7.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.4", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^2.11.2" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsep": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jsep/-/jsep-1.4.0.tgz", + "integrity": "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==", + "license": "MIT", + "engines": { + "node": ">= 10.16.0" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonata": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/jsonata/-/jsonata-2.1.0.tgz", + "integrity": "sha512-OCzaRMK8HobtX8fp37uIVmL8CY1IGc/a6gLsDqz3quExFR09/U78HUzWYr7T31UEB6+Eu0/8dkVD5fFDOl9a8w==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/jsonpath-plus": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-10.4.0.tgz", + "integrity": "sha512-T92WWatJXmhBbKsgH/0hl+jxjdXrifi5IKeMY02DWggRxX0UElcbVzPlmgLTbvsPeW1PasQ6xE2Q75stkhGbsA==", + "license": "MIT", + "dependencies": { + "@jsep-plugin/assignment": "^1.3.0", + "@jsep-plugin/regex": "^1.0.4", + "jsep": "^1.4.0" + }, + "bin": { + "jsonpath": "bin/jsonpath-cli.js", + "jsonpath-plus": "bin/jsonpath-cli.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=4", + "npm": ">=1.4.28" + } + }, + "node_modules/jsonwebtoken/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/jsqr": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jsqr/-/jsqr-1.4.0.tgz", + "integrity": "sha512-dxLob7q65Xg2DvstYkRpkYtmKm2sPJ9oFhrhmudT1dZvNFFTlroai3AWSpLey/w5vMcLBXRgOJsbXpdN9HzU/A==", + "license": "Apache-2.0" + }, + "node_modules/jsrsasign": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/jsrsasign/-/jsrsasign-11.1.1.tgz", + "integrity": "sha512-6w95OOXH8DNeGxakqLndBEqqwQ6A70zGaky1oxfg8WVLWOnghTfJsc5Tknx+Z88MHSb1bGLcqQHImOF8Lk22XA==", + "license": "MIT", + "funding": { + "url": "https://github.com/kjur/jsrsasign#donations" + } + }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "dev": true, + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/junk": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/junk/-/junk-4.0.1.tgz", + "integrity": "sha512-Qush0uP+G8ZScpGMZvHUiRfI0YBWuB3gVBYlI0v0vvOJt5FLicco+IkP0a50LqTTQhmts/m6tP5SWE+USyIvcQ==", + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jwa": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.3.tgz", + "integrity": "sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.2", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/kbpgp": { + "version": "2.1.17", + "resolved": "https://registry.npmjs.org/kbpgp/-/kbpgp-2.1.17.tgz", + "integrity": "sha512-pnjH7amyg6dZLXyF42BKbCTST0l0r1ErunqtFRrJCkHkGJb83cZZmx1pnqNFr+d/ls+5gvcHrZLPfUG5q7oRYw==", + "license": "BSD-3-Clause", + "dependencies": { + "bn": "^1.0.5", + "bzip-deflate": "^1.0.0", + "deep-equal": "^1.1.0", + "iced-error": "0.0.13", + "iced-lock": "^1.0.2", + "iced-runtime": "^1.0.4", + "keybase-ecurve": "^1.0.1", + "keybase-nacl": "^1.1.2", + "minimist": "^1.2.0", + "pgp-utils": "0.0.35", + "purepack": "^1.0.5", + "triplesec": "^4.0.3", + "tweetnacl": "^0.13.1" + } + }, + "node_modules/keybase-ecurve": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/keybase-ecurve/-/keybase-ecurve-1.0.1.tgz", + "integrity": "sha512-2GlVxDsNF+52LtYjgFsjoKuN7MQQgiVeR4HRdJxLuN8fm4mf4stGKPUjDJjky15c/98UsZseLjp7Ih5X0Sy1jQ==", + "dependencies": { + "bn": "^1.0.4" + } + }, + "node_modules/keybase-nacl": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/keybase-nacl/-/keybase-nacl-1.1.4.tgz", + "integrity": "sha512-7TFyWLq42CQs7JES9arR+Vnv/eMk5D6JT1Y8samrEA5ff3FOmaiRcXIVrwJQd3KJduxmSjgAjdkXlQK7Q437xQ==", + "license": "BSD-3-Clause", + "dependencies": { + "iced-runtime": "^1.0.2", + "tweetnacl": "^0.13.1", + "uint64be": "^1.0.1" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kuler": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-0.0.0.tgz", + "integrity": "sha512-5h7OEDPSHedoxB6alJXF4FtFB95QA2OTXGCFaLCutHdkh0VrcSSy/OwH9UHtYqsG2KTrdN7gVEc9KgCBNah/yA==", + "license": "MIT", + "dependencies": { + "colornames": "0.0.2" + } + }, + "node_modules/last-run": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/last-run/-/last-run-2.0.0.tgz", + "integrity": "sha512-j+y6WhTLN4Itnf9j5ZQos1BGPCS8DAwmgMroR3OzfxAsBxam0hMw7J8M3KqZl0pLQJ1jNnwIexg5DYpC/ctwEQ==", + "license": "MIT", + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/launch-editor": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.9.1.tgz", + "integrity": "sha512-Gcnl4Bd+hRO9P9icCP/RVVT2o8SFlPXofuCxvA2SaZuH45whSvf5p8x5oih5ftLiVhEI4sp5xDY+R+b3zJBh5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.0.0", + "shell-quote": "^1.8.1" + } + }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lead": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/lead/-/lead-4.0.0.tgz", + "integrity": "sha512-DpMa59o5uGUWWjruMp71e6knmwKU3jRBBn1kjuLWN9EeIOxNeSAwvHf03WIl8g/ZMR2oSQC9ej3yeLBwdDc/pg==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/libbzip2-wasm": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/libbzip2-wasm/-/libbzip2-wasm-0.0.4.tgz", + "integrity": "sha512-RqscTx95+RTKhFAyjedsboR0Lmo3zd8//EuRwQXkdWmsCwYlzarVRaiYg6kS1O8m10MCQkGdrnlK9L4eAmZUwA==", + "license": "ISC" + }, + "node_modules/libyara-wasm": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/libyara-wasm/-/libyara-wasm-1.2.1.tgz", + "integrity": "sha512-PNqUNWnwjZLe55iA8Rv6vLQRjSdO2OnVg24aRE8v+ytR8CRB8agIG6pS9h2VQejuJP1A/uR4pwcBggUxoNC7DA==", + "license": "ISC" + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/liftoff": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-5.0.1.tgz", + "integrity": "sha512-wwLXMbuxSF8gMvubFcFRp56lkFV69twvbU5vDPbaw+Q+/rF8j0HKjGbIdlSi+LuJm9jf7k9PB+nTxnsLMPcv2Q==", + "license": "MIT", + "dependencies": { + "extend": "^3.0.2", + "findup-sync": "^5.0.0", + "fined": "^2.0.0", + "flagged-respawn": "^2.0.0", + "is-plain-object": "^5.0.0", + "rechoir": "^0.8.0", + "resolve": "^1.20.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/liftoff/node_modules/fined": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fined/-/fined-2.0.0.tgz", + "integrity": "sha512-OFRzsL6ZMHz5s0JrsEr+TpdGNCtrVtnuG3x1yzGNiQHT0yaDnXAj8V/lWcpJVrnoDpcwXcASxAZYbuXda2Y82A==", + "license": "MIT", + "dependencies": { + "expand-tilde": "^2.0.2", + "is-plain-object": "^5.0.0", + "object.defaults": "^1.1.0", + "object.pick": "^1.3.0", + "parse-filepath": "^1.0.2" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/liftoff/node_modules/flagged-respawn": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-2.0.0.tgz", + "integrity": "sha512-Gq/a6YCi8zexmGHMuJwahTGzXlAZAOsbCVKduWXC6TlLCjjFRlExMJc4GC2NYPYZ0r/brw9P7CpRgQmlPVeOoA==", + "license": "MIT", + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/liftoff/node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/liftoff/node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "license": "MIT", + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/liftup": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/liftup/-/liftup-3.0.1.tgz", + "integrity": "sha512-yRHaiQDizWSzoXk3APcA71eOI/UuhEkNN9DiW2Tt44mhYzX4joFoCZlxsSOF7RyeLlfqzFLQI1ngFq3ggMPhOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "extend": "^3.0.2", + "findup-sync": "^4.0.0", + "fined": "^1.2.0", + "flagged-respawn": "^1.0.1", + "is-plain-object": "^2.0.4", + "object.map": "^1.0.1", + "rechoir": "^0.7.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/liftup/node_modules/findup-sync": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-4.0.0.tgz", + "integrity": "sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.0", + "micromatch": "^4.0.2", + "resolve-dir": "^1.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/livereload-js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-2.4.0.tgz", + "integrity": "sha512-XPQH8Z2GDP/Hwz2PCDrh2mth4yFejwA1OZ/81Ti3LgKyhDcEjsSsqFWZojHG0va/duGd+WyosY7eXLDoOyqcPw==", + "dev": true, + "license": "MIT" + }, + "node_modules/loader-runner": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/loader-utils-webpack-v4": { + "name": "loader-utils", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "license": "MIT" + }, + "node_modules/lodash._basecopy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha512-rFR6Vpm4HeCK1WPGvjZSJ+7yik8d8PVUdCJx5rT2pogG4Ve/2ZS7kfmO5l5T2o5V2mqlNIfSF5MZlr1+xOoYQQ==", + "license": "MIT" + }, + "node_modules/lodash._basetostring": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz", + "integrity": "sha512-mTzAr1aNAv/i7W43vOR/uD/aJ4ngbtsRaCubp2BfZhlGU/eORUjg/7F6X0orNMdv33JOrdgGybtvMN/po3EWrA==", + "license": "MIT" + }, + "node_modules/lodash._basevalues": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz", + "integrity": "sha512-H94wl5P13uEqlCg7OcNNhMQ8KvWSIyqXzOPusRgHC9DK3o54P6P3xtbXlVbRABG4q5gSmp7EDdJ0MSuW9HX6Mg==", + "license": "MIT" + }, + "node_modules/lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha512-RrL9VxMEPyDMHOd9uFbvMe8X55X16/cGM5IgOKgRElQZutpX89iS6vwl64duTV1/16w5JY7tuFNXqoekmh1EmA==", + "license": "MIT" + }, + "node_modules/lodash._isiterateecall": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha512-De+ZbrMu6eThFti/CSzhRvTKMgQToLxbij58LMfM8JnYDNSOjkjTCIaa8ixglOeGh2nyPlakbt5bJWJ7gvpYlQ==", + "license": "MIT" + }, + "node_modules/lodash._reescape": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reescape/-/lodash._reescape-3.0.0.tgz", + "integrity": "sha512-Sjlavm5y+FUVIF3vF3B75GyXrzsfYV8Dlv3L4mEpuB9leg8N6yf/7rU06iLPx9fY0Mv3khVp9p7Dx0mGV6V5OQ==", + "license": "MIT" + }, + "node_modules/lodash._reevaluate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz", + "integrity": "sha512-OrPwdDc65iJiBeUe5n/LIjd7Viy99bKwDdk7Z5ljfZg0uFRFlfQaCy9tZ4YMAag9WAZmlVpe1iZrkIMMSMHD3w==", + "license": "MIT" + }, + "node_modules/lodash._reinterpolate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha512-xYHt68QRoYGjeeM/XOE1uJtvXQAgvszfBhjV4yvsQH0u2i9I6cI6c6/eG4Hh3UAOVn0y/xAXwmTzEay49Q//HA==", + "license": "MIT" + }, + "node_modules/lodash._root": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", + "integrity": "sha512-O0pWuFSK6x4EXhM1dhZ8gchNtG7JMqBtrHdoUFUWXD7dJnNSUze1GuyQr5sOs0aCvgGeI3o/OJW8f4ca7FDxmQ==", + "license": "MIT" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.escape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", + "integrity": "sha512-n1PZMXgaaDWZDSvuNZ/8XOcYO2hOKDqZel5adtR30VKQAtoWs/5AOeFA0vPV8moiPzlqe7F4cP2tzpFewQyelQ==", + "license": "MIT", + "dependencies": { + "lodash._root": "^3.0.0" + } + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==", + "license": "MIT" + }, + "node_modules/lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha512-JwObCrNJuT0Nnbuecmqr5DgtuBppuCvGD9lxjFpAzwnVtdGoDQ1zig+5W8k5/6Gcn0gZ3936HDAlGd28i7sOGQ==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isfinite": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/lodash.isfinite/-/lodash.isfinite-3.3.2.tgz", + "integrity": "sha512-7FGG40uhC8Mm633uKW1r58aElFlBlxCrg9JfSi3P6aYiWmfiWF0PgMd86ZUsxE5GwWPdHoS2+48bwTh2VPkIQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha512-CuBsapFjcubOGMn3VD+24HOAPxM79tH+V6ivJL3CHYjtrawauDJHUk//Yew9Hvc6e9rbCrURGk8z6PC+8WJBfQ==", + "license": "MIT", + "dependencies": { + "lodash._getnative": "^3.0.0", + "lodash.isarguments": "^3.0.0", + "lodash.isarray": "^3.0.0" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/lodash.restparam": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", + "integrity": "sha512-L4/arjjuq4noiUJpt3yS6KIKDtJwNe2fIYgMqyYYKoeIfV1iEqvPwhCx23o+R9dzouGihDAPN1dTIRWa7zk8tw==", + "license": "MIT" + }, + "node_modules/lodash.template": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", + "integrity": "sha512-0B4Y53I0OgHUJkt+7RmlDFWKjVAI/YUpWNiL9GQz5ORDr4ttgfQGo+phBWKFLJbBdtOwgMuUkdOHOnPg45jKmQ==", + "deprecated": "This package is deprecated. Use https://socket.dev/npm/package/eta instead.", + "license": "MIT", + "dependencies": { + "lodash._basecopy": "^3.0.0", + "lodash._basetostring": "^3.0.0", + "lodash._basevalues": "^3.0.0", + "lodash._isiterateecall": "^3.0.0", + "lodash._reinterpolate": "^3.0.0", + "lodash.escape": "^3.0.0", + "lodash.keys": "^3.0.0", + "lodash.restparam": "^3.0.0", + "lodash.templatesettings": "^3.0.0" + } + }, + "node_modules/lodash.templatesettings": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz", + "integrity": "sha512-TcrlEr31tDYnWkHFWDCV3dHYroKEXpJZ2YJYvJdhN+y4AkWMDZ5I4I8XDtUKqSAyG81N7w+I1mFEJtcED+tGqQ==", + "license": "MIT", + "dependencies": { + "lodash._reinterpolate": "^3.0.0", + "lodash.escape": "^3.0.0" + } + }, + "node_modules/lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/loglevel": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz", + "integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + }, + "funding": { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/loglevel" + } + }, + "node_modules/loglevel-message-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/loglevel-message-prefix/-/loglevel-message-prefix-3.0.0.tgz", + "integrity": "sha512-/cBEOqsuU0vJsFm4n92R7h6mkiKqt8vh+JOmW722DTZVVD7egEpVOx66re3vWxO7pii3B4eQuqm2qfqq5cAs0w==", + "deprecated": "Use @natlibfi/loglevel-message-prefix instead", + "license": "MIT", + "dependencies": { + "es6-polyfills": "^2.0.0", + "loglevel": "^1.4.0" + } + }, + "node_modules/long": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.4.tgz", + "integrity": "sha512-qtzLbJE8hq7VabR3mISmVGtoXP8KGc2Z/AT8OuqlYD7JTR3oqrgwdjnk07wpj1twXxYmgDXgoKVWUG/fReSzHg==", + "license": "Apache-2.0" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lru-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", + "integrity": "sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==", + "license": "MIT", + "dependencies": { + "es5-ext": "~0.10.2" + } + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "license": "MIT", + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/lz4js": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/lz4js/-/lz4js-0.2.0.tgz", + "integrity": "sha512-gY2Ia9Lm7Ep8qMiuGRhvUq0Q7qUereeldZPP1PMEJxPtEWHJLqw9pgX68oHajBH0nzJK4MaZEA/YNV3jT8u8Bg==", + "license": "ISC" + }, + "node_modules/make-asynchronous": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/make-asynchronous/-/make-asynchronous-1.1.0.tgz", + "integrity": "sha512-ayF7iT+44LXdxJLTrTd3TLQpFDDvPCBxXxbv+pMUSuHA5Q8zyAfwkRP6aHHwNVFBUFWtxAHqwNJxF8vMZLAbVg==", + "license": "MIT", + "dependencies": { + "p-event": "^6.0.0", + "type-fest": "^4.6.0", + "web-worker": "^1.5.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-asynchronous/node_modules/p-event": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-6.0.1.tgz", + "integrity": "sha512-Q6Bekk5wpzW5qIyUP4gdMEujObYstZl6DMMOSenwBvV0BlE5LkDwkjs5yHbZmdCEq2o4RJx4tE1vwxFVf2FG1w==", + "license": "MIT", + "dependencies": { + "p-timeout": "^6.1.2" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-asynchronous/node_modules/p-timeout": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.4.tgz", + "integrity": "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-asynchronous/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/make-dir/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "license": "ISC" + }, + "node_modules/make-error-cause": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/make-error-cause/-/make-error-cause-1.2.2.tgz", + "integrity": "sha512-4TO2Y3HkBnis4c0dxhAgD/jprySYLACf7nwN6V0HAHDx59g12WlRpUmFy1bRHamjGUEEBrEvCq6SUpsEE2lhUg==", + "license": "Apache-2.0", + "dependencies": { + "make-error": "^1.2.0" + } + }, + "node_modules/make-iterator": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", + "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/markdown-it": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz", + "integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "license": "MIT", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "license": "CC0-1.0", + "optional": true + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "license": "MIT" + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memfs": { + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.17.0.tgz", + "integrity": "sha512-4eirfZ7thblFmqFjywlTmuWVSvccHAJbn1r8qQLzmTO11qcqpohOjmY2mFce6x7x7WtskzRqApPD0hv+Oa74jg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/json-pack": "^1.0.3", + "@jsonjoy.com/util": "^1.3.0", + "tree-dump": "^1.0.1", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">= 4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + } + }, + "node_modules/memoizee": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.3.10.tgz", + "integrity": "sha512-LLzVUuWwGBKK188spgOK/ukrp5zvd9JGsiLDH41pH9vt5jvhZfsu5pxDuAnYAMG8YEGce72KO07sSBy9KkvOfw==", + "license": "MIT", + "dependencies": { + "d": "~0.1.1", + "es5-ext": "~0.10.11", + "es6-weak-map": "~0.1.4", + "event-emitter": "~0.3.4", + "lru-queue": "0.1", + "next-tick": "~0.2.2", + "timers-ext": "0.1" + } + }, + "node_modules/memoizee/node_modules/next-tick": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-0.2.2.tgz", + "integrity": "sha512-f7h4svPtl+QidoBv4taKXUjJ70G2asaZ8G28nS0OkqaalX8dwwrtWtyxEDPK62AC00ur/+/E0pUwBwY5EPn15Q==", + "license": "MIT" + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "bin": { + "miller-rabin": "bin/miller-rabin" + } + }, + "node_modules/miller-rabin/node_modules/bn.js": { + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", + "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", + "license": "MIT" + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/mini-css-extract-plugin": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.10.0.tgz", + "integrity": "sha512-540P2c5dYnJlyJxTaSloliZexv8rji6rY8FhQN+WF/82iHQfA23j/xtJx97L+mXOML27EqksSek/g4eK7jaL3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "schema-utils": "^4.0.0", + "tapable": "^2.2.1" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "license": "ISC" + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "license": "MIT" + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minimize": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/minimize/-/minimize-1.8.1.tgz", + "integrity": "sha512-vMXaO5/HgxKi06udiQ4MibwOb0mwxzcpX4DDf6G9k2h6fKNoY1xt8Wrzp82qBm4oZ5aQRP2ry1NzbwEuWBx9Ig==", + "license": "MIT", + "dependencies": { + "argh": "~0.1.4", + "async": "~1.5.2", + "cli-color": "~1.1.0", + "diagnostics": "~1.0.1", + "emits": "~3.0.0", + "htmlparser2": "~3.9.0", + "node-uuid": "~1.4.7" + }, + "bin": { + "minimize": "bin/minimize" + } + }, + "node_modules/minimize/node_modules/async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==", + "license": "MIT" + }, + "node_modules/minimize/node_modules/dom-serializer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + } + }, + "node_modules/minimize/node_modules/dom-serializer/node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/minimize/node_modules/dom-serializer/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/minimize/node_modules/domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "license": "BSD-2-Clause" + }, + "node_modules/minimize/node_modules/domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "1" + } + }, + "node_modules/minimize/node_modules/domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "node_modules/minimize/node_modules/entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "license": "BSD-2-Clause" + }, + "node_modules/minimize/node_modules/htmlparser2": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.9.2.tgz", + "integrity": "sha512-RSOwLNCnCLDRB9XpSfCzsLzzX8COezhJ3D4kRBNWh0NC/facp1hAMmM8zD7kC01My8vD6lGEbPMlbRW/EwGK5w==", + "license": "MIT", + "dependencies": { + "domelementtype": "^1.3.0", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^2.0.2" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mocha": { + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", + "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.3", + "browser-stdout": "^1.3.1", + "chokidar": "^3.5.3", + "debug": "^4.3.5", + "diff": "^5.2.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^8.1.0", + "he": "^1.2.0", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^5.1.6", + "ms": "^2.1.3", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^6.5.1", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9", + "yargs-unparser": "^2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/mocha/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/mocha/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/modify-source-webpack-plugin": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/modify-source-webpack-plugin/-/modify-source-webpack-plugin-4.1.0.tgz", + "integrity": "sha512-UaLQyFXoPWpWxkNUBFo9BotC20CCAGe7HEX9iKtB0P0MgNXgURf9TXUgNGuY5iVV5lDDQtcMjT2vneQWnNmwEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "loader-utils-webpack-v4": "npm:loader-utils@^2.0.4", + "schema-utils": "^4.0.0" + }, + "peerDependencies": { + "webpack": "^4.37.0 || ^5.0.0" + } + }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/moment-timezone": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.6.0.tgz", + "integrity": "sha512-ldA5lRNm3iJCWZcBCab4pnNL3HSZYXVb/3TYr75/1WCTWYuTqYUb5f/S384pncYjJ88lbO8Z4uPDvmoluHJc8Q==", + "license": "MIT", + "dependencies": { + "moment": "^2.29.4" + }, + "engines": { + "node": "*" + } + }, + "node_modules/more-entropy": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/more-entropy/-/more-entropy-0.0.7.tgz", + "integrity": "sha512-e0TxQtU1F6/ZA8WnEA2JLQwwDqBTtZFLJSW7rWgUsQou35wx1IOL0g2O7q7oGoMgIJto+jHMnNGHLfSiylHRrw==", + "dependencies": { + "iced-runtime": ">=0.0.1" + } + }, + "node_modules/morgan": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz", + "integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.1.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/morgan/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/morgan/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/mozjpeg": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/mozjpeg/-/mozjpeg-8.0.0.tgz", + "integrity": "sha512-Ca2Yhah9hG0Iutgsn8MOrAl37P9ThnKsJatjXoWdUO+8X8GeG/6ahvHZrTyqvbs6leMww1SauWUCao/L9qBuFQ==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "bin-build": "^3.0.0", + "bin-wrapper": "^4.0.0" + }, + "bin": { + "mozjpeg": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/mrmime": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", + "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "dev": true, + "license": "MIT", + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/multipipe": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz", + "integrity": "sha512-7ZxrUybYv9NonoXgwoOqtStIu18D1c3eFZj27hqgf5kBrBF8Q+tE8V0MW8dKM5QLkQPh1JhhbKgHLY9kifov4Q==", + "license": "MIT", + "dependencies": { + "duplexer2": "0.0.2" + } + }, + "node_modules/mute-stdout": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-2.0.0.tgz", + "integrity": "sha512-32GSKM3Wyc8dg/p39lWPKYu8zci9mJFzV1Np9Of0ZEpe6Fhssn/FbI7ywAMd40uX+p3ZKh3T5EeCFv81qS3HmQ==", + "license": "MIT", + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true, + "license": "ISC" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "license": "MIT" + }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", + "license": "ISC" + }, + "node_modules/ngeohash": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/ngeohash/-/ngeohash-0.6.3.tgz", + "integrity": "sha512-kltF0cOxgx1AbmVzKxYZaoB0aj7mOxZeHaerEtQV0YaqnkXNq26WWqMmJ6lTqShYxVRWZ/mwvvTrNeOwdslWiw==", + "license": "MIT", + "engines": { + "node": ">=v0.2.0" + } + }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "license": "MIT", + "optional": true + }, + "node_modules/nightwatch": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/nightwatch/-/nightwatch-3.15.0.tgz", + "integrity": "sha512-Vvh7TsDyEN1YzOsDNoafEUPJDQ6jfnmJPAsWo/EmygljZiRk1Ja/pEqNAhE5UdYJzF38SNO46gJS8IRk9mUNfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nightwatch/chai": "5.0.3", + "@nightwatch/html-reporter-template": "^0.3.0", + "@nightwatch/nightwatch-inspector": "^1.0.1", + "@types/chai": "^4.3.5", + "@types/selenium-webdriver": "^4.1.14", + "ansi-to-html": "0.7.2", + "aria-query": "5.1.3", + "assertion-error": "1.1.0", + "boxen": "5.1.2", + "chai-nightwatch": "^0.5.3", + "chalk": "^4.1.2", + "ci-info": "3.3.0", + "cli-table3": "^0.6.3", + "devtools-protocol": "^0.0.1140464", + "didyoumean": "^1.2.2", + "dotenv": "16.3.1", + "ejs": "^3.1.10", + "envinfo": "7.11.0", + "glob": "7.2.3", + "jsdom": "^24.1.0", + "lodash": "^4.17.21", + "minimatch": "3.1.2", + "minimist": "1.2.6", + "mocha": "10.8.2", + "nightwatch-axe-verbose": "^2.3.0", + "open": "8.4.2", + "ora": "5.4.1", + "piscina": "^4.3.1", + "selenium-webdriver": "4.27.0", + "semver": "7.5.4", + "stacktrace-parser": "0.1.10", + "strip-ansi": "6.0.1", + "untildify": "4.0.0", + "uuid": "8.3.2" + }, + "bin": { + "nightwatch": "bin/nightwatch" + }, + "engines": { + "node": ">= 16" + }, + "peerDependencies": { + "@cucumber/cucumber": "*" + }, + "peerDependenciesMeta": { + "@cucumber/cucumber": { + "optional": true + }, + "chromedriver": { + "optional": true + }, + "geckodriver": { + "optional": true + } + } + }, + "node_modules/nightwatch-axe-verbose": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/nightwatch-axe-verbose/-/nightwatch-axe-verbose-2.3.1.tgz", + "integrity": "sha512-C6N95bwPHsRnv04eVIwJ6w5m6X1+Pddvo6nzpzOHQlO0j+pYRVU7zaQmFUJ0L4cqeUxReNEXyTUg/R9WWfHk7w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "axe-core": "^4.9.1" + } + }, + "node_modules/nightwatch/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/nightwatch/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/nightwatch/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/nightwatch/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/nightwatch/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/nightwatch/node_modules/minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/nightwatch/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/nightwatch/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nightwatch/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nightwatch/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/nightwatch/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/node-forge": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.3.tgz", + "integrity": "sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==", + "license": "(BSD-3-Clause OR GPL-2.0)", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-md6": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/node-md6/-/node-md6-0.1.0.tgz", + "integrity": "sha512-n+s6dhxfV2JCRFgNGzdX0SxxL/SyUwR68uGR/rlNVbXhrdti4M1xKHO+PzXtYLUo1DEsWFZe29B3Z9y5JgpfvA==", + "license": "CC0-1.0" + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-uuid": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", + "integrity": "sha512-TkCET/3rr9mUuRp+CpO7qfgT++aAxfDRaalQhwPFzI9BY/2rCDn6OfpZOVggi1AXfTPpfkTrg5f5WQx5G1uLxA==", + "deprecated": "Use uuid module instead", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/nodom": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/nodom/-/nodom-2.4.0.tgz", + "integrity": "sha512-qhfYgpoCSi37HLiViMlf94YqMQdvk3n3arI1uGbAWZK9NKCYRSI42W8lATeGloYGLYxb8us1C5rTvtsXjwdWQg==", + "license": "ISC" + }, + "node_modules/nofilter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/nofilter/-/nofilter-3.1.0.tgz", + "integrity": "sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==", + "license": "MIT", + "engines": { + "node": ">=12.19" + } + }, + "node_modules/nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-2.0.1.tgz", + "integrity": "sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw==", + "license": "MIT", + "optional": true, + "dependencies": { + "prepend-http": "^2.0.0", + "query-string": "^5.0.1", + "sort-keys": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/normalize-url/node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url/node_modules/prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/normalize-url/node_modules/sort-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz", + "integrity": "sha512-/dPCrG1s3ePpWm6yBbxZq5Be1dXGLyLn9Z791chDC3NFrpkVbWGzkBwPN1knaciexFXgRJ7hzdnwZ4stHSDmjg==", + "license": "MIT", + "optional": true, + "dependencies": { + "is-plain-obj": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/notepack.io": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/notepack.io/-/notepack.io-3.0.1.tgz", + "integrity": "sha512-TKC/8zH5pXIAMVQio2TvVDTtPRX+DJPHDqjRbxogtFiByHyzKmy96RA0JtCQJ+WouyyL4A10xomQzgbUT+1jCg==", + "license": "MIT" + }, + "node_modules/now-and-later": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-3.0.0.tgz", + "integrity": "sha512-pGO4pzSdaxhWTGkfSfHx3hVzJVslFPwBp2Myq9MYN/ChfJZF87ochMAXnvz6/58RJSf5ik2q9tXprBBrk2cpcg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/npm-conf": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/npm-conf/-/npm-conf-1.1.3.tgz", + "integrity": "sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw==", + "license": "MIT", + "optional": true, + "dependencies": { + "config-chain": "^1.1.11", + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-conf/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "devOptional": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/ntlm": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/ntlm/-/ntlm-0.1.3.tgz", + "integrity": "sha512-pPlHxhAegZP4QAaOYd51vRd6VXTGfF7VLKJwuwN0iEB1aIi3SnqXYuS/bH/6wWBOq+Ehdil49mHm1Nseon085w==", + "engines": [ + "node" + ] + }, + "node_modules/nwmatcher": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/nwmatcher/-/nwmatcher-1.4.4.tgz", + "integrity": "sha512-3iuY4N5dhgMpCUrOVnuAdGrgxVqV2cJpM+XNccjR2DKOB1RUP0aA+wGXEiNziG/UKboFyGBIoKOaNlJxx8bciQ==", + "license": "MIT" + }, + "node_modules/nwsapi": { + "version": "2.2.16", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.16.tgz", + "integrity": "sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.defaults": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", + "integrity": "sha512-c/K0mw/F11k4dEUBMW8naXUuBuhxRCfG7W+yFy8EcijU/rSmazOUd1XAEEe6bC0OuXY4HUKjTJv7xbxIMqdxrA==", + "license": "MIT", + "dependencies": { + "array-each": "^1.0.1", + "array-slice": "^1.0.0", + "for-own": "^1.0.0", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", + "integrity": "sha512-3+mAJu2PLfnSVGHwIWubpOFLscJANBKuB/6A4CxBstc4aqwQY0FWcsppuy4jU5GSB95yES5JHSI+33AWuS4k6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true, + "license": "MIT" + }, + "node_modules/omggif": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz", + "integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==", + "license": "MIT" + }, + "node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/opencollective-postinstall": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz", + "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==", + "license": "MIT", + "bin": { + "opencollective-postinstall": "index.js" + } + }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true, + "license": "(WTFPL OR MIT)", + "bin": { + "opener": "bin/opener-bin.js" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/optipng-bin": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/optipng-bin/-/optipng-bin-7.0.1.tgz", + "integrity": "sha512-W99mpdW7Nt2PpFiaO+74pkht7KEqkXkeRomdWXfEz3SALZ6hns81y/pm1dsGZ6ItUIfchiNIP6ORDr1zETU1jA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "bin-build": "^3.0.0", + "bin-wrapper": "^4.0.0" + }, + "bin": { + "optipng": "cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ora/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ora/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/os-filter-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/os-filter-obj/-/os-filter-obj-2.0.0.tgz", + "integrity": "sha512-uksVLsqG3pVdzzPvmAHpBK0wKxYItuzZr7SziusRPoz67tGV8rL1szZ6IdeUrbqLjGDwApBtN29eEE3IqGHOjg==", + "license": "MIT", + "optional": true, + "dependencies": { + "arch": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "deprecated": "This package is no longer supported.", + "dev": true, + "license": "ISC", + "dependencies": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "node_modules/otpauth": { + "version": "9.3.6", + "resolved": "https://registry.npmjs.org/otpauth/-/otpauth-9.3.6.tgz", + "integrity": "sha512-eIcCvuEvcAAPHxUKC9Q4uCe0Fh/yRc5jv9z+f/kvyIF2LPrhgAOuLB7J9CssGYhND/BL8M9hlHBTFmffpoQlMQ==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.6.1" + }, + "funding": { + "url": "https://github.com/hectorm/otpauth?sponsor=1" + } + }, + "node_modules/ow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ow/-/ow-2.0.0.tgz", + "integrity": "sha512-ESUigmGrdhUZ2nQSFNkeKSl6ZRPupXzprMs3yF9DYlNVpJ8XAjM/fI9RUZxA7PI1K9HQDCCvBo1jr/GEIo9joQ==", + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^6.3.0", + "callsites": "^4.1.0", + "dot-prop": "^8.0.2", + "environment": "^1.0.0", + "fast-equals": "^5.0.1", + "is-identifier": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ow/node_modules/callsites": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-4.2.0.tgz", + "integrity": "sha512-kfzR4zzQtAE9PC7CzZsjl3aBNbXWuXiSeOCdLcPpBfGW8YuCqQHcRPFDbr/BPVmd3EEPVpuFzLyuT/cUhPr4OQ==", + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-cancelable": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.3.0.tgz", + "integrity": "sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-event": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-1.3.0.tgz", + "integrity": "sha512-hV1zbA7gwqPVFcapfeATaNjQ3J0NuzorHPyG8GPL9g/Y/TplWVBVoCKCXL6Ej2zscrCEv195QNWJXuBH6XZuzA==", + "license": "MIT", + "optional": true, + "dependencies": { + "p-timeout": "^1.1.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-is-promise": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", + "integrity": "sha512-zL7VE4JVS2IFSkR2GQKDSPEVxkoH43/p7oEnwpdCndKYJO0HVeRB7fA8TJwuLOTBREtK0ea8eHaxdwcpob5dmg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map-series": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-map-series/-/p-map-series-1.0.0.tgz", + "integrity": "sha512-4k9LlvY6Bo/1FcIdV33wqZQES0Py+iKISU9Uc8p8AjWoZPnFKMpVIVD3s0EYn4jzLh1I+WeUZkJ0Yoa4Qfw3Kg==", + "license": "MIT", + "optional": true, + "dependencies": { + "p-reduce": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-pipe": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-pipe/-/p-pipe-4.0.0.tgz", + "integrity": "sha512-HkPfFklpZQPUKBFXzKFB6ihLriIHxnmuQdK9WmLDwe4hf2PdhhfWT/FJa+pc3bA1ywvKXtedxIRmd4Y7BTXE4w==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-reduce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-1.0.0.tgz", + "integrity": "sha512-3Tx1T3oM1xO/Y8Gj0sWyE78EIJZ+t+aEmXUdvQgvGmSMri7aPTHoovbXEreWKkL5j21Er60XAWLTzKbAKYOujQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-retry": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz", + "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.2", + "is-network-error": "^1.0.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-1.2.1.tgz", + "integrity": "sha512-gb0ryzr+K2qFqFv6qi3khoeqMZF/+ajxQipEF6NteZVnvz9tzdsfAVj3lYtn1gAXvH5lfLwfxEII799gt/mRIA==", + "license": "MIT", + "optional": true, + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pac-proxy-agent": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.1.0.tgz", + "integrity": "sha512-Z5FnLVVZSnX7WjBg0mhDtydeRZ1xMcATZThjySQUHqr+0ksP8kqaw23fNKkaaN/Z8gwLUs/W7xdl0I75eP2Xyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.6", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "dev": true, + "license": "MIT", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/pad-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pad-stream/-/pad-stream-2.0.0.tgz", + "integrity": "sha512-3QeQw19K48BQzUGZ9dEf/slX5Jbfy5ZeBTma2XICketO7kFNK7omF00riVcecOKN+DSiJZcK2em1eYKaVOeXKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "pumpify": "^1.3.3", + "split2": "^2.1.1", + "through2": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-asn1": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.9.tgz", + "integrity": "sha512-fIYNuZ/HastSb80baGOuPRo1O9cf4baWw5WsAp7dBuUzeTD/BoaG8sVTdlPFksBE2lF21dN+A1AnrpIjSWqHHg==", + "license": "ISC", + "dependencies": { + "asn1.js": "^4.10.1", + "browserify-aes": "^1.2.0", + "evp_bytestokey": "^1.0.3", + "pbkdf2": "^3.1.5", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/parse-bmfont-ascii": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz", + "integrity": "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==", + "license": "MIT" + }, + "node_modules/parse-bmfont-binary": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz", + "integrity": "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA==", + "license": "MIT" + }, + "node_modules/parse-bmfont-xml": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.6.tgz", + "integrity": "sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA==", + "license": "MIT", + "dependencies": { + "xml-parse-from-string": "^1.0.0", + "xml2js": "^0.5.0" + } + }, + "node_modules/parse-filepath": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", + "integrity": "sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q==", + "license": "MIT", + "dependencies": { + "is-absolute": "^1.0.0", + "map-cache": "^0.2.0", + "path-root": "^0.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/parse-imports": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/parse-imports/-/parse-imports-2.2.1.tgz", + "integrity": "sha512-OL/zLggRp8mFhKL0rNORUTR4yBYujK/uU+xZL+/0Rgm2QE4nLO9v8PzEweSJEbMGKmDRjJE4R3IMJlL2di4JeQ==", + "dev": true, + "license": "Apache-2.0 AND MIT", + "dependencies": { + "es-module-lexer": "^1.5.3", + "slashes": "^3.0.12" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse5": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", + "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^4.5.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/path": { + "version": "0.12.7", + "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", + "integrity": "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==", + "license": "MIT", + "dependencies": { + "process": "^0.11.1", + "util": "^0.10.3" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-root": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", + "integrity": "sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==", + "license": "MIT", + "dependencies": { + "path-root-regex": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-root-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", + "integrity": "sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-type": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", + "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/pbkdf2": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.5.tgz", + "integrity": "sha512-Q3CG/cYvCO1ye4QKkuH7EXxs3VC/rI1/trd+qX2+PolbaKG0H+bgcZzrTt96mMyRtejk+JMCiLUn3y29W8qmFQ==", + "license": "MIT", + "dependencies": { + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "ripemd160": "^2.0.3", + "safe-buffer": "^5.2.1", + "sha.js": "^2.4.12", + "to-buffer": "^1.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/peek-readable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz", + "integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/pgp-utils": { + "version": "0.0.35", + "resolved": "https://registry.npmjs.org/pgp-utils/-/pgp-utils-0.0.35.tgz", + "integrity": "sha512-gCT6EbSTgljgycVa5qGpfRITaLOLbIKsEVRTdsNRgmLMAJpuJNNdrTn/95r8IWo9rFLlccfmGMJXkG9nVDwmrA==", + "dependencies": { + "iced-error": ">=0.0.8", + "iced-runtime": ">=0.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "license": "MIT", + "optional": true, + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/piscina": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/piscina/-/piscina-4.8.0.tgz", + "integrity": "sha512-EZJb+ZxDrQf3dihsUL7p42pjNyrNIFJCrRHPMgxu/svsj+P3xS3fuEWp7k2+rfsavfl1N0G29b1HGs7J0m8rZA==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "@napi-rs/nice": "^1.0.1" + } + }, + "node_modules/pixelmatch": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.3.0.tgz", + "integrity": "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==", + "license": "ISC", + "dependencies": { + "pngjs": "^6.0.0" + }, + "bin": { + "pixelmatch": "bin/pixelmatch" + } + }, + "node_modules/pixelmatch/node_modules/pngjs": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", + "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", + "license": "MIT", + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/plur": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/plur/-/plur-5.1.0.tgz", + "integrity": "sha512-VP/72JeXqak2KiOzjgKtQen5y3IZHn+9GOuLDafPv0eXa47xq0At93XahYBs26MsifCQ4enGKwbjBTKgb9QJXg==", + "license": "MIT", + "dependencies": { + "irregular-plurals": "^3.3.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pngjs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", + "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", + "license": "MIT", + "engines": { + "node": ">=14.19.0" + } + }, + "node_modules/popper.js": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", + "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", + "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/portscanner": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/portscanner/-/portscanner-2.2.0.tgz", + "integrity": "sha512-IFroCz/59Lqa2uBvzK3bKDbDDIEaAY8XJ1jFxcLWTqosrsc32//P4VuSB2vZXoHiHqOmx8B5L5hnKOxL/7FlPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "async": "^2.6.0", + "is-number-like": "^1.0.3" + }, + "engines": { + "node": ">=0.4", + "npm": ">=1.0.0" + } + }, + "node_modules/portscanner/node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-css-variables": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/postcss-css-variables/-/postcss-css-variables-0.19.0.tgz", + "integrity": "sha512-Hr0WEYKLK9VCrY15anHXOd4RCvJy/xRvCnWdplGBeLInwEj6Z14hgzTb2W/39dYTCnS8hnHUfU4/F1zxX0IZuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "escape-string-regexp": "^1.0.3", + "extend": "^3.0.1" + }, + "peerDependencies": { + "postcss": "^8.2.6" + } + }, + "node_modules/postcss-import": { + "version": "16.1.1", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-16.1.1.tgz", + "integrity": "sha512-2xVS1NCZAfjtVdvXiyegxzJ447GyqCeEI5V7ApgQVOWnros1p5lGNovJNapwPpMombyFBfqDwt7AD3n2l0KOfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-loader": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.2.1.tgz", + "integrity": "sha512-k98jtRzthjj3f76MYTs9JTpRqV1RaaMhEU0Lpw9OTmQZQdppg4B30VZ74BojuBHt3F4KyubHJoXCMUeM8Bqeow==", + "dev": true, + "license": "MIT", + "dependencies": { + "cosmiconfig": "^9.0.0", + "jiti": "^2.5.1", + "semver": "^7.6.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || ^1.0.0 || ^2.0.0-0", + "postcss": "^7.0.0 || ^8.0.1", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/postcss-loader/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", + "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", + "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", + "dev": true, + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha512-PhmXi5XmoyKw1Un4E+opM2KcsJInDvKyuOumcjjw3waw86ZNjHwVUOOWLc4bCzLdcKNaWBH9e99sbWzDQsVaYg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pretty-bytes": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz", + "integrity": "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==", + "license": "MIT", + "engines": { + "node": "^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pretty-error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", + "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.20", + "renderkid": "^3.0.0" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/progress": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", + "integrity": "sha512-UdA8mJ4weIkUBO224tIarHzuHs4HuYiJvsuGT7j/SPQiUJVjYvNDBIPa0hAorduOfjGohB/qHWRa/lrrWX/mXw==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/prompt": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/prompt/-/prompt-1.3.0.tgz", + "integrity": "sha512-ZkaRWtaLBZl7KKAKndKYUL8WqNT+cQHKRZnT4RYYms48jQkFw3rrBL+/N5K/KtdEveHkxs982MX2BkDKub2ZMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@colors/colors": "1.5.0", + "async": "3.2.3", + "read": "1.0.x", + "revalidator": "0.1.x", + "winston": "2.x" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/prompt/node_modules/async": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", + "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "license": "ISC", + "optional": true + }, + "node_modules/protobufjs": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", + "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-addr/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-agent": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", + "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.6", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.1.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true, + "license": "MIT" + }, + "node_modules/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", + "license": "ISC", + "optional": true + }, + "node_modules/psl": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } + }, + "node_modules/public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/public-encrypt/node_modules/bn.js": { + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", + "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + } + }, + "node_modules/pumpify/node_modules/pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/purepack": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/purepack/-/purepack-1.0.6.tgz", + "integrity": "sha512-L/e3qq/3m/TrYtINo2aBB98oz6w8VHGyFy+arSKwPMZDUNNw2OaQxYnZO6UIZZw2OnRl2qkxGmuSOEfsuHXJdA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/qr-image": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/qr-image/-/qr-image-3.2.0.tgz", + "integrity": "sha512-rXKDS5Sx3YipVsqmlMJsJsk6jXylEpiHRC2+nJy66fxA5ExYyGa4PqwteW69SaVmAb2OQ18HbYriT7cGQMbduw==", + "license": "MIT" + }, + "node_modules/qs": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/query-string": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", + "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==", + "license": "MIT", + "optional": true, + "dependencies": { + "decode-uri-component": "^0.2.0", + "object-assign": "^4.1.0", + "strict-uri-encode": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "license": "MIT", + "dependencies": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-1.1.7.tgz", + "integrity": "sha512-WmJJU2e9Y6M5UzTOkHaM7xJGAPQD8PNzx3bAd2+uhZAim6wDk6dAZxPVYLF67XhbR4hmKGh33Lpmh4XWrCH5Mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "1", + "string_decoder": "0.10" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/raw-body/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/read": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", + "integrity": "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "mute-stream": "~0.0.4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/readable-web-to-node-stream": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.4.tgz", + "integrity": "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw==", + "license": "MIT", + "dependencies": { + "readable-stream": "^4.7.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/readable-web-to-node-stream/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/readable-web-to-node-stream/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/rechoir": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", + "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve": "^1.9.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "license": "Apache-2.0" + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", + "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "license": "MIT" + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpu-core": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", + "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.2", + "regjsgen": "^0.8.0", + "regjsparser": "^0.13.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.2.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/regjsparser": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz", + "integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "jsesc": "~3.1.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==", + "license": "ISC" + }, + "node_modules/renderkid": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", + "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-select": "^4.1.3", + "dom-converter": "^0.2.0", + "htmlparser2": "^6.1.0", + "lodash": "^4.17.21", + "strip-ansi": "^6.0.1" + } + }, + "node_modules/renderkid/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/renderkid/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/replace-ext": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", + "integrity": "sha512-AFBWBy9EVRTa/LhEcG8QDP3FvpwZqmvN2QFDuJswFeaVhWnZMp8q3E6Zd90SR04PlIwfGdyVjNyLPyen/ek5CQ==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/replace-homedir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/replace-homedir/-/replace-homedir-2.0.0.tgz", + "integrity": "sha512-bgEuQQ/BHW0XkkJtawzrfzHFSN70f/3cNOiHa2QsYxqrjaC30X1k74FJ6xswVBP0sr0SpGIdVFuPwfrYziVeyw==", + "license": "MIT", + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/reserved-identifiers": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/reserved-identifiers/-/reserved-identifiers-1.2.0.tgz", + "integrity": "sha512-yE7KUfFvaBFzGPs5H3Ops1RevfUEsDc5Iz65rOwWg4lE8HJSYtle77uul3+573457oHvBKuHYDl/xqUkKpEEdw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==", + "license": "MIT", + "dependencies": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-options": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-2.0.0.tgz", + "integrity": "sha512-/FopbmmFOQCfsCx77BRFdKOniglTiHumLgwvd6IDPihy1GKkadZbgQJBcTb2lMzSR1pndzd96b1nZrreZ7+9/A==", + "license": "MIT", + "dependencies": { + "value-or-function": "^4.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "lowercase-keys": "^1.0.0" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/revalidator": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/revalidator/-/revalidator-0.1.8.tgz", + "integrity": "sha512-xcBILK2pA9oh4SiinPEZfhP8HfrB/ha+a2fTMyl7Om2WjlDVrOQy99N2MXXlUHqGJz4qEu2duXxHJjDWuK/0xg==", + "dev": true, + "license": "Apache 2.0", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "devOptional": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/ripemd160": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.3.tgz", + "integrity": "sha512-5Di9UC0+8h1L6ZD2d7awM7E/T4uA1fJRlx6zk/NvdCCVEoAnFqvHmCuNeIKoCeIixBX/q8uM+6ycDvF8woqosA==", + "license": "MIT", + "dependencies": { + "hash-base": "^3.1.2", + "inherits": "^2.0.4" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ripemd160/node_modules/hash-base": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.2.tgz", + "integrity": "sha512-Bb33KbowVTIj5s7Ked1OsqHUeCpz//tPwR+E2zJgJKo9Z5XolZ9b6bdUgjmYlwnWhoOQKoTd1TYToZGn5mAYOg==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "^2.3.8", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/rison": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/rison/-/rison-0.1.1.tgz", + "integrity": "sha512-8C+/PKKTaAYE2quDtOUwny/eQpNn9YGby7T80wntbVWSGvw0aUT9M0YgLdLkUgIQzQwaB1ZTr80rwLVKyohHig==", + "license": "Apache-2.0" + }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", + "license": "Unlicense" + }, + "node_modules/rrweb-cssom": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", + "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/run-applescript": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", + "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-json-parse": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-1.0.1.tgz", + "integrity": "sha512-o0JmTu17WGUaUOHa1l0FPGXKBfijbxK6qoHzlkihsDXxzBHvJcA7zgviKR92Xs841rX9pK16unfphLq0/KqX7A==", + "dev": true + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/sax": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/schema-utils/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/schema-utils/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/schema-utils/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/scryptsy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/scryptsy/-/scryptsy-2.1.0.tgz", + "integrity": "sha512-1CdSqHQowJBnMAFyPEBRfqag/YP9OF394FV+4YREIJX4ljD7OxvQRDayyoyyCk+senRjSkP6VnUNQmVQqB6g7w==", + "license": "MIT" + }, + "node_modules/seek-bzip": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.6.tgz", + "integrity": "sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "commander": "^2.8.1" + }, + "bin": { + "seek-bunzip": "bin/seek-bunzip", + "seek-table": "bin/seek-bzip-table" + } + }, + "node_modules/seek-bzip/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT", + "optional": true + }, + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "dev": true, + "license": "MIT" + }, + "node_modules/selenium-webdriver": { + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.27.0.tgz", + "integrity": "sha512-LkTJrNz5socxpPnWPODQ2bQ65eYx9JK+DQMYNihpTjMCqHwgWGYQnQTCAAche2W3ZP87alA+1zYPvgS8tHNzMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/SeleniumHQ" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/selenium" + } + ], + "license": "Apache-2.0", + "dependencies": { + "@bazel/runfiles": "^6.3.1", + "jszip": "^3.10.1", + "tmp": "^0.2.3", + "ws": "^8.18.0" + }, + "engines": { + "node": ">= 14.21.0" + } + }, + "node_modules/selfsigned": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node-forge": "^1.3.0", + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "devOptional": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/semver-greatest-satisfied-range": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-2.0.0.tgz", + "integrity": "sha512-lH3f6kMbwyANB7HuOWRMlLCa2itaCrZJ+SAqqkSZrZKO/cAsk2EOyaKHUtNkVLFyFW9pct22SFesFp3Z7zpA0g==", + "license": "MIT", + "dependencies": { + "sver": "^1.8.3" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/semver-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-2.0.0.tgz", + "integrity": "sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/semver-truncate": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/semver-truncate/-/semver-truncate-1.1.2.tgz", + "integrity": "sha512-V1fGg9i4CL3qesB6U0L6XAm4xOJiHmt4QAacazumuasc03BvtFGIMCduv01JWQ69Nv+JST9TqhSCiJoxoY031w==", + "license": "MIT", + "optional": true, + "dependencies": { + "semver": "^5.3.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/semver-truncate/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/send/node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, + "license": "ISC" + }, + "node_modules/send/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-static/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "dev": true, + "license": "MIT" + }, + "node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/sha.js": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", + "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", + "license": "(MIT AND BSD-3-Clause)", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.0" + }, + "bin": { + "sha.js": "bin.js" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.2.tgz", + "integrity": "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/shelljs": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.5.3.tgz", + "integrity": "sha512-C2FisSSW8S6TIYHHiMHN0NqzdjWfTekdMpA2FJTbRWnQMLO1RRIXEB9eVZYOlofYmjZA7fY3ChoFu09MeI3wlQ==", + "dev": true, + "license": "BSD*", + "bin": { + "shjs": "bin/shjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/simple-xml-to-json": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/simple-xml-to-json/-/simple-xml-to-json-1.2.2.tgz", + "integrity": "sha512-bmJJf5YiYL60eOQk3gaVxbM6vgYuwrFydCEAA2x3jccHUTsAffiPyblS/yQGr8GDUQVxSDm3WwLNL5HmRqDUcg==", + "license": "MIT", + "engines": { + "node": ">=14.20.0" + } + }, + "node_modules/sirv": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", + "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/sitemap": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-8.0.2.tgz", + "integrity": "sha512-LwktpJcyZDoa0IL6KT++lQ53pbSrx2c9ge41/SeLTyqy2XUNA6uR4+P9u5IVo5lPeL2arAcOKn1aZAxoYbCKlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^17.0.5", + "@types/sax": "^1.2.1", + "arg": "^5.0.0", + "sax": "^1.4.1" + }, + "bin": { + "sitemap": "dist/cli.js" + }, + "engines": { + "node": ">=14.0.0", + "npm": ">=6.0.0" + } + }, + "node_modules/sitemap/node_modules/@types/node": { + "version": "17.0.45", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", + "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", + "dev": true, + "license": "MIT" + }, + "node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/slashes": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/slashes/-/slashes-3.0.12.tgz", + "integrity": "sha512-Q9VME8WyGkc7pJf6QEkj3wE+2CnvZMI+XJhwdTPR8Z/kWQRXi7boAWLDibRPyHRTUTPx5FaU7MsyrjI3yLB4HA==", + "dev": true, + "license": "ISC" + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/snackbarjs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/snackbarjs/-/snackbarjs-1.1.0.tgz", + "integrity": "sha512-WIMYor1cy6Q2r1GdfvKG4F+Rg9VpQM5/DiemAQFMlVEpnPA7HSk7dBoYam195+y3rMfzhg87v7SW5J0QGoMj2g==", + "license": "ISC" + }, + "node_modules/sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/sockjs/node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/sockjs/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/socks": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/sort-keys": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", + "integrity": "sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg==", + "license": "MIT", + "optional": true, + "dependencies": { + "is-plain-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sort-keys-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sort-keys-length/-/sort-keys-length-1.0.1.tgz", + "integrity": "sha512-GRbEOUqCxemTAk/b32F2xa8wDTs+Z1QHOkbhJDQTvv/6G3ZkbJ+frYWsTcc7cBB3Fu4wy4XlLCuNtJuMn7Gsvw==", + "license": "MIT", + "optional": true, + "dependencies": { + "sort-keys": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sort-keys/node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sortablejs": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.7.tgz", + "integrity": "sha512-Kk8wLQPlS+yi1ZEf48a4+fzHa4yxjC30M/Sr2AnQu+f/MPwvvX9XjZ6OWejiz8crBsLwSq8GHqaxaET7u6ux0A==", + "license": "MIT" + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sparkles": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-2.1.0.tgz", + "integrity": "sha512-r7iW1bDw8R/cFifrD3JnQJX0K1jqT0kprL48BiBpLZLJPmAm34zsVBsK5lc7HirZYZqMW65dOXZgbAGt/I6frg==", + "license": "MIT", + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", + "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.21", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz", + "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "node_modules/spdy-transport/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/split.js": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/split.js/-/split.js-1.6.5.tgz", + "integrity": "sha512-mPTnGCiS/RiuTNsVhCm9De9cCAUsrNFFviRbADdKiiV+Kk8HKp/0fWu7Kr8pi3/yBmsqLFHuXGT9UUZ+CNLwFw==", + "license": "MIT" + }, + "node_modules/split2": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-2.2.0.tgz", + "integrity": "sha512-RAb22TG39LhI31MbreBgIuKiIKhVsawfTgEGqKHTK87aG+ul/PB8Sqoi3I7kVdRWiCfrKxK3uo4/YUkpNvhPbw==", + "dev": true, + "license": "ISC", + "dependencies": { + "through2": "^2.0.2" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/ssdeep.js": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/ssdeep.js/-/ssdeep.js-0.0.3.tgz", + "integrity": "sha512-QXKADMuEsOmRYGlB9JvrulcF5NLAjAvYLg3qDHUrEEilUFLKoL4fUQDw1s7hF+mOL96jwcdyXpnmCqyNu7Wm3Q==" + }, + "node_modules/stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "deprecated": "Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility", + "license": "MIT", + "optional": true + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/stacktrace-parser": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz", + "integrity": "sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.7.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/stacktrace-parser/node_modules/type-fest": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", + "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=8" + } + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/stream-browserify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", + "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", + "license": "MIT", + "dependencies": { + "inherits": "~2.0.4", + "readable-stream": "^3.5.0" + } + }, + "node_modules/stream-browserify/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/stream-composer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-composer/-/stream-composer-1.0.2.tgz", + "integrity": "sha512-bnBselmwfX5K10AH6L4c8+S5lgZMWI7ZYrz2rvYjCPB2DIMC4Ig8OpxGpNJSxRZ58oti7y1IcNvjBAz9vW5m4w==", + "license": "MIT", + "dependencies": { + "streamx": "^2.13.2" + } + }, + "node_modules/stream-exhaust": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", + "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==", + "license": "MIT" + }, + "node_modules/stream-shift": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/streamx": { + "version": "2.25.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.25.0.tgz", + "integrity": "sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg==", + "license": "MIT", + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, + "node_modules/strict-uri-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", + "integrity": "sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/string-template": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", + "integrity": "sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw==", + "dev": true + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-2.0.1.tgz", + "integrity": "sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/strip-dirs": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz", + "integrity": "sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==", + "license": "MIT", + "optional": true, + "dependencies": { + "is-natural-number": "^4.0.1" + } + }, + "node_modules/strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-outer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", + "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "license": "MIT", + "optional": true, + "dependencies": { + "escape-string-regexp": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strnum": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", + "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/strtok3": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", + "integrity": "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==", + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "peek-readable": "^4.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/style-mod": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz", + "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==", + "dev": true, + "license": "MIT" + }, + "node_modules/super-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/super-regex/-/super-regex-1.1.0.tgz", + "integrity": "sha512-WHkws2ZflZe41zj6AolvvmaTrWds/VuyeYr9iPVv/oQeaIoVxMKaushfFWpOGDT+GuBrM/sVqF8KUCYQlSSTdQ==", + "license": "MIT", + "dependencies": { + "function-timeout": "^1.0.1", + "make-asynchronous": "^1.0.1", + "time-span": "^5.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sver": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/sver/-/sver-1.8.4.tgz", + "integrity": "sha512-71o1zfzyawLfIWBOmw8brleKyvnbn73oVHNCsu51uPMz/HWiKkkXsI31JjHW5zqXEqnPYkIiHd8ZmL7FCimLEA==", + "license": "MIT", + "optionalDependencies": { + "semver": "^6.3.0" + } + }, + "node_modules/svgo": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.2.tgz", + "integrity": "sha512-TyzE4NVGLUFy+H/Uy4N6c3G0HEeprsVfge6Lmq+0FdQQ/zqoVYB62IsBZORsiL+o96s6ff/V6/3UQo/C0cgCAA==", + "license": "MIT", + "optional": true, + "dependencies": { + "commander": "^7.2.0", + "css-select": "^4.1.3", + "css-tree": "^1.1.3", + "csso": "^4.2.0", + "picocolors": "^1.0.0", + "sax": "^1.5.0", + "stable": "^0.1.8" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/synckit": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz", + "integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/tcp-port-used": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tcp-port-used/-/tcp-port-used-1.0.2.tgz", + "integrity": "sha512-l7ar8lLUD3XS1V2lfoJlCBaeoaWo/2xfYt81hM7VlvR4RrMVFqfmzfhLVk40hAb368uitje5gPtBRL1m/DGvLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4.3.1", + "is2": "^2.0.6" + } + }, + "node_modules/tcp-port-used/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/tcp-port-used/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "license": "MIT", + "dependencies": { + "streamx": "^2.12.5" + } + }, + "node_modules/temp-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz", + "integrity": "sha512-xZFXEGbG7SNC3itwBzI3RYjq/cEhBkx2hJuKGIUOcEULmkQExXiHat2z/qkISYsuR+IKumhEfKKbV5qXmhICFQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/tempfile": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tempfile/-/tempfile-2.0.0.tgz", + "integrity": "sha512-ZOn6nJUgvgC09+doCEF3oB+r3ag7kUvlsXEGX069QRD60p+P3uP7XG9N2/at+EyIRGSN//ZY3LyEotA1YpmjuA==", + "license": "MIT", + "optional": true, + "dependencies": { + "temp-dir": "^1.0.0", + "uuid": "^3.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/tempfile/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "license": "MIT", + "optional": true, + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/terser": { + "version": "5.46.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz", + "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.16", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz", + "integrity": "sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/tesseract.js": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tesseract.js/-/tesseract.js-5.1.1.tgz", + "integrity": "sha512-lzVl/Ar3P3zhpUT31NjqeCo1f+D5+YfpZ5J62eo2S14QNVOmHBTtbchHm/YAbOOOzCegFnKf4B3Qih9LuldcYQ==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "bmp-js": "^0.1.0", + "idb-keyval": "^6.2.0", + "is-electron": "^2.2.2", + "is-url": "^1.2.4", + "node-fetch": "^2.6.9", + "opencollective-postinstall": "^2.0.3", + "regenerator-runtime": "^0.13.3", + "tesseract.js-core": "^5.1.1", + "wasm-feature-detect": "^1.2.11", + "zlibjs": "^0.3.1" + } + }, + "node_modules/tesseract.js-core": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tesseract.js-core/-/tesseract.js-core-5.1.1.tgz", + "integrity": "sha512-KX3bYSU5iGcO1XJa+QGPbi+Zjo2qq6eBhNjSGR5E5q0JtzkoipJKOUQD7ph8kFyteCEfEQ0maWLu8MCXtvX5uQ==", + "license": "Apache-2.0" + }, + "node_modules/text-decoder": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz", + "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, + "node_modules/text-hex": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-0.0.0.tgz", + "integrity": "sha512-RpZDSt2VIQnsPVDiOySPfi/RTRBbPyJj2fikmH5O2H5Zc/MC6ZPVcc4GYGcnbTS/j2v1HZOmy6F4CimfiLPMRg==", + "license": "MIT" + }, + "node_modules/thingies": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz", + "integrity": "sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==", + "dev": true, + "license": "Unlicense", + "engines": { + "node": ">=10.18" + }, + "peerDependencies": { + "tslib": "^2" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "license": "MIT", + "optional": true + }, + "node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true, + "license": "MIT" + }, + "node_modules/time-span": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/time-span/-/time-span-5.1.0.tgz", + "integrity": "sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==", + "license": "MIT", + "dependencies": { + "convert-hrtime": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/time-stamp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", + "integrity": "sha512-gLCeArryy2yNTRzTGKbZbloctj64jkZ57hj5zdraXue6aFgd6PmvVtEyiUU+hvU0v7q08oVv8r8ev0tRo6bvgw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/timed-out": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", + "integrity": "sha512-G7r3AhovYtr5YKOWQkta8RKAPb+J9IsO4uVmzjl8AZwfhs8UcUwTiD6gcJYSgOtzyjvQKrKYn41syHbUWMkafA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/timers-ext": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.8.tgz", + "integrity": "sha512-wFH7+SEAcKfJpfLPkrgMPvvwnEtj8W4IurvEyrKsDleXnKLCDw71w8jltvfLa8Rm4qQxxT4jmDBYbJG/z7qoww==", + "license": "ISC", + "dependencies": { + "es5-ext": "^0.10.64", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/tiny-lr": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tiny-lr/-/tiny-lr-1.1.1.tgz", + "integrity": "sha512-44yhA3tsaRoMOjQQ+5v5mVdqef+kH6Qze9jTpqtVufgYjYt08zyZAwNwwVBj3i1rJMnR52IxOW0LK0vBzgAkuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "body": "^5.1.0", + "debug": "^3.1.0", + "faye-websocket": "~0.10.0", + "livereload-js": "^2.3.0", + "object-assign": "^4.1.0", + "qs": "^6.4.0" + } + }, + "node_modules/tiny-lr/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/tinycolor2": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tmp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/to-buffer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", + "integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==", + "license": "MIT", + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/to-buffer/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "license": "MIT" + }, + "node_modules/to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha512-lxrWP8ejsq+7E3nNjwYmUBMAgjMTZoTI+sdBOpvNyijeDLa29LUn9QaoXAHv4+Z578hbmHHJKZknzxVtvo77og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/to-through": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/to-through/-/to-through-3.0.0.tgz", + "integrity": "sha512-y8MN937s/HVhEoBU1SxfHC+wxCHkV1a9gW8eAdTadYh/bGyesZIVcbjI+mSpFbSVwQici/XjBjuUyri1dnXwBw==", + "license": "MIT", + "dependencies": { + "streamx": "^2.12.5" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/toastr": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/toastr/-/toastr-2.1.4.tgz", + "integrity": "sha512-LIy77F5n+sz4tefMmFOntcJ6HL0Fv3k1TDnNmFZ0bU/GcvIIfy6eG2v7zQmMiYgaalAiUv75ttFrPn5s0gyqlA==", + "dependencies": { + "jquery": ">=1.12.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/token-types": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz", + "integrity": "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==", + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tr46": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tree-dump": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.2.tgz", + "integrity": "sha512-dpev9ABuLWdEubk+cIaI9cHwRNNDjkBBLXTwI4UCUFdQ5xXKqNXoK4FEciw/vxf+NQ7Cb7sGUyeUtORvHIdRXQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/trim-repeated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", + "integrity": "sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==", + "license": "MIT", + "optional": true, + "dependencies": { + "escape-string-regexp": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/triplesec": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/triplesec/-/triplesec-4.0.3.tgz", + "integrity": "sha512-fug70e1nJoCMxsXQJlETisAALohm84vl++IiTTHEqM7Lgqwz62jrlwqOC/gJEAJjO/ByN127sEcioB56HW3wIw==", + "dependencies": { + "iced-error": ">=0.0.9", + "iced-lock": "^1.0.1", + "iced-runtime": "^1.0.2", + "more-entropy": ">=0.0.7", + "progress": "~1.1.2", + "uglify-js": "^3.1.9" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.13.3.tgz", + "integrity": "sha512-iNWodk4oBsZ03Qfw/Yvv0KB90uYrJqvL4Je7Gy4C5t/GS3sCXPRmIT1lxmId4RzvUp0XG62bcxJ2CBu/3L5DSg==", + "license": "Public domain" + }, + "node_modules/type": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", + "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", + "license": "ISC" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ua-parser-js": { + "version": "1.0.41", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.41.tgz", + "integrity": "sha512-LbBDqdIC5s8iROCUjMbW1f5dJQTEFB1+KO9ogbvlb3nm9n4YHa5p4KTvFPWvh2Hs8gZMBuiB1/8+pdfe/tDPug==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + } + ], + "license": "MIT", + "bin": { + "ua-parser-js": "script/cli.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "license": "MIT" + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "license": "BSD-2-Clause", + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/uint64be": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uint64be/-/uint64be-1.0.1.tgz", + "integrity": "sha512-w+VZSp8hSZ/xWZfZNMppWNF6iqY+dcMYtG5CpwRDgxi94HIE6ematSdkzHGzVC4SDEaTsG65zrajN+oKoWG6ew==", + "license": "MIT" + }, + "node_modules/uint8array-extras": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", + "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "license": "MIT", + "optional": true, + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "node_modules/unbzip2-stream/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/unc-path-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", + "integrity": "sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/underscore.string": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.6.tgz", + "integrity": "sha512-VoC83HWXmCrF6rgkyxS9GHv8W9Q5nhMKho+OadDJGzL2oDYbYEppBaCMH6pFlwLeqj2QS+hhkw2kpXkSdD1JxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "^1.1.1", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/undertaker": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-2.0.0.tgz", + "integrity": "sha512-tO/bf30wBbTsJ7go80j0RzA2rcwX6o7XPBpeFcb+jzoeb4pfMM2zUeSDIkY1AWqeZabWxaQZ/h8N9t35QKDLPQ==", + "license": "MIT", + "dependencies": { + "bach": "^2.0.1", + "fast-levenshtein": "^3.0.0", + "last-run": "^2.0.0", + "undertaker-registry": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/undertaker-registry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-2.0.0.tgz", + "integrity": "sha512-+hhVICbnp+rlzZMgxXenpvTxpuvA67Bfgtt+O9WOE5jo7w/dyiF1VmoZVIHvP2EkUjsyKyTwYKlLhA+j47m1Ew==", + "license": "MIT", + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/undertaker/node_modules/fast-levenshtein": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-3.0.0.tgz", + "integrity": "sha512-hKKNajm46uNmTlhHSyZkmToAc56uZJwYq7yrciZjqOxnlfQwERDQJmHPUp7m1m9wx8vgOe8IaCKZ5Kv2k1DdCQ==", + "license": "MIT", + "dependencies": { + "fastest-levenshtein": "^1.0.7" + } + }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "license": "MIT" + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", + "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", + "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unorm": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/unorm/-/unorm-1.6.0.tgz", + "integrity": "sha512-b2/KCUlYZUeA7JFUuRJZPUtr4gZvBh7tavtv4fvk4+KV9pfGiR6CQAQAWl49ZpR3ts2dk4FYkP7EIgDJoiOLDA==", + "license": "MIT or GPL-2.0", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.4.tgz", + "integrity": "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==", + "license": "MIT", + "dependencies": { + "punycode": "^1.4.1", + "qs": "^6.12.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/url-parse-lax": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", + "integrity": "sha512-BVA4lR5PIviy2PMseNd2jbFQ+jwSwQGdJejf5ctd1rEXt0Ypd7yanUK9+lYechVlN5VaTJGsu2U/3MDDu6KgBA==", + "license": "MIT", + "optional": true, + "dependencies": { + "prepend-http": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/url-to-options": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz", + "integrity": "sha512-0kQLIzG4fdk/G5NONku64rSH/x32NOA39LVQqlK8Le6lvTF6GGRJpqaQFGgU+CLwySIqBSMdwYM0sYcW9f6P4A==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/url/node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "license": "MIT" + }, + "node_modules/urlsafe-base64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/urlsafe-base64/-/urlsafe-base64-1.0.0.tgz", + "integrity": "sha512-RtuPeMy7c1UrHwproMZN9gN6kiZ0SvJwRaEzwZY0j9MypEkFqyBaKv176jvlPtg58Zh36bOkS0NFABXMHvvGCA==" + }, + "node_modules/utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz", + "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==", + "license": "MIT" + }, + "node_modules/utif2": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/utif2/-/utif2-4.1.0.tgz", + "integrity": "sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w==", + "license": "MIT", + "dependencies": { + "pako": "^1.0.11" + } + }, + "node_modules/util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha512-5KiHfsmkqacuKjkRkdV7SsfDJ2EGiPsK92s2MhNSY0craxjTdKTtqKsJaCWp4LW33ZZ0OPUv1WO/TFvNQRiQxQ==", + "license": "MIT", + "dependencies": { + "inherits": "2.0.1" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/util/node_modules/inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha512-8nWq2nLTAwd02jTqJExUYFSD/fKq6VH9Y/oG2accc/kdI0V98Bag8d5a4gi3XHz73rDWa2PvTtvcWYquKqSENA==", + "license": "ISC" + }, + "node_modules/utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz", + "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, + "node_modules/v8flags": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", + "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "homedir-polyfill": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/value-or-function": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-4.0.0.tgz", + "integrity": "sha512-aeVK81SIuT6aMJfNo9Vte8Dw0/FZINGBV8BfCraGtqVxIeLAEhJyoWs8SmvRVmXfGss2PmmOwZCuBPbZR+IYWg==", + "license": "MIT", + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vinyl": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", + "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", + "license": "MIT", + "dependencies": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vinyl-bufferstream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/vinyl-bufferstream/-/vinyl-bufferstream-1.0.1.tgz", + "integrity": "sha512-yCCIoTf26Q9SQ0L9cDSavSL7Nt6wgQw8TU1B/bb9b9Z4A3XTypXCGdc5BvXl4ObQvVY8JrDkFnWa/UqBqwM2IA==", + "dependencies": { + "bufferstreams": "1.0.1" + } + }, + "node_modules/vinyl-contents": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vinyl-contents/-/vinyl-contents-2.0.0.tgz", + "integrity": "sha512-cHq6NnGyi2pZ7xwdHSW1v4Jfnho4TEGtxZHw01cmnc8+i7jgR6bRnED/LbrKan/Q7CvVLbnvA5OepnhbpjBZ5Q==", + "license": "MIT", + "dependencies": { + "bl": "^5.0.0", + "vinyl": "^3.0.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/vinyl-contents/node_modules/bl": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", + "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", + "license": "MIT", + "dependencies": { + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/vinyl-contents/node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/vinyl-contents/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/vinyl-contents/node_modules/replace-ext": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", + "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/vinyl-contents/node_modules/vinyl": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.1.tgz", + "integrity": "sha512-0QwqXteBNXgnLCdWdvPQBX6FXRHtIH3VhJPTd5Lwn28tJXc34YqSCWUmkOvtJHBmB3gGoPtrOKk3Ts8/kEZ9aA==", + "license": "MIT", + "dependencies": { + "clone": "^2.1.2", + "remove-trailing-separator": "^1.1.0", + "replace-ext": "^2.0.0", + "teex": "^1.0.1" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/vinyl-fs": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-4.0.2.tgz", + "integrity": "sha512-XRFwBLLTl8lRAOYiBqxY279wY46tVxLaRhSwo3GzKEuLz1giffsOquWWboD/haGf5lx+JyTigCFfe7DWHoARIA==", + "license": "MIT", + "dependencies": { + "fs-mkdirp-stream": "^2.0.1", + "glob-stream": "^8.0.3", + "graceful-fs": "^4.2.11", + "iconv-lite": "^0.6.3", + "is-valid-glob": "^1.0.0", + "lead": "^4.0.0", + "normalize-path": "3.0.0", + "resolve-options": "^2.0.0", + "stream-composer": "^1.0.2", + "streamx": "^2.14.0", + "to-through": "^3.0.0", + "value-or-function": "^4.0.0", + "vinyl": "^3.0.1", + "vinyl-sourcemap": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/vinyl-fs/node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/vinyl-fs/node_modules/replace-ext": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", + "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/vinyl-fs/node_modules/vinyl": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.1.tgz", + "integrity": "sha512-0QwqXteBNXgnLCdWdvPQBX6FXRHtIH3VhJPTd5Lwn28tJXc34YqSCWUmkOvtJHBmB3gGoPtrOKk3Ts8/kEZ9aA==", + "license": "MIT", + "dependencies": { + "clone": "^2.1.2", + "remove-trailing-separator": "^1.1.0", + "replace-ext": "^2.0.0", + "teex": "^1.0.1" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/vinyl-sourcemap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-2.0.0.tgz", + "integrity": "sha512-BAEvWxbBUXvlNoFQVFVHpybBbjW1r03WhohJzJDSfgrrK5xVYIDTan6xN14DlyImShgDRv2gl9qhM6irVMsV0Q==", + "license": "MIT", + "dependencies": { + "convert-source-map": "^2.0.0", + "graceful-fs": "^4.2.10", + "now-and-later": "^3.0.0", + "streamx": "^2.12.5", + "vinyl": "^3.0.0", + "vinyl-contents": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/vinyl-sourcemap/node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/vinyl-sourcemap/node_modules/replace-ext": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", + "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/vinyl-sourcemap/node_modules/vinyl": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.1.tgz", + "integrity": "sha512-0QwqXteBNXgnLCdWdvPQBX6FXRHtIH3VhJPTd5Lwn28tJXc34YqSCWUmkOvtJHBmB3gGoPtrOKk3Ts8/kEZ9aA==", + "license": "MIT", + "dependencies": { + "clone": "^2.1.2", + "remove-trailing-separator": "^1.1.0", + "replace-ext": "^2.0.0", + "teex": "^1.0.1" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/vinyl-sourcemaps-apply": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz", + "integrity": "sha512-+oDh3KYZBoZC8hfocrbrxbLUeaYtQK7J5WU5Br9VqWqmCll3tFJqKp97GC9GmMsVIL0qnx2DgEDVxdo5EZ5sSw==", + "license": "ISC", + "dependencies": { + "source-map": "^0.5.1" + } + }, + "node_modules/vinyl-sourcemaps-apply/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/vinyl/node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/vinyl/node_modules/replace-ext": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", + "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vkbeautify": { + "version": "0.99.3", + "resolved": "https://registry.npmjs.org/vkbeautify/-/vkbeautify-0.99.3.tgz", + "integrity": "sha512-2ozZEFfmVvQcHWoHLNuiKlUfDKlhh4KGsy54U0UrlLMR1SO+XKAIDqBxtBwHgNrekurlJwE8A9K6L49T78ZQ9Q==", + "license": "MIT" + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", + "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", + "dev": true, + "license": "MIT" + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/wasm-feature-detect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.8.0.tgz", + "integrity": "sha512-zksaLKM2fVlnB5jQQDqKXXwYHLQUVH9es+5TOOHwGOVJOCeRBCiPjwSg+3tN2AdTCzjgli4jijCH290kXb/zWQ==", + "license": "Apache-2.0" + }, + "node_modules/watchpack": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", + "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/web-worker": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.5.0.tgz", + "integrity": "sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==", + "license": "Apache-2.0" + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/webpack": { + "version": "5.105.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.2.tgz", + "integrity": "sha512-dRXm0a2qcHPUBEzVk8uph0xWSjV/xZxenQQbLwnwP7caQCYpqG1qddwlyEkIDkYn0K8tvmcrZ+bOrzoQ3HxCDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.15.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.28.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.19.0", + "es-module-lexer": "^2.0.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.3.1", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.3.16", + "watchpack": "^2.5.1", + "webpack-sources": "^3.3.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-bundle-analyzer": { + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.2.tgz", + "integrity": "sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@discoveryjs/json-ext": "0.5.7", + "acorn": "^8.0.4", + "acorn-walk": "^8.0.0", + "commander": "^7.2.0", + "debounce": "^1.2.1", + "escape-string-regexp": "^4.0.0", + "gzip-size": "^6.0.0", + "html-escaper": "^2.0.2", + "opener": "^1.5.2", + "picocolors": "^1.0.0", + "sirv": "^2.0.3", + "ws": "^7.3.1" + }, + "bin": { + "webpack-bundle-analyzer": "lib/bin/analyzer.js" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/webpack-dev-middleware": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.2.tgz", + "integrity": "sha512-xOO8n6eggxnwYpy1NlzUKpvrjfJTvae5/D6WOK0S2LSo7vjmo5gCM1DbLUmFqrMTJP+W/0YZNctm7jasWvLuBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^4.6.0", + "mime-types": "^2.1.31", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + } + } + }, + "node_modules/webpack-dev-middleware/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webpack-dev-server": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.0.4.tgz", + "integrity": "sha512-dljXhUgx3HqKP2d8J/fUMvhxGhzjeNVarDLcbO/EWMSgRizDkxHQDZQaLFL5VJY9tRBj2Gz+rvCEYYvhbqPHNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/bonjour": "^3.5.13", + "@types/connect-history-api-fallback": "^1.5.4", + "@types/express": "^4.17.21", + "@types/serve-index": "^1.9.4", + "@types/serve-static": "^1.15.5", + "@types/sockjs": "^0.3.36", + "@types/ws": "^8.5.10", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.2.1", + "chokidar": "^3.6.0", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "default-gateway": "^6.0.3", + "express": "^4.17.3", + "graceful-fs": "^4.2.6", + "html-entities": "^2.4.0", + "http-proxy-middleware": "^2.0.3", + "ipaddr.js": "^2.1.0", + "launch-editor": "^2.6.1", + "open": "^10.0.3", + "p-retry": "^6.2.0", + "rimraf": "^5.0.5", + "schema-utils": "^4.2.0", + "selfsigned": "^2.4.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^7.1.0", + "ws": "^8.16.0" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/webpack-dev-server/node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-dev-server/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/webpack-dev-server/node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-dev-server/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/webpack-dev-server/node_modules/open": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz", + "integrity": "sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-dev-server/node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/webpack-node-externals": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/webpack-node-externals/-/webpack-node-externals-3.0.0.tgz", + "integrity": "sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/webpack-sources": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.4.tgz", + "integrity": "sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/es-module-lexer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", + "dev": true, + "license": "MIT" + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.1.0.tgz", + "integrity": "sha512-jlf/foYIKywAt3x/XWKZ/3rz8OSJPiWktjmk891alJUEjiVxKX9LEO92qH3hv4aJ0mN3MWPvGMCy8jQi95xK4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.18.tgz", + "integrity": "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/winston": { + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/winston/-/winston-2.4.7.tgz", + "integrity": "sha512-vLB4BqzCKDnnZH9PHGoS2ycawueX4HLqENXQitvFHczhgW2vFpSOn31LZtVr1KU8YTw7DS4tM+cqyovxo8taVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "async": "^2.6.4", + "colors": "1.0.x", + "cycle": "1.0.x", + "eyes": "0.1.x", + "isstream": "0.1.x", + "stack-trace": "0.0.x" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/winston/node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/winston/node_modules/colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "license": "MIT" + }, + "node_modules/worker-loader": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/worker-loader/-/worker-loader-3.0.8.tgz", + "integrity": "sha512-XQyQkIFeRVC7f7uRhFdNMe/iJOdO6zxAaR3EWbDp45v3mDhrTi+++oswKNxShUNjPC/1xUp5DB29YKLhFo129g==", + "dev": true, + "license": "MIT", + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/worker-loader/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/workerpool": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", + "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xdg-basedir": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz", + "integrity": "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xml-parse-from-string": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz", + "integrity": "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==", + "license": "MIT" + }, + "node_modules/xml2js": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/xpath": { + "version": "0.0.34", + "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.34.tgz", + "integrity": "sha512-FxF6+rkr1rNSQrhUNYrAFJpRXNzlDoMxeXN5qI84939ylEv3qqPFKa85Oxr6tDaJKqwW6KKyo2v26TSv3k6LeA==", + "license": "MIT", + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/xregexp": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-5.1.2.tgz", + "integrity": "sha512-6hGgEMCGhqCTFEJbqmWrNIPqfpdirdGWkqshu7fFZddmTSfgv5Sn9D2SaKloR79s5VUiUlpwzg3CM3G6D3VIlw==", + "license": "MIT", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.9" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser/node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/yargs-unparser/node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zip-stream": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", + "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "archiver-utils": "^3.0.4", + "compress-commons": "^4.1.2", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/archiver-utils": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz", + "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob": "^7.2.3", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/zip-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/zlibjs": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/zlibjs/-/zlibjs-0.3.1.tgz", + "integrity": "sha512-+J9RrgTKOmlxFSDHo0pI1xM6BLVUv+o0ZT9ANtCxGkjIVCCUdx9alUF8Gm+dGLKbkkkidWIHFDZHDMpfITt4+w==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/plugins/srktoolbox/package.json b/plugins/srktoolbox/package.json new file mode 100644 index 00000000..52a5a458 --- /dev/null +++ b/plugins/srktoolbox/package.json @@ -0,0 +1,216 @@ +{ + "name": "srktoolbox", + "version": "10.22.1", + "description": "SRK Toolbox for ztools - a web plugin (based on CyberChef) for encryption, encoding, compression and data analysis, translated to Chinese locale", + "author": "Raka-loah&&LAOBILAXI233", + "homepage": "https://github.com/Raka-loah/SRK-Toolbox", + "copyright": "Crown copyright 2016 / Zack Zhou copyright 2026", + "license": "Apache-2.0", + "keywords": [ + "cipher", + "cypher", + "encode", + "decode", + "encrypt", + "decrypt", + "base64", + "xor", + "charset", + "hex", + "encoding", + "format", + "cybersecurity", + "data manipulation", + "data analysis" + ], + "repository": { + "type": "git", + "url": "https://github.com/Raka-loah/SRK-Toolbox" + }, + "main": "src/node/wrapper.js", + "exports": { + "import": "./src/node/index.mjs", + "require": "./src/node/wrapper.js" + }, + "bugs": "https://github.com/Raka-loah/SRK-Toolbox/issues", + "browserslist": [ + "Chrome >= 50", + "Firefox >= 38", + "node >= 16" + ], + "devDependencies": { + "@babel/core": "^7.29.0", + "@babel/eslint-parser": "^7.28.6", + "@babel/plugin-syntax-import-assertions": "^7.28.6", + "@babel/plugin-transform-runtime": "^7.29.0", + "@babel/preset-env": "^7.29.0", + "@babel/runtime": "^7.28.6", + "@codemirror/commands": "^6.10.2", + "@codemirror/language": "^6.12.1", + "@codemirror/search": "^6.6.0", + "@codemirror/state": "^6.5.4", + "@codemirror/view": "^6.39.15", + "autoprefixer": "^10.4.24", + "babel-loader": "^10.0.0", + "babel-plugin-dynamic-import-node": "^2.3.3", + "babel-plugin-transform-builtin-extend": "1.1.2", + "base64-loader": "^1.0.0", + "chromedriver": "^143.0.4", + "cli-progress": "^3.12.0", + "colors": "^1.4.0", + "compression-webpack-plugin": "^11.1.0", + "copy-webpack-plugin": "^13.0.1", + "core-js": "^3.48.0", + "cspell": "^8.19.4", + "css-loader": "7.1.4", + "eslint": "^9.39.3", + "eslint-plugin-jsdoc": "^48.11.0", + "globals": "^15.15.0", + "grunt": "^1.6.1", + "grunt-chmod": "~1.1.1", + "grunt-concurrent": "^3.0.0", + "grunt-contrib-clean": "~2.0.1", + "grunt-contrib-connect": "^5.0.1", + "grunt-contrib-copy": "~1.0.0", + "grunt-contrib-watch": "^1.1.0", + "grunt-eslint": "^25.0.0", + "grunt-exec": "~3.0.0", + "grunt-webpack": "^6.0.0", + "grunt-zip": "^1.0.0", + "html-webpack-plugin": "^5.6.6", + "imports-loader": "^5.0.0", + "mini-css-extract-plugin": "2.10.0", + "modify-source-webpack-plugin": "^4.1.0", + "nightwatch": "^3.15.0", + "postcss": "^8.5.6", + "postcss-css-variables": "^0.19.0", + "postcss-import": "^16.1.1", + "postcss-loader": "^8.2.1", + "prompt": "^1.3.0", + "sitemap": "^8.0.2", + "terser": "^5.46.0", + "webpack": "^5.105.2", + "webpack-bundle-analyzer": "^4.10.2", + "webpack-dev-server": "5.0.4", + "webpack-node-externals": "^3.0.0", + "worker-loader": "^3.0.8" + }, + "dependencies": { + "14": "^3.1.6", + "@astronautlabs/amf": "^0.0.6", + "@babel/polyfill": "^7.12.1", + "@blu3r4y/lzma": "^2.3.3", + "@wavesenterprise/crypto-gost-js": "^2.1.0-RC1", + "@xmldom/xmldom": "^0.8.11", + "argon2-browser": "^1.18.0", + "arrive": "^2.5.2", + "assert": "^2.1.0", + "avsc": "^5.7.9", + "bcryptjs": "^2.4.3", + "bignumber.js": "^9.3.1", + "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.1", + "crypto-js": "^4.2.0", + "ctph.js": "0.0.5", + "d3": "7.9.0", + "d3-hexbin": "^0.2.2", + "diff": "^5.2.2", + "dompurify": "^3.3.1", + "es6-promisify": "^7.0.0", + "escodegen": "^2.1.0", + "esprima": "^4.0.1", + "events": "^3.3.0", + "exif-parser": "^0.1.12", + "fernet": "^0.3.3", + "file-saver": "^2.0.5", + "flat": "^6.0.1", + "geodesy": "1.1.3", + "handlebars": "^4.7.8", + "hash-wasm": "^4.12.0", + "highlight.js": "^11.11.1", + "ieee754": "^1.2.1", + "jimp": "^1.6.0", + "jq-web": "^0.6.2", + "jquery": "3.7.1", + "js-sha3": "^0.9.3", + "jsesc": "^3.1.0", + "json5": "^2.2.3", + "jsonata": "^2.1.0", + "jsonpath-plus": "^10.4.0", + "jsonwebtoken": "8.5.1", + "jsqr": "^1.4.0", + "jsrsasign": "^11.1.1", + "kbpgp": "^2.1.17", + "libbzip2-wasm": "0.0.4", + "libyara-wasm": "^1.2.1", + "lodash": "^4.17.23", + "loglevel": "^1.9.2", + "loglevel-message-prefix": "^3.0.0", + "lz-string": "^1.5.0", + "lz4js": "^0.2.0", + "markdown-it": "^14.1.1", + "moment": "^2.30.1", + "moment-timezone": "^0.6.0", + "ngeohash": "^0.6.3", + "node-forge": "^1.3.3", + "node-md6": "^0.1.0", + "nodom": "^2.4.0", + "notepack.io": "^3.0.1", + "ntlm": "^0.1.3", + "nwmatcher": "^1.4.4", + "otpauth": "9.3.6", + "path": "^0.12.7", + "popper.js": "^1.16.1", + "process": "^0.11.10", + "protobufjs": "^7.5.4", + "qr-image": "^3.2.0", + "reflect-metadata": "^0.2.2", + "rison": "^0.1.1", + "scryptsy": "^2.1.0", + "snackbarjs": "^1.1.0", + "sortablejs": "^1.15.7", + "split.js": "^1.6.5", + "ssdeep.js": "0.0.3", + "stream-browserify": "^3.0.0", + "tesseract.js": "5.1.1", + "toastr": "^2.1.4", + "ua-parser-js": "^1.0.41", + "unorm": "^1.6.0", + "url": "^0.11.4", + "utf8": "^3.0.0", + "uuid": "^13.0.0", + "vkbeautify": "^0.99.3", + "xpath": "0.0.34", + "xregexp": "^5.1.2", + "zlibjs": "^0.3.1" + }, + "scripts": { + "start": "npx grunt dev", + "build": "npx grunt prod", + "build:ztools": "npx grunt ztools", + "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", + "lint:grammar": "cspell ./src", + "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 && npm version minor --git-tag-version=false && echo \"Updated to version v$(npm pkg get version | xargs), please create a pull request and once merged use 'npm run tag'\"", + "tag": "git tag -s \"v$(npm pkg get version | xargs)\" -m \"$(npm pkg get version | xargs)\" && echo \"Created v$(npm pkg get version | xargs), now check and push the tag\"", + "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" + } +} diff --git a/plugins/srktoolbox/plugin.json b/plugins/srktoolbox/plugin.json new file mode 100644 index 00000000..19878db7 --- /dev/null +++ b/plugins/srktoolbox/plugin.json @@ -0,0 +1,31 @@ +{ + "name": "srktoolbox", + "title": "SRK Toolbox for Ztools", + "description": "基于 CyberChef 的中文安全工具箱,支持编码、加密、压缩和数据分析。", + "author": "Raka-loah&&LAOBILAXI233", + "version": "10.22.1", + "main": "index.html", + "logo": "logo.png", + "pluginSetting": { + "height": 900 + }, + "development": { + "main": "http://localhost:54333" + }, + "features": [ + { + "code": "srk-toolbox", + "explain": "打开 SRK Toolbox 工具箱", + "icon": "logo.png", + "cmds": [ + "SRK Toolbox", + "srk", + "toolbox", + "工具箱", + "密码工具", + "编码转换", + "CyberChef" + ] + } + ] +} diff --git a/plugins/srktoolbox/postcss.config.js b/plugins/srktoolbox/postcss.config.js new file mode 100644 index 00000000..2d46543d --- /dev/null +++ b/plugins/srktoolbox/postcss.config.js @@ -0,0 +1,9 @@ +module.exports = { + plugins: [ + require("postcss-import"), + require("autoprefixer"), + require("postcss-css-variables")({ + preserve: true + }), + ] +}; diff --git a/plugins/srktoolbox/public/logo.png b/plugins/srktoolbox/public/logo.png new file mode 100644 index 00000000..753549aa Binary files /dev/null and b/plugins/srktoolbox/public/logo.png differ diff --git a/plugins/srktoolbox/scripts/fixCryptoApiImports.cjs b/plugins/srktoolbox/scripts/fixCryptoApiImports.cjs new file mode 100644 index 00000000..2c59b084 --- /dev/null +++ b/plugins/srktoolbox/scripts/fixCryptoApiImports.cjs @@ -0,0 +1,25 @@ +const fs = require("fs"); +const path = require("path"); + +const root = path.join("node_modules", "crypto-api", "src"); + +function walk(dir) { + for (const name of fs.readdirSync(dir)) { + const file = path.join(dir, name); + const stat = fs.statSync(file); + + if (stat.isDirectory()) { + if (name !== ".git") walk(file); + } else { + const content = fs.readFileSync(file, "utf8"); + const next = content.replace(/from (["'])(\.[^"']+)(["'];)/g, function(match, quote, importPath, suffix) { + if (importPath.endsWith(".mjs")) return match; + return "from " + quote + importPath + ".mjs" + suffix; + }); + + if (next !== content) fs.writeFileSync(file, next); + } + } +} + +walk(root); diff --git a/plugins/srktoolbox/scripts/fixSnackbarMarkup.cjs b/plugins/srktoolbox/scripts/fixSnackbarMarkup.cjs new file mode 100644 index 00000000..b649b422 --- /dev/null +++ b/plugins/srktoolbox/scripts/fixSnackbarMarkup.cjs @@ -0,0 +1,7 @@ +const fs = require("fs"); + +const file = "./node_modules/snackbarjs/src/snackbar.js"; +const content = fs.readFileSync(file, "utf8"); +const next = content.replace("
", "
"); + +if (next !== content) fs.writeFileSync(file, next); diff --git a/plugins/srktoolbox/src/core/Chef.mjs b/plugins/srktoolbox/src/core/Chef.mjs new file mode 100644 index 00000000..ab8f83de --- /dev/null +++ b/plugins/srktoolbox/src/core/Chef.mjs @@ -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; diff --git a/plugins/srktoolbox/src/core/ChefWorker.js b/plugins/srktoolbox/src/core/ChefWorker.js new file mode 100644 index 00000000..a43993f9 --- /dev/null +++ b/plugins/srktoolbox/src/core/ChefWorker.js @@ -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 + } + }); +}; diff --git a/plugins/srktoolbox/src/core/Dish.mjs b/plugins/srktoolbox/src/core/Dish.mjs new file mode 100644 index 00000000..11b1ff9f --- /dev/null +++ b/plugins/srktoolbox/src/core/Dish.mjs @@ -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": + 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"; + 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; diff --git a/plugins/srktoolbox/src/core/Ingredient.mjs b/plugins/srktoolbox/src/core/Ingredient.mjs new file mode 100644 index 00000000..319dfb15 --- /dev/null +++ b/plugins/srktoolbox/src/core/Ingredient.mjs @@ -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; diff --git a/plugins/srktoolbox/src/core/Operation.mjs b/plugins/srktoolbox/src/core/Operation.mjs new file mode 100644 index 00000000..24739d3f --- /dev/null +++ b/plugins/srktoolbox/src/core/Operation.mjs @@ -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; diff --git a/plugins/srktoolbox/src/core/Recipe.mjs b/plugins/srktoolbox/src/core/Recipe.mjs new file mode 100644 index 00000000..9b80a4bc --- /dev/null +++ b/plugins/srktoolbox/src/core/Recipe.mjs @@ -0,0 +1,354 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github + */ + +import OperationConfig from "./config/OperationConfig.json" with {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 => { + try { + this.opList.push({ + name: c.op, + module: OperationConfig[c.op].module, + ingValues: c.args, + breakpoint: c.breakpoint, + disabled: c.disabled || c.op === "Comment", + }); + } catch (error) { + log.warn(`[WARNING] ${c.op} Not found: ${error}`); + } + + }); + } + + + /** + * 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(`执行中... (${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) { + log.error(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}.

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; diff --git a/plugins/srktoolbox/src/core/Utils.mjs b/plugins/srktoolbox/src/core/Utils.mjs new file mode 100644 index 00000000..9a265427 --- /dev/null +++ b/plugins/srktoolbox/src/core/Utils.mjs @@ -0,0 +1,1606 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github + */ + +// loglevel import required for Node API +import log from "loglevel"; +import utf8 from "utf8"; +import {fromBase64, toBase64} from "./lib/Base64.mjs"; +import {fromHex} from "./lib/Hex.mjs"; +import {fromDecimal} from "./lib/Decimal.mjs"; +import {fromBinary} from "./lib/Binary.mjs"; + +/** + * Utility functions for use in operations, the core framework and the stage. + */ +class Utils { + + /** + * Translates an ordinal into a character. + * + * @param {number} o + * @returns {char} + * + * @example + * // returns 'a' + * Utils.chr(97); + */ + static chr(o) { + // Detect astral symbols + // Thanks to @mathiasbynens for this solution + // https://mathiasbynens.be/notes/javascript-unicode + if (o > 0xffff) { + o -= 0x10000; + const high = String.fromCharCode(o >>> 10 & 0x3ff | 0xd800); + o = 0xdc00 | o & 0x3ff; + return high + String.fromCharCode(o); + } + + return String.fromCharCode(o); + } + + + /** + * Translates a character into an ordinal. + * + * @param {char} c + * @returns {number} + * + * @example + * // returns 97 + * Utils.ord('a'); + */ + static ord(c) { + // Detect astral symbols + // Thanks to @mathiasbynens for this solution + // https://mathiasbynens.be/notes/javascript-unicode + if (c.length === 2) { + const high = c.charCodeAt(0); + const low = c.charCodeAt(1); + if (high >= 0xd800 && high < 0xdc00 && + low >= 0xdc00 && low < 0xe000) { + return (high - 0xd800) * 0x400 + low - 0xdc00 + 0x10000; + } + } + + return c.charCodeAt(0); + } + + + /** + * Adds trailing bytes to a byteArray. + * + * @author tlwr [toby@toby.codes] + * + * @param {byteArray} arr - byteArray to add trailing bytes to. + * @param {number} numBytes - Maximum width of the array. + * @param {Integer} [padByte=0] - The byte to pad with. + * @returns {byteArray} + * + * @example + * // returns ["a", 0, 0, 0] + * Utils.padBytesRight("a", 4); + * + * // returns ["a", 1, 1, 1] + * Utils.padBytesRight("a", 4, 1); + * + * // returns ["t", "e", "s", "t", 0, 0, 0, 0] + * Utils.padBytesRight("test", 8); + * + * // returns ["t", "e", "s", "t", 1, 1, 1, 1] + * Utils.padBytesRight("test", 8, 1); + */ + static padBytesRight(arr, numBytes, padByte=0) { + const paddedBytes = new Array(numBytes); + paddedBytes.fill(padByte); + + [...arr].forEach((b, i) => { + paddedBytes[i] = b; + }); + + return paddedBytes; + } + + + /** + * Truncates a long string to max length and adds suffix. + * + * @param {string} str - String to truncate + * @param {number} max - Maximum length of the final string + * @param {string} [suffix='...'] - The string to add to the end of the final string + * @returns {string} + * + * @example + * // returns "A long..." + * Utils.truncate("A long string", 9); + * + * // returns "A long s-" + * Utils.truncate("A long string", 9, "-"); + */ + static truncate(str, max, suffix="...") { + if (str.length > max) { + str = str.slice(0, max - suffix.length) + suffix; + } + return str; + } + + + /** + * Converts a character or number to its hex representation. + * + * @param {char|number} c + * @param {number} [length=2] - The width of the resulting hex number. + * @returns {string} + * + * @example + * // returns "6e" + * Utils.hex("n"); + * + * // returns "6e" + * Utils.hex(110); + */ + static hex(c, length=2) { + c = typeof c == "string" ? Utils.ord(c) : c; + return c.toString(16).padStart(length, "0"); + } + + + /** + * Converts a character or number to its binary representation. + * + * @param {char|number} c + * @param {number} [length=8] - The width of the resulting binary number. + * @returns {string} + * + * @example + * // returns "01101110" + * Utils.bin("n"); + * + * // returns "01101110" + * Utils.bin(110); + */ + static bin(c, length=8) { + c = typeof c == "string" ? Utils.ord(c) : c; + return c.toString(2).padStart(length, "0"); + } + + + /** + * Returns a string with all non-printable chars as dots, optionally preserving whitespace. + * + * @param {string} str - The input string to display. + * @param {boolean} [preserveWs=false] - Whether or not to print whitespace. + * @param {boolean} [onlyAscii=false] - Whether or not to replace non ASCII characters. + * @returns {string} + */ + static printable(str, preserveWs=false, onlyAscii=false) { + if (onlyAscii) { + return str.replace(/[^\x20-\x7e]/g, "."); + } + + // eslint-disable-next-line no-misleading-character-class + const re = /[\0-\x08\x0B-\x0C\x0E-\x1F\x7F-\x9F\xAD\u0378\u0379\u037F-\u0383\u038B\u038D\u03A2\u0528-\u0530\u0557\u0558\u0560\u0588\u058B-\u058E\u0590\u05C8-\u05CF\u05EB-\u05EF\u05F5-\u0605\u061C\u061D\u06DD\u070E\u070F\u074B\u074C\u07B2-\u07BF\u07FB-\u07FF\u082E\u082F\u083F\u085C\u085D\u085F-\u089F\u08A1\u08AD-\u08E3\u08FF\u0978\u0980\u0984\u098D\u098E\u0991\u0992\u09A9\u09B1\u09B3-\u09B5\u09BA\u09BB\u09C5\u09C6\u09C9\u09CA\u09CF-\u09D6\u09D8-\u09DB\u09DE\u09E4\u09E5\u09FC-\u0A00\u0A04\u0A0B-\u0A0E\u0A11\u0A12\u0A29\u0A31\u0A34\u0A37\u0A3A\u0A3B\u0A3D\u0A43-\u0A46\u0A49\u0A4A\u0A4E-\u0A50\u0A52-\u0A58\u0A5D\u0A5F-\u0A65\u0A76-\u0A80\u0A84\u0A8E\u0A92\u0AA9\u0AB1\u0AB4\u0ABA\u0ABB\u0AC6\u0ACA\u0ACE\u0ACF\u0AD1-\u0ADF\u0AE4\u0AE5\u0AF2-\u0B00\u0B04\u0B0D\u0B0E\u0B11\u0B12\u0B29\u0B31\u0B34\u0B3A\u0B3B\u0B45\u0B46\u0B49\u0B4A\u0B4E-\u0B55\u0B58-\u0B5B\u0B5E\u0B64\u0B65\u0B78-\u0B81\u0B84\u0B8B-\u0B8D\u0B91\u0B96-\u0B98\u0B9B\u0B9D\u0BA0-\u0BA2\u0BA5-\u0BA7\u0BAB-\u0BAD\u0BBA-\u0BBD\u0BC3-\u0BC5\u0BC9\u0BCE\u0BCF\u0BD1-\u0BD6\u0BD8-\u0BE5\u0BFB-\u0C00\u0C04\u0C0D\u0C11\u0C29\u0C34\u0C3A-\u0C3C\u0C45\u0C49\u0C4E-\u0C54\u0C57\u0C5A-\u0C5F\u0C64\u0C65\u0C70-\u0C77\u0C80\u0C81\u0C84\u0C8D\u0C91\u0CA9\u0CB4\u0CBA\u0CBB\u0CC5\u0CC9\u0CCE-\u0CD4\u0CD7-\u0CDD\u0CDF\u0CE4\u0CE5\u0CF0\u0CF3-\u0D01\u0D04\u0D0D\u0D11\u0D3B\u0D3C\u0D45\u0D49\u0D4F-\u0D56\u0D58-\u0D5F\u0D64\u0D65\u0D76-\u0D78\u0D80\u0D81\u0D84\u0D97-\u0D99\u0DB2\u0DBC\u0DBE\u0DBF\u0DC7-\u0DC9\u0DCB-\u0DCE\u0DD5\u0DD7\u0DE0-\u0DF1\u0DF5-\u0E00\u0E3B-\u0E3E\u0E5C-\u0E80\u0E83\u0E85\u0E86\u0E89\u0E8B\u0E8C\u0E8E-\u0E93\u0E98\u0EA0\u0EA4\u0EA6\u0EA8\u0EA9\u0EAC\u0EBA\u0EBE\u0EBF\u0EC5\u0EC7\u0ECE\u0ECF\u0EDA\u0EDB\u0EE0-\u0EFF\u0F48\u0F6D-\u0F70\u0F98\u0FBD\u0FCD\u0FDB-\u0FFF\u10C6\u10C8-\u10CC\u10CE\u10CF\u1249\u124E\u124F\u1257\u1259\u125E\u125F\u1289\u128E\u128F\u12B1\u12B6\u12B7\u12BF\u12C1\u12C6\u12C7\u12D7\u1311\u1316\u1317\u135B\u135C\u137D-\u137F\u139A-\u139F\u13F5-\u13FF\u169D-\u169F\u16F1-\u16FF\u170D\u1715-\u171F\u1737-\u173F\u1754-\u175F\u176D\u1771\u1774-\u177F\u17DE\u17DF\u17EA-\u17EF\u17FA-\u17FF\u180F\u181A-\u181F\u1878-\u187F\u18AB-\u18AF\u18F6-\u18FF\u191D-\u191F\u192C-\u192F\u193C-\u193F\u1941-\u1943\u196E\u196F\u1975-\u197F\u19AC-\u19AF\u19CA-\u19CF\u19DB-\u19DD\u1A1C\u1A1D\u1A5F\u1A7D\u1A7E\u1A8A-\u1A8F\u1A9A-\u1A9F\u1AAE-\u1AFF\u1B4C-\u1B4F\u1B7D-\u1B7F\u1BF4-\u1BFB\u1C38-\u1C3A\u1C4A-\u1C4C\u1C80-\u1CBF\u1CC8-\u1CCF\u1CF7-\u1CFF\u1DE7-\u1DFB\u1F16\u1F17\u1F1E\u1F1F\u1F46\u1F47\u1F4E\u1F4F\u1F58\u1F5A\u1F5C\u1F5E\u1F7E\u1F7F\u1FB5\u1FC5\u1FD4\u1FD5\u1FDC\u1FF0\u1FF1\u1FF5\u1FFF\u200B-\u200F\u202A-\u202E\u2060-\u206F\u2072\u2073\u208F\u209D-\u209F\u20BB-\u20CF\u20F1-\u20FF\u218A-\u218F\u23F4-\u23FF\u2427-\u243F\u244B-\u245F\u2700\u2B4D-\u2B4F\u2B5A-\u2BFF\u2C2F\u2C5F\u2CF4-\u2CF8\u2D26\u2D28-\u2D2C\u2D2E\u2D2F\u2D68-\u2D6E\u2D71-\u2D7E\u2D97-\u2D9F\u2DA7\u2DAF\u2DB7\u2DBF\u2DC7\u2DCF\u2DD7\u2DDF\u2E3C-\u2E7F\u2E9A\u2EF4-\u2EFF\u2FD6-\u2FEF\u2FFC-\u2FFF\u3040\u3097\u3098\u3100-\u3104\u312E-\u3130\u318F\u31BB-\u31BF\u31E4-\u31EF\u321F\u32FF\u4DB6-\u4DBF\u9FCD-\u9FFF\uA48D-\uA48F\uA4C7-\uA4CF\uA62C-\uA63F\uA698-\uA69E\uA6F8-\uA6FF\uA78F\uA794-\uA79F\uA7AB-\uA7F7\uA82C-\uA82F\uA83A-\uA83F\uA878-\uA87F\uA8C5-\uA8CD\uA8DA-\uA8DF\uA8FC-\uA8FF\uA954-\uA95E\uA97D-\uA97F\uA9CE\uA9DA-\uA9DD\uA9E0-\uA9FF\uAA37-\uAA3F\uAA4E\uAA4F\uAA5A\uAA5B\uAA7C-\uAA7F\uAAC3-\uAADA\uAAF7-\uAB00\uAB07\uAB08\uAB0F\uAB10\uAB17-\uAB1F\uAB27\uAB2F-\uABBF\uABEE\uABEF\uABFA-\uABFF\uD7A4-\uD7AF\uD7C7-\uD7CA\uD7FC-\uD7FF\uE000-\uF8FF\uFA6E\uFA6F\uFADA-\uFAFF\uFB07-\uFB12\uFB18-\uFB1C\uFB37\uFB3D\uFB3F\uFB42\uFB45\uFBC2-\uFBD2\uFD40-\uFD4F\uFD90\uFD91\uFDC8-\uFDEF\uFDFE\uFDFF\uFE1A-\uFE1F\uFE27-\uFE2F\uFE53\uFE67\uFE6C-\uFE6F\uFE75\uFEFD-\uFF00\uFFBF-\uFFC1\uFFC8\uFFC9\uFFD0\uFFD1\uFFD8\uFFD9\uFFDD-\uFFDF\uFFE7\uFFEF-\uFFFB\uFFFE\uFFFF]/g; + const wsRe = /[\x09-\x10\u2028\u2029]/g; + + str = str.replace(re, "."); + if (!preserveWs) str = str.replace(wsRe, "."); + return str; + } + + + /** + * Returns a string with whitespace represented as special characters from the + * Unicode Private Use Area, which CyberChef will display as control characters. + * Private Use Area characters are in the range U+E000..U+F8FF. + * https://en.wikipedia.org/wiki/Private_Use_Areas + * @param {string} str + * @returns {string} + */ + static escapeWhitespace(str) { + return str.replace(/[\x09-\x10]/g, function(c) { + return String.fromCharCode(0xe000 + c.charCodeAt(0)); + }); + } + + + /** + * Parse a string entered by a user and replace escaped chars with the bytes they represent. + * + * @param {string} str + * @returns {string} + * + * @example + * // returns "\x00" + * Utils.parseEscapedChars("\\x00"); + * + * // returns "\n" + * Utils.parseEscapedChars("\\n"); + */ + static parseEscapedChars(str) { + return str.replace(/\\([abfnrtv'"]|[0-3][0-7]{2}|[0-7]{1,2}|x[\da-fA-F]{2}|u[\da-fA-F]{4}|u\{[\da-fA-F]{1,6}\}|\\)/g, function(m, a) { + switch (a[0]) { + case "\\": + return "\\"; + case "0": + case "1": + case "2": + case "3": + case "4": + case "5": + case "6": + case "7": + return String.fromCharCode(parseInt(a, 8)); + case "a": + return String.fromCharCode(7); + case "b": + return "\b"; + case "t": + return "\t"; + case "n": + return "\n"; + case "v": + return "\v"; + case "f": + return "\f"; + case "r": + return "\r"; + case '"': + return '"'; + case "'": + return "'"; + case "x": + return String.fromCharCode(parseInt(a.substr(1), 16)); + case "u": + if (a[1] === "{") + return String.fromCodePoint(parseInt(a.slice(2, -1), 16)); + else + return String.fromCharCode(parseInt(a.substr(1), 16)); + } + }); + } + + + /** + * Escape a string containing regex control characters so that it can be safely + * used in a regex without causing unintended behaviours. + * + * @param {string} str + * @returns {string} + * + * @example + * // returns "\[example\]" + * Utils.escapeRegex("[example]"); + */ + static escapeRegex(str) { + return str.replace(/([.*+?^=!:${}()|[\]/\\])/g, "\\$1"); + } + + + /** + * Expand an alphabet range string into a list of the characters in that range. + * + * @param {string} alphStr + * @returns {char[]} + * + * @example + * // returns ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"] + * Utils.expandAlphRange("0-9"); + * + * // returns ["a", "b", "c", "d", "0", "1", "2", "3", "+", "/"] + * Utils.expandAlphRange("a-d0-3+/"); + * + * // returns ["a", "b", "c", "d", "0", "-", "3"] + * Utils.expandAlphRange("a-d0\\-3") + */ + static expandAlphRange(alphStr) { + const alphArr = []; + + for (let i = 0; i < alphStr.length; i++) { + if (i < alphStr.length - 2 && + alphStr[i+1] === "-" && + alphStr[i] !== "\\") { + const start = Utils.ord(alphStr[i]), + end = Utils.ord(alphStr[i+2]); + + for (let j = start; j <= end; j++) { + alphArr.push(Utils.chr(j)); + } + i += 2; + } else if (i < alphStr.length - 2 && + alphStr[i] === "\\" && + alphStr[i+1] === "-") { + alphArr.push("-"); + i++; + } else { + alphArr.push(alphStr[i]); + } + } + return alphArr; + } + + + /** + * Coverts data of varying types to a byteArray. + * Accepts hex, Base64, UTF8 and Latin1 strings. + * + * @param {string} str + * @param {string} type - One of "Binary", "Hex", "Decimal", "Base64", "UTF8" or "Latin1" + * @returns {byteArray} + * + * @example + * // returns [208, 159, 209, 128, 208, 184, 208, 178, 208, 181, 209, 130] + * Utils.convertToByteArray("Привет", "utf8"); + * + * // returns [208, 159, 209, 128, 208, 184, 208, 178, 208, 181, 209, 130] + * Utils.convertToByteArray("d097d0b4d180d0b0d0b2d181d182d0b2d183d0b9d182d0b5", "hex"); + * + * // returns [208, 159, 209, 128, 208, 184, 208, 178, 208, 181, 209, 130] + * Utils.convertToByteArray("0JfQtNGA0LDQstGB0YLQstGD0LnRgtC1", "base64"); + */ + static convertToByteArray(str, type) { + switch (type.toLowerCase()) { + case "binary": + return fromBinary(str); + case "hex": + return fromHex(str); + case "十六进制": + return fromHex(str); + case "decimal": + return fromDecimal(str); + case "十进制": + return fromDecimal(str); + case "base64": + return fromBase64(str, null, "byteArray"); + case "utf8": + return Utils.strToUtf8ByteArray(str); + case "latin1": + default: + return Utils.strToByteArray(str); + } + } + + + /** + * Coverts data of varying types to a byte string. + * Accepts hex, Base64, UTF8 and Latin1 strings. + * + * @param {string} str + * @param {string} type - One of "Binary", "Hex", "Decimal", "Base64", "UTF8" or "Latin1" + * @returns {string} + * + * @example + * // returns "Привет" + * Utils.convertToByteString("Привет", "utf8"); + * + * // returns "Здравствуйте" + * Utils.convertToByteString("d097d0b4d180d0b0d0b2d181d182d0b2d183d0b9d182d0b5", "hex"); + * + * // returns "Здравствуйте" + * Utils.convertToByteString("0JfQtNGA0LDQstGB0YLQstGD0LnRgtC1", "base64"); + */ + static convertToByteString(str, type) { + switch (type.toLowerCase()) { + case "binary": + return Utils.byteArrayToChars(fromBinary(str)); + case "二进制": + return Utils.byteArrayToChars(fromBinary(str)); + case "十六进制": + return Utils.byteArrayToChars(fromHex(str)); + case "hex": + return Utils.byteArrayToChars(fromHex(str)); + case "十进制": + return Utils.byteArrayToChars(fromDecimal(str)); + case "decimal": + return Utils.byteArrayToChars(fromDecimal(str)); + case "base64": + return Utils.byteArrayToChars(fromBase64(str, null, "byteArray")); + case "utf8": + return utf8.encode(str); + case "latin1": + default: + return str; + } + } + + + /** + * Converts a byte array to an integer. + * + * @param {byteArray} byteArray + * @param {string} byteorder - "little" or "big" + * @returns {integer} + * + * @example + * // returns 67305985 + * Utils.byteArrayToInt([1, 2, 3, 4], "little"); + * + * // returns 16909060 + * Utils.byteArrayToInt([1, 2, 3, 4], "big"); + */ + static byteArrayToInt(byteArray, byteorder) { + let value = 0; + if (byteorder === "big") { + for (let i = 0; i < byteArray.length; i++) { + value = (value * 256) + byteArray[i]; + } + } else { + for (let i = byteArray.length - 1; i >= 0; i--) { + value = (value * 256) + byteArray[i]; + } + } + return value; + } + + + /** + * Converts an integer to a byte array of {length} bytes. + * + * @param {integer} value + * @param {integer} length + * @param {string} byteorder - "little" or "big" + * @returns {byteArray} + * + * @example + * // returns [5, 255, 109, 1] + * Utils.intToByteArray(23985925, 4, "little"); + * + * // returns [1, 109, 255, 5] + * Utils.intToByteArray(23985925, 4, "big"); + * + * // returns [0, 0, 0, 0, 1, 109, 255, 5] + * Utils.intToByteArray(23985925, 8, "big"); + */ + static intToByteArray(value, length, byteorder) { + const arr = new Array(length); + if (byteorder === "little") { + for (let i = 0; i < length; i++) { + arr[i] = value & 0xFF; + value = value >>> 8; + } + } else { + for (let i = length - 1; i >= 0; i--) { + arr[i] = value & 0xFF; + value = value >>> 8; + } + } + return arr; + } + + + /** + * Converts a string to an ArrayBuffer. + * Treats the string as UTF-8 if any values are over 255. + * + * @param {string} str + * @returns {ArrayBuffer} + * + * @example + * // returns [72,101,108,108,111] + * Utils.strToArrayBuffer("Hello"); + * + * // returns [228,189,160,229,165,189] + * Utils.strToArrayBuffer("你好"); + */ + static strToArrayBuffer(str) { + log.debug(`Converting string[${str?.length}] to array buffer`); + if (!str) return new ArrayBuffer; + + const arr = new Uint8Array(str.length); + let i = str.length, b; + while (i--) { + b = str.charCodeAt(i); + arr[i] = b; + // If any of the bytes are over 255, read as UTF-8 + if (b > 255) return Utils.strToUtf8ArrayBuffer(str); + } + return arr.buffer; + } + + + /** + * Converts a string to a UTF-8 ArrayBuffer. + * + * @param {string} str + * @returns {ArrayBuffer} + * + * @example + * // returns [72,101,108,108,111] + * Utils.strToUtf8ArrayBuffer("Hello"); + * + * // returns [228,189,160,229,165,189] + * Utils.strToUtf8ArrayBuffer("你好"); + */ + static strToUtf8ArrayBuffer(str) { + log.debug(`Converting string[${str?.length}] to UTF8 array buffer`); + if (!str) return new ArrayBuffer; + + const buffer = new TextEncoder("utf-8").encode(str); + + if (str.length !== buffer.length) { + if (isWorkerEnvironment() && self && typeof self.setOption === "function") { + self.setOption("attemptHighlight", false); + } else if (isWebEnvironment()) { + window.app.options.attemptHighlight = false; + } + } + + return buffer.buffer; + } + + + /** + * Converts a string to a byte array. + * Treats the string as UTF-8 if any values are over 255. + * + * @param {string} str + * @returns {byteArray} + * + * @example + * // returns [72,101,108,108,111] + * Utils.strToByteArray("Hello"); + * + * // returns [228,189,160,229,165,189] + * Utils.strToByteArray("你好"); + */ + static strToByteArray(str) { + log.debug(`Converting string[${str?.length}] to byte array`); + if (!str) return []; + const byteArray = new Array(str.length); + let i = str.length, b; + while (i--) { + b = str.charCodeAt(i); + byteArray[i] = b; + // If any of the bytes are over 255, read as UTF-8 + if (b > 255) return Utils.strToUtf8ByteArray(str); + } + return byteArray; + } + + + /** + * Converts a string to a UTF-8 byte array. + * + * @param {string} str + * @returns {byteArray} + * + * @example + * // returns [72,101,108,108,111] + * Utils.strToUtf8ByteArray("Hello"); + * + * // returns [228,189,160,229,165,189] + * Utils.strToUtf8ByteArray("你好"); + */ + static strToUtf8ByteArray(str) { + log.debug(`Converting string[${str?.length}] to UTF8 byte array`); + if (!str) return []; + const utf8Str = utf8.encode(str); + + if (str.length !== utf8Str.length) { + if (isWorkerEnvironment()) { + self.setOption("attemptHighlight", false); + } else if (isWebEnvironment()) { + window.app.options.attemptHighlight = false; + } + } + + return Utils.strToByteArray(utf8Str); + } + + + /** + * Converts a string to a unicode charcode array + * + * @param {string} str + * @returns {byteArray} + * + * @example + * // returns [72,101,108,108,111] + * Utils.strToCharcode("Hello"); + * + * // returns [20320,22909] + * Utils.strToCharcode("你好"); + */ + static strToCharcode(str) { + log.debug(`Converting string[${str?.length}] to charcode`); + if (!str) return []; + const charcode = []; + + for (let i = 0; i < str.length; i++) { + let ord = str.charCodeAt(i); + + // Detect and merge astral symbols + if (i < str.length - 1 && ord >= 0xd800 && ord < 0xdc00) { + const low = str[i + 1].charCodeAt(0); + if (low >= 0xdc00 && low < 0xe000) { + ord = Utils.ord(str[i] + str[++i]); + } + } + + charcode.push(ord); + } + + return charcode; + } + + + /** + * Attempts to convert a byte array to a UTF-8 string. + * + * @param {byteArray|Uint8Array} byteArray + * @returns {string} + * + * @example + * // returns "Hello" + * Utils.byteArrayToUtf8([72,101,108,108,111]); + * + * // returns "你好" + * Utils.byteArrayToUtf8([228,189,160,229,165,189]); + */ + static byteArrayToUtf8(byteArray) { + log.debug(`Converting byte array[${byteArray?.length}] to UTF8`); + if (!byteArray || !byteArray.length) return ""; + if (!(byteArray instanceof Uint8Array)) + byteArray = new Uint8Array(byteArray); + + try { + const str = new TextDecoder("utf-8", {fatal: true}).decode(byteArray); + + if (str.length !== byteArray.length) { + if (isWorkerEnvironment()) { + self.setOption("attemptHighlight", false); + } else if (isWebEnvironment()) { + window.app.options.attemptHighlight = false; + } + } + + return str; + } catch (err) { + // If it fails, treat it as ANSI + return Utils.byteArrayToChars(byteArray); + } + } + + + /** + * Converts a charcode array to a string. + * + * @param {byteArray|Uint8Array} byteArray + * @returns {string} + * + * @example + * // returns "Hello" + * Utils.byteArrayToChars([72,101,108,108,111]); + * + * // returns "你好" + * Utils.byteArrayToChars([20320,22909]); + */ + static byteArrayToChars(byteArray) { + log.debug(`Converting byte array[${byteArray?.length}] to chars`); + if (!byteArray || !byteArray.length) return ""; + let str = ""; + // Maxiumum arg length for fromCharCode is 65535, but the stack may already be fairly deep, + // so don't get too near it. + for (let i = 0; i < byteArray.length; i += 20000) { + str += String.fromCharCode(...(byteArray.slice(i, i+20000))); + } + return str; + } + + + /** + * Converts an ArrayBuffer to a string. + * + * @param {ArrayBuffer} arrayBuffer + * @param {boolean} [utf8=true] - Whether to attempt to decode the buffer as UTF-8 + * @returns {string} + * + * @example + * // returns "hello" + * Utils.arrayBufferToStr(Uint8Array.from([104,101,108,108,111]).buffer); + */ + static arrayBufferToStr(arrayBuffer, utf8=true) { + log.debug(`Converting array buffer[${arrayBuffer?.byteLength}] to str`); + if (!arrayBuffer || !arrayBuffer.byteLength) return ""; + const arr = new Uint8Array(arrayBuffer); + return utf8 ? Utils.byteArrayToUtf8(arr) : Utils.byteArrayToChars(arr); + } + + /** + * Calculates the Shannon entropy for a given set of data. + * + * @param {Uint8Array|ArrayBuffer} input + * @returns {number} + */ + static calculateShannonEntropy(data) { + if (data instanceof ArrayBuffer) { + data = new Uint8Array(data); + } + const prob = [], + occurrences = new Array(256).fill(0); + + // Count occurrences of each byte in the input + let i; + for (i = 0; i < data.length; i++) { + occurrences[data[i]]++; + } + + // Store probability list + for (i = 0; i < occurrences.length; i++) { + if (occurrences[i] > 0) { + prob.push(occurrences[i] / data.length); + } + } + + // Calculate Shannon entropy + let entropy = 0, + p; + + for (i = 0; i < prob.length; i++) { + p = prob[i]; + entropy += p * Math.log(p) / Math.log(2); + } + + return -entropy; + } + + + /** + * Parses CSV data and returns it as a two dimensional array or strings. + * + * @param {string} data + * @param {string[]} [cellDelims=[","]] + * @param {string[]} [lineDelims=["\n", "\r"]] + * @returns {string[][]} + * + * @example + * // returns [["head1", "head2"], ["data1", "data2"]] + * Utils.parseCSV("head1,head2\ndata1,data2"); + */ + static parseCSV(data, cellDelims=[","], lineDelims=["\n", "\r"]) { + let b, + next, + renderNext = false, + inString = false, + cell = "", + line = []; + const lines = []; + + // Remove BOM, often present in Excel CSV files + if (data.length && data[0] === "\uFEFF") data = data.substr(1); + + for (let i = 0; i < data.length; i++) { + b = data[i]; + next = data[i+1] || ""; + if (renderNext) { + cell += b; + renderNext = false; + } else if (b === "\"" && !inString) { + inString = true; + } else if (b === "\"" && inString) { + if (next === "\"") renderNext = true; + else inString = false; + } else if (!inString && cellDelims.indexOf(b) >= 0) { + line.push(cell); + cell = ""; + } else if (!inString && lineDelims.indexOf(b) >= 0) { + line.push(cell); + cell = ""; + lines.push(line); + line = []; + // Skip next byte if it is also a line delim (e.g. \r\n) + if (lineDelims.indexOf(next) >= 0 && next !== b) { + i++; + } + } else { + cell += b; + } + } + + if (line.length) { + line.push(cell); + lines.push(line); + } + + return lines; + } + + + /** + * Removes all HTML (or XML) tags from the input string. + * + * @param {string} htmlStr + * @param {boolean} [removeScriptAndStyle=false] + * - Flag to specify whether to remove entire script or style blocks + * @returns {string} + * + * @example + * // returns "Test" + * Utils.stripHtmlTags("
Test
"); + */ + static stripHtmlTags(htmlStr, removeScriptAndStyle=false) { + /** + * Recursively remove a pattern from a string until there are no more matches. + * Avoids incomplete sanitization e.g. "aabcbc".replace(/abc/g, "") === "abc" + * + * @param {RegExp} pattern + * @param {string} str + * @returns {string} + */ + function recursiveRemove(pattern, str) { + const newStr = str.replace(pattern, ""); + return newStr.length === str.length ? newStr : recursiveRemove(pattern, newStr); + } + + if (removeScriptAndStyle) { + htmlStr = recursiveRemove(/]*>(\s|\S)*?<\/script[^>]*>/gi, htmlStr); + htmlStr = recursiveRemove(/]*>(\s|\S)*?<\/style[^>]*>/gi, htmlStr); + } + return recursiveRemove(/<[^>]+>/g, htmlStr); + } + + + /** + * Escapes HTML tags in a string to stop them being rendered. + * https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet + * + * Null bytes are a special case and are converted to a character from the Unicode + * Private Use Area, which CyberChef will display as a control character picture. + * This is done due to null bytes not being rendered or stored correctly in HTML + * DOM building. + * + * @param {string} str + * @returns string + * + * @example + * // return "A <script> tag" + * Utils.escapeHtml("A `; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {json} + */ + run(input, args) { + const visualizationType = args[0]; + input = new Uint8Array(input); + + switch (visualizationType) { + case "图表 (柱状图)": + case "图表 (折线图)": + return this.calculateByteFrequency(input); + case "曲线图": + case "图像": + return this.calculateScanningEntropy(input).entropyData; + case "香农量表": + default: + return this.calculateShannonEntropy(input); + } + } + + /** + * Displays the entropy in a visualisation for web apps. + * + * @param {json} entropyData + * @param {Object[]} args + * @returns {html} + */ + present(entropyData, args) { + const visualizationType = args[0]; + + switch (visualizationType) { + case "图表 (柱状图)": + return this.createByteFrequencyBarHistogram(entropyData); + case "图表 (折线图)": + return this.createByteFrequencyLineHistogram(entropyData); + case "曲线图": + return this.createEntropyCurve(entropyData); + case "图像": + return this.createEntropyImage(entropyData); + case "香农量表": + default: + return this.createShannonEntropyVisualization(entropyData); + } + } +} + +export default Entropy; diff --git a/plugins/srktoolbox/src/core/operations/EscapeString.mjs b/plugins/srktoolbox/src/core/operations/EscapeString.mjs new file mode 100644 index 00000000..40b039c1 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/EscapeString.mjs @@ -0,0 +1,96 @@ +/** + * @author Vel0x [dalemy@microsoft.com] + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import jsesc from "jsesc"; + +/** + * Escape string operation + */ +class EscapeString extends Operation { + + /** + * EscapeString constructor + */ + constructor() { + super(); + + this.name = "转义字符串"; + this.module = "Default"; + this.description = "将字符串中的特殊字符转义,防止和代码发生冲突。例如,Don't stop me now 转义成 Don\\'t stop me now

支持以下的字符转义:
  • \\n (换行,LF)
  • \\r (回车,CR)
  • \\t (制表符)
  • \\b (退格)
  • \\f (换页,FF)
  • \\xnn (十六进制,n是0到f)
  • \\\\ (反斜杠)
  • \\' (单引号)
  • \\" (双引号)
  • \\unnnn (Unicode字符)
  • \\u{nnnnnn} (Unicode码点)
"; + this.infoURL = "https://wikipedia.org/wiki/Escape_sequence"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "转义等级", + "type": "option", + "value": ["特殊字符", "所有", "最少"] + }, + { + "name": "转义引号", + "type": "option", + "value": ["单引号", "双引号", "反勾号"] + }, + { + "name": "JSON兼容", + "type": "boolean", + "value": false + }, + { + "name": "ES6兼容", + "type": "boolean", + "value": true + }, + { + "name": "十六进制大写", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + * + * @example + * EscapeString.run("Don't do that", []) + * > "Don\'t do that" + * EscapeString.run(`Hello + * World`, []) + * > "Hello\nWorld" + */ + run(input, args) { + const level = args[0], + quotes = args[1], + jsonCompat = args[2], + es6Compat = args[3], + lowercaseHex = !args[4]; + + const quotesDict = { + "单引号": "single", + "双引号": "double", + "反勾号": "backtick" + }; + + return jsesc(input, { + quotes: quotesDict[quotes], + es6: es6Compat, + escapeEverything: level === "所有", + minimal: level === "最少", + json: jsonCompat, + lowercaseHex: lowercaseHex, + }); + } + +} + +export default EscapeString; diff --git a/plugins/srktoolbox/src/core/operations/EscapeUnicodeCharacters.mjs b/plugins/srktoolbox/src/core/operations/EscapeUnicodeCharacters.mjs new file mode 100644 index 00000000..b4722621 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/EscapeUnicodeCharacters.mjs @@ -0,0 +1,81 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; + +/** + * Escape Unicode Characters operation + */ +class EscapeUnicodeCharacters extends Operation { + + /** + * EscapeUnicodeCharacters constructor + */ + constructor() { + super(); + + this.name = "Unicode字符转义"; + this.module = "Default"; + this.description = "把Unicode字符根据选定的前缀格式进行转义。

支持以下前缀:
  • \\u
  • %u
  • U+
例: σου 编码为 \\u03C3\\u03BF\\u03C5"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "前缀", + "type": "option", + "value": ["\\u", "%u", "U+"] + }, + { + "name": "转义所有字符", + "type": "boolean", + "value": false + }, + { + "name": "填充位数", + "type": "number", + "value": 4 + }, + { + "name": "十六进制大写", + "type": "boolean", + "value": true + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const regexWhitelist = /[ -~]/i, + [prefix, encodeAll, padding, uppercaseHex] = args; + + let output = "", + character = ""; + + for (let i = 0; i < input.length; i++) { + character = input[i]; + if (!encodeAll && regexWhitelist.test(character)) { + // It’s a printable ASCII character so don’t escape it. + output += character; + continue; + } + + let cp = character.codePointAt(0).toString(16); + if (uppercaseHex) cp = cp.toUpperCase(); + output += prefix + cp.padStart(padding, "0"); + } + + return output; + } + +} + +export default EscapeUnicodeCharacters; diff --git a/plugins/srktoolbox/src/core/operations/ExpandAlphabetRange.mjs b/plugins/srktoolbox/src/core/operations/ExpandAlphabetRange.mjs new file mode 100644 index 00000000..c426bf7a --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ExpandAlphabetRange.mjs @@ -0,0 +1,48 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +/** + * Expand alphabet range operation + */ +class ExpandAlphabetRange extends Operation { + + /** + * ExpandAlphabetRange constructor + */ + constructor() { + super(); + + this.name = "扩写字母范围"; + this.module = "Default"; + this.description = "把字母范围扩写成在此范围内的所有字母。

例如:a-z 扩写成 abcdefghijklmnopqrstuvwxyz。"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "分隔符", + "type": "binaryString", + "value": "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return Utils.expandAlphRange(input).join(args[0]); + } + +} + +export default ExpandAlphabetRange; diff --git a/plugins/srktoolbox/src/core/operations/ExtractDates.mjs b/plugins/srktoolbox/src/core/operations/ExtractDates.mjs new file mode 100644 index 00000000..1234dece --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ExtractDates.mjs @@ -0,0 +1,60 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import { search } from "../lib/Extract.mjs"; + +/** + * Extract dates operation + */ +class ExtractDates extends Operation { + + /** + * ExtractDates constructor + */ + constructor() { + super(); + + this.name = "提取日期"; + this.module = "Regex"; + this.description = "提取以下格式的日期:
  • yyyy-mm-dd
  • dd/mm/yyyy
  • mm/dd/yyyy
分隔符可以为/、-、.或空格。"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "显示总数", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const displayTotal = args[0], + date1 = "(?:19|20)\\d\\d[- /.](?:0[1-9]|1[012])[- /.](?:0[1-9]|[12][0-9]|3[01])", // yyyy-mm-dd + date2 = "(?:0[1-9]|[12][0-9]|3[01])[- /.](?:0[1-9]|1[012])[- /.](?:19|20)\\d\\d", // dd/mm/yyyy + date3 = "(?:0[1-9]|1[012])[- /.](?:0[1-9]|[12][0-9]|3[01])[- /.](?:19|20)\\d\\d", // mm/dd/yyyy + regex = new RegExp(date1 + "|" + date2 + "|" + date3, "ig"); + + const results = search(input, regex); + + if (displayTotal) { + return `共计: ${results.length}\n\n${results.join("\n")}`; + } else { + return results.join("\n"); + } + } + +} + +export default ExtractDates; diff --git a/plugins/srktoolbox/src/core/operations/ExtractDomains.mjs b/plugins/srktoolbox/src/core/operations/ExtractDomains.mjs new file mode 100644 index 00000000..1a332717 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ExtractDomains.mjs @@ -0,0 +1,78 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import { search, DOMAIN_REGEX, DMARC_DOMAIN_REGEX } from "../lib/Extract.mjs"; +import { caseInsensitiveSort } from "../lib/Sort.mjs"; + +/** + * Extract domains operation + */ +class ExtractDomains extends Operation { + + /** + * ExtractDomains constructor + */ + constructor() { + super(); + + this.name = "提取域名"; + this.module = "Regex"; + this.description = "提取完整域名。
注意不包括域名路径。用提取URL操作提取完整URL。"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "显示总数", + type: "boolean", + value: false + }, + { + name: "排序", + type: "boolean", + value: false + }, + { + name: "去重", + type: "boolean", + value: false + }, + { + name: "Underscore (DMARC, DKIM, etc)", + type: "boolean", + value: false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [displayTotal, sort, unique, dmarc] = args; + + const results = search( + input, + dmarc ? DMARC_DOMAIN_REGEX : DOMAIN_REGEX, + null, + sort ? caseInsensitiveSort : null, + unique + ); + + if (displayTotal) { + return `总计: ${results.length}\n\n${results.join("\n")}`; + } else { + return results.join("\n"); + } + } + +} + +export default ExtractDomains; diff --git a/plugins/srktoolbox/src/core/operations/ExtractEXIF.mjs b/plugins/srktoolbox/src/core/operations/ExtractEXIF.mjs new file mode 100644 index 00000000..bc2e680a --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ExtractEXIF.mjs @@ -0,0 +1,65 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import ExifParser from "exif-parser"; +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Extract EXIF operation + */ +class ExtractEXIF extends Operation { + + /** + * ExtractEXIF constructor + */ + constructor() { + super(); + + this.name = "提取EXIF"; + this.module = "Image"; + this.description = [ + "从图像中提取EXIF数据。", + "

", + "EXIF数据是嵌入在图片(JPEG, JPG, TIFF)和音频文件中的元数据。", + "

", + "照片的EXIF数据通常包括图像本身以及拍摄设备的信息。", + ].join("\n"); + this.infoURL = "https://wikipedia.org/wiki/Exif"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + try { + const parser = ExifParser.create(input); + const result = parser.parse(); + + const lines = []; + for (const tagName in result.tags) { + const value = result.tags[tagName]; + lines.push(`${tagName}: ${value}`); + } + + const numTags = lines.length; + lines.unshift(`找到 ${numTags} 个标签。\n`); + return lines.join("\n"); + } catch (err) { + throw new OperationError(`无法从图片中提取EXIF数据: ${err}`); + } + } + +} + +export default ExtractEXIF; diff --git a/plugins/srktoolbox/src/core/operations/ExtractEmailAddresses.mjs b/plugins/srktoolbox/src/core/operations/ExtractEmailAddresses.mjs new file mode 100644 index 00000000..cd5743b8 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ExtractEmailAddresses.mjs @@ -0,0 +1,75 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import { search } from "../lib/Extract.mjs"; +import { caseInsensitiveSort } from "../lib/Sort.mjs"; + +/** + * Extract email addresses operation + */ +class ExtractEmailAddresses extends Operation { + + /** + * ExtractEmailAddresses constructor + */ + constructor() { + super(); + + this.name = "提取Email地址"; + this.module = "Regex"; + this.description = "从输入中提取所有的Email地址。"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "显示总数", + type: "boolean", + value: false + }, + { + name: "排序", + type: "boolean", + value: false + }, + { + name: "去重", + type: "boolean", + value: false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [displayTotal, sort, unique] = args, + // email regex from: https://www.regextester.com/98066 + regex = /(?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9](?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9-]*[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9])?\.)+[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9](?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9-]*[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\])/ig; + + const results = search( + input, + regex, + null, + sort ? caseInsensitiveSort : null, + unique + ); + + if (displayTotal) { + return `总计: ${results.length}\n\n${results.join("\n")}`; + } else { + return results.join("\n"); + } + } + +} + +export default ExtractEmailAddresses; diff --git a/plugins/srktoolbox/src/core/operations/ExtractFilePaths.mjs b/plugins/srktoolbox/src/core/operations/ExtractFilePaths.mjs new file mode 100644 index 00000000..e68396f2 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ExtractFilePaths.mjs @@ -0,0 +1,104 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import { search } from "../lib/Extract.mjs"; +import { caseInsensitiveSort } from "../lib/Sort.mjs"; + +/** + * Extract file paths operation + */ +class ExtractFilePaths extends Operation { + + /** + * ExtractFilePaths constructor + */ + constructor() { + super(); + + this.name = "提取文件路径"; + this.module = "Regex"; + this.description = "从输入中提取任何长得像Windows或UNIX文件路径的字符串。

注意UNIX模式可能会产生很多误判。"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Windows", + type: "boolean", + value: true + }, + { + name: "UNIX", + type: "boolean", + value: true + }, + { + name: "显示总数", + type: "boolean", + value: false + }, + { + name: "排序", + type: "boolean", + value: false + }, + { + name: "去重", + type: "boolean", + value: false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [includeWinPath, includeUnixPath, displayTotal, sort, unique] = args, + winDrive = "[A-Z]:\\\\", + winName = "[A-Z\\d][A-Z\\d\\- '_\\(\\)~]{0,61}", + winExt = "[A-Z\\d]{1,6}", + winPath = winDrive + "(?:" + winName + "\\\\?)*" + winName + + "(?:\\." + winExt + ")?", + unixPath = "(?:/[A-Z\\d.][A-Z\\d\\-.]{0,61})+"; + let filePaths = ""; + + if (includeWinPath && includeUnixPath) { + filePaths = winPath + "|" + unixPath; + } else if (includeWinPath) { + filePaths = winPath; + } else if (includeUnixPath) { + filePaths = unixPath; + } + + if (!filePaths) { + return ""; + } + + const regex = new RegExp(filePaths, "ig"); + const results = search( + input, + regex, + null, + sort ? caseInsensitiveSort : null, + unique + ); + + if (displayTotal) { + return `总计: ${results.length}\n\n${results.join("\n")}`; + } else { + return results.join("\n"); + } + + } + +} + +export default ExtractFilePaths; diff --git a/plugins/srktoolbox/src/core/operations/ExtractFiles.mjs b/plugins/srktoolbox/src/core/operations/ExtractFiles.mjs new file mode 100644 index 00000000..22ab4c5f --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ExtractFiles.mjs @@ -0,0 +1,125 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import {scanForFileTypes, extractFile} from "../lib/FileType.mjs"; +import {FILE_SIGNATURES} from "../lib/FileSignatures.mjs"; + +/** + * Extract Files operation + */ +class ExtractFiles extends Operation { + + /** + * ExtractFiles constructor + */ + constructor() { + super(); + + // Get the first extension for each signature that can be extracted + let supportedExts = Object.keys(FILE_SIGNATURES).map(cat => { + return FILE_SIGNATURES[cat] + .filter(sig => sig.extractor) + .map(sig => sig.extension.toUpperCase()); + }); + + // Flatten categories and remove duplicates + supportedExts = [].concat(...supportedExts).unique(); + + this.name = "提取文件"; + this.module = "Default"; + this.description = `从输入内容中进行文件雕复来尝试提取文件。

此操作目前可提取以下格式: +
    +
  • + ${supportedExts.join("
  • ")} +
  • +
可以指定最小文件尺寸来防止误判。`; + this.infoURL = "https://forensics.wiki/file_carving"; + this.inputType = "ArrayBuffer"; + this.outputType = "List"; + this.presentType = "html"; + this.args = Object.keys(FILE_SIGNATURES).map(cat => { + return { + name: cat, + type: "boolean", + value: cat === "其它" ? false : true + }; + }).concat([ + { + name: "忽略失败提取", + type: "boolean", + value: true + }, + { + name: "最小文件尺寸", + type: "number", + value: 100 + } + ]); + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {List} + */ + run(input, args) { + const bytes = new Uint8Array(input), + categories = [], + minSize = args.pop(1), + ignoreFailedExtractions = args.pop(1); + + args.forEach((cat, i) => { + if (cat) categories.push(Object.keys(FILE_SIGNATURES)[i]); + }); + + // Scan for embedded files + const detectedFiles = scanForFileTypes(bytes, categories); + + // Extract each file that we support + const files = []; + const errors = []; + detectedFiles.forEach(detectedFile => { + try { + const file = extractFile(bytes, detectedFile.fileDetails, detectedFile.offset); + if (file.size >= minSize) + files.push(file); + } catch (err) { + if (!ignoreFailedExtractions && err.message.indexOf("No extraction algorithm available") < 0) { + errors.push( + `Error while attempting to extract ${detectedFile.fileDetails.name} ` + + `at offset ${detectedFile.offset}:\n` + + `${err.message}` + ); + } + } + }); + + if (errors.length) { + throw new OperationError(errors.join("\n\n")); + } + + return files; + } + + + /** + * Displays the files in HTML for web apps. + * + * @param {File[]} files + * @returns {html} + */ + async present(files) { + return await Utils.displayFilesAsHTML(files); + } + +} + +export default ExtractFiles; diff --git a/plugins/srktoolbox/src/core/operations/ExtractHashes.mjs b/plugins/srktoolbox/src/core/operations/ExtractHashes.mjs new file mode 100644 index 00000000..300d2843 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ExtractHashes.mjs @@ -0,0 +1,86 @@ +/** + * @author mshwed [m@ttshwed.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import { search } from "../lib/Extract.mjs"; + +/** + * Extract Hash Values operation + */ +class ExtractHashes extends Operation { + + /** + * ExtractHashValues constructor + */ + constructor() { + super(); + + this.name = "提取哈希"; + this.module = "Regex"; + this.description = "根据哈希值长度提取哈希值。"; + this.infoURL = "https://wikipedia.org/wiki/Comparison_of_cryptographic_hash_functions"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "哈希值长度", + type: "number", + value: 40 + }, + { + name: "搜索所有种类哈希值", + type: "boolean", + value: false + }, + { + name: "显示总数", + type: "boolean", + value: false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const results = []; + let hashCount = 0; + + const [hashLength, searchAllHashes, showDisplayTotal] = args; + + // Convert character length to bit length + let hashBitLengths = [(hashLength / 2) * 8]; + + if (searchAllHashes) hashBitLengths = [4, 8, 16, 32, 64, 128, 160, 192, 224, 256, 320, 384, 512, 1024]; + + for (const hashBitLength of hashBitLengths) { + // Convert bit length to character length + const hashCharacterLength = (hashBitLength / 8) * 2; + + const regex = new RegExp(`(\\b|^)[a-f0-9]{${hashCharacterLength}}(\\b|$)`, "g"); + const searchResults = search(input, regex, null, false); + + hashCount += searchResults.length; + results.push(...searchResults); + } + + let output = ""; + if (showDisplayTotal) { + output = `结果总数:${hashCount}\n\n`; + } + + output = output + results.join("\n"); + return output; + } + +} + +export default ExtractHashes; diff --git a/plugins/srktoolbox/src/core/operations/ExtractID3.mjs b/plugins/srktoolbox/src/core/operations/ExtractID3.mjs new file mode 100644 index 00000000..f614729f --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ExtractID3.mjs @@ -0,0 +1,326 @@ +/** + * @author n1073645 [n1073645@gmail.com] + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; + +/** + * Extract ID3 operation + */ +class ExtractID3 extends Operation { + + /** + * ExtractID3 constructor + */ + constructor() { + super(); + + this.name = "提取ID3"; + this.module = "Default"; + this.description = "此操作从MP3文件中提取ID3元数据。

ID3是一种元数据容器,多应用于MP3格式的音频文件中。它可以将相关的曲名、演唱者、专辑、音轨数等信息存储在MP3文件中,又称作“ID3Tags”。"; + this.infoURL = "https://wikipedia.org/wiki/ID3"; + this.inputType = "ArrayBuffer"; + this.outputType = "JSON"; + this.presentType = "html"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {JSON} + */ + run(input, args) { + input = new Uint8Array(input); + + /** + * Extracts the ID3 header fields. + */ + function extractHeader() { + if (!Array.from(input.slice(0, 3)).equals([0x49, 0x44, 0x33])) + throw new OperationError("未找到有效的ID3标头。"); + + const header = { + "Type": "ID3", + // Tag version + "Version": input[3].toString() + "." + input[4].toString(), + // Header version + "Flags": input[5].toString() + }; + + input = input.slice(6); + return header; + } + + /** + * Converts the size fields to a single integer. + * + * @param {number} num + * @returns {string} + */ + function readSize(num) { + let result = 0; + + // The sizes are 7 bit numbers stored in 8 bit locations + for (let i = (num) * 7; i; i -= 7) { + result = (result << i) | input[0]; + input = input.slice(1); + } + return result; + } + + /** + * Reads frame header based on ID. + * + * @param {string} id + * @returns {number} + */ + function readFrame(id) { + const frame = {}; + + // Size of frame + const size = readSize(4); + frame.Size = size.toString(); + frame.Description = FRAME_DESCRIPTIONS[id]; + input = input.slice(2); + + // Read data from frame + let data = ""; + for (let i = 1; i < size; i++) + data += String.fromCharCode(input[i]); + frame.Data = data; + + // Move to next Frame + input = input.slice(size); + + return [frame, size]; + } + + const result = extractHeader(); + + const headerTagSize = readSize(4); + result.Size = headerTagSize.toString(); + + const tags = {}; + let pos = 10; + + // While the current element is in the header + while (pos < headerTagSize) { + + // Frame Identifier of frame + let id = String.fromCharCode(input[0]) + String.fromCharCode(input[1]) + String.fromCharCode(input[2]); + input = input.slice(3); + + // If the next character is non-zero it is an identifier + if (input[0] !== 0) { + id += String.fromCharCode(input[0]); + } + input = input.slice(1); + + if (id in FRAME_DESCRIPTIONS) { + const [frame, size] = readFrame(id); + tags[id] = frame; + pos += 10 + size; + } else if (id === "\x00\x00\x00") { // end of header + break; + } else { + throw new OperationError("未知的Frame Identifier: " + id); + } + } + + result.Tags = tags; + + return result; + } + + /** + * Displays the extracted data in a more accessible format for web apps. + * @param {JSON} data + * @returns {html} + */ + present(data) { + if (!data || !Object.prototype.hasOwnProperty.call(data, "Tags")) + return JSON.stringify(data, null, 4); + + let output = ` + `; + + for (const tagID in data.Tags) { + const description = data.Tags[tagID].Description, + contents = data.Tags[tagID].Data; + output += ``; + } + output += "
标签描述Data
${tagID}${Utils.escapeHtml(description)}${Utils.escapeHtml(contents)}
"; + return output; + } + +} + +// Borrowed from https://github.com/aadsm/jsmediatags +const FRAME_DESCRIPTIONS = { + // v2.2 + "BUF": "Recommended buffer size", + "CNT": "Play counter", + "COM": "Comments", + "CRA": "Audio encryption", + "CRM": "Encrypted meta frame", + "ETC": "Event timing codes", + "EQU": "Equalization", + "GEO": "General encapsulated object", + "IPL": "Involved people list", + "LNK": "Linked information", + "MCI": "Music CD Identifier", + "MLL": "MPEG location lookup table", + "PIC": "Attached picture", + "POP": "Popularimeter", + "REV": "Reverb", + "RVA": "Relative volume adjustment", + "SLT": "Synchronized lyric/text", + "STC": "Synced tempo codes", + "TAL": "Album/Movie/Show title", + "TBP": "BPM (Beats Per Minute)", + "TCM": "Composer", + "TCO": "Content type", + "TCR": "Copyright message", + "TDA": "Date", + "TDY": "Playlist delay", + "TEN": "Encoded by", + "TFT": "File type", + "TIM": "Time", + "TKE": "Initial key", + "TLA": "Language(s)", + "TLE": "Length", + "TMT": "Media type", + "TOA": "Original artist(s)/performer(s)", + "TOF": "Original filename", + "TOL": "Original Lyricist(s)/text writer(s)", + "TOR": "Original release year", + "TOT": "Original album/Movie/Show title", + "TP1": "Lead artist(s)/Lead performer(s)/Soloist(s)/Performing group", + "TP2": "Band/Orchestra/Accompaniment", + "TP3": "Conductor/Performer refinement", + "TP4": "Interpreted, remixed, or otherwise modified by", + "TPA": "Part of a set", + "TPB": "Publisher", + "TRC": "ISRC (International Standard Recording Code)", + "TRD": "Recording dates", + "TRK": "Track number/Position in set", + "TSI": "Size", + "TSS": "Software/hardware and settings used for encoding", + "TT1": "Content group description", + "TT2": "Title/Songname/Content description", + "TT3": "Subtitle/Description refinement", + "TXT": "Lyricist/text writer", + "TXX": "User defined text information frame", + "TYE": "Year", + "UFI": "Unique file identifier", + "ULT": "Unsychronized lyric/text transcription", + "WAF": "Official audio file webpage", + "WAR": "Official artist/performer webpage", + "WAS": "Official audio source webpage", + "WCM": "Commercial information", + "WCP": "Copyright/Legal information", + "WPB": "Publishers official webpage", + "WXX": "User defined URL link frame", + // v2.3 + "AENC": "Audio encryption", + "APIC": "Attached picture", + "ASPI": "Audio seek point index", + "CHAP": "Chapter", + "CTOC": "Table of contents", + "COMM": "Comments", + "COMR": "Commercial frame", + "ENCR": "Encryption method registration", + "EQU2": "Equalisation (2)", + "EQUA": "Equalization", + "ETCO": "Event timing codes", + "GEOB": "General encapsulated object", + "GRID": "Group identification registration", + "IPLS": "Involved people list", + "LINK": "Linked information", + "MCDI": "Music CD identifier", + "MLLT": "MPEG location lookup table", + "OWNE": "Ownership frame", + "PRIV": "Private frame", + "PCNT": "Play counter", + "POPM": "Popularimeter", + "POSS": "Position synchronisation frame", + "RBUF": "Recommended buffer size", + "RVA2": "Relative volume adjustment (2)", + "RVAD": "Relative volume adjustment", + "RVRB": "Reverb", + "SEEK": "Seek frame", + "SYLT": "Synchronized lyric/text", + "SYTC": "Synchronized tempo codes", + "TALB": "Album/Movie/Show title", + "TBPM": "BPM (beats per minute)", + "TCOM": "Composer", + "TCON": "Content type", + "TCOP": "Copyright message", + "TDAT": "Date", + "TDLY": "Playlist delay", + "TDRC": "Recording time", + "TDRL": "Release time", + "TDTG": "Tagging time", + "TENC": "Encoded by", + "TEXT": "Lyricist/Text writer", + "TFLT": "File type", + "TIME": "Time", + "TIPL": "Involved people list", + "TIT1": "Content group description", + "TIT2": "Title/songname/content description", + "TIT3": "Subtitle/Description refinement", + "TKEY": "Initial key", + "TLAN": "Language(s)", + "TLEN": "Length", + "TMCL": "Musician credits list", + "TMED": "Media type", + "TMOO": "Mood", + "TOAL": "Original album/movie/show title", + "TOFN": "Original filename", + "TOLY": "Original lyricist(s)/text writer(s)", + "TOPE": "Original artist(s)/performer(s)", + "TORY": "Original release year", + "TOWN": "File owner/licensee", + "TPE1": "Lead performer(s)/Soloist(s)", + "TPE2": "Band/orchestra/accompaniment", + "TPE3": "Conductor/performer refinement", + "TPE4": "Interpreted, remixed, or otherwise modified by", + "TPOS": "Part of a set", + "TPRO": "Produced notice", + "TPUB": "Publisher", + "TRCK": "Track number/Position in set", + "TRDA": "Recording dates", + "TRSN": "Internet radio station name", + "TRSO": "Internet radio station owner", + "TSOA": "Album sort order", + "TSOP": "Performer sort order", + "TSOT": "Title sort order", + "TSIZ": "Size", + "TSRC": "ISRC (international standard recording code)", + "TSSE": "Software/Hardware and settings used for encoding", + "TSST": "Set subtitle", + "TYER": "Year", + "TXXX": "User defined text information frame", + "UFID": "Unique file identifier", + "USER": "Terms of use", + "USLT": "Unsychronized lyric/text transcription", + "WCOM": "Commercial information", + "WCOP": "Copyright/Legal information", + "WOAF": "Official audio file webpage", + "WOAR": "Official artist/performer webpage", + "WOAS": "Official audio source webpage", + "WORS": "Official internet radio station homepage", + "WPAY": "Payment", + "WPUB": "Publishers official webpage", + "WXXX": "User defined URL link frame" +}; + +export default ExtractID3; diff --git a/plugins/srktoolbox/src/core/operations/ExtractIPAddresses.mjs b/plugins/srktoolbox/src/core/operations/ExtractIPAddresses.mjs new file mode 100644 index 00000000..0b9f5d18 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ExtractIPAddresses.mjs @@ -0,0 +1,124 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import { search } from "../lib/Extract.mjs"; +import { ipSort } from "../lib/Sort.mjs"; + +/** + * Extract IP addresses operation + */ +class ExtractIPAddresses extends Operation { + + /** + * ExtractIPAddresses constructor + */ + constructor() { + super(); + + this.name = "提取IP地址"; + this.module = "Regex"; + this.description = "提取所有的IPv4和IPv6地址。

警告:类似 1.2.3.4.5.6.7.8 这样的非有效IP地址输入也会匹配出 1.2.3.4 and 5.6.7.8,务必确认输入文本正确!"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "IPv4", + type: "boolean", + value: true + }, + { + name: "IPv6", + type: "boolean", + value: false + }, + { + name: "移除内网IPv4地址", + type: "boolean", + value: false + }, + { + name: "显示总数", + type: "boolean", + value: false + }, + { + name: "排序", + type: "boolean", + value: false + }, + { + name: "去重", + type: "boolean", + value: false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [includeIpv4, includeIpv6, removeLocal, displayTotal, sort, unique] = args, + + // IPv4 decimal groups can have values 0 to 255. To construct a regex the following sub-regex is reused: + ipv4DecimalByte = "(?:25[0-5]|2[0-4]\\d|1?[0-9]\\d|\\d)", + ipv4OctalByte = "(?:0[1-3]?[0-7]{1,2})", + + // Look behind and ahead will be used to exclude matches with additional decimal digits left and right of IP address + lookBehind = "(? option !== "") + .map((option) => COLOUR_OPTIONS.indexOf(option)), + parsedImage = await Jimp.read(input), + width = parsedImage.bitmap.width, + height = parsedImage.bitmap.height, + rgba = parsedImage.bitmap.data; + + if (bit < 0 || bit > 7) { + throw new OperationError( + "错误:位参数只能是 0 到 7", + ); + } + + let i, + combinedBinary = ""; + + if (pixelOrder === "按行") { + for (i = 0; i < rgba.length; i += 4) { + for (const colour of colours) { + combinedBinary += Utils.bin(rgba[i + colour])[bit]; + } + } + } else { + let rowWidth; + const pixelWidth = width * 4; + for (let col = 0; col < width; col++) { + for (let row = 0; row < height; row++) { + rowWidth = row * pixelWidth; + for (const colour of colours) { + i = rowWidth + (col + colour * 4); + combinedBinary += Utils.bin(rgba[i])[bit]; + } + } + } + } + + return fromBinary(combinedBinary); + } +} + +const COLOUR_OPTIONS = ["R", "G", "B", "A"]; + +export default ExtractLSB; diff --git a/plugins/srktoolbox/src/core/operations/ExtractMACAddresses.mjs b/plugins/srktoolbox/src/core/operations/ExtractMACAddresses.mjs new file mode 100644 index 00000000..cb3551d0 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ExtractMACAddresses.mjs @@ -0,0 +1,73 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import { search } from "../lib/Extract.mjs"; +import { hexadecimalSort } from "../lib/Sort.mjs"; + +/** + * Extract MAC addresses operation + */ +class ExtractMACAddresses extends Operation { + + /** + * ExtractMACAddresses constructor + */ + constructor() { + super(); + + this.name = "提取MAC地址"; + this.module = "Regex"; + this.description = "从输入中提取Media Access Control (MAC)地址。"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "显示总数", + type: "boolean", + value: false + }, + { + name: "排序", + type: "boolean", + value: false + }, + { + name: "去重", + type: "boolean", + value: false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [displayTotal, sort, unique] = args, + regex = /[A-F\d]{2}(?:[:-][A-F\d]{2}){5}/ig, + results = search( + input, + regex, + null, + sort ? hexadecimalSort : null, + unique + ); + + if (displayTotal) { + return `总计: ${results.length}\n\n${results.join("\n")}`; + } else { + return results.join("\n"); + } + } + +} + +export default ExtractMACAddresses; diff --git a/plugins/srktoolbox/src/core/operations/ExtractRGBA.mjs b/plugins/srktoolbox/src/core/operations/ExtractRGBA.mjs new file mode 100644 index 00000000..282a0be7 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ExtractRGBA.mjs @@ -0,0 +1,69 @@ +/** + * @author Ge0rg3 [georgeomnet+cyberchef@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { isImage } from "../lib/FileType.mjs"; +import { Jimp } from "jimp"; + +import { RGBA_DELIM_OPTIONS } from "../lib/Delim.mjs"; + +/** + * Extract RGBA operation + */ +class ExtractRGBA extends Operation { + /** + * ExtractRGBA constructor + */ + constructor() { + super(); + + this.name = "提取RGBA"; + this.module = "Image"; + this.description = + "提取图像中每个像素的RGBA值。此数据有时用于隐写术,可隐藏文字和数据。"; + this.infoURL = "https://wikipedia.org/wiki/RGBA_color_space"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + name: "分隔符", + type: "editableOption", + value: RGBA_DELIM_OPTIONS, + }, + { + name: "包括Alpha", + type: "boolean", + value: true, + }, + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + if (!isImage(input)) + throw new OperationError("请输入合法的图像文件。"); + + const delimiter = args[0], + includeAlpha = args[1], + parsedImage = await Jimp.read(input); + + let bitmap = parsedImage.bitmap.data; + bitmap = includeAlpha ? + bitmap : + bitmap.filter((val, idx) => idx % 4 !== 3); + + return bitmap.join(delimiter); + } +} + +export default ExtractRGBA; diff --git a/plugins/srktoolbox/src/core/operations/ExtractURLs.mjs b/plugins/srktoolbox/src/core/operations/ExtractURLs.mjs new file mode 100644 index 00000000..b0e1f54d --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ExtractURLs.mjs @@ -0,0 +1,72 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import { search, URL_REGEX } from "../lib/Extract.mjs"; +import { caseInsensitiveSort } from "../lib/Sort.mjs"; + +/** + * Extract URLs operation + */ +class ExtractURLs extends Operation { + + /** + * ExtractURLs constructor + */ + constructor() { + super(); + + this.name = "提取URL"; + this.module = "Regex"; + this.description = "从输入中提取Uniform Resource Locator (URL)。必须要带协议名称 (如http, ftp 等),否则会有过多误判。"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "显示总数", + type: "boolean", + value: false + }, + { + name: "排序", + type: "boolean", + value: false + }, + { + name: "去重", + type: "boolean", + value: false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [displayTotal, sort, unique] = args; + const results = search( + input, + URL_REGEX, + null, + sort ? caseInsensitiveSort : null, + unique + ); + + if (displayTotal) { + return `总计: ${results.length}\n\n${results.join("\n")}`; + } else { + return results.join("\n"); + } + } + +} + +export default ExtractURLs; diff --git a/plugins/srktoolbox/src/core/operations/FangURL.mjs b/plugins/srktoolbox/src/core/operations/FangURL.mjs new file mode 100644 index 00000000..2f00063e --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/FangURL.mjs @@ -0,0 +1,80 @@ +/** + * @author arnydo [github@arnydo.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; + +/** + * FangURL operation + */ +class FangURL extends Operation { + + /** + * FangURL constructor + */ + constructor() { + super(); + + this.name = "URL无效化恢复"; + this.module = "Default"; + this.description = "将已经“无效化(Defanged)”的URL恢复成有效状态。"; + this.infoURL = "https://isc.sans.edu/forums/diary/Defang+all+the+things/22744/"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "恢复[.]", + type: "boolean", + value: true + }, + { + name: "恢复hxxp", + type: "boolean", + value: true + }, + { + name: "恢复://", + type: "boolean", + value: true + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [dots, http, slashes] = args; + + input = fangURL(input, dots, http, slashes); + + return input; + } + +} + + +/** + * Defangs a given URL + * + * @param {string} url + * @param {boolean} dots + * @param {boolean} http + * @param {boolean} slashes + * @returns {string} + */ +function fangURL(url, dots, http, slashes) { + if (dots) url = url.replace(/\[\.\]/g, "."); + if (http) url = url.replace(/hxxp/g, "http"); + if (slashes) url = url.replace(/\[:\/\/\]/g, "://"); + + return url; +} + +export default FangURL; diff --git a/plugins/srktoolbox/src/core/operations/FernetDecrypt.mjs b/plugins/srktoolbox/src/core/operations/FernetDecrypt.mjs new file mode 100644 index 00000000..8a74a125 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/FernetDecrypt.mjs @@ -0,0 +1,65 @@ +/** + * @author Karsten Silkenbäumer [github.com/kassi] + * @copyright Karsten Silkenbäumer 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import fernet from "fernet"; + +/** + * FernetDecrypt operation + */ +class FernetDecrypt extends Operation { + /** + * FernetDecrypt constructor + */ + constructor() { + super(); + + this.name = "Fernet解密"; + this.module = "Default"; + this.description = "Fernet是一种对称加密算法,设计目的是确保加密过的信息在没有密钥的情况下无法被解密和修改。密钥使用URL安全编码。Fernet使用128位AES算法(CBC模式,PKCS7填充)和使用SHA256算法的HMAC进行校验。IV使用os.random()生成。

密钥:密钥长度必须为32字节(256位),使用Base64编码。"; + this.infoURL = "https://asecuritysite.com/encryption/fer"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "密钥", + "type": "string", + "value": "" + }, + ]; + this.patterns = [ + { + match: "^[A-Z\\d\\-_=]{20,}$", + flags: "i", + args: [] + }, + ]; + } + /** + * @param {String} input + * @param {Object[]} args + * @returns {String} + */ + run(input, args) { + const [secretInput] = args; + try { + const secret = new fernet.Secret(secretInput); + const token = new fernet.Token({ + secret: secret, + token: input, + ttl: 0 + }); + return token.decode(); + } catch (err) { + throw new OperationError(err); + } + } +} + +export default FernetDecrypt; diff --git a/plugins/srktoolbox/src/core/operations/FernetEncrypt.mjs b/plugins/srktoolbox/src/core/operations/FernetEncrypt.mjs new file mode 100644 index 00000000..67c12231 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/FernetEncrypt.mjs @@ -0,0 +1,56 @@ +/** + * @author Karsten Silkenbäumer [github.com/kassi] + * @copyright Karsten Silkenbäumer 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import fernet from "fernet"; + +/** + * FernetEncrypt operation + */ +class FernetEncrypt extends Operation { + /** + * FernetEncrypt constructor + */ + constructor() { + super(); + + this.name = "Fernet加密"; + this.module = "Default"; + this.description = "Fernet是一种对称加密算法,设计目的是确保加密过的信息在没有密钥的情况下无法被解密和修改。密钥使用URL安全编码。Fernet使用128位AES算法(CBC模式,PKCS7填充)和使用SHA256算法的HMAC进行校验。IV使用os.random()生成。

密钥:密钥长度必须为32字节(256位),使用Base64编码。"; + this.infoURL = "https://asecuritysite.com/encryption/fer"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "密钥", + "type": "string", + "value": "" + }, + ]; + } + /** + * @param {String} input + * @param {Object[]} args + * @returns {String} + */ + run(input, args) { + const [secretInput] = args; + try { + const secret = new fernet.Secret(secretInput); + const token = new fernet.Token({ + secret: secret, + }); + return token.encode(input); + } catch (err) { + throw new OperationError(err); + } + } +} + +export default FernetEncrypt; diff --git a/plugins/srktoolbox/src/core/operations/FileTree.mjs b/plugins/srktoolbox/src/core/operations/FileTree.mjs new file mode 100644 index 00000000..b74baf07 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/FileTree.mjs @@ -0,0 +1,96 @@ +/** + * @author sw5678 + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import {INPUT_DELIM_OPTIONS} from "../lib/Delim.mjs"; + +/** + * Unique operation + */ +class FileTree extends Operation { + + /** + * Unique constructor + */ + constructor() { + super(); + + this.name = "文件树"; + this.module = "Default"; + this.description = "从给定的文件路径列表生成文件树(和Linux的tree命令类似)。"; + this.infoURL = "https://wikipedia.org/wiki/Tree_(command)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "文件路径分隔符", + type: "binaryString", + value: "/" + }, + { + name: "列表分隔符", + type: "option", + value: INPUT_DELIM_OPTIONS + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + + // Set up arrow and pipe for nice output display + const ARROW = "|---"; + const PIPE = "| "; + + // Get args from input + const fileDelim = args[0]; + const entryDelim = Utils.charRep(args[1]); + + // Store path to print + const completedList = []; + const printList = []; + + // Loop through all entries + const filePaths = input.split(entryDelim).unique().sort(); + for (let i = 0; i < filePaths.length; i++) { + // Split by file delimiter + let path = filePaths[i].split(fileDelim); + + if (path[0] === "") { + path = path.slice(1, path.length); + } + + for (let j = 0; j < path.length; j++) { + let printLine; + let key; + if (j === 0) { + printLine = path[j]; + key = path[j]; + } else { + printLine = PIPE.repeat(j-1) + ARROW + path[j]; + key = path.slice(0, j+1).join("/"); + } + + // Check to see we have already added that path + if (!completedList.includes(key)) { + completedList.push(key); + printList.push(printLine); + } + } + } + return printList.join("\n"); + } + +} + +export default FileTree; diff --git a/plugins/srktoolbox/src/core/operations/Filter.mjs b/plugins/srktoolbox/src/core/operations/Filter.mjs new file mode 100644 index 00000000..7b9cbd0b --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Filter.mjs @@ -0,0 +1,75 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import {INPUT_DELIM_OPTIONS} from "../lib/Delim.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import XRegExp from "xregexp"; + +/** + * Filter operation + */ +class Filter extends Operation { + + /** + * Filter constructor + */ + constructor() { + super(); + + this.name = "过滤"; + this.module = "Regex"; + this.description = "将输入字符串使用给定的分隔符拆分,之后使用给定的正则表达式过滤。"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "分隔符", + "type": "option", + "value": INPUT_DELIM_OPTIONS + }, + { + "name": "正则表达式", + "type": "string", + "value": "" + }, + { + "name": "反向过滤", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const delim = Utils.charRep(args[0]), + reverse = args[2]; + let regex; + + try { + regex = new XRegExp(args[1]); + } catch (err) { + throw new OperationError(`无效的正则表达式: ${err.message}`); + } + + const regexFilter = function(value) { + return reverse ^ regex.test(value); + }; + + return input.split(delim).filter(regexFilter).join(delim); + } + +} + +export default Filter; diff --git a/plugins/srktoolbox/src/core/operations/FindReplace.mjs b/plugins/srktoolbox/src/core/operations/FindReplace.mjs new file mode 100644 index 00000000..3ab79874 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/FindReplace.mjs @@ -0,0 +1,96 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import XRegExp from "xregexp"; + +/** + * Find / Replace operation + */ +class FindReplace extends Operation { + + /** + * FindReplace constructor + */ + constructor() { + super(); + + this.name = "查找/替换"; + this.module = "Regex"; + this.description = "把第一个字符串用第二个字符串替换。

支持正则表达式(regex)、简单字符串和扩展字符串(即使用转义字符\\n, \\r, \\t, \\b, \\f和用\\x表示的十六进制字节,例如 \\x00 代表空字节)。"; + this.infoURL = "https://wikipedia.org/wiki/Regular_expression"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "查找内容", + "type": "toggleString", + "value": "", + "toggleValues": ["正则表达式", "扩展 (\\n, \\t, \\x...)", "简单字符串"] + }, + { + "name": "替换", + "type": "binaryString", + "value": "" + }, + { + "name": "全局匹配(g)", + "type": "boolean", + "value": true + }, + { + "name": "不区分大小写(i)", + "type": "boolean", + "value": false + }, + { + "name": "匹配多行(m)", + "type": "boolean", + "value": true + }, + { + "name": "点(.)匹配所有字符(s)", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [{option: type}, replace, g, i, m, s] = args; + let find = args[0].string, + modifiers = ""; + + if (g) modifiers += "g"; + if (i) modifiers += "i"; + if (m) modifiers += "m"; + if (s) modifiers += "s"; + + if (type === "正则表达式") { + find = new XRegExp(find, modifiers); + return input.replace(find, replace); + } + + if (type.indexOf("扩展") === 0) { + find = Utils.parseEscapedChars(find); + } + + find = new XRegExp(Utils.escapeRegex(find), modifiers); + + return input.replace(find, replace); + } + +} + +export default FindReplace; diff --git a/plugins/srktoolbox/src/core/operations/Fletcher16Checksum.mjs b/plugins/srktoolbox/src/core/operations/Fletcher16Checksum.mjs new file mode 100644 index 00000000..495c6293 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Fletcher16Checksum.mjs @@ -0,0 +1,52 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +/** + * Fletcher-16 Checksum operation + */ +class Fletcher16Checksum extends Operation { + + /** + * Fletcher16Checksum constructor + */ + constructor() { + super(); + + this.name = "Fletcher-16校验和"; + this.module = "Crypto"; + this.description = "Fletcher校验和是用于计算位置相关校验和的算法,由Lawrence Livermore Labs的John Gould Fletcher在20世纪70年代末期设计。

Fletcher校验和可以提供接近循环冗余校验(Cyclic redundancy check, CRC)的错误检测功能,但所需的计算更少。"; + this.infoURL = "https://wikipedia.org/wiki/Fletcher%27s_checksum#Fletcher-16"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let a = 0, + b = 0; + input = new Uint8Array(input); + + for (let i = 0; i < input.length; i++) { + a = (a + input[i]) % 0xff; + b = (b + a) % 0xff; + } + + return Utils.hex(((b << 8) | a) >>> 0, 4); + } + +} + +export default Fletcher16Checksum; diff --git a/plugins/srktoolbox/src/core/operations/Fletcher32Checksum.mjs b/plugins/srktoolbox/src/core/operations/Fletcher32Checksum.mjs new file mode 100644 index 00000000..eba0ae6d --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Fletcher32Checksum.mjs @@ -0,0 +1,60 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +/** + * Fletcher-32 Checksum operation + */ +class Fletcher32Checksum extends Operation { + + /** + * Fletcher32Checksum constructor + */ + constructor() { + super(); + + this.name = "Fletcher-32校验和"; + this.module = "Crypto"; + this.description = "Fletcher校验和是用于计算位置相关校验和的算法,由Lawrence Livermore Labs的John Gould Fletcher在20世纪70年代末期设计。

Fletcher校验和可以提供接近循环冗余校验(Cyclic redundancy check, CRC)的错误检测功能,但所需的计算更少。"; + this.infoURL = "https://wikipedia.org/wiki/Fletcher%27s_checksum#Fletcher-32"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let a = 0, + b = 0; + if (ArrayBuffer.isView(input)) { + input = new DataView(input.buffer, input.byteOffset, input.byteLength); + } else { + input = new DataView(input); + } + + for (let i = 0; i < input.byteLength - 1; i += 2) { + a = (a + input.getUint16(i, true)) % 0xffff; + b = (b + a) % 0xffff; + } + if (input.byteLength % 2 !== 0) { + a = (a + input.getUint8(input.byteLength - 1)) % 0xffff; + b = (b + a) % 0xffff; + } + + return Utils.hex(((b << 16) | a) >>> 0, 8); + } + +} + +export default Fletcher32Checksum; diff --git a/plugins/srktoolbox/src/core/operations/Fletcher64Checksum.mjs b/plugins/srktoolbox/src/core/operations/Fletcher64Checksum.mjs new file mode 100644 index 00000000..c2b6f77f --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Fletcher64Checksum.mjs @@ -0,0 +1,64 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +/** + * Fletcher-64 Checksum operation + */ +class Fletcher64Checksum extends Operation { + + /** + * Fletcher64Checksum constructor + */ + constructor() { + super(); + + this.name = "Fletcher-64校验和"; + this.module = "Crypto"; + this.description = "Fletcher校验和是用于计算位置相关校验和的算法,由Lawrence Livermore Labs的John Gould Fletcher在20世纪70年代末期设计。

Fletcher校验和可以提供接近循环冗余校验(Cyclic redundancy check, CRC)的错误检测功能,但所需的计算更少。"; + this.infoURL = "https://wikipedia.org/wiki/Fletcher%27s_checksum#Fletcher-64"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let a = 0, + b = 0; + if (ArrayBuffer.isView(input)) { + input = new DataView(input.buffer, input.byteOffset, input.byteLength); + } else { + input = new DataView(input); + } + + for (let i = 0; i < input.byteLength - 3; i += 4) { + a = (a + input.getUint32(i, true)) % 0xffffffff; + b = (b + a) % 0xffffffff; + } + if (input.byteLength % 4 !== 0) { + let lastValue = 0; + for (let i = 0; i < input.byteLength % 4; i++) { + lastValue = (lastValue << 8) | input.getUint8(input.byteLength - 1 - i); + } + a = (a + lastValue) % 0xffffffff; + b = (b + a) % 0xffffffff; + } + + return Utils.hex(b >>> 0, 8) + Utils.hex(a >>> 0, 8); + } + +} + +export default Fletcher64Checksum; diff --git a/plugins/srktoolbox/src/core/operations/Fletcher8Checksum.mjs b/plugins/srktoolbox/src/core/operations/Fletcher8Checksum.mjs new file mode 100644 index 00000000..6efcf577 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Fletcher8Checksum.mjs @@ -0,0 +1,52 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +/** + * Fletcher-8 Checksum operation + */ +class Fletcher8Checksum extends Operation { + + /** + * Fletcher8Checksum constructor + */ + constructor() { + super(); + + this.name = "Fletcher-8校验和"; + this.module = "Crypto"; + this.description = "Fletcher校验和是用于计算位置相关校验和的算法,由Lawrence Livermore Labs的John Gould Fletcher在20世纪70年代末期设计。

Fletcher校验和可以提供接近循环冗余校验(Cyclic redundancy check, CRC)的错误检测功能,但所需的计算更少。"; + this.infoURL = "https://wikipedia.org/wiki/Fletcher%27s_checksum"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let a = 0, + b = 0; + input = new Uint8Array(input); + + for (let i = 0; i < input.length; i++) { + a = (a + input[i]) % 0xf; + b = (b + a) % 0xf; + } + + return Utils.hex(((b << 4) | a) >>> 0, 2); + } + +} + +export default Fletcher8Checksum; diff --git a/plugins/srktoolbox/src/core/operations/FlipImage.mjs b/plugins/srktoolbox/src/core/operations/FlipImage.mjs new file mode 100644 index 00000000..2cbfce91 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/FlipImage.mjs @@ -0,0 +1,107 @@ +/** + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { isImage } from "../lib/FileType.mjs"; +import { toBase64 } from "../lib/Base64.mjs"; +import { isWorkerEnvironment } from "../Utils.mjs"; +import { Jimp, JimpMime } from "jimp"; + +/** + * Flip Image operation + */ +class FlipImage extends Operation { + /** + * FlipImage constructor + */ + constructor() { + super(); + + this.name = "图像翻转"; + this.module = "Image"; + this.description = "将图像按X轴或Y轴翻转。"; + this.infoURL = ""; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.presentType = "html"; + this.args = [ + { + name: "翻转轴", + type: "option", + value: ["水平", "垂直"], + }, + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {byteArray} + */ + async run(input, args) { + const [flipAxis] = args; + if (!isImage(input)) { + throw new OperationError("无效的文件类型。"); + } + + let image; + try { + image = await Jimp.read(input); + } catch (err) { + throw new OperationError(`载入图像出错:(${err})`); + } + try { + if (isWorkerEnvironment()) + self.sendStatusMessage("翻转图像……"); + switch (flipAxis) { + case "水平": + image.flip({ + horizontal: true, + vertical: false, + }); + break; + case "垂直": + image.flip({ + horizontal: false, + vertical: true, + }); + break; + } + + let imageBuffer; + if (image.mime === "image/gif") { + imageBuffer = await image.getBuffer(JimpMime.png); + } else { + imageBuffer = await image.getBuffer(image.mime); + } + return imageBuffer.buffer; + } catch (err) { + throw new OperationError(`翻转图像错误:(${err})`); + } + } + + /** + * Displays the flipped image using HTML for web apps + * @param {ArrayBuffer} data + * @returns {html} + */ + present(data) { + if (!data.byteLength) return ""; + const dataArray = new Uint8Array(data); + + const type = isImage(dataArray); + if (!type) { + throw new OperationError("无效的文件类型。"); + } + + return ``; + } +} + +export default FlipImage; diff --git a/plugins/srktoolbox/src/core/operations/Fork.mjs b/plugins/srktoolbox/src/core/operations/Fork.mjs new file mode 100644 index 00000000..0ed48742 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Fork.mjs @@ -0,0 +1,128 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Recipe from "../Recipe.mjs"; +import Dish from "../Dish.mjs"; + +/** + * Fork operation + */ +class Fork extends Operation { + + /** + * Fork constructor + */ + constructor() { + super(); + + this.name = "Fork"; + this.flowControl = true; + this.module = "Default"; + this.description = "将输入数据按照给定的分隔符分割,并对分割后的每条数据单独执行下面的所有操作。

例如:解码多个Base64字符串,将所有的字符串每行一个放置在输入框,然后使用“Fork”和“Base64解码”操作。每个字符串都会被单独解码。"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "分割分隔符", + "type": "binaryShortString", + "value": "\\n" + }, + { + "name": "合并分隔符", + "type": "binaryShortString", + "value": "\\n" + }, + { + "name": "忽略报错", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @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. + */ + async run(state) { + const opList = state.opList, + inputType = opList[state.progress].inputType, + outputType = opList[state.progress].outputType, + input = await state.dish.get(inputType), + ings = opList[state.progress].ingValues, + [splitDelim, mergeDelim, ignoreErrors] = ings, + subOpList = []; + let inputs = [], + i; + + if (input) + inputs = input.split(splitDelim); + + // Set to 1 as if we are here, then there is one, the current one. + let numOp = 1; + // Create subOpList for each tranche to operate on + // all remaining operations unless we encounter a Merge + for (i = state.progress + 1; i < opList.length; i++) { + if (opList[i].name === "Merge" && !opList[i].disabled) { + numOp--; + if (numOp === 0 || opList[i].ingValues[0]) + break; + else + // Not this Fork's Merge. + subOpList.push(opList[i]); + } else { + if (opList[i].name === "Fork" || opList[i].name === "Subsection") + numOp++; + subOpList.push(opList[i]); + } + } + + const recipe = new Recipe(); + const outputs = []; + let progress = 0; + + state.forkOffset += state.progress + 1; + + recipe.addOperations(subOpList); + + // Take a deep(ish) copy of the ingredient values + const ingValues = subOpList.map(op => JSON.parse(JSON.stringify(op.ingValues))); + + // Run recipe over each tranche + for (i = 0; i < inputs.length; i++) { + // Baseline ing values for each tranche so that registers are reset + recipe.opList.forEach((op, i) => { + op.ingValues = JSON.parse(JSON.stringify(ingValues[i])); + }); + + const dish = new Dish(); + dish.set(inputs[i], inputType); + + try { + progress = await recipe.execute(dish, 0, state); + } catch (err) { + if (!ignoreErrors) { + throw err; + } + progress = err.progress + 1; + } + outputs.push(await dish.get(outputType)); + } + + state.dish.set(outputs.join(mergeDelim), outputType); + state.progress += progress; + return state; + } + +} + +export default Fork; diff --git a/plugins/srktoolbox/src/core/operations/FormatMACAddresses.mjs b/plugins/srktoolbox/src/core/operations/FormatMACAddresses.mjs new file mode 100644 index 00000000..919ede80 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/FormatMACAddresses.mjs @@ -0,0 +1,124 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; + +/** + * Format MAC addresses operation + */ +class FormatMACAddresses extends Operation { + + /** + * FormatMACAddresses constructor + */ + constructor() { + super(); + + this.name = "MAC地址格式化"; + this.module = "Default"; + this.description = "将给定的MAC地址用多种不同格式显示。

列表需要用回车、空格或逗号分隔。

警告:无法验证MAC地址的有效性。"; + this.infoURL = "https://wikipedia.org/wiki/MAC_address#Notational_conventions"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "输出大小写", + "type": "option", + "value": ["大小写", "仅大写", "仅小写"] + }, + { + "name": "无分隔", + "type": "boolean", + "value": true + }, + { + "name": "横杠分隔", + "type": "boolean", + "value": true + }, + { + "name": "冒号分隔", + "type": "boolean", + "value": true + }, + { + "name": "思科Style", + "type": "boolean", + "value": false + }, + { + "name": "IPv6接口ID", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + if (!input) return ""; + + const [ + outputCase, + noDelim, + dashDelim, + colonDelim, + ciscoStyle, + ipv6IntID + ] = args, + outputList = [], + macs = input.toLowerCase().split(/[,\s\r\n]+/); + + macs.forEach(function(mac) { + const cleanMac = mac.replace(/[:.-]+/g, ""), + macHyphen = cleanMac.replace(/(.{2}(?=.))/g, "$1-"), + macColon = cleanMac.replace(/(.{2}(?=.))/g, "$1:"), + macCisco = cleanMac.replace(/(.{4}(?=.))/g, "$1."); + let macIPv6 = cleanMac.slice(0, 6) + "fffe" + cleanMac.slice(6); + + macIPv6 = macIPv6.replace(/(.{4}(?=.))/g, "$1:"); + let bite = parseInt(macIPv6.slice(0, 2), 16) ^ 2; + bite = bite.toString(16).padStart(2, "0"); + macIPv6 = bite + macIPv6.slice(2); + + if (outputCase === "仅小写") { + if (noDelim) outputList.push(cleanMac); + if (dashDelim) outputList.push(macHyphen); + if (colonDelim) outputList.push(macColon); + if (ciscoStyle) outputList.push(macCisco); + if (ipv6IntID) outputList.push(macIPv6); + } else if (outputCase === "仅大写") { + if (noDelim) outputList.push(cleanMac.toUpperCase()); + if (dashDelim) outputList.push(macHyphen.toUpperCase()); + if (colonDelim) outputList.push(macColon.toUpperCase()); + if (ciscoStyle) outputList.push(macCisco.toUpperCase()); + if (ipv6IntID) outputList.push(macIPv6.toUpperCase()); + } else { + if (noDelim) outputList.push(cleanMac, cleanMac.toUpperCase()); + if (dashDelim) outputList.push(macHyphen, macHyphen.toUpperCase()); + if (colonDelim) outputList.push(macColon, macColon.toUpperCase()); + if (ciscoStyle) outputList.push(macCisco, macCisco.toUpperCase()); + if (ipv6IntID) outputList.push(macIPv6, macIPv6.toUpperCase()); + } + + outputList.push( + "" // Empty line to delimit groups + ); + }); + + // Return the data as a string + return outputList.join("\n"); + } + +} + +export default FormatMACAddresses; diff --git a/plugins/srktoolbox/src/core/operations/FrequencyDistribution.mjs b/plugins/srktoolbox/src/core/operations/FrequencyDistribution.mjs new file mode 100644 index 00000000..db2b25f1 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/FrequencyDistribution.mjs @@ -0,0 +1,134 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Frequency distribution operation + */ +class FrequencyDistribution extends Operation { + + /** + * FrequencyDistribution constructor + */ + constructor() { + super(); + + this.name = "频率分布"; + this.module = "Default"; + this.description = "将数据中字节的分布绘制成图表。"; + this.infoURL = "https://wikipedia.org/wiki/Frequency_distribution"; + this.inputType = "ArrayBuffer"; + this.outputType = "json"; + this.presentType = "html"; + this.args = [ + { + "name": "显示 0%", + "type": "boolean", + "value": true + }, + { + "name": "显示 ASCII", + "type": "boolean", + "value": true + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {json} + */ + run(input, args) { + const data = new Uint8Array(input); + if (!data.length) throw new OperationError("无数据"); + + const distrib = new Array(256).fill(0), + percentages = new Array(256), + len = data.length; + let i; + + // Count bytes + for (i = 0; i < len; i++) { + distrib[data[i]]++; + } + + // Calculate percentages + let repr = 0; + for (i = 0; i < 256; i++) { + if (distrib[i] > 0) repr++; + percentages[i] = distrib[i] / len * 100; + } + + return { + "dataLength": len, + "percentages": percentages, + "distribution": distrib, + "bytesRepresented": repr + }; + } + + /** + * Displays the frequency distribution as a bar chart for web apps. + * + * @param {json} freq + * @returns {html} + */ + present(freq, args) { + const [showZeroes, showAscii] = args; + + // Print + let output = `
+数据长度: ${freq.dataLength} +已展示的字节数: ${freq.bytesRepresented} +未展示的字节数: ${256 - freq.bytesRepresented} + + + + ${showAscii ? "" : ""}`; + + for (let i = 0; i < 256; i++) { + if (freq.distribution[i] || showZeroes) { + let c = ""; + if (showAscii) { + if (i <= 32) { + c = String.fromCharCode(0x2400 + i); + } else if (i === 127) { + c = String.fromCharCode(0x2421); + } else { + c = String.fromCharCode(i); + } + } + const bite = ``, + ascii = showAscii ? `` : "", + percentage = ``, + bars = ``; + + output += `${bite}${ascii}${percentage}${bars}`; + } + } + + output += "
字节ASCII百分比
${Utils.hex(i, 2)}${c}${(freq.percentages[i].toFixed(2).replace(".00", "") + "%").padEnd(8, " ")}${Array(Math.ceil(freq.percentages[i])+1).join("|")}
"; + return output; + } + +} + +export default FrequencyDistribution; diff --git a/plugins/srktoolbox/src/core/operations/FromBCD.mjs b/plugins/srktoolbox/src/core/operations/FromBCD.mjs new file mode 100644 index 00000000..ab54690a --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/FromBCD.mjs @@ -0,0 +1,125 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import {ENCODING_SCHEME, ENCODING_LOOKUP, FORMAT} from "../lib/BCD.mjs"; +import BigNumber from "bignumber.js"; + +/** + * From BCD operation + */ +class FromBCD extends Operation { + + /** + * FromBCD constructor + */ + constructor() { + super(); + + this.name = "BCD码解码"; + this.module = "Default"; + this.description = "BCD码(Binary-Coded Decimal)是一种十进制数字编码的形式。在这种编码下,每个十进制数字用一串单独的二进制比特来存储与表示。常见的有以4位或8位表示1个十进制数字。有时会用特殊的码位表示特殊符号。"; + this.infoURL = "https://wikipedia.org/wiki/Binary-coded_decimal"; + this.inputType = "string"; + this.outputType = "BigNumber"; + this.args = [ + { + "name": "编码方式", + "type": "option", + "value": ENCODING_SCHEME + }, + { + "name": "压缩", + "type": "boolean", + "value": true + }, + { + "name": "有符号", + "type": "boolean", + "value": false + }, + { + "name": "输入格式", + "type": "option", + "value": FORMAT + } + ]; + this.checks = [ + { + pattern: "^(?:\\d{4} ){3,}\\d{4}$", + flags: "", + args: ["8 4 2 1", true, false, "半字节"] + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {BigNumber} + */ + run(input, args) { + const encoding = ENCODING_LOOKUP[args[0]], + packed = args[1], + signed = args[2], + inputFormat = args[3], + nibbles = []; + + let output = "", + byteArray; + + // Normalise the input + switch (inputFormat) { + case "半字节": + case "字节": + input = input.replace(/\s/g, ""); + for (let i = 0; i < input.length; i += 4) { + nibbles.push(parseInt(input.substr(i, 4), 2)); + } + break; + case "原始数据": + default: + byteArray = new Uint8Array(Utils.strToArrayBuffer(input)); + byteArray.forEach(b => { + nibbles.push(b >>> 4); + nibbles.push(b & 15); + }); + break; + } + + if (!packed) { + // Discard each high nibble + for (let i = 0; i < nibbles.length; i++) { + nibbles.splice(i, 1); // lgtm [js/loop-iteration-skipped-due-to-shifting] + } + } + + if (signed) { + const sign = nibbles.pop(); + if (sign === 13 || + sign === 11) { + // Negative + output += "-"; + } + } + + nibbles.forEach(n => { + if (isNaN(n)) throw new OperationError("无效输入"); + const val = encoding.indexOf(n); + if (val < 0) throw new OperationError(`值 ${Utils.bin(n, 4)} 无法被编码`); + output += val.toString(); + }); + + return new BigNumber(output); + } + +} + +export default FromBCD; diff --git a/plugins/srktoolbox/src/core/operations/FromBase.mjs b/plugins/srktoolbox/src/core/operations/FromBase.mjs new file mode 100644 index 00000000..ff1360a7 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/FromBase.mjs @@ -0,0 +1,66 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import BigNumber from "bignumber.js"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * From Base operation + */ +class FromBase extends Operation { + + /** + * FromBase constructor + */ + constructor() { + super(); + + this.name = "其它进制数转十进制"; + this.module = "Default"; + this.description = "把给定进制的数字转换成十进制"; + this.infoURL = "https://wikipedia.org/wiki/Radix"; + this.inputType = "string"; + this.outputType = "BigNumber"; + this.args = [ + { + "name": "进制", + "type": "number", + "value": 36 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {BigNumber} + */ + run(input, args) { + const radix = args[0]; + if (radix < 2 || radix > 36) { + throw new OperationError("错误:进制必须在2~36之间"); + } + + const number = input.replace(/\s/g, "").split("."); + let result = new BigNumber(number[0], radix); + + if (number.length === 1) return result; + + // Fractional part + for (let i = 0; i < number[1].length; i++) { + const digit = new BigNumber(number[1][i], radix); + result += digit.div(Math.pow(radix, i+1)); + } + + return result; + } + +} + +export default FromBase; diff --git a/plugins/srktoolbox/src/core/operations/FromBase32.mjs b/plugins/srktoolbox/src/core/operations/FromBase32.mjs new file mode 100644 index 00000000..f5e5da68 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/FromBase32.mjs @@ -0,0 +1,108 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import {ALPHABET_OPTIONS} from "../lib/Base32.mjs"; + + +/** + * From Base32 operation + */ +class FromBase32 extends Operation { + + /** + * FromBase32 constructor + */ + constructor() { + super(); + + this.name = "Base32解码"; + this.module = "Default"; + this.description = "Base32是把字节数据转换成特定字符组合的编码方式,编码后便于人类阅读,也方便计算机读取。Base32比Base64使用的字母表小一些,通常只包含大写字母和数字2到7。"; + this.infoURL = "https://wikipedia.org/wiki/Base32"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = [ + { + name: "可用字符", + type: "editableOption", + value: ALPHABET_OPTIONS + }, + { + name: "移除输入中的非可用字符", + type: "boolean", + value: true + } + ]; + this.checks = [ + { + pattern: "^(?:[A-Z2-7]{8})+(?:[A-Z2-7]{2}={6}|[A-Z2-7]{4}={4}|[A-Z2-7]{5}={3}|[A-Z2-7]{7}={1})?$", + flags: "", + args: ["A-Z2-7=", false] + }, + { + pattern: "^(?:[0-9A-V]{8})+(?:[0-9A-V]{2}={6}|[0-9A-V]{4}={4}|[0-9A-V]{5}={3}|[0-9A-V]{7}={1})?$", + flags: "", + args: ["0-9A-V=", false] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + if (!input) return []; + + const alphabet = args[0] ? + Utils.expandAlphRange(args[0]).join("") : "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=", + removeNonAlphChars = args[1], + output = []; + + let chr1, chr2, chr3, chr4, chr5, + enc1, enc2, enc3, enc4, enc5, enc6, enc7, enc8, + i = 0; + + if (removeNonAlphChars) { + const re = new RegExp("[^" + alphabet.replace(/[\]\\\-^]/g, "\\$&") + "]", "g"); + input = input.replace(re, ""); + } + + while (i < input.length) { + enc1 = alphabet.indexOf(input.charAt(i++)); + enc2 = alphabet.indexOf(input.charAt(i++) || "="); + enc3 = alphabet.indexOf(input.charAt(i++) || "="); + enc4 = alphabet.indexOf(input.charAt(i++) || "="); + enc5 = alphabet.indexOf(input.charAt(i++) || "="); + enc6 = alphabet.indexOf(input.charAt(i++) || "="); + enc7 = alphabet.indexOf(input.charAt(i++) || "="); + enc8 = alphabet.indexOf(input.charAt(i++) || "="); + + chr1 = (enc1 << 3) | (enc2 >> 2); + chr2 = ((enc2 & 3) << 6) | (enc3 << 1) | (enc4 >> 4); + chr3 = ((enc4 & 15) << 4) | (enc5 >> 1); + chr4 = ((enc5 & 1) << 7) | (enc6 << 2) | (enc7 >> 3); + chr5 = ((enc7 & 7) << 5) | enc8; + + output.push(chr1); + if ((enc2 & 3) !== 0 || enc3 !== 32) output.push(chr2); + if ((enc4 & 15) !== 0 || enc5 !== 32) output.push(chr3); + if ((enc5 & 1) !== 0 || enc6 !== 32) output.push(chr4); + if ((enc7 & 7) !== 0 || enc8 !== 32) output.push(chr5); + } + + return output; + } + +} + +export default FromBase32; + diff --git a/plugins/srktoolbox/src/core/operations/FromBase45.mjs b/plugins/srktoolbox/src/core/operations/FromBase45.mjs new file mode 100644 index 00000000..d527bffc --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/FromBase45.mjs @@ -0,0 +1,103 @@ +/** + * @author Thomas Weißschuh [thomas@t-8ch.de] + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import {ALPHABET, highlightToBase45, highlightFromBase45} from "../lib/Base45.mjs"; +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; + + +/** + * From Base45 operation + */ +class FromBase45 extends Operation { + + /** + * FromBase45 constructor + */ + constructor() { + super(); + + this.name = "Base45解码"; + this.module = "Default"; + this.description = "Base45是把字节数据转换成特定字符组合的编码方式,编码后便于人类阅读,也方便计算机读取。越高的Base数目会生成越短的字符串。Base45是为二维码优化的编码方式。"; + this.infoURL = "https://wikipedia.org/wiki/List_of_numeral_systems"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = [ + { + name: "可用字符", + type: "string", + value: ALPHABET + }, + { + name: "移除输入中的非可用字符", + type: "boolean", + value: true + }, + ]; + + this.highlight = highlightFromBase45; + this.highlightReverse = highlightToBase45; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + if (!input) return []; + const alphabet = Utils.expandAlphRange(args[0]).join(""); + const removeNonAlphChars = args[1]; + + const res = []; + + // Remove non-alphabet characters + if (removeNonAlphChars) { + const re = new RegExp("[^" + alphabet.replace(/[[\]\\\-^$]/g, "\\$&") + "]", "g"); + input = input.replace(re, ""); + } + + for (const triple of Utils.chunked(input, 3)) { + triple.reverse(); + let b = 0; + for (const c of triple) { + const idx = alphabet.indexOf(c); + if (idx === -1) { + throw new OperationError(`非可用字符: '${c}'`); + } + b *= 45; + b += idx; + } + + if (b > 65535) { + throw new OperationError(`超出编码范围: '${triple.join("")}'`); + } + + if (triple.length > 2) { + /** + * The last triple may only have 2 bytes so we push the MSB when we got 3 bytes + * Pushing MSB + */ + res.push(b >> 8); + } + + /** + * Pushing LSB + */ + res.push(b & 0xff); + + } + + return res; + } + +} + +export default FromBase45; diff --git a/plugins/srktoolbox/src/core/operations/FromBase58.mjs b/plugins/srktoolbox/src/core/operations/FromBase58.mjs new file mode 100644 index 00000000..adb2878e --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/FromBase58.mjs @@ -0,0 +1,115 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import {ALPHABET_OPTIONS} from "../lib/Base58.mjs"; + +/** + * From Base58 operation + */ +class FromBase58 extends Operation { + + /** + * FromBase58 constructor + */ + constructor() { + super(); + + this.name = "Base58解码"; + this.module = "Default"; + this.description = "Base58(类似于Base64)是把字节数据转换成特定字符组合的编码方式。和Base64的区别是移除了形状相近的易混字符(例如l、I、0和O)来提高可读性。

此操作将已编码成ASCII字符的Base58字符串解码为原始数据。

例: StV1DL6CwTryKyV 解码为 hello world

Base58常见于加密货币(比特币、Ripple等)。"; + this.infoURL = "https://wikipedia.org/wiki/Base58"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = [ + { + "name": "可用字符", + "type": "editableOption", + "value": ALPHABET_OPTIONS + }, + { + "name": "移除输入中的非可用字符", + "type": "boolean", + "value": true + } + ]; + this.checks = [ + { + pattern: "^[1-9A-HJ-NP-Za-km-z]{20,}$", + flags: "", + args: ["123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz", false] + }, + { + pattern: "^[1-9A-HJ-NP-Za-km-z]{20,}$", + flags: "", + args: ["rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz", false] + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + let alphabet = args[0] || ALPHABET_OPTIONS[0].value; + const removeNonAlphaChars = args[1] === undefined ? true : args[1], + result = []; + + alphabet = Utils.expandAlphRange(alphabet).join(""); + + if (alphabet.length !== 58 || + [].unique.call(alphabet).length !== 58) { + throw new OperationError("错误:可用字符必须是58个"); + } + + if (input.length === 0) return []; + + let zeroPrefix = 0; + for (let i = 0; i < input.length && input[i] === alphabet[0]; i++) { + zeroPrefix++; + } + + [].forEach.call(input, function(c, charIndex) { + const index = alphabet.indexOf(c); + + if (index === -1) { + if (removeNonAlphaChars) { + return; + } else { + throw new OperationError(`字符'${c}'(位置 ${charIndex})不是可用字符`); + } + } + + let carry = index; + + for (let i = 0; i < result.length; i++) { + carry += result[i] * 58; + result[i] = carry & 0xFF; + carry = carry >> 8; + } + + while (carry > 0) { + result.push(carry & 0xFF); + carry = carry >> 8; + } + }); + + while (zeroPrefix--) { + result.push(0); + } + + return result.reverse(); + } + +} + +export default FromBase58; diff --git a/plugins/srktoolbox/src/core/operations/FromBase62.mjs b/plugins/srktoolbox/src/core/operations/FromBase62.mjs new file mode 100644 index 00000000..9d1b3f8e --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/FromBase62.mjs @@ -0,0 +1,67 @@ +/** + * @author tcode2k16 [tcode2k16@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github + */ + +import Operation from "../Operation.mjs"; +import BigNumber from "bignumber.js"; +import Utils from "../Utils.mjs"; + + +/** + * From Base62 operation + */ +class FromBase62 extends Operation { + + /** + * FromBase62 constructor + */ + constructor() { + super(); + + this.name = "Base62解码"; + this.module = "Default"; + this.description = "Base62是把字节数据转换成特定字符组合的编码方式,编码后便于人类阅读,也方便计算机读取。

此操作将已编码成ASCII字符的Base62字符串解码为原始数据。"; + this.infoURL = "https://wikipedia.org/wiki/List_of_numeral_systems"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = [ + { + name: "可用字符", + type: "string", + value: "0-9A-Za-z" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + if (input.length < 1) return []; + const alphabet = Utils.expandAlphRange(args[0]).join(""); + const BN62 = BigNumber.clone({ ALPHABET: alphabet }); + + const re = new RegExp("[^" + alphabet.replace(/[[\]\\\-^$]/g, "\\$&") + "]", "g"); + input = input.replace(re, ""); + + // Read number in using Base62 alphabet + const number = new BN62(input, 62); + // Copy to new BigNumber object that uses the default alphabet + const normalized = new BigNumber(number); + + // Convert to hex and add leading 0 if required + let hex = normalized.toString(16); + if (hex.length % 2 !== 0) hex = "0" + hex; + + return Utils.convertToByteArray(hex, "Hex"); + } + +} + +export default FromBase62; diff --git a/plugins/srktoolbox/src/core/operations/FromBase64.mjs b/plugins/srktoolbox/src/core/operations/FromBase64.mjs new file mode 100644 index 00000000..5f4bcce6 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/FromBase64.mjs @@ -0,0 +1,177 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import {fromBase64, ALPHABET_OPTIONS} from "../lib/Base64.mjs"; + +/** + * From Base64 operation + */ +class FromBase64 extends Operation { + + /** + * FromBase64 constructor + */ + constructor() { + super(); + + this.name = "Base64解码"; + this.module = "Default"; + this.description = "Base64是把字节数据转换成特定字符组合的编码方式,编码后便于人类阅读,也方便计算机读取。

此操作将已编码成ASCII字符的Base64字符串解码为原始数据。

例: aGVsbG8= 解码成 hello"; + this.infoURL = "https://wikipedia.org/wiki/Base64"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = [ + { + name: "可用字符", + type: "editableOption", + value: ALPHABET_OPTIONS + }, + { + name: "移除输入中的非可用字符", + type: "boolean", + value: true + }, + { + name: "严格模式", + type: "boolean", + value: false + } + ]; + this.checks = [ + { + pattern: "^\\s*(?:[A-Z\\d+/]{4})+(?:[A-Z\\d+/]{2}==|[A-Z\\d+/]{3}=)?\\s*$", + flags: "i", + args: ["A-Za-z0-9+/=", true, false] + }, + { + pattern: "^\\s*[A-Z\\d\\-_]{20,}\\s*$", + flags: "i", + args: ["A-Za-z0-9-_", true, false] + }, + { + pattern: "^\\s*(?:[A-Z\\d+\\-]{4}){5,}(?:[A-Z\\d+\\-]{2}==|[A-Z\\d+\\-]{3}=)?\\s*$", + flags: "i", + args: ["A-Za-z0-9+\\-=", true, false] + }, + { + pattern: "^\\s*(?:[A-Z\\d./]{4}){5,}(?:[A-Z\\d./]{2}==|[A-Z\\d./]{3}=)?\\s*$", + flags: "i", + args: ["./0-9A-Za-z=", true, false] + }, + { + pattern: "^\\s*[A-Z\\d_.]{20,}\\s*$", + flags: "i", + args: ["A-Za-z0-9_.", true, false] + }, + { + pattern: "^\\s*(?:[A-Z\\d._]{4}){5,}(?:[A-Z\\d._]{2}--|[A-Z\\d._]{3}-)?\\s*$", + flags: "i", + args: ["A-Za-z0-9._-", true, false] + }, + { + pattern: "^\\s*(?:[A-Z\\d+/]{4}){5,}(?:[A-Z\\d+/]{2}==|[A-Z\\d+/]{3}=)?\\s*$", + flags: "i", + args: ["0-9a-zA-Z+/=", true, false] + }, + { + pattern: "^\\s*(?:[A-Z\\d+/]{4}){5,}(?:[A-Z\\d+/]{2}==|[A-Z\\d+/]{3}=)?\\s*$", + flags: "i", + args: ["0-9A-Za-z+/=", true, false] + }, + { + pattern: "^[ !\"#$%&'()*+,\\-./\\d:;<=>?@A-Z[\\\\\\]^_]{20,}$", + flags: "", + args: [" -_", false, false] + }, + { + pattern: "^\\s*[A-Z\\d+\\-]{20,}\\s*$", + flags: "i", + args: ["+\\-0-9A-Za-z", true, false] + }, + { + pattern: "^\\s*[!\"#$%&'()*+,\\-0-689@A-NP-VX-Z[`a-fh-mp-r]{20,}\\s*$", + flags: "", + args: ["!-,-0-689@A-NP-VX-Z[`a-fh-mp-r", true, false] + }, + { + pattern: "^\\s*(?:[N-ZA-M\\d+/]{4}){5,}(?:[N-ZA-M\\d+/]{2}==|[N-ZA-M\\d+/]{3}=)?\\s*$", + flags: "i", + args: ["N-ZA-Mn-za-m0-9+/=", true, false] + }, + { + pattern: "^\\s*[A-Z\\d./]{20,}\\s*$", + flags: "i", + args: ["./0-9A-Za-z", true, false] + }, + { + pattern: "^\\s*(?:[A-Z=\\d\\+/]{4}){5,}(?:[A-Z=\\d\\+/]{2}CC|[A-Z=\\d\\+/]{3}C)?\\s*$", + flags: "i", + args: ["/128GhIoPQROSTeUbADfgHijKLM+n0pFWXY456xyzB7=39VaqrstJklmNuZvwcdEC", true, false] + }, + { + pattern: "^\\s*(?:[A-Z=\\d\\+/]{4}){5,}(?:[A-Z=\\d\\+/]{2}55|[A-Z=\\d\\+/]{3}5)?\\s*$", + flags: "i", + args: ["3GHIJKLMNOPQRSTUb=cdefghijklmnopWXYZ/12+406789VaqrstuvwxyzABCDEF5", true, false] + }, + { + pattern: "^\\s*(?:[A-Z=\\d\\+/]{4}){5,}(?:[A-Z=\\d\\+/]{2}22|[A-Z=\\d\\+/]{3}2)?\\s*$", + flags: "i", + args: ["ZKj9n+yf0wDVX1s/5YbdxSo=ILaUpPBCHg8uvNO4klm6iJGhQ7eFrWczAMEq3RTt2", true, false] + }, + { + pattern: "^\\s*(?:[A-Z=\\d\\+/]{4}){5,}(?:[A-Z=\\d\\+/]{2}55|[A-Z=\\d\\+/]{3}5)?\\s*$", + flags: "i", + args: ["HNO4klm6ij9n+J2hyf0gzA8uvwDEq3X1Q7ZKeFrWcVTts/MRGYbdxSo=ILaUpPBC5", true, false] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const [alphabet, removeNonAlphChars, strictMode] = args; + + return fromBase64(input, alphabet, "byteArray", removeNonAlphChars, strictMode); + } + + /** + * Highlight to Base64 + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + pos[0].start = Math.ceil(pos[0].start / 4 * 3); + pos[0].end = Math.floor(pos[0].end / 4 * 3); + return pos; + } + + /** + * Highlight from Base64 + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + pos[0].start = Math.floor(pos[0].start / 3 * 4); + pos[0].end = Math.ceil(pos[0].end / 3 * 4); + return pos; + } +} + +export default FromBase64; diff --git a/plugins/srktoolbox/src/core/operations/FromBase85.mjs b/plugins/srktoolbox/src/core/operations/FromBase85.mjs new file mode 100644 index 00000000..e828d049 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/FromBase85.mjs @@ -0,0 +1,162 @@ +/** + * @author PenguinGeorge [george@penguingeorge.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import {ALPHABET_OPTIONS} from "../lib/Base85.mjs"; + +/** + * From Base85 operation + */ +class FromBase85 extends Operation { + + /** + * From Base85 constructor + */ + constructor() { + super(); + + this.name = "Base85解码"; + this.module = "Default"; + this.description = "Base85(也叫Ascii85)是把字节数据转换成特定字符组合的编码方式。通常比Base64效率更高。

此操作将使用ASCII字符的Base64字符串解码成原始数据。字符表可选,带有预设。

例: BOu!rD]j7BEbo7 解码成 hello world

Base85在Adobe的PostScript和PDF格式中较为常见。"; + this.infoURL = "https://wikipedia.org/wiki/Ascii85"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = [ + { + name: "可用字符", + type: "editableOption", + value: ALPHABET_OPTIONS + }, + { + name: "移除不可用字符", + type: "boolean", + value: true + }, + { + name: "全零分组字符", + type: "binaryShortString", + value: "z", + maxLength: 1 + } + ]; + this.checks = [ + { + pattern: + "^\\s*(?:<~)?" + // Optional whitespace and starting marker + "[\\s!-uz]*" + // Any amount of base85 characters and whitespace + "[!-uz]{15}" + // At least 15 continoues base85 characters without whitespace + "[\\s!-uz]*" + // Any amount of base85 characters and whitespace + "(?:~>)?\\s*$", // Optional ending marker and whitespace + args: ["!-u"], + }, + { + pattern: + "^" + + "[\\s0-9a-zA-Z.\\-:+=^!/*?&<>()[\\]{}@%$#]*" + + "[0-9a-zA-Z.\\-:+=^!/*?&<>()[\\]{}@%$#]{15}" + // At least 15 continoues base85 characters without whitespace + "[\\s0-9a-zA-Z.\\-:+=^!/*?&<>()[\\]{}@%$#]*" + + "$", + args: ["0-9a-zA-Z.\\-:+=^!/*?&<>()[]{}@%$#"], + }, + { + pattern: + "^" + + "[\\s0-9A-Za-z!#$%&()*+\\-;<=>?@^_`{|}~]*" + + "[0-9A-Za-z!#$%&()*+\\-;<=>?@^_`{|}~]{15}" + // At least 15 continoues base85 characters without whitespace + "[\\s0-9A-Za-z!#$%&()*+\\-;<=>?@^_`{|}~]*" + + "$", + args: ["0-9A-Za-z!#$%&()*+\\-;<=>?@^_`{|}~"], + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const alphabet = Utils.expandAlphRange(args[0]).join(""), + removeNonAlphChars = args[1], + allZeroGroupChar = typeof args[2] === "string" ? args[2].slice(0, 1) : "", + result = []; + + if (alphabet.length !== 85 || + [].unique.call(alphabet).length !== 85) { + throw new OperationError("可用字符必须为85个"); + } + + if (allZeroGroupChar && alphabet.includes(allZeroGroupChar)) { + throw new OperationError("全零分组字符不能出现在可用字符中"); + } + + // Remove delimiters if present + const matches = input.match(/^<~(.+?)~>$/); + if (matches !== null) input = matches[1]; + + // Remove non-alphabet characters + if (removeNonAlphChars) { + const re = new RegExp("[^~" + allZeroGroupChar +alphabet.replace(/[[\]\\\-^$]/g, "\\$&") + "]", "g"); + input = input.replace(re, ""); + // Remove delimiters again if present (incase of non-alphabet characters in front/behind delimiters) + const matches = input.match(/^<~(.+?)~>$/); + if (matches !== null) input = matches[1]; + } + + if (input.length === 0) return []; + + let i = 0; + let block, blockBytes; + while (i < input.length) { + if (input[i] === allZeroGroupChar) { + result.push(0, 0, 0, 0); + i++; + } else { + let digits = []; + digits = input + .substr(i, 5) + .split("") + .map((chr, idx) => { + const digit = alphabet.indexOf(chr); + if ((digit < 0 || digit > 84) && chr !== allZeroGroupChar) { + throw `无效的字符 '${chr}' ,位置: ${i + idx}`; + } + return digit; + }); + + block = + digits[0] * 52200625 + + digits[1] * 614125 + + (i + 2 < input.length ? digits[2] : 84) * 7225 + + (i + 3 < input.length ? digits[3] : 84) * 85 + + (i + 4 < input.length ? digits[4] : 84); + + blockBytes = [ + (block >> 24) & 0xff, + (block >> 16) & 0xff, + (block >> 8) & 0xff, + block & 0xff + ]; + + if (input.length < i + 5) { + blockBytes.splice(input.length - (i + 5), 5); + } + + result.push.apply(result, blockBytes); + i += 5; + } + } + + return result; + } + +} + +export default FromBase85; diff --git a/plugins/srktoolbox/src/core/operations/FromBase92.mjs b/plugins/srktoolbox/src/core/operations/FromBase92.mjs new file mode 100644 index 00000000..416ea9bc --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/FromBase92.mjs @@ -0,0 +1,57 @@ +/** + * @author sg5506844 [sg5506844@gmail.com] + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import { base92Ord } from "../lib/Base92.mjs"; +import Operation from "../Operation.mjs"; + +/** + * From Base92 operation + */ +class FromBase92 extends Operation { + /** + * FromBase92 constructor + */ + constructor() { + super(); + + this.name = "Base92解码"; + this.module = "Default"; + this.description = "Base92是把字节数据转换成特定字符组合的编码方式,编码后便于人类阅读,也方便计算机读取。"; + this.infoURL = "https://wikipedia.org/wiki/List_of_numeral_systems"; + this.inputType = "string"; + this.outputType = "byteArray"; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const res = []; + let bitString = ""; + + for (let i = 0; i < input.length; i += 2) { + if (i + 1 !== input.length) { + const x = base92Ord(input[i]) * 91 + base92Ord(input[i + 1]); + bitString += x.toString(2).padStart(13, "0"); + } else { + const x = base92Ord(input[i]); + bitString += x.toString(2).padStart(6, "0"); + } + while (bitString.length >= 8) { + res.push(parseInt(bitString.slice(0, 8), 2)); + bitString = bitString.slice(8); + } + } + + return res; + } +} + +export default FromBase92; diff --git a/plugins/srktoolbox/src/core/operations/FromBech32.mjs b/plugins/srktoolbox/src/core/operations/FromBech32.mjs new file mode 100644 index 00000000..1464096a --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/FromBech32.mjs @@ -0,0 +1,151 @@ +/** + * @author Medjedtxm + * @copyright Crown Copyright 2025 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import { decode } from "../lib/Bech32.mjs"; +import { toHex } from "../lib/Hex.mjs"; + +/** + * From Bech32 operation + */ +class FromBech32 extends Operation { + + /** + * FromBech32 constructor + */ + constructor() { + super(); + + this.name = "Bech32解码"; + this.module = "Default"; + this.description = "Bech32 是一种编码方案,主要应用于比特币隔离见证地址(BIP-0173)。它采用 32 字符字母表,其中排除了易混淆的字符(1、b、i、o),并包含用于错误检测的校验和。

Bech32m(BIP-0350)是其更新版本,用于比特币 Taproot 地址。

自动检测功能会先尝试以 Bech32 格式解码,若校验和失败则尝试 Bech32m 格式。

输出格式选项允许您查看人类可读部分(HRP)及解码后的数据。"; + this.infoURL = "https://wikipedia.org/wiki/Bech32"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "编码方式", + "type": "option", + "value": ["自动检测", "Bech32", "Bech32m"] + }, + { + "name": "输出格式", + "type": "option", + "value": ["原始", "十六进制", "Bitcoin scriptPubKey", "HRP: Hex", "JSON"] + } + ]; + this.checks = [ + { + // Bitcoin mainnet SegWit/Taproot addresses + pattern: "^bc1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{6,87}$", + flags: "i", + args: ["自动检测", "十六进制"] + }, + { + // Bitcoin testnet addresses + pattern: "^tb1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{6,87}$", + flags: "i", + args: ["自动检测", "十六进制"] + }, + { + // AGE public keys + pattern: "^age1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{6,87}$", + flags: "i", + args: ["自动检测", "HRP: Hex"] + }, + { + // AGE secret keys + pattern: "^AGE-SECRET-KEY-1[QPZRY9X8GF2TVDW0S3JN54KHCE6MUA7L]{6,87}$", + flags: "", + args: ["自动检测", "HRP: Hex"] + }, + { + // Litecoin mainnet addresses + pattern: "^ltc1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{6,87}$", + flags: "i", + args: ["自动检测", "十六进制"] + }, + { + // Generic bech32 pattern + pattern: "^[a-z]{1,83}1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{6,}$", + flags: "i", + args: ["自动检测", "十六进制"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const encoding = args[0]; + const outputFormat = args[1]; + + input = input.trim(); + + if (input.length === 0) { + return ""; + } + + const decoded = decode(input, encoding); + + // Format output based on selected option + switch (outputFormat) { + case "原始": + return decoded.data.map(b => String.fromCharCode(b)).join(""); + + case "十六进制": + return toHex(decoded.data, ""); + + case "Bitcoin scriptPubKey": { + // Convert to Bitcoin scriptPubKey format as shown in BIP-0173/BIP-0350 + // Format: [OP_version][length][witness_program] + // OP_0 = 0x00, OP_1-OP_16 = 0x51-0x60 + if (decoded.witnessVersion === null || decoded.data.length < 2) { + // Not a SegWit address, fall back to hex + return toHex(decoded.data, ""); + } + const witnessVersion = decoded.data[0]; + const witnessProgram = decoded.data.slice(1); + + // Convert witness version to OP code + let opCode; + if (witnessVersion === 0) { + opCode = 0x00; // OP_0 + } else if (witnessVersion >= 1 && witnessVersion <= 16) { + opCode = 0x50 + witnessVersion; // OP_1 = 0x51, ..., OP_16 = 0x60 + } else { + // Invalid witness version, fall back to hex + return toHex(decoded.data, ""); + } + + // Build scriptPubKey: [OP_version][length][program] + const scriptPubKey = [opCode, witnessProgram.length, ...witnessProgram]; + return toHex(scriptPubKey, ""); + } + + case "HRP: Hex": + return `${decoded.hrp}: ${toHex(decoded.data, "")}`; + + case "JSON": + return JSON.stringify({ + hrp: decoded.hrp, + encoding: decoded.encoding, + data: toHex(decoded.data, "") + }, null, 2); + + default: + return toHex(decoded.data, ""); + } + } + +} + +export default FromBech32; diff --git a/plugins/srktoolbox/src/core/operations/FromBinary.mjs b/plugins/srktoolbox/src/core/operations/FromBinary.mjs new file mode 100644 index 00000000..358fbcf8 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/FromBinary.mjs @@ -0,0 +1,127 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import {BIN_DELIM_OPTIONS} from "../lib/Delim.mjs"; +import {fromBinary} from "../lib/Binary.mjs"; + +/** + * From Binary operation + */ +class FromBinary extends Operation { + + /** + * FromBinary constructor + */ + constructor() { + super(); + + this.name = "二进制转字符"; + this.module = "Default"; + this.description = "把二进制字符串解码为原先的内容

01001000 01101001 解码为 Hi"; + this.infoURL = "https://wikipedia.org/wiki/Binary_code"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = [ + { + "name": "分隔符", + "type": "option", + "value": BIN_DELIM_OPTIONS + }, + { + "name": "字节长度", + "type": "number", + "value": 8, + "min": 1 + } + ]; + this.checks = [ + { + pattern: "^(?:[01]{8})+$", + flags: "", + args: ["None"] + }, + { + pattern: "^(?:[01]{8})(?: [01]{8})*$", + flags: "", + args: ["Space"] + }, + { + pattern: "^(?:[01]{8})(?:,[01]{8})*$", + flags: "", + args: ["Comma"] + }, + { + pattern: "^(?:[01]{8})(?:;[01]{8})*$", + flags: "", + args: ["Semi-colon"] + }, + { + pattern: "^(?:[01]{8})(?::[01]{8})*$", + flags: "", + args: ["Colon"] + }, + { + pattern: "^(?:[01]{8})(?:\\n[01]{8})*$", + flags: "", + args: ["Line feed"] + }, + { + pattern: "^(?:[01]{8})(?:\\r\\n[01]{8})*$", + flags: "", + args: ["CRLF"] + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const byteLen = args[1] ? args[1] : 8; + return fromBinary(input, args[0], byteLen); + } + + /** + * Highlight From Binary + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + const delim = Utils.charRep(args[0] || "Space"); + pos[0].start = pos[0].start === 0 ? 0 : Math.floor(pos[0].start / (8 + delim.length)); + pos[0].end = pos[0].end === 0 ? 0 : Math.ceil(pos[0].end / (8 + delim.length)); + return pos; + } + + /** + * Highlight From Binary in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + const delim = Utils.charRep(args[0] || "Space"); + pos[0].start = pos[0].start * (8 + delim.length); + pos[0].end = pos[0].end * (8 + delim.length) - delim.length; + return pos; + } + +} + +export default FromBinary; diff --git a/plugins/srktoolbox/src/core/operations/FromBraille.mjs b/plugins/srktoolbox/src/core/operations/FromBraille.mjs new file mode 100644 index 00000000..f335f54e --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/FromBraille.mjs @@ -0,0 +1,72 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import {BRAILLE_LOOKUP} from "../lib/Braille.mjs"; + +/** + * From Braille operation + */ +class FromBraille extends Operation { + + /** + * FromBraille constructor + */ + constructor() { + super(); + + this.name = "盲文解码"; + this.module = "Default"; + this.description = "把六点盲文转换成文字。"; + this.infoURL = "https://wikipedia.org/wiki/Braille"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return input.split("").map(b => { + const idx = BRAILLE_LOOKUP.dot6.indexOf(b); + return idx < 0 ? b : BRAILLE_LOOKUP.ascii[idx]; + }).join(""); + } + + /** + * Highlight From Braille + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight From Braille 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 FromBraille; diff --git a/plugins/srktoolbox/src/core/operations/FromCaseInsensitiveRegex.mjs b/plugins/srktoolbox/src/core/operations/FromCaseInsensitiveRegex.mjs new file mode 100644 index 00000000..183a6c16 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/FromCaseInsensitiveRegex.mjs @@ -0,0 +1,41 @@ +/** + * @author masq [github.cyberchef@masq.cc] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; + +/** + * From Case Insensitive Regex operation + */ +class FromCaseInsensitiveRegex extends Operation { + + /** + * FromCaseInsensitiveRegex constructor + */ + constructor() { + super(); + + this.name = "从大小写不敏感正则恢复"; + this.module = "Default"; + this.description = "将大小写不敏感正则字符串恢复为大小写敏感形式(无法保证转换后的大小写形式正确),用于之前正则i选项不能用但现在能用了,所以你想转换回来的场合。

例如:[mM][oO][zZ][iI][lL][lL][aA]/[0-9].[0-9] .* 转换为 Mozilla/[0-9].[0-9] .*"; + this.infoURL = "https://wikipedia.org/wiki/Regular_expression"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return input.replace(/\[[a-z]{2}\]/ig, m => m[1].toUpperCase() === m[2].toUpperCase() ? m[1] : m); + } +} + +export default FromCaseInsensitiveRegex; diff --git a/plugins/srktoolbox/src/core/operations/FromCharcode.mjs b/plugins/srktoolbox/src/core/operations/FromCharcode.mjs new file mode 100644 index 00000000..809af3d7 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/FromCharcode.mjs @@ -0,0 +1,87 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import { DELIM_OPTIONS } from "../lib/Delim.mjs"; +import { isWorkerEnvironment } from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * From Charcode operation + */ +class FromCharcode extends Operation { + + /** + * FromCharcode constructor + */ + constructor() { + super(); + + this.name = "从字符码提取"; + this.module = "Default"; + this.description = "把Unicode字符码还原为字符。

例: 0393 03b5 03b9 03ac 20 03c3 03bf 03c5 解码为 Γειά σου"; + this.infoURL = "https://wikipedia.org/wiki/Plane_(Unicode)"; + this.inputType = "string"; + this.outputType = "ArrayBuffer"; + this.args = [ + { + "name": "分隔符", + "type": "option", + "value": DELIM_OPTIONS + }, + { + "name": "进制", + "type": "number", + "value": 16 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {ArrayBuffer} + * + * @throws {OperationError} if base out of range + */ + run(input, args) { + const delim = Utils.charRep(args[0] || "空格"), + base = args[1]; + let bites = input.split(delim), + i = 0; + + if (base < 2 || base > 36) { + throw new OperationError("错误:进制必须在2~36之间"); + } + + if (input.length === 0) { + return new ArrayBuffer; + } + + if (base !== 16 && isWorkerEnvironment()) self.setOption("attemptHighlight", false); + + // Split into groups of 2 if the whole string is concatenated and + // too long to be a single character + if (bites.length === 1 && input.length > 17) { + bites = []; + for (i = 0; i < input.length; i += 2) { + bites.push(input.slice(i, i+2)); + } + } + + let latin1 = ""; + for (i = 0; i < bites.length; i++) { + latin1 += Utils.chr(parseInt(bites[i], base)); + } + return Utils.strToArrayBuffer(latin1); + } + +} + +export default FromCharcode; diff --git a/plugins/srktoolbox/src/core/operations/FromDecimal.mjs b/plugins/srktoolbox/src/core/operations/FromDecimal.mjs new file mode 100644 index 00000000..ef689312 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/FromDecimal.mjs @@ -0,0 +1,90 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import {DELIM_OPTIONS} from "../lib/Delim.mjs"; +import {fromDecimal} from "../lib/Decimal.mjs"; + +/** + * From Decimal operation + */ +class FromDecimal extends Operation { + + /** + * FromDecimal constructor + */ + constructor() { + super(); + + this.name = "十进制转字符"; + this.module = "Default"; + this.description = "把十进制字符串解码为原先的内容。

例: 72 101 108 108 111 解码为 Hello"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = [ + { + "name": "分隔符", + "type": "option", + "value": DELIM_OPTIONS + }, + { + "name": "支持带符号数字(signed int)", + "type": "boolean", + "value": false + } + ]; + this.checks = [ + { + pattern: "^(?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5])(?: (?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5]))*$", + flags: "", + args: ["空格", false] + }, + { + pattern: "^(?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5])(?:,(?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5]))*$", + flags: "", + args: ["逗号", false] + }, + { + pattern: "^(?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5])(?:;(?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5]))*$", + flags: "", + args: ["分号", false] + }, + { + pattern: "^(?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5])(?::(?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5]))*$", + flags: "", + args: ["冒号", false] + }, + { + pattern: "^(?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5])(?:\\n(?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5]))*$", + flags: "", + args: ["换行符", false] + }, + { + pattern: "^(?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5])(?:\\r\\n(?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5]))*$", + flags: "", + args: ["CRLF", false] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + let data = fromDecimal(input, args[0]); + if (args[1]) { // Convert negatives + data = data.map(v => v < 0 ? 0xFF + v + 1 : v); + } + return data; + } + +} + +export default FromDecimal; diff --git a/plugins/srktoolbox/src/core/operations/FromFloat.mjs b/plugins/srktoolbox/src/core/operations/FromFloat.mjs new file mode 100644 index 00000000..2e272379 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/FromFloat.mjs @@ -0,0 +1,80 @@ +/** + * @author tcode2k16 [tcode2k16@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import ieee754 from "ieee754"; +import {DELIM_OPTIONS} from "../lib/Delim.mjs"; + +/** + * From Float operation + */ +class FromFloat extends Operation { + + /** + * FromFloat constructor + */ + constructor() { + super(); + + this.name = "浮点数转字符"; + this.module = "Default"; + this.description = "将 IEEE754 浮点数转换为字符"; + this.infoURL = "https://wikipedia.org/wiki/IEEE_754"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = [ + { + "name": "端序", + "type": "option", + "value": [ + "大端序", + "小端序" + ] + }, + { + "name": "类型", + "type": "option", + "value": [ + "Float (4字节)", + "Double (8字节)" + ] + }, + { + "name": "分隔符", + "type": "option", + "value": DELIM_OPTIONS + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + if (input.length === 0) return []; + + const [endianness, size, delimiterName] = args; + const delim = Utils.charRep(delimiterName || "空格"); + const byteSize = size === "Double (8字节)" ? 8 : 4; + const isLE = endianness === "小端序"; + const mLen = byteSize === 4 ? 23 : 52; + const floats = input.split(delim); + + const output = new Array(floats.length*byteSize); + for (let i = 0; i < floats.length; i++) { + ieee754.write(output, parseFloat(floats[i]), i*byteSize, isLE, mLen, byteSize); + } + return output; + } + +} + +export default FromFloat; diff --git a/plugins/srktoolbox/src/core/operations/FromHTMLEntity.mjs b/plugins/srktoolbox/src/core/operations/FromHTMLEntity.mjs new file mode 100644 index 00000000..8468503c --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/FromHTMLEntity.mjs @@ -0,0 +1,1541 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +/** + * From HTML Entity operation + */ +class FromHTMLEntity extends Operation { + + /** + * FromHTMLEntity constructor + */ + constructor() { + super(); + + this.name = "HTML实体解码"; + this.module = "Encodings"; + this.description = "把HTML实体解码为原始字符。

例: &amp; 解码为 &"; + this.infoURL = "https://wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + this.checks = [ + { + pattern: "&(?:#\\d{2,3}|#x[\\da-f]{2}|[a-z]{2,6});", + flags: "i", + args: [] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const regex = /&(#?x?[a-zA-Z0-9]{1,20});/g; + let output = "", + m, + i = 0; + + while ((m = regex.exec(input))) { + // Add up to match + for (; i < m.index;) + output += input[i++]; + + // Add match + const bite = entityToByte[m[1]]; + if (bite) { + output += Utils.chr(bite); + } else if (!bite && m[1][0] === "#" && m[1].length > 1 && /^#\d{1,6}$/.test(m[1])) { + // Numeric entity (e.g. ) + const num = m[1].slice(1, m[1].length); + output += Utils.chr(parseInt(num, 10)); + } else if (!bite && m[1][0] === "#" && m[1].length > 3 && /^#x[\dA-F]{2,8}$/i.test(m[1])) { + // Hex entity (e.g. :) + const hex = m[1].slice(2, m[1].length); + output += Utils.chr(parseInt(hex, 16)); + } else { + // Not a valid entity, print as normal + for (; i < regex.lastIndex;) + output += input[i++]; + } + + i = regex.lastIndex; + } + // Add all after final match + for (; i < input.length;) + output += input[i++]; + + return output; + } + +} + + +/** + * Lookup table to translate HTML entity codes to their byte values. + */ +const entityToByte = { + "Tab": 9, + "NewLine": 10, + "excl": 33, + "quot": 34, + "num": 35, + "dollar": 36, + "percnt": 37, + "amp": 38, + "apos": 39, + "lpar": 40, + "rpar": 41, + "ast": 42, + "plus": 43, + "comma": 44, + "period": 46, + "sol": 47, + "colon": 58, + "semi": 59, + "lt": 60, + "equals": 61, + "gt": 62, + "quest": 63, + "commat": 64, + "lsqb": 91, + "bsol": 92, + "rsqb": 93, + "Hat": 94, + "lowbar": 95, + "grave": 96, + "lcub": 123, + "verbar": 124, + "rcub": 125, + "nbsp": 160, + "iexcl": 161, + "cent": 162, + "pound": 163, + "curren": 164, + "yen": 165, + "brvbar": 166, + "sect": 167, + "uml": 168, + "copy": 169, + "ordf": 170, + "laquo": 171, + "not": 172, + "shy": 173, + "reg": 174, + "macr": 175, + "deg": 176, + "plusmn": 177, + "sup2": 178, + "sup3": 179, + "acute": 180, + "micro": 181, + "para": 182, + "middot": 183, + "cedil": 184, + "sup1": 185, + "ordm": 186, + "raquo": 187, + "frac14": 188, + "frac12": 189, + "frac34": 190, + "iquest": 191, + "Agrave": 192, + "Aacute": 193, + "Acirc": 194, + "Atilde": 195, + "Auml": 196, + "Aring": 197, + "AElig": 198, + "Ccedil": 199, + "Egrave": 200, + "Eacute": 201, + "Ecirc": 202, + "Euml": 203, + "Igrave": 204, + "Iacute": 205, + "Icirc": 206, + "Iuml": 207, + "ETH": 208, + "Ntilde": 209, + "Ograve": 210, + "Oacute": 211, + "Ocirc": 212, + "Otilde": 213, + "Ouml": 214, + "times": 215, + "Oslash": 216, + "Ugrave": 217, + "Uacute": 218, + "Ucirc": 219, + "Uuml": 220, + "Yacute": 221, + "THORN": 222, + "szlig": 223, + "agrave": 224, + "aacute": 225, + "acirc": 226, + "atilde": 227, + "auml": 228, + "aring": 229, + "aelig": 230, + "ccedil": 231, + "egrave": 232, + "eacute": 233, + "ecirc": 234, + "euml": 235, + "igrave": 236, + "iacute": 237, + "icirc": 238, + "iuml": 239, + "eth": 240, + "ntilde": 241, + "ograve": 242, + "oacute": 243, + "ocirc": 244, + "otilde": 245, + "ouml": 246, + "divide": 247, + "oslash": 248, + "ugrave": 249, + "uacute": 250, + "ucirc": 251, + "uuml": 252, + "yacute": 253, + "thorn": 254, + "yuml": 255, + "Amacr": 256, + "amacr": 257, + "Abreve": 258, + "abreve": 259, + "Aogon": 260, + "aogon": 261, + "Cacute": 262, + "cacute": 263, + "Ccirc": 264, + "ccirc": 265, + "Cdot": 266, + "cdot": 267, + "Ccaron": 268, + "ccaron": 269, + "Dcaron": 270, + "dcaron": 271, + "Dstrok": 272, + "dstrok": 273, + "Emacr": 274, + "emacr": 275, + "Edot": 278, + "edot": 279, + "Eogon": 280, + "eogon": 281, + "Ecaron": 282, + "ecaron": 283, + "Gcirc": 284, + "gcirc": 285, + "Gbreve": 286, + "gbreve": 287, + "Gdot": 288, + "gdot": 289, + "Gcedil": 290, + "Hcirc": 292, + "hcirc": 293, + "Hstrok": 294, + "hstrok": 295, + "Itilde": 296, + "itilde": 297, + "Imacr": 298, + "imacr": 299, + "Iogon": 302, + "iogon": 303, + "Idot": 304, + "imath": 305, + "IJlig": 306, + "ijlig": 307, + "Jcirc": 308, + "jcirc": 309, + "Kcedil": 310, + "kcedil": 311, + "kgreen": 312, + "Lacute": 313, + "lacute": 314, + "Lcedil": 315, + "lcedil": 316, + "Lcaron": 317, + "lcaron": 318, + "Lmidot": 319, + "lmidot": 320, + "Lstrok": 321, + "lstrok": 322, + "Nacute": 323, + "nacute": 324, + "Ncedil": 325, + "ncedil": 326, + "Ncaron": 327, + "ncaron": 328, + "napos": 329, + "ENG": 330, + "eng": 331, + "Omacr": 332, + "omacr": 333, + "Odblac": 336, + "odblac": 337, + "OElig": 338, + "oelig": 339, + "Racute": 340, + "racute": 341, + "Rcedil": 342, + "rcedil": 343, + "Rcaron": 344, + "rcaron": 345, + "Sacute": 346, + "sacute": 347, + "Scirc": 348, + "scirc": 349, + "Scedil": 350, + "scedil": 351, + "Scaron": 352, + "scaron": 353, + "Tcedil": 354, + "tcedil": 355, + "Tcaron": 356, + "tcaron": 357, + "Tstrok": 358, + "tstrok": 359, + "Utilde": 360, + "utilde": 361, + "Umacr": 362, + "umacr": 363, + "Ubreve": 364, + "ubreve": 365, + "Uring": 366, + "uring": 367, + "Udblac": 368, + "udblac": 369, + "Uogon": 370, + "uogon": 371, + "Wcirc": 372, + "wcirc": 373, + "Ycirc": 374, + "ycirc": 375, + "Yuml": 376, + "Zacute": 377, + "zacute": 378, + "Zdot": 379, + "zdot": 380, + "Zcaron": 381, + "zcaron": 382, + "fnof": 402, + "imped": 437, + "gacute": 501, + "jmath": 567, + "circ": 710, + "caron": 711, + "breve": 728, + "dot": 729, + "ring": 730, + "ogon": 731, + "tilde": 732, + "dblac": 733, + "DownBreve": 785, + "UnderBar": 818, + "Alpha": 913, + "Beta": 914, + "Gamma": 915, + "Delta": 916, + "Epsilon": 917, + "Zeta": 918, + "Eta": 919, + "Theta": 920, + "Iota": 921, + "Kappa": 922, + "Lambda": 923, + "Mu": 924, + "Nu": 925, + "Xi": 926, + "Omicron": 927, + "Pi": 928, + "Rho": 929, + "Sigma": 931, + "Tau": 932, + "Upsilon": 933, + "Phi": 934, + "Chi": 935, + "Psi": 936, + "Omega": 937, + "alpha": 945, + "beta": 946, + "gamma": 947, + "delta": 948, + "epsilon": 949, + "zeta": 950, + "eta": 951, + "theta": 952, + "iota": 953, + "kappa": 954, + "lambda": 955, + "mu": 956, + "nu": 957, + "xi": 958, + "omicron": 959, + "pi": 960, + "rho": 961, + "sigmaf": 962, + "sigma": 963, + "tau": 964, + "upsilon": 965, + "phi": 966, + "chi": 967, + "psi": 968, + "omega": 969, + "thetasym": 977, + "upsih": 978, + "straightphi": 981, + "piv": 982, + "Gammad": 988, + "gammad": 989, + "kappav": 1008, + "rhov": 1009, + "epsi": 1013, + "bepsi": 1014, + "IOcy": 1025, + "DJcy": 1026, + "GJcy": 1027, + "Jukcy": 1028, + "DScy": 1029, + "Iukcy": 1030, + "YIcy": 1031, + "Jsercy": 1032, + "LJcy": 1033, + "NJcy": 1034, + "TSHcy": 1035, + "KJcy": 1036, + "Ubrcy": 1038, + "DZcy": 1039, + "Acy": 1040, + "Bcy": 1041, + "Vcy": 1042, + "Gcy": 1043, + "Dcy": 1044, + "IEcy": 1045, + "ZHcy": 1046, + "Zcy": 1047, + "Icy": 1048, + "Jcy": 1049, + "Kcy": 1050, + "Lcy": 1051, + "Mcy": 1052, + "Ncy": 1053, + "Ocy": 1054, + "Pcy": 1055, + "Rcy": 1056, + "Scy": 1057, + "Tcy": 1058, + "Ucy": 1059, + "Fcy": 1060, + "KHcy": 1061, + "TScy": 1062, + "CHcy": 1063, + "SHcy": 1064, + "SHCHcy": 1065, + "HARDcy": 1066, + "Ycy": 1067, + "SOFTcy": 1068, + "Ecy": 1069, + "YUcy": 1070, + "YAcy": 1071, + "acy": 1072, + "bcy": 1073, + "vcy": 1074, + "gcy": 1075, + "dcy": 1076, + "iecy": 1077, + "zhcy": 1078, + "zcy": 1079, + "icy": 1080, + "jcy": 1081, + "kcy": 1082, + "lcy": 1083, + "mcy": 1084, + "ncy": 1085, + "ocy": 1086, + "pcy": 1087, + "rcy": 1088, + "scy": 1089, + "tcy": 1090, + "ucy": 1091, + "fcy": 1092, + "khcy": 1093, + "tscy": 1094, + "chcy": 1095, + "shcy": 1096, + "shchcy": 1097, + "hardcy": 1098, + "ycy": 1099, + "softcy": 1100, + "ecy": 1101, + "yucy": 1102, + "yacy": 1103, + "iocy": 1105, + "djcy": 1106, + "gjcy": 1107, + "jukcy": 1108, + "dscy": 1109, + "iukcy": 1110, + "yicy": 1111, + "jsercy": 1112, + "ljcy": 1113, + "njcy": 1114, + "tshcy": 1115, + "kjcy": 1116, + "ubrcy": 1118, + "dzcy": 1119, + "ensp": 8194, + "emsp": 8195, + "emsp13": 8196, + "emsp14": 8197, + "numsp": 8199, + "puncsp": 8200, + "thinsp": 8201, + "hairsp": 8202, + "ZeroWidthSpace": 8203, + "zwnj": 8204, + "zwj": 8205, + "lrm": 8206, + "rlm": 8207, + "hyphen": 8208, + "ndash": 8211, + "mdash": 8212, + "horbar": 8213, + "Verbar": 8214, + "lsquo": 8216, + "rsquo": 8217, + "sbquo": 8218, + "ldquo": 8220, + "rdquo": 8221, + "bdquo": 8222, + "dagger": 8224, + "Dagger": 8225, + "bull": 8226, + "nldr": 8229, + "hellip": 8230, + "permil": 8240, + "pertenk": 8241, + "prime": 8242, + "Prime": 8243, + "tprime": 8244, + "bprime": 8245, + "lsaquo": 8249, + "rsaquo": 8250, + "oline": 8254, + "caret": 8257, + "hybull": 8259, + "frasl": 8260, + "bsemi": 8271, + "qprime": 8279, + "MediumSpace": 8287, + "NoBreak": 8288, + "ApplyFunction": 8289, + "InvisibleTimes": 8290, + "InvisibleComma": 8291, + "euro": 8364, + "tdot": 8411, + "TripleDot": 8411, + "DotDot": 8412, + "Copf": 8450, + "incare": 8453, + "gscr": 8458, + "hamilt": 8459, + "Hfr": 8460, + "quaternions": 8461, + "planckh": 8462, + "planck": 8463, + "Iscr": 8464, + "image": 8465, + "Lscr": 8466, + "ell": 8467, + "Nopf": 8469, + "numero": 8470, + "copysr": 8471, + "weierp": 8472, + "Popf": 8473, + "rationals": 8474, + "Rscr": 8475, + "real": 8476, + "reals": 8477, + "rx": 8478, + "trade": 8482, + "integers": 8484, + "ohm": 8486, + "mho": 8487, + "Zfr": 8488, + "iiota": 8489, + "angst": 8491, + "bernou": 8492, + "Cfr": 8493, + "escr": 8495, + "Escr": 8496, + "Fscr": 8497, + "phmmat": 8499, + "order": 8500, + "alefsym": 8501, + "beth": 8502, + "gimel": 8503, + "daleth": 8504, + "CapitalDifferentialD": 8517, + "DifferentialD": 8518, + "ExponentialE": 8519, + "ImaginaryI": 8520, + "frac13": 8531, + "frac23": 8532, + "frac15": 8533, + "frac25": 8534, + "frac35": 8535, + "frac45": 8536, + "frac16": 8537, + "frac56": 8538, + "frac18": 8539, + "frac38": 8540, + "frac58": 8541, + "frac78": 8542, + "larr": 8592, + "uarr": 8593, + "rarr": 8594, + "darr": 8595, + "harr": 8596, + "varr": 8597, + "nwarr": 8598, + "nearr": 8599, + "searr": 8600, + "swarr": 8601, + "nlarr": 8602, + "nrarr": 8603, + "rarrw": 8605, + "Larr": 8606, + "Uarr": 8607, + "Rarr": 8608, + "Darr": 8609, + "larrtl": 8610, + "rarrtl": 8611, + "LeftTeeArrow": 8612, + "UpTeeArrow": 8613, + "map": 8614, + "DownTeeArrow": 8615, + "larrhk": 8617, + "rarrhk": 8618, + "larrlp": 8619, + "rarrlp": 8620, + "harrw": 8621, + "nharr": 8622, + "lsh": 8624, + "rsh": 8625, + "ldsh": 8626, + "rdsh": 8627, + "crarr": 8629, + "cularr": 8630, + "curarr": 8631, + "olarr": 8634, + "orarr": 8635, + "lharu": 8636, + "lhard": 8637, + "uharr": 8638, + "uharl": 8639, + "rharu": 8640, + "rhard": 8641, + "dharr": 8642, + "dharl": 8643, + "rlarr": 8644, + "udarr": 8645, + "lrarr": 8646, + "llarr": 8647, + "uuarr": 8648, + "rrarr": 8649, + "ddarr": 8650, + "lrhar": 8651, + "rlhar": 8652, + "nlArr": 8653, + "nhArr": 8654, + "nrArr": 8655, + "lArr": 8656, + "uArr": 8657, + "rArr": 8658, + "dArr": 8659, + "hArr": 8660, + "vArr": 8661, + "nwArr": 8662, + "neArr": 8663, + "seArr": 8664, + "swArr": 8665, + "lAarr": 8666, + "rAarr": 8667, + "zigrarr": 8669, + "larrb": 8676, + "rarrb": 8677, + "duarr": 8693, + "loarr": 8701, + "roarr": 8702, + "hoarr": 8703, + "forall": 8704, + "comp": 8705, + "part": 8706, + "exist": 8707, + "nexist": 8708, + "empty": 8709, + "nabla": 8711, + "isin": 8712, + "notin": 8713, + "ni": 8715, + "notni": 8716, + "prod": 8719, + "coprod": 8720, + "sum": 8721, + "minus": 8722, + "mnplus": 8723, + "plusdo": 8724, + "setmn": 8726, + "lowast": 8727, + "compfn": 8728, + "radic": 8730, + "prop": 8733, + "infin": 8734, + "angrt": 8735, + "ang": 8736, + "angmsd": 8737, + "angsph": 8738, + "mid": 8739, + "nmid": 8740, + "par": 8741, + "npar": 8742, + "and": 8743, + "or": 8744, + "cap": 8745, + "cup": 8746, + "int": 8747, + "Int": 8748, + "tint": 8749, + "conint": 8750, + "Conint": 8751, + "Cconint": 8752, + "cwint": 8753, + "cwconint": 8754, + "awconint": 8755, + "there4": 8756, + "becaus": 8757, + "ratio": 8758, + "Colon": 8759, + "minusd": 8760, + "mDDot": 8762, + "homtht": 8763, + "sim": 8764, + "bsim": 8765, + "ac": 8766, + "acd": 8767, + "wreath": 8768, + "nsim": 8769, + "esim": 8770, + "sime": 8771, + "nsime": 8772, + "cong": 8773, + "simne": 8774, + "ncong": 8775, + "asymp": 8776, + "nap": 8777, + "ape": 8778, + "apid": 8779, + "bcong": 8780, + "asympeq": 8781, + "bump": 8782, + "bumpe": 8783, + "esdot": 8784, + "eDot": 8785, + "efDot": 8786, + "erDot": 8787, + "colone": 8788, + "ecolon": 8789, + "ecir": 8790, + "cire": 8791, + "wedgeq": 8793, + "veeeq": 8794, + "trie": 8796, + "equest": 8799, + "ne": 8800, + "equiv": 8801, + "nequiv": 8802, + "le": 8804, + "ge": 8805, + "lE": 8806, + "gE": 8807, + "lnE": 8808, + "gnE": 8809, + "Lt": 8810, + "Gt": 8811, + "twixt": 8812, + "NotCupCap": 8813, + "nlt": 8814, + "ngt": 8815, + "nle": 8816, + "nge": 8817, + "lsim": 8818, + "gsim": 8819, + "nlsim": 8820, + "ngsim": 8821, + "lg": 8822, + "gl": 8823, + "ntlg": 8824, + "ntgl": 8825, + "pr": 8826, + "sc": 8827, + "prcue": 8828, + "sccue": 8829, + "prsim": 8830, + "scsim": 8831, + "npr": 8832, + "nsc": 8833, + "sub": 8834, + "sup": 8835, + "nsub": 8836, + "nsup": 8837, + "sube": 8838, + "supe": 8839, + "nsube": 8840, + "nsupe": 8841, + "subne": 8842, + "supne": 8843, + "cupdot": 8845, + "uplus": 8846, + "sqsub": 8847, + "sqsup": 8848, + "sqsube": 8849, + "sqsupe": 8850, + "sqcap": 8851, + "sqcup": 8852, + "oplus": 8853, + "ominus": 8854, + "otimes": 8855, + "osol": 8856, + "odot": 8857, + "ocir": 8858, + "oast": 8859, + "odash": 8861, + "plusb": 8862, + "minusb": 8863, + "timesb": 8864, + "sdotb": 8865, + "vdash": 8866, + "dashv": 8867, + "top": 8868, + "perp": 8869, + "models": 8871, + "vDash": 8872, + "Vdash": 8873, + "Vvdash": 8874, + "VDash": 8875, + "nvdash": 8876, + "nvDash": 8877, + "nVdash": 8878, + "nVDash": 8879, + "prurel": 8880, + "vltri": 8882, + "vrtri": 8883, + "ltrie": 8884, + "rtrie": 8885, + "origof": 8886, + "imof": 8887, + "mumap": 8888, + "hercon": 8889, + "intcal": 8890, + "veebar": 8891, + "barvee": 8893, + "angrtvb": 8894, + "lrtri": 8895, + "xwedge": 8896, + "xvee": 8897, + "xcap": 8898, + "xcup": 8899, + "diam": 8900, + "sdot": 8901, + "sstarf": 8902, + "divonx": 8903, + "bowtie": 8904, + "ltimes": 8905, + "rtimes": 8906, + "lthree": 8907, + "rthree": 8908, + "bsime": 8909, + "cuvee": 8910, + "cuwed": 8911, + "Sub": 8912, + "Sup": 8913, + "Cap": 8914, + "Cup": 8915, + "fork": 8916, + "epar": 8917, + "ltdot": 8918, + "gtdot": 8919, + "Ll": 8920, + "Gg": 8921, + "leg": 8922, + "gel": 8923, + "cuepr": 8926, + "cuesc": 8927, + "nprcue": 8928, + "nsccue": 8929, + "nsqsube": 8930, + "nsqsupe": 8931, + "lnsim": 8934, + "gnsim": 8935, + "prnsim": 8936, + "scnsim": 8937, + "nltri": 8938, + "nrtri": 8939, + "nltrie": 8940, + "nrtrie": 8941, + "vellip": 8942, + "ctdot": 8943, + "utdot": 8944, + "dtdot": 8945, + "disin": 8946, + "isinsv": 8947, + "isins": 8948, + "isindot": 8949, + "notinvc": 8950, + "notinvb": 8951, + "isinE": 8953, + "nisd": 8954, + "xnis": 8955, + "nis": 8956, + "notnivc": 8957, + "notnivb": 8958, + "barwed": 8965, + "Barwed": 8966, + "lceil": 8968, + "rceil": 8969, + "lfloor": 8970, + "rfloor": 8971, + "drcrop": 8972, + "dlcrop": 8973, + "urcrop": 8974, + "ulcrop": 8975, + "bnot": 8976, + "profline": 8978, + "profsurf": 8979, + "telrec": 8981, + "target": 8982, + "ulcorn": 8988, + "urcorn": 8989, + "dlcorn": 8990, + "drcorn": 8991, + "frown": 8994, + "smile": 8995, + "lang": 9001, + "rang": 9002, + "cylcty": 9005, + "profalar": 9006, + "topbot": 9014, + "ovbar": 9021, + "solbar": 9023, + "angzarr": 9084, + "lmoust": 9136, + "rmoust": 9137, + "tbrk": 9140, + "bbrk": 9141, + "bbrktbrk": 9142, + "OverParenthesis": 9180, + "UnderParenthesis": 9181, + "OverBrace": 9182, + "UnderBrace": 9183, + "trpezium": 9186, + "elinters": 9191, + "blank": 9251, + "oS": 9416, + "boxh": 9472, + "boxv": 9474, + "boxdr": 9484, + "boxdl": 9488, + "boxur": 9492, + "boxul": 9496, + "boxvr": 9500, + "boxvl": 9508, + "boxhd": 9516, + "boxhu": 9524, + "boxvh": 9532, + "boxH": 9552, + "boxV": 9553, + "boxdR": 9554, + "boxDr": 9555, + "boxDR": 9556, + "boxdL": 9557, + "boxDl": 9558, + "boxDL": 9559, + "boxuR": 9560, + "boxUr": 9561, + "boxUR": 9562, + "boxuL": 9563, + "boxUl": 9564, + "boxUL": 9565, + "boxvR": 9566, + "boxVr": 9567, + "boxVR": 9568, + "boxvL": 9569, + "boxVl": 9570, + "boxVL": 9571, + "boxHd": 9572, + "boxhD": 9573, + "boxHD": 9574, + "boxHu": 9575, + "boxhU": 9576, + "boxHU": 9577, + "boxvH": 9578, + "boxVh": 9579, + "boxVH": 9580, + "uhblk": 9600, + "lhblk": 9604, + "block": 9608, + "blk14": 9617, + "blk12": 9618, + "blk34": 9619, + "squ": 9633, + "squf": 9642, + "EmptyVerySmallSquare": 9643, + "rect": 9645, + "marker": 9646, + "fltns": 9649, + "xutri": 9651, + "utrif": 9652, + "utri": 9653, + "rtrif": 9656, + "rtri": 9657, + "xdtri": 9661, + "dtrif": 9662, + "dtri": 9663, + "ltrif": 9666, + "ltri": 9667, + "loz": 9674, + "cir": 9675, + "tridot": 9708, + "xcirc": 9711, + "ultri": 9720, + "urtri": 9721, + "lltri": 9722, + "EmptySmallSquare": 9723, + "FilledSmallSquare": 9724, + "starf": 9733, + "bigstar": 9733, + "star": 9734, + "phone": 9742, + "female": 9792, + "male": 9794, + "spades": 9824, + "clubs": 9827, + "hearts": 9829, + "diams": 9830, + "sung": 9834, + "flat": 9837, + "natur": 9838, + "sharp": 9839, + "check": 10003, + "cross": 10007, + "malt": 10016, + "sext": 10038, + "VerticalSeparator": 10072, + "lbbrk": 10098, + "rbbrk": 10099, + "lobrk": 10214, + "robrk": 10215, + "Lang": 10218, + "Rang": 10219, + "loang": 10220, + "roang": 10221, + "xlarr": 10229, + "xrarr": 10230, + "xharr": 10231, + "xlArr": 10232, + "xrArr": 10233, + "xhArr": 10234, + "xmap": 10236, + "dzigrarr": 10239, + "nvlArr": 10498, + "nvrArr": 10499, + "nvHarr": 10500, + "Map": 10501, + "lbarr": 10508, + "rbarr": 10509, + "lBarr": 10510, + "rBarr": 10511, + "RBarr": 10512, + "DDotrahd": 10513, + "UpArrowBar": 10514, + "DownArrowBar": 10515, + "Rarrtl": 10518, + "latail": 10521, + "ratail": 10522, + "lAtail": 10523, + "rAtail": 10524, + "larrfs": 10525, + "rarrfs": 10526, + "larrbfs": 10527, + "rarrbfs": 10528, + "nwarhk": 10531, + "nearhk": 10532, + "searhk": 10533, + "swarhk": 10534, + "nwnear": 10535, + "nesear": 10536, + "seswar": 10537, + "swnwar": 10538, + "rarrc": 10547, + "cudarrr": 10549, + "ldca": 10550, + "rdca": 10551, + "cudarrl": 10552, + "larrpl": 10553, + "curarrm": 10556, + "cularrp": 10557, + "rarrpl": 10565, + "harrcir": 10568, + "Uarrocir": 10569, + "lurdshar": 10570, + "ldrushar": 10571, + "LeftRightVector": 10574, + "RightUpDownVector": 10575, + "DownLeftRightVector": 10576, + "LeftUpDownVector": 10577, + "LeftVectorBar": 10578, + "RightVectorBar": 10579, + "RightUpVectorBar": 10580, + "RightDownVectorBar": 10581, + "DownLeftVectorBar": 10582, + "DownRightVectorBar": 10583, + "LeftUpVectorBar": 10584, + "LeftDownVectorBar": 10585, + "LeftTeeVector": 10586, + "RightTeeVector": 10587, + "RightUpTeeVector": 10588, + "RightDownTeeVector": 10589, + "DownLeftTeeVector": 10590, + "DownRightTeeVector": 10591, + "LeftUpTeeVector": 10592, + "LeftDownTeeVector": 10593, + "lHar": 10594, + "uHar": 10595, + "rHar": 10596, + "dHar": 10597, + "luruhar": 10598, + "ldrdhar": 10599, + "ruluhar": 10600, + "rdldhar": 10601, + "lharul": 10602, + "llhard": 10603, + "rharul": 10604, + "lrhard": 10605, + "udhar": 10606, + "duhar": 10607, + "RoundImplies": 10608, + "erarr": 10609, + "simrarr": 10610, + "larrsim": 10611, + "rarrsim": 10612, + "rarrap": 10613, + "ltlarr": 10614, + "gtrarr": 10616, + "subrarr": 10617, + "suplarr": 10619, + "lfisht": 10620, + "rfisht": 10621, + "ufisht": 10622, + "dfisht": 10623, + "lopar": 10629, + "ropar": 10630, + "lbrke": 10635, + "rbrke": 10636, + "lbrkslu": 10637, + "rbrksld": 10638, + "lbrksld": 10639, + "rbrkslu": 10640, + "langd": 10641, + "rangd": 10642, + "lparlt": 10643, + "rpargt": 10644, + "gtlPar": 10645, + "ltrPar": 10646, + "vzigzag": 10650, + "vangrt": 10652, + "angrtvbd": 10653, + "ange": 10660, + "range": 10661, + "dwangle": 10662, + "uwangle": 10663, + "angmsdaa": 10664, + "angmsdab": 10665, + "angmsdac": 10666, + "angmsdad": 10667, + "angmsdae": 10668, + "angmsdaf": 10669, + "angmsdag": 10670, + "angmsdah": 10671, + "bemptyv": 10672, + "demptyv": 10673, + "cemptyv": 10674, + "raemptyv": 10675, + "laemptyv": 10676, + "ohbar": 10677, + "omid": 10678, + "opar": 10679, + "operp": 10681, + "olcross": 10683, + "odsold": 10684, + "olcir": 10686, + "ofcir": 10687, + "olt": 10688, + "ogt": 10689, + "cirscir": 10690, + "cirE": 10691, + "solb": 10692, + "bsolb": 10693, + "boxbox": 10697, + "trisb": 10701, + "rtriltri": 10702, + "LeftTriangleBar": 10703, + "RightTriangleBar": 10704, + "race": 10714, + "iinfin": 10716, + "infintie": 10717, + "nvinfin": 10718, + "eparsl": 10723, + "smeparsl": 10724, + "eqvparsl": 10725, + "lozf": 10731, + "RuleDelayed": 10740, + "dsol": 10742, + "xodot": 10752, + "xoplus": 10753, + "xotime": 10754, + "xuplus": 10756, + "xsqcup": 10758, + "qint": 10764, + "fpartint": 10765, + "cirfnint": 10768, + "awint": 10769, + "rppolint": 10770, + "scpolint": 10771, + "npolint": 10772, + "pointint": 10773, + "quatint": 10774, + "intlarhk": 10775, + "pluscir": 10786, + "plusacir": 10787, + "simplus": 10788, + "plusdu": 10789, + "plussim": 10790, + "plustwo": 10791, + "mcomma": 10793, + "minusdu": 10794, + "loplus": 10797, + "roplus": 10798, + "Cross": 10799, + "timesd": 10800, + "timesbar": 10801, + "smashp": 10803, + "lotimes": 10804, + "rotimes": 10805, + "otimesas": 10806, + "Otimes": 10807, + "odiv": 10808, + "triplus": 10809, + "triminus": 10810, + "tritime": 10811, + "iprod": 10812, + "amalg": 10815, + "capdot": 10816, + "ncup": 10818, + "ncap": 10819, + "capand": 10820, + "cupor": 10821, + "cupcap": 10822, + "capcup": 10823, + "cupbrcap": 10824, + "capbrcup": 10825, + "cupcup": 10826, + "capcap": 10827, + "ccups": 10828, + "ccaps": 10829, + "ccupssm": 10832, + "And": 10835, + "Or": 10836, + "andand": 10837, + "oror": 10838, + "orslope": 10839, + "andslope": 10840, + "andv": 10842, + "orv": 10843, + "andd": 10844, + "ord": 10845, + "wedbar": 10847, + "sdote": 10854, + "simdot": 10858, + "congdot": 10861, + "easter": 10862, + "apacir": 10863, + "apE": 10864, + "eplus": 10865, + "pluse": 10866, + "Esim": 10867, + "Colone": 10868, + "Equal": 10869, + "eDDot": 10871, + "equivDD": 10872, + "ltcir": 10873, + "gtcir": 10874, + "ltquest": 10875, + "gtquest": 10876, + "les": 10877, + "ges": 10878, + "lesdot": 10879, + "gesdot": 10880, + "lesdoto": 10881, + "gesdoto": 10882, + "lesdotor": 10883, + "gesdotol": 10884, + "lap": 10885, + "gap": 10886, + "lne": 10887, + "gne": 10888, + "lnap": 10889, + "gnap": 10890, + "lEg": 10891, + "gEl": 10892, + "lsime": 10893, + "gsime": 10894, + "lsimg": 10895, + "gsiml": 10896, + "lgE": 10897, + "glE": 10898, + "lesges": 10899, + "gesles": 10900, + "els": 10901, + "egs": 10902, + "elsdot": 10903, + "egsdot": 10904, + "el": 10905, + "eg": 10906, + "siml": 10909, + "simg": 10910, + "simlE": 10911, + "simgE": 10912, + "LessLess": 10913, + "GreaterGreater": 10914, + "glj": 10916, + "gla": 10917, + "ltcc": 10918, + "gtcc": 10919, + "lescc": 10920, + "gescc": 10921, + "smt": 10922, + "lat": 10923, + "smte": 10924, + "late": 10925, + "bumpE": 10926, + "pre": 10927, + "sce": 10928, + "prE": 10931, + "scE": 10932, + "prnE": 10933, + "scnE": 10934, + "prap": 10935, + "scap": 10936, + "prnap": 10937, + "scnap": 10938, + "Pr": 10939, + "Sc": 10940, + "subdot": 10941, + "supdot": 10942, + "subplus": 10943, + "supplus": 10944, + "submult": 10945, + "supmult": 10946, + "subedot": 10947, + "supedot": 10948, + "subE": 10949, + "supE": 10950, + "subsim": 10951, + "supsim": 10952, + "subnE": 10955, + "supnE": 10956, + "csub": 10959, + "csup": 10960, + "csube": 10961, + "csupe": 10962, + "subsup": 10963, + "supsub": 10964, + "subsub": 10965, + "supsup": 10966, + "suphsub": 10967, + "supdsub": 10968, + "forkv": 10969, + "topfork": 10970, + "mlcp": 10971, + "Dashv": 10980, + "Vdashl": 10982, + "Barv": 10983, + "vBar": 10984, + "vBarv": 10985, + "Vbar": 10987, + "Not": 10988, + "bNot": 10989, + "rnmid": 10990, + "cirmid": 10991, + "midcir": 10992, + "topcir": 10993, + "nhpar": 10994, + "parsim": 10995, + "parsl": 11005, + "fflig": 64256, + "filig": 64257, + "fllig": 64258, + "ffilig": 64259, + "ffllig": 64260, + "Ascr": 119964, + "Cscr": 119966, + "Dscr": 119967, + "Gscr": 119970, + "Jscr": 119973, + "Kscr": 119974, + "Nscr": 119977, + "Oscr": 119978, + "Pscr": 119979, + "Qscr": 119980, + "Sscr": 119982, + "Tscr": 119983, + "Uscr": 119984, + "Vscr": 119985, + "Wscr": 119986, + "Xscr": 119987, + "Yscr": 119988, + "Zscr": 119989, + "ascr": 119990, + "bscr": 119991, + "cscr": 119992, + "dscr": 119993, + "fscr": 119995, + "hscr": 119997, + "iscr": 119998, + "jscr": 119999, + "kscr": 120000, + "lscr": 120001, + "mscr": 120002, + "nscr": 120003, + "pscr": 120005, + "qscr": 120006, + "rscr": 120007, + "sscr": 120008, + "tscr": 120009, + "uscr": 120010, + "vscr": 120011, + "wscr": 120012, + "xscr": 120013, + "yscr": 120014, + "zscr": 120015, + "Afr": 120068, + "Bfr": 120069, + "Dfr": 120071, + "Efr": 120072, + "Ffr": 120073, + "Gfr": 120074, + "Jfr": 120077, + "Kfr": 120078, + "Lfr": 120079, + "Mfr": 120080, + "Nfr": 120081, + "Ofr": 120082, + "Pfr": 120083, + "Qfr": 120084, + "Sfr": 120086, + "Tfr": 120087, + "Ufr": 120088, + "Vfr": 120089, + "Wfr": 120090, + "Xfr": 120091, + "Yfr": 120092, + "afr": 120094, + "bfr": 120095, + "cfr": 120096, + "dfr": 120097, + "efr": 120098, + "ffr": 120099, + "gfr": 120100, + "hfr": 120101, + "ifr": 120102, + "jfr": 120103, + "kfr": 120104, + "lfr": 120105, + "mfr": 120106, + "nfr": 120107, + "ofr": 120108, + "pfr": 120109, + "qfr": 120110, + "rfr": 120111, + "sfr": 120112, + "tfr": 120113, + "ufr": 120114, + "vfr": 120115, + "wfr": 120116, + "xfr": 120117, + "yfr": 120118, + "zfr": 120119, + "Aopf": 120120, + "Bopf": 120121, + "Dopf": 120123, + "Eopf": 120124, + "Fopf": 120125, + "Gopf": 120126, + "Iopf": 120128, + "Jopf": 120129, + "Kopf": 120130, + "Lopf": 120131, + "Mopf": 120132, + "Oopf": 120134, + "Sopf": 120138, + "Topf": 120139, + "Uopf": 120140, + "Vopf": 120141, + "Wopf": 120142, + "Xopf": 120143, + "Yopf": 120144, + "aopf": 120146, + "bopf": 120147, + "copf": 120148, + "dopf": 120149, + "eopf": 120150, + "fopf": 120151, + "gopf": 120152, + "hopf": 120153, + "iopf": 120154, + "jopf": 120155, + "kopf": 120156, + "lopf": 120157, + "mopf": 120158, + "nopf": 120159, + "oopf": 120160, + "popf": 120161, + "qopf": 120162, + "ropf": 120163, + "sopf": 120164, + "topf": 120165, + "uopf": 120166, + "vopf": 120167, + "wopf": 120168, + "xopf": 120169, + "yopf": 120170, + "zopf": 120171 +}; + +export default FromHTMLEntity; diff --git a/plugins/srktoolbox/src/core/operations/FromHex.mjs b/plugins/srktoolbox/src/core/operations/FromHex.mjs new file mode 100644 index 00000000..10abebe8 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/FromHex.mjs @@ -0,0 +1,154 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import {fromHex, FROM_HEX_DELIM_OPTIONS} from "../lib/Hex.mjs"; +import Utils from "../Utils.mjs"; + +/** + * From Hex operation + */ +class FromHex extends Operation { + + /** + * FromHex constructor + */ + constructor() { + super(); + + this.name = "十六进制转字符"; + this.module = "Default"; + this.description = "把十六进制字符串解码为原先的内容。

例如: ce 93 ce b5 ce b9 ce ac 20 cf 83 ce bf cf 85 0a 解码为UTF-8字符串 Γειά σου"; + this.infoURL = "https://wikipedia.org/wiki/Hexadecimal"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = [ + { + name: "分隔符", + type: "option", + value: FROM_HEX_DELIM_OPTIONS + } + ]; + this.checks = [ + { + pattern: "^(?:[\\dA-F]{2})+$", + flags: "i", + args: ["无"] + }, + { + pattern: "^[\\dA-F]{2}(?: [\\dA-F]{2})*$", + flags: "i", + args: ["空格"] + }, + { + pattern: "^[\\dA-F]{2}(?:,[\\dA-F]{2})*$", + flags: "i", + args: ["逗号"] + }, + { + pattern: "^[\\dA-F]{2}(?:;[\\dA-F]{2})*$", + flags: "i", + args: ["分号"] + }, + { + pattern: "^[\\dA-F]{2}(?::[\\dA-F]{2})*$", + flags: "i", + args: ["冒号"] + }, + { + pattern: "^[\\dA-F]{2}(?:\\n[\\dA-F]{2})*$", + flags: "i", + args: ["换行"] + }, + { + pattern: "^[\\dA-F]{2}(?:\\r\\n[\\dA-F]{2})*$", + flags: "i", + args: ["CRLF"] + }, + { + pattern: "^(?:0x[\\dA-F]{2})+$", + flags: "i", + args: ["0x"] + }, + { + pattern: "^0x[\\dA-F]{2}(?:,0x[\\dA-F]{2})*$", + flags: "i", + args: ["0x和逗号"] + }, + { + pattern: "^(?:\\\\x[\\dA-F]{2})+$", + flags: "i", + args: ["\\x"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const delim = args[0] || "自动"; + return fromHex(input, delim, 2); + } + + /** + * Highlight to Hex + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + if (args[0] === "自动") return false; + const delim = Utils.charRep(args[0] || "空格"), + len = delim === "\r\n" ? 1 : delim.length, + width = len + 2; + + // 0x and \x are added to the beginning if they are selected, so increment the positions accordingly + if (delim === "0x" || delim === "\\x") { + if (pos[0].start > 1) pos[0].start -= 2; + else pos[0].start = 0; + if (pos[0].end > 1) pos[0].end -= 2; + else pos[0].end = 0; + } + + pos[0].start = pos[0].start === 0 ? 0 : Math.round(pos[0].start / width); + pos[0].end = pos[0].end === 0 ? 0 : Math.ceil(pos[0].end / width); + return pos; + } + + /** + * Highlight from Hex + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + const delim = Utils.charRep(args[0] || "空格"), + len = delim === "\r\n" ? 1 : delim.length; + + pos[0].start = pos[0].start * (2 + len); + pos[0].end = pos[0].end * (2 + len) - len; + + // 0x and \x are added to the beginning if they are selected, so increment the positions accordingly + if (delim === "0x" || delim === "\\x") { + pos[0].start += 2; + pos[0].end += 2; + } + return pos; + } +} + +export default FromHex; diff --git a/plugins/srktoolbox/src/core/operations/FromHexContent.mjs b/plugins/srktoolbox/src/core/operations/FromHexContent.mjs new file mode 100644 index 00000000..8931f51f --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/FromHexContent.mjs @@ -0,0 +1,76 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import {fromHex} from "../lib/Hex.mjs"; + +/** + * From Hex Content operation + */ +class FromHexContent extends Operation { + + /** + * FromHexContent constructor + */ + constructor() { + super(); + + this.name = "Snort Content解码"; + this.module = "Default"; + this.description = "把十六进制恢复成原始字符。SNORT的Content关键字使用此格式。

例: foo|3d|bar 解码为foo=bar."; + this.infoURL = "http://manual-snort-org.s3-website-us-east-1.amazonaws.com/node32.html#SECTION00451000000000000000"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = []; + this.checks = [ + { + pattern: "\\|([\\da-f]{2} ?)+\\|", + flags: "i", + args: [] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const regex = /\|([a-f\d ]{2,})\|/gi, + output = []; + let m, i = 0; + while ((m = regex.exec(input))) { + // Add up to match + for (; i < m.index;) + output.push(Utils.ord(input[i++])); + + // Add match + const bytes = fromHex(m[1]); + if (bytes) { + for (let a = 0; a < bytes.length;) + output.push(bytes[a++]); + } else { + // Not valid hex, print as normal + for (; i < regex.lastIndex;) + output.push(Utils.ord(input[i++])); + } + + i = regex.lastIndex; + } + // Add all after final match + for (; i < input.length;) + output.push(Utils.ord(input[i++])); + + return output; + } + +} + +export default FromHexContent; diff --git a/plugins/srktoolbox/src/core/operations/FromHexdump.mjs b/plugins/srktoolbox/src/core/operations/FromHexdump.mjs new file mode 100644 index 00000000..7b4f507d --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/FromHexdump.mjs @@ -0,0 +1,168 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import { fromHex } from "../lib/Hex.mjs"; +import { isWorkerEnvironment } from "../Utils.mjs"; + + +/** + * From Hexdump operation + */ +class FromHexdump extends Operation { + + /** + * FromHexdump constructor + */ + constructor() { + super(); + + this.name = "从Hexdump提取"; + this.module = "Default"; + this.description = "尝试提取hexdump包含的原始数据。此操作支持大部分hexdump类型。在进行下一步分析之前务必确认输入数据的正确性。"; + this.infoURL = "https://wikipedia.org/wiki/Hex_dump"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = []; + this.checks = [ + { + pattern: "^(?:(?:[\\dA-F]{4,16}h?:?)?[ \\t]*((?:[\\dA-F]{2} ){1,8}(?:[ \\t]|[\\dA-F]{2}-)(?:[\\dA-F]{2} ){1,8}|(?:[\\dA-F]{4} )*[\\dA-F]{4}|(?:[\\dA-F]{2} )*[\\dA-F]{2})[^\\n]*\\n?){2,}$", + flags: "i", + args: [] + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const output = [], + regex = /^\s*(?:[\dA-F]{4,16}h?:?)?[ \t]+((?:[\dA-F]{2} ){1,8}(?:[ \t]|[\dA-F]{2}-)(?:[\dA-F]{2} ){1,8}|(?:[\dA-F]{4} )+(?:[\dA-F]{2})?|(?:[\dA-F]{2} )*[\dA-F]{2})/igm; + let block, line; + + while ((block = regex.exec(input))) { + line = fromHex(block[1].replace(/-/g, " ")); + for (let i = 0; i < line.length; i++) { + output.push(line[i]); + } + } + // Is this a CyberChef hexdump or is it from a different tool? + const width = input.indexOf("\n"); + const w = (width - 13) / 4; + // w should be the specified width of the hexdump and therefore a round number + if (Math.floor(w) !== w || input.indexOf("\r") !== -1 || output.indexOf(13) !== -1) { + if (isWorkerEnvironment()) self.setOption("attemptHighlight", false); + } + return output; + } + + /** + * Highlight From Hexdump + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + const w = args[0] || 16; + const width = 14 + (w*4); + + let line = Math.floor(pos[0].start / width); + let offset = pos[0].start % width; + + if (offset < 10) { // In line number section + pos[0].start = line*w; + } else if (offset > 10+(w*3)) { // In ASCII section + pos[0].start = (line+1)*w; + } else { // In byte section + pos[0].start = line*w + Math.floor((offset-10)/3); + } + + line = Math.floor(pos[0].end / width); + offset = pos[0].end % width; + + if (offset < 10) { // In line number section + pos[0].end = line*w; + } else if (offset > 10+(w*3)) { // In ASCII section + pos[0].end = (line+1)*w; + } else { // In byte section + pos[0].end = line*w + Math.ceil((offset-10)/3); + } + + return pos; + } + + /** + * Highlight From Hexdump in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + // Calculate overall selection + const w = args[0] || 16, + width = 14 + (w*4); + let line = Math.floor(pos[0].start / w), + offset = pos[0].start % w, + start = 0, + end = 0; + + pos[0].start = line*width + 10 + offset*3; + + line = Math.floor(pos[0].end / w); + offset = pos[0].end % w; + if (offset === 0) { + line--; + offset = w; + } + pos[0].end = line*width + 10 + offset*3 - 1; + + // Set up multiple selections for bytes + let startLineNum = Math.floor(pos[0].start / width); + const endLineNum = Math.floor(pos[0].end / width); + + if (startLineNum === endLineNum) { + pos.push(pos[0]); + } else { + start = pos[0].start; + end = (startLineNum+1) * width - w - 5; + pos.push({ start: start, end: end }); + while (end < pos[0].end) { + startLineNum++; + start = startLineNum * width + 10; + end = (startLineNum+1) * width - w - 5; + if (end > pos[0].end) end = pos[0].end; + pos.push({ start: start, end: end }); + } + } + + // Set up multiple selections for ASCII + const len = pos.length; + let lineNum = 0; + start = 0; + end = 0; + for (let i = 1; i < len; i++) { + lineNum = Math.floor(pos[i].start / width); + start = (((pos[i].start - (lineNum * width)) - 10) / 3) + (width - w -2) + (lineNum * width); + end = (((pos[i].end + 1 - (lineNum * width)) - 10) / 3) + (width - w -2) + (lineNum * width); + pos.push({ start: start, end: end }); + } + return pos; + } + +} + +export default FromHexdump; diff --git a/plugins/srktoolbox/src/core/operations/FromMessagePack.mjs b/plugins/srktoolbox/src/core/operations/FromMessagePack.mjs new file mode 100644 index 00000000..12bcf69e --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/FromMessagePack.mjs @@ -0,0 +1,49 @@ +/** + * @author Matt C [matt@artemisbot.uk] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import notepack from "notepack.io"; + +/** + * From MessagePack operation + */ +class FromMessagePack extends Operation { + + /** + * FromMessagePack constructor + */ + constructor() { + super(); + + this.name = "MessagePack解码"; + this.module = "Code"; + this.description = "把MessagePack编码的内容还原为JSON。MessagePack是一种计算机数据交换格式。它是一种二进制形式,用于表示简单的数据结构,如数组和关联数组。"; + this.infoURL = "https://wikipedia.org/wiki/MessagePack"; + this.inputType = "ArrayBuffer"; + this.outputType = "JSON"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {JSON} + */ + run(input, args) { + try { + const buf = Buffer.from(new Uint8Array(input)); + return notepack.decode(buf); + } catch (err) { + throw new OperationError(`无法将 MessagePack 转换为 JSON: ${err}`); + } + } + +} + +export default FromMessagePack; diff --git a/plugins/srktoolbox/src/core/operations/FromModhex.mjs b/plugins/srktoolbox/src/core/operations/FromModhex.mjs new file mode 100644 index 00000000..0bfb0780 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/FromModhex.mjs @@ -0,0 +1,86 @@ +/** + * @author linuxgemini [ilteris@asenkron.com.tr] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + * + * Modified by Raka-loah@github + */ + +import Operation from "../Operation.mjs"; +import { FROM_MODHEX_DELIM_OPTIONS, fromModhex } from "../lib/Modhex.mjs"; + +/** + * From Modhex operation + */ +class FromModhex extends Operation { + + /** + * FromModhex constructor + */ + constructor() { + super(); + + this.name = "Modhex解码"; + this.module = "Default"; + this.description = "将modhex字符串转换为原始值。"; + this.infoURL = "https://en.wikipedia.org/wiki/YubiKey#ModHex"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = [ + { + name: "分隔符", + type: "option", + value: FROM_MODHEX_DELIM_OPTIONS + } + ]; + this.checks = [ + { + pattern: "^(?:[cbdefghijklnrtuv]{2})+$", + flags: "i", + args: ["无"] + }, + { + pattern: "^[cbdefghijklnrtuv]{2}(?: [cbdefghijklnrtuv]{2})*$", + flags: "i", + args: ["空格"] + }, + { + pattern: "^[cbdefghijklnrtuv]{2}(?:,[cbdefghijklnrtuv]{2})*$", + flags: "i", + args: ["逗号"] + }, + { + pattern: "^[cbdefghijklnrtuv]{2}(?:;[cbdefghijklnrtuv]{2})*$", + flags: "i", + args: ["分号"] + }, + { + pattern: "^[cbdefghijklnrtuv]{2}(?::[cbdefghijklnrtuv]{2})*$", + flags: "i", + args: ["冒号"] + }, + { + pattern: "^[cbdefghijklnrtuv]{2}(?:\\n[cbdefghijklnrtuv]{2})*$", + flags: "i", + args: ["换行"] + }, + { + pattern: "^[cbdefghijklnrtuv]{2}(?:\\r\\n[cbdefghijklnrtuv]{2})*$", + flags: "i", + args: ["CRLF"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const delim = args[0] || "自动"; + return fromModhex(input, delim, 2); + } +} + +export default FromModhex; diff --git a/plugins/srktoolbox/src/core/operations/FromMorseCode.mjs b/plugins/srktoolbox/src/core/operations/FromMorseCode.mjs new file mode 100644 index 00000000..66085bc9 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/FromMorseCode.mjs @@ -0,0 +1,156 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import {LETTER_DELIM_OPTIONS, WORD_DELIM_OPTIONS} from "../lib/Delim.mjs"; + +/** + * From Morse Code operation + */ +class FromMorseCode extends Operation { + + /** + * FromMorseCode constructor + */ + constructor() { + super(); + + this.name = "摩尔斯电码解码"; + this.module = "Default"; + this.description = "把摩尔斯电码还原为大写字母。"; + this.infoURL = "https://wikipedia.org/wiki/Morse_code"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "字母分隔符", + "type": "option", + "value": LETTER_DELIM_OPTIONS + }, + { + "name": "单词分隔符", + "type": "option", + "value": WORD_DELIM_OPTIONS + } + ]; + this.checks = [ + { + pattern: "(?:^[-. \\n]{5,}$|^[_. \\n]{5,}$|^(?:dash|dot| |\\n){5,}$)", + flags: "i", + args: ["Space", "Line feed"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + if (!this.reversedTable) { + this.reverseTable(); + } + + const letterDelim = Utils.charRep(args[0]); + const wordDelim = Utils.charRep(args[1]); + + input = input.replace(/-|‐|−|_|–|—|dash/ig, ""); // hyphen-minus|hyphen|minus-sign|undersore|en-dash|em-dash + input = input.replace(/\.|·|dot/ig, ""); + + let words = input.split(wordDelim); + const self = this; + words = Array.prototype.map.call(words, function(word) { + const signals = word.split(letterDelim); + + const letters = signals.map(function(signal) { + return self.reversedTable[signal]; + }); + + return letters.join(""); + }); + words = words.join(" "); + + return words; + } + + + /** + * Reverses the Morse Code lookup table + */ + reverseTable() { + this.reversedTable = {}; + + for (const letter in MORSE_TABLE) { + const signal = MORSE_TABLE[letter]; + this.reversedTable[signal] = letter; + } + } + +} + +const MORSE_TABLE = { + "A": "", + "B": "", + "C": "", + "D": "", + "E": "", + "F": "", + "G": "", + "H": "", + "I": "", + "J": "", + "K": "", + "L": "", + "M": "", + "N": "", + "O": "", + "P": "", + "Q": "", + "R": "", + "S": "", + "T": "", + "U": "", + "V": "", + "W": "", + "X": "", + "Y": "", + "Z": "", + "1": "", + "2": "", + "3": "", + "4": "", + "5": "", + "6": "", + "7": "", + "8": "", + "9": "", + "0": "", + ".": "", + ",": "", + ":": "", + ";": "", + "!": "", + "?": "", + "'": "", + "\"": "", + "/": "", + "-": "", + "+": "", + "(": "", + ")": "", + "@": "", + "=": "", + "&": "", + "_": "", + "$": "", + " ": "" +}; + +export default FromMorseCode; diff --git a/plugins/srktoolbox/src/core/operations/FromOctal.mjs b/plugins/srktoolbox/src/core/operations/FromOctal.mjs new file mode 100644 index 00000000..62fa6853 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/FromOctal.mjs @@ -0,0 +1,84 @@ +/** + * @author Matt C [matt@artemisbot.uk] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import {DELIM_OPTIONS} from "../lib/Delim.mjs"; + +/** + * From Octal operation + */ +class FromOctal extends Operation { + + /** + * FromOctal constructor + */ + constructor() { + super(); + + this.name = "八进制转字符"; + this.module = "Default"; + this.description = "把八进制字符串解码为原先的内容。

例: 316 223 316 265 316 271 316 254 40 317 203 316 277 317 205 解码成UTF-8字符串 Γειά σου"; + this.infoURL = "https://wikipedia.org/wiki/Octal"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = [ + { + "name": "分隔符", + "type": "option", + "value": DELIM_OPTIONS + } + ]; + this.checks = [ + { + pattern: "^(?:[0-7]{1,2}|[123][0-7]{2})(?: (?:[0-7]{1,2}|[123][0-7]{2}))*$", + flags: "", + args: ["空格"] + }, + { + pattern: "^(?:[0-7]{1,2}|[123][0-7]{2})(?:,(?:[0-7]{1,2}|[123][0-7]{2}))*$", + flags: "", + args: ["逗号"] + }, + { + pattern: "^(?:[0-7]{1,2}|[123][0-7]{2})(?:;(?:[0-7]{1,2}|[123][0-7]{2}))*$", + flags: "", + args: ["分号"] + }, + { + pattern: "^(?:[0-7]{1,2}|[123][0-7]{2})(?::(?:[0-7]{1,2}|[123][0-7]{2}))*$", + flags: "", + args: ["冒号"] + }, + { + pattern: "^(?:[0-7]{1,2}|[123][0-7]{2})(?:\\n(?:[0-7]{1,2}|[123][0-7]{2}))*$", + flags: "", + args: ["换行"] + }, + { + pattern: "^(?:[0-7]{1,2}|[123][0-7]{2})(?:\\r\\n(?:[0-7]{1,2}|[123][0-7]{2}))*$", + flags: "", + args: ["CRLF"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const delim = Utils.charRep(args[0] || "Space"); + if (input.length === 0) return []; + return input.split(delim).map(val => parseInt(val, 8)); + } + +} + +export default FromOctal; diff --git a/plugins/srktoolbox/src/core/operations/FromPunycode.mjs b/plugins/srktoolbox/src/core/operations/FromPunycode.mjs new file mode 100644 index 00000000..83113e46 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/FromPunycode.mjs @@ -0,0 +1,55 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import punycode from "punycode"; + +/** + * From Punycode operation + */ +class FromPunycode extends Operation { + + /** + * FromPunycode constructor + */ + constructor() { + super(); + + this.name = "Punycode解码"; + this.module = "Encodings"; + this.description = "Punycode是用ASCII字符的一个子集来编码Unicode域名的一种方法。

例: mnchen-3ya 解码为 m\xfcnchen"; + this.infoURL = "https://wikipedia.org/wiki/Punycode"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "国际化域名(带xn--)", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const idn = args[0]; + + if (idn) { + return punycode.toUnicode(input); + } else { + return punycode.decode(input); + } + } + +} + +export default FromPunycode; diff --git a/plugins/srktoolbox/src/core/operations/FromQuotedPrintable.mjs b/plugins/srktoolbox/src/core/operations/FromQuotedPrintable.mjs new file mode 100644 index 00000000..767ce270 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/FromQuotedPrintable.mjs @@ -0,0 +1,71 @@ +/** + * Some parts taken from mimelib (http://github.com/andris9/mimelib) + * @author Andris Reinman + * @license MIT + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; + +/** + * From Quoted Printable operation + */ +class FromQuotedPrintable extends Operation { + + /** + * FromQuotedPrintable constructor + */ + constructor() { + super(); + + this.name = "QP解码"; + this.module = "Default"; + this.description = "Converts QP-encoded text back to standard text. This format is a content transfer encoding common in email messages.

e.g. The quoted-printable encoded string hello=20world becomes hello world"; + this.infoURL = "https://wikipedia.org/wiki/Quoted-printable"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = []; + this.checks = [ + { + pattern: "^[\\x21-\\x3d\\x3f-\\x7e \\t]{0,76}(?:=[\\da-f]{2}|=\\r?\\n)(?:[\\x21-\\x3d\\x3f-\\x7e \\t]|=[\\da-f]{2}|=\\r?\\n)*$", + flags: "i", + args: [] + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const str = input.replace(/=(?:\r?\n|$)/g, ""); + + const encodedBytesCount = (str.match(/=[\da-fA-F]{2}/g) || []).length, + bufferLength = str.length - encodedBytesCount * 2, + buffer = new Array(bufferLength); + let chr, hex, + bufferPos = 0; + + for (let i = 0, len = str.length; i < len; i++) { + chr = str.charAt(i); + if (chr === "=" && (hex = str.substr(i + 1, 2)) && /[\da-fA-F]{2}/.test(hex)) { + buffer[bufferPos++] = parseInt(hex, 16); + i += 2; + continue; + } + buffer[bufferPos++] = chr.charCodeAt(0); + } + + return buffer; + } + +} + +export default FromQuotedPrintable; diff --git a/plugins/srktoolbox/src/core/operations/FromUNIXTimestamp.mjs b/plugins/srktoolbox/src/core/operations/FromUNIXTimestamp.mjs new file mode 100644 index 00000000..c75440d1 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/FromUNIXTimestamp.mjs @@ -0,0 +1,94 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import moment from "moment-timezone"; +import {UNITS} from "../lib/DateTime.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * From UNIX Timestamp operation + */ +class FromUNIXTimestamp extends Operation { + + /** + * FromUNIXTimestamp constructor + */ + constructor() { + super(); + + this.name = "从UNIX时间戳提取"; + this.module = "Default"; + this.description = "将UNIX时间戳转换为DateTime字符串。

例: 978346800 转换为 Mon 1 January 2001 11:00:00 UTC

UNIX时间,或称POSIX时间是UNIX或类UNIX系统使用的时间表示方式:从UTC1970年1月1日0时0分0秒起至现在的总秒数,不考虑闰秒。"; + this.infoURL = "https://wikipedia.org/wiki/Unix_time"; + this.inputType = "number"; + this.outputType = "string"; + this.args = [ + { + "name": "单位", + "type": "option", + "value": UNITS + } + ]; + this.checks = [ + { + pattern: "^1?\\d{9}$", + flags: "", + args: ["秒 (s)"] + }, + { + pattern: "^1?\\d{12}$", + flags: "", + args: ["毫秒 (ms)"] + }, + { + pattern: "^1?\\d{15}$", + flags: "", + args: ["微秒 (μs)"] + }, + { + pattern: "^1?\\d{18}$", + flags: "", + args: ["纳秒 (ns)"] + } + ]; + } + + /** + * @param {number} input + * @param {Object[]} args + * @returns {string} + * + * @throws {OperationError} if invalid unit + */ + run(input, args) { + const units = args[0]; + let d; + + input = parseFloat(input); + + if (units === "秒 (s)") { + d = moment.unix(input); + return d.tz("UTC").format("ddd D MMMM YYYY HH:mm:ss") + " UTC"; + } else if (units === "毫秒 (ms)") { + d = moment(input); + return d.tz("UTC").format("ddd D MMMM YYYY HH:mm:ss.SSS") + " UTC"; + } else if (units === "微秒 (μs)") { + d = moment(input / 1000); + return d.tz("UTC").format("ddd D MMMM YYYY HH:mm:ss.SSS") + " UTC"; + } else if (units === "纳秒 (ns)") { + d = moment(input / 1000000); + return d.tz("UTC").format("ddd D MMMM YYYY HH:mm:ss.SSS") + " UTC"; + } else { + throw new OperationError("无效单位"); + } + } + +} + +export default FromUNIXTimestamp; diff --git a/plugins/srktoolbox/src/core/operations/FuzzyMatch.mjs b/plugins/srktoolbox/src/core/operations/FuzzyMatch.mjs new file mode 100644 index 00000000..fc8cf776 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/FuzzyMatch.mjs @@ -0,0 +1,123 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import {fuzzyMatch, calcMatchRanges, DEFAULT_WEIGHTS} from "../lib/FuzzyMatch.mjs"; +import Utils from "../Utils.mjs"; + +/** + * Fuzzy Match operation + */ +class FuzzyMatch extends Operation { + + /** + * FuzzyMatch constructor + */ + constructor() { + super(); + + this.name = "模糊匹配"; + this.module = "Default"; + this.description = "根据指定的权重对输入进行模糊匹配。

例如:搜索 dpan 可匹配 Don't Panic"; + this.infoURL = "https://wikipedia.org/wiki/Fuzzy_matching_(computer-assisted_translation)"; + this.inputType = "string"; + this.outputType = "html"; + this.args = [ + { + name: "查找内容", + type: "binaryString", + value: "" + }, + { + name: "顺序加成", + type: "number", + value: DEFAULT_WEIGHTS.sequentialBonus, + hint: "如果匹配内容相邻则提高权重" + }, + { + name: "分隔符加成", + type: "number", + value: DEFAULT_WEIGHTS.separatorBonus, + hint: "如果匹配内容刚好在分隔符后则提高权重" + }, + { + name: "驼峰加成", + type: "number", + value: DEFAULT_WEIGHTS.camelBonus, + hint: "如果匹配内容是大写而上一个匹配是小写则提高权重" + }, + { + name: "首字母加成", + type: "number", + value: DEFAULT_WEIGHTS.firstLetterBonus, + hint: "如果首字母匹配则提高权重" + }, + { + name: "前置字母惩罚", + type: "number", + value: DEFAULT_WEIGHTS.leadingLetterPenalty, + hint: "根据首个匹配之前的字符数降低权重" + }, + { + name: "前置字母惩罚最大值", + type: "number", + value: DEFAULT_WEIGHTS.maxLeadingLetterPenalty, + hint: "前置字母惩罚可降低权重的最大值" + }, + { + name: "不匹配字母惩罚", + type: "number", + value: DEFAULT_WEIGHTS.unmatchedLetterPenalty + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + const searchStr = args[0]; + const weights = { + sequentialBonus: args[1], + separatorBonus: args[2], + camelBonus: args[3], + firstLetterBonus: args[4], + leadingLetterPenalty: args[5], + maxLeadingLetterPenalty: args[6], + unmatchedLetterPenalty: args[7] + }; + const matches = fuzzyMatch(searchStr, input, true, weights); + + if (!matches) { + return "未找到对应内容。"; + } + + let result = "", pos = 0, hlClass = "hl1"; + matches.forEach(([matches, score, idxs]) => { + const matchRanges = calcMatchRanges(idxs); + + matchRanges.forEach(([start, length], i) => { + result += Utils.escapeHtml(input.slice(pos, start)); + if (i === 0) result += ``; + pos = start + length; + result += `${Utils.escapeHtml(input.slice(start, pos))}`; + }); + result += ""; + hlClass = hlClass === "hl1" ? "hl2" : "hl1"; + }); + + result += Utils.escapeHtml(input.slice(pos, input.length)); + + return result; + } + +} + +export default FuzzyMatch; diff --git a/plugins/srktoolbox/src/core/operations/GOSTDecrypt.mjs b/plugins/srktoolbox/src/core/operations/GOSTDecrypt.mjs new file mode 100644 index 00000000..644d6cf6 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/GOSTDecrypt.mjs @@ -0,0 +1,153 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { toHexFast, fromHex } from "../lib/Hex.mjs"; +import { CryptoGost, GostEngine } from "@wavesenterprise/crypto-gost-js/index.js"; + +/** + * GOST Decrypt operation + */ +class GOSTDecrypt extends Operation { + + /** + * GOSTDecrypt constructor + */ + constructor() { + super(); + + this.name = "GOST解密"; + this.module = "Ciphers"; + this.description = "GOST块密码(Magma)是苏联和俄罗斯政府标准的对称密钥块密码,块大小为64位,定义在标准GOST 28147-89(RFC 5830)中。最初的标准于1989年发布,未为密码命名,但最新修订的标准GOST R 34.12-2015(RFC 7801,RFC 8891)指定它可以被称为Magma。GOST哈希函数基于该密码。新标准还指定了一个名为Kuznyechik的新的128位块密码。

该标准在1970年代开发,曾被标记为“绝密”,1990年降级为“机密”。苏联解体后不久,该标准被解密,并在1994年向公众发布。GOST 28147是苏联替代美国标准算法DES的选择,因此两者在结构上非常相似。"; + this.infoURL = "https://wikipedia.org/wiki/GOST_(block_cipher)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Key", + type: "toggleString", + value: "", + toggleValues: ["十六进制", "UTF8", "Latin1", "Base64"] + }, + { + name: "IV", + type: "toggleString", + value: "", + toggleValues: ["十六进制", "UTF8", "Latin1", "Base64"] + }, + { + name: "输入类型", + type: "option", + value: ["十六进制", "原始字节"] + }, + { + name: "输出类型", + type: "option", + value: ["原始字节", "十六进制"] + }, + { + name: "算法", + type: "argSelector", + value: [ + { + name: "GOST 28147 (1989)", + on: [5] + }, + { + name: "GOST R 34.12 (Magma, 2015)", + off: [5] + }, + { + name: "GOST R 34.12 (Kuznyechik, 2015)", + off: [5] + } + ] + }, + { + name: "sBox", + type: "option", + value: ["E-TEST", "E-A", "E-B", "E-C", "E-D", "E-SC", "E-Z", "D-TEST", "D-A", "D-SC"] + }, + { + name: "块模式", + type: "option", + value: ["ECB", "CFB", "OFB", "CTR", "CBC"] + }, + { + name: "Key meshing模式", + type: "option", + value: ["NO", "CP"] + }, + { + name: "填充", + type: "option", + value: ["NO", "PKCS5", "ZERO", "RANDOM", "BIT"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + const [keyObj, ivObj, inputType, outputType, version, sBox, blockMode, keyMeshing, padding] = args; + + const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option)); + const iv = toHexFast(Utils.convertToByteArray(ivObj.string, ivObj.option)); + input = inputType === "十六进制" ? input : toHexFast(Utils.strToArrayBuffer(input)); + + let blockLength, versionNum; + switch (version) { + case "GOST 28147 (1989)": + versionNum = 1989; + blockLength = 64; + break; + case "GOST R 34.12 (Magma, 2015)": + versionNum = 2015; + blockLength = 64; + break; + case "GOST R 34.12 (Kuznyechik, 2015)": + versionNum = 2015; + blockLength = 128; + break; + default: + throw new OperationError(`Unknown algorithm version: ${version}`); + } + + const sBoxVal = versionNum === 1989 ? sBox : null; + + const algorithm = { + version: versionNum, + length: blockLength, + mode: "ES", + sBox: sBoxVal, + block: blockMode, + keyMeshing: keyMeshing, + padding: padding + }; + + try { + const Hex = CryptoGost.coding.Hex; + if (iv) algorithm.iv = Hex.decode(iv); + + const cipher = GostEngine.getGostCipher(algorithm); + const out = Hex.encode(cipher.decrypt(Hex.decode(key), Hex.decode(input))); + + return outputType === "十六进制" ? out : Utils.byteArrayToChars(fromHex(out)); + } catch (err) { + throw new OperationError(err); + } + } + +} + +export default GOSTDecrypt; diff --git a/plugins/srktoolbox/src/core/operations/GOSTEncrypt.mjs b/plugins/srktoolbox/src/core/operations/GOSTEncrypt.mjs new file mode 100644 index 00000000..f184e118 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/GOSTEncrypt.mjs @@ -0,0 +1,153 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { toHexFast, fromHex } from "../lib/Hex.mjs"; +import { CryptoGost, GostEngine } from "@wavesenterprise/crypto-gost-js/index.js"; + +/** + * GOST Encrypt operation + */ +class GOSTEncrypt extends Operation { + + /** + * GOSTEncrypt constructor + */ + constructor() { + super(); + + this.name = "GOST加密"; + this.module = "Ciphers"; + this.description = "GOST块密码(Magma)是苏联和俄罗斯政府标准的对称密钥块密码,块大小为64位,定义在标准GOST 28147-89(RFC 5830)中。最初的标准于1989年发布,未为密码命名,但最新修订的标准GOST R 34.12-2015(RFC 7801,RFC 8891)指定它可以被称为Magma。GOST哈希函数基于该密码。新标准还指定了一个名为Kuznyechik的新的128位块密码。

该标准在1970年代开发,曾被标记为“绝密”,1990年降级为“机密”。苏联解体后不久,该标准被解密,并在1994年向公众发布。GOST 28147是苏联替代美国标准算法DES的选择,因此两者在结构上非常相似。"; + this.infoURL = "https://wikipedia.org/wiki/GOST_(block_cipher)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Key", + type: "toggleString", + value: "", + toggleValues: ["十六进制", "UTF8", "Latin1", "Base64"] + }, + { + name: "IV", + type: "toggleString", + value: "", + toggleValues: ["十六进制", "UTF8", "Latin1", "Base64"] + }, + { + name: "输入类型", + type: "option", + value: ["原始字节", "十六进制"] + }, + { + name: "输出类型", + type: "option", + value: ["十六进制", "原始字节"] + }, + { + name: "算法", + type: "argSelector", + value: [ + { + name: "GOST 28147 (1989)", + on: [5] + }, + { + name: "GOST R 34.12 (Magma, 2015)", + off: [5] + }, + { + name: "GOST R 34.12 (Kuznyechik, 2015)", + off: [5] + } + ] + }, + { + name: "sBox", + type: "option", + value: ["E-TEST", "E-A", "E-B", "E-C", "E-D", "E-SC", "E-Z", "D-TEST", "D-A", "D-SC"] + }, + { + name: "块类型", + type: "option", + value: ["ECB", "CFB", "OFB", "CTR", "CBC"] + }, + { + name: "Key meshing模式", + type: "option", + value: ["NO", "CP"] + }, + { + name: "填充", + type: "option", + value: ["NO", "PKCS5", "ZERO", "RANDOM", "BIT"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + const [keyObj, ivObj, inputType, outputType, version, sBox, blockMode, keyMeshing, padding] = args; + + const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option)); + const iv = toHexFast(Utils.convertToByteArray(ivObj.string, ivObj.option)); + input = inputType === "十六进制" ? input : toHexFast(Utils.strToArrayBuffer(input)); + + let blockLength, versionNum; + switch (version) { + case "GOST 28147 (1989)": + versionNum = 1989; + blockLength = 64; + break; + case "GOST R 34.12 (Magma, 2015)": + versionNum = 2015; + blockLength = 64; + break; + case "GOST R 34.12 (Kuznyechik, 2015)": + versionNum = 2015; + blockLength = 128; + break; + default: + throw new OperationError(`Unknown algorithm version: ${version}`); + } + + const sBoxVal = versionNum === 1989 ? sBox : null; + + const algorithm = { + version: versionNum, + length: blockLength, + mode: "ES", + sBox: sBoxVal, + block: blockMode, + keyMeshing: keyMeshing, + padding: padding + }; + + try { + const Hex = CryptoGost.coding.Hex; + if (iv) algorithm.iv = Hex.decode(iv); + + const cipher = GostEngine.getGostCipher(algorithm); + const out = Hex.encode(cipher.encrypt(Hex.decode(key), Hex.decode(input))); + + return outputType === "十六进制" ? out : Utils.byteArrayToChars(fromHex(out)); + } catch (err) { + throw new OperationError(err); + } + } + +} + +export default GOSTEncrypt; diff --git a/plugins/srktoolbox/src/core/operations/GOSTHash.mjs b/plugins/srktoolbox/src/core/operations/GOSTHash.mjs new file mode 100644 index 00000000..c76c3342 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/GOSTHash.mjs @@ -0,0 +1,93 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import GostDigest from "../vendor/gost/gostDigest.mjs"; +import { toHexFast } from "../lib/Hex.mjs"; + +/** + * GOST hash operation + */ +class GOSTHash extends Operation { + + /** + * GOSTHash constructor + */ + constructor() { + super(); + + this.name = "GOST哈希"; + this.module = "Hashing"; + this.description = "GOST哈希算法,由标准GOST R 34.11-94和GOST 34.311-95定义,是一种256位哈希算法。最初为俄罗斯国家标准GOST R 34.11-94 Information Technology – Cryptographic Information Security – Hash Function。由其它独联体国家使用的等效标准是GOST 34.311-95。

此算法不是新标准GOST R 34.11-2012当中定义的Streebog哈希算法。

GOST哈希算法基于GOST块加密算法。"; + this.infoURL = "https://wikipedia.org/wiki/GOST_(hash_function)"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + name: "算法", + type: "argSelector", + value: [ + { + name: "GOST 28147 (1994)", + off: [1], + on: [2] + }, + { + name: "GOST R 34.11 (Streebog, 2012)", + on: [1], + off: [2] + } + ] + }, + { + name: "摘要长度", + type: "option", + value: ["256", "512"] + }, + { + name: "sBox", + type: "option", + value: ["E-TEST", "E-A", "E-B", "E-C", "E-D", "E-SC", "E-Z", "D-TEST", "D-A", "D-SC"] + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [version, length, sBox] = args; + + const versionNum = version === "GOST 28147 (1994)" ? 1994 : 2012; + const algorithm = { + name: versionNum === 1994 ? "GOST 28147" : "GOST R 34.10", + version: versionNum, + mode: "HASH" + }; + + if (versionNum === 1994) { + algorithm.sBox = sBox; + } else { + algorithm.length = parseInt(length, 10); + } + + try { + const gostDigest = new GostDigest(algorithm); + + return toHexFast(gostDigest.digest(input)); + } catch (err) { + throw new OperationError(err); + } + } + +} + +export default GOSTHash; diff --git a/plugins/srktoolbox/src/core/operations/GOSTKeyUnwrap.mjs b/plugins/srktoolbox/src/core/operations/GOSTKeyUnwrap.mjs new file mode 100644 index 00000000..16bfac67 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/GOSTKeyUnwrap.mjs @@ -0,0 +1,144 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { toHexFast, fromHex } from "../lib/Hex.mjs"; +import { CryptoGost, GostEngine } from "@wavesenterprise/crypto-gost-js/index.js"; + +/** + * GOST Key Unwrap operation + */ +class GOSTKeyUnwrap extends Operation { + + /** + * GOSTKeyUnwrap constructor + */ + constructor() { + super(); + + this.name = "GOST密钥解包装"; + this.module = "Ciphers"; + this.description = "对使用GOST块加密包装的密钥解包装。"; + this.infoURL = "https://wikipedia.org/wiki/GOST_(block_cipher)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Key", + type: "toggleString", + value: "", + toggleValues: ["十六进制", "UTF8", "Latin1", "Base64"] + }, + { + name: "User Key Material", + type: "toggleString", + value: "", + toggleValues: ["十六进制", "UTF8", "Latin1", "Base64"] + }, + { + name: "Input type", + type: "option", + value: ["十六进制", "原始字节"] + }, + { + name: "Output type", + type: "option", + value: ["原始字节", "十六进制"] + }, + { + name: "算法", + type: "argSelector", + value: [ + { + name: "GOST 28147 (1989)", + on: [5] + }, + { + name: "GOST R 34.12 (Magma, 2015)", + off: [5] + }, + { + name: "GOST R 34.12 (Kuznyechik, 2015)", + off: [5] + } + ] + }, + { + name: "sBox", + type: "option", + value: ["E-TEST", "E-A", "E-B", "E-C", "E-D", "E-SC", "E-Z", "D-TEST", "D-A", "D-SC"] + }, + { + name: "Key包装", + type: "option", + value: ["NO", "CP", "SC"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + const [keyObj, ukmObj, inputType, outputType, version, sBox, keyWrapping] = args; + + const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option)); + const ukm = toHexFast(Utils.convertToByteArray(ukmObj.string, ukmObj.option)); + input = inputType === "十六进制" ? input : toHexFast(Utils.strToArrayBuffer(input)); + + let blockLength, versionNum; + switch (version) { + case "GOST 28147 (1989)": + versionNum = 1989; + blockLength = 64; + break; + case "GOST R 34.12 (Magma, 2015)": + versionNum = 2015; + blockLength = 64; + break; + case "GOST R 34.12 (Kuznyechik, 2015)": + versionNum = 2015; + blockLength = 128; + break; + default: + throw new OperationError(`Unknown algorithm version: ${version}`); + } + + const sBoxVal = versionNum === 1989 ? sBox : null; + + const algorithm = { + version: versionNum, + length: blockLength, + mode: "KW", + sBox: sBoxVal, + keyWrapping: keyWrapping + }; + + try { + const Hex = CryptoGost.coding.Hex; + algorithm.ukm = Hex.decode(ukm); + + const cipher = GostEngine.getGostCipher(algorithm); + const out = Hex.encode(cipher.unwrapKey(Hex.decode(key), Hex.decode(input))); + + return outputType === "十六进制" ? out : Utils.byteArrayToChars(fromHex(out)); + } catch (err) { + if (err.toString().includes("Invalid typed array length")) { + throw new OperationError("无效的输入长度:必须为块大小的整数倍。"); + } + throw new OperationError(err); + } + } + +} + +export default GOSTKeyUnwrap; diff --git a/plugins/srktoolbox/src/core/operations/GOSTKeyWrap.mjs b/plugins/srktoolbox/src/core/operations/GOSTKeyWrap.mjs new file mode 100644 index 00000000..b10bcbf4 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/GOSTKeyWrap.mjs @@ -0,0 +1,144 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { toHexFast, fromHex } from "../lib/Hex.mjs"; +import { CryptoGost, GostEngine } from "@wavesenterprise/crypto-gost-js/index.js"; + +/** + * GOST Key Wrap operation + */ +class GOSTKeyWrap extends Operation { + + /** + * GOSTKeyWrap constructor + */ + constructor() { + super(); + + this.name = "GOST密钥包装"; + this.module = "Ciphers"; + this.description = "使用GOST块密码对密钥在不可信环境中进行包装。"; + this.infoURL = "https://wikipedia.org/wiki/GOST_(block_cipher)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Key", + type: "toggleString", + value: "", + toggleValues: ["十六进制", "UTF8", "Latin1", "Base64"] + }, + { + name: "User Key Material", + type: "toggleString", + value: "", + toggleValues: ["十六进制", "UTF8", "Latin1", "Base64"] + }, + { + name: "输入类型", + type: "option", + value: ["原始字节", "十六进制"] + }, + { + name: "输出类型", + type: "option", + value: ["十六进制", "原始字节"] + }, + { + name: "算法", + type: "argSelector", + value: [ + { + name: "GOST 28147 (1989)", + on: [5] + }, + { + name: "GOST R 34.12 (Magma, 2015)", + off: [5] + }, + { + name: "GOST R 34.12 (Kuznyechik, 2015)", + off: [5] + } + ] + }, + { + name: "sBox", + type: "option", + value: ["E-TEST", "E-A", "E-B", "E-C", "E-D", "E-SC", "E-Z", "D-TEST", "D-A", "D-SC"] + }, + { + name: "Key包装", + type: "option", + value: ["NO", "CP", "SC"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + const [keyObj, ukmObj, inputType, outputType, version, sBox, keyWrapping] = args; + + const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option)); + const ukm = toHexFast(Utils.convertToByteArray(ukmObj.string, ukmObj.option)); + input = inputType === "十六进制" ? input : toHexFast(Utils.strToArrayBuffer(input)); + + let blockLength, versionNum; + switch (version) { + case "GOST 28147 (1989)": + versionNum = 1989; + blockLength = 64; + break; + case "GOST R 34.12 (Magma, 2015)": + versionNum = 2015; + blockLength = 64; + break; + case "GOST R 34.12 (Kuznyechik, 2015)": + versionNum = 2015; + blockLength = 128; + break; + default: + throw new OperationError(`Unknown algorithm version: ${version}`); + } + + const sBoxVal = versionNum === 1989 ? sBox : null; + + const algorithm = { + version: versionNum, + length: blockLength, + mode: "KW", + sBox: sBoxVal, + keyWrapping: keyWrapping + }; + + try { + const Hex = CryptoGost.coding.Hex; + algorithm.ukm = Hex.decode(ukm); + + const cipher = GostEngine.getGostCipher(algorithm); + const out = Hex.encode(cipher.wrapKey(Hex.decode(key), Hex.decode(input))); + + return outputType === "十六进制" ? out : Utils.byteArrayToChars(fromHex(out)); + } catch (err) { + if (err.toString().includes("Invalid typed array length")) { + throw new OperationError("无效的输入长度:必须为块大小的整数倍。"); + } + throw new OperationError(err); + } + } + +} + +export default GOSTKeyWrap; diff --git a/plugins/srktoolbox/src/core/operations/GOSTSign.mjs b/plugins/srktoolbox/src/core/operations/GOSTSign.mjs new file mode 100644 index 00000000..260f100f --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/GOSTSign.mjs @@ -0,0 +1,144 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { toHexFast, fromHex } from "../lib/Hex.mjs"; +import { CryptoGost, GostEngine } from "@wavesenterprise/crypto-gost-js/index.js"; + +/** + * GOST Sign operation + */ +class GOSTSign extends Operation { + + /** + * GOSTSign constructor + */ + constructor() { + super(); + + this.name = "GOST签名"; + this.module = "Ciphers"; + this.description = "使用GOST块加密对明文信息签名。"; + this.infoURL = "https://wikipedia.org/wiki/GOST_(block_cipher)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Key", + type: "toggleString", + value: "", + toggleValues: ["十六进制", "UTF8", "Latin1", "Base64"] + }, + { + name: "IV", + type: "toggleString", + value: "", + toggleValues: ["十六进制", "UTF8", "Latin1", "Base64"] + }, + { + name: "输入类型", + type: "option", + value: ["原始字节", "十六进制"] + }, + { + name: "输出类型", + type: "option", + value: ["十六进制", "原始字节"] + }, + { + name: "算法", + type: "argSelector", + value: [ + { + name: "GOST 28147 (1989)", + on: [5] + }, + { + name: "GOST R 34.12 (Magma, 2015)", + off: [5] + }, + { + name: "GOST R 34.12 (Kuznyechik, 2015)", + off: [5] + } + ] + }, + { + name: "sBox", + type: "option", + value: ["E-TEST", "E-A", "E-B", "E-C", "E-D", "E-SC", "E-Z", "D-TEST", "D-A", "D-SC"] + }, + { + name: "MAC长度", + type: "number", + value: 32, + min: 8, + max: 64, + step: 8 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + const [keyObj, ivObj, inputType, outputType, version, sBox, macLength] = args; + + const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option)); + const iv = toHexFast(Utils.convertToByteArray(ivObj.string, ivObj.option)); + input = inputType === "十六进制" ? input : toHexFast(Utils.strToArrayBuffer(input)); + + let blockLength, versionNum; + switch (version) { + case "GOST 28147 (1989)": + versionNum = 1989; + blockLength = 64; + break; + case "GOST R 34.12 (Magma, 2015)": + versionNum = 2015; + blockLength = 64; + break; + case "GOST R 34.12 (Kuznyechik, 2015)": + versionNum = 2015; + blockLength = 128; + break; + default: + throw new OperationError(`Unknown algorithm version: ${version}`); + } + + const sBoxVal = versionNum === 1989 ? sBox : null; + + const algorithm = { + version: versionNum, + length: blockLength, + mode: "MAC", + sBox: sBoxVal, + macLength: macLength + }; + + try { + const Hex = CryptoGost.coding.Hex; + if (iv) algorithm.iv = Hex.decode(iv); + + const cipher = GostEngine.getGostCipher(algorithm); + const out = Hex.encode(cipher.sign(Hex.decode(key), Hex.decode(input))); + + return outputType === "十六进制" ? out : Utils.byteArrayToChars(fromHex(out)); + } catch (err) { + throw new OperationError(err); + } + } + +} + +export default GOSTSign; diff --git a/plugins/srktoolbox/src/core/operations/GOSTVerify.mjs b/plugins/srktoolbox/src/core/operations/GOSTVerify.mjs new file mode 100644 index 00000000..e828b291 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/GOSTVerify.mjs @@ -0,0 +1,138 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { toHexFast } from "../lib/Hex.mjs"; +import { CryptoGost, GostEngine } from "@wavesenterprise/crypto-gost-js/index.js"; + +/** + * GOST Verify operation + */ +class GOSTVerify extends Operation { + + /** + * GOSTVerify constructor + */ + constructor() { + super(); + + this.name = "GOST验证"; + this.module = "Ciphers"; + this.description = "使用GOST块加密算法验证明文信息的签名。将签名输入到MAC框中。"; + this.infoURL = "https://wikipedia.org/wiki/GOST_(block_cipher)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Key", + type: "toggleString", + value: "", + toggleValues: ["十六进制", "UTF8", "Latin1", "Base64"] + }, + { + name: "IV", + type: "toggleString", + value: "", + toggleValues: ["十六进制", "UTF8", "Latin1", "Base64"] + }, + { + name: "MAC", + type: "toggleString", + value: "", + toggleValues: ["十六进制", "UTF8", "Latin1", "Base64"] + }, + { + name: "输入类型", + type: "option", + value: ["原始字节", "十六进制"] + }, + { + name: "算法", + type: "argSelector", + value: [ + { + name: "GOST 28147 (1989)", + on: [5] + }, + { + name: "GOST R 34.12 (Magma, 2015)", + off: [5] + }, + { + name: "GOST R 34.12 (Kuznyechik, 2015)", + off: [5] + } + ] + }, + { + name: "sBox", + type: "option", + value: ["E-TEST", "E-A", "E-B", "E-C", "E-D", "E-SC", "E-Z", "D-TEST", "D-A", "D-SC"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + const [keyObj, ivObj, macObj, inputType, version, sBox] = args; + + const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option)); + const iv = toHexFast(Utils.convertToByteArray(ivObj.string, ivObj.option)); + const mac = toHexFast(Utils.convertToByteArray(macObj.string, macObj.option)); + input = inputType === "十六进制" ? input : toHexFast(Utils.strToArrayBuffer(input)); + + let blockLength, versionNum; + switch (version) { + case "GOST 28147 (1989)": + versionNum = 1989; + blockLength = 64; + break; + case "GOST R 34.12 (Magma, 2015)": + versionNum = 2015; + blockLength = 64; + break; + case "GOST R 34.12 (Kuznyechik, 2015)": + versionNum = 2015; + blockLength = 128; + break; + default: + throw new OperationError(`Unknown algorithm version: ${version}`); + } + + const sBoxVal = versionNum === 1989 ? sBox : null; + + const algorithm = { + version: versionNum, + length: blockLength, + mode: "MAC", + sBox: sBoxVal, + macLength: mac.length * 4 + }; + + try { + const Hex = CryptoGost.coding.Hex; + if (iv) algorithm.iv = Hex.decode(iv); + + const cipher = GostEngine.getGostCipher(algorithm); + const out = cipher.verify(Hex.decode(key), Hex.decode(mac), Hex.decode(input)); + + return out ? "签名相符" : "签名不符"; + } catch (err) { + throw new OperationError(err); + } + } + +} + +export default GOSTVerify; diff --git a/plugins/srktoolbox/src/core/operations/GenerateAllChecksums.mjs b/plugins/srktoolbox/src/core/operations/GenerateAllChecksums.mjs new file mode 100644 index 00000000..a5c65b46 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/GenerateAllChecksums.mjs @@ -0,0 +1,256 @@ +/** + * @author r4mos [2k95ljkhg@mozmail.com] + * @copyright Crown Copyright 2025 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Adler32Checksum from "./Adler32Checksum.mjs"; +import CRCChecksum from "./CRCChecksum.mjs"; +import Fletcher8Checksum from "./Fletcher8Checksum.mjs"; +import Fletcher16Checksum from "./Fletcher16Checksum.mjs"; +import Fletcher32Checksum from "./Fletcher32Checksum.mjs"; +import Fletcher64Checksum from "./Fletcher64Checksum.mjs"; + +/** + * Generate all checksums operation + */ +class GenerateAllChecksums extends Operation { + + /** + * GenerateAllChecksums constructor + */ + constructor() { + super(); + + this.name = "生成所有校验和"; + this.module = "Crypto"; + this.description = "对给定的输入生成所有支持的校验和。"; + this.infoURL = "https://wikipedia.org/wiki/Checksum"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + name: "长度(位)", + type: "option", + value: [ + "全部", "3", "4", "5", "6", "7", "8", "10", "11", "12", "13", "14", "15", "16", "17", "21", "24", "30", "31", "32", "40", "64", "82" + ] + }, + { + name: "包括名称", + type: "boolean", + value: true + }, + ]; + + const adler32 = new Adler32Checksum; + const crc = new CRCChecksum; + const fletcher8 = new Fletcher8Checksum; + const fletcher16 = new Fletcher16Checksum; + const fletcher32 = new Fletcher32Checksum; + const fletcher64 = new Fletcher64Checksum; + this.checksums = [ + {name: "CRC-3/GSM", algo: crc, params: ["CRC-3/GSM"]}, + {name: "CRC-3/ROHC", algo: crc, params: ["CRC-3/ROHC"]}, + {name: "CRC-4/G-704", algo: crc, params: ["CRC-4/G-704"]}, + {name: "CRC-4/INTERLAKEN", algo: crc, params: ["CRC-4/INTERLAKEN"]}, + {name: "CRC-4/ITU", algo: crc, params: ["CRC-4/ITU"]}, + {name: "CRC-5/EPC", algo: crc, params: ["CRC-5/EPC"]}, + {name: "CRC-5/EPC-C1G2", algo: crc, params: ["CRC-5/EPC-C1G2"]}, + {name: "CRC-5/G-704", algo: crc, params: ["CRC-5/G-704"]}, + {name: "CRC-5/ITU", algo: crc, params: ["CRC-5/ITU"]}, + {name: "CRC-5/USB", algo: crc, params: ["CRC-5/USB"]}, + {name: "CRC-6/CDMA2000-A", algo: crc, params: ["CRC-6/CDMA2000-A"]}, + {name: "CRC-6/CDMA2000-B", algo: crc, params: ["CRC-6/CDMA2000-B"]}, + {name: "CRC-6/DARC", algo: crc, params: ["CRC-6/DARC"]}, + {name: "CRC-6/G-704", algo: crc, params: ["CRC-6/G-704"]}, + {name: "CRC-6/GSM", algo: crc, params: ["CRC-6/GSM"]}, + {name: "CRC-6/ITU", algo: crc, params: ["CRC-6/ITU"]}, + {name: "CRC-7/MMC", algo: crc, params: ["CRC-7/MMC"]}, + {name: "CRC-7/ROHC", algo: crc, params: ["CRC-7/ROHC"]}, + {name: "CRC-7/UMTS", algo: crc, params: ["CRC-7/UMTS"]}, + {name: "CRC-8", algo: crc, params: ["CRC-8"]}, + {name: "CRC-8/8H2F", algo: crc, params: ["CRC-8/8H2F"]}, + {name: "CRC-8/AES", algo: crc, params: ["CRC-8/AES"]}, + {name: "CRC-8/AUTOSAR", algo: crc, params: ["CRC-8/AUTOSAR"]}, + {name: "CRC-8/BLUETOOTH", algo: crc, params: ["CRC-8/BLUETOOTH"]}, + {name: "CRC-8/CDMA2000", algo: crc, params: ["CRC-8/CDMA2000"]}, + {name: "CRC-8/DARC", algo: crc, params: ["CRC-8/DARC"]}, + {name: "CRC-8/DVB-S2", algo: crc, params: ["CRC-8/DVB-S2"]}, + {name: "CRC-8/EBU", algo: crc, params: ["CRC-8/EBU"]}, + {name: "CRC-8/GSM-A", algo: crc, params: ["CRC-8/GSM-A"]}, + {name: "CRC-8/GSM-B", algo: crc, params: ["CRC-8/GSM-B"]}, + {name: "CRC-8/HITAG", algo: crc, params: ["CRC-8/HITAG"]}, + {name: "CRC-8/I-432-1", algo: crc, params: ["CRC-8/I-432-1"]}, + {name: "CRC-8/I-CODE", algo: crc, params: ["CRC-8/I-CODE"]}, + {name: "CRC-8/ITU", algo: crc, params: ["CRC-8/ITU"]}, + {name: "CRC-8/LTE", algo: crc, params: ["CRC-8/LTE"]}, + {name: "CRC-8/MAXIM", algo: crc, params: ["CRC-8/MAXIM"]}, + {name: "CRC-8/MAXIM-DOW", algo: crc, params: ["CRC-8/MAXIM-DOW"]}, + {name: "CRC-8/MIFARE-MAD", algo: crc, params: ["CRC-8/MIFARE-MAD"]}, + {name: "CRC-8/NRSC-5", algo: crc, params: ["CRC-8/NRSC-5"]}, + {name: "CRC-8/OPENSAFETY", algo: crc, params: ["CRC-8/OPENSAFETY"]}, + {name: "CRC-8/ROHC", algo: crc, params: ["CRC-8/ROHC"]}, + {name: "CRC-8/SAE-J1850", algo: crc, params: ["CRC-8/SAE-J1850"]}, + {name: "CRC-8/SAE-J1850-ZERO", algo: crc, params: ["CRC-8/SAE-J1850-ZERO"]}, + {name: "CRC-8/SMBUS", algo: crc, params: ["CRC-8/SMBUS"]}, + {name: "CRC-8/TECH-3250", algo: crc, params: ["CRC-8/TECH-3250"]}, + {name: "CRC-8/WCDMA", algo: crc, params: ["CRC-8/WCDMA"]}, + {name: "Fletcher-8", algo: fletcher8, params: []}, + {name: "CRC-10/ATM", algo: crc, params: ["CRC-10/ATM"]}, + {name: "CRC-10/CDMA2000", algo: crc, params: ["CRC-10/CDMA2000"]}, + {name: "CRC-10/GSM", algo: crc, params: ["CRC-10/GSM"]}, + {name: "CRC-10/I-610", algo: crc, params: ["CRC-10/I-610"]}, + {name: "CRC-11/FLEXRAY", algo: crc, params: ["CRC-11/FLEXRAY"]}, + {name: "CRC-11/UMTS", algo: crc, params: ["CRC-11/UMTS"]}, + {name: "CRC-12/3GPP", algo: crc, params: ["CRC-12/3GPP"]}, + {name: "CRC-12/CDMA2000", algo: crc, params: ["CRC-12/CDMA2000"]}, + {name: "CRC-12/DECT", algo: crc, params: ["CRC-12/DECT"]}, + {name: "CRC-12/GSM", algo: crc, params: ["CRC-12/GSM"]}, + {name: "CRC-12/UMTS", algo: crc, params: ["CRC-12/UMTS"]}, + {name: "CRC-13/BBC", algo: crc, params: ["CRC-13/BBC"]}, + {name: "CRC-14/DARC", algo: crc, params: ["CRC-14/DARC"]}, + {name: "CRC-14/GSM", algo: crc, params: ["CRC-14/GSM"]}, + {name: "CRC-15/CAN", algo: crc, params: ["CRC-15/CAN"]}, + {name: "CRC-15/MPT1327", algo: crc, params: ["CRC-15/MPT1327"]}, + {name: "CRC-16", algo: crc, params: ["CRC-16"]}, + {name: "CRC-16/A", algo: crc, params: ["CRC-16/A"]}, + {name: "CRC-16/ACORN", algo: crc, params: ["CRC-16/ACORN"]}, + {name: "CRC-16/ARC", algo: crc, params: ["CRC-16/ARC"]}, + {name: "CRC-16/AUG-CCITT", algo: crc, params: ["CRC-16/AUG-CCITT"]}, + {name: "CRC-16/AUTOSAR", algo: crc, params: ["CRC-16/AUTOSAR"]}, + {name: "CRC-16/B", algo: crc, params: ["CRC-16/B"]}, + {name: "CRC-16/BLUETOOTH", algo: crc, params: ["CRC-16/BLUETOOTH"]}, + {name: "CRC-16/BUYPASS", algo: crc, params: ["CRC-16/BUYPASS"]}, + {name: "CRC-16/CCITT", algo: crc, params: ["CRC-16/CCITT"]}, + {name: "CRC-16/CCITT-FALSE", algo: crc, params: ["CRC-16/CCITT-FALSE"]}, + {name: "CRC-16/CCITT-TRUE", algo: crc, params: ["CRC-16/CCITT-TRUE"]}, + {name: "CRC-16/CCITT-ZERO", algo: crc, params: ["CRC-16/CCITT-ZERO"]}, + {name: "CRC-16/CDMA2000", algo: crc, params: ["CRC-16/CDMA2000"]}, + {name: "CRC-16/CMS", algo: crc, params: ["CRC-16/CMS"]}, + {name: "CRC-16/DARC", algo: crc, params: ["CRC-16/DARC"]}, + {name: "CRC-16/DDS-110", algo: crc, params: ["CRC-16/DDS-110"]}, + {name: "CRC-16/DECT-R", algo: crc, params: ["CRC-16/DECT-R"]}, + {name: "CRC-16/DECT-X", algo: crc, params: ["CRC-16/DECT-X"]}, + {name: "CRC-16/DNP", algo: crc, params: ["CRC-16/DNP"]}, + {name: "CRC-16/EN-13757", algo: crc, params: ["CRC-16/EN-13757"]}, + {name: "CRC-16/EPC", algo: crc, params: ["CRC-16/EPC"]}, + {name: "CRC-16/EPC-C1G2", algo: crc, params: ["CRC-16/EPC-C1G2"]}, + {name: "CRC-16/GENIBUS", algo: crc, params: ["CRC-16/GENIBUS"]}, + {name: "CRC-16/GSM", algo: crc, params: ["CRC-16/GSM"]}, + {name: "CRC-16/I-CODE", algo: crc, params: ["CRC-16/I-CODE"]}, + {name: "CRC-16/IBM", algo: crc, params: ["CRC-16/IBM"]}, + {name: "CRC-16/IBM-3740", algo: crc, params: ["CRC-16/IBM-3740"]}, + {name: "CRC-16/IBM-SDLC", algo: crc, params: ["CRC-16/IBM-SDLC"]}, + {name: "CRC-16/IEC-61158-2", algo: crc, params: ["CRC-16/IEC-61158-2"]}, + {name: "CRC-16/ISO-HDLC", algo: crc, params: ["CRC-16/ISO-HDLC"]}, + {name: "CRC-16/ISO-IEC-14443-3-A", algo: crc, params: ["CRC-16/ISO-IEC-14443-3-A"]}, + {name: "CRC-16/ISO-IEC-14443-3-B", algo: crc, params: ["CRC-16/ISO-IEC-14443-3-B"]}, + {name: "CRC-16/KERMIT", algo: crc, params: ["CRC-16/KERMIT"]}, + {name: "CRC-16/LHA", algo: crc, params: ["CRC-16/LHA"]}, + {name: "CRC-16/LJ1200", algo: crc, params: ["CRC-16/LJ1200"]}, + {name: "CRC-16/LTE", algo: crc, params: ["CRC-16/LTE"]}, + {name: "CRC-16/M17", algo: crc, params: ["CRC-16/M17"]}, + {name: "CRC-16/MAXIM", algo: crc, params: ["CRC-16/MAXIM"]}, + {name: "CRC-16/MAXIM-DOW", algo: crc, params: ["CRC-16/MAXIM-DOW"]}, + {name: "CRC-16/MCRF4XX", algo: crc, params: ["CRC-16/MCRF4XX"]}, + {name: "CRC-16/MODBUS", algo: crc, params: ["CRC-16/MODBUS"]}, + {name: "CRC-16/NRSC-5", algo: crc, params: ["CRC-16/NRSC-5"]}, + {name: "CRC-16/OPENSAFETY-A", algo: crc, params: ["CRC-16/OPENSAFETY-A"]}, + {name: "CRC-16/OPENSAFETY-B", algo: crc, params: ["CRC-16/OPENSAFETY-B"]}, + {name: "CRC-16/PROFIBUS", algo: crc, params: ["CRC-16/PROFIBUS"]}, + {name: "CRC-16/RIELLO", algo: crc, params: ["CRC-16/RIELLO"]}, + {name: "CRC-16/SPI-FUJITSU", algo: crc, params: ["CRC-16/SPI-FUJITSU"]}, + {name: "CRC-16/T10-DIF", algo: crc, params: ["CRC-16/T10-DIF"]}, + {name: "CRC-16/TELEDISK", algo: crc, params: ["CRC-16/TELEDISK"]}, + {name: "CRC-16/TMS37157", algo: crc, params: ["CRC-16/TMS37157"]}, + {name: "CRC-16/UMTS", algo: crc, params: ["CRC-16/UMTS"]}, + {name: "CRC-16/USB", algo: crc, params: ["CRC-16/USB"]}, + {name: "CRC-16/V-41-LSB", algo: crc, params: ["CRC-16/V-41-LSB"]}, + {name: "CRC-16/V-41-MSB", algo: crc, params: ["CRC-16/V-41-MSB"]}, + {name: "CRC-16/VERIFONE", algo: crc, params: ["CRC-16/VERIFONE"]}, + {name: "CRC-16/X-25", algo: crc, params: ["CRC-16/X-25"]}, + {name: "CRC-16/XMODEM", algo: crc, params: ["CRC-16/XMODEM"]}, + {name: "CRC-16/ZMODEM", algo: crc, params: ["CRC-16/ZMODEM"]}, + {name: "Fletcher-16", algo: fletcher16, params: []}, + {name: "CRC-17/CAN-FD", algo: crc, params: ["CRC-17/CAN-FD"]}, + {name: "CRC-21/CAN-FD", algo: crc, params: ["CRC-21/CAN-FD"]}, + {name: "CRC-24/BLE", algo: crc, params: ["CRC-24/BLE"]}, + {name: "CRC-24/FLEXRAY-A", algo: crc, params: ["CRC-24/FLEXRAY-A"]}, + {name: "CRC-24/FLEXRAY-B", algo: crc, params: ["CRC-24/FLEXRAY-B"]}, + {name: "CRC-24/INTERLAKEN", algo: crc, params: ["CRC-24/INTERLAKEN"]}, + {name: "CRC-24/LTE-A", algo: crc, params: ["CRC-24/LTE-A"]}, + {name: "CRC-24/LTE-B", algo: crc, params: ["CRC-24/LTE-B"]}, + {name: "CRC-24/OPENPGP", algo: crc, params: ["CRC-24/OPENPGP"]}, + {name: "CRC-24/OS-9", algo: crc, params: ["CRC-24/OS-9"]}, + {name: "CRC-30/CDMA", algo: crc, params: ["CRC-30/CDMA"]}, + {name: "CRC-31/PHILIPS", algo: crc, params: ["CRC-31/PHILIPS"]}, + {name: "Adler-32", algo: adler32, params: []}, + {name: "CRC-32", algo: crc, params: ["CRC-32"]}, + {name: "CRC-32/AAL5", algo: crc, params: ["CRC-32/AAL5"]}, + {name: "CRC-32/ADCCP", algo: crc, params: ["CRC-32/ADCCP"]}, + {name: "CRC-32/AIXM", algo: crc, params: ["CRC-32/AIXM"]}, + {name: "CRC-32/AUTOSAR", algo: crc, params: ["CRC-32/AUTOSAR"]}, + {name: "CRC-32/BASE91-C", algo: crc, params: ["CRC-32/BASE91-C"]}, + {name: "CRC-32/BASE91-D", algo: crc, params: ["CRC-32/BASE91-D"]}, + {name: "CRC-32/BZIP2", algo: crc, params: ["CRC-32/BZIP2"]}, + {name: "CRC-32/C", algo: crc, params: ["CRC-32/C"]}, + {name: "CRC-32/CASTAGNOLI", algo: crc, params: ["CRC-32/CASTAGNOLI"]}, + {name: "CRC-32/CD-ROM-EDC", algo: crc, params: ["CRC-32/CD-ROM-EDC"]}, + {name: "CRC-32/CKSUM", algo: crc, params: ["CRC-32/CKSUM"]}, + {name: "CRC-32/D", algo: crc, params: ["CRC-32/D"]}, + {name: "CRC-32/DECT-B", algo: crc, params: ["CRC-32/DECT-B"]}, + {name: "CRC-32/INTERLAKEN", algo: crc, params: ["CRC-32/INTERLAKEN"]}, + {name: "CRC-32/ISCSI", algo: crc, params: ["CRC-32/ISCSI"]}, + {name: "CRC-32/ISO-HDLC", algo: crc, params: ["CRC-32/ISO-HDLC"]}, + {name: "CRC-32/JAMCRC", algo: crc, params: ["CRC-32/JAMCRC"]}, + {name: "CRC-32/MEF", algo: crc, params: ["CRC-32/MEF"]}, + {name: "CRC-32/MPEG-2", algo: crc, params: ["CRC-32/MPEG-2"]}, + {name: "CRC-32/NVME", algo: crc, params: ["CRC-32/NVME"]}, + {name: "CRC-32/PKZIP", algo: crc, params: ["CRC-32/PKZIP"]}, + {name: "CRC-32/POSIX", algo: crc, params: ["CRC-32/POSIX"]}, + {name: "CRC-32/Q", algo: crc, params: ["CRC-32/Q"]}, + {name: "CRC-32/SATA", algo: crc, params: ["CRC-32/SATA"]}, + {name: "CRC-32/V-42", algo: crc, params: ["CRC-32/V-42"]}, + {name: "CRC-32/XFER", algo: crc, params: ["CRC-32/XFER"]}, + {name: "CRC-32/XZ", algo: crc, params: ["CRC-32/XZ"]}, + {name: "Fletcher-32", algo: fletcher32, params: []}, + {name: "CRC-40/GSM", algo: crc, params: ["CRC-40/GSM"]}, + {name: "CRC-64/ECMA-182", algo: crc, params: ["CRC-64/ECMA-182"]}, + {name: "CRC-64/GO-ECMA", algo: crc, params: ["CRC-64/GO-ECMA"]}, + {name: "CRC-64/GO-ISO", algo: crc, params: ["CRC-64/GO-ISO"]}, + {name: "CRC-64/MS", algo: crc, params: ["CRC-64/MS"]}, + {name: "CRC-64/NVME", algo: crc, params: ["CRC-64/NVME"]}, + {name: "CRC-64/REDIS", algo: crc, params: ["CRC-64/REDIS"]}, + {name: "CRC-64/WE", algo: crc, params: ["CRC-64/WE"]}, + {name: "CRC-64/XZ", algo: crc, params: ["CRC-64/XZ"]}, + {name: "Fletcher-64", algo: fletcher64, params: []}, + {name: "CRC-82/DARC", algo: crc, params: ["CRC-82/DARC"]} + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [length, includeNames] = args; + let output = ""; + this.checksums.forEach(checksum => { + const checksumLength = checksum.name.match(new RegExp("-(\\d{1,2})(\\/|$)"))[1]; + if (length === "全部" || length === checksumLength) { + const value = checksum.algo.run(new Uint8Array(input), checksum.params || []); + output += includeNames ? + `${checksum.name}:${" ".repeat(25-checksum.name.length)}${value}\n`: + `${value}\n`; + } + }); + return output; + } +} + +export default GenerateAllChecksums; diff --git a/plugins/srktoolbox/src/core/operations/GenerateAllHashes.mjs b/plugins/srktoolbox/src/core/operations/GenerateAllHashes.mjs new file mode 100644 index 00000000..8a660dd0 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/GenerateAllHashes.mjs @@ -0,0 +1,181 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @author john19696 [john19696@protonmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import MD2 from "./MD2.mjs"; +import MD4 from "./MD4.mjs"; +import MD5 from "./MD5.mjs"; +import MD6 from "./MD6.mjs"; +import SHA0 from "./SHA0.mjs"; +import SHA1 from "./SHA1.mjs"; +import SHA2 from "./SHA2.mjs"; +import SHA3 from "./SHA3.mjs"; +import Keccak from "./Keccak.mjs"; +import Shake from "./Shake.mjs"; +import RIPEMD from "./RIPEMD.mjs"; +import HAS160 from "./HAS160.mjs"; +import Whirlpool from "./Whirlpool.mjs"; +import SSDEEP from "./SSDEEP.mjs"; +import CTPH from "./CTPH.mjs"; +import BLAKE2b from "./BLAKE2b.mjs"; +import BLAKE2s from "./BLAKE2s.mjs"; +import Streebog from "./Streebog.mjs"; +import GOSTHash from "./GOSTHash.mjs"; +import LMHash from "./LMHash.mjs"; +import NTHash from "./NTHash.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Generate all hashes operation + */ +class GenerateAllHashes extends Operation { + + /** + * GenerateAllHashes constructor + */ + constructor() { + super(); + + this.name = "哈希生成"; + this.module = "Crypto"; + this.description = "对给定输入数据计算目前支持的所有哈希值和校验和。"; + this.infoURL = "https://wikipedia.org/wiki/Comparison_of_cryptographic_hash_functions"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + name: "长度(位)", + type: "option", + value: [ + "所有", "128", "160", "224", "256", "320", "384", "512" + ] + }, + { + name: "包括算法名称", + type: "boolean", + value: true + }, + ]; + this.hashes = [ + {name: "MD2", algo: (new MD2()), inputType: "arrayBuffer", params: []}, + {name: "MD4", algo: (new MD4()), inputType: "arrayBuffer", params: []}, + {name: "MD5", algo: (new MD5()), inputType: "arrayBuffer", params: []}, + {name: "MD6", algo: (new MD6()), inputType: "str", params: []}, + {name: "SHA0", algo: (new SHA0()), inputType: "arrayBuffer", params: []}, + {name: "SHA1", algo: (new SHA1()), inputType: "arrayBuffer", params: []}, + {name: "SHA2 224", algo: (new SHA2()), inputType: "arrayBuffer", params: ["224"]}, + {name: "SHA2 256", algo: (new SHA2()), inputType: "arrayBuffer", params: ["256"]}, + {name: "SHA2 384", algo: (new SHA2()), inputType: "arrayBuffer", params: ["384"]}, + {name: "SHA2 512", algo: (new SHA2()), inputType: "arrayBuffer", params: ["512"]}, + {name: "SHA3 224", algo: (new SHA3()), inputType: "arrayBuffer", params: ["224"]}, + {name: "SHA3 256", algo: (new SHA3()), inputType: "arrayBuffer", params: ["256"]}, + {name: "SHA3 384", algo: (new SHA3()), inputType: "arrayBuffer", params: ["384"]}, + {name: "SHA3 512", algo: (new SHA3()), inputType: "arrayBuffer", params: ["512"]}, + {name: "Keccak 224", algo: (new Keccak()), inputType: "arrayBuffer", params: ["224"]}, + {name: "Keccak 256", algo: (new Keccak()), inputType: "arrayBuffer", params: ["256"]}, + {name: "Keccak 384", algo: (new Keccak()), inputType: "arrayBuffer", params: ["384"]}, + {name: "Keccak 512", algo: (new Keccak()), inputType: "arrayBuffer", params: ["512"]}, + {name: "Shake 128", algo: (new Shake()), inputType: "arrayBuffer", params: ["128", 256]}, + {name: "Shake 256", algo: (new Shake()), inputType: "arrayBuffer", params: ["256", 512]}, + {name: "RIPEMD-128", algo: (new RIPEMD()), inputType: "arrayBuffer", params: ["128"]}, + {name: "RIPEMD-160", algo: (new RIPEMD()), inputType: "arrayBuffer", params: ["160"]}, + {name: "RIPEMD-256", algo: (new RIPEMD()), inputType: "arrayBuffer", params: ["256"]}, + {name: "RIPEMD-320", algo: (new RIPEMD()), inputType: "arrayBuffer", params: ["320"]}, + {name: "HAS-160", algo: (new HAS160()), inputType: "arrayBuffer", params: []}, + {name: "Whirlpool-0", algo: (new Whirlpool()), inputType: "arrayBuffer", params: ["Whirlpool-0"]}, + {name: "Whirlpool-T", algo: (new Whirlpool()), inputType: "arrayBuffer", params: ["Whirlpool-T"]}, + {name: "Whirlpool", algo: (new Whirlpool()), inputType: "arrayBuffer", params: ["Whirlpool"]}, + {name: "BLAKE2b-128", algo: (new BLAKE2b), inputType: "arrayBuffer", params: ["128", "十六进制", {string: "", option: "UTF8"}]}, + {name: "BLAKE2b-160", algo: (new BLAKE2b), inputType: "arrayBuffer", params: ["160", "十六进制", {string: "", option: "UTF8"}]}, + {name: "BLAKE2b-256", algo: (new BLAKE2b), inputType: "arrayBuffer", params: ["256", "十六进制", {string: "", option: "UTF8"}]}, + {name: "BLAKE2b-384", algo: (new BLAKE2b), inputType: "arrayBuffer", params: ["384", "十六进制", {string: "", option: "UTF8"}]}, + {name: "BLAKE2b-512", algo: (new BLAKE2b), inputType: "arrayBuffer", params: ["512", "十六进制", {string: "", option: "UTF8"}]}, + {name: "BLAKE2s-128", algo: (new BLAKE2s), inputType: "arrayBuffer", params: ["128", "十六进制", {string: "", option: "UTF8"}]}, + {name: "BLAKE2s-160", algo: (new BLAKE2s), inputType: "arrayBuffer", params: ["160", "十六进制", {string: "", option: "UTF8"}]}, + {name: "BLAKE2s-256", algo: (new BLAKE2s), inputType: "arrayBuffer", params: ["256", "十六进制", {string: "", option: "UTF8"}]}, + {name: "Streebog-256", algo: (new Streebog), inputType: "arrayBuffer", params: ["256"]}, + {name: "Streebog-512", algo: (new Streebog), inputType: "arrayBuffer", params: ["512"]}, + {name: "GOST", algo: (new GOSTHash), inputType: "arrayBuffer", params: ["GOST 28147 (1994)", "256", "D-A"]}, + {name: "LM Hash", algo: (new LMHash), inputType: "str", params: []}, + {name: "NT Hash", algo: (new NTHash), inputType: "str", params: []}, + {name: "SSDEEP", algo: (new SSDEEP()), inputType: "str"}, + {name: "CTPH", algo: (new CTPH()), inputType: "str"} + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [length, includeNames] = args; + this.inputArrayBuffer = input; + this.inputStr = Utils.arrayBufferToStr(input, false); + this.inputByteArray = new Uint8Array(input); + + let digest, output = ""; + // iterate over each of the hashes + this.hashes.forEach(hash => { + digest = this.executeAlgo(hash.algo, hash.inputType, hash.params || []); + output += this.formatDigest(digest, length, includeNames, hash.name); + }); + + return output; + } + + /** + * Executes a hash or checksum algorithm + * + * @param {Function} algo - The hash or checksum algorithm + * @param {string} inputType + * @param {Object[]} [params=[]] + * @returns {string} + */ + executeAlgo(algo, inputType, params=[]) { + let digest = null; + switch (inputType) { + case "arrayBuffer": + digest = algo.run(this.inputArrayBuffer, params); + break; + case "str": + digest = algo.run(this.inputStr, params); + break; + case "byteArray": + digest = algo.run(this.inputByteArray, params); + break; + default: + throw new OperationError("未知哈希类型: " + inputType); + } + + return digest; + } + + /** + * Formats the digest depending on user-specified arguments + * @param {string} digest + * @param {string} length + * @param {boolean} includeNames + * @param {string} name + * @returns {string} + */ + formatDigest(digest, length, includeNames, name) { + if (length !== "所有" && (digest.length * 4) !== parseInt(length, 10)) + return ""; + + if (!includeNames) + return digest + "\n"; + + return `${name}:${" ".repeat(13-name.length)}${digest}\n`; + } + +} + +export default GenerateAllHashes; diff --git a/plugins/srktoolbox/src/core/operations/GenerateDeBruijnSequence.mjs b/plugins/srktoolbox/src/core/operations/GenerateDeBruijnSequence.mjs new file mode 100644 index 00000000..c0fa02ca --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/GenerateDeBruijnSequence.mjs @@ -0,0 +1,87 @@ +/** + * @author gchq77703 [gchq77703@gchq.gov.uk] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Generate De Bruijn Sequence operation + */ +class GenerateDeBruijnSequence extends Operation { + + /** + * GenerateDeBruijnSequence constructor + */ + constructor() { + super(); + + this.name = "生成德布鲁因序列"; + this.module = "Default"; + this.description = "根据给定的元素表和密钥长度生成滚动码。De Bruijn sequence又译德布勒蕴序列、德布莱英序列。"; + this.infoURL = "https://wikipedia.org/wiki/De_Bruijn_sequence"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "元素表长度 (k)", + type: "number", + value: 2 + }, + { + name: "密钥长度 (n)", + type: "number", + value: 3 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [k, n] = args; + + if (k < 2 || k > 9) { + throw new OperationError("无效的元素表长度,必须位于2和9之间(包括2和9)。"); + } + + if (n < 2) { + throw new OperationError("无效的密钥长度,至少为2。"); + } + + if (Math.pow(k, n) > 50000) { + throw new OperationError("太多组合数量,请减少k^n到小于50000。"); + } + + const a = new Array(k * n).fill(0); + const sequence = []; + + (function db(t = 1, p = 1) { + if (t > n) { + if (n % p !== 0) return; + for (let j = 1; j <= p; j++) { + sequence.push(a[j]); + } + return; + } + + a[t] = a[t - p]; + db(t + 1, p); + for (let j = a[t - p] + 1; j < k; j++) { + a[t] = j; + db(t + 1, t); + } + })(); + + return sequence.join(""); + } +} + +export default GenerateDeBruijnSequence; diff --git a/plugins/srktoolbox/src/core/operations/GenerateECDSAKeyPair.mjs b/plugins/srktoolbox/src/core/operations/GenerateECDSAKeyPair.mjs new file mode 100644 index 00000000..fb21fdf4 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/GenerateECDSAKeyPair.mjs @@ -0,0 +1,104 @@ +/** + * @author cplussharp + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import { cryptNotice } from "../lib/Crypt.mjs"; +import r from "jsrsasign"; + +/** + * Generate ECDSA Key Pair operation + */ +class GenerateECDSAKeyPair extends Operation { + + /** + * GenerateECDSAKeyPair constructor + */ + constructor() { + super(); + + this.name = "生成ECDSA密钥对"; + this.module = "Ciphers"; + this.description = `使用给定的曲线生成ECDSA密钥对。

${cryptNotice}`; + this.infoURL = "https://wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "椭圆曲线", + type: "option", + value: [ + "P-256", + "P-384", + "P-521" + ] + }, + { + name: "输出格式", + type: "option", + value: [ + "PEM", + "DER", + "JWK" + ] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + const [curveName, outputFormat] = args; + + return new Promise((resolve, reject) => { + let internalCurveName; + switch (curveName) { + case "P-256": + internalCurveName = "secp256r1"; + break; + case "P-384": + internalCurveName = "secp384r1"; + break; + case "P-521": + internalCurveName = "secp521r1"; + break; + } + const keyPair = r.KEYUTIL.generateKeypair("EC", internalCurveName); + + let pubKey; + let privKey; + let result; + switch (outputFormat) { + case "PEM": + pubKey = r.KEYUTIL.getPEM(keyPair.pubKeyObj).replace(/\r/g, ""); + privKey = r.KEYUTIL.getPEM(keyPair.prvKeyObj, "PKCS8PRV").replace(/\r/g, ""); + result = pubKey + "\n" + privKey; + break; + case "DER": + result = keyPair.prvKeyObj.prvKeyHex; + break; + case "JWK": + pubKey = r.KEYUTIL.getJWKFromKey(keyPair.pubKeyObj); + pubKey.key_ops = ["verify"]; // eslint-disable-line camelcase + pubKey.kid = "PublicKey"; + privKey = r.KEYUTIL.getJWKFromKey(keyPair.prvKeyObj); + privKey.key_ops = ["sign"]; // eslint-disable-line camelcase + privKey.kid = "PrivateKey"; + result = JSON.stringify({keys: [privKey, pubKey]}, null, 4); + break; + } + + resolve(result); + }); + } + +} + +export default GenerateECDSAKeyPair; diff --git a/plugins/srktoolbox/src/core/operations/GenerateHOTP.mjs b/plugins/srktoolbox/src/core/operations/GenerateHOTP.mjs new file mode 100644 index 00000000..cbc4b56f --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/GenerateHOTP.mjs @@ -0,0 +1,70 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import * as OTPAuth from "otpauth"; + +/** + * Generate HOTP operation + */ +class GenerateHOTP extends Operation { + /** + * + */ + constructor() { + super(); + + this.name = "生成HOTP"; + this.module = "Default"; + this.description = "基于HMAC的一次性密码算法(英语:HMAC-based One-time Password algorithm,HOTP)是一种基于散列消息验证码(HMAC)的一次性密码(OTP)算法,同时也是开放验证提案的基础(OATH)。HOTP在2005年由IETF发布在RFC 4226标准文档中。

在输入框输入secret,或者留空来生成一个随机值。"; + this.infoURL = "https://wikipedia.org/wiki/HMAC-based_One-time_Password_algorithm"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "名称", + "type": "string", + "value": "" + }, + { + "name": "动态码长度", + "type": "number", + "value": 6 + }, + { + "name": "计数器", + "type": "number", + "value": 0 + } + ]; + } + + /** + * + */ + run(input, args) { + const secretStr = new TextDecoder("utf-8").decode(input).trim(); + const secret = secretStr ? secretStr.toUpperCase().replace(/\s+/g, "") : ""; + + const hotp = new OTPAuth.HOTP({ + issuer: "", + label: args[0], + algorithm: "SHA1", + digits: args[1], + counter: args[2], + secret: OTPAuth.Secret.fromBase32(secret) + }); + + const uri = hotp.toString(); + const code = hotp.generate(); + + return `URI: ${uri}\n\n动态码: ${code}`; + } +} + +export default GenerateHOTP; diff --git a/plugins/srktoolbox/src/core/operations/GenerateImage.mjs b/plugins/srktoolbox/src/core/operations/GenerateImage.mjs new file mode 100644 index 00000000..82ec33bc --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/GenerateImage.mjs @@ -0,0 +1,193 @@ +/** + * @author pointhi [thomas.pointhuber@gmx.at] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { isImage } from "../lib/FileType.mjs"; +import { toBase64 } from "../lib/Base64.mjs"; +import { isWorkerEnvironment } from "../Utils.mjs"; +import { Jimp, JimpMime, ResizeStrategy, rgbaToInt } from "jimp"; + +/** + * Generate Image operation + */ +class GenerateImage extends Operation { + /** + * GenerateImage constructor + */ + constructor() { + super(); + + this.name = "生成图像"; + this.module = "Image"; + this.description = + "使用输入作为像素值生成图像。"; + this.infoURL = ""; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.presentType = "html"; + this.args = [ + { + name: "模式", + type: "option", + value: ["灰度", "RG", "RGB", "RGBA", "位"], + }, + { + name: "像素缩放倍率", + type: "number", + value: 8, + }, + { + name: "每行像素数", + type: "number", + value: 64, + }, + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + async run(input, args) { + const [mode, scale, width] = args; + input = new Uint8Array(input); + + if (scale <= 0) { + throw new OperationError("像素缩放倍率必须大于0"); + } + + if (width <= 0) { + throw new OperationError("每行像素数必须大于0"); + } + + const bytePerPixelMap = { + 灰度: 1, + RG: 2, + RGB: 3, + RGBA: 4, + 位: 1 / 8, + }; + + const bytesPerPixel = bytePerPixelMap[mode]; + + if (bytesPerPixel > 0 && input.length % bytesPerPixel !== 0) { + throw new OperationError( + `字节数不是 ${bytesPerPixel} 的倍数`, + ); + } + + const height = Math.ceil(input.length / bytesPerPixel / width); + const image = new Jimp({ width, height }); + + if (isWorkerEnvironment()) + self.sendStatusMessage("从给定数据生成图像……"); + + if (mode === "位") { + let index = 0; + for (let j = 0; j < input.length; j++) { + const curByte = Utils.bin(input[j]); + for (let k = 0; k < 8; k++, index++) { + const x = index % width; + const y = Math.floor(index / width); + + const value = curByte[k] === "0" ? 0xff : 0x00; + const pixel = rgbaToInt(value, value, value, 0xff); + image.setPixelColor(pixel, x, y); + } + } + } else { + let i = 0; + while (i < input.length) { + const index = i / bytesPerPixel; + const x = index % width; + const y = Math.floor(index / width); + + let red = 0x00; + let green = 0x00; + let blue = 0x00; + let alpha = 0xff; + + switch (mode) { + case "灰度": + red = green = blue = input[i++]; + break; + + case "RG": + red = input[i++]; + green = input[i++]; + break; + + case "RGB": + red = input[i++]; + green = input[i++]; + blue = input[i++]; + break; + + case "RGBA": + red = input[i++]; + green = input[i++]; + blue = input[i++]; + alpha = input[i++]; + break; + + default: + throw new OperationError(`不支持的模式:(${mode})`); + } + + try { + const pixel = rgbaToInt(red, green, blue, alpha); + image.setPixelColor(pixel, x, y); + } catch (err) { + throw new OperationError( + `生成图像时报错:(${err})`, + ); + } + } + } + + if (scale !== 1) { + if (isWorkerEnvironment()) + self.sendStatusMessage("缩放图像……"); + + image.scaleToFit({ + w: width * scale, + h: height * scale, + mode: ResizeStrategy.NEAREST_NEIGHBOR, + }); + } + + try { + const imageBuffer = await image.getBuffer(JimpMime.png); + return imageBuffer.buffer; + } catch (err) { + throw new OperationError(`生成图像时报错:(${err})`); + } + } + + /** + * Displays the generated image using HTML for web apps + * @param {ArrayBuffer} data + * @returns {html} + */ + present(data) { + if (!data.byteLength) return ""; + const dataArray = new Uint8Array(data); + + const type = isImage(dataArray); + if (!type) { + throw new OperationError("无效的文件类型。"); + } + + return ``; + } +} + +export default GenerateImage; diff --git a/plugins/srktoolbox/src/core/operations/GenerateLoremIpsum.mjs b/plugins/srktoolbox/src/core/operations/GenerateLoremIpsum.mjs new file mode 100644 index 00000000..b95d8731 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/GenerateLoremIpsum.mjs @@ -0,0 +1,72 @@ +/** + * @author klaxon [klaxon@veyr.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { GenerateParagraphs, GenerateSentences, GenerateWords, GenerateBytes } from "../lib/LoremIpsum.mjs"; + +/** + * Generate Lorem Ipsum operation + */ +class GenerateLoremIpsum extends Operation { + + /** + * GenerateLoremIpsum constructor + */ + constructor() { + super(); + + this.name = "生成Lorem Ipsum"; + this.module = "Default"; + this.description = "生成给定长度的lorem ipsum占位文本。"; + this.infoURL = "https://wikipedia.org/wiki/Lorem_ipsum"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "长度", + "type": "number", + "value": "3" + }, + { + "name": "长度单位", + "type": "option", + "value": ["段落", "句子", "单词", "字节"] + } + + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [length, lengthType] = args; + if (length < 1) { + throw new OperationError("长度必须大于0"); + } + switch (lengthType) { + case "段落": + return GenerateParagraphs(length); + case "句子": + return GenerateSentences(length); + case "单词": + return GenerateWords(length); + case "字节": + return GenerateBytes(length); + default: + throw new OperationError("无效的长度类型"); + + } + } + +} + +export default GenerateLoremIpsum; diff --git a/plugins/srktoolbox/src/core/operations/GeneratePGPKeyPair.mjs b/plugins/srktoolbox/src/core/operations/GeneratePGPKeyPair.mjs new file mode 100644 index 00000000..0a5d4a3f --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/GeneratePGPKeyPair.mjs @@ -0,0 +1,124 @@ +/** + * @author tlwr [toby@toby.codes] + * @author Matt C [matt@artemisbot.uk] + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import kbpgp from "kbpgp"; +import { getSubkeySize, ASP } from "../lib/PGP.mjs"; +import { cryptNotice } from "../lib/Crypt.mjs"; +import * as es6promisify from "es6-promisify"; +const promisify = es6promisify.default ? es6promisify.default.promisify : es6promisify.promisify; + + +/** + * Generate PGP Key Pair operation + */ +class GeneratePGPKeyPair extends Operation { + + /** + * GeneratePGPKeyPair constructor + */ + constructor() { + super(); + + this.name = "生成PGP密钥对"; + this.module = "PGP"; + this.description = `生成PGP公钥/私钥对。支持RSA和椭圆曲线(Eliptic Curve,EC)密钥。

${cryptNotice}`; + this.infoURL = "https://wikipedia.org/wiki/Pretty_Good_Privacy"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "密钥类型", + "type": "option", + "value": ["RSA-1024", "RSA-2048", "RSA-4096", "ECC-256", "ECC-384", "ECC-521"] + }, + { + "name": "口令(非必填)", + "type": "string", + "value": "" + }, + { + "name": "姓名(非必填)", + "type": "string", + "value": "" + }, + { + "name": "Email(非必填)", + "type": "string", + "value": "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + let [keyType, keySize] = args[0].split("-"); + const password = args[1], + name = args[2], + email = args[3]; + let userIdentifier = ""; + + keyType = keyType.toLowerCase(); + keySize = parseInt(keySize, 10); + + if (name) userIdentifier += name; + if (email) userIdentifier += ` <${email}>`; + + let flags = kbpgp.const.openpgp.certify_keys; + flags |= kbpgp.const.openpgp.sign_data; + flags |= kbpgp.const.openpgp.auth; + flags |= kbpgp.const.openpgp.encrypt_comm; + flags |= kbpgp.const.openpgp.encrypt_storage; + + const keyGenerationOptions = { + userid: userIdentifier, + ecc: keyType === "ecc", + primary: { + "nbits": keySize, + "flags": flags, + "expire_in": 0 + }, + subkeys: [{ + "nbits": getSubkeySize(keySize), + "flags": kbpgp.const.openpgp.sign_data, + "expire_in": 86400 * 365 * 8 + }, { + "nbits": getSubkeySize(keySize), + "flags": kbpgp.const.openpgp.encrypt_comm | kbpgp.const.openpgp.encrypt_storage, + "expire_in": 86400 * 365 * 2 + }], + asp: ASP + }; + + return new Promise(async (resolve, reject) => { + try { + const unsignedKey = await promisify(kbpgp.KeyManager.generate)(keyGenerationOptions); + await promisify(unsignedKey.sign.bind(unsignedKey))({}); + + const signedKey = unsignedKey, + privateKeyExportOptions = {}; + + if (password) privateKeyExportOptions.passphrase = password; + const privateKey = await promisify(signedKey.export_pgp_private.bind(signedKey))(privateKeyExportOptions); + const publicKey = await promisify(signedKey.export_pgp_public.bind(signedKey))({}); + resolve(privateKey + "\n" + publicKey.trim()); + } catch (err) { + reject(`生成公私钥对出错: ${err}`); + } + }); + } + +} + +export default GeneratePGPKeyPair; diff --git a/plugins/srktoolbox/src/core/operations/GenerateQRCode.mjs b/plugins/srktoolbox/src/core/operations/GenerateQRCode.mjs new file mode 100644 index 00000000..62809da6 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/GenerateQRCode.mjs @@ -0,0 +1,96 @@ +/** + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { generateQrCode } from "../lib/QRCode.mjs"; +import { toBase64 } from "../lib/Base64.mjs"; +import { isImage } from "../lib/FileType.mjs"; +import Utils from "../Utils.mjs"; + +/** + * Generate QR Code operation + */ +class GenerateQRCode extends Operation { + + /** + * GenerateQRCode constructor + */ + constructor() { + super(); + + this.name = "生成二维码"; + this.module = "Image"; + this.description = "从输入生成一个Quick Response (QR)二维码。

QR码/图码(英语:Quick Response Code;全称为快速响应矩阵图码)是二维码的一种,于1994年由日本汽车零组件大厂电装公司的原昌宏所发明。条形码或称条码(英语:barcode),是将宽度不等的多个黑条和空白,按照一定的编码规则排列,用以表达一组信息的图形标识符。"; + this.infoURL = "https://wikipedia.org/wiki/QR_code"; + this.inputType = "string"; + this.outputType = "ArrayBuffer"; + this.presentType = "html"; + this.args = [ + { + "name": "图像格式", + "type": "option", + "value": ["PNG", "SVG", "EPS", "PDF"] + }, + { + "name": "码点大小 (px)", + "type": "number", + "value": 5, + "min": 1 + }, + { + "name": "留白 (码点数)", + "type": "number", + "value": 4, + "min": 0 + }, + { + "name": "错误检测", + "type": "option", + "value": ["Low", "Medium", "Quartile", "High"], + "defaultIndex": 1 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + const [format, size, margin, errorCorrection] = args; + + return generateQrCode(input, format, size, margin, errorCorrection); + } + + /** + * Displays the QR image using HTML for web apps + * + * @param {ArrayBuffer} data + * @returns {html} + */ + present(data, args) { + if (!data.byteLength && !data.length) return ""; + const dataArray = new Uint8Array(data), + [format] = args; + if (format === "PNG") { + const type = isImage(dataArray); + if (!type) { + throw new OperationError("无效的文件类型。"); + } + + return ``; + } + + return Utils.arrayBufferToStr(data); + } + +} + +export default GenerateQRCode; diff --git a/plugins/srktoolbox/src/core/operations/GenerateRSAKeyPair.mjs b/plugins/srktoolbox/src/core/operations/GenerateRSAKeyPair.mjs new file mode 100644 index 00000000..89358d84 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/GenerateRSAKeyPair.mjs @@ -0,0 +1,90 @@ +/** + * @author Matt C [me@mitt.dev] + * @author gchq77703 [] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import forge from "node-forge"; +import { cryptNotice } from "../lib/Crypt.mjs"; + +/** + * Generate RSA Key Pair operation + */ +class GenerateRSAKeyPair extends Operation { + + /** + * GenerateRSAKeyPair constructor + */ + constructor() { + super(); + + this.name = "生成RSA密钥对"; + this.module = "Ciphers"; + this.description = `生成给定长度的RSA密钥对。

${cryptNotice}`; + this.infoURL = "https://wikipedia.org/wiki/RSA_(cryptosystem)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "RSA密钥长度", + type: "option", + value: [ + "1024", + "2048", + "4096" + ] + }, + { + name: "输出格式", + type: "option", + value: [ + "PEM", + "JSON", + "DER" + ] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + const [keyLength, outputFormat] = args; + + return new Promise((resolve, reject) => { + forge.pki.rsa.generateKeyPair({ + bits: Number(keyLength), + workers: -1, + workerScript: "assets/forge/prime.worker.min.js" + }, (err, keypair) => { + if (err) return reject(err); + + let result; + + switch (outputFormat) { + case "PEM": + result = forge.pki.publicKeyToPem(keypair.publicKey) + "\n" + forge.pki.privateKeyToPem(keypair.privateKey); + break; + case "JSON": + result = JSON.stringify(keypair); + break; + case "DER": + result = forge.asn1.toDer(forge.pki.privateKeyToAsn1(keypair.privateKey)).getBytes(); + break; + } + + resolve(result); + }); + }); + } + +} + +export default GenerateRSAKeyPair; diff --git a/plugins/srktoolbox/src/core/operations/GenerateTOTP.mjs b/plugins/srktoolbox/src/core/operations/GenerateTOTP.mjs new file mode 100644 index 00000000..4c9a92b0 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/GenerateTOTP.mjs @@ -0,0 +1,75 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import * as OTPAuth from "otpauth"; + +/** + * Generate TOTP operation + */ +class GenerateTOTP extends Operation { + /** + * + */ + constructor() { + super(); + this.name = "生成TOTP"; + this.module = "Default"; + this.description = "基于时间的一次性密码算法(英语:Time-based One-Time Password,简称:TOTP)是一种根据预共享的密钥与当前时间计算一次性密码的算法。它已被互联网工程任务组接纳为RFC 6238标准,成为主动开放认证(英语:Initiative For Open Authentication)(OATH)的基石,并被用于众多多重要素验证系统当中。TOTP是散列消息认证码(HMAC)当中的一个例子。它结合一个私钥与当前时间戳,使用一个密码散列函数来生成一次性密码。

在输入框输入secret,或者留空来生成一个随机值。T0和T1的单位是秒。"; + this.infoURL = "https://wikipedia.org/wiki/Time-based_One-time_Password_algorithm"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "名称", + "type": "string", + "value": "" + }, + { + "name": "动态码长度", + "type": "number", + "value": 6 + }, + { + "name": "纪元偏移(Epoch offset, T0)", + "type": "number", + "value": 0 + }, + { + "name": "计算时间间隔(T1)", + "type": "number", + "value": 30 + } + ]; + } + + /** + * + */ + run(input, args) { + const secretStr = new TextDecoder("utf-8").decode(input).trim(); + const secret = secretStr ? secretStr.toUpperCase().replace(/\s+/g, "") : ""; + + const totp = new OTPAuth.TOTP({ + issuer: "", + label: args[0], + algorithm: "SHA1", + digits: args[1], + period: args[3], + epoch: args[2] * 1000, // Convert seconds to milliseconds + secret: OTPAuth.Secret.fromBase32(secret) + }); + + const uri = totp.toString(); + const code = totp.generate(); + + return `URI: ${uri}\n\nPassword: ${code}`; + } +} + +export default GenerateTOTP; diff --git a/plugins/srktoolbox/src/core/operations/GenerateUUID.mjs b/plugins/srktoolbox/src/core/operations/GenerateUUID.mjs new file mode 100644 index 00000000..d6193f3a --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/GenerateUUID.mjs @@ -0,0 +1,80 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import * as uuid from "uuid"; +import OperationError from "../errors/OperationError.mjs"; +/** + * Generate UUID operation + */ +class GenerateUUID extends Operation { + + /** + * GenerateUUID constructor + */ + constructor() { + super(); + + this.name = "生成UUID"; + this.module = "Crypto"; + this.description = + "Generates an RFC 9562 (formerly RFC 4122) compliant Universally Unique Identifier (UUID), " + + "also known as a Globally Unique Identifier (GUID).
" + + "
" + + "We currently support generating the following UUID versions:
" + + "
    " + + "
  • v1: Timestamp-based
  • " + + "
  • v3: Namespace w/ MD5
  • " + + "
  • v4: Random (default)
  • " + + "
  • v5: Namespace w/ SHA-1
  • " + + "
  • v6: Timestamp, reordered
  • " + + "
  • v7: Unix Epoch time-based
  • " + + "
" + + "UUIDs are generated using the
uuid package.
"; + this.infoURL = "https://wikipedia.org/wiki/Universally_unique_identifier"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Version", + hint: "UUID version", + type: "option", + value: ["v1", "v3", "v4", "v5", "v6", "v7"], + defaultIndex: 2, + }, + { + name: "Namespace", + hint: "UUID namespace (UUID; valid for v3 and v5)", + type: "string", + value: "1b671a64-40d5-491e-99b0-da01ff1f3341" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [version, namespace] = args; + const hasDesiredVersion = typeof uuid[version] === "function"; + if (!hasDesiredVersion) throw new OperationError("Invalid UUID version"); + + const requiresNamespace = ["v3", "v5"].includes(version); + if (!requiresNamespace) return uuid[version](); + + const hasValidNamespace = typeof namespace === "string" && uuid.validate(namespace); + if (!hasValidNamespace) throw new OperationError("Invalid UUID namespace"); + + return uuid[version](input, namespace); + } + +} + +export default GenerateUUID; diff --git a/plugins/srktoolbox/src/core/operations/GenericCodeBeautify.mjs b/plugins/srktoolbox/src/core/operations/GenericCodeBeautify.mjs new file mode 100644 index 00000000..81ef93ea --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/GenericCodeBeautify.mjs @@ -0,0 +1,163 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; + +/** + * Generic Code Beautify operation + */ +class GenericCodeBeautify extends Operation { + + /** + * GenericCodeBeautify constructor + */ + constructor() { + super(); + + this.name = "通用代码美化"; + this.module = "Code"; + this.description = "尝试美化C风格语言,例如C, C++, C#, Java, PHP, JavaScript等等。

此操作并不保证美化后的代码一定可用,只是为了提高混淆或丑化后的代码可读性。

无法正常美化的内容:
  • For循环格式化
  • Do-While循环格式化
  • Switch/Case缩进
  • 特定的位移运算符
"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const preservedTokens = []; + let code = input, + t = 0, + m; + + // Remove strings + const sstrings = /'([^'\\]|\\.)*'/g; + while ((m = sstrings.exec(code))) { + code = preserveToken(code, m, t++); + sstrings.lastIndex = m.index; + } + + const dstrings = /"([^"\\]|\\.)*"/g; + while ((m = dstrings.exec(code))) { + code = preserveToken(code, m, t++); + dstrings.lastIndex = m.index; + } + + // Remove comments + const scomments = /\/\/[^\n\r]*/g; + while ((m = scomments.exec(code))) { + code = preserveToken(code, m, t++); + scomments.lastIndex = m.index; + } + + const mcomments = /\/\*[\s\S]*?\*\//gm; + while ((m = mcomments.exec(code))) { + code = preserveToken(code, m, t++); + mcomments.lastIndex = m.index; + } + + const hcomments = /(^|\n)#[^\n\r#]+/g; + while ((m = hcomments.exec(code))) { + code = preserveToken(code, m, t++); + hcomments.lastIndex = m.index; + } + + // Remove regexes + const regexes = /\/.*?[^\\]\/[gim]{0,3}/gi; + while ((m = regexes.exec(code))) { + code = preserveToken(code, m, t++); + regexes.lastIndex = m.index; + } + + code = code + // Create newlines after ; + .replace(/;/g, ";\n") + // Create newlines after { and around } + .replace(/{/g, "{\n") + .replace(/}/g, "\n}\n") + // Remove carriage returns + .replace(/\r/g, "") + // Remove all indentation + .replace(/^\s+/g, "") + .replace(/\n\s+/g, "\n") + // Remove trailing spaces + .replace(/\s*$/g, "") + .replace(/\n{/g, "{"); + + // Indent + let i = 0, + level = 0, + indent; + while (i < code.length) { + switch (code[i]) { + case "{": + level++; + break; + case "\n": + if (i+1 >= code.length) break; + + if (code[i+1] === "}") level--; + indent = (level >= 0) ? Array(level*4+1).join(" ") : ""; + + code = code.substring(0, i+1) + indent + code.substring(i+1); + if (level > 0) i += level*4; + break; + } + i++; + } + + code = code + // Add strategic spaces + .replace(/\s*([!<>=+-/*]?)=\s*/g, " $1= ") + .replace(/\s*<([=]?)\s*/g, " <$1 ") + .replace(/\s*>([=]?)\s*/g, " >$1 ") + .replace(/([^+])\+([^+=])/g, "$1 + $2") + .replace(/([^-])-([^-=])/g, "$1 - $2") + .replace(/([^*])\*([^*=])/g, "$1 * $2") + .replace(/([^/])\/([^/=])/g, "$1 / $2") + .replace(/\s*,\s*/g, ", ") + .replace(/\s*{/g, " {") + .replace(/}\n/g, "}\n\n") + // Hacky horribleness + .replace(/(if|for|while|with|elif|elseif)\s*\(([^\n]*)\)\s*\n([^{])/gim, "$1 ($2)\n $3") + .replace(/(if|for|while|with|elif|elseif)\s*\(([^\n]*)\)([^{])/gim, "$1 ($2) $3") + .replace(/else\s*\n([^{])/gim, "else\n $1") + .replace(/else\s+([^{])/gim, "else $1") + // Remove strategic spaces + .replace(/\s+;/g, ";") + .replace(/\{\s+\}/g, "{}") + .replace(/\[\s+\]/g, "[]") + .replace(/}\s*(else|catch|except|finally|elif|elseif|else if)/gi, "} $1"); + + // Replace preserved tokens + const ptokens = /###preservedToken(\d+)###/g; + while ((m = ptokens.exec(code))) { + const ti = parseInt(m[1], 10); + code = code.substring(0, m.index) + preservedTokens[ti] + code.substring(m.index + m[0].length); + ptokens.lastIndex = m.index; + } + + return code; + + /** + * Replaces a matched token with a placeholder value. + */ + function preserveToken(str, match, t) { + preservedTokens[t] = match[0]; + return str.substring(0, match.index) + + "###preservedToken" + t + "###" + + str.substring(match.index + match[0].length); + } + } + +} + +export default GenericCodeBeautify; diff --git a/plugins/srktoolbox/src/core/operations/GetAllCasings.mjs b/plugins/srktoolbox/src/core/operations/GetAllCasings.mjs new file mode 100644 index 00000000..ab1450e7 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/GetAllCasings.mjs @@ -0,0 +1,55 @@ +/** + * @author n1073645 [n1073645@gmail.com] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; + +/** + * Permutate String operation + */ +class GetAllCasings extends Operation { + + /** + * GetAllCasings constructor + */ + constructor() { + super(); + + this.name = "大小写穷举"; + this.module = "Default"; + this.description = "输出一个字符串所有的大小写形式。

警告:显而易见是指数递增,输入字符串过长后果自负。"; + this.infoURL = ""; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const length = input.length; + const max = 1 << length; + input = input.toLowerCase(); + let result = ""; + + for (let i = 0; i < max; i++) { + const temp = input.split(""); + for (let j = 0; j < length; j++) { + if (((i >> j) & 1) === 1) { + temp[j] = temp[j].toUpperCase(); + } + } + result += temp.join("") + "\n"; + } + return result.slice(0, -1); + } +} + +export default GetAllCasings; diff --git a/plugins/srktoolbox/src/core/operations/GetTime.mjs b/plugins/srktoolbox/src/core/operations/GetTime.mjs new file mode 100644 index 00000000..3ddb0995 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/GetTime.mjs @@ -0,0 +1,65 @@ +/** + * @author n1073645 [n1073645@gmail.com] + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import {UNITS} from "../lib/DateTime.mjs"; + +/** + * Get Time operation + */ +class GetTime extends Operation { + + /** + * GetTime constructor + */ + constructor() { + super(); + + this.name = "获取当前时间"; + this.module = "Default"; + this.description = "获取当前时间的UNIX时间戳 (从1970-01-01 00:00:00 UTC开始的秒数)。使用W3C High Resolution Time API。"; + this.infoURL = "https://wikipedia.org/wiki/Unix_time"; + this.inputType = "string"; + this.outputType = "number"; + this.args = [ + { + name: "粒度", + type: "option", + value: UNITS + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {number} + */ + run(input, args) { + const nowMs = (performance.timeOrigin + performance.now()), + granularity = args[0]; + + switch (granularity) { + case "纳秒 (ns)": + return Math.round(nowMs * 1000 * 1000); + case "微秒 (μs)": + return Math.round(nowMs * 1000); + case "毫秒 (ms)": + return Math.round(nowMs); + case "秒 (s)": + return Math.round(nowMs / 1000); + default: + throw new OperationError("无效的单位: " + granularity); + } + } + +} + +export default GetTime; diff --git a/plugins/srktoolbox/src/core/operations/GroupIPAddresses.mjs b/plugins/srktoolbox/src/core/operations/GroupIPAddresses.mjs new file mode 100644 index 00000000..0b96852d --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/GroupIPAddresses.mjs @@ -0,0 +1,139 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import {IP_DELIM_OPTIONS} from "../lib/Delim.mjs"; +import {ipv6ToStr, genIpv6Mask, IPV4_REGEX, strToIpv6, ipv4ToStr, IPV6_REGEX, strToIpv4} from "../lib/IP.mjs"; + +/** + * Group IP addresses operation + */ +class GroupIPAddresses extends Operation { + + /** + * GroupIPAddresses constructor + */ + constructor() { + super(); + + this.name = "IP地址分组"; + this.module = "Default"; + this.description = "把IP地址分组汇总列出到对应的子网范围下。支持IPv4和IPv6。"; + this.infoURL = "https://wikipedia.org/wiki/Subnetwork"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "分隔符", + "type": "option", + "value": IP_DELIM_OPTIONS + }, + { + "name": "子网 (CIDR)", + "type": "number", + "value": 24 + }, + { + "name": "只显示子网", + "type": "boolean", + "value": false, + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const delim = Utils.charRep(args[0]), + cidr = args[1], + onlySubnets = args[2], + ipv4Mask = cidr < 32 ? ~(0xFFFFFFFF >>> cidr) : 0xFFFFFFFF, + ipv6Mask = genIpv6Mask(cidr), + ips = input.split(delim), + ipv4Networks = {}, + ipv6Networks = {}; + let match = null, + output = "", + ip = null, + network = null, + networkStr = "", + i; + + if (cidr < 0 || cidr > 127) { + throw new OperationError("CIDR必须小于32(IPv4)或者128(IPv6)"); + } + + // Parse all IPs and add to network dictionary + for (i = 0; i < ips.length; i++) { + if ((match = IPV4_REGEX.exec(ips[i]))) { + ip = strToIpv4(match[1]) >>> 0; + network = ip & ipv4Mask; + + if (network in ipv4Networks) { + ipv4Networks[network].push(ip); + } else { + ipv4Networks[network] = [ip]; + } + } else if ((match = IPV6_REGEX.exec(ips[i]))) { + ip = strToIpv6(match[1]); + network = []; + networkStr = ""; + + for (let j = 0; j < 8; j++) { + network.push(ip[j] & ipv6Mask[j]); + } + + networkStr = ipv6ToStr(network, true); + + if (networkStr in ipv6Networks) { + ipv6Networks[networkStr].push(ip); + } else { + ipv6Networks[networkStr] = [ip]; + } + } + } + + // Sort IPv4 network dictionaries and print + for (network in ipv4Networks) { + ipv4Networks[network] = ipv4Networks[network].sort(); + + output += ipv4ToStr(network) + "/" + cidr + "\n"; + + if (!onlySubnets) { + for (i = 0; i < ipv4Networks[network].length; i++) { + output += " " + ipv4ToStr(ipv4Networks[network][i]) + "\n"; + } + output += "\n"; + } + } + + // Sort IPv6 network dictionaries and print + for (networkStr in ipv6Networks) { + // ipv6Networks[networkStr] = ipv6Networks[networkStr].sort(); TODO + + output += networkStr + "/" + cidr + "\n"; + + if (!onlySubnets) { + for (i = 0; i < ipv6Networks[networkStr].length; i++) { + output += " " + ipv6ToStr(ipv6Networks[networkStr][i], true) + "\n"; + } + output += "\n"; + } + } + + return output; + } + +} + +export default GroupIPAddresses; diff --git a/plugins/srktoolbox/src/core/operations/Gunzip.mjs b/plugins/srktoolbox/src/core/operations/Gunzip.mjs new file mode 100644 index 00000000..1777ee61 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Gunzip.mjs @@ -0,0 +1,53 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import gunzip from "zlibjs/bin/gunzip.min.js"; + +const Zlib = gunzip.Zlib; + +/** + * Gunzip operation + */ +class Gunzip extends Operation { + + /** + * Gunzip constructor + */ + constructor() { + super(); + + this.name = "Gunzip"; + this.module = "Compression"; + this.description = "解压使用带有Gzip标头的deflate算法压缩的数据。"; + this.infoURL = "https://wikipedia.org/wiki/Gzip"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = []; + this.checks = [ + { + pattern: "^\\x1f\\x8b\\x08", + flags: "", + args: [] + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {File} + */ + run(input, args) { + const gzipObj = new Zlib.Gunzip(new Uint8Array(input)); + return new Uint8Array(gzipObj.decompress()).buffer; + } + +} + +export default Gunzip; diff --git a/plugins/srktoolbox/src/core/operations/Gzip.mjs b/plugins/srktoolbox/src/core/operations/Gzip.mjs new file mode 100644 index 00000000..a1121cbb --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Gzip.mjs @@ -0,0 +1,91 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import {COMPRESSION_TYPE, ZLIB_COMPRESSION_TYPE_LOOKUP} from "../lib/Zlib.mjs"; +import gzip from "zlibjs/bin/gzip.min.js"; + +const Zlib = gzip.Zlib; + +/** + * Gzip operation + */ +class Gzip extends Operation { + + /** + * Gzip constructor + */ + constructor() { + super(); + + this.name = "Gzip"; + this.module = "Compression"; + this.description = "使用带有Gzip标头的deflate算法压缩数据。"; + this.infoURL = "https://wikipedia.org/wiki/Gzip"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = [ + { + name: "压缩类型", + type: "option", + value: COMPRESSION_TYPE + }, + { + name: "文件名 (可选)", + type: "string", + value: "" + }, + { + name: "注释 (可选)", + type: "string", + value: "" + }, + { + name: "包括文件校验和", + type: "boolean", + value: false + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + const filename = args[1], + comment = args[2], + options = { + deflateOptions: { + compressionType: ZLIB_COMPRESSION_TYPE_LOOKUP[args[0]] + }, + flags: { + fhcrc: args[3] + } + }; + + if (filename.length) { + options.flags.fname = true; + options.filename = filename; + } + if (comment.length) { + options.flags.comment = true; + options.comment = comment; + } + const gzipObj = new Zlib.Gzip(new Uint8Array(input), options); + const compressed = new Uint8Array(gzipObj.compress()); + if (options.flags.comment && !(compressed[3] & 0x10)) { + compressed[3] |= 0x10; + } + return compressed.buffer; + } + +} + +export default Gzip; diff --git a/plugins/srktoolbox/src/core/operations/HAS160.mjs b/plugins/srktoolbox/src/core/operations/HAS160.mjs new file mode 100644 index 00000000..29499b3c --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/HAS160.mjs @@ -0,0 +1,51 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import {runHash} from "../lib/Hash.mjs"; + +/** + * HAS-160 operation + */ +class HAS160 extends Operation { + + /** + * HAS-160 constructor + */ + constructor() { + super(); + + this.name = "HAS-160"; + this.module = "Crypto"; + this.description = "HAS-160是设计用于韩国KCDSA数据签名的哈希算法。它是SHA-1的变种,并进行了提高安全性的修改。此算法产生160位长度的输出。

HAS-160和SHA-1计算方法相同。首先把输入分割成512位长度的块,并补齐最后一块。摘要函数按顺序处理块并不断更新哈希值。

此算法默认进行80轮运算。"; + this.infoURL = "https://wikipedia.org/wiki/HAS-160"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + name: "轮数", + type: "number", + value: 80, + min: 1, + max: 80 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return runHash("has160", input, {rounds: args[0]}); + } + +} + +export default HAS160; diff --git a/plugins/srktoolbox/src/core/operations/HASSHClientFingerprint.mjs b/plugins/srktoolbox/src/core/operations/HASSHClientFingerprint.mjs new file mode 100644 index 00000000..a3240b47 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/HASSHClientFingerprint.mjs @@ -0,0 +1,168 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + * + * HASSH created by Salesforce + * Ben Reardon (@benreardon) + * Adel Karimi (@0x4d31) + * and the JA3 crew: + * John B. Althouse + * Jeff Atkinson + * Josh Atkins + * + * Algorithm released under the BSD-3-clause licence + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import Stream from "../lib/Stream.mjs"; +import {runHash} from "../lib/Hash.mjs"; + +/** + * HASSH Client Fingerprint operation + */ +class HASSHClientFingerprint extends Operation { + + /** + * HASSHClientFingerprint constructor + */ + constructor() { + super(); + + this.name = "HASSH客户端指纹"; + this.module = "Crypto"; + this.description = "使用SSH的Client Key Exchange Init消息内容进行哈希生成HASSH指纹,用于辨识SSH客户端。

输入:由客户端发送至服务器端的应用层SSH_MSG_KEXINIT包十六进制流。"; + this.infoURL = "https://engineering.salesforce.com/open-sourcing-hassh-abed3ae5044c"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "输入格式", + type: "option", + value: ["十六进制", "Base64", "原始"] + }, + { + name: "输出格式", + type: "option", + value: ["哈希摘要", "HASSH算法字符串", "详细信息"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [inputFormat, outputFormat] = args; + + input = Utils.convertToByteArray(input, inputFormat); + const s = new Stream(new Uint8Array(input)); + + // Length + const length = s.readInt(4); + if (s.length !== length + 4) + throw new OperationError("错误的包长度。"); + + // Padding length + const paddingLength = s.readInt(1); + + // Message code + const messageCode = s.readInt(1); + if (messageCode !== 20) + throw new OperationError("不是Key Exchange Init。"); + + // Cookie + s.moveForwardsBy(16); + + // KEX Algorithms + const kexAlgosLength = s.readInt(4); + const kexAlgos = s.readString(kexAlgosLength); + + // Server Host Key Algorithms + const serverHostKeyAlgosLength = s.readInt(4); + s.moveForwardsBy(serverHostKeyAlgosLength); + + // Encryption Algorithms Client to Server + const encAlgosC2SLength = s.readInt(4); + const encAlgosC2S = s.readString(encAlgosC2SLength); + + // Encryption Algorithms Server to Client + const encAlgosS2CLength = s.readInt(4); + s.moveForwardsBy(encAlgosS2CLength); + + // MAC Algorithms Client to Server + const macAlgosC2SLength = s.readInt(4); + const macAlgosC2S = s.readString(macAlgosC2SLength); + + // MAC Algorithms Server to Client + const macAlgosS2CLength = s.readInt(4); + s.moveForwardsBy(macAlgosS2CLength); + + // Compression Algorithms Client to Server + const compAlgosC2SLength = s.readInt(4); + const compAlgosC2S = s.readString(compAlgosC2SLength); + + // Compression Algorithms Server to Client + const compAlgosS2CLength = s.readInt(4); + s.moveForwardsBy(compAlgosS2CLength); + + // Languages Client to Server + const langsC2SLength = s.readInt(4); + s.moveForwardsBy(langsC2SLength); + + // Languages Server to Client + const langsS2CLength = s.readInt(4); + s.moveForwardsBy(langsS2CLength); + + // First KEX packet follows + s.moveForwardsBy(1); + + // Reserved + s.moveForwardsBy(4); + + // Padding string + s.moveForwardsBy(paddingLength); + + // Output + const hassh = [ + kexAlgos, + encAlgosC2S, + macAlgosC2S, + compAlgosC2S + ]; + const hasshStr = hassh.join(";"); + const hasshHash = runHash("md5", Utils.strToArrayBuffer(hasshStr)); + + switch (outputFormat) { + case "HASSH算法字符串": + return hasshStr; + case "详细信息": + return `哈希摘要: +${hasshHash} + +完整HASSH算法字符串: +${hasshStr} + +密钥交换算法: +${kexAlgos} +客户端到服务器端加密算法: +${encAlgosC2S} +客户端到服务器端MAC算法: +${macAlgosC2S} +客户端到服务器端压缩算法: +${compAlgosC2S}`; + case "哈希摘要": + default: + return hasshHash; + } + } + +} + +export default HASSHClientFingerprint; diff --git a/plugins/srktoolbox/src/core/operations/HASSHServerFingerprint.mjs b/plugins/srktoolbox/src/core/operations/HASSHServerFingerprint.mjs new file mode 100644 index 00000000..9e4d3552 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/HASSHServerFingerprint.mjs @@ -0,0 +1,168 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + * + * HASSH created by Salesforce + * Ben Reardon (@benreardon) + * Adel Karimi (@0x4d31) + * and the JA3 crew: + * John B. Althouse + * Jeff Atkinson + * Josh Atkins + * + * Algorithm released under the BSD-3-clause licence + * + * Modified by Raka-loah@github for zh-CN i18n +*/ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import Stream from "../lib/Stream.mjs"; +import {runHash} from "../lib/Hash.mjs"; + +/** + * HASSH Server Fingerprint operation + */ +class HASSHServerFingerprint extends Operation { + + /** + * HASSHServerFingerprint constructor + */ + constructor() { + super(); + + this.name = "HASSH服务器指纹"; + this.module = "Crypto"; + this.description = "使用SSH的Server Key Exchange Init消息内容进行哈希生成HASSH指纹,用于辨识SSH服务器端。

输入:由服务器端发送至客户端的应用层SSH_MSG_KEXINIT包十六进制流。"; + this.infoURL = "https://engineering.salesforce.com/open-sourcing-hassh-abed3ae5044c"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "输入格式", + type: "option", + value: ["十六进制", "Base64", "原始"] + }, + { + name: "输出格式", + type: "option", + value: ["哈希摘要", "HASSH算法字符串", "详细信息"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [inputFormat, outputFormat] = args; + + input = Utils.convertToByteArray(input, inputFormat); + const s = new Stream(new Uint8Array(input)); + + // Length + const length = s.readInt(4); + if (s.length !== length + 4) + throw new OperationError("错误的包长度。"); + + // Padding length + const paddingLength = s.readInt(1); + + // Message code + const messageCode = s.readInt(1); + if (messageCode !== 20) + throw new OperationError("不是Key Exchange Init。"); + + // Cookie + s.moveForwardsBy(16); + + // KEX Algorithms + const kexAlgosLength = s.readInt(4); + const kexAlgos = s.readString(kexAlgosLength); + + // Server Host Key Algorithms + const serverHostKeyAlgosLength = s.readInt(4); + s.moveForwardsBy(serverHostKeyAlgosLength); + + // Encryption Algorithms Client to Server + const encAlgosC2SLength = s.readInt(4); + s.moveForwardsBy(encAlgosC2SLength); + + // Encryption Algorithms Server to Client + const encAlgosS2CLength = s.readInt(4); + const encAlgosS2C = s.readString(encAlgosS2CLength); + + // MAC Algorithms Client to Server + const macAlgosC2SLength = s.readInt(4); + s.moveForwardsBy(macAlgosC2SLength); + + // MAC Algorithms Server to Client + const macAlgosS2CLength = s.readInt(4); + const macAlgosS2C = s.readString(macAlgosS2CLength); + + // Compression Algorithms Client to Server + const compAlgosC2SLength = s.readInt(4); + s.moveForwardsBy(compAlgosC2SLength); + + // Compression Algorithms Server to Client + const compAlgosS2CLength = s.readInt(4); + const compAlgosS2C = s.readString(compAlgosS2CLength); + + // Languages Client to Server + const langsC2SLength = s.readInt(4); + s.moveForwardsBy(langsC2SLength); + + // Languages Server to Client + const langsS2CLength = s.readInt(4); + s.moveForwardsBy(langsS2CLength); + + // First KEX packet follows + s.moveForwardsBy(1); + + // Reserved + s.moveForwardsBy(4); + + // Padding string + s.moveForwardsBy(paddingLength); + + // Output + const hassh = [ + kexAlgos, + encAlgosS2C, + macAlgosS2C, + compAlgosS2C + ]; + const hasshStr = hassh.join(";"); + const hasshHash = runHash("md5", Utils.strToArrayBuffer(hasshStr)); + + switch (outputFormat) { + case "HASSH算法字符串": + return hasshStr; + case "详细信息": + return `哈希摘要: +${hasshHash} + +完整HASSH算法字符串: +${hasshStr} + +密钥交换算法: +${kexAlgos} +服务器端到客户端加密算法: +${encAlgosS2C} +服务器端到客户端MAC算法: +${macAlgosS2C} +服务器端到客户端压缩算法: +${compAlgosS2C}`; + case "哈希摘要": + default: + return hasshHash; + } + } + +} + +export default HASSHServerFingerprint; diff --git a/plugins/srktoolbox/src/core/operations/HMAC.mjs b/plugins/srktoolbox/src/core/operations/HMAC.mjs new file mode 100644 index 00000000..2b43049c --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/HMAC.mjs @@ -0,0 +1,84 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import CryptoApi from "crypto-api/src/crypto-api.mjs"; + +/** + * HMAC operation + */ +class HMAC extends Operation { + + /** + * HMAC constructor + */ + constructor() { + super(); + + this.name = "HMAC"; + this.module = "Crypto"; + this.description = "HMAC (有时扩展为 英语:keyed-hash message authentication code, 密钥散列消息认证码, 或 英语:hash-based message authentication code,散列消息认证码),是一种通过特别计算方式之后产生的消息认证码(MAC),使用密码散列函数,同时结合一个加密密钥。它可以用来保证资料的完整性,同时可以用来作某个消息的身份验证。"; + this.infoURL = "https://wikipedia.org/wiki/HMAC"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["十六进制", "十进制", "Base64", "UTF8", "Latin1"] + }, + { + "name": "哈希算法", + "type": "option", + "value": [ + "MD2", + "MD4", + "MD5", + "SHA0", + "SHA1", + "SHA224", + "SHA256", + "SHA384", + "SHA512", + "SHA512/224", + "SHA512/256", + "RIPEMD128", + "RIPEMD160", + "RIPEMD256", + "RIPEMD320", + "HAS160", + "Whirlpool", + "Whirlpool-0", + "Whirlpool-T", + "Snefru" + ] + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = Utils.convertToByteString(args[0].string || "", args[0].option), + hashFunc = args[1].toLowerCase(), + msg = Utils.arrayBufferToStr(input, false), + hasher = CryptoApi.getHasher(hashFunc); + + const mac = CryptoApi.getHmac(key, hasher); + mac.update(msg); + return CryptoApi.encoder.toHex(mac.finalize()); + } + +} + +export default HMAC; diff --git a/plugins/srktoolbox/src/core/operations/HTMLToText.mjs b/plugins/srktoolbox/src/core/operations/HTMLToText.mjs new file mode 100644 index 00000000..7becab4f --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/HTMLToText.mjs @@ -0,0 +1,43 @@ +/** + * @author tlwr [toby@toby.codes] + * @author Matt C [me@mitt.dev] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; + +/** + * HTML To Text operation + */ +class HTMLToText extends Operation { + + /** + * HTMLToText constructor + */ + constructor() { + super(); + + this.name = "HTML转文本"; + this.module = "Default"; + this.description = "将某个操作输出的HTML转换为纯文本形式,不在输出框进行渲染。"; + this.infoURL = ""; + this.inputType = "html"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {html} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return input; + } + +} + +export default HTMLToText; diff --git a/plugins/srktoolbox/src/core/operations/HTTPRequest.mjs b/plugins/srktoolbox/src/core/operations/HTTPRequest.mjs new file mode 100644 index 00000000..e3b9ac1e --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/HTTPRequest.mjs @@ -0,0 +1,149 @@ +/** + * @author tlwr [toby@toby.codes] + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * HTTP request operation + */ +class HTTPRequest extends Operation { + + /** + * HTTPRequest constructor + */ + constructor() { + super(); + + this.name = "HTTP请求"; + this.module = "Default"; + this.description = [ + "发起HTTP请求并接收响应内容。", + "

", + "此操作支持不同的HTTP方法,例如GET、POST、PUT等等。", + "

", + "你可以用 Key: Value 的格式按行添加请求头(Header)。", + "

", + "响应的状态码和一部分响应头可通过勾选“显示响应元数据”查看。由于浏览器安全设置,只能显示一部分响应头。", + ].join("\n"); + this.infoURL = "https://wikipedia.org/wiki/List_of_HTTP_header_fields#Request_fields"; + this.inputType = "string"; + this.outputType = "string"; + this.manualBake = true; + this.args = [ + { + "name": "方法", + "type": "option", + "value": [ + "GET", "POST", "HEAD", + "PUT", "PATCH", "DELETE", + "CONNECT", "TRACE", "OPTIONS" + ] + }, + { + "name": "URL", + "type": "string", + "value": "" + }, + { + "name": "请求头(Header)", + "type": "text", + "value": "" + }, + { + "name": "模式", + "type": "option", + "value": [ + "Cross-Origin Resource Sharing", + "No CORS (limited to HEAD, GET or POST)", + ] + }, + { + "name": "显示响应元数据", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [method, url, headersText, mode, showResponseMetadata] = args; + + if (url.length === 0) return ""; + + const headers = new Headers(); + headersText.split(/\r?\n/).forEach(line => { + line = line.trim(); + + if (line.length === 0) return; + + const split = line.split(":"); + if (split.length !== 2) throw `无法解析请求头,位于行: ${line}`; + + headers.set(split[0].trim(), split[1].trim()); + }); + + const config = { + method: method, + headers: headers, + mode: modeLookup[mode], + cache: "no-cache", + }; + + if (method !== "GET" && method !== "HEAD") { + config.body = input; + } + + return fetch(url, config) + .then(r => { + if (r.status === 0 && r.type === "opaque") { + throw new OperationError("错误:响应为空。尝试把连接模式设置为CORS。"); + } + + if (showResponseMetadata) { + let headers = ""; + for (const pair of r.headers.entries()) { + headers += " " + pair[0] + ": " + pair[1] + "\n"; + } + return r.text().then(b => { + return "####\n 状态: " + r.status + " " + r.statusText + + "\n 响应头:\n" + headers + "####\n\n" + b; + }); + } + return r.text(); + }) + .catch(e => { + throw new OperationError(e.toString() + + "\n\n此错误可能由以下原因产生:\n" + + " - 无效的URL\n" + + " - 从安全来源(HTTPS)发起到不安全来源(HTTP)的请求。\n" + + " - 对不支持CORS的服务器发起了跨域请求。\n"); + }); + } + +} + + +/** + * Lookup table for HTTP modes + * + * @private + */ +const modeLookup = { + "Cross-Origin Resource Sharing": "cors", + "No CORS (limited to HEAD, GET or POST)": "no-cors", +}; + + +export default HTTPRequest; diff --git a/plugins/srktoolbox/src/core/operations/HammingDistance.mjs b/plugins/srktoolbox/src/core/operations/HammingDistance.mjs new file mode 100644 index 00000000..86c5ba38 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/HammingDistance.mjs @@ -0,0 +1,100 @@ +/** + * @author GCHQ Contributor [2] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import {fromHex} from "../lib/Hex.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Hamming Distance operation + */ +class HammingDistance extends Operation { + + /** + * HammingDistance constructor + */ + constructor() { + super(); + + this.name = "汉明距离"; + this.module = "Default"; + this.description = "在信息论中,两个等长字符串之间的汉明距离(英语:Hamming distance)是两个字符串对应位置的不同字符的个数。换句话说,它就是将一个字符串变换成另外一个字符串所需要替换的字符个数。"; + this.infoURL = "https://wikipedia.org/wiki/Hamming_distance"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "分隔符", + "type": "binaryShortString", + "value": "\\n\\n" + }, + { + "name": "单位", + "type": "option", + "value": ["字节", "位"] + }, + { + "name": "输入格式", + "type": "option", + "value": ["原始字符串", "十六进制"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const delim = args[0], + byByte = args[1] === "字节", + inputType = args[2], + samples = input.split(delim); + + if (samples.length !== 2) { + throw new OperationError("错误:计算汉明距离需要两个字符串,请确保输入按照给定分隔符的两个字符串。"); + } + + if (samples[0].length !== samples[1].length) { + throw new OperationError("错误:两个字符串需要等长。"); + } + + if (inputType === "十六进制") { + samples[0] = fromHex(samples[0]); + samples[1] = fromHex(samples[1]); + } else { + samples[0] = new Uint8Array(Utils.strToArrayBuffer(samples[0])); + samples[1] = new Uint8Array(Utils.strToArrayBuffer(samples[1])); + } + + let dist = 0; + + for (let i = 0; i < samples[0].length; i++) { + const lhs = samples[0][i], + rhs = samples[1][i]; + + if (byByte && lhs !== rhs) { + dist++; + } else if (!byByte) { + let xord = lhs ^ rhs; + + while (xord) { + dist++; + xord &= xord - 1; + } + } + } + + return dist.toString(); + } + +} + +export default HammingDistance; diff --git a/plugins/srktoolbox/src/core/operations/HaversineDistance.mjs b/plugins/srktoolbox/src/core/operations/HaversineDistance.mjs new file mode 100644 index 00000000..3ecb1475 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/HaversineDistance.mjs @@ -0,0 +1,61 @@ +/** + * @author Dachande663 [dachande663@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * HaversineDistance operation + */ +class HaversineDistance extends Operation { + + /** + * HaversineDistance constructor + */ + constructor() { + super(); + + this.name = "半正矢距离"; + this.module = "Default"; + this.description = "计算两对GPS经纬度坐标之间的距离,单位米。

例如:51.487263,-0.124323, 38.9517,-77.1467"; + this.infoURL = "https://wikipedia.org/wiki/Haversine_formula"; + this.inputType = "string"; + this.outputType = "number"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {number} + */ + run(input, args) { + + const values = input.match(/^(-?\d+(\.\d+)?), ?(-?\d+(\.\d+)?), ?(-?\d+(\.\d+)?), ?(-?\d+(\.\d+)?)$/); + if (!values) { + throw new OperationError("输入格式必须是:纬度1, 经度1, 纬度2, 经度2"); + } + + const lat1 = parseFloat(values[1]); + const lng1 = parseFloat(values[3]); + const lat2 = parseFloat(values[5]); + const lng2 = parseFloat(values[7]); + + const TO_RAD = Math.PI / 180; + const dLat = (lat2-lat1) * TO_RAD; + const dLng = (lng2-lng1) * TO_RAD; + const a = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.cos(lat1 * TO_RAD) * Math.cos(lat2 * TO_RAD) * Math.sin(dLng/2) * Math.sin(dLng/2); + const metres = 6371000 * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); + + return metres; + + } + +} + +export default HaversineDistance; diff --git a/plugins/srktoolbox/src/core/operations/Head.mjs b/plugins/srktoolbox/src/core/operations/Head.mjs new file mode 100644 index 00000000..5bae32e6 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Head.mjs @@ -0,0 +1,70 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import {INPUT_DELIM_OPTIONS} from "../lib/Delim.mjs"; + +/** + * Head operation + */ +class Head extends Operation { + + /** + * Head constructor + */ + constructor() { + super(); + + this.name = "Head"; + this.module = "Default"; + this.description = "和UNIX的head工具类似。
输出前n行。
输入负数可以选取除最后n行之外的内容。
可以选择不同的分隔符来实现选取前n个数据。"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "分隔符", + "type": "option", + "value": INPUT_DELIM_OPTIONS + }, + { + "name": "选取数量", + "type": "number", + "value": 10 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let delimiter = args[0]; + const number = args[1]; + + delimiter = Utils.charRep(delimiter); + const splitInput = input.split(delimiter); + + return splitInput + .filter((line, lineIndex) => { + lineIndex += 1; + + if (number < 0) { + return lineIndex <= splitInput.length + number; + } else { + return lineIndex <= number; + } + }) + .join(delimiter); + } + +} + +export default Head; diff --git a/plugins/srktoolbox/src/core/operations/HeatmapChart.mjs b/plugins/srktoolbox/src/core/operations/HeatmapChart.mjs new file mode 100644 index 00000000..240962c0 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/HeatmapChart.mjs @@ -0,0 +1,268 @@ +/** + * @author tlwr [toby@toby.codes] + * @author Matt C [me@mitt.dev] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import * as d3temp from "d3"; +import * as nodomtemp from "nodom"; +import { getScatterValues, RECORD_DELIMITER_OPTIONS, COLOURS, FIELD_DELIMITER_OPTIONS } from "../lib/Charts.mjs"; + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; + +const d3 = d3temp.default ? d3temp.default : d3temp; +const nodom = nodomtemp.default ? nodomtemp.default: nodomtemp; + +/** + * Heatmap chart operation + */ +class HeatmapChart extends Operation { + + /** + * HeatmapChart constructor + */ + constructor() { + super(); + + this.name = "热图"; + this.module = "Charts"; + this.description = "热图(英语:heat map)是在二维空间中以颜色的形式显示一个现象的绝对量一种数据可视化技术。"; + this.infoURL = "https://wikipedia.org/wiki/Heat_map"; + this.inputType = "string"; + this.outputType = "html"; + this.args = [ + { + name: "记录分隔符", + type: "option", + value: RECORD_DELIMITER_OPTIONS, + }, + { + name: "字段分隔符", + type: "option", + value: FIELD_DELIMITER_OPTIONS, + }, + { + name: "垂直色块数", + type: "number", + value: 25, + }, + { + name: "水平色块数", + type: "number", + value: 25, + }, + { + name: "使用表头作为标签", + type: "boolean", + value: true, + }, + { + name: "X轴标签", + type: "string", + value: "", + }, + { + name: "Y轴标签", + type: "string", + value: "", + }, + { + name: "绘制色块边缘", + type: "boolean", + value: false, + }, + { + name: "最小值色彩", + type: "string", + value: COLOURS.min, + }, + { + name: "最大值色彩", + type: "string", + value: COLOURS.max, + }, + ]; + } + + /** + * Heatmap chart operation. + * + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + const recordDelimiter = Utils.charRep(args[0]), + fieldDelimiter = Utils.charRep(args[1]), + vBins = args[2], + hBins = args[3], + columnHeadingsAreIncluded = args[4], + drawEdges = args[7], + minColour = args[8], + maxColour = args[9], + dimension = 500; + if (vBins <= 0) throw new OperationError("水平色块数必须大于0"); + if (hBins <= 0) throw new OperationError("垂直色块数必须大于0"); + + let xLabel = args[5], + yLabel = args[6]; + const { headings, values } = getScatterValues( + input, + recordDelimiter, + fieldDelimiter, + columnHeadingsAreIncluded + ); + + if (headings) { + xLabel = headings.x; + yLabel = headings.y; + } + + const document = new nodom.Document(); + let svg = document.createElement("svg"); + svg = d3.select(svg) + .attr("width", "100%") + .attr("height", "100%") + .attr("viewBox", `0 0 ${dimension} ${dimension}`); + + const margin = { + top: 10, + right: 0, + bottom: 40, + left: 30, + }, + width = dimension - margin.left - margin.right, + height = dimension - margin.top - margin.bottom, + binWidth = width / hBins, + binHeight = height/ vBins, + marginedSpace = svg.append("g") + .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); + + const bins = this.getHeatmapPacking(values, vBins, hBins), + maxCount = Math.max(...bins.map(row => { + const lengths = row.map(cell => cell.length); + return Math.max(...lengths); + })); + + const xExtent = d3.extent(values, d => d[0]), + yExtent = d3.extent(values, d => d[1]); + + const xAxis = d3.scaleLinear() + .domain(xExtent) + .range([0, width]); + const yAxis = d3.scaleLinear() + .domain(yExtent) + .range([height, 0]); + + const colour = d3.scaleSequential(d3.interpolateLab(minColour, maxColour)) + .domain([0, maxCount]); + + marginedSpace.append("clipPath") + .attr("id", "clip") + .append("rect") + .attr("width", width) + .attr("height", height); + + marginedSpace.append("g") + .attr("class", "bins") + .attr("clip-path", "url(#clip)") + .selectAll("g") + .data(bins) + .enter() + .append("g") + .selectAll("rect") + .data(d => d) + .enter() + .append("rect") + .attr("x", (d) => binWidth * d.x) + .attr("y", (d) => (height - binHeight * (d.y + 1))) + .attr("width", binWidth) + .attr("height", binHeight) + .attr("fill", (d) => colour(d.length)) + .attr("stroke", drawEdges ? "rgba(0, 0, 0, 0.5)" : "none") + .attr("stroke-width", drawEdges ? "0.5" : "none") + .append("title") + .text(d => { + const count = d.length, + perc = 100.0 * d.length / values.length, + tooltip = `计数: ${count}\n + 占比: ${perc.toFixed(2)}%\n + `.replace(/\s{2,}/g, "\n"); + return tooltip; + }); + + marginedSpace.append("g") + .attr("class", "axis axis--y") + .call(d3.axisLeft(yAxis).tickSizeOuter(-width)); + + svg.append("text") + .attr("transform", "rotate(-90)") + .attr("y", -margin.left) + .attr("x", -(height / 2)) + .attr("dy", "1em") + .style("text-anchor", "middle") + .text(yLabel); + + marginedSpace.append("g") + .attr("class", "axis axis--x") + .attr("transform", "translate(0," + height + ")") + .call(d3.axisBottom(xAxis).tickSizeOuter(-height)); + + svg.append("text") + .attr("x", width / 2) + .attr("y", dimension) + .style("text-anchor", "middle") + .text(xLabel); + + return svg._groups[0][0].outerHTML; + } + + /** + * Packs a list of x, y coordinates into a number of bins for use in a heatmap. + * + * @param {Object[]} points + * @param {number} number of vertical bins + * @param {number} number of horizontal bins + * @returns {Object[]} a list of bins (each bin is an Array) with x y coordinates, filled with the points + */ + getHeatmapPacking(values, vBins, hBins) { + const xBounds = d3.extent(values, d => d[0]), + yBounds = d3.extent(values, d => d[1]), + bins = []; + + if (xBounds[0] === xBounds[1]) throw "无法对数据点分组。X坐标最大值和最小值之间没有差异。"; + if (yBounds[0] === yBounds[1]) throw "无法对数据点分组。Y坐标最大值和最小值之间没有差异。"; + + for (let y = 0; y < vBins; y++) { + bins.push([]); + for (let x = 0; x < hBins; x++) { + const item = []; + item.y = y; + item.x = x; + + bins[y].push(item); + } // x + } // y + + const epsilon = 0.000000001; // This is to clamp values that are exactly the maximum; + + values.forEach(v => { + const fractionOfY = (v[1] - yBounds[0]) / ((yBounds[1] + epsilon) - yBounds[0]), + fractionOfX = (v[0] - xBounds[0]) / ((xBounds[1] + epsilon) - xBounds[0]), + y = Math.floor(vBins * fractionOfY), + x = Math.floor(hBins * fractionOfX); + + bins[y][x].push({x: v[0], y: v[1]}); + }); + + return bins; + } + +} + +export default HeatmapChart; diff --git a/plugins/srktoolbox/src/core/operations/HexDensityChart.mjs b/plugins/srktoolbox/src/core/operations/HexDensityChart.mjs new file mode 100644 index 00000000..175a6e27 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/HexDensityChart.mjs @@ -0,0 +1,298 @@ +/** + * @author tlwr [toby@toby.codes] + * @author Matt C [me@mitt.dev] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import * as d3temp from "d3"; +import * as d3hexbintemp from "d3-hexbin"; +import * as nodomtemp from "nodom"; +import { getScatterValues, RECORD_DELIMITER_OPTIONS, COLOURS, FIELD_DELIMITER_OPTIONS } from "../lib/Charts.mjs"; + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +const d3 = d3temp.default ? d3temp.default : d3temp; +const d3hexbin = d3hexbintemp.default ? d3hexbintemp.default : d3hexbintemp; +const nodom = nodomtemp.default ? nodomtemp.default: nodomtemp; + + +/** + * Hex Density chart operation + */ +class HexDensityChart extends Operation { + + /** + * HexDensityChart constructor + */ + constructor() { + super(); + + this.name = "六边形密度图"; + this.module = "Charts"; + this.description = "六边形密度图(Hex density chart)和散点图的使用方法类似,但与其显示上万散点,六边形密度图将相近位置的数据合并,用较少的六边形和色彩来展示密度。"; + this.inputType = "string"; + this.outputType = "html"; + this.args = [ + { + name: "记录分隔符", + type: "option", + value: RECORD_DELIMITER_OPTIONS, + }, + { + name: "字段分隔符", + type: "option", + value: FIELD_DELIMITER_OPTIONS, + }, + { + name: "分组半径", + type: "number", + value: 25, + }, + { + name: "绘制半径", + type: "number", + value: 15, + }, + { + name: "使用表头作为标签", + type: "boolean", + value: true, + }, + { + name: "X轴标签", + type: "string", + value: "", + }, + { + name: "Y轴标签", + type: "string", + value: "", + }, + { + name: "绘制六边形边缘", + type: "boolean", + value: false, + }, + { + name: "最小值色彩", + type: "string", + value: COLOURS.min, + }, + { + name: "最大值色彩", + type: "string", + value: COLOURS.max, + }, + { + name: "绘制数据范围内的空六边形", + type: "boolean", + value: false, + } + ]; + } + + + /** + * Hex Bin chart operation. + * + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + const recordDelimiter = Utils.charRep(args[0]), + fieldDelimiter = Utils.charRep(args[1]), + packRadius = args[2], + drawRadius = args[3], + columnHeadingsAreIncluded = args[4], + drawEdges = args[7], + minColour = args[8], + maxColour = args[9], + drawEmptyHexagons = args[10], + dimension = 500; + + let xLabel = args[5], + yLabel = args[6]; + const { headings, values } = getScatterValues( + input, + recordDelimiter, + fieldDelimiter, + columnHeadingsAreIncluded + ); + + if (headings) { + xLabel = headings.x; + yLabel = headings.y; + } + + const document = new nodom.Document(); + let svg = document.createElement("svg"); + svg = d3.select(svg) + .attr("width", "100%") + .attr("height", "100%") + .attr("viewBox", `0 0 ${dimension} ${dimension}`); + + const margin = { + top: 10, + right: 0, + bottom: 40, + left: 30, + }, + width = dimension - margin.left - margin.right, + height = dimension - margin.top - margin.bottom, + marginedSpace = svg.append("g") + .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); + + const hexbin = d3hexbin.hexbin() + .radius(packRadius) + .extent([0, 0], [width, height]); + + const hexPoints = hexbin(values), + maxCount = Math.max(...hexPoints.map(b => b.length)); + + const xExtent = d3.extent(hexPoints, d => d.x), + yExtent = d3.extent(hexPoints, d => d.y); + xExtent[0] -= 2 * packRadius; + xExtent[1] += 3 * packRadius; + yExtent[0] -= 2 * packRadius; + yExtent[1] += 2 * packRadius; + + const xAxis = d3.scaleLinear() + .domain(xExtent) + .range([0, width]); + const yAxis = d3.scaleLinear() + .domain(yExtent) + .range([height, 0]); + + const colour = d3.scaleSequential(d3.interpolateLab(minColour, maxColour)) + .domain([0, maxCount]); + + marginedSpace.append("clipPath") + .attr("id", "clip") + .append("rect") + .attr("width", width) + .attr("height", height); + + if (drawEmptyHexagons) { + marginedSpace.append("g") + .attr("class", "empty-hexagon") + .selectAll("path") + .data(this.getEmptyHexagons(hexPoints, packRadius)) + .enter() + .append("path") + .attr("d", d => { + return `M${xAxis(d.x)},${yAxis(d.y)} ${hexbin.hexagon(drawRadius)}`; + }) + .attr("fill", (d) => colour(0)) + .attr("stroke", drawEdges ? "black" : "none") + .attr("stroke-width", drawEdges ? "0.5" : "none") + .append("title") + .text(d => { + const count = 0, + perc = 0, + tooltip = `计数: ${count}\n + 占比: ${perc.toFixed(2)}%\n + 中心: ${d.x.toFixed(2)}, ${d.y.toFixed(2)}\n + `.replace(/\s{2,}/g, "\n"); + return tooltip; + }); + } + + marginedSpace.append("g") + .attr("class", "hexagon") + .attr("clip-path", "url(#clip)") + .selectAll("path") + .data(hexPoints) + .enter() + .append("path") + .attr("d", d => { + return `M${xAxis(d.x)},${yAxis(d.y)} ${hexbin.hexagon(drawRadius)}`; + }) + .attr("fill", (d) => colour(d.length)) + .attr("stroke", drawEdges ? "black" : "none") + .attr("stroke-width", drawEdges ? "0.5" : "none") + .append("title") + .text(d => { + const count = d.length, + perc = 100.0 * d.length / values.length, + CX = d.x, + CY = d.y, + xMin = Math.min(...d.map(d => d[0])), + xMax = Math.max(...d.map(d => d[0])), + yMin = Math.min(...d.map(d => d[1])), + yMax = Math.max(...d.map(d => d[1])), + tooltip = `计数: ${count}\n + 占比: ${perc.toFixed(2)}%\n + 中心: ${CX.toFixed(2)}, ${CY.toFixed(2)}\n + X最小: ${xMin.toFixed(2)}\n + X最大: ${xMax.toFixed(2)}\n + Y最小: ${yMin.toFixed(2)}\n + Y最大: ${yMax.toFixed(2)} + `.replace(/\s{2,}/g, "\n"); + return tooltip; + }); + + marginedSpace.append("g") + .attr("class", "axis axis--y") + .call(d3.axisLeft(yAxis).tickSizeOuter(-width)); + + svg.append("text") + .attr("transform", "rotate(-90)") + .attr("y", -margin.left) + .attr("x", -(height / 2)) + .attr("dy", "1em") + .style("text-anchor", "middle") + .text(yLabel); + + marginedSpace.append("g") + .attr("class", "axis axis--x") + .attr("transform", "translate(0," + height + ")") + .call(d3.axisBottom(xAxis).tickSizeOuter(-height)); + + svg.append("text") + .attr("x", width / 2) + .attr("y", dimension) + .style("text-anchor", "middle") + .text(xLabel); + + return svg._groups[0][0].outerHTML; + } + + + /** + * Hex Bin chart operation. + * + * @param {Object[]} - centres + * @param {number} - radius + * @returns {Object[]} + */ + getEmptyHexagons(centres, radius) { + const emptyCentres = [], + boundingRect = [d3.extent(centres, d => d.x), d3.extent(centres, d => d.y)], + hexagonCenterToEdge = Math.cos(2 * Math.PI / 12) * radius, + hexagonEdgeLength = Math.sin(2 * Math.PI / 12) * radius; + let indent = false; + + for (let y = boundingRect[1][0]; y <= boundingRect[1][1] + radius; y += hexagonEdgeLength + radius) { + for (let x = boundingRect[0][0]; x <= boundingRect[0][1] + radius; x += 2 * hexagonCenterToEdge) { + let cx = x; + const cy = y; + + if (indent && x >= boundingRect[0][1]) break; + if (indent) cx += hexagonCenterToEdge; + + emptyCentres.push({x: cx, y: cy}); + } + indent = !indent; + } + + return emptyCentres; + } + +} + +export default HexDensityChart; diff --git a/plugins/srktoolbox/src/core/operations/HexToObjectIdentifier.mjs b/plugins/srktoolbox/src/core/operations/HexToObjectIdentifier.mjs new file mode 100644 index 00000000..4727369e --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/HexToObjectIdentifier.mjs @@ -0,0 +1,43 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import r from "jsrsasign"; +import Operation from "../Operation.mjs"; + +/** + * Hex to Object Identifier operation + */ +class HexToObjectIdentifier extends Operation { + + /** + * HexToObjectIdentifier constructor + */ + constructor() { + super(); + + this.name = "十六进制转OID"; + this.module = "PublicKey"; + this.description = "把十六进制字符串转换成对应的对象标识符(object identifier,OID)。"; + this.infoURL = "https://wikipedia.org/wiki/Object_identifier"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return r.KJUR.asn1.ASN1Util.oidHexToInt(input.replace(/\s/g, "")); + } + +} + +export default HexToObjectIdentifier; diff --git a/plugins/srktoolbox/src/core/operations/HexToPEM.mjs b/plugins/srktoolbox/src/core/operations/HexToPEM.mjs new file mode 100644 index 00000000..258a230b --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/HexToPEM.mjs @@ -0,0 +1,49 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import r from "jsrsasign"; +import Operation from "../Operation.mjs"; + +/** + * Hex to PEM operation + */ +class HexToPEM extends Operation { + + /** + * HexToPEM constructor + */ + constructor() { + super(); + + this.name = "十六进制转PEM"; + this.module = "PublicKey"; + this.description = "把十六进制DER(Distinguished Encoding Rules)字符串转换成PEM(Privacy Enhanced Mail)格式。"; + this.infoURL = "https://wikipedia.org/wiki/Privacy-Enhanced_Mail"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Header字符串", + "type": "string", + "value": "CERTIFICATE" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return r.KJUR.asn1.ASN1Util.getPEMStringFromHex(input.replace(/\s/g, ""), args[0]); + } + +} + +export default HexToPEM; diff --git a/plugins/srktoolbox/src/core/operations/IPv6TransitionAddresses.mjs b/plugins/srktoolbox/src/core/operations/IPv6TransitionAddresses.mjs new file mode 100644 index 00000000..1a2fc0ed --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/IPv6TransitionAddresses.mjs @@ -0,0 +1,211 @@ +/** + * @author jb30795 [jb30795@proton.me] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; + +/** + * IPv6 Transition Addresses operation + */ +class IPv6TransitionAddresses extends Operation { + + /** + * IPv6TransitionAddresses constructor + */ + constructor() { + super(); + + this.name = "IPv6过渡地址"; + this.module = "Default"; + this.description = "将IPv4地址转换为其IPv6过渡地址,亦可反向将IPv6过渡地址还原为原始IPv4地址。同时支持将MAC地址转换为EUI-64格式,该格式可直接附加至您的IPv6 /64地址段,从而生成完整的/128地址。过渡技术通过实现IPv4与IPv6地址间的转换或建立隧道,使流量能在不兼容的网络间传输,从而实现两种标准的共存。当前仅支持处理/24地址范围。提供“移除标头”功能,便于直接复制输出结果。"; + this.infoURL = "https://wikipedia.org/wiki/IPv6_transition_mechanism"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "忽略地址范围", + "type": "boolean", + "value": true + }, + { + "name": "移除标头", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const XOR = {"0": "2", "1": "3", "2": "0", "3": "1", "4": "6", "5": "7", "6": "4", "7": "5", "8": "a", "9": "b", "a": "8", "b": "9", "c": "e", "d": "f", "e": "c", "f": "d"}; + + /** + * Function to convert to hex + */ + function hexify(octet) { + return Number(octet).toString(16).padStart(2, "0"); + } + + /** + * Function to convert Hex to Int + */ + function intify(hex) { + return parseInt(hex, 16); + } + + /** + * Function converts IPv4 to IPv6 Transtion address + */ + function ipTransition(input, range) { + let output = ""; + const HEXIP = input.split("."); + + /** + * 6to4 + */ + if (!args[1]) { + output += "6to4:"; + } + output += "2002:" + hexify(HEXIP[0]) + hexify(HEXIP[1]) + ":" + hexify(HEXIP[2]); + if (range) { + output += "00::/40\n"; + } else { + output += hexify(HEXIP[3]) + "::/48\n"; + } + + /** + * Mapped + */ + if (!args[1]) { + output += "IPv4映射:"; + } + output += "::ffff:" + hexify(HEXIP[0]) + hexify(HEXIP[1]) + ":" + hexify(HEXIP[2]); + if (range) { + output += "00/120\n"; + } else { + output += hexify(HEXIP[3]) + "\n"; + } + + /** + * Translated + */ + if (!args[1]) { + output += "IPv4转译:"; + } + output += "::ffff:0:" + hexify(HEXIP[0]) + hexify(HEXIP[1]) + ":" + hexify(HEXIP[2]); + if (range) { + output += "00/120\n"; + } else { + output += hexify(HEXIP[3]) + "\n"; + } + + /** + * Nat64 + */ + if (!args[1]) { + output += "NAT64:"; + } + output += "64:ff9b::" + hexify(HEXIP[0]) + hexify(HEXIP[1]) + ":" + hexify(HEXIP[2]); + if (range) { + output += "00/120\n"; + } else { + output += hexify(HEXIP[3]) + "\n"; + } + + return output; + } + + /** + * Convert MAC to EUI-64 + */ + function macTransition(input) { + let output = ""; + const MACPARTS = input.split(":"); + if (!args[1]) { + output += "EUI-64接口标识符:"; + } + const MAC = MACPARTS[0] + MACPARTS[1] + ":" + MACPARTS[2] + "ff:fe" + MACPARTS[3] + ":" + MACPARTS[4] + MACPARTS[5]; + output += MAC.slice(0, 1) + XOR[MAC.slice(1, 2)] + MAC.slice(2); + + return output; + } + + + /** + * Convert IPv6 address to its original IPv4 or MAC address + */ + function unTransition(input) { + let output = ""; + let hextets = ""; + + /** + * 6to4 + */ + if (input.startsWith("2002:")) { + if (!args[1]) { + output += "IPv4:"; + } + output += String(intify(input.slice(5, 7))) + "." + String(intify(input.slice(7, 9)))+ "." + String(intify(input.slice(10, 12)))+ "." + String(intify(input.slice(12, 14))) + "\n"; + } else if (input.startsWith("::ffff:") || input.startsWith("0000:0000:0000:0000:0000:ffff:") || input.startsWith("::ffff:0000:") || input.startsWith("0000:0000:0000:0000:ffff:0000:") || input.startsWith("64:ff9b::") || input.startsWith("0064:ff9b:0000:0000:0000:0000:")) { + /** + * Mapped/Translated/Nat64 + */ + hextets = /:([0-9a-z]{1,4}):[0-9a-z]{1,4}$/.exec(input)[1].padStart(4, "0") + /:([0-9a-z]{1,4})$/.exec(input)[1].padStart(4, "0"); + if (!args[1]) { + output += "IPv4:"; + } + output += intify(hextets.slice(-8, -7) + hextets.slice(-7, -6)) + "." +intify(hextets.slice(-6, -5) + hextets.slice(-5, -4)) + "." +intify(hextets.slice(-4, -3) + hextets.slice(-3, -2)) + "." +intify(hextets.slice(-2, -1) + hextets.slice(-1,)) + "\n"; + } else if (input.slice(-12, -7).toUpperCase() === "FF:FE") { + /** + * EUI-64 + */ + if (!args[1]) { + output += "MAC地址:"; + } + const MAC = (input.slice(-19, -17) + ":" + input.slice(-17, -15) + ":" + input.slice(-14, -12) + ":" + input.slice(-7, -5) + ":" + input.slice(-4, -2) + ":" + input.slice(-2,)).toUpperCase(); + output += MAC.slice(0, 1) + XOR[MAC.slice(1, 2)] + MAC.slice(2) + "\n"; + } + + return output; + } + + + /** + * Main + */ + let output = ""; + let inputs = input.split("\n"); + // Remove blank rows + inputs = inputs.filter(Boolean); + + for (let i = 0; i < inputs.length; i++) { + // if ignore ranges is checked and input is a range, skip + if ((args[0] && !inputs[i].includes("/")) || (!args[0])) { + if (/^[0-9]{1,3}(?:\.[0-9]{1,3}){3}$/.test(inputs[i])) { + output += ipTransition(inputs[i], false); + } else if (/\/24$/.test(inputs[i])) { + output += ipTransition(inputs[i], true); + } else if (/^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$/.test(inputs[i])) { + output += macTransition(inputs[i]); + } else if (/^((?:[0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,7}:|(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,5}(?::[0-9a-fA-F]{1,4}){1,2}|(?:[0-9a-fA-F]{1,4}:){1,4}(?::[0-9a-fA-F]{1,4}){1,3}|(?:[0-9a-fA-F]{1,4}:){1,3}(?::[0-9a-fA-F]{1,4}){1,4}|(?:[0-9a-fA-F]{1,4}:){1,2}(?::[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:(?:(?::[0-9a-fA-F]{1,4}){1,6})|:(?:(?::[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(?::[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(?:ffff(?::0{1,4}){0,1}:){0,1}(?:(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])|(?:[0-9a-fA-F]{1,4}:){1,4}:(?:(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/.test(inputs[i])) { + output += unTransition(inputs[i]); + } else { + output = "请输入压缩或原始格式的IPv6、IPv4或MAC地址。"; + } + } + } + + return output; + } + +} + +export default IPv6TransitionAddresses; diff --git a/plugins/srktoolbox/src/core/operations/ImageBrightnessContrast.mjs b/plugins/srktoolbox/src/core/operations/ImageBrightnessContrast.mjs new file mode 100644 index 00000000..d286c793 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ImageBrightnessContrast.mjs @@ -0,0 +1,112 @@ +/** + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { isImage } from "../lib/FileType.mjs"; +import { toBase64 } from "../lib/Base64.mjs"; +import { isWorkerEnvironment } from "../Utils.mjs"; +import { Jimp, JimpMime } from "jimp"; + +/** + * Image Brightness / Contrast operation + */ +class ImageBrightnessContrast extends Operation { + /** + * ImageBrightnessContrast constructor + */ + constructor() { + super(); + + this.name = "图像亮度/对比度"; + this.module = "Image"; + this.description = "调整图像的亮度或对比度。"; + this.infoURL = ""; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.presentType = "html"; + this.args = [ + { + name: "亮度", + type: "number", + value: 0, + min: -100, + max: 100, + }, + { + name: "对比度", + type: "number", + value: 0, + min: -100, + max: 100, + }, + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {byteArray} + */ + async run(input, args) { + const [brightness, contrast] = args; + if (!isImage(input)) { + throw new OperationError("无效的文件类型。"); + } + + let image; + try { + image = await Jimp.read(input); + } catch (err) { + throw new OperationError(`图像加载出错:(${err})`); + } + try { + if (brightness !== 0) { + if (isWorkerEnvironment()) + self.sendStatusMessage("图像亮度调整……"); + image.brightness(brightness / 100); + } + if (contrast !== 0) { + if (isWorkerEnvironment()) + self.sendStatusMessage("图像对比度调整……"); + image.contrast(contrast / 100); + } + + let imageBuffer; + if (image.mime === "image/gif") { + imageBuffer = await image.getBuffer(JimpMime.png); + } else { + imageBuffer = await image.getBuffer(image.mime); + } + return imageBuffer.buffer; + } catch (err) { + throw new OperationError( + `调整图像亮度/对比度出错:(${err})`, + ); + } + } + + /** + * Displays the image using HTML for web apps + * @param {ArrayBuffer} data + * @returns {html} + */ + present(data) { + if (!data.byteLength) return ""; + const dataArray = new Uint8Array(data); + + const type = isImage(dataArray); + if (!type) { + throw new OperationError("无效的文件类型。"); + } + + return ``; + } +} + +export default ImageBrightnessContrast; diff --git a/plugins/srktoolbox/src/core/operations/ImageFilter.mjs b/plugins/srktoolbox/src/core/operations/ImageFilter.mjs new file mode 100644 index 00000000..5136fdd6 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ImageFilter.mjs @@ -0,0 +1,104 @@ +/** + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { isImage } from "../lib/FileType.mjs"; +import { toBase64 } from "../lib/Base64.mjs"; +import { isWorkerEnvironment } from "../Utils.mjs"; +import { Jimp, JimpMime } from "jimp"; + +/** + * Image Filter operation + */ +class ImageFilter extends Operation { + /** + * ImageFilter constructor + */ + constructor() { + super(); + + this.name = "图像滤镜"; + this.module = "Image"; + this.description = "为图像添加灰度或深褐(Sepia)滤镜。"; + this.infoURL = ""; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.presentType = "html"; + this.args = [ + { + name: "滤镜类型", + type: "option", + value: ["灰度", "深褐(Sepia)"], + }, + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {byteArray} + */ + async run(input, args) { + const [filterType] = args; + if (!isImage(input)) { + throw new OperationError("无效的文件类型。"); + } + + let image; + try { + image = await Jimp.read(input); + } catch (err) { + throw new OperationError(`载入图像出错:(${err})`); + } + try { + if (isWorkerEnvironment()) + self.sendStatusMessage( + "应用 " + + filterType.toLowerCase() + + " 滤镜……", + ); + if (filterType === "灰度") { + image.greyscale(); + } else { + image.sepia(); + } + + let imageBuffer; + if (image.mime === "image/gif") { + imageBuffer = await image.getBuffer(JimpMime.png); + } else { + imageBuffer = await image.getBuffer(image.mime); + } + return imageBuffer.buffer; + } catch (err) { + throw new OperationError( + `应用滤镜出错:(${err})`, + ); + } + } + + /** + * Displays the blurred image using HTML for web apps + * @param {ArrayBuffer} data + * @returns {html} + */ + present(data) { + if (!data.byteLength) return ""; + const dataArray = new Uint8Array(data); + + const type = isImage(dataArray); + if (!type) { + throw new OperationError("无效的文件类型。"); + } + + return ``; + } +} + +export default ImageFilter; diff --git a/plugins/srktoolbox/src/core/operations/ImageHueSaturationLightness.mjs b/plugins/srktoolbox/src/core/operations/ImageHueSaturationLightness.mjs new file mode 100644 index 00000000..a3b73342 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ImageHueSaturationLightness.mjs @@ -0,0 +1,141 @@ +/** + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { isImage } from "../lib/FileType.mjs"; +import { toBase64 } from "../lib/Base64.mjs"; +import { isWorkerEnvironment } from "../Utils.mjs"; +import { Jimp, JimpMime } from "jimp"; + +/** + * Image Hue/Saturation/Lightness operation + */ +class ImageHueSaturationLightness extends Operation { + /** + * ImageHueSaturationLightness constructor + */ + constructor() { + super(); + + this.name = "图像色调/饱和度/明度"; + this.module = "Image"; + this.description = + "调整图像的色调/饱和度/明度值(HSL)。"; + this.infoURL = ""; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.presentType = "html"; + this.args = [ + { + name: "色调", + type: "number", + value: 0, + min: -360, + max: 360, + }, + { + name: "饱和度", + type: "number", + value: 0, + min: -100, + max: 100, + }, + { + name: "明度", + type: "number", + value: 0, + min: -100, + max: 100, + }, + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {byteArray} + */ + async run(input, args) { + const [hue, saturation, lightness] = args; + + if (!isImage(input)) { + throw new OperationError("无效的文件类型。"); + } + + let image; + try { + image = await Jimp.read(input); + } catch (err) { + throw new OperationError(`载入图像出错:(${err})`); + } + try { + if (hue !== 0) { + if (isWorkerEnvironment()) + self.sendStatusMessage("调整图像色调……"); + image.color([ + { + apply: "hue", + params: [hue], + }, + ]); + } + if (saturation !== 0) { + if (isWorkerEnvironment()) + self.sendStatusMessage("调整图像饱和度……"); + image.color([ + { + apply: "saturate", + params: [saturation], + }, + ]); + } + if (lightness !== 0) { + if (isWorkerEnvironment()) + self.sendStatusMessage("调整图像明度……"); + image.color([ + { + apply: "lighten", + params: [lightness], + }, + ]); + } + + let imageBuffer; + if (image.mime === "image/gif") { + imageBuffer = await image.getBuffer(JimpMime.png); + } else { + imageBuffer = await image.getBuffer(image.mime); + } + return imageBuffer.buffer; + } catch (err) { + throw new OperationError( + `调整图像色调/饱和度/明度报错:(${err})`, + ); + } + } + + /** + * Displays the image using HTML for web apps + * @param {ArrayBuffer} data + * @returns {html} + */ + present(data) { + if (!data.byteLength) return ""; + const dataArray = new Uint8Array(data); + + const type = isImage(dataArray); + if (!type) { + throw new OperationError("无效的文件类型。"); + } + + return ``; + } +} + +export default ImageHueSaturationLightness; diff --git a/plugins/srktoolbox/src/core/operations/ImageOpacity.mjs b/plugins/srktoolbox/src/core/operations/ImageOpacity.mjs new file mode 100644 index 00000000..4f2716ab --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ImageOpacity.mjs @@ -0,0 +1,96 @@ +/** + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { isImage } from "../lib/FileType.mjs"; +import { toBase64 } from "../lib/Base64.mjs"; +import { isWorkerEnvironment } from "../Utils.mjs"; +import { Jimp, JimpMime } from "jimp"; + +/** + * Image Opacity operation + */ +class ImageOpacity extends Operation { + /** + * ImageOpacity constructor + */ + constructor() { + super(); + + this.name = "图像透明度"; + this.module = "Image"; + this.description = "调整图像透明度。"; + this.infoURL = ""; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.presentType = "html"; + this.args = [ + { + name: "透明度 (%)", + type: "number", + value: 100, + min: 0, + max: 100, + }, + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {byteArray} + */ + async run(input, args) { + const [opacity] = args; + if (!isImage(input)) { + throw new OperationError("无效的文件类型。"); + } + + let image; + try { + image = await Jimp.read(input); + } catch (err) { + throw new OperationError(`载入图像出错:(${err})`); + } + try { + if (isWorkerEnvironment()) + self.sendStatusMessage("调整图像透明度……"); + image.opacity(opacity / 100); + + let imageBuffer; + if (image.mime === "image/gif") { + imageBuffer = await image.getBuffer(JimpMime.png); + } else { + imageBuffer = await image.getBuffer(image.mime); + } + return imageBuffer.buffer; + } catch (err) { + throw new OperationError(`调整图像透明度出错:(${err})`); + } + } + + /** + * Displays the image using HTML for web apps + * @param {ArrayBuffer} data + * @returns {html} + */ + present(data) { + if (!data.byteLength) return ""; + const dataArray = new Uint8Array(data); + + const type = isImage(dataArray); + if (!type) { + throw new OperationError("无效的文件类型。"); + } + + return ``; + } +} + +export default ImageOpacity; diff --git a/plugins/srktoolbox/src/core/operations/IndexOfCoincidence.mjs b/plugins/srktoolbox/src/core/operations/IndexOfCoincidence.mjs new file mode 100644 index 00000000..988b40bf --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/IndexOfCoincidence.mjs @@ -0,0 +1,109 @@ +/** + * @author George O [georgeomnet+cyberchef@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +/** + * Index of Coincidence operation + */ +class IndexOfCoincidence extends Operation { + + /** + * IndexOfCoincidence constructor + */ + constructor() { + super(); + + this.name = "重合因子"; + this.module = "Default"; + this.description = "重合因子(Index of Coincidence, IC)指任意拿出两个字母,两个字母相同的概率。由于英语文本的IC通常大约为0.066,所以IC可以用来推测文本是否为可读文本。IC作为良好的判定条件被用来进行自动化文本频率分析。"; + this.infoURL = "https://wikipedia.org/wiki/Index_of_coincidence"; + this.inputType = "string"; + this.outputType = "number"; + this.presentType = "html"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {number} + */ + run(input, args) { + const text = input.toLowerCase().replace(/[^a-z]/g, ""), + frequencies = new Array(26).fill(0), + alphabet = Utils.expandAlphRange("a-z"); + let coincidence = 0.00, + density = 0.00, + result = 0.00, + i; + + for (i=0; i < alphabet.length; i++) { + frequencies[i] = text.count(alphabet[i]); + } + + for (i=0; i < frequencies.length; i++) { + coincidence += frequencies[i] * (frequencies[i] - 1); + } + + density = frequencies.sum(); + + // Ensure that we don't divide by 0 + if (density < 2) density = 2; + + result = coincidence / (density * (density - 1)); + + return result; + } + + /** + * Displays the IC as a scale bar for web apps. + * + * @param {number} ic + * @returns {html} + */ + present(ic) { + return `重合因子: ${ic} +标准化: ${ic * 26} +

+- 0表示完全随机(所有字符不重复),1代表完全不随机(所有字符相同)。 +- 英语文本的IC通常位于0.067至0.078。 +- 文本的随机度由每个字母和其它字母出现次数相同的概率决定。 + +以下图标显示输入数据的IC。较低的IC值通常表示文本是随机生成的,或被压缩/加密过。 + + + `; + } + +} + +export default IndexOfCoincidence; diff --git a/plugins/srktoolbox/src/core/operations/InvertImage.mjs b/plugins/srktoolbox/src/core/operations/InvertImage.mjs new file mode 100644 index 00000000..a6cffcbc --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/InvertImage.mjs @@ -0,0 +1,87 @@ +/** + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { isImage } from "../lib/FileType.mjs"; +import { toBase64 } from "../lib/Base64.mjs"; +import { isWorkerEnvironment } from "../Utils.mjs"; +import { Jimp, JimpMime } from "jimp"; + +/** + * Invert Image operation + */ +class InvertImage extends Operation { + /** + * InvertImage constructor + */ + constructor() { + super(); + + this.name = "图像反色"; + this.module = "Image"; + this.description = "对图像进行反色处理。"; + this.infoURL = ""; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.presentType = "html"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {byteArray} + */ + async run(input, args) { + if (!isImage(input)) { + throw new OperationError("无效的文件类型。"); + } + + let image; + try { + image = await Jimp.read(input); + } catch (err) { + throw new OperationError(`载入图片错误:(${err})`); + } + try { + if (isWorkerEnvironment()) + self.sendStatusMessage("图像反色……"); + image.invert(); + + let imageBuffer; + if (image.mime === "image/gif") { + imageBuffer = await image.getBuffer(JimpMime.png); + } else { + imageBuffer = await image.getBuffer(image.mime); + } + return imageBuffer.buffer; + } catch (err) { + throw new OperationError(`应用反色效果错误:(${err})`); + } + } + + /** + * Displays the inverted image using HTML for web apps + * @param {ArrayBuffer} data + * @returns {html} + */ + present(data) { + if (!data.byteLength) return ""; + const dataArray = new Uint8Array(data); + + const type = isImage(dataArray); + if (!type) { + throw new OperationError("无效的文件类型。"); + } + + return ``; + } +} + +export default InvertImage; diff --git a/plugins/srktoolbox/src/core/operations/JA3Fingerprint.mjs b/plugins/srktoolbox/src/core/operations/JA3Fingerprint.mjs new file mode 100644 index 00000000..ec0b47c1 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/JA3Fingerprint.mjs @@ -0,0 +1,207 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + * + * JA3 created by Salesforce + * John B. Althouse + * Jeff Atkinson + * Josh Atkins + * + * Algorithm released under the BSD-3-clause licence + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import Stream from "../lib/Stream.mjs"; +import {runHash} from "../lib/Hash.mjs"; + +/** + * JA3 Fingerprint operation + */ +class JA3Fingerprint extends Operation { + + /** + * JA3Fingerprint constructor + */ + constructor() { + super(); + + this.name = "JA3指纹"; + this.module = "Crypto"; + this.description = "使用客户端发起的Client Hello值进行哈希后生成JA3指纹,用于辨识TLS客户端。

输入:TLS Client Hello应用层数据包十六进制流。"; + this.infoURL = "https://engineering.salesforce.com/tls-fingerprinting-with-ja3-and-ja3s-247362855967"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "输入格式", + type: "option", + value: ["十六进制", "Base64", "原始"] + }, + { + name: "输出格式", + type: "option", + value: ["哈希摘要", "JA3字符串", "详细信息"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [inputFormat, outputFormat] = args; + + input = Utils.convertToByteArray(input, inputFormat); + const s = new Stream(new Uint8Array(input)); + + const handshake = s.readInt(1); + if (handshake !== 0x16) + throw new OperationError("没有握手数据。"); + + // Version + s.moveForwardsBy(2); + + // Length + const length = s.readInt(2); + if (s.length !== length + 5) + throw new OperationError("错误的握手长度。"); + + // Handshake type + const handshakeType = s.readInt(1); + if (handshakeType !== 1) + throw new OperationError("不是Client Hello。"); + + // Handshake length + const handshakeLength = s.readInt(3); + if (s.length !== handshakeLength + 9) + throw new OperationError("Client Hello包含的数据太少。"); + + // Hello version + const helloVersion = s.readInt(2); + + // Random + s.moveForwardsBy(32); + + // Session ID + const sessionIDLength = s.readInt(1); + s.moveForwardsBy(sessionIDLength); + + // Cipher suites + const cipherSuitesLength = s.readInt(2); + const cipherSuites = s.getBytes(cipherSuitesLength); + const cs = new Stream(cipherSuites); + const cipherSegment = parseJA3Segment(cs, 2); + + // Compression Methods + const compressionMethodsLength = s.readInt(1); + s.moveForwardsBy(compressionMethodsLength); + + // Extensions + const extensionsLength = s.readInt(2); + const extensions = s.getBytes(extensionsLength); + const es = new Stream(extensions); + let ecsLen, ecs, ellipticCurves = "", ellipticCurvePointFormats = ""; + const exts = []; + while (es.hasMore()) { + const type = es.readInt(2); + const length = es.readInt(2); + switch (type) { + case 0x0a: // Elliptic curves + ecsLen = es.readInt(2); + ecs = new Stream(es.getBytes(ecsLen)); + ellipticCurves = parseJA3Segment(ecs, 2); + break; + case 0x0b: // Elliptic curve point formats + ecsLen = es.readInt(1); + ecs = new Stream(es.getBytes(ecsLen)); + ellipticCurvePointFormats = parseJA3Segment(ecs, 1); + break; + default: + es.moveForwardsBy(length); + } + if (!GREASE_CIPHERSUITES.includes(type)) + exts.push(type); + } + + // Output + const ja3 = [ + helloVersion.toString(), + cipherSegment, + exts.join("-"), + ellipticCurves, + ellipticCurvePointFormats + ]; + const ja3Str = ja3.join(","); + const ja3Hash = runHash("md5", Utils.strToArrayBuffer(ja3Str)); + + switch (outputFormat) { + case "JA3字符串": + return ja3Str; + case "详细信息": + return `哈希摘要: +${ja3Hash} + +完整JA3字符串: +${ja3Str} + +TLS版本: +${helloVersion.toString()} +加密套件: +${cipherSegment} +扩展: +${exts.join("-")} +椭圆曲线: +${ellipticCurves} +椭圆曲线点格式: +${ellipticCurvePointFormats}`; + case "哈希摘要": + default: + return ja3Hash; + } + } + +} + +/** + * Parses a JA3 segment, returning a "-" separated list + * + * @param {Stream} stream + * @returns {string} + */ +function parseJA3Segment(stream, size=2) { + const segment = []; + while (stream.hasMore()) { + const element = stream.readInt(size); + if (!GREASE_CIPHERSUITES.includes(element)) + segment.push(element); + } + return segment.join("-"); +} + +const GREASE_CIPHERSUITES = [ + 0x0a0a, + 0x1a1a, + 0x2a2a, + 0x3a3a, + 0x4a4a, + 0x5a5a, + 0x6a6a, + 0x7a7a, + 0x8a8a, + 0x9a9a, + 0xaaaa, + 0xbaba, + 0xcaca, + 0xdada, + 0xeaea, + 0xfafa +]; + +export default JA3Fingerprint; diff --git a/plugins/srktoolbox/src/core/operations/JA3SFingerprint.mjs b/plugins/srktoolbox/src/core/operations/JA3SFingerprint.mjs new file mode 100644 index 00000000..f16b5cca --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/JA3SFingerprint.mjs @@ -0,0 +1,147 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + * + * JA3S created by Salesforce + * John B. Althouse + * Jeff Atkinson + * Josh Atkins + * + * Algorithm released under the BSD-3-clause licence + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import Stream from "../lib/Stream.mjs"; +import {runHash} from "../lib/Hash.mjs"; + +/** + * JA3S Fingerprint operation + */ +class JA3SFingerprint extends Operation { + + /** + * JA3SFingerprint constructor + */ + constructor() { + super(); + + this.name = "JA3S指纹"; + this.module = "Crypto"; + this.description = "使用服务器端发起的Server Hello值进行哈希后生成JA3S指纹,用于辨识TLS服务器端。

输入:TLS Server Hello应用层数据包十六进制流。"; + this.infoURL = "https://engineering.salesforce.com/tls-fingerprinting-with-ja3-and-ja3s-247362855967"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "输入格式", + type: "option", + value: ["十六进制", "Base64", "原始"] + }, + { + name: "输出格式", + type: "option", + value: ["哈希摘要", "JA3S字符串", "详细信息"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [inputFormat, outputFormat] = args; + + input = Utils.convertToByteArray(input, inputFormat); + const s = new Stream(new Uint8Array(input)); + + const handshake = s.readInt(1); + if (handshake !== 0x16) + throw new OperationError("没有握手数据。"); + + // Version + s.moveForwardsBy(2); + + // Length + const length = s.readInt(2); + if (s.length !== length + 5) + throw new OperationError("错误的握手长度。"); + + // Handshake type + const handshakeType = s.readInt(1); + if (handshakeType !== 2) + throw new OperationError("不是Server Hello。"); + + // Handshake length + const handshakeLength = s.readInt(3); + if (s.length !== handshakeLength + 9) + throw new OperationError("Server Hello包含的数据太少。"); + + // Hello version + const helloVersion = s.readInt(2); + + // Random + s.moveForwardsBy(32); + + // Session ID + const sessionIDLength = s.readInt(1); + s.moveForwardsBy(sessionIDLength); + + // Cipher suite + const cipherSuite = s.readInt(2); + + // Compression Method + s.moveForwardsBy(1); + + // Extensions + const extensionsLength = s.readInt(2); + const extensions = s.getBytes(extensionsLength); + const es = new Stream(extensions); + const exts = []; + while (es.hasMore()) { + const type = es.readInt(2); + const length = es.readInt(2); + es.moveForwardsBy(length); + exts.push(type); + } + + // Output + const ja3s = [ + helloVersion.toString(), + cipherSuite, + exts.join("-") + ]; + const ja3sStr = ja3s.join(","); + const ja3sHash = runHash("md5", Utils.strToArrayBuffer(ja3sStr)); + + switch (outputFormat) { + case "JA3S字符串": + return ja3sStr; + case "详细信息": + return `哈希摘要: +${ja3sHash} + +完整JA3S字符串: +${ja3sStr} + +TLS版本: +${helloVersion.toString()} +加密套件: +${cipherSuite} +扩展: +${exts.join("-")}`; + case "哈希摘要": + default: + return ja3sHash; + } + } + +} + +export default JA3SFingerprint; diff --git a/plugins/srktoolbox/src/core/operations/JA4Fingerprint.mjs b/plugins/srktoolbox/src/core/operations/JA4Fingerprint.mjs new file mode 100644 index 00000000..05219ea3 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/JA4Fingerprint.mjs @@ -0,0 +1,75 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import {toJA4} from "../lib/JA4.mjs"; + +/** + * JA4 Fingerprint operation + */ +class JA4Fingerprint extends Operation { + + /** + * JA4Fingerprint constructor + */ + constructor() { + super(); + + this.name = "JA4指纹"; + this.module = "Crypto"; + this.description = "通过将Client Hello中的值进行哈希,生成用于辨识TLS客户端的JA4指纹。

输入:TLS或QUIC客户端应用层Client Hello包的十六进制流。"; + this.infoURL = "https://medium.com/foxio/ja4-network-fingerprinting-9376fe9ca637"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "输入格式", + type: "option", + value: ["十六进制", "Base64", "原始"] + }, + { + name: "输出格式", + type: "option", + value: ["JA4", "JA4 Original Rendering", "JA4 Raw", "JA4 Raw Original Rendering", "所有"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [inputFormat, outputFormat] = args; + input = Utils.convertToByteArray(input, inputFormat); + const ja4 = toJA4(new Uint8Array(input)); + + // Output + switch (outputFormat) { + case "JA4": + return ja4.JA4; + case "JA4 Original Rendering": + return ja4.JA4_o; + case "JA4 Raw": + return ja4.JA4_r; + case "JA4 Raw Original Rendering": + return ja4.JA4_ro; + case "所有": + default: + return `JA4: ${ja4.JA4} +JA4_o: ${ja4.JA4_o} +JA4_r: ${ja4.JA4_r} +JA4_ro: ${ja4.JA4_ro}`; + } + } + +} + +export default JA4Fingerprint; diff --git a/plugins/srktoolbox/src/core/operations/JA4ServerFingerprint.mjs b/plugins/srktoolbox/src/core/operations/JA4ServerFingerprint.mjs new file mode 100644 index 00000000..4196176e --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/JA4ServerFingerprint.mjs @@ -0,0 +1,68 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import {toJA4S} from "../lib/JA4.mjs"; + +/** + * JA4Server Fingerprint operation + */ +class JA4ServerFingerprint extends Operation { + + /** + * JA4ServerFingerprint constructor + */ + constructor() { + super(); + + this.name = "JA4S指纹"; + this.module = "Crypto"; + this.description = "生成用于辨识TLS服务器或会话的JA4服务器指纹(JA4S),通过将Server Hello中的值合并后进行哈希计算得出。

输入:TLS或QUIC Server Hello数据包十六进制流。"; + this.infoURL = "https://medium.com/foxio/ja4-network-fingerprinting-9376fe9ca637"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "输入格式", + type: "option", + value: ["十六进制", "Base64", "原始"] + }, + { + name: "输出格式", + type: "option", + value: ["JA4S", "JA4S原始", "全部"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [inputFormat, outputFormat] = args; + input = Utils.convertToByteArray(input, inputFormat); + const ja4s = toJA4S(new Uint8Array(input)); + + // Output + switch (outputFormat) { + case "JA4S": + return ja4s.JA4S; + case "JA4S原始": + return ja4s.JA4S_r; + case "全部": + default: + return `JA4S: ${ja4s.JA4S}\nJA4S_r: ${ja4s.JA4S_r}`; + } + } + +} + +export default JA4ServerFingerprint; diff --git a/plugins/srktoolbox/src/core/operations/JPathExpression.mjs b/plugins/srktoolbox/src/core/operations/JPathExpression.mjs new file mode 100644 index 00000000..5765f762 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/JPathExpression.mjs @@ -0,0 +1,73 @@ +/** + * @author Matt C (matt@artemisbot.uk) + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import {JSONPath} from "jsonpath-plus"; +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * JPath expression operation + */ +class JPathExpression extends Operation { + + /** + * JPathExpression constructor + */ + constructor() { + super(); + + this.name = "JPath表达式"; + this.module = "Code"; + this.description = "从JSON object中使用给定的JPath表达式进行查询并提取内容。"; + this.infoURL = "http://goessner.net/articles/JsonPath/"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "JPath", + type: "string", + value: "" + }, + { + name: "查询结果分隔符", + type: "binaryShortString", + value: "\\n" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [query, delimiter] = args; + let results, jsonObj; + + try { + jsonObj = JSON.parse(input); + } catch (err) { + throw new OperationError(`无效的JSON: ${err.message}`); + } + + try { + results = JSONPath({ + path: query, + json: jsonObj + }); + } catch (err) { + throw new OperationError(`无效的JPath表达式: ${err.message}`); + } + + return results.map(result => JSON.stringify(result)).join(delimiter); + } + +} + +export default JPathExpression; diff --git a/plugins/srktoolbox/src/core/operations/JSONBeautify.mjs b/plugins/srktoolbox/src/core/operations/JSONBeautify.mjs new file mode 100644 index 00000000..357274c3 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/JSONBeautify.mjs @@ -0,0 +1,246 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @author Phillip Nordwall [phillip.nordwall@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import JSON5 from "json5"; +import OperationError from "../errors/OperationError.mjs"; +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +/** + * JSON Beautify operation + */ +class JSONBeautify extends Operation { + + /** + * JSONBeautify constructor + */ + constructor() { + super(); + + this.name = "JSON美化"; + this.module = "Code"; + this.description = "为JavaScript Object Notation (JSON)代码添加缩进与美化。

标签: json viewer, prettify, syntax highlighting"; + this.inputType = "string"; + this.outputType = "string"; + this.presentType = "html"; + this.args = [ + { + name: "缩进", + type: "binaryShortString", + value: " " + }, + { + name: "按键值排序", + type: "boolean", + value: false + }, + { + name: "格式化", + type: "boolean", + value: true + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + if (!input) return ""; + + const [indentStr, sortBool] = args; + let json = null; + + try { + json = JSON5.parse(input); + } catch (err) { + throw new OperationError("无法解析JSON。\n" + err); + } + + if (sortBool) json = sortKeys(json); + + return JSON.stringify(json, null, indentStr); + } + + /** + * Adds various dynamic features to the JSON blob + * + * @param {string} data + * @param {Object[]} args + * @returns {html} + */ + present(data, args) { + const formatted = args[2]; + if (!formatted) return Utils.escapeHtml(data); + + const json = JSON5.parse(data); + const options = { + withLinks: true, + bigNumbers: true + }; + let html = '
'; + + if (isCollapsable(json)) { + const isArr = json instanceof Array; + html += '
' + + `` + + json2html(json, options) + + "
"; + } else { + html += json2html(json, options); + } + + html += "
"; + return html; + } +} + +/** + * Sort keys in a JSON object + * + * @author Phillip Nordwall [phillip.nordwall@gmail.com] + * @param {object} o + * @returns {object} + */ +function sortKeys(o) { + if (Array.isArray(o)) { + return o.map(sortKeys); + } else if ("[object Object]" === Object.prototype.toString.call(o)) { + return Object.keys(o).sort().reduce(function(a, k) { + a[k] = sortKeys(o[k]); + return a; + }, {}); + } + return o; +} + + +/** + * Check if arg is either an array with at least 1 element, or a dict with at least 1 key + * @returns {boolean} + */ +function isCollapsable(arg) { + return arg instanceof Object && Object.keys(arg).length > 0; +} + +/** + * Check if a string looks like a URL, based on protocol + * @returns {boolean} + */ +function isUrl(string) { + const protocols = ["http", "https", "ftp", "ftps"]; + for (let i = 0; i < protocols.length; i++) { + if (string.startsWith(protocols[i] + "://")) { + return true; + } + } + return false; +} + +/** + * Transform a json object into html representation + * + * Adapted for CyberChef by @n1474335 from jQuery json-viewer + * @author Alexandre Bodelot + * @link https://github.com/abodelot/jquery.json-viewer + * @license MIT + * + * @returns {string} + */ +function json2html(json, options) { + let html = ""; + if (typeof json === "string") { + // Escape tags and quotes + json = Utils.escapeHtml(json); + + if (options.withLinks && isUrl(json)) { + html += `
${json}`; + } else { + // Escape double quotes in the rendered non-URL string. + json = json.replace(/"/g, "\\""); + html += `"${json}"`; + } + } else if (typeof json === "number" || typeof json === "bigint") { + html += `${json}`; + } else if (typeof json === "boolean") { + html += `${json}`; + } else if (json === null) { + html += 'null'; + } else if (json instanceof Array) { + if (json.length > 0) { + html += '[
    '; + for (let i = 0; i < json.length; i++) { + html += "
  1. "; + + // Add toggle button if item is collapsable + if (isCollapsable(json[i])) { + const isArr = json[i] instanceof Array; + html += '
    ' + + `` + + json2html(json[i], options) + + "
    "; + } else { + html += json2html(json[i], options); + } + + // Add comma if item is not last + if (i < json.length - 1) { + html += ','; + } + html += "
  2. "; + } + html += '
]'; + } else { + html += '[]'; + } + } else if (typeof json === "object") { + // Optional support different libraries for big numbers + // json.isLosslessNumber: package lossless-json + // json.toExponential(): packages bignumber.js, big.js, decimal.js, decimal.js-light, others? + if (options.bigNumbers && (typeof json.toExponential === "function" || json.isLosslessNumber)) { + html += `${json.toString()}`; + } else { + let keyCount = Object.keys(json).length; + if (keyCount > 0) { + html += '{
    '; + for (const key in json) { + if (Object.prototype.hasOwnProperty.call(json, key)) { + const safeKey = Utils.escapeHtml(key); + html += "
  • "; + + // Add toggle button if item is collapsable + if (isCollapsable(json[key])) { + const isArr = json[key] instanceof Array; + html += '
    ' + + `${safeKey}: ` + + json2html(json[key], options) + + "
    "; + } else { + html += safeKey + ': ' + json2html(json[key], options); + } + + // Add comma if item is not last + if (--keyCount > 0) { + html += ','; + } + html += "
  • "; + } + } + html += '
}'; + } else { + html += '{}'; + } + } + } + return html; +} + +export default JSONBeautify; diff --git a/plugins/srktoolbox/src/core/operations/JSONMinify.mjs b/plugins/srktoolbox/src/core/operations/JSONMinify.mjs new file mode 100644 index 00000000..23dc2acc --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/JSONMinify.mjs @@ -0,0 +1,43 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import vkbeautify from "vkbeautify"; +import Operation from "../Operation.mjs"; + +/** + * JSON Minify operation + */ +class JSONMinify extends Operation { + + /** + * JSONMinify constructor + */ + constructor() { + super(); + + this.name = "JSON压缩"; + this.module = "Code"; + this.description = "压缩JavaScript Object Notation (JSON)代码(Minify/Uglify)。"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + if (!input) return ""; + return vkbeautify.jsonmin(input); + } + +} + +export default JSONMinify; diff --git a/plugins/srktoolbox/src/core/operations/JSONToCSV.mjs b/plugins/srktoolbox/src/core/operations/JSONToCSV.mjs new file mode 100644 index 00000000..54a5d3fd --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/JSONToCSV.mjs @@ -0,0 +1,144 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import * as flat from "flat"; +const flatten = flat.default ? flat.default.flatten : flat.flatten; + +/** + * JSON to CSV operation + */ +class JSONToCSV extends Operation { + + /** + * JSONToCSV constructor + */ + constructor() { + super(); + + this.name = "JSON转CSV"; + this.module = "Default"; + this.description = "把JSON转换为CSV格式(RFC 4180)。"; + this.infoURL = "https://wikipedia.org/wiki/Comma-separated_values"; + this.inputType = "JSON"; + this.outputType = "string"; + this.args = [ + { + name: "单元格分隔符", + type: "binaryShortString", + value: "," + }, + { + name: "行分隔符", + type: "binaryShortString", + value: "\\r\\n" + } + ]; + } + + /** + * Converts JSON to a CSV equivalent. + * + * @param {boolean} force - Whether to force conversion of data to fit in a cell + * @returns {string} + */ + toCSV(force=false) { + const self = this; + // If the JSON is an array of arrays, this is easy + if (this.flattened[0] instanceof Array) { + return this.flattened + .map(row => row + .map(d => self.escapeCellContents(d, force)) + .join(this.cellDelim) + ) + .join(this.rowDelim) + + this.rowDelim; + } + + // If it's an array of dictionaries... + const header = Object.keys(this.flattened[0]); + return header + .map(d => self.escapeCellContents(d, force)) + .join(this.cellDelim) + + this.rowDelim + + this.flattened + .map(row => header + .map(h => row[h]) + .map(d => self.escapeCellContents(d, force)) + .join(this.cellDelim) + ) + .join(this.rowDelim) + + this.rowDelim; + } + + /** + * @param {JSON} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [cellDelim, rowDelim] = args; + + // Record values so they don't have to be passed to other functions explicitly + this.cellDelim = cellDelim; + this.rowDelim = rowDelim; + this.flattened = input; + if (!(this.flattened instanceof Array)) { + this.flattened = [input]; + } + + try { + return this.toCSV(); + } catch (err) { + try { + this.flattened = flatten(input); + if (!(this.flattened instanceof Array)) { + this.flattened = [this.flattened]; + } + return this.toCSV(true); + } catch (err) { + throw new OperationError("无法转换JSON到CSV:" + err.toString()); + } + } + } + + /** + * Correctly escapes a cell's contents based on the cell and row delimiters. + * + * @param {string} data + * @param {boolean} force - Whether to force conversion of data to fit in a cell + * @returns {string} + */ + escapeCellContents(data, force=false) { + if (data !== "string") { + const isPrimitive = data == null || typeof data !== "object"; + if (isPrimitive) data = `${data}`; + else if (force) data = JSON.stringify(data); + } + + // Double quotes should be doubled up + data = data.replace(/"/g, '""'); + + // If the cell contains a cell or row delimiter or a double quote, it must be enclosed in double quotes + if ( + data.indexOf(this.cellDelim) >= 0 || + data.indexOf(this.rowDelim) >= 0 || + data.indexOf("\n") >= 0 || + data.indexOf("\r") >= 0 || + data.indexOf('"') >= 0 + ) { + data = `"${data}"`; + } + + return data; + } + +} + +export default JSONToCSV; diff --git a/plugins/srktoolbox/src/core/operations/JSONtoYAML.mjs b/plugins/srktoolbox/src/core/operations/JSONtoYAML.mjs new file mode 100644 index 00000000..e573a3a8 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/JSONtoYAML.mjs @@ -0,0 +1,48 @@ +/** + * @author ccarpo [ccarpo@gmx.net] + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + * + * Modified by Raka-loah@github + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import YAML from "yaml"; + +/** + * JSON to YAML operation + */ +class JSONtoYAML extends Operation { + + /** + * JSONtoYAML constructor + */ + constructor() { + super(); + + this.name = "JSON转YAML"; + this.module = "Default"; + this.description = "将JSON对象转换为YAML"; + this.infoURL = "https://en.wikipedia.org/wiki/YAML"; + this.inputType = "JSON"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {JSON} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + try { + return YAML.stringify(input); + } catch (err) { + throw new OperationError("转换失败"); + } + } + +} + +export default JSONtoYAML; diff --git a/plugins/srktoolbox/src/core/operations/JWKToPem.mjs b/plugins/srktoolbox/src/core/operations/JWKToPem.mjs new file mode 100644 index 00000000..ec971a74 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/JWKToPem.mjs @@ -0,0 +1,82 @@ +/** + * @author cplussharp + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import r from "jsrsasign"; +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * PEM to JWK operation + */ +class PEMToJWK extends Operation { + + /** + * PEMToJWK constructor + */ + constructor() { + super(); + + this.name = "JWK转PEM"; + this.module = "PublicKey"; + this.description = "将JSON Web Key格式转换为PEM格式(PKCS#8)。"; + this.infoURL = "https://datatracker.ietf.org/doc/html/rfc7517"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + this.checks = [ + { + "pattern": "\"kty\":\\s*\"(EC|RSA)\"", + "flags": "gm", + "args": [] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const inputJson = JSON.parse(input); + + let keys = []; + if (Array.isArray(inputJson)) { + // list of keys => transform all keys + keys = inputJson; + } else if (Array.isArray(inputJson.keys)) { + // JSON Web Key Set => transform all keys + keys = inputJson.keys; + } else if (typeof inputJson === "object") { + // single key + keys.push(inputJson); + } else { + throw new OperationError("输入内容不是有效的JSON Web Key"); + } + + let output = ""; + for (let i=0; i" + e.message); + } + return result; + } + +} + +export default JavaScriptBeautify; diff --git a/plugins/srktoolbox/src/core/operations/JavaScriptMinify.mjs b/plugins/srktoolbox/src/core/operations/JavaScriptMinify.mjs new file mode 100644 index 00000000..161026b6 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/JavaScriptMinify.mjs @@ -0,0 +1,47 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import OperationError from "../errors/OperationError.mjs"; +import Operation from "../Operation.mjs"; +import * as terser from "terser"; + +/** + * JavaScript Minify operation + */ +class JavaScriptMinify extends Operation { + + /** + * JavaScriptMinify constructor + */ + constructor() { + super(); + + this.name = "JavaScript压缩"; + this.module = "Code"; + this.description = "压缩JavaScript代码(Minify/Uglify)。"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + const result = await terser.minify(input); + if (result.error) { + throw new OperationError(`压缩JavaScript时出错。 (${result.error})`); + } + return result.code; + } + +} + +export default JavaScriptMinify; diff --git a/plugins/srktoolbox/src/core/operations/JavaScriptParser.mjs b/plugins/srktoolbox/src/core/operations/JavaScriptParser.mjs new file mode 100644 index 00000000..141170ca --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/JavaScriptParser.mjs @@ -0,0 +1,80 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import * as esprima from "esprima"; + +/** + * JavaScript Parser operation + */ +class JavaScriptParser extends Operation { + + /** + * JavaScriptParser constructor + */ + constructor() { + super(); + + this.name = "JavaScript解析"; + this.module = "Code"; + this.description = "返回合法Javascript代码的抽象语法树(Abstract Syntax Tree, AST)。"; + this.infoURL = "https://wikipedia.org/wiki/Abstract_syntax_tree"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "位置信息", + "type": "boolean", + "value": false + }, + { + "name": "范围信息", + "type": "boolean", + "value": false + }, + { + "name": "包括token数组", + "type": "boolean", + "value": false + }, + { + "name": "包括注释数组", + "type": "boolean", + "value": false + }, + { + "name": "报告错误并尝试继续", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [parseLoc, parseRange, parseTokens, parseComment, parseTolerant] = args, + options = { + loc: parseLoc, + range: parseRange, + tokens: parseTokens, + comment: parseComment, + tolerant: parseTolerant + }; + let result = {}; + + result = esprima.parseScript(input, options); + return JSON.stringify(result, null, 2); + } + +} + +export default JavaScriptParser; diff --git a/plugins/srktoolbox/src/core/operations/Jq.mjs b/plugins/srktoolbox/src/core/operations/Jq.mjs new file mode 100644 index 00000000..1bca07af --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Jq.mjs @@ -0,0 +1,59 @@ +/** + * @author zhzy0077 [zhzy0077@hotmail.com] + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import jq from "jq-web"; + +/** + * jq operation + */ +class Jq extends Operation { + + /** + * Jq constructor + */ + constructor() { + super(); + + this.name = "Jq"; + this.module = "Jq"; + this.description = "jq是一款轻量且灵活的命令行JSON处理工具。"; + this.infoURL = "https://github.com/jqlang/jq"; + this.inputType = "JSON"; + this.outputType = "string"; + this.args = [ + { + name: "查询", + type: "string", + value: "" + } + ]; + } + + /** + * @param {JSON} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [query] = args; + let result; + + try { + result = jq.json(input, query); + } catch (err) { + throw new OperationError(`无效的jq表达式:${err.message}`); + } + + return JSON.stringify(result); + } + +} + +export default Jq; diff --git a/plugins/srktoolbox/src/core/operations/Jsonata.mjs b/plugins/srktoolbox/src/core/operations/Jsonata.mjs new file mode 100644 index 00000000..4ab0fe9c --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Jsonata.mjs @@ -0,0 +1,67 @@ +/** + * @author Jon K (jon@ajarsoftware.com) + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import jsonata from "jsonata"; +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Jsonata Query operation + */ +class JsonataQuery extends Operation { + /** + * JsonataQuery constructor + */ + constructor() { + super(); + + this.name = "Jsonata查询"; + this.module = "Code"; + this.description = + "使用Jsonata表达式查询与转换JSON数据。"; + this.infoURL = "https://docs.jsonata.org/overview.html"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "查询", + type: "text", + value: "string", + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + const [query] = args; + let result, jsonObj; + + try { + jsonObj = JSON.parse(input); + } catch (err) { + throw new OperationError(`输入的JSON无效:${err.message}`); + } + + try { + const expression = jsonata(query); + result = await expression.evaluate(jsonObj); + } catch (err) { + throw new OperationError( + `无效的Jsonata表达式:${err.message}` + ); + } + + return JSON.stringify(result === undefined ? "" : result); + } +} + +export default JsonataQuery; diff --git a/plugins/srktoolbox/src/core/operations/Jump.mjs b/plugins/srktoolbox/src/core/operations/Jump.mjs new file mode 100644 index 00000000..7ed3ec57 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Jump.mjs @@ -0,0 +1,68 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import { getLabelIndex } from "../lib/FlowControl.mjs"; + +/** + * Jump operation + */ +class Jump extends Operation { + + /** + * Jump constructor + */ + constructor() { + super(); + + this.name = "Jump"; + this.flowControl = true; + this.module = "Default"; + this.description = "跳转到特定的Label位置。"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Label名称", + "type": "string", + "value": "" + }, + { + "name": "最大跳转次数(用于向后跳转)", + "type": "number", + "value": 10 + } + ]; + } + + /** + * @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. + */ + run(state) { + const ings = state.opList[state.progress].ingValues; + const [label, maxJumps] = ings; + const jmpIndex = getLabelIndex(label, state); + + if (state.numJumps >= maxJumps || jmpIndex === -1) { + state.numJumps = 0; + return state; + } + + state.progress = jmpIndex; + state.numJumps++; + return state; + } + +} + +export default Jump; diff --git a/plugins/srktoolbox/src/core/operations/Keccak.mjs b/plugins/srktoolbox/src/core/operations/Keccak.mjs new file mode 100644 index 00000000..860cab53 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Keccak.mjs @@ -0,0 +1,70 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import JSSHA3 from "js-sha3"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Keccak operation + */ +class Keccak extends Operation { + + /** + * Keccak constructor + */ + constructor() { + super(); + + this.name = "Keccak"; + this.module = "Crypto"; + this.description = "Keccak是一个加密散列算法,由 Guido Bertoni,Joan Daemen,Michaël Peeters,以及Gilles Van Assche在RadioGatún上设计。2012年10月2日,Keccak被选为NIST散列函数竞赛的胜利者。

此版本算法为Keccak[c=2d],和SHA-3略有不同。"; + this.infoURL = "https://wikipedia.org/wiki/SHA-3"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "长度", + "type": "option", + "value": ["512", "384", "256", "224"] + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const size = parseInt(args[0], 10); + let algo; + + switch (size) { + case 224: + algo = JSSHA3.keccak224; + break; + case 384: + algo = JSSHA3.keccak384; + break; + case 256: + algo = JSSHA3.keccak256; + break; + case 512: + algo = JSSHA3.keccak512; + break; + default: + throw new OperationError("无效长度"); + } + + return algo(input); + } + +} + +export default Keccak; diff --git a/plugins/srktoolbox/src/core/operations/LMHash.mjs b/plugins/srktoolbox/src/core/operations/LMHash.mjs new file mode 100644 index 00000000..cd215993 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/LMHash.mjs @@ -0,0 +1,43 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import {smbhash} from "ntlm"; + +/** + * LM Hash operation + */ +class LMHash extends Operation { + + /** + * LMHash constructor + */ + constructor() { + super(); + + this.name = "LM哈希"; + this.module = "Crypto"; + this.description = "LM哈希,又叫LAN Manager哈希,是较旧的微软操作系统使用的存储密码方式,目前已被废弃。其安全性非常弱,使用现代硬件设备并利用彩虹表能在几秒之内完成破解。"; + this.infoURL = "https://wikipedia.org/wiki/LAN_Manager#Password_hashing_algorithm"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return smbhash.lmhash(input); + } + +} + +export default LMHash; diff --git a/plugins/srktoolbox/src/core/operations/LS47Decrypt.mjs b/plugins/srktoolbox/src/core/operations/LS47Decrypt.mjs new file mode 100644 index 00000000..25949ab3 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/LS47Decrypt.mjs @@ -0,0 +1,59 @@ +/** + * @author n1073645 [n1073645@gmail.com] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import * as LS47 from "../lib/LS47.mjs"; + +/** + * LS47 Decrypt operation + */ +class LS47Decrypt extends Operation { + + /** + * LS47Decrypt constructor + */ + constructor() { + super(); + + this.name = "LS47解密"; + this.module = "Crypto"; + this.description = "LS47是对Alan Kaminsky的ElsieFour加密进行少量优化后的版本。为了编码更为详尽的信息,把原版的6x6网格(几乎不够用)扩充到了7x7。同时提供了一种简单的key扩充算法,因为没人喜欢记密码。和ElsieFour有着类似的安全性考虑。
LS47字母表包括以下字符: _abcdefghijklmnopqrstuvwxyz.0123456789,-+*/:?!'()
LS47 key是字母表49个字符的排列,同时用于加解密。"; + this.infoURL = "https://github.com/exaexa/ls47"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "密码", + type: "string", + value: "" + }, + { + name: "填充", + type: "number", + value: 10 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + this.paddingSize = parseInt(args[1], 10); + + LS47.initTiles(); + + const key = LS47.deriveKey(args[0]); + return LS47.decryptPad(key, input, this.paddingSize); + } + +} + +export default LS47Decrypt; diff --git a/plugins/srktoolbox/src/core/operations/LS47Encrypt.mjs b/plugins/srktoolbox/src/core/operations/LS47Encrypt.mjs new file mode 100644 index 00000000..3d79dd06 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/LS47Encrypt.mjs @@ -0,0 +1,64 @@ +/** + * @author n1073645 [n1073645@gmail.com] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import * as LS47 from "../lib/LS47.mjs"; + +/** + * LS47 Encrypt operation + */ +class LS47Encrypt extends Operation { + + /** + * LS47Encrypt constructor + */ + constructor() { + super(); + + this.name = "LS47加密"; + this.module = "Crypto"; + this.description = "LS47是对Alan Kaminsky的ElsieFour加密进行少量优化后的版本。为了编码更为详尽的信息,把原版的6x6网格(几乎不够用)扩充到了7x7。同时提供了一种简单的key扩充算法,因为没人喜欢记密码。和ElsieFour有着类似的安全性考虑。
LS47字母表包括以下字符: _abcdefghijklmnopqrstuvwxyz.0123456789,-+*/:?!'()
LS47 key是字母表49个字符的排列,同时用于加解密。"; + this.infoURL = "https://github.com/exaexa/ls47"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "密码", + type: "string", + value: "" + }, + { + name: "填充", + type: "number", + value: 10 + }, + { + name: "签名", + type: "string", + value: "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + this.paddingSize = parseInt(args[1], 10); + + LS47.initTiles(); + + const key = LS47.deriveKey(args[0]); + return LS47.encryptPad(key, input, args[2], this.paddingSize); + } + +} + +export default LS47Encrypt; diff --git a/plugins/srktoolbox/src/core/operations/LZ4Compress.mjs b/plugins/srktoolbox/src/core/operations/LZ4Compress.mjs new file mode 100644 index 00000000..eb53bfcf --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/LZ4Compress.mjs @@ -0,0 +1,45 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import lz4 from "lz4js"; + +/** + * LZ4 Compress operation + */ +class LZ4Compress extends Operation { + + /** + * LZ4Compress constructor + */ + constructor() { + super(); + + this.name = "LZ4压缩"; + this.module = "Compression"; + this.description = "LZ4是一种无损数据压缩算法,着重于压缩和解压缩速度。它属于面向字节的LZ77压缩方案家族。"; + this.infoURL = "https://wikipedia.org/wiki/LZ4_(compression_algorithm)"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + const inBuf = new Uint8Array(input); + const compressed = lz4.compress(inBuf); + return compressed.buffer; + } + +} + +export default LZ4Compress; diff --git a/plugins/srktoolbox/src/core/operations/LZ4Decompress.mjs b/plugins/srktoolbox/src/core/operations/LZ4Decompress.mjs new file mode 100644 index 00000000..95598fb9 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/LZ4Decompress.mjs @@ -0,0 +1,45 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import lz4 from "lz4js"; + +/** + * LZ4 Decompress operation + */ +class LZ4Decompress extends Operation { + + /** + * LZ4Decompress constructor + */ + constructor() { + super(); + + this.name = "LZ4解压"; + this.module = "Compression"; + this.description = "LZ4是一种无损数据压缩算法,着重于压缩和解压缩速度。它属于面向字节的LZ77压缩方案家族。"; + this.infoURL = "https://wikipedia.org/wiki/LZ4_(compression_algorithm)"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + const inBuf = new Uint8Array(input); + const decompressed = lz4.decompress(inBuf); + return decompressed.buffer; + } + +} + +export default LZ4Decompress; diff --git a/plugins/srktoolbox/src/core/operations/LZMACompress.mjs b/plugins/srktoolbox/src/core/operations/LZMACompress.mjs new file mode 100644 index 00000000..2c5a7eb4 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/LZMACompress.mjs @@ -0,0 +1,66 @@ +/** + * @author Matt C [me@mitt.dev] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +import { compress } from "@blu3r4y/lzma"; +import {isWorkerEnvironment} from "../Utils.mjs"; + +/** + * LZMA Compress operation + */ +class LZMACompress extends Operation { + + /** + * LZMACompress constructor + */ + constructor() { + super(); + + this.name = "LZMA压缩"; + this.module = "Compression"; + this.description = "使用Lempel\u2013Ziv\u2013Markov chain算法压缩数据。压缩模式指定压缩的速度与效率:1为最快但压缩率最低,9最慢但压缩率最高。"; + this.infoURL = "https://wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Markov_chain_algorithm"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = [ + { + name: "压缩模式", + type: "option", + value: [ + "1", "2", "3", "4", "5", "6", "7", "8", "9" + ], + "defaultIndex": 6 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + async run(input, args) { + const mode = Number(args[0]); + return new Promise((resolve, reject) => { + compress(new Uint8Array(input), mode, (result, error) => { + if (error) { + reject(new OperationError(`压缩失败: ${error.message}`)); + } + // The compression returns as an Int8Array, but we can just get the unsigned data from the buffer + resolve(new Int8Array(result).buffer); + }, (percent) => { + if (isWorkerEnvironment()) self.sendStatusMessage(`压缩输入数据: ${(percent*100).toFixed(2)}%`); + }); + }); + } + +} + +export default LZMACompress; diff --git a/plugins/srktoolbox/src/core/operations/LZMADecompress.mjs b/plugins/srktoolbox/src/core/operations/LZMADecompress.mjs new file mode 100644 index 00000000..5cfc0ab6 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/LZMADecompress.mjs @@ -0,0 +1,59 @@ +/** + * @author Matt C [me@mitt.dev] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import {decompress} from "@blu3r4y/lzma"; +import Utils, {isWorkerEnvironment} from "../Utils.mjs"; + +/** + * LZMA Decompress operation + */ +class LZMADecompress extends Operation { + + /** + * LZMADecompress constructor + */ + constructor() { + super(); + + this.name = "LZMA解压"; + this.module = "Compression"; + this.description = "解压使用LZMA算法压缩的数据。"; + this.infoURL = "https://wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Markov_chain_algorithm"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + async run(input, args) { + return new Promise((resolve, reject) => { + decompress(new Uint8Array(input), (result, error) => { + if (error) { + reject(new OperationError(`解压失败: ${error.message}`)); + } + // The decompression returns either a String or an untyped unsigned int8 array, but we can just get the unsigned data from the buffer + + if (typeof result == "string") { + resolve(Utils.strToArrayBuffer(result)); + } else { + resolve(new Int8Array(result).buffer); + } + }, (percent) => { + if (isWorkerEnvironment()) self.sendStatusMessage(`解压: ${(percent*100).toFixed(2)}%`); + }); + }); + } + +} + +export default LZMADecompress; diff --git a/plugins/srktoolbox/src/core/operations/LZNT1Decompress.mjs b/plugins/srktoolbox/src/core/operations/LZNT1Decompress.mjs new file mode 100644 index 00000000..c7273b93 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/LZNT1Decompress.mjs @@ -0,0 +1,43 @@ +/** + * @author 0xThiebaut [thiebaut.dev] + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import {decompress} from "../lib/LZNT1.mjs"; + +/** + * LZNT1 Decompress operation + */ +class LZNT1Decompress extends Operation { + + /** + * LZNT1 Decompress constructor + */ + constructor() { + super(); + + this.name = "LZNT1解压"; + this.module = "Compression"; + this.description = "使用LZNT1算法解压数据。

功能和Windows API RtlDecompressBuffer类似。"; + this.infoURL = "https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-xca/5655f4a3-6ba4-489b-959f-e1f407c52f15"; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = []; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + return decompress(input); + } + +} + +export default LZNT1Decompress; diff --git a/plugins/srktoolbox/src/core/operations/LZStringCompress.mjs b/plugins/srktoolbox/src/core/operations/LZStringCompress.mjs new file mode 100644 index 00000000..7ef8e646 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/LZStringCompress.mjs @@ -0,0 +1,57 @@ +/** + * @author crespyl [peter@crespyl.net] + * @copyright Peter Jacobs 2021 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +import {COMPRESSION_OUTPUT_FORMATS, COMPRESSION_FUNCTIONS} from "../lib/LZString.mjs"; + +/** + * LZString Compress operation + */ +class LZStringCompress extends Operation { + + /** + * LZStringCompress constructor + */ + constructor() { + super(); + + this.name = "LZString压缩"; + this.module = "Compression"; + this.description = "使用 lz-string 算法压缩输入内容。"; + this.infoURL = "https://pieroxy.net/blog/pages/lz-string/index.html"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "压缩格式", + type: "option", + defaultIndex: 0, + value: COMPRESSION_OUTPUT_FORMATS + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const compress = COMPRESSION_FUNCTIONS[args[0]]; + if (compress) { + return compress(input); + } else { + throw new OperationError("压缩功能不可用"); + } + } + +} + +export default LZStringCompress; diff --git a/plugins/srktoolbox/src/core/operations/LZStringDecompress.mjs b/plugins/srktoolbox/src/core/operations/LZStringDecompress.mjs new file mode 100644 index 00000000..8b5159b1 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/LZStringDecompress.mjs @@ -0,0 +1,58 @@ +/** + * @author crespyl [peter@crespyl.net] + * @copyright Peter Jacobs 2021 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +import {COMPRESSION_OUTPUT_FORMATS, DECOMPRESSION_FUNCTIONS} from "../lib/LZString.mjs"; + +/** + * LZString Decompress operation + */ +class LZStringDecompress extends Operation { + + /** + * LZStringDecompress constructor + */ + constructor() { + super(); + + this.name = "LZString解压"; + this.module = "Compression"; + this.description = "解压使用 lz-string 算法压缩的数据。"; + this.infoURL = "https://pieroxy.net/blog/pages/lz-string/index.html"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "压缩格式", + type: "option", + defaultIndex: 0, + value: COMPRESSION_OUTPUT_FORMATS + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const decompress = DECOMPRESSION_FUNCTIONS[args[0]]; + if (decompress) { + return decompress(input); + } else { + throw new OperationError("压缩功能不可用"); + } + } + + +} + +export default LZStringDecompress; diff --git a/plugins/srktoolbox/src/core/operations/Label.mjs b/plugins/srktoolbox/src/core/operations/Label.mjs new file mode 100644 index 00000000..3e5721af --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Label.mjs @@ -0,0 +1,50 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; + +/** + * Label operation. For use with Jump and Conditional Jump. + */ +class Label extends Operation { + + /** + * Label constructor + */ + constructor() { + super(); + + this.name = "Label"; + this.flowControl = true; + this.module = "Default"; + this.description = "给Conditional和Fixed Jump(跳转操作)提供一个跳转位置。"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "名称", + "type": "shortString", + "value": "" + } + ]; + } + + /** + * @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. + */ + run(state) { + return state; + } + +} + +export default Label; diff --git a/plugins/srktoolbox/src/core/operations/LevenshteinDistance.mjs b/plugins/srktoolbox/src/core/operations/LevenshteinDistance.mjs new file mode 100644 index 00000000..3b45c86e --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/LevenshteinDistance.mjs @@ -0,0 +1,100 @@ +/** + * @author mikecat + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Levenshtein Distance operation + */ +class LevenshteinDistance extends Operation { + + /** + * LevenshteinDistance constructor + */ + constructor() { + super(); + + this.name = "莱文斯坦距离"; + this.module = "Default"; + this.description = "莱文斯坦距离,又称Levenshtein距离,是编辑距离的一种。指两个字串之间,由一个转成另一个所需的最少编辑操作次数。"; + this.infoURL = "https://wikipedia.org/wiki/Levenshtein_distance"; + this.inputType = "string"; + this.outputType = "number"; + this.args = [ + { + name: "分隔符", + type: "binaryString", + value: "\\n" + }, + { + name: "插入操作消耗步数", + type: "number", + value: 1 + }, + { + name: "删除操作消耗步数", + type: "number", + value: 1 + }, + { + name: "替换操作消耗步数", + type: "number", + value: 1 + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {number} + */ + run(input, args) { + const [delim, insCost, delCost, subCost] = args; + const samples = input.split(delim); + if (samples.length !== 2) { + throw new OperationError("错误:计算莱文斯坦距离需要两个字符串,请确保输入按照给定分隔符的两个字符串。"); + } + if (insCost < 0 || delCost < 0 || subCost < 0) { + throw new OperationError("消耗量不能为负数。"); + } + const src = samples[0], dest = samples[1]; + let currentCost = new Array(src.length + 1); + let nextCost = new Array(src.length + 1); + for (let i = 0; i < currentCost.length; i++) { + currentCost[i] = delCost * i; + } + for (let i = 0; i < dest.length; i++) { + const destc = dest.charAt(i); + nextCost[0] = currentCost[0] + insCost; + for (let j = 0; j < src.length; j++) { + let candidate; + // insertion + let optCost = currentCost[j + 1] + insCost; + // deletion + candidate = nextCost[j] + delCost; + if (candidate < optCost) optCost = candidate; + // substitution or matched character + candidate = currentCost[j]; + if (src.charAt(j) !== destc) candidate += subCost; + if (candidate < optCost) optCost = candidate; + // store calculated cost + nextCost[j + 1] = optCost; + } + const tempCost = nextCost; + nextCost = currentCost; + currentCost = tempCost; + } + + return currentCost[currentCost.length - 1]; + } + +} + +export default LevenshteinDistance; diff --git a/plugins/srktoolbox/src/core/operations/Lorenz.mjs b/plugins/srktoolbox/src/core/operations/Lorenz.mjs new file mode 100644 index 00000000..28ac3bf7 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Lorenz.mjs @@ -0,0 +1,763 @@ +/** + * Emulation of the Lorenz SZ40/42a/42b cipher attachment. + * + * Tested against the Colossus Rebuild at Bletchley Park's TNMOC + * using a variety of inputs and settings to confirm correctness. + * + * @author VirtualColossus [martin@virtualcolossus.co.uk] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Lorenz operation + */ +class Lorenz extends Operation { + + /** + * Lorenz constructor + */ + constructor() { + super(); + + this.name = "Lorenz"; + this.module = "Bletchley"; + this.description = "The Lorenz SZ40/42 cipher attachment was a WW2 German rotor cipher machine with twelve rotors which attached in-line between remote teleprinters.

It used the Vernam cipher with two groups of five rotors (named the psi(ψ) wheels and chi(χ) wheels at Bletchley Park) to create two pseudorandom streams of five bits, encoded in ITA2, which were XOR added to the plaintext. Two other rotors, dubbed the mu(μ) or motor wheels, could hold up the stepping of the psi wheels meaning they stepped intermittently.

Each rotor has a different number of cams/lugs around their circumference which could be set active or inactive changing the key stream.

Three models of the Lorenz are emulated, SZ40, SZ42a and SZ42b and three example wheel patterns (the lug settings) are included (KH, ZMUG & BREAM) with the option to set a custom set using the letter 'x' for active or '.' for an inactive lug.

The input can either be plaintext or ITA2 when sending and ITA2 when receiving.

To learn more, Virtual Lorenz, an online, browser based simulation of the Lorenz SZ40/42 is available at lorenz.virtualcolossus.co.uk.

A more detailed description of this operation can be found here."; + this.infoURL = "https://wikipedia.org/wiki/Lorenz_cipher"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Model", + type: "option", + value: ["SZ40", "SZ42a", "SZ42b"] + }, + { + name: "Wheel Pattern", + type: "argSelector", + value: [ + { + name: "KH Pattern", + off: [19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30] + }, + { + name: "ZMUG Pattern", + off: [19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30] + }, + { + name: "BREAM Pattern", + off: [19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30] + }, + { + name: "No Pattern", + off: [19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30] + }, + { + name: "Custom", + on: [19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30] + } + ] + }, + { + name: "KT-Schalter", + type: "boolean", + value: false + }, + { + name: "Mode", + type: "argSelector", + value: [ + { + name: "Send", + on: [4], + off: [5] + }, + { + name: "Receive", + off: [4], + on: [5] + } + ] + }, + { + name: "Input Type", + type: "option", + value: ["Plaintext", "ITA2"] + }, + { + name: "Output Type", + type: "option", + value: ["Plaintext", "ITA2"] + }, + { + name: "ITA2 Format", + type: "option", + value: ["5/8/9", "+/-/."] + }, + { + name: "Ψ1 start (1-43)", + type: "number", + value: 1 + }, + { + name: "Ψ2 start (1-47)", + type: "number", + value: 1 + }, + { + name: "Ψ3 start (1-51)", + type: "number", + value: 1 + }, + { + name: "Ψ4 start (1-53)", + type: "number", + value: 1 + }, + { + name: "Ψ5 start (1-59)", + type: "number", + value: 1 + }, + { + name: "Μ37 start (1-37)", + type: "number", + value: 1 + }, + { + name: "Μ61 start (1-61)", + type: "number", + value: 1 + }, + { + name: "Χ1 start (1-41)", + type: "number", + value: 1 + }, + { + name: "Χ2 start (1-31)", + type: "number", + value: 1 + }, + { + name: "Χ3 start (1-29)", + type: "number", + value: 1 + }, + { + name: "Χ4 start (1-26)", + type: "number", + value: 1 + }, + { + name: "Χ5 start (1-23)", + type: "number", + value: 1 + }, + { + name: "Ψ1 lugs (43)", + type: "string", + value: ".x...xx.x.x..xxx.x.x.xxxx.x.x.x.x.x..x.xx.x" + }, + { + name: "Ψ2 lugs (47)", + type: "string", + value: ".xx.x.xxx..x.x.x..x.xx.x.xxx.x....x.xx.x.x.x..x" + }, + { + name: "Ψ3 lugs (51)", + type: "string", + value: ".x.x.x..xxx....x.x.xx.x.x.x..xxx.x.x..x.x.xx..x.x.x" + }, + { + name: "Ψ4 lugs (53)", + type: "string", + value: ".xx...xxxxx.x.x.xx...x.xx.x.x..x.x.xx.x..x.x.x.x.x.x." + }, + { + name: "Ψ5 lugs (59)", + type: "string", + value: "xx...xx.x..x.xx.x...x.x.x.x.x.x.x.x.xx..xxxx.x.x...xx.x..x." + }, + { + name: "Μ37 lugs (37)", + type: "string", + value: "x.x.x.x.x.x...x.x.x...x.x.x...x.x...." + }, + { + name: "Μ61 lugs (61)", + type: "string", + value: ".xxxx.xxxx.xxx.xxxx.xx....xxx.xxxx.xxxx.xxxx.xxxx.xxx.xxxx..." + }, + { + name: "Χ1 lugs (41)", + type: "string", + value: ".x...xxx.x.xxxx.x...x.x..xxx....xx.xxxx.." + }, + { + name: "Χ2 lugs (31)", + type: "string", + value: "x..xxx...x.xxxx..xx..x..xx.xx.." + }, + { + name: "Χ3 lugs (29)", + type: "string", + value: "..xx..x.xxx...xx...xx..xx.xx." + }, + { + name: "Χ4 lugs (26)", + type: "string", + value: "xx..x..xxxx..xx.xxx....x.." + }, + { + name: "Χ5 lugs (23)", + type: "string", + value: "xx..xx....xxxx.x..x.x.." + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + + const model = args[0], + pattern = args[1], + kt = args[2], + mode = args[3], + intype = args[4], + outtype = args[5], + format = args[6], + lugs1 = args[19], + lugs2 = args[20], + lugs3 = args[21], + lugs4 = args[22], + lugs5 = args[23], + lugm37 = args[24], + lugm61 = args[25], + lugx1 = args[26], + lugx2 = args[27], + lugx3 = args[28], + lugx4 = args[29], + lugx5 = args[30]; + + let s1 = args[7], + s2 = args[8], + s3 = args[9], + s4 = args[10], + s5 = args[11], + m37 = args[12], + m61 = args[13], + x1 = args[14], + x2 = args[15], + x3 = args[16], + x4 = args[17], + x5 = args[18]; + + this.reverseTable(); + + if (s1<1 || s1>43) throw new OperationError("Ψ1 start must be between 1 and 43"); + if (s2<1 || s2>47) throw new OperationError("Ψ2 start must be between 1 and 47"); + if (s3<1 || s3>51) throw new OperationError("Ψ3 start must be between 1 and 51"); + if (s4<1 || s4>53) throw new OperationError("Ψ4 start must be between 1 and 53"); + if (s5<1 || s5>59) throw new OperationError("Ψ5 start must be between 1 and 59"); + if (m37<1 || m37>37) throw new OperationError("Μ37 start must be between 1 and 37"); + if (m61<1 || m61>61) throw new OperationError("Μ61 start must be between 1 and 61"); + if (x1<1 || x1>41) throw new OperationError("Χ1 start must be between 1 and 41"); + if (x2<1 || x2>31) throw new OperationError("Χ2 start must be between 1 and 31"); + if (x3<1 || x3>29) throw new OperationError("Χ3 start must be between 1 and 29"); + if (x4<1 || x4>26) throw new OperationError("Χ4 start must be between 1 and 26"); + if (x5<1 || x5>23) throw new OperationError("Χ5 start must be between 1 and 23"); + + // Initialise chosen wheel pattern + let chosenSetting = ""; + if (pattern === "Custom") { + const re = new RegExp("^[.xX]*$"); + if (lugs1.length !== 43 || !re.test(lugs1)) throw new OperationError("Ψ1 custom lugs must be 43 long and can only include . or x "); + if (lugs2.length !== 47 || !re.test(lugs2)) throw new OperationError("Ψ2 custom lugs must be 47 long and can only include . or x"); + if (lugs3.length !== 51 || !re.test(lugs3)) throw new OperationError("Ψ3 custom lugs must be 51 long and can only include . or x"); + if (lugs4.length !== 53 || !re.test(lugs4)) throw new OperationError("Ψ4 custom lugs must be 53 long and can only include . or x"); + if (lugs5.length !== 59 || !re.test(lugs5)) throw new OperationError("Ψ5 custom lugs must be 59 long and can only include . or x"); + if (lugm37.length !== 37 || !re.test(lugm37)) throw new OperationError("M37 custom lugs must be 37 long and can only include . or x"); + if (lugm61.length !== 61 || !re.test(lugm61)) throw new OperationError("M61 custom lugs must be 61 long and can only include . or x"); + if (lugx1.length !== 41 || !re.test(lugx1)) throw new OperationError("Χ1 custom lugs must be 41 long and can only include . or x"); + if (lugx2.length !== 31 || !re.test(lugx2)) throw new OperationError("Χ2 custom lugs must be 31 long and can only include . or x"); + if (lugx3.length !== 29 || !re.test(lugx3)) throw new OperationError("Χ3 custom lugs must be 29 long and can only include . or x"); + if (lugx4.length !== 26 || !re.test(lugx4)) throw new OperationError("Χ4 custom lugs must be 26 long and can only include . or x"); + if (lugx5.length !== 23 || !re.test(lugx5)) throw new OperationError("Χ5 custom lugs must be 23 long and can only include . or x"); + chosenSetting = INIT_PATTERNS["No Pattern"]; + chosenSetting.S[1] = this.readLugs(lugs1); + chosenSetting.S[2] = this.readLugs(lugs2); + chosenSetting.S[3] = this.readLugs(lugs3); + chosenSetting.S[4] = this.readLugs(lugs4); + chosenSetting.S[5] = this.readLugs(lugs5); + chosenSetting.M[1] = this.readLugs(lugm61); + chosenSetting.M[2] = this.readLugs(lugm37); + chosenSetting.X[1] = this.readLugs(lugx1); + chosenSetting.X[2] = this.readLugs(lugx2); + chosenSetting.X[3] = this.readLugs(lugx3); + chosenSetting.X[4] = this.readLugs(lugx4); + chosenSetting.X[5] = this.readLugs(lugx5); + } else { + chosenSetting = INIT_PATTERNS[pattern]; + } + const chiSettings = chosenSetting.X; // Pin settings for Chi links (X) + const psiSettings = chosenSetting.S; // Pin settings for Psi links (S) + const muSettings = chosenSetting.M; // Pin settings for Motor links (M) + + // Convert input text to ITA2 (including figure/letter shifts) + const ita2Input = this.convertToITA2(input, intype, mode); + + let thisPsi = []; + let thisChi = []; + let m61lug = muSettings[1][m61-1]; + let m37lug = muSettings[2][m37-1]; + const p5 = [0, 0, 0]; + + const self = this; + const letters = Array.prototype.map.call(ita2Input, function(character) { + const letter = character.toUpperCase(); + + // Store lugs used in limitations, need these later + let x2bptr = x2+1; + if (x2bptr===32) x2bptr=1; + let s1bptr = s1+1; + if (s1bptr===44) s1bptr=1; + + thisChi = [ + chiSettings[1][x1-1], + chiSettings[2][x2-1], + chiSettings[3][x3-1], + chiSettings[4][x4-1], + chiSettings[5][x5-1] + ]; + + thisPsi = [ + psiSettings[1][s1-1], + psiSettings[2][s2-1], + psiSettings[3][s3-1], + psiSettings[4][s4-1], + psiSettings[5][s5-1] + ]; + + if (typeof ITA2_TABLE[letter] == "undefined") { + return ""; + } + + // The encipher calculation + + // We calculate Bitwise XOR for each of the 5 bits across our input ( K XOR Psi XOR Chi ) + const xorSum = []; + for (let i=0;i<=4;i++) { + xorSum[i] = ITA2_TABLE[letter][i] ^ thisPsi[i] ^ thisChi[i]; + } + const resultStr = xorSum.join(""); + + // Wheel movement + + // Chi wheels always move one back after each letter + if (--x1 < 1) x1 = 41; + if (--x2 < 1) x2 = 31; + if (--x3 < 1) x3 = 29; + if (--x4 < 1) x4 = 26; + if (--x5 < 1) x5 = 23; + + // Motor wheel (61 pin) also moves one each letter + if (--m61 < 1) m61 = 61; + + // If M61 is set, we also move M37 + if (m61lug === 1) { + if (--m37 < 1) m37 = 37; + } + + // Psi wheels only move sometimes, dependent on M37 current setting and limitations + + const basicmotor = m37lug; + let totalmotor; + let lim = 0; + + p5[2] = p5[1]; + p5[1] = p5[0]; + if (mode==="Send") { + p5[0] = parseInt(ITA2_TABLE[letter][4], 10); + } else { + p5[0] = parseInt(xorSum[4], 10); + } + + // Limitations here + if (model==="SZ42a") { + // Chi 2 one back lim - The active character of Chi 2 (2nd Chi wheel) in the previous position + lim = parseInt(chiSettings[2][x2bptr-1], 10); + if (kt) { + // p5 back 2 + if (lim===p5[2]) { + lim = 0; + } else { + lim=1; + } + } + + // If basic motor = 0 and limitation = 1, Total motor = 0 [no move], otherwise, total motor = 1 [move] + if (basicmotor===0 && lim===1) { + totalmotor = 0; + } else { + totalmotor = 1; + } + + } else if (model==="SZ42b") { + // Chi 2 one back + Psi 1 one back. + const x2b1lug = parseInt(chiSettings[2][x2bptr-1], 10); + const s1b1lug = parseInt(psiSettings[1][s1bptr-1], 10); + lim = 1; + if (x2b1lug===s1b1lug) lim=0; + if (kt) { + // p5 back 2 + if (lim===p5[2]) { + lim=0; + } else { + lim=1; + } + } + // If basic motor = 0 and limitation = 1, Total motor = 0 [no move], otherwise, total motor = 1 [move] + if (basicmotor===0 && lim===1) { + totalmotor = 0; + } else { + totalmotor = 1; + } + + } else if (model==="SZ40") { + // SZ40 - just move based on the M37 motor wheel + totalmotor = basicmotor; + } else { + throw new OperationError("Lorenz model type not recognised"); + } + + // Move the Psi wheels when current totalmotor active + if (totalmotor === 1) { + if (--s1 < 1) s1 = 43; + if (--s2 < 1) s2 = 47; + if (--s3 < 1) s3 = 51; + if (--s4 < 1) s4 = 53; + if (--s5 < 1) s5 = 59; + } + + m61lug = muSettings[1][m61-1]; + m37lug = muSettings[2][m37-1]; + + let rtnstr = self.REVERSE_ITA2_TABLE[resultStr]; + if (format==="5/8/9") { + if (rtnstr==="+") rtnstr="5"; // + or 5 used to represent figure shift + if (rtnstr==="-") rtnstr="8"; // - or 8 used to represent letter shift + if (rtnstr===".") rtnstr="9"; // . or 9 used to represent space + } + return rtnstr; + }); + + const ita2output = letters.join(""); + + return this.convertFromITA2(ita2output, outtype, mode); + + } + + /** + * Reverses the ITA2 Code lookup table + */ + reverseTable() { + this.REVERSE_ITA2_TABLE = {}; + this.REVERSE_FIGSHIFT_TABLE = {}; + + for (const letter in ITA2_TABLE) { + const code = ITA2_TABLE[letter]; + this.REVERSE_ITA2_TABLE[code] = letter; + } + for (const letter in figShiftArr) { + const ltr = figShiftArr[letter]; + this.REVERSE_FIGSHIFT_TABLE[ltr] = letter; + } + } + + /** + * Read lugs settings - convert to 0|1 + */ + readLugs(lugstr) { + const arr = Array.prototype.map.call(lugstr, function(lug) { + if (lug===".") { + return 0; + } else { + return 1; + } + }); + return arr; + } + + /** + * Convert input plaintext to ITA2 + */ + convertToITA2(input, intype, mode) { + let result = ""; + let figShifted = false; + + for (const character of input) { + const letter = character.toUpperCase(); + + // Convert input text to ITA2 (including figure/letter shifts) + if (intype === "ITA2" || mode === "Receive") { + if (validITA2.indexOf(letter) === -1) { + let errltr = letter; + if (errltr==="\n") errltr = "Carriage Return"; + if (errltr===" ") errltr = "Space"; + throw new OperationError("Invalid ITA2 character : "+errltr); + } + result += letter; + } else { + if (validChars.indexOf(letter) === -1) throw new OperationError("Invalid Plaintext character : "+letter); + + if (!figShifted && figShiftedChars.indexOf(letter) !== -1) { + // in letters mode and next char needs to be figure shifted + figShifted = true; + result += "55" + figShiftArr[letter]; + } else if (figShifted) { + // in figures mode and next char needs to be letter shifted + if (letter==="\n") { + result += "34"; + } else if (letter==="\r") { + result += "4"; + } else if (figShiftedChars.indexOf(letter) === -1) { + figShifted = false; + result += "88" + letter; + } else { + result += figShiftArr[letter]; + } + + } else { + if (letter==="\n") { + result += "34"; + } else if (letter==="\r") { + result += "4"; + } else { + result += letter; + } + } + + } + + } + + return result; + } + + /** + * Convert final result ITA2 to plaintext + */ + convertFromITA2(input, outtype, mode) { + let result = ""; + let figShifted = false; + for (const letter of input) { + if (mode === "Receive") { + + // Convert output ITA2 to plaintext (including figure/letter shifts) + if (outtype === "Plaintext") { + + if (letter === "5" || letter === "+") { + figShifted = true; + } else if (letter === "8" || letter === "-") { + figShifted = false; + } else if (letter === "9") { + result += " "; + } else if (letter === "3") { + result += "\n"; + } else if (letter === "4") { + result += ""; + } else if (letter === "/") { + result += "/"; + } else { + + if (figShifted) { + result += this.REVERSE_FIGSHIFT_TABLE[letter]; + } else { + result += letter; + } + + } + + } else { + result += letter; + } + + } else { + result += letter; + } + } + + return result; + + } + +} + +const ITA2_TABLE = { + "A": "11000", + "B": "10011", + "C": "01110", + "D": "10010", + "E": "10000", + "F": "10110", + "G": "01011", + "H": "00101", + "I": "01100", + "J": "11010", + "K": "11110", + "L": "01001", + "M": "00111", + "N": "00110", + "O": "00011", + "P": "01101", + "Q": "11101", + "R": "01010", + "S": "10100", + "T": "00001", + "U": "11100", + "V": "01111", + "W": "11001", + "X": "10111", + "Y": "10101", + "Z": "10001", + "3": "00010", + "4": "01000", + "9": "00100", + "/": "00000", + " ": "00100", + ".": "00100", + "8": "11111", + "5": "11011", + "-": "11111", + "+": "11011" +}; + +const validChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890+-'()/:=?,. \n\r"; +const validITA2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ34589+-./"; +const figShiftedChars = "1234567890+-'()/:=?,."; + +const figShiftArr = { + "1": "Q", + "2": "W", + "3": "E", + "4": "R", + "5": "T", + "6": "Y", + "7": "U", + "8": "I", + "9": "O", + "0": "P", + " ": "9", + "-": "A", + "?": "B", + ":": "C", + "#": "D", + "%": "F", + "@": "G", + "£": "H", + "": "J", + "(": "K", + ")": "L", + ".": "M", + ",": "N", + "'": "S", + "=": "V", + "/": "X", + "+": "Z", + "\n": "3", + "\r": "4" +}; + +const INIT_PATTERNS = { + "No Pattern": { + "X": { + 1: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + 2: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + 3: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + 4: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + 5: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + }, + "S": { + 1: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + 2: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + 3: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + 4: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + 5: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + }, + "M": { + 1: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + 2: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + } + + }, + "KH Pattern": { + "X": { + 1: [0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0], + 2: [1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0], + 3: [0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0], + 4: [1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0], + 5: [1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0] + }, + "S": { + 1: [0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1], + 2: [0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1], + 3: [0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1], + 4: [0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0], + 5: [1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0] + }, + "M": { + 1: [0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0], + 2: [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0] + } + }, + "ZMUG Pattern": { + "X": { + 1: [0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0], + 2: [1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0], + 3: [0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0], + 4: [1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1], + 5: [0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1] + }, + "S": { + 1: [1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0], + 2: [0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1], + 3: [0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1], + 4: [0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1], + 5: [1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0] + }, + "M": { + 1: [1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1], + 2: [0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1] + } + }, + "BREAM Pattern": { + "X": { + 1: [0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0], + 2: [0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1], + 3: [1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0], + 4: [1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0], + 5: [0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0] + }, + "S": { + 1: [0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0], + 2: [1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0], + 3: [1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1], + 4: [0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1], + 5: [1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0] + }, + "M": { + 1: [1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1], + 2: [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1] + } + } +}; + +export default Lorenz; diff --git a/plugins/srktoolbox/src/core/operations/LuhnChecksum.mjs b/plugins/srktoolbox/src/core/operations/LuhnChecksum.mjs new file mode 100644 index 00000000..53bb838e --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/LuhnChecksum.mjs @@ -0,0 +1,98 @@ +/** + * @author n1073645 [n1073645@gmail.com] + * @author k3ach [k3ach@proton.me] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Luhn Checksum operation + */ +class LuhnChecksum extends Operation { + + /** + * LuhnChecksum constructor + */ + constructor() { + super(); + + this.name = "Luhn校验和"; + this.module = "Default"; + this.description = "The Luhn mod N algorithm using the english alphabet. The Luhn mod N algorithm is an extension to the Luhn algorithm (also known as mod 10 algorithm) that allows it to work with sequences of values in any even-numbered base. This can be useful when a check digit is required to validate an identification string composed of letters, a combination of letters and digits or any arbitrary set of N characters where N is divisible by 2."; + this.infoURL = "https://en.wikipedia.org/wiki/Luhn_mod_N_algorithm"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Radix", + "type": "number", + "value": 10 + } + ]; + } + + /** + * Generates the Luhn checksum from the input. + * + * @param {string} inputStr + * @returns {number} + */ + checksum(inputStr, radix = 10) { + let even = false; + return inputStr.split("").reverse().reduce((acc, elem) => { + // Convert element to an integer based on the provided radix. + let temp = parseInt(elem, radix); + + // If element is not a valid number in the given radix. + if (isNaN(temp)) { + throw new Error("Character: " + elem + " is not valid in radix " + radix + "."); + } + + // If element is in an even position + if (even) { + // Double the element and sum the quotient and remainder. + temp = 2 * temp; + temp = Math.floor(temp / radix) + (temp % radix); + } + + even = !even; + return acc + temp; + }, 0) % radix; // Use radix as the modulus base + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + if (!input) return ""; + + const radix = args[0]; + + if (radix < 2 || radix > 36) { + throw new OperationError("Error: Radix argument must be between 2 and 36"); + } + + if (radix % 2 !== 0) { + throw new OperationError("Error: Radix argument must be divisible by 2"); + } + + const checkSum = this.checksum(input, radix).toString(radix); + let checkDigit = this.checksum(input + "0", radix); + checkDigit = checkDigit === 0 ? 0 : (radix - checkDigit); + checkDigit = checkDigit.toString(radix); + + return `校验和: ${checkSum} +检验位: ${checkDigit} +Luhn校验字符串: ${input + "" + checkDigit}`; + } + +} + +export default LuhnChecksum; diff --git a/plugins/srktoolbox/src/core/operations/MD2.mjs b/plugins/srktoolbox/src/core/operations/MD2.mjs new file mode 100644 index 00000000..08511420 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/MD2.mjs @@ -0,0 +1,50 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import {runHash} from "../lib/Hash.mjs"; + +/** + * MD2 operation + */ +class MD2 extends Operation { + + /** + * MD2 constructor + */ + constructor() { + super(); + + this.name = "MD2"; + this.module = "Crypto"; + this.description = "MD2讯息摘要算法(英语:MD2 Message-Digest Algorithm)是由Ronald Rivest在1989年设计的密码杂凑函数,该算法针对8位计算机进行了优化。

尽管早就被认为不够安全,但直到2014年,MD2依然配合RSA算法用于生成证书公钥。此算法默认进行18轮计算。"; + this.infoURL = "https://wikipedia.org/wiki/MD2_(cryptography)"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + name: "轮数", + type: "number", + value: 18, + min: 0 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return runHash("md2", input, {rounds: args[0]}); + } + +} + +export default MD2; diff --git a/plugins/srktoolbox/src/core/operations/MD4.mjs b/plugins/srktoolbox/src/core/operations/MD4.mjs new file mode 100644 index 00000000..3d628c89 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/MD4.mjs @@ -0,0 +1,43 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import {runHash} from "../lib/Hash.mjs"; + +/** + * MD4 operation + */ +class MD4 extends Operation { + + /** + * MD4 constructor + */ + constructor() { + super(); + + this.name = "MD4"; + this.module = "Crypto"; + this.description = "MD4是麻省理工学院教授Ronald Rivest于1990年设计的一种信息摘要算法。它是一种用来测试信息完整性的密码散列函数的实行。其摘要长度为128位。这个算法影响了后来的算法如MD5、SHA家族和RIPEMD等。

MD4算法具有严重的安全漏洞,目前已被淘汰。"; + this.infoURL = "https://wikipedia.org/wiki/MD4"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return runHash("md4", input); + } + +} + +export default MD4; diff --git a/plugins/srktoolbox/src/core/operations/MD5.mjs b/plugins/srktoolbox/src/core/operations/MD5.mjs new file mode 100644 index 00000000..c93e0b54 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/MD5.mjs @@ -0,0 +1,43 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import {runHash} from "../lib/Hash.mjs"; + +/** + * MD5 operation + */ +class MD5 extends Operation { + + /** + * MD5 constructor + */ + constructor() { + super(); + + this.name = "MD5"; + this.module = "Crypto"; + this.description = "MD5消息摘要算法(英语:MD5 Message-Digest Algorithm),一种被广泛使用的密码散列函数,可以产生出一个128位(16个字符(BYTES))的散列值(hash value),用于确保信息传输完整一致。

2004年,证实MD5算法无法防止碰撞攻击,因此不适用于安全性认证,如SSL公开密钥认证或是数字签名等用途。"; + this.infoURL = "https://wikipedia.org/wiki/MD5"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return runHash("md5", input); + } + +} + +export default MD5; diff --git a/plugins/srktoolbox/src/core/operations/MD6.mjs b/plugins/srktoolbox/src/core/operations/MD6.mjs new file mode 100644 index 00000000..7ce1be0e --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/MD6.mjs @@ -0,0 +1,67 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import NodeMD6 from "node-md6"; + +/** + * MD6 operation + */ +class MD6 extends Operation { + + /** + * MD6 constructor + */ + constructor() { + super(); + + this.name = "MD6"; + this.module = "Crypto"; + this.description = "MD6消息摘要算法是一个密码散列函数。它使用默克尔树形式的结构,允许对很长的输入并行进行大量散列计算。"; + this.infoURL = "https://wikipedia.org/wiki/MD6"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "长度", + "type": "number", + "value": 256 + }, + { + "name": "层次(Level)", + "type": "number", + "value": 64 + }, + { + "name": "Key", + "type": "string", + "value": "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [size, levels, key] = args; + + if (size < 0 || size > 512) + throw new OperationError("长度必须位于 0 和 512 之间"); + if (levels < 0) + throw new OperationError("层次必须大于 0"); + + return NodeMD6.getHashOfText(input, size, key, levels); + } + +} + +export default MD6; diff --git a/plugins/srktoolbox/src/core/operations/MIMEDecoding.mjs b/plugins/srktoolbox/src/core/operations/MIMEDecoding.mjs new file mode 100644 index 00000000..80dd6766 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/MIMEDecoding.mjs @@ -0,0 +1,173 @@ +/** + * @author mshwed [m@ttshwed.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { fromHex } from "../lib/Hex.mjs"; +import { fromBase64 } from "../lib/Base64.mjs"; +import cptable from "codepage"; + +/** + * MIME Decoding operation + */ +class MIMEDecoding extends Operation { + + /** + * MIMEDecoding constructor + */ + constructor() { + super(); + + this.name = "MIME解码"; + this.module = "Default"; + this.description = "解码RFC2047使用“非ASCII文本扩展”的MIME标头。"; + this.infoURL = "https://tools.ietf.org/html/rfc2047"; + this.inputType = "byteArray"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const mimeEncodedText = Utils.byteArrayToUtf8(input); + const encodedHeaders = mimeEncodedText.replace(/\r\n/g, "\n"); + + const decodedHeader = this.decodeHeaders(encodedHeaders); + + return decodedHeader; + } + + /** + * Decode MIME header strings + * + * @param headerString + */ + decodeHeaders(headerString) { + // No encoded words detected + let i = headerString.indexOf("=?"); + if (i === -1) return headerString; + + let decodedHeaders = headerString.slice(0, i); + let header = headerString.slice(i); + + let isBetweenWords = false; + let start, cur, charset, encoding, j, end, text; + while (header.length > -1) { + start = header.indexOf("=?"); + if (start === -1) break; + cur = start + "=?".length; + + i = header.slice(cur).indexOf("?"); + if (i === -1) break; + + charset = header.slice(cur, cur + i); + cur += i + "?".length; + + if (header.length < cur + "Q??=".length) break; + + encoding = header[cur]; + cur += 1; + + if (header[cur] !== "?") break; + + cur += 1; + + j = header.slice(cur).indexOf("?="); + if (j === -1) break; + + text = header.slice(cur, cur + j); + end = cur + j + "?=".length; + + if (encoding.toLowerCase() === "b") { + text = fromBase64(text); + } else if (encoding.toLowerCase() === "q") { + text = this.parseQEncodedWord(text); + } else { + isBetweenWords = false; + decodedHeaders += header.slice(0, start + 2); + header = header.slice(start + 2); + } + + if (start > 0 && (!isBetweenWords || header.slice(0, start).search(/\S/g) > -1)) { + decodedHeaders += header.slice(0, start); + } + + decodedHeaders += this.convertFromCharset(charset, text); + + header = header.slice(end); + isBetweenWords = true; + } + + if (header.length > 0) { + decodedHeaders += header; + } + + return decodedHeaders; + } + + /** + * Converts decoded text for supported charsets. + * Supports UTF-8, US-ASCII, ISO-8859-* + * + * @param encodedWord + */ + convertFromCharset(charset, encodedText) { + charset = charset.toLowerCase(); + const parsedCharset = charset.split("-"); + + if (parsedCharset.length === 2 && parsedCharset[0] === "utf" && charset === "utf-8") { + return cptable.utils.decode(65001, encodedText); + } else if (parsedCharset.length === 2 && charset === "us-ascii") { + return cptable.utils.decode(20127, encodedText); + } else if (parsedCharset.length === 3 && parsedCharset[0] === "iso" && parsedCharset[1] === "8859") { + const isoCharset = parseInt(parsedCharset[2], 10); + if (isoCharset >= 1 && isoCharset <= 16) { + return cptable.utils.decode(28590 + isoCharset, encodedText); + } + } + + throw new OperationError("不支持的字符集"); + } + + /** + * Parses a Q encoded word + * + * @param encodedWord + */ + parseQEncodedWord(encodedWord) { + let decodedWord = ""; + for (let i = 0; i < encodedWord.length; i++) { + if (encodedWord[i] === "_") { + decodedWord += " "; + // Parse hex encoding + } else if (encodedWord[i] === "=") { + if ((i + 2) >= encodedWord.length) throw new OperationError("Q编码错误"); + const decodedHex = Utils.byteArrayToChars(fromHex(encodedWord.substring(i + 1, i + 3))); + decodedWord += decodedHex; + i += 2; + } else if ( + (encodedWord[i].charCodeAt(0) >= " ".charCodeAt(0) && encodedWord[i].charCodeAt(0) <= "~".charCodeAt(0)) || + encodedWord[i] === "\n" || + encodedWord[i] === "\r" || + encodedWord[i] === "\t") { + decodedWord += encodedWord[i]; + } else { + throw new OperationError("Q编码错误"); + } + } + + return decodedWord; + } +} + +export default MIMEDecoding; diff --git a/plugins/srktoolbox/src/core/operations/Magic.mjs b/plugins/srktoolbox/src/core/operations/Magic.mjs new file mode 100644 index 00000000..80681cee --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Magic.mjs @@ -0,0 +1,170 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import Dish from "../Dish.mjs"; +import MagicLib from "../lib/Magic.mjs"; + +/** + * Magic operation + */ +class Magic extends Operation { + + /** + * Magic constructor + */ + constructor() { + super(); + + this.name = "Magic"; + this.flowControl = true; + this.module = "Default"; + this.description = "Magic操作尝试检测输入数据的多种属性来建议对应的操作。

设定
深度:如果某个操作和数据相符,此操作会被运行一次,运行结果会被再次分析。此参数设置递归的最大次数。

加强模式:当此模式开启时,多种操作如XOR、位运算和字符编码会进行暴力破解来检测可能的数据处理方式。为提高性能,仅有前100个字节会被暴力破解。

扩展语言支持:在每个阶段,数据的相对字节频率数据会和各种现实语言的平均频率进行对照。默认情况下仅检测约40种互联网常见语言。扩展语言功能将语言数据提高到284种,这使得数据可以匹配更多语言种类,如果它们的频率数据相近的话。

支持输入一个正则表达式来过滤输出结果(crib)。"; + this.infoURL = "https://github.com/gchq/CyberChef/wiki/Automatic-detection-of-encoded-data-using-CyberChef-Magic"; + this.inputType = "ArrayBuffer"; + this.outputType = "JSON"; + this.presentType = "html"; + this.args = [ + { + "name": "深度", + "type": "number", + "value": 3 + }, + { + "name": "加强模式", + "type": "boolean", + "value": false + }, + { + "name": "扩展语言支持", + "type": "boolean", + "value": false + }, + { + "name": "Crib(已知明文字符串或正则)", + "type": "string", + "value": "" + } + ]; + } + + /** + * @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. + */ + async run(state) { + const ings = state.opList[state.progress].ingValues, + [depth, intensive, extLang, crib] = ings, + dish = state.dish, + magic = new MagicLib(await dish.get(Dish.ARRAY_BUFFER)), + cribRegex = (crib && crib.length) ? new RegExp(crib, "i") : null; + let options = await magic.speculativeExecution(depth, extLang, intensive, [], false, cribRegex); + + // Filter down to results which matched the crib + if (cribRegex) { + options = options.filter(option => option.matchesCrib); + } + + // Record the current state for use when presenting + this.state = state; + + dish.set(options, Dish.JSON); + return state; + } + + /** + * Displays Magic results in HTML for web apps. + * + * @param {JSON} options + * @returns {html} + */ + present(options) { + const currentRecipeConfig = this.state.opList.map(op => op.config); + + let output = ` + + + + + `; + + /** + * Returns a CSS colour value based on an integer input. + * + * @param {number} val + * @returns {string} + */ + function chooseColour(val) { + if (val < 3) return "green"; + if (val < 5) return "goldenrod"; + return "red"; + } + + options.forEach(option => { + // Construct recipe URL + // Replace this Magic op with the generated recipe + const recipeConfig = currentRecipeConfig.slice(0, this.state.progress) + .concat(option.recipe) + .concat(currentRecipeConfig.slice(this.state.progress + 1)), + recipeURL = "recipe=" + Utils.encodeURIFragment(Utils.generatePrettyRecipe(recipeConfig)); + + let language = "", + fileType = "", + matchingOps = "", + useful = ""; + const entropy = `信息熵: ${option.entropy.toFixed(2)}`, + validUTF8 = option.isUTF8 ? "有效UTF8\n" : ""; + + if (option.languageScores[0].probability > 0) { + let likelyLangs = option.languageScores.filter(l => l.probability > 0); + if (likelyLangs.length < 1) likelyLangs = [option.languageScores[0]]; + language = "" + + "可能为以下语言:\n " + + likelyLangs.map(lang => { + return MagicLib.codeToLanguage(lang.lang); + }).join("\n ") + + "\n"; + } + + if (option.fileType) { + fileType = `文件类型:${option.fileType.mime} (${option.fileType.ext})\n`; + } + + if (option.matchingOps.length) { + matchingOps = `对应操作:${[...new Set(option.matchingOps.map(op => op.op))].join(", ")}\n`; + } + + if (option.useful) { + useful = "检测到建议操作\n"; + } + + output += ` + + + + `; + }); + + output += "
操作(点击加载)结果摘要属性
${Utils.generatePrettyRecipe(option.recipe, true)}${Utils.escapeHtml(Utils.escapeWhitespace(Utils.truncate(option.data, 99)))}${language}${fileType}${matchingOps}${useful}${validUTF8}${entropy}
"; + + if (!options.length) { + output = "针对输入数据未检测到任何可用操作。\n请尝试调整操作参数。"; + } + + return output; + } + +} + +export default Magic; diff --git a/plugins/srktoolbox/src/core/operations/Mean.mjs b/plugins/srktoolbox/src/core/operations/Mean.mjs new file mode 100644 index 00000000..05bb1fa8 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Mean.mjs @@ -0,0 +1,53 @@ +/** + * @author bwhitn [brian.m.whitney@outlook.com] + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import { mean, createNumArray } from "../lib/Arithmetic.mjs"; +import { ARITHMETIC_DELIM_OPTIONS } from "../lib/Delim.mjs"; +import BigNumber from "bignumber.js"; + +/** + * Mean operation + */ +class Mean extends Operation { + + /** + * Mean constructor + */ + constructor() { + super(); + + this.name = "求平均"; + this.module = "Default"; + this.description = "对一组数字求平均值。非数字的值会被忽略。

例: 0x0a 8 .5 .5 计算为 4.75"; + this.infoURL = "https://wikipedia.org/wiki/Arithmetic_mean"; + this.inputType = "string"; + this.outputType = "BigNumber"; + this.args = [ + { + "name": "分隔符", + "type": "option", + "value": ARITHMETIC_DELIM_OPTIONS, + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {BigNumber} + */ + run(input, args) { + const val = mean(createNumArray(input, args[0])); + return BigNumber.isBigNumber(val) ? val : new BigNumber(NaN); + } + +} + +export default Mean; diff --git a/plugins/srktoolbox/src/core/operations/Median.mjs b/plugins/srktoolbox/src/core/operations/Median.mjs new file mode 100644 index 00000000..b03e6074 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Median.mjs @@ -0,0 +1,53 @@ +/** + * @author bwhitn [brian.m.whitney@outlook.com] + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import BigNumber from "bignumber.js"; +import Operation from "../Operation.mjs"; +import { median, createNumArray } from "../lib/Arithmetic.mjs"; +import { ARITHMETIC_DELIM_OPTIONS } from "../lib/Delim.mjs"; + +/** + * Median operation + */ +class Median extends Operation { + + /** + * Median constructor + */ + constructor() { + super(); + + this.name = "求中位数"; + this.module = "Default"; + this.description = "对一组数字求中位数。非数字的值会被忽略。

例: 0x0a 8 1 .5 计算为 4.5"; + this.infoURL = "https://wikipedia.org/wiki/Median"; + this.inputType = "string"; + this.outputType = "BigNumber"; + this.args = [ + { + "name": "分隔符", + "type": "option", + "value": ARITHMETIC_DELIM_OPTIONS, + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {BigNumber} + */ + run(input, args) { + const val = median(createNumArray(input, args[0])); + return BigNumber.isBigNumber(val) ? val : new BigNumber(NaN); + } + +} + +export default Median; diff --git a/plugins/srktoolbox/src/core/operations/Merge.mjs b/plugins/srktoolbox/src/core/operations/Merge.mjs new file mode 100644 index 00000000..6dc42b76 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Merge.mjs @@ -0,0 +1,52 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; + +/** + * Merge operation + */ +class Merge extends Operation { + + /** + * Merge constructor + */ + constructor() { + super(); + + this.name = "Merge"; + this.flowControl = true; + this.module = "Default"; + this.description = "将所有分支合并回一个单独进程。Fork的反操作。不勾选“合并全部”则只会抵消最近的Fork/Subsection。"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "合并全部", + type: "boolean", + value: true, + } + ]; + } + + /** + * @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. + */ + run(state) { + // No need to actually do anything here. The fork operation will + // merge when it sees this operation. + return state; + } + +} + +export default Merge; diff --git a/plugins/srktoolbox/src/core/operations/MicrosoftScriptDecoder.mjs b/plugins/srktoolbox/src/core/operations/MicrosoftScriptDecoder.mjs new file mode 100644 index 00000000..00dc6cbf --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/MicrosoftScriptDecoder.mjs @@ -0,0 +1,227 @@ +/** + * @author bmwhitn [brian.m.whitney@outlook.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; + +/** + * Microsoft Script Decoder operation + */ +class MicrosoftScriptDecoder extends Operation { + + /** + * MicrosoftScriptDecoder constructor + */ + constructor() { + super(); + + this.name = "Microsoft Script解码"; + this.module = "Default"; + this.description = "解码Microsoft Encoded Script文件。这些文件通常是VBS (Visual Basic Script)或JS (JScript)文件经过微软的特殊方式编码,带有“.vbe”或“.jse”扩展名。

例如:

编码后:
#@~^RQAAAA==-mD~sX|:/TP{~J:+dYbxL~@!F@*@!+@*@!&@*eEI@#@&@#@&.jm.raY 214Wv:zms/obI0xEAAA==^#~@

解码为:
var my_msg = "Testing <1><2><3>!";\n\nVScript.Echo(my_msg);"; + this.infoURL = "https://wikipedia.org/wiki/JScript.Encode"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + this.checks = [ + { + pattern: "#@~\\^.{6}==(.+).{6}==\\^#~@", + flags: "i", + args: [] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const matcher = /#@~\^.{6}==(.+).{6}==\^#~@/; + const encodedData = matcher.exec(input); + if (encodedData) { + return MicrosoftScriptDecoder._decode(encodedData[1]); + } else { + return ""; + } + } + + /** + * Decodes Microsoft Encoded Script files that can be read and executed by cscript.exe/wscript.exe. + * This is a conversion of a Python script that was originally created by Didier Stevens + * (https://DidierStevens.com). + * + * @private + * @param {string} data + * @returns {string} + */ + static _decode(data) { + const result = []; + let index = -1; + data = data.replace(/@&/g, String.fromCharCode(10)) + .replace(/@#/g, String.fromCharCode(13)) + .replace(/@\*/g, ">") + .replace(/@!/g, "<") + .replace(/@\$/g, "@"); + + for (let i = 0; i < data.length; i++) { + const byte = data.charCodeAt(i); + let char = data.charAt(i); + if (byte < 128) { + index++; + } + + if ((byte === 9 || byte > 31 && byte < 128) && + byte !== 60 && + byte !== 62 && + byte !== 64) { + char = D_DECODE[byte].charAt(D_COMBINATION[index % 64]); + } + result.push(char); + } + return result.join(""); + } + +} + +const D_DECODE = [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "\x57\x6E\x7B", + "\x4A\x4C\x41", + "\x0B\x0B\x0B", + "\x0C\x0C\x0C", + "\x4A\x4C\x41", + "\x0E\x0E\x0E", + "\x0F\x0F\x0F", + "\x10\x10\x10", + "\x11\x11\x11", + "\x12\x12\x12", + "\x13\x13\x13", + "\x14\x14\x14", + "\x15\x15\x15", + "\x16\x16\x16", + "\x17\x17\x17", + "\x18\x18\x18", + "\x19\x19\x19", + "\x1A\x1A\x1A", + "\x1B\x1B\x1B", + "\x1C\x1C\x1C", + "\x1D\x1D\x1D", + "\x1E\x1E\x1E", + "\x1F\x1F\x1F", + "\x2E\x2D\x32", + "\x47\x75\x30", + "\x7A\x52\x21", + "\x56\x60\x29", + "\x42\x71\x5B", + "\x6A\x5E\x38", + "\x2F\x49\x33", + "\x26\x5C\x3D", + "\x49\x62\x58", + "\x41\x7D\x3A", + "\x34\x29\x35", + "\x32\x36\x65", + "\x5B\x20\x39", + "\x76\x7C\x5C", + "\x72\x7A\x56", + "\x43\x7F\x73", + "\x38\x6B\x66", + "\x39\x63\x4E", + "\x70\x33\x45", + "\x45\x2B\x6B", + "\x68\x68\x62", + "\x71\x51\x59", + "\x4F\x66\x78", + "\x09\x76\x5E", + "\x62\x31\x7D", + "\x44\x64\x4A", + "\x23\x54\x6D", + "\x75\x43\x71", + "\x4A\x4C\x41", + "\x7E\x3A\x60", + "\x4A\x4C\x41", + "\x5E\x7E\x53", + "\x40\x4C\x40", + "\x77\x45\x42", + "\x4A\x2C\x27", + "\x61\x2A\x48", + "\x5D\x74\x72", + "\x22\x27\x75", + "\x4B\x37\x31", + "\x6F\x44\x37", + "\x4E\x79\x4D", + "\x3B\x59\x52", + "\x4C\x2F\x22", + "\x50\x6F\x54", + "\x67\x26\x6A", + "\x2A\x72\x47", + "\x7D\x6A\x64", + "\x74\x39\x2D", + "\x54\x7B\x20", + "\x2B\x3F\x7F", + "\x2D\x38\x2E", + "\x2C\x77\x4C", + "\x30\x67\x5D", + "\x6E\x53\x7E", + "\x6B\x47\x6C", + "\x66\x34\x6F", + "\x35\x78\x79", + "\x25\x5D\x74", + "\x21\x30\x43", + "\x64\x23\x26", + "\x4D\x5A\x76", + "\x52\x5B\x25", + "\x63\x6C\x24", + "\x3F\x48\x2B", + "\x7B\x55\x28", + "\x78\x70\x23", + "\x29\x69\x41", + "\x28\x2E\x34", + "\x73\x4C\x09", + "\x59\x21\x2A", + "\x33\x24\x44", + "\x7F\x4E\x3F", + "\x6D\x50\x77", + "\x55\x09\x3B", + "\x53\x56\x55", + "\x7C\x73\x69", + "\x3A\x35\x61", + "\x5F\x61\x63", + "\x65\x4B\x50", + "\x46\x58\x67", + "\x58\x3B\x51", + "\x31\x57\x49", + "\x69\x22\x4F", + "\x6C\x6D\x46", + "\x5A\x4D\x68", + "\x48\x25\x7C", + "\x27\x28\x36", + "\x5C\x46\x70", + "\x3D\x4A\x6E", + "\x24\x32\x7A", + "\x79\x41\x2F", + "\x37\x3D\x5F", + "\x60\x5F\x4B", + "\x51\x4F\x5A", + "\x20\x42\x2C", + "\x36\x65\x57" +]; + +const D_COMBINATION = [ + 0, 1, 2, 0, 1, 2, 1, 2, 2, 1, 2, 1, 0, 2, 1, 2, 0, 2, 1, 2, 0, 0, 1, 2, 2, 1, 0, 2, 1, 2, 2, 1, + 0, 0, 2, 1, 2, 1, 2, 0, 2, 0, 0, 1, 2, 0, 2, 1, 0, 2, 1, 2, 0, 0, 1, 2, 2, 0, 0, 1, 2, 0, 2, 1 +]; + +export default MicrosoftScriptDecoder; diff --git a/plugins/srktoolbox/src/core/operations/MultipleBombe.mjs b/plugins/srktoolbox/src/core/operations/MultipleBombe.mjs new file mode 100644 index 00000000..1af471c2 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/MultipleBombe.mjs @@ -0,0 +1,307 @@ +/** + * Emulation of the Bombe machine. + * This version carries out multiple Bombe runs to handle unknown rotor configurations. + * + * @author s2224834 + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { BombeMachine } from "../lib/Bombe.mjs"; +import { ROTORS, ROTORS_FOURTH, REFLECTORS, Reflector } from "../lib/Enigma.mjs"; +import { isWorkerEnvironment } from "../Utils.mjs"; + + +/** + * Convenience method for flattening the preset ROTORS object into a newline-separated string. + * @param {Object[]} - Preset rotors object + * @param {number} s - Start index + * @param {number} n - End index + * @returns {string} + */ +function rotorsFormat(rotors, s, n) { + const res = []; + for (const i of rotors.slice(s, n)) { + res.push(i.value); + } + return res.join("\n"); +} + +/** + * Combinatorics choose function + * @param {number} n + * @param {number} k + * @returns number + */ +function choose(n, k) { + let res = 1; + for (let i=1; i<=k; i++) { + res *= (n + 1 - i) / i; + } + return res; +} + +/** + * Bombe operation + */ +class MultipleBombe extends Operation { + /** + * Bombe constructor + */ + constructor() { + super(); + + this.name = "Multiple Bombe"; + this.module = "Bletchley"; + this.description = "Emulation of the Bombe machine used to attack Enigma. This version carries out multiple Bombe runs to handle unknown rotor configurations.

You should test your menu on the single Bombe operation before running it here. See the description of the Bombe operation for instructions on choosing a crib.

More detailed descriptions of the Enigma, Typex and Bombe operations can be found here."; + this.infoURL = "https://wikipedia.org/wiki/Bombe"; + this.inputType = "string"; + this.outputType = "JSON"; + this.presentType = "html"; + this.args = [ + { + "name": "Standard Enigmas", + "type": "populateMultiOption", + "value": [ + { + name: "German Service Enigma (First - 3 rotor)", + value: [ + rotorsFormat(ROTORS, 0, 5), + "", + rotorsFormat(REFLECTORS, 0, 1) + ] + }, + { + name: "German Service Enigma (Second - 3 rotor)", + value: [ + rotorsFormat(ROTORS, 0, 8), + "", + rotorsFormat(REFLECTORS, 0, 2) + ] + }, + { + name: "German Service Enigma (Third - 4 rotor)", + value: [ + rotorsFormat(ROTORS, 0, 8), + rotorsFormat(ROTORS_FOURTH, 1, 2), + rotorsFormat(REFLECTORS, 2, 3) + ] + }, + { + name: "German Service Enigma (Fourth - 4 rotor)", + value: [ + rotorsFormat(ROTORS, 0, 8), + rotorsFormat(ROTORS_FOURTH, 1, 3), + rotorsFormat(REFLECTORS, 2, 4) + ] + }, + { + name: "User defined", + value: ["", "", ""] + }, + ], + "target": [1, 2, 3] + }, + { + name: "Main rotors", + type: "text", + value: "" + }, + { + name: "4th rotor", + type: "text", + value: "" + }, + { + name: "Reflectors", + type: "text", + value: "" + }, + { + name: "Crib", + type: "string", + value: "" + }, + { + name: "Crib offset", + type: "number", + value: 0 + }, + { + name: "Use checking machine", + type: "boolean", + value: true + } + ]; + } + + /** + * Format and send a status update message. + * @param {number} nLoops - Number of loops in the menu + * @param {number} nStops - How many stops so far + * @param {number} progress - Progress (as a float in the range 0..1) + */ + updateStatus(nLoops, nStops, progress, start) { + const elapsed = Date.now() - start; + const remaining = (elapsed / progress) * (1 - progress) / 1000; + const hours = Math.floor(remaining / 3600); + const minutes = `0${Math.floor((remaining % 3600) / 60)}`.slice(-2); + const seconds = `0${Math.floor(remaining % 60)}`.slice(-2); + const msg = `Bombe run with ${nLoops} loop${nLoops === 1 ? "" : "s"} in menu (2+ desirable): ${nStops} stops, ${Math.floor(100 * progress)}% done, ${hours}:${minutes}:${seconds} remaining`; + self.sendStatusMessage(msg); + } + + /** + * Early rotor description string validation. + * Drops stepping information. + * @param {string} rstr - The rotor description string + * @returns {string} - Rotor description with stepping stripped, if any + */ + validateRotor(rstr) { + // The Bombe doesn't take stepping into account so we'll just ignore it here + if (rstr.includes("<")) { + rstr = rstr.split("<", 2)[0]; + } + // Duplicate the validation of the rotor strings here, otherwise you might get an error + // thrown halfway into a big Bombe run + if (!/^[A-Z]{26}$/.test(rstr)) { + throw new OperationError("Rotor wiring must be 26 unique uppercase letters"); + } + if (new Set(rstr).size !== 26) { + throw new OperationError("Rotor wiring must be 26 unique uppercase letters"); + } + return rstr; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const mainRotorsStr = args[1]; + const fourthRotorsStr = args[2]; + const reflectorsStr = args[3]; + let crib = args[4]; + const offset = args[5]; + const check = args[6]; + const rotors = []; + const fourthRotors = []; + const reflectors = []; + for (let rstr of mainRotorsStr.split("\n")) { + rstr = this.validateRotor(rstr); + rotors.push(rstr); + } + if (rotors.length < 3) { + throw new OperationError("A minimum of three rotors must be supplied"); + } + if (fourthRotorsStr !== "") { + for (let rstr of fourthRotorsStr.split("\n")) { + rstr = this.validateRotor(rstr); + fourthRotors.push(rstr); + } + } + if (fourthRotors.length === 0) { + fourthRotors.push(""); + } + for (const rstr of reflectorsStr.split("\n")) { + const reflector = new Reflector(rstr); + reflectors.push(reflector); + } + if (reflectors.length === 0) { + throw new OperationError("A minimum of one reflector must be supplied"); + } + if (crib.length === 0) { + throw new OperationError("Crib cannot be empty"); + } + if (offset < 0) { + throw new OperationError("Offset cannot be negative"); + } + // For symmetry with the Enigma op, for the input we'll just remove all invalid characters + input = input.replace(/[^A-Za-z]/g, "").toUpperCase(); + crib = crib.replace(/[^A-Za-z]/g, "").toUpperCase(); + const ciphertext = input.slice(offset); + let update; + if (isWorkerEnvironment()) { + update = this.updateStatus; + } else { + update = undefined; + } + let bombe = undefined; + const output = {bombeRuns: []}; + // I could use a proper combinatorics algorithm here... but it would be more code to + // write one, and we don't seem to have one in our existing libraries, so massively nested + // for loop it is + const totalRuns = choose(rotors.length, 3) * 6 * fourthRotors.length * reflectors.length; + let nRuns = 0; + let nStops = 0; + const start = Date.now(); + for (const rotor1 of rotors) { + for (const rotor2 of rotors) { + if (rotor2 === rotor1) { + continue; + } + for (const rotor3 of rotors) { + if (rotor3 === rotor2 || rotor3 === rotor1) { + continue; + } + for (const rotor4 of fourthRotors) { + for (const reflector of reflectors) { + nRuns++; + const runRotors = [rotor1, rotor2, rotor3]; + if (rotor4 !== "") { + runRotors.push(rotor4); + } + if (bombe === undefined) { + bombe = new BombeMachine(runRotors, reflector, ciphertext, crib, check); + output.nLoops = bombe.nLoops; + } else { + bombe.changeRotors(runRotors, reflector); + } + const result = bombe.run(); + nStops += result.length; + if (update !== undefined) { + update(bombe.nLoops, nStops, nRuns / totalRuns, start); + } + if (result.length > 0) { + output.bombeRuns.push({ + rotors: runRotors, + reflector: reflector.pairs, + result: result + }); + } + } + } + } + } + } + return output; + } + + + /** + * Displays the MultiBombe results in an HTML table + * + * @param {Object} output + * @param {number} output.nLoops + * @param {Array[]} output.result + * @returns {html} + */ + present(output) { + let html = `Bombe run on menu with ${output.nLoops} loop${output.nLoops === 1 ? "" : "s"} (2+ desirable). Note: Rotors and rotor positions are listed left to right, ignore stepping and the ring setting, and positions start at the beginning of the crib. Some plugboard settings are determined. A decryption preview starting at the beginning of the crib and ignoring stepping is also provided.\n`; + + for (const run of output.bombeRuns) { + html += `\nRotors: ${run.rotors.slice().reverse().join(", ")}\nReflector: ${run.reflector}\n`; + html += "\n"; + for (const [setting, stecker, decrypt] of run.result) { + html += `\n`; + } + html += "
Rotor stops Partial plugboard Decryption preview
${setting} ${stecker} ${decrypt}
\n"; + } + return html; + } +} + +export default MultipleBombe; diff --git a/plugins/srktoolbox/src/core/operations/Multiply.mjs b/plugins/srktoolbox/src/core/operations/Multiply.mjs new file mode 100644 index 00000000..f6a2da1c --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Multiply.mjs @@ -0,0 +1,54 @@ +/** + * @author bwhitn [brian.m.whitney@outlook.com] + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import BigNumber from "bignumber.js"; +import Operation from "../Operation.mjs"; +import { multi, createNumArray } from "../lib/Arithmetic.mjs"; +import { ARITHMETIC_DELIM_OPTIONS } from "../lib/Delim.mjs"; + + +/** + * Multiply operation + */ +class Multiply extends Operation { + + /** + * Multiply constructor + */ + constructor() { + super(); + + this.name = "求积"; + this.module = "Default"; + this.description = "对一组数字求积(aka. 乘法)。非数字的值会被忽略。

例: 0x0a 8 .5 计算为 40"; + this.infoURL = "https://wikipedia.org/wiki/Multiplication"; + this.inputType = "string"; + this.outputType = "BigNumber"; + this.args = [ + { + "name": "分隔符", + "type": "option", + "value": ARITHMETIC_DELIM_OPTIONS, + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {BigNumber} + */ + run(input, args) { + const val = multi(createNumArray(input, args[0])); + return BigNumber.isBigNumber(val) ? val : new BigNumber(NaN); + } + +} + +export default Multiply; diff --git a/plugins/srktoolbox/src/core/operations/MurmurHash3.mjs b/plugins/srktoolbox/src/core/operations/MurmurHash3.mjs new file mode 100644 index 00000000..12dd3518 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/MurmurHash3.mjs @@ -0,0 +1,141 @@ +/** + * Based on murmurhash-js (https://github.com/garycourt/murmurhash-js) + * @author Gary Court + * @license MIT + * + * @author AliceGrey [alice@grey.systems] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; + +/** + * MurmurHash3 operation + */ +class MurmurHash3 extends Operation { + + /** + * MurmurHash3 constructor + */ + constructor() { + super(); + + this.name = "MurmurHash3"; + this.module = "Hashing"; + this.description = "对给定的输入字符串(可自选种子)生成对应的MurmurHash v3哈希。"; + this.infoURL = "https://wikipedia.org/wiki/MurmurHash"; + this.inputType = "string"; + this.outputType = "number"; + this.args = [ + { + name: "种子", + type: "number", + value: 0 + }, + { + name: "转换为有符号数值", + type: "boolean", + value: false + } + ]; + } + + /** + * Calculates the MurmurHash3 hash of the input. + * Based on Gary Court's JS MurmurHash implementation + * @see http://github.com/garycourt/murmurhash-js + * @author AliceGrey [alice@grey.systems] + * @param {string} input ASCII only + * @param {number} seed Positive integer only + * @return {number} 32-bit positive integer hash + */ + mmh3(input, seed) { + let h1b; + let k1; + const remainder = input.length & 3; // input.length % 4 + const bytes = input.length - remainder; + let h1 = seed; + const c1 = 0xcc9e2d51; + const c2 = 0x1b873593; + let i = 0; + + while (i < bytes) { + k1 = + ((input.charCodeAt(i) & 0xff)) | + ((input.charCodeAt(++i) & 0xff) << 8) | + ((input.charCodeAt(++i) & 0xff) << 16) | + ((input.charCodeAt(++i) & 0xff) << 24); + ++i; + + k1 = ((((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16))) & 0xffffffff; + k1 = (k1 << 15) | (k1 >>> 17); + k1 = ((((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16))) & 0xffffffff; + + h1 ^= k1; + h1 = (h1 << 13) | (h1 >>> 19); + h1b = ((((h1 & 0xffff) * 5) + ((((h1 >>> 16) * 5) & 0xffff) << 16))) & 0xffffffff; + h1 = (((h1b & 0xffff) + 0x6b64) + ((((h1b >>> 16) + 0xe654) & 0xffff) << 16)); + } + + k1 = 0; + + if (remainder === 3) { + k1 ^= (input.charCodeAt(i + 2) & 0xff) << 16; + } + + if (remainder === 3 || remainder === 2) { + k1 ^= (input.charCodeAt(i + 1) & 0xff) << 8; + } + + if (remainder === 3 || remainder === 2 || remainder === 1) { + k1 ^= (input.charCodeAt(i) & 0xff); + + k1 = (((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16)) & 0xffffffff; + k1 = (k1 << 15) | (k1 >>> 17); + k1 = (((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16)) & 0xffffffff; + h1 ^= k1; + } + + h1 ^= input.length; + + h1 ^= h1 >>> 16; + h1 = (((h1 & 0xffff) * 0x85ebca6b) + ((((h1 >>> 16) * 0x85ebca6b) & 0xffff) << 16)) & 0xffffffff; + h1 ^= h1 >>> 13; + h1 = ((((h1 & 0xffff) * 0xc2b2ae35) + ((((h1 >>> 16) * 0xc2b2ae35) & 0xffff) << 16))) & 0xffffffff; + h1 ^= h1 >>> 16; + + return h1 >>> 0; + } + + /** + * Converts an unsigned 32-bit integer to a signed 32-bit integer + * @author AliceGrey [alice@grey.systems] + * @param {value} 32-bit unsigned integer + * @return {number} 32-bit signed integer + */ + unsignedToSigned(value) { + return value & 0x80000000 ? -0x100000000 + value : value; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {number} + */ + run(input, args) { + if (args && args.length >= 1) { + const seed = args[0]; + const hash = this.mmh3(input, seed); + if (args.length > 1 && args[1]) { + return this.unsignedToSigned(hash); + } + return hash; + } + return this.mmh3(input); + } +} + +export default MurmurHash3; diff --git a/plugins/srktoolbox/src/core/operations/NOT.mjs b/plugins/srktoolbox/src/core/operations/NOT.mjs new file mode 100644 index 00000000..9aaa71a1 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/NOT.mjs @@ -0,0 +1,70 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import { bitOp, not } from "../lib/BitwiseOp.mjs"; + +/** + * NOT operation + */ +class NOT extends Operation { + + /** + * NOT constructor + */ + constructor() { + super(); + + this.name = "NOT"; + this.module = "Default"; + this.description = "对每个字节取反。"; + this.infoURL = "https://wikipedia.org/wiki/Bitwise_operation#NOT"; + this.inputType = "ArrayBuffer"; + this.outputType = "byteArray"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + input = new Uint8Array(input); + return bitOp(input, null, not); + } + + /** + * Highlight NOT + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight NOT 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 NOT; diff --git a/plugins/srktoolbox/src/core/operations/NTHash.mjs b/plugins/srktoolbox/src/core/operations/NTHash.mjs new file mode 100644 index 00000000..1a418fb3 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/NTHash.mjs @@ -0,0 +1,50 @@ +/** + * @author brun0ne [brunonblok@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import {runHash} from "../lib/Hash.mjs"; + +/** + * NT Hash operation + */ +class NTHash extends Operation { + + /** + * NTHash constructor + */ + constructor() { + super(); + + this.name = "NT哈希"; + this.module = "Crypto"; + this.description = "NT哈希,有时又叫NTLM哈希,是Windows系统用于存储密码的一种方法。原理为在UTF-16LE编码的输入上执行MD4。由于NTLM哈希在现代硬件设备上非常容易被暴力破解,目前认为其安全性较弱。"; + this.infoURL = "https://wikipedia.org/wiki/NT_LAN_Manager"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + // Convert to UTF-16LE + const buf = new ArrayBuffer(input.length * 2); + const bufView = new Uint16Array(buf); + for (let i = 0; i < input.length; i++) { + bufView[i] = input.charCodeAt(i); + } + + const hashed = runHash("md4", buf); + return hashed.toUpperCase(); + } +} + +export default NTHash; diff --git a/plugins/srktoolbox/src/core/operations/NormaliseImage.mjs b/plugins/srktoolbox/src/core/operations/NormaliseImage.mjs new file mode 100644 index 00000000..8a06d10d --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/NormaliseImage.mjs @@ -0,0 +1,85 @@ +/** + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { isImage } from "../lib/FileType.mjs"; +import { toBase64 } from "../lib/Base64.mjs"; +import { Jimp, JimpMime } from "jimp"; + +/** + * Normalise Image operation + */ +class NormaliseImage extends Operation { + /** + * NormaliseImage constructor + */ + constructor() { + super(); + + this.name = "图像归一化"; + this.module = "Image"; + this.description = "图像色彩归一化(Normalize)。"; + this.infoURL = ""; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.presentType = "html"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {byteArray} + */ + async run(input, args) { + if (!isImage(input)) { + throw new OperationError("无效的文件类型。"); + } + + let image; + try { + image = await Jimp.read(input); + } catch (err) { + throw new OperationError(`打开图像文件出错:(${err})`); + } + + try { + image.normalize(); + + let imageBuffer; + if (image.mime === "image/gif") { + imageBuffer = await image.getBuffer(JimpMime.png); + } else { + imageBuffer = await image.getBuffer(image.mime); + } + return imageBuffer.buffer; + } catch (err) { + throw new OperationError(`归一化图像出错:(${err})`); + } + } + + /** + * Displays the normalised image using HTML for web apps + * @param {ArrayBuffer} data + * @returns {html} + */ + present(data) { + if (!data.byteLength) return ""; + const dataArray = new Uint8Array(data); + + const type = isImage(dataArray); + if (!type) { + throw new OperationError("无效的文件类型。"); + } + + return ``; + } +} + +export default NormaliseImage; diff --git a/plugins/srktoolbox/src/core/operations/NormaliseUnicode.mjs b/plugins/srktoolbox/src/core/operations/NormaliseUnicode.mjs new file mode 100644 index 00000000..429df3d3 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/NormaliseUnicode.mjs @@ -0,0 +1,64 @@ +/** + * @author Matthieu [m@tthieu.xyz] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import {UNICODE_NORMALISATION_FORMS} from "../lib/ChrEnc.mjs"; +import unorm from "unorm"; + +/** + * Normalise Unicode operation + */ +class NormaliseUnicode extends Operation { + + /** + * NormaliseUnicode constructor + */ + constructor() { + super(); + + this.name = "Unicode正规化"; + this.module = "Encodings"; + this.description = "按照选定的正规形式对Unicode字符执行正规化操作。"; + this.infoURL = "https://wikipedia.org/wiki/Unicode_equivalence#Normal_forms"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "正规形式", + type: "option", + value: UNICODE_NORMALISATION_FORMS + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [normalForm] = args; + + switch (normalForm) { + case "NFD": + return unorm.nfd(input); + case "NFC": + return unorm.nfc(input); + case "NFKD": + return unorm.nfkd(input); + case "NFKC": + return unorm.nfkc(input); + default: + throw new OperationError("未知的正规形式"); + } + } + +} + +export default NormaliseUnicode; diff --git a/plugins/srktoolbox/src/core/operations/Numberwang.mjs b/plugins/srktoolbox/src/core/operations/Numberwang.mjs new file mode 100644 index 00000000..7f94e360 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Numberwang.mjs @@ -0,0 +1,101 @@ +/** + * @author Unknown Male 282 + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; + +/** + * Numberwang operation. Remain indoors. + */ +class Numberwang extends Operation { + + /** + * Numberwang constructor + */ + constructor() { + super(); + + this.name = "Numberwang"; + this.module = "Default"; + this.description = "Based on the popular gameshow by Mitchell and Webb."; + this.infoURL = "https://wikipedia.org/wiki/That_Mitchell_and_Webb_Look#Recurring_sketches"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let output; + if (!input) { + output = "Let's play Wangernumb!"; + } else { + const match = input.match(/(f0rty-s1x|shinty-six|filth-hundred and neeb|-?√?\d+(\.\d+)?i?([a-z]?)%?)/i); + if (match) { + if (match[3]) output = match[0] + "! That's AlphaNumericWang!"; + else output = match[0] + "! That's Numberwang!"; + } else { + // That's a bad miss! + output = "Sorry, that's not Numberwang. Let's rotate the board!"; + } + } + + const rand = Math.floor(Math.random() * didYouKnow.length); + return output + "\n\nDid you know: " + didYouKnow[rand]; + } + +} + +/** + * Taken from http://numberwang.wikia.com/wiki/Numberwang_Wikia + * + * @constant + */ +const didYouKnow = [ + "Numberwang, contrary to popular belief, is a fruit and not a vegetable.", + "Robert Webb once got WordWang while presenting an episode of Numberwang.", + "The 6705th digit of pi is Numberwang.", + "Numberwang was invented on a Sevenday.", + "Contrary to popular belief, Albert Einstein always got good grades in Numberwang at school. He once scored ^4$ on a test.", + "680 asteroids have been named after Numberwang.", + "Archimedes is most famous for proclaiming \"That's Numberwang!\" during an epiphany about water displacement he had while taking a bath.", + "Numberwang Day is celebrated in Japan on every day of the year apart from June 6.", + "Biologists recently discovered Numberwang within a strand of human DNA.", + "Numbernot is a special type of non-Numberwang number. It is divisible by 3 and the letter \"y\".", + "Julie once got 612.04 Numberwangs in a single episode of Emmerdale.", + "In India, it is traditional to shout out \"Numberwang!\" instead of checkmate during games of chess.", + "There is a rule on Countdown which states that if you get Numberwang in the numbers round, you automatically win. It has only ever been invoked twice.", + "\"Numberwang\" was the third-most common baby name for a brief period in 1722.", + "\"The Lion King\" was loosely based on Numberwang.", + "\"A Numberwang a day keeps the doctor away\" is how Donny Cosy, the oldest man in the world, explained how he was in such good health at the age of 136.", + "The \"number lock\" button on a keyboard is based on the popular round of the same name in \"Numberwang\".", + "Cambridge became the first university to offer a course in Numberwang in 1567.", + "Schrödinger's Numberwang is a number that has been confusing dentists for centuries.", + "\"Harry Potter and the Numberwang of Numberwang\" was rejected by publishers -41 times before it became a bestseller.", + "\"Numberwang\" is the longest-running British game show in history; it has aired 226 seasons, each containing 19 episodes, which makes a grand total of 132 episodes.", + "The triple Numberwang bonus was discovered by archaeologist Thomas Jefferson in Somerset.", + "Numberwang is illegal in parts of Czechoslovakia.", + "Numberwang was discovered in India in the 12th century.", + "Numberwang has the chemical formula Zn4SO2(HgEs)3.", + "The first pack of cards ever created featured two \"Numberwang\" cards instead of jokers.", + "Julius Caesar was killed by an overdose of Numberwang.", + "The most Numberwang musical note is G#.", + "In 1934, the forty-third Google Doodle promoted the upcoming television show \"Numberwang on Ice\".", + "A recent psychology study found that toddlers were 17% faster at identifying numbers which were Numberwang.", + "There are 700 ways to commit a foul in the television show \"Numberwang\". All 700 of these fouls were committed by Julie in one single episode in 1473.", + "Astronomers suspect God is Numberwang.", + "Numberwang is the official beverage of Canada.", + "In the pilot episode of \"The Price is Right\", if a contestant got the value of an item exactly right they were told \"That's Numberwang!\" and immediately won ₹5.7032.", + "The first person to get three Numberwangs in a row was Madonna.", + "\"Numberwang\" has the code U+46402 in Unicode.", + "The musical note \"Numberwang\" is between D# and E♮.", + "Numberwang was first played on the moon in 1834.", +]; + +export default Numberwang; diff --git a/plugins/srktoolbox/src/core/operations/OR.mjs b/plugins/srktoolbox/src/core/operations/OR.mjs new file mode 100644 index 00000000..5c11e2a5 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/OR.mjs @@ -0,0 +1,80 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import { bitOp, or, BITWISE_OP_DELIMS } from "../lib/BitwiseOp.mjs"; + +/** + * OR operation + */ +class OR extends Operation { + + /** + * OR constructor + */ + constructor() { + super(); + + this.name = "OR"; + this.module = "Default"; + this.description = "使用给定的Key对输入进行OR(按位或)运算
e.g. fe023da5"; + this.infoURL = "https://wikipedia.org/wiki/Bitwise_operation#OR"; + this.inputType = "ArrayBuffer"; + this.outputType = "byteArray"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": BITWISE_OP_DELIMS + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const key = Utils.convertToByteArray(args[0].string || "", args[0].option); + input = new Uint8Array(input); + + return bitOp(input, key, or); + } + + /** + * Highlight OR + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight OR 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 OR; diff --git a/plugins/srktoolbox/src/core/operations/ObjectIdentifierToHex.mjs b/plugins/srktoolbox/src/core/operations/ObjectIdentifierToHex.mjs new file mode 100644 index 00000000..47f671ff --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ObjectIdentifierToHex.mjs @@ -0,0 +1,43 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import r from "jsrsasign"; +import Operation from "../Operation.mjs"; + +/** + * Object Identifier to Hex operation + */ +class ObjectIdentifierToHex extends Operation { + + /** + * ObjectIdentifierToHex constructor + */ + constructor() { + super(); + + this.name = "OID转十六进制"; + this.module = "PublicKey"; + this.description = "把对象标识符(object identifier,OID)转换成对应的十六进制字符串。"; + this.infoURL = "https://wikipedia.org/wiki/Object_identifier"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return r.KJUR.asn1.ASN1Util.oidIntToHex(input); + } + +} + +export default ObjectIdentifierToHex; diff --git a/plugins/srktoolbox/src/core/operations/OffsetChecker.mjs b/plugins/srktoolbox/src/core/operations/OffsetChecker.mjs new file mode 100644 index 00000000..e7cabc24 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/OffsetChecker.mjs @@ -0,0 +1,109 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Offset checker operation + */ +class OffsetChecker extends Operation { + + /** + * OffsetChecker constructor + */ + constructor() { + super(); + + this.name = "偏移检测"; + this.module = "Default"; + this.description = "高亮显示在所有给定内容中位置相同的字符,多个输入由给定的分隔符分隔。"; + this.inputType = "string"; + this.outputType = "html"; + this.args = [ + { + "name": "分隔符", + "type": "binaryString", + "value": "\\n\\n" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + const sampleDelim = args[0], + samples = input.split(sampleDelim), + outputs = new Array(samples.length); + let i = 0, + s = 0, + match = false, + inMatch = false, + chr; + + if (!samples || samples.length < 2) { + throw new OperationError("内容不足,至少需要两组文本,请添加更多内容或重新选择分隔符。"); + } + + // Initialise output strings + outputs.fill("", 0, samples.length); + + // Loop through each character in the first sample + for (i = 0; i < samples[0].length; i++) { + chr = samples[0][i]; + match = false; + + // Loop through each sample to see if the chars are the same + for (s = 1; s < samples.length; s++) { + if (samples[s][i] !== chr) { + match = false; + break; + } + match = true; + } + + // Write output for each sample + for (s = 0; s < samples.length; s++) { + if (samples[s].length <= i) { + if (inMatch) outputs[s] += ""; + if (s === samples.length - 1) inMatch = false; + continue; + } + + if (match && !inMatch) { + outputs[s] += "" + Utils.escapeHtml(samples[s][i]); + if (samples[s].length === i + 1) outputs[s] += ""; + if (s === samples.length - 1) inMatch = true; + } else if (!match && inMatch) { + outputs[s] += "" + Utils.escapeHtml(samples[s][i]); + if (s === samples.length - 1) inMatch = false; + } else { + outputs[s] += Utils.escapeHtml(samples[s][i]); + if (inMatch && samples[s].length === i + 1) { + outputs[s] += ""; + if (samples[s].length - 1 !== i) inMatch = false; + } + } + + if (samples[0].length - 1 === i) { + if (inMatch) outputs[s] += ""; + outputs[s] += Utils.escapeHtml(samples[s].substring(i + 1)); + } + } + } + + return outputs.join(sampleDelim); + } + +} + +export default OffsetChecker; diff --git a/plugins/srktoolbox/src/core/operations/OpticalCharacterRecognition.mjs b/plugins/srktoolbox/src/core/operations/OpticalCharacterRecognition.mjs new file mode 100644 index 00000000..ee586f61 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/OpticalCharacterRecognition.mjs @@ -0,0 +1,98 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @author mshwed [m@ttshwed.com] + * @author Matt C [me@mitt.dev] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { isImage } from "../lib/FileType.mjs"; +import { toBase64 } from "../lib/Base64.mjs"; +import { isWorkerEnvironment } from "../Utils.mjs"; + +import { createWorker } from "tesseract.js"; + +const OEM_MODES = ["Tesseract only", "LSTM only", "Tesseract/LSTM Combined"]; + +/** + * Optical Character Recognition operation + */ +class OpticalCharacterRecognition extends Operation { + + /** + * OpticalCharacterRecognition constructor + */ + constructor() { + super(); + + this.name = "光学字符识别"; + this.module = "OCR"; + this.description = "光学字符识别(英语:Optical Character Recognition,OCR)是指对文本资料的图像文件进行分析识别处理,获取文字及版面信息的过程。

目前仅支持识别英文。

支持的图像格式:png、 jpg、bmp、pbm。"; + this.infoURL = "https://wikipedia.org/wiki/Optical_character_recognition"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + name: "显示置信度", + type: "boolean", + value: true + }, + { + name: "OCR Engine Mode", + type: "option", + value: OEM_MODES, + defaultIndex: 1 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + const [showConfidence, oemChoice] = args; + + if (!isWorkerEnvironment()) throw new OperationError("此操作仅支持在浏览器运行"); + + const type = isImage(input); + if (!type) { + throw new OperationError("不支持的文件格式(仅支持:jpg、png、pbm、bmp)或未提供文件"); + } + + const assetDir = `${self.docURL}/assets/`; + const oem = OEM_MODES.indexOf(oemChoice); + + try { + self.sendStatusMessage("启动Tesseract worker……"); + const image = `data:${type};base64,${toBase64(input)}`; + const worker = await createWorker("eng", oem, { + workerPath: `${assetDir}tesseract/worker.min.js`, + langPath: `${assetDir}tesseract/lang-data`, + corePath: `${assetDir}tesseract/tesseract-core.wasm.js`, + logger: progress => { + if (isWorkerEnvironment()) { + self.sendStatusMessage(`状态: ${progress.status}${progress.status === "recognizing text" ? ` - ${(parseFloat(progress.progress)*100).toFixed(2)}%`: "" }`); + } + } + }); + self.sendStatusMessage("识别文本……"); + const result = await worker.recognize(image); + + if (showConfidence) { + return `置信度 ${result.data.confidence}%\n\n${result.data.text}`; + } else { + return result.data.text; + } + } catch (err) { + throw new OperationError(`OCR时报错:(${err})`); + } + } +} + +export default OpticalCharacterRecognition; diff --git a/plugins/srktoolbox/src/core/operations/PEMToHex.mjs b/plugins/srktoolbox/src/core/operations/PEMToHex.mjs new file mode 100644 index 00000000..b5f35991 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/PEMToHex.mjs @@ -0,0 +1,70 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @author cplussharp + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import { fromBase64 } from "../lib/Base64.mjs"; +import { toHexFast } from "../lib/Hex.mjs"; +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * PEM to Hex operation + */ +class PEMToHex extends Operation { + + /** + * PEMToHex constructor + */ + constructor() { + super(); + + this.name = "PEM转十六进制"; + this.module = "Default"; + this.description = "把PEM(Privacy Enhanced Mail)格式转换成十六进制DER(Distinguished Encoding Rules)格式字符串。"; + this.infoURL = "https://wikipedia.org/wiki/Privacy-Enhanced_Mail#Format"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + this.checks = [ + { + "pattern": "----BEGIN ([A-Z][A-Z ]+[A-Z])-----", + "args": [] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const output = []; + let match; + const regex = /-----BEGIN ([A-Z][A-Z ]+[A-Z])-----/g; + while ((match = regex.exec(input)) !== null) { + // find corresponding end tag + const indexBase64 = match.index + match[0].length; + const footer = `-----END ${match[1]}-----`; + const indexFooter = input.indexOf(footer, indexBase64); + if (indexFooter === -1) { + throw new OperationError(`PEM footer '${footer}' not found`); + } + + // decode base64 content + const base64 = input.substring(indexBase64, indexFooter); + const bytes = fromBase64(base64, "A-Za-z0-9+/=", "byteArray", true); + const hex = toHexFast(bytes); + output.push(hex); + } + return output.join("\n"); + } + +} + +export default PEMToHex; diff --git a/plugins/srktoolbox/src/core/operations/PEMToJWK.mjs b/plugins/srktoolbox/src/core/operations/PEMToJWK.mjs new file mode 100644 index 00000000..ab58ad5e --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/PEMToJWK.mjs @@ -0,0 +1,90 @@ +/** + * @author cplussharp + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import r from "jsrsasign"; +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * PEM to JWK operation + */ +class PEMToJWK extends Operation { + + /** + * PEMToJWK constructor + */ + constructor() { + super(); + + this.name = "PEM转JWK"; + this.module = "PublicKey"; + this.description = "将PEM格式转换为JSON Web Key格式。"; + this.infoURL = "https://datatracker.ietf.org/doc/html/rfc7517"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + this.checks = [ + { + "pattern": "-----BEGIN ((RSA |EC )?(PRIVATE|PUBLIC) KEY|CERTIFICATE)-----", + "args": [] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let output = ""; + let match; + const regex = /-----BEGIN ([A-Z][A-Z ]+[A-Z])-----/g; + while ((match = regex.exec(input)) !== null) { + // find corresponding end tag + const indexBase64 = match.index + match[0].length; + const header = input.substring(match.index, indexBase64); + const footer = `-----END ${match[1]}-----`; + const indexFooter = input.indexOf(footer, indexBase64); + if (indexFooter === -1) { + throw new OperationError(`未找到PEM footer '${footer}'`); + } + + const pem = input.substring(match.index, indexFooter + footer.length); + if (match[1].indexOf("KEY") !== -1) { + if (header === "-----BEGIN RSA PUBLIC KEY-----") { + throw new OperationError("不支持的RSA公钥格式。仅支持PKCS#8。"); + } + + const key = r.KEYUTIL.getKey(pem); + if (key.type === "DSA") { + throw new OperationError("JWK不支持DSA密钥。"); + } + const jwk = r.KEYUTIL.getJWKFromKey(key); + if (output.length > 0) { + output += "\n"; + } + output += JSON.stringify(jwk); + } else if (match[1] === "CERTIFICATE") { + const cert = new r.X509(); + cert.readCertPEM(pem); + const key = cert.getPublicKey(); + const jwk = r.KEYUTIL.getJWKFromKey(key); + if (output.length > 0) { + output += "\n"; + } + output += JSON.stringify(jwk); + } else { + throw new OperationError(`不支持的PEM类型'${match[1]}'`); + } + } + return output; + } +} + +export default PEMToJWK; diff --git a/plugins/srktoolbox/src/core/operations/PGPDecrypt.mjs b/plugins/srktoolbox/src/core/operations/PGPDecrypt.mjs new file mode 100644 index 00000000..90fb5c1e --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/PGPDecrypt.mjs @@ -0,0 +1,89 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import kbpgp from "kbpgp"; +import { ASP, importPrivateKey } from "../lib/PGP.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import * as es6promisify from "es6-promisify"; +const promisify = es6promisify.default ? es6promisify.default.promisify : es6promisify.promisify; + +/** + * PGP Decrypt operation + */ +class PGPDecrypt extends Operation { + + /** + * PGPDecrypt constructor + */ + constructor() { + super(); + + this.name = "PGP解密"; + this.module = "PGP"; + this.description = [ + "输入:你想要解密的经过ASCII-armour处理的PGP信息。", + "

", + "参数:经过ASCII-armour处理的接收者PGP私钥。", + "(如果有口令的话还需要填写口令)", + "

", + "PGP(英语:Pretty Good Privacy,直译:优良保密协议)是一套用于讯息加密、验证的应用程序。", + "

", + "此操作使用Keybase实现的PGP。", + ].join("\n"); + this.infoURL = "https://wikipedia.org/wiki/Pretty_Good_Privacy"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "接收者私钥", + "type": "text", + "value": "" + }, + { + "name": "私钥口令", + "type": "string", + "value": "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + * + * @throws {OperationError} if invalid private key + */ + async run(input, args) { + const encryptedMessage = input, + [privateKey, passphrase] = args, + keyring = new kbpgp.keyring.KeyRing(); + let plaintextMessage; + + if (!privateKey) throw new OperationError("请输入接收者的私钥"); + + const key = await importPrivateKey(privateKey, passphrase); + keyring.add_key_manager(key); + + try { + plaintextMessage = await promisify(kbpgp.unbox)({ + armored: encryptedMessage, + keyfetch: keyring, + asp: ASP + }); + } catch (err) { + throw new OperationError(`无法使用提供的私钥解密: ${err}`); + } + + return plaintextMessage.toString(); + } + +} + +export default PGPDecrypt; diff --git a/plugins/srktoolbox/src/core/operations/PGPDecryptAndVerify.mjs b/plugins/srktoolbox/src/core/operations/PGPDecryptAndVerify.mjs new file mode 100644 index 00000000..eaabb5f5 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/PGPDecryptAndVerify.mjs @@ -0,0 +1,126 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import kbpgp from "kbpgp"; +import { ASP, importPrivateKey, importPublicKey } from "../lib/PGP.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import * as es6promisify from "es6-promisify"; +const promisify = es6promisify.default ? es6promisify.default.promisify : es6promisify.promisify; + +/** + * PGP Decrypt and Verify operation + */ +class PGPDecryptAndVerify extends Operation { + + /** + * PGPDecryptAndVerify constructor + */ + constructor() { + super(); + + this.name = "PGP解密并验证"; + this.module = "PGP"; + this.description = [ + "输入:你想要验证的经过ASCII-armour处理的PGP信息。", + "

", + "参数:经过ASCII-armour处理的签名者PGP公钥", + "和经过ASCII-armour处理的接收者PGP私钥(以及口令,如果有)。", + "

", + "此操作用PGP来解密并验证数字签名。", + "

", + "PGP(英语:Pretty Good Privacy,直译:优良保密协议)是一套用于讯息加密、验证的应用程序。", + "

", + "此操作使用Keybase实现的PGP。", + ].join("\n"); + this.infoURL = "https://wikipedia.org/wiki/Pretty_Good_Privacy"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "签名者公钥", + "type": "text", + "value": "" + }, + { + "name": "接收者私钥", + "type": "text", + "value": "" + }, + { + "name": "私钥口令", + "type": "string", + "value": "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + const signedMessage = input, + [publicKey, privateKey, passphrase] = args, + keyring = new kbpgp.keyring.KeyRing(); + let unboxedLiterals; + + if (!publicKey) throw new OperationError("请输入签名者公钥。"); + if (!privateKey) throw new OperationError("请输入接收者私钥。"); + const privKey = await importPrivateKey(privateKey, passphrase); + const pubKey = await importPublicKey(publicKey); + keyring.add_key_manager(privKey); + keyring.add_key_manager(pubKey); + + try { + unboxedLiterals = await promisify(kbpgp.unbox)({ + armored: signedMessage, + keyfetch: keyring, + asp: ASP + }); + const ds = unboxedLiterals[0].get_data_signer(); + if (ds) { + const km = ds.get_key_manager(); + if (km) { + const signer = km.get_userids_mark_primary()[0].components; + let text = "签名: "; + if (signer.email || signer.username || signer.comment) { + if (signer.username) { + text += `${signer.username} `; + } + if (signer.comment) { + text += `(${signer.comment}) `; + } + if (signer.email) { + text += `<${signer.email}>`; + } + text += "\n"; + } + text += [ + `PGP key ID:${km.get_pgp_short_key_id()}`, + `PGP指纹:${km.get_pgp_fingerprint().toString("hex")}`, + `签名时间: ${new Date(ds.sig.when_generated() * 1000).toUTCString()}`, + "----------------------------------\n" + ].join("\n"); + text += unboxedLiterals.toString(); + return text.trim(); + } else { + throw new OperationError("Could not identify a key manager."); + } + } else { + throw new OperationError("数据似乎未被签名。"); + } + } catch (err) { + throw new OperationError(`无法验证消息: ${err}`); + } + } + +} + +export default PGPDecryptAndVerify; diff --git a/plugins/srktoolbox/src/core/operations/PGPEncrypt.mjs b/plugins/srktoolbox/src/core/operations/PGPEncrypt.mjs new file mode 100644 index 00000000..1346e54f --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/PGPEncrypt.mjs @@ -0,0 +1,81 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import kbpgp from "kbpgp"; +import { ASP, importPublicKey } from "../lib/PGP.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import * as es6promisify from "es6-promisify"; +const promisify = es6promisify.default ? es6promisify.default.promisify : es6promisify.promisify; + +/** + * PGP Encrypt operation + */ +class PGPEncrypt extends Operation { + + /** + * PGPEncrypt constructor + */ + constructor() { + super(); + + this.name = "PGP加密"; + this.module = "PGP"; + this.description = [ + "输入:你想要加密的信息。", + "

", + "参数:经过ASCII-armour处理的接收者PGP公钥。", + "

", + "PGP(英语:Pretty Good Privacy,直译:优良保密协议)是一套用于讯息加密、验证的应用程序。", + "

", + "此操作使用Keybase实现的PGP。", + ].join("\n"); + this.infoURL = "https://wikipedia.org/wiki/Pretty_Good_Privacy"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "接收者公钥", + "type": "text", + "value": "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + * + * @throws {OperationError} if failed private key import or failed encryption + */ + async run(input, args) { + const plaintextMessage = input, + plainPubKey = args[0]; + let encryptedMessage; + + if (!plainPubKey) throw new OperationError("请输入接收者的公钥"); + + const key = await importPublicKey(plainPubKey); + + try { + encryptedMessage = await promisify(kbpgp.box)({ + "msg": plaintextMessage, + "encrypt_for": key, + "asp": ASP + }); + } catch (err) { + throw new OperationError(`无法使用提供的公钥加密: ${err}`); + } + + return encryptedMessage.toString(); + } + +} + +export default PGPEncrypt; diff --git a/plugins/srktoolbox/src/core/operations/PGPEncryptAndSign.mjs b/plugins/srktoolbox/src/core/operations/PGPEncryptAndSign.mjs new file mode 100644 index 00000000..0eb24437 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/PGPEncryptAndSign.mjs @@ -0,0 +1,96 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import kbpgp from "kbpgp"; +import { ASP, importPrivateKey, importPublicKey } from "../lib/PGP.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import * as es6promisify from "es6-promisify"; +const promisify = es6promisify.default ? es6promisify.default.promisify : es6promisify.promisify; + +/** + * PGP Encrypt and Sign operation + */ +class PGPEncryptAndSign extends Operation { + + /** + * PGPEncryptAndSign constructor + */ + constructor() { + super(); + + this.name = "PGP加密并签名"; + this.module = "PGP"; + this.description = [ + "输入:你想要签名的明文内容。", + "

", + "参数:经过ASCII-armour处理的签名者私钥(以及口令,如果有)", + "经过ASCII-armour处理的接收者公钥。", + "

", + "此操作用PGP产生加密的数字签名。", + "

", + "PGP(英语:Pretty Good Privacy,直译:优良保密协议)是一套用于讯息加密、验证的应用程序。", + "

", + "此操作使用Keybase实现的PGP。", + ].join("\n"); + this.infoURL = "https://wikipedia.org/wiki/Pretty_Good_Privacy"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "签名者私钥", + "type": "text", + "value": "" + }, + { + "name": "私钥口令", + "type": "string", + "value": "" + }, + { + "name": "接收者公钥", + "type": "text", + "value": "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + * + * @throws {OperationError} if failure to sign message + */ + async run(input, args) { + const message = input, + [privateKey, passphrase, publicKey] = args; + let signedMessage; + + if (!privateKey) throw new OperationError("请输入签名者私钥。"); + if (!publicKey) throw new OperationError("请输入接收者公钥。"); + const privKey = await importPrivateKey(privateKey, passphrase); + const pubKey = await importPublicKey(publicKey); + + try { + signedMessage = await promisify(kbpgp.box)({ + "msg": message, + "encrypt_for": pubKey, + "sign_with": privKey, + "asp": ASP + }); + } catch (err) { + throw new OperationError(`无法给消息签名: ${err}`); + } + + return signedMessage; + } + +} + +export default PGPEncryptAndSign; diff --git a/plugins/srktoolbox/src/core/operations/PGPVerify.mjs b/plugins/srktoolbox/src/core/operations/PGPVerify.mjs new file mode 100644 index 00000000..4c46e259 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/PGPVerify.mjs @@ -0,0 +1,113 @@ +/** + * @author Matt C [me@mitt.dev] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +import kbpgp from "kbpgp"; +import { ASP, importPublicKey } from "../lib/PGP.mjs"; +import * as es6promisify from "es6-promisify"; +const promisify = es6promisify.default ? es6promisify.default.promisify : es6promisify.promisify; + +/** + * PGP Verify operation + */ +class PGPVerify extends Operation { + + /** + * PGPVerify constructor + */ + constructor() { + super(); + + this.name = "PGP验证"; + this.module = "PGP"; + this.description = [ + "输入:你想要验证的经过ASCII-armour处理的PGP信息。", + "

", + "参数:经过ASCII-armour处理的签名者PGP公钥。", + "

", + "此操作解密使用clearsign参数生成的PGP信息", + "

", + "PGP(英语:Pretty Good Privacy,直译:优良保密协议)是一套用于讯息加密、验证的应用程序。", + "

", + "此操作使用Keybase实现的PGP。", + ].join("\n"); + this.infoURL = "https://wikipedia.org/wiki/Pretty_Good_Privacy"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "签名者公钥", + "type": "text", + "value": "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + const signedMessage = input, + [publicKey] = args, + keyring = new kbpgp.keyring.KeyRing(); + let unboxedLiterals; + + if (!publicKey) throw new OperationError("请输入签名者的公钥"); + const pubKey = await importPublicKey(publicKey); + keyring.add_key_manager(pubKey); + + try { + unboxedLiterals = await promisify(kbpgp.unbox)({ + armored: signedMessage, + keyfetch: keyring, + asp: ASP + }); + const ds = unboxedLiterals[0].get_data_signer(); + if (ds) { + const km = ds.get_key_manager(); + if (km) { + const signer = km.get_userids_mark_primary()[0].components; + let text = "签名:"; + if (signer.email || signer.username || signer.comment) { + if (signer.username) { + text += `${signer.username} `; + } + if (signer.comment) { + text += `(${signer.comment}) `; + } + if (signer.email) { + text += `<${signer.email}>`; + } + text += "\n"; + } + text += [ + `PGP key ID: ${km.get_pgp_short_key_id()}`, + `PGP指纹: ${km.get_pgp_fingerprint().toString("hex")}`, + `签名日期: ${new Date(ds.sig.when_generated() * 1000).toUTCString()}`, + "----------------------------------\n" + ].join("\n"); + text += unboxedLiterals.toString(); + return text.trim(); + } else { + throw new OperationError("Could not identify a key manager."); + } + } else { + throw new OperationError("数据似乎未签名"); + } + } catch (err) { + throw new OperationError(`无法验证消息: ${err}`); + } + } + +} + +export default PGPVerify; diff --git a/plugins/srktoolbox/src/core/operations/PHPDeserialize.mjs b/plugins/srktoolbox/src/core/operations/PHPDeserialize.mjs new file mode 100644 index 00000000..2daaf216 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/PHPDeserialize.mjs @@ -0,0 +1,173 @@ +/** + * @author Jarmo van Lenthe [github.com/jarmovanlenthe] + * @copyright Jarmo van Lenthe + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * PHP Deserialize operation + */ +class PHPDeserialize extends Operation { + + /** + * PHPDeserialize constructor + */ + constructor() { + super(); + + this.name = "PHP反序列化"; + this.module = "Default"; + this.description = "对PHP序列化数据进行反序列化,输出JSON。

此操作不支持 object 标签。

例如:
a:2:{s:1:"a";i:10;i:0;a:1:{s:2:"ab";b:1;}}
反序列化为
{"a": 10,0: {"ab": true}}

输出合法JSON: JSON不支持整数作为键,但PHP支持。开启此项会将整数转为字符串。同样也会转义反斜杠。"; + this.infoURL = "http://www.phpinternalsbook.com/classes_objects/serialization.html"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "输出合法JSON", + "type": "boolean", + "value": true + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + /** + * Recursive method for deserializing. + * @returns {*} + */ + function handleInput() { + /** + * Read `length` characters from the input, shifting them out the input. + * @param length + * @returns {string} + */ + function read(length) { + let result = ""; + for (let idx = 0; idx < length; idx++) { + const char = inputPart.shift(); + if (char === undefined) { + throw new OperationError("输入内容不完整"); + } + result += char; + } + return result; + } + + /** + * Read characters from the input until `until` is found. + * @param until + * @returns {string} + */ + function readUntil(until) { + let result = ""; + for (;;) { + const char = read(1); + if (char === until) { + break; + } else { + result += char; + } + } + return result; + + } + + /** + * Read characters from the input that must be equal to `expect` + * @param expect + * @returns {string} + */ + function expect(expect) { + const result = read(expect.length); + if (result !== expect) { + throw new OperationError("检测到错误输入内容"); + } + return result; + } + + /** + * Helper function to handle deserialized arrays. + * @returns {Array} + */ + function handleArray() { + const items = parseInt(readUntil(":"), 10) * 2; + expect("{"); + const result = []; + let isKey = true; + let lastItem = null; + for (let idx = 0; idx < items; idx++) { + const item = handleInput(); + if (isKey) { + lastItem = item; + isKey = false; + } else { + const numberCheck = lastItem.match(/[0-9]+/); + if (args[0] && numberCheck && numberCheck[0].length === lastItem.length) { + result.push('"' + lastItem + '": ' + item); + } else { + result.push(lastItem + ": " + item); + } + isKey = true; + } + } + expect("}"); + return result; + } + + + const kind = read(1).toLowerCase(); + + switch (kind) { + case "n": + expect(";"); + return "null"; + case "i": + case "d": + case "b": { + expect(":"); + const data = readUntil(";"); + if (kind === "b") { + return (parseInt(data, 10) !== 0); + } + return data; + } + + case "a": + expect(":"); + return "{" + handleArray() + "}"; + + case "s": { + expect(":"); + const length = readUntil(":"); + expect("\""); + const value = read(length); + expect('";'); + if (args[0]) { + return '"' + value.replace(/"/g, '\\"') + '"'; // lgtm [js/incomplete-sanitization] + } else { + return '"' + value + '"'; + } + } + + default: + throw new OperationError("未知类型: " + kind); + } + } + + const inputPart = input.split(""); + return handleInput(); + } + +} + +export default PHPDeserialize; diff --git a/plugins/srktoolbox/src/core/operations/PHPSerialize.mjs b/plugins/srktoolbox/src/core/operations/PHPSerialize.mjs new file mode 100644 index 00000000..00fb1380 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/PHPSerialize.mjs @@ -0,0 +1,126 @@ +/** + * @author brun0ne [brunonblok@gmail.com] + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * PHP Serialize operation + */ +class PHPSerialize extends Operation { + + /** + * PHPSerialize constructor + */ + constructor() { + super(); + + this.name = "PHP Serialize"; + this.module = "Default"; + this.description = "Performs PHP serialization on JSON data.

This function does not support object tags.

Since PHP doesn't distinguish dicts and arrays, this operation is not always symmetric to PHP Deserialize.

Example:
[5,"abc",true]
becomes
a:3:{i:0;i:5;i:1;s:3:"abc";i:2;b:1;}"; + this.infoURL = "https://www.phpinternalsbook.com/php5/classes_objects/serialization.html"; + this.inputType = "JSON"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {JSON} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + /** + * Determines if a number is an integer + * @param {number} value + * @returns {boolean} + */ + function isInteger(value) { + return typeof value === "number" && parseInt(value.toString(), 10) === value; + } + + /** + * Serialize basic types + * @param {string | number | boolean} content + * @returns {string} + */ + function serializeBasicTypes(content) { + const basicTypes = { + "string": "s", + "integer": "i", + "float": "d", + "boolean": "b" + }; + /** + * Booleans + * cast to 0 or 1 + */ + if (typeof content === "boolean") { + return `${basicTypes.boolean}:${content ? 1 : 0}`; + } + /* Numbers */ + if (typeof content === "number") { + if (isInteger(content)) { + return `${basicTypes.integer}:${content.toString()}`; + } else { + return `${basicTypes.float}:${content.toString()}`; + } + } + /* Strings */ + if (typeof content === "string") + return `${basicTypes.string}:${content.length}:"${content}"`; + + /** This should be unreachable */ + throw new OperationError(`Encountered a non-implemented type: ${typeof content}`); + } + + /** + * Recursively serialize + * @param {*} object + * @returns {string} + */ + function serialize(object) { + /* Null */ + if (object == null) { + return `N;`; + } + + if (typeof object !== "object") { + /* Basic types */ + return `${serializeBasicTypes(object)};`; + } else if (object instanceof Array) { + /* Arrays */ + const serializedElements = []; + + for (let i = 0; i < object.length; i++) { + serializedElements.push(`${serialize(i)}${serialize(object[i])}`); + } + + return `a:${object.length}:{${serializedElements.join("")}}`; + } else if (object instanceof Object) { + /** + * Objects + * Note: the output cannot be guaranteed to be in the same order as the input + */ + const serializedElements = []; + const keys = Object.keys(object); + + for (const key of keys) { + serializedElements.push(`${serialize(key)}${serialize(object[key])}`); + } + + return `a:${keys.length}:{${serializedElements.join("")}}`; + } + + /** This should be unreachable */ + throw new OperationError(`Encountered a non-implemented type: ${typeof object}`); + } + + return serialize(input); + } +} + +export default PHPSerialize; diff --git a/plugins/srktoolbox/src/core/operations/PLISTViewer.mjs b/plugins/srktoolbox/src/core/operations/PLISTViewer.mjs new file mode 100644 index 00000000..7cbed5f9 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/PLISTViewer.mjs @@ -0,0 +1,135 @@ +/** + * @author n1073645 [n1073645@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; + +/** + * P-list Viewer operation + */ +class PlistViewer extends Operation { + + /** + * PlistViewer constructor + */ + constructor() { + super(); + + this.name = "P-list查看器"; + this.module = "Default"; + this.description = "在macOS、iOS、NeXTSTEP和GNUstep编程框架中,属性列表文件(Property list file)用于存储序列化对象。属性列表文件使用扩展名.plist,因此通常被称作p-list文件。

此操作将plist文件展示为人类可读格式。"; + this.infoURL = "https://wikipedia.org/wiki/Property_list"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + + // Regexes are designed to transform the xml format into a more readable string format. + input = input.slice(input.indexOf("/g, "plist => ") + .replace(//g, "{") + .replace(/<\/dict>/g, "}") + .replace(//g, "[") + .replace(/<\/array>/g, "]") + .replace(/.+<\/key>/g, m => `${m.slice(5, m.indexOf(/<\/key>/g)-5)}\t=> `) + .replace(/.+<\/real>/g, m => `${m.slice(6, m.indexOf(/<\/real>/g)-6)}\n`) + .replace(/.+<\/string>/g, m => `"${m.slice(8, m.indexOf(/<\/string>/g)-8)}"\n`) + .replace(/.+<\/integer>/g, m => `${m.slice(9, m.indexOf(/<\/integer>/g)-9)}\n`) + .replace(//g, m => "false") + .replace(//g, m => "true") + .replace(/<\/plist>/g, "/plist") + .replace(/.+<\/date>/g, m => `${m.slice(6, m.indexOf(/<\/integer>/g)-6)}`) + .replace(/[\s\S]+?<\/data>/g, m => `${m.slice(6, m.indexOf(/<\/data>/g)-6)}`) + .replace(/[ \t\r\f\v]/g, ""); + + /** + * Depending on the type of brace, it will increment the depth and amount of arrays accordingly. + * + * @param {string} elem + * @param {array} vals + * @param {number} offset + */ + function braces(elem, vals, offset) { + const temp = vals.indexOf(elem); + if (temp !== -1) { + depthCount += offset; + if (temp === 1) + arrCount += offset; + } + } + + let result = ""; + let arrCount = 0; + let depthCount = 0; + + /** + * Formats the input after the regex has replaced all of the relevant parts. + * + * @param {array} input + * @param {number} index + */ + function printIt(input, index) { + if (!(input.length)) + return; + + let temp = ""; + const origArr = arrCount; + let currElem = input[0]; + + // If the current position points at a larger dynamic structure. + if (currElem.indexOf("=>") !== -1) { + + // If the LHS also points at a larger structure (nested plists in a dictionary). + if (input[1].indexOf("=>") !== -1) + temp = currElem.slice(0, -2) + " => " + input[1].slice(0, -2) + " =>\n"; + else + temp = currElem.slice(0, -2) + " => " + input[1] + "\n"; + + input = input.slice(1); + } else { + // Controls the tab depth for how many closing braces there have been. + + braces(currElem, ["}", "]"], -1); + + // Has to be here since the formatting breaks otherwise. + temp = currElem + "\n"; + } + + currElem = input[0]; + + // Tab out to the correct distance. + result += ("\t".repeat(depthCount)); + + // If it is enclosed in an array show index. + if (arrCount > 0 && currElem !== "]") + result += index.toString() + " => "; + + result += temp; + + // Controls the tab depth for how many opening braces there have been. + braces(currElem, ["{", "["], 1); + + // If there has been a new array then reset index. + if (arrCount > origArr) + return printIt(input.slice(1), 0); + return printIt(input.slice(1), ++index); + } + + input = input.split("\n").filter(e => e !== ""); + printIt(input, 0); + return result; + } +} + +export default PlistViewer; diff --git a/plugins/srktoolbox/src/core/operations/PadLines.mjs b/plugins/srktoolbox/src/core/operations/PadLines.mjs new file mode 100644 index 00000000..589c42fc --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/PadLines.mjs @@ -0,0 +1,72 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; + +/** + * Pad lines operation + */ +class PadLines extends Operation { + + /** + * PadLines constructor + */ + constructor() { + super(); + + this.name = "填充行"; + this.module = "Default"; + this.description = "在每一行之前或之后添加特定数量的特定字符。"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "位置", + "type": "option", + "value": ["开头", "末尾"] + }, + { + "name": "长度", + "type": "number", + "value": 5 + }, + { + "name": "字符", + "type": "binaryShortString", + "value": " " + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [position, len, chr] = args, + lines = input.split("\n"); + let output = "", + i = 0; + + if (position === "开头") { + for (i = 0; i < lines.length; i++) { + output += lines[i].padStart(lines[i].length+len, chr) + "\n"; + } + } else if (position === "末尾") { + for (i = 0; i < lines.length; i++) { + output += lines[i].padEnd(lines[i].length+len, chr) + "\n"; + } + } + + return output.slice(0, output.length-1); + } + +} + +export default PadLines; diff --git a/plugins/srktoolbox/src/core/operations/ParseASN1HexString.mjs b/plugins/srktoolbox/src/core/operations/ParseASN1HexString.mjs new file mode 100644 index 00000000..635730c8 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ParseASN1HexString.mjs @@ -0,0 +1,57 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import r from "jsrsasign"; +import Operation from "../Operation.mjs"; + +/** + * Parse ASN.1 hex string operation + */ +class ParseASN1HexString extends Operation { + + /** + * ParseASN1HexString constructor + */ + constructor() { + super(); + + this.name = "解析ASN.1十六进制字符串"; + this.module = "PublicKey"; + this.description = "在电信和计算机网络领域,ASN.1(Abstract Syntax Notation One) 是一套标准,是描述数据的表示、编码、传输、解码的灵活的记法。

此操作可解析任意的ASN.1数据(必须为十六进制字符串,如果需要可以用“字符转十六进制”操作预处理)并展示结果树。"; + this.infoURL = "https://wikipedia.org/wiki/Abstract_Syntax_Notation_One"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "起始位置索引", + "type": "number", + "value": 0 + }, + { + "name": "截断超过以下长度的八字节字符串", + "type": "number", + "value": 32 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [index, truncateLen] = args; + return r.ASN1HEX.dump(input.replace(/\s/g, "").toLowerCase(), { + "ommit_long_octet": truncateLen + }, index); + } + +} + +export default ParseASN1HexString; diff --git a/plugins/srktoolbox/src/core/operations/ParseCSR.mjs b/plugins/srktoolbox/src/core/operations/ParseCSR.mjs new file mode 100644 index 00000000..39a76638 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ParseCSR.mjs @@ -0,0 +1,392 @@ +/** + * @author jkataja + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import r from "jsrsasign"; +import Operation from "../Operation.mjs"; +import { formatDnObj } from "../lib/PublicKey.mjs"; +import Utils from "../Utils.mjs"; + +/** + * Parse CSR operation + */ +class ParseCSR extends Operation { + + /** + * ParseCSR constructor + */ + constructor() { + super(); + + this.name = "解析CSR"; + this.module = "PublicKey"; + this.description = "解析X.509证书的证书签名申请(Certificate Signing Request,CSR)数据。"; + this.infoURL = "https://wikipedia.org/wiki/Certificate_signing_request"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "输入格式", + "type": "option", + "value": ["PEM"] + } + ]; + this.checks = [ + { + "pattern": "^-+BEGIN CERTIFICATE REQUEST-+\\r?\\n[\\da-z+/\\n\\r]+-+END CERTIFICATE REQUEST-+\\r?\\n?$", + "flags": "i", + "args": ["PEM"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} Human-readable description of a Certificate Signing Request (CSR). + */ + run(input, args) { + if (!input.length) { + return "输入内容为空"; + } + + // Parse the CSR into JSON parameters + const csrParam = new r.KJUR.asn1.csr.CSRUtil.getParam(input); + + return `主体\n${formatDnObj(csrParam.subject, 2)} +公钥${formatSubjectPublicKey(csrParam.sbjpubkey)} +签名${formatSignature(csrParam.sigalg, csrParam.sighex)} +请求的扩展程序${formatRequestedExtensions(csrParam)}`; + } +} + +/** + * Format signature of a CSR + * @param {*} sigAlg string + * @param {*} sigHex string + * @returns Multi-line string describing CSR Signature + */ +function formatSignature(sigAlg, sigHex) { + let out = `\n`; + + out += ` 算法: ${sigAlg}\n`; + + if (new RegExp("withdsa", "i").test(sigAlg)) { + const d = new r.KJUR.crypto.DSA(); + const sigParam = d.parseASN1Signature(sigHex); + out += ` 签名: + R: ${formatHexOntoMultiLine(absBigIntToHex(sigParam[0]))} + S: ${formatHexOntoMultiLine(absBigIntToHex(sigParam[1]))}\n`; + } else if (new RegExp("withrsa", "i").test(sigAlg)) { + out += ` 签名: ${formatHexOntoMultiLine(sigHex)}\n`; + } else { + out += ` 签名: ${formatHexOntoMultiLine(ensureHexIsPositiveInTwosComplement(sigHex))}\n`; + } + + return chop(out); +} + +/** + * Format Subject Public Key from PEM encoded public key string + * @param {*} publicKeyPEM string + * @returns Multi-line string describing Subject Public Key Info + */ +function formatSubjectPublicKey(publicKeyPEM) { + let out = "\n"; + + const publicKey = r.KEYUTIL.getKey(publicKeyPEM); + if (publicKey instanceof r.RSAKey) { + out += ` 算法: RSA + 长度: ${publicKey.n.bitLength()} bits + 模数: ${formatHexOntoMultiLine(absBigIntToHex(publicKey.n))} + 指数: ${publicKey.e} (0x${Utils.hex(publicKey.e)})\n`; + } else if (publicKey instanceof r.KJUR.crypto.ECDSA) { + out += ` 算法: ECDSA + 长度: ${publicKey.ecparams.keylen} bits + Pub: ${formatHexOntoMultiLine(publicKey.pubKeyHex)} + ASN1 OID: ${r.KJUR.crypto.ECDSA.getName(publicKey.getShortNISTPCurveName())} + NIST CURVE: ${publicKey.getShortNISTPCurveName()}\n`; + } else if (publicKey instanceof r.KJUR.crypto.DSA) { + out += ` 算法: DSA + 长度: ${publicKey.p.toString(16).length * 4} bits + Pub: ${formatHexOntoMultiLine(absBigIntToHex(publicKey.y))} + P: ${formatHexOntoMultiLine(absBigIntToHex(publicKey.p))} + Q: ${formatHexOntoMultiLine(absBigIntToHex(publicKey.q))} + G: ${formatHexOntoMultiLine(absBigIntToHex(publicKey.g))}\n`; + } else { + out += `不支持的公钥算法\n`; + } + + return chop(out); +} + +/** + * Format known extensions of a CSR + * @param {*} csrParam object + * @returns Multi-line string describing CSR Requested Extensions + */ +function formatRequestedExtensions(csrParam) { + const formattedExtensions = new Array(4).fill(""); + + if (Object.hasOwn(csrParam, "extreq")) { + for (const extension of csrParam.extreq) { + let parts = []; + switch (extension.extname) { + case "basicConstraints" : + parts = describeBasicConstraints(extension); + formattedExtensions[0] = ` 证书基本约束:${formatExtensionCriticalTag(extension)}\n${indent(4, parts)}`; + break; + case "keyUsage" : + parts = describeKeyUsage(extension); + formattedExtensions[1] = ` 证书密钥用法:${formatExtensionCriticalTag(extension)}\n${indent(4, parts)}`; + break; + case "extKeyUsage" : + parts = describeExtendedKeyUsage(extension); + formattedExtensions[2] = ` 扩展密钥用法:${formatExtensionCriticalTag(extension)}\n${indent(4, parts)}`; + break; + case "subjectAltName" : + parts = describeSubjectAlternativeName(extension); + formattedExtensions[3] = ` 主体备用名称:${formatExtensionCriticalTag(extension)}\n${indent(4, parts)}`; + break; + default : + parts = ["(不支持的扩展程序)"]; + formattedExtensions.push(` ${extension.extname}:${formatExtensionCriticalTag(extension)}\n${indent(4, parts)}`); + } + } + } + + let out = "\n"; + + formattedExtensions.forEach((formattedExtension) => { + if (formattedExtension !== undefined && formattedExtension !== null && formattedExtension.length !== 0) { + out += formattedExtension; + } + }); + + return chop(out); +} + +/** + * Format extension critical tag + * @param {*} extension Object + * @returns String describing whether the extension is critical or not + */ +function formatExtensionCriticalTag(extension) { + return Object.hasOwn(extension, "critical") && extension.critical ? " 关键" : ""; +} + +/** + * Format string input as a comma separated hex string on multiple lines + * @param {*} hex String + * @returns Multi-line string describing the Hex input + */ +function formatHexOntoMultiLine(hex) { + if (hex.length % 2 !== 0) { + hex = "0" + hex; + } + + return formatMultiLine(chop(hex.replace(/(..)/g, "$&:"))); +} + +/** + * Convert BigInt to abs value in Hex + * @param {*} int BigInt + * @returns String representing absolute value in Hex + */ +function absBigIntToHex(int) { + int = int < 0n ? -int : int; + + return ensureHexIsPositiveInTwosComplement(int.toString(16)); +} + +/** + * Ensure Hex String remains positive in 2's complement + * @param {*} hex String + * @returns Hex String ensuring value remains positive in 2's complement + */ +function ensureHexIsPositiveInTwosComplement(hex) { + if (hex.length % 2 !== 0) { + return "0" + hex; + } + + // prepend 00 if most significant bit is 1 (sign bit) + if (hex.length >=2 && (parseInt(hex.substring(0, 2), 16) & 128)) { + hex = "00" + hex; + } + + return hex; +} + +/** + * Format string onto multiple lines + * @param {*} longStr + * @returns String as a multi-line string + */ +function formatMultiLine(longStr) { + const lines = []; + + for (let remain = longStr ; remain !== "" ; remain = remain.substring(48)) { + lines.push(remain.substring(0, 48)); + } + + return lines.join("\n "); +} + +/** + * Describe Basic Constraints + * @see RFC 5280 4.2.1.9. Basic Constraints https://www.ietf.org/rfc/rfc5280.txt + * @param {*} extension CSR extension with the name `basicConstraints` + * @returns Array of strings describing Basic Constraints + */ +function describeBasicConstraints(extension) { + const constraints = []; + + constraints.push(`CA = ${Object.hasOwn(extension, "cA") && extension.cA ? "是" : "否"}`); + if (Object.hasOwn(extension, "pathLen")) constraints.push(`中级CA证书数目上限 = ${extension.pathLen}`); + + return constraints; +} + +/** + * Describe Key Usage extension permitted use cases + * @see RFC 5280 4.2.1.3. Key Usage https://www.ietf.org/rfc/rfc5280.txt + * @param {*} extension CSR extension with the name `keyUsage` + * @returns Array of strings describing Key Usage extension permitted use cases + */ +function describeKeyUsage(extension) { + const usage = []; + + const kuIdentifierToName = { + digitalSignature: "签名", + nonRepudiation: "非否认", + keyEncipherment: "密钥加密", + dataEncipherment: "数据加密", + keyAgreement: "密钥协商", + keyCertSign: "证书签名", + cRLSign: "CRL签名", + encipherOnly: "仅加密", + decipherOnly: "仅解密", + }; + + if (Object.hasOwn(extension, "names")) { + extension.names.forEach((ku) => { + if (Object.hasOwn(kuIdentifierToName, ku)) { + usage.push(kuIdentifierToName[ku]); + } else { + usage.push(`未知的密钥用法 (${ku})`); + } + }); + } + + if (usage.length === 0) usage.push("(无)"); + + return usage; +} + +/** + * Describe Extended Key Usage extension permitted use cases + * @see RFC 5280 4.2.1.12. Extended Key Usage https://www.ietf.org/rfc/rfc5280.txt + * @param {*} extension CSR extension with the name `extendedKeyUsage` + * @returns Array of strings describing Extended Key Usage extension permitted use cases + */ +function describeExtendedKeyUsage(extension) { + const usage = []; + + const ekuIdentifierToName = { + "serverAuth": "TLS WWW 服务器身份验证", + "clientAuth": "TLS WWW 客户端身份验证", + "codeSigning": "代码签名", + "emailProtection": "电子邮件保护 (S/MIME)", + "timeStamping": "可信任时间戳", + "1.3.6.1.4.1.311.2.1.21": "Microsoft Individual Code Signing", // msCodeInd + "1.3.6.1.4.1.311.2.1.22": "Microsoft Commercial Code Signing", // msCodeCom + "1.3.6.1.4.1.311.10.3.1": "Microsoft Trust List Signing", // msCTLSign + "1.3.6.1.4.1.311.10.3.3": "Microsoft Server Gated Crypto", // msSGC + "1.3.6.1.4.1.311.10.3.4": "Microsoft Encrypted File System", // msEFS + "1.3.6.1.4.1.311.20.2.2": "Microsoft Smartcard Login", // msSmartcardLogin + "2.16.840.1.113730.4.1": "Netscape Server Gated Crypto", // nsSGC + }; + + if (Object.hasOwn(extension, "array")) { + extension.array.forEach((eku) => { + if (Object.hasOwn(ekuIdentifierToName, eku)) { + usage.push(ekuIdentifierToName[eku]); + } else { + usage.push(eku); + } + }); + } + + if (usage.length === 0) usage.push("(无)"); + + return usage; +} + +/** + * Format Subject Alternative Names from the name `subjectAltName` extension + * @see RFC 5280 4.2.1.6. Subject Alternative Name https://www.ietf.org/rfc/rfc5280.txt + * @param {*} extension object + * @returns Array of strings describing Subject Alternative Name extension + */ +function describeSubjectAlternativeName(extension) { + const names = []; + + if (Object.hasOwn(extension, "extname") && extension.extname === "subjectAltName") { + if (Object.hasOwn(extension, "array")) { + for (const altName of extension.array) { + Object.keys(altName).forEach((key) => { + switch (key) { + case "rfc822": + names.push(`EMAIL: ${altName[key]}`); + break; + case "dns": + names.push(`DNS: ${altName[key]}`); + break; + case "uri": + names.push(`URI: ${altName[key]}`); + break; + case "ip": + names.push(`IP: ${altName[key]}`); + break; + case "dn": + names.push(`DIR: ${altName[key].str}`); + break; + case "other" : + names.push(`Other: ${altName[key].oid}::${altName[key].value.utf8str.str}`); + break; + default: + names.push(`(无法格式化SAN '${key}':${altName[key]})\n`); + } + }); + } + } + } + + return names; +} + +/** + * Join an array of strings and add leading spaces to each line. + * @param {*} n How many leading spaces + * @param {*} parts Array of strings + * @returns Joined and indented string. + */ +function indent(n, parts) { + const fluff = " ".repeat(n); + return fluff + parts.join("\n" + fluff) + "\n"; +} + +/** + * Remove last character from a string. + * @param {*} s String + * @returns Chopped string. + */ +function chop(s) { + return s.substring(0, s.length - 1); +} + +export default ParseCSR; diff --git a/plugins/srktoolbox/src/core/operations/ParseColourCode.mjs b/plugins/srktoolbox/src/core/operations/ParseColourCode.mjs new file mode 100644 index 00000000..d61c2ea4 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ParseColourCode.mjs @@ -0,0 +1,198 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; + +/** + * Parse colour code operation + */ +class ParseColourCode extends Operation { + + /** + * ParseColourCode constructor + */ + constructor() { + super(); + + this.name = "解析颜色代码"; + this.module = "Default"; + this.description = "将标准格式的颜色代码转换成其它标准格式并展示这个颜色。

示例输入
  • #d9edf7
  • rgba(217,237,247,1)
  • hsla(200,65%,91%,1)
  • cmyk(0.12, 0.04, 0.00, 0.03)
"; + this.infoURL = "https://wikipedia.org/wiki/Web_colors"; + this.inputType = "string"; + this.outputType = "html"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + let m = null, + r = 0, g = 0, b = 0, a = 1; + + // Read in the input + if ((m = input.match(/#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/i))) { + // Hex - #d9edf7 + r = parseInt(m[1], 16); + g = parseInt(m[2], 16); + b = parseInt(m[3], 16); + } else if ((m = input.match(/rgba?\((\d{1,3}(?:\.\d+)?),\s?(\d{1,3}(?:\.\d+)?),\s?(\d{1,3}(?:\.\d+)?)(?:,\s?(\d(?:\.\d+)?))?\)/i))) { + // RGB or RGBA - rgb(217,237,247) or rgba(217,237,247,1) + r = parseFloat(m[1]); + g = parseFloat(m[2]); + b = parseFloat(m[3]); + a = m[4] ? parseFloat(m[4]) : 1; + } else if ((m = input.match(/hsla?\((\d{1,3}(?:\.\d+)?),\s?(\d{1,3}(?:\.\d+)?)%,\s?(\d{1,3}(?:\.\d+)?)%(?:,\s?(\d(?:\.\d+)?))?\)/i))) { + // HSL or HSLA - hsl(200, 65%, 91%) or hsla(200, 65%, 91%, 1) + const h_ = parseFloat(m[1]) / 360, + s_ = parseFloat(m[2]) / 100, + l_ = parseFloat(m[3]) / 100, + rgb_ = ParseColourCode._hslToRgb(h_, s_, l_); + + r = rgb_[0]; + g = rgb_[1]; + b = rgb_[2]; + a = m[4] ? parseFloat(m[4]) : 1; + } else if ((m = input.match(/cmyk\((\d(?:\.\d+)?),\s?(\d(?:\.\d+)?),\s?(\d(?:\.\d+)?),\s?(\d(?:\.\d+)?)\)/i))) { + // CMYK - cmyk(0.12, 0.04, 0.00, 0.03) + const c_ = parseFloat(m[1]), + m_ = parseFloat(m[2]), + y_ = parseFloat(m[3]), + k_ = parseFloat(m[4]); + + r = Math.round(255 * (1 - c_) * (1 - k_)); + g = Math.round(255 * (1 - m_) * (1 - k_)); + b = Math.round(255 * (1 - y_) * (1 - k_)); + } + + const hsl_ = ParseColourCode._rgbToHsl(r, g, b), + h = Math.round(hsl_[0] * 360), + s = Math.round(hsl_[1] * 100), + l = Math.round(hsl_[2] * 100); + let k = 1 - Math.max(r/255, g/255, b/255), + c = (1 - r/255 - k) / (1 - k), + y = (1 - b/255 - k) / (1 - k); + + m = (1 - g/255 - k) / (1 - k); + + c = isNaN(c) ? "0" : c.toFixed(2); + m = isNaN(m) ? "0" : m.toFixed(2); + y = isNaN(y) ? "0" : y.toFixed(2); + k = k.toFixed(2); + + const hex = "#" + + Math.round(r).toString(16).padStart(2, "0") + + Math.round(g).toString(16).padStart(2, "0") + + Math.round(b).toString(16).padStart(2, "0"), + rgb = "rgb(" + r + ", " + g + ", " + b + ")", + rgba = "rgba(" + r + ", " + g + ", " + b + ", " + a + ")", + hsl = "hsl(" + h + ", " + s + "%, " + l + "%)", + hsla = "hsla(" + h + ", " + s + "%, " + l + "%, " + a + ")", + cmyk = "cmyk(" + c + ", " + m + ", " + y + ", " + k + ")"; + + // Generate output + return `
+Hex: ${hex} +RGB: ${rgb} +RGBA: ${rgba} +HSL: ${hsl} +HSLA: ${hsla} +CMYK: ${cmyk} +`; + } + + /** + * Converts an HSL color value to RGB. Conversion formula + * adapted from http://en.wikipedia.org/wiki/HSL_colorSpace. + * Assumes h, s, and l are contained in the set [0, 1] and + * returns r, g, and b in the set [0, 255]. + * + * @author Mohsen (http://stackoverflow.com/a/9493060) + * + * @param {number} h - The hue + * @param {number} s - The saturation + * @param {number} l - The lightness + * @return {Array} The RGB representation + */ + static _hslToRgb(h, s, l) { + let r, g, b; + + if (s === 0) { + r = g = b = l; // achromatic + } else { + const hue2rgb = function hue2rgb(p, q, t) { + if (t < 0) t += 1; + if (t > 1) t -= 1; + if (t < 1/6) return p + (q - p) * 6 * t; + if (t < 1/2) return q; + if (t < 2/3) return p + (q - p) * (2/3 - t) * 6; + return p; + }; + + const q = l < 0.5 ? l * (1 + s) : l + s - l * s; + const p = 2 * l - q; + r = hue2rgb(p, q, h + 1/3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1/3); + } + + return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)]; + } + + /** + * Converts an RGB color value to HSL. Conversion formula + * adapted from http://en.wikipedia.org/wiki/HSL_colorSpace. + * Assumes r, g, and b are contained in the set [0, 255] and + * returns h, s, and l in the set [0, 1]. + * + * @author Mohsen (http://stackoverflow.com/a/9493060) + * + * @param {number} r - The red color value + * @param {number} g - The green color value + * @param {number} b - The blue color value + * @return {Array} The HSL representation + */ + static _rgbToHsl(r, g, b) { + r /= 255; g /= 255; b /= 255; + const max = Math.max(r, g, b), + min = Math.min(r, g, b), + l = (max + min) / 2; + let h, s; + + if (max === min) { + h = s = 0; // achromatic + } else { + const d = max - min; + s = l > 0.5 ? d / (2 - max - min) : d / (max + min); + switch (max) { + case r: h = (g - b) / d + (g < b ? 6 : 0); break; + case g: h = (b - r) / d + 2; break; + case b: h = (r - g) / d + 4; break; + } + h /= 6; + } + + return [h, s, l]; + } +} + +export default ParseColourCode; diff --git a/plugins/srktoolbox/src/core/operations/ParseDateTime.mjs b/plugins/srktoolbox/src/core/operations/ParseDateTime.mjs new file mode 100644 index 00000000..eac20a24 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ParseDateTime.mjs @@ -0,0 +1,85 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import moment from "moment-timezone"; +import {DATETIME_FORMATS, FORMAT_EXAMPLES} from "../lib/DateTime.mjs"; + +/** + * Parse DateTime operation + */ +class ParseDateTime extends Operation { + + /** + * ParseDateTime constructor + */ + constructor() { + super(); + + this.name = "解析DateTime"; + this.module = "Default"; + this.description = "根据指定的格式与时区解析DateTime字符串。可解析出以下内容:
  • 日期
  • 时间
  • 上下午 (AM/PM)
  • 时区
  • UTC偏移量
  • 夏令时
  • 闰年
  • 当月天数
  • 当年的第几天
  • 星期
  • 季度
不输入任何内容来查看格式示例字符串。"; + this.infoURL = "https://momentjs.com/docs/#/parsing/string-format/"; + this.inputType = "string"; + this.outputType = "html"; + this.args = [ + { + "name": "内置格式", + "type": "populateOption", + "value": DATETIME_FORMATS, + "target": 1 + }, + { + "name": "输入格式", + "type": "binaryString", + "value": "DD/MM/YYYY HH:mm:ss" + }, + { + "name": "输入时区", + "type": "option", + "value": ["UTC"].concat(moment.tz.names()) + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + const inputFormat = args[1], + inputTimezone = args[2]; + let date, + output = ""; + + try { + date = moment.tz(input, inputFormat, inputTimezone); + if (!date || date.format() === "Invalid date") throw Error; + } catch (err) { + return `无效格式。\n\n${FORMAT_EXAMPLES}`; + } + + output += "日期: " + date.format("dddd Do MMMM YYYY") + + "\n时间: " + date.format("HH:mm:ss") + + "\n上下午: " + date.format("A") + + "\n时区: " + date.format("z") + + "\nUTC偏移量: " + date.format("ZZ") + + "\n\n夏令时: " + date.isDST() + + "\n闰年: " + date.isLeapYear() + + "\n当月天数: " + date.daysInMonth() + + "\n\n当年第几天: " + date.dayOfYear() + + "\n当年第几周: " + date.week() + + "\n季度: " + date.quarter(); + + return output; + } + +} + +export default ParseDateTime; diff --git a/plugins/srktoolbox/src/core/operations/ParseIPRange.mjs b/plugins/srktoolbox/src/core/operations/ParseIPRange.mjs new file mode 100644 index 00000000..91d03ea0 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ParseIPRange.mjs @@ -0,0 +1,91 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @author Klaxon [klaxon@veyr.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import {ipv4CidrRange, ipv4HyphenatedRange, ipv4ListedRange, ipv6CidrRange, ipv6HyphenatedRange, ipv6ListedRange} from "../lib/IP.mjs"; + +/** + * Parse IP range operation + */ +class ParseIPRange extends Operation { + + /** + * ParseIPRange constructor + */ + constructor() { + super(); + + this.name = "解析IP范围"; + this.module = "Default"; + this.description = "输入 CIDR 范围 (例: 10.0.0.0/24) 、连字符表示的范围 (例: 10.0.0.0 - 10.0.1.0)或一系列的IP和CIDR(回车分隔),此操作会解析输入IP范围的网络信息与列出所有可能的IP地址。

支持IPv6但不会列出详细地址。"; + this.infoURL = "https://wikipedia.org/wiki/Subnetwork"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "包括网络信息", + "type": "boolean", + "value": true + }, + { + "name": "列出所有IP地址", + "type": "boolean", + "value": true + }, + { + "name": "允许大数据量查询", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [ + includeNetworkInfo, + enumerateAddresses, + allowLargeList + ] = args; + + // Check what type of input we are looking at + const ipv4CidrRegex = /^\s*((?:\d{1,3}\.){3}\d{1,3})\/(\d\d?)\s*$/, + ipv4RangeRegex = /^\s*((?:\d{1,3}\.){3}\d{1,3})\s*-\s*((?:\d{1,3}\.){3}\d{1,3})\s*$/, + ipv4ListRegex = /^\s*(((?:\d{1,3}\.){3}\d{1,3})(\/(\d\d?))?(\n|$)(\n*))+\s*$/, + ipv6CidrRegex = /^\s*(((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\4)::|:\b|(?![\dA-F])))|(?!\3\4)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4}))\/(\d\d?\d?)\s*$/i, + ipv6RangeRegex = /^\s*(((?=.*::)(?!.*::[^-]+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\4)::|:\b|(?![\dA-F])))|(?!\3\4)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4}))\s*-\s*(((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\17)::|:\b|(?![\dA-F])))|(?!\16\17)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4}))\s*$/i, + ipv6ListRegex = /^\s*((((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\4)::|:\b|(?![\dA-F])))|(?!\3\4)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4}))(\/(\d\d?\d?))?(\n|$)(\n*))+\s*$/i; + let match; + + if ((match = ipv4CidrRegex.exec(input))) { + return ipv4CidrRange(match, includeNetworkInfo, enumerateAddresses, allowLargeList); + } else if ((match = ipv4RangeRegex.exec(input))) { + return ipv4HyphenatedRange(match, includeNetworkInfo, enumerateAddresses, allowLargeList); + } else if ((match = ipv4ListRegex.exec(input))) { + return ipv4ListedRange(match, includeNetworkInfo, enumerateAddresses, allowLargeList); + } else if ((match = ipv6CidrRegex.exec(input))) { + return ipv6CidrRange(match, includeNetworkInfo); + } else if ((match = ipv6RangeRegex.exec(input))) { + return ipv6HyphenatedRange(match, includeNetworkInfo); + } else if ((match = ipv6ListRegex.exec(input))) { + return ipv6ListedRange(match, includeNetworkInfo); + } else { + throw new OperationError("无效的输入。\n\n此操作支持CIDR范围(例:10.0.0.0/24)或连字符表示的范围(例:10.0.0.0 - 10.0.1.0)。同时支持IPv6。"); + } + } + +} + + +export default ParseIPRange; diff --git a/plugins/srktoolbox/src/core/operations/ParseIPv4Header.mjs b/plugins/srktoolbox/src/core/operations/ParseIPv4Header.mjs new file mode 100644 index 00000000..d09137e4 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ParseIPv4Header.mjs @@ -0,0 +1,132 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import {fromHex, toHex} from "../lib/Hex.mjs"; +import {ipv4ToStr, protocolLookup} from "../lib/IP.mjs"; +import TCPIPChecksum from "./TCPIPChecksum.mjs"; + +/** + * Parse IPv4 header operation + */ +class ParseIPv4Header extends Operation { + + /** + * ParseIPv4Header constructor + */ + constructor() { + super(); + + this.name = "解析IPv4标头"; + this.module = "Default"; + this.description = "输入IPv4标头(Header)数据,此操作对其进行解析,用易于阅读的格式展示各字段信息。"; + this.infoURL = "https://wikipedia.org/wiki/IPv4#Header"; + this.inputType = "string"; + this.outputType = "html"; + this.args = [ + { + "name": "输入格式", + "type": "option", + "value": ["十六进制", "原始"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + const format = args[0]; + let output; + + if (format === "十六进制") { + input = fromHex(input); + } else if (format === "原始") { + input = new Uint8Array(Utils.strToArrayBuffer(input)); + } else { + throw new OperationError("未知的输入格式。"); + } + + let ihl = input[0] & 0x0f; + const dscp = (input[1] >>> 2) & 0x3f, + ecn = input[1] & 0x03, + length = input[2] << 8 | input[3], + identification = input[4] << 8 | input[5], + flags = (input[6] >>> 5) & 0x07, + fragOffset = (input[6] & 0x1f) << 8 | input[7], + ttl = input[8], + protocol = input[9], + checksum = input[10] << 8 | input[11], + srcIP = input[12] << 24 | input[13] << 16 | input[14] << 8 | input[15], + dstIP = input[16] << 24 | input[17] << 16 | input[18] << 8 | input[19], + checksumHeader = input.slice(0, 10).concat([0, 0]).concat(input.slice(12, 20)); + let version = (input[0] >>> 4) & 0x0f, + options = []; + + + // Version + if (version !== 4) { + version = version + " (错误:对于IPv4标头,版本值应该为4)"; + } + + // IHL + if (ihl < 5) { + ihl = ihl + " (错误:应至少为5)"; + } else if (ihl > 5) { + // sort out options... + const optionsLen = ihl * 4 - 20; + options = input.slice(20, optionsLen + 20); + } + + // Protocol + const protocolInfo = protocolLookup[protocol] || {keyword: "", protocol: ""}; + + // Checksum + const correctChecksum = (new TCPIPChecksum).run(checksumHeader), + givenChecksum = Utils.hex(checksum); + let checksumResult; + if (correctChecksum === givenChecksum) { + checksumResult = givenChecksum + " (正确)"; + } else { + checksumResult = givenChecksum + " (不正确,应为 " + correctChecksum + ")"; + } + + output = ` + + + + + + + + + + + + +`; + + if (ihl > 5) { + output += ``; + } + + return output + "
字段
版本${version}
标头长度(IHL)${ihl} (${ihl * 4} 字节)
区分服务码点(DSCP)${dscp}
显式拥塞通告(ECN)${ecn}
全长${length} 字节 + IP标头: ${ihl * 4} 字节 + 数据: ${length - ihl * 4} 字节
标识符0x${Utils.hex(identification)} (${identification})
标志位0x${Utils.hex(flags, 2)} + 保留位:${flags >> 2} (必须为0) + 禁止分片:${flags >> 1 & 1} + 更多分片:${flags & 1}
分片偏移${fragOffset}
存活时间${ttl}
协议${protocol}, ${protocolInfo.protocol} (${protocolInfo.keyword})
标头检验和${checksumResult}
源地址${ipv4ToStr(srcIP)}
目的地址${ipv4ToStr(dstIP)}
选项${toHex(options)}
"; + } + +} + +export default ParseIPv4Header; diff --git a/plugins/srktoolbox/src/core/operations/ParseIPv6Address.mjs b/plugins/srktoolbox/src/core/operations/ParseIPv6Address.mjs new file mode 100644 index 00000000..94edbd99 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ParseIPv6Address.mjs @@ -0,0 +1,278 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import {strToIpv6, ipv6ToStr, ipv4ToStr, IPV6_REGEX} from "../lib/IP.mjs"; +import BigNumber from "bignumber.js"; + +/** + * Parse IPv6 address operation + */ +class ParseIPv6Address extends Operation { + + /** + * ParseIPv6Address constructor + */ + constructor() { + super(); + + this.name = "解析IPv6地址"; + this.module = "Default"; + this.description = "显示有效IPv6地址的longhand和shorthand版本。

可识别所有保留范围以及封装/隧道地址如Teredo和6to4。"; + this.infoURL = "https://wikipedia.org/wiki/IPv6_address"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let match, + output = ""; + + if ((match = IPV6_REGEX.exec(input))) { + const ipv6 = strToIpv6(match[1]), + longhand = ipv6ToStr(ipv6), + shorthand = ipv6ToStr(ipv6, true); + + output += "Longhand: " + longhand + "\nShorthand: " + shorthand + "\n"; + + // Detect reserved addresses + if (shorthand === "::") { + // Unspecified address + output += "\n未指定的地址:对应IPv4的0.0.0.0/32。"; + output += "\n未指定的地址范围: ::/128"; + } else if (shorthand === "::1") { + // Loopback address + output += "\n本地回环地址,对应IPv4的127.0.0.1/8。"; + output += "\n本地回环地址范围: ::1/128"; + } else if (ipv6[0] === 0 && ipv6[1] === 0 && ipv6[2] === 0 && + ipv6[3] === 0 && ipv6[4] === 0 && ipv6[5] === 0xffff) { + // IPv4-mapped IPv6 address + output += "\n检测到IPv4映射地址。对于混合双栈的网络部署,IPv6客户端默认被原生支持,IPv4的客户端展示为位于此类地址的IPv6客户端。"; + output += "\nIPv4映射地址: " + ipv4ToStr((ipv6[6] << 16) + ipv6[7]); + output += "\nIPv4映射地址范围: ::ffff:0:0/96"; + } else if (ipv6[0] === 0 && ipv6[1] === 0 && ipv6[2] === 0 && + ipv6[3] === 0 && ipv6[4] === 0xffff && ipv6[5] === 0) { + // IPv4-translated address + output += "\n检测到IPv4翻译地址。无状态IP/ICMP翻译(SIIT)技术。详细信息请参考RFC 6145 和 6052。"; + output += "\nIPv4翻译地址: " + ipv4ToStr((ipv6[6] << 16) + ipv6[7]); + output += "\nIPv4翻译地址范围: ::ffff:0:0:0/96"; + } else if (ipv6[0] === 0x100) { + // Discard prefix per RFC 6666 + output += "\n检测到丢弃前缀(Discard Prefix)。一般用于抵御DoS攻击的黑洞路由。详细信息请参考RFC 6666。"; + output += "\n丢弃地址范围: 100::/64"; + } else if (ipv6[0] === 0x64 && ipv6[1] === 0xff9b && ipv6[2] === 0 && + ipv6[3] === 0 && ipv6[4] === 0 && ipv6[5] === 0) { + // IPv4/IPv6 translation per RFC 6052 + output += "\n检测到“Well-Known”前缀的IPv4/IPv6翻译地址。详细信息请参考RFC 6052。"; + output += "\nIPv4翻译地址: " + ipv4ToStr((ipv6[6] << 16) + ipv6[7]); + output += "\n“Well-Known”前缀范围: 64:ff9b::/96"; + } else if (ipv6[0] === 0x2001 && ipv6[1] === 0) { + // Teredo tunneling + output += "\n检测到Teredo隧道IPv6地址。\n"; + const serverIpv4 = (ipv6[2] << 16) + ipv6[3], + udpPort = (~ipv6[5]) & 0xffff, + clientIpv4 = ~((ipv6[6] << 16) + ipv6[7]), + flagCone = (ipv6[4] >>> 15) & 1, + flagR = (ipv6[4] >>> 14) & 1, + flagRandom1 = (ipv6[4] >>> 10) & 15, + flagUg = (ipv6[4] >>> 8) & 3, + flagRandom2 = ipv6[4] & 255; + + output += "\n服务器IPv4地址: " + ipv4ToStr(serverIpv4) + + "\n客户端IPv4地址: " + ipv4ToStr(clientIpv4) + + "\n客户端UDP端口: " + udpPort + + "\nFlag:" + + "\n\tCone: " + flagCone; + + if (flagCone) { + output += " (客户端位于锥形NAT后)"; + } else { + output += " (客户端没有位于锥形NAT后)"; + } + + output += "\n\tR: " + flagR; + + if (flagR) { + output += " 错误:这个flag应该设置为0。详细信息请参考RFC 5991 和 4380。"; + } + + output += "\n\tRandom1: " + Utils.bin(flagRandom1, 4) + + "\n\tUG: " + Utils.bin(flagUg, 2); + + if (flagUg) { + output += " 错误:这个flag应该设置为00。请参考RFC 4380。"; + } + + output += "\n\tRandom2: " + Utils.bin(flagRandom2, 8); + + if (!flagR && !flagUg && flagRandom1 && flagRandom2) { + output += "\n\n有效Teredo地址,符合RFC 4380 和 RFC 5991。"; + } else if (!flagR && !flagUg) { + output += "\n\n有效Teredo地址,符合RFC 4380,但不符合RFC 5991 (Teredo Security Updates),因为Flag字段中不包括随机数值。"; + } else { + output += "\n\n无效Teredo地址。"; + } + output += "\n\nTeredo前缀范围: 2001::/32"; + } else if (ipv6[0] === 0x2001 && ipv6[1] === 0x2 && ipv6[2] === 0) { + // Benchmarking + output += "\n分配给Benchmarking Methodology Working Group (BMWG)用于IPv6性能测试。对应IPv4的198.18.0.0/15。详细信息请参考RFC 5180。"; + output += "\nBMWG范围: 2001:2::/48"; + } else if (ipv6[0] === 0x2001 && ipv6[1] >= 0x10 && ipv6[1] <= 0x1f) { + // ORCHIDv1 + output += "\n废弃的ORCHIDv1 (Overlay Routable Cryptographic Hash Identifiers)地址。\nORCHIDv1范围: 2001:10::/28\nORCHIDv2现在使用2001:20::/28。"; + } else if (ipv6[0] === 0x2001 && ipv6[1] >= 0x20 && ipv6[1] <= 0x2f) { + // ORCHIDv2 + output += "\nORCHIDv2 (Overlay Routable Cryptographic Hash Identifiers)。\n用于加密哈希标识符(Cryptographic Hash Identifiers)的非路由IPv6地址"; + output += "\nORCHIDv2范围: 2001:20::/28"; + } else if (ipv6[0] === 0x2001 && ipv6[1] === 0xdb8) { + // Documentation + output += "\n文档用IPv6地址。此范围用于文档中需要IPv6地址举例用于描述网络场景的时候。对应IPv4的192.0.2.0/24、198.51.100.0/24和203.0.113.0/24。"; + output += "\n文档地址范围: 2001:db8::/32"; + } else if (ipv6[0] === 0x2002) { + // 6to4 + output += "\n检测到6to4过渡IPv6地址。详细信息请参考RFC 3056。" + + "\n6to4前缀范围: 2002::/16"; + + const v4Addr = ipv4ToStr((ipv6[1] << 16) + ipv6[2]), + slaId = ipv6[3], + interfaceIdStr = ipv6[4].toString(16) + ipv6[5].toString(16) + ipv6[6].toString(16) + ipv6[7].toString(16), + interfaceId = new BigNumber(interfaceIdStr, 16); + + output += "\n\n封装的IPv4地址: " + v4Addr + + "\nSLA ID: " + slaId + + "\n接口ID (16进制): " + interfaceIdStr + + "\n接口ID (10进制): " + interfaceId.toString(); + } else if (ipv6[0] >= 0xfc00 && ipv6[0] <= 0xfdff) { + // Unique local address + output += "\n唯一本地地址(Unique local address),类似于IPv4的私有地址范围10.0.0.0/8、172.16.0.0/12和192.168.0.0/16。详细信息请参考RFC 4193。"; + output += "\n唯一本地地址范围: fc00::/7"; + } else if (ipv6[0] >= 0xfe80 && ipv6[0] <= 0xfebf) { + // Link-local address + output += "\n链路本地地址(Link-local address),类似于IPv4的自动私有地址范围169.254.0.0/16。"; + output += "\n链路本地地址范围: fe80::/10"; + } else if (ipv6[0] >= 0xff00) { + // Multicast + output += "\n保留的多播地址。"; + output += "\n多播地址范围: ff00::/8"; + + switch (ipv6[0]) { + case 0xff01: + output += "\n\n保留的多播地址块,用于接口本地范围(Interface Local Scope)"; + break; + case 0xff02: + output += "\n\n保留的多播地址块,用于链路本地范围(Link Local Scope)"; + break; + case 0xff03: + output += "\n\n保留的多播地址块,用于地域本地范围(Realm Local Scope)"; + break; + case 0xff04: + output += "\n\n保留的多播地址块,用于管理本地范围(Admin Local Scope)"; + break; + case 0xff05: + output += "\n\n保留的多播地址块,用于站点本地范围(Site Local Scope)"; + break; + case 0xff08: + output += "\n\n保留的多播地址块,用于组织本地范围(Organisation Local Scope)"; + break; + case 0xff0e: + output += "\n\n保留的多播地址块,用于全局范围(Global Scope)"; + break; + } + + if (ipv6[6] === 1) { + if (ipv6[7] === 2) { + output += "\n保留的多播地址,用于 'All DHCP Servers and Relay Agents (defined in RFC3315)'"; + } else if (ipv6[7] === 3) { + output += "\n保留的多播地址,用于 'All LLMNR Hosts (defined in RFC4795)'"; + } + } else { + switch (ipv6[7]) { + case 1: + output += "\n保留的多播地址,用于 'All nodes'"; + break; + case 2: + output += "\n保留的多播地址,用于 'All routers'"; + break; + case 5: + output += "\n保留的多播地址,用于 'OSPFv3 - All OSPF routers'"; + break; + case 6: + output += "\n保留的多播地址,用于 'OSPFv3 - All Designated Routers'"; + break; + case 8: + output += "\n保留的多播地址,用于 'IS-IS for IPv6 Routers'"; + break; + case 9: + output += "\n保留的多播地址,用于 'RIP Routers'"; + break; + case 0xa: + output += "\n保留的多播地址,用于 'EIGRP Routers'"; + break; + case 0xc: + output += "\n保留的多播地址,用于 'Simple Service Discovery Protocol'"; + break; + case 0xd: + output += "\n保留的多播地址,用于 'PIM Routers'"; + break; + case 0x16: + output += "\n保留的多播地址,用于 'MLDv2 Reports (defined in RFC3810)'"; + break; + case 0x6b: + output += "\n保留的多播地址,用于 'Precision Time Protocol v2 Peer Delay Measurement Messages'"; + break; + case 0xfb: + output += "\n保留的多播地址,用于 'Multicast DNS'"; + break; + case 0x101: + output += "\n保留的多播地址,用于 'Network Time Protocol'"; + break; + case 0x108: + output += "\n保留的多播地址,用于 'Network Information Service'"; + break; + case 0x114: + output += "\n保留的多播地址,用于 'Experiments'"; + break; + case 0x181: + output += "\n保留的多播地址,用于 'Precision Time Protocol v2 Messages (exc. Peer Delay)'"; + break; + } + } + } + + + // Detect possible EUI-64 addresses + if (((ipv6[5] & 0xff) === 0xff) && (ipv6[6] >>> 8 === 0xfe)) { + output += "\n\n此IPv6地址包含修改过的EUI-64地址,由于第12个和第13个八位组是FF:FE。"; + + const intIdent = Utils.hex(ipv6[4] >>> 8) + ":" + Utils.hex(ipv6[4] & 0xff) + ":" + + Utils.hex(ipv6[5] >>> 8) + ":" + Utils.hex(ipv6[5] & 0xff) + ":" + + Utils.hex(ipv6[6] >>> 8) + ":" + Utils.hex(ipv6[6] & 0xff) + ":" + + Utils.hex(ipv6[7] >>> 8) + ":" + Utils.hex(ipv6[7] & 0xff), + mac = Utils.hex((ipv6[4] >>> 8) ^ 2) + ":" + Utils.hex(ipv6[4] & 0xff) + ":" + + Utils.hex(ipv6[5] >>> 8) + ":" + Utils.hex(ipv6[6] & 0xff) + ":" + + Utils.hex(ipv6[7] >>> 8) + ":" + Utils.hex(ipv6[7] & 0xff); + output += "\n接口标识符: " + intIdent + + "\nMAC地址: " + mac; + } + } else { + throw new OperationError("无效IPv6地址"); + } + return output; + } + +} + +export default ParseIPv6Address; diff --git a/plugins/srktoolbox/src/core/operations/ParseObjectIDTimestamp.mjs b/plugins/srktoolbox/src/core/operations/ParseObjectIDTimestamp.mjs new file mode 100644 index 00000000..b33430d7 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ParseObjectIDTimestamp.mjs @@ -0,0 +1,49 @@ +/** + * @author dmfj [dominic@dmfj.io] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import BSON from "bson"; + +/** + * Parse ObjectID timestamp operation + */ +class ParseObjectIDTimestamp extends Operation { + + /** + * ParseObjectIDTimestamp constructor + */ + constructor() { + super(); + + this.name = "解析ObjectID时间戳"; + this.module = "Serialise"; + this.description = "解析MongoDB/BSON ObjectID十六进制时间戳。"; + this.infoURL = "https://docs.mongodb.com/manual/reference/method/ObjectId.getTimestamp/"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + try { + const objectId = new BSON.ObjectID(input); + return objectId.getTimestamp().toISOString(); + } catch (err) { + throw new OperationError(err); + } + } + +} + +export default ParseObjectIDTimestamp; diff --git a/plugins/srktoolbox/src/core/operations/ParseQRCode.mjs b/plugins/srktoolbox/src/core/operations/ParseQRCode.mjs new file mode 100644 index 00000000..3dbae00b --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ParseQRCode.mjs @@ -0,0 +1,64 @@ +/** + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { isImage } from "../lib/FileType.mjs"; +import { parseQrCode } from "../lib/QRCode.mjs"; + +/** + * Parse QR Code operation + */ +class ParseQRCode extends Operation { + /** + * ParseQRCode constructor + */ + constructor() { + super(); + + this.name = "解析二维码"; + this.module = "Image"; + this.description = + "读取图像文件并尝试检测并读取其中Quick Response (QR)二维码的内容。

标准化图像
在尝试读取前先对图像进行标准化处理。"; + this.infoURL = "https://wikipedia.org/wiki/QR_code"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + name: "标准化图像", + type: "boolean", + value: false, + }, + ]; + this.checks = [ + { + pattern: + "^(?:\\xff\\xd8\\xff|\\x89\\x50\\x4e\\x47|\\x47\\x49\\x46|.{8}\\x57\\x45\\x42\\x50|\\x42\\x4d)", + flags: "", + args: [false], + useful: true, + }, + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + const [normalise] = args; + + if (!isImage(input)) { + throw new OperationError("无效的文件类型。"); + } + return parseQrCode(input, normalise); + } +} + +export default ParseQRCode; diff --git a/plugins/srktoolbox/src/core/operations/ParseSSHHostKey.mjs b/plugins/srktoolbox/src/core/operations/ParseSSHHostKey.mjs new file mode 100644 index 00000000..0aec5be9 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ParseSSHHostKey.mjs @@ -0,0 +1,161 @@ +/** + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { fromBase64 } from "../lib/Base64.mjs"; +import { fromHex, toHexFast } from "../lib/Hex.mjs"; + +/** + * Parse SSH Host Key operation + */ +class ParseSSHHostKey extends Operation { + + /** + * ParseSSHHostKey constructor + */ + constructor() { + super(); + + this.name = "解析SSH主机密钥"; + this.module = "Default"; + this.description = "解析SSH主机密钥(host key),提取其中字段。
支持的密钥类型:
  • ssh-rsa
  • ssh-dss
  • ecdsa-sha2
  • ssh-ed25519
密钥可以为十六进制或Base64格式"; + this.infoURL = "https://wikipedia.org/wiki/Secure_Shell"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "输入格式", + type: "option", + value: [ + "自动", + "Base64", + "十六进制" + ] + } + ]; + this.checks = [ + { + pattern: "^\\s*([A-F\\d]{2}[,;:]){15,}[A-F\\d]{2}\\s*$", + flags: "i", + args: ["十六进制"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [inputFormat] = args, + inputKey = this.convertKeyToBinary(input.trim(), inputFormat), + fields = this.parseKey(inputKey), + keyType = Utils.byteArrayToChars(fromHex(fields[0]), ""); + + let output = `密钥类型: ${keyType}`; + + if (keyType === "ssh-rsa") { + output += `\n指数: 0x${fields[1]}`; + output += `\n模数: 0x${fields[2]}`; + } else if (keyType === "ssh-dss") { + output += `\np: 0x${fields[1]}`; + output += `\nq: 0x${fields[2]}`; + output += `\ng: 0x${fields[3]}`; + output += `\ny: 0x${fields[4]}`; + } else if (keyType.startsWith("ecdsa-sha2")) { + output += `\n曲线: ${Utils.byteArrayToChars(fromHex(fields[1]))}`; + output += `\n点位: 0x${fields.slice(2)}`; + } else if (keyType === "ssh-ed25519") { + output += `\nx: 0x${fields[1]}`; + } else { + output += "\n不支持的密钥类型"; + output += `\n参数: ${fields.slice(1)}`; + } + + return output; + } + + /** + * Converts the key to binary format from either hex or base64 + * + * @param {string} inputKey + * @param {string} inputFormat + * @returns {byteArray} + */ + convertKeyToBinary(inputKey, inputFormat) { + const keyPattern = new RegExp(/^(?:ssh|ecdsa-sha2)\S+\s+(\S*)/), + keyMatch = inputKey.match(keyPattern); + + if (keyMatch) { + inputKey = keyMatch[1]; + } + + if (inputFormat === "自动") { + inputFormat = this.detectKeyFormat(inputKey); + } + if (inputFormat === "十六进制") { + return fromHex(inputKey); + } else if (inputFormat === "Base64") { + return fromBase64(inputKey, null, "byteArray"); + } else { + throw new OperationError("无效的输入格式"); + } + } + + + /** + * Detects if the key is base64 or hex encoded + * + * @param {string} inputKey + * @returns {string} + */ + detectKeyFormat(inputKey) { + const hexPattern = new RegExp(/^(?:[\dA-Fa-f]{2}[ ,;:]?)+$/); + const b64Pattern = new RegExp(/^\s*(?:[A-Za-z\d+/]{4})+(?:[A-Za-z\d+/]{2}==|[A-Za-z\d+/]{3}=)?\s*$/); + + if (hexPattern.test(inputKey)) { + return "十六进制"; + } else if (b64Pattern.test(inputKey)) { + return "Base64"; + } else { + throw new OperationError("无法检测密钥格式。"); + } + } + + + /** + * Parses fields from the key + * + * @param {byteArray} key + */ + parseKey(key) { + const fields = []; + while (key.length > 0) { + const lengthField = key.slice(0, 4); + let decodedLength = 0; + for (let i = 0; i < lengthField.length; i++) { + decodedLength += lengthField[i]; + decodedLength = decodedLength << 8; + } + decodedLength = decodedLength >> 8; + // Break if length wasn't decoded correctly + if (decodedLength <= 0) break; + + fields.push(toHexFast(key.slice(4, 4 + decodedLength))); + key = key.slice(4 + decodedLength); + } + + return fields; + } + +} + +export default ParseSSHHostKey; diff --git a/plugins/srktoolbox/src/core/operations/ParseTCP.mjs b/plugins/srktoolbox/src/core/operations/ParseTCP.mjs new file mode 100644 index 00000000..8f73842e --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ParseTCP.mjs @@ -0,0 +1,247 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Stream from "../lib/Stream.mjs"; +import {toHexFast, fromHex} from "../lib/Hex.mjs"; +import {toBinary} from "../lib/Binary.mjs"; +import {objToTable, bytesToLargeNumber} from "../lib/Protocol.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import BigNumber from "bignumber.js"; + +/** + * Parse TCP operation + */ +class ParseTCP extends Operation { + + /** + * ParseTCP constructor + */ + constructor() { + super(); + + this.name = "解析TCP"; + this.module = "Default"; + this.description = "解析TCP标头和载荷(如果有)。"; + this.infoURL = "https://wikipedia.org/wiki/Transmission_Control_Protocol"; + this.inputType = "string"; + this.outputType = "json"; + this.presentType = "html"; + this.args = [ + { + name: "输入格式", + type: "option", + value: ["十六进制", "原始"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + const format = args[0]; + + if (format === "十六进制") { + input = fromHex(input); + } else if (format === "原始") { + input = Utils.strToArrayBuffer(input); + } else { + throw new OperationError("未知的输入格式。"); + } + + const s = new Stream(new Uint8Array(input)); + if (s.length < 20) { + throw new OperationError("TCP标头需要至少20字节。"); + } + + // Parse Header + const TCPPacket = { + "来源连接端口": s.readInt(2), + "目的连接端口": s.readInt(2), + "序列号": bytesToLargeNumber(s.getBytes(4)), + "确认号": s.readInt(4), + "资料偏移": s.readBits(4), + "标志符": { + "保留": toBinary(s.readBits(3), "", 3), + "NS": s.readBits(1), + "CWR": s.readBits(1), + "ECE": s.readBits(1), + "URG": s.readBits(1), + "ACK": s.readBits(1), + "PSH": s.readBits(1), + "RST": s.readBits(1), + "SYN": s.readBits(1), + "FIN": s.readBits(1), + }, + "窗口大小": s.readInt(2), + "校验和": "0x" + toHexFast(s.getBytes(2)), + "紧急指针": "0x" + toHexFast(s.getBytes(2)) + }; + + // Parse options if present + let windowScaleShift = 0; + if (TCPPacket["资料偏移"] > 5) { + let remainingLength = TCPPacket["资料偏移"] * 4 - 20; + + const options = {}; + while (remainingLength > 0) { + const option = { + "Kind": s.readInt(1) + }; + + let opt = { name: "Reserved", length: true }; + if (Object.prototype.hasOwnProperty.call(TCP_OPTION_KIND_LOOKUP, option.Kind)) { + opt = TCP_OPTION_KIND_LOOKUP[option.Kind]; + } + + // Add Length and Value fields + if (opt.length) { + option.Length = s.readInt(1); + + if (option.Length > 2) { + if (Object.prototype.hasOwnProperty.call(opt, "parser")) { + option.Value = opt.parser(s.getBytes(option.Length - 2)); + } else { + option.Value = option.Length <= 6 ? + s.readInt(option.Length - 2): + "0x" + toHexFast(s.getBytes(option.Length - 2)); + } + + // Store Window Scale shift for later + if (option.Kind === 3 && option.Value) { + windowScaleShift = option.Value["移位偏移量"]; + } + } + } + options[opt.name] = option; + + const length = option.Length || 1; + remainingLength -= length; + } + TCPPacket.Options = options; + } + + if (s.hasMore()) { + TCPPacket.Data = "0x" + toHexFast(s.getBytes()); + } + + // Improve values + TCPPacket["资料偏移"] = `${TCPPacket["资料偏移"]} (${TCPPacket["资料偏移"] * 4} 字节)`; + const trueWndSize = BigNumber(TCPPacket["窗口大小"]).multipliedBy(BigNumber(2).pow(BigNumber(windowScaleShift))); + TCPPacket["窗口大小"] = `${TCPPacket["窗口大小"]} (扩大后: ${trueWndSize})`; + + return TCPPacket; + } + + /** + * Displays the TCP Packet in a tabular style + * @param {Object} data + * @returns {html} + */ + present(data) { + return objToTable(data); + } + +} + +// Taken from https://www.iana.org/assignments/tcp-parameters/tcp-parameters.xhtml +// on 2022-05-30 +const TCP_OPTION_KIND_LOOKUP = { + 0: { name: "End of Option List", length: false }, + 1: { name: "No-Operation", length: false }, + 2: { name: "Maximum Segment Size", length: true }, + 3: { name: "Window Scale", length: true, parser: windowScaleParser }, + 4: { name: "SACK Permitted", length: true }, + 5: { name: "SACK", length: true }, + 6: { name: "Echo (obsoleted by option 8)", length: true }, + 7: { name: "Echo Reply (obsoleted by option 8)", length: true }, + 8: { name: "Timestamps", length: true, parser: tcpTimestampParser }, + 9: { name: "Partial Order Connection Permitted (obsolete)", length: true }, + 10: { name: "Partial Order Service Profile (obsolete)", length: true }, + 11: { name: "CC (obsolete)", length: true }, + 12: { name: "CC.NEW (obsolete)", length: true }, + 13: { name: "CC.ECHO (obsolete)", length: true }, + 14: { name: "TCP Alternate Checksum Request (obsolete)", length: true, parser: tcpAlternateChecksumParser }, + 15: { name: "TCP Alternate Checksum Data (obsolete)", length: true }, + 16: { name: "Skeeter", length: true }, + 17: { name: "Bubba", length: true }, + 18: { name: "Trailer Checksum Option", length: true }, + 19: { name: "MD5 Signature Option (obsoleted by option 29)", length: true }, + 20: { name: "SCPS Capabilities", length: true }, + 21: { name: "Selective Negative Acknowledgements", length: true }, + 22: { name: "Record Boundaries", length: true }, + 23: { name: "Corruption experienced", length: true }, + 24: { name: "SNAP", length: true }, + 25: { name: "Unassigned (released 2000-12-18)", length: true }, + 26: { name: "TCP Compression Filter", length: true }, + 27: { name: "Quick-Start Response", length: true }, + 28: { name: "User Timeout Option (also, other known unauthorized use)", length: true }, + 29: { name: "TCP Authentication Option (TCP-AO)", length: true }, + 30: { name: "Multipath TCP (MPTCP)", length: true }, + 69: { name: "Encryption Negotiation (TCP-ENO)", length: true }, + 70: { name: "Reserved (known unauthorized use without proper IANA assignment)", length: true }, + 76: { name: "Reserved (known unauthorized use without proper IANA assignment)", length: true }, + 77: { name: "Reserved (known unauthorized use without proper IANA assignment)", length: true }, + 78: { name: "Reserved (known unauthorized use without proper IANA assignment)", length: true }, + 253: { name: "RFC3692-style Experiment 1 (also improperly used for shipping products) ", length: true }, + 254: { name: "RFC3692-style Experiment 2 (also improperly used for shipping products) ", length: true } +}; + +/** + * Parses the TCP Alternate Checksum Request field + * @param {Uint8Array} data + */ +function tcpAlternateChecksumParser(data) { + const lookup = { + 0: "TCP Checksum", + 1: "8-bit Fletchers's algorithm", + 2: "16-bit Fletchers's algorithm", + 3: "Redundant Checksum Avoidance" + }[data[0]]; + + return `${lookup} (0x${toHexFast(data)})`; +} + +/** + * Parses the TCP Timestamp field + * @param {Uint8Array} data + */ +function tcpTimestampParser(data) { + const s = new Stream(data); + + if (s.length !== 8) + return `错误:时间戳字段必须为8个字节(接收到 0x${toHexFast(data)})`; + + const tsval = bytesToLargeNumber(s.getBytes(4)), + tsecr = bytesToLargeNumber(s.getBytes(4)); + + return { + "当前时间戳": tsval, + "应答回复": tsecr + }; +} + +/** + * Parses the Window Scale field + * @param {Uint8Array} data + */ +function windowScaleParser(data) { + if (data.length !== 1) + return `错误:窗口扩大值需要1个字节(接收到 0x${toHexFast(data)})`; + + return { + "移位偏移量": data[0], + "乘数": 1 << data[0] + }; +} + +export default ParseTCP; diff --git a/plugins/srktoolbox/src/core/operations/ParseTLSRecord.mjs b/plugins/srktoolbox/src/core/operations/ParseTLSRecord.mjs new file mode 100644 index 00000000..92e4a408 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ParseTLSRecord.mjs @@ -0,0 +1,886 @@ +/** + * @author c65722 [] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import {toHexFast} from "../lib/Hex.mjs"; +import {objToTable} from "../lib/Protocol.mjs"; +import Stream from "../lib/Stream.mjs"; + +/** + * Parse TLS record operation. + */ +class ParseTLSRecord extends Operation { + + /** + * ParseTLSRecord constructor. + */ + constructor() { + super(); + + this.name = "解析TLS记录"; + this.module = "Default"; + this.description = "解析一个或多个TLS记录"; + this.infoURL = "https://wikipedia.org/wiki/Transport_Layer_Security"; + this.inputType = "ArrayBuffer"; + this.outputType = "json"; + this.presentType = "html"; + this.args = []; + this._handshakeParser = new HandshakeParser(); + this._contentTypes = new Map(); + + for (const key in ContentType) { + this._contentTypes[ContentType[key]] = key.toString().toLocaleLowerCase(); + } + } + + /** + * @param {ArrayBuffer} input - Stream, containing one or more raw TLS Records. + * @param {Object[]} args + * @returns {Object[]} Array of Object representations of TLS Records contained within input. + */ + run(input, args) { + const s = new Stream(new Uint8Array(input)); + + const output = []; + + while (s.hasMore()) { + const record = this._readRecord(s); + if (record) { + output.push(record); + } + } + + return output; + } + + /** + * Reads a TLS Record from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a raw TLS Record. + * @returns {Object} Object representation of TLS Record. + */ + _readRecord(input) { + const RECORD_HEADER_LEN = 5; + + if (input.position + RECORD_HEADER_LEN > input.length) { + input.moveTo(input.length); + + return null; + } + + const type = input.readInt(1); + const typeString = this._contentTypes[type] ?? type.toString(); + const version = "0x" + toHexFast(input.getBytes(2)); + const length = input.readInt(2); + const content = input.getBytes(length); + const truncated = content.length < length; + + const recordHeader = new RecordHeader(typeString, version, length, truncated); + + if (!content.length) { + return {...recordHeader}; + } + + if (type === ContentType.HANDSHAKE) { + return this._handshakeParser.parse(new Stream(content), recordHeader); + } + + const record = {...recordHeader}; + record.value = "0x" + toHexFast(content); + + return record; + } + + /** + * Displays the parsed TLS Records in a tabular style. + * + * @param {Object[]} data - Array of Object representations of the TLS Records. + * @returns {html} HTML representation of TLS Records contained within data. + */ + present(data) { + return data.map(r => objToTable(r)).join("\n\n"); + } +} + +export default ParseTLSRecord; + +/** + * Repesents the known values of type field of a TLS Record header. + */ +const ContentType = Object.freeze({ + CHANGE_CIPHER_SPEC: 20, + ALERT: 21, + HANDSHAKE: 22, + APPLICATION_DATA: 23, +}); + +/** + * Represents a TLS Record header + */ +class RecordHeader { + /** + * RecordHeader cosntructor. + * + * @param {string} type - String representation of TLS Record type field. + * @param {string} version - Hex representation of TLS Record version field. + * @param {int} length - Length of TLS Record. + * @param {bool} truncated - Is TLS Record truncated. + */ + constructor(type, version, length, truncated) { + this.type = type; + this.version = version; + this.length = length; + + if (truncated) { + this.truncated = true; + } + } +} + +/** + * Parses TLS Handshake messages. + */ +class HandshakeParser { + + /** + * HandshakeParser constructor. + */ + constructor() { + this._clientHelloParser = new ClientHelloParser(); + this._serverHelloParser = new ServerHelloParser(); + this._newSessionTicketParser = new NewSessionTicketParser(); + this._certificateParser = new CertificateParser(); + this._certificateRequestParser = new CertificateRequestParser(); + this._certificateVerifyParser = new CertificateVerifyParser(); + this._handshakeTypes = new Map(); + + for (const key in HandshakeType) { + this._handshakeTypes[HandshakeType[key]] = key.toString().toLowerCase(); + } + } + + /** + * Parses a single TLS handshake message. + * + * @param {Stream} input - Stream, containing a raw Handshake message. + * @param {RecordHeader} recordHeader - TLS Record header. + * @returns {Object} Object representation of Handshake. + */ + parse(input, recordHeader) { + const output = {...recordHeader}; + + if (!input.hasMore()) { + return output; + } + + const handshakeType = input.readInt(1); + output.handshakeType = this._handshakeTypes[handshakeType] ?? handshakeType.toString(); + + if (input.position + 3 > input.length) { + input.moveTo(input.length); + + return output; + } + + const handshakeLength = input.readInt(3); + + if (handshakeLength + 4 !== recordHeader.length) { + input.moveTo(0); + + output.handshakeType = this._handshakeTypes[HandshakeType.FINISHED]; + output.handshakeValue = "0x" + toHexFast(input.bytes); + + return output; + } + + const content = input.getBytes(handshakeLength); + if (!content.length) { + return output; + } + + switch (handshakeType) { + case HandshakeType.CLIENT_HELLO: + return {...output, ...this._clientHelloParser.parse(new Stream(content))}; + case HandshakeType.SERVER_HELLO: + return {...output, ...this._serverHelloParser.parse(new Stream(content))}; + case HandshakeType.NEW_SESSION_TICKET: + return {...output, ...this._newSessionTicketParser.parse(new Stream(content))}; + case HandshakeType.CERTIFICATE: + return {...output, ...this._certificateParser.parse(new Stream(content))}; + case HandshakeType.CERTIFICATE_REQUEST: + return {...output, ...this._certificateRequestParser.parse(new Stream(content))}; + case HandshakeType.CERTIFICATE_VERIFY: + return {...output, ...this._certificateVerifyParser.parse(new Stream(content))}; + default: + output.handshakeValue = "0x" + toHexFast(content); + } + + return output; + } +} + +/** + * Represents the known values of the msg_type field of a TLS Handshake message. + */ +const HandshakeType = Object.freeze({ + HELLO_REQUEST: 0, + CLIENT_HELLO: 1, + SERVER_HELLO: 2, + NEW_SESSION_TICKET: 4, + CERTIFICATE: 11, + SERVER_KEY_EXCHANGE: 12, + CERTIFICATE_REQUEST: 13, + SERVER_HELLO_DONE: 14, + CERTIFICATE_VERIFY: 15, + CLIENT_KEY_EXCHANGE: 16, + FINISHED: 20, +}); + +/** + * Parses TLS Handshake ClientHello messages. + */ +class ClientHelloParser { + + /** + * ClientHelloParser constructor. + */ + constructor() { + this._extensionsParser = new ExtensionsParser(); + } + + /** + * Parses a single TLS Handshake ClientHello message. + * + * @param {Stream} input - Stream, containing a raw ClientHello message. + * @returns {Object} Object representation of ClientHello. + */ + parse(input) { + const output = {}; + + output.clientVersion = this._readClientVersion(input); + output.random = this._readRandom(input); + + const sessionID = this._readSessionID(input); + if (sessionID) { + output.sessionID = sessionID; + } + + output.cipherSuites = this._readCipherSuites(input); + output.compressionMethods = this._readCompressionMethods(input); + output.extensions = this._readExtensions(input); + + return output; + } + + /** + * Reads the client_version field from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a raw ClientHello message, with position before client_version field. + * @returns {string} Hex representation of client_version. + */ + _readClientVersion(input) { + return readBytesAsHex(input, 2); + } + + /** + * Reads the random field from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a raw ClientHello message, with position before random field. + * @returns {string} Hex representation of random. + */ + _readRandom(input) { + return readBytesAsHex(input, 32); + } + + /** + * Reads the session_id field from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a raw ClientHello message, with position before session_id length field. + * @returns {string} Hex representation of session_id, or empty string if session_id not present. + */ + _readSessionID(input) { + return readSizePrefixedBytesAsHex(input, 1); + } + + /** + * Reads the cipher_suites field from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a raw ClientHello message, with position before cipher_suites length field. + * @returns {Object} Object represention of cipher_suites field. + */ + _readCipherSuites(input) { + const output = {}; + + output.length = input.readInt(2); + if (!output.length) { + return {}; + } + + const cipherSuites = new Stream(input.getBytes(output.length)); + if (cipherSuites.length < output.length) { + output.truncated = true; + } + + output.values = []; + + while (cipherSuites.hasMore()) { + const cipherSuite = readBytesAsHex(cipherSuites, 2); + if (cipherSuite) { + output.values.push(cipherSuite); + } + } + + return output; + } + + /** + * Reads the compression_methods field from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a raw ClientHello message, with position before compression_methods length field. + * @returns {Object} Object representation of compression_methods field. + */ + _readCompressionMethods(input) { + const output = {}; + + output.length = input.readInt(1); + if (!output.length) { + return {}; + } + + const compressionMethods = new Stream(input.getBytes(output.length)); + if (compressionMethods.length < output.length) { + output.truncated = true; + } + + output.values = []; + + while (compressionMethods.hasMore()) { + const compressionMethod = readBytesAsHex(compressionMethods, 1); + if (compressionMethod) { + output.values.push(compressionMethod); + } + } + + return output; + } + + /** + * Reads the extensions field from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a raw ClientHello message, with position before extensions length field. + * @returns {Object} Object representations of extensions field. + */ + _readExtensions(input) { + const output = {}; + + output.length = input.readInt(2); + if (!output.length) { + return {}; + } + + const extensions = new Stream(input.getBytes(output.length)); + if (extensions.length < output.length) { + output.truncated = true; + } + + output.values = this._extensionsParser.parse(extensions); + + return output; + } +} + +/** + * Parses TLS Handshake ServeHello messages. + */ +class ServerHelloParser { + + /** + * ServerHelloParser constructor. + */ + constructor() { + this._extensionsParser = new ExtensionsParser(); + } + + /** + * Parses a single TLS Handshake ServerHello message. + * + * @param {Stream} input - Stream, containing a raw ServerHello message. + * @return {Object} Object representation of ServerHello. + */ + parse(input) { + const output = {}; + + output.serverVersion = this._readServerVersion(input); + output.random = this._readRandom(input); + + const sessionID = this._readSessionID(input); + if (sessionID) { + output.sessionID = sessionID; + } + + output.cipherSuite = this._readCipherSuite(input); + output.compressionMethod = this._readCompressionMethod(input); + output.extensions = this._readExtensions(input); + + return output; + } + + /** + * Reads the server_version field from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a raw ServerHello message, with position before server_version field. + * @returns {string} Hex representation of server_version. + */ + _readServerVersion(input) { + return readBytesAsHex(input, 2); + } + + /** + * Reads the random field from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a raw ServerHello message, with position before random field. + * @returns {string} Hex representation of random. + */ + _readRandom(input) { + return readBytesAsHex(input, 32); + } + + /** + * Reads the session_id field from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a raw ServertHello message, with position before session_id length field. + * @returns {string} Hex representation of session_id, or empty string if session_id not present. + */ + _readSessionID(input) { + return readSizePrefixedBytesAsHex(input, 1); + } + + /** + * Reads the cipher_suite field from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a raw ServerHello message, with position before cipher_suite field. + * @returns {string} Hex represention of cipher_suite. + */ + _readCipherSuite(input) { + return readBytesAsHex(input, 2); + } + + /** + * Reads the compression_method field from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a raw ServerHello message, with position before compression_method field. + * @returns {string} Hex represention of compression_method. + */ + _readCompressionMethod(input) { + return readBytesAsHex(input, 1); + } + + /** + * Reads the extensions field from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a raw ServerHello message, with position before extensions length field. + * @returns {Object} Object representation of extensions field. + */ + _readExtensions(input) { + const output = {}; + + output.length = input.readInt(2); + if (!output.length) { + return {}; + } + + const extensions = new Stream(input.getBytes(output.length)); + if (extensions.length < output.length) { + output.truncated = true; + } + + output.values = this._extensionsParser.parse(extensions); + + return output; + } +} + +/** + * Parses TLS Handshake Hello Extensions. + */ +class ExtensionsParser { + + /** + * Parses a stream of TLS Handshake Hello Extensions. + * + * @param {Stream} input - Stream, containing multiple raw Extensions, with position before first extension length field. + * @returns {Object[]} Array of Object representations of Extensions contained within input. + */ + parse(input) { + const output = []; + + while (input.hasMore()) { + const extension = this._readExtension(input); + if (extension) { + output.push(extension); + } + } + + return output; + } + + /** + * Reads a single Extension from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a list of Extensions, with position before the length field of the next Extension. + * @returns {Object} Object representation of Extension. + */ + _readExtension(input) { + const output = {}; + + if (input.position + 4 > input.length) { + input.moveTo(input.length); + return null; + } + + output.type = "0x" + toHexFast(input.getBytes(2)); + output.length = input.readInt(2); + if (!output.length) { + return output; + } + + const value = input.getBytes(output.length); + if (!value || value.length !== output.length) { + output.truncated = true; + } + + if (value && value.length) { + output.value = "0x" + toHexFast(value); + } + + return output; + } +} + +/** + * Parses TLS Handshake NewSessionTicket messages. + */ +class NewSessionTicketParser { + + /** + * Parses a single TLS Handshake NewSessionTicket message. + * + * @param {Stream} input - Stream, containing a raw NewSessionTicket message. + * @returns {Object} Object representation of NewSessionTicket. + */ + parse(input) { + return { + ticketLifetimeHint: this._readTicketLifetimeHint(input), + ticket: this._readTicket(input), + }; + } + + /** + * Reads the ticket_lifetime_hint field from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a raw NewSessionTicket message, with position before ticket_lifetime_hint field. + * @returns {string} Lifetime hint, in seconds. + */ + _readTicketLifetimeHint(input) { + if (input.position + 4 > input.length) { + input.moveTo(input.length); + return ""; + } + + return input.readInt(4) + "s"; + } + + /** + * Reads the ticket field fromt the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a raw NewSessionTicket message, with position before ticket length field. + * @returns {string} Hex representation of ticket. + */ + _readTicket(input) { + return readSizePrefixedBytesAsHex(input, 2); + } +} + +/** + * Parses TLS Handshake Certificate messages. + */ +class CertificateParser { + + /** + * Parses a single TLS Handshake Certificate message. + * + * @param {Stream} input - Stream, containing a raw Certificate message. + * @returns {Object} Object representation of Certificate. + */ + parse(input) { + const output = {}; + + output.certificateList = this._readCertificateList(input); + + return output; + } + + /** + * Reads the certificate_list field from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a raw Certificate message, with position before certificate_list length field. + * @returns {string[]} Array of strings, each containing a hex representation of a value within the certificate_list field. + */ + _readCertificateList(input) { + const output = {}; + + if (input.position + 3 > input.length) { + input.moveTo(input.length); + return output; + } + + output.length = input.readInt(3); + if (!output.length) { + return output; + } + + const certificates = new Stream(input.getBytes(output.length)); + if (certificates.length < output.length) { + output.truncated = true; + } + + output.values = []; + + while (certificates.hasMore()) { + const certificate = this._readCertificate(certificates); + if (certificate) { + output.values.push(certificate); + } + } + + return output; + } + + /** + * Reads a single certificate from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a list of certificicates, with position before the length field of the next certificate. + * @returns {string} Hex representation of certificate. + */ + _readCertificate(input) { + return readSizePrefixedBytesAsHex(input, 3); + } +} + +/** + * Parses TLS Handshake CertificateRequest messages. + */ +class CertificateRequestParser { + + /** + * Parses a single TLS Handshake CertificateRequest message. + * + * @param {Stream} input - Stream, containing a raw CertificateRequest message. + * @return {Object} Object representation of CertificateRequest. + */ + parse(input) { + const output = {}; + + output.certificateTypes = this._readCertificateTypes(input); + output.supportedSignatureAlgorithms = this._readSupportedSignatureAlgorithms(input); + + const certificateAuthorities = this._readCertificateAuthorities(input); + if (certificateAuthorities.length) { + output.certificateAuthorities = certificateAuthorities; + } + + return output; + } + + /** + * Reads the certificate_types field from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a raw CertificateRequest message, with position before certificate_types length field. + * @return {string[]} Array of strings, each containing a hex representation of a value within the certificate_types field. + */ + _readCertificateTypes(input) { + const output = {}; + + output.length = input.readInt(1); + if (!output.length) { + return {}; + } + + const certificateTypes = new Stream(input.getBytes(output.length)); + if (certificateTypes.length < output.length) { + output.truncated = true; + } + + output.values = []; + + while (certificateTypes.hasMore()) { + const certificateType = readBytesAsHex(certificateTypes, 1); + if (certificateType) { + output.values.push(certificateType); + } + } + + return output; + } + + /** + * Reads the supported_signature_algorithms field from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a raw CertificateRequest message, with position before supported_signature_algorithms length field. + * @returns {string[]} Array of strings, each containing a hex representation of a value within the supported_signature_algorithms field. + */ + _readSupportedSignatureAlgorithms(input) { + const output = {}; + + output.length = input.readInt(2); + if (!output.length) { + return {}; + } + + const signatureAlgorithms = new Stream(input.getBytes(output.length)); + if (signatureAlgorithms.length < output.length) { + output.truncated = true; + } + + output.values = []; + + while (signatureAlgorithms.hasMore()) { + const signatureAlgorithm = readBytesAsHex(signatureAlgorithms, 2); + if (signatureAlgorithm) { + output.values.push(signatureAlgorithm); + } + } + + return output; + } + + /** + * Reads the certificate_authorities field from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a raw CertificateRequest message, with position before certificate_authorities length field. + * @returns {string[]} Array of strings, each containing a hex representation of a value within the certificate_authorities field. + */ + _readCertificateAuthorities(input) { + const output = {}; + + output.length = input.readInt(2); + if (!output.length) { + return {}; + } + + const certificateAuthorities = new Stream(input.getBytes(output.length)); + if (certificateAuthorities.length < output.length) { + output.truncated = true; + } + + output.values = []; + + while (certificateAuthorities.hasMore()) { + const certificateAuthority = this._readCertificateAuthority(certificateAuthorities); + if (certificateAuthority) { + output.values.push(certificateAuthority); + } + } + + return output; + } + + /** + * Reads a single certificate authority from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a list of raw certificate authorities, with position before the length field of the next certificate authority. + * @returns {string} Hex representation of certificate authority. + */ + _readCertificateAuthority(input) { + return readSizePrefixedBytesAsHex(input, 2); + } +} + +/** + * Parses TLS Handshake CertificateVerify messages. + */ +class CertificateVerifyParser { + + /** + * Parses a single CertificateVerify Message. + * + * @param {Stream} input - Stream, containing a raw CertificateVerify message. + * @returns {Object} Object representation of CertificateVerify. + */ + parse(input) { + return { + algorithmHash: this._readAlgorithmHash(input), + algorithmSignature: this._readAlgorithmSignature(input), + signature: this._readSignature(input), + }; + } + + /** + * Reads the algorithm.hash field from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a raw CertificateVerify message, with position before algorithm.hash field. + * @return {string} Hex representation of hash algorithm. + */ + _readAlgorithmHash(input) { + return readBytesAsHex(input, 1); + } + + /** + * Reads the algorithm.signature field from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a raw CertificateVerify message, with position before algorithm.signature field. + * @return {string} Hex representation of signature algorithm. + */ + _readAlgorithmSignature(input) { + return readBytesAsHex(input, 1); + } + + /** + * Reads the signature field from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a raw CertificateVerify message, with position before signature field. + * @return {string} Hex representation of signature. + */ + _readSignature(input) { + return readSizePrefixedBytesAsHex(input, 2); + } +} + +/** + * Read the following size prefixed bytes from the provided Stream, and reuturn as a hex string. + * + * @param {Stream} input - Stream to read from. + * @param {int} sizePrefixLength - Length of the size prefix field. + * @returns {string} Hex representation of bytes read from Stream, empty string is returned if + * field cannot be read in full. + */ +function readSizePrefixedBytesAsHex(input, sizePrefixLength) { + const length = input.readInt(sizePrefixLength); + if (!length) { + return ""; + } + + return readBytesAsHex(input, length); +} + +/** + * Read n bytes from the provided Stream, and return as a hex string. + * + * @param {Stream} input - Stream to read from. + * @param {int} n - Number of bytes to read. + * @returns {string} Hex representation of bytes read from Stream, or empty string if field cannot + * be read in full. + */ +function readBytesAsHex(input, n) { + const bytes = input.getBytes(n); + if (!bytes || bytes.length !== n) { + return ""; + } + + return "0x" + toHexFast(bytes); +} diff --git a/plugins/srktoolbox/src/core/operations/ParseTLV.mjs b/plugins/srktoolbox/src/core/operations/ParseTLV.mjs new file mode 100644 index 00000000..dbb89f7e --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ParseTLV.mjs @@ -0,0 +1,79 @@ +/** + * @author gchq77703 [] + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import TLVParser from "../lib/TLVParser.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Parse TLV operation + */ +class ParseTLV extends Operation { + + /** + * ParseTLV constructor + */ + constructor() { + super(); + + this.name = "解析TLV"; + this.module = "Default"; + this.description = "把Type-Length-Value (TLV)编码的字符串转换为JSON对象。可选包含一个 Key / Type 字段。

标签: Key-Length-Value, KLV, Length-Value, LV"; + this.infoURL = "https://wikipedia.org/wiki/Type-length-value"; + this.inputType = "ArrayBuffer"; + this.outputType = "JSON"; + this.args = [ + { + name: "Type/Key大小", + type: "number", + value: 1 + }, + { + name: "Length大小", + type: "number", + value: 1 + }, + { + name: "使用BER", + type: "boolean", + value: false + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [bytesInKey, bytesInLength, basicEncodingRules] = args; + input = new Uint8Array(input); + + if (bytesInKey <= 0 && bytesInLength <= 0) + throw new OperationError("Type或Length大小必须大于0"); + + const tlv = new TLVParser(input, { bytesInLength, basicEncodingRules }); + + const data = []; + + while (!tlv.atEnd()) { + const key = bytesInKey ? tlv.getValue(bytesInKey) : undefined; + const length = tlv.getLength(); + const value = tlv.getValue(length); + + data.push({ key, length, value }); + } + + return data; + } + +} + +export default ParseTLV; diff --git a/plugins/srktoolbox/src/core/operations/ParseUDP.mjs b/plugins/srktoolbox/src/core/operations/ParseUDP.mjs new file mode 100644 index 00000000..a80bf46d --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ParseUDP.mjs @@ -0,0 +1,91 @@ +/** + * @author h345983745 [] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Stream from "../lib/Stream.mjs"; +import {toHexFast, fromHex} from "../lib/Hex.mjs"; +import {objToTable} from "../lib/Protocol.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Parse UDP operation + */ +class ParseUDP extends Operation { + + /** + * ParseUDP constructor + */ + constructor() { + super(); + + this.name = "解析UDP"; + this.module = "Default"; + this.description = "解析UDP标头和载荷(如果有)。"; + this.infoURL = "https://wikipedia.org/wiki/User_Datagram_Protocol"; + this.inputType = "string"; + this.outputType = "json"; + this.presentType = "html"; + this.args = [ + { + name: "输入格式", + type: "option", + value: ["十六进制", "原始"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {Object} + */ + run(input, args) { + const format = args[0]; + + if (format === "十六进制") { + input = fromHex(input); + } else if (format === "原始") { + input = Utils.strToArrayBuffer(input); + } else { + throw new OperationError("未知的输入格式"); + } + + const s = new Stream(new Uint8Array(input)); + if (s.length < 8) { + throw new OperationError("UDP标头需要至少8字节。"); + } + + // Parse Header + const UDPPacket = { + "来源连接端口": s.readInt(2), + "目的连接端口": s.readInt(2), + "长度": s.readInt(2), + "校验和": "0x" + toHexFast(s.getBytes(2)) + }; + // Parse data if present + if (s.hasMore()) { + UDPPacket.数据 = "0x" + toHexFast(s.getBytes(UDPPacket.长度 - 8)); + } + + return UDPPacket; + } + + /** + * Displays the UDP Packet in a tabular style + * @param {Object} data + * @returns {html} + */ + present(data) { + return objToTable(data); + } + +} + + +export default ParseUDP; diff --git a/plugins/srktoolbox/src/core/operations/ParseUNIXFilePermissions.mjs b/plugins/srktoolbox/src/core/operations/ParseUNIXFilePermissions.mjs new file mode 100644 index 00000000..11919965 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ParseUNIXFilePermissions.mjs @@ -0,0 +1,334 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Parse UNIX file permissions operation + */ +class ParseUNIXFilePermissions extends Operation { + + /** + * ParseUNIXFilePermissions constructor + */ + constructor() { + super(); + + this.name = "解析UNIX文件权限"; + this.module = "Default"; + this.description = "根据UNIX/Linux文件权限字符串(八进制或文本形式),解释具体什么权限赋给了谁。

输入必须为八进制(例如 755)或文本(例如 drwxr-xr-x)格式。"; + this.infoURL = "https://wikipedia.org/wiki/File_system_permissions#Traditional_Unix_permissions"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + this.checks = [ + { + pattern: "^\\s*d[rxw-]{9}\\s*$", + flags: "", + args: [] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const perms = { + d: false, // directory + sl: false, // symbolic link + np: false, // named pipe + s: false, // socket + cd: false, // character device + bd: false, // block device + dr: false, // door + sb: false, // sticky bit + su: false, // setuid + sg: false, // setgid + ru: false, // read user + wu: false, // write user + eu: false, // execute user + rg: false, // read group + wg: false, // write group + eg: false, // execute group + ro: false, // read other + wo: false, // write other + eo: false // execute other + }; + let d = 0, + u = 0, + g = 0, + o = 0, + output = "", + octal = null, + textual = null; + + if (input.search(/\s*[0-7]{1,4}\s*/i) === 0) { + // Input is octal + octal = input.match(/\s*([0-7]{1,4})\s*/i)[1]; + + if (octal.length === 4) { + d = parseInt(octal[0], 8); + u = parseInt(octal[1], 8); + g = parseInt(octal[2], 8); + o = parseInt(octal[3], 8); + } else { + if (octal.length > 0) u = parseInt(octal[0], 8); + if (octal.length > 1) g = parseInt(octal[1], 8); + if (octal.length > 2) o = parseInt(octal[2], 8); + } + + perms.su = d >> 2 & 0x1; + perms.sg = d >> 1 & 0x1; + perms.sb = d & 0x1; + + perms.ru = u >> 2 & 0x1; + perms.wu = u >> 1 & 0x1; + perms.eu = u & 0x1; + + perms.rg = g >> 2 & 0x1; + perms.wg = g >> 1 & 0x1; + perms.eg = g & 0x1; + + perms.ro = o >> 2 & 0x1; + perms.wo = o >> 1 & 0x1; + perms.eo = o & 0x1; + } else if (input.search(/\s*[dlpcbDrwxsStT-]{1,10}\s*/) === 0) { + // Input is textual + textual = input.match(/\s*([dlpcbDrwxsStT-]{1,10})\s*/)[1]; + + switch (textual[0]) { + case "d": + perms.d = true; + break; + case "l": + perms.sl = true; + break; + case "p": + perms.np = true; + break; + case "s": + perms.s = true; + break; + case "c": + perms.cd = true; + break; + case "b": + perms.bd = true; + break; + case "D": + perms.dr = true; + break; + } + + if (textual.length > 1) perms.ru = textual[1] === "r"; + if (textual.length > 2) perms.wu = textual[2] === "w"; + if (textual.length > 3) { + switch (textual[3]) { + case "x": + perms.eu = true; + break; + case "s": + perms.eu = true; + perms.su = true; + break; + case "S": + perms.su = true; + break; + } + } + + if (textual.length > 4) perms.rg = textual[4] === "r"; + if (textual.length > 5) perms.wg = textual[5] === "w"; + if (textual.length > 6) { + switch (textual[6]) { + case "x": + perms.eg = true; + break; + case "s": + perms.eg = true; + perms.sg = true; + break; + case "S": + perms.sg = true; + break; + } + } + + if (textual.length > 7) perms.ro = textual[7] === "r"; + if (textual.length > 8) perms.wo = textual[8] === "w"; + if (textual.length > 9) { + switch (textual[9]) { + case "x": + perms.eo = true; + break; + case "t": + perms.eo = true; + perms.sb = true; + break; + case "T": + perms.sb = true; + break; + } + } + } else { + throw new OperationError("无效的输入格式。\n请输入八进制形式(例如:755)或文本形式(例如:drwxr-xr-x)。"); + } + + output += "文本形式: " + permsToStr(perms); + output += "\n八进制形式: " + permsToOctal(perms); + + // File type + if (textual) { + output += "\n文件类型: " + ftFromPerms(perms); + } + + // setuid, setgid + if (perms.su) { + output += "\n设置了SUID(setuid)权限"; + } + if (perms.sg) { + output += "\n设置了SGID(setgid)权限"; + } + + // sticky bit + if (perms.sb) { + output += "\n设置了SBIT(sticky bit)权限"; + } + + // Permission matrix + output += ` + + +---------+-------+-------+-------+ + | | 用户 | 组 | 其它 | + +---------+-------+-------+-------+ + | 读 | ${perms.ru ? "X" : " "} | ${perms.rg ? "X" : " "} | ${perms.ro ? "X" : " "} | + +---------+-------+-------+-------+ + | 写 | ${perms.wu ? "X" : " "} | ${perms.wg ? "X" : " "} | ${perms.wo ? "X" : " "} | + +---------+-------+-------+-------+ + | 执行 | ${perms.eu ? "X" : " "} | ${perms.eg ? "X" : " "} | ${perms.eo ? "X" : " "} | + +---------+-------+-------+-------+`; + + return output; + } + +} + + +/** + * Given a permissions object dictionary, generates a textual permissions string. + * + * @param {Object} perms + * @returns {string} + */ +function permsToStr(perms) { + let str = "", + type = "-"; + + if (perms.d) type = "d"; + if (perms.sl) type = "l"; + if (perms.np) type = "p"; + if (perms.s) type = "s"; + if (perms.cd) type = "c"; + if (perms.bd) type = "b"; + if (perms.dr) type = "D"; + + str = type; + + str += perms.ru ? "r" : "-"; + str += perms.wu ? "w" : "-"; + if (perms.eu && perms.su) { + str += "s"; + } else if (perms.su) { + str += "S"; + } else if (perms.eu) { + str += "x"; + } else { + str += "-"; + } + + str += perms.rg ? "r" : "-"; + str += perms.wg ? "w" : "-"; + if (perms.eg && perms.sg) { + str += "s"; + } else if (perms.sg) { + str += "S"; + } else if (perms.eg) { + str += "x"; + } else { + str += "-"; + } + + str += perms.ro ? "r" : "-"; + str += perms.wo ? "w" : "-"; + if (perms.eo && perms.sb) { + str += "t"; + } else if (perms.sb) { + str += "T"; + } else if (perms.eo) { + str += "x"; + } else { + str += "-"; + } + + return str; +} + +/** + * Given a permissions object dictionary, generates an octal permissions string. + * + * @param {Object} perms + * @returns {string} + */ +function permsToOctal(perms) { + let d = 0, + u = 0, + g = 0, + o = 0; + + if (perms.su) d += 4; + if (perms.sg) d += 2; + if (perms.sb) d += 1; + + if (perms.ru) u += 4; + if (perms.wu) u += 2; + if (perms.eu) u += 1; + + if (perms.rg) g += 4; + if (perms.wg) g += 2; + if (perms.eg) g += 1; + + if (perms.ro) o += 4; + if (perms.wo) o += 2; + if (perms.eo) o += 1; + + return d.toString() + u.toString() + g.toString() + o.toString(); +} + + +/** + * Given a permissions object dictionary, returns the file type. + * + * @param {Object} perms + * @returns {string} + */ +function ftFromPerms(perms) { + if (perms.d) return "目录"; + if (perms.sl) return "符号链接"; + if (perms.np) return "管道"; + if (perms.s) return "套接字"; + if (perms.cd) return "字符设备"; + if (perms.bd) return "块设备"; + if (perms.dr) return "Door"; + return "普通文件"; +} + +export default ParseUNIXFilePermissions; diff --git a/plugins/srktoolbox/src/core/operations/ParseURI.mjs b/plugins/srktoolbox/src/core/operations/ParseURI.mjs new file mode 100644 index 00000000..ecc5f76d --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ParseURI.mjs @@ -0,0 +1,72 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import url from "url"; + +/** + * Parse URI operation + */ +class ParseURI extends Operation { + + /** + * ParseURI constructor + */ + constructor() { + super(); + + this.name = "解析URI"; + this.module = "URL"; + this.description = "将复杂的Uniform Resource Identifier (URI)字符串解析成容易阅读的形式。可用于查看有较多参数的Uniform Resource Locator (URL)。"; + this.infoURL = "https://wikipedia.org/wiki/Uniform_Resource_Identifier"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const uri = url.parse(input, true); + + let output = ""; + + if (uri.protocol) output += "协议:\t" + uri.protocol + "\n"; + if (uri.auth) output += "鉴权:\t\t" + uri.auth + "\n"; + if (uri.hostname) output += "主机名称:\t" + uri.hostname + "\n"; + if (uri.port) output += "端口:\t\t" + uri.port + "\n"; + if (uri.pathname) output += "路径名称:\t" + uri.pathname + "\n"; + if (uri.query) { + const keys = Object.keys(uri.query); + let padding = 0; + + keys.forEach(k => { + padding = (k.length > padding) ? k.length : padding; + }); + + output += "参数:\n"; + for (const key in uri.query) { + output += "\t" + key.padEnd(padding, " "); + if (uri.query[key].length) { + output += " = " + uri.query[key] + "\n"; + } else { + output += "\n"; + } + } + } + if (uri.hash) output += "哈希值:\t\t" + uri.hash + "\n"; + + return output; + } + +} + +export default ParseURI; diff --git a/plugins/srktoolbox/src/core/operations/ParseUserAgent.mjs b/plugins/srktoolbox/src/core/operations/ParseUserAgent.mjs new file mode 100644 index 00000000..b676b55e --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ParseUserAgent.mjs @@ -0,0 +1,65 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import UAParser from "ua-parser-js"; + +/** + * Parse User Agent operation + */ +class ParseUserAgent extends Operation { + + /** + * ParseUserAgent constructor + */ + constructor() { + super(); + + this.name = "解析User Agent"; + this.module = "UserAgent"; + this.description = "尝试对User-Agent字符串中的内容进行解析。"; + this.infoURL = "https://wikipedia.org/wiki/User_agent"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + this.checks = [ + { + pattern: "^(User-Agent:|Mozilla\\/)[^\\n\\r]+\\s*$", + flags: "i", + args: [] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const ua = UAParser(input); + return `浏览器 + 名称: ${ua.browser.name || "未知"} + 版本: ${ua.browser.version || "未知"} +设备 + 型号: ${ua.device.model || "未知"} + 类型: ${ua.device.type || "未知"} + 厂商: ${ua.device.vendor || "未知"} +内核 + 名称: ${ua.engine.name || "未知"} + 版本: ${ua.engine.version || "未知"} +操作系统 + 名称: ${ua.os.name || "未知"} + 版本: ${ua.os.version || "未知"} +CPU + 架构: ${ua.cpu.architecture || "未知"}`; + } + +} + +export default ParseUserAgent; diff --git a/plugins/srktoolbox/src/core/operations/ParseX509CRL.mjs b/plugins/srktoolbox/src/core/operations/ParseX509CRL.mjs new file mode 100644 index 00000000..c1431986 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ParseX509CRL.mjs @@ -0,0 +1,393 @@ +/** + * @author robinsandhu + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + * + * Modified by Raka-loah@github + */ + +import r from "jsrsasign"; +import Operation from "../Operation.mjs"; +import { fromBase64 } from "../lib/Base64.mjs"; +import { toHex } from "../lib/Hex.mjs"; +import { formatDnObj } from "../lib/PublicKey.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; + +/** + * Parse X.509 CRL operation + */ +class ParseX509CRL extends Operation { + + /** + * ParseX509CRL constructor + */ + constructor() { + super(); + + this.name = "解析X.509证书吊销列表"; + this.module = "PublicKey"; + this.description = "解析证书吊销列表(Certificate Revocation List,CRL)"; + this.infoURL = "https://wikipedia.org/wiki/Certificate_revocation_list"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "输入格式", + "type": "option", + "value": ["PEM", "DER十六进制", "Base64", "原始"] + } + ]; + this.checks = [ + { + "pattern": "^-+BEGIN X509 CRL-+\\r?\\n[\\da-z+/\\n\\r]+-+END X509 CRL-+\\r?\\n?$", + "flags": "i", + "args": ["PEM"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} Human-readable description of a Certificate Revocation List (CRL). + */ + run(input, args) { + if (!input.length) { + return "输入内容为空"; + } + + const inputFormat = args[0]; + + let undefinedInputFormat = false; + try { + switch (inputFormat) { + case "DER十六进制": + input = input.replace(/\s/g, "").toLowerCase(); + break; + case "PEM": + break; + case "Base64": + input = toHex(fromBase64(input, null, "byteArray"), ""); + break; + case "原始": + input = toHex(Utils.strToArrayBuffer(input), ""); + break; + default: + undefinedInputFormat = true; + } + } catch (e) { + throw "证书加载错误(请确保输入内容为证书)"; + } + if (undefinedInputFormat) throw "未知的输入格式"; + + const crl = new r.X509CRL(input); + + let out = `证书吊销列表 (CRL): + 版本: ${crl.getVersion() === null ? "1 (0x0)" : "2 (0x1)"} + 签名算法: ${crl.getSignatureAlgorithmField()} + 颁发者:\n${formatDnObj(crl.getIssuer(), 8)} + 最近更新: ${generalizedDateTimeToUTC(crl.getThisUpdate())} + 下次更新: ${generalizedDateTimeToUTC(crl.getNextUpdate())}\n`; + + if (crl.getParam().ext !== undefined) { + out += `\tCRL扩展:\n${formatCRLExtensions(crl.getParam().ext, 8)}\n`; + } + + out += `已吊销的证书:\n${formatRevokedCertificates(crl.getRevCertArray(), 4)} +签名值:\n${formatCRLSignature(crl.getSignatureValueHex(), 8)}`; + + return out; + } +} + +/** + * Generalized date time string to UTC. + * @param {string} datetime + * @returns UTC datetime string. + */ +function generalizedDateTimeToUTC(datetime) { + // Ensure the string is in the correct format + if (!/^\d{12,14}Z$/.test(datetime)) { + throw new OperationError(`datetime字符串 ${datetime} 构造失败`); + } + + // Extract components + let centuary = "20"; + if (datetime.length === 15) { + centuary = datetime.substring(0, 2); + datetime = datetime.slice(2); + } + const year = centuary + datetime.substring(0, 2); + const month = datetime.substring(2, 4); + const day = datetime.substring(4, 6); + const hour = datetime.substring(6, 8); + const minute = datetime.substring(8, 10); + const second = datetime.substring(10, 12); + + // Construct ISO 8601 format string + const isoString = `${year}-${month}-${day}T${hour}:${minute}:${second}Z`; + + // Parse using standard Date object + const isoDateTime = new Date(isoString); + + return isoDateTime.toUTCString(); +} + +/** + * Format CRL extensions. + * @param {r.ExtParam[] | undefined} extensions + * @param {Number} indent + * @returns Formatted string detailing CRL extensions. + */ +function formatCRLExtensions(extensions, indent) { + if (Array.isArray(extensions) === false || extensions.length === 0) { + return indentString(`无CRL扩展。`, indent); + } + + let out = ``; + + extensions.sort((a, b) => { + if (!Object.hasOwn(a, "extname") || !Object.hasOwn(b, "extname")) { + return 0; + } + if (a.extname < b.extname) { + return -1; + } else if (a.extname === b.extname) { + return 0; + } else { + return 1; + } + }); + + extensions.forEach((ext) => { + if (!Object.hasOwn(ext, "extname")) { + throw new OperationError(`CRL条目扩展对象缺少'extname'键: ${ext}`); + } + switch (ext.extname) { + case "authorityKeyIdentifier": + out += `X509v3 颁发机构密钥标识符:\n`; + if (Object.hasOwn(ext, "kid")) { + out += `\tkeyid:${colonDelimitedHexFormatString(ext.kid.hex.toUpperCase())}\n`; + } + if (Object.hasOwn(ext, "issuer")) { + out += `\tDirName:${ext.issuer.str}\n`; + } + if (Object.hasOwn(ext, "sn")) { + out += `\tserial:${colonDelimitedHexFormatString(ext.sn.hex.toUpperCase())}\n`; + } + break; + case "cRLDistributionPoints": + out += `X509v3 CRL分发点:\n`; + ext.array.forEach((distPoint) => { + const fullName = `全名:\n${formatGeneralNames(distPoint.dpname.full, 4)}`; + out += indentString(fullName, 4) + "\n"; + }); + break; + case "cRLNumber": + if (!Object.hasOwn(ext, "num")) { + throw new OperationError(`'cRLNumber' CRL条目扩展缺少'num'键: ${ext}`); + } + out += `X509v3 CRL号码:\n\t${ext.num.hex.toUpperCase()}\n`; + break; + case "issuerAltName": + out += `X509v3 颁发者别名:\n${formatGeneralNames(ext.array, 4)}\n`; + break; + default: + out += `${ext.extname}:\n`; + out += `\t不支持的CRL扩展。试试Openssl命令行。\n`; + break; + } + }); + + return indentString(chop(out), indent); +} + +/** + * Format general names array. + * @param {Object[]} names + * @returns Multi-line formatted string describing all supported general name types. + */ +function formatGeneralNames(names, indent) { + let out = ``; + + names.forEach((name) => { + const key = Object.keys(name)[0]; + + switch (key) { + case "ip": + out += `IP:${name.ip}\n`; + break; + case "dns": + out += `DNS:${name.dns}\n`; + break; + case "uri": + out += `URI:${name.uri}\n`; + break; + case "rfc822": + out += `EMAIL:${name.rfc822}\n`; + break; + case "dn": + out += `DIR:${name.dn.str}\n`; + break; + case "other": + out += `OtherName:${name.other.oid}::${Object.values(name.other.value)[0].str}\n`; + break; + default: + out += `${key}: 不支持的通用名称类型`; + break; + } + }); + + return indentString(chop(out), indent); +} + +/** + * Colon-delimited hex formatted output. + * @param {string} hexString Hex String + * @returns String representing input hex string with colon delimiter. + */ +function colonDelimitedHexFormatString(hexString) { + if (hexString.length % 2 !== 0) { + hexString = "0" + hexString; + } + + return chop(hexString.replace(/(..)/g, "$&:")); +} + +/** + * Format revoked certificates array + * @param {r.RevokedCertificate[] | null} revokedCertificates + * @param {Number} indent + * @returns Multi-line formatted string output of revoked certificates array + */ +function formatRevokedCertificates(revokedCertificates, indent) { + if (Array.isArray(revokedCertificates) === false || revokedCertificates.length === 0) { + return indentString("没有被吊销的证书。", indent); + } + + let out=``; + + revokedCertificates.forEach((revCert) => { + if (!Object.hasOwn(revCert, "sn") || !Object.hasOwn(revCert, "date")) { + throw new OperationError("无效的吊销证书对象,缺少序列号或日期。"); + } + + out += `序列号: ${revCert.sn.hex.toUpperCase()} + 吊销日期: ${generalizedDateTimeToUTC(revCert.date)}\n`; + if (Object.hasOwn(revCert, "ext") && Array.isArray(revCert.ext) && revCert.ext.length !== 0) { + out += `\tCRL条目扩展:\n${indentString(formatCRLEntryExtensions(revCert.ext), 2*indent)}\n`; + } + }); + + return indentString(chop(out), indent); +} + +/** + * Format CRL entry extensions. + * @param {Object[]} exts + * @returns Formatted multi-line string describing CRL entry extensions. + */ +function formatCRLEntryExtensions(exts) { + let out = ``; + + const crlReasonCodeToReasonMessage = { + 0: "Unspecified", + 1: "Key Compromise", + 2: "CA Compromise", + 3: "Affiliation Changed", + 4: "Superseded", + 5: "Cessation Of Operation", + 6: "Certificate Hold", + 8: "Remove From CRL", + 9: "Privilege Withdrawn", + 10: "AA Compromise", + }; + + const holdInstructionOIDToName = { + "1.2.840.10040.2.1": "Hold Instruction None", + "1.2.840.10040.2.2": "Hold Instruction Call Issuer", + "1.2.840.10040.2.3": "Hold Instruction Reject", + }; + + exts.forEach((ext) => { + if (!Object.hasOwn(ext, "extname")) { + throw new OperationError(`CRL条目扩展对象缺少'extname'键: ${ext}`); + } + switch (ext.extname) { + case "cRLReason": + if (!Object.hasOwn(ext, "code")) { + throw new OperationError(`'cRLReason' CRL条目扩展对象缺少'code'键: ${ext}`); + } + out += `X509v3 CRL原因编码: + ${Object.hasOwn(crlReasonCodeToReasonMessage, ext.code) ? crlReasonCodeToReasonMessage[ext.code] : `无效的原因编码: ${ext.code}`}\n`; + break; + case "2.5.29.23": // Hold instruction + out += `持有指令代码:\n\t${Object.hasOwn(holdInstructionOIDToName, ext.extn.oid) ? holdInstructionOIDToName[ext.extn.oid] : `${ext.extn.oid}: 未知的持有指令OID`}\n`; + break; + case "2.5.29.24": // Invalidity Date + out += `失效日期:\n\t${generalizedDateTimeToUTC(ext.extn.gentime.str)}\n`; + break; + default: + out += `${ext.extname}:\n`; + out += `\t不支持的CRL扩展。试试Openssl命令行。\n`; + break; + } + }); + + return chop(out); +} + +/** + * Format CRL signature. + * @param {String} sigHex + * @param {Number} indent + * @returns String representing hex signature value formatted on multiple lines. + */ +function formatCRLSignature(sigHex, indent) { + if (sigHex.length % 2 !== 0) { + sigHex = "0" + sigHex; + } + + return indentString(formatMultiLine(chop(sigHex.replace(/(..)/g, "$&:"))), indent); +} + +/** + * Format string onto multiple lines. + * @param {string} longStr + * @returns String as a multi-line string. + */ +function formatMultiLine(longStr) { + const lines = []; + + for (let remain = longStr ; remain !== "" ; remain = remain.substring(54)) { + lines.push(remain.substring(0, 54)); + } + + return lines.join("\n"); +} + +/** + * Indent a multi-line string by n spaces. + * @param {string} input String + * @param {number} spaces How many leading spaces + * @returns Indented string. + */ +function indentString(input, spaces) { + const indent = " ".repeat(spaces); + return input.replace(/^/gm, indent); +} + +/** + * Remove last character from a string. + * @param {string} s String + * @returns Chopped string. + */ +function chop(s) { + if (s.length < 1) { + return s; + } + return s.substring(0, s.length - 1); +} + +export default ParseX509CRL; diff --git a/plugins/srktoolbox/src/core/operations/ParseX509Certificate.mjs b/plugins/srktoolbox/src/core/operations/ParseX509Certificate.mjs new file mode 100644 index 00000000..8c7a1386 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ParseX509Certificate.mjs @@ -0,0 +1,233 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import r from "jsrsasign"; +import { fromBase64 } from "../lib/Base64.mjs"; +import { runHash } from "../lib/Hash.mjs"; +import { fromHex, toHex } from "../lib/Hex.mjs"; +import { formatByteStr, formatDnObj } from "../lib/PublicKey.mjs"; +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +/** + * Parse X.509 certificate operation + */ +class ParseX509Certificate extends Operation { + + /** + * ParseX509Certificate constructor + */ + constructor() { + super(); + + this.name = "解析X.509证书"; + this.module = "PublicKey"; + this.description = "X.509是密码学里公钥证书的格式标准。X.509证书已应用在包括TLS/SSL在内的众多网络协议里,同时它也用在很多非在线应用场景里,比如电子签名服务。

此操作把证书内容显示为人类可读的形式,和openssl命令行的效果类似。

标签: X509, server hello, handshake"; + this.infoURL = "https://wikipedia.org/wiki/X.509"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "输入格式", + "type": "option", + "value": ["PEM", "DER十六进制", "Base64", "原始"] + } + ]; + this.checks = [ + { + "pattern": "^-+BEGIN CERTIFICATE-+\\r?\\n[\\da-z+/\\n\\r]+-+END CERTIFICATE-+\\r?\\n?$", + "flags": "i", + "args": ["PEM"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + if (!input.length) { + return "输入为空"; + } + + const cert = new r.X509(), + inputFormat = args[0]; + + let undefinedInputFormat = false; + try { + switch (inputFormat) { + case "DER十六进制": + input = input.replace(/\s/g, "").toLowerCase(); + cert.readCertHex(input); + break; + case "PEM": + cert.readCertPEM(input); + break; + case "Base64": + cert.readCertHex(toHex(fromBase64(input, null, "byteArray"), "")); + break; + case "原始": + cert.readCertHex(toHex(Utils.strToArrayBuffer(input), "")); + break; + default: + undefinedInputFormat = true; + } + } catch (e) { + throw "证书读取错误(输入内容有误?)"; + } + if (undefinedInputFormat) throw "无效输入格式"; + + const hex = Utils.strToArrayBuffer(Utils.byteArrayToChars(fromHex(cert.hex))), + sn = cert.getSerialNumberHex(), + issuer = cert.getIssuer(), + subject = cert.getSubject(), + pk = cert.getPublicKey(), + pkFields = [], + sig = cert.getSignatureValueHex(); + + let pkStr = "", + sigStr = "", + extensions = ""; + + // Public Key fields + pkFields.push({ + key: "Algorithm", + value: pk.type + }); + + if (pk.type === "EC") { // ECDSA + pkFields.push({ + key: "Curve Name", + value: pk.curveName + }); + pkFields.push({ + key: "Length", + value: (((new r.BigInteger(pk.pubKeyHex, 16)).bitLength()-3) /2) + " bits" + }); + pkFields.push({ + key: "pub", + value: formatByteStr(pk.pubKeyHex, 16, 18) + }); + } else if (pk.type === "DSA") { // DSA + pkFields.push({ + key: "pub", + value: formatByteStr(pk.y.toString(16), 16, 18) + }); + pkFields.push({ + key: "P", + value: formatByteStr(pk.p.toString(16), 16, 18) + }); + pkFields.push({ + key: "Q", + value: formatByteStr(pk.q.toString(16), 16, 18) + }); + pkFields.push({ + key: "G", + value: formatByteStr(pk.g.toString(16), 16, 18) + }); + } else if (pk.e) { // RSA + pkFields.push({ + key: "Length", + value: pk.n.bitLength() + " bits" + }); + pkFields.push({ + key: "Modulus", + value: formatByteStr(pk.n.toString(16), 16, 18) + }); + pkFields.push({ + key: "Exponent", + value: pk.e + " (0x" + pk.e.toString(16) + ")" + }); + } else { + pkFields.push({ + key: "Error", + value: "未知的公钥类型" + }); + } + + // Format Public Key fields + for (let i = 0; i < pkFields.length; i++) { + pkStr += ` ${pkFields[i].key}:${(pkFields[i].value + "\n").padStart( + 18 - (pkFields[i].key.length + 3) + pkFields[i].value.length + 1, + " " + )}`; + } + + // Signature fields + let breakoutSig = false; + try { + breakoutSig = r.ASN1HEX.dump(sig).indexOf("SEQUENCE") === 0; + } catch (err) { + // Error processing signature, output without further breakout + } + + if (breakoutSig) { // DSA or ECDSA + sigStr = ` r: ${formatByteStr(r.ASN1HEX.getV(sig, 4), 16, 18)} + s: ${formatByteStr(r.ASN1HEX.getV(sig, 48), 16, 18)}`; + } else { // RSA or unknown + sigStr = ` 签名: ${formatByteStr(sig, 16, 18)}`; + } + + // Extensions + try { + extensions = cert.getInfo().split("X509v3 Extensions:\n")[1].split("signature")[0]; + } catch (err) {} + + const issuerStr = formatDnObj(issuer, 2), + nbDate = formatDate(cert.getNotBefore()), + naDate = formatDate(cert.getNotAfter()), + subjectStr = formatDnObj(subject, 2); + + return ` +版本: ${cert.version} (0x${Utils.hex(cert.version - 1)}) +序列号: ${new r.BigInteger(sn, 16).toString()} (0x${sn}) +算法ID: ${cert.getSignatureAlgorithmField()} +有效期: + 从: ${nbDate} (dd-mm-yyyy hh:mm:ss) (${cert.getNotBefore()}) + 到: ${naDate} (dd-mm-yyyy hh:mm:ss) (${cert.getNotAfter()}) +颁发者: +${issuerStr} +使用者: +${subjectStr} +指纹信息: + MD5: ${runHash("md5", hex)} + SHA1: ${runHash("sha1", hex)} + SHA256: ${runHash("sha256", hex)} +公钥: +${pkStr.slice(0, -1)} +证书签名: + 算法: ${cert.getSignatureAlgorithmName()} +${sigStr} + +扩展: +${extensions}`; + } + +} + +/** + * Formats dates. + * + * @param {string} dateStr + * @returns {string} + */ +function formatDate (dateStr) { + if (dateStr.length === 13) { // UTC Time + dateStr = (dateStr[0] < "5" ? "20" : "19") + dateStr; + } + return dateStr[6] + dateStr[7] + "/" + + dateStr[4] + dateStr[5] + "/" + + dateStr[0] + dateStr[1] + dateStr[2] + dateStr[3] + " " + + dateStr[8] + dateStr[9] + ":" + + dateStr[10] + dateStr[11] + ":" + + dateStr[12] + dateStr[13]; +} + +export default ParseX509Certificate; diff --git a/plugins/srktoolbox/src/core/operations/PlayMedia.mjs b/plugins/srktoolbox/src/core/operations/PlayMedia.mjs new file mode 100644 index 00000000..46c426e8 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/PlayMedia.mjs @@ -0,0 +1,103 @@ +/** + * @author anthony-arnold [anthony.arnold@uqconnect.edu.au] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import { fromBase64, toBase64 } from "../lib/Base64.mjs"; +import { fromHex } from "../lib/Hex.mjs"; +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { isType, detectFileType } from "../lib/FileType.mjs"; + +/** + * PlayMedia operation + */ +class PlayMedia extends Operation { + + /** + * PlayMedia constructor + */ + constructor() { + super(); + + this.name = "播放媒体文件"; + this.module = "Default"; + this.description = "将输入按照音频或视频文件进行播放。

标签: sound, movie, mp3, mp4, mov, webm, wav, ogg"; + this.infoURL = ""; + this.inputType = "string"; + this.outputType = "byteArray"; + this.presentType = "html"; + this.args = [ + { + "name": "输入格式", + "type": "option", + "value": ["原始", "Base64", "十六进制"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} The multimedia data as bytes. + */ + run(input, args) { + const [inputFormat] = args; + + if (!input.length) return []; + + // Convert input to raw bytes + switch (inputFormat) { + case "十六进制": + input = fromHex(input); + break; + case "Base64": + // Don't trust the Base64 entered by the user. + // Unwrap it first, then re-encode later. + input = fromBase64(input, undefined, "byteArray"); + break; + case "原始": + default: + input = Utils.strToByteArray(input); + break; + } + + + // Determine file type + if (!isType(/^(audio|video)/, input)) { + throw new OperationError("无效或不支持的文件类型"); + } + + return input; + } + + /** + * Displays an audio or video element that may be able to play the media + * file. + * + * @param {byteArray} data Data containing an audio or video file. + * @returns {string} Markup to display a media player. + */ + async present(data) { + if (!data.length) return ""; + + const types = detectFileType(data); + const matches = /^audio|video/.exec(types[0].mime); + if (!matches) { + throw new OperationError("无效的文件类型"); + } + const dataURI = `data:${types[0].mime};base64,${toBase64(data)}`; + const element = matches[0]; + + let html = `<${element} src='${dataURI}' type='${types[0].mime}' controls>`; + html += "

不支持的媒体格式。

"; + html += ``; + return html; + } +} + +export default PlayMedia; diff --git a/plugins/srktoolbox/src/core/operations/PowerSet.mjs b/plugins/srktoolbox/src/core/operations/PowerSet.mjs new file mode 100644 index 00000000..b6b00038 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/PowerSet.mjs @@ -0,0 +1,95 @@ +/** + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; + +/** + * Power Set operation + */ +class PowerSet extends Operation { + + /** + * Power set constructor + */ + constructor() { + super(); + + this.name = "幂集"; + this.module = "Default"; + this.description = "计算集合的所有子集"; + this.infoURL = "https://wikipedia.org/wiki/Power_set"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "元素分隔符", + type: "binaryString", + value: "," + }, + ]; + } + + /** + * Generate the power set + * + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + [this.itemDelimiter] = args; + // Split and filter empty strings + const inputArray = input.split(this.itemDelimiter).filter(a => a); + + if (inputArray.length) { + return this.runPowerSet(inputArray); + } + + return ""; + } + + /** + * Return the power set of the inputted set. + * + * @param {Object[]} a + * @returns {Object[]} + */ + runPowerSet(a) { + // empty array items getting picked up + a = a.filter(i => i.length); + if (!a.length) { + return []; + } + + /** + * Decimal to binary function + * @param {*} dec + */ + const toBinary = (dec) => (dec >>> 0).toString(2); + const result = new Set(); + // Get the decimal number to make a binary as long as the input + const maxBinaryValue = parseInt(Number(a.map(i => "1").reduce((p, c) => p + c)), 2); + // Make an array of each binary number from 0 to maximum + const binaries = [...Array(maxBinaryValue + 1).keys()] + .map(toBinary) + .map(i => i.padStart(toBinary(maxBinaryValue).length, "0")); + + // XOR the input with each binary to get each unique permutation + binaries.forEach((binary) => { + const split = binary.split(""); + result.add(a.filter((item, index) => split[index] === "1")); + }); + + // map for formatting & put in length order. + return [...result] + .map(r => r.join(this.itemDelimiter)).sort((a, b) => a.length - b.length) + .map(i => `${i}\n`).join(""); + } +} + +export default PowerSet; diff --git a/plugins/srktoolbox/src/core/operations/ProtobufDecode.mjs b/plugins/srktoolbox/src/core/operations/ProtobufDecode.mjs new file mode 100644 index 00000000..2db1e8c4 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ProtobufDecode.mjs @@ -0,0 +1,67 @@ +/** + * @author GCHQ Contributor [3] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Protobuf from "../lib/Protobuf.mjs"; + +/** + * Protobuf Decode operation + */ +class ProtobufDecode extends Operation { + + /** + * ProtobufDecode constructor + */ + constructor() { + super(); + + this.name = "Protobuf解码"; + this.module = "Protobuf"; + this.description = "将Protobuf编码的数据解码为JSON表示,使用字段数值作为字段key。

如果指定了.proto模式文件,数据将会引用模式文件内容进行解码。只会解码一个消息实例。

显示未知字段
当指定了模式文件时,如果输入数据在模式文件中没有定义,勾选此选项强行显示这些数据。

显示类型
在字段名称旁边显示其类型。对于未定义的字段,显示其Wire Type和示例类型。"; + this.infoURL = "https://wikipedia.org/wiki/Protocol_Buffers"; + this.inputType = "ArrayBuffer"; + this.outputType = "JSON"; + this.args = [ + { + name: "模式文件(Schema,.proto文本)", + type: "text", + value: "", + rows: 8, + hint: "可以直接拖放文件到此位置" + }, + { + name: "显示未知字段", + type: "boolean", + value: false + }, + { + name: "显示类型", + type: "boolean", + value: false + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {JSON} + */ + run(input, args) { + input = new Uint8Array(input); + try { + return Protobuf.decode(input, args); + } catch (err) { + throw new OperationError(err); + } + } + +} + +export default ProtobufDecode; diff --git a/plugins/srktoolbox/src/core/operations/ProtobufEncode.mjs b/plugins/srktoolbox/src/core/operations/ProtobufEncode.mjs new file mode 100644 index 00000000..6076ab71 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ProtobufEncode.mjs @@ -0,0 +1,56 @@ +/** + * @author GCHQ Contributor [3] + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Protobuf from "../lib/Protobuf.mjs"; + +/** + * Protobuf Encode operation + */ +class ProtobufEncode extends Operation { + + /** + * ProtobufEncode constructor + */ + constructor() { + super(); + + this.name = "Protobuf编码"; + this.module = "Protobuf"; + this.description = "使用.proto模式文件把有效的JSON object编码成protobuf字节数组。"; + this.infoURL = "https://developers.google.com/protocol-buffers/docs/encoding"; + this.inputType = "JSON"; + this.outputType = "ArrayBuffer"; + this.args = [ + { + name: "模式文件(Schema,.proto文本)", + type: "text", + value: "", + rows: 8, + hint: "可以直接拖放文件到此位置" + } + ]; + } + + /** + * @param {Object} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + try { + return Protobuf.encode(input, args); + } catch (error) { + throw new OperationError(error); + } + } + +} + +export default ProtobufEncode; diff --git a/plugins/srktoolbox/src/core/operations/PseudoRandomNumberGenerator.mjs b/plugins/srktoolbox/src/core/operations/PseudoRandomNumberGenerator.mjs new file mode 100644 index 00000000..13dbd31a --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/PseudoRandomNumberGenerator.mjs @@ -0,0 +1,88 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import forge from "node-forge"; +import BigNumber from "bignumber.js"; +import { isWorkerEnvironment } from "../Utils.mjs"; + +/** + * Pseudo-Random Number Generator operation + */ +class PseudoRandomNumberGenerator extends Operation { + + /** + * PseudoRandomNumberGenerator constructor + */ + constructor() { + super(); + + this.name = "伪随机数生成器"; + this.module = "Ciphers"; + this.description = "密码学安全伪随机数生成器(CSPRNG)。

这个操作使用浏览器内置的 crypto.getRandomValues() 方法。如果不可用,则回退到基于Fortuna的随机数算法。"; + this.infoURL = "https://wikipedia.org/wiki/Pseudorandom_number_generator"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "字节数", + "type": "number", + "value": 32 + }, + { + "name": "输出", + "type": "option", + "value": ["十六进制", "整型", "字节数组", "原始"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [numBytes, outputAs] = args; + + let bytes; + + if (isWorkerEnvironment() && self.crypto) { + bytes = new ArrayBuffer(numBytes); + const CHUNK_SIZE = 65536; + for (let i = 0; i < numBytes; i += CHUNK_SIZE) { + self.crypto.getRandomValues(new Uint8Array(bytes, i, Math.min(numBytes - i, CHUNK_SIZE))); + } + bytes = Utils.arrayBufferToStr(bytes); + } else { + bytes = forge.random.getBytesSync(numBytes); + } + + let value = new BigNumber(0), + i; + + switch (outputAs) { + case "十六进制": + return forge.util.bytesToHex(bytes); + case "整型": + for (i = bytes.length - 1; i >= 0; i--) { + value = value.times(256).plus(bytes.charCodeAt(i)); + } + return value.toFixed(); + case "字节数组": + return JSON.stringify(Utils.strToCharcode(bytes)); + case "原始": + default: + return bytes; + } + } + +} + +export default PseudoRandomNumberGenerator; diff --git a/plugins/srktoolbox/src/core/operations/PubKeyFromCert.mjs b/plugins/srktoolbox/src/core/operations/PubKeyFromCert.mjs new file mode 100644 index 00000000..7637753d --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/PubKeyFromCert.mjs @@ -0,0 +1,70 @@ +/** + * @author cplussharp + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import r from "jsrsasign"; +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Public Key from Certificate operation + */ +class PubKeyFromCert extends Operation { + + /** + * PubKeyFromCert constructor + */ + constructor() { + super(); + + this.name = "从证书提取公钥"; + this.module = "PublicKey"; + this.description = "从证书中提取公钥。"; + this.infoURL = "https://en.wikipedia.org/wiki/X.509"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + this.checks = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let output = ""; + let match; + const regex = /-----BEGIN CERTIFICATE-----/g; + while ((match = regex.exec(input)) !== null) { + // find corresponding end tag + const indexBase64 = match.index + match[0].length; + const footer = "-----END CERTIFICATE-----"; + const indexFooter = input.indexOf(footer, indexBase64); + if (indexFooter === -1) { + throw new OperationError(`未找到PEM footer '${footer}'`); + } + + const certPem = input.substring(match.index, indexFooter + footer.length); + const cert = new r.X509(); + cert.readCertPEM(certPem); + let pubKey; + try { + pubKey = cert.getPublicKey(); + } catch { + throw new OperationError("不支持的公钥类型"); + } + const pubKeyPem = r.KEYUTIL.getPEM(pubKey); + + // PEM ends with '\n', so a new key always starts on a new line + output += pubKeyPem; + } + return output; + } +} + +export default PubKeyFromCert; diff --git a/plugins/srktoolbox/src/core/operations/PubKeyFromPrivKey.mjs b/plugins/srktoolbox/src/core/operations/PubKeyFromPrivKey.mjs new file mode 100644 index 00000000..723e7d70 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/PubKeyFromPrivKey.mjs @@ -0,0 +1,84 @@ +/** + * @author cplussharp + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import r from "jsrsasign"; +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Public Key from Private Key operation + */ +class PubKeyFromPrivKey extends Operation { + + /** + * PubKeyFromPrivKey constructor + */ + constructor() { + super(); + + this.name = "从私钥提取公钥"; + this.module = "PublicKey"; + this.description = "从私钥中提取公钥。"; + this.infoURL = "https://en.wikipedia.org/wiki/PKCS_8"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + this.checks = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let output = ""; + let match; + const regex = /-----BEGIN ((RSA |EC |DSA )?PRIVATE KEY)-----/g; + while ((match = regex.exec(input)) !== null) { + // find corresponding end tag + const indexBase64 = match.index + match[0].length; + const footer = `-----END ${match[1]}-----`; + const indexFooter = input.indexOf(footer, indexBase64); + if (indexFooter === -1) { + throw new OperationError(`未找到PEM footer '${footer}'`); + } + + const privKeyPem = input.substring(match.index, indexFooter + footer.length); + let privKey; + try { + privKey = r.KEYUTIL.getKey(privKeyPem); + } catch (err) { + throw new OperationError(`不支持的密钥类型:${err}`); + } + let pubKey; + if (privKey.type && privKey.type === "EC") { + pubKey = new r.KJUR.crypto.ECDSA({ curve: privKey.curve }); + pubKey.setPublicKeyHex(privKey.generatePublicKeyHex()); + } else if (privKey.type && privKey.type === "DSA") { + if (!privKey.y) { + throw new OperationError(`不支持PKCS#8格式DSA私钥`); + } + pubKey = new r.KJUR.crypto.DSA(); + pubKey.setPublic(privKey.p, privKey.q, privKey.g, privKey.y); + } else if (privKey.n && privKey.e) { + pubKey = new r.RSAKey(); + pubKey.setPublic(privKey.n, privKey.e); + } else { + throw new OperationError(`不支持的密钥类型`); + } + const pubKeyPem = r.KEYUTIL.getPEM(pubKey); + + // PEM ends with '\n', so a new key always starts on a new line + output += pubKeyPem; + } + return output; + } +} + +export default PubKeyFromPrivKey; diff --git a/plugins/srktoolbox/src/core/operations/RAKE.mjs b/plugins/srktoolbox/src/core/operations/RAKE.mjs new file mode 100644 index 00000000..2ce076cd --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/RAKE.mjs @@ -0,0 +1,146 @@ +/** + * @author sw5678 + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; + +/** + * RAKE operation + */ +class RAKE extends Operation { + + /** + * RAKE constructor + */ + constructor() { + super(); + + this.name = "RAKE"; + this.module = "Default"; + this.description = [ + "快速自动关键词提取(Rapid Automatic Keyword Extraction,RAKE)", + "

", + "RAKE是自然语言处理(NLP)中的一种领域无关的关键词提取算法。", + "

", + "终止词(Stop words)的列表来自NLTK Python包。", + ].join("\n"); + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "词分隔符(正则)", + type: "text", + value: "\\s" + }, + { + name: "句分隔符(正则)", + type: "text", + value: "\\.\\s|\\n" + }, + { + name: "终止词(Stop Words)", + type: "text", + value: "i,me,my,myself,we,our,ours,ourselves,you,you're,you've,you'll,you'd,your,yours,yourself,yourselves,he,him,his,himself,she,she's,her,hers,herself,it,it's,its,itsef,they,them,their,theirs,themselves,what,which,who,whom,this,that,that'll,these,those,am,is,are,was,were,be,been,being,have,has,had,having,do,does',did,doing,a,an,the,and,but,if,or,because,as,until,while,of,at,by,for,with,about,against,between,into,through,during,before,after,above,below,to,from,up,down,in,out,on,off,over,under,again,further,then,once,here,there,when,where,why,how,all,any,both,each,few,more,most,other,some,such,no,nor,not,only,own,same,so,than,too,very,s,t,can,will,just,don,don't,should,should've,now,d,ll,m,o,re,ve,y,ain,aren,aren't,couldn,couldn't,didn,didn't,doesn,doesn't,hadn,hadn't,hasn,hasn't,haven,haven't,isn,isn't,ma,mightn,mightn't,mustn,mustn't,needn,needn't,shan,shan't,shouldn,shouldn't,wasn,wasn't,weren,weren't,won,won't,wouldn,wouldn't" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + + // Get delimiter regexs + const wordDelim = new RegExp(args[0], "g"); + const sentDelim = new RegExp(args[1], "g"); + + // Deduplicate the stop words and add the empty string + const stopWords = args[2].toLowerCase().replace(/ /g, "").split(",").unique(); + stopWords.push(""); + + // Lower case input and remove start and ending whitespace + input = input.toLowerCase().trim(); + + // Get tokens, token count, and phrases + const tokens = []; + const wordFrequencies = []; + let phrases = []; + + // Build up list of phrases and token counts + const sentences = input.split(sentDelim); + for (const sent of sentences) { + + // Split sentence into words + const splitSent = sent.split(wordDelim); + let startIndex = 0; + + for (let i = 0; i < splitSent.length; i++) { + const token = splitSent[i]; + if (stopWords.includes(token)) { + // If token is stop word then split to create phrase + phrases.push(splitSent.slice(startIndex, i)); + startIndex = i + 1; + } else { + // If token is not a stop word add to the count of the list of words + if (tokens.includes(token)) { + wordFrequencies[tokens.indexOf(token)]+=1; + } else { + tokens.push(token); + wordFrequencies.push(1); + } + } + } + phrases.push(splitSent.slice(startIndex)); + } + + // remove empty phrases + phrases = phrases.filter(subArray => subArray.length > 0); + + // Remove duplicate phrases + phrases = phrases.unique(); + + // Generate word_degree_matrix and populate + const wordDegreeMatrix = Array(tokens.length).fill().map(() => Array(tokens.length).fill(0)); + for (const phrase of phrases) { + for (const word1 of phrase) { + for (const word2 of phrase) { + wordDegreeMatrix[tokens.indexOf(word1)][tokens.indexOf(word2)]++; + } + } + } + + // Calculate degree score for each token + const degreeScores = Array(tokens.length).fill(0); + for (let i=0; i b[0] - a[0]); + scores.unshift(new Array("评分:", "关键词:")); + + // Output works with the 'To Table' functionality already built into CC + return scores.map(function (score) { + return score.join(", "); + }).join("\n"); + } +} + +export default RAKE; diff --git a/plugins/srktoolbox/src/core/operations/RC2Decrypt.mjs b/plugins/srktoolbox/src/core/operations/RC2Decrypt.mjs new file mode 100644 index 00000000..9b901745 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/RC2Decrypt.mjs @@ -0,0 +1,78 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import forge from "node-forge"; + +/** + * RC2 Decrypt operation + */ +class RC2Decrypt extends Operation { + + /** + * RC2Decrypt constructor + */ + constructor() { + super(); + + this.name = "RC2解密"; + this.module = "Ciphers"; + this.description = "RC2(又叫ARC2)是Ron Rivest在1987年发明的对称加密算法。“RC”是“Rivest Cipher”的缩写。

Key: RC2使用变长的key。

你可以通过密钥派生操作来生成基于密码的key。

IV:CBC模式的初始化向量必须是8字节,如果IV留空则会使用ECB模式。

填充:CBC和ECB模式下会使用PKCS#7填充。"; + this.infoURL = "https://wikipedia.org/wiki/RC2"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["十六进制", "UTF8", "Latin1", "Base64"] + }, + { + "name": "IV", + "type": "toggleString", + "value": "", + "toggleValues": ["十六进制", "UTF8", "Latin1", "Base64"] + }, + { + "name": "Input", + "type": "option", + "value": ["十六进制", "原始内容"] + }, + { + "name": "Output", + "type": "option", + "value": ["原始内容", "十六进制"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = Utils.convertToByteString(args[0].string, args[0].option), + iv = Utils.convertToByteString(args[1].string, args[1].option), + [,, inputType, outputType] = args, + decipher = forge.rc2.createDecryptionCipher(key); + + input = Utils.convertToByteString(input, inputType); + + decipher.start(iv || null); + decipher.update(forge.util.createBuffer(input)); + decipher.finish(); + + return outputType === "十六进制" ? decipher.output.toHex() : decipher.output.getBytes(); + } + +} + +export default RC2Decrypt; diff --git a/plugins/srktoolbox/src/core/operations/RC2Encrypt.mjs b/plugins/srktoolbox/src/core/operations/RC2Encrypt.mjs new file mode 100644 index 00000000..42752d9d --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/RC2Encrypt.mjs @@ -0,0 +1,79 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import forge from "node-forge"; + + +/** + * RC2 Encrypt operation + */ +class RC2Encrypt extends Operation { + + /** + * RC2Encrypt constructor + */ + constructor() { + super(); + + this.name = "RC2加密"; + this.module = "Ciphers"; + this.description = "RC2(又叫ARC2)是Ron Rivest在1987年发明的对称加密算法。“RC”是“Rivest Cipher”的缩写。

Key: RC2使用变长的key。

你可以通过密钥派生操作来生成基于密码的key。

IV:CBC模式的初始化向量必须是8字节,如果IV留空则会使用ECB模式。

填充:CBC和ECB模式下会使用PKCS#7填充。"; + this.infoURL = "https://wikipedia.org/wiki/RC2"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["十六进制", "UTF8", "Latin1", "Base64"] + }, + { + "name": "IV", + "type": "toggleString", + "value": "", + "toggleValues": ["十六进制", "UTF8", "Latin1", "Base64"] + }, + { + "name": "Input", + "type": "option", + "value": ["原始内容", "十六进制"] + }, + { + "name": "Output", + "type": "option", + "value": ["十六进制", "原始内容"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = Utils.convertToByteString(args[0].string, args[0].option), + iv = Utils.convertToByteString(args[1].string, args[1].option), + [,, inputType, outputType] = args, + cipher = forge.rc2.createEncryptionCipher(key); + + input = Utils.convertToByteString(input, inputType); + + cipher.start(iv || null); + cipher.update(forge.util.createBuffer(input)); + cipher.finish(); + + return outputType === "十六进制" ? cipher.output.toHex() : cipher.output.getBytes(); + } + +} + +export default RC2Encrypt; diff --git a/plugins/srktoolbox/src/core/operations/RC4.mjs b/plugins/srktoolbox/src/core/operations/RC4.mjs new file mode 100644 index 00000000..15e30e8f --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/RC4.mjs @@ -0,0 +1,91 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import CryptoJS from "crypto-js"; +import { format } from "../lib/Ciphers.mjs"; + +/** + * RC4 operation + */ +class RC4 extends Operation { + + /** + * RC4 constructor + */ + constructor() { + super(); + + this.name = "RC4"; + this.module = "Ciphers"; + this.description = "RC4(又叫ARC4)是一种广泛使用的流加密算法,由Ron Rivest设计。在很多流行协议中均有应用,如SSL和WEP。RC4加密方法简洁高效,但安全性没有保障。"; + this.infoURL = "https://wikipedia.org/wiki/RC4"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "加密密码", + "type": "toggleString", + "value": "", + "toggleValues": ["UTF8", "UTF16", "UTF16LE", "UTF16BE", "Latin1", "十六进制", "Base64"] + }, + { + "name": "输入格式", + "type": "option", + "value": ["Latin1", "UTF8", "UTF16", "UTF16LE", "UTF16BE", "十六进制", "Base64"] + }, + { + "name": "输出格式", + "type": "option", + "value": ["Latin1", "UTF8", "UTF16", "UTF16LE", "UTF16BE", "十六进制", "Base64"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const message = format[args[1]].parse(input), + passphrase = format[args[0].option].parse(args[0].string), + encrypted = CryptoJS.RC4.encrypt(message, passphrase); + + return encrypted.ciphertext.toString(format[args[2]]); + } + + /** + * Highlight RC4 + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight RC4 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 RC4; diff --git a/plugins/srktoolbox/src/core/operations/RC4Drop.mjs b/plugins/srktoolbox/src/core/operations/RC4Drop.mjs new file mode 100644 index 00000000..58030bba --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/RC4Drop.mjs @@ -0,0 +1,97 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import { format } from "../lib/Ciphers.mjs"; +import CryptoJS from "crypto-js"; + +/** + * RC4 Drop operation + */ +class RC4Drop extends Operation { + + /** + * RC4Drop constructor + */ + constructor() { + super(); + + this.name = "RC4 Drop"; + this.module = "Ciphers"; + this.description = "由于RC4加密流前部的数个字节随机性不足且泄露关于key的信息,因此丢弃前部数据能提高加密安全性。通常这种优化后的算法称作RC4-drop。"; + this.infoURL = "https://wikipedia.org/wiki/RC4#Fluhrer,_Mantin_and_Shamir_attack"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "加密密码", + "type": "toggleString", + "value": "", + "toggleValues": ["UTF8", "UTF16", "UTF16LE", "UTF16BE", "Latin1", "十六进制", "Base64"] + }, + { + "name": "输入格式", + "type": "option", + "value": ["Latin1", "UTF8", "UTF16", "UTF16LE", "UTF16BE", "十六进制", "Base64"] + }, + { + "name": "输出格式", + "type": "option", + "value": ["Latin1", "UTF8", "UTF16", "UTF16LE", "UTF16BE", "十六进制", "Base64"] + }, + { + "name": "Number of dwords to drop", + "type": "number", + "value": 192 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const message = format[args[1]].parse(input), + passphrase = format[args[0].option].parse(args[0].string), + drop = args[3], + encrypted = CryptoJS.RC4Drop.encrypt(message, passphrase, { drop: drop }); + + return encrypted.ciphertext.toString(format[args[2]]); + } + + /** + * Highlight RC4 Drop + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight RC4 Drop 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 RC4Drop; diff --git a/plugins/srktoolbox/src/core/operations/RIPEMD.mjs b/plugins/srktoolbox/src/core/operations/RIPEMD.mjs new file mode 100644 index 00000000..299c9e9c --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/RIPEMD.mjs @@ -0,0 +1,50 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import {runHash} from "../lib/Hash.mjs"; + +/** + * RIPEMD operation + */ +class RIPEMD extends Operation { + + /** + * RIPEMD constructor + */ + constructor() { + super(); + + this.name = "RIPEMD"; + this.module = "Crypto"; + this.description = "RIPEMD(RACE原始完整性校验讯息摘要)是一种加密哈希函数,由 鲁汶大学 Hans Dobbertin,Antoon Bosselaers 和 Bart Prenee组成的COSIC 研究小组发布于1996年。

RIPEMD是以MD4为基础原则所设计的 ,而且其表现与更有名的SHA-1类似。"; + this.infoURL = "https://wikipedia.org/wiki/RIPEMD"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "长度", + "type": "option", + "value": ["320", "256", "160", "128"] + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const size = args[0]; + return runHash("ripemd" + size, input); + } + +} + +export default RIPEMD; diff --git a/plugins/srktoolbox/src/core/operations/ROT13.mjs b/plugins/srktoolbox/src/core/operations/ROT13.mjs new file mode 100644 index 00000000..be2e5c2e --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ROT13.mjs @@ -0,0 +1,116 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; + + +/** + * ROT13 operation. + */ +class ROT13 extends Operation { + + /** + * ROT13 constructor + */ + constructor() { + super(); + + this.name = "ROT13"; + this.module = "Default"; + this.description = "一个简单的凯撒密码,默认情况下把字母偏移13个位置。"; + this.infoURL = "https://wikipedia.org/wiki/ROT13"; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = [ + { + name: "偏移小写字母", + type: "boolean", + value: true + }, + { + name: "偏移大写字母", + type: "boolean", + value: true + }, + { + name: "偏移数字", + type: "boolean", + value: false + }, + { + name: "偏移数量", + type: "number", + value: 13 + }, + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const output = input, + rot13Lowercase = args[0], + rot13Upperacse = args[1], + rotNumbers = args[2]; + let amount = args[3], + amountNumbers = args[3]; + + if (amount) { + if (amount < 0) { + amount = 26 - (Math.abs(amount) % 26); + amountNumbers = 10 - (Math.abs(amountNumbers) % 10); + } + + for (let i = 0; i < input.length; i++) { + let chr = input[i]; + if (rot13Upperacse && chr >= 65 && chr <= 90) { // Upper case + chr = (chr - 65 + amount) % 26; + output[i] = chr + 65; + } else if (rot13Lowercase && chr >= 97 && chr <= 122) { // Lower case + chr = (chr - 97 + amount) % 26; + output[i] = chr + 97; + } else if (rotNumbers && chr >= 48 && chr <= 57) { // Numbers + chr = (chr - 48 + amountNumbers) % 10; + output[i] = chr + 48; + } + } + } + return output; + } + + /** + * Highlight ROT13 + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight ROT13 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 ROT13; diff --git a/plugins/srktoolbox/src/core/operations/ROT13BruteForce.mjs b/plugins/srktoolbox/src/core/operations/ROT13BruteForce.mjs new file mode 100644 index 00000000..ea7a1b22 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ROT13BruteForce.mjs @@ -0,0 +1,104 @@ +/** + * @author MikeCAT + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +/** + * ROT13 Brute Force operation. + */ +class ROT13BruteForce extends Operation { + + /** + * ROT13BruteForce constructor + */ + constructor() { + super(); + + this.name = "ROT13暴力破解"; + this.module = "Default"; + this.description = "尝试ROT13所有可能的偏移量。

你可以输入已知的明文部分(Crib)来筛选结果。"; + this.infoURL = "https://wikipedia.org/wiki/ROT13"; + this.inputType = "byteArray"; + this.outputType = "string"; + this.args = [ + { + name: "偏移小写字母", + type: "boolean", + value: true + }, + { + name: "偏移大写字母", + type: "boolean", + value: true + }, + { + name: "偏移数字", + type: "boolean", + value: false + }, + { + name: "取样长度", + type: "number", + value: 100 + }, + { + name: "取样偏移", + type: "number", + value: 0 + }, + { + name: "输出偏移量", + type: "boolean", + value: true + }, + { + name: "Crib (已知明文)", + type: "string", + value: "" + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [rotateLower, rotateUpper, rotateNum, sampleLength, sampleOffset, printAmount, crib] = args; + const sample = input.slice(sampleOffset, sampleOffset + sampleLength); + const cribLower = crib.toLowerCase(); + const lowerStart = "a".charCodeAt(0), upperStart = "A".charCodeAt(0), numStart = "0".charCodeAt(0); + const result = []; + for (let amount = 1; amount < 26; amount++) { + const rotated = sample.slice(); + for (let i = 0; i < rotated.length; i++) { + if (rotateLower && lowerStart <= rotated[i] && rotated[i] < lowerStart + 26) { + rotated[i] = (rotated[i] - lowerStart + amount) % 26 + lowerStart; + } else if (rotateUpper && upperStart <= rotated[i] && rotated[i] < upperStart + 26) { + rotated[i] = (rotated[i] - upperStart + amount) % 26 + upperStart; + } else if (rotateNum && numStart <= rotated[i] && rotated[i] < numStart + 10) { + rotated[i] = (rotated[i] - numStart + amount) % 10 + numStart; + } + } + const rotatedString = Utils.byteArrayToUtf8(rotated); + if (rotatedString.toLowerCase().indexOf(cribLower) >= 0) { + const rotatedStringEscaped = Utils.escapeWhitespace(rotatedString); + if (printAmount) { + const amountStr = "偏移量 = " + (" " + amount).slice(-2) + ": "; + result.push(amountStr + rotatedStringEscaped); + } else { + result.push(rotatedStringEscaped); + } + } + } + return result.join("\n"); + } +} + +export default ROT13BruteForce; diff --git a/plugins/srktoolbox/src/core/operations/ROT47.mjs b/plugins/srktoolbox/src/core/operations/ROT47.mjs new file mode 100644 index 00000000..b9b4343e --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ROT47.mjs @@ -0,0 +1,91 @@ +/** + * @author Matt C [matt@artemisbot.uk] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; + + +/** + * ROT47 operation. + */ +class ROT47 extends Operation { + + /** + * ROT47 constructor + */ + constructor() { + super(); + + this.name = "ROT47"; + this.module = "Default"; + this.description = "稍微复杂点的凯撒密码,使用了ASCII里从 33 '!' 到 126 '~'的字符。默认的偏移数量是47。"; + this.infoURL = "https://wikipedia.org/wiki/ROT13#Variants"; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = [ + { + name: "偏移数量", + type: "number", + value: 47 + }, + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const output = input; + let amount = args[0], + chr; + + if (amount) { + if (amount < 0) { + amount = 94 - (Math.abs(amount) % 94); + } + + for (let i = 0; i < input.length; i++) { + chr = input[i]; + if (chr >= 33 && chr <= 126) { + chr = (chr - 33 + amount) % 94; + output[i] = chr + 33; + } + } + } + return output; + } + + /** + * Highlight ROT47 + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight ROT47 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 ROT47; diff --git a/plugins/srktoolbox/src/core/operations/ROT47BruteForce.mjs b/plugins/srktoolbox/src/core/operations/ROT47BruteForce.mjs new file mode 100644 index 00000000..3836c415 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ROT47BruteForce.mjs @@ -0,0 +1,84 @@ +/** + * @author MikeCAT + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +/** + * ROT47 Brute Force operation. + */ +class ROT47BruteForce extends Operation { + + /** + * ROT47BruteForce constructor + */ + constructor() { + super(); + + this.name = "ROT47暴力破解"; + this.module = "Default"; + this.description = "尝试ROT47所有可能的偏移量。

你可以输入已知的明文部分(Crib)来筛选结果。"; + this.infoURL = "https://wikipedia.org/wiki/ROT13#Variants"; + this.inputType = "byteArray"; + this.outputType = "string"; + this.args = [ + { + name: "取样长度", + type: "number", + value: 100 + }, + { + name: "取样偏移", + type: "number", + value: 0 + }, + { + name: "输出偏移量", + type: "boolean", + value: true + }, + { + name: "Crib (已知明文)", + type: "string", + value: "" + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [sampleLength, sampleOffset, printAmount, crib] = args; + const sample = input.slice(sampleOffset, sampleOffset + sampleLength); + const cribLower = crib.toLowerCase(); + const result = []; + for (let amount = 1; amount < 94; amount++) { + const rotated = sample.slice(); + for (let i = 0; i < rotated.length; i++) { + if (33 <= rotated[i] && rotated[i] <= 126) { + rotated[i] = (rotated[i] - 33 + amount) % 94 + 33; + } + } + const rotatedString = Utils.byteArrayToUtf8(rotated); + if (rotatedString.toLowerCase().indexOf(cribLower) >= 0) { + const rotatedStringEscaped = Utils.escapeWhitespace(rotatedString); + if (printAmount) { + const amountStr = "偏移量 = " + (" " + amount).slice(-2) + ": "; + result.push(amountStr + rotatedStringEscaped); + } else { + result.push(rotatedStringEscaped); + } + } + } + return result.join("\n"); + } +} + +export default ROT47BruteForce; diff --git a/plugins/srktoolbox/src/core/operations/ROT8000.mjs b/plugins/srktoolbox/src/core/operations/ROT8000.mjs new file mode 100644 index 00000000..2b482c61 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ROT8000.mjs @@ -0,0 +1,125 @@ +/** + * @author Daniel Temkin [http://danieltemkin.com] + * @author Thomas Leplus [https://www.leplus.org] + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; + +/** + * ROT8000 operation. + */ +class ROT8000 extends Operation { + + /** + * ROT8000 constructor + */ + constructor() { + super(); + this.name = "ROT8000"; + this.module = "Default"; + this.description = "简单的凯撒密码,把Unicode字符替换成之前或之后的第0x8000个字符。"; + this.infoURL = "https://rot8000.com/info"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + // Inspired from https://github.com/rottytooth/rot8000/blob/main/rot8000.js + // these come from the valid-code-point-transitions.json file generated from the c# proj + // this is done bc: 1) don't trust JS's understanging of surrogate pairs and 2) consistency with original rot8000 + const validCodePoints = { + "33": true, + "127": false, + "161": true, + "5760": false, + "5761": true, + "8192": false, + "8203": true, + "8232": false, + "8234": true, + "8239": false, + "8240": true, + "8287": false, + "8288": true, + "12288": false, + "12289": true, + "55296": false, + "57344": true + }; + const bmpSize = 0x10000; + const rotList = {}; // the mapping of char to rotated char + const hiddenBlocks = []; + let startBlock = 0; + for (const key in validCodePoints) { + if (Object.prototype.hasOwnProperty.call(validCodePoints, key)) { + if (validCodePoints[key] === true) + hiddenBlocks.push({ start: startBlock, end: parseInt(key, 10) - 1 }); + else + startBlock = parseInt(key, 10); + } + } + const validIntList = []; // list of all valid chars + let currValid = false; + for (let i = 0; i < bmpSize; i++) { + if (validCodePoints[i] !== undefined) { + currValid = validCodePoints[i]; + } + if (currValid) validIntList.push(i); + } + const rotateNum = Object.keys(validIntList).length / 2; + // go through every valid char and find its match + for (let i = 0; i < validIntList.length; i++) { + rotList[String.fromCharCode(validIntList[i])] = + String.fromCharCode(validIntList[(i + rotateNum) % (rotateNum * 2)]); + } + let output = ""; + for (let count = 0; count < input.length; count++) { + // if it is not in the mappings list, just add it directly (no rotation) + if (rotList[input[count]] === undefined) { + output += input[count]; + continue; + } + // otherwise, rotate it and add it to the string + output += rotList[input[count]]; + } + return output; + } + + /** + * Highlight ROT8000 + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight ROT8000 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 ROT8000; diff --git a/plugins/srktoolbox/src/core/operations/RSADecrypt.mjs b/plugins/srktoolbox/src/core/operations/RSADecrypt.mjs new file mode 100644 index 00000000..254520ff --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/RSADecrypt.mjs @@ -0,0 +1,88 @@ +/** + * @author Matt C [me@mitt.dev] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import forge from "node-forge"; +import { MD_ALGORITHMS } from "../lib/RSA.mjs"; + +/** + * RSA Decrypt operation + */ +class RSADecrypt extends Operation { + + /** + * RSADecrypt constructor + */ + constructor() { + super(); + + this.name = "RSA解密"; + this.module = "Ciphers"; + this.description = "使用PEM格式的RSA私钥解密消息。"; + this.infoURL = "https://wikipedia.org/wiki/RSA_(cryptosystem)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "RSA私钥 (PEM)", + type: "text", + value: "-----BEGIN RSA PRIVATE KEY-----" + }, + { + name: "密码", + type: "text", + value: "" + }, + { + name: "加密方式", + type: "argSelector", + value: [ + { + name: "RSA-OAEP", + on: [3] + }, + { + name: "RSAES-PKCS1-V1_5", + off: [3] + }, + { + name: "RAW", + off: [3] + }] + }, + { + name: "消息摘要算法", + type: "option", + value: Object.keys(MD_ALGORITHMS) + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [pemKey, password, scheme, md] = args; + if (pemKey.replace("-----BEGIN RSA PRIVATE KEY-----", "").length === 0) { + throw new OperationError("请输入私钥。"); + } + try { + const privKey = forge.pki.decryptRsaPrivateKey(pemKey, password); + const dMsg = privKey.decrypt(input, scheme, {md: MD_ALGORITHMS[md].create()}); + return forge.util.decodeUtf8(dMsg); + } catch (err) { + throw new OperationError(err); + } + } + +} + +export default RSADecrypt; diff --git a/plugins/srktoolbox/src/core/operations/RSAEncrypt.mjs b/plugins/srktoolbox/src/core/operations/RSAEncrypt.mjs new file mode 100644 index 00000000..efd1bd82 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/RSAEncrypt.mjs @@ -0,0 +1,91 @@ +/** + * @author Matt C [me@mitt.dev] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import forge from "node-forge"; +import { MD_ALGORITHMS } from "../lib/RSA.mjs"; + +/** + * RSA Encrypt operation + */ +class RSAEncrypt extends Operation { + + /** + * RSAEncrypt constructor + */ + constructor() { + super(); + + this.name = "RSA加密"; + this.module = "Ciphers"; + this.description = "使用PEM格式RSA公钥加密消息。"; + this.infoURL = "https://wikipedia.org/wiki/RSA_(cryptosystem)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "RSA公钥 (PEM)", + type: "text", + value: "-----BEGIN RSA PUBLIC KEY-----" + }, + { + name: "加密方式", + type: "argSelector", + value: [ + { + name: "RSA-OAEP", + on: [2] + }, + { + name: "RSAES-PKCS1-V1_5", + off: [2] + }, + { + name: "RAW", + off: [2] + }] + }, + { + name: "消息摘要算法", + type: "option", + value: Object.keys(MD_ALGORITHMS) + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [pemKey, scheme, md] = args; + + if (pemKey.replace("-----BEGIN RSA PUBLIC KEY-----", "").length === 0) { + throw new OperationError("请输入公钥。"); + } + try { + // Load public key + const pubKey = forge.pki.publicKeyFromPem(pemKey); + // https://github.com/digitalbazaar/forge/issues/465#issuecomment-271097600 + const plaintextBytes = forge.util.encodeUtf8(input); + // Encrypt message + const eMsg = pubKey.encrypt(plaintextBytes, scheme, {md: MD_ALGORITHMS[md].create()}); + return eMsg; + } catch (err) { + if (err.message === "RSAES-OAEP input message length is too long.") { + throw new OperationError(`RSAES-OAEP 输入消息长度 (${err.length}) 超过最大长度 (${err.maxLength})。`); + } + throw new OperationError(err); + } + } + +} + +export default RSAEncrypt; diff --git a/plugins/srktoolbox/src/core/operations/RSASign.mjs b/plugins/srktoolbox/src/core/operations/RSASign.mjs new file mode 100644 index 00000000..3d26da1a --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/RSASign.mjs @@ -0,0 +1,76 @@ +/** + * @author Matt C [me@mitt.dev] + * @author gchq77703 [] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import forge from "node-forge"; +import { MD_ALGORITHMS } from "../lib/RSA.mjs"; + +/** + * RSA Sign operation + */ +class RSASign extends Operation { + + /** + * RSASign constructor + */ + constructor() { + super(); + + this.name = "RSA签名"; + this.module = "Ciphers"; + this.description = "使用PEM编码的RSA密钥签名文本信息。"; + this.infoURL = "https://wikipedia.org/wiki/RSA_(cryptosystem)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "RSA私钥 (PEM)", + type: "text", + value: "-----BEGIN RSA PRIVATE KEY-----" + }, + { + name: "密码", + type: "text", + value: "" + }, + { + name: "消息摘要算法", + type: "option", + value: Object.keys(MD_ALGORITHMS) + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [key, password, mdAlgo] = args; + if (key.replace("-----BEGIN RSA PRIVATE KEY-----", "").length === 0) { + throw new OperationError("请输入私钥"); + } + try { + const privateKey = forge.pki.decryptRsaPrivateKey(key, password); + // Generate message hash + const md = MD_ALGORITHMS[mdAlgo].create(); + md.update(input, "raw"); + // Sign message hash + const sig = privateKey.sign(md); + return sig; + } catch (err) { + throw new OperationError(err); + } + } + +} + +export default RSASign; diff --git a/plugins/srktoolbox/src/core/operations/RSAVerify.mjs b/plugins/srktoolbox/src/core/operations/RSAVerify.mjs new file mode 100644 index 00000000..c0c6f429 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/RSAVerify.mjs @@ -0,0 +1,86 @@ +/** + * @author Matt C [me@mitt.dev] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import forge from "node-forge"; +import { MD_ALGORITHMS } from "../lib/RSA.mjs"; +import Utils from "../Utils.mjs"; + +/** + * RSA Verify operation + */ +class RSAVerify extends Operation { + + /** + * RSAVerify constructor + */ + constructor() { + super(); + + this.name = "RSA验证"; + this.module = "Ciphers"; + this.description = "使用PEM编码的RSA公钥和签名验证文本消息。"; + this.infoURL = "https://wikipedia.org/wiki/RSA_(cryptosystem)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "RSA公钥 (PEM)", + type: "text", + value: "-----BEGIN RSA PUBLIC KEY-----" + }, + { + name: "消息", + type: "text", + value: "" + }, + { + name: "消息格式", + type: "option", + value: ["原始字节", "十六进制", "Base64"] + }, + { + name: "消息摘要算法", + type: "option", + value: Object.keys(MD_ALGORITHMS) + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [pemKey, message, format, mdAlgo] = args; + if (pemKey.replace("-----BEGIN RSA PUBLIC KEY-----", "").length === 0) { + throw new OperationError("请输入公钥。"); + } + try { + // Load public key + const pubKey = forge.pki.publicKeyFromPem(pemKey); + // Generate message digest + const md = MD_ALGORITHMS[mdAlgo].create(); + const messageStr = Utils.convertToByteString(message, format); + md.update(messageStr, "raw"); + // Compare signed message digest and generated message digest + const result = pubKey.verify(md.digest().bytes(), input); + return result ? "验证成功" : "验证失败"; + } catch (err) { + if (err.message === "Encrypted message length is invalid.") { + throw new OperationError(`签名长度 (${err.length}) 与密钥长度 (${err.expected}) 的期待值不符。`); + } + throw new OperationError(err); + } + } + +} + +export default RSAVerify; diff --git a/plugins/srktoolbox/src/core/operations/Rabbit.mjs b/plugins/srktoolbox/src/core/operations/Rabbit.mjs new file mode 100644 index 00000000..dea0e7f4 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Rabbit.mjs @@ -0,0 +1,249 @@ +/** + * @author mikecat + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import { toHexFast } from "../lib/Hex.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Rabbit operation + */ +class Rabbit extends Operation { + + /** + * Rabbit constructor + */ + constructor() { + super(); + + this.name = "Rabbit"; + this.module = "Ciphers"; + this.description = "Rabbit算法是高速的流密码算法,于2003年发布并在RFC 4503中定义。

加密算法使用128位长度的key和可选的64位初始化向量(IV).

大端序:根据RFC4503和RFC3447的定义
小端序:和Crypto++兼容"; + this.infoURL = "https://wikipedia.org/wiki/Rabbit_(cipher)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["十六进制", "UTF8", "Latin1", "Base64"] + }, + { + "name": "IV", + "type": "toggleString", + "value": "", + "toggleValues": ["十六进制", "UTF8", "Latin1", "Base64"] + }, + { + "name": "端序", + "type": "option", + "value": ["大端序", "小端序"] + }, + { + "name": "输入", + "type": "option", + "value": ["原始", "十六进制"] + }, + { + "name": "输出", + "type": "option", + "value": ["原始", "十六进制"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = Utils.convertToByteArray(args[0].string, args[0].option), + iv = Utils.convertToByteArray(args[1].string, args[1].option), + endianness = args[2], + inputType = args[3], + outputType = args[4]; + + const littleEndian = endianness === "小端序"; + + if (key.length !== 16) { + throw new OperationError(`无效的key长度:${key.length} 字节(正确值:16)`); + } + if (iv.length !== 0 && iv.length !== 8) { + throw new OperationError(`无效的IV长度:${iv.length} 字节(正确值:0或8)`); + } + + // Inner State + const X = new Uint32Array(8), C = new Uint32Array(8); + let b = 0; + + // Counter System + const A = [ + 0x4d34d34d, 0xd34d34d3, 0x34d34d34, 0x4d34d34d, + 0xd34d34d3, 0x34d34d34, 0x4d34d34d, 0xd34d34d3 + ]; + const counterUpdate = function() { + for (let j = 0; j < 8; j++) { + const temp = C[j] + A[j] + b; + b = (temp / ((1 << 30) * 4)) >>> 0; + C[j] = temp; + } + }; + + // Next-State Function + const g = function(u, v) { + const uv = (u + v) >>> 0; + const upper = uv >>> 16, lower = uv & 0xffff; + const upperUpper = upper * upper; + const upperLower2 = 2 * upper * lower; + const lowerLower = lower * lower; + const mswTemp = upperUpper + ((upperLower2 / (1 << 16)) >>> 0); + const lswTemp = lowerLower + (upperLower2 & 0xffff) * (1 << 16); + const msw = mswTemp + ((lswTemp / ((1 << 30) * 4)) >>> 0); + const lsw = lswTemp >>> 0; + return lsw ^ msw; + }; + const leftRotate = function(value, width) { + return (value << width) | (value >>> (32 - width)); + }; + const nextStateHelper1 = function(v0, v1, v2) { + return v0 + leftRotate(v1, 16) + leftRotate(v2, 16); + }; + const nextStateHelper2 = function(v0, v1, v2) { + return v0 + leftRotate(v1, 8) + v2; + }; + const G = new Uint32Array(8); + const nextState = function() { + for (let j = 0; j < 8; j++) { + G[j] = g(X[j], C[j]); + } + X[0] = nextStateHelper1(G[0], G[7], G[6]); + X[1] = nextStateHelper2(G[1], G[0], G[7]); + X[2] = nextStateHelper1(G[2], G[1], G[0]); + X[3] = nextStateHelper2(G[3], G[2], G[1]); + X[4] = nextStateHelper1(G[4], G[3], G[2]); + X[5] = nextStateHelper2(G[5], G[4], G[3]); + X[6] = nextStateHelper1(G[6], G[5], G[4]); + X[7] = nextStateHelper2(G[7], G[6], G[5]); + }; + + // Key Setup Scheme + const K = new Uint16Array(8); + if (littleEndian) { + for (let i = 0; i < 8; i++) { + K[i] = (key[1 + 2 * i] << 8) | key[2 * i]; + } + } else { + for (let i = 0; i < 8; i++) { + K[i] = (key[14 - 2 * i] << 8) | key[15 - 2 * i]; + } + } + for (let j = 0; j < 8; j++) { + if (j % 2 === 0) { + X[j] = (K[(j + 1) % 8] << 16) | K[j]; + C[j] = (K[(j + 4) % 8] << 16) | K[(j + 5) % 8]; + } else { + X[j] = (K[(j + 5) % 8] << 16) | K[(j + 4) % 8]; + C[j] = (K[j] << 16) | K[(j + 1) % 8]; + } + } + for (let i = 0; i < 4; i++) { + counterUpdate(); + nextState(); + } + for (let j = 0; j < 8; j++) { + C[j] = C[j] ^ X[(j + 4) % 8]; + } + + // IV Setup Scheme + if (iv.length === 8) { + const getIVValue = function(a, b, c, d) { + if (littleEndian) { + return (iv[a] << 24) | (iv[b] << 16) | + (iv[c] << 8) | iv[d]; + } else { + return (iv[7 - a] << 24) | (iv[7 - b] << 16) | + (iv[7 - c] << 8) | iv[7 - d]; + } + }; + C[0] = C[0] ^ getIVValue(3, 2, 1, 0); + C[1] = C[1] ^ getIVValue(7, 6, 3, 2); + C[2] = C[2] ^ getIVValue(7, 6, 5, 4); + C[3] = C[3] ^ getIVValue(5, 4, 1, 0); + C[4] = C[4] ^ getIVValue(3, 2, 1, 0); + C[5] = C[5] ^ getIVValue(7, 6, 3, 2); + C[6] = C[6] ^ getIVValue(7, 6, 5, 4); + C[7] = C[7] ^ getIVValue(5, 4, 1, 0); + for (let i = 0; i < 4; i++) { + counterUpdate(); + nextState(); + } + } + + // Extraction Scheme + const S = new Uint8Array(16); + const extract = function() { + let pos = 0; + const addPart = function(value) { + S[pos++] = value >>> 8; + S[pos++] = value & 0xff; + }; + counterUpdate(); + nextState(); + addPart((X[6] >>> 16) ^ (X[1] & 0xffff)); + addPart((X[6] & 0xffff) ^ (X[3] >>> 16)); + addPart((X[4] >>> 16) ^ (X[7] & 0xffff)); + addPart((X[4] & 0xffff) ^ (X[1] >>> 16)); + addPart((X[2] >>> 16) ^ (X[5] & 0xffff)); + addPart((X[2] & 0xffff) ^ (X[7] >>> 16)); + addPart((X[0] >>> 16) ^ (X[3] & 0xffff)); + addPart((X[0] & 0xffff) ^ (X[5] >>> 16)); + if (littleEndian) { + for (let i = 0, j = S.length - 1; i < j;) { + const temp = S[i]; + S[i] = S[j]; + S[j] = temp; + i++; + j--; + } + } + }; + + const data = Utils.convertToByteString(input, inputType); + const result = new Uint8Array(data.length); + for (let i = 0; i <= data.length - 16; i += 16) { + extract(); + for (let j = 0; j < 16; j++) { + result[i + j] = data.charCodeAt(i + j) ^ S[j]; + } + } + if (data.length % 16 !== 0) { + const offset = data.length - data.length % 16; + const length = data.length - offset; + extract(); + if (littleEndian) { + for (let j = 0; j < length; j++) { + result[offset + j] = data.charCodeAt(offset + j) ^ S[j]; + } + } else { + for (let j = 0; j < length; j++) { + result[offset + j] = data.charCodeAt(offset + j) ^ S[16 - length + j]; + } + } + } + if (outputType === "十六进制") { + return toHexFast(result); + } + return Utils.byteArrayToChars(result); + } + +} + +export default Rabbit; diff --git a/plugins/srktoolbox/src/core/operations/RailFenceCipherDecode.mjs b/plugins/srktoolbox/src/core/operations/RailFenceCipherDecode.mjs new file mode 100644 index 00000000..5e308b34 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/RailFenceCipherDecode.mjs @@ -0,0 +1,83 @@ +/** + * @author Flavio Diez [flaviofdiez+cyberchef@gmail.com] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Rail Fence Cipher Decode operation + */ +class RailFenceCipherDecode extends Operation { + + /** + * RailFenceCipherDecode constructor + */ + constructor() { + super(); + + this.name = "篱笆密码解密"; + this.module = "Ciphers"; + this.description = "根据提供的篱笆数量和偏移量进行篱笆密码(Rail fence Cipher)解密。"; + this.infoURL = "https://wikipedia.org/wiki/Rail_fence_cipher"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "篱笆数量", + type: "number", + value: 2 + }, + { + name: "偏移量", + type: "number", + value: 0 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [key, offset] = args; + + const cipher = input; + + if (key < 2) { + throw new OperationError("篱笆数量不少于2"); + } else if (key > cipher.length) { + throw new OperationError("篱笆数量不能超过明文长度"); + } + + if (offset < 0) { + throw new OperationError("偏移量必须为正整数"); + } + + const cycle = (key - 1) * 2; + const plaintext = new Array(cipher.length); + + let j = 0; + let x, y; + + for (y = 0; y < key; y++) { + for (x = 0; x < cipher.length; x++) { + if ((y + x + offset) % cycle === 0 || (y - x - offset) % cycle === 0) { + plaintext[x] = cipher[j++]; + } + } + } + + return plaintext.join(""); + } + +} + + +export default RailFenceCipherDecode; diff --git a/plugins/srktoolbox/src/core/operations/RailFenceCipherEncode.mjs b/plugins/srktoolbox/src/core/operations/RailFenceCipherEncode.mjs new file mode 100644 index 00000000..34318065 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/RailFenceCipherEncode.mjs @@ -0,0 +1,76 @@ +/** + * @author Flavio Diez [flaviofdiez+cyberchef@gmail.com] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Rail Fence Cipher Encode operation + */ +class RailFenceCipherEncode extends Operation { + + /** + * RailFenceCipherEncode constructor + */ + constructor() { + super(); + + this.name = "篱笆密码加密"; + this.module = "Ciphers"; + this.description = "根据提供的篱笆数量和偏移量进行篱笆密码(Rail fence Cipher)加密。"; + this.infoURL = "https://wikipedia.org/wiki/Rail_fence_cipher"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "篱笆数量", + type: "number", + value: 2 + }, + { + name: "偏移量", + type: "number", + value: 0 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [key, offset] = args; + + const plaintext = input; + if (key < 2) { + throw new OperationError("篱笆数量不能少于2"); + } else if (key > plaintext.length) { + throw new OperationError("篱笆数量不能超过明文长度"); + } + + if (offset < 0) { + throw new OperationError("偏移量必须为正整数"); + } + + const cycle = (key - 1) * 2; + const rows = new Array(key).fill(""); + + for (let pos = 0; pos < plaintext.length; pos++) { + const rowIdx = key - 1 - Math.abs(cycle / 2 - (pos + offset) % cycle); + + rows[rowIdx] += plaintext[pos]; + } + + return rows.join(""); + } + +} + +export default RailFenceCipherEncode; diff --git a/plugins/srktoolbox/src/core/operations/RandomizeColourPalette.mjs b/plugins/srktoolbox/src/core/operations/RandomizeColourPalette.mjs new file mode 100644 index 00000000..a3d06872 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/RandomizeColourPalette.mjs @@ -0,0 +1,85 @@ +/** + * @author Ge0rg3 [georgeomnet+cyberchef@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { isImage } from "../lib/FileType.mjs"; +import { runHash } from "../lib/Hash.mjs"; +import { toBase64 } from "../lib/Base64.mjs"; +import { Jimp } from "jimp"; + +/** + * Randomize Colour Palette operation + */ +class RandomizeColourPalette extends Operation { + /** + * RandomizeColourPalette constructor + */ + constructor() { + super(); + + this.name = "色板随机化"; + this.module = "Image"; + this.description = + "将给定图片中的每种颜色单独进行随机化处理。通常用于显示使用非常相近颜色隐写的文字或符号。"; + this.infoURL = "https://wikipedia.org/wiki/Indexed_color"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.presentType = "html"; + this.args = [ + { + name: "随机数种子", + type: "string", + value: "", + }, + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + async run(input, args) { + if (!isImage(input)) + throw new OperationError("请输入合法的图像文件。"); + + const seed = args[0] || Math.random().toString().substr(2), + parsedImage = await Jimp.read(input), + width = parsedImage.bitmap.width, + height = parsedImage.bitmap.height; + + let rgbString, rgbHash, rgbHex; + + parsedImage.scan(0, 0, width, height, function (x, y, idx) { + rgbString = this.bitmap.data.slice(idx, idx + 3).join("."); + rgbHash = runHash("md5", Utils.strToArrayBuffer(seed + rgbString)); + rgbHex = rgbHash.substr(0, 6) + "ff"; + parsedImage.setPixelColor(parseInt(rgbHex, 16), x, y); + }); + + const imageBuffer = await parsedImage.getBuffer(parsedImage.mime); + + return new Uint8Array(imageBuffer).buffer; + } + + /** + * Displays the extracted data as an image for web apps. + * @param {ArrayBuffer} data + * @returns {html} + */ + present(data) { + if (!data.byteLength) return ""; + const type = isImage(data); + + return ``; + } +} + +export default RandomizeColourPalette; diff --git a/plugins/srktoolbox/src/core/operations/RawDeflate.mjs b/plugins/srktoolbox/src/core/operations/RawDeflate.mjs new file mode 100644 index 00000000..84865ae6 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/RawDeflate.mjs @@ -0,0 +1,61 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import {COMPRESSION_TYPE} from "../lib/Zlib.mjs"; +import rawdeflate from "zlibjs/bin/rawdeflate.min.js"; + +const Zlib = rawdeflate.Zlib; + +const RAW_COMPRESSION_TYPE_LOOKUP = { + "静态霍夫曼压缩": Zlib.RawDeflate.CompressionType.FIXED, + "动态霍夫曼压缩": Zlib.RawDeflate.CompressionType.DYNAMIC, + "无压缩 (Store)": Zlib.RawDeflate.CompressionType.NONE, +}; + +/** + * Raw Deflate operation + */ +class RawDeflate extends Operation { + + /** + * RawDeflate constructor + */ + constructor() { + super(); + + this.name = "Raw Deflate"; + this.module = "Compression"; + this.description = "使用不带标头的deflate算法压缩数据。"; + this.infoURL = "https://wikipedia.org/wiki/DEFLATE"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = [ + { + name: "压缩类型", + type: "option", + value: COMPRESSION_TYPE + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + const deflate = new Zlib.RawDeflate(new Uint8Array(input), { + compressionType: RAW_COMPRESSION_TYPE_LOOKUP[args[0]] + }); + return new Uint8Array(deflate.compress()).buffer; + } + +} + +export default RawDeflate; diff --git a/plugins/srktoolbox/src/core/operations/RawInflate.mjs b/plugins/srktoolbox/src/core/operations/RawInflate.mjs new file mode 100644 index 00000000..05e4847d --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/RawInflate.mjs @@ -0,0 +1,92 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import {INFLATE_BUFFER_TYPE} from "../lib/Zlib.mjs"; +import rawinflate from "zlibjs/bin/rawinflate.min.js"; + +const Zlib = rawinflate.Zlib; + +const RAW_BUFFER_TYPE_LOOKUP = { + "自适应": Zlib.RawInflate.BufferType.ADAPTIVE, + "块": Zlib.RawInflate.BufferType.BLOCK, +}; + +/** + * Raw Inflate operation + */ +class RawInflate extends Operation { + + /** + * RawInflate constructor + */ + constructor() { + super(); + + this.name = "Raw Inflate"; + this.module = "Compression"; + this.description = "解压缩使用无标头deflate算法压缩的数据。"; + this.infoURL = "https://wikipedia.org/wiki/DEFLATE"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = [ + { + name: "起始索引", + type: "number", + value: 0 + }, + { + name: "起始输出缓冲区尺寸", + type: "number", + value: 0 + }, + { + name: "缓冲区扩展类型", + type: "option", + value: INFLATE_BUFFER_TYPE + }, + { + name: "解压缩后重置缓冲区尺寸", + type: "boolean", + value: false + }, + { + name: "验证结果", + type: "boolean", + value: false + } + ]; + this.checks = [ + { + entropyRange: [7.5, 8], + args: [0, 0, INFLATE_BUFFER_TYPE, false, false] + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + const inflate = new Zlib.RawInflate(new Uint8Array(input), { + index: args[0], + bufferSize: args[1], + bufferType: RAW_BUFFER_TYPE_LOOKUP[args[2]], + resize: args[3], + verify: args[4] + }), + result = new Uint8Array(inflate.decompress()); + + return result.buffer; + } + +} + +export default RawInflate; diff --git a/plugins/srktoolbox/src/core/operations/Register.mjs b/plugins/srktoolbox/src/core/operations/Register.mjs new file mode 100644 index 00000000..4903b190 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Register.mjs @@ -0,0 +1,122 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Dish from "../Dish.mjs"; +import XRegExp from "xregexp"; +import { isWorkerEnvironment } from "../Utils.mjs"; + +/** + * Register operation + */ +class Register extends Operation { + + /** + * Register constructor + */ + constructor() { + super(); + + this.name = "Register"; + this.flowControl = true; + this.module = "Regex"; + this.description = "从输入中提取数据并存储在“寄存器(Register)”中,用于作为变量传递到下面的操作参数中。使用正则表达式捕获组来选择需要提取的数据。

在其它操作的参数中使用$Rn(n是寄存器编号,从0开始)来访问存储的值。

例如:
输入:Test
提取规则:(.*)
参数:$R0就代表Test

寄存器语法可以用反斜线来转义。例如:\\$R0代表字符$R0而不是Test。"; + this.infoURL = "https://wikipedia.org/wiki/Regular_expression#Syntax"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "提取规则", + "type": "binaryString", + "value": "([\\s\\S]*)" + }, + { + "name": "大小写不敏感(i)", + "type": "boolean", + "value": true + }, + { + "name": "多行匹配(m)", + "type": "boolean", + "value": false + }, + { + "name": "点允许匹配换行符(s)", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @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. + */ + async run(state) { + const ings = state.opList[state.progress].ingValues; + const [extractorStr, i, m, s] = ings; + + let modifiers = ""; + if (i) modifiers += "i"; + if (m) modifiers += "m"; + if (s) modifiers += "s"; + + const extractor = new XRegExp(extractorStr, modifiers), + input = await state.dish.get(Dish.STRING), + registers = input.match(extractor); + + if (!registers) return state; + + if (isWorkerEnvironment()) { + self.setRegisters(state.forkOffset + state.progress, state.numRegisters, registers.slice(1)); + } + + /** + * Replaces references to registers (e.g. $R0) with the contents of those registers. + * + * @param {string} str + * @returns {string} + */ + function replaceRegister(str) { + // Replace references to registers ($Rn) with contents of registers + return str.replace(/(\\*)\$R(\d{1,2})/g, (match, slashes, regNum) => { + const index = parseInt(regNum, 10) + 1; + if (index <= state.numRegisters || index >= state.numRegisters + registers.length) + return match; + if (slashes.length % 2 !== 0) return match.slice(1); // Remove escape + return slashes + registers[index - state.numRegisters]; + }); + } + + // Step through all subsequent ops and replace registers in args with extracted content + for (let i = state.progress + 1; i < state.opList.length; i++) { + if (state.opList[i].disabled) continue; + + let args = state.opList[i].ingValues; + args = args.map(arg => { + if (typeof arg !== "string" && typeof arg !== "object") return arg; + + if (typeof arg === "object" && Object.prototype.hasOwnProperty.call(arg, "string")) { + arg.string = replaceRegister(arg.string); + return arg; + } + return replaceRegister(arg); + }); + state.opList[i].ingValues = args; + } + + state.numRegisters += registers.length - 1; + return state; + } + +} + +export default Register; diff --git a/plugins/srktoolbox/src/core/operations/RegularExpression.mjs b/plugins/srktoolbox/src/core/operations/RegularExpression.mjs new file mode 100644 index 00000000..613f4122 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/RegularExpression.mjs @@ -0,0 +1,274 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import XRegExp from "xregexp"; +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Regular expression operation + */ +class RegularExpression extends Operation { + + /** + * RegularExpression constructor + */ + constructor() { + super(); + + this.name = "正则表达式"; + this.module = "Regex"; + this.description = "用自定义的正则表达式(regex)在输入内容中执行查找。也可以在内置的正则表达式模板中选择一个。

支持扩展正则语法,如“点(.)匹配所有字符”、命名匹配组,完整Unicode支持(包括 \\p{} 类别以及Astral平面字符)和递归匹配。"; + this.infoURL = "https://wikipedia.org/wiki/Regular_expression"; + this.inputType = "string"; + this.outputType = "html"; + this.args = [ + { + "name": "内置正则", + "type": "populateOption", + "value": [ + { + name: "自定义", + value: "" + }, + { + name: "IPv4地址", + value: "(?:(?:\\d|[01]?\\d\\d|2[0-4]\\d|25[0-5])\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d|\\d)(?:\\/\\d{1,2})?" + }, + { + name: "IPv6地址", + value: "((?=.*::)(?!.*::.+::)(::)?([\\dA-Fa-f]{1,4}:(:|\\b)|){5}|([\\dA-Fa-f]{1,4}:){6})((([\\dA-Fa-f]{1,4}((?!\\3)::|:\\b|(?![\\dA-Fa-f])))|(?!\\2\\3)){2}|(((2[0-4]|1\\d|[1-9])?\\d|25[0-5])\\.?\\b){4})" + }, + { + name: "Email地址", + value: "(?:[\\u00A0-\\uD7FF\\uE000-\\uFFFFa-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\u00A0-\\uD7FF\\uE000-\\uFFFFa-z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[\\u00A0-\\uD7FF\\uE000-\\uFFFFa-z0-9](?:[\\u00A0-\\uD7FF\\uE000-\\uFFFF-a-z0-9-]*[\\u00A0-\\uD7FF\\uE000-\\uFFFFa-z0-9])?\\.)+[\\u00A0-\\uD7FF\\uE000-\\uFFFFa-z0-9](?:[\\u00A0-\\uD7FF\\uE000-\\uFFFFa-z0-9-]*[\\u00A0-\\uD7FF\\uE000-\\uFFFFa-z0-9])?|\\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\\.){3}\\])" + }, + { + name: "URL", + value: "([A-Za-z]+://)([-\\w]+(?:\\.\\w[-\\w]*)+)(:\\d+)?(/[^.!,?\"<>\\[\\]{}\\s\\x7F-\\xFF]*(?:[.!,?]+[^.!,?\"<>\\[\\]{}\\s\\x7F-\\xFF]+)*)?" + }, + { + name: "域名", + value: "\\b((?=[a-z0-9-]{1,63}\\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,63}\\b" + }, + { + name: "Windows文件路径", + value: "([A-Za-z]):\\\\((?:[A-Za-z\\d][A-Za-z\\d\\- \\x27_\\(\\)~]{0,61}\\\\?)*[A-Za-z\\d][A-Za-z\\d\\- \\x27_\\(\\)]{0,61})(\\.[A-Za-z\\d]{1,6})?" + }, + { + name: "UNIX文件路径", + value: "(?:/[A-Za-z\\d.][A-Za-z\\d\\-.]{0,61})+" + }, + { + name: "MAC地址", + value: "[A-Fa-f\\d]{2}(?:[:-][A-Fa-f\\d]{2}){5}" + }, + { + name: "UUID", + value: "[0-9a-fA-F]{8}\\b-[0-9a-fA-F]{4}\\b-[0-9a-fA-F]{4}\\b-[0-9a-fA-F]{4}\\b-[0-9a-fA-F]{12}" + }, + { + name: "日期 (yyyy-mm-dd)", + value: "((?:19|20)\\d\\d)[- /.](0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])" + }, + { + name: "日期 (dd/mm/yyyy)", + value: "(0[1-9]|[12][0-9]|3[01])[- /.](0[1-9]|1[012])[- /.]((?:19|20)\\d\\d)" + }, + { + name: "日期 (mm/dd/yyyy)", + value: "(0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])[- /.]((?:19|20)\\d\\d)" + }, + { + name: "字符串", + value: "[A-Za-z\\d/\\-:.,_$%\\x27\"()<>= !\\[\\]{}@]{4,}" + }, + ], + "target": 1 + }, + { + "name": "正则", + "type": "text", + "value": "" + }, + { + "name": "忽略大小写", + "type": "boolean", + "value": true + }, + { + "name": "^ 和 $ 按行匹配", + "type": "boolean", + "value": true + }, + { + "name": "点(.)匹配所有字符", + "type": "boolean", + "value": false + }, + { + "name": "Unicode支持", + "type": "boolean", + "value": false + }, + { + "name": "Astral支持", + "type": "boolean", + "value": false + }, + { + "name": "显示匹配总数", + "type": "boolean", + "value": false + }, + { + "name": "输出格式", + "type": "option", + "value": ["高亮匹配", "列出匹配", "列出匹配组", "列出匹配和对应匹配组"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + const [, + userRegex, + i, m, s, u, a, + displayTotal, + outputFormat + ] = args; + let modifiers = "g"; + + if (i) modifiers += "i"; + if (m) modifiers += "m"; + if (s) modifiers += "s"; + if (u) modifiers += "u"; + if (a) modifiers += "A"; + + if (userRegex && userRegex !== "^" && userRegex !== "$") { + try { + const regex = new XRegExp(userRegex, modifiers); + + switch (outputFormat) { + case "高亮匹配": + return regexHighlight(input, regex, displayTotal); + case "列出匹配": + return Utils.escapeHtml(regexList(input, regex, displayTotal, true, false)); + case "列出匹配组": + return Utils.escapeHtml(regexList(input, regex, displayTotal, false, true)); + case "列出匹配和对应匹配组": + return Utils.escapeHtml(regexList(input, regex, displayTotal, true, true)); + default: + throw new OperationError("错误:无效输出格式"); + } + } catch (err) { + throw new OperationError("无效的正则表达式: " + err.message); + } + } else { + return Utils.escapeHtml(input); + } + } + +} + +/** + * Creates a string listing the matches within a string. + * + * @param {string} input + * @param {RegExp} regex + * @param {boolean} displayTotal + * @param {boolean} matches - Display full match + * @param {boolean} captureGroups - Display each of the capture groups separately + * @returns {string} + */ +function regexList(input, regex, displayTotal, matches, captureGroups) { + let output = "", + total = 0, + match; + + while ((match = regex.exec(input))) { + // Moves pointer when an empty string is matched (prevents infinite loop) + if (match.index === regex.lastIndex) { + regex.lastIndex++; + } + + total++; + if (matches) { + output += match[0] + "\n"; + } + if (captureGroups) { + for (let i = 1; i < match.length; i++) { + if (matches) { + output += " 组 " + i + ": "; + } + output += match[i] + "\n"; + } + } + } + + if (displayTotal) + output = "总计查找到: " + total + "\n\n" + output; + + return output.slice(0, -1); +} + +/** + * Adds HTML highlights to matches within a string. + * + * @private + * @param {string} input + * @param {RegExp} regex + * @param {boolean} displayTotal + * @returns {string} + */ +function regexHighlight(input, regex, displayTotal) { + let output = "", + title = "", + hl = 1, + total = 0; + const captureGroups = []; + + output = input.replace(regex, (match, ...args) => { + args.pop(); // Throw away full string + const offset = args.pop(), + groups = args; + + title = `偏移: ${offset}\n`; + if (groups.length) { + title += "匹配组:\n"; + for (let i = 0; i < groups.length; i++) { + title += `\t${i+1}: ${Utils.escapeHtml(groups[i] || "")}\n`; + } + } + + // Switch highlight + hl = hl === 1 ? 2 : 1; + + // Store highlighted match and replace with a placeholder + captureGroups.push(`${Utils.escapeHtml(match)}`); + return `[cc_capture_group_${total++}]`; + }); + + // Safely escape all remaining text, then replace placeholders + output = Utils.escapeHtml(output); + output = output.replace(/\[cc_capture_group_(\d+)\]/g, (_, i) => { + return captureGroups[i]; + }); + + if (displayTotal) + output = "总计查找到: " + total + "\n\n" + output; + + return output; +} + +export default RegularExpression; diff --git a/plugins/srktoolbox/src/core/operations/RemoveDiacritics.mjs b/plugins/srktoolbox/src/core/operations/RemoveDiacritics.mjs new file mode 100644 index 00000000..972c183e --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/RemoveDiacritics.mjs @@ -0,0 +1,43 @@ +/** + * @author Klaxon [klaxon@veyr.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; + +/** + * Remove Diacritics operation + */ +class RemoveDiacritics extends Operation { + + /** + * RemoveDiacritics constructor + */ + constructor() { + super(); + + this.name = "移除变音符号"; + this.module = "Default"; + this.description = "将带有变音符号的字符转换为对应不带变音符号的拉丁字母。带有变音符号的字符原理上是使用了Unicode结合字符,所以相同原理的下划线和删除线也会一并被移除。"; + this.infoURL = "https://wikipedia.org/wiki/Diacritic"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + // reference: https://stackoverflow.com/questions/990904/remove-accents-diacritics-in-a-string-in-javascript/37511463 + return input.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); + } + +} + +export default RemoveDiacritics; diff --git a/plugins/srktoolbox/src/core/operations/RemoveEXIF.mjs b/plugins/srktoolbox/src/core/operations/RemoveEXIF.mjs new file mode 100644 index 00000000..3fa2b90e --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/RemoveEXIF.mjs @@ -0,0 +1,58 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import { removeEXIF } from "../vendor/remove-exif.mjs"; +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Remove EXIF operation + */ +class RemoveEXIF extends Operation { + + /** + * RemoveEXIF constructor + */ + constructor() { + super(); + + this.name = "移除EXIF"; + this.module = "Image"; + this.description = [ + "从JPEG图片移除EXIF数据。", + "

", + "照片的EXIF数据通常包括图像本身以及拍摄设备的信息。", + ].join("\n"); + this.infoURL = "https://wikipedia.org/wiki/Exif"; + this.inputType = "ArrayBuffer"; + this.outputType = "byteArray"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + input = new Uint8Array(input); + // Do nothing if input is empty + if (input.length === 0) return input; + + try { + return removeEXIF(input); + } catch (err) { + // Simply return input if no EXIF data is found + if (err === "Exif not found.") return input; + throw new OperationError(`无法从图像中移除EXIF数据: ${err}`); + } + } + +} + +export default RemoveEXIF; diff --git a/plugins/srktoolbox/src/core/operations/RemoveLineNumbers.mjs b/plugins/srktoolbox/src/core/operations/RemoveLineNumbers.mjs new file mode 100644 index 00000000..2ae8b2b6 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/RemoveLineNumbers.mjs @@ -0,0 +1,41 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; + +/** + * Remove line numbers operation + */ +class RemoveLineNumbers extends Operation { + + /** + * RemoveLineNumbers constructor + */ + constructor() { + super(); + + this.name = "移除行号"; + this.module = "Default"; + this.description = "在输出内容中移除行号(仅限很容易被检测到的格式)。"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return input.replace(/^[ \t]{0,5}\d+[\s:|\-,.)\]]/gm, ""); + } + +} + +export default RemoveLineNumbers; diff --git a/plugins/srktoolbox/src/core/operations/RemoveNullBytes.mjs b/plugins/srktoolbox/src/core/operations/RemoveNullBytes.mjs new file mode 100644 index 00000000..04656fa9 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/RemoveNullBytes.mjs @@ -0,0 +1,46 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; + +/** + * Remove null bytes operation + */ +class RemoveNullBytes extends Operation { + + /** + * RemoveNullBytes constructor + */ + constructor() { + super(); + + this.name = "移除空字节"; + this.module = "Default"; + this.description = "从输入内容中移除所有的空字节(0x00)。"; + this.inputType = "ArrayBuffer"; + this.outputType = "byteArray"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + input = new Uint8Array(input); + const output = []; + for (let i = 0; i < input.length; i++) { + if (input[i] !== 0) output.push(input[i]); + } + return output; + } + +} + +export default RemoveNullBytes; diff --git a/plugins/srktoolbox/src/core/operations/RemoveWhitespace.mjs b/plugins/srktoolbox/src/core/operations/RemoveWhitespace.mjs new file mode 100644 index 00000000..9113f2e6 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/RemoveWhitespace.mjs @@ -0,0 +1,88 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; + +/** + * Remove whitespace operation + */ +class RemoveWhitespace extends Operation { + + /** + * RemoveWhitespace constructor + */ + constructor() { + super(); + + this.name = "移除空白字符"; + this.module = "Default"; + this.description = "从输入文本中移除空格、回车(CR)、换行(LF)、制表符(Tab)和换页(FF)。

此操作也支持移除英文句点(.),因为有时句点用于表示ASCII中无法显示的字符。"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "空格", + "type": "boolean", + "value": true + }, + { + "name": "回车(CR,\\r)", + "type": "boolean", + "value": true + }, + { + "name": "换行(LF,\\n)", + "type": "boolean", + "value": true + }, + { + "name": "制表符(Tab,\\t)", + "type": "boolean", + "value": true + }, + { + "name": "换页符(FF,\\f)", + "type": "boolean", + "value": true + }, + { + "name": "英文句点(.)", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [ + removeSpaces, + removeCarriageReturns, + removeLineFeeds, + removeTabs, + removeFormFeeds, + removeFullStops + ] = args; + let data = input; + + if (removeSpaces) data = data.replace(/ /g, ""); + if (removeCarriageReturns) data = data.replace(/\r/g, ""); + if (removeLineFeeds) data = data.replace(/\n/g, ""); + if (removeTabs) data = data.replace(/\t/g, ""); + if (removeFormFeeds) data = data.replace(/\f/g, ""); + if (removeFullStops) data = data.replace(/\./g, ""); + return data; + } + +} + +export default RemoveWhitespace; diff --git a/plugins/srktoolbox/src/core/operations/RenderImage.mjs b/plugins/srktoolbox/src/core/operations/RenderImage.mjs new file mode 100644 index 00000000..59113711 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/RenderImage.mjs @@ -0,0 +1,114 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import { fromBase64, toBase64 } from "../lib/Base64.mjs"; +import { fromHex } from "../lib/Hex.mjs"; +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import {isImage} from "../lib/FileType.mjs"; + +/** + * Render Image operation + */ +class RenderImage extends Operation { + + /** + * RenderImage constructor + */ + constructor() { + super(); + + this.name = "渲染图像"; + this.module = "Image"; + this.description = "将输入内容显示成图像。支持以下格式:

  • jpg/jpeg
  • png
  • gif
  • webp
  • bmp
  • ico
"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.presentType = "html"; + this.args = [ + { + "name": "输入格式", + "type": "option", + "value": ["原始", "Base64", "十六进制"] + } + ]; + this.checks = [ + { + pattern: "^(?:\\xff\\xd8\\xff|\\x89\\x50\\x4e\\x47|\\x47\\x49\\x46|.{8}\\x57\\x45\\x42\\x50|\\x42\\x4d)", + flags: "", + args: ["原始"], + useful: true, + output: { + mime: "image" + } + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + const inputFormat = args[0]; + + if (!input.length) return []; + + // Convert input to raw bytes + switch (inputFormat) { + case "十六进制": + input = fromHex(input); + break; + case "Base64": + // Don't trust the Base64 entered by the user. + // Unwrap it first, then re-encode later. + input = fromBase64(input, undefined, "byteArray"); + break; + case "原始": + default: + input = Utils.strToByteArray(input); + break; + } + + // Determine file type + if (!isImage(input)) { + throw new OperationError("无效的文件类型"); + } + + return input; + } + + /** + * Displays the image using HTML for web apps. + * + * @param {byteArray} data + * @returns {html} + */ + async present(data) { + if (!data.length) return ""; + + let dataURI = "data:"; + + // Determine file type + const mime = isImage(data); + if (mime) { + dataURI += mime + ";"; + } else { + throw new OperationError("无效的文件类型"); + } + + // Add image data to URI + dataURI += "base64," + toBase64(data); + + return ""; + } + +} + +export default RenderImage; diff --git a/plugins/srktoolbox/src/core/operations/RenderMarkdown.mjs b/plugins/srktoolbox/src/core/operations/RenderMarkdown.mjs new file mode 100644 index 00000000..d1b3a5f9 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/RenderMarkdown.mjs @@ -0,0 +1,71 @@ +/** + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import MarkdownIt from "markdown-it"; +import hljs from "highlight.js"; + +/** + * Render Markdown operation + */ +class RenderMarkdown extends Operation { + + /** + * RenderMarkdown constructor + */ + constructor() { + super(); + + this.name = "渲染Markdown"; + this.module = "Code"; + this.description = "将输入的Markdown渲染为HTML。HTML不会直接被渲染,防止XSS攻击。"; + this.infoURL = "https://wikipedia.org/wiki/Markdown"; + this.inputType = "string"; + this.outputType = "html"; + this.args = [ + { + name: "自动将URL转换为链接", + type: "boolean", + value: false + }, + { + name: "开启语法高亮", + type: "boolean", + value: true + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + const [convertLinks, enableHighlighting] = args, + md = new MarkdownIt({ + linkify: convertLinks, + html: false, // Explicitly disable HTML rendering + highlight: function(str, lang) { + if (lang && hljs.getLanguage(lang) && enableHighlighting) { + try { + return hljs.highlight(lang, str).value; + } catch (__) {} + } + + return ""; + } + }), + rendered = md.render(input); + + return `
${rendered}
`; + } + +} + +export default RenderMarkdown; diff --git a/plugins/srktoolbox/src/core/operations/ResizeImage.mjs b/plugins/srktoolbox/src/core/operations/ResizeImage.mjs new file mode 100644 index 00000000..a562310d --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ResizeImage.mjs @@ -0,0 +1,154 @@ +/** + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { isImage } from "../lib/FileType.mjs"; +import { toBase64 } from "../lib/Base64.mjs"; +import { isWorkerEnvironment } from "../Utils.mjs"; +import { Jimp, JimpMime, ResizeStrategy } from "jimp"; + +/** + * Resize Image operation + */ +class ResizeImage extends Operation { + /** + * ResizeImage constructor + */ + constructor() { + super(); + + this.name = "图像尺寸修改"; + this.module = "Image"; + this.description = + "将图像尺寸变更为给定的高和宽。"; + this.infoURL = "https://wikipedia.org/wiki/Image_scaling"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.presentType = "html"; + this.args = [ + { + name: "宽度", + type: "number", + value: 100, + min: 1, + }, + { + name: "高度", + type: "number", + value: 100, + min: 1, + }, + { + name: "单位", + type: "option", + value: ["像素", "百分比"], + }, + { + name: "保持长宽比", + type: "boolean", + value: false, + }, + { + name: "缩放插值算法", + type: "option", + value: [ + "临近", + "双线性", + "双三次", + "Hermite", + "Bezier", + ], + defaultIndex: 1, + }, + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {byteArray} + */ + async run(input, args) { + let width = args[0], + height = args[1]; + const unit = args[2], + aspect = args[3], + resizeAlg = args[4]; + + const resizeMap = { + "临近": ResizeStrategy.NEAREST_NEIGHBOR, + 双线性: ResizeStrategy.BILINEAR, + 双三次: ResizeStrategy.BICUBIC, + Hermite: ResizeStrategy.HERMITE, + Bezier: ResizeStrategy.BEZIER, + }; + + if (!isImage(input)) { + throw new OperationError("无效的文件类型。"); + } + + let image; + try { + image = await Jimp.read(input); + } catch (err) { + throw new OperationError(`载入图片错误:(${err})`); + } + try { + if (unit === "百分比") { + width = image.width * (width / 100); + height = image.height * (height / 100); + } + + if (isWorkerEnvironment()) + self.sendStatusMessage("缩放图像……"); + if (aspect) { + image.scaleToFit({ + w: width, + h: height, + mode: resizeMap[resizeAlg], + }); + } else { + image.resize({ + w: width, + h: height, + mode: resizeMap[resizeAlg], + }); + } + + let imageBuffer; + if (image.mime === "image/gif") { + imageBuffer = await image.getBuffer(JimpMime.png); + } else { + imageBuffer = await image.getBuffer(image.mime); + } + return imageBuffer.buffer; + } catch (err) { + throw new OperationError(`缩放图像出错:(${err})`); + } + } + + /** + * Displays the resized image using HTML for web apps + * @param {ArrayBuffer} data + * @returns {html} + */ + present(data) { + if (!data.byteLength) return ""; + const dataArray = new Uint8Array(data); + + const type = isImage(dataArray); + if (!type) { + throw new OperationError("无效的文件类型"); + } + + return ``; + } +} + +export default ResizeImage; diff --git a/plugins/srktoolbox/src/core/operations/Return.mjs b/plugins/srktoolbox/src/core/operations/Return.mjs new file mode 100644 index 00000000..07c1e497 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Return.mjs @@ -0,0 +1,45 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; + +/** + * Return operation + */ +class Return extends Operation { + + /** + * Return constructor + */ + constructor() { + super(); + + this.name = "Return"; + this.flowControl = true; + this.module = "Default"; + this.description = "结束此流程的操作运行。"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @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. + */ + run(state) { + state.progress = state.opList.length; + return state; + } + +} + +export default Return; diff --git a/plugins/srktoolbox/src/core/operations/Reverse.mjs b/plugins/srktoolbox/src/core/operations/Reverse.mjs new file mode 100644 index 00000000..27e006b9 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Reverse.mjs @@ -0,0 +1,89 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +/** + * Reverse operation + */ +class Reverse extends Operation { + + /** + * Reverse constructor + */ + constructor() { + super(); + + this.name = "反转"; + this.module = "Default"; + this.description = "反转字符串。"; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = [ + { + "name": "粒度", + "type": "option", + "value": ["字节", "字符", "行"], + "defaultIndex": 1 + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + let i; + if (args[0] === "行") { + const lines = []; + let line = [], + result = []; + for (i = 0; i < input.length; i++) { + if (input[i] === 0x0a) { + lines.push(line); + line = []; + } else { + line.push(input[i]); + } + } + lines.push(line); + lines.reverse(); + for (i = 0; i < lines.length; i++) { + result = result.concat(lines[i]); + result.push(0x0a); + } + return result.slice(0, input.length); + } else if (args[0] === "字符") { + const inputString = Utils.byteArrayToUtf8(input); + let result = ""; + for (let i = inputString.length - 1; i >= 0; i--) { + const c = inputString.charCodeAt(i); + if (i > 0 && 0xdc00 <= c && c <= 0xdfff) { + const c2 = inputString.charCodeAt(i - 1); + if (0xd800 <= c2 && c2 <= 0xdbff) { + // surrogates + result += inputString.charAt(i - 1); + result += inputString.charAt(i); + i--; + continue; + } + } + result += inputString.charAt(i); + } + return Utils.strToUtf8ByteArray(result); + } else { + return input.reverse(); + } + } + +} + +export default Reverse; diff --git a/plugins/srktoolbox/src/core/operations/RisonDecode.mjs b/plugins/srktoolbox/src/core/operations/RisonDecode.mjs new file mode 100644 index 00000000..229c9231 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/RisonDecode.mjs @@ -0,0 +1,59 @@ +/** + * @author sg5506844 [sg5506844@gmail.com] + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import rison from "rison"; + +/** + * Rison Decode operation + */ +class RisonDecode extends Operation { + + /** + * RisonDecode constructor + */ + constructor() { + super(); + + this.name = "Rison解码"; + this.module = "Encodings"; + this.description = "Rison是一种紧凑的数据序列化格式,专门为能在URI中使用进行了优化。Rison在JSON的基础上进行了少量改进,使得序列化数据在URI编码后看起来十分直观。Rison表示的数据结构和JSON是一致的,因此数据可以在两种格式间无损互转。"; + this.infoURL = "https://github.com/Nanonid/rison"; + this.inputType = "string"; + this.outputType = "Object"; + this.args = [ + { + name: "解码选项", + type: "Option", + value: ["普通解码", "解码为对象(O-Rison)", "解码为数组(A-Rison)"] + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {Object} + */ + run(input, args) { + const [decodeOption] = args; + switch (decodeOption) { + case "普通解码": + return rison.decode(input); + case "解码为对象(O-Rison)": + return rison.decode_object(input); + case "解码为数组(A-Rison)": + return rison.decode_array(input); + default: + throw new OperationError("无效的解码选项"); + } + } +} + +export default RisonDecode; diff --git a/plugins/srktoolbox/src/core/operations/RisonEncode.mjs b/plugins/srktoolbox/src/core/operations/RisonEncode.mjs new file mode 100644 index 00000000..56b14491 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/RisonEncode.mjs @@ -0,0 +1,61 @@ +/** + * @author sg5506844 [sg5506844@gmail.com] + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import rison from "rison"; + +/** + * Rison Encode operation + */ +class RisonEncode extends Operation { + + /** + * RisonEncode constructor + */ + constructor() { + super(); + + this.name = "Rison编码"; + this.module = "Encodings"; + this.description = "Rison是一种紧凑的数据序列化格式,专门为能在URI中使用进行了优化。Rison在JSON的基础上进行了少量改进,使得序列化数据在URI编码后看起来十分直观。Rison表示的数据结构和JSON是一致的,因此数据可以在两种格式间无损互转。"; + this.infoURL = "https://github.com/Nanonid/rison"; + this.inputType = "Object"; + this.outputType = "string"; + this.args = [ + { + name: "编码选项", + type: "Option", + value: ["普通编码", "编码为对象(O-Rison)", "编码为数组(A-Rison)", "编码为URI"] + }, + ]; + } + + /** + * @param {Object} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [encodeOption] = args; + switch (encodeOption) { + case "普通编码": + return rison.encode(input); + case "编码为对象(O-Rison)": + return rison.encode_object(input); + case "编码为数组(A-Rison)": + return rison.encode_array(input); + case "编码为URI": + return rison.encode_uri(input); + default: + throw new OperationError("无效的编码选项"); + } + } +} + +export default RisonEncode; diff --git a/plugins/srktoolbox/src/core/operations/RoarCipherDecode.mjs b/plugins/srktoolbox/src/core/operations/RoarCipherDecode.mjs new file mode 100644 index 00000000..2ac50a75 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/RoarCipherDecode.mjs @@ -0,0 +1,87 @@ +/** + * @author Raka-loah [i@lotc.cc] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Roar Cipher Decode operation + */ +class RoarCipherDecode extends Operation { + + /** + * RoarCipherDecode constructor + */ + constructor() { + super(); + + this.name = "兽音译者解密"; + this.module = "Default"; + this.description = "把各种“嗷呜啊”解密为原始文本,支持自定义字典。"; + this.infoURL = ""; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "字典(4个字符)", + type: "editableOption", + value: [ + { + name: "默认", + value: "嗷呜啊~" + }, + { + name: "自定义", + value: "" + } + ] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [alphabet] = args; + + if (alphabet.length !== 4) { + throw new OperationError("错误:字典长度必须为4个字符!"); + } + + if (input.length <= 5) { + throw new OperationError("错误:密文长度必须大于5个字符!"); + } + + const alphaList = alphabet.split(""); + const roarChunks = input.substring(3, input.length - 1).match(/.{2}/g); + + const rawHex = roarChunks.reduce(function (rawStr, roarChunk, currentIndex) { + const roarValue1 = alphaList.indexOf(roarChunk[0]); + const roarValue2 = alphaList.indexOf(roarChunk[1]); + + if (roarValue1 === -1 || roarValue2 === -1) { + throw new OperationError("错误:密文中包含不在字典中的字符!"); + } + + let temp = roarValue1 * 4 + roarValue2 - currentIndex % 0x10; + if (temp < 0) { + temp += 0x10; + } + + rawStr += temp.toString(16); + + return rawStr; + }, ""); + + const raw = rawHex.match(/.{4}/g).reduce((rawStr, hexChunk) => rawStr + String.fromCharCode(parseInt("0x" + hexChunk, 16)), ""); + return raw; + } +} + +export default RoarCipherDecode; diff --git a/plugins/srktoolbox/src/core/operations/RoarCipherEncode.mjs b/plugins/srktoolbox/src/core/operations/RoarCipherEncode.mjs new file mode 100644 index 00000000..61ced1d2 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/RoarCipherEncode.mjs @@ -0,0 +1,78 @@ +/** + * @author Raka-loah [i@lotc.cc] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Roar Cipher Encode operation + */ +class RoarCipherEncode extends Operation { + + /** + * RoarCipherEncode constructor + */ + constructor() { + super(); + + this.name = "兽音译者加密"; + this.module = "Default"; + this.description = "把输入的文本加密为各种“嗷呜啊”,支持自定义字典。"; + this.infoURL = ""; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "字典(4个字符)", + type: "editableOption", + value: [ + { + name: "默认", + value: "嗷呜啊~" + }, + { + name: "自定义", + value: "" + } + ] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [alphabet] = args; + + if (alphabet.length !== 4) { + throw new OperationError("错误:字典长度必须为4个字符!"); + } + + if (input.length === 0) { + return ""; + } + + const alphaList = alphabet.split(""); + const hexInput = input.split("").map(character => character.charCodeAt(0).toString(16).padStart(4, "0")).join(""); + const roar = hexInput.split("").reduce(function (roarStr, hexStr, currentIndex) { + let temp = 0; + temp = parseInt("0x" + hexStr, 16) + currentIndex % 0x10; + if (temp >= 0x10) { + temp -= 0x10; + } + roarStr += alphaList[parseInt(temp / 4, 10)] + alphaList[temp % 4]; + return roarStr; + }, ""); + + return alphaList[3] + alphaList[1] + alphaList[0] + roar + alphaList[2]; + } + +} + +export default RoarCipherEncode; diff --git a/plugins/srktoolbox/src/core/operations/RotateImage.mjs b/plugins/srktoolbox/src/core/operations/RotateImage.mjs new file mode 100644 index 00000000..6529a39a --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/RotateImage.mjs @@ -0,0 +1,96 @@ +/** + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { isImage } from "../lib/FileType.mjs"; +import { toBase64 } from "../lib/Base64.mjs"; +import { isWorkerEnvironment } from "../Utils.mjs"; +import { Jimp, JimpMime } from "jimp"; + +/** + * Rotate Image operation + */ +class RotateImage extends Operation { + /** + * RotateImage constructor + */ + constructor() { + super(); + + this.name = "旋转图像"; + this.module = "Image"; + this.description = + "按给定的角度旋转图像。"; + this.infoURL = ""; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.presentType = "html"; + this.args = [ + { + name: "旋转角度", + type: "number", + value: 90, + }, + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {byteArray} + */ + async run(input, args) { + const [degrees] = args; + + if (!isImage(input)) { + throw new OperationError("无效的文件类型。"); + } + + let image; + try { + image = await Jimp.read(input); + } catch (err) { + throw new OperationError(`载入图像出错:(${err})`); + } + try { + if (isWorkerEnvironment()) + self.sendStatusMessage("旋转图像……"); + image.rotate(degrees); + + let imageBuffer; + if (image.mime === "image/gif") { + imageBuffer = await image.getBuffer(JimpMime.png); + } else { + imageBuffer = await image.getBuffer(image.mime); + } + return imageBuffer.buffer; + } catch (err) { + throw new OperationError(`旋转图像出错:(${err})`); + } + } + + /** + * Displays the rotated image using HTML for web apps + * @param {ArrayBuffer} data + * @returns {html} + */ + present(data) { + if (!data.byteLength) return ""; + const dataArray = new Uint8Array(data); + + const type = isImage(dataArray); + if (!type) { + throw new OperationError("无效的文件类型。"); + } + + return ``; + } +} + +export default RotateImage; diff --git a/plugins/srktoolbox/src/core/operations/RotateLeft.mjs b/plugins/srktoolbox/src/core/operations/RotateLeft.mjs new file mode 100644 index 00000000..e874b754 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/RotateLeft.mjs @@ -0,0 +1,84 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import {rot, rotl, rotlCarry} from "../lib/Rotate.mjs"; + + +/** + * Rotate left operation. + */ +class RotateLeft extends Operation { + + /** + * RotateLeft constructor + */ + constructor() { + super(); + + this.name = "循环左移"; + this.module = "Default"; + this.description = "将每个字节按照给定的偏移量循环左移,可选择是否进位。当前只支持8位数值。"; + this.infoURL = "https://wikipedia.org/wiki/Bitwise_operation#Bit_shifts"; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = [ + { + name: "偏移量", + type: "number", + value: 1 + }, + { + name: "进位", + type: "boolean", + value: false + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + if (args[1]) { + return rotlCarry(input, args[0]); + } else { + return rot(input, args[0], rotl); + } + } + + /** + * Highlight rotate left + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight rotate left 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 RotateLeft; diff --git a/plugins/srktoolbox/src/core/operations/RotateRight.mjs b/plugins/srktoolbox/src/core/operations/RotateRight.mjs new file mode 100644 index 00000000..13bdf80e --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/RotateRight.mjs @@ -0,0 +1,84 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import {rot, rotr, rotrCarry} from "../lib/Rotate.mjs"; + + +/** + * Rotate right operation. + */ +class RotateRight extends Operation { + + /** + * RotateRight constructor + */ + constructor() { + super(); + + this.name = "循环右移"; + this.module = "Default"; + this.description = "将每个字节按照给定的偏移量循环右移,可选择是否进位。当前只支持8位数值。"; + this.infoURL = "https://wikipedia.org/wiki/Bitwise_operation#Bit_shifts"; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = [ + { + name: "偏移量", + type: "number", + value: 1 + }, + { + name: "进位", + type: "boolean", + value: false + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + if (args[1]) { + return rotrCarry(input, args[0]); + } else { + return rot(input, args[0], rotr); + } + } + + /** + * Highlight rotate right + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight rotate right 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 RotateRight; diff --git a/plugins/srktoolbox/src/core/operations/SHA0.mjs b/plugins/srktoolbox/src/core/operations/SHA0.mjs new file mode 100644 index 00000000..66b6d32f --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/SHA0.mjs @@ -0,0 +1,50 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import {runHash} from "../lib/Hash.mjs"; + +/** + * SHA0 operation + */ +class SHA0 extends Operation { + + /** + * SHA0 constructor + */ + constructor() { + super(); + + this.name = "SHA0"; + this.module = "Crypto"; + this.description = "SHA最初载明的算法于1993年发布,称做安全散列标准(Secure Hash Standard),FIPS PUB 180。这个版本现在常被称为SHA-0。它在发布之后很快就被NSA撤回,并且由1995年发布的修订版本FIPS PUB 180-1(通常称为SHA-1)取代。此算法默认进行80轮运算。"; + this.infoURL = "https://wikipedia.org/wiki/SHA-1#SHA-0"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + name: "轮数", + type: "number", + value: 80, + min: 16 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return runHash("sha0", input, {rounds: args[0]}); + } + +} + +export default SHA0; diff --git a/plugins/srktoolbox/src/core/operations/SHA1.mjs b/plugins/srktoolbox/src/core/operations/SHA1.mjs new file mode 100644 index 00000000..ae5880c1 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/SHA1.mjs @@ -0,0 +1,50 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import {runHash} from "../lib/Hash.mjs"; + +/** + * SHA1 operation + */ +class SHA1 extends Operation { + + /** + * SHA1 constructor + */ + constructor() { + super(); + + this.name = "SHA1"; + this.module = "Crypto"; + this.description = "SHA-1(英语:Secure Hash Algorithm 1,中文名:安全散列算法1)是一种密码散列函数,美国国家安全局设计,并由美国国家标准技术研究所(NIST)发布为联邦资料处理标准(FIPS)。SHA-1是SHA系列算法中最常见的一种,广泛用于安全应用与协议中。

2020年,针对SHA-1的选择前缀冲突攻击已经实际可行。此算法默认进行80轮运算。"; + this.infoURL = "https://wikipedia.org/wiki/SHA-1"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + name: "轮数", + type: "number", + value: 80, + min: 16 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return runHash("sha1", input, {rounds: args[0]}); + } + +} + +export default SHA1; diff --git a/plugins/srktoolbox/src/core/operations/SHA2.mjs b/plugins/srktoolbox/src/core/operations/SHA2.mjs new file mode 100644 index 00000000..b4abb49b --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/SHA2.mjs @@ -0,0 +1,94 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import {runHash} from "../lib/Hash.mjs"; + +/** + * SHA2 operation + */ +class SHA2 extends Operation { + + /** + * SHA2 constructor + */ + constructor() { + super(); + + this.name = "SHA2"; + this.module = "Crypto"; + this.description = "SHA-2,名称来自于安全散列算法2(英语:Secure Hash Algorithm 2)的缩写,一种密码散列函数算法标准,由美国国家安全局研发[3],由美国国家标准与技术研究院(NIST)在2001年发布。属于SHA算法之一,是SHA-1的后继者。其下又可再分为六个不同的算法标准,包括了:SHA-224、SHA-256、SHA-384、SHA-512、SHA-512/224、SHA-512/256。

  • SHA-512操作于64位字长。
  • SHA-256操作于32位字长。
  • SHA-384和SHA-512基本相同,但截断到了384字节。
  • SHA-224和SHA-256基本相同,但截断到了224字节。
  • SHA-512/224和SHA-512/256是SHA-512的截断版本,但初始值使用记载于Federal Information Processing Standards (FIPS)的PUB 180-4文档中的方法计算。
此算法对于SHA256系列默认进行64轮运算,对SHA512系列默认160轮。"; + this.infoURL = "https://wikipedia.org/wiki/SHA-2"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + name: "长度", + type: "argSelector", + value: [ + { + name: "512", + on: [2], + off: [1] + }, + { + name: "384", + on: [2], + off: [1] + }, + { + name: "256", + on: [1], + off: [2] + }, + { + name: "224", + on: [1], + off: [2] + }, + { + name: "512/256", + on: [2], + off: [1] + }, + { + name: "512/224", + on: [2], + off: [1] + } + ] + }, + { + name: "轮数", // For SHA256 variants + type: "number", + value: 64, + min: 16 + }, + { + name: "轮数", // For SHA512 variants + type: "number", + value: 160, + min: 32 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const size = args[0]; + const rounds = (size === "256" || size === "224") ? args[1] : args[2]; + return runHash("sha" + size, input, {rounds: rounds}); + } + +} + +export default SHA2; diff --git a/plugins/srktoolbox/src/core/operations/SHA3.mjs b/plugins/srktoolbox/src/core/operations/SHA3.mjs new file mode 100644 index 00000000..bb36f173 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/SHA3.mjs @@ -0,0 +1,70 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import JSSHA3 from "js-sha3"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * SHA3 operation + */ +class SHA3 extends Operation { + + /** + * SHA3 constructor + */ + constructor() { + super(); + + this.name = "SHA3"; + this.module = "Crypto"; + this.description = "SHA-3第三代安全散列算法(Secure Hash Algorithm 3)在2015年8月5日由 NIST 通过 FIPS 202 正式发表。

SHA-3是Keccak系列算法的子集。Keccak是由 Guido Bertoni,Joan Daemen,Michaël Peeters,以及Gilles Van Assche在RadioGatún上设计的哈希算法。"; + this.infoURL = "https://wikipedia.org/wiki/SHA-3"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "长度", + "type": "option", + "value": ["512", "384", "256", "224"] + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const size = parseInt(args[0], 10); + let algo; + + switch (size) { + case 224: + algo = JSSHA3.sha3_224; + break; + case 384: + algo = JSSHA3.sha3_384; + break; + case 256: + algo = JSSHA3.sha3_256; + break; + case 512: + algo = JSSHA3.sha3_512; + break; + default: + throw new OperationError("无效长度"); + } + + return algo(input); + } + +} + +export default SHA3; diff --git a/plugins/srktoolbox/src/core/operations/SIGABA.mjs b/plugins/srktoolbox/src/core/operations/SIGABA.mjs new file mode 100644 index 00000000..e3a9b82e --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/SIGABA.mjs @@ -0,0 +1,287 @@ +/** + * Emulation of the SIGABA machine. + * + * @author hettysymes + * @copyright hettysymes 2020 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import {LETTERS} from "../lib/Enigma.mjs"; +import {NUMBERS, CR_ROTORS, I_ROTORS, SigabaMachine, CRRotor, IRotor} from "../lib/SIGABA.mjs"; + +/** + * Sigaba operation + */ +class Sigaba extends Operation { + + /** + * Sigaba constructor + */ + constructor() { + super(); + + this.name = "SIGABA"; + this.module = "Bletchley"; + this.description = "Encipher/decipher with the WW2 SIGABA machine.

SIGABA, otherwise known as ECM Mark II, was used by the United States for message encryption during WW2 up to the 1950s. It was developed in the 1930s by the US Army and Navy, and has up to this day never been broken. Consisting of 15 rotors: 5 cipher rotors and 10 rotors (5 control rotors and 5 index rotors) controlling the stepping of the cipher rotors, the rotor stepping for SIGABA is much more complex than other rotor machines of its time, such as Enigma. All example rotor wirings are random example sets.

To configure rotor wirings, for the cipher and control rotors enter a string of letters which map from A to Z, and for the index rotors enter a sequence of numbers which map from 0 to 9. Note that encryption is not the same as decryption, so first choose the desired mode.

Note: Whilst this has been tested against other software emulators, it has not been tested against hardware."; + this.infoURL = "https://wikipedia.org/wiki/SIGABA"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "1st (left-hand) cipher rotor", + type: "editableOption", + value: CR_ROTORS, + defaultIndex: 0 + }, + { + name: "1st cipher rotor reversed", + type: "boolean", + value: false + }, + { + name: "1st cipher rotor initial value", + type: "option", + value: LETTERS + }, + { + name: "2nd cipher rotor", + type: "editableOption", + value: CR_ROTORS, + defaultIndex: 0 + }, + { + name: "2nd cipher rotor reversed", + type: "boolean", + value: false + }, + { + name: "2nd cipher rotor initial value", + type: "option", + value: LETTERS + }, + { + name: "3rd (middle) cipher rotor", + type: "editableOption", + value: CR_ROTORS, + defaultIndex: 0 + }, + { + name: "3rd cipher rotor reversed", + type: "boolean", + value: false + }, + { + name: "3rd cipher rotor initial value", + type: "option", + value: LETTERS + }, + { + name: "4th cipher rotor", + type: "editableOption", + value: CR_ROTORS, + defaultIndex: 0 + }, + { + name: "4th cipher rotor reversed", + type: "boolean", + value: false + }, + { + name: "4th cipher rotor initial value", + type: "option", + value: LETTERS + }, + { + name: "5th (right-hand) cipher rotor", + type: "editableOption", + value: CR_ROTORS, + defaultIndex: 0 + }, + { + name: "5th cipher rotor reversed", + type: "boolean", + value: false + }, + { + name: "5th cipher rotor initial value", + type: "option", + value: LETTERS + }, + { + name: "1st (left-hand) control rotor", + type: "editableOption", + value: CR_ROTORS, + defaultIndex: 0 + }, + { + name: "1st control rotor reversed", + type: "boolean", + value: false + }, + { + name: "1st control rotor initial value", + type: "option", + value: LETTERS + }, + { + name: "2nd control rotor", + type: "editableOption", + value: CR_ROTORS, + defaultIndex: 0 + }, + { + name: "2nd control rotor reversed", + type: "boolean", + value: false + }, + { + name: "2nd control rotor initial value", + type: "option", + value: LETTERS + }, + { + name: "3rd (middle) control rotor", + type: "editableOption", + value: CR_ROTORS, + defaultIndex: 0 + }, + { + name: "3rd control rotor reversed", + type: "boolean", + value: false + }, + { + name: "3rd control rotor initial value", + type: "option", + value: LETTERS + }, + { + name: "4th control rotor", + type: "editableOption", + value: CR_ROTORS, + defaultIndex: 0 + }, + { + name: "4th control rotor reversed", + type: "boolean", + value: false + }, + { + name: "4th control rotor initial value", + type: "option", + value: LETTERS + }, + { + name: "5th (right-hand) control rotor", + type: "editableOption", + value: CR_ROTORS, + defaultIndex: 0 + }, + { + name: "5th control rotor reversed", + type: "boolean", + value: false + }, + { + name: "5th control rotor initial value", + type: "option", + value: LETTERS + }, + { + name: "1st (left-hand) index rotor", + type: "editableOption", + value: I_ROTORS, + defaultIndex: 0 + }, + { + name: "1st index rotor initial value", + type: "option", + value: NUMBERS + }, + { + name: "2nd index rotor", + type: "editableOption", + value: I_ROTORS, + defaultIndex: 0 + }, + { + name: "2nd index rotor initial value", + type: "option", + value: NUMBERS + }, + { + name: "3rd (middle) index rotor", + type: "editableOption", + value: I_ROTORS, + defaultIndex: 0 + }, + { + name: "3rd index rotor initial value", + type: "option", + value: NUMBERS + }, + { + name: "4th index rotor", + type: "editableOption", + value: I_ROTORS, + defaultIndex: 0 + }, + { + name: "4th index rotor initial value", + type: "option", + value: NUMBERS + }, + { + name: "5th (right-hand) index rotor", + type: "editableOption", + value: I_ROTORS, + defaultIndex: 0 + }, + { + name: "5th index rotor initial value", + type: "option", + value: NUMBERS + }, + { + name: "SIGABA mode", + type: "option", + value: ["Encrypt", "Decrypt"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const sigabaSwitch = args[40]; + const cipherRotors = []; + const controlRotors = []; + const indexRotors = []; + for (let i=0; i<5; i++) { + const rotorWiring = args[i*3]; + cipherRotors.push(new CRRotor(rotorWiring, args[i*3+2], args[i*3+1])); + } + for (let i=5; i<10; i++) { + const rotorWiring = args[i*3]; + controlRotors.push(new CRRotor(rotorWiring, args[i*3+2], args[i*3+1])); + } + for (let i=15; i<20; i++) { + const rotorWiring = args[i*2]; + indexRotors.push(new IRotor(rotorWiring, args[i*2+1])); + } + const sigaba = new SigabaMachine(cipherRotors, controlRotors, indexRotors); + let result; + if (sigabaSwitch === "Encrypt") { + result = sigaba.encrypt(input); + } else if (sigabaSwitch === "Decrypt") { + result = sigaba.decrypt(input); + } + return result; + } + +} +export default Sigaba; diff --git a/plugins/srktoolbox/src/core/operations/SM2Decrypt.mjs b/plugins/srktoolbox/src/core/operations/SM2Decrypt.mjs new file mode 100644 index 00000000..46ac631b --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/SM2Decrypt.mjs @@ -0,0 +1,73 @@ +/** + * @author flakjacket95 [dflack95@gmail.com] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + * + * Modified by Raka-loah@github + */ + +import OperationError from "../errors/OperationError.mjs"; +import Operation from "../Operation.mjs"; + +import { SM2 } from "../lib/SM2.mjs"; + +/** + * SM2Decrypt operation + */ +class SM2Decrypt extends Operation { + + /** + * SM2Decrypt constructor + */ + constructor() { + super(); + + this.name = "SM2解密"; + this.module = "Crypto"; + this.description = "解密使用SM2标准编码的消息。"; + this.infoURL = "https://zh.wikipedia.org/wiki/SM2"; + this.inputType = "string"; + this.outputType = "ArrayBuffer"; + this.args = [ + { + name: "私钥", + type: "string", + value: "DEADBEEF" + }, + { + "name": "输入格式", + "type": "option", + "value": ["C1C3C2", "C1C2C3"], + "defaultIndex": 0 + }, + { + name: "曲线", + type: "option", + "value": ["sm2p256v1"], + "defaultIndex": 0 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + const [privateKey, inputFormat, curveName] = args; + + if (privateKey.length !== 64) { + throw new OperationError("输入的私钥必须为32字节长度的十六进制"); + } + + const sm2 = new SM2(curveName, inputFormat); + sm2.setPrivateKey(privateKey); + + const result = sm2.decrypt(input); + return result; + } + +} + +export default SM2Decrypt; diff --git a/plugins/srktoolbox/src/core/operations/SM2Encrypt.mjs b/plugins/srktoolbox/src/core/operations/SM2Encrypt.mjs new file mode 100644 index 00000000..70a5b7f2 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/SM2Encrypt.mjs @@ -0,0 +1,79 @@ +/** + * @author flakjacket95 [dflack95@gmail.com] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + * + * Modified by Raka-loah@github + */ + +import OperationError from "../errors/OperationError.mjs"; +import Operation from "../Operation.mjs"; + +import { SM2 } from "../lib/SM2.mjs"; + +/** + * SM2 Encrypt operation + */ +class SM2Encrypt extends Operation { + + /** + * SM2Encrypt constructor + */ + constructor() { + super(); + + this.name = "SM2加密"; + this.module = "Crypto"; + this.description = "使用SM2标准加密消息"; + this.infoURL = "https://zh.wikipedia.org/wiki/SM2"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + + this.args = [ + { + name: "公钥X", + type: "string", + value: "DEADBEEF" + }, + { + name: "公钥Y", + type: "string", + value: "DEADBEEF" + }, + { + "name": "输出格式", + "type": "option", + "value": ["C1C3C2", "C1C2C3"], + "defaultIndex": 0 + }, + { + name: "曲线", + type: "option", + "value": ["sm2p256v1"], + "defaultIndex": 0 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const [publicKeyX, publicKeyY, outputFormat, curveName] = args; + this.outputFormat = outputFormat; + + if (publicKeyX.length !== 64 || publicKeyY.length !== 64) { + throw new OperationError("无效的公钥 - 确保每个公钥都是32字节长度的十六进制"); + } + + const sm2 = new SM2(curveName, outputFormat); + sm2.setPublicKey(publicKeyX, publicKeyY); + + const result = sm2.encrypt(new Uint8Array(input)); + return result; + } +} + +export default SM2Encrypt; diff --git a/plugins/srktoolbox/src/core/operations/SM3.mjs b/plugins/srktoolbox/src/core/operations/SM3.mjs new file mode 100644 index 00000000..0d4bd762 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/SM3.mjs @@ -0,0 +1,59 @@ +/** + * @author n1073645 [n1073645@gmail.com] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import Sm3 from "crypto-api/src/hasher/sm3.mjs"; +import {toHex} from "crypto-api/src/encoder/hex.mjs"; + +/** + * SM3 operation + */ +class SM3 extends Operation { + + /** + * SM3 constructor + */ + constructor() { + super(); + + this.name = "SM3"; + this.module = "Crypto"; + this.description = "SM3是中华人民共和国政府采用的一种密码散列函数标准,前身为SCH4杂凑算法,由国家密码管理局于2010年12月17日发布,相关标准为“GM/T 0004-2012 《SM3密码杂凑算法》”。2016年,成为中国国家密码标准(GB/T 32905-2016)。此算法默认进行64轮运算,长度256位。"; + this.infoURL = "https://wikipedia.org/wiki/SM3_(hash_function)"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + name: "长度", + type: "number", + value: 256 + }, + { + name: "轮数", + type: "number", + value: 64, + min: 16 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const msg = Utils.arrayBufferToStr(input, false); + const hasher = new Sm3({length: args[0], rounds: args[1]}); + hasher.update(msg); + return toHex(hasher.finalize()); + } +} + +export default SM3; diff --git a/plugins/srktoolbox/src/core/operations/SM4Decrypt.mjs b/plugins/srktoolbox/src/core/operations/SM4Decrypt.mjs new file mode 100644 index 00000000..85e0acb5 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/SM4Decrypt.mjs @@ -0,0 +1,90 @@ +/** + * @author swesven + * @copyright 2021 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { toHex } from "../lib/Hex.mjs"; +import { decryptSM4 } from "../lib/SM4.mjs"; + +/** + * SM4 Decrypt operation + */ +class SM4Decrypt extends Operation { + + /** + * SM4Encrypt constructor + */ + constructor() { + super(); + + this.name = "SM4解密"; + this.module = "Ciphers"; + this.description = "SM4是128位的分组密码,2016年8月,成为中国国家密码标准(GB/T 32907-2016)。"; + this.infoURL = "https://wikipedia.org/wiki/SM4_(cipher)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "IV", + "type": "toggleString", + "value": "", + "toggleValues": ["十六进制", "UTF8", "Latin1", "Base64"] + }, + { + "name": "模式", + "type": "option", + "value": ["CBC", "CFB", "OFB", "CTR", "ECB", "CBC/NoPadding", "ECB/NoPadding"] + }, + { + "name": "输入", + "type": "option", + "value": ["原始", "十六进制"] + }, + { + "name": "输出", + "type": "option", + "value": ["原始", "十六进制"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = Utils.convertToByteArray(args[0].string, args[0].option), + iv = Utils.convertToByteArray(args[1].string, args[1].option), + [,, mode, inputType, outputType] = args; + + if (key.length !== 16) + throw new OperationError(`无效的key长度: ${key.length} 字节 + +SM4使用16字节(128位)长度的key。`); + if (iv.length !== 16 && !mode.startsWith("ECB")) + throw new OperationError(`无效的IV长度: ${iv.length} 字节 + +SM4使用16字节(128位)长度的IV。 +请确认选择了正确的格式 (例如十六进制或UTF8)。`); + + input = Utils.convertToByteArray(input, inputType); + const output = decryptSM4(input, key, iv, mode.substring(0, 3), mode.endsWith("NoPadding")); + return outputType === "十六进制" ? toHex(output) : Utils.byteArrayToUtf8(output); + } + +} + +export default SM4Decrypt; diff --git a/plugins/srktoolbox/src/core/operations/SM4Encrypt.mjs b/plugins/srktoolbox/src/core/operations/SM4Encrypt.mjs new file mode 100644 index 00000000..667d8129 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/SM4Encrypt.mjs @@ -0,0 +1,90 @@ +/** + * @author swesven + * @copyright 2021 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { toHex } from "../lib/Hex.mjs"; +import { encryptSM4 } from "../lib/SM4.mjs"; + +/** + * SM4 Encrypt operation + */ +class SM4Encrypt extends Operation { + + /** + * SM4Encrypt constructor + */ + constructor() { + super(); + + this.name = "SM4加密"; + this.module = "Ciphers"; + this.description = "SM4是128位的分组密码,2016年8月,成为中国国家密码标准(GB/T 32907-2016)。支持多种加密模式。当使用CBC或ECB模式时,使用PKCS#7填充。"; + this.infoURL = "https://wikipedia.org/wiki/SM4_(cipher)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["十六进制", "UTF8", "Latin1", "Base64"] + }, + { + "name": "IV", + "type": "toggleString", + "value": "", + "toggleValues": ["十六进制", "UTF8", "Latin1", "Base64"] + }, + { + "name": "模式", + "type": "option", + "value": ["CBC", "CFB", "OFB", "CTR", "ECB"] + }, + { + "name": "输入", + "type": "option", + "value": ["原始", "十六进制"] + }, + { + "name": "输出", + "type": "option", + "value": ["十六进制", "原始"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = Utils.convertToByteArray(args[0].string, args[0].option), + iv = Utils.convertToByteArray(args[1].string, args[1].option), + [,, mode, inputType, outputType] = args; + + if (key.length !== 16) + throw new OperationError(`无效的key长度: ${key.length} 字节 + +SM4使用16字节(128位)长度的key。`); + if (iv.length !== 16 && !mode.startsWith("ECB")) + throw new OperationError(`无效的IV长度: ${iv.length} 字节 + +SM4使用16字节(128位)长度的IV。 +请确认选择了正确的格式 (例如十六进制或UTF8)。`); + + input = Utils.convertToByteArray(input, inputType); + const output = encryptSM4(input, key, iv, mode.substring(0, 3), mode.endsWith("NoPadding")); + return outputType === "十六进制" ? toHex(output) : Utils.byteArrayToUtf8(output); + } + +} + +export default SM4Encrypt; diff --git a/plugins/srktoolbox/src/core/operations/SQLBeautify.mjs b/plugins/srktoolbox/src/core/operations/SQLBeautify.mjs new file mode 100644 index 00000000..28ae768c --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/SQLBeautify.mjs @@ -0,0 +1,49 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import vkbeautify from "vkbeautify"; +import Operation from "../Operation.mjs"; + +/** + * SQL Beautify operation + */ +class SQLBeautify extends Operation { + + /** + * SQLBeautify constructor + */ + constructor() { + super(); + + this.name = "SQL美化"; + this.module = "Code"; + this.description = "为Structured Query Language (SQL)代码添加缩进与美化。"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "缩进", + "type": "binaryShortString", + "value": "\\t" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const indentStr = args[0]; + return vkbeautify.sql(input, indentStr); + } + +} + +export default SQLBeautify; diff --git a/plugins/srktoolbox/src/core/operations/SQLMinify.mjs b/plugins/srktoolbox/src/core/operations/SQLMinify.mjs new file mode 100644 index 00000000..c7a58e7f --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/SQLMinify.mjs @@ -0,0 +1,42 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import vkbeautify from "vkbeautify"; +import Operation from "../Operation.mjs"; + +/** + * SQL Minify operation + */ +class SQLMinify extends Operation { + + /** + * SQLMinify constructor + */ + constructor() { + super(); + + this.name = "SQL压缩"; + this.module = "Code"; + this.description = "压缩Structured Query Language (SQL)代码(Minify/Uglify)。"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return vkbeautify.sqlmin(input); + } + +} + +export default SQLMinify; diff --git a/plugins/srktoolbox/src/core/operations/SSDEEP.mjs b/plugins/srktoolbox/src/core/operations/SSDEEP.mjs new file mode 100644 index 00000000..88d9852e --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/SSDEEP.mjs @@ -0,0 +1,43 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import ssdeepjs from "ssdeep.js"; + +/** + * SSDEEP operation + */ +class SSDEEP extends Operation { + + /** + * SSDEEP constructor + */ + constructor() { + super(); + + this.name = "SSDEEP"; + this.module = "Crypto"; + this.description = "SSDEEP是用于计算基于内容分割的分片哈希(Context Triggered Piecewise Hash, CTPH)的程序。CTPH也被叫做模糊哈希,可以用来检测数据同源性。例如几段具有相同内容片段的数据,相同片段间的其它内容和长度不同,但CTPH的计算结果相近。

SSDEEP哈希现在广泛用于简单的辨识目的(例如VirusTotal的“Basic Properties”)。尽管有“更好”的模糊哈希算法,SSDEEP依然是日常使用首选,得益于它的运算速度,以及它已经成为一个事实标准。

此操作本质上与下面的CTPH操作是相同的,只是输出格式不同。"; + this.infoURL = "https://forensics.wiki/ssdeep"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return ssdeepjs.digest(input); + } + +} + +export default SSDEEP; diff --git a/plugins/srktoolbox/src/core/operations/SUB.mjs b/plugins/srktoolbox/src/core/operations/SUB.mjs new file mode 100644 index 00000000..8d4369fc --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/SUB.mjs @@ -0,0 +1,79 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import { bitOp, sub, BITWISE_OP_DELIMS } from "../lib/BitwiseOp.mjs"; + +/** + * SUB operation + */ +class SUB extends Operation { + + /** + * SUB constructor + */ + constructor() { + super(); + + this.name = "SUB"; + this.module = "Default"; + this.description = "使用给定的Key对输入进行SUB(按位减)操作。(e.g. fe023da5), MOD 255"; + this.infoURL = "https://wikipedia.org/wiki/Bitwise_operation#Bitwise_operators"; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": BITWISE_OP_DELIMS + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const key = Utils.convertToByteArray(args[0].string || "", args[0].option); + + return bitOp(input, key, sub); + } + + /** + * Highlight SUB + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight SUB 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 SUB; diff --git a/plugins/srktoolbox/src/core/operations/Salsa20.mjs b/plugins/srktoolbox/src/core/operations/Salsa20.mjs new file mode 100644 index 00000000..3f43fef9 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Salsa20.mjs @@ -0,0 +1,156 @@ +/** + * @author joostrijneveld [joost@joostrijneveld.nl] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { toHex } from "../lib/Hex.mjs"; +import { salsa20Block } from "../lib/Salsa20.mjs"; + +/** + * Salsa20 operation + */ +class Salsa20 extends Operation { + + /** + * Salsa20 constructor + */ + constructor() { + super(); + + this.name = "Salsa20"; + this.module = "Ciphers"; + this.description = "Salsa20是一种流加密算法,由丹尼尔·J·伯恩斯坦提交到eSTREAM。Salsa20/8和Salsa20/12是加密轮数减少的版本。Salsa20和ChaCha流加密算法有着紧密联系。

密钥: Salsa20使用16或32字节(128或256位)长度的密钥。

Nonce: Salsa20使用8字节(64位)长度的nonce。

计数: Salsa使用8字节(64位)长度的计数。计数在流的起始处为0,每64字节递增。"; + this.infoURL = "https://wikipedia.org/wiki/Salsa20"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "密钥", + "type": "toggleString", + "value": "", + "toggleValues": ["十六进制", "UTF8", "Latin1", "Base64"] + }, + { + "name": "Nonce", + "type": "toggleString", + "value": "", + "toggleValues": ["十六进制", "UTF8", "Latin1", "Base64", "整数"] + }, + { + "name": "计数", + "type": "number", + "value": 0, + "min": 0 + }, + { + "name": "轮数", + "type": "option", + "value": ["20", "12", "8"] + }, + { + "name": "输入", + "type": "option", + "value": ["十六进制", "原始"] + }, + { + "name": "输出", + "type": "option", + "value": ["原始", "十六进制"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = Utils.convertToByteArray(args[0].string, args[0].option), + nonceType = args[1].option, + rounds = parseInt(args[3], 10), + inputType = args[4], + outputType = args[5]; + + if (key.length !== 16 && key.length !== 32) { + throw new OperationError(`无效的密钥长度: ${key.length} 字节。 + +Salsa20使用16或32字节(128或256位)的密钥。`); + } + + let counter, nonce; + if (nonceType === "整数") { + nonce = Utils.intToByteArray(parseInt(args[1].string, 10), 8, "little"); + } else { + nonce = Utils.convertToByteArray(args[1].string, args[1].option); + if (!(nonce.length === 8)) { + throw new OperationError(`无效的nonce长度: ${nonce.length} 字节。 + +Salsa20使用8字节(64位)的nonce。`); + } + } + counter = Utils.intToByteArray(args[2], 8, "little"); + + const output = []; + input = Utils.convertToByteArray(input, inputType); + + let counterAsInt = Utils.byteArrayToInt(counter, "little"); + for (let i = 0; i < input.length; i += 64) { + counter = Utils.intToByteArray(counterAsInt, 8, "little"); + const stream = salsa20Block(key, nonce, counter, rounds); + for (let j = 0; j < 64 && i + j < input.length; j++) { + output.push(input[i + j] ^ stream[j]); + } + counterAsInt++; + } + + if (outputType === "十六进制") { + return toHex(output); + } else { + return Utils.arrayBufferToStr(Uint8Array.from(output).buffer); + } + } + + /** + * Highlight Salsa20 + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + const inputType = args[4], + outputType = args[5]; + if (inputType === "原始" && outputType === "原始") { + return pos; + } + } + + /** + * Highlight Salsa20 in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + const inputType = args[4], + outputType = args[5]; + if (inputType === "原始" && outputType === "原始") { + return pos; + } + } + +} + +export default Salsa20; diff --git a/plugins/srktoolbox/src/core/operations/ScanForEmbeddedFiles.mjs b/plugins/srktoolbox/src/core/operations/ScanForEmbeddedFiles.mjs new file mode 100644 index 00000000..c2bee4f9 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ScanForEmbeddedFiles.mjs @@ -0,0 +1,80 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import { scanForFileTypes } from "../lib/FileType.mjs"; +import { FILE_SIGNATURES } from "../lib/FileSignatures.mjs"; + +/** + * Scan for Embedded Files operation + */ +class ScanForEmbeddedFiles extends Operation { + + /** + * ScanForEmbeddedFiles constructor + */ + constructor() { + super(); + + this.name = "扫描嵌入文件"; + this.module = "Default"; + this.description = "在输入内容中检测魔术字节(Magic bytes)来扫描潜在的嵌入文件。此操作容易误报。

警告:超过100KB的文件可能会需要非常长的时间处理。"; + this.infoURL = "https://wikipedia.org/wiki/List_of_file_signatures"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = Object.keys(FILE_SIGNATURES).map(cat => { + return { + name: cat, + type: "boolean", + value: cat === "Miscellaneous" ? false : true + }; + }); + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let output = "在输入内容中检测魔术字节(Magic bytes)来扫描潜在的嵌入文件。以下内容可能为误报,因为足够长的数据通常都会碰巧具有这些魔术字节。\n", + numFound = 0; + const categories = [], + data = new Uint8Array(input); + + args.forEach((cat, i) => { + if (cat) categories.push(Object.keys(FILE_SIGNATURES)[i]); + }); + + const types = scanForFileTypes(data, categories); + + if (types.length) { + types.forEach(type => { + numFound++; + output += `\n偏移量 ${type.offset} (0x${Utils.hex(type.offset)}): + 文件类型: ${type.fileDetails.name} + 扩展名: ${type.fileDetails.extension} + MIME类型: ${type.fileDetails.mime}\n`; + + if (type?.fileDetails?.description?.length) { + output += ` 描述: ${type.fileDetails.description}\n`; + } + }); + } + + if (numFound === 0) { + output += "\n未找到嵌入的文件。"; + } + + return output; + } + +} + +export default ScanForEmbeddedFiles; diff --git a/plugins/srktoolbox/src/core/operations/ScatterChart.mjs b/plugins/srktoolbox/src/core/operations/ScatterChart.mjs new file mode 100644 index 00000000..4c3205c8 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ScatterChart.mjs @@ -0,0 +1,201 @@ +/** + * @author tlwr [toby@toby.codes] + * @author Matt C [me@mitt.dev] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import * as d3temp from "d3"; +import * as nodomtemp from "nodom"; +import { getScatterValues, getScatterValuesWithColour, RECORD_DELIMITER_OPTIONS, COLOURS, FIELD_DELIMITER_OPTIONS } from "../lib/Charts.mjs"; + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +const d3 = d3temp.default ? d3temp.default : d3temp; +const nodom = nodomtemp.default ? nodomtemp.default: nodomtemp; + +/** + * Scatter chart operation + */ +class ScatterChart extends Operation { + + /** + * ScatterChart constructor + */ + constructor() { + super(); + + this.name = "散点图"; + this.module = "Charts"; + this.description = "将横纵坐标数据绘制为图表上的点。"; + this.infoURL = "https://wikipedia.org/wiki/Scatter_plot"; + this.inputType = "string"; + this.outputType = "html"; + this.args = [ + { + name: "记录分隔符", + type: "option", + value: RECORD_DELIMITER_OPTIONS, + }, + { + name: "字段分隔符", + type: "option", + value: FIELD_DELIMITER_OPTIONS, + }, + { + name: "使用表头作为标签", + type: "boolean", + value: true, + }, + { + name: "X轴标签", + type: "string", + value: "", + }, + { + name: "Y轴标签", + type: "string", + value: "", + }, + { + name: "颜色", + type: "string", + value: COLOURS.max, + }, + { + name: "点半径", + type: "number", + value: 10, + }, + { + name: "使用第三列的颜色", + type: "boolean", + value: false, + } + ]; + } + + /** + * Scatter chart operation. + * + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + const recordDelimiter = Utils.charRep(args[0]), + fieldDelimiter = Utils.charRep(args[1]), + columnHeadingsAreIncluded = args[2], + fillColour = Utils.escapeHtml(args[5]), + radius = args[6], + colourInInput = args[7], + dimension = 500; + + let xLabel = args[3], + yLabel = args[4]; + + const dataFunction = colourInInput ? getScatterValuesWithColour : getScatterValues; + const { headings, values } = dataFunction( + input, + recordDelimiter, + fieldDelimiter, + columnHeadingsAreIncluded + ); + + if (headings) { + xLabel = headings.x; + yLabel = headings.y; + } + + const document = new nodom.Document(); + let svg = document.createElement("svg"); + svg = d3.select(svg) + .attr("width", "100%") + .attr("height", "100%") + .attr("viewBox", `0 0 ${dimension} ${dimension}`); + + const margin = { + top: 10, + right: 0, + bottom: 40, + left: 30, + }, + width = dimension - margin.left - margin.right, + height = dimension - margin.top - margin.bottom, + marginedSpace = svg.append("g") + .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); + + const xExtent = d3.extent(values, d => d[0]), + xDelta = xExtent[1] - xExtent[0], + yExtent = d3.extent(values, d => d[1]), + yDelta = yExtent[1] - yExtent[0], + xAxis = d3.scaleLinear() + .domain([xExtent[0] - (0.1 * xDelta), xExtent[1] + (0.1 * xDelta)]) + .range([0, width]), + yAxis = d3.scaleLinear() + .domain([yExtent[0] - (0.1 * yDelta), yExtent[1] + (0.1 * yDelta)]) + .range([height, 0]); + + marginedSpace.append("clipPath") + .attr("id", "clip") + .append("rect") + .attr("width", width) + .attr("height", height); + + marginedSpace.append("g") + .attr("class", "points") + .attr("clip-path", "url(#clip)") + .selectAll("circle") + .data(values) + .enter() + .append("circle") + .attr("cx", (d) => xAxis(d[0])) + .attr("cy", (d) => yAxis(d[1])) + .attr("r", d => radius) + .attr("fill", d => { + return colourInInput ? d[2] : fillColour; + }) + .attr("stroke", "rgba(0, 0, 0, 0.5)") + .attr("stroke-width", "0.5") + .append("title") + .text(d => { + const x = d[0], + y = d[1], + tooltip = `X: ${x}\n + Y: ${y}\n + `.replace(/\s{2,}/g, "\n"); + return tooltip; + }); + + marginedSpace.append("g") + .attr("class", "axis axis--y") + .call(d3.axisLeft(yAxis).tickSizeOuter(-width)); + + svg.append("text") + .attr("transform", "rotate(-90)") + .attr("y", -margin.left) + .attr("x", -(height / 2)) + .attr("dy", "1em") + .style("text-anchor", "middle") + .text(yLabel); + + marginedSpace.append("g") + .attr("class", "axis axis--x") + .attr("transform", "translate(0," + height + ")") + .call(d3.axisBottom(xAxis).tickSizeOuter(-height)); + + svg.append("text") + .attr("x", width / 2) + .attr("y", dimension) + .style("text-anchor", "middle") + .text(xLabel); + + return svg._groups[0][0].outerHTML; + } + +} + +export default ScatterChart; diff --git a/plugins/srktoolbox/src/core/operations/Scrypt.mjs b/plugins/srktoolbox/src/core/operations/Scrypt.mjs new file mode 100644 index 00000000..4ad07926 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Scrypt.mjs @@ -0,0 +1,92 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import scryptsy from "scryptsy"; +import { isWorkerEnvironment } from "../Utils.mjs"; + +/** + * Scrypt operation + */ +class Scrypt extends Operation { + + /** + * Scrypt constructor + */ + constructor() { + super(); + + this.name = "Scrypt"; + this.module = "Crypto"; + this.description = "scrypt(念作“ess crypt”),是加拿大计算机科学家暨计算机安全研究人员科林·珀西瓦尔(Colin Percival)于2009年所发明的密钥派生函数,当初设计用在他所创立的Tarsnap服务上。设计时考虑到大规模的客制硬件攻击而刻意设计需要大量内存运算。2016年,scrypt算法发布在RFC 7914。

在输入区输入口令来生成对应的哈希值。"; + this.infoURL = "https://wikipedia.org/wiki/Scrypt"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "盐", + "type": "toggleString", + "value": "", + "toggleValues": ["十六进制", "Base64", "UTF8", "Latin1"] + }, + { + "name": "迭代次数(N)", + "type": "number", + "value": 16384 + }, + { + "name": "内存因子(r)", + "type": "number", + "value": 8 + }, + { + "name": "并行因子(p)", + "type": "number", + "value": 1 + }, + { + "name": "Key长度", + "type": "number", + "value": 64 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const salt = Buffer.from(Utils.convertToByteArray(args[0].string || "", args[0].option)), + iterations = args[1], + memFactor = args[2], + parallelFactor = args[3], + keyLength = args[4]; + + try { + const data = scryptsy( + input, salt, iterations, memFactor, parallelFactor, keyLength, + p => { + // Progress callback + if (isWorkerEnvironment()) + self.sendStatusMessage(`进度: ${p.percent.toFixed(0)}%`); + } + ); + + return data.toString("hex"); + } catch (err) { + throw new OperationError("错误: " + err.toString()); + } + } + +} + +export default Scrypt; diff --git a/plugins/srktoolbox/src/core/operations/SeriesChart.mjs b/plugins/srktoolbox/src/core/operations/SeriesChart.mjs new file mode 100644 index 00000000..a0adb4e4 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/SeriesChart.mjs @@ -0,0 +1,232 @@ +/** + * @author tlwr [toby@toby.codes] + * @author Matt C [me@mitt.dev] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import * as d3temp from "d3"; +import * as nodomtemp from "nodom"; +import { getSeriesValues, RECORD_DELIMITER_OPTIONS, FIELD_DELIMITER_OPTIONS } from "../lib/Charts.mjs"; + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +const d3 = d3temp.default ? d3temp.default : d3temp; +const nodom = nodomtemp.default ? nodomtemp.default: nodomtemp; + +/** + * Series chart operation + */ +class SeriesChart extends Operation { + + /** + * SeriesChart constructor + */ + constructor() { + super(); + + this.name = "折线图"; + this.module = "Charts"; + this.description = "时分系列图(Time series graph)是折线图的一种,用于表示在给定时间范围内的不同测量值。"; + this.inputType = "string"; + this.outputType = "html"; + this.args = [ + { + name: "记录分隔符", + type: "option", + value: RECORD_DELIMITER_OPTIONS, + }, + { + name: "字段分隔符", + type: "option", + value: FIELD_DELIMITER_OPTIONS, + }, + { + name: "X轴标签", + type: "string", + value: "", + }, + { + name: "点半径", + type: "number", + value: 1, + }, + { + name: "数据系列颜色", + type: "string", + value: "mediumseagreen, dodgerblue, tomato", + }, + ]; + } + + /** + * Series chart operation. + * + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + const recordDelimiter = Utils.charRep(args[0]), + fieldDelimiter = Utils.charRep(args[1]), + xLabel = args[2], + pipRadius = args[3], + // Escape HTML from all colours to prevent reflected XSS. See https://github.com/gchq/CyberChef/issues/1265 + seriesColours = args[4].split(",").map((colour) => { + return Utils.escapeHtml(colour); + }), + svgWidth = 500, + interSeriesPadding = 20, + xAxisHeight = 50, + seriesLabelWidth = 50, + seriesHeight = 100, + seriesWidth = svgWidth - seriesLabelWidth - interSeriesPadding; + + const { xValues, series } = getSeriesValues(input, recordDelimiter, fieldDelimiter), + allSeriesHeight = Object.keys(series).length * (interSeriesPadding + seriesHeight), + svgHeight = allSeriesHeight + xAxisHeight + interSeriesPadding; + + const document = new nodom.Document(); + let svg = document.createElement("svg"); + svg = d3.select(svg) + .attr("width", "100%") + .attr("height", "100%") + .attr("viewBox", `0 0 ${svgWidth} ${svgHeight}`); + + const xAxis = d3.scalePoint() + .domain(xValues) + .range([0, seriesWidth]); + + svg.append("g") + .attr("class", "axis axis--x") + .attr("transform", `translate(${seriesLabelWidth}, ${xAxisHeight})`) + .call( + d3.axisTop(xAxis).tickValues(xValues.filter((x, i) => { + return [0, Math.round(xValues.length / 2), xValues.length -1].indexOf(i) >= 0; + })) + ); + + svg.append("text") + .attr("x", svgWidth / 2) + .attr("y", xAxisHeight / 2) + .style("text-anchor", "middle") + .text(xLabel); + + const tooltipText = {}, + tooltipAreaWidth = seriesWidth / xValues.length; + + xValues.forEach(x => { + const tooltip = []; + + series.forEach(serie => { + const y = serie.data[x]; + if (typeof y === "undefined") return; + + tooltip.push(`${serie.name}: ${y}`); + }); + + tooltipText[x] = tooltip.join("\n"); + }); + + const chartArea = svg.append("g") + .attr("transform", `translate(${seriesLabelWidth}, ${xAxisHeight})`); + + chartArea + .append("g") + .selectAll("rect") + .data(xValues) + .enter() + .append("rect") + .attr("x", x => { + return xAxis(x) - (tooltipAreaWidth / 2); + }) + .attr("y", 0) + .attr("width", tooltipAreaWidth) + .attr("height", allSeriesHeight) + .attr("stroke", "none") + .attr("fill", "transparent") + .append("title") + .text(x => { + return `${x}\n + --\n + ${tooltipText[x]}\n + `.replace(/\s{2,}/g, "\n"); + }); + + const yAxesArea = svg.append("g") + .attr("transform", `translate(0, ${xAxisHeight})`); + + series.forEach((serie, seriesIndex) => { + const yExtent = d3.extent(Object.values(serie.data)), + yAxis = d3.scaleLinear() + .domain(yExtent) + .range([seriesHeight, 0]); + + const seriesGroup = chartArea + .append("g") + .attr("transform", `translate(0, ${seriesHeight * seriesIndex + interSeriesPadding * (seriesIndex + 1)})`); + + let path = ""; + xValues.forEach((x, xIndex) => { + let nextX = xValues[xIndex + 1], + y = serie.data[x], + nextY= serie.data[nextX]; + + if (typeof y === "undefined" || typeof nextY === "undefined") return; + + x = xAxis(x); nextX = xAxis(nextX); + y = yAxis(y); nextY = yAxis(nextY); + + path += `M ${x} ${y} L ${nextX} ${nextY} z `; + }); + + seriesGroup + .append("path") + .attr("d", path) + .attr("fill", "none") + .attr("stroke", seriesColours[seriesIndex % seriesColours.length]) + .attr("stroke-width", "1"); + + xValues.forEach(x => { + const y = serie.data[x]; + if (typeof y === "undefined") return; + + seriesGroup + .append("circle") + .attr("cx", xAxis(x)) + .attr("cy", yAxis(y)) + .attr("r", pipRadius) + .attr("fill", seriesColours[seriesIndex % seriesColours.length]) + .append("title") + .text(d => { + return `${x}\n + --\n + ${tooltipText[x]}\n + `.replace(/\s{2,}/g, "\n"); + }); + }); + + yAxesArea + .append("g") + .attr("transform", `translate(${seriesLabelWidth - interSeriesPadding}, ${seriesHeight * seriesIndex + interSeriesPadding * (seriesIndex + 1)})`) + .attr("class", "axis axis--y") + .call(d3.axisLeft(yAxis).ticks(5)); + + yAxesArea + .append("g") + .attr("transform", `translate(0, ${seriesHeight / 2 + seriesHeight * seriesIndex + interSeriesPadding * (seriesIndex + 1)})`) + .append("text") + .style("text-anchor", "middle") + .attr("transform", "rotate(-90)") + .text(serie.name); + }); + + return svg._groups[0][0].outerHTML; + } + +} + +export default SeriesChart; diff --git a/plugins/srktoolbox/src/core/operations/SetDifference.mjs b/plugins/srktoolbox/src/core/operations/SetDifference.mjs new file mode 100644 index 00000000..505b6179 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/SetDifference.mjs @@ -0,0 +1,89 @@ +/** + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Set Difference operation + */ +class SetDifference extends Operation { + + /** + * Set Difference constructor + */ + constructor() { + super(); + + this.name = "补集"; + this.module = "Default"; + this.description = "计算集合的补集(相对差集)。"; + this.infoURL = "https://wikipedia.org/wiki/Complement_(set_theory)#Relative_complement"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "集合分隔符", + type: "binaryString", + value: "\\n\\n" + }, + { + name: "元素分隔符", + type: "binaryString", + value: "," + }, + ]; + } + + /** + * Validate input length + * + * @param {Object[]} sets + * @throws {Error} if not two sets + */ + validateSampleNumbers(sets) { + if (!sets || (sets.length !== 2)) { + throw new OperationError("集合数量错误,你可能需要调整集合分隔符或者添加一些数据。"); + } + } + + /** + * Run the difference operation + * + * @param {string} input + * @param {Object[]} args + * @returns {string} + * @throws {OperationError} + */ + run(input, args) { + [this.sampleDelim, this.itemDelimiter] = args; + const sets = input.split(this.sampleDelim); + + this.validateSampleNumbers(sets); + + return this.runSetDifference(...sets.map(s => s.split(this.itemDelimiter))); + } + + /** + * Get elements in set a that are not in set b + * + * @param {Object[]} a + * @param {Object[]} b + * @returns {Object[]} + */ + runSetDifference(a, b) { + return a + .filter((item) => { + return b.indexOf(item) === -1; + }) + .join(this.itemDelimiter); + } + +} + +export default SetDifference; diff --git a/plugins/srktoolbox/src/core/operations/SetIntersection.mjs b/plugins/srktoolbox/src/core/operations/SetIntersection.mjs new file mode 100644 index 00000000..ca108d81 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/SetIntersection.mjs @@ -0,0 +1,89 @@ +/** + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Set Intersection operation + */ +class SetIntersection extends Operation { + + /** + * Set Intersection constructor + */ + constructor() { + super(); + + this.name = "交集"; + this.module = "Default"; + this.description = "计算两个集合的交集。"; + this.infoURL = "https://wikipedia.org/wiki/Intersection_(set_theory)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "集合分隔符", + type: "binaryString", + value: "\\n\\n" + }, + { + name: "元素分隔符", + type: "binaryString", + value: "," + }, + ]; + } + + /** + * Validate input length + * + * @param {Object[]} sets + * @throws {Error} if not two sets + */ + validateSampleNumbers(sets) { + if (!sets || (sets.length !== 2)) { + throw new OperationError("集合数量错误,你可能需要调整集合分隔符或者添加一些数据。"); + } + } + + /** + * Run the intersection operation + * + * @param {string} input + * @param {Object[]} args + * @returns {string} + * @throws {OperationError} + */ + run(input, args) { + [this.sampleDelim, this.itemDelimiter] = args; + const sets = input.split(this.sampleDelim); + + this.validateSampleNumbers(sets); + + return this.runIntersect(...sets.map(s => s.split(this.itemDelimiter))); + } + + /** + * Get the intersection of the two sets. + * + * @param {Object[]} a + * @param {Object[]} b + * @returns {Object[]} + */ + runIntersect(a, b) { + return a + .filter((item) => { + return b.indexOf(item) > -1; + }) + .join(this.itemDelimiter); + } + +} + +export default SetIntersection; diff --git a/plugins/srktoolbox/src/core/operations/SetUnion.mjs b/plugins/srktoolbox/src/core/operations/SetUnion.mjs new file mode 100644 index 00000000..feb8cae0 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/SetUnion.mjs @@ -0,0 +1,99 @@ +/** + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Set Union operation + */ +class SetUnion extends Operation { + + /** + * Set Union constructor + */ + constructor() { + super(); + + this.name = "并集"; + this.module = "Default"; + this.description = "计算两个集合的并集。"; + this.infoURL = "https://wikipedia.org/wiki/Union_(set_theory)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "集合分隔符", + type: "binaryString", + value: "\\n\\n" + }, + { + name: "元素分隔符", + type: "binaryString", + value: "," + }, + ]; + } + + /** + * Validate input length + * + * @param {Object[]} sets + * @throws {Error} if not two sets + */ + validateSampleNumbers(sets) { + if (!sets || (sets.length !== 2)) { + throw new OperationError("集合数量错误,你可能需要调整集合分隔符或者添加一些数据。"); + } + } + + /** + * Run the union operation + * + * @param {string} input + * @param {Object[]} args + * @returns {string} + * @throws {OperationError} + */ + run(input, args) { + [this.sampleDelim, this.itemDelimiter] = args; + const sets = input.split(this.sampleDelim); + + this.validateSampleNumbers(sets); + + return this.runUnion(...sets.map(s => s.split(this.itemDelimiter))); + } + + /** + * Get the union of the two sets. + * + * @param {Object[]} a + * @param {Object[]} b + * @returns {Object[]} + */ + runUnion(a, b) { + const result = {}; + + /** + * Only add non-existing items + * @param {Object} hash + */ + const addUnique = (hash) => (item) => { + if (!hash[item]) { + hash[item] = true; + } + }; + + a.map(addUnique(result)); + b.map(addUnique(result)); + + return Object.keys(result).join(this.itemDelimiter); + } +} + +export default SetUnion; diff --git a/plugins/srktoolbox/src/core/operations/Shake.mjs b/plugins/srktoolbox/src/core/operations/Shake.mjs new file mode 100644 index 00000000..31d4529d --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Shake.mjs @@ -0,0 +1,73 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import JSSHA3 from "js-sha3"; + +/** + * Shake operation + */ +class Shake extends Operation { + + /** + * Shake constructor + */ + constructor() { + super(); + + this.name = "Shake"; + this.module = "Crypto"; + this.description = "Shake是SHA-3算法的一个可扩展输出函数(Extendable Output Function, XOF),Keccak系列算法之一,允许可变的输出长度。"; + this.infoURL = "https://wikipedia.org/wiki/SHA-3#Instances"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "容量(Capacity)", + "type": "option", + "value": ["256", "128"] + }, + { + "name": "长度", + "type": "number", + "value": 512 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const capacity = parseInt(args[0], 10), + size = args[1]; + let algo; + + if (size < 0) + throw new OperationError("长度必须大于0"); + + switch (capacity) { + case 128: + algo = JSSHA3.shake128; + break; + case 256: + algo = JSSHA3.shake256; + break; + default: + throw new OperationError("无效长度"); + } + + return algo(input, size); + } + +} + +export default Shake; diff --git a/plugins/srktoolbox/src/core/operations/SharpenImage.mjs b/plugins/srktoolbox/src/core/operations/SharpenImage.mjs new file mode 100644 index 00000000..57cce59e --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/SharpenImage.mjs @@ -0,0 +1,203 @@ +/** + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { isImage } from "../lib/FileType.mjs"; +import { toBase64 } from "../lib/Base64.mjs"; +import { isWorkerEnvironment } from "../Utils.mjs"; +import { Jimp, JimpMime } from "jimp"; + +/** + * Sharpen Image operation + */ +class SharpenImage extends Operation { + /** + * SharpenImage constructor + */ + constructor() { + super(); + + this.name = "锐化图像"; + this.module = "Image"; + this.description = "锐化图像(钝化蒙版,USM)。"; + this.infoURL = "https://wikipedia.org/wiki/Unsharp_masking"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.presentType = "html"; + this.args = [ + { + name: "半径", + type: "number", + value: 2, + min: 1, + }, + { + name: "强度", + type: "number", + value: 1, + min: 0, + step: 0.1, + }, + { + name: "阈值", + type: "number", + value: 10, + min: 0, + max: 100, + }, + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {byteArray} + */ + async run(input, args) { + const [radius, amount, threshold] = args; + + if (!isImage(input)) { + throw new OperationError("无效的文件类型。"); + } + + let image; + try { + image = await Jimp.read(input); + } catch (err) { + throw new OperationError(`载入图像出错:(${err})`); + } + + try { + if (isWorkerEnvironment()) + self.sendStatusMessage("锐化图像……(复制图像)"); + const blurMask = image.clone(); + + if (isWorkerEnvironment()) + self.sendStatusMessage( + "锐化图像……(模糊图像)", + ); + const blurImage = image.clone().gaussian(radius); + + if (isWorkerEnvironment()) + self.sendStatusMessage( + "锐化图像……(生成钝化蒙版)", + ); + blurMask.scan( + 0, + 0, + blurMask.bitmap.width, + blurMask.bitmap.height, + function (x, y, idx) { + const blurRed = blurImage.bitmap.data[idx]; + const blurGreen = blurImage.bitmap.data[idx + 1]; + const blurBlue = blurImage.bitmap.data[idx + 2]; + + const normalRed = this.bitmap.data[idx]; + const normalGreen = this.bitmap.data[idx + 1]; + const normalBlue = this.bitmap.data[idx + 2]; + + // Subtract blurred pixel value from normal image + this.bitmap.data[idx] = + normalRed > blurRed ? normalRed - blurRed : 0; + this.bitmap.data[idx + 1] = + normalGreen > blurGreen ? normalGreen - blurGreen : 0; + this.bitmap.data[idx + 2] = + normalBlue > blurBlue ? normalBlue - blurBlue : 0; + }, + ); + + if (isWorkerEnvironment()) + self.sendStatusMessage( + "锐化图像……(蒙版合并)", + ); + image.scan( + 0, + 0, + image.bitmap.width, + image.bitmap.height, + function (x, y, idx) { + let maskRed = blurMask.bitmap.data[idx]; + let maskGreen = blurMask.bitmap.data[idx + 1]; + let maskBlue = blurMask.bitmap.data[idx + 2]; + + const normalRed = this.bitmap.data[idx]; + const normalGreen = this.bitmap.data[idx + 1]; + const normalBlue = this.bitmap.data[idx + 2]; + + // Calculate luminance + const maskLuminance = + 0.2126 * maskRed + + 0.7152 * maskGreen + + 0.0722 * maskBlue; + const normalLuminance = + 0.2126 * normalRed + + 0.7152 * normalGreen + + 0.0722 * normalBlue; + + let luminanceDiff; + if (maskLuminance > normalLuminance) { + luminanceDiff = maskLuminance - normalLuminance; + } else { + luminanceDiff = normalLuminance - maskLuminance; + } + + // Scale mask colours by amount + maskRed = maskRed * amount; + maskGreen = maskGreen * amount; + maskBlue = maskBlue * amount; + + // Only change pixel value if the difference is higher than threshold + if ((luminanceDiff / 255) * 100 >= threshold) { + this.bitmap.data[idx] = + normalRed + maskRed <= 255 ? + normalRed + maskRed : + 255; + this.bitmap.data[idx + 1] = + normalGreen + maskGreen <= 255 ? + normalGreen + maskGreen : + 255; + this.bitmap.data[idx + 2] = + normalBlue + maskBlue <= 255 ? + normalBlue + maskBlue : + 255; + } + }, + ); + + let imageBuffer; + if (image.mime === "image/gif") { + imageBuffer = await image.getBuffer(JimpMime.png); + } else { + imageBuffer = await image.getBuffer(image.mime); + } + return imageBuffer.buffer; + } catch (err) { + throw new OperationError(`锐化图像出错:(${err})`); + } + } + + /** + * Displays the sharpened image using HTML for web apps + * @param {ArrayBuffer} data + * @returns {html} + */ + present(data) { + if (!data.byteLength) return ""; + const dataArray = new Uint8Array(data); + + const type = isImage(dataArray); + if (!type) { + throw new OperationError("无效的文件类型。"); + } + + return ``; + } +} + +export default SharpenImage; diff --git a/plugins/srktoolbox/src/core/operations/ShowBase64Offsets.mjs b/plugins/srktoolbox/src/core/operations/ShowBase64Offsets.mjs new file mode 100644 index 00000000..1bb72d8f --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ShowBase64Offsets.mjs @@ -0,0 +1,175 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import {fromBase64, toBase64} from "../lib/Base64.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Show Base64 offsets operation + */ +class ShowBase64Offsets extends Operation { + + /** + * ShowBase64Offsets constructor + */ + constructor() { + super(); + + this.name = "显示Base64偏移形式"; + this.module = "Default"; + this.description = "当一个字符串被包含在其他数据中间一起被Base64编码的时候,根据字符串所在的位置,可能有三种不同的编码结果。

此操作会显示三种不同结果,用于后期匹配。"; + this.infoURL = "https://wikipedia.org/wiki/Base64#Output_padding"; + this.inputType = "byteArray"; + this.outputType = "html"; + this.args = [ + { + name: "可用字符", + type: "binaryString", + value: "A-Za-z0-9+/=" + }, + { + name: "显示可变字符与填充位", + type: "boolean", + value: true + }, + { + name: "输入格式", + type: "option", + value: ["原始字符串", "Base64"] + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + const [alphabet, showVariable, format] = args; + + if (format === "Base64") { + input = fromBase64(Utils.byteArrayToUtf8(input), null, "byteArray"); + } + + let offset0 = toBase64(input, alphabet), + offset1 = toBase64([0].concat(input), alphabet), + offset2 = toBase64([0, 0].concat(input), alphabet), + staticSection = "", + padding = ""; + + const len0 = offset0.indexOf("="), + len1 = offset1.indexOf("="), + len2 = offset2.indexOf("="), + script = ""; + + if (input.length < 1) { + throw new OperationError("请输入字符串"); + } + + // Highlight offset 0 + if (len0 % 4 === 2) { + staticSection = offset0.slice(0, -3); + offset0 = "" + + staticSection + "" + + "" + offset0.substr(offset0.length - 3, 1) + "" + + "" + offset0.substr(offset0.length - 2) + ""; + } else if (len0 % 4 === 3) { + staticSection = offset0.slice(0, -2); + offset0 = "" + + staticSection + "" + + "" + offset0.substr(offset0.length - 2, 1) + "" + + "" + offset0.substr(offset0.length - 1) + ""; + } else { + staticSection = offset0; + offset0 = "" + + staticSection + ""; + } + + if (!showVariable) { + offset0 = staticSection; + } + + + // Highlight offset 1 + padding = "" + offset1.substr(0, 1) + "" + + "" + offset1.substr(1, 1) + ""; + offset1 = offset1.substr(2); + if (len1 % 4 === 2) { + staticSection = offset1.slice(0, -3); + offset1 = padding + "" + + staticSection + "" + + "" + offset1.substr(offset1.length - 3, 1) + "" + + "" + offset1.substr(offset1.length - 2) + ""; + } else if (len1 % 4 === 3) { + staticSection = offset1.slice(0, -2); + offset1 = padding + "" + + staticSection + "" + + "" + offset1.substr(offset1.length - 2, 1) + "" + + "" + offset1.substr(offset1.length - 1) + ""; + } else { + staticSection = offset1; + offset1 = padding + "" + + staticSection + ""; + } + + if (!showVariable) { + offset1 = staticSection; + } + + // Highlight offset 2 + padding = "" + offset2.substr(0, 2) + "" + + "" + offset2.substr(2, 1) + ""; + offset2 = offset2.substr(3); + if (len2 % 4 === 2) { + staticSection = offset2.slice(0, -3); + offset2 = padding + "" + + staticSection + "" + + "" + offset2.substr(offset2.length - 3, 1) + "" + + "" + offset2.substr(offset2.length - 2) + ""; + } else if (len2 % 4 === 3) { + staticSection = offset2.slice(0, -2); + offset2 = padding + "" + + staticSection + "" + + "" + offset2.substr(offset2.length - 2, 1) + "" + + "" + offset2.substr(offset2.length - 1) + ""; + } else { + staticSection = offset2; + offset2 = padding + "" + + staticSection + ""; + } + + if (!showVariable) { + offset2 = staticSection; + } + + return (showVariable ? "绿色字符表示它根据前后数据的不同可能会发生变化。" + + "\n红色字符只是用于填充占位。" + + "\n无背景色的字符表示固定内容。" + + "\n鼠标放到固定内容部分查看此部分会解码成什么内容。\n" + + "\n偏移量0: " + offset0 + + "\n偏移量1: " + offset1 + + "\n偏移量2: " + offset2 + + script : + offset0 + "\n" + offset1 + "\n" + offset2); + } + +} + +export default ShowBase64Offsets; diff --git a/plugins/srktoolbox/src/core/operations/ShowOnMap.mjs b/plugins/srktoolbox/src/core/operations/ShowOnMap.mjs new file mode 100644 index 00000000..294527b3 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ShowOnMap.mjs @@ -0,0 +1,123 @@ +/** + * @author j433866 [j433866@gmail.com] + * @author 0xff1ce [github.com/0xff1ce] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import {FORMATS, convertCoordinates} from "../lib/ConvertCoordinates.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Show on map operation + */ +class ShowOnMap extends Operation { + + /** + * ShowOnMap constructor + */ + constructor() { + super(); + + this.name = "在地图上显示"; + this.module = "Hashing"; + this.description = "在网页地图上展示坐标位置

坐标会被转换成度数格式后在地图上显示。

支持的格式:
  • 度分秒 (DMS)
  • 度分 (DDM)
  • 度数 (DD)
  • Geohash
  • 军事格网参考系统 (MGRS)
  • 地形测量局国家格网参考系统 (OSNG)
  • 通用横轴墨卡托投影 (UTM)

此操作无法离线使用。"; + this.infoURL = "https://osmfoundation.org/wiki/Terms_of_Use"; + this.inputType = "string"; + this.outputType = "string"; + this.presentType = "html"; + this.args = [ + { + name: "缩放级别", + type: "number", + value: 13 + }, + { + name: "输入格式", + type: "option", + value: ["自动"].concat(FORMATS) + }, + { + name: "输入分隔符", + type: "option", + value: [ + "自动", + "方向在前", + "方向在后", + "\\n", + "逗号", + "分号", + "冒号" + ] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + if (input.replace(/\s+/g, "") !== "") { + const inFormat = args[1], + inDelim = args[2]; + let latLong; + try { + latLong = convertCoordinates(input, inFormat, inDelim, "度数", "逗号", "无", 5); + } catch (error) { + throw new OperationError(error); + } + latLong = latLong.replace(/[,]$/, ""); + latLong = latLong.replace(/°/g, ""); + return latLong; + } + return input; + } + + /** + * @param {string} data + * @param {Object[]} args + * @returns {string} + */ + async present(data, args) { + if (data.replace(/\s+/g, "") === "") { + data = "0, 0"; + } + const zoomLevel = args[0]; + const tileUrl = "https://tile.openstreetmap.org/{z}/{x}/{y}.png", + tileAttribution = "© OpenStreetMap contributors", + leafletUrl = "https://unpkg.com/leaflet@1.9.4/dist/leaflet.js", + leafletCssUrl = "https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"; + return ` + +
+`; + } +} + +export default ShowOnMap; diff --git a/plugins/srktoolbox/src/core/operations/Shuffle.mjs b/plugins/srktoolbox/src/core/operations/Shuffle.mjs new file mode 100644 index 00000000..c3596d77 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Shuffle.mjs @@ -0,0 +1,80 @@ +/** + * @author mikecat + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import {INPUT_DELIM_OPTIONS} from "../lib/Delim.mjs"; + +/** + * Shuffle operation + */ +class Shuffle extends Operation { + + /** + * Shuffle constructor + */ + constructor() { + super(); + + this.name = "乱序"; + this.module = "Default"; + this.description = "对输入元素随机重排。."; + this.infoURL = "https://wikipedia.org/wiki/Shuffling"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "分隔符", + type: "option", + value: INPUT_DELIM_OPTIONS + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const delim = Utils.charRep(args[0]); + if (input.length === 0) return input; + + // return a random number in [0, 1) + const rng = (typeof crypto) !== "undefined" && crypto.getRandomValues ? (function() { + const buf = new Uint32Array(2); + return function() { + // generate 53-bit random integer: 21 + 32 bits + crypto.getRandomValues(buf); + const value = (buf[0] >>> (32 - 21)) * ((1 << 30) * 4) + buf[1]; + return value / ((1 << 23) * (1 << 30)); + }; + })() : Math.random; + + // return a random integer in [0, max) + const randint = function(max) { + return Math.floor(rng() * max); + }; + + // Split input into shuffleable sections + const toShuffle = input.split(delim); + + // shuffle elements + for (let i = toShuffle.length - 1; i > 0; i--) { + const idx = randint(i + 1); + const tmp = toShuffle[idx]; + toShuffle[idx] = toShuffle[i]; + toShuffle[i] = tmp; + } + + return toShuffle.join(delim); + } + +} + +export default Shuffle; diff --git a/plugins/srktoolbox/src/core/operations/Sleep.mjs b/plugins/srktoolbox/src/core/operations/Sleep.mjs new file mode 100644 index 00000000..aa07cd93 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Sleep.mjs @@ -0,0 +1,49 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; + +/** + * Sleep operation + */ +class Sleep extends Operation { + + /** + * Sleep constructor + */ + constructor() { + super(); + + this.name = "Sleep"; + this.module = "Default"; + this.description = "Sleep操作让流程执行时在等待给定的毫秒数后继续。"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = [ + { + "name": "时长 (ms)", + "type": "number", + "value": 1000 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + async run(input, args) { + const ms = args[0]; + await new Promise(r => setTimeout(r, ms)); + return input; + } + +} + +export default Sleep; diff --git a/plugins/srktoolbox/src/core/operations/Snefru.mjs b/plugins/srktoolbox/src/core/operations/Snefru.mjs new file mode 100644 index 00000000..df9ec510 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Snefru.mjs @@ -0,0 +1,60 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import {runHash} from "../lib/Hash.mjs"; + +/** + * Snefru operation + */ +class Snefru extends Operation { + + /** + * Snefru constructor + */ + constructor() { + super(); + + this.name = "Snefru"; + this.module = "Crypto"; + this.description = "Snefru是由Ralph Merkle于1990年在Xerox PARC工作时设计的哈希算法。此算法支持128位和256位长度的输出。它的名字来源于埃及法老斯尼夫鲁(Sneferu),和该作者的Khufu和Khafre块加密算法采用了相同的命名方式。

Snefru原始设计的安全漏洞由Eli Biham和Adi Shamir发现,他们使用差分密码分析找到了哈希碰撞。后续该算法通过将计算轮数从2提高到8次来解决此问题。"; + this.infoURL = "https://wikipedia.org/wiki/Snefru"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + name: "长度", + type: "number", + value: 128, + min: 32, + max: 480, + step: 32 + }, + { + name: "轮数", + type: "option", + value: ["8", "4", "2"] + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return runHash("snefru", input, { + length: args[0], + rounds: args[1] + }); + } + +} + +export default Snefru; diff --git a/plugins/srktoolbox/src/core/operations/Sort.mjs b/plugins/srktoolbox/src/core/operations/Sort.mjs new file mode 100644 index 00000000..36ed155c --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Sort.mjs @@ -0,0 +1,80 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import {INPUT_DELIM_OPTIONS} from "../lib/Delim.mjs"; +import {caseInsensitiveSort, ipSort, numericSort, hexadecimalSort, lengthSort} from "../lib/Sort.mjs"; + +/** + * Sort operation + */ +class Sort extends Operation { + + /** + * Sort constructor + */ + constructor() { + super(); + + this.name = "排序"; + this.module = "Default"; + this.description = "按字母顺序对给定分隔符分隔的字符串进行排序。

IP地址排序仅限IPv4。"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "分隔符", + "type": "option", + "value": INPUT_DELIM_OPTIONS + }, + { + "name": "反向排序", + "type": "boolean", + "value": false + }, + { + "name": "顺序", + "type": "option", + "value": ["字母顺序(区分大小写)", "字母顺序(不区分大小写)", "IP地址", "数字", "数字(十六进制)", "长度"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const delim = Utils.charRep(args[0]), + sortReverse = args[1], + order = args[2]; + let sorted = input.split(delim); + + if (order === "字母顺序(区分大小写)") { + sorted = sorted.sort(); + } else if (order === "字母顺序(不区分大小写)") { + sorted = sorted.sort(caseInsensitiveSort); + } else if (order === "IP地址") { + sorted = sorted.sort(ipSort); + } else if (order === "数字") { + sorted = sorted.sort(numericSort); + } else if (order === "数字(十六进制)") { + sorted = sorted.sort(hexadecimalSort); + } else if (order === "长度") { + sorted = sorted.sort(lengthSort); + } + + if (sortReverse) sorted.reverse(); + return sorted.join(delim); + } + +} + +export default Sort; diff --git a/plugins/srktoolbox/src/core/operations/Split.mjs b/plugins/srktoolbox/src/core/operations/Split.mjs new file mode 100644 index 00000000..5c14d888 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Split.mjs @@ -0,0 +1,57 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import {SPLIT_DELIM_OPTIONS, JOIN_DELIM_OPTIONS} from "../lib/Delim.mjs"; + +/** + * Split operation + */ +class Split extends Operation { + + /** + * Split constructor + */ + constructor() { + super(); + + this.name = "拆分"; + this.module = "Default"; + this.description = "使用给定的分隔符拆分字符串。"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Split delimiter", + "type": "editableOptionShort", + "value": SPLIT_DELIM_OPTIONS + }, + { + "name": "Join delimiter", + "type": "editableOptionShort", + "value": JOIN_DELIM_OPTIONS + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const splitDelim = args[0], + joinDelim = args[1], + sections = input.split(splitDelim); + + return sections.join(joinDelim); + } + +} + +export default Split; diff --git a/plugins/srktoolbox/src/core/operations/SplitColourChannels.mjs b/plugins/srktoolbox/src/core/operations/SplitColourChannels.mjs new file mode 100644 index 00000000..7570f474 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/SplitColourChannels.mjs @@ -0,0 +1,130 @@ +/** + * @author Matt C [matt@artemisbot.uk] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { isImage } from "../lib/FileType.mjs"; +import { Jimp, JimpMime } from "jimp"; + +/** + * Split Colour Channels operation + */ +class SplitColourChannels extends Operation { + /** + * SplitColourChannels constructor + */ + constructor() { + super(); + + this.name = "色彩通道分离"; + this.module = "Image"; + this.description = + "将给定图像分离成红、绿、蓝色通道。"; + this.infoURL = "https://wikipedia.org/wiki/Channel_(digital_image)"; + this.inputType = "ArrayBuffer"; + this.outputType = "List"; + this.presentType = "html"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {List} + */ + async run(input, args) { + input = new Uint8Array(input); + // Make sure that the input is an image + if (!isImage(input)) throw new OperationError("无效的文件类型。"); + + const parsedImage = await Jimp.read(Buffer.from(input)); + + const red = new Promise(async (resolve, reject) => { + try { + const split = parsedImage + .clone() + .color([ + { apply: "blue", params: [-255] }, + { apply: "green", params: [-255] }, + ]) + .getBuffer(JimpMime.png); + resolve( + new File( + [new Uint8Array((await split).values())], + "红.png", + { type: "image/png" }, + ), + ); + } catch (err) { + reject( + new OperationError(`无法分离红色通道:${err}`), + ); + } + }); + + const green = new Promise(async (resolve, reject) => { + try { + const split = parsedImage + .clone() + .color([ + { apply: "red", params: [-255] }, + { apply: "blue", params: [-255] }, + ]) + .getBuffer(JimpMime.png); + resolve( + new File( + [new Uint8Array((await split).values())], + "绿.png", + { type: "image/png" }, + ), + ); + } catch (err) { + reject( + new OperationError(`无法分离绿色通道:${err}`), + ); + } + }); + + const blue = new Promise(async (resolve, reject) => { + try { + const split = parsedImage + .color([ + { apply: "red", params: [-255] }, + { apply: "green", params: [-255] }, + ]) + .getBuffer(JimpMime.png); + resolve( + new File( + [new Uint8Array((await split).values())], + "蓝.png", + { type: "image/png" }, + ), + ); + } catch (err) { + reject( + new OperationError(`无法分离蓝色通道:${err}`), + ); + } + }); + + return await Promise.all([red, green, blue]); + } + + /** + * Displays the files in HTML for web apps. + * + * @param {File[]} files + * @returns {html} + */ + async present(files) { + return await Utils.displayFilesAsHTML(files); + } +} + +export default SplitColourChannels; diff --git a/plugins/srktoolbox/src/core/operations/StandardDeviation.mjs b/plugins/srktoolbox/src/core/operations/StandardDeviation.mjs new file mode 100644 index 00000000..19cc997d --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/StandardDeviation.mjs @@ -0,0 +1,55 @@ +/** + * @author bwhitn [brian.m.whitney@outlook.com] + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import BigNumber from "bignumber.js"; +import Operation from "../Operation.mjs"; +import { stdDev, createNumArray } from "../lib/Arithmetic.mjs"; +import { ARITHMETIC_DELIM_OPTIONS } from "../lib/Delim.mjs"; + + +/** + * Standard Deviation operation + */ +class StandardDeviation extends Operation { + + /** + * StandardDeviation constructor + */ + constructor() { + super(); + + this.name = "求标准差"; + this.module = "Default"; + this.description = "对一组数字求标准差。非数字的值会被忽略。

例: 0x0a 8 .5 计算出 4.089281382128433"; + this.infoURL = "https://wikipedia.org/wiki/Standard_deviation"; + this.inputType = "string"; + this.outputType = "BigNumber"; + this.args = [ + { + "name": "分隔符", + "type": "option", + "value": ARITHMETIC_DELIM_OPTIONS, + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {BigNumber} + */ + run(input, args) { + const val = stdDev(createNumArray(input, args[0])); + return BigNumber.isBigNumber(val) ? val : new BigNumber(NaN); + + } + +} + +export default StandardDeviation; diff --git a/plugins/srktoolbox/src/core/operations/Streebog.mjs b/plugins/srktoolbox/src/core/operations/Streebog.mjs new file mode 100644 index 00000000..a9f106ca --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Streebog.mjs @@ -0,0 +1,65 @@ +/** + * @author mshwed [m@ttshwed.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import GostDigest from "../vendor/gost/gostDigest.mjs"; +import {toHexFast} from "../lib/Hex.mjs"; + +/** + * Streebog operation + */ +class Streebog extends Operation { + + /** + * Streebog constructor + */ + constructor() { + super(); + + this.name = "Streebog"; + this.module = "Hashing"; + this.description = "Streebog是俄罗斯国家标准GOST R 34.11-2012 Information Technology \u2013 Cryptographic Information Security \u2013 Hash Function定义的哈希算法。此算法设计的初衷为替换旧GOST哈希算法GOST R 34.11-94,同时也是为了对标由美国国安局发起的SHA-3。"; + this.infoURL = "https://wikipedia.org/wiki/Streebog"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "长度", + "type": "option", + "value": ["256", "512"] + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [length] = args; + + const algorithm = { + version: 2012, + mode: "HASH", + length: parseInt(length, 10) + }; + + try { + const gostDigest = new GostDigest(algorithm); + + return toHexFast(gostDigest.digest(input)); + } catch (err) { + throw new OperationError(err); + } + } + +} + +export default Streebog; diff --git a/plugins/srktoolbox/src/core/operations/Strings.mjs b/plugins/srktoolbox/src/core/operations/Strings.mjs new file mode 100644 index 00000000..1a9b0c2d --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Strings.mjs @@ -0,0 +1,141 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import XRegExp from "xregexp"; +import { search } from "../lib/Extract.mjs"; +import { caseInsensitiveSort } from "../lib/Sort.mjs"; + +/** + * Strings operation + */ +class Strings extends Operation { + + /** + * Strings constructor + */ + constructor() { + super(); + + this.name = "Strings"; + this.module = "Regex"; + this.description = "从输入中提取所有的字符串,类似Unix的strings工具。"; + this.infoURL = "https://wikipedia.org/wiki/Strings_(Unix)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "编码", + type: "option", + value: ["单字节", "16位小端序", "16位大端序", "所有"] + }, + { + name: "最短长度", + type: "number", + value: 4 + }, + { + name: "匹配类型", + type: "option", + value: [ + "[ASCII]", "字母数字+标点 (A)", "所有可打印字符 (A)", "C风格字符串 (A)", + "[Unicode]", "字母数字+标点 (U)", "所有可打印字符 (U)", "C风格字符串 (U)" + ] + }, + { + name: "显示总数", + type: "boolean", + value: false + }, + { + name: "排序", + type: "boolean", + value: false + }, + { + name: "去重", + type: "boolean", + value: false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [encoding, minLen, matchType, displayTotal, sort, unique] = args, + alphanumeric = "A-Z\\d", + punctuation = "/\\-:.,_$%'\"()<>= !\\[\\]{}@", + printable = "\x20-\x7e", + uniAlphanumeric = "\\pL\\pN", + uniPunctuation = "\\pP\\pZ", + uniPrintable = "\\pL\\pM\\pZ\\pS\\pN\\pP"; + + let strings = ""; + + switch (matchType) { + case "字母数字+标点 (A)": + strings = `[${alphanumeric + punctuation}]`; + break; + case "所有可打印字符 (A)": + case "C风格字符串 (A)": + strings = `[${printable}]`; + break; + case "字母数字+标点 (U)": + strings = `[${uniAlphanumeric + uniPunctuation}]`; + break; + case "所有可打印字符 (U)": + case "C风格字符串 (U)": + strings = `[${uniPrintable}]`; + break; + } + + // UTF-16 support is hacked in by allowing null bytes on either side of the matched chars + switch (encoding) { + case "所有": + strings = `(\x00?${strings}\x00?)`; + break; + case "16位小端序": + strings = `(${strings}\x00)`; + break; + case "16位大端序": + strings = `(\x00${strings})`; + break; + case "单字节": + default: + break; + } + + strings = `${strings}{${minLen},}`; + + if (matchType.includes("C风格字符串")) { + strings += "\x00"; + } + + const regex = new XRegExp(strings, "ig"); + const results = search( + input, + regex, + null, + sort ? caseInsensitiveSort : null, + unique + ); + + if (displayTotal) { + return `总计: ${results.length}\n\n${results.join("\n")}`; + } else { + return results.join("\n"); + } + } + +} + +export default Strings; diff --git a/plugins/srktoolbox/src/core/operations/StripHTMLTags.mjs b/plugins/srktoolbox/src/core/operations/StripHTMLTags.mjs new file mode 100644 index 00000000..858fda09 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/StripHTMLTags.mjs @@ -0,0 +1,74 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +/** + * Strip HTML tags operation + */ +class StripHTMLTags extends Operation { + + /** + * StripHTMLTags constructor + */ + constructor() { + super(); + + this.name = "移除HTML标签"; + this.module = "Default"; + this.description = "从输入中移除所有的HTML标签。"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "移除缩进", + "type": "boolean", + "value": true + }, + { + "name": "移除多余换行", + "type": "boolean", + "value": true + } + ]; + this.checks = [ + { + pattern: "(|
|)", + flags: "i", + args: [true, true] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [removeIndentation, removeLineBreaks] = args; + + input = Utils.stripHtmlTags(input); + + if (removeIndentation) { + input = input.replace(/\n[ \f\t]+/g, "\n"); + } + + if (removeLineBreaks) { + input = input + .replace(/^\s*\n/, "") // first line + .replace(/(\n\s*){2,}/g, "\n"); // all others + } + + return input; + } + +} + +export default StripHTMLTags; diff --git a/plugins/srktoolbox/src/core/operations/StripHTTPHeaders.mjs b/plugins/srktoolbox/src/core/operations/StripHTTPHeaders.mjs new file mode 100644 index 00000000..99d5efd7 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/StripHTTPHeaders.mjs @@ -0,0 +1,52 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; + +/** + * Strip HTTP headers operation + */ +class StripHTTPHeaders extends Operation { + + /** + * StripHTTPHeaders constructor + */ + constructor() { + super(); + + this.name = "移除HTTP标头"; + this.module = "Default"; + this.description = "从HTTP请求或响应文本中删除请求头或响应头,使用首次出现的两个换行符作为定位。"; + this.infoURL = "https://wikipedia.org/wiki/Hypertext_Transfer_Protocol#Message_format"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + this.checks = [ + { + pattern: "^HTTP(.|\\s)+?(\\r?\\n){2}", + flags: "", + args: [] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let headerEnd = input.indexOf("\r\n\r\n"); + headerEnd = (headerEnd < 0) ? input.indexOf("\n\n") + 2 : headerEnd + 4; + + return (headerEnd < 2) ? input : input.slice(headerEnd, input.length); + } + +} + +export default StripHTTPHeaders; diff --git a/plugins/srktoolbox/src/core/operations/StripIPv4Header.mjs b/plugins/srktoolbox/src/core/operations/StripIPv4Header.mjs new file mode 100644 index 00000000..63b0f6c2 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/StripIPv4Header.mjs @@ -0,0 +1,59 @@ +/** + * @author c65722 [] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Stream from "../lib/Stream.mjs"; + +/** + * Strip IPv4 header operation + */ +class StripIPv4Header extends Operation { + + /** + * StripIPv4Header constructor + */ + constructor() { + super(); + + this.name = "移除IPv4标头"; + this.module = "Default"; + this.description = "从IPv4数据包移除IPv4标头,只输出数据包载荷"; + this.infoURL = "https://wikipedia.org/wiki/IPv4"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + const MIN_HEADER_LEN = 20; + + const s = new Stream(new Uint8Array(input)); + if (s.length < MIN_HEADER_LEN) { + throw new OperationError("输入长度小于IPv4标头最小长度"); + } + + const ihl = s.readInt(1) & 0x0f; + const dataOffsetBytes = ihl * 4; + if (s.length < dataOffsetBytes) { + throw new OperationError("输入长度小于IHL"); + } + + s.moveTo(dataOffsetBytes); + + return s.getBytes().buffer; + } + +} + +export default StripIPv4Header; diff --git a/plugins/srktoolbox/src/core/operations/StripTCPHeader.mjs b/plugins/srktoolbox/src/core/operations/StripTCPHeader.mjs new file mode 100644 index 00000000..d6af87be --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/StripTCPHeader.mjs @@ -0,0 +1,62 @@ +/** + * @author c65722 [] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Stream from "../lib/Stream.mjs"; + +/** + * Strip TCP header operation + */ +class StripTCPHeader extends Operation { + + /** + * StripTCPHeader constructor + */ + constructor() { + super(); + + this.name = "移除TCP标头"; + this.module = "Default"; + this.description = "从TCP段移除TCP标头,只输出载荷。"; + this.infoURL = "https://wikipedia.org/wiki/Transmission_Control_Protocol"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + const MIN_HEADER_LEN = 20; + const DATA_OFFSET_OFFSET = 12; + const DATA_OFFSET_LEN_BITS = 4; + + const s = new Stream(new Uint8Array(input)); + if (s.length < MIN_HEADER_LEN) { + throw new OperationError("TCP标头长度至少20字节"); + } + + s.moveTo(DATA_OFFSET_OFFSET); + const dataOffsetWords = s.readBits(DATA_OFFSET_LEN_BITS); + const dataOffsetBytes = dataOffsetWords * 4; + if (s.length < dataOffsetBytes) { + throw new OperationError("输入长度小于数据偏移量"); + } + + s.moveTo(dataOffsetBytes); + + return s.getBytes().buffer; + } + +} + +export default StripTCPHeader; diff --git a/plugins/srktoolbox/src/core/operations/StripUDPHeader.mjs b/plugins/srktoolbox/src/core/operations/StripUDPHeader.mjs new file mode 100644 index 00000000..98b23d1c --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/StripUDPHeader.mjs @@ -0,0 +1,53 @@ +/** + * @author c65722 [] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Stream from "../lib/Stream.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Strip UDP header operation + */ +class StripUDPHeader extends Operation { + + /** + * StripUDPHeader constructor + */ + constructor() { + super(); + + this.name = "移除UDP标头"; + this.module = "Default"; + this.description = "从UDP数据报移除UDP标头,只输出载荷。"; + this.infoURL = "https://wikipedia.org/wiki/User_Datagram_Protocol"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + const HEADER_LEN = 8; + + const s = new Stream(new Uint8Array(input)); + if (s.length < HEADER_LEN) { + throw new OperationError("UDP标头长度至少8字节"); + } + + s.moveTo(HEADER_LEN); + + return s.getBytes().buffer; + } + +} + +export default StripUDPHeader; diff --git a/plugins/srktoolbox/src/core/operations/Subsection.mjs b/plugins/srktoolbox/src/core/operations/Subsection.mjs new file mode 100644 index 00000000..4e7086ff --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Subsection.mjs @@ -0,0 +1,163 @@ +/** + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Recipe from "../Recipe.mjs"; +import Dish from "../Dish.mjs"; + +/** + * Subsection operation + */ +class Subsection extends Operation { + + /** + * Subsection constructor + */ + constructor() { + super(); + + this.name = "Subsection"; + this.flowControl = true; + this.module = "Default"; + this.description = "使用正则对输入数据进行匹配,然后对匹配到的内容进行下面的所有操作。

最多只能使用一个捕获组,整个流程只会针对这个捕获组的匹配内容进行操作。如果有多个捕获组,只会使用第一个的内容。

使用Merge操作来重置Subsection操作的效果。"; + this.infoURL = ""; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "匹配内容(正则)", + "type": "string", + "value": "" + }, + { + "name": "大小写不敏感(i)", + "type": "boolean", + "value": true + }, + { + "name": "全局匹配(g)", + "type": "boolean", + "value": true + }, + { + "name": "忽略报错", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @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 + */ + async run(state) { + const opList = state.opList, + inputType = opList[state.progress].inputType, + outputType = opList[state.progress].outputType, + input = await state.dish.get(inputType), + ings = opList[state.progress].ingValues, + [section, caseSensitive, global, ignoreErrors] = ings, + subOpList = []; + + if (input && section !== "") { + // Set to 1 as if we are here, then there is one, the current one. + let numOp = 1; + // Create subOpList for each tranche to operate on + // all remaining operations unless we encounter a Merge + for (let i = state.progress + 1; i < opList.length; i++) { + if (opList[i].name === "Merge" && !opList[i].disabled) { + numOp--; + if (numOp === 0 || opList[i].ingValues[0]) + break; + else + // Not this subsection's Merge. + subOpList.push(opList[i]); + } else { + if (opList[i].name === "Fork" || opList[i].name === "Subsection") + numOp++; + subOpList.push(opList[i]); + } + } + + let flags = "", + inOffset = 0, + output = "", + m, + progress = 0; + + if (!caseSensitive) flags += "i"; + if (global) flags += "g"; + + const regex = new RegExp(section, flags), + recipe = new Recipe(); + + recipe.addOperations(subOpList); + state.forkOffset += state.progress + 1; + + // Take a deep(ish) copy of the ingredient values + const ingValues = subOpList.map(op => JSON.parse(JSON.stringify(op.ingValues))); + let matched = false; + + // Run recipe over each match + while ((m = regex.exec(input))) { + matched = true; + // Add up to match + let matchStr = m[0]; + + if (m.length === 1) { // No capture groups + output += input.slice(inOffset, m.index); + inOffset = m.index + m[0].length; + } else if (m.length >= 2) { + matchStr = m[1]; + + // Need to add some of the matched string that isn't in the capture group + output += input.slice(inOffset, m.index + m[0].indexOf(m[1])); + // Set i to be after the end of the first capture group + inOffset = m.index + m[0].indexOf(m[1]) + m[1].length; + } + + // Baseline ing values for each tranche so that registers are reset + recipe.opList.forEach((op, i) => { + op.ingValues = JSON.parse(JSON.stringify(ingValues[i])); + }); + + const dish = new Dish(); + dish.set(matchStr, inputType); + + try { + progress = await recipe.execute(dish, 0, state); + } catch (err) { + if (!ignoreErrors) { + throw err; + } + progress = err.progress + 1; + } + output += await dish.get(outputType); + if (!regex.global) break; + } + + // If no matches were found, advance progress to after a Merge op + // Otherwise, the operations below Subsection will be run on all the input data + if (!matched) { + state.progress += subOpList.length + 1; + } + + output += input.slice(inOffset); + state.progress += progress; + state.dish.set(output, outputType); + } + return state; + } + +} + +export default Subsection; diff --git a/plugins/srktoolbox/src/core/operations/Substitute.mjs b/plugins/srktoolbox/src/core/operations/Substitute.mjs new file mode 100644 index 00000000..901330d8 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Substitute.mjs @@ -0,0 +1,114 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +/** + * Substitute operation + */ +class Substitute extends Operation { + + /** + * Substitute constructor + */ + constructor() { + super(); + + this.name = "替换密码"; + this.module = "Default"; + this.description = "替换密码允许你将明文中的任意字节替换成其它字节。可以用来生成凯撒密码,但同时提供了更多的加密手段。

输入想要替换的内容在明文框,然后替换成的内容在密文框。

无法显示的字节可以用转义形式。例如换行可以写成 \\n\\x0a

用连字符来指定字节范围。例如 0123456789 可以写成 0-9

注意反斜杠是用来转义其它字符的,所以如果需要使用反斜杠,它自身也要被转义(例\\\\)。"; + this.infoURL = "https://wikipedia.org/wiki/Substitution_cipher"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "明文", + "type": "binaryString", + "value": "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + }, + { + "name": "密文", + "type": "binaryString", + "value": "XYZABCDEFGHIJKLMNOPQRSTUVW" + }, + { + "name": "忽略大小写", + "type": "boolean", + "value": false + } + ]; + } + + /** + * Convert a single character using the dictionary, if ignoreCase is true then + * check in the dictionary for both upper and lower case versions of the character. + * In output the input character case is preserved. + * @param {string} char + * @param {Object} dict + * @param {boolean} ignoreCase + * @returns {string} + */ + cipherSingleChar(char, dict, ignoreCase) { + if (!ignoreCase) + return dict[char] || char; + + const isUpperCase = char === char.toUpperCase(); + + // convert using the dictionary keeping the case of the input character + + if (dict[char] !== undefined) { + // if the character is in the dictionary return the value with the input case + return isUpperCase ? dict[char].toUpperCase() : dict[char].toLowerCase(); + } + + // check for the other case, if it is in the dictionary return the value with the right case + if (isUpperCase) { + if (dict[char.toLowerCase()] !== undefined) + return dict[char.toLowerCase()].toUpperCase(); + } else { + if (dict[char.toUpperCase()] !== undefined) + return dict[char.toUpperCase()].toLowerCase(); + } + + return char; + } + + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const plaintext = Utils.expandAlphRange([...args[0]]), + ciphertext = Utils.expandAlphRange([...args[1]]), + ignoreCase = args[2]; + let output = ""; + + if (plaintext.length !== ciphertext.length) { + output = "警告:明文和密文长度不同\n\n"; + } + + // create dictionary for conversion + const dict = {}; + for (let i = 0; i < Math.min(ciphertext.length, plaintext.length); i++) { + dict[plaintext[i]] = ciphertext[i]; + } + + // map every letter with the conversion function + for (const character of input) { + output += this.cipherSingleChar(character, dict, ignoreCase); + } + + return output; + } + +} + +export default Substitute; diff --git a/plugins/srktoolbox/src/core/operations/Subtract.mjs b/plugins/srktoolbox/src/core/operations/Subtract.mjs new file mode 100644 index 00000000..675cf818 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Subtract.mjs @@ -0,0 +1,54 @@ +/** + * @author bwhitn [brian.m.whitney@outlook.com] + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import BigNumber from "bignumber.js"; +import Operation from "../Operation.mjs"; +import { sub, createNumArray } from "../lib/Arithmetic.mjs"; +import { ARITHMETIC_DELIM_OPTIONS } from "../lib/Delim.mjs"; + + +/** + * Subtract operation + */ +class Subtract extends Operation { + + /** + * Subtract constructor + */ + constructor() { + super(); + + this.name = "求差"; + this.module = "Default"; + this.description = "对一组数字求差(aka. 减法)。非数字的值会被忽略。

例: 0x0a 8 .5 计算为 1.5"; + this.infoURL = "https://wikipedia.org/wiki/Subtraction"; + this.inputType = "string"; + this.outputType = "BigNumber"; + this.args = [ + { + "name": "分隔符", + "type": "option", + "value": ARITHMETIC_DELIM_OPTIONS, + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {BigNumber} + */ + run(input, args) { + const val = sub(createNumArray(input, args[0])); + return BigNumber.isBigNumber(val) ? val : new BigNumber(NaN); + } + +} + +export default Subtract; diff --git a/plugins/srktoolbox/src/core/operations/Sum.mjs b/plugins/srktoolbox/src/core/operations/Sum.mjs new file mode 100644 index 00000000..bf3cea78 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Sum.mjs @@ -0,0 +1,54 @@ +/** + * @author bwhitn [brian.m.whitney@outlook.com] + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import BigNumber from "bignumber.js"; +import Operation from "../Operation.mjs"; +import { sum, createNumArray } from "../lib/Arithmetic.mjs"; +import { ARITHMETIC_DELIM_OPTIONS } from "../lib/Delim.mjs"; + + +/** + * Sum operation + */ +class Sum extends Operation { + + /** + * Sum constructor + */ + constructor() { + super(); + + this.name = "求和"; + this.module = "Default"; + this.description = "对一组数字求和(aka. 加法)。非数字的值会被忽略。

例: 0x0a 8 .5 计算为 18.5"; + this.infoURL = "https://wikipedia.org/wiki/Summation"; + this.inputType = "string"; + this.outputType = "BigNumber"; + this.args = [ + { + "name": "分隔符", + "type": "option", + "value": ARITHMETIC_DELIM_OPTIONS, + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {BigNumber} + */ + run(input, args) { + const val = sum(createNumArray(input, args[0])); + return BigNumber.isBigNumber(val) ? val : new BigNumber(NaN); + } + +} + +export default Sum; diff --git a/plugins/srktoolbox/src/core/operations/SwapCase.mjs b/plugins/srktoolbox/src/core/operations/SwapCase.mjs new file mode 100644 index 00000000..7925e01e --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/SwapCase.mjs @@ -0,0 +1,78 @@ +/** + * @author mikecat + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; + +/** + * Swap case operation + */ +class SwapCase extends Operation { + + /** + * SwapCase constructor + */ + constructor() { + super(); + + this.name = "大小写互换"; + this.module = "Default"; + this.description = "大写转小写,小写转大写。"; + this.infoURL = ""; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let result = ""; + for (let i = 0; i < input.length; i++) { + const c = input.charAt(i); + const upper = c.toUpperCase(); + if (c === upper) { + result += c.toLowerCase(); + } else { + result += upper; + } + } + return result; + } + + /** + * Highlight Swap case + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight Swap case 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 SwapCase; diff --git a/plugins/srktoolbox/src/core/operations/SwapEndianness.mjs b/plugins/srktoolbox/src/core/operations/SwapEndianness.mjs new file mode 100644 index 00000000..c30aaa1c --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/SwapEndianness.mjs @@ -0,0 +1,140 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import {toHex, fromHex} from "../lib/Hex.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Swap endianness operation + */ +class SwapEndianness extends Operation { + + /** + * SwapEndianness constructor + */ + constructor() { + super(); + + this.name = "端序转换"; + this.module = "Default"; + this.description = "转换输入数据的大小端序。可以读取十六进制或原始文本。输出格式与输入保持一致。"; + this.infoURL = "https://wikipedia.org/wiki/Endianness"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "数据格式", + "type": "option", + "value": ["十六进制", "原始文本"] + }, + { + "name": "字长 (字节)", + "type": "number", + "value": 4 + }, + { + "name": "填充不完整字编码", + "type": "boolean", + "value": true + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [dataFormat, wordLength, padIncompleteWords] = args, + result = [], + words = []; + let i = 0, + j = 0, + data = []; + + if (wordLength <= 0) { + throw new OperationError("字长必须大于0"); + } + + // Convert input to raw data based on specified data format + switch (dataFormat) { + case "十六进制": + data = fromHex(input); + break; + case "原始文本": + data = Utils.strToByteArray(input); + break; + default: + data = input; + } + + // Split up into words + for (i = 0; i < data.length; i += wordLength) { + const word = data.slice(i, i + wordLength); + + // Pad word if too short + if (padIncompleteWords && word.length < wordLength) { + for (j = word.length; j < wordLength; j++) { + word.push(0); + } + } + + words.push(word); + } + + // Swap endianness and flatten + for (i = 0; i < words.length; i++) { + j = words[i].length; + while (j--) { + result.push(words[i][j]); + } + } + + // Convert data back to specified data format + switch (dataFormat) { + case "十六进制": + return toHex(result); + case "原始文本": + return Utils.byteArrayToUtf8(result); + default: + return result; + } + } + + /** + * Highlight Swap endianness + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight Swap endianness 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 SwapEndianness; diff --git a/plugins/srktoolbox/src/core/operations/SymmetricDifference.mjs b/plugins/srktoolbox/src/core/operations/SymmetricDifference.mjs new file mode 100644 index 00000000..ec78f7dd --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/SymmetricDifference.mjs @@ -0,0 +1,101 @@ +/** + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Utils from "../Utils.mjs"; +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Set Symmetric Difference operation + */ +class SymmetricDifference extends Operation { + + /** + * Symmetric Difference constructor + */ + constructor() { + super(); + + this.name = "对称差"; + this.module = "Default"; + this.description = "计算两个集合的对称差。"; + this.infoURL = "https://wikipedia.org/wiki/Symmetric_difference"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "集合分隔符", + type: "binaryString", + value: Utils.escapeHtml("\\n\\n") + }, + { + name: "元素分隔符", + type: "binaryString", + value: "," + }, + ]; + } + + /** + * Validate input length + * + * @param {Object[]} sets + * @throws {Error} if not two sets + */ + validateSampleNumbers(sets) { + if (!sets || (sets.length !== 2)) { + throw new OperationError("集合数量错误,你可能需要调整集合分隔符或者添加一些数据。"); + } + } + + /** + * Run the difference operation + * + * @param {string} input + * @param {Object[]} args + * @returns {string} + * @throws {OperationError} + */ + run(input, args) { + [this.sampleDelim, this.itemDelimiter] = args; + const sets = input.split(this.sampleDelim); + + this.validateSampleNumbers(sets); + + return this.runSymmetricDifference(...sets.map(s => s.split(this.itemDelimiter))); + } + + /** + * Get elements in set a that are not in set b + * + * @param {Object[]} a + * @param {Object[]} b + * @returns {Object[]} + */ + runSetDifference(a, b) { + return a.filter((item) => { + return b.indexOf(item) === -1; + }); + } + + /** + * Get elements of each set that aren't in the other set. + * + * @param {Object[]} a + * @param {Object[]} b + * @return {Object[]} + */ + runSymmetricDifference(a, b) { + return this.runSetDifference(a, b) + .concat(this.runSetDifference(b, a)) + .join(this.itemDelimiter); + } + +} + +export default SymmetricDifference; diff --git a/plugins/srktoolbox/src/core/operations/SyntaxHighlighter.mjs b/plugins/srktoolbox/src/core/operations/SyntaxHighlighter.mjs new file mode 100644 index 00000000..b336bc23 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/SyntaxHighlighter.mjs @@ -0,0 +1,81 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import hljs from "highlight.js"; + +/** + * Syntax highlighter operation + */ +class SyntaxHighlighter extends Operation { + + /** + * SyntaxHighlighter constructor + */ + constructor() { + super(); + + this.name = "语法高亮"; + this.module = "Code"; + this.description = "为多种编程语言添加语法高亮。注意:此操作不会对代码进行缩进,需配合使用对应的“美化(Beautify)”操作。"; + this.infoURL = "https://wikipedia.org/wiki/Syntax_highlighting"; + this.inputType = "string"; + this.outputType = "html"; + this.args = [ + { + "name": "语言", + "type": "option", + "value": ["自动检测"].concat(hljs.listLanguages()) + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + const language = args[0]; + + if (language === "自动检测") { + return hljs.highlightAuto(input).value; + } + + return hljs.highlight(language, input, true).value; + } + + /** + * Highlight Syntax highlighter + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight Syntax highlighter 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 SyntaxHighlighter; diff --git a/plugins/srktoolbox/src/core/operations/TCPIPChecksum.mjs b/plugins/srktoolbox/src/core/operations/TCPIPChecksum.mjs new file mode 100644 index 00000000..368247c5 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/TCPIPChecksum.mjs @@ -0,0 +1,56 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +/** + * TCP/IP Checksum operation + */ +class TCPIPChecksum extends Operation { + + /** + * TCPIPChecksum constructor + */ + constructor() { + super(); + + this.name = "TCP/IP校验和"; + this.module = "Crypto"; + this.description = "从输入的原始字节内容计算TCP (Transport Control Protocol)或IP (Internet Protocol)标头的校验和。"; + this.infoURL = "https://wikipedia.org/wiki/IPv4_header_checksum"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + input = new Uint8Array(input); + let csum = 0; + + for (let i = 0; i < input.length; i++) { + if (i % 2 === 0) { + csum += (input[i] << 8); + } else { + csum += input[i]; + } + } + + csum = (csum >> 16) + (csum & 0xffff); + + return Utils.hex(0xffff - csum); + } + +} + +export default TCPIPChecksum; diff --git a/plugins/srktoolbox/src/core/operations/Tail.mjs b/plugins/srktoolbox/src/core/operations/Tail.mjs new file mode 100644 index 00000000..de2bd703 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Tail.mjs @@ -0,0 +1,72 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import {INPUT_DELIM_OPTIONS} from "../lib/Delim.mjs"; + +/** + * Tail operation + */ +class Tail extends Operation { + + /** + * Tail constructor + */ + constructor() { + super(); + + this.name = "Tail"; + this.module = "Default"; + this.description = "和UNIX的tail工具类似。
输出后n行。
输入负数可以选取除最前n行之外的内容。
可以选择不同的分隔符来实现选取最后n个数据。"; + this.infoURL = "https://wikipedia.org/wiki/Tail_(Unix)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "分隔符", + "type": "option", + "value": INPUT_DELIM_OPTIONS + }, + { + "name": "选取数量", + "type": "number", + "value": 10 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let delimiter = args[0]; + const number = args[1]; + + delimiter = Utils.charRep(delimiter); + const splitInput = input.split(delimiter); + + return splitInput + .filter((line, lineIndex) => { + lineIndex += 1; + + if (number < 0) { + return lineIndex > -number; + } else { + return lineIndex > splitInput.length - number; + } + }) + .join(delimiter); + + } + +} + +export default Tail; diff --git a/plugins/srktoolbox/src/core/operations/TakeBytes.mjs b/plugins/srktoolbox/src/core/operations/TakeBytes.mjs new file mode 100644 index 00000000..51f2ce84 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/TakeBytes.mjs @@ -0,0 +1,119 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; + +/** + * Take bytes operation + */ +class TakeBytes extends Operation { + + /** + * TakeBytes constructor + */ + constructor() { + super(); + + this.name = "提取字节"; + this.module = "Default"; + this.description = "从数据中提取特定数量的字节。允许使用负数值。"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = [ + { + "name": "起始位置", + "type": "number", + "value": 0 + }, + { + "name": "长度", + "type": "number", + "value": 5 + }, + { + "name": "对每一行单独进行提取", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + * + * @throws {OperationError} if invalid value + */ + run(input, args) { + let start = args[0], + length = args[1]; + const applyToEachLine = args[2]; + + if (!applyToEachLine) { + if (start < 0) { // Take from the end + start = input.byteLength + start; + } + + if (length < 0) { // Flip start point + start = start + length; + if (start < 0) { + start = input.byteLength + start; + length = start - length; + } else { + length = -length; + } + } + + return input.slice(start, start+length); + } + + // Split input into lines + const data = new Uint8Array(input); + const lines = []; + let line = [], + i; + + for (i = 0; i < data.length; i++) { + if (data[i] === 0x0a) { + lines.push(line); + line = []; + } else { + line.push(data[i]); + } + } + lines.push(line); + + let output = []; + let s = start, + l = length; + for (i = 0; i < lines.length; i++) { + if (s < 0) { // Take from the end + s = lines[i].length + s; + } + + if (l < 0) { // Flip start point + s = s + l; + if (s < 0) { + s = lines[i].length + s; + l = s - l; + } else { + l = -l; + } + } + output = output.concat(lines[i].slice(s, s+l)); + output.push(0x0a); + s = start; + l = length; + } + return new Uint8Array(output.slice(0, output.length-1)).buffer; + } + +} + +export default TakeBytes; diff --git a/plugins/srktoolbox/src/core/operations/TakeNthBytes.mjs b/plugins/srktoolbox/src/core/operations/TakeNthBytes.mjs new file mode 100644 index 00000000..e0c13d3e --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/TakeNthBytes.mjs @@ -0,0 +1,81 @@ +/** + * @author Oshawk [oshawk@protonmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Take nth bytes operation + */ +class TakeNthBytes extends Operation { + + /** + * TakeNthBytes constructor + */ + constructor() { + super(); + + this.name = "提取每N个字节"; + this.module = "Default"; + this.description = "提取输入内容的每N个字节。"; + this.infoURL = ""; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = [ + { + name: "提取每N个字节", + type: "number", + value: 4 + }, + { + name: "起始位置", + type: "number", + value: 0 + }, + { + name: "应用到每一行", + type: "boolean", + value: false + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const n = args[0]; + const start = args[1]; + const eachLine = args[2]; + + if (parseInt(n, 10) !== n || n <= 0) { + throw new OperationError("'提取每N个字节'必须为正整数"); + } + if (parseInt(start, 10) !== start || start < 0) { + throw new OperationError("'起始位置'必须为非负整数"); + } + + let offset = 0; + const output = []; + for (let i = 0; i < input.length; i++) { + if (eachLine && input[i] === 0x0a) { + output.push(0x0a); + offset = i + 1; + } else if (i - offset >= start && (i - (start + offset)) % n === 0) { + output.push(input[i]); + } + } + + return output; + } + +} + +export default TakeNthBytes; diff --git a/plugins/srktoolbox/src/core/operations/Tar.mjs b/plugins/srktoolbox/src/core/operations/Tar.mjs new file mode 100644 index 00000000..8d44b430 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Tar.mjs @@ -0,0 +1,144 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +/** + * Tar operation + */ +class Tar extends Operation { + + /** + * Tar constructor + */ + constructor() { + super(); + + this.name = "Tar"; + this.module = "Compression"; + this.description = "把输入打包成tarball。

目前不支持多个文件。"; + this.infoURL = "https://wikipedia.org/wiki/Tar_(computing)"; + this.inputType = "ArrayBuffer"; + this.outputType = "File"; + this.args = [ + { + "name": "文件名", + "type": "string", + "value": "file.txt" + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + input = new Uint8Array(input); + + const Tarball = function() { + this.bytes = new Array(512); + this.position = 0; + }; + + Tarball.prototype.addEmptyBlock = function() { + const filler = new Array(512); + filler.fill(0); + this.bytes = this.bytes.concat(filler); + }; + + Tarball.prototype.writeBytes = function(bytes) { + const self = this; + + if (this.position + bytes.length > this.bytes.length) { + this.addEmptyBlock(); + } + + Array.prototype.forEach.call(bytes, function(b, i) { + if (typeof b.charCodeAt !== "undefined") { + b = b.charCodeAt(); + } + + self.bytes[self.position] = b; + self.position += 1; + }); + }; + + Tarball.prototype.writeEndBlocks = function() { + const numEmptyBlocks = 2; + for (let i = 0; i < numEmptyBlocks; i++) { + this.addEmptyBlock(); + } + }; + + const fileSize = input.length.toString(8).padStart(11, "0"); + const currentUnixTimestamp = Math.floor(Date.now() / 1000); + const lastModTime = currentUnixTimestamp.toString(8).padStart(11, "0"); + + const file = { + fileName: Utils.padBytesRight(args[0], 100), + fileMode: Utils.padBytesRight("0000664", 8), + ownerUID: Utils.padBytesRight("0", 8), + ownerGID: Utils.padBytesRight("0", 8), + size: Utils.padBytesRight(fileSize, 12), + lastModTime: Utils.padBytesRight(lastModTime, 12), + checksum: " ", + type: "0", + linkedFileName: Utils.padBytesRight("", 100), + USTARFormat: Utils.padBytesRight("ustar", 6), + version: "00", + ownerUserName: Utils.padBytesRight("", 32), + ownerGroupName: Utils.padBytesRight("", 32), + deviceMajor: Utils.padBytesRight("", 8), + deviceMinor: Utils.padBytesRight("", 8), + fileNamePrefix: Utils.padBytesRight("", 155), + }; + + let checksum = 0; + for (const key in file) { + const bytes = file[key]; + Array.prototype.forEach.call(bytes, function(b) { + if (typeof b.charCodeAt !== "undefined") { + checksum += b.charCodeAt(); + } else { + checksum += b; + } + }); + } + checksum = Utils.padBytesRight(checksum.toString(8).padStart(7, "0"), 8); + file.checksum = checksum; + + const tarball = new Tarball(); + tarball.writeBytes(file.fileName); + tarball.writeBytes(file.fileMode); + tarball.writeBytes(file.ownerUID); + tarball.writeBytes(file.ownerGID); + tarball.writeBytes(file.size); + tarball.writeBytes(file.lastModTime); + tarball.writeBytes(file.checksum); + tarball.writeBytes(file.type); + tarball.writeBytes(file.linkedFileName); + tarball.writeBytes(file.USTARFormat); + tarball.writeBytes(file.version); + tarball.writeBytes(file.ownerUserName); + tarball.writeBytes(file.ownerGroupName); + tarball.writeBytes(file.deviceMajor); + tarball.writeBytes(file.deviceMinor); + tarball.writeBytes(file.fileNamePrefix); + tarball.writeBytes(Utils.padBytesRight("", 12)); + tarball.writeBytes(input); + tarball.writeEndBlocks(); + + return new File([new Uint8Array(tarball.bytes)], args[0]); + } + +} + +export default Tar; diff --git a/plugins/srktoolbox/src/core/operations/Template.mjs b/plugins/srktoolbox/src/core/operations/Template.mjs new file mode 100644 index 00000000..13210943 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Template.mjs @@ -0,0 +1,55 @@ +/** + * @author kendallgoto [k@kgo.to] + * @copyright Crown Copyright 2025 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Handlebars from "handlebars"; + +/** + * Template operation + */ +class Template extends Operation { + + /** + * Template constructor + */ + constructor() { + super(); + + this.name = "模板渲染"; + this.module = "Handlebars"; + this.description = "使用Handlebars/Mustache模板引擎,通过JSON输入数据替换变量并渲染模板。模板渲染结果将仅输出纯文本,以防止跨站脚本攻击。"; + this.infoURL = "https://handlebarsjs.com/"; + this.inputType = "JSON"; + this.outputType = "string"; + this.args = [ + { + name: "模板定义(.handlebars)", + type: "text", + value: "" + } + ]; + } + + /** + * @param {JSON} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [templateStr] = args; + try { + const template = Handlebars.compile(templateStr); + return template(input); + } catch (e) { + throw new OperationError(e); + } + } +} + +export default Template; diff --git a/plugins/srktoolbox/src/core/operations/TextEncodingBruteForce.mjs b/plugins/srktoolbox/src/core/operations/TextEncodingBruteForce.mjs new file mode 100644 index 00000000..56d25928 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/TextEncodingBruteForce.mjs @@ -0,0 +1,94 @@ +/** + * @author Cynser + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import cptable from "codepage"; +import {CHR_ENC_CODE_PAGES} from "../lib/ChrEnc.mjs"; + +/** + * Text Encoding Brute Force operation + */ +class TextEncodingBruteForce extends Operation { + + /** + * TextEncodingBruteForce constructor + */ + constructor() { + super(); + + this.name = "文本编码暴力破解"; + this.module = "Encodings"; + this.description = [ + "列出所有支持的字符集解码/编码结果,让你可以看出哪个是正确的。", + "

", + "支持的字符集:", + "
    ", + Object.keys(CHR_ENC_CODE_PAGES).map(e => `
  • ${e}
  • `).join("\n"), + "
" + ].join("\n"); + this.infoURL = "https://wikipedia.org/wiki/Character_encoding"; + this.inputType = "string"; + this.outputType = "json"; + this.presentType = "html"; + this.args = [ + { + name: "模式", + type: "option", + value: ["编码", "解码"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {json} + */ + run(input, args) { + const output = {}, + charsets = Object.keys(CHR_ENC_CODE_PAGES), + mode = args[0]; + + charsets.forEach(charset => { + try { + if (mode === "解码") { + output[charset] = cptable.utils.decode(CHR_ENC_CODE_PAGES[charset], input); + } else { + output[charset] = Utils.arrayBufferToStr(cptable.utils.encode(CHR_ENC_CODE_PAGES[charset], input)); + } + } catch (err) { + output[charset] = "无法解码"; + } + }); + + return output; + } + + /** + * Displays the encodings in an HTML table for web apps. + * + * @param {Object[]} encodings + * @returns {html} + */ + present(encodings) { + let table = ""; + + for (const enc in encodings) { + const value = Utils.escapeHtml(Utils.escapeWhitespace(encodings[enc])); + table += ``; + } + + table += "
字符集结果
${enc}${value}
"; + return table; + } + +} + +export default TextEncodingBruteForce; diff --git a/plugins/srktoolbox/src/core/operations/ToBCD.mjs b/plugins/srktoolbox/src/core/operations/ToBCD.mjs new file mode 100644 index 00000000..9f6f14ad --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ToBCD.mjs @@ -0,0 +1,144 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import {ENCODING_SCHEME, ENCODING_LOOKUP, FORMAT} from "../lib/BCD.mjs"; +import BigNumber from "bignumber.js"; + +/** + * To BCD operation + */ +class ToBCD extends Operation { + + /** + * ToBCD constructor + */ + constructor() { + super(); + + this.name = "BCD码编码"; + this.module = "Default"; + this.description = "BCD码(Binary-Coded Decimal)是一种十进制数字编码的形式。在这种编码下,每个十进制数字用一串单独的二进制比特来存储与表示。常见的有以4位或8位表示1个十进制数字。有时会用特殊的码位表示特殊符号。"; + this.infoURL = "https://wikipedia.org/wiki/Binary-coded_decimal"; + this.inputType = "BigNumber"; + this.outputType = "string"; + this.args = [ + { + "name": "编码方式", + "type": "option", + "value": ENCODING_SCHEME + }, + { + "name": "压缩", + "type": "boolean", + "value": true + }, + { + "name": "有符号", + "type": "boolean", + "value": false + }, + { + "name": "输出格式", + "type": "option", + "value": FORMAT + } + ]; + } + + /** + * @param {BigNumber} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + if (input.isNaN()) + throw new OperationError("无效输入"); + if (!input.integerValue(BigNumber.ROUND_DOWN).isEqualTo(input)) + throw new OperationError("BCD不支持非整数"); + + const encoding = ENCODING_LOOKUP[args[0]], + packed = args[1], + signed = args[2], + outputFormat = args[3]; + + // Split input number up into separate digits + const digits = input.toFixed().split(""); + + if (digits[0] === "-" || digits[0] === "+") { + digits.shift(); + } + + let nibbles = []; + + digits.forEach(d => { + const n = parseInt(d, 10); + nibbles.push(encoding[n]); + }); + + if (signed) { + if (packed && digits.length % 2 === 0) { + // If there are an even number of digits, we add a leading 0 so + // that the sign nibble doesn't sit in its own byte, leading to + // ambiguity around whether the number ends with a 0 or not. + nibbles.unshift(encoding[0]); + } + + nibbles.push(input > 0 ? 12 : 13); + // 12 ("C") for + (credit) + // 13 ("D") for - (debit) + } + + let bytes = []; + + if (packed) { + let encoded = 0, + little = false; + + nibbles.forEach(n => { + encoded ^= little ? n : (n << 4); + if (little) { + bytes.push(encoded); + encoded = 0; + } + little = !little; + }); + + if (little) bytes.push(encoded); + } else { + bytes = nibbles; + + // Add null high nibbles + nibbles = nibbles.map(n => { + return [0, n]; + }).reduce((a, b) => { + return a.concat(b); + }); + } + + // Output + switch (outputFormat) { + case "半字节": + return nibbles.map(n => { + return n.toString(2).padStart(4, "0"); + }).join(" "); + case "字节": + return bytes.map(b => { + return b.toString(2).padStart(8, "0"); + }).join(" "); + case "原始数据": + default: + return Utils.byteArrayToChars(bytes); + } + } + +} + +export default ToBCD; diff --git a/plugins/srktoolbox/src/core/operations/ToBase.mjs b/plugins/srktoolbox/src/core/operations/ToBase.mjs new file mode 100644 index 00000000..6f2afbbe --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ToBase.mjs @@ -0,0 +1,56 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * To Base operation + */ +class ToBase extends Operation { + + /** + * ToBase constructor + */ + constructor() { + super(); + + this.name = "十进制数转其它进制"; + this.module = "Default"; + this.description = "把十进制数转换成其它给定进制"; + this.infoURL = "https://wikipedia.org/wiki/Radix"; + this.inputType = "BigNumber"; + this.outputType = "string"; + this.args = [ + { + "name": "进制", + "type": "number", + "value": 36 + } + ]; + } + + /** + * @param {BigNumber} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + if (!input) { + throw new OperationError("错误:输入必须是数字"); + } + const radix = args[0]; + if (radix < 2 || radix > 36) { + throw new OperationError("错误:进制必须在2~36之间"); + } + return input.toString(radix); + } + +} + +export default ToBase; diff --git a/plugins/srktoolbox/src/core/operations/ToBase32.mjs b/plugins/srktoolbox/src/core/operations/ToBase32.mjs new file mode 100644 index 00000000..8a4c048a --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ToBase32.mjs @@ -0,0 +1,89 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import {ALPHABET_OPTIONS} from "../lib/Base32.mjs"; + +/** + * To Base32 operation + */ +class ToBase32 extends Operation { + + /** + * ToBase32 constructor + */ + constructor() { + super(); + + this.name = "Base32编码"; + this.module = "Default"; + this.description = "Base32是把字节数据转换成特定字符组合的编码方式,编码后便于人类阅读,也方便计算机读取。Base32比Base64使用的字母表小一些,通常只包含大写字母和数字2到7。"; + this.infoURL = "https://wikipedia.org/wiki/Base32"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + name: "可用字符", + type: "editableOption", + value: ALPHABET_OPTIONS + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + if (!input) return ""; + input = new Uint8Array(input); + + const alphabet = args[0] ? Utils.expandAlphRange(args[0]).join("") : "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567="; + let output = "", + chr1, chr2, chr3, chr4, chr5, + enc1, enc2, enc3, enc4, enc5, enc6, enc7, enc8, + i = 0; + while (i < input.length) { + chr1 = input[i++]; + chr2 = input[i++]; + chr3 = input[i++]; + chr4 = input[i++]; + chr5 = input[i++]; + + enc1 = chr1 >> 3; + enc2 = ((chr1 & 7) << 2) | (chr2 >> 6); + enc3 = (chr2 >> 1) & 31; + enc4 = ((chr2 & 1) << 4) | (chr3 >> 4); + enc5 = ((chr3 & 15) << 1) | (chr4 >> 7); + enc6 = (chr4 >> 2) & 31; + enc7 = ((chr4 & 3) << 3) | (chr5 >> 5); + enc8 = chr5 & 31; + + if (isNaN(chr2)) { + enc3 = enc4 = enc5 = enc6 = enc7 = enc8 = 32; + } else if (isNaN(chr3)) { + enc5 = enc6 = enc7 = enc8 = 32; + } else if (isNaN(chr4)) { + enc6 = enc7 = enc8 = 32; + } else if (isNaN(chr5)) { + enc8 = 32; + } + + output += alphabet.charAt(enc1) + alphabet.charAt(enc2) + alphabet.charAt(enc3) + + alphabet.charAt(enc4) + alphabet.charAt(enc5) + alphabet.charAt(enc6) + + alphabet.charAt(enc7) + alphabet.charAt(enc8); + } + return output; + } + +} + +export default ToBase32; + diff --git a/plugins/srktoolbox/src/core/operations/ToBase45.mjs b/plugins/srktoolbox/src/core/operations/ToBase45.mjs new file mode 100644 index 00000000..e99f5cda --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ToBase45.mjs @@ -0,0 +1,84 @@ +/** + * @author Thomas Weißschuh [thomas@t-8ch.de] + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import {ALPHABET, highlightToBase45, highlightFromBase45} from "../lib/Base45.mjs"; +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +/** + * To Base45 operation + */ +class ToBase45 extends Operation { + + /** + * ToBase45 constructor + */ + constructor() { + super(); + + this.name = "Base45编码"; + this.module = "Default"; + this.description = "Base45是把字节数据转换成特定字符组合的编码方式,编码后便于人类阅读,也方便计算机读取。越高的Base数目会生成越短的字符串。Base45是为二维码优化的编码方式。"; + this.infoURL = "https://wikipedia.org/wiki/List_of_numeral_systems"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + name: "可用字符", + type: "string", + value: ALPHABET + } + ]; + + this.highlight = highlightToBase45; + this.highlightReverse = highlightFromBase45; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + if (!input) return ""; + input = new Uint8Array(input); + const alphabet = Utils.expandAlphRange(args[0]); + + const res = []; + + for (const pair of Utils.chunked(input, 2)) { + let b = 0; + for (const e of pair) { + b *= 256; + b += e; + } + + let chars = 0; + do { + res.push(alphabet[b % 45]); + chars++; + b = Math.floor(b / 45); + } while (b > 0); + + if (chars < 2) { + res.push("0"); + chars++; + } + if (pair.length > 1 && chars < 3) { + res.push("0"); + } + } + + + return res.join(""); + + } + +} + +export default ToBase45; diff --git a/plugins/srktoolbox/src/core/operations/ToBase58.mjs b/plugins/srktoolbox/src/core/operations/ToBase58.mjs new file mode 100644 index 00000000..57da9ee4 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ToBase58.mjs @@ -0,0 +1,92 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import {ALPHABET_OPTIONS} from "../lib/Base58.mjs"; + +/** + * To Base58 operation + */ +class ToBase58 extends Operation { + + /** + * ToBase58 constructor + */ + constructor() { + super(); + + this.name = "Base58编码"; + this.module = "Default"; + this.description = "Base58(类似于Base64)是把字节数据转换成特定字符组合的编码方式。和Base64的区别是移除了形状相近的易混字符(例如l、I、0和O)来提高可读性。

此操作将原始数据编码成使用ASCII字符的Base64字符串。

例: hello world 编码为 StV1DL6CwTryKyV

Base58常见于加密货币(比特币、Ripple等)。"; + this.infoURL = "https://wikipedia.org/wiki/Base58"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "可用字符", + "type": "editableOption", + "value": ALPHABET_OPTIONS + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + input = new Uint8Array(input); + let alphabet = args[0] || ALPHABET_OPTIONS[0].value, + result = []; + + alphabet = Utils.expandAlphRange(alphabet).join(""); + + if (alphabet.length !== 58 || + [].unique.call(alphabet).length !== 58) { + throw new OperationError("错误:可用字符必须是58个"); + } + + if (input.length === 0) return ""; + + let zeroPrefix = 0; + for (let i = 0; i < input.length && input[i] === 0; i++) { + zeroPrefix++; + } + + input.forEach(function(b) { + let carry = b; + + for (let i = 0; i < result.length; i++) { + carry += result[i] << 8; + result[i] = carry % 58; + carry = (carry / 58) | 0; + } + + while (carry > 0) { + result.push(carry % 58); + carry = (carry / 58) | 0; + } + }); + + result = result.map(function(b) { + return alphabet[b]; + }).reverse().join(""); + + while (zeroPrefix--) { + result = alphabet[0] + result; + } + + return result; + } + +} + +export default ToBase58; diff --git a/plugins/srktoolbox/src/core/operations/ToBase62.mjs b/plugins/srktoolbox/src/core/operations/ToBase62.mjs new file mode 100644 index 00000000..e94c8343 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ToBase62.mjs @@ -0,0 +1,64 @@ +/** + * @author tcode2k16 [tcode2k16@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import BigNumber from "bignumber.js"; +import Utils from "../Utils.mjs"; +import {toHexFast} from "../lib/Hex.mjs"; + +/** + * To Base62 operation + */ +class ToBase62 extends Operation { + + /** + * ToBase62 constructor + */ + constructor() { + super(); + + this.name = "Base62编码"; + this.module = "Default"; + this.description = "Base62是把字节数据转换成特定字符组合的编码方式,编码后便于人类阅读,也方便计算机读取。

此操作将原始数据编码成使用ASCII字符的Base62字符串。"; + this.infoURL = "https://wikipedia.org/wiki/List_of_numeral_systems"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + name: "可用字符", + type: "string", + value: "0-9A-Za-z" + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + input = new Uint8Array(input); + if (input.length < 1) return ""; + + const alphabet = Utils.expandAlphRange(args[0]).join(""); + const BN62 = BigNumber.clone({ ALPHABET: alphabet }); + + input = toHexFast(input).toUpperCase(); + + // Read number in as hex using normal alphabet + const normalized = new BigNumber(input, 16); + // Copy to BigNumber clone that uses the specified Base62 alphabet + const number = new BN62(normalized); + + return number.toString(62); + } + +} + +export default ToBase62; diff --git a/plugins/srktoolbox/src/core/operations/ToBase64.mjs b/plugins/srktoolbox/src/core/operations/ToBase64.mjs new file mode 100644 index 00000000..31e98be7 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ToBase64.mjs @@ -0,0 +1,79 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import {toBase64, ALPHABET_OPTIONS} from "../lib/Base64.mjs"; + +/** + * To Base64 operation + */ +class ToBase64 extends Operation { + + /** + * ToBase64 constructor + */ + constructor() { + super(); + + this.name = "Base64编码"; + this.module = "Default"; + this.description = "Base64是把字节数据转换成特定字符组合的编码方式,编码后便于人类阅读,也方便计算机读取。

此操作将原始数据编码成使用ASCII字符的Base64字符串。

例: hello 编码成 aGVsbG8="; + this.infoURL = "https://wikipedia.org/wiki/Base64"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + name: "可用字符", + type: "editableOption", + value: ALPHABET_OPTIONS + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const alphabet = args[0]; + return toBase64(input, alphabet); + } + + /** + * Highlight to Base64 + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + pos[0].start = Math.floor(pos[0].start / 3 * 4); + pos[0].end = Math.ceil(pos[0].end / 3 * 4); + return pos; + } + + /** + * Highlight from Base64 + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + pos[0].start = Math.ceil(pos[0].start / 4 * 3); + pos[0].end = Math.floor(pos[0].end / 4 * 3); + return pos; + } +} + +export default ToBase64; diff --git a/plugins/srktoolbox/src/core/operations/ToBase85.mjs b/plugins/srktoolbox/src/core/operations/ToBase85.mjs new file mode 100644 index 00000000..23bf6c5c --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ToBase85.mjs @@ -0,0 +1,96 @@ +/** + * @author PenguinGeorge [george@penguingeorge.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import {alphabetName, ALPHABET_OPTIONS} from "../lib/Base85.mjs"; + +/** + * To Base85 operation + */ +class ToBase85 extends Operation { + + /** + * To Base85 constructor + */ + constructor() { + super(); + + this.name = "Base85编码"; + this.module = "Default"; + this.description = "Base85(也叫Ascii85)是把字节数据转换成特定字符组合的编码方式。通常比Base64效率更高。

此操作将原始数据编码成使用ASCII字符的Base64字符串。字符表可选,带有预设。

例如:hello world 编码成 BOu!rD]j7BEbo7

Base85在Adobe的PostScript和PDF格式中较为常见。

选项
可用字符
  • 标准 - 标准字母表,又叫Ascii85
  • Z85 (ZeroMQ) - 不带有引号和反斜杠之类,适用于生成字符串。
  • IPv6 - 适合编码IPV6地址的变体 (RFC 1924)
包括分隔符
在数据开头和结尾添加 '<~' 和 '~>'。Adobe的Base85一般采用此格式。"; + this.infoURL = "https://wikipedia.org/wiki/Ascii85"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + name: "可用字符", + type: "editableOption", + value: ALPHABET_OPTIONS + }, + { + name: "包括分隔符", + type: "boolean", + value: false + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + input = new Uint8Array(input); + const alphabet = Utils.expandAlphRange(args[0]).join(""), + encoding = alphabetName(alphabet), + includeDelim = args[1]; + let result = ""; + + if (alphabet.length !== 85 || + [].unique.call(alphabet).length !== 85) { + throw new OperationError("错误:字符表必须包含85个字符"); + } + + if (input.length === 0) return ""; + + let block; + for (let i = 0; i < input.length; i += 4) { + block = ( + ((input[i]) << 24) + + ((input[i + 1] || 0) << 16) + + ((input[i + 2] || 0) << 8) + + ((input[i + 3] || 0)) + ) >>> 0; + + if (encoding !== "标准" || block > 0) { + let digits = []; + for (let j = 0; j < 5; j++) { + digits.push(block % 85); + block = Math.floor(block / 85); + } + + digits = digits.reverse(); + + if (input.length < i + 4) { + digits.splice(input.length - (i + 4), 4); + } + + result += digits.map(digit => alphabet[digit]).join(""); + } else { + result += (encoding === "标准") ? "z" : null; + } + } + + return includeDelim ? `<~${result}~>` : result; + } +} + +export default ToBase85; diff --git a/plugins/srktoolbox/src/core/operations/ToBase92.mjs b/plugins/srktoolbox/src/core/operations/ToBase92.mjs new file mode 100644 index 00000000..d1ed50d8 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ToBase92.mjs @@ -0,0 +1,69 @@ +/** + * @author sg5506844 [sg5506844@gmail.com] + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import { base92Chr } from "../lib/Base92.mjs"; +import Operation from "../Operation.mjs"; + +/** + * To Base92 operation + */ +class ToBase92 extends Operation { + /** + * ToBase92 constructor + */ + constructor() { + super(); + + this.name = "Base92编码"; + this.module = "Default"; + this.description = "Base92是把字节数据转换成特定字符组合的编码方式,编码后便于人类阅读,也方便计算机读取。"; + this.infoURL = "https://wikipedia.org/wiki/List_of_numeral_systems"; + this.inputType = "string"; + this.outputType = "byteArray"; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const res = []; + let bitString = ""; + + while (input.length > 0) { + while (bitString.length < 13 && input.length > 0) { + bitString += input[0].charCodeAt(0).toString(2).padStart(8, "0"); + input = input.slice(1); + } + if (bitString.length < 13) + break; + const i = parseInt(bitString.slice(0, 13), 2); + res.push(base92Chr(Math.floor(i / 91))); + res.push(base92Chr(i % 91)); + bitString = bitString.slice(13); + } + + if (bitString.length > 0) { + if (bitString.length < 7) { + bitString = bitString.padEnd(6, "0"); + res.push(base92Chr(parseInt(bitString, 2))); + } else { + bitString = bitString.padEnd(13, "0"); + const i = parseInt(bitString.slice(0, 13), 2); + res.push(base92Chr(Math.floor(i / 91))); + res.push(base92Chr(i % 91)); + } + } + + return res; + + } +} + +export default ToBase92; diff --git a/plugins/srktoolbox/src/core/operations/ToBech32.mjs b/plugins/srktoolbox/src/core/operations/ToBech32.mjs new file mode 100644 index 00000000..8d6d56ba --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ToBech32.mjs @@ -0,0 +1,94 @@ +/** + * @author Medjedtxm + * @copyright Crown Copyright 2025 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import { encode } from "../lib/Bech32.mjs"; +import { fromHex } from "../lib/Hex.mjs"; + +/** + * To Bech32 operation + */ +class ToBech32 extends Operation { + + /** + * ToBech32 constructor + */ + constructor() { + super(); + + this.name = "Bech32编码"; + this.module = "Default"; + this.description = "Bech32 是一种编码方案,主要应用于比特币隔离见证地址(BIP-0173)。它采用 32 字符字母表,其中排除了易混淆的字符(1、b、i、o),并包含用于错误检测的校验和。

Bech32m(BIP-0350)是其更新版本,用于比特币 Taproot 地址。

自动检测功能会先尝试以 Bech32 格式解码,若校验和失败则尝试 Bech32m 格式。

输出格式选项允许您查看人类可读部分(HRP)及解码后的数据。"; + this.infoURL = "https://wikipedia.org/wiki/Bech32"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "人类可读部分(HRP)", + "type": "string", + "value": "bc" + }, + { + "name": "编码方式", + "type": "option", + "value": ["Bech32", "Bech32m"] + }, + { + "name": "输入格式", + "type": "option", + "value": ["原始字节", "十六进制"] + }, + { + "name": "模式", + "type": "option", + "value": ["通用", "Bitcoin SegWit"] + }, + { + "name": "见证版本(Witness Version)", + "type": "number", + "value": 0, + "hint": "SegWit 隔离见证版本 (0-16)。只用于比特币隔离见证(Bitcoin SegWit)模式。" + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const hrp = args[0]; + const encoding = args[1]; + const inputFormat = args[2]; + const mode = args[3]; + const witnessVersion = args[4]; + + let inputArray; + if (inputFormat === "十六进制") { + // Convert hex string to bytes + const hexStr = new TextDecoder().decode(new Uint8Array(input)).replace(/\s/g, ""); + inputArray = fromHex(hexStr); + } else { + inputArray = new Uint8Array(input); + } + + if (mode === "Bitcoin SegWit") { + // Prepend witness version to the input data + const withVersion = new Uint8Array(inputArray.length + 1); + withVersion[0] = witnessVersion; + withVersion.set(inputArray, 1); + return encode(hrp, withVersion, encoding, true); + } + + return encode(hrp, inputArray, encoding, false); + } + +} + +export default ToBech32; diff --git a/plugins/srktoolbox/src/core/operations/ToBinary.mjs b/plugins/srktoolbox/src/core/operations/ToBinary.mjs new file mode 100644 index 00000000..fbbf98ae --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ToBinary.mjs @@ -0,0 +1,90 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import {BIN_DELIM_OPTIONS} from "../lib/Delim.mjs"; +import {toBinary} from "../lib/Binary.mjs"; + +/** + * To Binary operation + */ +class ToBinary extends Operation { + + /** + * ToBinary constructor + */ + constructor() { + super(); + + this.name = "字符转二进制"; + this.module = "Default"; + this.description = "将输入字符串转换为对应的二进制表示(使用给定的分隔符)。

Hi 编码为 01001000 01101001"; + this.infoURL = "https://wikipedia.org/wiki/Binary_code"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "分隔符", + "type": "option", + "value": BIN_DELIM_OPTIONS + }, + { + "name": "字节长度", + "type": "number", + "value": 8 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + input = new Uint8Array(input); + const padding = args[1] ? args[1] : 8; + return toBinary(input, args[0], padding); + } + + /** + * Highlight To Binary + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + const delim = Utils.charRep(args[0] || "空格"); + pos[0].start = pos[0].start * (8 + delim.length); + pos[0].end = pos[0].end * (8 + delim.length) - delim.length; + return pos; + } + + /** + * Highlight To Binary in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + const delim = Utils.charRep(args[0] || "空格"); + pos[0].start = pos[0].start === 0 ? 0 : Math.floor(pos[0].start / (8 + delim.length)); + pos[0].end = pos[0].end === 0 ? 0 : Math.ceil(pos[0].end / (8 + delim.length)); + return pos; + } + +} + +export default ToBinary; diff --git a/plugins/srktoolbox/src/core/operations/ToBraille.mjs b/plugins/srktoolbox/src/core/operations/ToBraille.mjs new file mode 100644 index 00000000..c84f5815 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ToBraille.mjs @@ -0,0 +1,72 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import {BRAILLE_LOOKUP} from "../lib/Braille.mjs"; + +/** + * To Braille operation + */ +class ToBraille extends Operation { + + /** + * ToBraille constructor + */ + constructor() { + super(); + + this.name = "盲文编码"; + this.module = "Default"; + this.description = "把文本编码为六点盲文符号。"; + this.infoURL = "https://wikipedia.org/wiki/Braille"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return input.split("").map(c => { + const idx = BRAILLE_LOOKUP.ascii.indexOf(c.toUpperCase()); + return idx < 0 ? c : BRAILLE_LOOKUP.dot6[idx]; + }).join(""); + } + + /** + * Highlight To Braille + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight To Braille 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 ToBraille; diff --git a/plugins/srktoolbox/src/core/operations/ToCNNumberUpperCase.mjs b/plugins/srktoolbox/src/core/operations/ToCNNumberUpperCase.mjs new file mode 100644 index 00000000..e0a96c94 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ToCNNumberUpperCase.mjs @@ -0,0 +1,86 @@ +/** + * @author Raka-loah [i@lotc.cc] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; + + +/** + * To CN Number Upper Case operation + */ +class ToCNNumberUpperCase extends Operation { + + /** + * ToCNNumberUpperCase constructor + */ + constructor() { + super(); + + this.name = "转换为大写数字"; + this.module = "Default"; + this.description = "把简体中文汉字数字转换为对应的大写形式。

例如: 一十一万四千五百一十四 转换为 壹拾壹万肆仟伍佰壹拾肆。"; + this.infoURL = "https://zh.wikipedia.org/wiki/%E5%A4%A7%E5%86%99%E6%95%B0%E5%AD%97"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const numberDict = { + "一": "壹", + "二": "贰", + "三": "叁", + "四": "肆", + "五": "伍", + "六": "陆", + "七": "柒", + "八": "捌", + "九": "玖", + "十": "拾", + "〇": "零", + "百": "佰", + "千": "仟" + }; + + const upperStr = input.split("").map(ch => numberDict[ch] ?? ch).join(""); + + return upperStr; + } + + /** + * Highlight To CN Number Upper Case + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight To CN Number Upper Case 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 ToCNNumberUpperCase; diff --git a/plugins/srktoolbox/src/core/operations/ToCamelCase.mjs b/plugins/srktoolbox/src/core/operations/ToCamelCase.mjs new file mode 100644 index 00000000..631ff80b --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ToCamelCase.mjs @@ -0,0 +1,56 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import camelCase from "lodash/camelCase.js"; +import Operation from "../Operation.mjs"; +import { replaceVariableNames } from "../lib/Code.mjs"; + +/** + * To Camel case operation + */ +class ToCamelCase extends Operation { + + /** + * ToCamelCase constructor + */ + constructor() { + super(); + + this.name = "转换为Camel case"; + this.module = "Code"; + this.description = "将输入字符串转换为camel case。\n

\nCamel case是除了单词边界之外的字母全小写的格式。\n

\n例如: thisIsCamelCase\n

\n勾选“尝试识别上下文”后此操作将尝试只转换函数和变量名。"; + this.infoURL = "https://wikipedia.org/wiki/Camel_case"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "尝试识别上下文", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const smart = args[0]; + + if (smart) { + return replaceVariableNames(input, camelCase); + } else { + return camelCase(input); + } + } + +} + +export default ToCamelCase; diff --git a/plugins/srktoolbox/src/core/operations/ToCaseInsensitiveRegex.mjs b/plugins/srktoolbox/src/core/operations/ToCaseInsensitiveRegex.mjs new file mode 100644 index 00000000..c94f708b --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ToCaseInsensitiveRegex.mjs @@ -0,0 +1,97 @@ +/** + * @author masq [github.cyberchef@masq.cc] + * @author n1073645 + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * To Case Insensitive Regex operation + */ +class ToCaseInsensitiveRegex extends Operation { + + /** + * ToCaseInsensitiveRegex constructor + */ + constructor() { + super(); + + this.name = "转换为大小写不敏感正则"; + this.module = "Default"; + this.description = "把大小写敏感正则字符串转换为大小写不敏感形式,用于无法使用正则i选项的场合。

例如:Mozilla/[0-9].[0-9] .* 转换为 [mM][oO][zZ][iI][lL][lL][aA]/[0-9].[0-9] .*"; + this.infoURL = "https://wikipedia.org/wiki/Regular_expression"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + + /** + * Simulates look behind behaviour since javascript doesn't support it. + * + * @param {string} input + * @returns {string} + */ + function preProcess(input) { + let result = ""; + for (let i = 0; i < input.length; i++) { + const temp = input.charAt(i); + if (temp.match(/[a-zA-Z]/g) && (input.charAt(i-1) !== "-") && (input.charAt(i+1) !== "-")) + result += "[" + temp.toLowerCase() + temp.toUpperCase() + "]"; + else + result += temp; + } + return result; + } + + try { + RegExp(input); + } catch (error) { + throw new OperationError("无效的正则表达式(请注意此版本的Node不支持正则的后行断言)。"); + } + + // Example: [test] -> [[tT][eE][sS][tT]] + return preProcess(input) + + // Example: [A-Z] -> [A-Za-z] + .replace(/([A-Z]-[A-Z]|[a-z]-[a-z])/g, m => `${m[0].toUpperCase()}-${m[2].toUpperCase()}${m[0].toLowerCase()}-${m[2].toLowerCase()}`) + + // Example: [H-d] -> [A-DH-dh-z] + .replace(/[A-Z]-[a-z]/g, m => `A-${m[2].toUpperCase()}${m}${m[0].toLowerCase()}-z`) + + // Example: [!-D] -> [!-Da-d] + .replace(/\\?[ -@]-[A-Z]/g, m => `${m}a-${m[2].toLowerCase()}`) + + // Example: [%-^] -> [%-^a-z] + .replace(/\\?[ -@]-\\?[[-`]/g, m => `${m}a-z`) + + // Example: [K-`] -> [K-`k-z] + .replace(/[A-Z]-\\?[[-`]/g, m => `${m}${m[0].toLowerCase()}-z`) + + // Example: [[-}] -> [[-}A-Z] + .replace(/\\?[[-`]-\\?[{-~]/g, m => `${m}A-Z`) + + // Example: [b-}] -> [b-}B-Z] + .replace(/[a-z]-\\?[{-~]/g, m => `${m}${m[0].toUpperCase()}-Z`) + + // Example: [<-j] -> [<-z] + .replace(/\\?[ -@]-[a-z]/g, m => `${m[0]}-z`) + + // Example: [^-j] -> [A-J^-j] + .replace(/\\?[[-`]-[a-z]/g, m => `A-${m[2].toUpperCase()}${m}`); + + } +} + +export default ToCaseInsensitiveRegex; diff --git a/plugins/srktoolbox/src/core/operations/ToCharcode.mjs b/plugins/srktoolbox/src/core/operations/ToCharcode.mjs new file mode 100644 index 00000000..f269b6dc --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ToCharcode.mjs @@ -0,0 +1,89 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import { DELIM_OPTIONS } from "../lib/Delim.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { isWorkerEnvironment } from "../Utils.mjs"; + +/** + * To Charcode operation + */ +class ToCharcode extends Operation { + + /** + * ToCharcode constructor + */ + constructor() { + super(); + + this.name = "转换为字符码"; + this.module = "Default"; + this.description = "把字符转换成对应的Unicode字符码

例: Γειά σου 编码为 0393 03b5 03b9 03ac 20 03c3 03bf 03c5"; + this.infoURL = "https://wikipedia.org/wiki/Plane_(Unicode)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "分隔符", + "type": "option", + "value": DELIM_OPTIONS + }, + { + "name": "进制", + "type": "number", + "value": 16 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + * + * @throws {OperationError} if base argument out of range + */ + run(input, args) { + const delim = Utils.charRep(args[0] || "空格"), + base = args[1]; + let output = "", + padding, + ordinal; + + if (base < 2 || base > 36) { + throw new OperationError("错误:进制必须在2~36之间"); + } + + const charcode = Utils.strToCharcode(input); + for (let i = 0; i < charcode.length; i++) { + ordinal = charcode[i]; + + if (base === 16) { + if (ordinal < 256) padding = 2; + else if (ordinal < 65536) padding = 4; + else if (ordinal < 16777216) padding = 6; + else if (ordinal < 4294967296) padding = 8; + else padding = 2; + + if (padding > 2 && isWorkerEnvironment()) self.setOption("attemptHighlight", false); + + output += Utils.hex(ordinal, padding) + delim; + } else { + if (isWorkerEnvironment()) self.setOption("attemptHighlight", false); + output += ordinal.toString(base) + delim; + } + } + + return output.slice(0, -delim.length); + } + +} + +export default ToCharcode; diff --git a/plugins/srktoolbox/src/core/operations/ToDecimal.mjs b/plugins/srktoolbox/src/core/operations/ToDecimal.mjs new file mode 100644 index 00000000..26f762bc --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ToDecimal.mjs @@ -0,0 +1,62 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import {DELIM_OPTIONS} from "../lib/Delim.mjs"; + + +/** + * To Decimal operation + */ +class ToDecimal extends Operation { + + /** + * ToDecimal constructor + */ + constructor() { + super(); + + this.name = "字符转十进制"; + this.module = "Default"; + this.description = "将输入字符串转换为对应的十进制表示(使用给定的分隔符)。

例: Hello 编码为 72 101 108 108 111"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "分隔符", + "type": "option", + "value": DELIM_OPTIONS + }, + { + "name": "支持带符号数值(signed int)", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const delim = Utils.charRep(args[0]), + signed = args[1]; + if (signed) { + input = new Int8Array(input); + } else { + input = new Uint8Array(input); + } + return input.join(delim); + } + +} + +export default ToDecimal; diff --git a/plugins/srktoolbox/src/core/operations/ToFloat.mjs b/plugins/srktoolbox/src/core/operations/ToFloat.mjs new file mode 100644 index 00000000..9ed769dc --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ToFloat.mjs @@ -0,0 +1,82 @@ +/** + * @author tcode2k16 [tcode2k16@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import ieee754 from "ieee754"; +import {DELIM_OPTIONS} from "../lib/Delim.mjs"; + +/** + * To Float operation + */ +class ToFloat extends Operation { + + /** + * ToFloat constructor + */ + constructor() { + super(); + + this.name = "字符转浮点数"; + this.module = "Default"; + this.description = "转换为 IEEE754 浮点数"; + this.infoURL = "https://wikipedia.org/wiki/IEEE_754"; + this.inputType = "byteArray"; + this.outputType = "string"; + this.args = [ + { + "name": "端序", + "type": "option", + "value": [ + "大端序", + "小端序" + ] + }, + { + "name": "类型", + "type": "option", + "value": [ + "Float (4字节)", + "Double (8字节)" + ] + }, + { + "name": "分隔符", + "type": "option", + "value": DELIM_OPTIONS + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [endianness, size, delimiterName] = args; + const delim = Utils.charRep(delimiterName || "空格"); + const byteSize = size === "Double (8字节)" ? 8 : 4; + const isLE = endianness === "小端序"; + const mLen = byteSize === 4 ? 23 : 52; + + if (input.length % byteSize !== 0) { + throw new OperationError(`输入长度不是 ${byteSize} 字节的倍数`); + } + + const output = []; + for (let i = 0; i < input.length; i+=byteSize) { + output.push(ieee754.read(input, i, isLE, mLen, byteSize)); + } + return output.join(delim); + } + +} + +export default ToFloat; diff --git a/plugins/srktoolbox/src/core/operations/ToHTMLEntity.mjs b/plugins/srktoolbox/src/core/operations/ToHTMLEntity.mjs new file mode 100644 index 00000000..f0be1e2c --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ToHTMLEntity.mjs @@ -0,0 +1,1514 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +/** + * To HTML Entity operation + */ +class ToHTMLEntity extends Operation { + + /** + * ToHTMLEntity constructor + */ + constructor() { + super(); + + this.name = "HTML实体编码"; + this.module = "Encodings"; + this.description = "把字符编码为HTML实体

例: & 编码为 &amp;"; + this.infoURL = "https://wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "转换所有字符", + "type": "boolean", + "value": false + }, + { + "name": "转换成", + "type": "option", + "value": ["名称", "十进制", "十六进制"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const convertAll = args[0], + numeric = args[1] === "十进制", + hexa = args[1] === "十六进制"; + + const charcodes = Utils.strToCharcode(input); + let output = ""; + + for (let i = 0; i < charcodes.length; i++) { + if (convertAll && numeric) { + output += "&#" + charcodes[i] + ";"; + } else if (convertAll && hexa) { + output += "&#x" + Utils.hex(charcodes[i]) + ";"; + } else if (convertAll) { + output += byteToEntity[charcodes[i]] || "&#" + charcodes[i] + ";"; + } else if (numeric) { + if (charcodes[i] > 255 || charcodes[i] in byteToEntity) { + output += "&#" + charcodes[i] + ";"; + } else { + output += Utils.chr(charcodes[i]); + } + } else if (hexa) { + if (charcodes[i] > 255 || charcodes[i] in byteToEntity) { + output += "&#x" + Utils.hex(charcodes[i]) + ";"; + } else { + output += Utils.chr(charcodes[i]); + } + } else { + output += byteToEntity[charcodes[i]] || ( + charcodes[i] > 255 ? + "&#" + charcodes[i] + ";" : + Utils.chr(charcodes[i]) + ); + } + } + return output; + } + +} + +/** + * Lookup table to translate byte values to their HTML entity codes. + */ +const byteToEntity = { + 9: " ", + 10: " ", + 33: "!", + 34: """, + 35: "#", + 36: "$", + 37: "%", + 38: "&", + 39: "'", + 40: "(", + 41: ")", + 42: "*", + 43: "+", + 44: ",", + 46: ".", + 47: "/", + 58: ":", + 59: ";", + 60: "<", + 61: "=", + 62: ">", + 63: "?", + 64: "@", + 91: "[", + 92: "\", + 93: "]", + 94: "^", + 95: "_", + 96: "`", + 123: "{", + 124: "|", + 125: "}", + 160: " ", + 161: "¡", + 162: "¢", + 163: "£", + 164: "¤", + 165: "¥", + 166: "¦", + 167: "§", + 168: "¨", + 169: "©", + 170: "ª", + 171: "«", + 172: "¬", + 173: "­", + 174: "®", + 175: "¯", + 176: "°", + 177: "±", + 178: "²", + 179: "³", + 180: "´", + 181: "µ", + 182: "¶", + 183: "·", + 184: "¸", + 185: "¹", + 186: "º", + 187: "»", + 188: "¼", + 189: "½", + 190: "¾", + 191: "¿", + 192: "À", + 193: "Á", + 194: "Â", + 195: "Ã", + 196: "Ä", + 197: "Å", + 198: "Æ", + 199: "Ç", + 200: "È", + 201: "É", + 202: "Ê", + 203: "Ë", + 204: "Ì", + 205: "Í", + 206: "Î", + 207: "Ï", + 208: "Ð", + 209: "Ñ", + 210: "Ò", + 211: "Ó", + 212: "Ô", + 213: "Õ", + 214: "Ö", + 215: "×", + 216: "Ø", + 217: "Ù", + 218: "Ú", + 219: "Û", + 220: "Ü", + 221: "Ý", + 222: "Þ", + 223: "ß", + 224: "à", + 225: "á", + 226: "â", + 227: "ã", + 228: "ä", + 229: "å", + 230: "æ", + 231: "ç", + 232: "è", + 233: "é", + 234: "ê", + 235: "ë", + 236: "ì", + 237: "í", + 238: "î", + 239: "ï", + 240: "ð", + 241: "ñ", + 242: "ò", + 243: "ó", + 244: "ô", + 245: "õ", + 246: "ö", + 247: "÷", + 248: "ø", + 249: "ù", + 250: "ú", + 251: "û", + 252: "ü", + 253: "ý", + 254: "þ", + 255: "ÿ", + 256: "Ā", + 257: "ā", + 258: "Ă", + 259: "ă", + 260: "Ą", + 261: "ą", + 262: "Ć", + 263: "ć", + 264: "Ĉ", + 265: "ĉ", + 266: "Ċ", + 267: "ċ", + 268: "Č", + 269: "č", + 270: "Ď", + 271: "ď", + 272: "Đ", + 273: "đ", + 274: "Ē", + 275: "ē", + 278: "Ė", + 279: "ė", + 280: "Ę", + 281: "ę", + 282: "Ě", + 283: "ě", + 284: "Ĝ", + 285: "ĝ", + 286: "Ğ", + 287: "ğ", + 288: "Ġ", + 289: "ġ", + 290: "Ģ", + 292: "Ĥ", + 293: "ĥ", + 294: "Ħ", + 295: "ħ", + 296: "Ĩ", + 297: "ĩ", + 298: "Ī", + 299: "ī", + 302: "Į", + 303: "į", + 304: "İ", + 305: "ı", + 306: "IJ", + 307: "ij", + 308: "Ĵ", + 309: "ĵ", + 310: "Ķ", + 311: "ķ", + 312: "ĸ", + 313: "Ĺ", + 314: "ĺ", + 315: "Ļ", + 316: "ļ", + 317: "Ľ", + 318: "ľ", + 319: "Ŀ", + 320: "ŀ", + 321: "Ł", + 322: "ł", + 323: "Ń", + 324: "ń", + 325: "Ņ", + 326: "ņ", + 327: "Ň", + 328: "ň", + 329: "ʼn", + 330: "Ŋ", + 331: "ŋ", + 332: "Ō", + 333: "ō", + 336: "Ő", + 337: "ő", + 338: "Œ", + 339: "œ", + 340: "Ŕ", + 341: "ŕ", + 342: "Ŗ", + 343: "ŗ", + 344: "Ř", + 345: "ř", + 346: "Ś", + 347: "ś", + 348: "Ŝ", + 349: "ŝ", + 350: "Ş", + 351: "ş", + 352: "Š", + 353: "š", + 354: "Ţ", + 355: "ţ", + 356: "Ť", + 357: "ť", + 358: "Ŧ", + 359: "ŧ", + 360: "Ũ", + 361: "ũ", + 362: "Ū", + 363: "ū", + 364: "Ŭ", + 365: "ŭ", + 366: "Ů", + 367: "ů", + 368: "Ű", + 369: "ű", + 370: "Ų", + 371: "ų", + 372: "Ŵ", + 373: "ŵ", + 374: "Ŷ", + 375: "ŷ", + 376: "Ÿ", + 377: "Ź", + 378: "ź", + 379: "Ż", + 380: "ż", + 381: "Ž", + 382: "ž", + 402: "ƒ", + 437: "Ƶ", + 501: "ǵ", + 567: "ȷ", + 710: "ˆ", + 711: "ˇ", + 728: "˘", + 729: "˙", + 730: "˚", + 731: "˛", + 732: "˜", + 785: "̑", + 818: "_", + 913: "Α", + 914: "Β", + 915: "Γ", + 916: "Δ", + 917: "Ε", + 918: "Ζ", + 919: "Η", + 920: "Θ", + 921: "Ι", + 922: "Κ", + 923: "Λ", + 924: "Μ", + 925: "Ν", + 926: "Ξ", + 927: "Ο", + 928: "Π", + 929: "Ρ", + 931: "Σ", + 932: "Τ", + 933: "Υ", + 934: "Φ", + 935: "Χ", + 936: "Ψ", + 937: "Ω", + 945: "α", + 946: "β", + 947: "γ", + 948: "δ", + 949: "ε", + 950: "ζ", + 951: "η", + 952: "θ", + 953: "ι", + 954: "κ", + 955: "λ", + 956: "μ", + 957: "ν", + 958: "ξ", + 959: "ο", + 960: "π", + 961: "ρ", + 962: "ς", + 963: "σ", + 964: "τ", + 965: "υ", + 966: "φ", + 967: "χ", + 968: "ψ", + 969: "ω", + 977: "ϑ", + 978: "ϒ", + 981: "ϕ", + 982: "ϖ", + 988: "Ϝ", + 989: "ϝ", + 1008: "ϰ", + 1009: "ϱ", + 1013: "ε,", + 1014: "϶", + 1025: "Ё", + 1026: "Ђ", + 1027: "Ѓ", + 1028: "Є", + 1029: "Ѕ", + 1030: "І", + 1031: "Ї", + 1032: "Ј", + 1033: "Љ", + 1034: "Њ", + 1035: "Ћ", + 1036: "Ќ", + 1038: "Ў", + 1039: "Џ", + 1040: "А", + 1041: "Б", + 1042: "В", + 1043: "Г", + 1044: "Д", + 1045: "Е", + 1046: "Ж", + 1047: "З", + 1048: "И", + 1049: "Й", + 1050: "К", + 1051: "Л", + 1052: "М", + 1053: "Н", + 1054: "О", + 1055: "П", + 1056: "Р", + 1057: "С", + 1058: "Т", + 1059: "У", + 1060: "Ф", + 1061: "Х", + 1062: "Ц", + 1063: "Ч", + 1064: "Ш", + 1065: "Щ", + 1066: "Ъ", + 1067: "Ы", + 1068: "Ь", + 1069: "Э", + 1070: "Ю", + 1071: "Я", + 1072: "а", + 1073: "б", + 1074: "в", + 1075: "г", + 1076: "д", + 1077: "е", + 1078: "ж", + 1079: "з", + 1080: "и", + 1081: "й", + 1082: "к", + 1083: "л", + 1084: "м", + 1085: "н", + 1086: "о", + 1087: "п", + 1088: "р", + 1089: "с", + 1090: "т", + 1091: "у", + 1092: "ф", + 1093: "х", + 1094: "ц", + 1095: "ч", + 1096: "ш", + 1097: "щ", + 1098: "ъ", + 1099: "ы", + 1100: "ь", + 1101: "э", + 1102: "ю", + 1103: "я", + 1105: "ё", + 1106: "ђ", + 1107: "ѓ", + 1108: "є", + 1109: "ѕ", + 1110: "і", + 1111: "ї", + 1112: "ј", + 1113: "љ", + 1114: "њ", + 1115: "ћ", + 1116: "ќ", + 1118: "ў", + 1119: "џ", + 8194: " ", + 8195: " ", + 8196: " ", + 8197: " ", + 8199: " ", + 8200: " ", + 8201: " ", + 8202: " ", + 8203: "​", + 8204: "‌", + 8205: "‍", + 8206: "‎", + 8207: "‏", + 8208: "‐", + 8211: "–", + 8212: "—", + 8213: "―", + 8214: "‖", + 8216: "‘", + 8217: "’", + 8218: "‚", + 8220: "“", + 8221: "”", + 8222: "„", + 8224: "†", + 8225: "‡", + 8226: "•", + 8229: "‥", + 8230: "…", + 8240: "‰", + 8241: "‱", + 8242: "′", + 8243: "″", + 8244: "‴", + 8245: "‵", + 8249: "‹", + 8250: "›", + 8254: "‾", + 8257: "⁁", + 8259: "⁃", + 8260: "⁄", + 8271: "⁏", + 8279: "⁗", + 8287: " ", + 8288: "⁠", + 8289: "⁡", + 8290: "⁢", + 8291: "⁣", + 8364: "€", + 8411: "⃛", + 8412: "⃜", + 8450: "ℂ", + 8453: "℅", + 8458: "ℊ", + 8459: "ℋ", + 8460: "ℌ", + 8461: "ℍ", + 8462: "ℎ", + 8463: "ℏ", + 8464: "ℐ", + 8465: "ℑ", + 8466: "ℒ", + 8467: "ℓ", + 8469: "ℕ", + 8470: "№", + 8471: "℗", + 8472: "℘", + 8473: "ℙ", + 8474: "ℚ", + 8475: "ℛ", + 8476: "ℜ", + 8477: "ℝ", + 8478: "℞", + 8482: "™", + 8484: "ℤ", + 8486: "Ω", + 8487: "℧", + 8488: "ℨ", + 8489: "℩", + 8491: "Å", + 8492: "ℬ", + 8493: "ℭ", + 8495: "ℯ", + 8496: "ℰ", + 8497: "ℱ", + 8499: "ℳ", + 8500: "ℴ", + 8501: "ℵ", + 8502: "ℶ", + 8503: "ℷ", + 8504: "ℸ", + 8517: "ⅅ", + 8518: "ⅆ", + 8519: "ⅇ", + 8520: "ⅈ", + 8531: "⅓", + 8532: "⅔", + 8533: "⅕", + 8534: "⅖", + 8535: "⅗", + 8536: "⅘", + 8537: "⅙", + 8538: "⅚", + 8539: "⅛", + 8540: "⅜", + 8541: "⅝", + 8542: "⅞", + 8592: "←", + 8593: "↑", + 8594: "→", + 8595: "↓", + 8596: "↔", + 8597: "↕", + 8598: "↖", + 8599: "↗", + 8600: "↘", + 8601: "↙", + 8602: "↚", + 8603: "↛", + 8605: "↝", + 8606: "↞", + 8607: "↟", + 8608: "↠", + 8609: "↡", + 8610: "↢", + 8611: "↣", + 8612: "↤", + 8613: "↥", + 8614: "↦", + 8615: "↧", + 8617: "↩", + 8618: "↪", + 8619: "↫", + 8620: "↬", + 8621: "↭", + 8622: "↮", + 8624: "↰", + 8625: "↱", + 8626: "↲", + 8627: "↳", + 8629: "↵", + 8630: "↶", + 8631: "↷", + 8634: "↺", + 8635: "↻", + 8636: "↼", + 8637: "↽", + 8638: "↾", + 8639: "↿", + 8640: "⇀", + 8641: "⇁", + 8642: "⇂", + 8643: "⇃", + 8644: "⇄", + 8645: "⇅", + 8646: "⇆", + 8647: "⇇", + 8648: "⇈", + 8649: "⇉", + 8650: "⇊", + 8651: "⇋", + 8652: "⇌;", + 8653: "⇍", + 8654: "⇎", + 8655: "⇏", + 8656: "⇐", + 8657: "⇑", + 8658: "⇒", + 8659: "⇓", + 8660: "⇔", + 8661: "⇕", + 8662: "⇖", + 8663: "⇗", + 8664: "⇘", + 8665: "⇙", + 8666: "⇚", + 8667: "⇛", + 8669: "⇝", + 8676: "⇤", + 8677: "⇥", + 8693: "⇵", + 8701: "⇽", + 8702: "⇾", + 8703: "⇿", + 8704: "∀", + 8705: "∁", + 8706: "∂", + 8707: "∃", + 8708: "∄", + 8709: "∅", + 8711: "∇", + 8712: "∈", + 8713: "∉", + 8715: "∋", + 8716: "∌", + 8719: "∏", + 8720: "∐", + 8721: "∑", + 8722: "−", + 8723: "∓", + 8724: "∔", + 8726: "∖", + 8727: "∗", + 8728: "∘", + 8730: "√", + 8733: "∝", + 8734: "∞", + 8735: "∟", + 8736: "∠", + 8737: "∡", + 8738: "∢", + 8739: "∣", + 8740: "∤", + 8741: "∥", + 8742: "∦", + 8743: "∧", + 8744: "∨", + 8745: "∩", + 8746: "∪", + 8747: "∫", + 8748: "∬", + 8749: "∭", + 8750: "∮", + 8751: "∯", + 8752: "∰", + 8753: "∱", + 8754: "∲", + 8755: "∳", + 8756: "∴", + 8757: "∵", + 8758: "∶", + 8759: "∷", + 8760: "∸", + 8762: "∺", + 8763: "∻", + 8764: "∼", + 8765: "∽", + 8766: "∾", + 8767: "∿", + 8768: "≀", + 8769: "≁", + 8770: "≂", + 8771: "≃", + 8772: "≄", + 8773: "≅", + 8774: "≆", + 8775: "≇", + 8776: "≈", + 8777: "≉", + 8778: "≊", + 8779: "≋", + 8780: "≌", + 8781: "≍", + 8782: "≎", + 8783: "≏", + 8784: "≐", + 8785: "≑", + 8786: "≒", + 8787: "≓", + 8788: "≔", + 8789: "≕", + 8790: "≖", + 8791: "≗", + 8793: "≙", + 8794: "≚", + 8796: "≜", + 8799: "≟", + 8800: "≠", + 8801: "≡", + 8802: "≢", + 8804: "≤", + 8805: "≥", + 8806: "≦", + 8807: "≧", + 8808: "≨", + 8809: "≩", + 8810: "≪", + 8811: "≫", + 8812: "≬", + 8813: "≭", + 8814: "≮", + 8815: "≯", + 8816: "≰", + 8817: "≱;", + 8818: "≲", + 8819: "≳", + 8820: "≴", + 8821: "≵", + 8822: "≶", + 8823: "≷", + 8824: "≸", + 8825: "≹", + 8826: "≺", + 8827: "≻", + 8828: "≼", + 8829: "≽", + 8830: "≾", + 8831: "≿", + 8832: "⊀", + 8833: "⊁", + 8834: "⊂", + 8835: "⊃", + 8836: "⊄", + 8837: "⊅", + 8838: "⊆", + 8839: "⊇", + 8840: "⊈", + 8841: "⊉", + 8842: "⊊", + 8843: "⊋", + 8845: "⊍", + 8846: "⊎", + 8847: "⊏", + 8848: "⊐", + 8849: "⊑", + 8850: "⊒", + 8851: "⊓", + 8852: "⊔", + 8853: "⊕", + 8854: "⊖", + 8855: "⊗", + 8856: "⊘", + 8857: "⊙", + 8858: "⊚", + 8859: "⊛", + 8861: "⊝", + 8862: "⊞", + 8863: "⊟", + 8864: "⊠", + 8865: "⊡", + 8866: "⊢", + 8867: "⊣", + 8868: "⊤", + 8869: "⊥", + 8871: "⊧", + 8872: "⊨", + 8873: "⊩", + 8874: "⊪", + 8875: "⊫", + 8876: "⊬", + 8877: "⊭", + 8878: "⊮", + 8879: "⊯", + 8880: "⊰", + 8882: "⊲", + 8883: "⊳", + 8884: "⊴", + 8885: "⊵", + 8886: "⊶", + 8887: "⊷", + 8888: "⊸", + 8889: "⊹", + 8890: "⊺", + 8891: "⊻", + 8893: "⊽", + 8894: "⊾", + 8895: "⊿", + 8896: "⋀", + 8897: "⋁", + 8898: "⋂", + 8899: "⋃", + 8900: "⋄", + 8901: "⋅", + 8902: "⋆", + 8903: "⋇", + 8904: "⋈", + 8905: "⋉", + 8906: "⋊", + 8907: "⋋", + 8908: "⋌", + 8909: "⋍", + 8910: "⋎", + 8911: "⋏", + 8912: "⋐", + 8913: "⋑", + 8914: "⋒", + 8915: "⋓", + 8916: "⋔", + 8917: "⋕", + 8918: "⋖", + 8919: "⋗", + 8920: "⋘", + 8921: "⋙", + 8922: "⋚", + 8923: "⋛", + 8926: "⋞", + 8927: "⋟", + 8928: "⋠", + 8929: "⋡", + 8930: "⋢", + 8931: "⋣", + 8934: "⋦", + 8935: "⋧", + 8936: "⋨", + 8937: "⋩", + 8938: "⋪", + 8939: "⋫", + 8940: "⋬", + 8941: "⋭", + 8942: "⋮", + 8943: "⋯", + 8944: "⋰", + 8945: "⋱", + 8946: "⋲", + 8947: "⋳", + 8948: "⋴", + 8949: "⋵", + 8950: "⋶", + 8951: "⋷", + 8953: "⋹", + 8954: "⋺", + 8955: "⋻", + 8956: "⋼", + 8957: "⋽", + 8958: "⋾", + 8965: "⌅", + 8966: "⌆", + 8968: "⌈", + 8969: "⌉", + 8970: "⌊", + 8971: "⌋", + 8972: "⌌", + 8973: "⌍", + 8974: "⌎", + 8975: "⌏", + 8976: "⌐", + 8978: "⌒", + 8979: "⌓", + 8981: "⌕", + 8982: "⌖", + 8988: "⌜", + 8989: "⌝", + 8990: "⌞", + 8991: "⌟", + 8994: "⌢", + 8995: "⌣", + 9001: "⟨", + 9002: "⟩", + 9005: "⌭", + 9006: "⌮", + 9014: "⌶", + 9021: "⌽", + 9023: "⌿", + 9084: "⍼", + 9136: "⎰", + 9137: "⎱", + 9140: "⎴", + 9141: "⎵", + 9142: "⎶", + 9180: "⏜", + 9181: "⏝", + 9182: "⏞", + 9183: "⏟", + 9186: "⏢", + 9191: "⏧", + 9251: "␣", + 9416: "Ⓢ", + 9472: "─", + 9474: "│", + 9484: "┌", + 9488: "┐", + 9492: "└", + 9496: "┘", + 9500: "├", + 9508: "┤", + 9516: "┬", + 9524: "┴", + 9532: "┼", + 9552: "═", + 9553: "║", + 9554: "╒", + 9555: "╓", + 9556: "╔", + 9557: "╕", + 9558: "╖", + 9559: "╗", + 9560: "╘", + 9561: "╙", + 9562: "╚", + 9563: "╛", + 9564: "╜", + 9565: "╝", + 9566: "╞", + 9567: "╟", + 9568: "╠", + 9569: "╡", + 9570: "╢", + 9571: "╣", + 9572: "╤", + 9573: "╥", + 9674: "◊", + 9675: "○", + 9708: "◬", + 9711: "◯", + 9720: "◸", + 9721: "◹", + 9722: "◺", + 9723: "◻", + 9724: "◼", + 9733: "★", + 9734: "☆", + 9742: "☎", + 9792: "♀", + 9794: "♂", + 9824: "♠", + 9827: "♣", + 9829: "♥", + 9830: "♦", + 9834: "♪", + 9837: "♭", + 9838: "♮", + 9839: "♯", + 10003: "✓", + 10007: "✗", + 10016: "✠", + 10038: "✶", + 10072: "❘", + 10098: "❲", + 10099: "❳", + 10214: "⟦", + 10215: "⟧", + 10216: "⟨", + 10217: "⟩", + 10218: "⟪", + 10219: "⟫", + 10220: "⟬", + 10221: "⟭", + 10229: "⟵", + 10230: "⟶", + 10231: "⟷", + 10232: "⟸", + 10233: "⟹", + 10234: "⟺", + 10236: "⟼", + 10239: "⟿", + 10498: "⤂", + 10499: "⤃", + 10500: "⤄", + 10501: "⤅", + 10508: "⤌", + 10509: "⤍", + 10510: "⤎", + 10511: "⤏", + 10512: "⤐", + 10513: "⤑", + 10514: "⤒", + 10515: "⤓", + 10518: "⤖", + 10521: "⤙", + 10522: "⤚", + 10523: "⤛", + 10524: "⤜", + 10525: "⤝", + 10526: "⤞", + 10527: "⤟", + 10528: "⤠", + 10531: "⤣", + 10532: "⤤", + 10533: "⤥", + 10534: "⤦", + 10535: "⤧", + 10536: "⤨", + 10537: "⤩", + 10538: "⤪", + 10547: "⤳", + 10549: "⤵", + 10550: "⤶", + 10551: "⤷", + 10552: "⤸", + 10553: "⤹", + 10556: "⤼", + 10557: "⤽", + 10565: "⥅", + 10568: "⥈", + 10569: "⥉", + 10570: "⥊", + 10571: "⥋", + 10574: "⥎", + 10575: "⥏", + 10576: "⥐", + 10577: "⥑", + 10578: "⥒", + 10579: "⥓", + 10580: "⥔", + 10581: "⥕", + 10582: "⥖", + 10583: "⥗", + 10584: "⥘", + 10585: "⥙", + 10586: "⥚", + 10587: "⥛", + 10588: "⥜", + 10589: "⥝", + 10590: "⥞", + 10591: "⥟", + 10592: "⥠", + 10593: "⥡", + 10594: "⥢", + 10595: "⥣", + 10596: "⥤", + 10597: "⥥", + 10598: "⥦", + 10599: "⥧", + 10600: "⥨", + 10601: "⥩", + 10602: "⥪", + 10603: "⥫", + 10604: "⥬", + 10605: "⥭", + 10606: "⥮", + 10607: "⥯", + 10608: "⥰", + 10609: "⥱", + 10610: "⥲", + 10611: "⥳", + 10612: "⥴", + 10613: "⥵", + 10614: "⥶", + 10616: "⥸", + 10617: "⥹", + 10619: "⥻", + 10620: "⥼", + 10621: "⥽", + 10622: "⥾", + 10623: "⥿", + 10629: "⦅", + 10630: "⦆", + 10635: "⦋", + 10636: "⦌", + 10637: "⦍", + 10638: "⦎", + 10639: "⦏", + 10640: "⦐", + 10641: "⦑", + 10642: "⦒", + 10643: "⦓", + 10644: "⦔", + 10645: "⦕", + 10646: "⦖", + 10650: "⦚", + 10652: "⦜", + 10653: "⦝", + 10660: "⦤", + 10661: "⦥", + 10662: "⦦", + 10663: "⦧", + 10664: "⦨", + 10665: "⦩", + 10666: "⦪", + 10667: "⦫", + 10668: "⦬", + 10669: "⦭", + 10670: "⦮", + 10671: "⦯", + 10672: "⦰", + 10673: "⦱", + 10674: "⦲", + 10675: "⦳", + 10676: "⦴", + 10677: "⦵", + 10678: "⦶", + 10679: "⦷", + 10681: "⦹", + 10683: "⦻", + 10684: "⦼", + 10686: "⦾", + 10687: "⦿", + 10688: "⧀", + 10689: "⧁", + 10690: "⧂", + 10691: "⧃", + 10692: "⧄", + 10693: "⧅", + 10697: "⧉", + 10701: "⧍", + 10702: "⧎", + 10703: "⧏", + 10704: "⧐", + 10714: "∽̱", + 10716: "⧜", + 10717: "⧝", + 10718: "⧞", + 10723: "⧣", + 10724: "⧤", + 10725: "⧥", + 10731: "⧫", + 10740: "⧴", + 10742: "⧶", + 10752: "⨀", + 10753: "⨁", + 10754: "⨂", + 10756: "⨄", + 10758: "⨆", + 10764: "⨌", + 10765: "⨍", + 10768: "⨐", + 10769: "⨑", + 10770: "⨒", + 10771: "⨓", + 10772: "⨔", + 10773: "⨕", + 10774: "⨖", + 10775: "⨗", + 10786: "⨢", + 10787: "⨣", + 10788: "⨤", + 10789: "⨥", + 10790: "⨦", + 10791: "⨧", + 10793: "⨩", + 10794: "⨪", + 10797: "⨭", + 10798: "⨮", + 10799: "⨯", + 10800: "⨰", + 10801: "⨱", + 10803: "⨳", + 10804: "⨴", + 10805: "⨵", + 10806: "⨶", + 10807: "⨷", + 10808: "⨸", + 10809: "⨹", + 10810: "⨺", + 10811: "⨻", + 10812: "⨼", + 10815: "⨿", + 10816: "⩀", + 10818: "⩂", + 10819: "⩃", + 10820: "⩄", + 10821: "⩅", + 10822: "⩆", + 10823: "⩇", + 10824: "⩈", + 10825: "⩉", + 10826: "⩊", + 10827: "⩋", + 10828: "⩌", + 10829: "⩍", + 10832: "⩐", + 10835: "⩓", + 10836: "⩔", + 10837: "⩕", + 10838: "⩖", + 10839: "⩗", + 10840: "⩘", + 10842: "⩚", + 10843: "⩛", + 10844: "⩜", + 10845: "⩝", + 10847: "⩟", + 10854: "⩦", + 10858: "⩪", + 10861: "⩭", + 10862: "⩮", + 10863: "⩯", + 10864: "⩰", + 10865: "⩱", + 10866: "⩲", + 10867: "⩳", + 10868: "⩴", + 10869: "⩵", + 10871: "⩷", + 10872: "⩸", + 10873: "⩹", + 10874: "⩺", + 10875: "⩻", + 10876: "⩼", + 10877: "⩽", + 10878: "⩾", + 10879: "⩿", + 10880: "⪀", + 10881: "⪁", + 10882: "⪂", + 10883: "⪃", + 10884: "⪄", + 10885: "⪅", + 10886: "⪆", + 10887: "⪇", + 10888: "⪈", + 10889: "⪉", + 10890: "⪊", + 10891: "⪋", + 10892: "⪌", + 10893: "⪍", + 10894: "⪎", + 10895: "⪏", + 10896: "⪐", + 10897: "⪑", + 10898: "⪒", + 10899: "⪓", + 10900: "⪔", + 10901: "⪕", + 10902: "⪖", + 10903: "⪗", + 10904: "⪘", + 10905: "⪙", + 10906: "⪚", + 10909: "⪝", + 10910: "⪞", + 10911: "⪟", + 10912: "⪠", + 10913: "⪡", + 10914: "⪢", + 10916: "⪤", + 10917: "⪥", + 10918: "⪦", + 10919: "⪧", + 10920: "⪨", + 10921: "⪩", + 10922: "⪪", + 10923: "⪫", + 10924: "⪬", + 10925: "⪭", + 10926: "⪮", + 10927: "⪯", + 10928: "⪰", + 10931: "⪳", + 10932: "⪴", + 10933: "⪵", + 10934: "⪶", + 10935: "⪷", + 10936: "⪸", + 10937: "⪹", + 10938: "⪺", + 10939: "⪻", + 10940: "⪼", + 10941: "⪽", + 10942: "⪾", + 10943: "⪿", + 10944: "⫀", + 10945: "⫁", + 10946: "⫂", + 10947: "⫃", + 10948: "⫄", + 10949: "⫅", + 10950: "⫆", + 10951: "⫇", + 10952: "⫈", + 10955: "⫋", + 10956: "⫌", + 10959: "⫏", + 10960: "⫐", + 10961: "⫑", + 10962: "⫒", + 10963: "⫓", + 10964: "⫔", + 10965: "⫕", + 10966: "⫖", + 10967: "⫗", + 10968: "⫘", + 10969: "⫙", + 10970: "⫚", + 10971: "⫛", + 10980: "⫤", + 10982: "⫦", + 10983: "⫧", + 10984: "⫨", + 10985: "⫩", + 10987: "⫫", + 10988: "⫬", + 10989: "⫭", + 10990: "⫮", + 10991: "⫯", + 10992: "⫰", + 10993: "⫱", + 10994: "⫲", + 10995: "⫳", + 11005: "⫽", + 64256: "ff", + 64257: "fi", + 64258: "fl", + 64259: "ffi", + 64260: "ffl", + 119964: "𝒜", + 119966: "𝒞", + 119967: "𝒟", + 119970: "𝒢", + 119973: "𝒥", + 119974: "𝒦", + 119977: "𝒩", + 119978: "𝒪", + 119979: "𝒫", + 119980: "𝒬", + 119982: "𝒮", + 119983: "𝒯", + 119984: "𝒰", + 119985: "𝒱", + 119986: "𝒲", + 119987: "𝒳", + 119988: "𝒴", + 119989: "𝒵", + 119990: "𝒶", + 119991: "𝒷", + 119992: "𝒸", + 119993: "𝒹", + 119995: "𝒻", + 119997: "𝒽", + 119998: "𝒾", + 119999: "𝒿", + 120000: "𝓀", + 120001: "𝓁", + 120002: "𝓂", + 120003: "𝓃", + 120005: "𝓅", + 120006: "𝓆", + 120007: "𝓇", + 120008: "𝓈", + 120009: "𝓉", + 120010: "𝓊", + 120011: "𝓋", + 120012: "𝓌", + 120013: "𝓍", + 120014: "𝓎", + 120015: "𝓏", + 120068: "𝔄", + 120069: "𝔅", + 120071: "𝔇", + 120072: "𝔈", + 120073: "𝔉", + 120074: "𝔊", + 120077: "𝔍", + 120078: "𝔎", + 120079: "𝔏", + 120080: "𝔐", + 120081: "𝔑", + 120082: "𝔒", + 120083: "𝔓", + 120084: "𝔔", + 120086: "𝔖", + 120087: "𝔗", + 120088: "𝔘", + 120089: "𝔙", + 120090: "𝔚", + 120091: "𝔛", + 120092: "𝔜", + 120094: "𝔞", + 120095: "𝔟", + 120096: "𝔠", + 120097: "𝔡", + 120098: "𝔢", + 120099: "𝔣", + 120100: "𝔤", + 120101: "𝔥", + 120102: "𝔦", + 120103: "𝔧", + 120104: "𝔨", + 120105: "𝔩", + 120106: "𝔪", + 120107: "𝔫", + 120108: "𝔬", + 120109: "𝔭", + 120110: "𝔮", + 120111: "𝔯", + 120112: "𝔰", + 120113: "𝔱", + 120114: "𝔲", + 120115: "𝔳", + 120116: "𝔴", + 120117: "𝔵", + 120118: "𝔶", + 120119: "𝔷", + 120120: "𝔸", + 120121: "𝔹", + 120123: "𝔻", + 120124: "𝔼", + 120125: "𝔽", + 120126: "𝔾", + 120128: "𝕀", + 120129: "𝕁", + 120130: "𝕂", + 120131: "𝕃", + 120132: "𝕄", + 120134: "𝕆", + 120138: "𝕊", + 120139: "𝕋", + 120140: "𝕌", + 120141: "𝕍", + 120142: "𝕎", + 120143: "𝕏", + 120144: "𝕐", + 120146: "𝕒", + 120147: "𝕓", + 120148: "𝕔", + 120149: "𝕕", + 120150: "𝕖", + 120151: "𝕗", + 120152: "𝕘", + 120153: "𝕙", + 120154: "𝕚", + 120155: "𝕛", + 120156: "𝕜", + 120157: "𝕝", + 120158: "𝕞", + 120159: "𝕟", + 120160: "𝕠", + 120161: "𝕡", + 120162: "𝕢", + 120163: "𝕣", + 120164: "𝕤", + 120165: "𝕥", + 120166: "𝕦", + 120167: "𝕧", + 120168: "𝕨", + 120169: "𝕩", + 120170: "𝕪", + 120171: "𝕫" +}; + +export default ToHTMLEntity; diff --git a/plugins/srktoolbox/src/core/operations/ToHex.mjs b/plugins/srktoolbox/src/core/operations/ToHex.mjs new file mode 100644 index 00000000..d1f31305 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ToHex.mjs @@ -0,0 +1,134 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import {toHex, TO_HEX_DELIM_OPTIONS} from "../lib/Hex.mjs"; +import Utils from "../Utils.mjs"; + +/** + * To Hex operation + */ +class ToHex extends Operation { + + /** + * ToHex constructor + */ + constructor() { + super(); + + this.name = "字符转十六进制"; + this.module = "Default"; + this.description = "将输入字符串转换为对应的十六进制表示(使用给定的分隔符)。

例如:UTF-8字符串 Γειά σου 编码成 ce 93 ce b5 ce b9 ce ac 20 cf 83 ce bf cf 85 0a"; + this.infoURL = "https://wikipedia.org/wiki/Hexadecimal"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + name: "分隔符", + type: "option", + value: TO_HEX_DELIM_OPTIONS + }, + { + name: "每行字节数", + type: "number", + value: 0 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let delim, comma; + if (args[0] === "0x和逗号") { + delim = "0x"; + comma = ","; + } else { + delim = Utils.charRep(args[0] || "空格"); + } + const lineSize = args[1]; + + return toHex(new Uint8Array(input), delim, 2, comma, lineSize); + } + + /** + * Highlight to Hex + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + let delim, commaLen = 0; + if (args[0] === "0x with comma") { + delim = "0x"; + commaLen = 1; + } else { + delim = Utils.charRep(args[0] || "空格"); + } + + const lineSize = args[1], + len = delim.length + commaLen; + + const countLF = function(p) { + // Count the number of LFs from 0 upto p + return (p / lineSize | 0) - (p >= lineSize && p % lineSize === 0); + }; + + pos[0].start = pos[0].start * (2 + len) + countLF(pos[0].start); + pos[0].end = pos[0].end * (2 + len) + countLF(pos[0].end); + + // if the delimiters are not prepended, trim the trailing delimiter + if (!(delim === "0x" || delim === "\\x")) { + pos[0].end -= delim.length; + } + // if there is comma, trim the trailing comma + pos[0].end -= commaLen; + return pos; + } + + /** + * Highlight from Hex + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + let delim, commaLen = 0; + if (args[0] === "0x和逗号") { + delim = "0x"; + commaLen = 1; + } else { + delim = Utils.charRep(args[0] || "空格"); + } + + const lineSize = args[1], + len = delim.length + commaLen, + width = len + 2; + + const countLF = function(p) { + // Count the number of LFs from 0 up to p + const lineLength = width * lineSize; + return (p / lineLength | 0) - (p >= lineLength && p % lineLength === 0); + }; + + pos[0].start = pos[0].start === 0 ? 0 : Math.round((pos[0].start - countLF(pos[0].start)) / width); + pos[0].end = pos[0].end === 0 ? 0 : Math.ceil((pos[0].end - countLF(pos[0].end)) / width); + return pos; + } +} + +export default ToHex; diff --git a/plugins/srktoolbox/src/core/operations/ToHexContent.mjs b/plugins/srktoolbox/src/core/operations/ToHexContent.mjs new file mode 100644 index 00000000..f2be57c8 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ToHexContent.mjs @@ -0,0 +1,85 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import {toHex} from "../lib/Hex.mjs"; + +/** + * To Hex Content operation + */ +class ToHexContent extends Operation { + + /** + * ToHexContent constructor + */ + constructor() { + super(); + + this.name = "Snort Content编码"; + this.module = "Default"; + this.description = "把字符串的特殊字符转换成十六进制。SNORT的Content关键字使用此格式。

例: foo=bar 编码为 foo|3d|bar."; + this.infoURL = "http://manual-snort-org.s3-website-us-east-1.amazonaws.com/node32.html#SECTION00451000000000000000"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "转换", + "type": "option", + "value": ["仅特殊字符", "仅特殊字符(包括空格)", "所有字符"] + }, + { + "name": "字节间用空格分隔", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + input = new Uint8Array(input); + const convert = args[0]; + const spaces = args[1]; + if (convert === "所有字符") { + let result = "|" + toHex(input) + "|"; + if (!spaces) result = result.replace(/ /g, ""); + return result; + } + + let output = "", + inHex = false, + b; + const convertSpaces = convert === "仅特殊字符(包括空格)"; + for (let i = 0; i < input.length; i++) { + b = input[i]; + if ((b === 32 && convertSpaces) || (b < 48 && b !== 32) || (b > 57 && b < 65) || (b > 90 && b < 97) || b > 122) { + if (!inHex) { + output += "|"; + inHex = true; + } else if (spaces) output += " "; + output += toHex([b]); + } else { + if (inHex) { + output += "|"; + inHex = false; + } + output += Utils.chr(input[i]); + } + } + if (inHex) output += "|"; + return output; + } + +} + +export default ToHexContent; diff --git a/plugins/srktoolbox/src/core/operations/ToHexdump.mjs b/plugins/srktoolbox/src/core/operations/ToHexdump.mjs new file mode 100644 index 00000000..496889fa --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ToHexdump.mjs @@ -0,0 +1,197 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * To Hexdump operation + */ +class ToHexdump extends Operation { + + /** + * ToHexdump constructor + */ + constructor() { + super(); + + this.name = "转换到Hexdump"; + this.module = "Default"; + this.description = "生成输入数据的hexdump,显示十六进制以及每个字节的ASCII字符。

'UNIX格式'参数用于确定显示哪些可打印的ASCII字符。"; + this.infoURL = "https://wikipedia.org/wiki/Hex_dump"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "宽度", + "type": "number", + "value": 16, + "min": 1 + }, + { + "name": "十六进制大写", + "type": "boolean", + "value": false + }, + { + "name": "包括最终长度", + "type": "boolean", + "value": false + }, + { + "name": "UNIX格式", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const data = new Uint8Array(input); + const [length, upperCase, includeFinalLength, unixFormat] = args; + const padding = 2; + + if (length < 1 || Math.round(length) !== length) + throw new OperationError("宽度必须为正整数"); + + const lines = []; + for (let i = 0; i < data.length; i += length) { + let lineNo = Utils.hex(i, 8); + + const buff = data.slice(i, i+length); + const hex = []; + buff.forEach(b => hex.push(Utils.hex(b, padding))); + let hexStr = hex.join(" ").padEnd(length*(padding+1), " "); + + const ascii = Utils.printable(Utils.byteArrayToChars(buff), false, unixFormat); + const asciiStr = ascii.padEnd(buff.length, " "); + + if (upperCase) { + hexStr = hexStr.toUpperCase(); + lineNo = lineNo.toUpperCase(); + } + + lines.push(`${lineNo} ${hexStr} |${asciiStr}|`); + + + if (includeFinalLength && i+buff.length === data.length) { + lines.push(Utils.hex(i+buff.length, 8)); + } + } + + return lines.join("\n"); + } + + /** + * Highlight To Hexdump + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + // Calculate overall selection + const w = args[0] || 16, + width = 14 + (w*4); + let line = Math.floor(pos[0].start / w), + offset = pos[0].start % w, + start = 0, + end = 0; + + pos[0].start = line*width + 10 + offset*3; + + line = Math.floor(pos[0].end / w); + offset = pos[0].end % w; + if (offset === 0) { + line--; + offset = w; + } + pos[0].end = line*width + 10 + offset*3 - 1; + + // Set up multiple selections for bytes + let startLineNum = Math.floor(pos[0].start / width); + const endLineNum = Math.floor(pos[0].end / width); + + if (startLineNum === endLineNum) { + pos.push(pos[0]); + } else { + start = pos[0].start; + end = (startLineNum+1) * width - w - 5; + pos.push({ start: start, end: end }); + while (end < pos[0].end) { + startLineNum++; + start = startLineNum * width + 10; + end = (startLineNum+1) * width - w - 5; + if (end > pos[0].end) end = pos[0].end; + pos.push({ start: start, end: end }); + } + } + + // Set up multiple selections for ASCII + const len = pos.length; + let lineNum = 0; + start = 0; + end = 0; + for (let i = 1; i < len; i++) { + lineNum = Math.floor(pos[i].start / width); + start = (((pos[i].start - (lineNum * width)) - 10) / 3) + (width - w -2) + (lineNum * width); + end = (((pos[i].end + 1 - (lineNum * width)) - 10) / 3) + (width - w -2) + (lineNum * width); + pos.push({ start: start, end: end }); + } + return pos; + } + + /** + * Highlight To Hexdump in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + const w = args[0] || 16; + const width = 14 + (w*4); + + let line = Math.floor(pos[0].start / width); + let offset = pos[0].start % width; + + if (offset < 10) { // In line number section + pos[0].start = line*w; + } else if (offset > 10+(w*3)) { // In ASCII section + pos[0].start = (line+1)*w; + } else { // In byte section + pos[0].start = line*w + Math.floor((offset-10)/3); + } + + line = Math.floor(pos[0].end / width); + offset = pos[0].end % width; + + if (offset < 10) { // In line number section + pos[0].end = line*w; + } else if (offset > 10+(w*3)) { // In ASCII section + pos[0].end = (line+1)*w; + } else { // In byte section + pos[0].end = line*w + Math.ceil((offset-10)/3); + } + + return pos; + } + +} + +export default ToHexdump; diff --git a/plugins/srktoolbox/src/core/operations/ToKebabCase.mjs b/plugins/srktoolbox/src/core/operations/ToKebabCase.mjs new file mode 100644 index 00000000..a254e439 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ToKebabCase.mjs @@ -0,0 +1,56 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import kebabCase from "lodash/kebabCase.js"; +import Operation from "../Operation.mjs"; +import { replaceVariableNames } from "../lib/Code.mjs"; + +/** + * To Kebab case operation + */ +class ToKebabCase extends Operation { + + /** + * ToKebabCase constructor + */ + constructor() { + super(); + + this.name = "转换为Kebab case"; + this.module = "Code"; + this.description = "将输入字符串转换为kebab case。\n

\nKebab case是全小写使用连字符作为单词边界的格式。\n

\n例如: this-is-kebab-case\n

\n勾选“尝试识别上下文”后此操作将尝试只转换函数和变量名。"; + this.infoURL = "https://wikipedia.org/wiki/Kebab_case"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "尝试识别上下文", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const smart = args[0]; + + if (smart) { + return replaceVariableNames(input, kebabCase); + } else { + return kebabCase(input); + } + } + +} + +export default ToKebabCase; diff --git a/plugins/srktoolbox/src/core/operations/ToLowerCase.mjs b/plugins/srktoolbox/src/core/operations/ToLowerCase.mjs new file mode 100644 index 00000000..0e49f97e --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ToLowerCase.mjs @@ -0,0 +1,67 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; + +/** + * To Lower case operation + */ +class ToLowerCase extends Operation { + + /** + * ToLowerCase constructor + */ + constructor() { + super(); + + this.name = "转换为小写"; + this.module = "Default"; + this.description = "将输入中的所有字符转换为小写。"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return input.toLowerCase(); + } + + /** + * Highlight To Lower case + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight To Lower case 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 ToLowerCase; diff --git a/plugins/srktoolbox/src/core/operations/ToMessagePack.mjs b/plugins/srktoolbox/src/core/operations/ToMessagePack.mjs new file mode 100644 index 00000000..255b0dbc --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ToMessagePack.mjs @@ -0,0 +1,55 @@ +/** + * @author Matt C [matt@artemisbot.uk] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import notepack from "notepack.io"; +import { isWorkerEnvironment } from "../Utils.mjs"; + +/** + * To MessagePack operation + */ +class ToMessagePack extends Operation { + + /** + * ToMessagePack constructor + */ + constructor() { + super(); + + this.name = "MessagePack编码"; + this.module = "Code"; + this.description = "把JSON转换成MessagePack编码的字节buffer。MessagePack是一种计算机数据交换格式。它是一种二进制形式,用于表示简单的数据结构,如数组和关联数组。"; + this.infoURL = "https://wikipedia.org/wiki/MessagePack"; + this.inputType = "JSON"; + this.outputType = "ArrayBuffer"; + this.args = []; + } + + /** + * @param {JSON} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + try { + if (isWorkerEnvironment()) { + return notepack.encode(input); + } else { + const res = notepack.encode(input); + // Safely convert from Node Buffer to ArrayBuffer using the correct view of the data + return (new Uint8Array(res)).buffer; + } + } catch (err) { + throw new OperationError(`无法将 JSON 转换为 MessagePack: ${err}`); + } + } + +} + +export default ToMessagePack; diff --git a/plugins/srktoolbox/src/core/operations/ToModhex.mjs b/plugins/srktoolbox/src/core/operations/ToModhex.mjs new file mode 100644 index 00000000..c027f502 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ToModhex.mjs @@ -0,0 +1,57 @@ +/** + * @author linuxgemini [ilteris@asenkron.com.tr] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + * + * Modified by Raka-loah@github + */ + +import Operation from "../Operation.mjs"; +import { TO_MODHEX_DELIM_OPTIONS, toModhex } from "../lib/Modhex.mjs"; +import Utils from "../Utils.mjs"; + +/** + * To Modhex operation + */ +class ToModhex extends Operation { + + /** + * ToModhex constructor + */ + constructor() { + super(); + + this.name = "Modhex编码"; + this.module = "Default"; + this.description = "将输入字符串使用给定的分隔符编码为Modhex字符串。"; + this.infoURL = "https://en.wikipedia.org/wiki/YubiKey#ModHex"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + name: "分隔符", + type: "option", + value: TO_MODHEX_DELIM_OPTIONS + }, + { + name: "每行字节数", + type: "number", + value: 0 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const delim = Utils.charRep(args[0]); + const lineSize = args[1]; + + return toModhex(new Uint8Array(input), delim, 2, "", lineSize); + } +} + +export default ToModhex; diff --git a/plugins/srktoolbox/src/core/operations/ToMorseCode.mjs b/plugins/srktoolbox/src/core/operations/ToMorseCode.mjs new file mode 100644 index 00000000..e445d5e8 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ToMorseCode.mjs @@ -0,0 +1,157 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import {LETTER_DELIM_OPTIONS, WORD_DELIM_OPTIONS} from "../lib/Delim.mjs"; + +/** + * To Morse Code operation + */ +class ToMorseCode extends Operation { + + /** + * ToMorseCode constructor + */ + constructor() { + super(); + + this.name = "摩尔斯电码编码"; + this.module = "Default"; + this.description = "把字母编码为国际摩尔斯电码。

忽略非摩尔斯码字符。

例: SOS 编码为 ... --- ..."; + this.infoURL = "https://wikipedia.org/wiki/Morse_code"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "格式", + "type": "option", + "value": ["-/.", "_/.", "Dash/Dot", "DASH/DOT", "dash/dot"] + }, + { + "name": "字母分隔符", + "type": "option", + "value": LETTER_DELIM_OPTIONS + }, + { + "name": "单词分隔符", + "type": "option", + "value": WORD_DELIM_OPTIONS + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const format = args[0].split("/"); + const dash = format[0]; + const dot = format[1]; + + const letterDelim = Utils.charRep(args[1]); + const wordDelim = Utils.charRep(args[2]); + + input = input.split(/\r?\n/); + input = Array.prototype.map.call(input, function(line) { + let words = line.split(/ +/); + words = Array.prototype.map.call(words, function(word) { + const letters = Array.prototype.map.call(word, function(character) { + const letter = character.toUpperCase(); + if (typeof MORSE_TABLE[letter] == "undefined") { + return ""; + } + + return MORSE_TABLE[letter]; + }); + + return letters.join(""); + }); + line = words.join(""); + return line; + }); + input = input.join("\n"); + + input = input.replace( + /|||/g, + function(match) { + switch (match) { + case "": return dash; + case "": return dot; + case "": return letterDelim; + case "": return wordDelim; + } + } + ); + + return input; + } + +} + +const MORSE_TABLE = { + "A": "", + "B": "", + "C": "", + "D": "", + "E": "", + "F": "", + "G": "", + "H": "", + "I": "", + "J": "", + "K": "", + "L": "", + "M": "", + "N": "", + "O": "", + "P": "", + "Q": "", + "R": "", + "S": "", + "T": "", + "U": "", + "V": "", + "W": "", + "X": "", + "Y": "", + "Z": "", + "1": "", + "2": "", + "3": "", + "4": "", + "5": "", + "6": "", + "7": "", + "8": "", + "9": "", + "0": "", + ".": "", + ",": "", + ":": "", + ";": "", + "!": "", + "?": "", + "'": "", + "\"": "", + "/": "", + "-": "", + "+": "", + "(": "", + ")": "", + "@": "", + "=": "", + "&": "", + "_": "", + "$": "", + " ": "" +}; + +export default ToMorseCode; diff --git a/plugins/srktoolbox/src/core/operations/ToOctal.mjs b/plugins/srktoolbox/src/core/operations/ToOctal.mjs new file mode 100644 index 00000000..78fe0060 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ToOctal.mjs @@ -0,0 +1,52 @@ +/** + * @author Matt C [matt@artemisbot.uk] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import {DELIM_OPTIONS} from "../lib/Delim.mjs"; + + +/** + * To Octal operation + */ +class ToOctal extends Operation { + + /** + * ToOctal constructor + */ + constructor() { + super(); + + this.name = "字符转八进制"; + this.module = "Default"; + this.description = "将输入字符串转换为对应的八进制表示(使用给定的分隔符)。

例: UTF-8编码字符串 Γειά σου 编码为 316 223 316 265 316 271 316 254 40 317 203 316 277 317 205"; + this.infoURL = "https://wikipedia.org/wiki/Octal"; + this.inputType = "byteArray"; + this.outputType = "string"; + this.args = [ + { + "name": "分隔符", + "type": "option", + "value": DELIM_OPTIONS + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const delim = Utils.charRep(args[0] || "Space"); + return input.map(val => val.toString(8)).join(delim); + } + +} + +export default ToOctal; diff --git a/plugins/srktoolbox/src/core/operations/ToPunycode.mjs b/plugins/srktoolbox/src/core/operations/ToPunycode.mjs new file mode 100644 index 00000000..6bdc6d73 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ToPunycode.mjs @@ -0,0 +1,55 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import punycode from "punycode"; + +/** + * To Punycode operation + */ +class ToPunycode extends Operation { + + /** + * ToPunycode constructor + */ + constructor() { + super(); + + this.name = "Punycode编码"; + this.module = "Encodings"; + this.description = "Punycode是用ASCII字符的一个子集来编码Unicode域名的一种方法。

例: m\xfcnchen 编码为 mnchen-3ya"; + this.infoURL = "https://wikipedia.org/wiki/Punycode"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "国际化域名(带xn--)", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const idn = args[0]; + + if (idn) { + return punycode.toASCII(input); + } else { + return punycode.encode(input); + } + } + +} + +export default ToPunycode; diff --git a/plugins/srktoolbox/src/core/operations/ToQuotedPrintable.mjs b/plugins/srktoolbox/src/core/operations/ToQuotedPrintable.mjs new file mode 100644 index 00000000..92a5efcf --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ToQuotedPrintable.mjs @@ -0,0 +1,248 @@ +/** + * Some parts taken from mimelib (http://github.com/andris9/mimelib) + * @author Andris Reinman + * @license MIT + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; + +/** + * To Quoted Printable operation + */ +class ToQuotedPrintable extends Operation { + + /** + * ToQuotedPrintable constructor + */ + constructor() { + super(); + + this.name = "QP编码"; + this.module = "Default"; + this.description = "Quoted-printable或QP encoding,没有规范的中文译名,可译为可打印字符引用编码或使用可打印字符的编码。Quoted-printable是使用可打印的ASCII字符(如字母、数字与“=”)表示各种编码格式下的字符,以便能在7-bit数据通路上传输8-bit数据, 或者更一般地说在非8-bit clean媒体上正确处理数据。它被定义为在Email中使用的MIME。

QP使用“=”开头的转义字符。一般限制行宽为76,因为有些软件限制了行宽。"; + this.infoURL = "https://wikipedia.org/wiki/Quoted-printable"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + input = new Uint8Array(input); + let mimeEncodedStr = this.mimeEncode(input); + + // fix line breaks + mimeEncodedStr = mimeEncodedStr.replace(/\r?\n|\r/g, function() { + return "\r\n"; + }).replace(/[\t ]+$/gm, function(spaces) { + return spaces.replace(/ /g, "=20").replace(/\t/g, "=09"); + }); + + return this._addSoftLinebreaks(mimeEncodedStr, "qp"); + } + + + /** @license + ======================================================================== + mimelib: http://github.com/andris9/mimelib + Copyright (c) 2011-2012 Andris Reinman + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + + /** + * Encodes mime data. + * + * @param {byteArray|Uint8Array} buffer + * @returns {string} + */ + mimeEncode(buffer) { + const ranges = [ + [0x09], + [0x0A], + [0x0D], + [0x20], + [0x21], + [0x23, 0x3C], + [0x3E], + [0x40, 0x5E], + [0x60, 0x7E] + ]; + let result = ""; + + for (let i = 0, len = buffer.length; i < len; i++) { + if (this._checkRanges(buffer[i], ranges)) { + result += String.fromCharCode(buffer[i]); + continue; + } + result += "=" + (buffer[i] < 0x10 ? "0" : "") + buffer[i].toString(16).toUpperCase(); + } + + return result; + } + + /** + * Checks if a given number falls within a given set of ranges. + * + * @private + * @param {number} nr + * @param {byteArray[]} ranges + * @returns {boolean} + */ + _checkRanges(nr, ranges) { + for (let i = ranges.length - 1; i >= 0; i--) { + if (!ranges[i].length) + continue; + if (ranges[i].length === 1 && nr === ranges[i][0]) + return true; + if (ranges[i].length === 2 && nr >= ranges[i][0] && nr <= ranges[i][1]) + return true; + } + return false; + } + + /** + * Adds soft line breaks to a string. + * Lines can't be longer that 76 + = 78 bytes + * http://tools.ietf.org/html/rfc2045#section-6.7 + * + * @private + * @param {string} str + * @param {string} encoding + * @returns {string} + */ + _addSoftLinebreaks(str, encoding) { + const lineLengthMax = 76; + + encoding = (encoding || "base64").toString().toLowerCase().trim(); + + if (encoding === "qp") { + return this._addQPSoftLinebreaks(str, lineLengthMax); + } else { + return this._addBase64SoftLinebreaks(str, lineLengthMax); + } + } + + /** + * Adds soft line breaks to a base64 string. + * + * @private + * @param {string} base64EncodedStr + * @param {number} lineLengthMax + * @returns {string} + */ + _addBase64SoftLinebreaks(base64EncodedStr, lineLengthMax) { + base64EncodedStr = (base64EncodedStr || "").toString().trim(); + return base64EncodedStr.replace(new RegExp(".{" + lineLengthMax + "}", "g"), "$&\r\n").trim(); + } + + /** + * Adds soft line breaks to a quoted printable string. + * + * @private + * @param {string} mimeEncodedStr + * @param {number} lineLengthMax + * @returns {string} + */ + _addQPSoftLinebreaks(mimeEncodedStr, lineLengthMax) { + const len = mimeEncodedStr.length, + lineMargin = Math.floor(lineLengthMax / 3); + let pos = 0, + match, code, line, + result = ""; + + // insert soft linebreaks where needed + while (pos < len) { + line = mimeEncodedStr.substr(pos, lineLengthMax); + if ((match = line.match(/\r\n/))) { + line = line.substr(0, match.index + match[0].length); + result += line; + pos += line.length; + continue; + } + + if (line.substr(-1) === "\n") { + // nothing to change here + result += line; + pos += line.length; + continue; + } else if ((match = line.substr(-lineMargin).match(/\n.*?$/))) { + // truncate to nearest line break + line = line.substr(0, line.length - (match[0].length - 1)); + result += line; + pos += line.length; + continue; + } else if (line.length > lineLengthMax - lineMargin && (match = line.substr(-lineMargin).match(/[ \t.,!?][^ \t.,!?]*$/))) { + // truncate to nearest space + line = line.substr(0, line.length - (match[0].length - 1)); + } else if (line.substr(-1) === "\r") { + line = line.substr(0, line.length - 1); + } else { + if (line.match(/=[\da-f]{0,2}$/i)) { + + // push incomplete encoding sequences to the next line + if ((match = line.match(/=[\da-f]{0,1}$/i))) { + line = line.substr(0, line.length - match[0].length); + } + + // ensure that utf-8 sequences are not split + while (line.length > 3 && line.length < len - pos && !line.match(/^(?:=[\da-f]{2}){1,4}$/i) && (match = line.match(/=[\da-f]{2}$/ig))) { + code = parseInt(match[0].substr(1, 2), 16); + if (code < 128) { + break; + } + + line = line.substr(0, line.length - 3); + + if (code >= 0xC0) { + break; + } + } + + } + } + + if (pos + line.length < len && line.substr(-1) !== "\n") { + if (line.length === 76 && line.match(/=[\da-f]{2}$/i)) { + line = line.substr(0, line.length - 3); + } else if (line.length === 76) { + line = line.substr(0, line.length - 1); + } + pos += line.length; + line += "=\r\n"; + } else { + pos += line.length; + } + + result += line; + } + + return result; + } + +} + +export default ToQuotedPrintable; diff --git a/plugins/srktoolbox/src/core/operations/ToSnakeCase.mjs b/plugins/srktoolbox/src/core/operations/ToSnakeCase.mjs new file mode 100644 index 00000000..a0a28d5f --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ToSnakeCase.mjs @@ -0,0 +1,55 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import snakeCase from "lodash/snakeCase.js"; +import Operation from "../Operation.mjs"; +import { replaceVariableNames } from "../lib/Code.mjs"; + +/** + * To Snake case operation + */ +class ToSnakeCase extends Operation { + + /** + * ToSnakeCase constructor + */ + constructor() { + super(); + + this.name = "转换为Snake case"; + this.module = "Code"; + this.description = "将输入字符串转换为snake case。\n

\nSnake case是全小写并使用下划线作为单词分隔的格式。\n

\n例如: this_is_snake_case\n

\n勾选“尝试识别上下文”后此操作将尝试只转换函数和变量名。"; + this.infoURL = "https://wikipedia.org/wiki/Snake_case"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "尝试识别上下文", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const smart = args[0]; + + if (smart) { + return replaceVariableNames(input, snakeCase); + } else { + return snakeCase(input); + } + } +} + +export default ToSnakeCase; diff --git a/plugins/srktoolbox/src/core/operations/ToTable.mjs b/plugins/srktoolbox/src/core/operations/ToTable.mjs new file mode 100644 index 00000000..29426857 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ToTable.mjs @@ -0,0 +1,248 @@ +/** + * @author Mark Jones [github.com/justanothermark] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +/** + * To Table operation + */ +class ToTable extends Operation { + + /** + * ToTable constructor + */ + constructor() { + super(); + + this.name = "转换为表格"; + this.module = "Default"; + this.description = "用给定的分隔符分隔数据后渲染成HTML、ASCII或Markdown表格,可以额外添加表头。

默认支持CSV(逗号分隔)格式。将单元格分隔符修改为 \\t 用于支持TSV(Tab分隔),修改为 | 用于支持PSV(管道符分隔)。

你可以输入任意个数的分隔符,每个字符都会用作单独的分隔符。"; + this.infoURL = "https://wikipedia.org/wiki/Comma-separated_values"; + this.inputType = "string"; + this.outputType = "html"; + this.args = [ + { + "name": "单元格分隔符", + "type": "binaryShortString", + "value": "," + }, + { + "name": "行分隔符", + "type": "binaryShortString", + "value": "\\r\\n" + }, + { + "name": "第一行作为表头", + "type": "boolean", + "value": false + }, + { + "name": "格式", + "type": "option", + "value": ["ASCII", "HTML", "Markdown"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + const [cellDelims, rowDelims, firstRowHeader, format] = args; + + // Process the input into a nested array of elements. + const tableData = Utils.parseCSV(Utils.escapeHtml(input), cellDelims.split(""), rowDelims.split("")); + + if (!tableData.length) return ""; + + // Render the data in the requested format. + switch (format) { + case "ASCII": + return asciiOutput(tableData); + case "HTML": + return htmlOutput(tableData); + case "Markdown": + return markdownOutput(tableData); + default: + return htmlOutput(tableData); + } + + /** + * Outputs an array of data as an ASCII table. + * + * @param {string[][]} tableData + * @returns {string} + */ + function asciiOutput(tableData) { + const horizontalBorder = "-"; + const verticalBorder = "|"; + const crossBorder = "+"; + + let output = ""; + const longestCells = []; + + // Find longestCells value per column to pad cells equally. + tableData.forEach(function(row, index) { + row.forEach(function(cell, cellIndex) { + if (longestCells[cellIndex] === undefined || cell.length > longestCells[cellIndex]) { + longestCells[cellIndex] = cell.length; + } + }); + }); + + // Add the top border of the table to the output. + output += outputHorizontalBorder(longestCells); + + // If the first row is a header, remove the row from the data and + // add it to the output with another horizontal border. + if (firstRowHeader) { + const row = tableData.shift(); + output += outputRow(row, longestCells); + output += outputHorizontalBorder(longestCells); + } + + // Add the rest of the table rows. + tableData.forEach(function(row, index) { + output += outputRow(row, longestCells); + }); + + // Close the table with a final horizontal border. + output += outputHorizontalBorder(longestCells); + + return output; + + /** + * Outputs a row of correctly padded cells. + */ + function outputRow(row, longestCells) { + let rowOutput = verticalBorder; + row.forEach(function(cell, index) { + rowOutput += " " + cell + " ".repeat(longestCells[index] - cell.length) + " " + verticalBorder; + }); + rowOutput += "\n"; + return rowOutput; + } + + /** + * Outputs a horizontal border with a different character where + * the horizontal border meets a vertical border. + */ + function outputHorizontalBorder(longestCells) { + let rowOutput = crossBorder; + longestCells.forEach(function(cellLength) { + rowOutput += horizontalBorder.repeat(cellLength + 2) + crossBorder; + }); + rowOutput += "\n"; + return rowOutput; + } + } + + /** + * Outputs a table of data as a HTML table. + * + * @param {string[][]} tableData + * @returns {string} + */ + function htmlOutput(tableData) { + // Start the HTML output with suitable classes for styling. + let output = "
"; + + // If the first row is a header then put it in with "; + output += outputRow(row, "th"); + output += ""; + } + + // Output the rest of the rows in the . + output += ""; + tableData.forEach(function(row, index) { + output += outputRow(row, "td"); + }); + + // Close the body and table elements. + output += "
cells. + if (firstRowHeader) { + const row = tableData.shift(); + output += "
"; + return output; + + /** + * Outputs a table row. + * + * @param {string[]} row + * @param {string} cellType + */ + function outputRow(row, cellType) { + let output = ""; + row.forEach(function(cell) { + output += "<" + cellType + ">" + cell + ""; + }); + output += ""; + return output; + } + } + + /** + * Outputs an array of data as a Markdown table. + * + * @param {string[][]} tableData + * @returns {string} + */ + function markdownOutput(tableData) { + const headerDivider = "-"; + const verticalBorder = "|"; + + let output = ""; + const longestCells = []; + + // Find longestCells value per column to pad cells equally. + tableData.forEach(function(row, index) { + row.forEach(function(cell, cellIndex) { + if (longestCells[cellIndex] === undefined || cell.length > longestCells[cellIndex]) { + longestCells[cellIndex] = cell.length; + } + }); + }); + + // Ignoring the checkbox, as current Mardown renderer in CF doesn't handle table without headers + const row = tableData.shift(); + output += outputRow(row, longestCells); + let rowOutput = verticalBorder; + row.forEach(function(cell, index) { + rowOutput += " " + headerDivider.repeat(longestCells[index]) + " " + verticalBorder; + }); + output += rowOutput += "\n"; + + // Add the rest of the table rows. + tableData.forEach(function(row, index) { + output += outputRow(row, longestCells); + }); + + return output; + + /** + * Outputs a row of correctly padded cells. + */ + function outputRow(row, longestCells) { + let rowOutput = verticalBorder; + row.forEach(function(cell, index) { + rowOutput += " " + cell + " ".repeat(longestCells[index] - cell.length) + " " + verticalBorder; + }); + rowOutput += "\n"; + return rowOutput; + } + + } + + } + +} + +export default ToTable; diff --git a/plugins/srktoolbox/src/core/operations/ToUNIXTimestamp.mjs b/plugins/srktoolbox/src/core/operations/ToUNIXTimestamp.mjs new file mode 100644 index 00000000..ba9d1cbb --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ToUNIXTimestamp.mjs @@ -0,0 +1,80 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import moment from "moment-timezone"; +import {UNITS} from "../lib/DateTime.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * To UNIX Timestamp operation + */ +class ToUNIXTimestamp extends Operation { + + /** + * ToUNIXTimestamp constructor + */ + constructor() { + super(); + + this.name = "转换到UNIX时间戳"; + this.module = "Default"; + this.description = "解析DateTime字符串(UTC时区)并返回对应的UNIX时间戳。

例: Mon 1 January 2001 11:00:00 转换为 978346800

UNIX时间,或称POSIX时间是UNIX或类UNIX系统使用的时间表示方式:从UTC1970年1月1日0时0分0秒起至现在的总秒数,不考虑闰秒。"; + this.infoURL = "https://wikipedia.org/wiki/Unix_time"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "单位", + "type": "option", + "value": UNITS + }, + { + "name": "当作UTC时间", + "type": "boolean", + "value": true + }, + { + "name": "显示解析后的DateTime", + "type": "boolean", + "value": true + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + * + * @throws {OperationError} if unit unrecognised + */ + run(input, args) { + const [units, treatAsUTC, showDateTime] = args, + d = treatAsUTC ? moment.utc(input) : moment(input); + + let result = ""; + + if (units === "秒 (s)") { + result = d.unix(); + } else if (units === "毫秒 (ms)") { + result = d.valueOf(); + } else if (units === "微秒 (μs)") { + result = d.valueOf() * 1000; + } else if (units === "纳秒 (ns)") { + result = d.valueOf() * 1000000; + } else { + throw new OperationError("无效单位"); + } + + return showDateTime ? `${result} (${d.tz("UTC").format("ddd D MMMM YYYY HH:mm:ss")} UTC)` : result.toString(); + } + +} + +export default ToUNIXTimestamp; diff --git a/plugins/srktoolbox/src/core/operations/ToUpperCase.mjs b/plugins/srktoolbox/src/core/operations/ToUpperCase.mjs new file mode 100644 index 00000000..c7cdcd1e --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ToUpperCase.mjs @@ -0,0 +1,97 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * To Upper case operation + */ +class ToUpperCase extends Operation { + + /** + * ToUpperCase constructor + */ + constructor() { + super(); + + this.name = "转换为大写"; + this.module = "Default"; + this.description = "将输入字符串转换成大写,可选限制为每个单词、句子或段落首字母大写。"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "粒度", + "type": "option", + "value": ["所有字符", "单词", "句子", "段落"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + if (!args || args.length === 0) { + throw new OperationError("未提供操作粒度信息。"); + } + + const scope = args[0]; + + if (scope === "所有字符") { + return input.toUpperCase(); + } + + const scopeRegex = { + "单词": /(\b\w)/gi, + "句子": /(?:\.|^)\s*(\b\w)/gi, + "段落": /(?:\n|^)\s*(\b\w)/gi + }[scope]; + + if (scopeRegex === undefined) { + throw new OperationError("无效的操作粒度。"); + } + + // Use the regex to capitalize the input + return input.replace(scopeRegex, function(m) { + return m.toUpperCase(); + }); + } + + /** + * Highlight To Upper case + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight To Upper case 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 ToUpperCase; diff --git a/plugins/srktoolbox/src/core/operations/TranslateDateTimeFormat.mjs b/plugins/srktoolbox/src/core/operations/TranslateDateTimeFormat.mjs new file mode 100644 index 00000000..695b405f --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/TranslateDateTimeFormat.mjs @@ -0,0 +1,95 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import moment from "moment-timezone"; +import {DATETIME_FORMATS, FORMAT_EXAMPLES} from "../lib/DateTime.mjs"; + +/** + * Translate DateTime Format operation + */ +class TranslateDateTimeFormat extends Operation { + + /** + * TranslateDateTimeFormat constructor + */ + constructor() { + super(); + + this.name = "转换DateTime格式"; + this.module = "Default"; + this.description = "将输入的DateTime转换成另一种格式。

不输入任何内容来查看格式示例字符串。"; + this.infoURL = "https://momentjs.com/docs/#/parsing/string-format/"; + this.inputType = "string"; + this.outputType = "string"; + this.presentType = "html"; + this.args = [ + { + "name": "内置格式", + "type": "populateOption", + "value": DATETIME_FORMATS, + "target": 1 + }, + { + "name": "输入格式", + "type": "binaryString", + "value": "DD/MM/YYYY HH:mm:ss" + }, + { + "name": "输入时区", + "type": "option", + "value": ["UTC"].concat(moment.tz.names()) + }, + { + "name": "输出格式", + "type": "binaryString", + "value": "dddd Do MMMM YYYY HH:mm:ss Z z" + }, + { + "name": "输出时区", + "type": "option", + "value": ["UTC"].concat(moment.tz.names()) + } + ]; + + this.invalidFormatMessage = "无效格式。"; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [inputFormat, inputTimezone, outputFormat, outputTimezone] = args.slice(1); + let date; + + try { + date = moment.tz(input, inputFormat, inputTimezone); + if (!date || date.format() === "Invalid date") throw Error; + } catch (err) { + return this.invalidFormatMessage; + } + + return date.tz(outputTimezone).format(outputFormat.replace(/[<>]/g, "")); + } + + /** + * @param {string} data + * @returns {html} + */ + present(data) { + if (data === this.invalidFormatMessage) { + return `${data}\n\n${FORMAT_EXAMPLES}`; + } + return Utils.escapeHtml(data); + } +} + +export default TranslateDateTimeFormat; diff --git a/plugins/srktoolbox/src/core/operations/TripleDESDecrypt.mjs b/plugins/srktoolbox/src/core/operations/TripleDESDecrypt.mjs new file mode 100644 index 00000000..7175af54 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/TripleDESDecrypt.mjs @@ -0,0 +1,112 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import forge from "node-forge"; + +/** + * Triple DES Decrypt operation + */ +class TripleDESDecrypt extends Operation { + + /** + * TripleDESDecrypt constructor + */ + constructor() { + super(); + + this.name = "3DES解密"; + this.module = "Ciphers"; + this.description = "Triple DES applies DES three times to each block to increase key size.

Key: Triple DES uses a key length of 24 bytes (192 bits).

IV: The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.

Padding: In CBC and ECB mode, PKCS#7 padding will be used as a default."; + this.infoURL = "https://wikipedia.org/wiki/Triple_DES"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "IV", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "模式", + "type": "option", + "value": ["CBC", "CFB", "OFB", "CTR", "ECB", "CBC/NoPadding", "ECB/NoPadding"] + }, + { + "name": "输入", + "type": "option", + "value": ["十六进制", "原始内容"] + }, + { + "name": "输出", + "type": "option", + "value": ["原始内容", "十六进制"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = Utils.convertToByteString(args[0].string, args[0].option), + iv = Utils.convertToByteArray(args[1].string, args[1].option), + mode = args[2].substring(0, 3), + noPadding = args[2].endsWith("NoPadding"), + inputType = args[3], + outputType = args[4]; + + if (key.length !== 24 && key.length !== 16) { + throw new OperationError(`无效的key长度: ${key.length} 字节 + +Triple DES uses a key length of 24 bytes (192 bits).`); + } + if (iv.length !== 8 && mode !== "ECB") { + throw new OperationError(`无效的IV长度: ${iv.length} 字节 + +三重DES的IV长度为8字节(64位)。 +核实IV格式选取正确(例:十六进制或UTF8)。`); + } + + input = Utils.convertToByteString(input, inputType); + + const decipher = forge.cipher.createDecipher("3DES-" + mode, + key.length === 16 ? key + key.substring(0, 8) : key); + + /* Allow for a "no padding" mode */ + if (noPadding) { + decipher.mode.unpad = function(output, options) { + return true; + }; + } + + decipher.start({iv: iv}); + decipher.update(forge.util.createBuffer(input)); + const result = decipher.finish(); + + if (result) { + return outputType === "十六进制" ? decipher.output.toHex() : decipher.output.getBytes(); + } else { + throw new OperationError("无法解密,参数错误"); + } + } + +} + +export default TripleDESDecrypt; diff --git a/plugins/srktoolbox/src/core/operations/TripleDESEncrypt.mjs b/plugins/srktoolbox/src/core/operations/TripleDESEncrypt.mjs new file mode 100644 index 00000000..a2a20198 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/TripleDESEncrypt.mjs @@ -0,0 +1,99 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import forge from "node-forge"; + +/** + * Triple DES Encrypt operation + */ +class TripleDESEncrypt extends Operation { + + /** + * TripleDESEncrypt constructor + */ + constructor() { + super(); + + this.name = "3DES加密"; + this.module = "Ciphers"; + this.description = "Triple DES applies DES three times to each block to increase key size.

Key: Triple DES uses a key length of 24 bytes (192 bits).

You can generate a password-based key using one of the KDF operations.

IV: The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.

Padding: In CBC and ECB mode, PKCS#7 padding will be used."; + this.infoURL = "https://wikipedia.org/wiki/Triple_DES"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "IV", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "模式", + "type": "option", + "value": ["CBC", "CFB", "OFB", "CTR", "ECB"] + }, + { + "name": "输入", + "type": "option", + "value": ["原始内容", "十六进制"] + }, + { + "name": "输出", + "type": "option", + "value": ["十六进制", "原始内容"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = Utils.convertToByteString(args[0].string, args[0].option), + iv = Utils.convertToByteArray(args[1].string, args[1].option), + mode = args[2], + inputType = args[3], + outputType = args[4]; + + if (key.length !== 24 && key.length !== 16) { + throw new OperationError(`无效的key长度: ${key.length} 字节 + +Triple DES uses a key length of 24 bytes (192 bits).`); + } + if (iv.length !== 8 && mode !== "ECB") { + throw new OperationError(`无效的IV长度: ${iv.length} 字节 + +三重DES的IV长度为8字节(64位)。 +核实IV格式选取正确(例:十六进制或UTF8)。`); + } + + input = Utils.convertToByteString(input, inputType); + + const cipher = forge.cipher.createCipher("3DES-" + mode, + key.length === 16 ? key + key.substring(0, 8) : key); + cipher.start({iv: iv}); + cipher.update(forge.util.createBuffer(input)); + cipher.finish(); + + return outputType === "十六进制" ? cipher.output.toHex() : cipher.output.getBytes(); + } + +} + +export default TripleDESEncrypt; diff --git a/plugins/srktoolbox/src/core/operations/Typex.mjs b/plugins/srktoolbox/src/core/operations/Typex.mjs new file mode 100644 index 00000000..5c9d952e --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Typex.mjs @@ -0,0 +1,253 @@ +/** + * Emulation of the Typex machine. + * + * Tested against a genuine Typex machine using a variety of inputs + * and settings to confirm correctness. + * + * @author s2224834 + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import {LETTERS, Reflector} from "../lib/Enigma.mjs"; +import {ROTORS, REFLECTORS, TypexMachine, Plugboard, Rotor} from "../lib/Typex.mjs"; + +/** + * Typex operation + */ +class Typex extends Operation { + /** + * Typex constructor + */ + constructor() { + super(); + + this.name = "Typex"; + this.module = "Bletchley"; + this.description = "Encipher/decipher with the WW2 Typex machine.

Typex was originally built by the British Royal Air Force prior to WW2, and is based on the Enigma machine with some improvements made, including using five rotors with more stepping points and interchangeable wiring cores. It was used across the British and Commonwealth militaries. A number of later variants were produced; here we simulate a WW2 era Mark 22 Typex with plugboards for the reflector and input. Typex rotors were changed regularly and none are public: a random example set are provided.

To configure the reflector plugboard, enter a string of connected pairs of letters in the reflector box, e.g. AB CD EF connects A to B, C to D, and E to F (you'll need to connect every letter). There is also an input plugboard: unlike Enigma's plugboard, it's not restricted to pairs, so it's entered like a rotor (without stepping). To create your own rotor, enter the letters that the rotor maps A to Z to, in order, optionally followed by < then a list of stepping points.

More detailed descriptions of the Enigma, Typex and Bombe operations can be found here."; + this.infoURL = "https://wikipedia.org/wiki/Typex"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "1st (left-hand) rotor", + type: "editableOption", + value: ROTORS, + defaultIndex: 0 + }, + { + name: "1st rotor reversed", + type: "boolean", + value: false + }, + { + name: "1st rotor ring setting", + type: "option", + value: LETTERS + }, + { + name: "1st rotor initial value", + type: "option", + value: LETTERS + }, + { + name: "2nd rotor", + type: "editableOption", + value: ROTORS, + defaultIndex: 1 + }, + { + name: "2nd rotor reversed", + type: "boolean", + value: false + }, + { + name: "2nd rotor ring setting", + type: "option", + value: LETTERS + }, + { + name: "2nd rotor initial value", + type: "option", + value: LETTERS + }, + { + name: "3rd (middle) rotor", + type: "editableOption", + value: ROTORS, + defaultIndex: 2 + }, + { + name: "3rd rotor reversed", + type: "boolean", + value: false + }, + { + name: "3rd rotor ring setting", + type: "option", + value: LETTERS + }, + { + name: "3rd rotor initial value", + type: "option", + value: LETTERS + }, + { + name: "4th (static) rotor", + type: "editableOption", + value: ROTORS, + defaultIndex: 3 + }, + { + name: "4th rotor reversed", + type: "boolean", + value: false + }, + { + name: "4th rotor ring setting", + type: "option", + value: LETTERS + }, + { + name: "4th rotor initial value", + type: "option", + value: LETTERS + }, + { + name: "5th (right-hand, static) rotor", + type: "editableOption", + value: ROTORS, + defaultIndex: 4 + }, + { + name: "5th rotor reversed", + type: "boolean", + value: false + }, + { + name: "5th rotor ring setting", + type: "option", + value: LETTERS + }, + { + name: "5th rotor initial value", + type: "option", + value: LETTERS + }, + { + name: "Reflector", + type: "editableOption", + value: REFLECTORS + }, + { + name: "Plugboard", + type: "string", + value: "" + }, + { + name: "Typex keyboard emulation", + type: "option", + value: ["None", "Encrypt", "Decrypt"] + }, + { + name: "Strict output", + hint: "Remove non-alphabet letters and group output", + type: "boolean", + value: true + }, + ]; + } + + /** + * Helper - for ease of use rotors are specified as a single string; this + * method breaks the spec string into wiring and steps parts. + * + * @param {string} rotor - Rotor specification string. + * @param {number} i - For error messages, the number of this rotor. + * @returns {string[]} + */ + parseRotorStr(rotor, i) { + if (rotor === "") { + throw new OperationError(`Rotor ${i} must be provided.`); + } + if (!rotor.includes("<")) { + return [rotor, ""]; + } + return rotor.split("<", 2); + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const reflectorstr = args[20]; + const plugboardstr = args[21]; + const typexKeyboard = args[22]; + const removeOther = args[23]; + const rotors = []; + for (let i=0; i<5; i++) { + const [rotorwiring, rotorsteps] = this.parseRotorStr(args[i*4]); + rotors.push(new Rotor(rotorwiring, rotorsteps, args[i*4 + 1], args[i*4+2], args[i*4+3])); + } + // Rotors are handled in reverse + rotors.reverse(); + const reflector = new Reflector(reflectorstr); + let plugboardstrMod = plugboardstr; + if (plugboardstrMod === "") { + plugboardstrMod = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + } + const plugboard = new Plugboard(plugboardstrMod); + if (removeOther) { + if (typexKeyboard === "Encrypt") { + input = input.replace(/[^A-Za-z0-9 /%£()',.-]/g, ""); + } else { + input = input.replace(/[^A-Za-z]/g, ""); + } + } + const typex = new TypexMachine(rotors, reflector, plugboard, typexKeyboard); + let result = typex.crypt(input); + if (removeOther && typexKeyboard !== "Decrypt") { + // Five character cipher groups is traditional + result = result.replace(/([A-Z]{5})(?!$)/g, "$1 "); + } + return result; + } + + /** + * Highlight Typex + * This is only possible if we're passing through non-alphabet characters. + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + if (args[18] === false) { + return pos; + } + } + + /** + * Highlight Typex in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + if (args[18] === false) { + return pos; + } + } + +} + +export default Typex; diff --git a/plugins/srktoolbox/src/core/operations/UNIXTimestampToWindowsFiletime.mjs b/plugins/srktoolbox/src/core/operations/UNIXTimestampToWindowsFiletime.mjs new file mode 100644 index 00000000..4110a6e1 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/UNIXTimestampToWindowsFiletime.mjs @@ -0,0 +1,95 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import BigNumber from "bignumber.js"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * UNIX Timestamp to Windows Filetime operation + */ +class UNIXTimestampToWindowsFiletime extends Operation { + + /** + * UNIXTimestampToWindowsFiletime constructor + */ + constructor() { + super(); + + this.name = "UNIX时间戳转Windows Filetime"; + this.module = "Default"; + this.description = "将UNIX时间戳转换为Windows Filetime数值。br>
Windows Filetime是对应从1601年1月1日(UTC)开始的以100纳秒为单位的64位数值。

UNIX 时间戳是对应从1970年1月1日(UTC)开始的以秒为单位的32位数值。

此操作也支持不同的UNIX时间单位如毫秒、微秒和纳秒。"; + this.infoURL = "https://msdn.microsoft.com/en-us/library/windows/desktop/ms724284(v=vs.85).aspx"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "输入单位", + "type": "option", + "value": ["秒 (s)", "毫秒 (ms)", "微秒 (μs)", "纳秒 (ns)"] + }, + { + "name": "输出格式", + "type": "option", + "value": ["十进制", "十六进制 (大端序)", "十六进制 (小端序)"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [units, format] = args; + + if (!input) return ""; + + input = new BigNumber(input); + + if (units === "秒 (s)") { + input = input.multipliedBy(new BigNumber("10000000")); + } else if (units === "毫秒 (ms)") { + input = input.multipliedBy(new BigNumber("10000")); + } else if (units === "微秒 (μs)") { + input = input.multipliedBy(new BigNumber("10")); + } else if (units === "纳秒 (ns)") { + input = input.dividedBy(new BigNumber("100")); + } else { + throw new OperationError("无效单位"); + } + + input = input.plus(new BigNumber("116444736000000000")); + + let result; + if (format.startsWith("十六进制")) { + result = input.toString(16); + } else { + result = input.toFixed(); + } + + if (format === "十六进制 (小端序)") { + // Swap endianness + let flipped = ""; + for (let i = result.length - 2; i >= 0; i -= 2) { + flipped += result.charAt(i); + flipped += result.charAt(i + 1); + } + if (result.length % 2 !== 0) { + flipped += "0" + result.charAt(0); + } + result = flipped; + } + + return result; + } + +} + +export default UNIXTimestampToWindowsFiletime; diff --git a/plugins/srktoolbox/src/core/operations/URLDecode.mjs b/plugins/srktoolbox/src/core/operations/URLDecode.mjs new file mode 100644 index 00000000..6923f774 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/URLDecode.mjs @@ -0,0 +1,61 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; + +/** + * URL Decode operation + */ +class URLDecode extends Operation { + + /** + * URLDecode constructor + */ + constructor() { + super(); + + this.name = "URL解码"; + this.module = "URL"; + this.description = "把URI/URL百分号编码的内容解码为原始内容。

例: %3d 解码为 ="; + this.infoURL = "https://wikipedia.org/wiki/Percent-encoding"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Treat \"+\" as space", + "type": "boolean", + "value": true + }, + ]; + this.checks = [ + { + pattern: ".*(?:%[\\da-f]{2}.*){4}", + flags: "i", + args: [] + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const plusIsSpace = args[0]; + const data = plusIsSpace ? input.replace(/\+/g, "%20") : input; + try { + return decodeURIComponent(data); + } catch (err) { + return unescape(data); + } + } + +} + +export default URLDecode; diff --git a/plugins/srktoolbox/src/core/operations/URLEncode.mjs b/plugins/srktoolbox/src/core/operations/URLEncode.mjs new file mode 100644 index 00000000..d7c037cc --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/URLEncode.mjs @@ -0,0 +1,71 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; + +/** + * URL Encode operation + */ +class URLEncode extends Operation { + + /** + * URLEncode constructor + */ + constructor() { + super(); + + this.name = "URL编码"; + this.module = "URL"; + this.description = "把特殊字符编码为百分号开头的形式,即URI/URL编码。

例: = 编码为 %3d"; + this.infoURL = "https://wikipedia.org/wiki/Percent-encoding"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "编码所有特殊字符", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const encodeAll = args[0]; + return encodeAll ? this.encodeAllChars(input) : encodeURI(input); + } + + /** + * Encode characters in URL outside of encodeURI() function spec + * + * @param {string} str + * @returns {string} + */ + encodeAllChars (str) { + // TODO Do this programmatically + return encodeURIComponent(str) + .replace(/!/g, "%21") + .replace(/#/g, "%23") + .replace(/'/g, "%27") + .replace(/\(/g, "%28") + .replace(/\)/g, "%29") + .replace(/\*/g, "%2A") + .replace(/-/g, "%2D") + .replace(/\./g, "%2E") + .replace(/_/g, "%5F") + .replace(/~/g, "%7E"); + } + +} + + +export default URLEncode; diff --git a/plugins/srktoolbox/src/core/operations/UnescapeString.mjs b/plugins/srktoolbox/src/core/operations/UnescapeString.mjs new file mode 100644 index 00000000..30e283a8 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/UnescapeString.mjs @@ -0,0 +1,43 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +/** + * Unescape string operation + */ +class UnescapeString extends Operation { + + /** + * UnescapeString constructor + */ + constructor() { + super(); + + this.name = "字符串转义恢复"; + this.module = "Default"; + this.description = "将已经转义的字符串恢复。例如,Don\\'t stop me now 恢复为 Don't stop me now

支持以下的字符转义:
  • \\n (换行,LF)
  • \\r (回车,CR)
  • \\t (制表符)
  • \\b (退格)
  • \\f (换页,FF)
  • \\xnn (十六进制,n是0到f)
  • \\\\ (反斜杠)
  • \\' (单引号)
  • \\" (双引号)
  • \\unnnn (Unicode字符)
  • \\u{nnnnnn} (Unicode码点)
"; + this.infoURL = "https://wikipedia.org/wiki/Escape_sequence"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return Utils.parseEscapedChars(input); + } + +} + +export default UnescapeString; diff --git a/plugins/srktoolbox/src/core/operations/UnescapeUnicodeCharacters.mjs b/plugins/srktoolbox/src/core/operations/UnescapeUnicodeCharacters.mjs new file mode 100644 index 00000000..199f3411 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/UnescapeUnicodeCharacters.mjs @@ -0,0 +1,93 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +/** + * Unescape Unicode Characters operation + */ +class UnescapeUnicodeCharacters extends Operation { + + /** + * UnescapeUnicodeCharacters constructor + */ + constructor() { + super(); + + this.name = "Unicode转义恢复"; + this.module = "Default"; + this.description = "把转义后的Unicode字符恢复成原本形式。

支持以下前缀:
  • \\u
  • %u
  • U+
例: \\u03c3\\u03bf\\u03c5 解码为 σου"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "前缀", + "type": "option", + "value": ["\\u", "%u", "U+"] + } + ]; + this.checks = [ + { + pattern: "\\\\u(?:[\\da-f]{4,6})", + flags: "i", + args: ["\\u"] + }, + { + pattern: "%u(?:[\\da-f]{4,6})", + flags: "i", + args: ["%u"] + }, + { + pattern: "U\\+(?:[\\da-f]{4,6})", + flags: "i", + args: ["U+"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const prefix = prefixToRegex[args[0]], + regex = new RegExp(prefix+"([a-f\\d]{4})", "ig"); + let output = "", + m, + i = 0; + + while ((m = regex.exec(input))) { + // Add up to match + output += input.slice(i, m.index); + + // Add match + output += Utils.chr(parseInt(m[1], 16)); + + i = regex.lastIndex; + } + + // Add all after final match + output += input.slice(i, input.length); + + return output; + } + +} + +/** + * Lookup table to add prefixes to unicode delimiters so that they can be used in a regex. + */ +const prefixToRegex = { + "\\u": "\\\\u", + "%u": "%u", + "U+": "U\\+" +}; + +export default UnescapeUnicodeCharacters; diff --git a/plugins/srktoolbox/src/core/operations/UnicodeTextFormat.mjs b/plugins/srktoolbox/src/core/operations/UnicodeTextFormat.mjs new file mode 100644 index 00000000..6cf7e8af --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/UnicodeTextFormat.mjs @@ -0,0 +1,69 @@ +/** + * @author Matt C [me@mitt.dev] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +/** + * Unicode Text Format operation + */ +class UnicodeTextFormat extends Operation { + + /** + * UnicodeTextFormat constructor + */ + constructor() { + super(); + + this.name = "Unicode文本格式"; + this.module = "Default"; + this.description = "使用Unicode组合字符为纯文本添加格式。"; + this.infoURL = "https://wikipedia.org/wiki/Combining_character"; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = [ + { + name: "下划线", + type: "boolean", + value: "false" + }, + { + name: "删除线", + type: "boolean", + value: "false" + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const [underline, strikethrough] = args; + let output = input.map(char => [char]); + if (strikethrough) { + output = output.map(charFormat => { + charFormat.push(...Utils.strToUtf8ByteArray("\u0336")); + return charFormat; + }); + } + if (underline) { + output = output.map(charFormat => { + charFormat.push(...Utils.strToUtf8ByteArray("\u0332")); + return charFormat; + }); + } + // return output.flat(); - Not supported in Node 10, polyfilled + return [].concat(...output); + } + +} + +export default UnicodeTextFormat; diff --git a/plugins/srktoolbox/src/core/operations/Unique.mjs b/plugins/srktoolbox/src/core/operations/Unique.mjs new file mode 100644 index 00000000..5dce1a31 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Unique.mjs @@ -0,0 +1,70 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import {INPUT_DELIM_OPTIONS} from "../lib/Delim.mjs"; + +/** + * Unique operation + */ +class Unique extends Operation { + + /** + * Unique constructor + */ + constructor() { + super(); + + this.name = "去重"; + this.module = "Default"; + this.description = "从输入移除重复的字符串。"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "分隔符", + type: "option", + value: INPUT_DELIM_OPTIONS + }, + { + name: "显示个数", + type: "boolean", + value: false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const delim = Utils.charRep(args[0]), + count = args[1]; + + if (count) { + const valMap = input.split(delim).reduce((acc, curr) => { + if (Object.prototype.hasOwnProperty.call(acc, curr)) { + acc[curr]++; + } else { + acc[curr] = 1; + } + return acc; + }, {}); + + return Object.keys(valMap).map(val => `${valMap[val]} ${val}`).join(delim); + } else { + return input.split(delim).unique().join(delim); + } + } + +} + +export default Unique; diff --git a/plugins/srktoolbox/src/core/operations/Untar.mjs b/plugins/srktoolbox/src/core/operations/Untar.mjs new file mode 100644 index 00000000..253f5292 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Untar.mjs @@ -0,0 +1,111 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import Stream from "../lib/Stream.mjs"; + +/** + * Untar operation + */ +class Untar extends Operation { + + /** + * Untar constructor + */ + constructor() { + super(); + + this.name = "Untar"; + this.module = "Compression"; + this.description = "解包 tarball 并按文件展示内容。"; + this.infoURL = "https://wikipedia.org/wiki/Tar_(computing)"; + this.inputType = "ArrayBuffer"; + this.outputType = "List"; + this.presentType = "html"; + this.args = []; + this.checks = [ + { + "pattern": "^.{257}\\x75\\x73\\x74\\x61\\x72", + "flags": "", + "args": [] + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {List} + */ + run(input, args) { + input = new Uint8Array(input); + const stream = new Stream(input), + files = []; + + while (stream.hasMore()) { + const dataPosition = stream.position + 512; + + const file = { + fileName: stream.readString(100), + fileMode: stream.readString(8), + ownerUID: stream.readString(8), + ownerGID: stream.readString(8), + size: parseInt(stream.readString(12), 8), // Octal + lastModTime: new Date(1000 * parseInt(stream.readString(12), 8)), // Octal + checksum: stream.readString(8), + type: stream.readString(1), + linkedFileName: stream.readString(100), + USTARFormat: stream.readString(6).indexOf("ustar") >= 0, + }; + + if (file.USTARFormat) { + file.version = stream.readString(2); + file.ownerUserName = stream.readString(32); + file.ownerGroupName = stream.readString(32); + file.deviceMajor = stream.readString(8); + file.deviceMinor = stream.readString(8); + file.filenamePrefix = stream.readString(155); + } + + stream.position = dataPosition; + + if (file.type === "0") { + // File + let endPosition = stream.position + file.size; + if (file.size % 512 !== 0) { + endPosition += 512 - (file.size % 512); + } + + file.bytes = stream.getBytes(file.size); + files.push(new File([new Uint8Array(file.bytes)], file.fileName)); + stream.position = endPosition; + } else if (file.type === "5") { + // Directory + files.push(new File([new Uint8Array(file.bytes)], file.fileName)); + } else { + // Symlink or empty bytes + } + } + + return files; + } + + /** + * Displays the files in HTML for web apps. + * + * @param {File[]} files + * @returns {html} + */ + async present(files) { + return await Utils.displayFilesAsHTML(files); + } + +} + +export default Untar; diff --git a/plugins/srktoolbox/src/core/operations/Unzip.mjs b/plugins/srktoolbox/src/core/operations/Unzip.mjs new file mode 100644 index 00000000..11366128 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Unzip.mjs @@ -0,0 +1,85 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import unzip from "zlibjs/bin/unzip.min.js"; + +const Zlib = unzip.Zlib; + +/** + * Unzip operation + */ +class Unzip extends Operation { + + /** + * Unzip constructor + */ + constructor() { + super(); + + this.name = "Unzip"; + this.module = "Compression"; + this.description = "使用PKZIP算法解压缩并按文件显示内容,支持密码。"; + this.infoURL = "https://wikipedia.org/wiki/Zip_(file_format)"; + this.inputType = "ArrayBuffer"; + this.outputType = "List"; + this.presentType = "html"; + this.args = [ + { + name: "密码", + type: "binaryString", + value: "" + }, + { + name: "验证结果", + type: "boolean", + value: false + } + ]; + this.checks = [ + { + pattern: "^\\x50\\x4b(?:\\x03|\\x05|\\x07)(?:\\x04|\\x06|\\x08)", + flags: "", + args: ["", false] + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {File[]} + */ + run(input, args) { + const options = { + password: Utils.strToByteArray(args[0]), + verify: args[1] + }, + unzip = new Zlib.Unzip(new Uint8Array(input), options), + filenames = unzip.getFilenames(); + + return filenames.map(fileName => { + const bytes = unzip.decompress(fileName); + return new File([bytes], fileName); + }); + } + + /** + * Displays the files in HTML for web apps. + * + * @param {File[]} files + * @returns {html} + */ + async present(files) { + return await Utils.displayFilesAsHTML(files); + } + +} + +export default Unzip; diff --git a/plugins/srktoolbox/src/core/operations/VarIntDecode.mjs b/plugins/srktoolbox/src/core/operations/VarIntDecode.mjs new file mode 100644 index 00000000..c036eb2c --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/VarIntDecode.mjs @@ -0,0 +1,59 @@ +/** + * @author GCHQ Contributor [3] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Protobuf from "../lib/Protobuf.mjs"; + +/** + * VarInt Decode operation + */ +class VarIntDecode extends Operation { + + /** + * VarIntDecode constructor + */ + constructor() { + super(); + + this.name = "VarInt解码"; + this.module = "Default"; + this.description = "把VarInt编码的整数进行解码。VarInt是效率较高的编码变长整数的方式,通常用于Protobuf。"; + this.infoURL = "https://developers.google.com/protocol-buffers/docs/encoding#varints"; + this.inputType = "byteArray"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {number} + */ + run(input, args) { + try { + if (typeof BigInt === "function") { + let result = BigInt(0); + let offset = BigInt(0); + for (let i = 0; i < input.length; i++) { + result |= BigInt(input[i] & 0x7f) << offset; + if (!(input[i] & 0x80)) break; + offset += BigInt(7); + } + return result.toString(); + } else { + return Protobuf.varIntDecode(input).toString(); + } + } catch (err) { + throw new OperationError(err); + } + } + +} + +export default VarIntDecode; diff --git a/plugins/srktoolbox/src/core/operations/VarIntEncode.mjs b/plugins/srktoolbox/src/core/operations/VarIntEncode.mjs new file mode 100644 index 00000000..90fdb79a --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/VarIntEncode.mjs @@ -0,0 +1,60 @@ +/** + * @author GCHQ Contributor [3] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Protobuf from "../lib/Protobuf.mjs"; + +/** + * VarInt Encode operation + */ +class VarIntEncode extends Operation { + + /** + * VarIntEncode constructor + */ + constructor() { + super(); + + this.name = "VarInt编码"; + this.module = "Default"; + this.description = "把整数编码成VarInt。VarInt是效率较高的编码变长整数的方式,通常用于Protobuf。"; + this.infoURL = "https://developers.google.com/protocol-buffers/docs/encoding#varints"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + try { + if (typeof BigInt === "function") { + let value = BigInt(input); + if (value < 0) throw new OperationError("Negative values cannot be represented as VarInt"); + const result = []; + while (value >= 0x80) { + result.push(Number(value & BigInt(0x7f)) | 0x80); + value >>= BigInt(7); + } + result.push(Number(value)); + return result; + } else { + return Protobuf.varIntEncode(Number(input)); + } + } catch (err) { + throw new OperationError(err); + } + } + +} + +export default VarIntEncode; diff --git a/plugins/srktoolbox/src/core/operations/ViewBitPlane.mjs b/plugins/srktoolbox/src/core/operations/ViewBitPlane.mjs new file mode 100644 index 00000000..4aab244b --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ViewBitPlane.mjs @@ -0,0 +1,105 @@ +/** + * @author Ge0rg3 [georgeomnet+cyberchef@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { isImage } from "../lib/FileType.mjs"; +import { toBase64 } from "../lib/Base64.mjs"; +import { Jimp } from "jimp"; + +/** + * View Bit Plane operation + */ +class ViewBitPlane extends Operation { + /** + * ViewBitPlane constructor + */ + constructor() { + super(); + + this.name = "查看位平面"; + this.module = "Image"; + this.description = + "提取并显示任何给定图像的位平面。每张图像显示原图像当中每个像素字节数据的给定一个位,通常用于图像隐写术。"; + this.infoURL = "https://wikipedia.org/wiki/Bit_plane"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.presentType = "html"; + this.args = [ + { + name: "颜色", + type: "option", + value: COLOUR_OPTIONS, + }, + { + name: "位", + type: "number", + value: 0, + }, + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + async run(input, args) { + if (!isImage(input)) + throw new OperationError("请输入合法的图像文件。"); + + const [colour, bit] = args, + parsedImage = await Jimp.read(input), + width = parsedImage.bitmap.width, + height = parsedImage.bitmap.height, + colourIndex = COLOUR_OPTIONS.indexOf(colour), + bitIndex = 7 - bit; + + if (bit < 0 || bit > 7) { + throw new OperationError( + "错误:位参数只能是 0 到 7", + ); + } + + let pixel, bin, newPixelValue; + + parsedImage.scan(0, 0, width, height, function (x, y, idx) { + pixel = this.bitmap.data[idx + colourIndex]; + bin = Utils.bin(pixel); + newPixelValue = 255; + + if (bin.charAt(bitIndex) === "1") newPixelValue = 0; + + for (let i = 0; i < 3; i++) { + this.bitmap.data[idx + i] = newPixelValue; + } + this.bitmap.data[idx + 3] = 255; + }); + + const imageBuffer = await parsedImage.getBuffer(parsedImage.mime); + + return new Uint8Array(imageBuffer).buffer; + } + + /** + * Displays the extracted data as an image for web apps. + * @param {ArrayBuffer} data + * @returns {html} + */ + present(data) { + if (!data.byteLength) return ""; + const type = isImage(data); + + return ``; + } +} + +const COLOUR_OPTIONS = ["Red", "Green", "Blue", "Alpha"]; + +export default ViewBitPlane; diff --git "a/plugins/srktoolbox/src/core/operations/Vigen\303\250reDecode.mjs" "b/plugins/srktoolbox/src/core/operations/Vigen\303\250reDecode.mjs" new file mode 100644 index 00000000..666465c8 --- /dev/null +++ "b/plugins/srktoolbox/src/core/operations/Vigen\303\250reDecode.mjs" @@ -0,0 +1,104 @@ +/** + * @author Matt C [matt@artemisbot.uk] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +/** + * Vigenère Decode operation + */ +class VigenèreDecode extends Operation { + + /** + * VigenèreDecode constructor + */ + constructor() { + super(); + + this.name = "维吉尼亚密码解密"; + this.module = "Ciphers"; + this.description = "维吉尼亚密码(又译维热纳尔密码)是使用一系列凯撒密码组成密码字母表的加密算法,属于多表密码的一种简单形式。"; + this.infoURL = "https://wikipedia.org/wiki/Vigenère_cipher"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "string", + "value": "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const alphabet = "abcdefghijklmnopqrstuvwxyz", + key = args[0].toLowerCase(); + let output = "", + fail = 0, + keyIndex, + msgIndex, + chr; + + if (!key) throw new OperationError("未输入Key"); + if (!/^[a-zA-Z]+$/.test(key)) throw new OperationError("Key只能是字母"); + + for (let i = 0; i < input.length; i++) { + if (alphabet.indexOf(input[i]) >= 0) { + chr = key[(i - fail) % key.length]; + keyIndex = alphabet.indexOf(chr); + msgIndex = alphabet.indexOf(input[i]); + // Subtract indexes from each other, add 26 just in case the value is negative, + // modulo to remove if necessary + output += alphabet[(msgIndex - keyIndex + alphabet.length) % 26]; + } else if (alphabet.indexOf(input[i].toLowerCase()) >= 0) { + chr = key[(i - fail) % key.length].toLowerCase(); + keyIndex = alphabet.indexOf(chr); + msgIndex = alphabet.indexOf(input[i].toLowerCase()); + output += alphabet[(msgIndex + alphabet.length - keyIndex) % 26].toUpperCase(); + } else { + output += input[i]; + fail++; + } + } + + return output; + } + + /** + * Highlight Vigenère Decode + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight Vigenère Decode 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 VigenèreDecode; diff --git "a/plugins/srktoolbox/src/core/operations/Vigen\303\250reEncode.mjs" "b/plugins/srktoolbox/src/core/operations/Vigen\303\250reEncode.mjs" new file mode 100644 index 00000000..84fd6968 --- /dev/null +++ "b/plugins/srktoolbox/src/core/operations/Vigen\303\250reEncode.mjs" @@ -0,0 +1,109 @@ +/** + * @author Matt C [matt@artemisbot.uk] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Vigenère Encode operation + */ +class VigenèreEncode extends Operation { + + /** + * VigenèreEncode constructor + */ + constructor() { + super(); + + this.name = "维吉尼亚密码加密"; + this.module = "Ciphers"; + this.description = "维吉尼亚密码(又译维热纳尔密码)是使用一系列凯撒密码组成密码字母表的加密算法,属于多表密码的一种简单形式。"; + this.infoURL = "https://wikipedia.org/wiki/Vigenère_cipher"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "string", + "value": "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const alphabet = "abcdefghijklmnopqrstuvwxyz", + key = args[0].toLowerCase(); + let output = "", + fail = 0, + keyIndex, + msgIndex, + chr; + + if (!key) throw new OperationError("未输入Key"); + if (!/^[a-zA-Z]+$/.test(key)) throw new OperationError("Key只能是字母"); + + for (let i = 0; i < input.length; i++) { + if (alphabet.indexOf(input[i]) >= 0) { + // Get the corresponding character of key for the current letter, accounting + // for chars not in alphabet + chr = key[(i - fail) % key.length]; + // Get the location in the vigenere square of the key char + keyIndex = alphabet.indexOf(chr); + // Get the location in the vigenere square of the message char + msgIndex = alphabet.indexOf(input[i]); + // Get the encoded letter by finding the sum of indexes modulo 26 and finding + // the letter corresponding to that + output += alphabet[(keyIndex + msgIndex) % 26]; + } else if (alphabet.indexOf(input[i].toLowerCase()) >= 0) { + chr = key[(i - fail) % key.length].toLowerCase(); + keyIndex = alphabet.indexOf(chr); + msgIndex = alphabet.indexOf(input[i].toLowerCase()); + output += alphabet[(keyIndex + msgIndex) % 26].toUpperCase(); + } else { + output += input[i]; + fail++; + } + } + + return output; + } + + /** + * Highlight Vigenère Encode + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight Vigenère Encode 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 VigenèreEncode; diff --git a/plugins/srktoolbox/src/core/operations/Whirlpool.mjs b/plugins/srktoolbox/src/core/operations/Whirlpool.mjs new file mode 100644 index 00000000..0c76505c --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Whirlpool.mjs @@ -0,0 +1,57 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import {runHash} from "../lib/Hash.mjs"; + +/** + * Whirlpool operation + */ +class Whirlpool extends Operation { + + /** + * Whirlpool constructor + */ + constructor() { + super(); + + this.name = "Whirlpool"; + this.module = "Crypto"; + this.description = "Whirlpool是由Vincent Rijmen和Paulo S. L. M. Barreto在2000年公开的加密算法。

存在多个变种:
  • Whirlpool-0是2000年发布的最初版本。
  • Whirlpool-T是在2001年发布的第一个更新版本,改进了s-box的生成过程。
  • Whirlpool是最新版本,于2003年发布,修复了diffusion matrix中的缺陷。
"; + this.infoURL = "https://wikipedia.org/wiki/Whirlpool_(cryptography)"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + name: "变种", + type: "option", + value: ["Whirlpool", "Whirlpool-T", "Whirlpool-0"] + }, + { + name: "轮数", + type: "number", + value: 10, + min: 1, + max: 10 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const variant = args[0].toLowerCase(); + return runHash(variant, input, {rounds: args[1]}); + } + +} + +export default Whirlpool; diff --git a/plugins/srktoolbox/src/core/operations/WindowsFiletimeToUNIXTimestamp.mjs b/plugins/srktoolbox/src/core/operations/WindowsFiletimeToUNIXTimestamp.mjs new file mode 100644 index 00000000..25f4aad7 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/WindowsFiletimeToUNIXTimestamp.mjs @@ -0,0 +1,92 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import BigNumber from "bignumber.js"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Windows Filetime to UNIX Timestamp operation + */ +class WindowsFiletimeToUNIXTimestamp extends Operation { + + /** + * WindowsFiletimeToUNIXTimestamp constructor + */ + constructor() { + super(); + + this.name = "Windows Filetime转UNIX时间戳"; + this.module = "Default"; + this.description = "将Windows Filetime值转换为UNIX时间戳。

Windows Filetime是对应从1601年1月1日(UTC)开始的以100纳秒为单位的64位数值。

UNIX 时间戳是对应从1970年1月1日(UTC)开始的以秒为单位的32位数值。

此操作也支持不同的UNIX时间单位如毫秒、微秒和纳秒。"; + this.infoURL = "https://msdn.microsoft.com/en-us/library/windows/desktop/ms724284(v=vs.85).aspx"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "输出单位", + "type": "option", + "value": ["秒 (s)", "毫秒 (ms)", "微秒 (μs)", "纳秒 (ns)"] + }, + { + "name": "输入格式", + "type": "option", + "value": ["十进制", "十六进制 (大端序)", "十六进制 (小端序)"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [units, format] = args; + + if (!input) return ""; + + if (format === "Hex (小端序)") { + // Swap endianness + let result = ""; + if (input.length % 2 !== 0) { + result += input.charAt(input.length - 1); + } + for (let i = input.length - input.length % 2 - 2; i >= 0; i -= 2) { + result += input.charAt(i); + result += input.charAt(i + 1); + } + input = result; + } + + if (format.startsWith("十六进制")) { + input = new BigNumber(input, 16); + } else { + input = new BigNumber(input); + } + + input = input.minus(new BigNumber("116444736000000000")); + + if (units === "秒 (s)") { + input = input.dividedBy(new BigNumber("10000000")); + } else if (units === "毫秒 (ms)") { + input = input.dividedBy(new BigNumber("10000")); + } else if (units === "微秒 (μs)") { + input = input.dividedBy(new BigNumber("10")); + } else if (units === "纳秒 (ns)") { + input = input.multipliedBy(new BigNumber("100")); + } else { + throw new OperationError("无效单位"); + } + + return input.toFixed(); + } + +} + +export default WindowsFiletimeToUNIXTimestamp; diff --git a/plugins/srktoolbox/src/core/operations/XKCDRandomNumber.mjs b/plugins/srktoolbox/src/core/operations/XKCDRandomNumber.mjs new file mode 100644 index 00000000..ba9b5e27 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/XKCDRandomNumber.mjs @@ -0,0 +1,41 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; + +/** + * XKCD Random Number operation + */ +class XKCDRandomNumber extends Operation { + + /** + * XKCDRandomNumber constructor + */ + constructor() { + super(); + + this.name = "XKCD Random Number"; + this.module = "Default"; + this.description = "RFC 1149.5 specifies 4 as the standard IEEE-vetted random number."; + this.infoURL = "https://xkcd.com/221/"; + this.inputType = "string"; + this.outputType = "number"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {number} + */ + run(input, args) { + return 4; // chosen by fair dice roll. + // guaranteed to be random. + } + +} + +export default XKCDRandomNumber; diff --git a/plugins/srktoolbox/src/core/operations/XMLBeautify.mjs b/plugins/srktoolbox/src/core/operations/XMLBeautify.mjs new file mode 100644 index 00000000..b0987b5c --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/XMLBeautify.mjs @@ -0,0 +1,49 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import vkbeautify from "vkbeautify"; +import Operation from "../Operation.mjs"; + +/** + * XML Beautify operation + */ +class XMLBeautify extends Operation { + + /** + * XMLBeautify constructor + */ + constructor() { + super(); + + this.name = "XML美化"; + this.module = "Code"; + this.description = "为eXtensible Markup Language (XML)代码添加缩进与美化。"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "缩进", + "type": "binaryShortString", + "value": "\\t" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const indentStr = args[0]; + return vkbeautify.xml(input, indentStr); + } + +} + +export default XMLBeautify; diff --git a/plugins/srktoolbox/src/core/operations/XMLMinify.mjs b/plugins/srktoolbox/src/core/operations/XMLMinify.mjs new file mode 100644 index 00000000..10bd85c0 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/XMLMinify.mjs @@ -0,0 +1,49 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import vkbeautify from "vkbeautify"; +import Operation from "../Operation.mjs"; + +/** + * XML Minify operation + */ +class XMLMinify extends Operation { + + /** + * XMLMinify constructor + */ + constructor() { + super(); + + this.name = "XML压缩"; + this.module = "Code"; + this.description = "压缩eXtensible Markup Language (XML)代码(Minify/Uglify)。"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "保留注释", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const preserveComments = args[0]; + return vkbeautify.xmlmin(input, preserveComments); + } + +} + +export default XMLMinify; diff --git a/plugins/srktoolbox/src/core/operations/XOR.mjs b/plugins/srktoolbox/src/core/operations/XOR.mjs new file mode 100644 index 00000000..2ce1b55c --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/XOR.mjs @@ -0,0 +1,91 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import { bitOp, xor, BITWISE_OP_DELIMS } from "../lib/BitwiseOp.mjs"; + +/** + * XOR operation + */ +class XOR extends Operation { + + /** + * XOR constructor + */ + constructor() { + super(); + + this.name = "XOR"; + this.module = "Default"; + this.description = "用给定的key对输入做异或(XOR)操作。
例: fe023da5

选项:
保留Null:如果当前字节是0x00或者和key相同,则跳过不进行XOR操作。

加密方式:
  • 标准 - key保持不变
  • 输入差分 - key设置为上一个处理前的字节
  • 输出差分 - key设置为上一个处理后的字节
  • 级联 - key设置成输入当前处理位置后移1个字节的内容
"; + this.infoURL = "https://wikipedia.org/wiki/XOR"; + this.inputType = "ArrayBuffer"; + this.outputType = "byteArray"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": BITWISE_OP_DELIMS + }, + { + "name": "加密方式", + "type": "option", + "value": ["标准", "输入差分", "输出差分", "级联"] + }, + { + "name": "保留Null", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + input = new Uint8Array(input); + const key = Utils.convertToByteArray(args[0].string || "", args[0].option), + [, scheme, nullPreserving] = args; + + return bitOp(input, key, xor, nullPreserving, scheme); + } + + /** + * Highlight XOR + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight XOR 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 XOR; diff --git a/plugins/srktoolbox/src/core/operations/XORBruteForce.mjs b/plugins/srktoolbox/src/core/operations/XORBruteForce.mjs new file mode 100644 index 00000000..2d12e428 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/XORBruteForce.mjs @@ -0,0 +1,141 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import { bitOp, xor } from "../lib/BitwiseOp.mjs"; +import { toHex } from "../lib/Hex.mjs"; +import { isWorkerEnvironment } from "../Utils.mjs"; + +/** + * XOR Brute Force operation + */ +class XORBruteForce extends Operation { + + /** + * XORBruteForce constructor + */ + constructor() { + super(); + + this.name = "XOR暴力破解"; + this.module = "Default"; + this.description = "枚举所有的XOR解码结果。受限于浏览器性能,目前仅限key长度不超过2。

你可以输入一个已知的明文字符串来筛选结果(已知明文攻击中称为crib)。"; + this.infoURL = "https://wikipedia.org/wiki/Exclusive_or"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "Key长度", + "type": "number", + "value": 1 + }, + { + "name": "样本长度", + "type": "number", + "value": 100 + }, + { + "name": "样本偏移量", + "type": "number", + "value": 0 + }, + { + "name": "加密方式", + "type": "option", + "value": ["标准", "输入差分", "输出差分"] + }, + { + "name": "保留Null", + "type": "boolean", + "value": false + }, + { + "name": "输出key", + "type": "boolean", + "value": true + }, + { + "name": "输出十六进制", + "type": "boolean", + "value": false + }, + { + "name": "Crib(已知明文部分)", + "type": "binaryString", + "value": "" + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + input = new Uint8Array(input); + const [ + keyLength, + sampleLength, + sampleOffset, + scheme, + nullPreserving, + printKey, + outputHex, + rawCrib + ] = args, + crib = rawCrib.toLowerCase(), + output = []; + let result, + resultUtf8, + record = ""; + + input = input.slice(sampleOffset, sampleOffset + sampleLength); + + if (isWorkerEnvironment()) + self.sendStatusMessage("正在计算 " + Math.pow(256, keyLength) + " 种结果..."); + + /** + * Converts an integer to an array of bytes expressing that number. + * + * @param {number} int + * @param {number} len - Length of the resulting array + * @returns {array} + */ + const intToByteArray = (int, len) => { + const res = Array(len).fill(0); + for (let i = len - 1; i >= 0; i--) { + res[i] = int & 0xff; + int = int >>> 8; + } + return res; + }; + + for (let key = 1, l = Math.pow(256, keyLength); key < l; key++) { + if (key % 10000 === 0 && isWorkerEnvironment()) { + self.sendStatusMessage("正在计算 " + l + " 种结果... " + Math.floor(key / l * 100) + "%"); + } + + result = bitOp(input, intToByteArray(key, keyLength), xor, nullPreserving, scheme); + resultUtf8 = Utils.byteArrayToUtf8(result); + record = ""; + + if (crib && resultUtf8.toLowerCase().indexOf(crib) < 0) continue; + if (printKey) record += "Key = " + Utils.hex(key, (2*keyLength)) + ": "; + record += outputHex ? toHex(result) : Utils.escapeWhitespace(resultUtf8); + + output.push(record); + } + + return output.join("\n"); + } + +} + +export default XORBruteForce; diff --git a/plugins/srktoolbox/src/core/operations/XORChecksum.mjs b/plugins/srktoolbox/src/core/operations/XORChecksum.mjs new file mode 100644 index 00000000..97408c89 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/XORChecksum.mjs @@ -0,0 +1,61 @@ +/** + * @author Thomas Weißschuh [thomas@t-8ch.de] + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import { toHex } from "../lib/Hex.mjs"; + +/** + * XOR Checksum operation + */ +class XORChecksum extends Operation { + + /** + * XORChecksum constructor + */ + constructor() { + super(); + + this.name = "XOR校验和"; + this.module = "Crypto"; + this.description = "异或校验和将输入数据按可配置大小的块进行分割,并对这些数据块执行异或运算以生成校验值。"; + this.infoURL = "https://wikipedia.org/wiki/XOR"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + name: "块大小", + type: "number", + value: 4 + }, + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const blocksize = args[0]; + input = new Uint8Array(input); + + const res = Array(blocksize); + res.fill(0); + + for (const chunk of Utils.chunked(input, blocksize)) { + for (let i = 0; i < blocksize; i++) { + res[i] ^= chunk[i]; + } + } + + return toHex(res, ""); + } +} + +export default XORChecksum; diff --git a/plugins/srktoolbox/src/core/operations/XPathExpression.mjs b/plugins/srktoolbox/src/core/operations/XPathExpression.mjs new file mode 100644 index 00000000..29603a42 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/XPathExpression.mjs @@ -0,0 +1,82 @@ +/** + * @author Mikescher (https://github.com/Mikescher | https://mikescher.com) + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import xmldom from "@xmldom/xmldom"; +import xpath from "xpath"; + +/** + * XPath expression operation + */ +class XPathExpression extends Operation { + + /** + * XPathExpression constructor + */ + constructor() { + super(); + + this.name = "XPath表达式"; + this.module = "Code"; + this.description = "从XML文档中使用给定的XPath表达式进行查询并提取内容。"; + this.infoURL = "https://wikipedia.org/wiki/XPath"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "XPath", + "type": "string", + "value": "" + }, + { + "name": "查询结果分隔符", + "type": "binaryShortString", + "value": "\\n" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [query, delimiter] = args; + + let doc; + try { + doc = new xmldom.DOMParser({ + errorHandler: { + fatalError(e) { + throw e; + } + } + }).parseFromString(input, "application/xml"); + } catch (err) { + throw new OperationError("无效的XML。"); + } + + let nodes; + try { + nodes = xpath.parse(query).select({ node: doc, allowAnyNamespaceForNoPrefix: true }); + } catch (err) { + throw new OperationError(`无效的XPath。错误信息:\n${err.message}.`); + } + + const nodeToString = function(node) { + return node.toString(); + }; + + return nodes.map(nodeToString).join(delimiter); + } + +} + +export default XPathExpression; diff --git a/plugins/srktoolbox/src/core/operations/XSalsa20.mjs b/plugins/srktoolbox/src/core/operations/XSalsa20.mjs new file mode 100644 index 00000000..2d479b49 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/XSalsa20.mjs @@ -0,0 +1,158 @@ +/** + * @author joostrijneveld [joost@joostrijneveld.nl] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { toHex } from "../lib/Hex.mjs"; +import { salsa20Block, hsalsa20 } from "../lib/Salsa20.mjs"; + +/** + * XSalsa20 operation + */ +class XSalsa20 extends Operation { + + /** + * XSalsa20 constructor + */ + constructor() { + super(); + + this.name = "XSalsa20"; + this.module = "Ciphers"; + this.description = "XSalsa20是Salsa20流加密算法的变种,由丹尼尔·J·伯恩斯坦设计。XSalsa相比Salsa使用更长的nonce。

密钥: XSalsa20使用16或32字节(128或256位)。

Nonce: XSalsa20使用24字节(192位)长度的nonce。

计数: XSalsa使用8字节(64位)长度的计数。计数在流的起始处为0,每64字节递增。"; + this.infoURL = "https://en.wikipedia.org/wiki/Salsa20#XSalsa20_with_192-bit_nonce"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "密钥", + "type": "toggleString", + "value": "", + "toggleValues": ["十六进制", "UTF8", "Latin1", "Base64"] + }, + { + "name": "Nonce", + "type": "toggleString", + "value": "", + "toggleValues": ["十六进制", "UTF8", "Latin1", "Base64", "整数"] + }, + { + "name": "计数", + "type": "number", + "value": 0, + "min": 0 + }, + { + "name": "轮数", + "type": "option", + "value": ["20", "12", "8"] + }, + { + "name": "输入", + "type": "option", + "value": ["十六进制", "原始"] + }, + { + "name": "输出", + "type": "option", + "value": ["原始", "十六进制"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = Utils.convertToByteArray(args[0].string, args[0].option), + nonceType = args[1].option, + rounds = parseInt(args[3], 10), + inputType = args[4], + outputType = args[5]; + + if (key.length !== 16 && key.length !== 32) { + throw new OperationError(`无效的密钥长度: ${key.length} 字节。 + +XSalsa20使用16或32字节(128或256位)的密钥。`); + } + + let counter, nonce; + if (nonceType === "整数") { + nonce = Utils.intToByteArray(parseInt(args[1].string, 10), 8, "little"); + } else { + nonce = Utils.convertToByteArray(args[1].string, args[1].option); + if (!(nonce.length === 24)) { + throw new OperationError(`无效的nonce长度: ${nonce.length} 字节。 + +XSalsa20使用24字节(192位)的nonce。`); + } + } + counter = Utils.intToByteArray(args[2], 8, "little"); + + const xsalsaKey = hsalsa20(key, nonce.slice(0, 16), rounds); + + const output = []; + input = Utils.convertToByteArray(input, inputType); + + let counterAsInt = Utils.byteArrayToInt(counter, "little"); + for (let i = 0; i < input.length; i += 64) { + counter = Utils.intToByteArray(counterAsInt, 8, "little"); + const stream = salsa20Block(xsalsaKey, nonce.slice(16, 24), counter, rounds); + for (let j = 0; j < 64 && i + j < input.length; j++) { + output.push(input[i + j] ^ stream[j]); + } + counterAsInt++; + } + + if (outputType === "十六进制") { + return toHex(output); + } else { + return Utils.arrayBufferToStr(Uint8Array.from(output).buffer); + } + } + + /** + * Highlight XSalsa20 + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + const inputType = args[4], + outputType = args[5]; + if (inputType === "原始" && outputType === "原始") { + return pos; + } + } + + /** + * Highlight XSalsa20 in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + const inputType = args[4], + outputType = args[5]; + if (inputType === "原始" && outputType === "原始") { + return pos; + } + } + +} + +export default XSalsa20; diff --git a/plugins/srktoolbox/src/core/operations/XXTEADecrypt.mjs b/plugins/srktoolbox/src/core/operations/XXTEADecrypt.mjs new file mode 100644 index 00000000..5711723f --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/XXTEADecrypt.mjs @@ -0,0 +1,59 @@ +/** + * @author devcydo [devcydo@gmail.com] + * @author Ma Bingyao [mabingyao@gmail.com] + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import {decrypt} from "../lib/XXTEA.mjs"; + +/** + * XXTEA Decrypt operation + */ +class XXTEADecrypt extends Operation { + + /** + * XXTEADecrypt constructor + */ + constructor() { + super(); + + this.name = "XXTEA解密"; + this.module = "Ciphers"; + this.description = "纠正后的Block TEA(通常被称为XXTEA)是一种设计用于纠正原始Block TEA中弱点的分组密码。XXTEA操作于可变长度的块,其大小为32位的整数倍(最小64位)。完整的循环次数取决于块的大小,但至少有6次(对于小块大小则上升到32次)。原始的Block TEA对块中的每个字应用XTEA循环函数,并与其最左侧的邻居进行加法组合。解密过程中扩散率较慢被立即利用来破解该密码。纠正后的Block TEA使用了一个更复杂的循环函数,在处理块中的每个字时利用了两个直接邻居。"; + this.infoURL = "https://wikipedia.org/wiki/XXTEA"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = [ + { + "name": "密钥", + "type": "toggleString", + "value": "", + "toggleValues": ["十六进制", "UTF8", "Latin1", "Base64"] + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = new Uint8Array(Utils.convertToByteArray(args[0].string, args[0].option)); + try { + return decrypt(new Uint8Array(input), key).buffer; + } catch (err) { + throw new OperationError("使用给定的密钥无法解密"); + } + } + +} + +export default XXTEADecrypt; diff --git a/plugins/srktoolbox/src/core/operations/XXTEAEncrypt.mjs b/plugins/srktoolbox/src/core/operations/XXTEAEncrypt.mjs new file mode 100644 index 00000000..c963a73f --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/XXTEAEncrypt.mjs @@ -0,0 +1,54 @@ +/** + * @author devcydo [devcydo@gmail.com] + * @author Ma Bingyao [mabingyao@gmail.com] + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import {encrypt} from "../lib/XXTEA.mjs"; + +/** + * XXTEA Encrypt operation + */ +class XXTEAEncrypt extends Operation { + + /** + * XXTEAEncrypt constructor + */ + constructor() { + super(); + + this.name = "XXTEA加密"; + this.module = "Ciphers"; + this.description = "纠正后的Block TEA(通常被称为XXTEA)是一种设计用于纠正原始Block TEA中弱点的分组密码。XXTEA操作于可变长度的块,其大小为32位的整数倍(最小64位)。完整的循环次数取决于块的大小,但至少有6次(对于小块大小则上升到32次)。原始的Block TEA对块中的每个字应用XTEA循环函数,并与其最左侧的邻居进行加法组合。解密过程中扩散率较慢被立即利用来破解该密码。纠正后的Block TEA使用了一个更复杂的循环函数,在处理块中的每个字时利用了两个直接邻居。"; + this.infoURL = "https://wikipedia.org/wiki/XXTEA"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = [ + { + "name": "密钥", + "type": "toggleString", + "value": "", + "toggleValues": ["十六进制", "UTF8", "Latin1", "Base64"] + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = new Uint8Array(Utils.convertToByteArray(args[0].string, args[0].option)); + return encrypt(new Uint8Array(input), key).buffer; + } + +} + +export default XXTEAEncrypt; diff --git a/plugins/srktoolbox/src/core/operations/YAMLToJSON.mjs b/plugins/srktoolbox/src/core/operations/YAMLToJSON.mjs new file mode 100644 index 00000000..1cbe8b1f --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/YAMLToJSON.mjs @@ -0,0 +1,47 @@ +/** + * @author ccarpo [ccarpo@gmx.net] + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + * + * Modified by Raka-loah@github + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import jsYaml from "js-yaml"; +/** + * YAML to JSON operation + */ +class YAMLToJSON extends Operation { + + /** + * YAMLToJSON constructor + */ + constructor() { + super(); + + this.name = "YAML转JSON"; + this.module = "Default"; + this.description = "将YAML转换为JSON"; + this.infoURL = "https://en.wikipedia.org/wiki/YAML"; + this.inputType = "string"; + this.outputType = "JSON"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {JSON} + */ + run(input, args) { + try { + return jsYaml.load(input); + } catch (err) { + throw new OperationError("无法解析YAML: " + err); + } + } + +} + +export default YAMLToJSON; diff --git a/plugins/srktoolbox/src/core/operations/YARARules.mjs b/plugins/srktoolbox/src/core/operations/YARARules.mjs new file mode 100644 index 00000000..622a5ebb --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/YARARules.mjs @@ -0,0 +1,143 @@ +/** + * @author Matt C [matt@artemisbot.uk] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Yara from "libyara-wasm"; +import { isWorkerEnvironment } from "../Utils.mjs"; + +/** + * YARA Rules operation + */ +class YARARules extends Operation { + + /** + * YARARules constructor + */ + constructor() { + super(); + + this.name = "YARA规则"; + this.module = "Yara"; + this.description = "YARA是VirusTotal制作的一个主要用于恶意软件研究和检测的工具。它提供了一种创建基于文本或二进制模式的恶意软件系列的描述方法。一个描述本质上是一个 YARA 规则名称,其中这些规则由字符串集和一个布尔表达式组成。使用的语言具有与 Perl 兼容的正则表达式的特点。对于规则编写,请参考:YARA文档。"; + this.infoURL = "https://wikipedia.org/wiki/YARA"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + name: "规则", + type: "text", + value: "", + rows: 5 + }, + { + name: "显示字符串", + type: "boolean", + value: false + }, + { + name: "显示字符串长度", + type: "boolean", + value: false + }, + { + name: "显示元数据", + type: "boolean", + value: false + }, + { + name: "显示计数", + type: "boolean", + value: true + }, + { + name: "显示规则警告", + type: "boolean", + value: true + }, + { + name: "显示控制台模块消息", + type: "boolean", + value: true + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + if (isWorkerEnvironment()) + self.sendStatusMessage("YARA载入中……"); + const [rules, showStrings, showLengths, showMeta, showCounts, showRuleWarns, showConsole] = args; + return new Promise((resolve, reject) => { + Yara().then(yara => { + if (isWorkerEnvironment()) self.sendStatusMessage("为 YARA 转换数据"); + let matchString = ""; + + const inpArr = new Uint8Array(input); // Turns out embind knows that JS uint8array <==> C++ std::string + + if (isWorkerEnvironment()) self.sendStatusMessage("执行 YARA 匹配"); + + const resp = yara.run(inpArr, rules); + + if (isWorkerEnvironment()) self.sendStatusMessage("处理数据"); + + if (resp.compileErrors.size() > 0) { + for (let i = 0; i < resp.compileErrors.size(); i++) { + const compileError = resp.compileErrors.get(i); + if (!compileError.warning) { + reject(new OperationError(`行 ${compileError.lineNumber} 报错: ${compileError.message}`)); + } else if (showRuleWarns) { + matchString += `行 ${compileError.lineNumber} 警告: ${compileError.message}\n`; + } + } + } + + if (showConsole) { + const consoleLogs = resp.consoleLogs; + for (let i = 0; i < consoleLogs.size(); i++) { + matchString += consoleLogs.get(i) + "\n"; + } + } + + const matchedRules = resp.matchedRules; + for (let i = 0; i < matchedRules.size(); i++) { + const rule = matchedRules.get(i); + const matches = rule.resolvedMatches; + let meta = ""; + if (showMeta && rule.metadata.size() > 0) { + meta += " ["; + for (let j = 0; j < rule.metadata.size(); j++) { + meta += `${rule.metadata.get(j).identifier}: ${rule.metadata.get(j).data}, `; + } + meta = meta.slice(0, -2) + "]"; + } + const countString = matches.size() === 0 ? "" : (showCounts ? ` (${matches.size()} 次)` : ""); + if (matches.size() === 0 || !(showStrings || showLengths)) { + matchString += `输入匹配规则 "${rule.ruleName}"${meta}${countString.length > 0 ? ` ${countString}`: ""}.\n`; + } else { + matchString += `规则 "${rule.ruleName}"${meta} 匹配${countString}:\n`; + for (let j = 0; j < matches.size(); j++) { + const match = matches.get(j); + if (showStrings || showLengths) { + matchString += `位置 ${match.location}, ${showLengths ? `长度 ${match.matchLength}, ` : ""}标识符 ${match.stringIdentifier}${showStrings ? `, 数据: "${match.data}"` : ""}\n`; + } + } + } + } + resolve(matchString); + }); + }); + } + +} + +export default YARARules; diff --git a/plugins/srktoolbox/src/core/operations/Zip.mjs b/plugins/srktoolbox/src/core/operations/Zip.mjs new file mode 100644 index 00000000..0a42921f --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/Zip.mjs @@ -0,0 +1,105 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import {COMPRESSION_TYPE, ZLIB_COMPRESSION_TYPE_LOOKUP} from "../lib/Zlib.mjs"; +import zip from "zlibjs/bin/zip.min.js"; + +const Zlib = zip.Zlib; + +const ZIP_COMPRESSION_METHOD_LOOKUP = { + "Deflate": Zlib.Zip.CompressionMethod.DEFLATE, + "无压缩 (Store)": Zlib.Zip.CompressionMethod.STORE +}; + +const ZIP_OS_LOOKUP = { + "MSDOS": Zlib.Zip.OperatingSystem.MSDOS, + "Unix": Zlib.Zip.OperatingSystem.UNIX, + "Macintosh": Zlib.Zip.OperatingSystem.MACINTOSH +}; + +/** + * Zip operation + */ +class Zip extends Operation { + + /** + * Zip constructor + */ + constructor() { + super(); + + this.name = "Zip"; + this.module = "Compression"; + this.description = "将输入数据使用给定的文件名用PKZIP算法进行压缩。

当前不支持多个文件。"; + this.infoURL = "https://wikipedia.org/wiki/Zip_(file_format)"; + this.inputType = "ArrayBuffer"; + this.outputType = "File"; + this.args = [ + { + name: "文件名", + type: "string", + value: "file.txt" + }, + { + name: "注释", + type: "string", + value: "" + }, + { + name: "密码", + type: "binaryString", + value: "" + }, + { + name: "压缩方式", + type: "option", + value: ["Deflate", "无压缩 (Store)"] + }, + { + name: "操作系统", + type: "option", + value: ["MSDOS", "Unix", "Macintosh"] + }, + { + name: "压缩类型", + type: "option", + value: COMPRESSION_TYPE + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {File} + */ + run(input, args) { + const filename = args[0], + password = Utils.strToByteArray(args[2]), + options = { + filename: Utils.strToByteArray(filename), + comment: Utils.strToByteArray(args[1]), + compressionMethod: ZIP_COMPRESSION_METHOD_LOOKUP[args[3]], + os: ZIP_OS_LOOKUP[args[4]], + deflateOption: { + compressionType: ZLIB_COMPRESSION_TYPE_LOOKUP[args[5]] + }, + }, + zip = new Zlib.Zip(); + + if (password.length) + zip.setPassword(password); + zip.addFile(new Uint8Array(input), options); + return new File([zip.compress()], filename); + } + +} + +export default Zip; diff --git a/plugins/srktoolbox/src/core/operations/ZlibDeflate.mjs b/plugins/srktoolbox/src/core/operations/ZlibDeflate.mjs new file mode 100644 index 00000000..f110fd50 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ZlibDeflate.mjs @@ -0,0 +1,55 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import {COMPRESSION_TYPE, ZLIB_COMPRESSION_TYPE_LOOKUP} from "../lib/Zlib.mjs"; +import zlibAndGzip from "zlibjs/bin/zlib_and_gzip.min.js"; + +const Zlib = zlibAndGzip.Zlib; + +/** + * Zlib Deflate operation + */ +class ZlibDeflate extends Operation { + + /** + * ZlibDeflate constructor + */ + constructor() { + super(); + + this.name = "Zlib Deflate"; + this.module = "Compression"; + this.description = "使用带有Zlib标头的deflate算法压缩数据。"; + this.infoURL = "https://wikipedia.org/wiki/Zlib"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = [ + { + name: "压缩类型", + type: "option", + value: COMPRESSION_TYPE + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + const deflate = new Zlib.Deflate(new Uint8Array(input), { + compressionType: ZLIB_COMPRESSION_TYPE_LOOKUP[args[0]] + }); + return new Uint8Array(deflate.compress()).buffer; + } + +} + +export default ZlibDeflate; diff --git a/plugins/srktoolbox/src/core/operations/ZlibInflate.mjs b/plugins/srktoolbox/src/core/operations/ZlibInflate.mjs new file mode 100644 index 00000000..d5d70ea0 --- /dev/null +++ b/plugins/srktoolbox/src/core/operations/ZlibInflate.mjs @@ -0,0 +1,91 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Operation from "../Operation.mjs"; +import {INFLATE_BUFFER_TYPE} from "../lib/Zlib.mjs"; +import zlibAndGzip from "zlibjs/bin/zlib_and_gzip.min.js"; + +const Zlib = zlibAndGzip.Zlib; + +const ZLIB_BUFFER_TYPE_LOOKUP = { + "自适应": Zlib.Inflate.BufferType.ADAPTIVE, + "块": Zlib.Inflate.BufferType.BLOCK, +}; + +/** + * Zlib Inflate operation + */ +class ZlibInflate extends Operation { + + /** + * ZlibInflate constructor + */ + constructor() { + super(); + + this.name = "Zlib Inflate"; + this.module = "Compression"; + this.description = "解压使用带有Zlib标头的deflate算法压缩的数据。"; + this.infoURL = "https://wikipedia.org/wiki/Zlib"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = [ + { + name: "起始索引", + type: "number", + value: 0 + }, + { + name: "起始输出缓冲区尺寸", + type: "number", + value: 0 + }, + { + name: "缓冲区扩展类型", + type: "option", + value: INFLATE_BUFFER_TYPE + }, + { + name: "解压缩后重置缓冲区尺寸", + type: "boolean", + value: false + }, + { + name: "验证结果", + type: "boolean", + value: false + } + ]; + this.checks = [ + { + pattern: "^\\x78(\\x01|\\x9c|\\xda|\\x5e)", + flags: "", + args: [0, 0, "自适应", false, false] + }, + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + const inflate = new Zlib.Inflate(new Uint8Array(input), { + index: args[0], + bufferSize: args[1], + bufferType: ZLIB_BUFFER_TYPE_LOOKUP[args[2]], + resize: args[3], + verify: args[4] + }); + return new Uint8Array(inflate.decompress()).buffer; + } + +} + +export default ZlibInflate; diff --git a/plugins/srktoolbox/src/core/vendor/DisassembleX86-64.mjs b/plugins/srktoolbox/src/core/vendor/DisassembleX86-64.mjs new file mode 100644 index 00000000..aeae426b --- /dev/null +++ b/plugins/srktoolbox/src/core/vendor/DisassembleX86-64.mjs @@ -0,0 +1,5760 @@ +/*------------------------------------------------------------------------------------------------------------------------- +Created by Damian Recoskie (https://github.com/Recoskie/X86-64-Disassembler-JS) + & exported for CyberChef by Matt [me@mitt.dev] +--------------------------------------------------------------------------------------------------------------------------- +MIT License + +Copyright (c) 2019 Damian Recoskie + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +-------------------------------------------------------------------------------------------------------------------------*/ + + +/*------------------------------------------------------------------------------------------------------------------------- +Binary byte code array. +--------------------------------------------------------------------------------------------------------------------------- +Function ^LoadBinCode()^ takes a string input of hex and loads it into the BinCode array it is recommended that the location +the hex string is read from a file, or sector matches the disassemblers set base address using function ^SetBasePosition()^. +-------------------------------------------------------------------------------------------------------------------------*/ + +var BinCode = []; + +/*------------------------------------------------------------------------------------------------------------------------- +When Bit Mode is 2 the disassembler will default to decoding 64 bit binary code possible settings are 0=16 bit, 1=32 bit, 2=64 bit. +-------------------------------------------------------------------------------------------------------------------------*/ + +var BitMode = 2; + +/*------------------------------------------------------------------------------------------------------------------------- +The variable CodePos is the position in the BinCode array starts at 0 for each new section loaded in by ^LoadBinCode()^. +--------------------------------------------------------------------------------------------------------------------------- +The function ^NextByte()^ moves CodePos, and the Disassemblers Base address by one stored in Pos64, Pos32. +The BinCode array is designed for loading in a section of binary that is supposed to be from the set Base address in Pos64, and Pos32. +-------------------------------------------------------------------------------------------------------------------------*/ + +var CodePos = 0x00000000; + +/*------------------------------------------------------------------------------------------------------------------------- +The Pos64, and Pos32 is the actual base address that instructions are supposed to be from in memory when they are loaded +into the BinCode array using the Function ^LoadBinCode()^. +--------------------------------------------------------------------------------------------------------------------------- +The function ^SetBasePosition()^ sets the base location in Pos64, and Pos32, and Code Segment. +-------------------------------------------------------------------------------------------------------------------------*/ + +var Pos64 = 0x00000000, Pos32 = 0x00000000; + +/*------------------------------------------------------------------------------------------------------------------------- +Code Segment is used in 16 bit binaries in which the segment is times 16 (Left Shift 4) added to the 16 bit address position. +This was done to load more programs in 16 bit space at an selected segment location. In 16 bit X86 processors the instruction +pointer register counts from 0000 hex to FFFF hex and starts over at 0000 hex. Allowing a program to be a max length of +65535 bytes long. The Code Segment is multiplied by 16 then is added to the instruction pointer position in memory. +--------------------------------------------------------------------------------------------------------------------------- +In 32 bit, and 64 bit the address combination is large enough that segmented program loading was no longer required. +However 32 bit still supports Segmented addressing if used, but 64 bit binaries do not. Also if the code segment is set +36, or higher in 32 bit binaries this sets SEG:OFFSET address format for each instructions Memory position. +--------------------------------------------------------------------------------------------------------------------------- +In 64 bit mode, an programs instructions are in a 64 bit address using the processors full instruction pointer, but in 32 +bit instructions the first 32 bit of the instruction pointer is used. In 16 bit the first 16 bits of the instruction pointer +is used, but with the code segment. Each instruction is executed in order by the Instruction pointer that goes in sectional sizes +"RIP (64)/EIP (32)/IP (16)" Depending on the Bit mode the 64 bit CPU is set in, or if the CPU is 32 bit to begin with. +-------------------------------------------------------------------------------------------------------------------------*/ + +var CodeSeg = 0x0000; + +/*------------------------------------------------------------------------------------------------------------------------- +The InstructionHex String stores the Bytes of decoded instructions. It is shown to the left side of the disassembled instruction. +-------------------------------------------------------------------------------------------------------------------------*/ + +var InstructionHex = ""; + +/*------------------------------------------------------------------------------------------------------------------------- +The InstructionPos String stores the start position of a decoded binary instruction in memory from the function ^GetPosition()^. +-------------------------------------------------------------------------------------------------------------------------*/ + +var InstructionPos = ""; + +/*------------------------------------------------------------------------------------------------------------------------- +Decoding display options. +-------------------------------------------------------------------------------------------------------------------------*/ + +var ShowInstructionHex = true; //setting to show the hex code of the instruction beside the decoded instruction output. +var ShowInstructionPos = true; //setting to show the instruction address position. + +/*------------------------------------------------------------------------------------------------------------------------- +The Opcode, and Opcode map. +--------------------------------------------------------------------------------------------------------------------------- +The first 0 to 255 (Byte) value that is read is the selected instruction code, however some codes are used as Adjustment to +remove limitations that are read by the function ^DecodePrefixAdjustments()^. +--------------------------------------------------------------------------------------------------------------------------- +Because X86 was limited to 255 instructions An number was sacrificed to add more instructions. +By using one of the 0 to 255 instructions like 15 which is "0F" as an hex number the next 0 to 255 value is an hole +new set of 0 to 255 instructions these are called escape code prefixes. +--------------------------------------------------------------------------------------------------------------------------- +Bellow XX is the opcode combined with the adjustment escape codes thus how opcode is used numerically in the disassembler. +--------------------------------------------------------------------------------------------------------------------------- +00,00000000 = 0, lower 8 bit opcode at max 00,11111111 = 255. (First byte opcodes XX) Opcodes values 0 to 255. +01,00000000 = 256, lower 8 bit opcode at max 01,11111111 = 511. (Two byte opcodes 0F XX) Opcodes values 256 to 511. +10,00000000 = 512, lower 8 bit opcode at max 10,11111111 = 767. (Three byte opcodes 0F 38 XX) Opcodes values 512 to 767. +11,00000000 = 768, lower 8 bit opcode at max 11,11111111 = 1023. (Three byte opcodes 0F 3A XX) Opcodes values 768 to 1023. +--------------------------------------------------------------------------------------------------------------------------- +The lower 8 bits is the selectable opcode 0 to 255 plus one from 255 is 1,00000000 = 256 thus 256 acts as the place holder. +The vector adjustment codes contain an map bit selection the map bits go in order to the place holder map bits are in. +This makes it so the map bits can be placed where the place holder bits are. +--------------------------------------------------------------------------------------------------------------------------- +VEX.mmmmm = 000_00b (1-byte map), 000_01b (2-byte map), 000_10b (0Fh,38h), 000_11b (0Fh,3Ah) +EVEX.mm = 00b (1-byte map), 01b (2-byte map), 10b (0Fh,38h), 11b (0Fh,3Ah) +-------------------------------------------------------------------------------------------------------------------------- +Function ^DecodePrefixAdjustments()^ reads opcodes that act as settings it only ends when Opcode is an actual +instruction code value 0 to 1023 inducing escape codes. Opcode is Used by function ^DecodeOpcode()^ with the Mnemonic array. +-------------------------------------------------------------------------------------------------------------------------*/ + +var Opcode = 0; + +/*------------------------------------------------------------------------------------------------------------------------- +Opcode is used as the index for the point in the structure to land on in the "Mnemonics". +--------------------------------------------------------------------------------------------------------------------------- +X86 has an amazing architectural pattern that is like an fractal in many ways. Previously an experiment was done to make +this an one dimensional array, but after testing it proved that it was slower because each of the branches had to be +calculated to an unique index in memory in which lots of combinations map to the same instructions well some changed. +The calculation took more time than comparing if an index is an reference to another array to optionally use an encoding. +--------------------------------------------------------------------------------------------------------------------------- +The first branch is an array 2 in size which separates opcodes that change between register, and memory mode. +--------------------------------------------------------------------------------------------------------------------------- +The second branch is an array 8 in size which uses an register as an 0 to 7 value for the selected instruction code called grouped opcodes. +The second branch can be branched into another array 8 in size this covers the last three bits of the ModR/M byte for static opcodes. +--------------------------------------------------------------------------------------------------------------------------- +The third branch is an array 4 in size which is the SIMD modes. The third branch can branch to an array 4 in size again under +any of the 4 elements in the SIMD modes for instructions that change by vector extension type. +--------------------------------------------------------------------------------------------------------------------------- +The fifth branch is an array 3 in size which branches to encoding's that change by the set size attribute. +--------------------------------------------------------------------------------------------------------------------------- +Each branch can be combined in any combination, but only in order. If we branch to an array 2 in size under an specific opcode +like this ["",""] then decide to branch memory mode to an array 4 in size we end up with ["",["","","",""]] for making it only +active in memory mode and controlled by SIMD modes, but then if we decide to branch one of the 4 SIMD modes to an array 8 +in size for register opcode separation under one SIMD mode, or an few we can't. We can only branch to an array 3 in size +as that comes next after the array 4 in size. WE also do not need the first branch to be an array it can be an single opcode +encoding. We also do not need the first branch to be an array 2 in size it can be any starting branch then the rest must go +in order from that branch point. +--------------------------------------------------------------------------------------------------------------------------- +Opcode is used by the function ^DecodeOpcode()^ after ^DecodePrefixAdjustments()^. +The function ^DecodeOpcode()^ Gives back the instructions name. +--------------------------------------------------------------------------------------------------------------------------*/ + +const Mnemonics = [ + /*------------------------------------------------------------------------------------------------------------------------ + First Byte operations 0 to 255. + ------------------------------------------------------------------------------------------------------------------------*/ + "ADD","ADD","ADD","ADD","ADD","ADD","PUSH ES","POP ES", + "OR","OR","OR","OR","OR","OR","PUSH CS" + , + "" //*Two byte instructions prefix sets opcode 01,000000000 next byte read is added to the lower 8 bit's. + , + "ADC","ADC","ADC","ADC","ADC","ADC","PUSH SS","POP SS", + "SBB","SBB","SBB","SBB","SBB","SBB","PUSH DS","POP DS", + "AND","AND","AND","AND","AND","AND", + "ES:[", //Extra segment override sets SegOveride "ES:[". + "DAA", + "SUB","SUB","SUB","SUB","SUB","SUB", + "CS:[", //Code segment override sets SegOveride "CS:[". + "DAS", + "XOR","XOR","XOR","XOR","XOR","XOR", + "SS:[", //Stack segment override sets SegOveride "SS:[". + "AAA", + "CMP","CMP","CMP","CMP","CMP","CMP", + "DS:[", //Data Segment override sets SegOveride "DS:[". + "AAS", + /*------------------------------------------------------------------------------------------------------------------------ + Start of Rex Prefix adjustment setting uses opcodes 40 to 4F. These opcodes are only decoded as adjustment settings + by the function ^DecodePrefixAdjustments()^ while in 64 bit mode. If not in 64 bit mode the codes are not read + by the function ^DecodePrefixAdjustments()^ which allows the opcode to be set 40 to 4F hex in which the defined + instructions bellow are used by ^DecodeOpcode()^. + ------------------------------------------------------------------------------------------------------------------------*/ + "INC","INC","INC","INC","INC","INC","INC","INC", + "DEC","DEC","DEC","DEC","DEC","DEC","DEC","DEC", + /*------------------------------------------------------------------------------------------------------------------------ + End of the Rex Prefix adjustment setting opcodes. + ------------------------------------------------------------------------------------------------------------------------*/ + "PUSH","PUSH","PUSH","PUSH","PUSH","PUSH","PUSH","PUSH", + "POP","POP","POP","POP","POP","POP","POP","POP", + ["PUSHA","PUSHAD",""],["POPA","POPAD",""], + ["BOUND","BOUND",""], //EVEX prefix adjustment settings only if used in register to register, or in 64 bit mode, otherwise the defined BOUND instruction is used. + "MOVSXD", + "FS:[","GS:[", //Sets SegOveride "FS:[" next opcode sets "GS:[". + "","", //Operand Size, and Address size adjustment to ModR/M. + "PUSH","IMUL","PUSH","IMUL", + "INS","INS","OUTS","OUTS", + "JO","JNO","JB","JAE","JE","JNE","JBE","JA", + "JS","JNS","JP","JNP","JL","JGE","JLE","JG", + ["ADD","OR","ADC","SBB","AND","SUB","XOR","CMP"], //Group opcode uses the ModR/M register selection 0 though 7 giving 8 instruction in one opcode. + ["ADD","OR","ADC","SBB","AND","SUB","XOR","CMP"], + ["ADD","OR","ADC","SBB","AND","SUB","XOR","CMP"], + ["ADD","OR","ADC","SBB","AND","SUB","XOR","CMP"], + "TEST","TEST","XCHG","XCHG", + "MOV","MOV","MOV","MOV","MOV", + ["LEA","???"], //*ModR/M Register, and memory mode separation. + "MOV", + ["POP","???","???","???","???","???","???","???"], + [["NOP","","",""],["NOP","","",""],["PAUSE","","",""],["NOP","","",""]], + "XCHG","XCHG","XCHG","XCHG","XCHG","XCHG","XCHG", + ["CWDE","CBW","CDQE"], //*Opcode 0 to 3 for instructions that change name by size setting. + ["CDQ","CWD","CQO"], + "CALL","WAIT", + ["PUSHFQ","PUSHF","PUSHFQ"], + ["POPFQ","POPF","POPFQ"], + "SAHF","LAHF", + "MOV","MOV","MOV","MOV", + "MOVS","MOVS", + "CMPS","CMPS", + "TEST","TEST", + "STOS","STOS", + "LODS","LODS", + "SCAS","SCAS", + "MOV","MOV","MOV","MOV","MOV","MOV","MOV","MOV", + "MOV","MOV","MOV","MOV","MOV","MOV","MOV","MOV", + ["ROL","ROR","RCL","RCR","SHL","SHR","SAL","SAR"], + ["ROL","ROR","RCL","RCR","SHL","SHR","SAL","SAR"], + "RET","RET", + "LES", //VEX prefix adjustment settings only if used in register to register, or in 64 bit mode, otherwise the defined instruction is used. + "LDS", //VEX prefix adjustment settings only if used in register to register, or in 64 bit mode, otherwise the defined instruction is used. + [ + "MOV","???","???","???","???","???","???", + ["XABORT","XABORT","XABORT","XABORT","XABORT","XABORT","XABORT","XABORT"] + ], + [ + "MOV","???","???","???","???","???","???", + ["XBEGIN","XBEGIN","XBEGIN","XBEGIN","XBEGIN","XBEGIN","XBEGIN","XBEGIN"] + ], + "ENTER","LEAVE","RETF","RETF","INT","INT","INTO", + ["IRETD","IRET","IRETQ"], + ["ROL","ROR","RCL","RCR","SHL","SHR","SAL","SAR"], + ["ROL","ROR","RCL","RCR","SHL","SHR","SAL","SAR"], + ["ROL","ROR","RCL","RCR","SHL","SHR","SAL","SAR"], + ["ROL","ROR","RCL","RCR","SHL","SHR","SAL","SAR"], + "AAMB","AADB","???", + "XLAT", + /*------------------------------------------------------------------------------------------------------------------------ + X87 FPU. + ------------------------------------------------------------------------------------------------------------------------*/ + [ + ["FADD","FMUL","FCOM","FCOMP","FSUB","FSUBR","FDIV","FDIVR"], + ["FADD","FMUL","FCOM","FCOMP","FSUB","FSUBR","FDIV","FDIVR"] + ], + [ + ["FLD","???","FST","FSTP","FLDENV","FLDCW","FNSTENV","FNSTCW"], + [ + "FLD","FXCH", + ["FNOP","???","???","???","???","???","???","???"], + "FSTP1", + ["FCHS","FABS","???","???","FTST","FXAM","???","???"], + ["FLD1","FLDL2T","FLDL2E","FLDPI","FLDLG2","FLDLN2","FLDZ","???"], + ["F2XM1","FYL2X","FPTAN","FPATAN","FXTRACT","FPREM1","FDECSTP","FINCSTP"], + ["FPREM","FYL2XP1","FSQRT","FSINCOS","FRNDINT","FSCALE","FSIN","FCOS"] + ] + ], + [ + ["FIADD","FIMUL","FICOM","FICOMP","FISUB","FISUBR","FIDIV","FIDIVR"], + [ + "FCMOVB","FCMOVE","FCMOVBE","FCMOVU","???", + ["???","FUCOMPP","???","???","???","???","???","???"], + "???","???" + ] + ], + [ + ["FILD","FISTTP","FIST","FISTP","???","FLD","???","FSTP"], + [ + "CMOVNB","FCMOVNE","FCMOVNBE","FCMOVNU", + ["FENI","FDISI","FNCLEX","FNINIT","FSETPM","???","???","???"], + "FUCOMI","FCOMI","???" + ] + ], + [ + ["FADD","FMUL","FCOM","DCOMP","FSUB","FSUBR","FDIV","FDIVR"], + ["FADD","FMUL","FCOM2","FCOMP3","FSUBR","FSUB","FDIVR","FDIV"] + ], + [ + ["FLD","FISTTP","FST","FSTP","FRSTOR","???","FNSAVE","FNSTSW"], + ["FFREE","FXCH4","FST","FSTP","FUCOM","FUCOMP","???","???"] + ], + [ + ["FIADD","FIMUL","FICOM","FICOMP","FISUB","FISUBR","FIDIV","FIDIVR"], + [ + "FADDP","FMULP","FCOMP5", + ["???","FCOMPP","???","???","???","???","???","???"], + "FSUBRP","FSUBP","FDIVRP","FDIVP" + ] + ], + [ + ["FILD","FISTTP","FIST","FISTP","FBLD","FILD","FBSTP","FISTP"], + [ + "FFREEP","FXCH7","FSTP8","FSTP9", + ["FNSTSW","???","???","???","???","???","???","???"], + "FUCOMIP","FCOMIP","???" + ] + ], + /*------------------------------------------------------------------------------------------------------------------------ + End of X87 FPU. + ------------------------------------------------------------------------------------------------------------------------*/ + "LOOPNE","LOOPE","LOOP","JRCXZ", + "IN","IN","OUT","OUT", + "CALL","JMP","JMP","JMP", + "IN","IN","OUT","OUT", + /*------------------------------------------------------------------------------------------------------------------------ + The Repeat, and lock prefix opcodes apply to the next opcode. + ------------------------------------------------------------------------------------------------------------------------*/ + "LOCK", //Adds LOCK to the start of instruction. When Opcode F0 hex is read by function ^DecodePrefixAdjustments()^ sets PrefixG2 to LOCK. + "ICEBP", //Instruction ICEBP. + "REPNE", //Adds REPNE (Opcode F2 hex) to the start of instruction. Read by function ^DecodePrefixAdjustments()^ sets PrefixG1 to REPNE. + "REP", //Adds REP (Opcode F3 hex) to the start of instruction. Read by function ^DecodePrefixAdjustments()^ sets PrefixG1 to REP. + /*------------------------------------------------------------------------------------------------------------------------ + End of Repeat, and lock instruction adjustment codes. + ------------------------------------------------------------------------------------------------------------------------*/ + "HLT","CMC", + ["TEST","???","NOT","NEG","MUL","IMUL","DIV","IDIV"], + ["TEST","???","NOT","NEG","MUL","IMUL","DIV","IDIV"], + "CLC","STC","CLI","STI","CLD","STD", + ["INC","DEC","???","???","???","???","???","???"], + [ + ["INC","DEC","CALL","CALL","JMP","JMP","PUSH","???"], + ["INC","DEC","CALL","???","JMP","???","PUSH","???"] + ], + /*------------------------------------------------------------------------------------------------------------------------ + Two Byte Opcodes 256 to 511. Opcodes plus 256 goes to 511 used by escape code "0F", Or + set directly by adding map bits "01" because "01 00000000" bin = 256 plus opcode. + ------------------------------------------------------------------------------------------------------------------------*/ + [ + ["SLDT","STR","LLDT","LTR","VERR","VERW","JMPE","???"], + ["SLDT","STR","LLDT","LTR","VERR","VERW","JMPE","???"] + ], + [ + ["SGDT","SIDT","LGDT","LIDT","SMSW","???","LMSW","INVLPG"], + [ + ["???","VMCALL","VMLAUNCH","VMRESUME","VMXOFF","???","???","???"], + ["MONITOR","MWAIT","CLAC","STAC","???","???","???","ENCLS"], + ["XGETBV","XSETBV","???","???","VMFUNC","XEND","XTEST","ENCLU"], + ["VMRUN","VMMCALL","VMLOAD","VMSAVE","STGI","CLGI","SKINIT","INVLPGA"], + "SMSW","???","LMSW", + ["SWAPGS","RDTSCP","MONITORX","MWAITX","???","???","???","???"] + ] + ], + ["LAR","LAR"],["LSL","LSL"],"???", + "SYSCALL","CLTS","SYSRET","INVD", + "WBINVD","???","UD2","???", + [["PREFETCH","PREFETCHW","???","???","???","???","???","???"],"???"], + "FEMMS", + "", //3DNow Instruction name is encoded by the IMM8 operand. + [ + ["MOVUPS","MOVUPD","MOVSS","MOVSD"], + ["MOVUPS","MOVUPD","MOVSS","MOVSD"] + ], + [ + ["MOVUPS","MOVUPD","MOVSS","MOVSD"], + ["MOVUPS","MOVUPD","MOVSS","MOVSD"] + ], + [ + ["MOVLPS","MOVLPD","MOVSLDUP","MOVDDUP"], + ["MOVHLPS","???","MOVSLDUP","MOVDDUP"] + ], + [["MOVLPS","MOVLPD","???","???"],"???"], + ["UNPCKLPS","UNPCKLPD","???","???"], //An instruction with 4 operations uses the 4 SIMD modes as an Vector instruction. + ["UNPCKHPS","UNPCKHPD","???","???"], + [["MOVHPS","MOVHPD","MOVSHDUP","???"],["MOVLHPS","???","MOVSHDUP","???"]], + [["MOVHPS","MOVHPD","???","???"],"???"], + [["PREFETCHNTA","PREFETCHT0","PREFETCHT1","PREFETCHT2","???","???","???","???"],"???"], + "???", + [[["BNDLDX","","",""],["BNDMOV","","",""],["BNDCL","","",""],["BNDCU","","",""]], + ["???",["BNDMOV","","",""],["BNDCL","","",""],["BNDCU","","",""]]], + [[["BNDSTX","","",""],["BNDMOV","","",""],["BNDMK","","",""],["BNDCN","","",""]], + ["???",["BNDMOV","","",""],"???",["BNDCN","","",""]]], + "???","???","???", + "NOP", + ["???","MOV"],["???","MOV"], //CR and DR register Move + ["???","MOV"],["???","MOV"], //CR and DR register Move + ["???","MOV"],"???", //TR (TEST REGISTER) register Move + ["???","MOV"],"???", //TR (TEST REGISTER) register Move + [ + ["MOVAPS","MOVAPS","MOVAPS","MOVAPS"], + ["MOVAPD","MOVAPD","MOVAPD","MOVAPD"], + "???","???" + ], + [ + [ + ["MOVAPS","MOVAPS","MOVAPS","MOVAPS"], + ["MOVAPD","MOVAPD","MOVAPD","MOVAPD"], + ["","","",["MOVNRAPS","MOVNRNGOAPS","MOVNRAPS"]], + ["","","",["MOVNRAPD","MOVNRNGOAPD","MOVNRAPD"]] + ], + [ + ["MOVAPS","MOVAPS","MOVAPS","MOVAPS"], + ["MOVAPD","MOVAPD","MOVAPD","MOVAPD"], + "???","???" + ] + ], + [ + ["CVTPI2PS","","",""],["CVTPI2PD","","",""], //Is not allowed to be Vector encoded. + "CVTSI2SS","CVTSI2SD" + ], + [ + [ + "MOVNTPS","MOVNTPD", + ["MOVNTSS","","",""],["MOVNTSD","","",""] //SSE4a can not be vector encoded. + ],"???" + ], + [ + ["CVTTPS2PI","","",""],["CVTTPD2PI","","",""], //Is not allowed to be Vector encoded. + "CVTTSS2SI","CVTTSD2SI" + ], + [ + ["CVTPS2PI","","",""],["CVTPD2PI","","",""], //Is not allowed to be Vector encoded. + "CVTSS2SI","CVTSD2SI" + ], + ["UCOMISS","UCOMISD","???","???"], + ["COMISS","COMISD","???","???"], + "WRMSR","RDTSC","RDMSR","RDPMC", + "SYSENTER","SYSEXIT","???", + "GETSEC", + "", //*Three byte instructions prefix combo 0F 38 (Opcode = 01,00111000) sets opcode 10,000000000 next byte read is added to the lower 8 bit's. + "???", + "", //*Three byte instructions prefix combo 0F 3A (Opcode = 01,00111010) sets opcode 11,000000000 next byte read is added to the lower 8 bit's. + "???","???","???","???","???", + "CMOVO", + [ + ["CMOVNO",["KANDW","","KANDQ"],"",""], + ["CMOVNO",["KANDB","","KANDD"],"",""],"","" + ], + [ + ["CMOVB",["KANDNW","","KANDNQ"],"",""], + ["CMOVB",["KANDNB","","KANDND"],"",""],"","" + ], + [["CMOVAE","KANDNR","",""],"","",""], + [ + ["CMOVE",["KNOTW","","KNOTQ"],"",""], + ["CMOVE",["KNOTB","","KNOTD"],"",""],"","" + ], + [ + ["CMOVNE",["KORW","","KORQ"],"",""], + ["CMOVNE",["KORB","","KORD"],"",""],"","" + ], + [ + ["CMOVBE",["KXNORW","","KXNORQ"],"",""], + ["CMOVBE",["KXNORB","","KXNORD"],"",""],"","" + ], + [ + ["CMOVA",["KXORW","","KXORQ"],"",""], + ["CMOVA",["KXORB","","KXORD"],"",""],"","" + ], + [["CMOVS","KMERGE2L1H","",""],"","",""], + [["CMOVNS","KMERGE2L1L","",""],"","",""], + [ + ["CMOVP",["KADDW","","KADDQ"],"",""], + ["CMOVP",["KADDB","","KADDD"],"",""],"","" + ], + [ + ["CMOVNP",["KUNPCKWD","","KUNPCKDQ"],"",""], + ["CMOVNP",["KUNPCKBW","","???"],"",""],"","" + ], + "CMOVL","CMOVGE","CMOVLE","CMOVG", + [ + "???", + [ + ["MOVMSKPS","MOVMSKPS","",""],["MOVMSKPD","MOVMSKPD","",""], + "???","???" + ] + ], + ["SQRTPS","SQRTPD","SQRTSS","SQRTSD"], + [ + ["RSQRTPS","RSQRTPS","",""],"???", + ["RSQRTSS","RSQRTSS","",""],"???" + ], + [ + ["RCPPS","RCPPS","",""],"???", + ["RCPSS","RCPSS","",""],"???" + ], + ["ANDPS","ANDPD","???","???"], + ["ANDNPS","ANDNPD","???","???"], + ["ORPS","ORPD","???","???"], + ["XORPS","XORPD","???","???"], + [ + ["ADDPS","ADDPS","ADDPS","ADDPS"], + ["ADDPD","ADDPD","ADDPD","ADDPD"], + "ADDSS","ADDSD" + ], + [ + ["MULPS","MULPS","MULPS","MULPS"], + ["MULPD","MULPD","MULPD","MULPD"], + "MULSS","MULSD" + ], + [ + ["CVTPS2PD","CVTPS2PD","CVTPS2PD","CVTPS2PD"], + ["CVTPD2PS","CVTPD2PS","CVTPD2PS","CVTPD2PS"], + "CVTSS2SD","CVTSD2SS" + ], + [["CVTDQ2PS","","CVTQQ2PS"],["CVTPS2DQ","","???"],"CVTTPS2DQ","???"], + [ + ["SUBPS","SUBPS","SUBPS","SUBPS"], + ["SUBPD","SUBPD","SUBPD","SUBPD"], + "SUBSS","SUBSD" + ], + ["MINPS","MINPD","MINSS","MINSD"], + ["DIVPS","DIVPD","DIVSS","DIVSD"], + ["MAXPS","MAXPD","MAXSS","MAXSD"], + [["PUNPCKLBW","","",""],"PUNPCKLBW","",""], + [["PUNPCKLWD","","",""],"PUNPCKLWD","",""], + [["PUNPCKLDQ","","",""],"PUNPCKLDQ","",""], + [["PACKSSWB","","",""],"PACKSSWB","",""], + [["PCMPGTB","","",""],["PCMPGTB","PCMPGTB","PCMPGTB",""],"",""], + [["PCMPGTW","","",""],["PCMPGTW","PCMPGTW","PCMPGTW",""],"",""], + [["PCMPGTD","","",""],["PCMPGTD","PCMPGTD",["PCMPGTD","","???"],["PCMPGTD","","???"]],"",""], + [["PACKUSWB","","",""],"PACKUSWB","",""], + [["PUNPCKHBW","","",""],"PUNPCKHBW","",""], + [["PUNPCKHWD","","",""],"PUNPCKHWD","",""], + [["PUNPCKHDQ","","",""],["PUNPCKHDQ","","???"],"",""], + [["PACKSSDW","","",""],["PACKSSDW","","???"],"",""], + ["???","PUNPCKLQDQ","???","???"], + ["???","PUNPCKHQDQ","???","???"], + [["MOVD","","",""],["MOVD","","MOVQ"],"",""], + [ + [ + ["MOVQ","","",""], + ["MOVDQA","MOVDQA",["MOVDQA32","","MOVDQA64"],["MOVDQA32","","MOVDQA64"]], + ["MOVDQU","MOVDQU",["MOVDQU32","","MOVDQU64"],""], + ["","",["MOVDQU8","","MOVDQU16"],""] + ], + [ + ["MOVQ","","",""], + ["MOVDQA","MOVDQA",["MOVDQA32","","MOVDQA64"],["MOVDQA32","","MOVDQA64"]], + ["MOVDQU","MOVDQU",["MOVDQU32","","MOVDQU64"],""], + ["","",["MOVDQU8","","MOVDQU16"],""] + ] + ], + [ + ["PSHUFW","","",""], + ["PSHUFD","PSHUFD",["PSHUFD","","???"],["PSHUFD","","???"]], + "PSHUFHW", + "PSHUFLW" + ], + [ + "???", + [ + "???","???", + [["PSRLW","","",""],"PSRLW","",""],"???", + [["PSRAW","","",""],"PSRAW","",""],"???", + [["PSLLW","","",""],"PSLLW","",""],"???" + ] + ], + [ + ["???",["","",["PRORD","","PRORQ"],""],"???","???"], + ["???",["","",["PROLD","","PROLQ"],""],"???","???"], + [["PSRLD","","",""],["PSRLD","PSRLD",["PSRLD","","???"],["PSRLD","","???"]],"",""], + "???", + [["PSRAD","","",""],["PSRAD","PSRAD",["PSRAD","","PSRAQ"],["PSRAD","","???"]],"",""], + "???", + [["PSLLD","","",""],["PSLLD","PSLLD",["PSLLD","","???"],["PSLLD","","???"]],"",""], + "???" + ], + [ + "???", + [ + "???","???", + [["PSRLQ","PSRLQ","",""],"PSRLQ","",""],["???","PSRLDQ","???","???"], + "???","???", + [["PSLLQ","PSLLQ","",""],"PSLLQ","",""],["???","PSLLDQ","???","???"] + ] + ], + [["PCMPEQB","","",""],["PCMPEQB","PCMPEQB","PCMPEQB",""],"",""], + [["PCMPEQW","","",""],["PCMPEQW","PCMPEQW","PCMPEQW",""],"",""], + [["PCMPEQD","","",""],["PCMPEQD","PCMPEQD",["PCMPEQD","","???"],["PCMPEQD","","???"]],"",""], + [["EMMS",["ZEROUPPER","ZEROALL",""],"",""],"???","???","???"], + [ + ["VMREAD","",["CVTTPS2UDQ","","CVTTPD2UDQ"],""], + ["EXTRQ","",["CVTTPS2UQQ","","CVTTPD2UQQ"],""], + ["???","","CVTTSS2USI",""], + ["INSERTQ","","CVTTSD2USI",""] + ], + [ + ["VMWRITE","",["CVTPS2UDQ","","CVTPD2UDQ"], ""], + ["EXTRQ","",["CVTPS2UQQ","","CVTPD2UQQ"],""], + ["???","","CVTSS2USI",""], + ["INSERTQ","","CVTSD2USI",""] + ], + [ + "???", + ["","",["CVTTPS2QQ","","CVTTPD2QQ"],""], + ["","",["CVTUDQ2PD","","CVTUQQ2PD"],"CVTUDQ2PD"], + ["","",["CVTUDQ2PS","","CVTUQQ2PS"],""] + ], + [ + "???", + ["","",["CVTPS2QQ","","CVTPD2QQ"],""], + ["","","CVTUSI2SS",""], + ["","","CVTUSI2SD",""] + ], + [ + "???",["HADDPD","HADDPD","",""], + "???",["HADDPS","HADDPS","",""] + ], + [ + "???",["HSUBPD","HSUBPD","",""], + "???",["HSUBPS","HSUBPS","",""] + ], + [["MOVD","","",""],["MOVD","","MOVQ"],["MOVQ","MOVQ",["???","","MOVQ"],""],"???"], + [ + ["MOVQ","","",""], + ["MOVDQA","MOVDQA",["MOVDQA32","","MOVDQA64"],["MOVDQA32","","MOVDQA64"]], + ["MOVDQU","MOVDQU",["MOVDQU32","","MOVDQU64"],""], + ["???","",["MOVDQU8","","MOVDQU16"],""] + ], + "JO","JNO","JB","JAE", + [["JE","JKZD","",""],"","",""],[["JNE","JKNZD","",""],"","",""], //K1OM. + "JBE","JA","JS","JNS","JP","JNP","JL","JGE","JLE","JG", + [ + ["SETO",["KMOVW","","KMOVQ"],"",""], + ["SETO",["KMOVB","","KMOVD"],"",""],"","" + ], + [ + ["SETNO",["KMOVW","","KMOVQ"],"",""], + ["SETNO",["KMOVB","","KMOVD"],"",""],"","" + ], + [ + ["SETB",["KMOVW","","???"],"",""], + ["SETB",["KMOVB","","???"],"",""],"", + ["SETB",["KMOVD","","KMOVQ"],"",""] + ], + [ + ["SETAE",["KMOVW","","???"],"",""], + ["SETAE",["KMOVB","","???"],"",""],"", + ["SETAE",["KMOVD","","KMOVQ"],"",""] + ], + "SETE",[["SETNE","KCONCATH","",""],"","",""], + "SETBE",[["SETA","KCONCATL","",""],"","",""], + [ + ["SETS",["KORTESTW","","KORTESTQ"],"",""], + ["SETS",["KORTESTB","","KORTESTD"],"",""],"","" + ], + [ + ["SETNS",["KTESTW","","KTESTQ"],"",""], + ["SETNS",["KTESTB","","KTESTD"],"",""],"","" + ], + "SETP","SETNP","SETL","SETGE","SETLE","SETG", + "PUSH","POP", + "CPUID", //Identifies the CPU and which Instructions the current CPU can use. + "BT", + "SHLD","SHLD", + "XBTS","IBTS", + "PUSH","POP", + "RSM", + "BTS", + "SHRD","SHRD", + [ + [ + ["FXSAVE","???","FXSAVE64"],["FXRSTOR","???","FXRSTOR64"], + "LDMXCSR","STMXCSR", + ["XSAVE","","XSAVE64"],["XRSTOR","","XRSTOR64"], + ["XSAVEOPT","CLWB","XSAVEOPT64"], + ["CLFLUSHOPT","CLFLUSH",""] + ], + [ + ["???","???",["RDFSBASE","","",""],"???"],["???","???",["RDGSBASE","","",""],"???"], + ["???","???",["WRFSBASE","","",""],"???"],["???","???",["WRGSBASE","","",""],"???"], + "???", + ["LFENCE","???","???","???","???","???","???","???"], + ["MFENCE","???","???","???","???","???","???","???"], + ["SFENCE","???","???","???","???","???","???","???"] + ] + ], + "IMUL", + "CMPXCHG","CMPXCHG", + ["LSS","???"], + "BTR", + ["LFS","???"], + ["LGS","???"], + "MOVZX","MOVZX", + [ + ["JMPE","","",""],"???", + ["POPCNT","POPCNT","",""],"???" + ], + "???", + ["???","???","???","???","BT","BTS","BTR","BTC"], + "BTC", + [ + ["BSF","","",""],"???", + ["TZCNT","TZCNT","",""],["BSF","TZCNTI","",""] + ], + [ + ["BSR","","",""],"???", + ["LZCNT","LZCNT","",""],["BSR","","",""] + ], + "MOVSX","MOVSX", + "XADD","XADD", + [ + ["CMP,PS,","CMP,PS,","CMP,PS,","CMP,PS,"], + ["CMP,PD,","CMP,PD,","CMP,PD,","CMP,PD,"], + ["CMP,SS,","CMP,SS,","CMP,SS,",""], + ["CMP,SD,","CMP,SD,","CMP,SD,",""] + ], + ["MOVNTI","???"], + [["PINSRW","","",""],"PINSRW","",""], + ["???",[["PEXTRW","","",""],"PEXTRW","",""]], + ["SHUFPS","SHUFPD","???","???"], + [ + [ + "???", + ["CMPXCHG8B","","CMPXCHG16B"], + "???", + ["XRSTORS","","XRSTORS64"], + ["XSAVEC","","XSAVEC64"], + ["XSAVES","","XSAVES64"], + ["VMPTRLD","VMCLEAR","VMXON","???"],["VMPTRST","???","???","???"] + ], + [ + "???", + ["SSS","???","???","???","???","???","???","???"], //Synthetic virtual machine operation codes. + "???","???","???","???", + "RDRAND","RDSEED" + ] + ], + "BSWAP","BSWAP","BSWAP","BSWAP","BSWAP","BSWAP","BSWAP","BSWAP", + ["???",["ADDSUBPD","ADDSUBPD","",""],"???",["ADDSUBPS","ADDSUBPS","",""]], + [["PSRLW","","",""],"PSRLW","",""], + [["PSRLD","","",""],["PSRLD","PSRLD",["PSRLD","","???"],""],"",""], + [["PSRLQ","","",""],"PSRLQ","",""], + [["PADDQ","","",""],"PADDQ","",""], + [["PMULLW","","",""],"PMULLW","",""], + [ + ["???","MOVQ","???","???"], + ["???","MOVQ",["MOVQ2DQ","","",""],["MOVDQ2Q","","",""]] + ], + ["???",[["PMOVMSKB","","",""],["PMOVMSKB","PMOVMSKB","",""],"???","???"]], + [["PSUBUSB","","",""],"PSUBUSB","",""], + [["PSUBUSW","","",""],"PSUBUSW","",""], + [["PMINUB","","",""],"PMINUB","",""], + [["PAND","","",""],["PAND","PAND",["PANDD","","PANDQ"],["PANDD","","PANDQ"]],"",""], + [["PADDUSB","","",""],"PADDUSB","",""], + [["PADDUSW","","",""],"PADDUSW","",""], + [["PMAXUB","","",""],"PMAXUB","",""], + [["PANDN","","",""],["PANDN","PANDN",["PANDND","","PANDNQ"],["PANDND","","PANDNQ"]],"",""], + [["PAVGB","","",""],"PAVGB","",""], + [ + [["PSRAW","","",""],["PSRAW","PSRAW","PSRAW",""],"",""], + [["PSRAW","","",""],["PSRAW","PSRAW","PSRAW",""],"",""] + ], + [["PSRAD","","",""],["PSRAD","PSRAD",["PSRAD","","PSRAQ"],""],"",""], + [["PAVGW","","",""],"PAVGW","",""], + [["PMULHUW","","",""],"PMULHUW","",""], + [["PMULHW","","",""],"PMULHW","",""], + [ + "???", + ["CVTTPD2DQ","CVTTPD2DQ","CVTTPD2DQ",""], + ["CVTDQ2PD","CVTDQ2PD",["CVTDQ2PD","CVTDQ2PD","CVTQQ2PD"],"CVTDQ2PD"], + "CVTPD2DQ" + ], + [[["MOVNTQ","","",""],["MOVNTDQ","","???"],"???","???"],"???"], + [["PSUBSB","","",""],"PSUBSB","",""], + [["PSUBSW","","",""],"PSUBSW","",""], + [["PMINSW","","",""],"PMINSW","",""], + [["POR","","",""],["POR","POR",["PORD","","PORQ"],["PORD","","PORQ"]],"",""], + [["PADDSB","","",""],"PADDSB","",""], + [["PADDSW","","",""],"PADDSW","",""], + [["PMAXSW","","",""],"PMAXSW","",""], + [["PXOR","","",""],["PXOR","PXOR",["PXORD","","PXORQ"],["PXORD","","PXORQ"]],"",""], + [["???","???","???",["LDDQU","LDDQU","",""]],"???"], + [["PSLLW","","",""],"PSLLW","",""], + [["PSLLD","","",""],["PSLLD","","???"],"",""], + [["PSLLQ","","",""],"PSLLQ","",""], + [["PMULUDQ","","",""],"PMULUDQ","",""], + [["PMADDWD","","",""],"PMADDWD","",""], + [["PSADBW","","",""],"PSADBW","",""], + ["???",[["MASKMOVQ","","",""],["MASKMOVDQU","MASKMOVDQU","",""],"???","???"]], + [["PSUBB","","",""],"PSUBB","",""], + [["PSUBW","","",""],"PSUBW","",""], + [["PSUBD","","",""],["PSUBD","PSUBD",["PSUBD","","???"],["PSUBD","","???"]],"",""], + [["PSUBQ","","",""],"PSUBQ","",""], + [["PADDB","","",""],"PADDB","",""], + [["PADDW","","",""],"PADDW","",""], + [["PADDD","","",""],["PADDD","PADDD",["PADDD","","???"],["PADDD","","???"]],"",""], + "???", + /*------------------------------------------------------------------------------------------------------------------------ + Three Byte operations 0F38. Opcodes plus 512 goes to 767 used by escape codes "0F,38", Or + set directly by adding map bits "10" because "10 00000000" bin = 512 plus opcode. + ------------------------------------------------------------------------------------------------------------------------*/ + [["PSHUFB","","",""],"PSHUFB","???","???"], + [["PHADDW","","",""],["PHADDW","PHADDW","",""],"???","???"], + [["PHADDD","","",""],["PHADDD","PHADDD","",""],"???","???"], + [["PHADDSW","","",""],["PHADDSW","PHADDSW","",""],"???","???"], + [["PMADDUBSW","","",""],"PMADDUBSW","???","???"], + [["PHSUBW","","",""],["PHSUBW","PHSUBW","",""],"???","???"], + [["PHSUBD","","",""],["PHSUBD","PHSUBD","",""],"???","???"], + [["PHSUBSW","","",""],["PHSUBSW","PHSUBSW","",""],"???","???"], + [["PSIGNB","","",""],["PSIGNB","PSIGNB","",""],"???","???"], + [["PSIGNW","","",""],["PSIGNW","PSIGNW","",""],"???","???"], + [["PSIGND","","",""],["PSIGND","PSIGND","",""],"???","???"], + [["PMULHRSW","","",""],"PMULHRSW","???","???"], + ["???",["","PERMILPS",["PERMILPS","","???"],""],"???","???"], + ["???",["","PERMILPD","PERMILPD",""],"???","???"], + ["???",["","TESTPS","",""],"???","???"], + ["???",["","TESTPD","",""],"???","???"], + ["???",["PBLENDVB","PBLENDVB","PSRLVW",""],["","","PMOVUSWB",""],"???"], + ["???",["","","PSRAVW",""],["","","PMOVUSDB",""],"???"], + ["???",["","","PSLLVW",""],["","","PMOVUSQB",""],"???"], + ["???",["","CVTPH2PS",["CVTPH2PS","","???"],""],["","","PMOVUSDW",""],"???"], + ["???",["BLENDVPS","BLENDVPS",["PRORVD","","PRORVQ"],""],["","","PMOVUSQW",""],"???"], + ["???",["BLENDVPD","BLENDVPD",["PROLVD","","PROLVQ"],""],["","","PMOVUSQD",""],"???"], + ["???",["","PERMPS",["PERMPS","","PERMPD"],""],"???","???"], + ["???",["PTEST","PTEST","",""],"???","???"], + ["???",["","BROADCASTSS",["BROADCASTSS","","???"],["BROADCASTSS","","???"]],"???","???"], + ["???",["","BROADCASTSD",["BROADCASTF32X2","","BROADCASTSD"],["???","","BROADCASTSD"]],"???","???"], + ["???",["","BROADCASTF128",["BROADCASTF32X4","","BROADCASTF64X2"],["BROADCASTF32X4","","???"]],"???","???"], + ["???",["","",["BROADCASTF32X8","","BROADCASTF64X4"],["???","","BROADCASTF64X4"]],"???","???"], + [["PABSB","","",""],"PABSB","???","???"], + [["PABSW","","",""],"PABSW","???","???"], + [["PABSD","","",""],["PABSD","","???"],"???","???"], + ["???",["","","PABSQ",""],"???","???"], + ["???","PMOVSXBW",["","","PMOVSWB",""],"???"], + ["???","PMOVSXBD",["","","PMOVSDB",""],"???"], + ["???","PMOVSXBQ",["","","PMOVSQB",""],"???"], + ["???","PMOVSXWD",["","","PMOVSDW",""],"???"], + ["???","PMOVSXWQ",["","","PMOVSQW",""],"???"], + ["???","PMOVSXDQ",["","","PMOVSQD",""],"???"], + ["???",["","",["PTESTMB","","PTESTMW"],""],["","",["PTESTNMB","","PTESTNMW"],""],"???"], + ["???",["","",["PTESTMD","","PTESTMQ"],["PTESTMD","","???"]],["","",["PTESTNMD","","PTESTNMQ"],""],"???"], + ["???","PMULDQ",["","",["PMOVM2B","","PMOVM2W"],""],"???"], + ["???",["PCMPEQQ","PCMPEQQ","PCMPEQQ",""],["","",["PMOVB2M","","PMOVW2M"],""],"???"], + [["???",["MOVNTDQA","","???"],"???","???"],["???","???",["","",["???","","PBROADCASTMB2Q"],""],"???"]], + ["???",["PACKUSDW","","???"],"???","???"], + ["???",["","MASKMOVPS",["SCALEFPS","","SCALEFPD"],""],"???","???"], + ["???",["","MASKMOVPD",["SCALEFSS","","SCALEFSD"],""],"???","???"], + ["???",["","MASKMOVPS","",""],"???","???"], + ["???",["","MASKMOVPD","",""],"???","???"], + ["???","PMOVZXBW",["","","PMOVWB",""],"???"], + ["???","PMOVZXBD",["","","PMOVDB",""],"???"], + ["???","PMOVZXBQ",["","","PMOVQB",""],"???"], + ["???","PMOVZXWD",["","","PMOVDW",""],"???"], + ["???","PMOVZXWQ",["","","PMOVQW",""],"???"], + ["???","PMOVZXDQ",["","",["PMOVQD","PMOVQD",""],""],"???"], + ["???",["","PERMD",["PERMD","","PERMQ"],["PERMD","","???"]],"???","???"], + ["???",["PCMPGTQ","PCMPGTQ","PCMPGTQ",""],"???","???"], + ["???","PMINSB",["","",["PMOVM2D","","PMOVM2Q"],""],"???"], + ["???",["PMINSD","PMINSD",["PMINSD","","PMINSQ"],["PMINSD","","???"]],["","",["PMOVD2M","","PMOVQ2M"],""],"???"], + ["???","PMINUW",["","","PBROADCASTMW2D",""],"???"], + ["???",["PMINUD","PMINUD",["PMINUD","","PMINUQ"],["PMINUD","","???"]],"???","???"], + ["???","PMAXSB","???","???"], + ["???",["PMAXSD","PMAXSD",["PMAXSD","","PMAXSQ"],["PMAXSD","","???"]],"???","???"], + ["???","PMAXUW","???","???"], + ["???",["PMAXUD","PMAXUD",["PMAXUD","","PMAXUQ"],["PMAXUD","","???"]],"???","???"], + ["???",["PMULLD","PMULLD",["PMULLD","","PMULLQ"],["PMULLD","",""]],"???","???"], + ["???",["PHMINPOSUW",["PHMINPOSUW","PHMINPOSUW",""],"",""],"???","???"], + ["???",["","",["GETEXPPS","","GETEXPPD"],["GETEXPPS","","GETEXPPD"]],"???","???"], + ["???",["","",["GETEXPSS","","GETEXPSD"],""],"???","???"], + ["???",["","",["PLZCNTD","","PLZCNTQ"],""],"???","???"], + ["???",["",["PSRLVD","","PSRLVQ"],["PSRLVD","","PSRLVQ"],["PSRLVD","","???"]],"???","???"], + ["???",["",["PSRAVD","",""],["PSRAVD","","PSRAVQ"],["PSRAVD","","???"]],"???","???"], + ["???",["",["PSLLVD","","PSLLVQ"],["PSLLVD","","PSLLVQ"],["PSLLVD","","???"]],"???","???"], + "???","???","???","???", + ["???",["","",["RCP14PS","","RCP14PD"],""],"???","???"], + ["???",["","",["RCP14SS","","RCP14SD"],""],"???","???"], + ["???",["","",["RSQRT14PS","","RSQRT14PD"],""],"???","???"], + ["???",["","",["RSQRT14SS","","RSQRT14SD"],""],"???","???"], + ["???",["","","",["ADDNPS","","ADDNPD"]],"???","???"], + ["???",["","","",["GMAXABSPS","","???"]],"???","???"], + ["???",["","","",["GMINPS","","GMINPD"]],"???","???"], + ["???",["","","",["GMAXPS","","GMAXPD"]],"???","???"], + "", + ["???",["","","",["FIXUPNANPS","","FIXUPNANPD"]],"???","???"], + "","", + ["???",["","PBROADCASTD",["PBROADCASTD","","???"],["PBROADCASTD","","???"]],"???","???"], + ["???",["","PBROADCASTQ",["BROADCASTI32X2","","PBROADCASTQ"],["???","","PBROADCASTQ"]],"???","???"], + ["???",["","BROADCASTI128",["BROADCASTI32X4","","BROADCASTI64X2"],["BROADCASTI32X4","","???"]],"???","???"], + ["???",["","",["BROADCASTI32X8","","BROADCASTI64X4"],["???","","BROADCASTI64X4"]],"???","???"], + ["???",["","","",["PADCD","","???"]],"???","???"], + ["???",["","","",["PADDSETCD","","???"]],"???","???"], + ["???",["","","",["PSBBD","","???"]],"???","???"], + ["???",["","","",["PSUBSETBD","","???"]],"???","???"], + "???","???","???","???", + ["???",["","",["PBLENDMD","","PBLENDMQ"],["PBLENDMD","","PBLENDMQ"]],"???","???"], + ["???",["","",["BLENDMPS","","BLENDMPD"],["BLENDMPS","","BLENDMPD"]],"???","???"], + ["???",["","",["PBLENDMB","","PBLENDMW"],""],"???","???"], + "???","???","???","???","???", + ["???",["","","",["PSUBRD","","???"]],"???","???"], + ["???",["","","",["SUBRPS","","SUBRPD"]],"???","???"], + ["???",["","","",["PSBBRD","","???"]],"???","???"], + ["???",["","","",["PSUBRSETBD","","???"]],"???","???"], + "???","???","???","???", + ["???",["","","",["PCMPLTD","","???"]],"???","???"], + ["???",["","",["PERMI2B","","PERMI2W"],""],"???","???"], + ["???",["","",["PERMI2D","","PERMI2Q"],""],"???","???"], + ["???",["","",["PERMI2PS","","PERMI2PD"],""],"???","???"], + ["???",["","PBROADCASTB",["PBROADCASTB","","???"],""],"???","???"], + ["???",["","PBROADCASTW",["PBROADCASTW","","???"],""],"???","???"], + ["???",["???",["","",["PBROADCASTB","","???"],""],"???","???"]], + ["???",["???",["","",["PBROADCASTW","","???"],""],"???","???"]], + ["???",["","",["PBROADCASTD","","PBROADCASTQ"],""],"???","???"], + ["???",["","",["PERMT2B","","PERMT2W"],""],"???","???"], + ["???",["","",["PERMT2D","","PERMT2Q"],""],"???","???"], + ["???",["","",["PERMT2PS","","PERMT2PD"],""],"???","???"], + [["???","INVEPT","???","???"],"???"], + [["???","INVVPID","???","???"],"???"], + [["???","INVPCID","???","???"],"???"], + ["???",["???","???","PMULTISHIFTQB","???"],"???","???"], + ["???",["","","",["SCALEPS","","???"]],"???","???"], + "???", + ["???",["","","",["PMULHUD","","???"]],"???","???"], + ["???",["","","",["PMULHD","","???"]],"???","???"], + ["???",["","",["EXPANDPS","","EXPANDPD"],""],"???","???"], + ["???",["","",["PEXPANDD","","PEXPANDQ"],""],"???","???"], + ["???",["","",["COMPRESSPS","","COMPRESSPD"],""],"???","???"], + ["???",["","",["PCOMPRESSD","","PCOMPRESSQ"],""],"???","???"], + "???", + ["???",["","",["PERMB","","PERMW"],""],"???","???"], + "???","???", + ["???",["",["PGATHERDD","","PGATHERDQ"],["PGATHERDD","","PGATHERDQ"],["PGATHERDD","","PGATHERDQ"]],"???","???"], + ["???",["",["PGATHERQD","","PGATHERQQ"],["PGATHERQD","","PGATHERQQ"],""],"???","???"], + ["???",["",["GATHERDPS","","GATHERDPD"],["GATHERDPS","","GATHERDPD"],["GATHERDPS","","GATHERDPD"]],"???","???"], + ["???",["",["GATHERQPS","","GATHERQPD"],["GATHERQPS","","GATHERQPD"],""],"???","???"], + "???","???", + ["???",["",["FMADDSUB132PS","","FMADDSUB132PD"],["FMADDSUB132PS","","FMADDSUB132PD"],""],"???","???"], + ["???",["",["FMSUBADD132PS","","FMSUBADD132PD"],["FMSUBADD132PS","","FMSUBADD132PD"],""],"???","???"], + ["???",["",["FMADD132PS","","FMADD132PD"],["FMADD132PS","","FMADD132PD"],["FMADD132PS","","FMADD132PD"]],"???","???"], + ["???",["",["FMADD132SS","","FMADD132SD"],["FMADD132SS","","FMADD132SD"],""],"???","???"], + ["???",["",["FMSUB132PS","","FMSUB132PD"],["FMSUB132PS","","FMSUB132PD"],["FMSUB132PS","","FMSUB132PD"]],"???","???"], + ["???",["",["FMSUB132SS","","FMSUB132SD"],["FMSUB132SS","","FMSUB132SD"],""],"???","???"], + ["???",["",["FNMADD132PS","","FNMADD132PD"],["FNMADD132PS","","FNMADD132PD"],["NMADD132PS","","FNMADD132PD"]],"???","???"], + ["???",["",["FNMADD132SS","","FNMADD132SD"],["FNMADD132SS","","FNMADD132SD"],""],"???","???"], + ["???",["",["FNMSUB132PS","","FNMSUB132PD"],["FNMSUB132PS","","FNMSUB132PD"],["FNMSUB132PS","","FNMSUB132PS"]],"???","???"], + ["???",["",["FNMSUB132SS","","FNMSUB132SD"],["FNMSUB132SS","","FNMSUB132SD"],""],"???","???"], + ["???",["","",["PSCATTERDD","","PSCATTERDQ"],["PSCATTERDD","","PSCATTERDQ"]],"???","???"], + ["???",["","",["PSCATTERQD","","PSCATTERQQ"],""],"???","???"], + ["???",["","",["SCATTERDPS","","SCATTERDPD"],["SCATTERDPS","","SCATTERDPD"]],"???","???"], + ["???",["","",["SCATTERQPS","","SCATTERQPD"],""],"???","???"], + ["???",["","","",["FMADD233PS","","???"]],"???","???"], + "???", + ["???",["",["FMADDSUB213PS","","FMADDSUB213PD"],["FMADDSUB213PS","","FMADDSUB213PD"],""],"???","???"], + ["???",["",["FMSUBADD213PS","","FMSUBADD213PD"],["FMSUBADD213PS","","FMSUBADD213PD"],""],"???","???"], + ["???",["",["FMADD213PS","","FMADD213PD"],["FMADD213PS","","FMADD213PD"],["FMADD213PS","","FMADD213PD"]],"???","???"], + ["???",["",["FMADD213SS","","FMADD213SD"],["FMADD213SS","","FMADD213SD"],""],"???","???"], + ["???",["",["FMSUB213PS","","FMSUB213PD"],["FMSUB213PS","","FMSUB213PD"],["FMSUB213PS","","FMSUB213PD"]],"???","???"], + ["???",["",["FMSUB213SS","","FMSUB213SD"],["FMSUB213SS","","FMSUB213SD"],""],"???","???"], + ["???",["",["FNMADD213PS","","FNMADD213PD"],["FNMADD213PS","","FNMADD213PD"],["FNMADD213PS","","FNMADD213PD"]],"???","???"], + ["???",["",["FNMADD213SS","","FNMADD213SD"],["FNMADD213SS","","FNMADD213SD"],""],"???","???"], + ["???",["",["FNMSUB213PS","","FNMSUB213PD"],["FNMSUB213PS","","FNMSUB213PD"],["FNMSUB213PS","","FNMSUB213PD"]],"???","???"], + ["???",["",["FNMSUB213SS","","FNMSUB213SD"],["FNMSUB213SS","","FNMSUB213SD"],""],"???","???"], + "???","???","???","???", + ["???",["","","PMADD52LUQ",["PMADD233D","","???"]],"???","???"], + ["???",["","","PMADD52HUQ",["PMADD231D","","???"]],"???","???"], + ["???",["",["FMADDSUB231PS","","FMADDSUB231PD"],["FMADDSUB231PS","","FMADDSUB231PD"],""],"???","???"], + ["???",["",["FMSUBADD231PS","","FMSUBADD231PD"],["FMSUBADD231PS","","FMSUBADD231PD"],""],"???","???"], + ["???",["",["FMADD231PS","","FMADD231PD"],["FMADD231PS","","FMADD231PD"],["FMADD231PS","","FMADD231PD"]],"???","???"], + ["???",["",["FMADD231SS","","FMADD231SD"],["FMADD231SS","","FMADD231SD"],""],"???","???"], + ["???",["",["FMSUB231PS","","FMSUB231PD"],["FMSUB231PS","","FMSUB231PD"],["FMSUB231PS","","FMSUB231PD"]],"???","???"], + ["???",["",["FMSUB231SS","","FMSUB231SD"],["FMSUB231SS","","FMSUB231SD"],""],"???","???"], + ["???",["",["FNMADD231PS","","FNMADD231PD"],["FNMADD231PS","","FNMADD231PD"],["FNMADD231PS","","FNMADD231PD"]],"???","???"], + ["???",["",["FNMADD231SS","","FNMADD231SD"],["FNMADD231SS","","FNMADD231SD"],""],"???","???"], + ["???",["",["FNMSUB231PS","","FNMSUB231PD"],["FNMSUB231PS","","FNMSUB231PD"],["FNMSUB231PS","","FNMSUB231PD"]],"???","???"], + ["???",["",["FNMSUB231SS","","FNMSUB231SD"],["FNMSUB231SS","","FNMSUB231SD"],""],"???","???"], + "???","???","???","???", + ["???",["","",["PCONFLICTD","","PCONFLICTQ"],""],"???","???"], + "???", + [ + [ + ["???",["","","",["GATHERPF0HINTDPS","","GATHERPF0HINTDPD"]],"???","???"], + ["???",["","",["GATHERPF0DPS","","GATHERPF0DPD"],["GATHERPF0DPS","",""]],"???","???"], + ["???",["","",["GATHERPF1DPS","","GATHERPF1DPD"],["GATHERPF1DPS","",""]],"???","???"], + "???", + ["???",["","","",["SCATTERPF0HINTDPS","","SCATTERPF0HINTDPD"]],"???","???"], + ["???",["","",["SCATTERPF0DPS","","SCATTERPF0DPD"],["VSCATTERPF0DPS","",""]],"???","???"], + ["???",["","",["SCATTERPF1DPS","","SCATTERPF1DPD"],["VSCATTERPF1DPS","",""]],"???","???"], + "???" + ],"???" + ], + [ + [ + "???", + ["???",["","",["GATHERPF0QPS","","GATHERPF0QPD"],""],"???","???"], + ["???",["","",["GATHERPF1QPS","","GATHERPF1QPD"],""],"???","???"], + "???","???", + ["???",["","",["SCATTERPF0QPS","","SCATTERPF0QPD"],""],"???","???"], + ["???",["","",["SCATTERPF1QPS","","SCATTERPF1QPD"],""],"???","???"], + "???" + ],"???" + ], + [["SHA1NEXTE","","",""],["","",["EXP2PS","","EXP2PD"],["EXP223PS","","???"]],"???","???"], + [["SHA1MSG1","","",""],["","","",["LOG2PS","","???"]],"???","???"], + [["SHA1MSG2","","",""],["","",["RCP28PS","","RCP28PD"],["RCP23PS","","???"]],"???","???"], + [["SHA256RNDS2","","",""],["","",["RCP28SS","","RCP28SD"],["RSQRT23PS","","???"]],"???","???"], + [["SHA256MSG1","","",""],["","",["RSQRT28PS","","RSQRT28PD"],["ADDSETSPS","","???"]],"???","???"], + [["SHA256MSG2","","",""],["","",["RSQRT28SS","","RSQRT28SD"],["PADDSETSD","","???"]],"???","???"], + "???","???", + [[["","","",["LOADUNPACKLD","","LOADUNPACKLQ"]],["","","",["PACKSTORELD","","PACKSTORELQ"]],"???","???"],"???"], + [[["","","",["LOADUNPACKLPS","","LOADUNPACKLPD"]],["","","",["PACKSTORELPS","","PACKSTORELPD"]],"???","???"],"???"], + "???","???", + [[["","","",["LOADUNPACKHD","","LOADUNPACKHQ"]],["","","",["PACKSTOREHD","","PACKSTOREHQ"]],"???","???"],"???"], + [[["","","",["LOADUNPACKHPS","","LOADUNPACKHPD"]],["","","",["PACKSTOREHPS","","PACKSTOREHPD"]],"???","???"],"???"], + "???","???","???","???","???", + ["???",["AESIMC","AESIMC","",""],"???","???"], + ["???",["AESENC","AESENC","",""],"???","???"], + ["???",["AESENCLAST","AESENCLAST","",""],"???","???"], + ["???",["AESDEC","AESDEC","",""],"???","???"], + ["???",["AESDECLAST","AESDECLAST","",""],"???","???"], + "???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???", + [ + ["MOVBE","","",""], + ["MOVBE","","",""],"???", + ["CRC32","","",""] + ], + [ + ["MOVBE","","",""], + ["MOVBE","","",""],"???", + ["CRC32","","",""] + ], + ["???",["","ANDN","",""],"???","???"], + [ + "???", + ["???",["","BLSR","",""],"???","???"], + ["???",["","BLSMSK","",""],"???","???"], + ["???",["","BLSI","",""],"???","???"], + "???","???","???","???" + ],"???", + [ + ["","BZHI","",""],"???", + ["","PEXT","",""], + ["","PDEP","",""] + ], + [ + "???", + ["ADCX","","",""], + ["ADOX","","",""], + ["","MULX","",""] + ], + [ + ["","BEXTR","",""], + ["","SHLX","",""], + ["","SARX","",""], + ["","SHRX","",""] + ], + "???","???","???","???","???","???","???","???", + /*------------------------------------------------------------------------------------------------------------------------ + Three Byte operations 0F38. Opcodes plus 768 goes to 767 used by escape codes "0F, 3A", Or + set directly by adding map bits "11" because "11 00000000" bin = 768 plus opcode. + ------------------------------------------------------------------------------------------------------------------------*/ + ["???",["","PERMQ","PERMQ",""],"???","???"], + ["???",["","PERMPD","PERMPD",""],"???","???"], + ["???",["",["PBLENDD","",""],"",""],"???","???"], + ["???",["","",["ALIGND","","ALIGNQ"],["ALIGND","","???"]],"???","???"], + ["???",["","PERMILPS",["PERMILPS","","???"],""],"???","???"], + ["???",["","PERMILPD","PERMILPD",""],"???","???"], + ["???",["","PERM2F128","",""],"???","???"], + ["???",["","","",["PERMF32X4","","???"]],"???","???"], + ["???",["ROUNDPS","ROUNDPS",["RNDSCALEPS","","???"],""],"???","???"], + ["???",["ROUNDPD","ROUNDPD","RNDSCALEPD",""],"???","???"], + ["???",["ROUNDSS","ROUNDSS",["RNDSCALESS","","???"],""],"???","???"], + ["???",["ROUNDSD","ROUNDSD","RNDSCALESD",""],"???","???"], + ["???",["BLENDPS","BLENDPS","",""],"???","???"], + ["???",["BLENDPD","BLENDPD","",""],"???","???"], + ["???",["PBLENDW","PBLENDW","",""],"???","???"], + [["PALIGNR","","",""],"PALIGNR","???","???"], + "???","???","???","???", + [["???","PEXTRB","???","???"],["???","PEXTRB","???","???"]], + [["???","PEXTRW","???","???"],["???","PEXTRW","???","???"]], + ["???",["PEXTRD","","PEXTRQ"],"???","???"], + ["???","EXTRACTPS","???","???"], + ["???",["","INSERTF128",["INSERTF32X4","","INSERTF64X2"],""],"???","???"], + ["???",["","EXTRACTF128",["EXTRACTF32X4","","EXTRACTF64X2"],""],"???","???"], + ["???",["","",["INSERTF32X8","","INSERTF64X4"],""],"???","???"], + ["???",["","",["EXTRACTF32X8","","EXTRACTF64X4"],""],"???","???"], + "???", + ["???",["","CVTPS2PH",["CVTPS2PH","","???"],""],"???","???"], + ["???",["","",["PCMP,UD,","","PCMP,UQ,"],["PCMP,UD,","","???"]],"???","???"], + ["???",["","",["PCM,PD,","","PCM,PQ,"],["PCM,PD,","","???"]],"???","???"], + ["???","PINSRB","???","???"], + ["???",["INSERTPS","","???"],"???","???"], + ["???",["",["PINSRD","","PINSRQ"],["PINSRD","","PINSRQ"],""],"???","???"], + ["???",["","",["SHUFF32X4","","SHUFF64X2"],""],"???","???"], + "???", + ["???",["","",["PTERNLOGD","","PTERNLOGQ"],""],"???","???"], + ["???",["","",["GETMANTPS","","GETMANTPD"],["GETMANTPS","","GETMANTPD"]],"???","???"], + ["???",["","",["GETMANTSS","","GETMANTSD"],""],"???","???"], + "???","???","???","???","???","???","???","???", + ["???",["",["KSHIFTRB","","KSHIFTRW"],"",""],"???","???"], + ["???",["",["KSHIFTRD","","KSHIFTRQ"],"",""],"???","???"], + ["???",["",["KSHIFTLB","","KSHIFTLW"],"",""],"???","???"], + ["???",["",["KSHIFTLD","","KSHIFTLQ"],"",""],"???","???"], + "???","???","???","???", + ["???",["","INSERTI128",["INSERTI32X4","","INSERTI64X2"],""],"???","???"], + ["???",["","EXTRACTI128",["EXTRACTI32X4","","EXTRACTI64X2"],""],"???","???"], + ["???",["","",["INSERTI32X8","","INSERTI64X4"],""],"???","???"], + ["???",["","",["EXTRACTI32X8","","EXTRACTI64X4"],""],"???","???"], + "???","???", + ["???",["","KEXTRACT",["PCMP,UB,","","PCMP,UW,"],""],"???","???"], + ["???",["","",["PCM,PB,","","PCM,PW,"],""],"???","???"], + ["???",["DPPS","DPPS","",""],"???","???"], + ["???",["DPPD","DPPD","",""],"???","???"], + ["???",["MPSADBW","MPSADBW",["DBPSADBW","","???"],""],"???","???"], + ["???",["","",["SHUFI32X4","","SHUFI64X2"],""],"???","???"], + ["???",["PCLMULQDQ","PCLMULQDQ","",""],"???","???"], + "???", + ["???",["","PERM2I128","",""],"???","???"], + "???", + ["???",["",["PERMIL2PS","","PERMIL2PS"],"",""],"???","???"], + ["???",["",["PERMIL2PD","","PERMIL2PD"],"",""],"???","???"], + ["???",["","BLENDVPS","",""],"???","???"], + ["???",["","BLENDVPD","",""],"???","???"], + ["???",["","PBLENDVB","",""],"???","???"], + "???","???","???", + ["???",["","",["RANGEPS","","RANGEPD"],""],"???","???"], + ["???",["","",["RANGESS","","RANGESD"],""],"???","???"], + ["???",["","","",["RNDFXPNTPS","","RNDFXPNTPD"]],"???","???"], + "???", + ["???",["","",["FIXUPIMMPS","","FIXUPIMMPD"],""],"???","???"], + ["???",["","",["FIXUPIMMSS","","FIXUPIMMSD"],""],"???","???"], + ["???",["","",["REDUCEPS","","REDUCEPD"],""],"???","???"], + ["???",["","",["REDUCESS","","REDUCESD"],""],"???","???"], + "???","???","???","???", + ["???",["",["FMADDSUBPS","","FMADDSUBPS"],"",""],"???","???"], + ["???",["",["FMADDSUBPD","","FMADDSUBPD"],"",""],"???","???"], + ["???",["",["FMSUBADDPS","","FMSUBADDPS"],"",""],"???","???"], + ["???",["",["FMSUBADDPD","","FMSUBADDPD"],"",""],"???","???"], + ["???",["PCMPESTRM","PCMPESTRM","",""],"???","???"], + ["???",["PCMPESTRI","PCMPESTRI","",""],"???","???"], + ["???",["PCMPISTRM","PCMPISTRM","",""],"???","???"], + ["???",["PCMPISTRI","PCMPISTRI","",""],"???","???"], + "???","???", + ["???",["","",["FPCLASSPS","","FPCLASSPD"],""],"???","???"], + ["???",["","",["FPCLASSSS","","FPCLASSSD"],""],"???","???"], + ["???",["",["FMADDPS","","FMADDPS"],"",""],"???","???"], + ["???",["",["FMADDPD","","FMADDPD"],"",""],"???","???"], + ["???",["",["FMADDSS","","FMADDSS"],"",""],"???","???"], + ["???",["",["FMADDSD","","FMADDSD"],"",""],"???","???"], + ["???",["",["FMSUBPS","","FMSUBPS"],"",""],"???","???"], + ["???",["",["FMSUBPD","","FMSUBPD"],"",""],"???","???"], + ["???",["",["FMSUBSS","","FMSUBSS"],"",""],"???","???"], + ["???",["",["FMSUBSD","","FMSUBSD"],"",""],"???","???"], + "???","???","???","???","???","???","???","???", + ["???",["",["FNMADDPS","","FNMADDPS"],"",""],"???","???"], + ["???",["",["FNMADDPD","","FNMADDPD"],"",""],"???","???"], + ["???",["",["FNMADDSS","","FNMADDSS"],"",""],"???","???"], + ["???",["",["FNMADDSD","","FNMADDSD"],"",""],"???","???"], + ["???",["",["FNMSUBPS","","FNMSUBPS"],"",""],"???","???"], + ["???",["",["FNMSUBPD","","FNMSUBPD"],"",""],"???","???"], + ["???",["",["FNMSUBSS","","FNMSUBSS"],"",""],"???","???"], + ["???",["",["FNMSUBSD","","FNMSUBSD"],"",""],"???","???"], + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???", + [["","","","CVTFXPNTUDQ2PS"],["","","",["CVTFXPNTPS2UDQ","","???"]],"???",["","","","CVTFXPNTPD2UDQ"]], + [["","","","CVTFXPNTDQ2PS"],["","","",["CVTFXPNTPS2DQ","","???"]],"???","???"], + "SHA1RNDS4", + "???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + ["???",["AESKEYGENASSIST","AESKEYGENASSIST","",""],"???","???"], + "???","???","???","???","???","???", + ["???","???","???",["","","","CVTFXPNTPD2DQ"]], + "???","???","???","???","???","???","???","???","???", + ["???","???","???",["","RORX","",""]], + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + /*------------------------------------------------------------------------------------------------------------------------ + AMD XOP 8. + ------------------------------------------------------------------------------------------------------------------------*/ + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "VPMACSSWW","VPMACSSWD","VPMACSSDQL","???","???","???","???","???","???", + "VPMACSSDD","VPMACSSDQH","???","???","???","???","???","VPMACSWW","VPMACSWD","VPMACSDQL", + "???","???","???","???","???","???","VPMACSDD","VPMACSDQH", + "???","???",["VPCMOV","","VPCMOV"],["VPPERM","","VPPERM"],"???","???","VPMADCSSWD", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "VPMADCSWD","???","???","???","???","???","???","???","???","???", + "VPROTB","VPROTW","VPROTD","VPROTQ","???","???","???","???","???","???","???","???", + "VPCOM,B,","VPCOM,W,","VPCOM,D,","VPCOM,Q,","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "VPCOM,UB,","VPCOM,UW,","VPCOM,UD,","VPCOM,UQ,", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + /*------------------------------------------------------------------------------------------------------------------------ + AMD XOP 9. + ------------------------------------------------------------------------------------------------------------------------*/ + "???", + ["???","BLCFILL","BLSFILL","BLCS","TZMSK","BLCIC","BLSIC","T1MSKC"],["???","BLCMSK","???","???","???","???","BLCI","???"], + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + ["???",["LLWPCB","SLWPCB","???","???","???","???","???","???"]], + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "VFRCZPS","VFRCZPD","VFRCZSS","VFRCZSD","???","???","???","???","???","???","???","???","???","???","???","???", + ["VPROTB","","VPROTB"],["VPROTW","","VPROTW"],["VPROTD","","VPROTD"],["VPROTQ","","VPROTQ"], + ["VPSHLB","","VPSHLB"],["VPSHLW","","VPSHLW"],["VPSHLD","","VPSHLD"],["VPSHLQ","","VPSHLQ"], + ["VPSHAB","","VPSHAB"],["VPSHAW","","VPSHAW"],["VPSHAD","","VPSHAD"],["VPSHAQ","","VPSHAQ"], + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "VPHADDBW","VPHADDBD","VPHADDBQ","???","???","VPHADDWD","VPHADDWQ","???","???","???","VPHADDDQ","???","???","???","???","???", + "VPHADDUBWD","VPHADDUBD","VPHADDUBQ","???","???","VPHADDUWD","VPHADDUWQ","???","???","???","VPHADDUDQ","???","???","???","???","???", + "VPHSUBBW","VPHSUBWD","VPHSUBDQ","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???", + /*------------------------------------------------------------------------------------------------------------------------ + AMD XOP A. + ------------------------------------------------------------------------------------------------------------------------*/ + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "BEXTR","???",["LWPINS","LWPVAL","???","???","???","???","???","???"], + "???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + /*------------------------------------------------------------------------------------------------------------------------- + L1OM Vector. + -------------------------------------------------------------------------------------------------------------------------*/ + "???","???","???","???","DELAY","???","???","???","???","???","???","???","???","???","???","???", + [["VLOADD","VLOADQ","",""],"???"],"???", + [["VLOADUNPACKLD","VLOADUNPACKLQ","",""],"???"], + [["VLOADUNPACKHD","VLOADUNPACKHQ","",""],"???"], + [["VSTORED","VSTOREQ","",""],"???"],"???", + [["VPACKSTORELD","VPACKSTORELQ","",""],"???"], + [["VPACKSTOREHD","VPACKSTOREHQ","",""],"???"], + ["VGATHERD","???"],["VGATHERPFD","???"],"???",["VGATHERPF2D","???"], + ["VSCATTERD","???"],["VSCATTERPFD","???"],"???",["VSCATTERPF2D","???"], + ["VCMP,PS,","VCMP,PD,","",""],"VCMP,PI,","VCMP,PU,","???", + ["VCMP,PS,","VCMP,PD,","",""],"VCMP,PI,","VCMP,PU,","???", + "???","???","???","???","???","???","???","???", + "VTESTPI","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + ["VADDPS","VADDPD","",""],"VADDPI","???","VADDSETCPI","???","VADCPI","VADDSETSPS","VADDSETSPI", + ["VADDNPS","VADDNPD","",""],"???","???","???","???","???","???","???", + ["VSUBPS","VSUBPD","",""],"VSUBPI","???","VSUBSETBPI","???","VSBBPI","???","???", + ["VSUBRPS","VSUBRPD","",""],"VSUBRPI","???","VSUBRSETBPI","???","VSBBRPI","???","???", + ["VMADD231PS","VMADD231PD","",""],"VMADD231PI", + ["VMADD213PS","VMADD213PD","",""],"???", + ["VMADD132PS","VMADD132PD","",""],"???", + "VMADD233PS","VMADD233PI", + ["VMSUB231PS","VMSUB231PD","",""],"???", + ["VMSUB213PS","VMSUB213PD","",""],"???", + ["VMSUB132PS","VMSUB132PD","",""],"???","???","???", + ["VMADDN231PS","VMADDN231PD","",""],"???", + ["VMADDN213PS","VMADDN213PD","",""],"???", + ["VMADDN132PS","VMADDN132PD","",""],"???","???","???", + ["VMSUBR231PS","VMSUBR231PD","",""],"???", + ["VMSUBR213PS","VMSUBR213PD","",""],"???", + ["VMSUBR132PS","VMSUBR132PD","",""],"???", + ["VMSUBR23C1PS","VMSUBR23C1PD","",""],"???", + ["VMULPS","VMULPD","",""],"VMULHPI","VMULHPU","VMULLPI","???","???","VCLAMPZPS","VCLAMPZPI", + ["VMAXPS","VMAXPD","",""],"VMAXPI","VMAXPU","???", + ["VMINPS","VMINPD","",""],"VMINPI","VMINPU","???", + ["???","VCVT,PD2PS,","",""],["VCVTPS2PI","VCVT,PD2PI,","",""],["VCVTPS2PU","VCVT,PD2PU,","",""],"???", + ["???","VCVT,PS2PD,","",""],["VCVTPI2PS","VCVT,PI2PD,","",""],["VCVTPU2PS","VCVT,PU2PD,","",""],"???", + "VROUNDPS","???","VCVTINSPS2U10","VCVTINSPS2F11","???","VCVTPS2SRGB8","VMAXABSPS","???", + "VSLLPI","VSRAPI","VSRLPI","???", + ["VANDNPI","VANDNPQ","",""],["VANDPI","VANDPQ","",""], + ["VORPI","VORPQ","",""],["VXORPI","VXORPQ","",""], + "VBINTINTERLEAVE11PI","VBINTINTERLEAVE21PI","???","???","???","???","???","???", + "VEXP2LUTPS","VLOG2LUTPS","VRSQRTLUTPS","???","VGETEXPPS","???","???","???", + "VSCALEPS","???","???","???","???","???","???","???", + "VRCPRESPS","???","VRCPREFINEPS","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "VFIXUPPS","VSHUF128X32","VINSERTFIELDPI","VROTATEFIELDPI","???","???","???","???", + "???","???","???","???","???","???","???","???", + /*------------------------------------------------------------------------------------------------------------------------- + L1OM Mask, Mem, and bit opcodes. + -------------------------------------------------------------------------------------------------------------------------*/ + ["???","BSFI"],["???","BSFI"],["???","BSFI"],["???","BSFI"], + ["???","BSRI"],["???","BSRI"],["???","BSRI"],["???","BSRI"], + ["???","BSFF"],["???","BSFF"],["???","BSFF"],["???","BSFF"], + ["???","BSRF"],["???","BSRF"],["???","BSRF"],["???","BSRF"], + ["???","BITINTERLEAVE11"],["???","BITINTERLEAVE11"],["???","BITINTERLEAVE11"],["???","BITINTERLEAVE11"], + ["???","BITINTERLEAVE21"],["???","BITINTERLEAVE21"],["???","BITINTERLEAVE21"],["???","BITINTERLEAVE21"], + ["???","INSERTFIELD"],["???","INSERTFIELD"],["???","INSERTFIELD"],["???","INSERTFIELD"], + ["???","ROTATEFIELD"],["???","ROTATEFIELD"],["???","ROTATEFIELD"],["???","ROTATEFIELD"], + ["???","COUNTBITS"],["???","COUNTBITS"],["???","COUNTBITS"],["???","COUNTBITS"], + ["???","QUADMASK16"],["???","QUADMASK16"],["???","QUADMASK16"],["???","QUADMASK16"], + "???","???","???","???", + "VKMOVLHB", + [["CLEVICT1","CLEVICT2","LDVXCSR","STVXCSR","???","???","???","???"],"???"], + [["VPREFETCH1","VPREFETCH2","???","???","???","???","???","???"],"???"], + [["VPREFETCH1","VPREFETCH2","???","???","???","???","???","???"],"???"], + "VKMOV","VKMOV","VKMOV","VKMOV", + "VKNOT","VKANDNR","VKANDN","VKAND", + "VKXNOR","VKXOR","VKORTEST","VKOR", + "???","VKSWAPB", + ["???",["DELAY","SPFLT","???","???","???","???","???","???"]], + ["???",["DELAY","SPFLT","???","???","???","???","???","???"]] +]; + +/*------------------------------------------------------------------------------------------------------------------------- +The Operand type array each operation code can use different operands that must be decoded after the select Opcode. +Basically some instruction may use the ModR/M talked about above while some may use an Immediate, or Both. +An Immediate input uses the byte after the opcode as a number some instructions combine a number and an ModR/M address selection +By using two bytes for each encoding after the opcode. X86 uses very few operand types for input selections to instructions, but +there are many useful combinations. The order the operands are "displayed" is the order they are in the Operands string for the +operation code. +--------------------------------------------------------------------------------------------------------------------------- +The first 2 digits is the selected operand type, and for if the operand can change size. Again more opcodes where sacrificed +to make this an setting Opcode "66" goes 16 bit this is explained in detail in the SizeAttrSelect variable section that is +adjusted by the function ^DecodePrefixAdjustments()^. The Variable SizeAttrSelect effects all operand formats that are decoded by +different functions except for single size. Don't forget X86 uses very few operand types in which different prefix adjustments +are used to add extra functionality to each operand type. The next two numbers is the operands size settings. +If the operand number is set to the operand version that can not change size then the next two numbers act as a single size for +faster decoding. Single size is also used to select numbers that are higher than the max size to select special registers that +are used by some instructions like Debug Registers. +--------------------------------------------------------------------------------------------------------------------------- +Registers have 8, 16, 32, 64, 128, 256, 512 names. The selected ModR/M address location uses a pointer name that shows it's select +size then it's location in left, and right brackets like "QWORD PTR[Address]". The pointer name changes by sizes 8, 16, 64, 128, 256, 512. +--------------------------------------------------------------------------------------------------------------------------- +Used by function ^DecodeOpcode()^ after ^DecodePrefixAdjustments()^. +-------------------------------------------------------------------------------------------------------------------------*/ + +const Operands = [ + //------------------------------------------------------------------------------------------------------------------------ + //First Byte operations. + //------------------------------------------------------------------------------------------------------------------------ + "06000A000003","070E0B0E0003","0A0006000003","0B0E070E0003","16000C000003","170E0DE60003","","", + "06000A000003","070E0B0E0003","0A0006000003","0B0E070E0003","16000C000003","170E0DE60003","","", + "06000A000003","070E0B0E0003","0A0006000003","0B0E070E0003","16000C000003","170E0DE60003","","", + "06000A000003","070E0B0E0003","0A0006000003","0B0E070E0003","16000C000003","170E0DE60003","","", + "06000A000003","070E0B0E0003","0A0006000003","0B0E070E0003","16000C000003","170E0DE60003","","", + "06000A000003","070E0B0E0003","0A0006000003","0B0E070E0003","16000C000003","170E0DE60003","","", + "06000A000003","070E0B0E0003","0A0006000003","0B0E070E0003","16000C000003","170E0DE60003","","", + "06000A00","070E0B0E","0A000600","0B0E070E","16000C00","170E0DE6","","", + "03060003","03060003","03060003","03060003","03060003","03060003","03060003","03060003", + "03060003","03060003","03060003","03060003","03060003","03060003","03060003","03060003", + "030A","030A","030A","030A","030A","030A","030A","030A", + "030A","030A","030A","030A","030A","030A","030A","030A", + ["","",""],["","",""], + ["0A020606","0A010604",""], + "0B0E0704", + "","","","", + "0DE6","0B0E070E0DE6", + "0DA1","0B0E070E0DE1", + "22001A01","230E1A01","1A012000","1A01210E", + "10000002000C","10000002000C","10000002000C","10000002000C","10000002000C","10000002000C","10000002000C","10000002000C", + "10000002000C","10000002000C","10000002000C","10000002000C","10000002000C","10000002000C","10000002000C","10000002000C", + ["06000C000003","06000C000003","06000C000003","06000C000003","06000C000003","06000C000003","06000C000003","06000C00"], + ["070E0DE60003","070E0DE60003","070E0DE60003","070E0DE60003","070E0DE60003","070E0DE60003","070E0DE60003","070E0DE6"], + ["06000C000003","06000C000003","06000C000003","06000C000003","06000C000003","06000C000003","06000C000003","06000C00"], + ["070E0DE10003","070E0DE10003","070E0DE10003","070E0DE10003","070E0DE10003","070E0DE10003","070E0DE10003","070E0DE1"], + "06000A00","070E0B0E", + "0A0006000003","0B0E070E0003", + "06000A000001","070E0B0E0001", + "0A0006000001","0B0E070E0001", + "06020A080001", + ["0B0E0601",""], + "0A0806020001", + ["070A","","","","","","",""], + [["","","",""],["","","",""],["","","",""],["","","",""]], + "170E030E0003","170E030E0003","170E030E0003","170E030E0003","170E030E0003","170E030E0003","170E030E0003", + ["","",""],["","",""], + "0D060C01", //CALL Ap (w:z). + "", + ["","",""],["","",""], + "","", + "160004000001","170E050E0001", + "040016000001","050E170E0001", + "22002000","230E210E", + "22002000","230E210E", + "16000C00","170E0DE6", + "22001600","230E170E","16002000","170E210E","16002200","170E230E", + "02000C000001","02000C000001","02000C000001","02000C000001","02000C000001","02000C000001","02000C000001","02000C000001", + "030E0D0E0001","030E0D0E0001","030E0D0E0001","030E0D0E0001","030E0D0E0001","030E0D0E0001","030E0D0E0001","030E0D0E0001", + ["06000C00","06000C00","06000C00","06000C00","06000C00","06000C00","06000C00","06000C00"], + ["070E0C00","070E0C00","070E0C00","070E0C00","070E0C00","070E0C00","070E0C00","070E0C00"], + "0C010008","0008", + "0B060906","0B060906", + [ + "06000C000001","","","","","","", + ["0C00","0C00","0C00","0C00","0C00","0C00","0C00","0C00"] + ], + [ + "070E0D060001","","","","","","", + ["1002","1002","1002","1002","1002","1002","1002","1002"] + ], + "0C010C00","", + "0C01","","2C00", + "0C00","", + ["","",""], + ["06002A00","06002A00","06002A00","06002A00","06002A00","06002A00","06002A00","06002A00"], + ["070E2A00","070E2A00","070E2A00","070E2A00","070E2A00","070E2A00","070E2A00","070E2A00"], + ["06001800","06001800","06001800","06001800","06001800","06001800","06001800","06001800"], + ["070E1800","070E1800","070E1800","070E1800","070E1800","070E1800","070E1800","070E1800"], + "0C00","0C00","", + "1E00", + /*------------------------------------------------------------------------------------------------------------------------ + X87 FPU. + ------------------------------------------------------------------------------------------------------------------------*/ + [ + ["0604","0604","0604","0604","0604","0604","0604","0604"], + ["24080609","24080609","0609","0609","24080609","24080609","24080609","24080609"] + ], + [ + ["0604","","0604","0604","0601","0602","0601","0602"], + [ + "0609","0609", + ["","","","","","","",""], + "0609", + ["","","","","","","",""], + ["","","","","","","",""], + ["","","","","","","",""], + ["","","","","","","",""] + ] + ], + [ + ["0604","0604","0604","0604","0604","0604","0604","0604"], + [ + "24080609","24080609","24080609","24080609","", + ["","","","","","","",""],"","" + ] + ], + [ + ["0604","0604","0604","0604","","0607","","0607",""], + [ + "24080609","24080609","24080609","24080609", + ["","","","","","","",""], + "24080609","24080609","" + ] + ], + [ + ["0606","0606","0606","0606","0606","0606","0606","0606"], + ["06092408","06092408","0609","0609","06092408","06092408","06092408","06092408"] + ], + [ + ["0606","0606","0606","0606","0606","","0601","0602"], + ["0609","0609","0609","0609","0609","0609","",""] + ], + [ + ["0602","0602","0602","0602","0602","0602","0602","0602"], + [ + "06092408","06092408","0609", + ["","","","","","","",""], + "06092408","06092408","06092408","06092408" + ] + ], + [ + ["0602","0602","0602","0602","0607","0606","0607","0606"], + [ + "0609","0609","0609","0609", + ["1601","","","","","","",""], + "24080609","24080609", + "" + ] + ], + /*------------------------------------------------------------------------------------------------------------------------ + End of X87 FPU. + ------------------------------------------------------------------------------------------------------------------------*/ + "10000004","10000004","10000004","10000004", + "16000C00","170E0C00","0C001600","0C00170E", + "110E0008", + "110E0008", + "0D060C01", //JMP Ap (w:z). + "100000040004", + "16001A01","170E1A01", + "1A011600","1A01170E", + "","","","","","", + ["06000C00","","06000003","06000003","16000600","0600","16000600","0600"], + ["070E0D06","","070E0003","070E0003","170E070E","070E","170E070E","170E070E"], + "","","","","","", + ["06000003","06000003","","","","","",""], + [ + ["070E0003","070E0003","070A0004","090E0008","070A0008","090E0008","070A",""], + ["070E0003","070E0003","070A0008","","070A0008","","070A",""] + ], + /*------------------------------------------------------------------------------------------------------------------------ + Two Byte operations. + ------------------------------------------------------------------------------------------------------------------------*/ + [ + ["0602","0602","0602","0602","0602","0602","070E",""], + ["070E","070E","0601","0601","0601","0601","070E",""] + ], + [ + ["0908","0908","0908","0908","0602","","0602","0601"], + [ + ["","","","","","","",""], + ["170819081B08","17081908","","","","","",""], + ["","","","","","","",""], + ["1708","","1708","1708","","","1602","17081802"], + "070E","","0601", + ["","","170819081B08","170819081B08","","","",""] + ] + ], + ["0B0E0612","0B0E070E"],["0B0E0612","0B0E070E"],"", + "","","","", + "","","","", + [["0601","0601","","","","","",""],""], + "", + "0A0A06A9", //3DNow takes ModR/M, IMM8. + [ + ["0B700770","0B700770","0A040603","0A040609"], + ["0B700770","0B700770","0A0412040604","0A0412040604"] + ], + [ + ["07700B70","07700B70","06030A04","06090A04"], + ["07700B70","07700B70","060412040A04","060412040A04"] + ], + [ + ["0A0412040606","0A0412040606","0B700770","0B700768"], + ["0A0412040604","","0B700770","0B700770"] + ], + [["06060A04","06060A04","",""],""], + ["0B70137007700140","0B70137007700140","",""], + ["0B70137007700140","0B70137007700140","",""], + [["0A0412040606","0A0412040606","0B700770",""],["0A0412040604","","0B700770",""]], + [["06060A04","06060A04","",""],""], + [["0601","0601","0601","0601","","","",""],""], + "", + [[["0A0B07080180","","",""],["0A0B07100180","","",""],["0A0B07080180","","",""],["0A0B07080180","","",""]], + ["",["0A0B060B","","",""],["0A0B07080180","","",""],["0A0B07080180","","",""]]], + [[["07080A0B0180","","",""],["07100A0B0180","","",""],["0A0B07080180","","",""],["0A0B07080180","","",""]], + ["",["0A0B060B","","",""],"",["0A0B07080180","","",""]]], + "","","", + "070E", + ["","07080A0C0001"],["","07080A0D0001"], + ["","0A0C07080001"],["","0A0D07080001"], + ["","07080A0E0001"],"", + ["","0A0E07080001"],"", + [ + ["0A040648","0B300730","0B700770","0A06066C0130"], + ["0A040648","0B300730","0B700770","0A06066C0130"], + "","" + ], + [ + [ + ["06480A04","07300B30","07700B70","066C0A060130"], + ["06480A04","07300B30","07700B70","066C0A060130"], + ["","","",["066C0A060138","066C0A060138","066C0A060138"]], + ["","","",["066C0A060138","066C0A060138","066C0A060138"]] + ], + [ + ["06480A04","07300B30","07700B70","066C0A06"], + ["06480A04","07300B30","07700B70","066C0A06"], + "","" + ] + ], + [ + ["0A0406A9","","",""],["0A0406A9","","",""], //Not Allowed to be Vector encoded. + "0A041204070C010A","0A041204070C010A" + ], + [ + [ + "07700B70","07700B70", + ["06030A04","","",""],["06060A04","","",""] //SSE4a can not be vector encoded. + ],"" + ], + [ + ["0A0A0649","","",""],["0A0A0648","","",""], //Not allowed to be Vector encoded. + "0B0C06430109","0B0C06490109" + ], + [ + ["0A0A0649","","",""],["0A0A0648","","",""], //Not allowed to be vector encoded. + "0B0C0643010A","0B0C0649010A" + ], + ["0A0406430101","0A0406490101","",""], + ["0A0406430101","0A0406490101","",""], + "","","","", + "","","", + "", + "",//Three byte opcodes 0F38 + "", + "",//Three byte opcodes 0F3A + "","","","","", + "0B0E070E", + [ + ["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""], + ["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""],"","" + ], + [ + ["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""], + ["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""],"","" + ], + [["0B0E070E0180","0A0F06FF","",""],"","",""], + [ + ["0B0E070E0180",["0A0F06FF","","0A0F06FF"],"",""], + ["0B0E070E0180",["0A0F06FF","","0A0F06FF"],"",""],"","" + ], + [ + ["0A02070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""], + ["0A02070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""],"","" + ], + [ + ["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""], + ["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""],"","" + ], + [ + ["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""], + ["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""],"","" + ], + [["0B0E070E0180","0A0F06FF","",""],"","",""], + [["0B0E070E0180","0A0F06FF","",""],"","",""], + [ + ["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""], + ["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""],"","" + ], + [ + ["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""], + ["0B0E070E0180",["0A0F120F06FF","",""],"",""],"","" + ], + "0B0E070E","0B0E070E","0B0E070E","0B0E070E", + ["",[["0B0C0648","0B0C0730","",""],["0B0C0648","0B0C0730","",""],"",""]], + ["0B7007700142","0B7007700142","0A04120406430102","0A04120406490102"], + [ + ["0A040648","0A040648","",""],"", + ["0A040643","0A0412040643","",""],"" + ], + [ + ["0A040648","0A040648","",""],"", + ["0A040643","0A0412040643","",""],"" + ], + ["0B70137007700140","0B70137007700140","",""], + ["0B70137007700140","0B70137007700140","",""], + ["0B70137007700140","0B70137007700140","",""], + ["0B70137007700140","0B70137007700140","",""], + [ + ["0A040648","0B3013300730","0B70137007700152","0A061206066C0152"], + ["0A040648","0B3013300730","0B70137007700152","0A061206066C0152"], + "0A04120406430102","0A04120406460102" + ], + [ + ["0A040648","0B3013300730","0B70137007700152","0A061206066C0152"], + ["0A040648","0B3013300730","0B70137007700152","0A061206066C0152"], + "0A04120406430102","0A04120406460102" + ], + [ + ["0A040648","0B300718","0B7007380151","0A06065A0171"], + ["0A040648","0B180730","0B3807700152","0A05066C0152"], + "0A04120406430101","0A04120406460102" + ], + [["0B7007700142","","0B380770014A"],["0B700770014A","",""],"0B7007700141",""], + [ + ["0A040648","0B3013300730","0B70137007700152","0A061206066C0152"], + ["0A040648","0B3013300730","0B70137007700152","0A061206066C0152"], + "0A04120406430102","0A04120406460102" + ], + ["0B70137007700141","0B70137007700141","0A04120406430101","0A04120406460101"], + ["0B70137007700142","0B70137007700142","0A04120406430102","0A04120406460102"], + ["0B70137007700141","0B70137007700141","0A04120406430101","0A04120406460101"], + [["0A0A06A3","","",""],"0B70137007700108","",""], + [["0A0A06A3","","",""],"0B70137007700108","",""], + [["0A0A06A3","","",""],"0B701370077001400108","",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],["0A040648","0B3013300730","0A0F137007700108",""],"",""], + [["0A0A06A9","","",""],["0A040648","0B3013300730","0A0F137007700108",""],"",""], + [["0A0A06A9","","",""],["0A040648","0B3013300730",["0A0F137007700148","",""],["0A0F1206066C0148","",""]],"",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],["0B70137007700148","",""],"",""], + [["0A0A06A9","","",""],["0B70137007700148","",""],"",""], + ["","0B70137007700140","",""], + ["","0B70137007700140","",""], + [["0A0A070C","","",""],["0A04070C0108","","0A04070C0108"],"",""], + [ + [ + ["0A0A06A9","", "",""], + ["0B700770","0B700770",["0B7007700108","","0B700770"],["0A06066C0128","","0A06066C0120"]], + ["0A040710","0B700770",["0B700770","","0B7007700108"],""], + ["","",["0B7007700108","","0B700770"],""] + ], + [ + ["0A0A06A9","", "",""], + ["0B700770","0B700770",["0B7007700108","","0B700770"],["0A06066C0148","","0A06066C0140"]], + ["0A040710","0B700770",["0B700770","","0B7007700108"],""], + ["","",["0B7007700108","","0B700770"],""] + ] + ], + [ + ["0A0A06A90C00","","",""], + ["0A0406480C00","0B3007300C00",["0B7007700C000108","",""],["0A06066C0C000108","",""]], + "0B7007700C000108", + "0B7007700C000108" + ], + [ + "", + [ + "","", + [["060A0C00","","",""],"137007700C000108","",""],"", + [["060A0C00","","",""],"137007700C000108","",""],"", + [["060A0C00","","",""],"137007700C000108","",""],"" + ] + ], + [ + ["",["","",["137007700C000148","","137007700C000140"],""],"",""], + ["",["","",["137007700C000148","","137007700C000140"],""],"",""], + [["060A0C00","","",""],["06480C00","133007300C00",["137007700C000148","",""],["1206066C0C000148","",""]],"",""], + "", + [["060A0C00","","",""],["06480C00","133007300C00",["137007700C000148","","137007700C000140"],["1206066C0C000148","",""]],"",""], + "", + [["060A0C00","","",""],["06480C00","133007300C00",["137007700C000148","",""],["1206066C0C000148","",""]],"",""], + "" + ], + [ + "", + [ + "","", + [["137007700C00","137007700C00","",""],"137007700C000140","",""],["","137007700C000108","",""], + "","", + [["137007700C00","137007700C00","",""],"137007100C000140","",""],["","137007700C000108","",""] + ] + ], + [["0A0A06A9","","",""],["0A040710","13300B300730","0A0F137007700108",""],"",""], + [["0A0A06A9","","",""],["0A040710","13300B300730","0A0F137007700108",""],"",""], + [["0A0A06A9","","",""],["0A040710","13300B300730",["0A0F137007700148","",""],["0A0F1206066C0148","",""]],"",""], + [["",["","",""],"",""],"","",""], + [ + ["07080B080180","",["0B7007700141","","0B3807700149"],""], + ["064F0C000C00","",["0B7007380149","","0B7007700141"],""], + ["","","0B0C06440109",""], + ["0A04064F0C000C00","","0B0C06460109",""] + ], + [ + ["0B0807080180","",["0B7007700142","","0B380770014A"],""], + ["0A04064F","",["0B700738014A","","0B7007700142"],""], + ["","","0B0C0644010A",""], + ["0A04064F","","0B0C0646010A",""] + ], + [ + "", + ["","",["0B7007380149","","0B7007700141"],""], + ["","",["0B7007380142","","0B700770014A"],"0A06065A0170"], + ["","",["0B700770014A","","0B3807700142"],""] + ], + [ + "", + ["","",["0B700738014A","","0B7007700142"],""], + ["","","0A041204070C010A",""], + ["","","0A041204070C010A",""] + ], + [ + "",["0A040604","0B7013700770","",""], + "",["0A040604","0B7013700770","",""] + ], + [ + "",["0A040604","0B7013700770","",""], + "",["0A040604","0B7013700770","",""] + ], + [["070C0A0A","","",""],["06240A040108","","06360A040108"],["0A040646","0A040646",["","","0A0406460108"],""],""], + [ + ["06A90A0A","","",""], + ["06480A04","07300B30",["07700B700108","","07700B70"],["066C0A060128","","066C0A060120"]], + ["06480A04","07300B30",["07700B70","","07700B700108"],""], + ["","",["07700B700108","","07700B70"],""] + ], + "1106000C","1106000C","1106000C","1106000C", + [["1106000C","120F1002","",""],"","",""],[["1106000C","120F1002","",""],"","",""], + "1106000C","1106000C","1106000C","1106000C","1106000C","1106000C","1106000C","1106000C","1106000C","1106000C", + [ + ["0600",["0A0F06F2","","0A0F06F6"],"",""], + ["0600",["0A0F06F0","","0A0F06F4"],"",""],"","" + ], + [ + ["0600",["06120A0F","","06360A0F"],"",""], + ["0600",["06000A0F","","06240A0F"],"",""],"","" + ], + [ + ["0600",["0A0F062F","",""],"",""], + ["0600",["0A0F062F","",""],"",""],"", + ["0600",["0A0F062F","","0A0F063F"],"",""] + ], + [ + ["0600",["062F0A0F","",""],"",""], + ["0600",["062F0A0F","",""],"",""],"", + ["0600",["062F0A0F","","063F0A0F"],"",""] + ], + "0600",[["0600","0A03120F06FF","",""],"","",""], + "0600",[["0600","0A03120F06FF","",""],"","",""], + [ + ["0600",["0A0F06FF","","0A0F06FF"],"",""], + ["0600",["0A0F06FF","","0A0F06FF"],"",""],"","" + ], + [ + ["0600",["0A0F06FF","","0A0F06FF"],"",""], + ["0600",["0A0F06FF","","0A0F06FF"],"",""],"","" + ], + "0600","0600","0600","0600","0600","0600", + "2608","2608", + "", + "070E0B0E0003", + "070E0B0E0C00","070E0B0E1800", + "0B0E070E","070E0B0E", + "2808","2808", + "", + "070E0B0E0003", + "070E0B0E0C00","070E0B0E1800", + [ + [ + ["0601","","0601"],["0601","","0601"], + "0603","0603", + ["0601","","0601"],["0601","","0601"], + ["0601","0601","0601"], + ["0601","0601",""] + ], + [ + ["","",["0602","","",""],""],["","",["0602","","",""],""], + ["","",["0602","","",""],""],["","",["0602","","",""],""], + "", + ["","","","","","","",""], + ["","","","","","","",""], + ["","","","","","","",""] + ] + ], + "0B0E070E", + "06000A000003","070E0B0E0003", + ["0B0E090E",""], + "070E0B0E0003", + ["0B0E090E",""], + ["0B0E090E",""], + "0B0E0600","0B0E0602", + [ + ["1002","","",""],"", + ["0B060706","0A020602","",""],"" + ],"", + ["","","","","070E0C000003","070E0C000003","070E0C000003","070E0C000003"], + "0B0E070E0003", + [ + ["0B0E070E0180","","",""],"", + ["0B0E070E0180","0A020602","",""],["0B0E070E0180","0A020602","",""] + ], + [ + ["0B0E070E0180","","",""],"", + ["0B0E070E0180","0A020602","",""],["0B0E070E0180","","",""] + ], + "0B0E0600","0B0E0602", + "06000A000003","070E0B0E0003", + [ + ["0A0406480C00","0B30133007300C00","0A0F137007700C000151","0A0F066C0C000151"], + ["0A0406480C00","0B30133007300C00","0A0F137007700C000151","0A0F066C0C000151"], + ["0A0406440C00","0A04120406480C00","0A0F120406440C000151",""], + ["0A0406490C00","0A04120406480C00","0A0F120406460C000151",""] + ], + ["06030A02",""], + [["0A0A06220C00","","",""],"0A04120406220C000108","",""], + ["",[["06020A0A0C00","","",""],"06020A040C000108","",""]], + ["0B70137007700C000140","0B70137007700C000140","",""], + [ + [ + "", + ["06060003","","060B0003"], + "", + ["0601","","0601"], + ["0601","","0601"], + ["0601","","0601"], + ["0606","0606","0606",""],["0606","","",""] + ], + [ + "", + ["","","","","","","",""], + "","","","", + "070E","070E" + ] + ], + "030E","030E","030E","030E","030E","030E","030E","030E", + ["",["0A040648","0B3013300730","",""],"",["0A040648","0B3013300730","",""]], + [["0A0A06A9","","",""],"0B70137006480108","",""], + [["0A0A06A9","","",""],["0A040648","0B3013300648",["0B70137006480108","",""],""],"",""], + [["0A0A06A9","","",""],"0B70137006480100","",""], + [["0A0A06A9","","",""],"0B70137007700140","",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [ + ["","06490A040100","",""], + ["","06490A040100",["0A040649","","",""],["0A040649","","",""]] + ], + ["",[["0B0C06A0","","",""],["0B0C0640","0B0C0730","",""],"",""]], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],["0A040648","0B3013300730",["0B70137007700148","","0B70137007700140"],["0A061206066C0148","","0A061206066C0140"]],"",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],["0A040648","0B3013300730",["0B70137007700148","","0B70137007700140"],["0A061206066C0148","","0A061206066C0140"]],"",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [ + [["0A0A06A9","","",""],["0A040648","0B3013300648","0B70137006480108",""],"",""], + [["0A0A06A9","","",""],["0A040648","0B3013300730","0B70137006480108",""],"",""] + ], + [["0A0A06A9","","",""],["0A040648","0B3013300648",["0B70137006480108","","0B7013700648"],""],"",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [ + "", + ["0A040648","0A040730","0B3807700141",""], + ["0A040649","0B300738",["0A0406480140","0B7007380140","0B700770014A"],"0A06065A0170"], + "0B3807700142" + ], + [[["06090A0A","","",""],["07700B700108","",""],"",""],""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],["0A040648","0B3013300730",["0B70137007700148","","0B70137007700140"],["0A061206066C0148","","0A061206066C0140"]],"",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],["0A040648","0B3013300730",["0B70137007700148","","0B70137007700140"],["0A061206066C0148","","0A061206066C0140"]],"",""], + [["","","",["0A040648","0A040730","",""]],"0000"], + [["0A0A06A9","","",""],"0B70137006480108","",""], + [["0A0A06A9","","",""],["0B70137006480108","",""],"",""], + [["0A0A06A9","","",""],"0B7013700648","",""], + [["0A0A06A9","","",""],"0B70137007700140","",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + ["",[["0A0A060A","","",""],["0B040648","0B040648","",""],"",""]], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],["0A040648","0B3013300730",["0B70137007700148","",""],["0A061206066C0148","",""]],"",""], + [["0A0A06A9","","",""],"0B70137007700140","",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],["0A040648","0B3013300730",["0B70137007700148","",""],["0A061206066C0148","",""]],"",""], + "", + /*------------------------------------------------------------------------------------------------------------------------ + Three Byte operations 0F38. + ------------------------------------------------------------------------------------------------------------------------*/ + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],["0A040648","0B3013300730","",""],"",""], + [["0A0A06A9","","",""],["0A040648","0B3013300730","",""],"",""], + [["0A0A06A9","","",""],["0A040648","0B3013300730","",""],"",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],["0A040648","0B3013300730","",""],"",""], + [["0A0A06A9","","",""],["0A040648","0B3013300730","",""],"",""], + [["0A0A06A9","","",""],["0A040648","0B3013300730","",""],"",""], + [["0A0A06A9","","",""],["0A040648","0B3013300730","",""],"",""], + [["0A0A06A9","","",""],["0A040648","0B3013300730","",""],"",""], + [["0A0A06A9","","",""],["0A040648","0B3013300730","",""],"",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + ["",["","0B3013300730",["0B70137007700148","",""],""],"",""], + ["",["","0B3013300730","0B70137007700140",""],"",""], + ["",["","0B300730","",""],"",""], + ["",["","0B300730","",""],"",""], + ["",["0A0406482E00","0B30133007301530","0B7013700770",""],["","","07380B70",""],""], + ["",["","","0B7013700770",""],["","","071C0B70",""],""], + ["",["","","0B7013700770",""],["","","070E0B70",""],""], + ["",["","0B300718",["0B7007380109","",""],""],["","","07380B70",""],""], + ["",["0A0407102E00","0B30133007301530",["0B70137007700148","","0B70137007700140"],""],["","","071C0B70",""],""], + ["",["0A0407102E00","0B30133007301530",["0B70137007700148","","0B70137007700140"],""],["","","07380B70",""],""], + ["",["","0B3013300730",["0B70137007700148","","0B70137007700140"],""],"",""], + ["",["0A040648","0B300730","",""],"",""], + ["",["","0B300644",["0B7006440138","",""],["0A0606440138","",""]],"",""], + ["",["","0A050646",["0B6806460108","","0B700646"],["","","0A060646"]],"",""], + ["",["","0A050648",["0B6806480138","","0B680648"],["0A0606480138","",""]],"",""], + ["",["","",["0A06065A0108","","0A06065A"],["","","0A06065A"]],"",""], + [["0A0A06A9","","",""],"0B7007700108","",""], + [["0A0A06A9","","",""],"0B7007700108","",""], + [["0A0A06A9","","",""],["0B7007700148","",""],"",""], + ["",["","","0B7007700140",""],"",""], + ["","0B7007380108",["","","07380B70",""],""], + ["","0B70071C0108",["","","071C0B70",""],""], + ["","0B70070E0108",["","","070E0B70",""],""], + ["","0B7007380108",["","","07380B70",""],""], + ["","0B70071C0108",["","","071C0B70",""],""], + ["","0B7007380108",["","","07380B70",""],""], + ["",["","",["0A0F137007700108","","0A0F13700770"],""],["","",["0A0F13700770","","0A0F137007700108"],""],""], + ["",["","",["0A0F137007700148","","0A0F137007700140"],["0A0F1206066C0148","",""]],["","",["0A0F137007700140","","0A0F137007700148"],""],""], + ["","0B70137007700140",["","",["0B7006FF","","0B7006FF0108"],""],""], + ["",["0A040648","0B3013300730","0A0F137007700140",""],["","",["0A0F0770","","0A0F07700108"],""],""], + [["",["0B7007700108","",""],"",""],["","",["","",["","","0B7006FF0108"],""],""]], + ["",["0B70137007700148","",""],"",""], + ["",["","0B3013300730",["0B7013700770014A","","0B70137007700142"],""],"",""], + ["",["","0B3013300730",["0A0412040644014A","","0A04120406480142"],""],"",""], + ["",["","073013300B30","",""],"",""], + ["",["","0B3013300730","",""],"",""], + ["","0B7007380108",["","","07380B70",""],""], + ["","0B70071C0108",["","","071C0B70",""],""], + ["","0B70070E0108",["","","070E0B70",""],""], + ["","0B7007380108",["","","07380B70",""],""], + ["","0B70071C0108",["","","071C0B70",""],""], + ["","0B7007380108",["","",["06480A04","07380B70",""],""],""], + ["",["","0A051205065A",["0B70137007700148","","0B70137007700140"],["0A061206066C0108","",""]],"",""], + ["",["0A040710","0B3013300730","0A0F137007700140",""],"",""], + ["","0B70137007700108",["","",["0B7006FF","","0B7006FF0108"],""],""], + ["",["0A0412040648","0B3013300730",["0B70137007700148","","0B70137007700140"],["0A061206066C0148","",""]],["","",["0A0F0770","","0A0F07700108"],""],""], + ["","0B70137007700108",["","","0B7006FF0100",""],""], + ["",["0A0412040648","0B3013300730",["0B70137007700148","","0B70137007700140"],["0A061206066C0148","",""]],"",""], + ["","0B70137007700108","",""], + ["",["0A0412040648","0B3013300730",["0B70137007700148","","0B70137007700140"],["0A061206066C0148","",""]],"",""], + ["","0B70137007700108","",""], + ["",["0A0412040648","0B3013300730",["0B70137007700148","","0B70137007700140"],["0A061206066C0148","",""]],"",""], + ["",["0A0412040648","0B3013300730",["0B70137007700148","","0B70137007700140"],["0A061206066C0148","",""]],"",""], + ["",["0A040648",["0A040648","0A040648","",""],"",""],"",""], + ["",["","",["0B7007700159","","0B7007700151"],["0A06066C0159","","0A06066C0151"]],"",""], + ["",["","",["0A0412040644010A","","0A04120406460102"],""],"",""], + ["",["","",["0B7007700148","","0B7007700140"],""],"",""], + ["",["",["0B3013300730","","0B3013300730"],["0B70137007700148","","0B70137007700140"],["0A061206066C0148","",""]],"",""], + ["",["",["0B3013300730","",""],["0B70137007700148","","0B70137007700140"],["0A061206066C0148","",""]],"",""], + ["",["",["0B3013300730","","0B3013300730"],["0B70137007700148","","0B70137007700140"],["0A061206066C0148","",""]],"",""], + "","","","", + ["",["","",["0B7007700148","","0B7007700140"],""],"",""], + ["",["","",["0A04120406440108","","0A0412040646"],""],"",""], + ["",["","",["0B7007700148","","0B7007700140"],""],"",""], + ["",["","",["0A04120406440108","","0A0412040646"],""],"",""], + ["",["","","",["0A061206066C015A","","0A061206066C0152"]],"",""], + ["",["","","",["0A061206066C0159","",""]],"",""], + ["",["","","",["0A061206066C0159","","0A061206066C0151"]],"",""], + ["",["","","",["0A061206066C0159","","0A061206066C0151"]],"",""], + "", + ["",["","","",["0A061206066C0149","","0A061206066C0141"]],"",""], + "","", + ["",["","0B300644",["0B7006440128","",""],["0A0606440128","",""]],"",""], + ["",["","0B300646",["0B7006460128","","0B7006460120"],["","","0A0606460120"]],"",""], + ["",["","0A050648",["0B6806480128","","0B6806480120"],["0A0606480128","",""]],"",""], + ["",["","",["0A06065A0128","","0A06065A0120"],["","","0A06065A0120"]],"",""], + ["",["","","",["0A06120F066C0148","",""]],"",""], + ["",["","","",["0A06120F066C0148","",""]],"",""], + ["",["","","",["0A06120F066C0148","",""]],"",""], + ["",["","","",["0A06120F066C0148","",""]],"",""], + "","","","", + ["",["","",["0B70137007700148","","0B70137007700140"],["0A061206066C0148","","0A061206066C0140"]],"",""], + ["",["","",["0B70137007700158","","0B70137007700150"],["0A061206066C0158","","0A061206066C0150"]],"",""], + ["",["","",["0B70137007700108","","0B7013700770"],""],"",""], + "","","","","", + ["",["","","",["0A061206066C0148","",""]],"",""], + ["",["","","",["0A061206066C015A","","0A061206066C0152"]],"",""], + ["",["","","",["0A06120F066C0148","",""]],"",""], + ["",["","","",["0A06120F066C0148","",""]],"",""], + "","","","", + ["",["","","",["0A0F1206066C0148","",""]],"",""], + ["",["","",["0B70137007700108","","0B7013700770"],""],"",""], + ["",["","",["0B70137007700148","","0B70137007700140"],""],"",""], + ["",["","",["0B70137007700148","","0B70137007700140"],""],"",""], + ["",["","0B300640",["0B7006400108","",""],""],"",""], + ["",["","0B300642",["0B7006420108","",""],""],"",""], + ["",["",["","",["0B7006000108","",""],""],"",""]], + ["",["",["","",["0B7006100108","",""],""],"",""]], + ["",["","",["0B70062F0108","","0B70063F"],""],"",""], + ["",["","",["0B70137007700108","","0B7013700770"],""],"",""], + ["",["","",["0B70137007700148","","0B70137007700140"],""],"",""], + ["",["","",["0B70137007700148","","0B70137007700140"],""],"",""], + [["","0B0C060B0180","",""],""], + [["","0B0C060B0180","",""],""], + [["","0B0C060B0180","",""],""], + ["",["","","0B70137007700140",""],"",""], + ["",["","","",["0A061206066C014A","",""]],"",""], + "", + ["",["","","",["0A061206066C0148","",""]],"",""], + ["",["","","",["0A061206066C0148","",""]],"",""], + ["",["","",["0B7007700108","","0B700770"],""],"",""], + ["",["","",["0B7007700108","","0B700770"],""],"",""], + ["",["","",["07700B700108","","07700B70"],""],"",""], + ["",["","",["07700B700108","","07700B70"],""],"",""], + "", + ["",["","",["0B70137007700108","","0B7013700770"],""],"",""], + "","", + ["",["",["0B30073013300124","","0B30064813300124"],["0B700770012C","","0B7007380124"],["0A06066C012C","","0A06065A0124"]],"",""], + ["",["",["0A04073012040104","","0B30073013300104"],["0B380770010C","","0B7007700104"],""],"",""], + ["",["",["0B30073013300134","","0B30064813300134"],["0B700770013C","","0B7007380134"],["0A06066C013C","","0A06065A0104"]],"",""], + ["",["",["0A04073012040104","","0B30073013300104"],["0B380770010C","","0B7007700104"],""],"",""], + "","", + ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],""],"",""], + ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],""],"",""], + ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],["0A061206066C015A","","0A061206066C0152"]],"",""], + ["",["",["0A0412040714","","0A0412040718"],["0A0412040644010A","","0A04120406460102"],""],"",""], + ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],["0A061206066C015A","","0A061206066C0152"]],"",""], + ["",["",["0A0412040714","","0A0412040718"],["0A0412040644010A","","0A04120406460102"],""],"",""], + ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],["0A061206066C015A","","0A061206066C0152"]],"",""], + ["",["",["0A0412040714","","0A0412040718"],["0A0412040644010A","","0A04120406460102"],""],"",""], + ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],["0A061206066C015A","","0A061206066C0152"]],"",""], + ["",["",["0A0412040714","","0A0412040718"],["0A0412040644010A","","0A04120406460102"],""],"",""], + ["",["","",["07700B70010C","","07380B700104"],["066C0A06012C","","065A0A060124"]],"",""], + ["",["","",["07700B38010C","","07700B700104"],""],"",""], + ["",["","",["07700B70013C","","07380B700134"],["066C0A06013C","","065A0A060134"]],"",""], + ["",["","",["07700B38010C","","07700B700104"],""],"",""], + ["",["","","",["0A061206066C011A","",""]],"",""], + "", + ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],""],"",""], + ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],""],"",""], + ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],["0A061206066C015A","","0A061206066C0152"]],"",""], + ["",["",["0A0412040644","","0A0412040646"],["0A0412040644010A","","0A04120406460102"],""],"",""], + ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],["0A061206066C015A","","0A061206066C0152"]],"",""], + ["",["",["0A0412040644","","0A0412040646"],["0A0412040644010A","","0A04120406460102"],""],"",""], + ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],["0A061206066C015A","","0A061206066C0152"]],"",""], + ["",["",["0A0412040644","","0A0412040646"],["0A0412040644010A","","0A04120406460102"],""],"",""], + ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],["0A061206066C015A","","0A061206066C0152"]],"",""], + ["",["",["0A0412040644","","0A0412040646"],["0A0412040644010A","","0A04120406460102"],""],"",""], + "","","","", + ["",["","","0B70137007700140",["0A061206066C0118","",""]],"",""], + ["",["","","0B70137007700140",["0A061206066C0148","",""]],"",""], + ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],""],"",""], + ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],""],"",""], + ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],["0A061206066C015A","","0A061206066C0152"]],"",""], + ["",["",["0A0412040644","","0A0412040646"],["0A0412040644010A","","0A04120406460102"],""],"",""], + ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],["0A061206066C015A","","0A061206066C0152"]],"",""], + ["",["",["0A0412040644","","0A0412040646"],["0A0412040644010A","","0A04120406460102"],""],"",""], + ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],["0A061206066C015A","","0A061206066C0152"]],"",""], + ["",["",["0A0412040644","","0A0412040646"],["0A0412040644010A","","0A04120406460102"],""],"",""], + ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],["0A061206066C015A","","0A061206066C0152"]],"",""], + ["",["",["0A0412040644","","0A0412040646"],["0A0412040644010A","","0A04120406460102"],""],"",""], + "","","","", + ["",["","",["0B7007700148","","0B7007700140"],""],"",""], + "", + [ + [ + ["",["","","",["060C013C","","060A0134"]],"",""], + ["",["","",["060C013C","","060A0134"],["060C013C","",""]],"",""], + ["",["","",["060C013C","","070A0134"],["060C013C","",""]],"",""], + "", + ["",["","","",["060C013C","","060A0134"]],"",""], + ["",["","",["060C013C","","060A0134"],["060C013C","",""]],"",""], + ["",["","",["060C013C","","060A0134"],["060C013C","",""]],"",""], + "" + ],"" + ], + [ + [ + "", + ["",["","",["060C010C","","060C0104"],""],"",""], + ["",["","",["060C010C","","060C0104"],""],"",""], + "","", + ["",["","",["060C010C","","060C0104"],""],"",""], + ["",["","",["060C010C","","060C0104"],""],"",""], + "" + ],"" + ], + [["0A040648","","",""],["","",["0A06066C0159","","0A06066C0151"],["0A06066C0109","",""]],"",""], + [["0A040648","","",""],["","","",["0A06066C0109","",""]],"",""], + [["0A040648","","",""],["","",["0A06066C0159","","0A06066C0151"],["0A06066C0109","",""]],"",""], + [["0A0406482E00","","",""],["","",["0A04120406440109","","0A04120406460101"],["0A06066C0109","",""]],"",""], + [["0A040648","","",""],["","",["0A06066C0159","","0A06066C0151"],["0A06066C015A","",""]],"",""], + [["0A040648","","",""],["","",["0A04120406440109","","0A04120406460101"],["0A06066C0148","",""]],"",""], + "","", + [[["","","",["0A06060C0120","","0A06060C0128"]],["","","",["060C0A060128","","060C0A060120"]],"",""],""], + [[["","","",["0A06060C0130","","0A06060C0138"]],["","","",["060C0A060138","","060C0A060130"]],"",""],""], + "","", + [[["","","",["0A06060C0120","","0A06060C0128"]],["","","",["060C0A060128","","060C0A060120"]],"",""],""], + [[["","","",["0A06060C0130","","0A06060C0138"]],["","","",["060C0A060138","","060C0A060130"]],"",""],""], + "","","","","", + ["",["0A040648","0A040648","",""],"",""], + ["",["0A040648","0A0412040648","",""],"",""], + ["",["0A040648","0A0412040648","",""],"",""], + ["",["0A040648","0A0412040648","",""],"",""], + ["",["0A040648","0A0412040648","",""],"",""], + "","","","","","","","","","","","","","","","", + [ + ["0B0E070E0180","","",""], + ["0B0E070E0180","","",""],"", + ["0B0C06000180","","",""] + ], + [ + ["070E0B0E0180","","",""], + ["070E0B0E0180","","",""],"", + ["0B0C070E0180","","",""] + ], + ["",["","0B0C130C070C","",""],"",""], + [ + "", + ["",["","130C070C","",""],"",""], + ["",["","130C070C","",""],"",""], + ["",["","130C070C","",""],"",""], + "","","","" + ],"", + [ + ["","0B0C070C130C","",""],"", + ["","0B0C130C070C","",""], + ["","0B0C130C070C","",""] + ], + [ + "", + ["0B0C070C","","",""], + ["0B0C070C","","",""], + ["","0B0C130C070C1B0C","",""] + ], + [ + ["","0B0C130C070C","",""], + ["","0B0C130C070C","",""], + ["","0B0C130C070C","",""], + ["","0B0C130C070C","",""] + ], + "","","","","","","","", + /*------------------------------------------------------------------------------------------------------------------------ + Three Byte operations 0F3A. + ------------------------------------------------------------------------------------------------------------------------*/ + ["",["","0A05065A0C00","0B7007700C000140",""],"",""], + ["",["","0A05065A0C00","0B7007700C000140",""],"",""], + ["",["",["0B30133007300C00","",""],"",""],"",""], + ["",["","",["0B70137007700C000148","","0B70137007700C000140"],["0A061206066C0C000108","",""]],"",""], + ["",["","0B3007300C00",["0B7007700C000148","",""],""],"",""], + ["",["","0B3007300C00","0B7007700C000140",""],"",""], + ["",["","0A051205065A0C00","",""],"",""], + ["",["","","",["0A06066C0C000108","",""]],"",""], + ["",["0A0406480C00","0B3007300C00",["0B7007700C000149","",""],""],"",""], + ["",["0A0406480C00","0B3007300C00","0B7007700C000141",""],"",""], + ["",["0A0406440C00","0A04120406440C00",["0A04120406440C000109","",""],""],"",""], + ["",["0A0406460C00","0A04120406460C00","0A04120406460C000101",""],"",""], + ["",["0A0406480C00","0B30133007300C00","",""],"",""], + ["",["0A0406480C00","0B30133007300C00","",""],"",""], + ["",["0A0406480C00","0B30133007300C00","",""],"",""], + [["0A0A06A90C00","","",""],"0B70137007700C000108","",""], + "","","","", + [["","06000A040C000108","",""],["","070C0A040C000108","",""]], + [["","06020A040C000108","",""],["","070C0A040C000108","",""]], + ["",["06240A040C000108","","06360A040C00"],"",""], + ["","070C0A040C000108","",""], + ["",["","0A05120506480C00",["0B70137006480C000108","","0B70137006480C00"],""],"",""], + ["",["","06480A050C00",["06480B700C000108","","06480B700C00"],""],"",""], + ["",["","",["0A061206065A0C000108","","0A061206065A0C00"],""],"",""], + ["",["","",["065A0A060C000108","","065A0A060C00"],""],"",""], + "", + ["",["","07180B300C00",["07380B700C000109","",""],""],"",""], + ["",["","",["0A0F137007700C000148","","0A0F137007700C000140"],["0A0F1206066C0C000148","",""]],"",""], + ["",["","",["0A0F137007700C000148","","0A0F137007700C000140"],["0A0F1206066C0C000148","",""]],"",""], + ["","0A04120406200C000108","",""], + ["",["0A04120406440C000108","",""],"",""], + ["",["",["0A04120406240C00","","0A04120406360C00"],["0A04120406240C000108","","0A04120406360C00"],""],"",""], + ["",["","",["0B70137007700C000148","","0B70137007700C000140"],""],"",""], + "", + ["",["","",["0B70137007700C000148","","0B70137007700C000140"],""],"",""], + ["",["","",["0B7007700C000149","","0B7007700C000141"],["0A06066C0C000159","","0A06066C0C000151"]],"",""], + ["",["","",["0A04120406440C000109","","0A04120406460C000101"],""],"",""], + "","","","","","","","", + ["",["",["0A0F06FF0C00","","0A0F06FF0C00"],"",""],"",""], + ["",["",["0A0F06FF0C00","","0A0F06FF0C00"],"",""],"",""], + ["",["",["0A0F06FF0C00","","0A0F06FF0C00"],"",""],"",""], + ["",["",["0A0F06FF0C00","","0A0F06FF0C00"],"",""],"",""], + "","","","", + ["",["","0A05120506480C00",["0B70137006480C000108","","0B70137006480C00"],""],"",""], + ["",["","06480A050C00",["06480B700C000108","","06480B700C00"],""],"",""], + ["",["","",["0A061206065A0C000108","","0A061206065A0C00"],""],"",""], + ["",["","",["065A0A060C000108","","065A0A060C00"],""],"",""], + "","", + ["",["","0A0F063F0C00",["0A0F137007700C000108","","0A0F137007700C00"],""],"",""], + ["",["","",["0A0F137007700C000108","","0A0F137007700C00"],""],"",""], + ["",["0A0406480C00","0B30133007300C00","",""],"",""], + ["",["0A0406480C00","0A04120406480C00","",""],"",""], + ["",["0A0406480C00","0B30133007300C00",["0B70137007700C000108","",""],""],"",""], + ["",["","",["0B70137007700C000148","","0B70137007700C000140"],""],"",""], + ["",["0A0406480C00","0A04120406480C00","",""],"",""], + "", + ["",["","0A051205065A0C00","",""],"",""], + "", + ["",["",["0B301330073015300E00","","0B301330153007300E00"],"",""],"",""], + ["",["",["0B301330073015300E00","","0B301330153007300E00"],"",""],"",""], + ["",["","0B30133007301530","",""],"",""], + ["",["","0B30133007301530","",""],"",""], + ["",["","0A051205065A1505","",""],"",""], + "","","", + ["",["","",["0B70137007700C000149","","0B70137007700C000141"],""],"",""], + ["",["","",["0A04120406440C000109","","0A04120406460C000101"],""],"",""], + ["",["","","",["0A06066C0C000159","","0A06066C0C000151"]],"",""], + "", + ["",["","",["0B70137007700C000149","","0B70137007700C000141"],""],"",""], + ["",["","",["0A04120406440C000109","","0A04120406460C000101"],""],"",""], + ["",["","",["0B7007700C000149","","0B7007700C000141"],""],"",""], + ["",["","",["0A04120406440C000109","","0A04120406460C000101"],""],"",""], + "","","","", + ["",["",["0B30133007301530","","0B30133015300730"],"",""],"",""], + ["",["",["0B30133007301530","","0B30133015300730"],"",""],"",""], + ["",["",["0B30133007301530","","0B30133015300730"],"",""],"",""], + ["",["",["0B30133007301530","","0B30133015300730"],"",""],"",""], + ["",["0A0406480C00","0A0406480C00","",""],"",""], + ["",["0A0406480C00","0A0406480C00","",""],"",""], + ["",["0A0406480C00","0A0406480C00","",""],"",""], + ["",["0A0406480C00","0A0406480C00","",""],"",""], + "","", + ["",["","",["0A0F07700C000148","","0A0F07700C000140"],""],"",""], + ["",["","",["0A0F06440C000108","","0A0F06460C00"],""],"",""], + ["",["",["0B30133007301530","","0B30133015300730"],"",""],"",""], + ["",["",["0B30133007301530","","0B30133015300730"],"",""],"",""], + ["",["",["0A04120406441530","","0A04120415300644"],"",""],"",""], + ["",["",["0A04120406461530","","0A04120415300646"],"",""],"",""], + ["",["",["0B30133007301530","","0B30133015300730"],"",""],"",""], + ["",["",["0B30133007301530","","0B30133015300730"],"",""],"",""], + ["",["",["0A04120406441530","","0A04120415300644"],"",""],"",""], + ["",["",["0A04120406461530","","0A04120415300646"],"",""],"",""], + "","","","","","","","", + ["",["",["0B30133007301530","","0B30133015300730"],"",""],"",""], + ["",["",["0B30133007301530","","0B30133015300730"],"",""],"",""], + ["",["",["0A04120406441530","","0A04120415300644"],"",""],"",""], + ["",["",["0A04120406461530","","0A04120415300646"],"",""],"",""], + ["",["",["0B30133007301530","","0B30133015300730"],"",""],"",""], + ["",["",["0B30133007301530","","0B30133015300730"],"",""],"",""], + ["",["",["0A04120406441530","","0A04120415300644"],"",""],"",""], + ["",["",["0A04120406461530","","0A04120415300646"],"",""],"",""], + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","", + [["","","","0A06066C0C000141"],["","","",["0A06066C0C000159","",""]],"",["","","","0A06066C0C000151"]], + [["","","","0A06066C0C000141"],["","","",["0A06066C0C000159","",""]],"",""], + "0A0406480C00","","","", + "","","","","","","","","","","","","","","", + ["",["0A0406480C00","0A0406480C00","",""],"",""], + "","","","","","", + ["","","",["","","","0A06066C0C000151"]], + "","","","","","","","","", + ["","","",["","0B0C070C0C00","",""]], + "","","","","","","","","","","","","","","", + /*------------------------------------------------------------------------------------------------------------------------ + AMD XOP 8. + ------------------------------------------------------------------------------------------------------------------------*/ + "","","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","", + "0A04120406481404","0A04120406481404","0A04120406481404","","","","","","", + "0A04120406481404","0A04120406481404","","","","","","0A04120406481404","0A04120406481404","0A04120406481404", + "","","","","","","0A04120406481404","0A04120406481404", + "","",["0B30133007301530","","0B30133015300730"],["0A04120406481404","","0A04120414040648"],"","","0A04120406481404", + "","","","","","","","","","","","","","","", + "0A04120406481404","","","","","","","","","","0A0406480C00","0A0406480C00","0A0406480C00","0A0406480C00", + "","","","","","","","", + "0A04120406480C00","0A04120406480C00","0A04120406480C00","0A04120406480C00", + "","","","","","","","","","","","","","","","","","","","","","","","","","","","", + "0A04120406480C00","0A04120406480C00","0A04120406480C00","0A04120406480C00", + "","","","","","","","","","","","","","","","", + /*------------------------------------------------------------------------------------------------------------------------ + AMD XOP 9. + ------------------------------------------------------------------------------------------------------------------------*/ + "", + ["","130C070C","130C070C","130C070C","130C070C","130C070C","130C070C","130C070C"], + ["","130C070C","","","","","130C070C",""], + "","","","","","","","","","","","","","","", + ["",["070C","070C","","","","","",""]], + "","","","","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","","","", + "0B300730","0B300730","0B300730","0B300730", + "","","","","","","","","","","","", + ["0A0406481204","","0A0412040648"],["0A0406481204","","0A0412040648"],["0A0406481204","","0A0412040648"],["0A0406481204","","0A0412040648"], + ["0A0406481204","","0A0412040648"],["0A0406481204","","0A0412040648"],["0A0406481204","","0A0412040648"],["0A0406481204","","0A0412040648"], + ["0A0406481204","","0A0412040648"],["0A0406481204","","0A0412040648"],["0A0406481204","","0A0412040648"],["0A0406481204","","0A0412040648"], + "","","","","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","","","", + "0A040648","0A040648","0A040648","","","0A040648","0A040648","","","","0A040648","","","","","", + "0A040648","0A040648","0A040648","","","0A040648","0A040648","","","","0A040648","","","","","", + "0A040648","0A040648","0A040648","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","", + /*------------------------------------------------------------------------------------------------------------------------ + AMD XOP A. + ------------------------------------------------------------------------------------------------------------------------*/ + "","","","","","","","","","","","","","","","", + "0B0C070C0C020180","",["130C06240C020180","130C06240C020180","","","","","",""], + "","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + /*------------------------------------------------------------------------------------------------------------------------- + L1OM Vector. + -------------------------------------------------------------------------------------------------------------------------*/ + "","","","","1206","","","","","","","","","","","", + [["0A0606610120","0A0606610120","",""],""],"", + [["0A0606610120","0A0606610120","",""],""], + [["0A0606610120","0A0606610120","",""],""], + [["0A0606610100","0A0606610100","",""],""],"", + [["0A0606610100","0A0606610100","",""],""], + [["0A0606610100","0A0606610100","",""],""], + ["0A06066C0124",""],["066C0124",""],"",["066C0124",""], + ["066C0A060104",""],["066C0104",""],"",["066C0104",""], + ["0A0F120606610150","0A0F120606610150","",""],"0A0F120606610140","0A0F120606610140","", + ["0A0F120606610150","0A0F120606610150","",""],"0A0F120606610140","0A0F120606610140","", + "","","","","","","","", + "0A0F120606610140","","","","","","","","","","","","","","","", + ["0A06120606610150","0A06120606610150","",""],"0A06120606610140","","0A06120F06610140","","0A06120F06610140","0A06120606610150","0A06120606610140", + ["0A06120606610150","0A06120606610150","",""],"","","","","","","", + ["0A06120606610150","0A06120606610150","",""],"0A06120606610140","","0A06120F06610140","","0A06120F06610140","","", + ["0A06120606610150","0A06120606610150","",""],"0A06120606610140","","0A06120F06610140","","0A06120F06610140","","", + ["0A06120606610150","0A06120606610150","",""],"0A06120606610140", + ["0A06120606610150","0A06120606610150","",""],"", + ["0A06120606610150","0A06120606610150","",""],"", + "0A06120606610150","0A06120606610140", + ["0A06120606610150","0A06120606610150","",""],"", + ["0A06120606610150","0A06120606610150","",""],"", + ["0A06120606610150","0A06120606610150","",""],"","","", + ["0A06120606610150","0A06120606610150","",""],"", + ["0A06120606610150","0A06120606610150","",""],"", + ["0A06120606610150","0A06120606610150","",""],"","","", + ["0A06120606610150","0A06120606610150","",""],"", + ["0A06120606610150","0A06120606610150","",""],"", + ["0A06120606610150","0A06120606610150","",""],"", + ["0A06120606610150","0A06120606610150","",""],"", + ["0A06120606610150","0A06120606610150","",""],"0A06120606610140","0A06120606610140","0A06120606610140","","","0A06120606610150","0A06120606610140", + ["0A06120606610150","0A06120606610150","",""],"0A06120606610140","0A06120606610140","", + ["0A06120606610150","0A06120606610150","",""],"0A06120606610140","0A06120606610140","", + ["","0A0606610152","",""],["0A0606610153","0A0606610152","",""],["0A0606610153","0A0606610152","",""],"", + ["","0A0606610158","",""],["0A0606610141","0A0606610148","",""],["0A0606610141","0A0606610148","",""],"", + "0A0606610153","","0A0606610150","0A0606610152","","0A0606610150","0A0606610150","", + "0A06120606610140","0A06120606610140","0A06120606610140","", + ["0A06120606610140","0A06120606610140","",""],["0A06120606610140","0A06120606610140","",""], + ["0A06120606610140","0A06120606610140","",""],["0A06120606610140","0A06120606610140","",""], + "0A06120606610140","0A06120606610140","","","","","","", + "0A0606610140","0A0606610150","0A0606610150","","0A0606610150","","","", + "0A06120606610140","","","","","","","", + "0A0606610150","","0A06120606610150","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "0A0606610C010150","0A0606610C000C00","0A06120606610C010140","0A0606610C010140","","","","", + "","","","","","","","", + /*------------------------------------------------------------------------------------------------------------------------- + L1OM Mask, Mem, and bit opcodes. + -------------------------------------------------------------------------------------------------------------------------*/ + ["","0B0E070E"],["","0B0E070E"],["","0B0E070E"],["","0B0E070E"], + ["","0B0E070E"],["","0B0E070E"],["","0B0E070E"],["","0B0E070E"], + ["","0B0E070E"],["","0B0E070E"],["","0B0E070E"],["","0B0E070E"], + ["","0B0E070E"],["","0B0E070E"],["","0B0E070E"],["","0B0E070E"], + ["","0B0E070E"],["","0B0E070E"],["","0B0E070E"],["","0B0E070E"], + ["","0B0E070E"],["","0B0E070E"],["","0B0E070E"],["","0B0E070E"], + ["","0B0E070E0C010C000C00"],["","0B0E070E0C010C000C00"],["","0B0E070E0C010C000C00"],["","0B0E070E0C010C000C00"], + ["","0B0E070E0C010C000C00"],["","0B0E070E0C010C000C00"],["","0B0E070E0C010C000C00"],["","0B0E070E0C010C000C00"], + ["","0B0E070E"],["","0B0E070E"],["","0B0E070E"],["","0B0E070E"], + ["","0B0E070E"],["","0B0E070E"],["","0B0E070E"],["","0B0E070E"], + "","","","", + "06FF0A0F", + [["0601","0601","0604","0604","","","",""],""], + [["0601","0601","","","","","",""],""], + [["0601","0601","","","","","",""],""], + "06FF0A0F","06FF0B06","07060A0F","06FF0B06", + "06FF0A0F","06FF0A0F","06FF0A0F","06FF0A0F", + "06FF0A0F","06FF0A0F","06FF0A0F","06FF0A0F", + "","06FF0A0F", + ["",["0B07","0B07","","","","","",""]], + ["",["0B07","0B07","","","","","",""]] +]; + +/*------------------------------------------------------------------------------------------------------------------------- +3DNow uses the byte after the operands as the select instruction code, so in the Mnemonics there is no instruction name, but +in the Operands array the operation code 0F0F which is two byte opcode 0x10F (using the disassemblers opcode value system) +automatically takes operands ModR/M, and MM register. Once the operands are decoded the byte value after the operands is +the selected instruction code for 3DNow. The byte value is an 0 to 255 value so the listing is 0 to 255. +--------------------------------------------------------------------------------------------------------------------------- +At the very end of the function ^DecodeInstruction()^ an undefined instruction name with the operands MM, and MM/MMWORD is +compared for if the operation code is 0x10F then the next byte is read and is used as the selected 3DNow instruction. +-------------------------------------------------------------------------------------------------------------------------*/ + +const M3DNow = [ + "","","","","","","","","","","","","PI2FW","PI2FD","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","PFNACC","","","","PFPNACC","", + "PFCMPGE","","","","PFMIN","","PFRCP","PFRSQRT","","","FPSUB","","","","FPADD","", + "PFCMPGT","","","","PFMAX","","PFRCPIT1","PFRSQIT1","","","PFSUBR","","","","PFACC","", + "PFCMPEQ","","","","PFMUL","","PFRCPIT2","PMULHRW","","","","PSWAPD","","","","PAVGUSB", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","" +]; + +/*------------------------------------------------------------------------------------------------------------------------- +Virtual machine synthetic operation codes is under two byte operation code 0FC7 which is opcode 0x1C7 using the disassemblers +opcode value system. The operation code 0x1C7 is an group opcode containing 3 operation codes, but only one of the codes +is used in the ModR/M grouped opcode for synthetic virtual machine operation codes. The ModR/M byte has to be in register mode +using register code 001 for the virtual machine synthetic operation codes. The effective address has to be set 000 which uses +the full ModR/M byte as an static opcode encoding under the group opcode 001. This makes the operation code 0F C7 C8. +The resulting instruction name in the Mnemonics map is "SSS", and takes no Operands in the Operands array. The two bytes after +0F C7 C8 are used as the select synthetic operation code. Only the first 4 values of both bytes have an select operation code, +so an 5x5 map is used to keep the mapping small. +--------------------------------------------------------------------------------------------------------------------------- +When the operation code is 0F C7 and takes the ModR/M byte value C8 the operation code is "SSS" with no operands. +At the very end of the function ^DecodeInstruction()^ an instruction that is "SSS" is compared if it is instruction "SSS". +If it is operation "SSS" then the two bytes are then read as two codes which are used as the selected operation code in the 5x5 map. +--------------------------------------------------------------------------------------------------------------------------- +link to the patent https://www.google.com/patents/US7552426 +-------------------------------------------------------------------------------------------------------------------------*/ + +const MSynthetic = [ + "VMGETINFO","VMSETINFO","VMDXDSBL","VMDXENBL","", + "VMCPUID","VMHLT","VMSPLAF","","", + "VMPUSHFD","VMPOPFD","VMCLI","VMSTI","VMIRETD", + "VMSGDT","VMSIDT","VMSLDT","VMSTR","", + "VMSDTE","","","","" +]; + +/*------------------------------------------------------------------------------------------------------------------------- +Condition codes Note that the SSE, and MVEX versions are limited to the first 7 condition codes. +XOP condition codes map differently. +-------------------------------------------------------------------------------------------------------------------------*/ + +const ConditionCodes = [ + "EQ","LT","LE","UNORD","NEQ","NLT","NLE","ORD", //SSE/L1OM/MVEX. + "EQ_UQ","NGE","NGT","FALSE","NEQ_OQ","GE","GT","TRUE", //VEX/EVEX. + "EQ_OS","LT_OQ","LE_OQ","UNORD_S","NEQ_US","NLT_UQ","NLE_UQ","ORD_S", //VEX/EVEX. + "EQ_US","NGE_UQ","NGT_UQ","FALSE_OS","NEQ_OS","GE_OQ","GT_OQ","TRUE_US", //VEX/EVEX. + "LT","LE","GT","GE","EQ","NEQ","FALSE","TRUE" //XOP. +]; + +/*------------------------------------------------------------------------------------------------------------------------- +The Decoded operation name. +-------------------------------------------------------------------------------------------------------------------------*/ + +var Instruction = ""; + +/*------------------------------------------------------------------------------------------------------------------------- +The Instructions operands. +-------------------------------------------------------------------------------------------------------------------------*/ + +var InsOperands = ""; + +/*------------------------------------------------------------------------------------------------------------------------- +This object stores a single decoded Operand, and gives it an number in OperandNum (Operand Number) for the order they are +read in the operand string. It also stores all of the Settings for the operand. +--------------------------------------------------------------------------------------------------------------------------- +Each Operand is sorted into an decoder array in the order they are decoded by the CPU in series. +--------------------------------------------------------------------------------------------------------------------------- +Used by function ^DecodeOperandString()^ Which sets the operands active and gives them there settings along the X86Decoder array. +--------------------------------------------------------------------------------------------------------------------------- +The following X86 patent link might help http://www.google.com/patents/US7640417 +-------------------------------------------------------------------------------------------------------------------------*/ + +var Operand = function(){ + return( + { + Type:0, //The operand type some operands have different formats like DecodeImmediate() which has a type input. + BySizeAttrubute:false, //Effects how size is used depends on which operand type for which operand across the decoder array. + /*------------------------------------------------------------------------------------------------------------------------- + How Size is used depends on the operand it is along the decoder array for which function it uses to + decode Like DecodeRegValue(), or Decode_ModRM_SIB_Address(), and lastly DecodeImmediate() as they all take the BySize. + -------------------------------------------------------------------------------------------------------------------------*/ + Size:0x00, //The Setting. + OperandNum:0, //The operand number basically the order each operand is read in the operand string. + Active:false, //This is set by the set function not all operand are used across the decoder array. + //set the operands attributes then set it active in the decoder array. + set:function(T, BySize, Settings, OperandNumber) + { + this.Type = T; + this.BySizeAttrubute = BySize; + this.Size = Settings; + this.OpNum = OperandNumber; //Give the operand the number it was read in the operand string. + this.Active = true; //set the operand active so it's settings are decoded by the ^DecodeOperands()^ function. + }, + //Deactivates the operand after they are decoded by the ^DecodeOperands()^ function. + Deactivate:function(){ this.Active = false; } + } + ); +}; + +/*------------------------------------------------------------------------------------------------------------------------- +The Decoder array is the order each operand is decoded after the select opcode if used. They are set during the decoding of +the operand string using the function ^DecodeOperandString()^ which also gives each operand an number for the order they are +read in. Then they are decoded by the Function ^DecodeOperands()^ which decodes each set operand across the X86Decoder in order. +The number the operands are set during the decoding of the operand string is the order they will be positioned after decoding. +As the operands are decoded they are also Deactivated so the next instruction can be decoded using different operands. +--------------------------------------------------------------------------------------------------------------------------- +The following X86 patent link might help http://www.google.com/patents/US7640417 +--------------------------------------------------------------------------------------------------------------------------- +Used by functions ^DecodeOperandString()^, and ^DecodeOperands()^, after function ^DecodeOpcode()^. +-------------------------------------------------------------------------------------------------------------------------*/ + +var X86Decoder = [ + /*------------------------------------------------------------------------------------------------------------------------- + First operand that is always decoded is "Reg Opcode" if used. + Uses the function ^DecodeRegValue()^ the input RValue is the three first bits of the opcode. + -------------------------------------------------------------------------------------------------------------------------*/ + new Operand(), //Reg Opcode if used. + /*------------------------------------------------------------------------------------------------------------------------- + The Second operand that is decoded in series is the ModR/M address if used. + Reads a byte using function ^Decode_ModRM_SIB_Value()^ gives it to the function ^Decode_ModRM_SIB_Address()^ which only + reads the Mode, and Base register for the address, and then decodes the SIB byte if base register is "100" binary in value. + does not use the Register value in the ModR/M because the register can also be used as a group opcode used by the + function ^DecodeOpcode()^, or uses a different register in single size with a different address pointer. + -------------------------------------------------------------------------------------------------------------------------*/ + new Operand(), //ModR/M address if used. + /*------------------------------------------------------------------------------------------------------------------------- + The third operand that is decoded if used is for the ModR/M reg bits. + Uses the already decoded byte from ^Decode_ModRM_SIB_Value()^ gives the three bit reg value to the function ^DecodeRegValue()^. + The ModR/M address, and reg are usually used together, but can also change direction in the encoding string. + -------------------------------------------------------------------------------------------------------------------------*/ + new Operand(), //ModR/M reg bits if used. + /*------------------------------------------------------------------------------------------------------------------------- + The fourth operand that is decoded in sequence is the first Immediate input if used. + The function ^DecodeImmediate()^ starts reading bytes as a number for input to instruction. + -------------------------------------------------------------------------------------------------------------------------*/ + new Operand(), //First Immediate if used. + /*------------------------------------------------------------------------------------------------------------------------- + The fifth operand that is decoded in sequence is the second Immediate input if used. + The function ^DecodeImmediate()^ starts reading bytes as a number for input to instruction. + -------------------------------------------------------------------------------------------------------------------------*/ + new Operand(), //Second Immediate if used (Note that the instruction "Enter" uses two immediate inputs). + /*------------------------------------------------------------------------------------------------------------------------- + The sixth operand that is decoded in sequence is the third Immediate input if used. + The function ^DecodeImmediate()^ starts reading bytes as a number for input to instruction. + -------------------------------------------------------------------------------------------------------------------------*/ + new Operand(), //Third Immediate if used (Note that the Larrabee vector instructions can use three immediate inputs). + /*------------------------------------------------------------------------------------------------------------------------- + Vector adjustment codes allow the selection of the vector register value that is stored into variable + VectorRegister that applies to the selected SSE instruction that is read after that uses it. + The adjusted vector value is given to the function ^DecodeRegValue()^. + -------------------------------------------------------------------------------------------------------------------------*/ + new Operand(), //Vector register if used. And if vector adjustments are applied to the SSE instruction. + /*------------------------------------------------------------------------------------------------------------------------- + Immediate Register encoding if used. + During the decoding of the immediate operands the ^DecodeImmediate()^ function stores the read IMM into an variable called + IMMValue. The upper four bits of IMMValue is given to the input RValue to the function ^DecodeRegValue()^. + -------------------------------------------------------------------------------------------------------------------------*/ + new Operand(), //Immediate Register encoding if used. + /*------------------------------------------------------------------------------------------------------------------------- + It does not matter which order the explicit operands decode as they do not require reading another byte after the opcode. + Explicit operands are selected internally in the cpu for instruction codes that only use one register, or pointer, or number input. + -------------------------------------------------------------------------------------------------------------------------*/ + new Operand(), //Explicit Operand one. + new Operand(), //Explicit Operand two. + new Operand(), //Explicit Operand three. + new Operand() //Explicit Operand four. +]; + +/*------------------------------------------------------------------------------------------------------------------------- +SizeAttrSelect controls the General arithmetic extended sizes "8/16/32/64", and SIMD Vector register extended sizes "128/256/512/1024". +--------------------------------------------------------------------------------------------------------------------------- +General arithmetic sizes "8/16/32/64" change by operand override which makes all operands go 16 bit. +The width bit which is in the REX prefix makes operands go all 64 bits the changes depend on the instructions adjustable size. +The value system goes as follows: 0=8, or 16, then 1=Default32, then 2=Max64. Smallest to largest in order. +Changeable from prefixes. Code 66 hex is operand override, 48 hex is the REX.W setting. By default operands are 32 bit +in size in both 32 bit mode, and 64 bit modes so by default the Size attribute setting is 1 in value so it lines up with 32. +In the case of fewer size settings the size system aligns in order to the correct prefix settings. +--------------------------------------------------------------------------------------------------------------------------- +If in 16 bit mode the 16 bit operand size trades places with 32, so when the operand override is used it goes from 16 to 32. +Also in 32 bit mode any size that is 64 changes to 32, but except for operands that do not use the BySize system. +--------------------------------------------------------------------------------------------------------------------------- +During Vector instructions size settings "128/256/512" use the SizeAttrSelect as the vector length setting as a 0 to 3 value from +smallest to largest Note 1024 is Reserved the same system used for General arithmetic sizes "8/16/32/64" that go in order. +If an operand is used that is 32/64 in size the Width bit allows to move between Sizes 32/64 separately. +--------------------------------------------------------------------------------------------------------------------------- +Used by the function ^GetOperandSize()^ which uses a fast base 2 logarithm system. +The function ^DecodeOpcode()^ also uses the current size setting for operation names that change name by size, Or +In vector instructions the select instruction by size is used to Add additional instructions between the width bit (W=0), and (W=1). +-------------------------------------------------------------------------------------------------------------------------*/ + +var SizeAttrSelect = 1; + +/*------------------------------------------------------------------------------------------------------------------------- +The Width bit is used in combination with SizeAttrSelect only with Vector instructions. +-------------------------------------------------------------------------------------------------------------------------*/ + +var WidthBit = 0; + +/*------------------------------------------------------------------------------------------------------------------------- +Pointer size plus 16 bit's used by FAR JUMP and other instructions. +For example FAR JUMP is size attributes 16/32/64 normally 32 is the default size, but it is 32+16=48 FWORD PTR. +In 16 bit CPU mode the FAR jump defaults to 16 bits, but because it is a far jump it is 16+16=32 which is DWORD PTR. +Set by the function ^DecodeOperandString()^ for if the ModR/M operand type is far pointer address. +--------------------------------------------------------------------------------------------------------------------------- +Used by the function ^Decode_ModRM_SIB_Address()^. +-------------------------------------------------------------------------------------------------------------------------*/ + +var FarPointer = 0; + +/*------------------------------------------------------------------------------------------------------------------------- +AddressOverride is hex opcode 67 then when used with any operation that uses the ModR/M in address mode the ram address +goes down one in bit mode. Switches 64 address mode to 32 bit address mode, and in 32 bit mode the address switches to +16 bit address mode which uses a completely different ModR/M format. When in 16 bit mode the address switches to 32 bit. +Set true when Opcode 67 is read by ^DecodePrefixAdjustments()^ which effects the next opcode that is not a prefix opcode +then is set false after instruction decodes. +--------------------------------------------------------------------------------------------------------------------------- +Used by the function ^Decode_ModRM_SIB_Address()^. +-------------------------------------------------------------------------------------------------------------------------*/ + +var AddressOverride = false; + +/*------------------------------------------------------------------------------------------------------------------------- +Extended Register value changes by the "R bit" in the REX prefix, or by the "Double R bit" settings in EVEX Extension +which makes the Register operand reach to a max value of 32 registers along the register array. +Normally the Register selection in ModR/M, is limited to three bits in binary 000 = 0 to 111 = 7. +RegExtend stores the two binary bits that are added onto the three bit register selection. +--------------------------------------------------------------------------------------------------------------------------- +When RegExtend is 00,000 the added lower three bits is 00,000 = 0 to 00,111 = 7. +When RegExtend is 01,000 the added lower three bits is 01,000 = 8 to 01,111 = 15. +When RegExtend is 10,000 the added lower three bits is 10,000 = 16 to 10,111 = 23. +When RegExtend is 11,000 the added lower three bits is 11,000 = 24 to 10,111 = 31. +--------------------------------------------------------------------------------------------------------------------------- +The Register expansion bits make the binary number from a 3 bit number to a 5 bit number by combining the EVEX.R'R bits. +The REX opcode, and EVEX opcode 62 hex are decoded with function ^DecodePrefixAdjustments()^ which contain R bit settings. +--------------------------------------------------------------------------------------------------------------------------- +Used by function ^DecodeRegValue()^. +-------------------------------------------------------------------------------------------------------------------------*/ + +var RegExtend = 0; + +/*------------------------------------------------------------------------------------------------------------------------- +The base register is used in ModR/M address mode, and Register mode and can be extended to 8 using the "B bit" setting +from the REX prefix, or VEX Extension, and EVEX Extension, however in EVEX the tow bits "X, and B" are used together to +make the base register reach 32 in register value if the ModR/M is in Register mode. +--------------------------------------------------------------------------------------------------------------------------- +The highest the Base Register can be extended is from a 3 bit number to a 5 bit number. +--------------------------------------------------------------------------------------------------------------------------- +Used by the function ^Decode_ModRM_SIB_Address()^. +-------------------------------------------------------------------------------------------------------------------------*/ + +var BaseExtend = 0; + +/*------------------------------------------------------------------------------------------------------------------------- +The index register is used in ModR/M memory address mode if the base register is "100" bin in the ModR/M which sets SIB mode. +The Index register can be extended to 8 using the "X bit" setting when the Index register is used. +The X bit setting is used in the REX prefix settings, and also the VEX Extension, and EVEX Extension. +--------------------------------------------------------------------------------------------------------------------------- +The highest the Index Register can be extended is from a 3 bit number to a 4 bit number. +--------------------------------------------------------------------------------------------------------------------------- +Used by the function ^Decode_ModRM_SIB_Address()^. +-------------------------------------------------------------------------------------------------------------------------*/ + +var IndexExtend = 0; + +/*------------------------------------------------------------------------------------------------------------------------- +SegOverride is the bracket that is added onto the start of the decoded address it is designed this way so that if a segment +Override Prefix is used it is stored with the segment. +--------------------------------------------------------------------------------------------------------------------------- +used by function ^Decode_ModRM_SIB_Address()^. +-------------------------------------------------------------------------------------------------------------------------*/ + +var SegOverride = "["; + +/*------------------------------------------------------------------------------------------------------------------------- +This may seem confusing, but the 8 bit high low registers are used all in "low order" when any REX prefix is used. +Set RexActive true when the REX Prefix is used, for the High, and low Register separation. +--------------------------------------------------------------------------------------------------------------------------- +Used by function ^DecodeRegValue()^. +-------------------------------------------------------------------------------------------------------------------------*/ + +var RexActive = 0; + +/*------------------------------------------------------------------------------------------------------------------------- +The SIMD value is set according to SIMD MODE by prefixes (none, 66, F2, F3), or by the value of VEX.pp, and EVEX.pp. +Changes the selected instruction in ^DecodeOpcode()^ only for SSE vector opcodes that have 4 possible instructions in +one instruction for the 4 modes otherwise 66 is Operand override, and F2 is REPNE, and F3 is REP prefix adjustments. +By reusing some of the already used Prefix adjustments more opcodes did not have to be sacrificed. +--------------------------------------------------------------------------------------------------------------------------- +SIMD is set 00 in binary by default, SIMD is set 01 in binary when opcode 66 is read by ^DecodePrefixAdjustments()^, +SIMD is set 10 in binary when opcode F2 is read by ^DecodePrefixAdjustments()^, and SIMD is set 11 in binary when F3 is read +by ^DecodePrefixAdjustments()^. +--------------------------------------------------------------------------------------------------------------------------- +The VEX, and EVEX adjustment codes contain SIMD mode adjustment bits in which each code that is used to change the mode go +in the same order as SIMD. This allows SIMD to be set directly by the VEX.pp, and EVEX.pp bit value. +--------------------------------------------------------------------------------------------------------------------------- +VEX.pp = 00b (None), 01b (66h), 10b (F2h), 11b (F3h) +EVEX.pp = 00b (None), 01b (66h), 10b (F2h), 11b (F3h) +--------------------------------------------------------------------------------------------------------------------------- +Used by the function ^DecodeOpcode()^. +-------------------------------------------------------------------------------------------------------------------------*/ + +var SIMD = 0; + +/*------------------------------------------------------------------------------------------------------------------------- +Vect is set true during the decoding of an instruction code. If the instruction is an Vector instruction 4 in length for +the four modes then Vect is set true. When Vect is set true the Function ^Decode_ModRM_SIB_Address()^ Will decode the +ModR/M as a Vector address. +--------------------------------------------------------------------------------------------------------------------------- +Set By function ^DecodeOpcode()^, and used by function ^Decode_ModRM_SIB_Address()^. +-------------------------------------------------------------------------------------------------------------------------*/ + +var Vect = false; + +/*------------------------------------------------------------------------------------------------------------------------- +In AVX512 The width bit can be ignored, or used. The width bit relates to the SIMD mode for size of the numbers in the vector. +Modes N/A, F3 are 32 bit, while 66, F2 are 64 bit. The width bit has to be set for the extend data size for +most AVX512 instructions unless the width bit is ignored. Some AVX512 vectors can also broadcast round to there extend data size +controlled by the width bit extend size and SIMD mode. +-------------------------------------------------------------------------------------------------------------------------*/ + +var IgnoresWidthbit = false; + +/*------------------------------------------------------------------------------------------------------------------------- +The VSIB setting is used for vectors that multiply the displacement by the Element size of the vectors, and use index as an vector pointer. +-------------------------------------------------------------------------------------------------------------------------*/ + +var VSIB = false; + +/*------------------------------------------------------------------------------------------------------------------------- +EVEX also has error suppression modes {ER} controlled by vector length, and if the broadcast round is active in register mode, +or {SAE} suppresses all exceptions then it can not change rounding mode by vector length. +MVEX also has error suppression modes {ER} controlled by conversion mode, and if the MVEX.E bit is set to round in register mode, +or {SAE} suppresses all exceptions then it can not change rounding mode by vector length. +L1OM vectors use {ER} as round control, and {SEA} as exponent adjustment. +-------------------------------------------------------------------------------------------------------------------------*/ + +var RoundingSetting = 0; //1 = SAE, and 2 = ER. + +/*------------------------------------------------------------------------------------------------------------------------- +The MVEX prefix can Integer convert, and Float convert, and Broadcast round using Swizzle. +The EVEX prefix can only Broadcast round using an "b" control which sets the Broadcast round option for Swizzle. +-------------------------------------------------------------------------------------------------------------------------*/ + +var Swizzle = false; //Swizzle based instruction. If false then Up, or Down conversion. +var Up = false; //Only used if Swizzle is false. If set false then it is an down conversion. +var Float = false; //If False Integer data is used. +var VectS = 0x00; //Stores the three vector settings Swizzle, Up, and Float, for faster comparison to special cases. + +/*------------------------------------------------------------------------------------------------------------------------- +The Extension is set 2 during opcode 62 hex for EVEX in which the ^DecodePrefixAdjustments()^ decodes the settings, but if +the bit that must be set 0 for EVEX is set 1 then Extension is set 3 for MVEX. +The Extension is set 1 during opcodes C4, and C5 hex in which the ^DecodePrefixAdjustments()^ decodes the settings for the VEX prefixes. +--------------------------------------------------------------------------------------------------------------------------- +An instruction that has 4 opcode combinations based on SIMD can use another 4 in length separator in the select SIMD mode +which selects the opcode based on extension used. This is used to separate codes that can be Vector adjusted, and not. +Some codes can only be used in VEX, but not EVEX, and not all EVEX can be MVEX encoded as the EVEX versions were introduced after, +also MMX instruction can not be used with vector adjustments. +--------------------------------------------------------------------------------------------------------------------------- +By default Extension is 0 for decoding instructions normally. +--------------------------------------------------------------------------------------------------------------------------- +Used by function ^DecodeOpcode()^ adds the letter "V" to the instruction name to show it uses Vector adjustments. +When the Function ^DecodeOpcode()^ completes if Vect is not true and an Extension is active the instruction is invalid. +Used By function ^DecodeOperandString()^ which allows the Vector operand to be used if existent in the operand string. +-------------------------------------------------------------------------------------------------------------------------*/ + +var Extension = 0; + +/*------------------------------------------------------------------------------------------------------------------------- +MVEX/EVEX conversion modes. MVEX can directly set the conversion mode between float, or integer, to broadcast round using option bits. +The EVEX Extension only has the broadcast rounding control. In which some instructions support "]{1to16}" (B32), or "]{1to8}" (B64) +Based on the data size using the width bit setting. EVEX only can use the 1ToX broadcast round control. +--------------------------------------------------------------------------------------------------------------------------- +Used by function ^Decode_ModRM_SIB_Address()^. +-------------------------------------------------------------------------------------------------------------------------*/ + +var ConversionMode = 0; + +/*------------------------------------------------------------------------------------------------------------------------- +MVEX/EVEX rounding modes. In EVEX if the ModR/M is used in register mode and Bround Is active. +The EVEX Error Suppression type is set by the RoundingSetting {ER}, and {SAE} settings for if the instruction supports it. +The MVEX version allows the use of both rounding modes. MVEX can select the rounding type using option bits if the +"MVEX.E" control is set in an register to register operation. +--------------------------------------------------------------------------------------------------------------------------- +The function ^Decode_ModRM_SIB_Address()^ sets RoundMode. +The function DecodeInstruction() adds the error Suppression to the end of the instruction. +-------------------------------------------------------------------------------------------------------------------------*/ + +var RoundMode = 0; + +/*------------------------------------------------------------------------------------------------------------------------- +MVEX/EVEX register round modes. +--------------------------------------------------------------------------------------------------------------------------- +Some instructions use SAE which suppresses all errors, but if an instruction uses {er} the 4 others are used by vector length. +-------------------------------------------------------------------------------------------------------------------------*/ + +const RoundModes = [ + "","","","","","","","", //First 8 No rounding mode. + /*------------------------------------------------------------------------------------------------------------------------- + MVEX/EVEX round Modes {SAE} Note MVEX (1xx) must be set 4 or higher, while EVEX uses upper 4 in rounding mode by vector length. + -------------------------------------------------------------------------------------------------------------------------*/ + ", {Error}", ", {Error}", ", {Error}", ", {Error}", ", {SAE}", ", {SAE}", ", {SAE}", ", {SAE}", + /*------------------------------------------------------------------------------------------------------------------------- + L1OM/MVEX/EVEX round modes {ER}. L1OM uses the first 4, and EVEX uses the upper 4, while MVEX can use all 8. + -------------------------------------------------------------------------------------------------------------------------*/ + ", {RN}", ", {RD}", ", {RU}", ", {RZ}", ", {RN-SAE}", ", {RD-SAE}", ", {RU-SAE}", ", {RZ-SAE}", + /*------------------------------------------------------------------------------------------------------------------------- + MVEX/EVEX round modes {SAE}, {ER} Both rounding modes can not possibly be set both at the same time. + -------------------------------------------------------------------------------------------------------------------------*/ + "0B", "4B", "5B", "8B", "16B", "24B", "31B", "32B" //L1OM exponent adjustments. +]; + +/*------------------------------------------------------------------------------------------------------------------------- +L1OM/MVEX register swizzle modes. When an swizzle operation is done register to register. +Note L1OM skips swizzle type DACB thus the last swizzle type is an repeat of the DACB as the last L1OM swizzle. +-------------------------------------------------------------------------------------------------------------------------*/ + +const RegSwizzleModes = [ "", "CDAB", "BADC", "DACB", "AAAA", "BBBB", "CCCC", "DDDD", "DACB" ]; + +/*------------------------------------------------------------------------------------------------------------------------- +EVEX does not support conversion modes. Only broadcast round of 1To16, or 1To8 controlled by the data size. +--------------------------------------------------------------------------------------------------------------------------- +MVEX.sss permits the use of conversion types by value without relating to the Swizzle conversion type. +However During Up, and Down conversion MVEX does not allow Broadcast round control. +--------------------------------------------------------------------------------------------------------------------------- +L1OM.CCCCC can only be used with Up, and Down conversion data types, and L1OM.sss can only be used with broadcast round. +L1OM.SSS can only be used with swizzle conversions. +--------------------------------------------------------------------------------------------------------------------------- +The Width bit relates to the data size of broadcast round as 32 bit it is X=16, and 64 bit number are larger and are X=8 in the "(1, or 4)ToX". +The Width bit also relates to the Up conversion, and down conversion data size. +Currently in K1OM, and L1OM there are no 64 bit Up, or Down conversions. +--------------------------------------------------------------------------------------------------------------------------- +Note 66 hex is used as data size 64 in L1OM. +--------------------------------------------------------------------------------------------------------------------------- +The element to grab from the array bellow is calculated mathematically. +Note each element is an multiple of 2 in which the first element is the 32 size, and second element is 64 size. +Lastly the elements are in order to the "CCCCC" value, and "SSS" value times 2, and plus 1 if 64 data size. +-------------------------------------------------------------------------------------------------------------------------*/ + +const ConversionModes = [ + //------------------------------------------------------------------------ + "", "", //Not used. + //------------------------------------------------------------------------ + "1To16", "1To8", //Settable as L1OM.sss/MVEX.sss = 001. Settable using EVEX broadcast round. + "4To16", "4To8", //Settable as L1OM.sss/MVEX.sss = 010. Settable using EVEX broadcast round. + //------------------------------------------------------------------------ + "Float16", "Error", //Settable as "MVEX.sss = 011", and "L1OM.sss = 110 , L1OM.CCCCC = 00001". + //------------------------------------------------------------------------ + "Float16RZ", "Error", //Settable only as L1OM.CCCCC = 00010. + //------------------------------------------------------------------------ + "SRGB8", "Error", //Settable only as L1OM.CCCCC = 00011. + /*------------------------------------------------------------------------ + MVEX/L1OM Up conversion, and down conversion types. + ------------------------------------------------------------------------*/ + "UInt8", "Error", //Settable as L1OM.sss/MVEX.sss = 100, and L1OM.CCCCC = 00100. + "SInt8", "Error", //Settable as L1OM.sss/MVEX.sss = 101, and L1OM.CCCCC = 00101. + //------------------------------------------------------------------------ + "UNorm8", "Error", //Settable as L1OM.sss = 101, or L1OM.CCCCC = 00110. + "SNorm8", "Error", //Settable as L1OM.CCCCC = 00111. + //------------------------------------------------------------------------ + "UInt16", "Error", //Settable as L1OM.sss/MVEX.sss = 110, and L1OM.CCCCC = 01000 + "SInt16", "Error", //Settable as L1OM.sss/MVEX.sss = 111, and L1OM.CCCCC = 01001 + //------------------------------------------------------------------------ + "UNorm16", "Error", //Settable as L1OM.CCCCC = 01010. + "SNorm16", "Error", //Settable as L1OM.CCCCC = 01011. + "UInt8I", "Error", //Settable as L1OM.CCCCC = 01100. + "SInt8I", "Error", //Settable as L1OM.CCCCC = 01101. + "UInt16I", "Error", //Settable as L1OM.CCCCC = 01110. + "SInt16I", "Error", //Settable as L1OM.CCCCC = 01111. + /*------------------------------------------------------------------------ + L1OM Up conversion, and field conversion. + ------------------------------------------------------------------------*/ + "UNorm10A", "Error", //Settable as L1OM.CCCCC = 10000. Also Usable as Integer Field control. + "UNorm10B", "Error", //Settable as L1OM.CCCCC = 10001. Also Usable as Integer Field control. + "UNorm10C", "Error", //Settable as L1OM.CCCCC = 10010. Also Usable as Integer Field control. + "UNorm2D", "Error", //Settable as L1OM.CCCCC = 10011. Also Usable as Integer Field control. + //------------------------------------------------------------------------ + "Float11A", "Error", //Settable as L1OM.CCCCC = 10100. Also Usable as Float Field control. + "Float11B", "Error", //Settable as L1OM.CCCCC = 10101. Also Usable as Float Field control. + "Float10C", "Error", //Settable as L1OM.CCCCC = 10110. Also Usable as Float Field control. + "Error", "Error", //Settable as L1OM.CCCCC = 10111. Also Usable as Float Field control. + /*------------------------------------------------------------------------ + Unused Conversion modes. + ------------------------------------------------------------------------*/ + "Error", "Error", //Settable as L1OM.CCCCC = 11000. + "Error", "Error", //Settable as L1OM.CCCCC = 11001. + "Error", "Error", //Settable as L1OM.CCCCC = 11010. + "Error", "Error", //Settable as L1OM.CCCCC = 11011. + "Error", "Error", //Settable as L1OM.CCCCC = 11100. + "Error", "Error", //Settable as L1OM.CCCCC = 11101. + "Error", "Error", //Settable as L1OM.CCCCC = 11110. + "Error", "Error" //Settable as L1OM.CCCCC = 11111. +]; + +/*------------------------------------------------------------------------------------------------------------------------- +The VEX Extension, and MVEX/EVEX Extension have an Vector register selection built in for Vector operation codes that use the +vector register. This operand is only read in the "operand string" if an VEX, or EVEX prefix was decoded by the +function ^DecodePrefixAdjustments()^, and making Extension 1 for VEX, or 2 for EVEX instead of 0 by default. +During a VEX, or EVEX version of the SSE instruction the vector bits are a 4 bit binary value of 0 to 15, and are extended +in EVEX and MVEX to 32 by adding the EVEX.V, or MVEX.V bit to the vector register value. +--------------------------------------------------------------------------------------------------------------------------- +Used with the function ^DecodeRegValue()^ to decode the Register value. +-------------------------------------------------------------------------------------------------------------------------*/ + +var VectorRegister = 0; + +/*------------------------------------------------------------------------------------------------------------------------- +The MVEX/EVEX Extension has an mask Register value selection for {K0-K7} mask to destination operand. +The K mask register is always displayed to the destination operand in any Vector instruction used with MVEX/EVEX settings. +--------------------------------------------------------------------------------------------------------------------------- +The {K} is added onto the first operand in OpNum before returning the decoded operands from the function ^DecodeOperands()^. +-------------------------------------------------------------------------------------------------------------------------*/ + +var MaskRegister = 0; + +/*------------------------------------------------------------------------------------------------------------------------- +The EVEX Extension has an zero mask bit setting for {z} zeroing off the registers. +--------------------------------------------------------------------------------------------------------------------------- +The {z} is added onto the first operand in OpNum before returning the decoded operands from the function ^DecodeOperands()^. +--------------------------------------------------------------------------------------------------------------------------- +In L1OM/MVEX this is used as the {NT}/{EH} control which when used with an memory address that supports it will prevent +the data from going into the cache memory. Used as Hint control in the function ^Decode_ModRM_SIB_Address()^. +-------------------------------------------------------------------------------------------------------------------------*/ + +var HInt_ZeroMerg = false; + +/*------------------------------------------------------------------------------------------------------------------------- +Some operands use the value of the Immediate operand as an opcode, or upper 4 bits as Another register, or condition codes. +The Immediate is decoded normally, but this variable stores the integer value of the first IMM byte for the other byte +encodings if used. +--------------------------------------------------------------------------------------------------------------------------- +Used By the function ^DecodeOpcode()^ for condition codes, and by ^DecodeOperands()^ using the upper four bits as a register. +-------------------------------------------------------------------------------------------------------------------------*/ + +var IMMValue = 0; + +/*------------------------------------------------------------------------------------------------------------------------- +Prefix G1, and G2 are used with Intel HLE, and other prefix codes such as repeat the instruction Codes F2, F3 which can be +applied to any instruction unless it is an SIMD instruction which uses F2, and F3 as the SIMD Mode. +-------------------------------------------------------------------------------------------------------------------------*/ + +var PrefixG1 = "", PrefixG2 = ""; + +/*------------------------------------------------------------------------------------------------------------------------- +Intel HLE is used with basic arithmetic instructions like Add, and subtract, and shift operations. +Intel HLE instructions replace the Repeat F2, and F3, also lock F0 with XACQUIRE, and XRELEASE. +--------------------------------------------------------------------------------------------------------------------------- +This is used by function ^DecodeInstruction()^. +-------------------------------------------------------------------------------------------------------------------------*/ + +var XRelease = false, XAcquire = false; + +/*------------------------------------------------------------------------------------------------------------------------- +Intel HLE flip "G1 is used as = REP (XACQUIRE), or RENP (XRELEASE)", and "G2 is used as = LOCK" if the lock prefix was +not read first then G1, and G2 flip. Also XACQUIRE, and XRELEASE replace REP, and REPNE if the LOCK prefix is used with +REP, or REPNE if the instruction supports Intel HLE. +--------------------------------------------------------------------------------------------------------------------------- +This is used by function ^DecodeInstruction()^. +-------------------------------------------------------------------------------------------------------------------------*/ + +var HLEFlipG1G2 = false; + +/*------------------------------------------------------------------------------------------------------------------------- +Replaces segment overrides CS, and DS with HT, and HNT prefix for Branch taken and not taken used by jump instructions. +--------------------------------------------------------------------------------------------------------------------------- +This is used by functions ^Decode_ModRM_SIB_Address()^, and ^DecodeInstruction()^. +-------------------------------------------------------------------------------------------------------------------------*/ + +var HT = false; + +/*------------------------------------------------------------------------------------------------------------------------- +Instruction that support MPX replace the REPNE prefix with BND if operation is a MPX instruction. +--------------------------------------------------------------------------------------------------------------------------- +This is used by function ^DecodeInstruction()^. +-------------------------------------------------------------------------------------------------------------------------*/ + +var BND = false; + +/*------------------------------------------------------------------------------------------------------------------------- +The Invalid Instruction variable is very important as some bit settings in vector extensions create invalid operation codes. +Also some opcodes are invalid in different cpu bit modes. +--------------------------------------------------------------------------------------------------------------------------- +Function ^DecodePrefixAdjustments()^ Set the Invalid Opcode if an instruction or prefix is compared that is invalid for CPU bit mode. +The function ^DecodeInstruction()^ returns an invalid instruction if Invalid Operation is used for CPU bit mode. +-------------------------------------------------------------------------------------------------------------------------*/ + +var InvalidOp = false; + +/*------------------------------------------------------------------------------------------------------------------------- +The Register array holds arrays in order from 0 though 7 for the GetOperandSize function Which goes by Prefix size settings, +and SIMD Vector length instructions using the adjusted variable SizeAttrSelect. +--------------------------------------------------------------------------------------------------------------------------- +Used by functions ^DecodeRegValue()^, ^Decode_ModRM_SIB_Address()^. +-------------------------------------------------------------------------------------------------------------------------*/ + +const REG = [ + /*------------------------------------------------------------------------------------------------------------------------- + REG array Index 0 Is used only if the value returned from the GetOperandSize is 0 in value which is the 8 bit general use + Arithmetic registers names. Note that these same registers can be made 16 bit across instead of using just the first 8 bit + in size it depends on the instruction codes extension size. + --------------------------------------------------------------------------------------------------------------------------- + The function ^GetOperandSize()^ takes the size value the instruction uses for it's register selection by looking up binary + bit positions in the size value in log 2. Different instructions can be adjusted to different sizes using the operand size + override adjustment code, or width bit to adjust instructions to 64 in size introduced by AMD64, and EM64T in 64 bit computers. + --------------------------------------------------------------------------------------------------------------------------- + REG array Index 0 is the first 8 bit's of Arithmetic registers, however they can be used in both high, and low order in + which the upper 16 bit's is used as 8 bit's for H (High part), and the first 8 bits is L (LOW part) unless the rex prefix is + used then the first 8 bit's is used by all general use arithmetic registers. Because of this the array is broken into two + name listings that is used with the "RValue" number given to the function ^DecodeRegValue()^. + -------------------------------------------------------------------------------------------------------------------------*/ + [ + /*------------------------------------------------------------------------------------------------------------------------- + 8 bit registers without any rex prefix active is the normal low byte to high byte order of the + first 4 general use registers "A, C, D, and B" using 8 bits. + -------------------------------------------------------------------------------------------------------------------------*/ + [ + //Registers 8 bit names without any rex prefix index 0 to 7. + "AL", "CL", "DL", "BL", "AH", "CH", "DH", "BH" + ], + /*------------------------------------------------------------------------------------------------------------------------- + 8 bit registers with any rex prefix active uses all 15 registers in low byte order. + -------------------------------------------------------------------------------------------------------------------------*/ + [ + //Registers 8 bit names with any rex prefix index 0 to 7. + "AL", "CL", "DL", "BL", "SPL", "BPL", "SIL", "DIL", + /*------------------------------------------------------------------------------------------------------------------------- + Registers 8 bit names Extended using the REX.R extend setting in the Rex prefix, or VEX.R bit, or EVEX.R. + What ever RegExtend is set based on prefix settings is added to the select Reg Index + -------------------------------------------------------------------------------------------------------------------------*/ + "R8B", "R9B", "R10B", "R11B", "R12B", "R13B", "R14B", "R15B" + ] + ], + /*------------------------------------------------------------------------------------------------------------------------- + REG array Index 1 Is used only if the value returned from the GetOperandSize function is 1 in value in which bellow is the + general use Arithmetic register names 16 in size. + -------------------------------------------------------------------------------------------------------------------------*/ + [ + //Registers 16 bit names index 0 to 15. + "AX", "CX", "DX", "BX", "SP", "BP", "SI", "DI", "R8W", "R9W", "R10W", "R11W", "R12W", "R13W", "R14W", "R15W" + ], + /*------------------------------------------------------------------------------------------------------------------------- + REG array Index 2 Is used only if the value from the GetOperandSize function is 2 in value in which bellow is the + general use Arithmetic register names 32 in size. + -------------------------------------------------------------------------------------------------------------------------*/ + [ + //Registers 32 bit names index 0 to 15. + "EAX", "ECX", "EDX", "EBX", "ESP", "EBP", "ESI", "EDI", "R8D", "R9D", "R10D", "R11D", "R12D", "R13D", "R14D", "R15D" + ], + /*------------------------------------------------------------------------------------------------------------------------- + REG array Index 3 Is used only if the value returned from the GetOperandSize function is 3 in value in which bellow is the + general use Arithmetic register names 64 in size. + -------------------------------------------------------------------------------------------------------------------------*/ + [ + //general use Arithmetic registers 64 names index 0 to 15. + "RAX", "RCX", "RDX", "RBX", "RSP", "RBP", "RSI", "RDI", "R8", "R9", "R10", "R11", "R12", "R13", "R14", "R15" + ], + /*------------------------------------------------------------------------------------------------------------------------- + REG array Index 4 SIMD registers 128 across in size names. The SIMD registers are used by the SIMD Vector math unit. + Used only if the value from the GetOperandSize function is 4 in value. + -------------------------------------------------------------------------------------------------------------------------*/ + [ + //Register XMM names index 0 to 15. + "XMM0", "XMM1", "XMM2", "XMM3", "XMM4", "XMM5", "XMM6", "XMM7", "XMM8", "XMM9", "XMM10", "XMM11", "XMM12", "XMM13", "XMM14", "XMM15", + /*------------------------------------------------------------------------------------------------------------------------- + Register XMM names index 16 to 31. + Note different bit settings in the EVEX prefixes allow higher Extension values in the Register Extend variables. + -------------------------------------------------------------------------------------------------------------------------*/ + "XMM16", "XMM17", "XMM18", "XMM19", "XMM20", "XMM21", "XMM22", "XMM23", "XMM24", "XMM25", "XMM26", "XMM27", "XMM28", "XMM29", "XMM30", "XMM31" + ], + /*------------------------------------------------------------------------------------------------------------------------- + REG array Index 5 SIMD registers 256 across in size names. + Used only if the value from the GetOperandSize function is 5 in value. Set by vector length setting. + -------------------------------------------------------------------------------------------------------------------------*/ + [ + //Register YMM names index 0 to 15. + "YMM0", "YMM1", "YMM2", "YMM3", "YMM4", "YMM5", "YMM6", "YMM7", "YMM8", "YMM9", "YMM10", "YMM11", "YMM12", "YMM13", "YMM14", "YMM15", + /*------------------------------------------------------------------------------------------------------------------------- + Register YMM names index 16 to 31. + Note different bit settings in the EVEX prefixes allow higher Extension values in the Register Extend variables. + -------------------------------------------------------------------------------------------------------------------------*/ + "YMM16", "YMM17", "YMM18", "YMM19", "YMM20", "YMM21", "YMM22", "YMM23", "YMM24", "YMM25", "YMM26", "YMM27", "YMM28", "YMM29", "YMM30", "YMM31" + ], + /*------------------------------------------------------------------------------------------------------------------------- + REG array Index 6 SIMD registers 512 across in size names. + Used only if the value from the GetOperandSize function is 6 in value. Set by Vector length setting. + -------------------------------------------------------------------------------------------------------------------------*/ + [ + //Register ZMM names index 0 to 15. + "ZMM0", "ZMM1", "ZMM2", "ZMM3", "ZMM4", "ZMM5", "ZMM6", "ZMM7", "ZMM8", "ZMM9", "ZMM10", "ZMM11", "ZMM12", "ZMM13", "ZMM14", "ZMM15", + /*------------------------------------------------------------------------------------------------------------------------- + Register ZMM names index 16 to 31. + Note different bit settings in the EVEX prefixes allow higher Extension values in the Register Extend variables. + -------------------------------------------------------------------------------------------------------------------------*/ + "ZMM16", "ZMM17", "ZMM18", "ZMM19", "ZMM20", "ZMM21", "ZMM22", "ZMM23", "ZMM24", "ZMM25", "ZMM26", "ZMM27", "ZMM28", "ZMM29", "ZMM30", "ZMM31" + ], + /*------------------------------------------------------------------------------------------------------------------------- + REG array Index 7 SIMD registers 1024 bit. The SIMD registers have not been made this long yet. + -------------------------------------------------------------------------------------------------------------------------*/ + [ + //Register unknowable names index 0 to 15. + "?MM0", "?MM1", "?MM2", "?MM3", "?MM4", "?MM5", "?MM6", "?MM7", "?MM8", "?MM9", "?MM10", "?MM11", "?MM12", "?MM13", "?MM14", "?MM15", + /*------------------------------------------------------------------------------------------------------------------------- + Register unknowable names index 16 to 31. + Note different bit settings in the EVEX prefixes allow higher Extension values in the Register Extend variables. + -------------------------------------------------------------------------------------------------------------------------*/ + "?MM16", "?MM17", "?MM18", "?MM19", "?MM20", "?MM21", "?MM22", "?MM23", "?MM24", "?MM25", "?MM26", "?MM27", "?MM28", "?MM29", "?MM30", "?MM31" + ], + /*------------------------------------------------------------------------------------------------------------------------- + The Registers bellow do not change size they are completely separate, thus are used for special purposes. These registers + are selected by using size as a value for the index instead instead of giving size to the function ^GetOperandSize()^. + --------------------------------------------------------------------------------------------------------------------------- + REG array Index 8 Segment Registers. + -------------------------------------------------------------------------------------------------------------------------*/ + [ + //Segment Registers names index 0 to 7 + "ES", "CS", "SS", "DS", "FS", "GS", "ST(-2)", "ST(-1)" + ], + /*------------------------------------------------------------------------------------------------------------------------- + REG array Index 9 Stack, and MM registers used by the X87 Float point unit. + -------------------------------------------------------------------------------------------------------------------------*/ + [ + //ST registers Names index 0 to 7 + //note these are used with the X87 FPU, but are aliased to MM in MMX SSE. + "ST(0)", "ST(1)", "ST(2)", "ST(3)", "ST(4)", "ST(5)", "ST(6)", "ST(7)" + ], + /*------------------------------------------------------------------------------------------------------------------------- + REG index 10 Intel MM qword technology MMX vector instructions. + --------------------------------------------------------------------------------------------------------------------------- + These can not be used with Vector length adjustment used in vector extensions. The MM register are the ST registers aliased + to MM register. Instructions that use these registers use the SIMD vector unit registers (MM), these are called the old + MMX vector instructions. When Intel added the SSE instructions to the SIMD math vector unit the new 128 bit XMM registers, + are added into the SIMD unit then they ware made longer in size 256, then 512 across in length, with 1024 (?MM Reserved) + In which the vector length setting was added to control there size though vector setting adjustment codes. Instruction + that can be adjusted by vector length are separate from the MM registers, but still use the same SIMD unit. Because of this + some Vector instruction codes can not be used with vector extension setting codes. + -------------------------------------------------------------------------------------------------------------------------*/ + [ + //Register MM names index 0 to 7 + "MM0", "MM1", "MM2", "MM3", "MM4", "MM5", "MM6", "MM7" + ], + /*------------------------------------------------------------------------------------------------------------------------- + REG Array Index 11 bound registers introduced with MPX instructions. + -------------------------------------------------------------------------------------------------------------------------*/ + [ + //BND0 to BND3,and CR0 to CR3 for two byte opcodes 0x0F1A,and 0x0F1B register index 0 to 7 + "BND0", "BND1", "BND2", "BND3", "CR0", "CR1", "CR2", "CR3" + ], + /*------------------------------------------------------------------------------------------------------------------------- + REG array Index 12 control registers depending on the values they are set changes the modes of the CPU. + -------------------------------------------------------------------------------------------------------------------------*/ + [ + //control Registers index 0 to 15 + "CR0", "CR1", "CR2", "CR3", "CR4", "CR5", "CR6", "CR7", "CR8", "CR9", "CR10", "CR11", "CR12", "CR13", "CR14", "CR15" + ], + /*------------------------------------------------------------------------------------------------------------------------- + REG array Index 13 Debug mode registers. + -------------------------------------------------------------------------------------------------------------------------*/ + [ + //debug registers index 0 to 15 + "DR0", "DR1", "DR2", "DR3", "DR4", "DR5", "DR6", "DR7", "DR8", "DR9", "DR10", "DR11", "DR12", "DR13", "DR14", "DR15" + ], + /*------------------------------------------------------------------------------------------------------------------------- + REG array Index 14 test registers. + -------------------------------------------------------------------------------------------------------------------------*/ + [ + //TR registers index 0 to 7 + "TR0", "TR1", "TR2", "TR3", "TR4", "TR5", "TR6", "TR7" + ], + /*------------------------------------------------------------------------------------------------------------------------- + REG Array Index 15 SIMD vector mask registers. + -------------------------------------------------------------------------------------------------------------------------*/ + [ + //K registers index 0 to 7, because of vector extensions it is repeated till last extension. + "K0", "K1", "K2", "K3", "K4", "K5", "K6", "K7","K0", "K1", "K2", "K3", "K4", "K5", "K6", "K7", + "K0", "K1", "K2", "K3", "K4", "K5", "K6", "K7","K0", "K1", "K2", "K3", "K4", "K5", "K6", "K7" + ], + /*------------------------------------------------------------------------------------------------------------------------- + REG Array Index 16 SIMD L1OM vector registers. + -------------------------------------------------------------------------------------------------------------------------*/ + [ + "V0", "V1", "V2", "V3", "V4", "V5", "V6", "V7", "V8", "V9", "V10", "V11", "V12", "V13", "V14", "V15", + "V16", "V17", "V18", "V19", "V20", "V21", "V22", "V23", "V24", "V25", "V26", "V27", "V28", "V29", "V30", "V31" + ] +]; + +/*------------------------------------------------------------------------------------------------------------------------- +RAM Pointer sizes are controlled by the GetOperandSize function which uses the Size Setting attributes for +the select pointer in the PTR array alignment. The REG array above uses the same alignment to the returned +size attribute except address pointers have far address pointers which are 16 bits plus there (8, or 16)/32/64 size attribute. +--------------------------------------------------------------------------------------------------------------------------- +Far pointers add 16 bits to the default pointer sizes. +16 bits become 16+16=32 DWORD, 32 bits becomes 32+16=48 FWORD, and 64+16=80 TBYTE. +The function GetOperandSize goes 0=8 bit, 1=16 bit, 2=32 bit, 3=64 bit, 4=128, 5=256, 6=512, 7=1024. +--------------------------------------------------------------------------------------------------------------------------- +The pointers are stored in doubles this is so every second position is each size setting. +So the Returned size attribute has to be in multiples of 2 each size multiplied by 2 looks like this. +(0*2=0)=8 bit, (1*2=2)=16 bit, (2*2=4)=32 bit, (3*2=6)=64 bit, (4*2=8)=128, (5*2=10)=256, (6*2=12)=512. +This is the same as moving by 2 this is why each pointer is in groups of two before the next line. +When the 16 bit shift is used for far pointers only plus one is added for the 16 bit shifted name of the pointer. +--------------------------------------------------------------------------------------------------------------------------- +Used by the function ^Decode_ModRM_SIB_Address()^. +-------------------------------------------------------------------------------------------------------------------------*/ + +const PTR = [ + /*------------------------------------------------------------------------------------------------------------------------- + Pointer array index 0 when GetOperandSize returns size 0 then times 2 for 8 bit pointer. + In plus 16 bit shift array index 0 is added by 1 making 0+1=1 no pointer name is used. + The blank pointer is used for instructions like LEA which loads the effective address. + -------------------------------------------------------------------------------------------------------------------------*/ + "BYTE PTR ","", + /*------------------------------------------------------------------------------------------------------------------------- + Pointer array index 2 when GetOperandSize returns size 1 then times 2 for 16 bit pointer alignment. + In plus 16 bit shift index 2 is added by 1 making 2+1=3 The 32 bit pointer name is used (mathematically 16+16=32). + -------------------------------------------------------------------------------------------------------------------------*/ + "WORD PTR ","DWORD PTR ", + /*------------------------------------------------------------------------------------------------------------------------- + Pointer array index 4 when GetOperandSize returns size 2 then multiply by 2 for index 4 for the 32 bit pointer. + In plus 16 bit shift index 4 is added by 1 making 4+1=5 the 48 bit Far pointer name is used (mathematically 32+16=48). + -------------------------------------------------------------------------------------------------------------------------*/ + "DWORD PTR ","FWORD PTR ", + /*------------------------------------------------------------------------------------------------------------------------- + Pointer array index 6 when GetOperandSize returns size 3 then multiply by 2 gives index 6 for the 64 bit pointer. + The Non shifted 64 bit pointer has two types the 64 bit vector "MM", and regular "QWORD" the same as the REG array. + In plus 16 bit shift index 6 is added by 1 making 6+1=7 the 80 bit TBYTE pointer name is used (mathematically 64+16=80). + -------------------------------------------------------------------------------------------------------------------------*/ + "QWORD PTR ","TBYTE PTR ", + /*------------------------------------------------------------------------------------------------------------------------- + Pointer array index 8 when GetOperandSize returns size 4 then multiply by 2 gives index 8 for the 128 bit Vector pointer. + In far pointer shift the MMX vector pointer is used. + MM is designed to be used when the by size system is false using index 9 for Pointer, and index 10 for Reg. + -------------------------------------------------------------------------------------------------------------------------*/ + "XMMWORD PTR ","MMWORD PTR ", + /*------------------------------------------------------------------------------------------------------------------------- + Pointer array index 10 when GetOperandSize returns size 5 then multiply by 2 gives index 10 for the 256 bit SIMD pointer. + In far pointer shift the OWORD pointer is used with the bounds instructions it is also designed to be used when the by size is set false same as MM. + -------------------------------------------------------------------------------------------------------------------------*/ + "YMMWORD PTR ","OWORD PTR ", + /*------------------------------------------------------------------------------------------------------------------------- + Pointer array index 12 when GetOperandSize returns size 6 then multiply by 2 gives index 12 for the 512 bit pointer. + In plus 16 bit shift index 12 is added by 1 making 12+1=13 there is no 528 bit pointer name (mathematically 5126+16=528). + -------------------------------------------------------------------------------------------------------------------------*/ + "ZMMWORD PTR ","ERROR PTR ", + /*------------------------------------------------------------------------------------------------------------------------- + Pointer array index 14 when GetOperandSize returns size 7 then multiply by 2 gives index 12 for the 1024 bit pointer. + In plus 16 bit shift index 14 is added by 1 making 12+1=13 there is no 1 bit pointer name (mathematically 5126+16=528). + -------------------------------------------------------------------------------------------------------------------------*/ + "?MMWORD PTR ","ERROR PTR "]; + +/*------------------------------------------------------------------------------------------------------------------------- +SIB byte scale Note the Scale bits value is the selected index of the array bellow only used under +a Memory address that uses the SIB Address mode which uses another byte for the address selection. +--------------------------------------------------------------------------------------------------------------------------- +used by the ^Decode_ModRM_SIB_Address function()^. +-------------------------------------------------------------------------------------------------------------------------*/ + +const scale = [ + "", //when scale bits are 0 in value no scale multiple is used + "*2", //when scale bits are 1 in value a scale multiple of times two is used + "*4", //when scale bits are 2 in value a scale multiple of times four is used + "*8" //when scale bits are 3 in value a scale multiple of times eight is used + ]; + +/*------------------------------------------------------------------------------------------------------------------------- +This function changes the Mnemonics array, for older instruction codes used by specific X86 cores that are under the same instruction codes. +--------------------------------------------------------------------------------------------------------------------------- +Input "type" can be any number 0 to 6. If the input is 0 it sets the mnemonics back to normal. +If input "type" is set 1 it will adjust the few conflicting mask instructions to the K1OM instruction names used by the knights corner processor. +If input "type" is set 2 it will adjust the mnemonic array to decode Larrabee instructions. +If input "type" is set 3 it will adjust the mnemonic array to decode Cyrix instructions which are now deprecated from the architecture. +If input "type" is set 4 it will adjust the mnemonic array to decode Geode instructions which are now deprecated from the architecture. +If input "type" is set 5 it will adjust the mnemonic array to decode Centaur instructions which are now deprecated from the architecture. +If input "type" is set 6 it will adjust the mnemonic array to decode instruction for the X86/486 CPU which conflict with the vector unit instructions with UMOV. +-------------------------------------------------------------------------------------------------------------------------*/ + +export function CompatibilityMode( type ) +{ + //Reset the changeable sections of the Mnemonics array, and operand encoding array. + + Mnemonics[0x062] = ["BOUND","BOUND",""]; + Mnemonics[0x110] = [["MOVUPS","MOVUPD","MOVSS","MOVSD"],["MOVUPS","MOVUPD","MOVSS","MOVSD"]]; + Mnemonics[0x111] = [["MOVUPS","MOVUPD","MOVSS","MOVSD"],["MOVUPS","MOVUPD","MOVSS","MOVSD"]]; + Mnemonics[0x112] = [["MOVLPS","MOVLPD","MOVSLDUP","MOVDDUP"],["MOVHLPS","???","MOVSLDUP","MOVDDUP"]]; + Mnemonics[0x113] = [["MOVLPS","MOVLPD","???","???"],"???"]; + Mnemonics[0x138] = ""; Mnemonics[0x139] = "???"; Mnemonics[0x13A] = ""; Mnemonics[0x13B] = "???"; Mnemonics[0x13C] = "???"; Mnemonics[0x13D] = "???"; Mnemonics[0x13F] = "???"; + Mnemonics[0x141] = [["CMOVNO",["KANDW","","KANDQ"],"",""],["CMOVNO",["KANDB","","KANDD"],"",""],"",""]; + Mnemonics[0x142] = [["CMOVB",["KANDNW","","KANDNQ"],"",""],["CMOVB",["KANDNB","","KANDND"],"",""],"",""]; + Mnemonics[0x144] = [["CMOVE",["KNOTW","","KNOTQ"],"",""],["CMOVE",["KNOTB","","KNOTD"],"",""],"",""]; + Mnemonics[0x145] = [["CMOVNE",["KORW","","KORQ"],"",""],["CMOVNE",["KORB","","KORD"],"",""],"",""]; + Mnemonics[0x146] = [["CMOVBE",["KXNORW","","KXNORQ"],"",""],["CMOVBE",["KXNORB","","KXNORD"],"",""],"",""]; + Mnemonics[0x147] = [["CMOVA",["KXORW","","KXORQ"],"",""],["CMOVA",["KXORB","","KXORD"],"",""],"",""]; + Mnemonics[0x150] = ["???",[["MOVMSKPS","MOVMSKPS","",""],["MOVMSKPD","MOVMSKPD","",""],"???","???"]]; + Mnemonics[0x151] = ["SQRTPS","SQRTPD","SQRTSS","SQRTSD"]; + Mnemonics[0x152] = [["RSQRTPS","RSQRTPS","",""],"???",["RSQRTSS","RSQRTSS","",""],"???"]; + Mnemonics[0x154] = ["ANDPS","ANDPD","???","???"]; + Mnemonics[0x155] = ["ANDNPS","ANDNPD","???","???"]; + Mnemonics[0x158] = [["ADDPS","ADDPS","ADDPS","ADDPS"],["ADDPD","ADDPD","ADDPD","ADDPD"],"ADDSS","ADDSD"]; + Mnemonics[0x159] = [["MULPS","MULPS","MULPS","MULPS"],["MULPD","MULPD","MULPD","MULPD"],"MULSS","MULSD"]; + Mnemonics[0x15A] = [["CVTPS2PD","CVTPS2PD","CVTPS2PD","CVTPS2PD"],["CVTPD2PS","CVTPD2PS","CVTPD2PS","CVTPD2PS"],"CVTSS2SD","CVTSD2SS"]; + Mnemonics[0x15B] = [[["CVTDQ2PS","","CVTQQ2PS"],"CVTPS2DQ",""],"???","CVTTPS2DQ","???"]; + Mnemonics[0x15C] = [["SUBPS","SUBPS","SUBPS","SUBPS"],["SUBPD","SUBPD","SUBPD","SUBPD"],"SUBSS","SUBSD"]; + Mnemonics[0x15D] = ["MINPS","MINPD","MINSS","MINSD"]; + Mnemonics[0x15E] = ["DIVPS","DIVPD","DIVSS","DIVSD"]; + Mnemonics[0x178] = [["VMREAD","",["CVTTPS2UDQ","","CVTTPD2UDQ"],""],["EXTRQ","",["CVTTPS2UQQ","","CVTTPD2UQQ"],""],["???","","CVTTSS2USI",""],["INSERTQ","","CVTTSD2USI",""]]; + Mnemonics[0x179] = [["VMWRITE","",["CVTPS2UDQ","","CVTPD2UDQ"],""],["EXTRQ","",["CVTPS2UQQ","","CVTPD2UQQ"],""],["???","","CVTSS2USI",""],["INSERTQ","","CVTSD2USI",""]]; + Mnemonics[0x17A] = ["???",["","",["CVTTPS2QQ","","CVTTPD2QQ"],""],["","",["CVTUDQ2PD","","CVTUQQ2PD"],"CVTUDQ2PD"],["","",["CVTUDQ2PS","","CVTUQQ2PS"],""]]; + Mnemonics[0x17B] = ["???",["","",["CVTPS2QQ","","CVTPD2QQ"],""],["","","CVTUSI2SS",""],["","","CVTUSI2SD",""]]; + Mnemonics[0x17C] = ["???",["HADDPD","HADDPD","",""],"???",["HADDPS","HADDPS","",""]]; + Mnemonics[0x17D] = ["???",["HSUBPD","HSUBPD","",""],"???",["HSUBPS","HSUBPS","",""]]; + Mnemonics[0x17E] = [["MOVD","","",""],["MOVD","","MOVQ"],["MOVQ","MOVQ",["???","","MOVQ"],""],"???"], + Mnemonics[0x190] = [["SETO",["KMOVW","","KMOVQ"],"",""],["SETO",["KMOVB","","KMOVD"],"",""],"",""]; + Mnemonics[0x192] = [["SETB",["KMOVW","","???"],"",""],["SETB",["KMOVB","","???"],"",""],"",["SETB",["KMOVD","","KMOVQ"],"",""]]; + Mnemonics[0x193] = [["SETAE",["KMOVW","","???"],"",""],["SETAE",["KMOVB","","???"],"",""],"",["SETAE",["KMOVD","","KMOVQ"],"",""]]; + Mnemonics[0x198] = [["SETS",["KORTESTW","","KORTESTQ"],"",""],["SETS",["KORTESTB","","KORTESTD"],"",""],"",""]; + Mnemonics[0x1A6] = "XBTS"; + Mnemonics[0x1A7] = "IBTS"; + + Operands[0x110] = [["0B700770","0B700770","0A040603","0A040609"],["0B700770","0B700770","0A0412040604","0A0412040604"]]; + Operands[0x111] = [["07700B70","07700B70","06030A04","06090A04"],["07700B70","07700B70","060412040A04","060412040A04"]]; + Operands[0x112] = [["0A0412040606","0A0412040606","0B700770","0B700768"],["0A0412040604","","0B700770","0B700770"]]; + Operands[0x113] = [["06060A04","06060A04","",""],""]; + Operands[0x141] = [["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""],["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""],"",""]; + Operands[0x142] = [["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""],["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""],"",""]; + Operands[0x144] = [["0B0E070E0180",["0A0F06FF","","0A0F06FF"],"",""],["0B0E070E0180",["0A0F06FF","","0A0F06FF"],"",""],"",""]; + Operands[0x145] = [["0A02070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""],["0A02070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""],"",""]; + Operands[0x146] = [["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""],["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""],"",""]; + Operands[0x147] = [["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""],["0B0E070E0180",["0A0F120F06FF","",""],"",""],"",""]; + Operands[0x150] = ["",[["0B0C0648","0B0C0730","",""],["0B0C0648","0B0C0730","",""],"",""]]; + Operands[0x151] = ["0B7007700112","0B7007700112","0A04120406430102","0A04120406490102"]; + Operands[0x152] = [["0A040648","0A040648","",""],"",["0A040643","0A0412040643","",""],""]; + Operands[0x154] = ["0B70137007700110","0B70137007700110","",""]; + Operands[0x155] = ["0B70137007700110","0B70137007700110","",""]; + Operands[0x158] = [["0A040648","0B3013300730","0B70137007700112","0A061206066C0172"],["0A040648","0B3013300730","0B70137007700112","0A061206066C0112"],"0A04120406430102","0A04120406460102"]; + Operands[0x159] = [["0A040648","0B3013300730","0B70137007700112","0A061206066C0172"],["0A040648","0B3013300730","0B70137007700112","0A061206066C0112"],"0A04120406430102","0A04120406460102"]; + Operands[0x15A] = [["0A040648","0B300718","0B7007380111","0A06065A0111"],["0A040648","0B180730","0B3807700112","0A05066C0112"],"0A04120406430101","0A04120406460102"]; + Operands[0x15B] = [[["0B7007700112","","0B380770011A"],"0B700770011A","",""],"","0B7007700111",""]; + Operands[0x15C] = [["0A060648","0B3013300730","0B70137007700112","0A061206066C0172"],["0A060648","0B3013300730","0B70137007700112","0A061206066C0112"],"0A04120406430102","0A04120406460102"]; + Operands[0x15D] = ["0B70137007700111","0B70137007700111","0A04120406430101","0A04120406460101"]; + Operands[0x15E] = ["0B70137007700112","0B70137007700112","0A04120406430102","0A04120406460102"]; + Operands[0x178] = [["07080B080180","",["0B7007700111","","0B3807700119"],""],["064F0C000C00","",["0B7007380119","","0B7007700111"],""],["","","0B0C06440109",""],["0A04064F0C000C00","","0B0C06460109",""]]; + Operands[0x179] = [["0B0807080180","",["0B7007700112","","0B380770011A"],""],["0A04064F","",["0B700738011A","","0B7007700112"],""],["","","0B0C0644010A",""],["0A04064F","","0B0C0646010A",""]]; + Operands[0x17A] = ["",["","",["0B7007380119","","0B7007700111"],""],["","",["0B7007380112","","0B700770011A"],"0A06065A0112"],["","",["0B700770011A","","0B3807700112"],""]]; + Operands[0x17B] = ["",["","",["0B700738011A","","0B7007700112"],""],["","","0A041204070C010A",""],["","","0A041204070C010A",""]]; + Operands[0x17C] = ["",["0A040604","0B7013700770","",""],"",["0A040604","0B7013700770","",""]]; + Operands[0x17D] = ["",["0A040604","0B7013700770","",""],"",["0A040604","0B7013700770","",""]]; + Operands[0x17E] = [["070C0A0A","","",""],["06240A040108","","06360A040108"],["0A040646","0A040646",["","","0A0406460108"],""],""]; + Operands[0x190] = [["0600",["0A0F0612","","0A0F0636"],"",""],["0600",["0A0F0600","","0A0F0624"],"",""],"",""]; + Operands[0x192] = [["0600",["0A0F06F4","",""],"",""],["0600",["0A0F06F4","",""],"",""],"",["0600",["0A0F06F6","","0A0F06F6"],"",""]]; + Operands[0x193] = [["0600",["06F40A0F","",""],"",""],["0600",["06F40A0F","",""],"",""],"",["0600",["06F60A0F","","06F60A0F"],"",""]]; + Operands[0x198] = [["0600",["0A0F06FF","","0A0F06FF"],"",""],["0600",["0A0F06FF","","0A0F06FF"],"",""],"",""]; + Operands[0x1A6] = "0B0E070E"; + Operands[0x1A7] = "070E0B0E"; + + //Adjust the VEX mask instructions for K1OM (Knights corner) which conflict with the enhanced AVX512 versions. + + if( type === 1 ) + { + Mnemonics[0x141] = [["CMOVNO","KAND","",""],"","",""]; + Mnemonics[0x142] = [["CMOVB","KANDN","",""],"","",""]; + Mnemonics[0x144] = [["CMOVE","KNOT","",""],"","",""]; + Mnemonics[0x145] = [["CMOVNE","KOR","",""],"","",""]; + Mnemonics[0x146] = [["CMOVBE","KXNOR","",""],"","",""]; + Mnemonics[0x147] = [["CMOVA","KXOR","",""],"","",""]; + Mnemonics[0x190] = [["SETO","KMOV","",""],"","",""]; + Mnemonics[0x192] = [["SETB","KMOV","",""],"","",""]; + Mnemonics[0x193] = [["SETAE","KMOV","",""],"","",""]; + Mnemonics[0x198] = [["SETS","KORTEST","",""],"","",""]; + Operands[0x141] = [["0B0E070E0180","0A0F06FF","",""],"","",""]; + Operands[0x142] = [["0B0E070E0180","0A0F06FF","",""],"","",""]; + Operands[0x144] = [["0B0E070E0180","0A0F06FF","",""],"","",""]; + Operands[0x145] = [["0A02070E0180","0A0F06FF","",""],"","",""]; + Operands[0x146] = [["0B0E070E0180","0A0F06FF","",""],"","",""]; + Operands[0x147] = [["0B0E070E0180","0A0F06FF","",""],"","",""]; + Operands[0x190] = [["0600","0A0F06FF","",""],"","",""]; + Operands[0x192] = [["0600","06FF0B06","",""],"","",""]; + Operands[0x193] = [["0600","07060A0F","",""],"","",""]; + Operands[0x198] = [["0600","0A0F06FF","",""],"","",""]; + } + + //Disable Knights corner, and AVX512, for L1OM (Intel Larrabee). + + if( type === 2 ) + { + Mnemonics[0x62] = ""; + } + + //Adjust the Mnemonics, and Operand encoding, for the Cyrix processors. + + if( type === 3 ) + { + Mnemonics[0x138] = "SMINT"; Mnemonics[0x13A] = "BB0_RESET"; Mnemonics[0x13B] = "BB1_RESET"; Mnemonics[0x13C] = "CPU_WRITE"; Mnemonics[0x13D] = "CPU_READ"; + Mnemonics[0x150] = "PAVEB"; Mnemonics[0x151] = "PADDSIW"; Mnemonics[0x152] = "PMAGW"; + Mnemonics[0x154] = "PDISTIB"; Mnemonics[0x155] = "PSUBSIW"; + Mnemonics[0x158] = "PMVZB"; Mnemonics[0x159] = "PMULHRW"; Mnemonics[0x15A] = "PMVNZB"; + Mnemonics[0x15B] = "PMVLZB"; Mnemonics[0x15C] = "PMVGEZB"; Mnemonics[0x15D] = "PMULHRIW"; + Mnemonics[0x15E] = "PMACHRIW"; + Mnemonics[0x178] = "SVDC"; Mnemonics[0x179] = "RSDC"; Mnemonics[0x17A] = "SVLDT"; + Mnemonics[0x17B] = "RSLDT"; Mnemonics[0x17C] = "SVTS"; Mnemonics[0x17D] = "RSTS"; + Mnemonics[0x17E] = "SMINT"; + Operands[0x150] = "0A0A06A9"; Operands[0x151] = "0A0A06A9"; Mnemonics[0x152] = "0A0A06A9"; + Operands[0x154] = "0A0A06AF"; Operands[0x155] = "0A0A06A9"; + Operands[0x158] = "0A0A06AF"; Operands[0x159] = "0A0A06A9"; Mnemonics[0x15A] = "0A0A06AF"; + Operands[0x15B] = "0A0A06AF"; Operands[0x15C] = "0A0A06AF"; Mnemonics[0x15D] = "0A0A06A9"; + Operands[0x15E] = "0A0A06AF"; + Operands[0x178] = "30000A08"; Operands[0x179] = "0A083000"; Operands[0x17A] = "3000"; + Operands[0x17B] = "3000"; Operands[0x17C] = "3000"; Operands[0x17D] = "3000"; + Operands[0x17E] = ""; + } + + //Adjust the Mnemonics, and Operand encoding, for the Geode processor. + + if( type === 4 ) + { + Mnemonics[0x138] = "SMINT"; Mnemonics[0x139] = "DMINT"; Mnemonics[0x13A] = "RDM"; + } + + //Adjust the Mnemonics, for the Centaur processor. + + if( type === 5 ) + { + Mnemonics[0x13F] = "ALTINST"; + Mnemonics[0x1A6] = ["???",["MONTMUL","XSA1","XSA256","???","???","???","???","???"]]; + Mnemonics[0x1A7] = [ + "???", + [ + "XSTORE", + ["???","???","XCRYPT-ECB","???"], + ["???","???","XCRYPT-CBC","???"], + ["???","???","XCRYPT-CTR","???"], + ["???","???","XCRYPT-CFB","???"], + ["???","???","XCRYPT-OFB","???"], + "???", + "???" + ] + ]; + Operands[0x1A6] = ["",["","","","","","","",""]]; + Operands[0x1A7] = [ + "", + [ + "", + ["","","",""], + ["","","",""], + ["","","",""], + ["","","",""], + ["","","",""], + "", + "" + ] + ]; + } + + //Adjust the Mnemonics, for the X86/486 processor and older. + + if( type === 6 ) + { + Mnemonics[0x110] = "UMOV"; Mnemonics[0x111] = "UMOV"; Mnemonics[0x112] = "UMOV"; Mnemonics[0x113] = "UMOV"; + Mnemonics[0x1A6] = "CMPXCHG"; Mnemonics[0x1A7] = "CMPXCHG"; + Operands[0x110] = "06000A00"; Operands[0x111] = "070E0B0E"; Operands[0x112] = "0A000600"; Operands[0x113] = "0B0E070E"; + Operands[0x1A6] = ""; Operands[0x1A7] = ""; + } + +} + +/*------------------------------------------------------------------------------------------------------------------------- +This function loads the BinCode array using an hex string as input, and Resets the Code position along the array, but does not +reset the base address. This allows programs to be decoded in sections well maintaining the accurate 64 bit base address. +--------------------------------------------------------------------------------------------------------------------------- +The function "SetBasePosition()" sets the location that the Code is from in memory. +The function "GotoPosition()" tests if the address is within rage of the current loaded binary. +The function "GetPosition()" Gives back the current base address in it's proper format for the current BitMode. +--------------------------------------------------------------------------------------------------------------------------- +If the hex input is invalid returns false. +-------------------------------------------------------------------------------------------------------------------------*/ + +export function LoadBinCode( HexStr ) +{ + //Clear BinCode, and Reset Code Position in Bin Code array. + + BinCode = []; CodePos = 0; + + //Iterate though the hex string and covert to 0 to 255 byte values into the BinCode array. + + var len = HexStr.length; + + for( var i = 0, el = 0, Sign = 0, int32 = 0; i < len; i += 8 ) + { + //It is faster to read 8 hex digits at a time if possible. + + int32 = parseInt( HexStr.slice( i, i + 8 ), 16 ); + + //If input is invalid return false. + + if( isNaN( int32 ) ){ return ( false ); } + + //If the end of the Hex string is reached and is not 8 digits the number has to be lined up. + + ( ( len - i ) < 8 ) && ( int32 <<= ( 8 - len - i ) << 2 ); + + //The variable sing corrects the unusable sing bits during the 4 byte rotation algorithm. + + Sign = int32; + + //Remove the Sign bit value if active for when the number is changed to int32 during rotation. + + int32 ^= int32 & 0x80000000; + + //Rotate the 32 bit int so that each number is put in order in the BinCode array. Add the Sign Bit positions back though each rotation. + + int32 = ( int32 >> 24 ) | ( ( int32 << 8 ) & 0x7FFFFFFF ); + BinCode[el++] = ( ( ( Sign >> 24 ) & 0x80 ) | int32 ) & 0xFF; + int32 = ( int32 >> 24 ) | ( ( int32 << 8 ) & 0x7FFFFFFF ); + BinCode[el++] = ( ( ( Sign >> 16 ) & 0x80 ) | int32 ) & 0xFF; + int32 = ( int32 >> 24 ) | ( ( int32 << 8 ) & 0x7FFFFFFF ); + BinCode[el++] = ( ( ( Sign >> 8 ) & 0x80 ) | int32 ) & 0xFF; + int32 = ( int32 >> 24 ) | ( ( int32 << 8 ) & 0x7FFFFFFF ); + BinCode[el++] = ( ( Sign & 0x80 ) | int32 ) & 0xFF; + } + + //Remove elements past the Number of bytes in HexStr because int 32 is always 4 bytes it is possible to end in an uneven number. + + len >>= 1; + + for(; len < BinCode.length; BinCode.pop() ); + + //Return true for that the binary code loaded properly. + + return ( true ); +} + +/*------------------------------------------------------------------------------------------------------------------------- +This function moves the address by one and caries to 64 section for the Base address. The BitMode settings limit how much of +the 64 bit address is used in functions "GetPosition()", and "GotoPosition()", for the type of binary being disassemble. +This function also moves the binary code array position CodePos by one basically this function is used to progress the +disassembler as it is decoding a sequence of bytes. +-------------------------------------------------------------------------------------------------------------------------*/ + +function NextByte() +{ + //Add the current byte as hex to InstructionHex which will be displayed beside the decoded instruction. + //After an instruction decodes InstructionHex is only added beside the instruction if ShowInstructionHex is active. + var t; + if ( CodePos < BinCode.length ) //If not out of bounds. + { + //Convert current byte to String, and pad. + + ( ( t = BinCode[CodePos++].toString(16) ).length === 1) && ( t = "0" + t ); + + //Add it to the current bytes used for the decode instruction. + + InstructionHex += t; + + //Continue the Base address. + + ( ( Pos32 += 1 ) > 0xFFFFFFFF ) && ( Pos32 = 0, ( ( Pos64 += 1 ) > 0xFFFFFFFF ) && ( Pos64 = 0 ) ); + } +} + +/*------------------------------------------------------------------------------------------------------------------------- +Takes a 64/32/16 bit hex string and sets it as the address position depending on the address format it is split into an +segment, and offset address. Note that the Code Segment is used in 16 bit code. Also code segment is also used in 32 bit +if set 36, or higher. Effects instruction location in memory when decoding a program. +-------------------------------------------------------------------------------------------------------------------------*/ + +export function SetBasePosition( Address ) +{ + //Split the Segment:offset. + + var t = Address.split(":"); + + //Set the 16 bit code segment position if there is one. + + if ( typeof t[1] !== "undefined" ){ CodeSeg = parseInt( t[0].slice( t[0].length - 4 ), 16 ); Address = t[1]; } + + //Adjust the Instruction pointer 16(IP)/32(EIP)/64(RIP). Also varies based on Bit Mode. + + var Len = Address.length; + + if( Len >= 9 && BitMode == 2 ){ Pos64 = parseInt( Address.slice( Len - 16, Len - 8 ), 16 ); } + if( Len >= 5 && BitMode >= 1 && !( BitMode == 1 & CodeSeg >= 36 ) ){ Pos32 = parseInt( Address.slice( Len - 8 ), 16 ); } + else if( Len >= 1 && BitMode >= 0 ){ Pos32 = ( Pos32 & 0xFFFF0000 ) | ( parseInt( Address.slice( Len - 4 ), 16 ) ); } + + //Convert Pos32 to undignified integer. + + if ( Pos32 < 0 ) { Pos32 += 0x100000000; } +} + +/*------------------------------------------------------------------------------------------------------------------------- +Gives back the current Instruction address position. +In 16 bit an instruction location is Bound to the code segment location in memory, and the first 16 bit of the instruction pointer 0 to 65535. +In 32 bit an instruction location uses the first 32 bit's of the instruction pointer. +-------------------------------------------------------------------------------------------------------------------------*/ + +function GetPosition() +{ + //If 16 bit Seg:Offset, or if 32 bit and CodeSeg is 36, or higher. + + if( BitMode === 0 | ( BitMode === 1 & CodeSeg >= 36 ) ) + { + for ( var S16 = ( Pos32 & 0xFFFF ).toString(16); S16.length < 4; S16 = "0" + S16 ); + for ( var Seg = ( CodeSeg ).toString(16); Seg.length < 4; Seg = "0" + Seg ); + return( ( Seg + ":" + S16 ).toUpperCase() ); + } + + //32 bit, and 64 bit section. + + var S64="", S32=""; + + //If 32 bit or higher. + + if( BitMode >= 1 ) + { + for ( S32 = Pos32.toString(16); S32.length < 8; S32 = "0" + S32 ); + } + + //If 64 bit. + + if( BitMode === 2 ) + { + for ( S64 = Pos64.toString(16); S64.length < 8; S64 = "0" + S64 ); + } + + //Return the 32/64 address. + + return ( ( S64 + S32 ).toUpperCase() ); +} + +/*------------------------------------------------------------------------------------------------------------------------- +Moves the dissembler 64 bit address, and CodePos to correct address. Returns false if address location is out of bounds. +-------------------------------------------------------------------------------------------------------------------------*/ + +function GotoPosition( Address ) +{ + //Current address location. + + var LocPos32 = Pos32; + var LocPos64 = Pos64; + var LocCodeSeg = CodeSeg; + + //Split the by Segment:offset address format. + + var t = Address.split(":"); + + //Set the 16 bit code segment location if there is one. + + if ( typeof t[1] !== "undefined" ) + { + LocCodeSeg = parseInt(t[0].slice( t[0].length - 4 ), 16); + Address = t[1]; + } + + var len = Address.length; + + //If the address is 64 bit's long, and bit mode is 64 bit adjust the 64 bit location. + + if( len >= 9 && BitMode === 2 ) + { + LocPos64 = parseInt( Address.slice( len - 16, len - 8 ), 16 ); + } + + //If the address is 32 bit's long, and bit mode is 32 bit, or higher adjust the 32 bit location. + + if( len >= 5 && BitMode >= 1 & !( BitMode === 1 & CodeSeg >= 36 ) ) + { + LocPos32 = parseInt( Address.slice( len - 8 ), 16 ); + } + + //Else If the address is 16 bit's long, and bit mode is 16 bit, or higher adjust the first 16 bit's in location 32. + + else if( len >= 1 && BitMode >= 0 ) + { + LocPos32 = ( LocPos32 - LocPos32 + parseInt( Address.slice( len - 4 ), 16 ) ); + } + + //Find the difference between the current base address and the selected address location. + + var Dif32 = Pos32 - LocPos32, Dif64 = Pos64 - LocPos64; + + //Only calculate the Code Segment location if The program uses 16 bit address mode otherwise the + //code segment does not affect the address location. + + if( ( BitMode === 1 & CodeSeg >= 36 ) || BitMode === 0 ) + { + Dif32 += ( CodeSeg - LocCodeSeg ) << 4; + } + + //Before adjusting the Code Position Backup the Code Position in case that the address is out of bounds. + + t = CodePos; + + //Subtract the difference to the CodePos position. + + CodePos -= Dif64 * 4294967296 + Dif32; + + //If code position is out of bound for the loaded binary in the BinCode array, or + //is a negative index return false and reset CodePos. + + if( CodePos < 0 || CodePos > BinCode.length ) + { + CodePos = t; return ( false ); + } + + //Set the base address so that it matches the Selected address location that Code position is moved to in relative space in the BinCode Array. + + CodeSeg = LocCodeSeg; + Pos32 = LocPos32 + Pos64 = LocPos64; + + //Return true for that the address Position is moved in range correctly. + + return ( true ); +} + +/*------------------------------------------------------------------------------------------------------------------------- +Finds bit positions to the Size attribute indexes in REG array, and the Pointer Array. For the Size Attribute variations. +--------------------------------------------------------------------------------------------------------------------------- +The SizeAttribute settings is 8 digits big consisting of 1, or 0 to specify the extended size that an operand can be made. +In which an value of 01100100 is decoded as "0 = 1024, 1 = 512, 1 = 256, 0 = 128, 0 = 64, 1 = 32, 0 = 16, 0 = 8". +In which the largest bit position is 512, and is the 6th number "0 = 7, 1 = 6, 1 = 5, 0 = 4, 0 = 3, 1 = 2, 0 = 1, 0 = 0". +In which 6 is the bit position for 512 as the returned Size . Each size is in order from 0 to 7, thus the size given back +from this function Lines up With the Pinter array, and Register array indexes for the register names by size, and Pointers. +--------------------------------------------------------------------------------------------------------------------------- +The variable SizeAttrSelect is separate from this function it is adjusted by prefixes that adjust Vector size, and General purpose registers. +-------------------------------------------------------------------------------------------------------------------------*/ + +function GetOperandSize( SizeAttribute ) +{ + /*---------------------------------------------------------------------------------------------------------------------------------------- + Each S value goes in order to the vector length value in EVEX, and VEX Smallest to biggest in perfect alignment. + SizeAttrSelect is set 1 by default, unless it is set 0 to 3 by the vector length bit's in the EVEX prefix, or 0 to 1 in the VEX prefix. + In which if it is not an Vector instruction S2 acts as the mid default size attribute in 32 bit mode, and 64 bit mode for all instructions. + ----------------------------------------------------------------------------------------------------------------------------------------*/ + + var S4 = 0, S3 = 0, S2 = 0, S1 = 0, S0 = -1; //Note S0 is Vector size 1024, which is unused. + + /*---------------------------------------------------------------------------------------------------------------------------------------- + Lookup the Highest active bit in the SizeAttribute value giving the position the bit is in the number. S1 will be the biggest size attribute. + In which this size attribute is only used when the extended size is active from the Rex prefix using the W (width) bit setting. + In which sets variable SizeAttrSelect to 2 in value when the Width bit prefix setting is decoded, or if it is an Vector this is the + Max vector size 512 in which when the EVEX.L'L bit's are set 10 = 2 sets SizeAttrSelect 2, note 11 = 3 is reserved for vectors 1024 in size. + ----------------------------------------------------------------------------------------------------------------------------------------*/ + + S1 = SizeAttribute; S1 = ( ( S1 & 0xF0 ) !== 0 ? ( S1 >>= 4, 4 ) : 0 ) | ( ( S1 & 0xC ) !== 0 ? ( S1 >>= 2, 2 ) : 0 ) | ( ( S1 >>= 1 ) !== 0 ); + + /*---------------------------------------------------------------------------------------------------------------------------------------- + If there is no size attributes then set S1 to -1 then the rest are set to S1 as they should have no size setting. + ----------------------------------------------------------------------------------------------------------------------------------------*/ + + if( SizeAttribute === 0 ) { S1 = -1; } + + /*---------------------------------------------------------------------------------------------------------------------------------------- + Convert the Bit Position of S1 into it's value and remove it by subtracting it into the SizeAttribute settings. + ----------------------------------------------------------------------------------------------------------------------------------------*/ + + SizeAttribute -= ( 1 << S1 ); + + /*---------------------------------------------------------------------------------------------------------------------------------------- + Lookup the Highest Second active bit in the SizeAttribute value giving the position the bit is in the number. + In which S2 will be the default size attribute when SizeAttrSelect is 1 and has not been changed by prefixes, or If this is an vector + SizeAttrSelect is set one by the EVEX.L'L bit's 01 = 1, or VEX.L is active 1 = 1 in which the Mid vector size is used. + In which 256 is the Mid vector size some vectors are smaller some go 64/128/256 in which the mid size is 128. + ----------------------------------------------------------------------------------------------------------------------------------------*/ + + S2 = SizeAttribute; S2 = ( ( S2 & 0xF0 ) !== 0 ? ( S2 >>= 4, 4 ) : 0 ) | ( ( S2 & 0xC ) !== 0 ? ( S2 >>= 2, 2 ) : 0 ) | ( ( S2 >>= 1 ) !== 0 ); + + /*---------------------------------------------------------------------------------------------------------------------------------------- + Convert the Bit Position of S2 into it's value and remove it by subtracting it if it is not 0. + ----------------------------------------------------------------------------------------------------------------------------------------*/ + + if( S2 !== 0 ) { SizeAttribute -= ( 1 << S2 ); } + + /*---------------------------------------------------------------------------------------------------------------------------------------- + If it is 0 The highest size attribute is set as the default operand size. So S2 is aliased to S1, if there is no other Size setting attributes. + ----------------------------------------------------------------------------------------------------------------------------------------*/ + + else { S2 = S1; } + + /*---------------------------------------------------------------------------------------------------------------------------------------- + Lookup the Highest third active bit in the SizeAttribute value giving the position the bit is in the number. + The third Size is only used if the Operand override prefix is used setting SizeAttrSelect to 0, or if this is an vector the + EVEX.L'L bit's are 00 = 0 sets SizeAttrSelect 0, or VEX.L = 0 in which SizeAttrSelect is 0 using the smallest vector size. + ----------------------------------------------------------------------------------------------------------------------------------------*/ + + S3 = SizeAttribute; S3 = ( ( S3 & 0xF0 ) !== 0 ? ( S3 >>= 4, 4 ) : 0 ) | ( ( S3 & 0xC ) !== 0 ? ( S3 >>= 2, 2 ) : 0 ) | ( ( S3 >>= 1 ) !== 0 ); + + /*---------------------------------------------------------------------------------------------------------------------------------------- + Convert the Bit Position of S3 into it's value and remove it by subtracting it if it is not 0. + ----------------------------------------------------------------------------------------------------------------------------------------*/ + + if( S3 !== 0 ) { SizeAttribute -= ( 1 << S3 ); } + + /*---------------------------------------------------------------------------------------------------------------------------------------- + If it is 0 The second size attribute is set as the operand size. So S3 is aliased to S2, if there is no other Size setting attributes. + ----------------------------------------------------------------------------------------------------------------------------------------*/ + + else { S3 = S2; if( S2 !== 2 ) { S2 = S1; } }; + + //In 32/16 bit mode the operand size must never exceed 32. + + if ( BitMode <= 1 && S2 >= 3 && !Vect ) + { + if( ( S1 | S2 | S3 ) === S3 ){ S1 = 2; S3 = 2; } //If single size all adjust 32. + S2 = 2; //Default operand size 32. + } + + //In 16 bit mode The operand override is always active until used. This makes all operands 16 bit size. + //When Operand override is used it is the default 32 size. Flip S3 with S2. + + if( BitMode === 0 && !Vect ) { var t = S3; S3 = S2; S2 = t; } + + //If an Vect is active, then EVEX.W, VEX.W, or XOP.W bit acts as 32/64. + + if( ( Vect || Extension > 0 ) && ( ( S1 + S2 + S3 ) === 7 | ( S1 + S2 + S3 ) === 5 ) ) { Vect = false; return( ( [ S2, S1 ] )[ WidthBit & 1 ] ); } + + //If it is an vector, and Bround is active vector goes max size. + + if( Vect && ConversionMode === 1 ) + { + S0 = S1; S3 = S1; S2 = S1; + } + + //Note the fourth size that is -1 in the returned size attribute is Vector length 11=3 which is invalid unless Intel decides to add 1024 bit vectors. + //The only time S0 is not negative one is if vector broadcast round is active. + + return( ( [ S3, S2, S1, S0 ] )[ SizeAttrSelect ] ); + +} + +/*------------------------------------------------------------------------------------------------------------------------- +This function returns an array with three numbers. +--------------------------------------------------------------------------------------------------------------------------- +The first element is the two bits for the ModR/M byte for Register mode, Memory mode, and Displacement settings, or the SIB byte +scale as a number value 0 to 3 if it is not an ModR/M byte since they both use the same bit grouping. +The second element is the three bits for the ModR/M byte Opcode/Reg bits, or the SIB Index Register value as a number value 0 to 7. +The third element is the last three bits for the ModR/M byte the R/M bits, or the SIB Base Register value as a number value 0 to 7. +-------------------------------------------------------------------------------------------------------------------------*/ + +function Decode_ModRM_SIB_Value() +{ + //Get the current position byte value + + var v = BinCode[CodePos]; + + //The first tow binary digits of the read byte is the Mode bits of the ModR/M byte or + //The first tow binary digits of the byte is the Scale bits of the SIB byte. + + var ModeScale = (v >> 6) & 0x03; //value 0 to 3 + + //The three binary digits of the read byte after the first two digits is the OpcodeReg Value of the ModR/M byte or + //The three binary digits of the read byte after the first two digits is the Index Register value for the SIB byte. + + var OpcodeRegIndex = (v >> 3) & 0x07; //value 0 to 7 + + //The three binary digits at the end of the read byte is the R/M (Register,or Memory) Value of the ModR/M byte or + //The three binary digits at the end of the read byte is the Base Register Value of the SIB byte. + + var RMBase = v & 0x07; //value 0 to 7 + + //Put the array together containing the three indexes with the value + //Note both the ModR/M byte and SIB byte use the same bit value pattern + + var ByteValueArray = [ + ModeScale,//Index 0 is the first tow bits for the Mode, or Scale Depending on what the byte value is used for. + OpcodeRegIndex,//Index 1 is the three bits for the OpcodeReg, or Index Depending on what the byte value is used for. + RMBase //Index 2 is the three bits for the RM, or BASE bits Depending on what the byte value is used for. + ]; + + //Move the Decoders Position by one. + + NextByte(); + + //return the array containing the decoded values of the byte. + + return (ByteValueArray); + +} + +/*------------------------------------------------------------------------------------------------------------------------- +When input type is value 0 decode the immediate input regularly to it's size setting for accumulator Arithmetic, and IO. +When input type is value 1 decode the immediate input regularly, but zeros out the upper 4 bits for Register encoding. +When input type is value 2 decode the immediate as a relative address used by jumps, and function calls. +When input type is value 3 decode the immediate as a Integer Used by Displacements. +--------------------------------------------------------------------------------------------------------------------------- +The function argument SizeSetting is the size attributes of the IMM that is decoded using the GetOperandSize function. +The Imm uses two size setting, the first 4 bits are used for the Immediate actual adjustable sizes 8,16,32,64. +--------------------------------------------------------------------------------------------------------------------------- +If BySize is false the SizeSetting is used numerically as a single size selection as +0=8,1=16,2=32,3=64 by size setting value. +-------------------------------------------------------------------------------------------------------------------------*/ + +function DecodeImmediate( type, BySize, SizeSetting ) +{ + + /*------------------------------------------------------------------------------------------------------------------------- + Initialize V32, and V64 which will store the Immediate value. + JavaScript Float64 numbers can not accurately work with numbers 64 bit's long. + So numbers are split into two numbers that should never exceed an 32 bit value though calculation. + Numbers that are too big for the first 32 bit's are stored as the next 32 bit's in V64. + -------------------------------------------------------------------------------------------------------------------------*/ + + var V32 = 0, V64 = 0; + + //*Initialize the Pad Size for V32, and V64 depending On the Immediate type Calculation they use. + + var Pad32 = 0, Pad64 = 0; + + //*Initialize the Sign value that is only set for Negative, or Positive Relative displacements. + + var Sign = 0; + + //*Initialize the Sign Extend variable size as 0 Some Immediate numbers Sign extend. + + var Extend = 0; + + //*The variable S is the size of the Immediate. + + var S = SizeSetting & 0x0F; + + //*Extend size. + + Extend = SizeSetting >> 4; + + //*If by Size attributes is set true. + + if ( BySize ) + { + S = GetOperandSize( S ); + + if ( Extend > 0 ) + { + Extend = GetOperandSize( Extend ); + } + } + + /*------------------------------------------------------------------------------------------------------------------------- + The possible values of S (Calculated Size) are S=0 is IMM8, S=1 is IMM16, S=2 is IMM32, S=3 is IMM64. + Calculate how many bytes that are going to have to be read based on the value of S. + S=0 is 1 byte, S=1 is 2 bytes, S=2 is 4 bytes, S=3 is 8 bytes. + The Number of bytes to read is 2 to the power of S. + -------------------------------------------------------------------------------------------------------------------------*/ + + var n = 1 << S; + + //Adjust Pad32, and Pad64. + + Pad32 = Math.min( n, 4 ); ( n >= 8 ) && ( Pad64 = 8 ); + + //Store the first byte of the immediate because IMM8 can use different encodings. + + IMMValue = BinCode[CodePos]; + + //*Loop and Move the Decoder to the next byte Code position to the number of bytes to read for V32, and V64. + + for ( var i = 0, v = 1; i < Pad32; V32 += BinCode[CodePos] * v, i++, v *= 256, NextByte() ); + for ( v = 1; i < Pad64; V64 += BinCode[CodePos] * v, i++, v *= 256, NextByte() ); + + //*Adjust Pad32 so it matches the length the Immediate should be in hex for number of bytes read. + + Pad32 <<= 1; Pad64 <<= 1; + + /*--------------------------------------------------------------------------------------------------------------------------- + If the IMM type is used with an register operand on the upper four bit's then the IMM byte does not use the upper 4 bit's. + ---------------------------------------------------------------------------------------------------------------------------*/ + + if( type === 1 ) { V32 &= ( 1 << ( ( n << 3 ) - 4 ) ) - 1; } + + /*--------------------------------------------------------------------------------------------------------------------------- + If the Immediate is an relative address calculation. + ---------------------------------------------------------------------------------------------------------------------------*/ + + if ( type === 2 ) + { + //Calculate the Padded size for at the end of the function an Relative is padded to the size of the address based on bit mode. + + Pad32 = ( Math.min( BitMode, 1 ) << 2 ) + 4; Pad64 = Math.max( Math.min( BitMode, 2 ), 1 ) << 3; + + //Carry bit to 64 bit section. + + var C64 = 0; + + //Relative size. + + var n = Math.min( 0x100000000, Math.pow( 2, 4 << ( S + 1 ) ) ); + + //Sign bit adjust. + + if( V32 >= ( n / 2 ) ) { V32 -= n; } + + //Add position. + + V32 += Pos32; + + //Remove carry bit and add it to C64. + + ( C64 = ( ( V32 ) >= 0x100000000 ) ) && ( V32 -= 0x100000000 ); + + //Do not carry to 64 if address is 32, and below. + + if ( S <= 2 ) { C64 = false; } + + //Add the 64 bit position plus carry. + + ( ( V64 += Pos64 + C64 ) > 0xFFFFFFFF ) && ( V64 -= 0x100000000 ); + } + + /*--------------------------------------------------------------------------------------------------------------------------- + If the Immediate is an displacement calculation. + ---------------------------------------------------------------------------------------------------------------------------*/ + + if ( type === 3 ) + { + /*------------------------------------------------------------------------------------------------------------------------- + Calculate the displacement center point based on Immediate size. + -------------------------------------------------------------------------------------------------------------------------*/ + + //An displacement can not be bigger than 32 bit's, so Pad64 is set 0. + + Pad64 = 0; + + //Now calculate the Center Point. + + var Center = 2 * ( 1 << ( n << 3 ) - 2 ); + + //By default the Sign is Positive. + + Sign = 1; + + /*------------------------------------------------------------------------------------------------------------------------- + Calculate the VSIB displacement size if it is a VSIB Disp8. + -------------------------------------------------------------------------------------------------------------------------*/ + + if ( VSIB && S === 0 ) + { + var VScale = WidthBit | 2; + Center <<= VScale; V32 <<= VScale; + } + + //When the value is higher than the center it is negative. + + if ( V32 >= Center ) + { + //Convert the number to the negative side of the center point. + + V32 = Center * 2 - V32; + + //The Sign is negative. + + Sign = 2; + } + } + + /*--------------------------------------------------------------------------------------------------------------------------- + Pad Imm based on the calculated Immediate size, because when an value is converted to an number as text that can be displayed + the 0 digits to the left are removed. Think of this as like the number 000179 the actual length of the number is 6 digits, + but is displayed as 179, because the unused digits are not displayed, but they still exist in the memory. + ---------------------------------------------------------------------------------------------------------------------------*/ + + for( var Imm = V32.toString(16), L = Pad32; Imm.length < L; Imm = "0" + Imm ); + if( Pad64 > 8 ) { for( Imm = V64.toString(16) + Imm, L = Pad64; Imm.length < L; Imm = "0" + Imm ); } + + /*--------------------------------------------------------------------------------------------------------------------------- + Extend Imm if it's extend size is bigger than the Current Imm size. + ---------------------------------------------------------------------------------------------------------------------------*/ + + if ( Extend !== S ) + { + //Calculate number of bytes to Extend till by size. + + Extend = Math.pow( 2, Extend ) * 2; + + //Setup the Signified pad value. + + var spd = "00"; ( ( ( parseInt( Imm.substring(0, 1), 16) & 8 ) >> 3 ) ) && ( spd = "FF" ); + + //Start padding. + + for (; Imm.length < Extend; Imm = spd + Imm); + } + + //*Return the Imm. + + return ( ( Sign > 0 ? ( Sign > 1 ? "-" : "+" ) : "" ) + Imm.toUpperCase() ); + +} + +/*------------------------------------------------------------------------------------------------------------------------- +Decode registers by Size attributes, or a select register index. +-------------------------------------------------------------------------------------------------------------------------*/ + +function DecodeRegValue( RValue, BySize, Setting ) +{ + //If the instruction is a Vector instruction, and no extension is active like EVEX, VEX Make sure Size attribute uses the default vector size. + + if( Vect && Extension === 0 ) + { + SizeAttrSelect = 0; + } + + //If By size is true Use the Setting with the GetOperandSize + + if ( BySize ) + { + Setting = GetOperandSize( Setting ); //get decoded size value. + + //Any Vector register smaller than 128 has to XMM because XMM is the smallest SIMD Vector register. + + if( Vect && Setting < 4 ) { Setting = 4; } + } + + //If XOP only vector 0 to 15 are usable. + + if( Opcode >= 0x400 ) { RValue &= 15; } + + //Else If 16/32 bit mode in VEX/EVEX/MVEX vctor register can only go 0 though 7. + + else if( BitMode <= 1 && Extension >= 1 ) { RValue &= 7; } + + //If L1OM ZMM to V reg. + + if ( Opcode >= 0x700 && Setting === 6 ) + { + Setting = 16; + } + + //Else if 8 bit high/low Registers + + else if ( Setting === 0 ) + { + return (REG[0][RexActive][ RValue ]); + } + + //Return the Register. + + return (REG[Setting][ RValue ]); +} + +/*------------------------------------------------------------------------------------------------------------------------- +Decode the ModR/M pointer, and Optional SIB if used. +Note if by size attributes is false the lower four bits is the selected Memory pointer, +and the higher four bits is the selected register. +-------------------------------------------------------------------------------------------------------------------------*/ + +function Decode_ModRM_SIB_Address( ModRM, BySize, Setting ) +{ + var out = ""; //the variable out is what stores the decoded address pointer, or Register if Register mode. + var S_C = "{"; //L1OM, and K1OM {SSS,CCCCC} setting decoding, or EVEX broadcast round. + + //------------------------------------------------------------------------------------------------------------------------- + //If the ModR/M is not in register mode decode it as an Effective address. + //------------------------------------------------------------------------------------------------------------------------- + + if( ModRM[0] !== 3 ) + { + + //If the instruction is a Vector instruction, and no extension is active like EVEX, VEX Make sure Size attribute uses the default vector size. + + if( Vect && Extension === 0 ) + { + SizeAttrSelect = 0; + } + + //------------------------------------------------------------------------------------------------------------------------- + //The Selected Size is setting unless BySize attribute is true. + //------------------------------------------------------------------------------------------------------------------------- + + if ( BySize ) + { + //------------------------------------------------------------------------------------------------------------------------- + //Check if it is not the non vectorized 128 which uses "Oword ptr". + //------------------------------------------------------------------------------------------------------------------------- + + if ( Setting !== 16 || Vect ) + { + Setting = ( GetOperandSize( Setting ) << 1 ) | FarPointer; + } + + //------------------------------------------------------------------------------------------------------------------------- + //Non vectorized 128 uses "Oword ptr" alaises to "QWord ptr" in 32 bit mode, or lower. + //------------------------------------------------------------------------------------------------------------------------- + + else if ( !Vect ) { Setting = 11 - ( ( BitMode <= 1 ) * 5 ); } + } + + //------------------------------------------------------------------------------------------------------------------------- + //If By size attributes is false the selected Memory pointer is the first four bits of the size setting for all pointer indexes 0 to 15. + //Also if By size attribute is also true the selected by size index can not exceed 15 anyways which is the max combination the first four bits. + //------------------------------------------------------------------------------------------------------------------------- + + Setting = Setting & 0x0F; + + //If Vector extended then MM is changed to QWORD. + + if( Extension !== 0 && Setting === 9 ){ Setting = 6; } + + //Bround control, or 32/64 VSIB. + + if ( ConversionMode === 1 || ConversionMode === 2 || VSIB ) { out += PTR[WidthBit > 0 ? 6 : 4]; } + + //------------------------------------------------------------------------------------------------------------------------- + //Get the pointer size by Size setting. + //------------------------------------------------------------------------------------------------------------------------- + + else{ out = PTR[Setting]; } + + //Add the Segment override left address bracket if any segment override was used otherwise the SegOverride string should be just a normal left bracket. + + out += SegOverride; + + //------------------------------------------------------------------------------------------------------------------------- + //calculate the actual address size according to the Address override and the CPU bit mode. + //------------------------------------------------------------------------------------------------------------------------- + //AddressSize 1 is 16, AddressSize 2 is 32, AddressSize 3 is 64. + //The Bit mode is the address size except AddressOverride reacts differently in different bit modes. + //In 16 bit AddressOverride switches to the 32 bit ModR/M effective address system. + //In both 32/64 the Address size goes down by one is size. + //------------------------------------------------------------------------------------------------------------------------- + + var AddressSize = BitMode + 1; + + if (AddressOverride) + { + AddressSize = AddressSize - 1; + + //the only time the address size is 0 is if the BitMode is 16 bit's and is subtracted by one resulting in 0. + + if(AddressSize === 0) + { + AddressSize = 2; //set the address size to 32 bit from the 16 bit address mode. + } + } + + /*------------------------------------------------------------------------------------------------------------------------- + The displacement size calculation. + --------------------------------------------------------------------------------------------------------------------------- + In 16/32/64 the mode setting 1 will always add a Displacement of 8 to the address. + In 16 the Mode setting 2 adds a displacement of 16 to the address. + In 32/64 the Mode Setting 2 for the effective address adds an displacement of 32 to the effective address. + -------------------------------------------------------------------------------------------------------------------------*/ + + var Disp = ModRM[0] - 1; //Let disp relate size to mode value of the ModR/M. + + //if 32 bit and above, and if Mode is 2 then disp size is disp32. + + if(AddressSize >= 2 && ModRM[0] === 2) + { + Disp += 1; //Only one more higher in size is 32. + } + + /*------------------------------------------------------------------------------------------------------------------------- + End of calculation. + -------------------------------------------------------------------------------------------------------------------------*/ + /*------------------------------------------------------------------------------------------------------------------------- + Normally the displacement type is an relative Immediate that is added ("+"), + or subtracted ("-") from the center point to the selected base register, + and the size depends on mode settings 1, and 2, and also Address bit mode (Displacement calculation). + Because the normal ModR/M format was limited to Relative addresses, and unfixed locations, + so some modes, and registers combinations where used for different Immediate displacements. + -------------------------------------------------------------------------------------------------------------------------*/ + + var DispType = 3; //by default the displacement size is added to the selected base register, or Index register if SIB byte combination is used. + + //-------------------------------------------16 Bit ModR/M address decode logic------------------------------------------- + + if( AddressSize === 1 ) + { + + //if ModR/M mode bits 0, and Base Register value is 6 then disp16 with DispType mode 0. + + if(AddressSize === 1 && ModRM[0] === 0 && ModRM[2] === 6) + { + Disp = 1; + DispType = 0; + } + + //BX , BP switch based on bit 2 of the Register value + + if( ModRM[2] < 4 ){ out += REG[ AddressSize ][ 3 + ( ModRM[2] & 2 ) ] + "+"; } + + //The first bit switches between Destination index, and source index + + if( ModRM[2] < 6 ){ out += REG[ AddressSize ][ 6 + ( ModRM[2] & 1 ) ]; } + + //[BP], and [BX] as long as Mode is not 0, and Register is not 6 which sets DispType 0. + + else if ( DispType !== 0 ) { out += REG[ AddressSize ][ 17 - ( ModRM[2] << 1 ) ]; } + } //End of 16 bit ModR/M decode logic. + + //-------------------------------------------Else 32/64 ModR/M------------------------------------------- + + else + { + + //if Mode is 0 and Base Register value is 5 then it uses an Relative (RIP) disp32. + + if( ModRM[0] === 0 && ModRM[2] === 5 ) + { + Disp = 2; + DispType = 2; + } + + //check if Base Register is 4 which goes into the SIB address system + + if( ModRM[2] === 4 ) + { + //Decode the SIB byte. + + var SIB = Decode_ModRM_SIB_Value(); + + //Calculate the Index register with it's Extended value because the index register will only cancel out if 4 in value. + + var IndexReg = IndexExtend | SIB[1]; + + //check if the base register is 5 in value in the SIB without it's added extended value, and that the ModR/M Mode is 0 this activates Disp32 + + if ( ModRM[0] === 0 && SIB[2] === 5 && !VSIB ) + { + Disp = 2; //Set Disp32 + + //check if the Index register is canceled out as well + + if (IndexReg === 4) //if the Index is canceled out then + { + DispType = 0; //a regular IMM32 is used as the address. + + //*if the Address size is 64 then the 32 bit Immediate must pad to the full 64 bit address. + + if( AddressSize === 3 ) { Disp = 50; } + } + } + + //Else Base register is not 5, and the Mode is not 0 then decode the base register normally. + + else + { + out += REG[ AddressSize ][ BaseExtend & 8 | SIB[2] ]; + + //If the Index Register is not Canceled out (Note this is only reachable if base register was decoded and not canceled out) + + if ( IndexReg !== 4 || VSIB ) + { + out = out + "+"; //Then add the Plus in front of the Base register to add the index register + } + } + + //if Index Register is not Canceled, and that it is not an Vector register then decode the Index with the possibility of the base register. + + if ( IndexReg !== 4 && !VSIB ) + { + out += REG[ AddressSize ][ IndexExtend | IndexReg ]; + + //add what the scale bits decode to the Index register by the value of the scale bits which select the name from the scale array. + + out = out + scale[SIB[0]]; + } + + //Else if it is an vector register. + + else if ( VSIB ) + { + Setting = ( Setting < 8 ) ? 4 : Setting >> 1; + + if( Opcode < 0x700 ) { IndexReg |= ( VectorRegister & 0x10 ); } + + out += DecodeRegValue( IndexExtend | IndexReg, false, Setting ); //Decode Vector register by length setting and the V' extension. + + //add what the scale bits decode to the Index register by the value of the scale bits which select the name from the scale array. + + out = out + scale[SIB[0]]; + } + } //END OF THE SIB BYTE ADDRESS DECODE. + + //else Base register is not 4 and does not go into the SIB ADDRESS. + //Decode the Base register regularly plus it's Extended value if relative (RIP) disp32 is not used. + + else if(DispType !== 2) + { + out += REG[ AddressSize ][ BaseExtend & 8 | ModRM[2] ]; + } + } + + + //Finally the Immediate displacement is put into the Address last. + + if( Disp >= 0 ) { out += DecodeImmediate( DispType, false, Disp ); } + + //Put the right bracket on the address. + + out += "]"; + + //----------------------L1OM/MVEX/EVEX memory conversion mode, or broadcast round----------------------- + + if( + ( ConversionMode !== 0 ) && //Not used if set 0. + !( + ( ConversionMode === 3 && ( Opcode >= 0x700 || !( Opcode >= 0x700 ) && !Float ) ) || //If bad L1OM/K1OM float conversion. + ( !( Opcode >= 0x700 ) && ( VectS === 0 || ( ConversionMode === 5 && VectS === 5 ) || //If K1OM UNorm conversion L1OM only. + ( ConversionMode !== 1 && VectS === 1 ) ^ ( ConversionMode < 3 && !Swizzle ) ) ) //Or K1OM broadcast Swizzle, and special case {4to16} only. + ) + ) + { + //Calculate Conversion. + + if( ConversionMode >= 4 ){ ConversionMode += 2; } + if( ConversionMode >= 8 ){ ConversionMode += 2; } + + //If L1OM. + + if( Opcode >= 0x700 ) + { + //If L1OM without Swizzle. + + if ( !Swizzle && ConversionMode > 2 ) { ConversionMode = 31; } + + //L1OM Float adjust. + + else if( Float ) + { + if( ConversionMode === 7 ) { ConversionMode++; } + if( ConversionMode === 10 ) { ConversionMode = 3; } + } + } + + //Set conversion. Note K1OM special case inverts width bit. + + out += S_C + ConversionModes[ ( ConversionMode << 1 ) | ( WidthBit ^ ( !( Opcode >= 0x700 ) & VectS === 7 ) ) & 1 ]; S_C = ","; + } + + //Else bad Conversion setting. + + else if( ConversionMode !== 0 ) { out += S_C + "Error"; S_C = ","; } + + //--------------------------------END of memory Conversion control logic-------------------------------- + + } //End of Memory address Modes 00, 01, 10 decode. + + //-----------------------------else the ModR/M mode bits are 11 register Mode----------------------------- + + else + { + //------------------------------------------------------------------------------------------------------------------------- + //The Selected Size is setting unless BySize attribute is true. + //------------------------------------------------------------------------------------------------------------------------- + + //MVEX/EVEX round mode. + + if ( ( Extension === 3 && HInt_ZeroMerg ) || ( Extension === 2 && ConversionMode === 1 ) ) + { + RoundMode |= RoundingSetting; + } + + //If the upper 4 bits are defined and by size is false then the upper four bits is the selected register. + + if( ( ( Setting & 0xF0 ) > 0 ) && !BySize ) { Setting >>= 4; } + + //Decode the register with Base expansion. + + out = DecodeRegValue( BaseExtend | ModRM[2], BySize, Setting ); + + //L1OM/K1OM Register swizzle modes. + + if( Opcode >= 0x700 || ( Extension === 3 && !HInt_ZeroMerg && Swizzle ) ) + { + if( Opcode >= 0x700 && ConversionMode >= 3 ){ ConversionMode++; } //L1OM skips swizzle type DACB. + if( ConversionMode !== 0 ){ out += S_C + RegSwizzleModes[ ConversionMode ]; S_C = ","; } + } + if( Extension !== 2 ){ HInt_ZeroMerg = false; } //Cache memory control is not possible in Register mode. + } + + //--------------------------------------------------L1OM.CCCCC conversions------------------------------------------------- + + if( Opcode >= 0x700 ) + { + //Swizzle Field control Int/Float, or Exponent adjustment. + + if(Swizzle) + { + if( Opcode === 0x79A ) { out += S_C + ConversionModes[ ( 18 | ( VectorRegister & 3 ) ) << 1 ]; S_C = "}"; } + else if( Opcode === 0x79B ) { out += S_C + ConversionModes[ ( 22 + ( VectorRegister & 3 ) ) << 1 ]; S_C = "}"; } + else if( ( RoundingSetting & 8 ) === 8 ) { out += S_C + RoundModes [ 24 | ( VectorRegister & 7 ) ]; S_C = "}"; } + } + + //Up/Down Conversion. + + else if( VectorRegister !== 0 ) + { + if( ( ( Up && VectorRegister !== 2 ) || //Up conversion "float16rz" is bad. + ( !Up && VectorRegister !== 3 && VectorRegister <= 15 ) ) //Down conversion "srgb8", and field control is bad. + ) { out += S_C + ConversionModes[ ( ( VectorRegister + 2 ) << 1 ) | WidthBit ]; S_C = "}"; } + else { out += S_C + "Error"; S_C = "}"; } //Else Invalid setting. + } + } + if ( S_C === "," ) { S_C = "}"; } + + //Right bracket if any SSS,CCCCC conversion mode setting. + + if( S_C === "}" ) { out += S_C; } + + //------------------------------------------L1OM/K1OM Hint cache memory control-------------------------------------------- + + if( HInt_ZeroMerg ) + { + if ( Extension === 3 ) { out += "{EH}"; } + else if ( Opcode >= 0x700 ) { out += "{NT}"; } + } + + //-------------------------------------------Return the Register/Memory address-------------------------------------------- + + return (out); +} + +/*------------------------------------------------------------------------------------------------------------------------- +Decode Prefix Mnemonic codes. Prefixes are instruction codes that do not do an operation instead adjust +controls in the CPU to be applied to an select instruction code that is not an Prefix instruction. +--------------------------------------------------------------------------------------------------------------------------- +At the end of this function "Opcode" should not hold any prefix code, so then Opcode contains an operation code. +-------------------------------------------------------------------------------------------------------------------------*/ + +function DecodePrefixAdjustments() +{ + //------------------------------------------------------------------------------------------------------------------------- + Opcode = ( Opcode & 0x300 ) | BinCode[CodePos]; //Add 8 bit opcode while bits 9, and 10 are used for opcode map. + NextByte(); //Move to the next byte. + //------------------------------------------------------------------------------------------------------------------------- + + //if 0F hex start at 256 for Opcode. + + if(Opcode === 0x0F) + { + Opcode = 0x100; //By starting at 0x100 with binary bit 9 set one then adding the 8 bit opcode then Opcode goes 256 to 511. + return(DecodePrefixAdjustments()); //restart function decode more prefix settings that can effect the decode instruction. + } + + //if 38 hex while using two byte opcode. + + else if(Opcode === 0x138 && Mnemonics[0x138] === "") + { + Opcode = 0x200; //By starting at 0x200 with binary bit 10 set one then adding the 8 bit opcode then Opcode goes 512 to 767. + return(DecodePrefixAdjustments()); //restart function decode more prefix settings that can effect the decode instruction. + } + + //if 3A hex while using two byte opcode go three byte opcodes. + + else if(Opcode === 0x13A && Mnemonics[0x13A] === "") + { + Opcode = 0x300; //By starting at 0x300 hex and adding the 8 bit opcode then Opcode goes 768 to 1023. + return(DecodePrefixAdjustments()); //restart function decode more prefix settings that can effect the decode instruction. + } + + //Rex prefix decodes only in 64 bit mode. + + if( Opcode >= 0x40 & Opcode <= 0x4F && BitMode === 2 ) + { + RexActive = 1; //Set Rex active uses 8 bit registers in lower order as 0 to 15. + BaseExtend = ( Opcode & 0x01 ) << 3; //Base Register extend setting. + IndexExtend = ( Opcode & 0x02 ) << 2; //Index Register extend setting. + RegExtend = ( Opcode & 0x04 ) << 1; //Register Extend Setting. + WidthBit = ( Opcode & 0x08 ) >> 3; //Set The Width Bit setting if active. + SizeAttrSelect = WidthBit ? 2 : 1; //The width Bit open all 64 bits. + return(DecodePrefixAdjustments()); //restart function decode more prefix settings that can effect the decode instruction. + } + + //The VEX2 Operation code Extension to SSE settings decoding. + + if( Opcode === 0xC5 && ( BinCode[CodePos] >= 0xC0 || BitMode === 2 ) ) + { + Extension = 1; + //------------------------------------------------------------------------------------------------------------------------- + Opcode = BinCode[CodePos]; //read VEX2 byte settings. + NextByte(); //Move to the next byte. + //------------------------------------------------------------------------------------------------------------------------- + + //some bits are inverted, so uninvert them arithmetically. + + Opcode ^= 0xF8; + + //Decode bit settings. + + if( BitMode === 2 ) + { + RegExtend = ( Opcode & 0x80 ) >> 4; //Register Extend. + VectorRegister = ( Opcode & 0x78 ) >> 3; //The added in Vector register to SSE. + } + + SizeAttrSelect = ( Opcode & 0x04 ) >> 2; //The L bit for 256 vector size. + SIMD = Opcode & 0x03; //The SIMD mode. + + //Automatically uses the two byte opcode map starts at 256 goes to 511. + + Opcode = 0x100; + + //------------------------------------------------------------------------------------------------------------------------- + Opcode = ( Opcode & 0x300 ) | BinCode[CodePos]; //read the opcode. + NextByte(); //Move to the next byte. + //------------------------------------------------------------------------------------------------------------------------- + + //Stop decoding prefixes. + + return(null); + } + + //The VEX3 prefix settings decoding. + + if( Opcode === 0xC4 && ( BinCode[CodePos] >= 0xC0 || BitMode === 2 ) ) + { + Extension = 1; + //------------------------------------------------------------------------------------------------------------------------- + Opcode = BinCode[CodePos]; //read VEX3 byte settings. + NextByte(); //Move to the next byte. + //------------------------------------------------------------------------------------------------------------------------- + Opcode |= ( BinCode[CodePos] << 8 ); //Read next VEX3 byte settings. + NextByte(); //Move to the next byte. + //------------------------------------------------------------------------------------------------------------------------- + + //Some bits are inverted, so uninvert them arithmetically. + + Opcode ^= 0x78E0; + + //Decode bit settings. + + if( BitMode === 2 ) + { + RegExtend = ( Opcode & 0x0080 ) >> 4; //Extend Register Setting. + IndexExtend = ( Opcode & 0x0040 ) >> 3; //Extend Index register setting. + BaseExtend = ( Opcode & 0x0020 ) >> 2; //Extend base Register setting. + } + + WidthBit = ( Opcode & 0x8000 ) >> 15; //The width bit works as a separator. + VectorRegister = ( Opcode & 0x7800 ) >> 11; //The added in Vector register to SSE. + SizeAttrSelect = ( Opcode & 0x0400 ) >> 10; //Vector length for 256 setting. + SIMD = ( Opcode & 0x0300 ) >> 8; //The SIMD mode. + Opcode = ( Opcode & 0x001F ) << 8; //Change Operation code map. + + //------------------------------------------------------------------------------------------------------------------------- + Opcode = ( Opcode & 0x300 ) | BinCode[CodePos]; //read the 8 bit opcode put them in the lower 8 bits away from opcode map bit's. + NextByte(); //Move to the next byte. + //------------------------------------------------------------------------------------------------------------------------- + + return(null); + } + + //The AMD XOP prefix. + + if( Opcode === 0x8F ) + { + //If XOP + + var Code = BinCode[ CodePos ] & 0x0F; + + if( Code >= 8 && Code <= 10 ) + { + Extension = 1; + //------------------------------------------------------------------------------------------------------------------------- + Opcode = BinCode[CodePos]; //read XOP byte settings. + NextByte(); //Move to the next byte. + //------------------------------------------------------------------------------------------------------------------------- + Opcode |= ( BinCode[CodePos] << 8 ); //Read next XOP byte settings. + NextByte(); //Move to the next byte. + //------------------------------------------------------------------------------------------------------------------------- + + //Some bits are inverted, so uninvert them arithmetically. + + Opcode ^= 0x78E0; + + //Decode bit settings. + + RegExtend = ( Opcode & 0x0080 ) >> 4; //Extend Register Setting. + IndexExtend = ( Opcode & 0x0040 ) >> 3; //Extend Index register setting. + BaseExtend = ( Opcode & 0x0020 ) >> 2; //Extend base Register setting. + WidthBit = ( Opcode & 0x8000 ) >> 15; //The width bit works as a separator. + VectorRegister = ( Opcode & 0x7800 ) >> 11; //The added in Vector register to SSE. + SizeAttrSelect = ( Opcode & 0x0400 ) >> 10; //Vector length for 256 setting. + SIMD = ( Opcode & 0x0300 ) >> 8; //The SIMD mode. + if( SIMD > 0 ) { InvalidOp = true; } //If SIMD MODE is set anything other than 0 the instruction is invalid. + Opcode = 0x400 | ( ( Opcode & 0x0003 ) << 8 ); //Change Operation code map. + + //------------------------------------------------------------------------------------------------------------------------- + Opcode = ( Opcode & 0x700 ) | BinCode[CodePos]; //read the 8 bit opcode put them in the lower 8 bits away from opcode map bit's. + NextByte(); //Move to the next byte. + //------------------------------------------------------------------------------------------------------------------------- + + return(null); + } + } + + //The L1OM vector prefix settings decoding. + + if( Opcode === 0xD6 ) + { + //------------------------------------------------------------------------------------------------------------------------- + Opcode = BinCode[CodePos]; //read L1OM byte settings. + NextByte(); //Move to the next byte. + //------------------------------------------------------------------------------------------------------------------------- + Opcode |= ( BinCode[CodePos] << 8 ); //Read next L1OM byte settings. + NextByte(); //Move to the next byte. + //------------------------------------------------------------------------------------------------------------------------- + + WidthBit = SIMD & 1; + VectorRegister = ( Opcode & 0xF800 ) >> 11; + RoundMode = VectorRegister >> 3; + MaskRegister = ( Opcode & 0x0700 ) >> 8; + HInt_ZeroMerg = ( Opcode & 0x0080 ) >> 7; + ConversionMode = ( Opcode & 0x0070 ) >> 4; + RegExtend = ( Opcode & 0x000C ) << 1; + BaseExtend = ( Opcode & 0x0003 ) << 3; + IndexExtend = ( Opcode & 0x0002 ) << 2; + + //------------------------------------------------------------------------------------------------------------------------- + Opcode = 0x700 | BinCode[CodePos]; //read the 8 bit opcode. + NextByte(); //Move to the next byte. + //------------------------------------------------------------------------------------------------------------------------- + + return(null); + } + + //Only decode L1OM instead of MVEX/EVEX if L1OM compatibility mode is set. + + if( Mnemonics[0x62] === "" && Opcode === 0x62 ) + { + //------------------------------------------------------------------------------------------------------------------------- + Opcode = BinCode[CodePos]; //read L1OM byte settings. + NextByte(); //Move to the next byte. + //------------------------------------------------------------------------------------------------------------------------- + + Opcode ^= 0xF0; + + IndexExtend = ( Opcode & 0x80 ) >> 4; + BaseExtend = ( Opcode & 0x40 ) >> 3; + RegExtend = ( Opcode & 0x20 ) >> 2; + + if ( SIMD !== 1 ) { SizeAttrSelect = ( ( Opcode & 0x10 ) === 0x10 ) ? 2 : 1; } else { SIMD = 0; } + + Opcode = 0x800 | ( ( Opcode & 0x30 ) >> 4 ) | ( ( Opcode & 0x0F ) << 2 ); + + return(null); + } + + //The MVEX/EVEX prefix settings decoding. + + if ( Opcode === 0x62 && ( BinCode[CodePos] >= 0xC0 || BitMode === 2 ) ) + { + Extension = 2; + //------------------------------------------------------------------------------------------------------------------------- + Opcode = BinCode[CodePos]; //read MVEX/EVEX byte settings. + NextByte(); //Move to the next byte. + //------------------------------------------------------------------------------------------------------------------------- + Opcode |= ( BinCode[CodePos] << 8 ); //read next MVEX/EVEX byte settings. + NextByte(); //Move to the next byte. + //------------------------------------------------------------------------------------------------------------------------- + Opcode |= ( BinCode[CodePos] << 16 ); //read next MVEX/EVEX byte settings. + NextByte(); //Move to the next byte. + //------------------------------------------------------------------------------------------------------------------------- + + //Some bits are inverted, so uninvert them arithmetically. + + Opcode ^= 0x0878F0; + + //Check if Reserved bits are 0 if they are not 0 the MVEX/EVEX instruction is invalid. + + InvalidOp = ( Opcode & 0x00000C ) > 0; + + //Decode bit settings. + + if( BitMode === 2 ) + { + RegExtend = ( ( Opcode & 0x80 ) >> 4 ) | ( Opcode & 0x10 ); //The Double R'R bit decode for Register Extension 0 to 32. + BaseExtend = ( Opcode & 0x60 ) >> 2; //The X bit, and B Bit base register extend combination 0 to 32. + IndexExtend = ( Opcode & 0x40 ) >> 3; //The X extends the SIB Index by 8. + } + + VectorRegister = ( ( Opcode & 0x7800 ) >> 11 ) | ( ( Opcode & 0x080000 ) >> 15 ); //The Added in Vector Register for SSE under MVEX/EVEX. + + WidthBit = ( Opcode & 0x8000 ) >> 15; //The width bit separator for MVEX/EVEX. + SIMD = ( Opcode & 0x0300 ) >> 8; //decode the SIMD mode setting. + HInt_ZeroMerg = ( Opcode & 0x800000 ) >> 23; //Zero Merge to destination control, or MVEX EH control. + + //EVEX option bits take the place of Vector length control. + + if ( ( Opcode & 0x0400 ) > 0 ) + { + SizeAttrSelect = ( Opcode & 0x600000 ) >> 21; //The EVEX.L'L Size combination. + RoundMode = SizeAttrSelect | 4; //Rounding mode is Vector length if used. + ConversionMode = (Opcode & 0x100000 ) >> 20; //Broadcast Round Memory address system. + } + + //MVEX Vector Length, and Broadcast round. + + else + { + SizeAttrSelect = 2; //Max Size by default. + ConversionMode = ( Opcode & 0x700000 ) >> 20; //"MVEX.sss" Option bits. + RoundMode = ConversionMode; //Rounding mode selection is ConversionMode if used. + Extension = 3; + } + + MaskRegister = ( Opcode & 0x070000 ) >> 16; //Mask to destination. + Opcode = ( Opcode & 0x03 ) << 8; //Change Operation code map. + + //------------------------------------------------------------------------------------------------------------------------- + Opcode = ( Opcode & 0x300 ) | BinCode[CodePos]; //read the 8 bit opcode put them in the lower 8 bits away from opcode map extend bit's. + NextByte(); //Move to the next byte. + //------------------------------------------------------------------------------------------------------------------------- + + //Stop decoding prefixes. + + return(null); + } + + //Segment overrides + + if ( ( Opcode & 0x7E7 ) === 0x26 || ( Opcode & 0x7FE ) === 0x64 ) + { + SegOverride = Mnemonics[ Opcode ]; //Set the Left Bracket for the ModR/M memory address. + return(DecodePrefixAdjustments()); //restart function decode more prefix settings that can effect the decode instruction. + } + + //Operand override Prefix + + if(Opcode === 0x66) + { + SIMD = 1; //sets SIMD mode 1 in case of SSE instruction opcode. + SizeAttrSelect = 0; //Adjust the size attribute setting for the size adjustment to the next instruction. + return(DecodePrefixAdjustments()); //restart function decode more prefix settings that can effect the decode instruction. + } + + //Ram address size override. + + if(Opcode === 0x67) + { + AddressOverride = true; //Set the setting active for the ModR/M address size. + return(DecodePrefixAdjustments()); //restart function decode more prefix settings that can effect the decode instruction. + } + + //if repeat Prefixes F2 hex REP,or F3 hex RENP + + if (Opcode === 0xF2 || Opcode === 0xF3) + { + SIMD = (Opcode & 0x02 ) | ( 1 - Opcode & 0x01 ); //F2, and F3 change the SIMD mode during SSE instructions. + PrefixG1 = Mnemonics[ Opcode ]; //set the Prefix string. + HLEFlipG1G2 = true; //set Filp HLE in case this is the last prefix read, and LOCK was set in string G2 first for HLE. + return(DecodePrefixAdjustments()); //restart function decode more prefix settings that can effect the decode instruction. + } + + //if the lock prefix note the lock prefix is separate + + if (Opcode === 0xF0) + { + PrefixG2 = Mnemonics[ Opcode ]; //set the Prefix string + HLEFlipG1G2 = false; //set Flip HLE false in case this is the last prefix read, and REP, or REPNE was set in string G2 first for HLE. + return(DecodePrefixAdjustments()); //restart function decode more prefix settings that can effect the decode instruction. + } + + //Before ending the function "DecodePrefixAdjustments()" some opcode combinations are invalid in 64 bit mode. + + if ( BitMode === 2 ) + { + InvalidOp |= ( ( ( Opcode & 0x07 ) >= 0x06 ) & ( Opcode <= 0x40 ) ); + InvalidOp |= ( Opcode === 0x60 | Opcode === 0x61 ); + InvalidOp |= ( Opcode === 0xD4 | Opcode === 0xD5 ); + InvalidOp |= ( Opcode === 0x9A | Opcode === 0xEA ); + InvalidOp |= ( Opcode === 0x82 ); + } + +} + +/*------------------------------------------------------------------------------------------------------------------------- +The Decode opcode function gives back the operation name, and what it uses for input. +The input types are for example which registers it uses with the ModR/M, or which Immediate type is used. +The input types are stored into an operand string. This function gives back the instruction name, And what the operands use. +--------------------------------------------------------------------------------------------------------------------------- +This function is designed to be used after the Decode prefix adjustments function because the Opcode should contain an real instruction code. +This is because the Decode prefix adjustments function will only end if the Opcode value is not a prefix adjustment code to the ModR/M etc. +However DecodePrefixAdjustments can also prevent this function from being called next if the prefix settings are bad or an invalid instruction is +used for the bit mode the CPU is in as it will set InvalidOp true. +-------------------------------------------------------------------------------------------------------------------------*/ + +function DecodeOpcode() +{ + //get the Operation name by the operations opcode. + + Instruction = Mnemonics[Opcode]; + + //get the Operands for this opcode it follows the same array structure as Mnemonics array + + InsOperands = Operands[Opcode]; + + //Some Opcodes use the next byte automatically for extended opcode selection. Or current SIMD mode. + + var ModRMByte = BinCode[CodePos]; //Read the byte but do not move to the next byte. + + //If the current Mnemonic is an array two in size then Register Mode, and memory mode are separate from each other. + //Used in combination with Grouped opcodes, and Static opcodes. + + if(Instruction instanceof Array && Instruction.length == 2) { var bits = ( ModRMByte >> 6 ) & ( ModRMByte >> 7 ); Instruction = Instruction[bits]; InsOperands = InsOperands[bits]; } + + //Arithmetic unit 8x8 combinational logic array combinations. + //If the current Mnemonic is an array 8 in length It is a group opcode instruction may repeat previous instructions in different forums. + + if(Instruction instanceof Array && Instruction.length == 8) { var bits = ( ModRMByte & 0x38 ) >> 3; Instruction = Instruction[bits]; InsOperands = InsOperands[bits]; + + //if The select Group opcode is another array 8 in size it is a static opcode selection which makes the last three bits of the ModR/M byte combination. + + if(Instruction instanceof Array && Instruction.length == 8) { var bits = ( ModRMByte & 0x07 ); Instruction = Instruction[bits]; InsOperands = InsOperands[bits]; NextByte(); } } + + //Vector unit 4x4 combinational array logic. + //if the current Mnemonic is an array 4 in size it is an SIMD instruction with four possible modes N/A, 66, F3, F2. + //The mode is set to SIMD, it could have been set by the EVEX.pp, VEX.pp bit combination, or by prefixes N/A, 66, F3, F2. + + if(Instruction instanceof Array && Instruction.length == 4) + { + Vect = true; //Set Vector Encoding true. + + //Reset the prefix string G1 because prefix codes F2, and F3 are used with SSE which forum the repeat prefix. + //Some SSE instructions can use the REP, RENP prefixes. + //The Vectors that do support the repeat prefix uses Packed Single format. + + if(Instruction[2] !== "" && Instruction[3] !== "") { PrefixG1 = ""; } else { SIMD = ( SIMD === 1 ) & 1; } + Instruction = Instruction[SIMD]; InsOperands = InsOperands[SIMD]; + + //If the SIMD instruction uses another array 4 in length in the Selected SIMD vector Instruction. + //Then each vector Extension is separate. The first extension is used if no extension is active for Regular instructions, and vector instruction septation. + //0=None. 1=VEX only. 2=EVEX only. 3=??? unused. + + if(Instruction instanceof Array && Instruction.length == 4) + { + //Get the correct Instruction for the Active Extension type. + + if(Instruction[Extension] !== "") { Instruction = Instruction[Extension]; InsOperands = InsOperands[Extension]; } + else{ Instruction = "???"; InsOperands = ""; } + } + else if( Extension === 3 ){ Instruction = "???"; InsOperands = ""; } + } + else if( Opcode >= 0x700 && SIMD > 0 ){ Instruction = "???"; InsOperands = ""; } + + //if Any Mnemonic is an array 3 in size the instruction name goes by size. + + if(Instruction instanceof Array && Instruction.length == 3) + { + var bits = ( Extension === 0 & BitMode !== 0 ) ^ ( SizeAttrSelect >= 1 ); //The first bit in SizeAttrSelect for size 32/16 Flips if 16 bit mode. + ( WidthBit ) && ( bits = 2 ); //Goes 64 using the Width bit. + ( Extension === 3 && HInt_ZeroMerg && Instruction[1] !== "" ) && ( HInt_ZeroMerg = false, bits = 1 ); //MVEX uses element 1 if MVEX.E is set for instruction that change name. + + if (Instruction[bits] !== "") { Instruction = Instruction[bits]; InsOperands = InsOperands[bits]; } + + //else no size prefix name then use the default size Mnemonic name. + + else { Instruction = Instruction[0]; InsOperands = InsOperands[0]; } + } + + //If Extension is not 0 then add the vector extend "V" to the instruction. + //During the decoding of the operands the instruction can be returned as invalid if it is an Arithmetic, or MM, ST instruction. + //Vector mask instructions start with K instead of V any instruction that starts with K and is an + //vector mask Instruction which starts with K instead of V. + + if( Opcode <= 0x400 && Extension > 0 && Instruction.charAt(0) !== "K" && Instruction !== "???" ) { Instruction = "V" + Instruction; } + + //In 32 bit mode, or bellow only one instruction MOVSXD is replaced with ARPL. + + if( BitMode <= 1 && Instruction === "MOVSXD" ) { Instruction = "ARPL"; InsOperands = "06020A01"; } +} + +/*------------------------------------------------------------------------------------------------------------------------- +Read each operand in the Operand String then set the correct operand in the X86 decoder array. +OpNum is the order the operands are read in the operand string. The Operand type is which operand will be set +active along the X86Decoder. The OpNum is the order the decoded operands will be positioned after they are decoded +in order along the X86 decoder. The order the operands display is different than the order they decode in sequence. +--------------------------------------------------------------------------------------------------------------------------- +This function is used after the function ^DecodeOpcode()^ because the function ^DecodeOpcode()^ gives back the +operand string for what the instruction takes as input. +-------------------------------------------------------------------------------------------------------------------------*/ + +function DecodeOperandString() +{ + //Variables that are used for decoding one operands across the operand string. + + var OperandValue = 0, Code = 0, BySize = 0, Setting = 0; + + //It does not matter which order the explicit operands decode as they do not require reading another byte. + //They start at 7 and are set in order, but the order they are displayed is the order they are read in the operand string because of OpNum. + + var ExplicitOp = 8, ImmOp = 3; + + //Each operand is 4 hex digits, and OpNum is added by one for each operand that is read per Iteration. + + for( var i = 0, OpNum = 0; i < InsOperands.length; i+=4 ) //Iterate though operand string. + { + OperandValue = parseInt( InsOperands.substring(i, (i + 4) ), 16 ); //Convert the four hex digits to a 16 bit number value. + Code = ( OperandValue & 0xFE00 ) >> 9; //Get the operand Code. + BySize = ( OperandValue & 0x0100 ) >> 8; //Get it's by size attributes setting for if Setting is used as size attributes. + Setting = ( OperandValue & 0x00FF ); //Get the 8 bit Size setting. + + //If code is 0 the next 8 bit value specifies which type of of prefix settings are active. + + if( Code === 0 ) + { + if(BySize) //Vector adjustment settings. + { + RoundingSetting = ( Setting & 0x03 ) << 3; + if( Opcode >= 0x700 && RoundingSetting >= 0x10 ){ RoundMode |= 0x10; } + VSIB = ( Setting >> 2 ) & 1; + IgnoresWidthbit = ( Setting >> 3 ) & 1; + VectS = ( Setting >> 4 ) & 7; + Swizzle = ( VectS >> 2 ) & 1; + Up = ( VectS >> 1 ) & 1; + Float = VectS & 1; + if( ( Setting & 0x80 ) == 0x80 ) { Vect = false; } //If Non vector instruction set Vect false. + } + else //Instruction Prefix types. + { + XRelease = Setting & 0x01; + XAcquire = ( Setting & 0x02 ) >> 1; + HT = ( Setting & 0x04 ) >> 2; + BND = ( Setting & 0x08 ) >> 3; + } + } + + //if it is a opcode Reg Encoding then first element along the decoder is set as this has to be decode first, before moving to the + //byte for modR/M. + + else if( Code === 1 ) + { + X86Decoder[0].set( 0, BySize, Setting, OpNum++ ); + } + + //if it is a ModR/M, or Far pointer ModR/M, or Moffs address then second decoder element is set. + + else if( Code >= 2 && Code <= 4 ) + { + X86Decoder[1].set( ( Code - 2 ), BySize, Setting, OpNum++ ); + if( Code == 4 ){ FarPointer = 1; } //If code is 4 it is a far pointer. + } + + //The ModR/M Reg bit's are separated from the address system above. The ModR/M register can be used as a different register with a + //different address pointer. The Reg bits of the ModR/M decode next as it would be inefficient to read the register value if the + //decoder moves to the immediate. + + else if( Code === 5 ) + { + X86Decoder[2].set( 0, BySize, Setting, OpNum++ ); + } + + //Immediate input one. The immediate input is just a number input it is decoded last unless the instruction does not use a + //ModR/M encoding, or Reg Opcode. + + else if( Code >= 6 && Code <= 8 && ImmOp <= 5 ) + { + X86Decoder[ImmOp++].set( ( Code - 6 ), BySize, Setting, OpNum++ ); + } + + //Vector register. If the instruction uses this register it will not be decoded or displayed unless one of the vector extension codes are + //decoded by the function ^DecodePrefixAdjustments()^. The Vector extension codes also have a Vector register value that is stored into + //the variable VectorRegister. The variable VectorRegister is given to the function ^DecodeRegValue()^. + + else if( Code === 9 && ( Extension > 0 || Opcode >= 0x700 ) ) + { + X86Decoder[6].set( 0, BySize, Setting, OpNum++ ); + } + + //The upper four bits of the Immediate is used as an register. The variable IMM stores the last immediate byte that is read by ^DecodeImmediate()^. + //The upper four bits of the IMM is given to the function ^DecodeRegValue()^. + + else if( Code === 10 ) + { + X86Decoder[7].set( 0, BySize, Setting, OpNum++ ); + } + + //Else any other encoding type higher than 13 is an explicit operand selection. + //And also there can only be an max of four explicit operands. + + else if( Code >= 11 && ExplicitOp <= 11) + { + X86Decoder[ExplicitOp].set( ( Code - 11 ), BySize, Setting, OpNum++ ); + ExplicitOp++; //move to the next Explicit operand. + } + } +} + +/*------------------------------------------------------------------------------------------------------------------------- +Decode each of the operands along the X86Decoder and deactivate them. +This function is used after ^DecodeOperandString()^ which sets up the X86 Decoder for the instructions operands. +-------------------------------------------------------------------------------------------------------------------------*/ + +function DecodeOperands() +{ + //The Operands array is a string array in which the operand number is the element the decoded operand is positioned. + + var out = []; + + //This holds the decoded ModR/M byte from the "Decode_ModRM_SIB_Value()" function because the Register, and Address can decode differently. + + var ModRMByte = [ -1, //Mode is set negative one used to check if the ModR/M has been decoded. + 0, //The register value is decoded separately if used. + 0 //the base register for the address location. + ]; + + //If no Immediate operand is used then the Immediate register encoding forces an IMM8 for the register even if the immediate is not used. + + var IMM_Used = false; //This is set true for if any Immediate is read because the last Immediate byte is used as the register on the upper four bits. + + //If reg opcode is active. + + if( X86Decoder[0].Active ) + { + out[ X86Decoder[0].OpNum ] = DecodeRegValue( + ( RegExtend | ( Opcode & 0x07 ) ), //Register value. + X86Decoder[0].BySizeAttrubute, //By size attribute or not. + X86Decoder[0].Size //Size settings. + ); + } + + //If ModR/M Address is active. + + if( X86Decoder[1].Active ) + { + //Decode the ModR/M byte Address which can end up reading another byte for SIB address, and including displacements. + + if(X86Decoder[1].Type !== 0) + { + ModRMByte = Decode_ModRM_SIB_Value(); //Decode the ModR/M byte. + out[ X86Decoder[1].OpNum ] = Decode_ModRM_SIB_Address( + ModRMByte, //The ModR/M byte. + X86Decoder[1].BySizeAttrubute, //By size attribute or not. + X86Decoder[1].Size //Size settings. + ); + } + + //Else If the ModR/M type is 0 then it is a moffs address. + + else + { + var s=0, AddrsSize = 0; + if( X86Decoder[1].BySizeAttrubute ) + { + AddrsSize = ( Math.pow( 2, BitMode ) << 1 ); + s = GetOperandSize( X86Decoder[1].Size, true ) << 1; + } + else + { + AddrsSize = BitMode + 1; + s = X86Decoder[1].Size; + } + out[ X86Decoder[1].OpNum ] = PTR[ s ]; + out[ X86Decoder[1].OpNum ] += SegOverride + DecodeImmediate( 0, X86Decoder[1].BySizeAttrubute, AddrsSize ) + "]"; + } + } + + //Decode the Register value of the ModR/M byte. + + if( X86Decoder[2].Active ) + { + //If the ModR/M address is not used, and ModR/M byte was not previously decoded then decode it. + + if( ModRMByte[0] === -1 ){ ModRMByte = Decode_ModRM_SIB_Value(); } + + //Decode only the Register Section of the ModR/M byte values. + + out[ X86Decoder[2].OpNum ] = DecodeRegValue( + ( RegExtend | ( ModRMByte[1] & 0x07 ) ), //Register value. + X86Decoder[2].BySizeAttrubute, //By size attribute or not. + X86Decoder[2].Size //Size settings. + ); + } + + //First Immediate if used. + + if( X86Decoder[3].Active ) + { + var t = DecodeImmediate( + X86Decoder[3].Type, //Immediate input type. + X86Decoder[3].BySizeAttrubute, //By size attribute or not. + X86Decoder[3].Size //Size settings. + ); + + //Check if Instruction uses condition codes. + + if( Instruction.slice(-1) === "," ) + { + Instruction = Instruction.split(","); + + if( ( Extension >= 1 && Extension <= 2 && Opcode <= 0x400 && IMMValue < 0x20 ) || IMMValue < 0x08 ) + { + IMMValue |= ( ( ( Opcode > 0x400 ) & 1 ) << 5 ); //XOP adjust. + Instruction = Instruction[0] + ConditionCodes[ IMMValue ] + Instruction[1]; + } + else { Instruction = Instruction[0] + Instruction[1]; out[ X86Decoder[3].OpNum ] = t; } + } + + //else add the Immediate byte encoding to the decoded instruction operands. + + else { out[ X86Decoder[3].OpNum ] = t; } + + IMM_Used = true; //Immediate byte is read. + } + + //Second Immediate if used. + + if( X86Decoder[4].Active ) + { + out[ X86Decoder[4].OpNum ] = DecodeImmediate( + X86Decoder[4].Type, //Immediate input type. + X86Decoder[4].BySizeAttrubute, //By size attribute or not. + X86Decoder[4].Size //Size settings. + ); + } + + //Third Immediate if used. + + if( X86Decoder[5].Active ) + { + out[ X86Decoder[5].OpNum ] = DecodeImmediate( + X86Decoder[5].Type, //Immediate input type. + X86Decoder[5].BySizeAttrubute, //By size attribute or not. + X86Decoder[5].Size //Size settings. + ); + } + + //Vector register if used from an SIMD vector extended instruction. + + if( X86Decoder[6].Active ) + { + out[ X86Decoder[6].OpNum ] = DecodeRegValue( + VectorRegister, //Register value. + X86Decoder[6].BySizeAttrubute, //By size attribute or not. + X86Decoder[6].Size //Size settings. + ); + } + + //Immediate register encoding. + + if( X86Decoder[7].Active ) + { + if( !IMM_Used ) { DecodeImmediate(0, false, 0); } //forces IMM8 if no Immediate has been used. + out[ X86Decoder[7].OpNum ] = DecodeRegValue( + ( ( ( IMMValue & 0xF0 ) >> 4 ) | ( ( IMMValue & 0x08 ) << 1 ) ), //Register value. + X86Decoder[7].BySizeAttrubute, //By size attribute or not. + X86Decoder[7].Size //Size settings. + ); + } + + //------------------------------------------------------------------------------------------------------------------------- + //Iterate though the 4 possible Explicit operands The first operands that is not active ends the Iteration. + //------------------------------------------------------------------------------------------------------------------------- + + for( var i = 8; i < 11; i++ ) + { + //------------------------------------------------------------------------------------------------------------------------- + //if Active Type is used as which Explicit operand. + //------------------------------------------------------------------------------------------------------------------------- + + if( X86Decoder[i].Active ) + { + //General use registers value 0 though 4 there size can change by size setting but can not be extended or changed. + + if( X86Decoder[i].Type <= 3 ) + { + out[ X86Decoder[i].OpNum ] = DecodeRegValue( + X86Decoder[i].Type, //register by value for Explicit Registers A, C, D, B. + X86Decoder[i].BySizeAttrubute, //By size attribute or not. + X86Decoder[i].Size //Size attribute. + ); + } + + //RBX address Explicit Operands prefixes can extend the registers and change pointer size RegMode 0. + + else if( X86Decoder[i].Type === 4 ) + { + s = 3; //If 32, or 64 bit ModR/M. + if( ( BitMode === 0 && !AddressOverride ) || ( BitMode === 1 && AddressOverride ) ){ s = 7; } //If 16 bit ModR/M. + out[ X86Decoder[i].OpNum ] = Decode_ModRM_SIB_Address( + [ 0, 0, s ], //the RBX register only for the pointer. + X86Decoder[i].BySizeAttrubute, //By size attribute or not. + X86Decoder[i].Size //size attributes. + ); + } + + //source and destination address Explicit Operands prefixes can extend the registers and change pointer size. + + else if( X86Decoder[i].Type === 5 | X86Decoder[i].Type === 6 ) + { + s = 1; //If 32, or 64 bit ModR/M. + if( ( BitMode === 0 && !AddressOverride ) || ( BitMode === 1 & AddressOverride ) ) { s = -1; } //If 16 bit ModR/M. + out[ X86Decoder[i].OpNum ] = Decode_ModRM_SIB_Address( + [ 0, 0, ( X86Decoder[i].Type + s ) ], //source and destination pointer register by type value. + X86Decoder[i].BySizeAttrubute, //By size attribute or not. + X86Decoder[i].Size //size attributes. + ); + } + + //The ST only Operand, and FS, GS. + + else if( X86Decoder[i].Type >= 7 ) + { + out[ X86Decoder[i].OpNum ] = ["ST", "FS", "GS", "1", "3", "XMM0", "M10"][ ( X86Decoder[i].Type - 7 ) ]; + } + } + + //------------------------------------------------------------------------------------------------------------------------- + //else inactive end iteration. + //------------------------------------------------------------------------------------------------------------------------- + + else { break; } + + } + + /*------------------------------------------------------------------------------------------------------------------------- + If the EVEX vector extension is active the Mask, and Zero merge control are inserted into operand 0 (Destination operand). + -------------------------------------------------------------------------------------------------------------------------*/ + + //Mask Register is used if it is not 0 in value. + + if( MaskRegister !== 0 ){ out[0] += "{K" + MaskRegister + "}"; } + + //EVEX Zero Merge control. + + if( Extension === 2 && HInt_ZeroMerg ) { out[0] += "{Z}"; } + + //convert the operand array to a string and return it. + + InsOperands = out.toString(); +} + +/*------------------------------------------------------------------------------------------------------------------------- +The main Instruction decode function plugs everything in together for the steps required to decode a full X86 instruction. +-------------------------------------------------------------------------------------------------------------------------*/ + +function DecodeInstruction() +{ + //Reset Prefix adjustments, and vector setting adjustments. + + Reset(); + + var out = ""; //The instruction code that will be returned back from this function. + + //Record the starting position. + + InstructionPos = GetPosition(); + + //First read any opcodes (prefix) that act as adjustments to the main three operand decode functions ^DecodeRegValue()^, + //^Decode_ModRM_SIB_Address()^, and ^DecodeImmediate()^. + + DecodePrefixAdjustments(); + + //Only continue if an invalid opcode is not read by DecodePrefixAdjustments() for cpu bit mode setting. + + if( !InvalidOp ) + { + //Decode the instruction. + + DecodeOpcode(); + + //------------------------------------------------------------------------------------------------------------------------- + //Intel Larrabee CCCCC condition codes. + //------------------------------------------------------------------------------------------------------------------------- + + if( Opcode >= 0x700 && Instruction.slice(-1) === "," ) + { + Instruction = Instruction.split(","); + + //CMP conditions. + + if( Opcode >= 0x720 && Opcode <= 0x72F ) + { + IMMValue = VectorRegister >> 2; + + if( Float || ( IMMValue !== 3 && IMMValue !== 7 ) ) + { + Instruction = Instruction[0] + ConditionCodes[IMMValue] + Instruction[1]; + } + else { Instruction = Instruction[0] + Instruction[1]; } + + IMMValue = 0; VectorRegister &= 0x03; + } + + //Else High/Low. + + else + { + Instruction = Instruction[0] + ( ( ( VectorRegister & 1 ) === 1 ) ? "H" : "L" ) + Instruction[1]; + } + } + + //Setup the X86 Decoder for which operands the instruction uses. + + DecodeOperandString(); + + //Now only some instructions can vector extend, and that is only if the instruction is an SIMD Vector format instruction. + + if( !Vect && Extension > 0 && Opcode <= 0x400 ) { InvalidOp = true; } + + //The Width Bit setting must match the vector numbers size otherwise this create an invalid operation code in MVEX/EVEX unless the Width bit is ignored. + + if( Vect && !IgnoresWidthbit && Extension >= 2 ) + { + InvalidOp = ( ( SIMD & 1 ) !== ( WidthBit & 1 ) ); //Note use, and ignore width bit pastern EVEX. + } + if( Opcode >= 0x700 ) { WidthBit ^= IgnoresWidthbit; } //L1OM Width bit invert. + } + + //If the instruction is invalid then set the instruction to "???" + + if( InvalidOp ) + { + out = "???" //set the returned instruction to invalid + } + + //Else finish decoding the valid instruction. + + else + { + //Decode each operand along the Decoder array in order, and deactivate them. + + DecodeOperands(); + + /*------------------------------------------------------------------------------------------------------------------------- + 3DNow Instruction name is encoded by the next byte after the ModR/M, and Reg operands. + -------------------------------------------------------------------------------------------------------------------------*/ + + if( Opcode === 0x10F ) + { + //Lookup operation code. + + Instruction = M3DNow[ BinCode[CodePos] ]; NextByte(); + + //If Invalid instruction. + + if( Instruction === "" || Instruction == null ) + { + Instruction = "???"; InsOperands = ""; + } + } + + /*------------------------------------------------------------------------------------------------------------------------- + Synthetic virtual machine operation codes. + -------------------------------------------------------------------------------------------------------------------------*/ + + else if( Instruction === "SSS" ) + { + //The Next two bytes after the static opcode is the select synthetic virtual machine operation code. + + var Code1 = BinCode[CodePos]; NextByte(); + var Code2 = BinCode[CodePos]; NextByte(); + + //No operations exist past 4 in value for both bytes that combine to the operation code. + + if( Code1 >= 5 || Code2 >= 5 ) { Instruction = "???"; } + + //Else calculate the operation code in the 5x5 map. + + else + { + Instruction = MSynthetic[ ( Code1 * 5 ) + Code2 ]; + + //If Invalid instruction. + + if( Instruction === "" || Instruction == null ) + { + Instruction = "???"; + } + } + } + + //32/16 bit instructions 9A, and EA use Segment, and offset with Immediate format. + + if( Opcode === 0x9A || Opcode === 0xEA ) + { + var t = InsOperands.split(","); + InsOperands = t[1] + ":" +t[0]; + } + + //**Depending on the operation different prefixes replace others for HLE, or MPX, and branch prediction. + //if REP prefix, and LOCK prefix are used together, and the current decoded operation allows HLE XRELEASE. + + if(PrefixG1 === Mnemonics[0xF3] && PrefixG2 === Mnemonics[0xF0] && XRelease) + { + PrefixG1 = "XRELEASE"; //Then change REP to XRELEASE. + } + + //if REPNE prefix, and LOCK prefix are used together, and the current decoded operation allows HLE XACQUIRE. + + if(PrefixG1 === Mnemonics[0xF2] && PrefixG2 === Mnemonics[0xF0] && XAcquire) + { + PrefixG1 = "XACQUIRE"; //Then change REP to XACQUIRE + } + + //Depending on the order that the Repeat prefix, and Lock prefix is used flip Prefix G1, and G2 if HLEFlipG1G2 it is true. + + if((PrefixG1 === "XRELEASE" || PrefixG1 === "XACQUIRE") && HLEFlipG1G2) + { + t = PrefixG1; PrefixG1 = PrefixG2; PrefixG2 = t; + } + + //if HT is active then it is a jump instruction check and adjust for the HT,and HNT prefix. + + if(HT) + { + if (SegOverride === Mnemonics[0x2E]) + { + PrefixG1 = "HNT"; + } + else if (SegOverride === Mnemonics[0x3E]) + { + PrefixG1 = "HT"; + } + } + + //else if Prefix is REPNE switch it to BND if operation is a MPX instruction. + + if(PrefixG1 === Mnemonics[0xF2] && BND) + { + PrefixG1 = "BND"; + } + + //Before the Instruction is put together check the length of the instruction if it is longer than 15 bytes the instruction is undefined. + + if ( InstructionHex.length > 30 ) + { + //Calculate how many bytes over. + + var Dif32 = ( ( InstructionHex.length - 30 ) >> 1 ); + + //Limit the instruction hex output to 15 bytes. + + InstructionHex = InstructionHex.substring( 0, 30 ); + + //Calculate the Difference between the Disassembler current position. + + Dif32 = Pos32 - Dif32; + + //Convert Dif to unsignified numbers. + + if( Dif32 < 0 ) { Dif32 += 0x100000000; } + + //Convert to strings. + + for (var S32 = Dif32.toString(16) ; S32.length < 8; S32 = "0" + S32); + for (var S64 = Pos64.toString(16) ; S64.length < 8; S64 = "0" + S64); + + //Go to the Calculated address right after the Instruction UD. + + GotoPosition( S64 + S32 ); + + //Set prefixes, and operands to empty strings, and set Instruction to UD. + + PrefixG1 = "";PrefixG2 = ""; Instruction = "???"; InsOperands = ""; + } + + //Put the Instruction sequence together. + + out = PrefixG1 + " " + PrefixG2 + " " + Instruction + " " + InsOperands; + + //Remove any trailing spaces because of unused prefixes. + + out = out.replace(/^[ ]+|[ ]+$/g,''); + + //Add error suppression if used. + + if( Opcode >= 0x700 || RoundMode !== 0 ) + { + out += RoundModes[ RoundMode ]; + } + + //Return the instruction. + } + + return( out ); +} + +/*------------------------------------------------------------------------------------------------------------------------- +This function Resets the Decoder in case of error, or an full instruction has been decoded. +-------------------------------------------------------------------------------------------------------------------------*/ + +function Reset() +{ + //Reset Opcode, and Size attribute selector. + + Opcode = 0; SizeAttrSelect = 1; + + //Reset Operands and instruction. + + Instruction = ""; InsOperands = ""; + + //Reset ModR/M. + + RexActive = 0; RegExtend = 0; BaseExtend = 0; IndexExtend = 0; + SegOverride = "["; AddressOverride = false; FarPointer = 0; + + //Reset Vector extensions controls. + + Extension = 0; SIMD = 0; Vect = false; ConversionMode = 0; WidthBit = false; + VectorRegister = 0; MaskRegister = 0; HInt_ZeroMerg = false; RoundMode = 0x00; + + //Reset vector format settings. + + IgnoresWidthbit = false; VSIB = false; RoundingSetting = 0; + Swizzle = false; Up = false; Float = false; VectS = 0x00; + + //Reset IMMValue used for Imm register encoding, and Condition codes. + + IMMValue = 0; + + //Reset instruction Prefixes. + + PrefixG1 = "", PrefixG2 = ""; + XRelease = false; XAcquire = false; HLEFlipG1G2 = false; + HT = false; + BND = false; + + //Reset Invalid operation code. + + InvalidOp = false; + + //Reset instruction hex because it is used to check if the instruction is longer than 15 bytes which is impossible for the X86 Decoder Circuit. + + InstructionHex = ""; + + //Deactivate all operands along the X86Decoder. + + for( var i = 0; i < X86Decoder.length; X86Decoder[i++].Deactivate() ); +} + +/*------------------------------------------------------------------------------------------------------------------------- +do an linear disassemble. +-------------------------------------------------------------------------------------------------------------------------*/ + +export function LDisassemble() +{ + var Instruction = ""; //Stores the Decoded instruction. + var Out = ""; //The Disassemble output + + //Disassemble binary code using an linear pass. + + var len = BinCode.length; + + //Backup the base address. + + var BPos64 = Pos64, BPos32 = Pos32; + + while( CodePos < len ) + { + Instruction = DecodeInstruction(); + + //Add the 64 bit address of the output if ShowInstructionPos decoding is active. + + if( ShowInstructionPos ) + { + Out += InstructionPos + " "; + } + + //Show Each byte that was read to decode the instruction if ShowInstructionHex decoding is active. + + if(ShowInstructionHex) + { + InstructionHex = InstructionHex.toUpperCase(); + for(; InstructionHex.length < 32; InstructionHex = InstructionHex + " " ); + Out += InstructionHex + ""; + } + + //Put the decoded instruction into the output and make a new line. + + Out += Instruction + "\r\n"; + + //Reset instruction Pos and Hex. + + InstructionPos = ""; InstructionHex = ""; + } + + CodePos = 0; //Reset the Code position + Pos32 = BPos32; Pos64 = BPos64; //Reset Base address. + + //return the decoded binary code + + return(Out); + +} + +//////////////////////////////////////////////////////////////////////////////////////////////// + +/* + * The following code has been added to expose public methods for use in CyberChef + */ + +export function setBitMode (val) { + BitMode = val; +}; +export function setShowInstructionHex (val) { + ShowInstructionHex = val; +}; +export function setShowInstructionPos (val) { + ShowInstructionPos = val; +}; + diff --git a/plugins/srktoolbox/src/core/vendor/gost/gostCipher.mjs b/plugins/srktoolbox/src/core/vendor/gost/gostCipher.mjs new file mode 100644 index 00000000..8505fd34 --- /dev/null +++ b/plugins/srktoolbox/src/core/vendor/gost/gostCipher.mjs @@ -0,0 +1,2259 @@ +/** + * GOST 28147-89/GOST R 34.12-2015/GOST R 32.13-2015 Encryption Algorithm + * 1.76 + * 2014-2016, Rudolf Nickolaev. All rights reserved. + * + * Exported for CyberChef by mshwed [m@ttshwed.com] + */ + +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 HOLDER 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. + * + */ + +import GostRandom from './gostRandom.mjs'; + +import crypto from 'crypto' + +/* +* Initial parameters and common algortithms of GOST 28147-89 +* +* http://tools.ietf.org/html/rfc5830 +* +*/ // + +var root = {}; +var rootCrypto = crypto; +var CryptoOperationData = ArrayBuffer; +var SyntaxError = Error, + DataError = Error, + NotSupportedError = Error; +/* +* Check supported +* This implementation support only Little Endian arhitecture +*/ + +var littleEndian = (function () { + var buffer = new CryptoOperationData(2); + new DataView(buffer).setInt16(0, 256, true); + return new Int16Array(buffer)[0] === 256; +})(); + +// Default initial vector +var defaultIV = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]); + +// Predefined sBox collection +var sBoxes = { + 'E-TEST': [ + 0x4, 0x2, 0xF, 0x5, 0x9, 0x1, 0x0, 0x8, 0xE, 0x3, 0xB, 0xC, 0xD, 0x7, 0xA, 0x6, + 0xC, 0x9, 0xF, 0xE, 0x8, 0x1, 0x3, 0xA, 0x2, 0x7, 0x4, 0xD, 0x6, 0x0, 0xB, 0x5, + 0xD, 0x8, 0xE, 0xC, 0x7, 0x3, 0x9, 0xA, 0x1, 0x5, 0x2, 0x4, 0x6, 0xF, 0x0, 0xB, + 0xE, 0x9, 0xB, 0x2, 0x5, 0xF, 0x7, 0x1, 0x0, 0xD, 0xC, 0x6, 0xA, 0x4, 0x3, 0x8, + 0x3, 0xE, 0x5, 0x9, 0x6, 0x8, 0x0, 0xD, 0xA, 0xB, 0x7, 0xC, 0x2, 0x1, 0xF, 0x4, + 0x8, 0xF, 0x6, 0xB, 0x1, 0x9, 0xC, 0x5, 0xD, 0x3, 0x7, 0xA, 0x0, 0xE, 0x2, 0x4, + 0x9, 0xB, 0xC, 0x0, 0x3, 0x6, 0x7, 0x5, 0x4, 0x8, 0xE, 0xF, 0x1, 0xA, 0x2, 0xD, + 0xC, 0x6, 0x5, 0x2, 0xB, 0x0, 0x9, 0xD, 0x3, 0xE, 0x7, 0xA, 0xF, 0x4, 0x1, 0x8 + ], + 'E-A': [ + 0x9, 0x6, 0x3, 0x2, 0x8, 0xB, 0x1, 0x7, 0xA, 0x4, 0xE, 0xF, 0xC, 0x0, 0xD, 0x5, + 0x3, 0x7, 0xE, 0x9, 0x8, 0xA, 0xF, 0x0, 0x5, 0x2, 0x6, 0xC, 0xB, 0x4, 0xD, 0x1, + 0xE, 0x4, 0x6, 0x2, 0xB, 0x3, 0xD, 0x8, 0xC, 0xF, 0x5, 0xA, 0x0, 0x7, 0x1, 0x9, + 0xE, 0x7, 0xA, 0xC, 0xD, 0x1, 0x3, 0x9, 0x0, 0x2, 0xB, 0x4, 0xF, 0x8, 0x5, 0x6, + 0xB, 0x5, 0x1, 0x9, 0x8, 0xD, 0xF, 0x0, 0xE, 0x4, 0x2, 0x3, 0xC, 0x7, 0xA, 0x6, + 0x3, 0xA, 0xD, 0xC, 0x1, 0x2, 0x0, 0xB, 0x7, 0x5, 0x9, 0x4, 0x8, 0xF, 0xE, 0x6, + 0x1, 0xD, 0x2, 0x9, 0x7, 0xA, 0x6, 0x0, 0x8, 0xC, 0x4, 0x5, 0xF, 0x3, 0xB, 0xE, + 0xB, 0xA, 0xF, 0x5, 0x0, 0xC, 0xE, 0x8, 0x6, 0x2, 0x3, 0x9, 0x1, 0x7, 0xD, 0x4 + ], + 'E-B': [ + 0x8, 0x4, 0xB, 0x1, 0x3, 0x5, 0x0, 0x9, 0x2, 0xE, 0xA, 0xC, 0xD, 0x6, 0x7, 0xF, + 0x0, 0x1, 0x2, 0xA, 0x4, 0xD, 0x5, 0xC, 0x9, 0x7, 0x3, 0xF, 0xB, 0x8, 0x6, 0xE, + 0xE, 0xC, 0x0, 0xA, 0x9, 0x2, 0xD, 0xB, 0x7, 0x5, 0x8, 0xF, 0x3, 0x6, 0x1, 0x4, + 0x7, 0x5, 0x0, 0xD, 0xB, 0x6, 0x1, 0x2, 0x3, 0xA, 0xC, 0xF, 0x4, 0xE, 0x9, 0x8, + 0x2, 0x7, 0xC, 0xF, 0x9, 0x5, 0xA, 0xB, 0x1, 0x4, 0x0, 0xD, 0x6, 0x8, 0xE, 0x3, + 0x8, 0x3, 0x2, 0x6, 0x4, 0xD, 0xE, 0xB, 0xC, 0x1, 0x7, 0xF, 0xA, 0x0, 0x9, 0x5, + 0x5, 0x2, 0xA, 0xB, 0x9, 0x1, 0xC, 0x3, 0x7, 0x4, 0xD, 0x0, 0x6, 0xF, 0x8, 0xE, + 0x0, 0x4, 0xB, 0xE, 0x8, 0x3, 0x7, 0x1, 0xA, 0x2, 0x9, 0x6, 0xF, 0xD, 0x5, 0xC + ], + 'E-C': [ + 0x1, 0xB, 0xC, 0x2, 0x9, 0xD, 0x0, 0xF, 0x4, 0x5, 0x8, 0xE, 0xA, 0x7, 0x6, 0x3, + 0x0, 0x1, 0x7, 0xD, 0xB, 0x4, 0x5, 0x2, 0x8, 0xE, 0xF, 0xC, 0x9, 0xA, 0x6, 0x3, + 0x8, 0x2, 0x5, 0x0, 0x4, 0x9, 0xF, 0xA, 0x3, 0x7, 0xC, 0xD, 0x6, 0xE, 0x1, 0xB, + 0x3, 0x6, 0x0, 0x1, 0x5, 0xD, 0xA, 0x8, 0xB, 0x2, 0x9, 0x7, 0xE, 0xF, 0xC, 0x4, + 0x8, 0xD, 0xB, 0x0, 0x4, 0x5, 0x1, 0x2, 0x9, 0x3, 0xC, 0xE, 0x6, 0xF, 0xA, 0x7, + 0xC, 0x9, 0xB, 0x1, 0x8, 0xE, 0x2, 0x4, 0x7, 0x3, 0x6, 0x5, 0xA, 0x0, 0xF, 0xD, + 0xA, 0x9, 0x6, 0x8, 0xD, 0xE, 0x2, 0x0, 0xF, 0x3, 0x5, 0xB, 0x4, 0x1, 0xC, 0x7, + 0x7, 0x4, 0x0, 0x5, 0xA, 0x2, 0xF, 0xE, 0xC, 0x6, 0x1, 0xB, 0xD, 0x9, 0x3, 0x8 + ], + 'E-D': [ + 0xF, 0xC, 0x2, 0xA, 0x6, 0x4, 0x5, 0x0, 0x7, 0x9, 0xE, 0xD, 0x1, 0xB, 0x8, 0x3, + 0xB, 0x6, 0x3, 0x4, 0xC, 0xF, 0xE, 0x2, 0x7, 0xD, 0x8, 0x0, 0x5, 0xA, 0x9, 0x1, + 0x1, 0xC, 0xB, 0x0, 0xF, 0xE, 0x6, 0x5, 0xA, 0xD, 0x4, 0x8, 0x9, 0x3, 0x7, 0x2, + 0x1, 0x5, 0xE, 0xC, 0xA, 0x7, 0x0, 0xD, 0x6, 0x2, 0xB, 0x4, 0x9, 0x3, 0xF, 0x8, + 0x0, 0xC, 0x8, 0x9, 0xD, 0x2, 0xA, 0xB, 0x7, 0x3, 0x6, 0x5, 0x4, 0xE, 0xF, 0x1, + 0x8, 0x0, 0xF, 0x3, 0x2, 0x5, 0xE, 0xB, 0x1, 0xA, 0x4, 0x7, 0xC, 0x9, 0xD, 0x6, + 0x3, 0x0, 0x6, 0xF, 0x1, 0xE, 0x9, 0x2, 0xD, 0x8, 0xC, 0x4, 0xB, 0xA, 0x5, 0x7, + 0x1, 0xA, 0x6, 0x8, 0xF, 0xB, 0x0, 0x4, 0xC, 0x3, 0x5, 0x9, 0x7, 0xD, 0x2, 0xE + ], + 'E-SC': [ + 0x3, 0x6, 0x1, 0x0, 0x5, 0x7, 0xd, 0x9, 0x4, 0xb, 0x8, 0xc, 0xe, 0xf, 0x2, 0xa, + 0x7, 0x1, 0x5, 0x2, 0x8, 0xb, 0x9, 0xc, 0xd, 0x0, 0x3, 0xa, 0xf, 0xe, 0x4, 0x6, + 0xf, 0x1, 0x4, 0x6, 0xc, 0x8, 0x9, 0x2, 0xe, 0x3, 0x7, 0xa, 0xb, 0xd, 0x5, 0x0, + 0x3, 0x4, 0xf, 0xc, 0x5, 0x9, 0xe, 0x0, 0x6, 0x8, 0x7, 0xa, 0x1, 0xb, 0xd, 0x2, + 0x6, 0x9, 0x0, 0x7, 0xb, 0x8, 0x4, 0xc, 0x2, 0xe, 0xa, 0xf, 0x1, 0xd, 0x5, 0x3, + 0x6, 0x1, 0x2, 0xf, 0x0, 0xb, 0x9, 0xc, 0x7, 0xd, 0xa, 0x5, 0x8, 0x4, 0xe, 0x3, + 0x0, 0x2, 0xe, 0xc, 0x9, 0x1, 0x4, 0x7, 0x3, 0xf, 0x6, 0x8, 0xa, 0xd, 0xb, 0x5, + 0x5, 0x2, 0xb, 0x8, 0x4, 0xc, 0x7, 0x1, 0xa, 0x6, 0xe, 0x0, 0x9, 0x3, 0xd, 0xf + ], + 'E-Z': [// This is default S-box in according to draft of new standard + 0xc, 0x4, 0x6, 0x2, 0xa, 0x5, 0xb, 0x9, 0xe, 0x8, 0xd, 0x7, 0x0, 0x3, 0xf, 0x1, + 0x6, 0x8, 0x2, 0x3, 0x9, 0xa, 0x5, 0xc, 0x1, 0xe, 0x4, 0x7, 0xb, 0xd, 0x0, 0xf, + 0xb, 0x3, 0x5, 0x8, 0x2, 0xf, 0xa, 0xd, 0xe, 0x1, 0x7, 0x4, 0xc, 0x9, 0x6, 0x0, + 0xc, 0x8, 0x2, 0x1, 0xd, 0x4, 0xf, 0x6, 0x7, 0x0, 0xa, 0x5, 0x3, 0xe, 0x9, 0xb, + 0x7, 0xf, 0x5, 0xa, 0x8, 0x1, 0x6, 0xd, 0x0, 0x9, 0x3, 0xe, 0xb, 0x4, 0x2, 0xc, + 0x5, 0xd, 0xf, 0x6, 0x9, 0x2, 0xc, 0xa, 0xb, 0x7, 0x8, 0x1, 0x4, 0x3, 0xe, 0x0, + 0x8, 0xe, 0x2, 0x5, 0x6, 0x9, 0x1, 0xc, 0xf, 0x4, 0xb, 0x0, 0xd, 0xa, 0x3, 0x7, + 0x1, 0x7, 0xe, 0xd, 0x0, 0x5, 0x8, 0x3, 0x4, 0xf, 0xa, 0x6, 0x9, 0xc, 0xb, 0x2 + ], + //S-box for digest + 'D-TEST': [ + 0x4, 0xA, 0x9, 0x2, 0xD, 0x8, 0x0, 0xE, 0x6, 0xB, 0x1, 0xC, 0x7, 0xF, 0x5, 0x3, + 0xE, 0xB, 0x4, 0xC, 0x6, 0xD, 0xF, 0xA, 0x2, 0x3, 0x8, 0x1, 0x0, 0x7, 0x5, 0x9, + 0x5, 0x8, 0x1, 0xD, 0xA, 0x3, 0x4, 0x2, 0xE, 0xF, 0xC, 0x7, 0x6, 0x0, 0x9, 0xB, + 0x7, 0xD, 0xA, 0x1, 0x0, 0x8, 0x9, 0xF, 0xE, 0x4, 0x6, 0xC, 0xB, 0x2, 0x5, 0x3, + 0x6, 0xC, 0x7, 0x1, 0x5, 0xF, 0xD, 0x8, 0x4, 0xA, 0x9, 0xE, 0x0, 0x3, 0xB, 0x2, + 0x4, 0xB, 0xA, 0x0, 0x7, 0x2, 0x1, 0xD, 0x3, 0x6, 0x8, 0x5, 0x9, 0xC, 0xF, 0xE, + 0xD, 0xB, 0x4, 0x1, 0x3, 0xF, 0x5, 0x9, 0x0, 0xA, 0xE, 0x7, 0x6, 0x8, 0x2, 0xC, + 0x1, 0xF, 0xD, 0x0, 0x5, 0x7, 0xA, 0x4, 0x9, 0x2, 0x3, 0xE, 0x6, 0xB, 0x8, 0xC + ], + 'D-A': [ + 0xA, 0x4, 0x5, 0x6, 0x8, 0x1, 0x3, 0x7, 0xD, 0xC, 0xE, 0x0, 0x9, 0x2, 0xB, 0xF, + 0x5, 0xF, 0x4, 0x0, 0x2, 0xD, 0xB, 0x9, 0x1, 0x7, 0x6, 0x3, 0xC, 0xE, 0xA, 0x8, + 0x7, 0xF, 0xC, 0xE, 0x9, 0x4, 0x1, 0x0, 0x3, 0xB, 0x5, 0x2, 0x6, 0xA, 0x8, 0xD, + 0x4, 0xA, 0x7, 0xC, 0x0, 0xF, 0x2, 0x8, 0xE, 0x1, 0x6, 0x5, 0xD, 0xB, 0x9, 0x3, + 0x7, 0x6, 0x4, 0xB, 0x9, 0xC, 0x2, 0xA, 0x1, 0x8, 0x0, 0xE, 0xF, 0xD, 0x3, 0x5, + 0x7, 0x6, 0x2, 0x4, 0xD, 0x9, 0xF, 0x0, 0xA, 0x1, 0x5, 0xB, 0x8, 0xE, 0xC, 0x3, + 0xD, 0xE, 0x4, 0x1, 0x7, 0x0, 0x5, 0xA, 0x3, 0xC, 0x8, 0xF, 0x6, 0x2, 0x9, 0xB, + 0x1, 0x3, 0xA, 0x9, 0x5, 0xB, 0x4, 0xF, 0x8, 0x6, 0x7, 0xE, 0xD, 0x0, 0x2, 0xC + ], + 'D-SC': [ + 0xb, 0xd, 0x7, 0x0, 0x5, 0x4, 0x1, 0xf, 0x9, 0xe, 0x6, 0xa, 0x3, 0xc, 0x8, 0x2, + 0x1, 0x2, 0x7, 0x9, 0xd, 0xb, 0xf, 0x8, 0xe, 0xc, 0x4, 0x0, 0x5, 0x6, 0xa, 0x3, + 0x5, 0x1, 0xd, 0x3, 0xf, 0x6, 0xc, 0x7, 0x9, 0x8, 0xb, 0x2, 0x4, 0xe, 0x0, 0xa, + 0xd, 0x1, 0xb, 0x4, 0x9, 0xc, 0xe, 0x0, 0x7, 0x5, 0x8, 0xf, 0x6, 0x2, 0xa, 0x3, + 0x2, 0xd, 0xa, 0xf, 0x9, 0xb, 0x3, 0x7, 0x8, 0xc, 0x5, 0xe, 0x6, 0x0, 0x1, 0x4, + 0x0, 0x4, 0x6, 0xc, 0x5, 0x3, 0x8, 0xd, 0xa, 0xb, 0xf, 0x2, 0x1, 0x9, 0x7, 0xe, + 0x1, 0x3, 0xc, 0x8, 0xa, 0x6, 0xb, 0x0, 0x2, 0xe, 0x7, 0x9, 0xf, 0x4, 0x5, 0xd, + 0xa, 0xb, 0x6, 0x0, 0x1, 0x3, 0x4, 0x7, 0xe, 0xd, 0x5, 0xf, 0x8, 0x2, 0x9, 0xc + ] +}; + +var C = new Uint8Array([ + 0x69, 0x00, 0x72, 0x22, 0x64, 0xC9, 0x04, 0x23, + 0x8D, 0x3A, 0xDB, 0x96, 0x46, 0xE9, 0x2A, 0xC4, + 0x18, 0xFE, 0xAC, 0x94, 0x00, 0xED, 0x07, 0x12, + 0xC0, 0x86, 0xDC, 0xC2, 0xEF, 0x4C, 0xA9, 0x2B +]); + +function signed(x) { + return x >= 0x80000000 ? x - 0x100000000 : x; +} + +function unsigned(x) { + return x < 0 ? x + 0x100000000 : x; +} + +// Set random values into Uint8Arry +// Random generator +function randomSeed(e) { + GostRandom = GostRandom || root.GostRandom; + var randomSource = GostRandom ? new (GostRandom || root.GostRandom) : rootCrypto; + if (randomSource.getRandomValues) + randomSource.getRandomValues(e); + else + throw new NotSupportedError('Random generator not found'); +} + +// Get buffer +function buffer(d) { + if (d instanceof CryptoOperationData) + return d; + else if (d && d?.buffer instanceof CryptoOperationData) + return d.byteOffset === 0 && d.byteLength === d.buffer.byteLength ? + d.buffer : new Uint8Array(new Uint8Array(d, d.byteOffset, d.byteLength)).buffer; + else + throw new DataError('CryptoOperationData required'); +} + +// Get byte array +function byteArray(d) { + return new Uint8Array(buffer(d)); +} + +// Clone byte array +function cloneArray(d) { + return new Uint8Array(byteArray(d)); +} + + +// Get int32 array +function intArray(d) { + return new Int32Array(buffer(d)); +} + +// Swap bytes for version 2015 +function swap32(b) { + return ((b & 0xff) << 24) + | ((b & 0xff00) << 8) + | ((b >> 8) & 0xff00) + | ((b >> 24) & 0xff); +} + +// + +/* + * Initial parameters and common algortithms of GOST R 34.12-15 + * Algorithm "Kuznechik" 128bit + * + */ // + +// Default initial vector +var defaultIV128 = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + +// Mult table for R function +var multTable = (function () { + + // Multiply two numbers in the GF(2^8) finite field defined + // by the polynomial x^8 + x^7 + x^6 + x + 1 = 0 */ + function gmul(a, b) { + var p = 0, counter, carry; + for (counter = 0; counter < 8; counter++) { + if (b & 1) + p ^= a; + carry = a & 0x80; // detect if x^8 term is about to be generated + a = (a << 1) & 0xff; + if (carry) + a ^= 0xc3; // replace x^8 with x^7 + x^6 + x + 1 + b >>= 1; + } + return p & 0xff; + } + + // It is required only this values for R function + // 0 1 2 3 4 5 6 7 + var x = [1, 16, 32, 133, 148, 192, 194, 251]; + var m = []; + for (var i = 0; i < 8; i++) { + m[i] = []; + for (var j = 0; j < 256; j++) + m[i][j] = gmul(x[i], j); + } + return m; +})(); + +// 148, 32, 133, 16, 194, 192, 1, 251, 1, 192, 194, 16, 133, 32, 148, 1 +var kB = [4, 2, 3, 1, 6, 5, 0, 7, 0, 5, 6, 1, 3, 2, 4, 0]; + +// R - function +function funcR(d) { + var sum = 0; + for (var i = 0; i < 16; i++) + sum ^= multTable[kB[i]][d[i]]; + + for (var i = 16; i > 0; --i) + d[i] = d[i - 1]; + d[0] = sum; +} + +function funcReverseR(d) { + var tmp = d[0]; + for (var i = 0; i < 15; i++) + d[i] = d[i + 1]; + d[15] = tmp; + + var sum = 0; + for (i = 0; i < 16; i++) + sum ^= multTable[kB[i]][d[i]]; + d[15] = sum; +} + +// Nonlinear transformation +var kPi = [ + 252, 238, 221, 17, 207, 110, 49, 22, 251, 196, 250, 218, 35, 197, 4, 77, + 233, 119, 240, 219, 147, 46, 153, 186, 23, 54, 241, 187, 20, 205, 95, 193, + 249, 24, 101, 90, 226, 92, 239, 33, 129, 28, 60, 66, 139, 1, 142, 79, + 5, 132, 2, 174, 227, 106, 143, 160, 6, 11, 237, 152, 127, 212, 211, 31, + 235, 52, 44, 81, 234, 200, 72, 171, 242, 42, 104, 162, 253, 58, 206, 204, + 181, 112, 14, 86, 8, 12, 118, 18, 191, 114, 19, 71, 156, 183, 93, 135, + 21, 161, 150, 41, 16, 123, 154, 199, 243, 145, 120, 111, 157, 158, 178, 177, + 50, 117, 25, 61, 255, 53, 138, 126, 109, 84, 198, 128, 195, 189, 13, 87, + 223, 245, 36, 169, 62, 168, 67, 201, 215, 121, 214, 246, 124, 34, 185, 3, + 224, 15, 236, 222, 122, 148, 176, 188, 220, 232, 40, 80, 78, 51, 10, 74, + 167, 151, 96, 115, 30, 0, 98, 68, 26, 184, 56, 130, 100, 159, 38, 65, + 173, 69, 70, 146, 39, 94, 85, 47, 140, 163, 165, 125, 105, 213, 149, 59, + 7, 88, 179, 64, 134, 172, 29, 247, 48, 55, 107, 228, 136, 217, 231, 137, + 225, 27, 131, 73, 76, 63, 248, 254, 141, 83, 170, 144, 202, 216, 133, 97, + 32, 113, 103, 164, 45, 43, 9, 91, 203, 155, 37, 208, 190, 229, 108, 82, + 89, 166, 116, 210, 230, 244, 180, 192, 209, 102, 175, 194, 57, 75, 99, 182 +]; + +var kReversePi = (function () { + var m = []; + for (var i = 0, n = kPi.length; i < n; i++) + m[kPi[i]] = i; + return m; +})(); + +function funcS(d) { + for (var i = 0; i < 16; ++i) + d[i] = kPi[d[i]]; +} + +function funcReverseS(d) { + for (var i = 0; i < 16; ++i) + d[i] = kReversePi[d[i]]; +} + +function funcX(a, b) { + for (var i = 0; i < 16; ++i) + a[i] ^= b[i]; +} + +function funcL(d) { + for (var i = 0; i < 16; ++i) + funcR(d); +} + +function funcReverseL(d) { + for (var i = 0; i < 16; ++i) + funcReverseR(d); +} + +function funcLSX(a, b) { + funcX(a, b); + funcS(a); + funcL(a); +} + +function funcReverseLSX(a, b) { + funcX(a, b); + funcReverseL(a); + funcReverseS(a); +} + +function funcF(inputKey, inputKeySecond, iterationConst) { + var tmp = new Uint8Array(inputKey); + funcLSX(inputKey, iterationConst); + funcX(inputKey, inputKeySecond); + inputKeySecond.set(tmp); +} + +function funcC(number, d) { + for (var i = 0; i < 15; i++) + d[i] = 0; + d[15] = number; + funcL(d); +} + +// + +/** + * Key schedule for GOST R 34.12-15 128bits + * + * @memberOf GostCipher + * @private + * @instance + * @method keySchedule + * @param {type} k + * @returns {Uint8Array} + */ +function keySchedule128(k) // +{ + var keys = new Uint8Array(160), c = new Uint8Array(16); + keys.set(byteArray(k)); + for (var j = 0; j < 4; j++) { + var j0 = 32 * j, j1 = 32 * (j + 1); + keys.set(new Uint8Array(keys.buffer, j0, 32), j1); + for (var i = 1; i < 9; i++) { + funcC(j * 8 + i, c); + funcF(new Uint8Array(keys.buffer, j1, 16), + new Uint8Array(keys.buffer, j1 + 16, 16), c); + } + } + return keys; +} // + +/** + * GOST R 34.12-15 128 bits encrypt/decrypt process + * + * @memberOf GostCipher + * @private + * @instance + * @method round + * @param {Uint8Array} k Scheduled key + * @param {Uint8Array} d Data + * @param {number} ofs Offsec + * @param {number} e true - decrypt + */ +function process128(k, d, ofs, e) // +{ + ofs = ofs || d.byteOffset; + var r = new Uint8Array(d.buffer, ofs, 16); + if (e) { + for (var i = 0; i < 9; i++) + funcReverseLSX(r, new Uint8Array(k.buffer, (9 - i) * 16, 16)); + + funcX(r, new Uint8Array(k.buffer, 0, 16)); + } else { + for (var i = 0; i < 9; i++) + funcLSX(r, new Uint8Array(k.buffer, 16 * i, 16)); + + funcX(r, new Uint8Array(k.buffer, 16 * 9, 16)); + } +} // + +/** + * One GOST encryption round + * + * @memberOf GostCipher + * @private + * @instance + * @method round + * @param {Int8Array} S sBox + * @param {Int32Array} m 2x32 bits cipher block + * @param {Int32Array} k 32 bits key[i] + */ +function round(S, m, k) // +{ + var cm = (m[0] + k) & 0xffffffff; + + var om = S[ 0 + ((cm >> (0 * 4)) & 0xF)] << (0 * 4); + om |= S[ 16 + ((cm >> (1 * 4)) & 0xF)] << (1 * 4); + om |= S[ 32 + ((cm >> (2 * 4)) & 0xF)] << (2 * 4); + om |= S[ 48 + ((cm >> (3 * 4)) & 0xF)] << (3 * 4); + om |= S[ 64 + ((cm >> (4 * 4)) & 0xF)] << (4 * 4); + om |= S[ 80 + ((cm >> (5 * 4)) & 0xF)] << (5 * 4); + om |= S[ 96 + ((cm >> (6 * 4)) & 0xF)] << (6 * 4); + om |= S[112 + ((cm >> (7 * 4)) & 0xF)] << (7 * 4); + cm = om << 11 | om >>> (32 - 11); + + cm ^= m[1]; + m[1] = m[0]; + m[0] = cm; + +} // + +/** + * Process encrypt/decrypt block with key K using GOST 28147-89 + * + * @memberOf GostCipher + * @private + * @instance + * @method process + * @param k {Int32Array} 8x32 bits key + * @param d {Int32Array} 8x8 bits cipher block + * @param ofs {number} offset + */ +function process89(k, d, ofs) // +{ + ofs = ofs || d.byteOffset; + var s = this.sBox, + m = new Int32Array(d.buffer, ofs, 2); + + for (var i = 0; i < 32; i++) + round(s, m, k[i]); + + var r = m[0]; + m[0] = m[1]; + m[1] = r; +} // + +/** + * Process encrypt/decrypt block with key K using GOST R 34.12-15 64bit block + * + * @memberOf GostCipher + * @private + * @instance + * @method process + * @param k {Int32Array} 8x32 bits key + * @param d {Int32Array} 8x8 bits cipher block + * @param ofs {number} offset + */ +function process15(k, d, ofs) // +{ + ofs = ofs || d.byteOffset; + var s = this.sBox, + m = new Int32Array(d.buffer, ofs, 2), + r = swap32(m[0]); + m[0] = swap32(m[1]); + m[1] = r; + + for (var i = 0; i < 32; i++) + round(s, m, k[i]); + + m[0] = swap32(m[0]); + m[1] = swap32(m[1]); +} // + +/** + * Key keySchedule algorithm for GOST 28147-89 64bit cipher + * + * @memberOf GostCipher + * @private + * @instance + * @method process + * @param k {Uint8Array} 8 bit key array + * @param e {boolean} true - decrypt + * @returns {Int32Array} keyScheduled 32-bit key + */ +function keySchedule89(k, e) // +{ + var sch = new Int32Array(32), + key = new Int32Array(buffer(k)); + + for (var i = 0; i < 8; i++) + sch[i] = key[i]; + + if (e) { + for (var i = 0; i < 8; i++) + sch[i + 8] = sch[7 - i]; + + for (var i = 0; i < 8; i++) + sch[i + 16] = sch[7 - i]; + } else { + for (var i = 0; i < 8; i++) + sch[i + 8] = sch[i]; + + for (var i = 0; i < 8; i++) + sch[i + 16] = sch[i]; + } + + for (var i = 0; i < 8; i++) + sch[i + 24] = sch[7 - i]; + + return sch; +} // + +/** + * Key keySchedule algorithm for GOST R 34.12-15 64bit cipher + * + * @memberOf GostCipher + * @private + * @instance + * @method process + * @param k {Uint8Array} 8 bit key array + * @param e {boolean} true - decrypt + * @returns {Int32Array} keyScheduled 32-bit key + */ +function keySchedule15(k, e) // +{ + var sch = new Int32Array(32), + key = new Int32Array(buffer(k)); + + for (var i = 0; i < 8; i++) + sch[i] = swap32(key[i]); + + if (e) { + for (var i = 0; i < 8; i++) + sch[i + 8] = sch[7 - i]; + + for (var i = 0; i < 8; i++) + sch[i + 16] = sch[7 - i]; + } else { + for (var i = 0; i < 8; i++) + sch[i + 8] = sch[i]; + + for (var i = 0; i < 8; i++) + sch[i + 16] = sch[i]; + } + + for (var i = 0; i < 8; i++) + sch[i + 24] = sch[7 - i]; + + return sch; +} // + +/** + * Key schedule for RC2 + * + * https://tools.ietf.org/html/rfc2268 + * + * @memberOf GostCipher + * @private + * @instance + * @method keySchedule + * @param {Uint8Array} k + * @returns {Uint16Array} + */ +var keyScheduleRC2 = (function () // +{ + // an array of "random" bytes based on the digits of PI = 3.14159... + var PITABLE = new Uint8Array([ + 0xd9, 0x78, 0xf9, 0xc4, 0x19, 0xdd, 0xb5, 0xed, 0x28, 0xe9, 0xfd, 0x79, 0x4a, 0xa0, 0xd8, 0x9d, + 0xc6, 0x7e, 0x37, 0x83, 0x2b, 0x76, 0x53, 0x8e, 0x62, 0x4c, 0x64, 0x88, 0x44, 0x8b, 0xfb, 0xa2, + 0x17, 0x9a, 0x59, 0xf5, 0x87, 0xb3, 0x4f, 0x13, 0x61, 0x45, 0x6d, 0x8d, 0x09, 0x81, 0x7d, 0x32, + 0xbd, 0x8f, 0x40, 0xeb, 0x86, 0xb7, 0x7b, 0x0b, 0xf0, 0x95, 0x21, 0x22, 0x5c, 0x6b, 0x4e, 0x82, + 0x54, 0xd6, 0x65, 0x93, 0xce, 0x60, 0xb2, 0x1c, 0x73, 0x56, 0xc0, 0x14, 0xa7, 0x8c, 0xf1, 0xdc, + 0x12, 0x75, 0xca, 0x1f, 0x3b, 0xbe, 0xe4, 0xd1, 0x42, 0x3d, 0xd4, 0x30, 0xa3, 0x3c, 0xb6, 0x26, + 0x6f, 0xbf, 0x0e, 0xda, 0x46, 0x69, 0x07, 0x57, 0x27, 0xf2, 0x1d, 0x9b, 0xbc, 0x94, 0x43, 0x03, + 0xf8, 0x11, 0xc7, 0xf6, 0x90, 0xef, 0x3e, 0xe7, 0x06, 0xc3, 0xd5, 0x2f, 0xc8, 0x66, 0x1e, 0xd7, + 0x08, 0xe8, 0xea, 0xde, 0x80, 0x52, 0xee, 0xf7, 0x84, 0xaa, 0x72, 0xac, 0x35, 0x4d, 0x6a, 0x2a, + 0x96, 0x1a, 0xd2, 0x71, 0x5a, 0x15, 0x49, 0x74, 0x4b, 0x9f, 0xd0, 0x5e, 0x04, 0x18, 0xa4, 0xec, + 0xc2, 0xe0, 0x41, 0x6e, 0x0f, 0x51, 0xcb, 0xcc, 0x24, 0x91, 0xaf, 0x50, 0xa1, 0xf4, 0x70, 0x39, + 0x99, 0x7c, 0x3a, 0x85, 0x23, 0xb8, 0xb4, 0x7a, 0xfc, 0x02, 0x36, 0x5b, 0x25, 0x55, 0x97, 0x31, + 0x2d, 0x5d, 0xfa, 0x98, 0xe3, 0x8a, 0x92, 0xae, 0x05, 0xdf, 0x29, 0x10, 0x67, 0x6c, 0xba, 0xc9, + 0xd3, 0x00, 0xe6, 0xcf, 0xe1, 0x9e, 0xa8, 0x2c, 0x63, 0x16, 0x01, 0x3f, 0x58, 0xe2, 0x89, 0xa9, + 0x0d, 0x38, 0x34, 0x1b, 0xab, 0x33, 0xff, 0xb0, 0xbb, 0x48, 0x0c, 0x5f, 0xb9, 0xb1, 0xcd, 0x2e, + 0xc5, 0xf3, 0xdb, 0x47, 0xe5, 0xa5, 0x9c, 0x77, 0x0a, 0xa6, 0x20, 0x68, 0xfe, 0x7f, 0xc1, 0xad + ]); + + return function (k) + { + var key = new Uint8Array(buffer(k)), + T = Math.min(key.length, 128), + T1 = this.effectiveLength, + T8 = Math.floor((T1 + 7) / 8), + TM = 0xff % Math.pow(2, 8 + T1 - 8 * T8); + + var L = new Uint8Array(128), K = new Uint16Array(L.buffer); + for (var i = 0; i < T; i++) + L[i] = key[i]; + for (var i = T; i < 128; i++) + L[i] = PITABLE[(L[i - 1] + L[i - T]) % 256]; + L[128 - T8] = PITABLE[L[128 - T8] & TM]; + for (var i = 127 - T8; i >= 0; --i) + L[i] = PITABLE[L[i + 1] ^ L[i + T8]]; + return K; + }; +} // +)(); + +/** + * RC2 encrypt/decrypt process + * + * https://tools.ietf.org/html/rfc2268 + * + * @memberOf GostCipher + * @private + * @instance + * @method round + * @param {CryptoOperationData} k Scheduled key + * @param {CryptoOperationData} d Data + * @param {number} ofs Offsec + * @param {number} e true - decrypt + */ +var processRC2 = (function () // +{ + var K, j, R = new Uint16Array(4), + s = new Uint16Array([1, 2, 3, 5]), reverse; + + function rol(R, s) { + return (R << s | R >>> (16 - s)) & 0xffff; + } + + function ror(R, s) { + return (R >>> s | R << (16 - s)) & 0xffff; + } + + function mix(i) { + if (reverse) { + R[i] = ror(R[i], s[i]); + R[i] = R[i] - K[j] - (R[(i + 3) % 4] & R[(i + 2) % 4]) - ((~R[(i + 3) % 4]) & R[(i + 1) % 4]); + j = j - 1; + } else { + R[i] = R[i] + K[j] + (R[(i + 3) % 4] & R[(i + 2) % 4]) + ((~R[(i + 3) % 4]) & R[(i + 1) % 4]); + j = j + 1; + R[i] = rol(R[i], s[i]); + } + } + + function mash(i) { + if (reverse) { + R[i] = R[i] - K[R[(i + 3) % 4] & 63]; + } else { + R[i] = R[i] + K[R[(i + 3) % 4] & 63]; + } + } + + function perform(method, count) { + count = count || 1; + for (var j = 0; j < count; j++) { + if (reverse) { + for (var i = 3; i >= 0; --i) + method(i); + } else { + for (var i = 0; i < 4; i++) + method(i); + } + } + } + + return function (k, d, ofs, e) { + reverse = e; + // 1. Initialize words R[0], ..., R[3] to contain the 64-bit + // ciphertext value. + R = new Uint16Array(d.buffer, ofs || d.byteOffset, 4); + // 2. Expand the key, so that words K[0], ..., K[63] become + // defined. + K = k; + // 3. Initialize j to zero (enc) j to 63 (dec). + j = e ? 63 : 0; + // 4. Perform five mixing rounds. + perform(mix, 5); + // 5. Perform one mashing round. + perform(mash); + // 6. Perform six mixing rounds. + perform(mix, 6); + // 7. Perform one mashing round. + perform(mash); + // 8. Perform five mixing rounds. + perform(mix, 5); + }; +} // +)(); + +/** + * Algorithm name GOST 28147-ECB

+ * + * encryptECB (K, D) is D, encrypted with key k using GOST 28147/GOST R 34.13 in + * "prostaya zamena" (Electronic Codebook, ECB) mode. + * @memberOf GostCipher + * @method encrypt + * @instance + * @param k {CryptoOperationData} 8x32 bit key + * @param d {CryptoOperationData} 8 bits message + * @return {CryptoOperationData} result + */ +function encryptECB(k, d) // +{ + var p = this.pad(byteArray(d)), + n = this.blockSize, + b = p.byteLength / n, + key = this.keySchedule(k); + + for (var i = 0; i < b; i++) + this.process(key, p, n * i); + + return p.buffer; +} // + +/** + * Algorithm name GOST 28147-ECB

+ * + * decryptECB (K, D) is D, decrypted with key K using GOST 28147/GOST R 34.13 in + * "prostaya zamena" (Electronic Codebook, ECB) mode. + * + * @memberOf GostCipher + * @method decrypt + * @instance + * @param k {CryptoOperationData} 8x32 bits key + * @param d {CryptoOperationData} 8 bits message + * @return {CryptoOperationData} result + */ +function decryptECB(k, d) // +{ + var p = cloneArray(d), + n = this.blockSize, + b = p.byteLength / n, + key = this.keySchedule(k, 1); + + for (var i = 0; i < b; i++) + this.process(key, p, n * i, 1); + + return this.unpad(p).buffer; +} // + +/** + * Algorithm name GOST 28147-CFB

+ * + * encryptCFB (IV, K, D) is D, encrypted with key K using GOST 28147/GOST R 34.13 + * in "gammirovanie s obratnoj svyaziyu" (Cipher Feedback, CFB) mode, and IV is + * used as the initialization vector. + * + * @memberOf GostCipher + * @method encrypt + * @instance + * @param {CryptoOperationData} k 8x32 bits key + * @param {CryptoOperationData} d 8 bits array with data + * @param {CryptoOperationData} iv initial vector + * @return {CryptoOperationData} result + */ +function encryptCFB(k, d, iv) // +{ + var s = new Uint8Array(iv || this.iv), + c = cloneArray(d), + m = s.length, + t = new Uint8Array(m), + b = this.shiftBits >> 3, + cb = c.length, r = cb % b, q = (cb - r) / b, + key = this.keySchedule(k); + + for (var i = 0; i < q; i++) { + + for (var j = 0; j < m; j++) + t[j] = s[j]; + + this.process(key, s); + + for (var j = 0; j < b; j++) + c[i * b + j] ^= s[j]; + + for (var j = 0; j < m - b; j++) + s[j] = t[b + j]; + + for (var j = 0; j < b; j++) + s[m - b + j] = c[i * b + j]; + + k = this.keyMeshing(k, s, i, key); + } + + if (r > 0) { + this.process(key, s); + + for (var i = 0; i < r; i++) + c[q * b + i] ^= s[i]; + } + return c.buffer; +} // + +/** + * Algorithm name GOST 28147-CFB

+ * + * decryptCFB (IV, K, D) is D, decrypted with key K using GOST 28147/GOST R 34.13 + * in "gammirovanie s obratnoj svyaziyu po shifrotekstu" (Cipher Feedback, CFB) mode, and IV is + * used as the initialization vector. + * + * @memberOf GostCipher + * @method decrypt + * @instance + * @param {CryptoOperationData} k 8x32 bits key + * @param {CryptoOperationData} d 8 bits array with data + * @param {CryptoOperationData} iv initial vector + * @return {CryptoOperationData} result + */ +function decryptCFB(k, d, iv) // +{ + var s = new Uint8Array(iv || this.iv), + c = cloneArray(d), + m = s.length, + t = new Uint8Array(m), + b = this.shiftBits >> 3, + cb = c.length, r = cb % b, q = (cb - r) / b, + key = this.keySchedule(k); + + for (var i = 0; i < q; i++) { + + for (var j = 0; j < m; j++) + t[j] = s[j]; + + this.process(key, s); + + for (var j = 0; j < b; j++) { + t[j] = c[i * b + j]; + c[i * b + j] ^= s[j]; + } + + for (var j = 0; j < m - b; j++) + s[j] = t[b + j]; + + for (var j = 0; j < b; j++) + s[m - b + j] = t[j]; + + k = this.keyMeshing(k, s, i, key); + } + + if (r > 0) { + this.process(key, s); + + for (var i = 0; i < r; i++) + c[q * b + i] ^= s[i]; + } + return c.buffer; +} // + +/** + * Algorithm name GOST 28147-OFB

+ * + * encryptOFB/decryptOFB (IV, K, D) is D, encrypted with key K using GOST 28147/GOST R 34.13 + * in "gammirovanie s obratnoj svyaziyu po vyhodu" (Output Feedback, OFB) mode, and IV is + * used as the initialization vector. + * + * @memberOf GostCipher + * @method encrypt + * @instance + * @param {CryptoOperationData} k 8x32 bits key + * @param {CryptoOperationData} d 8 bits array with data + * @param {CryptoOperationData} iv 8x8 optional bits initial vector + * @return {CryptoOperationData} result + */ +/** + * Algorithm name GOST 28147-OFB

+ * + * encryptOFB/decryptOFB (IV, K, D) is D, encrypted with key K using GOST 28147/GOST R 34.13 + * in "gammirovanie s obratnoj svyaziyu po vyhodu" (Output Feedback, OFB) mode, and IV is + * used as the initialization vector. + * + * @memberOf GostCipher + * @method decrypt + * @instance + * @param {CryptoOperationData} k 8x32 bits key + * @param {CryptoOperationData} d 8 bits array with data + * @param {CryptoOperationData} iv initial vector + * @return {CryptoOperationData} result + */ +function processOFB(k, d, iv) // +{ + var s = new Uint8Array(iv || this.iv), + c = cloneArray(d), + m = s.length, + t = new Uint8Array(m), + b = this.shiftBits >> 3, + p = new Uint8Array(b), + cb = c.length, r = cb % b, q = (cb - r) / b, + key = this.keySchedule(k); + + for (var i = 0; i < q; i++) { + + for (var j = 0; j < m; j++) + t[j] = s[j]; + + this.process(key, s); + + for (var j = 0; j < b; j++) + p[j] = s[j]; + + for (var j = 0; j < b; j++) + c[i * b + j] ^= s[j]; + + for (var j = 0; j < m - b; j++) + s[j] = t[b + j]; + + for (var j = 0; j < b; j++) + s[m - b + j] = p[j]; + + k = this.keyMeshing(k, s, i, key); + } + + if (r > 0) { + this.process(key, s); + + for (var i = 0; i < r; i++) + c[q * b + i] ^= s[i]; + } + return c.buffer; +} // + +/** + * Algorithm name GOST 28147-CTR

+ * + * encryptCTR/decryptCTR (IV, K, D) is D, encrypted with key K using GOST 28147/GOST R 34.13 + * in "gammirovanie" (Counter Mode-CTR) mode, and IV is used as the + * initialization vector. + * @memberOf GostCipher + * @method encrypt + * @instance + * @param {CryptoOperationData} k 8x32 bits key + * @param {CryptoOperationData} d 8 bits array with data + * @param {CryptoOperationData} iv 8x8 optional bits initial vector + * @return {CryptoOperationData} result + */ +/** + * Algorithm name GOST 28147-CTR

+ * + * encryptCTR/decryptCTR (IV, K, D) is D, encrypted with key K using GOST 28147/GOST R 34.13 + * in "gammirovanie" (Counter Mode-CTR) mode, and IV is used as the + * initialization vector. + * @memberOf GostCipher + * @method decrypt + * @instance + * @param {CryptoOperationData} k 8x32 bits key + * @param {CryptoOperationData} d 8 bits array with data + * @param {CryptoOperationData} iv initial vector + * @return {CryptoOperationData} result + */ +function processCTR89(k, d, iv) // +{ + var s = new Uint8Array(iv || this.iv), + c = cloneArray(d), + b = this.blockSize, + t = new Int8Array(b), + cb = c.length, r = cb % b, q = (cb - r) / b, + key = this.keySchedule(k), + syn = new Int32Array(s.buffer); + + this.process(key, s); + + for (var i = 0; i < q; i++) { + syn[0] = (syn[0] + 0x1010101) & 0xffffffff; + // syn[1] = signed(unsigned((syn[1] + 0x1010104) & 0xffffffff) % 0xffffffff); + var tmp = unsigned(syn[1]) + 0x1010104; // Special thanks to Ilya Matveychikov + syn[1] = signed(tmp < 0x100000000 ? tmp : tmp - 0xffffffff); + + for (var j = 0; j < b; j++) + t[j] = s[j]; + + this.process(key, syn); + + for (var j = 0; j < b; j++) + c[i * b + j] ^= s[j]; + + for (var j = 0; j < b; j++) + s[j] = t[j]; + + k = this.keyMeshing(k, s, i, key); + } + if (r > 0) { + syn[0] = (syn[0] + 0x1010101) & 0xffffffff; + // syn[1] = signed(unsigned((syn[1] + 0x1010104) & 0xffffffff) % 0xffffffff); + var tmp = unsigned(syn[1]) + 0x1010104; // Special thanks to Ilya Matveychikov + syn[1] = signed(tmp < 0x100000000 ? tmp : tmp - 0xffffffff); + + this.process(key, syn); + + for (var i = 0; i < r; i++) + c[q * b + i] ^= s[i]; + } + return c.buffer; +} // + +function processCTR15(k, d, iv) // +{ + var c = cloneArray(d), + n = this.blockSize, + b = this.shiftBits >> 3, + cb = c.length, r = cb % b, q = (cb - r) / b, + s = new Uint8Array(n), + t = new Int32Array(n), + key = this.keySchedule(k); + + s.set(iv || this.iv); + for (var i = 0; i < q; i++) { + + for (var j = 0; j < n; j++) + t[j] = s[j]; + + this.process(key, s); + + for (var j = 0; j < b; j++) + c[b * i + j] ^= s[j]; + + for (var j = 0; j < n; j++) + s[j] = t[j]; + + for (var j = n - 1; i >= 0; --i) { + if (s[j] > 0xfe) { + s[j] -= 0xfe; + } else { + s[j]++; + break; + } + } + } + + if (r > 0) { + this.process(key, s); + for (var j = 0; j < r; j++) + c[b * q + j] ^= s[j]; + } + + return c.buffer; +} // + +/** + * Algorithm name GOST 28147-CBC

+ * + * encryptCBC (IV, K, D) is D, encrypted with key K using GOST 28147/GOST R 34.13 + * in "Prostaya zamena s zatsepleniem" (Cipher-Block-Chaining, CBC) mode and IV is used as the initialization + * vector. + * + * @memberOf GostCipher + * @method encrypt + * @instance + * @param {CryptoOperationData} k 8x32 bits key + * @param {CryptoOperationData} d 8 bits array with data + * @param {CryptoOperationData} iv initial vector + * @return {CryptoOperationData} result + */ +function encryptCBC(k, d, iv) // +{ + var s = new Uint8Array(iv || this.iv), + n = this.blockSize, + m = s.length, + c = this.pad(byteArray(d)), + key = this.keySchedule(k); + + for (var i = 0, b = c.length / n; i < b; i++) { + + for (var j = 0; j < n; j++) + s[j] ^= c[i * n + j]; + + this.process(key, s); + + for (var j = 0; j < n; j++) + c[i * n + j] = s[j]; + + if (m !== n) { + for (var j = 0; j < m - n; j++) + s[j] = s[n + j]; + + for (var j = 0; j < n; j++) + s[j + m - n] = c[i * n + j]; + } + + k = this.keyMeshing(k, s, i, key); + } + + return c.buffer; +} // + +/** + * Algorithm name GOST 28147-CBC

+ * + * decryptCBC (IV, K, D) is D, decrypted with key K using GOST 28147/GOST R 34.13 + * in "Prostaya zamena s zatsepleniem" (Cipher-Block-Chaining, CBC) mode and IV is used as the initialization + * vector. + * + * @memberOf GostCipher + * @method decrypt + * @instance + * @param {CryptoOperationData} k 8x32 bits key + * @param {CryptoOperationData} d 8 bits array with data + * @param {CryptoOperationData} iv initial vector + * @return {CryptoOperationData} result + */ +function decryptCBC(k, d, iv) // +{ + var s = new Uint8Array(iv || this.iv), + n = this.blockSize, + m = s.length, + c = cloneArray(d), + next = new Uint8Array(n), + key = this.keySchedule(k, 1); + + for (var i = 0, b = c.length / n; i < b; i++) { + + for (var j = 0; j < n; j++) + next[j] = c[i * n + j]; + + this.process(key, c, i * n, 1); + + for (var j = 0; j < n; j++) + c[i * n + j] ^= s[j]; + + if (m !== n) { + for (var j = 0; j < m - n; j++) + s[j] = s[n + j]; + } + + for (var j = 0; j < n; j++) + s[j + m - n] = next[j]; + + k = this.keyMeshing(k, s, i, key, 1); + } + + return this.unpad(c).buffer; +} // + +/** + * The generateKey method returns a new generated key. + * + * @memberOf GostCipher + * @method generateKey + * @instance + * @return {CryptoOperationData} result + */ + +function generateKey() // +{ + // Simple generate 256 bit random seed + var k = new Uint8Array(this.keySize); + randomSeed(k); + return k.buffer; +} // + + +/** + * makeIMIT (K, D) is the 32-bit result of the GOST 28147/GOST R 34.13 in + * "imitovstavka" (MAC) mode, used with D as plaintext, K as key and IV + * as initialization vector. Note that the standard specifies its use + * in this mode only with an initialization vector of zero. + * + * @memberOf GostCipher + * @method processMAC + * @private + * @instance + * @param {Int32Array} key 8x32 bits key + * @param {Int32Array} s 8x8 sum array + * @param {Uint8Array} d 8 bits array with data + * @return {Uint8Array} result + */ +function processMAC89(key, s, d) // +{ + var c = zeroPad.call(this, byteArray(d)), + n = this.blockSize, + q = c.length / n, + sBox = this.sBox, + sum = new Int32Array(s.buffer); + + for (var i = 0; i < q; i++) { + + for (var j = 0; j < n; j++) + s[j] ^= c[i * n + j]; + + for (var j = 0; j < 16; j++) // 1-16 steps + round(sBox, sum, key[j]); + } +} // + +function processKeyMAC15(s) // +{ + var t = 0, n = s.length; + for (var i = n - 1; i >= 0; --i) { + var t1 = s[i] >>> 7; + s[i] = (s[i] << 1) & 0xff | t; + t = t1; + } + if (t !== 0) { + if (n === 16) + s[15] ^= 0x87; + else + s[7] ^= 0x1b; + } +} // + +function processMAC15(key, s, d) // +{ + var n = this.blockSize, + sBox = this.sBox, c = byteArray(d), + r = new Uint8Array(n); + // R + this.process(key, r); + // K1 + processKeyMAC15(r); + if (d.byteLength % n !== 0) { + c = bitPad.call(this, byteArray(d)); + // K2 + processKeyMAC15(r); + } + + for (var i = 0, q = c.length / n; i < q; i++) { + + for (var j = 0; j < n; j++) + s[j] ^= c[i * n + j]; + + if (i === q - 1) {// Last block + for (var j = 0; j < n; j++) + s[j] ^= r[j]; + } + + this.process(key, s); + } +} // + +/** + * signMAC (K, D, IV) is the 32-bit result of the GOST 28147/GOST R 34.13 in + * "imitovstavka" (MAC) mode, used with D as plaintext, K as key and IV + * as initialization vector. Note that the standard specifies its use + * in this mode only with an initialization vector of zero. + * + * @memberOf GostCipher + * @method sign + * @instance + * @param {CryptoOperationData} k 8x32 bits key + * @param {CryptoOperationData} d 8 bits array with data + * @param {CryptoOperationData} iv initial vector + * @return {CryptoOperationData} result + */ +function signMAC(k, d, iv) // +{ + var key = this.keySchedule(k), + s = new Uint8Array(iv || this.iv), + m = Math.ceil(this.macLength >> 3) || this.blockSize >> 1; + + this.processMAC(key, s, d); + + var mac = new Uint8Array(m); // mac size + mac.set(new Uint8Array(s.buffer, 0, m)); + return mac.buffer; +} // + +/** + * verifyMAC (K, M, D, IV) the 32-bit result verification of the GOST 28147/GOST R 34.13 in + * "imitovstavka" (MAC) mode, used with D as plaintext, K as key and IV + * as initialization vector. Note that the standard specifies its use + * in this mode only with an initialization vector of zero. + * + * @memberOf GostCipher + * @method verify + * @instance + * @param {CryptoOperationData} k 8x32 bits key + * @param {CryptoOperationData} m 8 bits array with signature + * @param {CryptoOperationData} d 8 bits array with data + * @param {CryptoOperationData} iv 8x8 optional bits initial vector + * @return {boolen} MAC verified = true + */ +function verifyMAC(k, m, d, iv) // +{ + var mac = new Uint8Array(signMAC.call(this, k, d, iv)), + test = byteArray(m); + if (mac.length !== test.length) + return false; + for (var i = 0, n = mac.length; i < n; i++) + if (mac[i] !== test[i]) + return false; + return true; +} // + +/** + * Algorithm name GOST 28147-KW

+ * + * This algorithm encrypts GOST 28147-89 CEK with a GOST 28147/GOST R 34.13 KEK. + * Ref. rfc4357 6.1 GOST 28147-89 Key Wrap + * Note: This algorithm MUST NOT be used with a KEK produced by VKO GOST + * R 34.10-94, because such a KEK is constant for every sender-recipient + * pair. Encrypting many different content encryption keys on the same + * constant KEK may reveal that KEK. + * + * @memberOf GostCipher + * @method wrapKey + * @instance + * @param {CryptoOperationData} kek Key encryption key + * @param {CryptoOperationData} cek Content encryption key + * @returns {CryptoOperationData} Encrypted cek + */ +function wrapKeyGOST(kek, cek) // +{ + var n = this.blockSize, k = this.keySize, len = k + (n >> 1); + // 1) For a unique symmetric KEK, generate 8 octets at random and call + // the result UKM. For a KEK, produced by VKO GOST R 34.10-2001, use + // the UKM that was used for key derivation. + if (!this.ukm) + throw new DataError('UKM must be defined'); + var ukm = new Uint8Array(this.ukm); + // 2) Compute a 4-byte checksum value, GOST 28147IMIT (UKM, KEK, CEK). + // Call the result CEK_MAC. + var mac = signMAC.call(this, kek, cek, ukm); + // 3) Encrypt the CEK in ECB mode using the KEK. Call the ciphertext CEK_ENC. + var enc = encryptECB.call(this, kek, cek); + // 4) The wrapped content-encryption key is (UKM | CEK_ENC | CEK_MAC). + var r = new Uint8Array(len); + r.set(new Uint8Array(enc), 0); + r.set(new Uint8Array(mac), k); + return r.buffer; +} // + +/** + * Algorithm name GOST 28147-KW

+ * + * This algorithm decrypts GOST 28147-89 CEK with a GOST 28147 KEK. + * Ref. rfc4357 6.2 GOST 28147-89 Key Unwrap + * + * @memberOf GostCipher + * @method unwrapKey + * @instance + * @param {type} kek Key encryption key + * @param {type} data Content encryption key + * @return {CryptoOperationData} result + */ +function unwrapKeyGOST(kek, data) // +{ + var n = this.blockSize, k = this.keySize, len = k + (n >> 1); + // 1) If the wrapped content-encryption key is not 44 octets, then error. + var d = buffer(data); + if (d.byteLength !== len) + throw new DataError('Wrapping key size must be ' + len + ' bytes'); + // 2) Decompose the wrapped content-encryption key into UKM, CEK_ENC, and CEK_MAC. + // UKM is the most significant (first) 8 octets. CEK_ENC is next 32 octets, + // and CEK_MAC is the least significant (last) 4 octets. + if (!this.ukm) + throw new DataError('UKM must be defined'); + var ukm = new Uint8Array(this.ukm), + enc = new Uint8Array(d, 0, k), + mac = new Uint8Array(d, k, n >> 1); + // 3) Decrypt CEK_ENC in ECB mode using the KEK. Call the output CEK. + var cek = decryptECB.call(this, kek, enc); + // 4) Compute a 4-byte checksum value, GOST 28147IMIT (UKM, KEK, CEK), + // compare the result with CEK_MAC. If they are not equal, then error. + var check = verifyMAC.call(this, kek, mac, cek, ukm); + if (!check) + throw new DataError('Error verify MAC of wrapping key'); + return cek; +} // + +/** + * Algorithm name GOST 28147-CPKW

+ * + * Given a random 64-bit UKM and a GOST 28147 key K, this algorithm + * creates a new GOST 28147-89 key K(UKM). + * Ref. rfc4357 6.3 CryptoPro KEK Diversification Algorithm + * + * @memberOf GostCipher + * @method diversify + * @instance + * @private + * @param {CryptoOperationData} kek Key encryption key + * @param {CryptoOperationData} ukm Random generated value + * @returns {CryptoOperationData} Diversified kek + */ +function diversifyKEK(kek, ukm) // +{ + var n = this.blockSize; + + // 1) Let K[0] = K; + var k = intArray(kek); + // 2) UKM is split into components a[i,j]: + // UKM = a[0]|..|a[7] (a[i] - byte, a[i,0]..a[i,7] - it’s bits) + var a = []; + for (var i = 0; i < n; i++) { + a[i] = []; + for (var j = 0; j < 8; j++) { + a[i][j] = (ukm[i] >>> j) & 0x1; + } + } + // 3) Let i be 0. + // 4) K[1]..K[8] are calculated by repeating the following algorithm + // eight times: + for (var i = 0; i < n; i++) { + // A) K[i] is split into components k[i,j]: + // K[i] = k[i,0]|k[i,1]|..|k[i,7] (k[i,j] - 32-bit integer) + // B) Vector S[i] is calculated: + // S[i] = ((a[i,0]*k[i,0] + ... + a[i,7]*k[i,7]) mod 2^32) | + // (((~a[i,0])*k[i,0] + ... + (~a[i,7])*k[i,7]) mod 2^32); + var s = new Int32Array(2); + for (var j = 0; j < 8; j++) { + if (a[i][j]) + s[0] = (s[0] + k[j]) & 0xffffffff; + else + s[1] = (s[1] + k[j]) & 0xffffffff; + } + // C) K[i+1] = encryptCFB (S[i], K[i], K[i]) + var iv = new Uint8Array(s.buffer); + k = new Int32Array(encryptCFB.call(this, k, k, iv)); + // D) i = i + 1 + } + // 5) Let K(UKM) be K[8]. + return k; +} // + +/** + * Algorithm name GOST 28147-CPKW

+ * + * This algorithm encrypts GOST 28147-89 CEK with a GOST 28147 KEK. + * It can be used with any KEK (e.g., produced by VKO GOST R 34.10-94 or + * VKO GOST R 34.10-2001) because a unique UKM is used to diversify the KEK. + * Ref. rfc4357 6.3 CryptoPro Key Wrap + * + * @memberOf GostCipher + * @method wrapKey + * @instance + * @param {CryptoOperationData} kek Key encryption key + * @param {CryptoOperationData} cek Content encryption key + * @returns {CryptoOperationData} Encrypted cek + */ +function wrapKeyCP(kek, cek) // +{ + var n = this.blockSize, k = this.keySize, len = k + (n >> 1); + // 1) For a unique symmetric KEK or a KEK produced by VKO GOST R + // 34.10-94, generate 8 octets at random. Call the result UKM. For + // a KEK, produced by VKO GOST R 34.10-2001, use the UKM that was + // used for key derivation. + if (!this.ukm) + throw new DataError('UKM must be defined'); + var ukm = new Uint8Array(this.ukm); + // 2) Diversify KEK, using the CryptoPro KEK Diversification Algorithm, + // described in Section 6.5. Call the result KEK(UKM). + var dek = diversifyKEK.call(this, kek, ukm); + // 3) Compute a 4-byte checksum value, GOST 28147IMIT (UKM, KEK(UKM), + // CEK). Call the result CEK_MAC. + var mac = signMAC.call(this, dek, cek, ukm); + // 4) Encrypt CEK in ECB mode using KEK(UKM). Call the ciphertext + // CEK_ENC. + var enc = encryptECB.call(this, dek, cek); + // 5) The wrapped content-encryption key is (UKM | CEK_ENC | CEK_MAC). + var r = new Uint8Array(len); + r.set(new Uint8Array(enc), 0); + r.set(new Uint8Array(mac), k); + return r.buffer; +} // + +/** + * Algorithm name GOST 28147-CPKW

+ * + * This algorithm encrypts GOST 28147-89 CEK with a GOST 28147 KEK. + * Ref. rfc4357 6.4 CryptoPro Key Unwrap + * + * @memberOf GostCipher + * @method unwrapKey + * @instance + * @param {CryptoOperationData} kek Key encryption key + * @param {CryptoOperationData} data Encrypted content encryption keu + * @return {CryptoOperationData} result Decrypted content encryption keu + */ +function unwrapKeyCP(kek, data) // +{ + var n = this.blockSize, k = this.keySize, len = k + (n >> 1); + // 1) If the wrapped content-encryption key is not 44 octets, then error. + var d = buffer(data); + if (d.byteLength !== len) + throw new DataError('Wrapping key size must be ' + len + ' bytes'); + // 2) Decompose the wrapped content-encryption key into UKM, CEK_ENC, + // and CEK_MAC. UKM is the most significant (first) 8 octets. + // CEK_ENC is next 32 octets, and CEK_MAC is the least significant + // (last) 4 octets. + if (!this.ukm) + throw new DataError('UKM must be defined'); + var ukm = new Uint8Array(this.ukm), + enc = new Uint8Array(d, 0, k), + mac = new Uint8Array(d, k, n >> 1); + // 3) Diversify KEK using the CryptoPro KEK Diversification Algorithm, + // described in section 6.5. Call the result KEK(UKM). + var dek = diversifyKEK.call(this, kek, ukm); + // 4) Decrypt CEK_ENC in ECB mode using KEK(UKM). Call the output CEK. + var cek = decryptECB.call(this, dek, enc); + // 5) Compute a 4-byte checksum value, GOST 28147IMIT (UKM, KEK(UKM), + // CEK), compare the result with CEK_MAC. If they are not equal, + // then it is an error. + var check = verifyMAC.call(this, dek, mac, cek, ukm); + if (!check) + throw new DataError('Error verify MAC of wrapping key'); + return cek; +} // + +/** + * SignalCom master key packing algorithm + * + * kek stored in 3 files - kek.opq, mk.db3, masks.db3 + * kek.opq - always 36 bytes length = 32 bytes encrypted kek + 4 bytes mac of decrypted kek + * mk.db3 - 6 bytes header (1 byte magic code 0x22 + 1 byte count of masks + 4 bytes mac of + * xor summarizing masks value) + attached masks + * masks.db3 - detached masks. + * Total length of attached + detached masks = 32 bits * count of masks + * Default value of count 8 = (7 attached + 1 detached). But really no reason for such + * separation - all masks xor summarizing - order is not matter. + * Content of file rand.opq can used as ukm. Don't forget change file content after using. + * + * For usb-token files has names: + * a001 - mk.db3, b001 - masks.db3, c001 - kek.opq, d001 - rand.opq + * For windows registry + * 00000001 - mk.db3, 00000002 - masks.db3, 00000003 - key.opq, 00000004 - rand.opq, + * 00000006 - keys\00000001.key, 0000000A - certificate + * + * @memberOf GostCipher + * @method packKey + * @instance + * @private + * @param {CryptoOperationData} unpacked - clear main key 32 bytes + * @param {CryptoOperationData} ukm - random vector for packing - 32 bytes * (count of masks - 1) + * @returns {CryptoOperationData} packed master key - concatination of mk.db3 + masks.db3 + */ +function packKeySC(unpacked, ukm) // +{ + var m = this.blockSize >> 1, k = this.keySize; + var mcount = 8; + var key = new Uint8Array(buffer(unpacked)); + if (key.byteLength !== k) + throw new DataError('Wrong cleartext size ' + key.byteLength + ' bytes'); + // Check or generate UKM + ukm = ukm || this.ukm; + if (ukm) { + ukm = new Uint8Array(buffer(ukm)); + if (ukm.byteLength > 0 && ukm.byteLength % k === 0) + mcount = ukm.byteLength / k + 1; + else + throw new DataError('Wrong rand size ' + ukm.byteLength + ' bytes'); + } else + randomSeed(ukm = new Uint8Array((mcount - 1) * k)); + // Output array + var d = new Uint8Array(mcount * k + m + 2), b = d.buffer; + // Calculate MAC + var zero32 = new Uint8Array(k); + var mac = signMAC.call(this, key, zero32); + d[0] = 0x22; // Magic code + d[1] = mcount; // Count of masks + d.set(new Uint8Array(mac), 2); + d.set(ukm, k + m + 2); + for (var i = 1; i < mcount; i++) { + var mask = new Uint8Array(b, 2 + m + k * i); + for (var j = 0; j < k; j++) + key[j] ^= mask[j]; + } + d.set(key, m + 2); + return d.buffer; +} // + +/** + * Algorithm name GOST 28147-SCKW

+ * + * SignalCom master key unpacking algorithm + * + * @memberOf GostCipher + * @method unpackKey + * @instance + * @private + * @param {CryptoOperationData} packed - concatination of mk.db3 + masks.db3 + * @returns {CryptoOperationData} unpacked master key + */ +function unpackKeySC(packed) // +{ + var m = this.blockSize >> 1, k = this.keySize; + var b = buffer(packed); + // Unpack master key + var magic = new Uint8Array(b, 0, 1)[0]; + if (magic !== 0x22) + throw new DataError('Invalid magic number'); + var mcount = new Uint8Array(b, 1, 1)[0]; + var mac = new Uint8Array(b, 2, m); // MAC for summarized mask + // Compute packKey xor summing for all masks + var key = new Uint8Array(k); + for (var i = 0; i < mcount; i++) { + var mask = new Uint8Array(b, 2 + m + k * i, k); + for (var j = 0; j < k; j++) + key[j] ^= mask[j]; + } + // Test MAC for packKey with default sBox on zero 32 bytes array + var zero32 = new Uint8Array(k); + var test = verifyMAC.call(this, key, mac, zero32); + if (!test) { + // Try to use different sBoxes + var names = ['E-A', 'E-B', 'E-C', 'E-D', 'E-SC']; + for (var i = 0, n = names.length; i < n; i++) { + this.sBox = sBoxes[names[i]]; + test = verifyMAC.call(this, key, mac, zero32); + if (test) + break; + } + } + if (!test) + throw new DataError('Invalid main key MAC'); + return key.buffer; +} // + +/** + * Algorithm name GOST 28147-SCKW

+ * + * SignalCom Key Wrapping algorithm + * + * @memberOf GostCipher + * @method wrapKey + * @instance + * @param {CryptoOperationData} kek - clear kek or concatination of mk.db3 + masks.db3 + * @param {CryptoOperationData} cek - key for wrapping + * @returns {CryptoOperationData} wrapped key - file kek.opq + */ +function wrapKeySC(kek, cek) // +{ + var m = this.blockSize >> 1, n = this.keySize; + var k = buffer(kek); + var c = buffer(cek); + if (k.byteLength !== n) + k = unpackKeySC.call(this, k); + var enc = encryptECB.call(this, k, c); + var mac = signMAC.call(this, k, c); + var d = new Uint8Array(m + n); + d.set(new Uint8Array(enc), 0); + d.set(new Uint8Array(mac), n); + return d.buffer; +} // + +/** + * Algorithm name GOST 28147-SCKW

+ * + * SignalCom Key UnWrapping algorithm + * + * @memberOf GostCipher + * @method unwrapKey + * @instance + * @param {CryptoOperationData} kek - concatination of files mk.db3 + masks.db3 or clear kek + * @param {CryptoOperationData} cek - wrapping key - file kek.opq + * @return {CryptoOperationData} result + */ +function unwrapKeySC(kek, cek) // +{ + var m = this.blockSize >> 1, n = this.keySize; + var k = buffer(kek); + var c = buffer(cek); + if (k.byteLength !== n) + k = unpackKeySC.call(this, k); + var enc = new Uint8Array(c, 0, n); // Encrypted kek + var mac = new Uint8Array(c, n, m); // MAC for clear kek + var d = decryptECB.call(this, k, enc); + if (!verifyMAC.call(this, k, mac, d)) + throw new DataError('Invalid key MAC'); + return d; +} // + +/** + * Algorithm name GOST 28147-SCKW

+ * + * SignalCom master key generation for wrapping + * + * @memberOf GostCipher + * @method generateKey + * @instance + * @return {CryptoOperationData} result + */ +function generateWrappingKeySC() // +{ + return packKeySC.call(this, generateKey.call(this)); +} // + +function maskKey(mask, key, inverse, keySize) // +{ + var k = keySize / 4, + m32 = new Int32Array(buffer(mask)), + k32 = new Int32Array(buffer(key)), + r32 = new Int32Array(k); + if (inverse) + for (var i = 0; i < k; i++) + r32[i] = (k32[i] + m32[i]) & 0xffffffff; + else + for (var i = 0; i < k; i++) + r32[i] = (k32[i] - m32[i]) & 0xffffffff; + return r32.buffer; +} // + +/** + * Algorithm name GOST 28147-MASK

+ * + * This algorithm wrap key mask + * + * @memberOf GostCipher + * @method wrapKey + * @instance + * @param {CryptoOperationData} mask The mask + * @param {CryptoOperationData} key The key + * @returns {CryptoOperationData} The masked key + */ +function wrapKeyMask(mask, key) // +{ + return maskKey(mask, key, this.procreator === 'VN', this.keySize); +} // + +/** + * Algorithm name GOST 28147-CPKW

+ * + * This algorithm unwrap key mask + * + * @memberOf GostCipher + * @method unwrapKey + * @instance + * @param {CryptoOperationData} mask The mask + * @param {CryptoOperationData} key The masked key + * @return {CryptoOperationData} result The key + */ +function unwrapKeyMask(mask, key) // +{ + return maskKey(mask, key, this.procreator !== 'VN', this.keySize); +} // + +/** + * Algorithm name GOST 28147-CPKM

+ * + * Key meshing in according to rfc4357 2.3.2. CryptoPro Key Meshing + * + * @memberOf GostCipher + * @method keyMeshing + * @instance + * @private + * @param {(Uint8Array|CryptoOperationData)} k 8x8 bit key + * @param {Uint8Array} s 8x8 bit sync (iv) + * @param {Integer} i block index + * @param {Int32Array} key 8x32 bit key schedule + * @param {boolean} e true - decrypt + * @returns CryptoOperationData next 8x8 bit key + */ +function keyMeshingCP(k, s, i, key, e) // +{ + if ((i + 1) * this.blockSize % 1024 === 0) { // every 1024 octets + // K[i+1] = decryptECB (K[i], C); + k = decryptECB.call(this, k, C); + // IV0[i+1] = encryptECB (K[i+1],IVn[i]) + s.set(new Uint8Array(encryptECB.call(this, k, s))); + // restore key schedule + key.set(this.keySchedule(k, e)); + } + return k; +} // + +/** + * Null Key Meshing in according to rfc4357 2.3.1 + * + * @memberOf GostCipher + * @method keyMeshing + * @instance + * @private + * @param {(Uint8Array|CryptoOperationData)} k 8x8 bit key + */ +function noKeyMeshing(k) // +{ + return k; +} // + +/** + * Algorithm name GOST 28147-NoPadding

+ * + * No padding. + * + * @memberOf GostCipher + * @method padding + * @instance + * @private + * @param {Uint8Array} d array with source data + * @returns {Uint8Array} result + */ +function noPad(d) // +{ + return new Uint8Array(d); +} // + +/** + * Algorithm name GOST 28147-PKCS5Padding

+ * + * PKCS#5 padding: 8-x remaining bytes are filled with the value of + * 8-x. If there’s no incomplete block, one extra block filled with + * value 8 is added + * + * @memberOf GostCipher + * @method padding + * @instance + * @private + * @param {Uint8Array} d array with source data + * @returns {Uint8Array} result + */ +function pkcs5Pad(d) // +{ + var n = d.byteLength, + nb = this.blockSize, + q = nb - n % nb, + m = Math.ceil((n + 1) / nb) * nb, + r = new Uint8Array(m); + r.set(d); + for (var i = n; i < m; i++) + r[i] = q; + return r; +} // + +function pkcs5Unpad(d) // +{ + var m = d.byteLength, + nb = this.blockSize, + q = d[m - 1], + n = m - q; + if (q > nb) + throw DataError('Invalid padding'); + var r = new Uint8Array(n); + if (n > 0) + r.set(new Uint8Array(d.buffer, 0, n)); + return r; +} // + + +/** + * Algorithm name GOST 28147-ZeroPadding

+ * + * Zero padding: 8-x remaining bytes are filled with zero + * + * @memberOf GostCipher + * @method padding + * @instance + * @private + * @param {Uint8Array} d array with source data + * @returns {Uint8Array} result + */ +function zeroPad(d) // +{ + var n = d.byteLength, + nb = this.blockSize, + m = Math.ceil(n / nb) * nb, + r = new Uint8Array(m); + r.set(d); + for (var i = n; i < m; i++) + r[i] = 0; + return r; +} // + + +/** + * Algorithm name GOST 28147-BitPadding

+ * + * Bit padding: P* = P || 1 || 000...0 If there’s no incomplete block, + * one extra block filled with 1 || 000...0 + * + * @memberOf GostCipher + * @method padding + * @instance + * @private + * @param {Uint8Array} d array with source data + * @returns {Uint8Array} result + */ +function bitPad(d) // +{ + var n = d.byteLength, + nb = this.blockSize, + m = Math.ceil((n + 1) / nb) * nb, + r = new Uint8Array(m); + r.set(d); + r[n] = 1; + for (var i = n + 1; i < m; i++) + r[i] = 0; + return r; +} // + +function bitUnpad(d) // +{ + var m = d.byteLength, + n = m; + while (n > 1 && d[n - 1] === 0) + n--; + if (d[n - 1] !== 1) + throw DataError('Invalid padding'); + n--; + var r = new Uint8Array(n); + if (n > 0) + r.set(new Uint8Array(d.buffer, 0, n)); + return r; +} // + +/** + * Algorithm name GOST 28147-RandomPadding

+ * + * Random padding: 8-x remaining bytes of the last block are set to + * random. + * + * @memberOf GostCipher + * @method padding + * @instance + * @private + * @param {Uint8Array} d array with source data + * @returns {Uint8Array} result + */ +function randomPad(d) // +{ + var n = d.byteLength, + nb = this.blockSize, + q = nb - n % nb, + m = Math.ceil(n / nb) * nb, + r = new Uint8Array(m), e = new Uint8Array(r.buffer, n, q); + r.set(d); + randomSeed(e); + return r; +} // + +/** + * GOST 28147-89 Encryption Algorithm

+ * + * References {@link http://tools.ietf.org/html/rfc5830}

+ * + * When keys and initialization vectors are converted to/from byte arrays, + * little-endian byte order is assumed.

+ * + * Normalized algorithm identifier common parameters: + * + *
    + *
  • name Algorithm name 'GOST 28147' or 'GOST R 34.12'
  • + *
  • version Algorithm version, number + *
      + *
    • 1989 Current version of standard
    • + *
    • 2015 New draft version of standard
    • + *
    + *
  • + *
  • length Block length + *
      + *
    • 64 64 bits length (default)
    • + *
    • 128 128 bits length (only for version 2015)
    • + *
    + *
  • + *
  • mode Algorithm mode, string + *
      + *
    • ES Encryption mode (default)
    • + *
    • MAC "imitovstavka" (MAC) mode
    • + *
    • KW Key wrapping mode
    • + *
    + *
  • + *
  • sBox Paramset sBox for GOST 28147-89, string. Used only if version = 1989
  • + *
+ * + * Supported algorithms, modes and parameters: + * + *
    + *
  • Encript/Decrypt mode (ES) + *
      + *
    • block Block mode, string. Default ECB
    • + *
    • keyMeshing Key meshing mode, string. Default NO
    • + *
    • padding Padding mode, string. Default NO for CFB and CTR modes, or ZERO for others
    • + *
    • iv {@link CryptoOperationData} Initial vector with length of block. Default - zero block
    • + *
    + *
  • + *
  • Sign/Verify mode (MAC) + *
      + *
    • macLength Length of mac in bits (default - 32 bits)
    • + *
    • iv {@link CryptoOperationData} Initial vector with length of block. Default - zero block
    • + *
    + *
  • + *
  • Wrap/Unwrap key mode (KW) + *
      + *
    • keyWrapping Mode of keywrapping, string. Default NO - standard GOST key wrapping
    • + *
    • ukm {@link CryptoOperationData} User key material. Default - random generated value
    • + *
    + *
  • + *
+ * + * Supported paramters values: + * + *
    + *
  • Block modes (parameter 'block') + *
      + *
    • ECB "prostaya zamena" (ECB) mode (default)
    • + *
    • CFB "gammirovanie s obratnoj svyaziyu po shifrotekstu" (CFB) mode
    • + *
    • OFB "gammirovanie s obratnoj svyaziyu po vyhodu" (OFB) mode
    • + *
    • CTR "gammirovanie" (counter) mode
    • + *
    • CBC Cipher-Block-Chaining (CBC) mode
    • + *
    + *
  • + *
  • Key meshing modes (parameter 'keyMeshing') + *
      + *
    • NO No key wrapping (default)
    • + *
    • CP CryptoPor Key key meshing
    • + *
    + *
  • + *
  • Padding modes (parameter 'padding') + *
      + *
    • NO No padding only for CFB, OFB and CTR modes
    • + *
    • PKCS5 PKCS#5 padding mode
    • + *
    • ZERO Zero bits padding mode
    • + *
    • RANDOM Random bits padding mode
    • + *
    • BIT One bit padding mode
    • + *
    + *
  • + *
  • Wrapping key modes (parameter 'keyWrapping') + *
      + *
    • NO Ref. rfc4357 6.1 GOST 28147-89 Key wrapping
    • + *
    • CP CryptoPro Key wrapping mode
    • + *
    • SC SignalCom Key wrapping mode
    • + *
    + *
  • + *
+ * + * @class GostCipher + * @param {AlgorithmIndentifier} algorithm WebCryptoAPI algorithm identifier + */ +function GostCipher(algorithm) // +{ + // Check little endian support + if (!littleEndian) + throw new NotSupportedError('Big endian platform not supported'); + algorithm = algorithm || {}; + this.keySize = 32; + this.blockLength = algorithm.length || 64; + this.blockSize = this.blockLength >> 3; + + this.name = (algorithm.name || (algorithm.version === 1 ? 'RC2' : + algorithm.version === 1989 ? 'GOST 28147' : 'GOST R 34.12')) + + (algorithm.version > 4 ? '-' + ((algorithm.version || 1989) % 100) : '') + '-' + + (this.blockLength === 64 ? '' : this.blockLength + '-') + + ((algorithm.mode === 'MAC') ? 'MAC-' + (algorithm.macLength || this.blockLength >> 1) : + (algorithm.mode === 'KW' || algorithm.keyWrapping) ? + ((algorithm.keyWrapping || 'NO') !== 'NO' ? algorithm.keyWrapping : '') + 'KW' : + (algorithm.block || 'ECB') + ((algorithm.block === 'CFB' || algorithm.block === 'OFB' || + (algorithm.block === 'CTR' && algorithm.version === 2015)) && + algorithm?.shiftBits !== this.blockLength ? '-' + algorithm.shiftBits : '') + + (algorithm.padding ? '-' + (algorithm.padding || (algorithm.block === 'CTR' || + algorithm.block === 'CFB' || algorithm.block === 'OFB' ? 'NO' : 'ZERO')) + 'PADDING' : '') + + ((algorithm.keyMeshing || 'NO') !== 'NO' ? '-CPKEYMESHING' : '')) + + (algorithm.procreator ? '/' + algorithm.procreator : '') + + (typeof algorithm.sBox === 'string' ? '/' + algorithm.sBox : ''); + + // Algorithm procreator + this.procreator = algorithm.procreator; + + switch (algorithm.version || 1989) { + case 1: + this.process = processRC2; + this.keySchedule = keyScheduleRC2; + this.blockLength = 64; + this.effectiveLength = algorithm.length || 32; + this.keySize = 8 * Math.ceil(this.effectiveLength / 8); // Max 128 + this.blockSize = this.blockLength >> 3; + break; + case 2015: + this.version = 2015; + if (this.blockLength === 64) { + this.process = process15; + this.keySchedule = keySchedule15; + } else if (this.blockLength === 128) { + this.process = process128; + this.keySchedule = keySchedule128; + } else + throw new DataError('Invalid block length'); + this.processMAC = processMAC15; + break; + case 1989: + this.version = 1989; + this.process = process89; + this.processMAC = processMAC89; + this.keySchedule = keySchedule89; + if (this.blockLength !== 64) + throw new DataError('Invalid block length'); + break; + default: + throw new NotSupportedError('Algorithm version ' + algorithm.version + ' not supported'); + } + + switch (algorithm.mode || (algorithm.keyWrapping && 'KW') || 'ES') { + + case 'ES': + switch (algorithm.block || 'ECB') { + case 'ECB': + this.encrypt = encryptECB; + this.decrypt = decryptECB; + break; + case 'CTR': + if (this.version === 1989) { + this.encrypt = processCTR89; + this.decrypt = processCTR89; + } else { + this.encrypt = processCTR15; + this.decrypt = processCTR15; + this.shiftBits = algorithm.shiftBits || this.blockLength; + } + break + case 'CBC': + this.encrypt = encryptCBC; + this.decrypt = decryptCBC; + break + case 'CFB': + this.encrypt = encryptCFB; + this.decrypt = decryptCFB; + this.shiftBits = algorithm.shiftBits || this.blockLength; + break; + case 'OFB': + this.encrypt = processOFB; + this.decrypt = processOFB; + this.shiftBits = algorithm.shiftBits || this.blockLength; + break; + default: + throw new NotSupportedError('Block mode ' + algorithm.block + ' not supported'); + } + switch (algorithm.keyMeshing) { + case 'CP': + this.keyMeshing = keyMeshingCP; + break; + default: + this.keyMeshing = noKeyMeshing; + } + if (this.encrypt === encryptECB || this.encrypt === encryptCBC) { + switch (algorithm.padding) { + case 'PKCS5P': + this.pad = pkcs5Pad; + this.unpad = pkcs5Unpad; + break; + case 'RANDOM': + this.pad = randomPad; + this.unpad = noPad; + break; + case 'BIT': + this.pad = bitPad; + this.unpad = bitUnpad; + break; + default: + this.pad = zeroPad; + this.unpad = noPad; + } + } else { + this.pad = noPad; + this.unpad = noPad; + } + this.generateKey = generateKey; + break; + case 'MAC': + this.sign = signMAC; + this.verify = verifyMAC; + this.generateKey = generateKey; + this.macLength = algorithm.macLength || (this.blockLength >> 1); + this.pad = noPad; + this.unpad = noPad; + this.keyMeshing = noKeyMeshing; + break; + case 'KW': + this.pad = noPad; + this.unpad = noPad; + this.keyMeshing = noKeyMeshing; + switch (algorithm.keyWrapping) { + case 'CP': + this.wrapKey = wrapKeyCP; + this.unwrapKey = unwrapKeyCP; + this.generateKey = generateKey; + this.shiftBits = algorithm.shiftBits || this.blockLength; + break; + case 'SC': + this.wrapKey = wrapKeySC; + this.unwrapKey = unwrapKeySC; + this.generateKey = generateWrappingKeySC; + break; + default: + this.wrapKey = wrapKeyGOST; + this.unwrapKey = unwrapKeyGOST; + this.generateKey = generateKey; + } + break; + case 'MASK': + this.wrapKey = wrapKeyMask; + this.unwrapKey = unwrapKeyMask; + this.generateKey = generateKey; + break; + default: + throw new NotSupportedError('Mode ' + algorithm.mode + ' not supported'); + } + + // Define sBox parameter + var sBox = algorithm.sBox, sBoxName; + if (!sBox) + sBox = this.version === 2015 ? sBoxes['E-Z'] : this.procreator === 'SC' ? sBoxes['E-SC'] : sBoxes['E-A']; + else if (typeof sBox === 'string') { + sBoxName = sBox.toUpperCase(); + sBox = sBoxes[sBoxName]; + if (!sBox) + throw new SyntaxError('Unknown sBox name: ' + algorithm.sBox); + } else if (!sBox.length || sBox.length !== sBoxes['E-Z'].length) + throw new SyntaxError('Length of sBox must be ' + sBoxes['E-Z'].length); + this.sBox = sBox; + // Initial vector + if (algorithm.iv) { + this.iv = new Uint8Array(algorithm.iv); + if (this.iv.byteLength !== this.blockSize && this.version === 1989) + throw new SyntaxError('Length of iv must be ' + this.blockLength + ' bits'); + else if (this.iv.byteLength !== this.blockSize >> 1 && this.encrypt === processCTR15) + throw new SyntaxError('Length of iv must be ' + this.blockLength >> 1 + ' bits'); + else if (this.iv.byteLength % this.blockSize !== 0 && this.encrypt !== processCTR15) + throw new SyntaxError('Length of iv must be a multiple of ' + this.blockLength + ' bits'); + } else + this.iv = this.blockLength === 128 ? defaultIV128 : defaultIV; + // User key material + if (algorithm.ukm) { + this.ukm = new Uint8Array(algorithm.ukm); + if (this.ukm.byteLength * 8 !== this.blockLength) + throw new SyntaxError('Length of ukm must be ' + this.blockLength + ' bits'); + } +} // + +export default GostCipher; diff --git a/plugins/srktoolbox/src/core/vendor/gost/gostCoding.mjs b/plugins/srktoolbox/src/core/vendor/gost/gostCoding.mjs new file mode 100644 index 00000000..ed42b3cc --- /dev/null +++ b/plugins/srktoolbox/src/core/vendor/gost/gostCoding.mjs @@ -0,0 +1,1160 @@ +/** + * Coding algorithms: Base64, Hex, Int16, Chars, BER and PEM + * version 1.76 + * 2014-2016, Rudolf Nickolaev. All rights reserved. + * + * Exported for CyberChef by mshwed [m@ttshwed.com] + */ + +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 HOLDER 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. + * + */ + +import gostCrypto from './gostCrypto.mjs'; + +/** + * The Coding interface provides string converting methods: Base64, Hex, + * Int16, Chars, BER and PEM + * @class GostCoding + * + */ // +var root = {}; +var DataError = Error; +var CryptoOperationData = ArrayBuffer; +var Date = Date; + +function buffer(d) { + if (d instanceof CryptoOperationData) + return d; + else if (d && d?.buffer instanceof CryptoOperationData) + return d.byteOffset === 0 && d.byteLength === d.buffer.byteLength ? + d.buffer : new Uint8Array(new Uint8Array(d, d.byteOffset, d.byteLength)).buffer; + else + throw new DataError('CryptoOperationData required'); +} // + +function GostCoding() { +} + +/** + * BASE64 conversion + * + * @class GostCoding.Base64 + */ +var Base64 = {// + /** + * Base64.decode convert BASE64 string s to CryptoOperationData + * + * @memberOf GostCoding.Base64 + * @param {String} s BASE64 encoded string value + * @returns {CryptoOperationData} Binary decoded data + */ + decode: function (s) { + s = s.replace(/[^A-Za-z0-9\+\/]/g, ''); + var n = s.length, + k = n * 3 + 1 >> 2, r = new Uint8Array(k); + + for (var m3, m4, u24 = 0, j = 0, i = 0; i < n; i++) { + m4 = i & 3; + var c = s.charCodeAt(i); + + c = c > 64 && c < 91 ? + c - 65 : c > 96 && c < 123 ? + c - 71 : c > 47 && c < 58 ? + c + 4 : c === 43 ? + 62 : c === 47 ? + 63 : 0; + + u24 |= c << 18 - 6 * m4; + if (m4 === 3 || n - i === 1) { + for (m3 = 0; m3 < 3 && j < k; m3++, j++) { + r[j] = u24 >>> (16 >>> m3 & 24) & 255; + } + u24 = 0; + + } + } + return r.buffer; + }, + /** + * Base64.encode(data) convert CryptoOperationData data to BASE64 string + * + * @memberOf GostCoding.Base64 + * @param {CryptoOperationData} data Bynary data for encoding + * @returns {String} BASE64 encoded data + */ + encode: function (data) { + var slen = 8, d = new Uint8Array(buffer(data)); + var m3 = 2, s = ''; + for (var n = d.length, u24 = 0, i = 0; i < n; i++) { + m3 = i % 3; + if (i > 0 && (i * 4 / 3) % (12 * slen) === 0) + s += '\r\n'; + u24 |= d[i] << (16 >>> m3 & 24); + if (m3 === 2 || n - i === 1) { + for (var j = 18; j >= 0; j -= 6) { + var c = u24 >>> j & 63; + c = c < 26 ? c + 65 : c < 52 ? c + 71 : c < 62 ? c - 4 : + c === 62 ? 43 : c === 63 ? 47 : 65; + s += String.fromCharCode(c); + } + u24 = 0; + } + } + return s.substr(0, s.length - 2 + m3) + (m3 === 2 ? '' : m3 === 1 ? '=' : '=='); + } // +}; + +/** + * BASE64 conversion + * + * @memberOf GostCoding + * @insnance + * @type GostCoding.Base64 + */ +GostCoding.prototype.Base64 = Base64; + +/** + * Text string conversion
+ * Methods support charsets: ascii, win1251, utf8, utf16 (ucs2, unicode), utf32 (ucs4) + * + * @class GostCoding.Chars + */ +var Chars = (function () { // + + var _win1251_ = { + 0x402: 0x80, 0x403: 0x81, 0x201A: 0x82, 0x453: 0x83, 0x201E: 0x84, 0x2026: 0x85, 0x2020: 0x86, 0x2021: 0x87, + 0x20AC: 0x88, 0x2030: 0x89, 0x409: 0x8A, 0x2039: 0x8B, 0x40A: 0x8C, 0x40C: 0x8D, 0x40B: 0x8E, 0x40f: 0x8f, + 0x452: 0x90, 0x2018: 0x91, 0x2019: 0x92, 0x201C: 0x93, 0x201D: 0x94, 0x2022: 0x95, 0x2013: 0x96, 0x2014: 0x97, + 0x2122: 0x99, 0x459: 0x9A, 0x203A: 0x9B, 0x45A: 0x9C, 0x45C: 0x9D, 0x45B: 0x9E, 0x45f: 0x9f, + 0xA0: 0xA0, 0x40E: 0xA1, 0x45E: 0xA2, 0x408: 0xA3, 0xA4: 0xA4, 0x490: 0xA5, 0xA6: 0xA6, 0xA7: 0xA7, + 0x401: 0xA8, 0xA9: 0xA9, 0x404: 0xAA, 0xAB: 0xAB, 0xAC: 0xAC, 0xAD: 0xAD, 0xAE: 0xAE, 0x407: 0xAf, + 0xB0: 0xB0, 0xB1: 0xB1, 0x406: 0xB2, 0x456: 0xB3, 0x491: 0xB4, 0xB5: 0xB5, 0xB6: 0xB6, 0xB7: 0xB7, + 0x451: 0xB8, 0x2116: 0xB9, 0x454: 0xBA, 0xBB: 0xBB, 0x458: 0xBC, 0x405: 0xBD, 0x455: 0xBE, 0x457: 0xBf + }; + var _win1251back_ = {}; + for (var from in _win1251_) { + var to = _win1251_[from]; + _win1251back_[to] = from; + } + + return { + /** + * Chars.decode(s, charset) convert string s with defined charset to CryptoOperationData + * + * @memberOf GostCoding.Chars + * @param {string} s Javascript string + * @param {string} charset Charset, default 'win1251' + * @returns {CryptoOperationData} Decoded binary data + */ + decode: function (s, charset) { + charset = (charset || 'win1251').toLowerCase().replace('-', ''); + var r = []; + for (var i = 0, j = s.length; i < j; i++) { + var c = s.charCodeAt(i); + if (charset === 'utf8') { + if (c < 0x80) { + r.push(c); + } else if (c < 0x800) { + r.push(0xc0 + (c >>> 6)); + r.push(0x80 + (c & 63)); + } else if (c < 0x10000) { + r.push(0xe0 + (c >>> 12)); + r.push(0x80 + (c >>> 6 & 63)); + r.push(0x80 + (c & 63)); + } else if (c < 0x200000) { + r.push(0xf0 + (c >>> 18)); + r.push(0x80 + (c >>> 12 & 63)); + r.push(0x80 + (c >>> 6 & 63)); + r.push(0x80 + (c & 63)); + } else if (c < 0x4000000) { + r.push(0xf8 + (c >>> 24)); + r.push(0x80 + (c >>> 18 & 63)); + r.push(0x80 + (c >>> 12 & 63)); + r.push(0x80 + (c >>> 6 & 63)); + r.push(0x80 + (c & 63)); + } else { + r.push(0xfc + (c >>> 30)); + r.push(0x80 + (c >>> 24 & 63)); + r.push(0x80 + (c >>> 18 & 63)); + r.push(0x80 + (c >>> 12 & 63)); + r.push(0x80 + (c >>> 6 & 63)); + r.push(0x80 + (c & 63)); + } + } else if (charset === 'unicode' || charset === 'ucs2' || charset === 'utf16') { + if (c < 0xD800 || (c >= 0xE000 && c <= 0x10000)) { + r.push(c >>> 8); + r.push(c & 0xff); + } else if (c >= 0x10000 && c < 0x110000) { + c -= 0x10000; + var first = ((0xffc00 & c) >> 10) + 0xD800; + var second = (0x3ff & c) + 0xDC00; + r.push(first >>> 8); + r.push(first & 0xff); + r.push(second >>> 8); + r.push(second & 0xff); + } + } else if (charset === 'utf32' || charset === 'ucs4') { + r.push(c >>> 24 & 0xff); + r.push(c >>> 16 & 0xff); + r.push(c >>> 8 & 0xff); + r.push(c & 0xff); + } else if (charset === 'win1251') { + if (c >= 0x80) { + if (c >= 0x410 && c < 0x450) // А..Яа..я + c -= 0x350; + else + c = _win1251_[c] || 0; + } + r.push(c); + } else + r.push(c & 0xff); + } + return new Uint8Array(r).buffer; + }, + /** + * Chars.encode(data, charset) convert CryptoOperationData data to string with defined charset + * + * @memberOf GostCoding.Chars + * @param {CryptoOperationData} data Binary data + * @param {string} charset Charset, default win1251 + * @returns {string} Encoded javascript string + */ + encode: function (data, charset) { + charset = (charset || 'win1251').toLowerCase().replace('-', ''); + var r = [], d = new Uint8Array(buffer(data)); + for (var i = 0, n = d.length; i < n; i++) { + var c = d[i]; + if (charset === 'utf8') { + c = c >= 0xfc && c < 0xfe && i + 5 < n ? // six bytes + (c - 0xfc) * 1073741824 + (d[++i] - 0x80 << 24) + (d[++i] - 0x80 << 18) + (d[++i] - 0x80 << 12) + (d[++i] - 0x80 << 6) + d[++i] - 0x80 + : c >> 0xf8 && c < 0xfc && i + 4 < n ? // five bytes + (c - 0xf8 << 24) + (d[++i] - 0x80 << 18) + (d[++i] - 0x80 << 12) + (d[++i] - 0x80 << 6) + d[++i] - 0x80 + : c >> 0xf0 && c < 0xf8 && i + 3 < n ? // four bytes + (c - 0xf0 << 18) + (d[++i] - 0x80 << 12) + (d[++i] - 0x80 << 6) + d[++i] - 0x80 + : c >= 0xe0 && c < 0xf0 && i + 2 < n ? // three bytes + (c - 0xe0 << 12) + (d[++i] - 0x80 << 6) + d[++i] - 0x80 + : c >= 0xc0 && c < 0xe0 && i + 1 < n ? // two bytes + (c - 0xc0 << 6) + d[++i] - 0x80 + : c; // one byte + } else if (charset === 'unicode' || charset === 'ucs2' || charset === 'utf16') { + c = (c << 8) + d[++i]; + if (c >= 0xD800 && c < 0xE000) { + var first = (c - 0xD800) << 10; + c = d[++i]; + c = (c << 8) + d[++i]; + var second = c - 0xDC00; + c = first + second + 0x10000; + } + } else if (charset === 'utf32' || charset === 'ucs4') { + c = (c << 8) + d[++i]; + c = (c << 8) + d[++i]; + c = (c << 8) + d[++i]; + } else if (charset === 'win1251') { + if (c >= 0x80) { + if (c >= 0xC0 && c < 0x100) + c += 0x350; // А..Яа..я + else + c = _win1251back_[c] || 0; + } + } + r.push(String.fromCharCode(c)); + } + return r.join(''); + } + }; // +})(); + +/** + * Text string conversion + * + * @memberOf GostCoding + * @insnance + * @type GostCoding.Chars + */ +GostCoding.prototype.Chars = Chars; + +/** + * HEX conversion + * + * @class GostCoding.Hex + */ +var Hex = {// + /** + * Hex.decode(s, endean) convert HEX string s to CryptoOperationData in endean mode + * + * @memberOf GostCoding.Hex + * @param {string} s Hex encoded string + * @param {boolean} endean Little or Big Endean, default Little + * @returns {CryptoOperationData} Decoded binary data + */ + decode: function (s, endean) { + s = s.replace(/[^A-Fa-f0-9]/g, ''); + var n = Math.ceil(s.length / 2), r = new Uint8Array(n); + s = (s.length % 2 > 0 ? '0' : '') + s; + if (endean && ((typeof endean !== 'string') || + (endean.toLowerCase().indexOf('little') < 0))) + for (var i = 0; i < n; i++) + r[i] = parseInt(s.substr((n - i - 1) * 2, 2), 16); + else + for (var i = 0; i < n; i++) + r[i] = parseInt(s.substr(i * 2, 2), 16); + return r.buffer; + }, + /** + * Hex.encode(data, endean) convert CryptoOperationData data to HEX string in endean mode + * + * @memberOf GostCoding.Hex + * @param {CryptoOperationData} data Binary data + * @param {boolean} endean Little/Big Endean, default Little + * @returns {string} Hex decoded string + */ + encode: function (data, endean) { + var s = [], d = new Uint8Array(buffer(data)), n = d.length; + if (endean && ((typeof endean !== 'string') || + (endean.toLowerCase().indexOf('little') < 0))) + for (var i = 0; i < n; i++) { + var j = n - i - 1; + s[j] = (j > 0 && j % 32 === 0 ? '\r\n' : '') + + ('00' + d[i].toString(16)).slice(-2); + } + else + for (var i = 0; i < n; i++) + s[i] = (i > 0 && i % 32 === 0 ? '\r\n' : '') + + ('00' + d[i].toString(16)).slice(-2); + return s.join(''); + } // +}; + +/** + * HEX conversion + * @memberOf GostCoding + * @insnance + * @type GostCoding.Hex + */ +GostCoding.prototype.Hex = Hex; + +/** + * String hex-encoded integer conversion + * + * @class GostCoding.Int16 + */ +var Int16 = {// + /** + * Int16.decode(s) convert hex big insteger s to CryptoOperationData + * + * @memberOf GostCoding.Int16 + * @param {string} s Int16 string + * @returns {CryptoOperationData} Decoded binary data + */ + decode: function (s) { + s = (s || '').replace(/[^\-A-Fa-f0-9]/g, ''); + if (s.length === 0) + s = '0'; + // Signature + var neg = false; + if (s.charAt(0) === '-') { + neg = true; + s = s.substring(1); + } + // Align 2 chars + while (s.charAt(0) === '0' && s.length > 1) + s = s.substring(1); + s = (s.length % 2 > 0 ? '0' : '') + s; + // Padding for singanuture + // '800000' - 'ffffff' - for positive + // '800001' - 'ffffff' - for negative + if ((!neg && !/^[0-7]/.test(s)) || + (neg && !/^[0-7]|8[0]+$/.test(s))) + s = '00' + s; + // Convert hex + var n = s.length / 2, r = new Uint8Array(n), t = 0; + for (var i = n - 1; i >= 0; --i) { + var c = parseInt(s.substr(i * 2, 2), 16); + if (neg && (c + t > 0)) { + c = 256 - c - t; + t = 1; + } + r[i] = c; + } + return r.buffer; + }, + /** + * Int16.encode(data) convert CryptoOperationData data to big integer hex string + * + * @memberOf GostCoding.Int16 + * @param {CryptoOperationData} data Binary data + * @returns {string} Int16 encoded string + */ + encode: function (data) { + var d = new Uint8Array(buffer(data)), n = d.length; + if (d.length === 0) + return '0x00'; + var s = [], neg = d[0] > 0x7f, t = 0; + for (var i = n - 1; i >= 0; --i) { + var v = d[i]; + if (neg && (v + t > 0)) { + v = 256 - v - t; + t = 1; + } + s[i] = ('00' + v.toString(16)).slice(-2); + } + s = s.join(''); + while (s.charAt(0) === '0') + s = s.substring(1); + return (neg ? '-' : '') + '0x' + s; + } // +}; + +/** + * String hex-encoded integer conversion + * @memberOf GostCoding + * @insnance + * @type GostCoding.Int16 + */ +GostCoding.prototype.Int16 = Int16; + +/** + * BER, DER, CER conversion + * + * @class GostCoding.BER + */ +var BER = (function () { // + + // Predefenition block + function encodeBER(source, format, onlyContent) { + // Correct primitive type + var object = source.object; + if (object === undefined) + object = source; + + // Determinate tagClass + var tagClass = source.tagClass = source.tagClass || 0; // Universial default + + // Determinate tagNumber. Use only for Universal class + if (tagClass === 0) { + var tagNumber = source.tagNumber; + if (typeof tagNumber === 'undefined') { + if (typeof object === 'string') { + if (object === '') // NULL + tagNumber = 0x05; + else if (/^\-?0x[0-9a-fA-F]+$/.test(object)) // INTEGER + tagNumber = 0x02; + else if (/^(\d+\.)+\d+$/.test(object)) // OID + tagNumber = 0x06; + else if (/^[01]+$/.test(object)) // BIT STRING + tagNumber = 0x03; + else if (/^(true|false)$/.test(object)) // BOOLEAN + tagNumber = 0x01; + else if (/^[0-9a-fA-F]+$/.test(object)) // OCTET STRING + tagNumber = 0x04; + else + tagNumber = 0x13; // Printable string (later can be changed to UTF8String) + } else if (typeof object === 'number') { // INTEGER + tagNumber = 0x02; + } else if (typeof object === 'boolean') { // BOOLEAN + tagNumber = 0x01; + } else if (object instanceof Array) { // SEQUENCE + tagNumber = 0x10; + } else if (object instanceof Date) { // GeneralizedTime + tagNumber = 0x18; + } else if (object instanceof CryptoOperationData || (object && object.buffer instanceof CryptoOperationData)) { + tagNumber = 0x04; + } else + throw new DataError('Unrecognized type for ' + object); + } + } + + // Determinate constructed + var tagConstructed = source.tagConstructed; + if (typeof tagConstructed === 'undefined') + tagConstructed = source.tagConstructed = object instanceof Array; + + // Create content + var content; + if (object instanceof CryptoOperationData || (object && object.buffer instanceof CryptoOperationData)) { // Direct + content = new Uint8Array(buffer(object)); + if (tagNumber === 0x03) { // BITSTRING + // Set unused bits + var a = new Uint8Array(buffer(content)); + content = new Uint8Array(a.length + 1); + content[0] = 0; // No unused bits + content.set(a, 1); + } + } else if (tagConstructed) { // Sub items coding + if (object instanceof Array) { + var bytelen = 0, ba = [], offset = 0; + for (var i = 0, n = object.length; i < n; i++) { + ba[i] = encodeBER(object[i], format); + bytelen += ba[i].length; + } + if (tagNumber === 0x11) + ba.sort(function (a, b) { // Sort order for SET components + for (var i = 0, n = Math.min(a.length, b.length); i < n; i++) { + var r = a[i] - b[i]; + if (r !== 0) + return r; + } + return a.length - b.length; + }); + if (format === 'CER') { // final for CER 00 00 + ba[n] = new Uint8Array(2); + bytelen += 2; + } + content = new Uint8Array(bytelen); + for (var i = 0, n = ba.length; i < n; i++) { + content.set(ba[i], offset); + offset = offset + ba[i].length; + } + } else + throw new DataError('Constracted block can\'t be primitive'); + } else { + switch (tagNumber) { + // 0x00: // EOC + case 0x01: // BOOLEAN + content = new Uint8Array(1); + content[0] = object ? 0xff : 0; + break; + case 0x02: // INTEGER + case 0x0a: // ENUMIRATED + content = Int16.decode( + typeof object === 'number' ? object.toString(16) : object); + break; + case 0x03: // BIT STRING + if (typeof object === 'string') { + var unusedBits = 7 - (object.length + 7) % 8; + var n = Math.ceil(object.length / 8); + content = new Uint8Array(n + 1); + content[0] = unusedBits; + for (var i = 0; i < n; i++) { + var c = 0; + for (var j = 0; j < 8; j++) { + var k = i * 8 + j; + c = (c << 1) + (k < object.length ? (object.charAt(k) === '1' ? 1 : 0) : 0); + } + content[i + 1] = c; + } + } + break; + case 0x04: + content = Hex.decode( + typeof object === 'number' ? object.toString(16) : object); + break; + // case 0x05: // NULL + case 0x06: // OBJECT IDENTIFIER + var a = object.match(/\d+/g), r = []; + for (var i = 1; i < a.length; i++) { + var n = +a[i], r1 = []; + if (i === 1) + n = n + a[0] * 40; + do { + r1.push(n & 0x7F); + n = n >>> 7; + } while (n); + // reverse order + for (j = r1.length - 1; j >= 0; --j) + r.push(r1[j] + (j === 0 ? 0x00 : 0x80)); + } + content = new Uint8Array(r); + break; + // case 0x07: // ObjectDescriptor + // case 0x08: // EXTERNAL + // case 0x09: // REAL + // case 0x0A: // ENUMERATED + // case 0x0B: // EMBEDDED PDV + case 0x0C: // UTF8String + content = Chars.decode(object, 'utf8'); + break; + // case 0x10: // SEQUENCE + // case 0x11: // SET + case 0x12: // NumericString + case 0x16: // IA5String // ASCII + case 0x13: // PrintableString // ASCII subset + case 0x14: // TeletexString // aka T61String + case 0x15: // VideotexString + case 0x19: // GraphicString + case 0x1A: // VisibleString // ASCII subset + case 0x1B: // GeneralString + // Reflect on character encoding + for (var i = 0, n = object.length; i < n; i++) + if (object.charCodeAt(i) > 255) + tagNumber = 0x0C; + if (tagNumber === 0x0C) + content = Chars.decode(object, 'utf8'); + else + content = Chars.decode(object, 'ascii'); + break; + case 0x17: // UTCTime + case 0x18: // GeneralizedTime + var result = object.original; + if (!result) { + var date = new Date(object); + date.setMinutes(date.getMinutes() + date.getTimezoneOffset()); // to UTC + var ms = tagNumber === 0x18 ? date.getMilliseconds().toString() : ''; // Milliseconds, remove trailing zeros + while (ms.length > 0 && ms.charAt(ms.length - 1) === '0') + ms = ms.substring(0, ms.length - 1); + if (ms.length > 0) + ms = '.' + ms; + result = (tagNumber === 0x17 ? date.getYear().toString().slice(-2) : date.getFullYear().toString()) + + ('00' + (date.getMonth() + 1)).slice(-2) + + ('00' + date.getDate()).slice(-2) + + ('00' + date.getHours()).slice(-2) + + ('00' + date.getMinutes()).slice(-2) + + ('00' + date.getSeconds()).slice(-2) + ms + 'Z'; + } + content = Chars.decode(result, 'ascii'); + break; + case 0x1C: // UniversalString + content = Chars.decode(object, 'utf32'); + break; + case 0x1E: // BMPString + content = Chars.decode(object, 'utf16'); + break; + } + } + + if (!content) + content = new Uint8Array(0); + if (content instanceof CryptoOperationData) + content = new Uint8Array(content); + + if (!tagConstructed && format === 'CER') { + // Encoding CER-form for string types + var k; + switch (tagNumber) { + case 0x03: // BIT_STRING + k = 1; // ingnore unused bit for bit string + case 0x04: // OCTET_STRING + case 0x0C: // UTF8String + case 0x12: // NumericString + case 0x13: // PrintableString + case 0x14: // TeletexString + case 0x15: // VideotexString + case 0x16: // IA5String + case 0x19: // GraphicString + case 0x1A: // VisibleString + case 0x1B: // GeneralString + case 0x1C: // UniversalString + case 0x1E: // BMPString + k = k || 0; + // Split content on 1000 octet len parts + var size = 1000; + var bytelen = 0, ba = [], offset = 0; + for (var i = k, n = content.length; i < n; i += size - k) { + ba[i] = encodeBER({ + object: new Unit8Array(content.buffer, i, Math.min(size - k, n - i)), + tagNumber: tagNumber, + tagClass: 0, + tagConstructed: false + }, format); + bytelen += ba[i].length; + } + ba[n] = new Uint8Array(2); // final for CER 00 00 + bytelen += 2; + content = new Uint8Array(bytelen); + for (var i = 0, n = ba.length; i < n; i++) { + content.set(ba[i], offset); + offset = offset + ba[i].length; + } + } + } + + // Restore tagNumber for all classes + if (tagClass === 0) + source.tagNumber = tagNumber; + else + source.tagNumber = tagNumber = source.tagNumber || 0; + source.content = content; + + if (onlyContent) + return content; + + // Create header + // tagNumber + var ha = [], first = tagClass === 3 ? 0xC0 : tagClass === 2 ? 0x80 : + tagClass === 1 ? 0x40 : 0x00; + if (tagConstructed) + first |= 0x20; + if (tagNumber < 0x1F) { + first |= tagNumber & 0x1F; + ha.push(first); + } else { + first |= 0x1F; + ha.push(first); + var n = tagNumber, ha1 = []; + do { + ha1.push(n & 0x7F); + n = n >>> 7; + } while (n) + // reverse order + for (var j = ha1.length - 1; j >= 0; --j) + ha.push(ha1[j] + (j === 0 ? 0x00 : 0x80)); + } + // Length + if (tagConstructed && format === 'CER') { + ha.push(0x80); + } else { + var len = content.length; + if (len > 0x7F) { + var l2 = len, ha2 = []; + do { + ha2.push(l2 & 0xff); + l2 = l2 >>> 8; + } while (l2); + ha.push(ha2.length + 0x80); // reverse order + for (var j = ha2.length - 1; j >= 0; --j) + ha.push(ha2[j]); + } else { + // simple len + ha.push(len); + } + } + var header = source.header = new Uint8Array(ha); + + // Result - complete buffer + var block = new Uint8Array(header.length + content.length); + block.set(header, 0); + block.set(content, header.length); + return block; + } + + function decodeBER(source, offset) { + + // start pos + var pos = offset || 0, start = pos; + var tagNumber, tagClass, tagConstructed, + content, header, buffer, sub, len; + + if (source.object) { + // Ready from source + tagNumber = source.tagNumber; + tagClass = source.tagClass; + tagConstructed = source.tagConstructed; + content = source.content; + header = source.header; + buffer = source.object instanceof CryptoOperationData ? + new Uint8Array(source.object) : null; + sub = source.object instanceof Array ? source.object : null; + len = buffer && buffer.length || null; + } else { + // Decode header + var d = source; + + // Read tag + var buf = d[pos++]; + tagNumber = buf & 0x1f; + tagClass = buf >> 6; + tagConstructed = (buf & 0x20) !== 0; + if (tagNumber === 0x1f) { // long tag + tagNumber = 0; + do { + if (tagNumber > 0x1fffffffffff80) + throw new DataError('Convertor not supported tag number more then (2^53 - 1) at position ' + offset); + buf = d[pos++]; + tagNumber = (tagNumber << 7) + (buf & 0x7f); + } while (buf & 0x80); + } + + // Read len + buf = d[pos++]; + len = buf & 0x7f; + if (len !== buf) { + if (len > 6) // no reason to use Int10, as it would be a huge buffer anyways + throw new DataError('Length over 48 bits not supported at position ' + offset); + if (len === 0) + len = null; // undefined + else { + buf = 0; + for (var i = 0; i < len; ++i) + buf = (buf << 8) + d[pos++]; + len = buf; + } + } + + start = pos; + sub = null; + + if (tagConstructed) { + // must have valid content + sub = []; + if (len !== null) { + // definite length + var end = start + len; + while (pos < end) { + var s = decodeBER(d, pos); + sub.push(s); + pos += s.header.length + s.content.length; + } + if (pos !== end) + throw new DataError('Content size is not correct for container starting at offset ' + start); + } else { + // undefined length + try { + for (; ; ) { + var s = decodeBER(d, pos); + pos += s.header.length + s.content.length; + if (s.tagClass === 0x00 && s.tagNumber === 0x00) + break; + sub.push(s); + } + len = pos - start; + } catch (e) { + throw new DataError('Exception ' + e + ' while decoding undefined length content at offset ' + start); + } + } + } + + // Header and content + header = new Uint8Array(d.buffer, offset, start - offset); + content = new Uint8Array(d.buffer, start, len); + buffer = content; + } + + // Constructed types - check for string concationation + if (sub !== null && tagClass === 0) { + var k; + switch (tagNumber) { + case 0x03: // BIT_STRING + k = 1; // ingnore unused bit for bit string + case 0x04: // OCTET_STRING + case 0x0C: // UTF8String + case 0x12: // NumericString + case 0x13: // PrintableString + case 0x14: // TeletexString + case 0x15: // VideotexString + case 0x16: // IA5String + case 0x19: // GraphicString + case 0x1A: // VisibleString + case 0x1B: // GeneralString + case 0x1C: // UniversalString + case 0x1E: // BMPString + k = k || 0; + // Concatination + if (sub.length === 0) + throw new DataError('No constructed encoding content of string type at offset ' + start); + len = k; + for (var i = 0, n = sub.length; i < n; i++) { + var s = sub[i]; + if (s.tagClass !== tagClass || s.tagNumber !== tagNumber || s.tagConstructed) + throw new DataError('Invalid constructed encoding of string type at offset ' + start); + len += s.content.length - k; + } + buffer = new Uint8Array(len); + for (var i = 0, n = sub.length, j = k; i < n; i++) { + var s = sub[i]; + if (k > 0) + buffer.set(s.content.subarray(1), j); + else + buffer.set(s.content, j); + j += s.content.length - k; + } + tagConstructed = false; // follow not required + sub = null; + break; + } + } + // Primitive types + var object = ''; + if (sub === null) { + if (len === null) + throw new DataError('Invalid tag with undefined length at offset ' + start); + + if (tagClass === 0) { + switch (tagNumber) { + case 0x01: // BOOLEAN + object = buffer[0] !== 0; + break; + case 0x02: // INTEGER + case 0x0a: // ENUMIRATED + if (len > 6) { + object = Int16.encode(buffer); + } else { + var v = buffer[0]; + if (buffer[0] > 0x7f) + v = v - 256; + for (var i = 1; i < len; i++) + v = v * 256 + buffer[i]; + object = v; + } + break; + case 0x03: // BIT_STRING + if (len > 5) { // Content buffer + object = new Uint8Array(buffer.subarray(1)).buffer; + } else { // Max bit mask only for 32 bit + var unusedBit = buffer[0], + skip = unusedBit, s = []; + for (var i = len - 1; i >= 1; --i) { + var b = buffer[i]; + for (var j = skip; j < 8; ++j) + s.push((b >> j) & 1 ? '1' : '0'); + skip = 0; + } + object = s.reverse().join(''); + } + break; + case 0x04: // OCTET_STRING + object = new Uint8Array(buffer).buffer; + break; + // case 0x05: // NULL + case 0x06: // OBJECT_IDENTIFIER + var s = '', + n = 0, + bits = 0; + for (var i = 0; i < len; ++i) { + var v = buffer[i]; + n = (n << 7) + (v & 0x7F); + bits += 7; + if (!(v & 0x80)) { // finished + if (s === '') { + var m = n < 80 ? n < 40 ? 0 : 1 : 2; + s = m + "." + (n - m * 40); + } else + s += "." + n.toString(); + n = 0; + bits = 0; + } + } + if (bits > 0) + throw new DataError('Incompleted OID at offset ' + start); + object = s; + break; + //case 0x07: // ObjectDescriptor + //case 0x08: // EXTERNAL + //case 0x09: // REAL + //case 0x0A: // ENUMERATED + //case 0x0B: // EMBEDDED_PDV + case 0x10: // SEQUENCE + case 0x11: // SET + object = []; + break; + case 0x0C: // UTF8String + object = Chars.encode(buffer, 'utf8'); + break; + case 0x12: // NumericString + case 0x13: // PrintableString + case 0x14: // TeletexString + case 0x15: // VideotexString + case 0x16: // IA5String + case 0x19: // GraphicString + case 0x1A: // VisibleString + case 0x1B: // GeneralString + object = Chars.encode(buffer, 'ascii'); + break; + case 0x1C: // UniversalString + object = Chars.encode(buffer, 'utf32'); + break; + case 0x1E: // BMPString + object = Chars.encode(buffer, 'utf16'); + break; + case 0x17: // UTCTime + case 0x18: // GeneralizedTime + var shortYear = tagNumber === 0x17; + var s = Chars.encode(buffer, 'ascii'), + m = (shortYear ? + /^(\d\d)(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])([01]\d|2[0-3])(?:([0-5]\d)(?:([0-5]\d)(?:[.,](\d{1,3}))?)?)?(Z|[-+](?:[0]\d|1[0-2])([0-5]\d)?)?$/ : + /^(\d\d\d\d)(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])([01]\d|2[0-3])(?:([0-5]\d)(?:([0-5]\d)(?:[.,](\d{1,3}))?)?)?(Z|[-+](?:[0]\d|1[0-2])([0-5]\d)?)?$/).exec(s); + if (!m) + throw new DataError('Unrecognized time format "' + s + '" at offset ' + start); + if (shortYear) { + // Where YY is greater than or equal to 50, the year SHALL be interpreted as 19YY; and + // Where YY is less than 50, the year SHALL be interpreted as 20YY + m[1] = +m[1]; + m[1] += (m[1] < 50) ? 2000 : 1900; + } + var dt = new Date(m[1], +m[2] - 1, +m[3], +(m[4] || '0'), +(m[5] || '0'), +(m[6] || '0'), +(m[7] || '0')), + tz = dt.getTimezoneOffset(); + if (m[8] || tagNumber === 0x17) { + if (m[8].toUpperCase() !== 'Z' && m[9]) { + tz = tz + parseInt(m[9]); + } + dt.setMinutes(dt.getMinutes() - tz); + } + dt.original = s; + object = dt; + break; + } + } else // OCTET_STRING + object = new Uint8Array(buffer).buffer; + } else + object = sub; + + // result + return { + tagConstructed: tagConstructed, + tagClass: tagClass, + tagNumber: tagNumber, + header: header, + content: content, + object: object + }; + } + + return { + /** + * BER.decode(object, format) convert javascript object to ASN.1 format CryptoOperationData

+ * If object has members tagNumber, tagClass and tagConstructed + * it is clear define encoding rules. Else method use defaul rules: + *
    + *
  • Empty string or null - NULL
  • + *
  • String starts with '0x' and has 0-9 and a-f characters - INTEGER
  • + *
  • String like d.d.d.d (d - set of digits) - OBJECT IDENTIFIER
  • + *
  • String with characters 0 and 1 - BIT STRING
  • + *
  • Strings 'true' or 'false' - BOOLEAN
  • + *
  • String has only 0-9 and a-f characters - OCTET STRING
  • + *
  • String has only characters with code 0-255 - PrintableString
  • + *
  • Other strings - UTF8String
  • + *
  • Number - INTEGER
  • + *
  • Date - GeneralizedTime
  • + *
  • Boolean - SEQUENCE
  • + *
  • CryptoOperationData - OCTET STRING
  • + *
+ * SEQUENCE or SET arrays recursively encoded for each item.
+ * OCTET STRING and BIT STRING can presents as array with one item. + * It means encapsulates encoding for child element.
+ * + * If CONTEXT or APPLICATION classes item presents as array with one + * item we use EXPLICIT encoding for element, else IMPLICIT encoding.
+ * + * @memberOf GostCoding.BER + * @param {Object} object Object to encoding + * @param {string} format Encoding rule: 'DER' or 'CER', default 'DER' + * @param {boolean} onlyContent Encode content only, without header + * @returns {CryptoOperationData} BER encoded data + */ + encode: function (object, format, onlyContent) { + return encodeBER(object, format, onlyContent).buffer; + }, + /** + * BER.encode(data) convert ASN.1 format CryptoOperationData data to javascript object

+ * + * Conversion rules to javascript object: + *
    + *
  • BOOLEAN - Boolean object
  • + *
  • INTEGER, ENUMIRATED - Integer object if len <= 6 (48 bits) else Int16 encoded string
  • + *
  • BIT STRING - Integer object if len <= 5 (w/o unsedBit octet - 32 bits) else String like '10111100' or Array with one item in case of incapsulates encoding
  • + *
  • OCTET STRING - Hex encoded string or Array with one item in case of incapsulates encoding
  • + *
  • OBJECT IDENTIFIER - String with object identifier
  • + *
  • SEQUENCE, SET - Array of encoded items
  • + *
  • UTF8String, NumericString, PrintableString, TeletexString, VideotexString, + * IA5String, GraphicString, VisibleString, GeneralString, UniversalString, + * BMPString - encoded String
  • + *
  • UTCTime, GeneralizedTime - Date
  • + *
+ * @memberOf GostCoding.BER + * @param {(CryptoOperationData|GostCoding.BER)} data Binary data to decode + * @returns {Object} Javascript object with result of decoding + */ + decode: function (data) { + return decodeBER(data.object ? data : new Uint8Array(buffer(data)), 0); + } + }; //
+})(); + +/** + * BER, DER, CER conversion + * @memberOf GostCoding + * @insnance + * @type GostCoding.BER + */ +GostCoding.prototype.BER = BER; + +/** + * PEM conversion + * @class GostCoding.PEM + */ +var PEM = {// + /** + * PEM.encode(data, name) encode CryptoOperationData to PEM format with name label + * + * @memberOf GostCoding.PEM + * @param {(Object|CryptoOperationData)} data Java script object or BER-encoded binary data + * @param {string} name Name of PEM object: 'certificate', 'private key' etc. + * @returns {string} Encoded object + */ + encode: function (data, name) { + return (name ? '-----BEGIN ' + name.toUpperCase() + '-----\r\n' : '') + + Base64.encode(data instanceof CryptoOperationData ? data : BER.encode(data)) + + (name ? '\r\n-----END ' + name.toUpperCase() + '-----' : ''); + }, + /** + * PEM.decode(s, name, deep) decode PEM format s labeled name to CryptoOperationData or javascript object in according to deep parameter + * + * @memberOf GostCoding.PEM + * @param {string} s PEM encoded string + * @param {string} name Name of PEM object: 'certificate', 'private key' etc. + * @param {boolean} deep If true method do BER-decoding, else only BASE64 decoding + * @param {integer} index Index of decoded value + * @returns {(Object|CryptoOperationData)} Decoded javascript object if deep=true, else CryptoOperationData for father BER decoding + */ + decode: function (s, name, deep, index) { + // Try clear base64 + var re1 = /([A-Za-z0-9\+\/\s\=]+)/g, + valid = re1.exec(s); + if (valid[1].length !== s.length) + valid = false; + if (!valid && name) { + // Try with the name + var re2 = new RegExp( + '-----\\s?BEGIN ' + name.toUpperCase() + + '-----([A-Za-z0-9\\+\\/\\s\\=]+)-----\\s?END ' + + name.toUpperCase() + '-----', 'g'); + valid = re2.exec(s); + } + if (!valid) { + // Try with some name + var re3 = new RegExp( + '-----\\s?BEGIN [A-Z0-9\\s]+' + + '-----([A-Za-z0-9\\+\\/\\s\\=]+)-----\\s?END ' + + '[A-Z0-9\\s]+-----', 'g'); + valid = re3.exec(s); + } + var r = valid && valid[1 + (index || 0)]; + if (!r) + throw new DataError('Not valid PEM format'); + var out = Base64.decode(r); + if (deep) + out = BER.decode(out); + return out; + } // +}; + +/** + * PEM conversion + * @memberOf GostCoding + * @insnance + * @type GostCoding.PEM + */ +GostCoding.prototype.PEM = PEM; + +if (gostCrypto) + /** + * Coding algorithms: Base64, Hex, Int16, Chars, BER and PEM + * + * @memberOf gostCrypto + * @type GostCoding + */ + gostCrypto.coding = new GostCoding(); + +export default GostCoding; diff --git a/plugins/srktoolbox/src/core/vendor/gost/gostCrypto.mjs b/plugins/srktoolbox/src/core/vendor/gost/gostCrypto.mjs new file mode 100644 index 00000000..77132f6c --- /dev/null +++ b/plugins/srktoolbox/src/core/vendor/gost/gostCrypto.mjs @@ -0,0 +1,1653 @@ +/** + * Implementation Web Crypto interfaces for GOST algorithms + * 1.76 + * 2014-2016, Rudolf Nickolaev. All rights reserved. + * + * Exported for CyberChef by mshwed [m@ttshwed.com] + */ + +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 HOLDER 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. + * + */ + +import GostRandom from './gostRandom.mjs'; +import gostEngine from './gostEngine.mjs'; + +import crypto from 'crypto' + +/* +* Algorithm normalization +* +*/ // + +var root = {}; +root.gostEngine = gostEngine; + +var rootCrypto = crypto + +var SyntaxError = Error, + DataError = Error, + NotSupportedError = Error, + OperationError = Error, + InvalidStateError = Error, + InvalidAccessError = Error; + +// Normalize algorithm +function normalize(algorithm, method) { + if (typeof algorithm === 'string' || algorithm instanceof String) + algorithm = {name: algorithm}; + var name = algorithm.name; + if (!name) + throw new SyntaxError('Algorithm name not defined'); + // Extract algorithm modes from name + var modes = name.split('/'), modes = modes[0].split('-').concat(modes.slice(1)); + // Normalize the name with default modes + var na = {}; + name = modes[0].replace(/[\.\s]/g, ''); + modes = modes.slice(1); + if (name.indexOf('28147') >= 0) { + na = { + name: 'GOST 28147', + version: 1989, + mode: (algorithm.mode || (// ES, MAC, KW + (method === 'sign' || method === 'verify') ? 'MAC' : + (method === 'wrapKey' || method === 'unwrapKey') ? 'KW' : 'ES')).toUpperCase(), + length: algorithm.length || 64 + }; + } else if (name.indexOf('3412') >= 0) { + na = { + name: 'GOST R 34.12', + version: 2015, + mode: (algorithm.mode || (// ES, MAC, KW + (method === 'sign' || method === 'verify') ? 'MAC' : + (method === 'wrapKey' || method === 'unwrapKey') ? 'KW' : 'ES')).toUpperCase(), + length: algorithm.length || 64 // 128 + }; + } else if (name.indexOf('3411') >= 0) { + na = { + name: 'GOST R 34.11', + version: 2012, // 1994 + mode: (algorithm.mode || (// HASH, KDF, HMAC, PBKDF2, PFXKDF, CPKDF + (method === 'deriveKey' || method === 'deriveBits') ? 'KDF' : + (method === 'sign' || method === 'verify') ? 'HMAC' : 'HASH')).toUpperCase(), + length: algorithm.length || 256 // 512 + }; + } else if (name.indexOf('3410') >= 0) { + na = { + name: 'GOST R 34.10', + version: 2012, // 1994, 2001 + mode: (algorithm.mode || (// SIGN, DH, MASK + (method === 'deriveKey' || method === 'deriveBits') ? 'DH' : 'SIGN')).toUpperCase(), + length: algorithm.length || 256 // 512 + }; + } else if (name.indexOf('SHA') >= 0) { + na = { + name: 'SHA', + version: (algorithm.length || 160) === 160 ? 1 : 2, // 1, 2 + mode: (algorithm.mode || (// HASH, KDF, HMAC, PBKDF2, PFXKDF + (method === 'deriveKey' || method === 'deriveBits') ? 'KDF' : + (method === 'sign' || method === 'verify') ? 'HMAC' : 'HASH')).toUpperCase(), + length: algorithm.length || 160 + }; + } else if (name.indexOf('RC2') >= 0) { + na = { + name: 'RC2', + version: 1, + mode: (algorithm.mode || (// ES, MAC, KW + (method === 'sign' || method === 'verify') ? 'MAC' : + (method === 'wrapKey' || method === 'unwrapKey') ? 'KW' : 'ES')).toUpperCase(), + length: algorithm.length || 32 // 1 - 1024 + }; + } else if (name.indexOf('PBKDF2') >= 0) { + na = normalize(algorithm.hash, 'digest'); + na.mode = 'PBKDF2'; + } else if (name.indexOf('PFXKDF') >= 0) { + na = normalize(algorithm.hash, 'digest'); + na.mode = 'PFXKDF'; + } else if (name.indexOf('CPKDF') >= 0) { + na = normalize(algorithm.hash, 'digest'); + na.mode = 'CPKDF'; + } else if (name.indexOf('HMAC') >= 0) { + na = normalize(algorithm.hash, 'digest'); + na.mode = 'HMAC'; + } else + throw new NotSupportedError('Algorithm not supported'); + + // Compile modes + modes.forEach(function (mode) { + mode = mode.toUpperCase(); + if (/^[0-9]+$/.test(mode)) { + if ((['8', '16', '32'].indexOf(mode) >= 0) || (na.length === '128' && mode === '64')) { // Shift bits + if (na.mode === 'ES') + na.shiftBits = parseInt(mode); + else if (na.mode === 'MAC') + na.macLength = parseInt(mode); + else + throw new NotSupportedError('Algorithm ' + na.name + ' mode ' + mode + ' not supported'); + } else if (['89', '94', '01', '12', '15', '1989', '1994', '2001', '2012', '2015'].indexOf(mode) >= 0) { // GOST Year + var version = parseInt(mode); + version = version < 1900 ? (version < 80 ? 2000 + version : 1900 + version) : version; + na.version = version; + } else if (['1'].indexOf(mode) >= 0 && na.name === 'SHA') { // SHA-1 + na.version = 1; + na.length = 160; + } else if (['256', '384', '512'].indexOf(mode) >= 0 && na.name === 'SHA') { // SHA-2 + na.version = 2; + na.length = parseInt(mode); + } else if (['40', '128'].indexOf(mode) >= 0 && na.name === 'RC2') { // RC2 + na.version = 1; + na.length = parseInt(mode); // key size + } else if (['64', '128', '256', '512'].indexOf(mode) >= 0) // block size + na.length = parseInt(mode); + else if (['1000', '2000'].indexOf(mode) >= 0) // Iterations + na.iterations = parseInt(mode); + // Named Paramsets + } else if (['E-TEST', 'E-A', 'E-B', 'E-C', 'E-D', 'E-SC', 'E-Z', 'D-TEST', 'D-A', 'D-SC'].indexOf(mode) >= 0) { + na.sBox = mode; + } else if (['S-TEST', 'S-A', 'S-B', 'S-C', 'S-D', 'X-A', 'X-B', 'X-C'].indexOf(mode) >= 0) { + na.namedParam = mode; + } else if (['S-256-TEST', 'S-256-A', 'S-256-B', 'S-256-C', 'P-256', 'T-512-TEST', 'T-512-A', + 'T-512-B', 'X-256-A', 'X-256-B', 'T-256-TEST', 'T-256-A', 'T-256-B', 'S-256-B', 'T-256-C', 'S-256-C'].indexOf(mode) >= 0) { + na.namedCurve = mode; + } else if (['SC', 'CP', 'VN'].indexOf(mode) >= 0) { + na.procreator = mode; + + // Encription GOST 28147 or GOST R 34.12 + } else if (na.name === 'GOST 28147' || na.name === 'GOST R 34.12' || na.name === 'RC2') { + if (['ES', 'MAC', 'KW', 'MASK'].indexOf(mode) >= 0) { + na.mode = mode; + } else if (['ECB', 'CFB', 'OFB', 'CTR', 'CBC'].indexOf(mode) >= 0) { + na.mode = 'ES'; + na.block = mode; + } else if (['CPKW', 'NOKW', 'SCKW'].indexOf(mode) >= 0) { + na.mode = 'KW'; + na.keyWrapping = mode.replace('KW', ''); + } else if (['ZEROPADDING', 'PKCS5PADDING', 'NOPADDING', 'RANDOMPADDING', 'BITPADDING'].indexOf(mode) >= 0) { + na.padding = mode.replace('PADDING', ''); + } else if (['NOKM', 'CPKM'].indexOf(mode) >= 0) { + na.keyMeshing = mode.replace('KM', ''); + } else + throw new NotSupportedError('Algorithm ' + na.name + ' mode ' + mode + ' not supported'); + + // Digesting GOST 34.11 + } else if (na.name === 'GOST R 34.11' || na.name === 'SHA') { + if (['HASH', 'KDF', 'HMAC', 'PBKDF2', 'PFXKDF', 'CPKDF'].indexOf(mode) >= 0) + na.mode = mode; + else + throw new NotSupportedError('Algorithm ' + na.name + ' mode ' + mode + ' not supported'); + + // Signing GOST 34.10 + } else if (na.name === 'GOST R 34.10') { + var hash = mode.replace(/[\.\s]/g, ''); + if (hash.indexOf('GOST') >= 0 && hash.indexOf('3411') >= 0) + na.hash = mode; + else if (['SIGN', 'DH', 'MASK'].indexOf(mode)) + na.mode = mode; + else + throw new NotSupportedError('Algorithm ' + na.name + ' mode ' + mode + ' not supported'); + } + }); + + // Procreator + na.procreator = algorithm.procreator || na.procreator || 'CP'; + + // Key size + switch (na.name) { + case 'GOST R 34.10': + na.keySize = na.length / (na.version === 1994 ? 4 : 8); + break; + case 'GOST R 34.11': + na.keySize = 32; + break; + case 'GOST 28147': + case 'GOST R 34.12': + na.keySize = 32; + break; + case 'RC2': + na.keySize = Math.ceil(na.length / 8); + break; + case 'SHA': + na.keySize = na.length / 8; + break; + } + + // Encrypt additional modes + if (na.mode === 'ES') { + if (algorithm.block) + na.block = algorithm.block; // ECB, CFB, OFB, CTR, CBC + if (na.block) + na.block = na.block.toUpperCase(); + if (algorithm.padding) + na.padding = algorithm.padding; // NO, ZERO, PKCS5, RANDOM, BIT + if (na.padding) + na.padding = na.padding.toUpperCase(); + if (algorithm.shiftBits) + na.shiftBits = algorithm.shiftBits; // 8, 16, 32, 64 + if (algorithm.keyMeshing) + na.keyMeshing = algorithm.keyMeshing; // NO, CP + if (na.keyMeshing) + na.keyMeshing = na.keyMeshing.toUpperCase(); + // Default values + if (method !== 'importKey' && method !== 'generateKey') { + na.block = na.block || 'ECB'; + na.padding = na.padding || (na.block === 'CBC' || na.block === 'ECB' ? 'ZERO' : 'NO'); + if (na.block === 'CFB' || na.block === 'OFB') + na.shiftBits = na.shiftBits || na.length; + na.keyMeshing = na.keyMeshing || 'NO'; + } + } + if (na.mode === 'KW') { + if (algorithm.keyWrapping) + na.keyWrapping = algorithm.keyWrapping; // NO, CP, SC + if (na.keyWrapping) + na.keyWrapping = na.keyWrapping.toUpperCase(); + if (method !== 'importKey' && method !== 'generateKey') + na.keyWrapping = na.keyWrapping || 'NO'; + } + + // Paramsets + ['sBox', 'namedParam', 'namedCurve', 'curve', 'param', 'modulusLength'].forEach(function (name) { + algorithm[name] && (na[name] = algorithm[name]); + }); + // Default values + if (method !== 'importKey' && method !== 'generateKey') { + if (na.name === 'GOST 28147') { + na.sBox = na.sBox || (na.procreator === 'SC' ? 'E-SC' : 'E-A'); // 'E-A', 'E-B', 'E-C', 'E-D', 'E-SC' + } else if (na.name === 'GOST R 34.12' && na.length === 64) { + na.sBox = 'E-Z'; + } else if (na.name === 'GOST R 34.11' && na.version === 1994) { + na.sBox = na.sBox || (na.procreator === 'SC' ? 'D-SC' : 'D-A'); // 'D-SC' + } else if (na.name === 'GOST R 34.10' && na.version === 1994) { + na.namedParam = na.namedParam || (na.mode === 'DH' ? 'X-A' : 'S-A'); // 'S-B', 'S-C', 'S-D', 'X-B', 'X-C' + } else if (na.name === 'GOST R 34.10' && na.version === 2001) { + na.namedCurve = na.namedCurve || (na.length === 256 ? + na.procreator === 'SC' ? 'P-256' : (na.mode === 'DH' ? 'X-256-A' : 'S-256-A') : // 'S-256-B', 'S-256-C', 'X-256-B', 'T-256-A', 'T-256-B', 'T-256-C', 'P-256' + na.mode === 'T-512-A'); // 'T-512-B', 'T-512-C' + } else if (na.name === 'GOST R 34.10' && na.version === 2012) { + na.namedCurve = na.namedCurve || (na.length === 256 ? + na.procreator === 'SC' ? 'P-256' : (na.mode === 'DH' ? 'X-256-A' : 'S-256-A') : // 'S-256-B', 'S-256-C', 'X-256-B', 'T-256-A', 'T-256-B', 'T-256-C', 'P-256' + na.mode === 'T-512-A'); // 'T-512-B', 'T-512-C' + } + } + + // Vectors + switch (na.mode) { + case 'DH': + algorithm.ukm && (na.ukm = algorithm.ukm); + algorithm['public'] && (na['public'] = algorithm['public']); + break; + case 'SIGN': + case 'KW': + algorithm.ukm && (na.ukm = algorithm.ukm); + break; + case 'ES': + case 'MAC': + algorithm.iv && (na.iv = algorithm.iv); + break; + case 'KDF': + algorithm.label && (na.label = algorithm.label); + algorithm.contex && (na.context = algorithm.contex); + break; + case 'PBKDF2': + algorithm.salt && (na.salt = algorithm.salt); + algorithm.iterations && (na.iterations = algorithm.iterations); + algorithm.diversifier && (na.diversifier = algorithm.diversifier); + break; + case 'PFXKDF': + algorithm.salt && (na.salt = algorithm.salt); + algorithm.iterations && (na.iterations = algorithm.iterations); + algorithm.diversifier && (na.diversifier = algorithm.diversifier); + break; + case 'CPKDF': + algorithm.salt && (na.salt = algorithm.salt); + algorithm.iterations && (na.iterations = algorithm.iterations); + break; + } + + // Verification method and modes + if (method && ( + ((na.mode !== 'ES' && na.mode !== 'SIGN' && na.mode !== 'MAC' && + na.mode !== 'HMAC' && na.mode !== 'KW' && na.mode !== 'DH' + && na.mode !== 'MASK') && + (method === 'generateKey')) || + ((na.mode !== 'ES') && + (method === 'encrypt' || method === 'decrypt')) || + ((na.mode !== 'SIGN' && na.mode !== 'MAC' && na.mode !== 'HMAC') && + (method === 'sign' || method === 'verify')) || + ((na.mode !== 'HASH') && + (method === 'digest')) || + ((na.mode !== 'KW' && na.mode !== 'MASK') && + (method === 'wrapKey' || method === 'unwrapKey')) || + ((na.mode !== 'DH' && na.mode !== 'PBKDF2' && na.mode !== 'PFXKDF' && + na.mode !== 'CPKDF' && na.mode !== 'KDF') && + (method === 'deriveKey' || method === 'deriveBits')))) + throw new NotSupportedError('Algorithm mode ' + na.mode + ' not valid for method ' + method); + + // Normalize hash algorithm + algorithm.hash && (na.hash = algorithm.hash); + if (na.hash) { + if ((typeof na.hash === 'string' || na.hash instanceof String) + && na.procreator) + na.hash = na.hash + '/' + na.procreator; + na.hash = normalize(na.hash, 'digest'); + } + + // Algorithm object identirifer + algorithm.id && (na.id = algorithm.id); + + return na; +} + +// Check for possibility use native crypto.subtle +function checkNative(algorithm) { + if (!rootCrypto || !rootCrypto.subtle || !algorithm) + return false; + // Prepare name + var name = (typeof algorithm === 'string' || algorithm instanceof String) ? + name = algorithm : algorithm.name; + if (!name) + return false; + name = name.toUpperCase(); + // Digest algorithm for key derivation + if ((name.indexOf('KDF') >= 0 || name.indexOf('HMAC') >= 0) && algorithm.hash) + return checkNative(algorithm.hash); + // True if no supported names + return name.indexOf('GOST') === -1 && + name.indexOf('SHA-1') === -1 && + name.indexOf('RC2') === -1 && + name.indexOf('?DES') === -1; +} +// + +/* + * Key conversion methods + * + */ // + +// Check key parameter +function checkKey(key, method) { + if (!key.algorithm) + throw new SyntaxError('Key algorithm not defined'); + + if (!key.algorithm.name) + throw new SyntaxError('Key algorithm name not defined'); + + var name = key.algorithm.name, + gostCipher = name === 'GOST 28147' || name === 'GOST R 34.12' || name === 'RC2', + gostDigest = name === 'GOST R 34.11' || name === 'SHA', + gostSign = name === 'GOST R 34.10'; + + if (!gostCipher && !gostSign && !gostDigest) + throw new NotSupportedError('Key algorithm ' + name + ' is unsupproted'); + + if (!key.type) + throw new SyntaxError('Key type not defined'); + + if (((gostCipher || gostDigest) && key.type !== 'secret') || + (gostSign && !(key.type === 'public' || key.type === 'private'))) + throw new DataError('Key type ' + key.type + ' is not valid for algorithm ' + name); + + if (!key.usages || !key.usages.indexOf) + throw new SyntaxError('Key usages not defined'); + + for (var i = 0, n = key.usages.length; i < n; i++) { + var md = key.usages[i]; + if (((md === 'encrypt' || md === 'decrypt') && key.type !== 'secret') || + (md === 'sign' && key.type === 'public') || + (md === 'verify' && key.type === 'private')) + throw new InvalidStateError('Key type ' + key.type + ' is not valid for ' + md); + } + + if (method) + if (key.usages.indexOf(method) === -1) + throw new InvalidAccessError('Key usages is not contain method ' + method); + + if (!key.buffer) + throw new SyntaxError('Key buffer is not defined'); + + var size = key.buffer.byteLength * 8, keySize = 8 * key.algorithm.keySize; + if ((key.type === 'secret' && size !== (keySize || 256) && + (key.usages.indexOf('encrypt') >= 0 || key.usages.indexOf('decrypt') >= 0)) || + (key.type === 'private' && !(size === 256 || size === 512)) || + (key.type === 'public' && !(size === 512 || size === 1024))) + throw new SyntaxError('Key buffer has wrong size ' + size + ' bit'); +} + +// Extract key and enrich cipher algorithm +function extractKey(method, algorithm, key) { + checkKey(key, method); + if (algorithm) { + var params; + switch (algorithm.mode) { + case 'ES': + params = ['sBox', 'keyMeshing', 'padding', 'block']; + break; + case 'SIGN': + params = ['namedCurve', 'namedParam', 'sBox', 'curve', 'param', 'modulusLength']; + break; + case 'MAC': + params = ['sBox']; + break; + case 'KW': + params = ['keyWrapping', 'ukm']; + break; + case 'DH': + params = ['namedCurve', 'namedParam', 'sBox', 'ukm', 'curve', 'param', 'modulusLength']; + break; + case 'KDF': + params = ['context', 'label']; + break; + case 'PBKDF2': + params = ['sBox', 'iterations', 'salt']; + break; + case 'PFXKDF': + params = ['sBox', 'iterations', 'salt', 'diversifier']; + break; + case 'CPKDF': + params = ['sBox', 'salt']; + break; + } + if (params) + params.forEach(function (name) { + key.algorithm[name] && (algorithm[name] = key.algorithm[name]); + }); + } + return key.buffer; +} + +// Make key definition +function convertKey(algorithm, extractable, keyUsages, keyData, keyType) { + var key = { + type: keyType || (algorithm.name === 'GOST R 34.10' ? 'private' : 'secret'), + extractable: extractable || 'false', + algorithm: algorithm, + usages: keyUsages || [], + buffer: keyData + }; + checkKey(key); + return key; +} + +function convertKeyPair(publicAlgorithm, privateAlgorithm, extractable, keyUsages, publicBuffer, privateBuffer) { + + if (!keyUsages || !keyUsages.indexOf) + throw new SyntaxError('Key usages not defined'); + + var publicUsages = keyUsages.filter(function (value) { + return value !== 'sign'; + }); + var privateUsages = keyUsages.filter(function (value) { + return value !== 'verify'; + }); + + return { + publicKey: convertKey(publicAlgorithm, extractable, publicUsages, publicBuffer, 'public'), + privateKey: convertKey(privateAlgorithm, extractable, privateUsages, privateBuffer, 'private') + }; +} + +// Swap bytes in buffer +function swapBytes(src) { + if (src instanceof CryptoOperationData) + src = new Uint8Array(src); + var dst = new Uint8Array(src.length); + for (var i = 0, n = src.length; i < n; i++) + dst[n - i - 1] = src[i]; + return dst.buffer; +} +// + +/** + * Promise stub object (not fulfill specification, only for internal use) + * Class not defined if Promise class already defined in root context

+ * + * The Promise object is used for deferred and asynchronous computations. A Promise is in one of the three states: + *
    + *
  • pending: initial state, not fulfilled or rejected.
  • + *
  • fulfilled: successful operation
  • + *
  • rejected: failed operation.
  • + *
+ * Another term describing the state is settled: the Promise is either fulfilled or rejected, but not pending.

+ * @class Promise + * @global + * @param {function} executor Function object with two arguments resolve and reject. + * The first argument fulfills the promise, the second argument rejects it. + * We can call these functions, once our operation is completed. + */ // +if (!Promise) { + + root.Promise = (function () { + + function mswrap(value) { + if (value && value.oncomplete === null && value.onerror === null) { + return new Promise(function (resolve, reject) { + value.oncomplete = function () { + resolve(value.result); + }; + value.onerror = function () { + reject(new OperationError(value.toString())); + }; + }); + } else + return value; + } + + function Promise(executor) { + + var state = 'pending', result, + resolveQueue = [], rejectQueue = []; + + function call(callback) { + try { + callback(); + } catch (e) { + } + } + + try { + executor(function (value) { + if (state === 'pending') { + state = 'fulfilled'; + result = value; + resolveQueue.forEach(call); + } + }, function (reason) { + if (state === 'pending') { + state = 'rejected'; + result = reason; + rejectQueue.forEach(call); + } + }); + } catch (error) { + if (state === 'pending') { + state = 'rejected'; + result = error; + rejectQueue.forEach(call); + } + } + /** + * The then() method returns a Promise. It takes two arguments, both are + * callback functions for the success and failure cases of the Promise. + * + * @method then + * @memberOf Promise + * @instance + * @param {function} onFulfilled A Function called when the Promise is fulfilled. This function has one argument, the fulfillment value. + * @param {function} onRejected A Function called when the Promise is rejected. This function has one argument, the rejection reason. + * @returns {Promise} + */ + this.then = function (onFulfilled, onRejected) { + + return new Promise(function (resolve, reject) { + + function asyncOnFulfilled() { + var value; + try { + value = onFulfilled ? onFulfilled(result) : result; + } catch (error) { + reject(error); + return; + } + value = mswrap(value); + if (value && value?.then?.call) { + value.then(resolve, reject); + } else { + resolve(value); + } + } + + function asyncOnRejected() { + var reason; + try { + reason = onRejected ? onRejected(result) : result; + } catch (error) { + reject(error); + return; + } + reason = mswrap(reason); + if (reason && reason?.then?.call) { + reason.then(resolve, reject); + } else { + reject(reason); + } + } + + if (state === 'fulfilled') { + asyncOnFulfilled(); + } else if (state === 'rejected') { + asyncOnRejected(); + } else { + resolveQueue.push(asyncOnFulfilled); + rejectQueue.push(asyncOnRejected); + } + + }); + + }; + /** + * The catch() method returns a Promise and deals with rejected cases only. + * It behaves the same as calling Promise.prototype.then(undefined, onRejected). + * + * @method catch + * @memberOf Promise + * @instance + * @param {function} onRejected A Function called when the Promise is rejected. This function has one argument, the rejection reason. + * @returns {Promise} + */ + this['catch'] = function (onRejected) { + return this.then(undefined, onRejected); + }; + } + + /** + * The Promise.all(iterable) method returns a promise that resolves when all + * of the promises in the iterable argument have resolved.

+ * + * The result is passed as an array of values from all the promises. + * If something passed in the iterable array is not a promise, it's converted to + * one by Promise.resolve. If any of the passed in promises rejects, the + * all Promise immediately rejects with the value of the promise that rejected, + * discarding all the other promises whether or not they have resolved. + * + * @method all + * @memberOf Promise + * @static + * @param {KeyUsages} promises Array with promises. + * @returns {Promise} + */ + Promise.all = function (promises) { + return new Promise(function (resolve, reject) { + var result = [], count = 0; + function asyncResolve(k) { + count++; + return function (data) { + result[k] = data; + count--; + if (count === 0) + resolve(result); + }; + } + + function asyncReject(reason) { + if (count > 0) + reject(reason); + count = 0; + } + + for (var i = 0, n = promises.length; i < n; i++) { + var data = promises[i]; + if (data?.then?.call) + data.then(asyncResolve(i), asyncReject); + else + result[i] = data; + } + + if (count === 0) + resolve(result); + }); + }; + + return Promise; + })(); +} //
+ +/* + * Worker executor + * + */ // + +var baseUrl = '', nameSuffix = ''; +// Try to define from DOM model +if (typeof document !== 'undefined') { + (function () { + var regs = /^(.*)gostCrypto(.*)\.js$/i; + var list = document.querySelectorAll('script'); + for (var i = 0, n = list.length; i < n; i++) { + var value = list[i].getAttribute('src'); + var test = regs.exec(value); + if (test) { + baseUrl = test[1]; + nameSuffix = test[2]; + } + } + })(); +} + +// Local importScripts procedure for include dependens +function importScripts() { + for (var i = 0, n = arguments.length; i < n; i++) { + var name = arguments[i].split('.'), + src = baseUrl + name[0] + nameSuffix + '.' + name[1]; + var el = document.querySelector('script[src="' + src + '"]'); + if (!el) { + el = document.createElement('script'); + el.setAttribute('src', src); + document.head.appendChild(el); + } + } +} + +// Create Worker +var worker = false, tasks = [], sequence = 0; +// Worker will create only for first child process and +// Gost implementation libraries not yet loaded +if (!root.importScripts && !root.gostEngine) { + + try { + worker = new Worker(baseUrl + 'gostEngine' + nameSuffix + '.js'); + + // Result of opertion + worker.onmessage = function (event) { + // Find task + var id = event.data.id; + for (var i = 0, n = tasks.length; i < n; i++) + if (tasks[i].id === id) + break; + if (i < n) { + var task = tasks[i]; + tasks.splice(i, 1); + // Reject if error or resolve with result + if (event.data.error) + task.reject(new OperationError(event.data.error)); + else + task.resolve(event.data.result); + } + }; + + // Worker error - reject all waiting tasks + worker.onerror = function (event) { + for (var i = 0, n = tasks.length; i < n; i++) + tasks[i].reject(event.error); + tasks = []; + }; + + } catch (e) { + // Worker is't supported + worker = false; + } +} + +if (!root.importScripts) { + // This procedure emulate load dependents as in Worker + root.importScripts = importScripts; + +} + +if (!worker) { + // Import main module + // Reason: we are already in worker process or Worker interface is not + // yet supported + root.gostEngine || require('./gostEngine'); +} + +// Executor for any method +function execute(algorithm, method, args) { + return new Promise(function (resolve, reject) { + try { + if (worker) { + var id = ++sequence; + tasks.push({ + id: id, + resolve: resolve, + reject: reject + }); + worker.postMessage({ + id: id, algorithm: algorithm, + method: method, args: args + }); + } else { + if (root.gostEngine) + resolve(root.gostEngine.execute(algorithm, method, args)); + else + reject(new OperationError('Module gostEngine not found')); + } + } catch (error) { + reject(error); + } + }); +} + +// Self resolver +function call(callback) { + try { + callback(); + } catch (e) { + } +} + +// + +/* + * WebCrypto common class references + * + */ // +/** + * The Algorithm object is a dictionary object [WebIDL] which is used to + * specify an algorithm and any additional parameters required to fully + * specify the desired operation.
+ *
+ *  dictionary Algorithm {
+ *      DOMString name;
+ *  };
+ * 
+ * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#algorithm-dictionary} + * @class Algorithm + * @param {DOMString} name The name of the registered algorithm to use. + */ + +/** + * AlgorithmIdentifier - Algorithm or DOMString name of algorithm
+ *
+ *  typedef (Algorithm or DOMString) AlgorithmIdentifier;
+ * 
+ * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#algorithm-dictionary} + * @class AlgorithmIdentifier + */ + +/** + * The KeyAlgorithm interface represents information about the contents of a + * given Key object. + *
+ *  interface KeyAlgorithm {
+ *      readonly attribute DOMString name
+ *  };
+ * 
+ * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#key-algorithm-interface} + * @class KeyAlgorithm + * @param {DOMString} name The name of the algorithm used to generate the Key + */ + +/** + * The type of a key. The recognized key type values are "public", "private" + * and "secret". Opaque keying material, including that used for symmetric + * algorithms, is represented by "secret", while keys used as part of asymmetric + * algorithms composed of public/private keypairs will be either "public" or "private". + *
+ *  typedef DOMString KeyType;
+ * 
+ * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#key-interface} + * @class KeyType + */ + +/** + * Sequence of operation type that may be performed using a key. The recognized + * key usage values are "encrypt", "decrypt", "sign", "verify", "deriveKey", + * "deriveBits", "wrapKey" and "unwrapKey". + *
+ *  typedef DOMString[] KeyUsages;
+ * 
+ * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#key-interface} + * @class KeyUsages + */ + +/** + * The Key object represents an opaque reference to keying material that is + * managed by the user agent.
+ * This specification provides a uniform interface for many different kinds of + * keying material managed by the user agent. This may include keys that have + * been generated by the user agent, derived from other keys by the user agent, + * imported to the user agent through user actions or using this API, + * pre-provisioned within software or hardware to which the user agent has + * access or made available to the user agent in other ways. The term key refers + * broadly to any keying material including actual keys for cryptographic + * operations and secret values obtained within key derivation or exchange operations.
+ * The Key object is not required to directly interface with the underlying key + * storage mechanism, and may instead simply be a reference for the user agent + * to understand how to obtain the keying material when needed, eg. when performing + * a cryptographic operation. + *
+ *  interface Key {
+ *      readonly attribute KeyType type;
+ *      readonly attribute boolean extractable;
+ *      readonly attribute KeyAlgorithm algorithm;
+ *      readonly attribute KeyUsages usages;
+ *  };
+ * 
+ * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#key-interface} + * @class Key + * @param {KeyType} type The type of a key. The recognized key type values are "public", "private" and "secret". + * @param {boolean} extractable Whether or not the raw keying material may be exported by the application. + * @param {KeyAlgorithm} algorithm The Algorithm used to generate the key. + * @param {KeyUsages} usages Key usage array: type of operation that may be performed using a key. + */ + +/** + * The KeyPair interface represents an asymmetric key pair that is comprised of both public and private keys. + *
+ *  interface KeyPair {
+ *      readonly attribute Key publicKey;
+ *      readonly attribute Key privateKey;
+ *  };
+ * 
+ * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#keypair} + * @class KeyPair + * @param {Key} privateKey Private key + * @param {Key} publicKey Public key + */ + +/** + * Specifies a serialization format for a key. The recognized key format values are: + *
    + *
  • 'raw' - An unformatted sequence of bytes. Intended for secret keys.
  • + *
  • 'pkcs8' - The DER encoding of the PrivateKeyInfo structure from RFC 5208.
  • + *
  • 'spki' - The DER encoding of the SubjectPublicKeyInfo structure from RFC 5280.
  • + *
  • 'jwk' - The key is represented as JSON according to the JSON Web Key format.
  • + *
+ *
+ *  typedef DOMString KeyFormat;
+ *  
+ * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#key-interface} + * @class KeyFormat + */ + +/** + * Binary data + *
+ *  typedef (ArrayBuffer or ArrayBufferView) CryptoOperationData;
+ *  
+ * @class CryptoOperationData + */ +var CryptoOperationData = ArrayBuffer; + +/** + * DER-encoded ArrayBuffer or PEM-encoded DOMString constains ASN.1 object
+ *
+ *  typedef (ArrayBuffer or DOMString) FormatedData;
+ * 
+ * @class FormatedData + */ +//
+ +/** + * The gostCrypto provide general purpose cryptographic functionality for + * GOST standards including a cryptographically strong pseudo-random number + * generator seeded with truly random values. + * + * @namespace gostCrypto + */ +var gostCrypto = {}; + +/** + * The SubtleCrypto class provides low-level cryptographic primitives and algorithms. + * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#subtlecrypto-interface} + * + * @class SubtleCrypto + */ // +function SubtleCrypto() { +} + +/** + * The encrypt method returns a new Promise object that will encrypt data + * using the specified algorithm identifier with the supplied Key. + * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-encrypt}

+ * + * Supported algorithm names: + *
    + *
  • GOST 28147-ECB "prostaya zamena" (ECB) mode (default)
  • + *
  • GOST 28147-CFB "gammirovanie s obratnoj svyaziyu po shifrotekstu" (CFB) mode
  • + *
  • GOST 28147-OFB "gammirovanie s obratnoj svyaziyu po vyhodu" (OFB) mode
  • + *
  • GOST 28147-CTR "gammirovanie" (counter) mode
  • + *
  • GOST 28147-CBC Cipher-Block-Chaining (CBC) mode
  • + *
  • GOST R 34.12-ECB "prostaya zamena" (ECB) mode (default)
  • + *
  • GOST R 34.12-CFB "gammirovanie s obratnoj svyaziyu po shifrotekstu" (CFB) mode
  • + *
  • GOST R 34.12-OFB "gammirovanie s obratnoj svyaziyu po vyhodu" (OFB) mode
  • + *
  • GOST R 34.12-CTR "gammirovanie" (counter) mode
  • + *
  • GOST R 34.12-CBC Cipher-Block-Chaining (CBC) mode
  • + *
+ * For more information see {@link GostCipher} + * + * @memberOf SubtleCrypto + * @method encrypt + * @instance + * @param {AlgorithmIdentifier} algorithm Algorithm identifier + * @param {Key} key Key object + * @param {CryptoOperationData} data Operation data + * @returns {Promise} Promise that resolves with {@link CryptoOperationData} + */ +SubtleCrypto.prototype.encrypt = function (algorithm, key, data) // +{ + return new Promise(call).then(function () { + if (checkNative(algorithm)) + return rootCrypto.subtle.encrypt(algorithm, key, data); + + algorithm = normalize(algorithm, 'encrypt'); + return execute(algorithm, 'encrypt', + [extractKey('encrypt', algorithm, key), data]); + }); +}; // + +/** + * The decrypt method returns a new Promise object that will decrypt data + * using the specified algorithm identifier with the supplied Key. + * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-decrypt}

+ * + * Supported algorithm names: + *
    + *
  • GOST 28147-ECB "prostaya zamena" (ECB) mode (default)
  • + *
  • GOST 28147-CFB "gammirovanie s obratnoj svyaziyu po shifrotekstu" (CFB) mode
  • + *
  • GOST 28147-OFB "gammirovanie s obratnoj svyaziyu po vyhodu" (OFB) mode
  • + *
  • GOST 28147-CTR "gammirovanie" (counter) mode
  • + *
  • GOST 28147-CBC Cipher-Block-Chaining (CBC) mode
  • + *
  • GOST R 34.12-ECB "prostaya zamena" (ECB) mode (default)
  • + *
  • GOST R 34.12-CFB "gammirovanie s obratnoj svyaziyu po shifrotekstu" (CFB) mode
  • + *
  • GOST R 34.12-OFB "gammirovanie s obratnoj svyaziyu po vyhodu" (OFB) mode
  • + *
  • GOST R 34.12-CTR "gammirovanie" (counter) mode
  • + *
  • GOST R 34.12-CBC Cipher-Block-Chaining (CBC) mode
  • + *
+ * For additional modes see {@link GostCipher} + * + * @memberOf SubtleCrypto + * @method decrypt + * @instance + * @param {AlgorithmIdentifier} algorithm Algorithm identifier + * @param {Key} key Key object + * @param {CryptoOperationData} data Operation data + * @returns {Promise} Promise that resolves with {@link CryptoOperationData} + */ +SubtleCrypto.prototype.decrypt = function (algorithm, key, data) // +{ + return new Promise(call).then(function () { + if (checkNative(algorithm)) + return rootCrypto.subtle.decrypt(algorithm, key, data); + + algorithm = normalize(algorithm, 'decrypt'); + return execute(algorithm, 'decrypt', + [extractKey('decrypt', algorithm, key), data]); + }); +}; // + +/** + * The sign method returns a new Promise object that will sign data using + * the specified algorithm identifier with the supplied Key. + * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-sign}

+ * + * Supported algorithm names: + *
    + *
  • GOST R 34.10-94 GOST Signature
  • + *
  • GOST R 34.10-94/GOST R 34.11-94 GOST Signature with Hash
  • + *
  • GOST R 34.10 ECGOST Signature
  • + *
  • GOST R 34.10/GOST R 34.11-94 ECGOST Signature with Old-Style Hash
  • + *
  • GOST R 34.10/GOST R 34.11 ECGOST Signature with Streebog Hash
  • + *
  • GOST 28147-MAC MAC base on GOST 28147
  • + *
  • GOST R 34.12-MAC MAC base on GOST R 43.12
  • + *
  • GOST R 34.11-HMAC HMAC base on GOST 34.11
  • + *
  • SHA-HMAC HMAC base on SHA
  • + *
+ * For additional modes see {@link GostSign}, {@link GostDigest} and {@link GostCipher} + * + * @memberOf SubtleCrypto + * @method sign + * @instance + * @param {AlgorithmIdentifier} algorithm Algorithm identifier + * @param {Key} key Key object + * @param {CryptoOperationData} data Operation data + * @returns {Promise} Promise that resolves with {@link CryptoOperationData} + */ +SubtleCrypto.prototype.sign = function (algorithm, key, data) // +{ + return new Promise(call).then(function () { + if (checkNative(algorithm)) + return rootCrypto.subtle.sign(algorithm, key, data); + + algorithm = normalize(algorithm, 'sign'); + var value = execute(algorithm, 'sign', + [extractKey('sign', algorithm, key), data]).then(function (data) { + if (algorithm.procreator === 'SC' && algorithm.mode === 'SIGN') { + data = gostCrypto.asn1.GostSignature.encode(data); + } + return data; + }); + return value; + }); +}; // + +/** + * The verify method returns a new Promise object that will verify data + * using the specified algorithm identifier with the supplied Key. + * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-verify}

+ * + * Supported algorithm names: + *
    + *
  • GOST R 34.10-94 GOST Signature
  • + *
  • GOST R 34.10-94/GOST R 34.11-94 GOST Signature with Hash
  • + *
  • GOST R 34.10 ECGOST Signature
  • + *
  • GOST R 34.10/GOST R 34.11-94 ECGOST Signature with Old-Style Hash
  • + *
  • GOST R 34.10/GOST R 34.11 ECGOST Signature with Streebog Hash
  • + *
  • GOST 28147-MAC MAC base on GOST 28147
  • + *
  • GOST R 34.12-MAC MAC base on GOST R 34.12
  • + *
  • GOST R 34.11-HMAC HMAC base on GOST 34.11
  • + *
  • SHA-HMAC HMAC base on SHA
  • + *
+ * For additional modes see {@link GostSign}, {@link GostDigest} and {@link GostCipher} + * + * @memberOf SubtleCrypto + * @method verify + * @instance + * @param {AlgorithmIdentifier} algorithm Algorithm identifier + * @param {Key} key Key object + * @param {CryptoOperationData} signature Signature data + * @param {CryptoOperationData} data Operation data + * @returns {Promise} Promise that resolves with boolean value of verification result + */ +SubtleCrypto.prototype.verify = function (algorithm, key, signature, data) // +{ + return new Promise(call).then(function () { + if (checkNative(algorithm)) + return rootCrypto.subtle.verify(algorithm, key, signature, data); + + algorithm = normalize(algorithm, 'verify'); + if (algorithm.procreator === 'SC' && algorithm.mode === 'SIGN') { + var obj = gostCrypto.asn1.GostSignature.decode(signature); + signature = {r: obj.r, s: obj.s}; + } + return execute(algorithm, 'verify', + [extractKey('verify', algorithm, key), signature, data]); + }); +}; // + +/** + * The digest method returns a new Promise object that will digest data + * using the specified algorithm identifier. + * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-digest}

+ * + * Supported algorithm names: + *
    + *
  • GOST R 34.11-94 Old-Style GOST Hash
  • + *
  • GOST R 34.11 GOST Streebog Hash
  • + *
  • SHA SHA Hash
  • + *
+ * For additional modes see {@link GostDigest} + * + * @memberOf SubtleCrypto + * @method digest + * @instance + * @param {AlgorithmIdentifier} algorithm Algorithm identifier + * @param {CryptoOperationData} data Operation data + * @returns {Promise} Promise that resolves with {@link CryptoOperationData} + */ +SubtleCrypto.prototype.digest = function (algorithm, data) // +{ + return new Promise(call).then(function () { + if (checkNative(algorithm)) + return rootCrypto.subtle.digest(algorithm, data); + + algorithm = normalize(algorithm, 'digest'); + return execute(algorithm, 'digest', [data]); + }); +}; // + +/** + * The generateKey method returns a new Promise object that will key(s) using + * the specified algorithm identifier. Key can be used in according with + * KeyUsages sequence. The recognized key usage values are "encrypt", "decrypt", + * "sign", "verify", "deriveKey", "deriveBits", "wrapKey" and "unwrapKey". + * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-generateKey}

+ * + * Supported algorithm names: + *
    + *
  • GOST R 34.10 ECGOST Key Pairs
  • + *
  • GOST 28147 Key for encryption GOST 28147 modes
  • + *
  • GOST 28147-KW Key for wrapping GOST 28147 modes
  • + *
  • GOST R 34.12 Key for encryption GOST R 34.12 modes
  • + *
  • GOST R 34.12-KW Key for wrapping GOST R 34.12 modes
  • + *
  • GOST R 34.11-KDF Key for Derivation Algorithm
  • + *
+ * For additional modes see {@link GostSign}, {@link GostDigest} and {@link GostCipher}
+ * Note: Generation key for GOST R 34.10-94 not supported. + * + * @memberOf SubtleCrypto + * @method generateKey + * @instance + * @param {AlgorithmIdentifier} algorithm Key algorithm identifier + * @param {boolean} extractable Whether or not the raw keying material may be exported by the application + * @param {KeyUsages} keyUsages Key usage array: type of operation that may be performed using a key + * @returns {Promise} Promise that resolves with {@link Key} or {@link KeyPair} in according to key algorithm + */ +SubtleCrypto.prototype.generateKey = function (algorithm, extractable, keyUsages) // +{ + return new Promise(call).then(function () { + if (checkNative(algorithm)) + return rootCrypto.subtle.generateKey(algorithm, extractable, keyUsages); + + var privateAlgorithm = algorithm.privateKey, + publicAlgorithm = algorithm.publicKey; + algorithm = normalize(algorithm, 'generateKey'); + if (privateAlgorithm) + privateAlgorithm = normalize(privateAlgorithm, 'generateKey'); + else + privateAlgorithm = algorithm; + if (publicAlgorithm) + publicAlgorithm = normalize(publicAlgorithm, 'generateKey'); + else + publicAlgorithm = algorithm; + return execute(algorithm, 'generateKey', []).then(function (data) { + if (data.publicKey && data.privateKey) + return convertKeyPair(publicAlgorithm, privateAlgorithm, extractable, keyUsages, data.publicKey, data.privateKey); + else + return convertKey(algorithm, extractable, keyUsages, data); + }); + }); +}; // + +/** + * The deriveKey method returns a new Promise object that will key(s) using + * the specified algorithm identifier. Key can be used in according with + * KeyUsage sequence. The recognized key usage values are "encrypt", "decrypt", + * "sign", "verify", "deriveKey", "deriveBits", "wrapKey" and "unwrapKey". + * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-deriveKey}

+ * + * Supported algorithm names: + *
    + *
  • GOST R 34.10-DH ECDH Key Agreement mode
  • + *
  • GOST R 34.11-KDF Key for Derivation Algorithm
  • + *
  • GOST R 34.11-PBKDF2 Password Based Key for Derivation Algorithm
  • + *
  • GOST R 34.11-PFXKDF PFX Key for Derivation Algorithm
  • + *
  • GOST R 34.11-CPKDF Password Based Key for CryptoPro Derivation Algorithm
  • + *
  • SHA-PBKDF2 Password Based Key for Derivation Algorithm
  • + *
  • SHA-PFXKDF PFX Key for Derivation Algorithm
  • + *
+ * For additional modes see {@link GostSign} and {@link GostDigest} + * + * @memberOf SubtleCrypto + * @method deriveKey + * @instance + * @param {AlgorithmIdentifier} algorithm Algorithm identifier + * @param {Key} baseKey Derivation key object + * @param {AlgorithmIdentifier} derivedKeyType Derived key algorithm identifier + * @param {boolean} extractable Whether or not the raw keying material may be exported by the application + * @param {KeyUsages} keyUsages Key usage array: type of operation that may be performed using a key + * @returns {Promise} Promise that resolves with {@link Key} + */ +SubtleCrypto.prototype.deriveKey = function (algorithm, baseKey, + derivedKeyType, extractable, keyUsages) // +{ + return new Promise(call).then(function () { + if (checkNative(algorithm)) + return rootCrypto.subtle.deriveKey(algorithm, baseKey, + derivedKeyType, extractable, keyUsages); + + algorithm = normalize(algorithm, 'deriveKey'); + derivedKeyType = normalize(derivedKeyType, 'generateKey'); + algorithm.keySize = derivedKeyType.keySize; + if (algorithm['public']) { + algorithm['public'].algorithm = normalize(algorithm['public'].algorithm); + algorithm['public'] = extractKey('deriveKey', algorithm, algorithm['public']); + } + return execute(algorithm, 'deriveKey', [extractKey('deriveKey', algorithm, baseKey)]).then(function (data) { + return convertKey(derivedKeyType, extractable, keyUsages, data); + }); + }); +}; // + +/** + * The deriveBits method returns length bits on baseKey using the + * specified algorithm identifier. + * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-deriveBits}

+ * + * Supported algorithm names: + *
    + *
  • GOST R 34.10-DH ECDH Key Agreement mode
  • + *
  • GOST R 34.11-KDF Key for Derivation Algorithm
  • + *
  • GOST R 34.11-PBKDF2 Password Based Key for Derivation Algorithm
  • + *
  • GOST R 34.11-PFXKDF PFX Key for Derivation Algorithm
  • + *
  • GOST R 34.11-CPKDF Password Based Key for CryptoPro Derivation Algorithm
  • + *
  • SHA-PBKDF2 Password Based Key for Derivation Algorithm
  • + *
  • SHA-PFXKDF PFX Key for Derivation Algorithm
  • + *
+ * For additional modes see {@link GostSign} and {@link GostDigest} + * + * @memberOf SubtleCrypto + * @method deriveBits + * @instance + * @param {AlgorithmIdentifier} algorithm Algorithm identifier + * @param {Key} baseKey Derivation key object + * @param {number} length Length bits + * @returns {Promise} Promise that resolves with {@link CryptoOperationData} + */ +SubtleCrypto.prototype.deriveBits = function (algorithm, baseKey, length) // +{ + return new Promise(call).then(function () { + if (checkNative(algorithm)) + return rootCrypto.subtle.deriveBits(algorithm, baseKey, length); + + algorithm = normalize(algorithm, 'deriveBits'); + if (algorithm['public']) + algorithm['public'] = extractKey('deriveBits', algorithm, algorithm['public']); + return execute(algorithm, 'deriveBits', [extractKey('deriveBits', algorithm, baseKey), length]); + }); +}; // + +/** + * The importKey method returns a new Promise object that will key(s) using + * the specified algorithm identifier. Key can be used in according with + * KeyUsage sequence. The recognized key usage values are "encrypt", "decrypt", + * "sign", "verify", "deriveKey", "deriveBits", "wrapKey" and "unwrapKey".

+ * Parameter keyData contains data in defined format. + * The suppored key format values are: + *
    + *
  • 'raw' - An unformatted sequence of bytes. Intended for secret keys.
  • + *
  • 'pkcs8' - The DER encoding of the PrivateKeyInfo structure from RFC 5208.
  • + *
  • 'spki' - The DER encoding of the SubjectPublicKeyInfo structure from RFC 5280.
  • + *
+ * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-importKey}

+ * + * Supported algorithm names: + *
    + *
  • GOST R 34.10-94 GOST Private and Public keys
  • + *
  • GOST R 34.10 ECGOST Private and Public keys
  • + *
  • GOST 28147 Key for encryption GOST 28147 modes
  • + *
  • GOST 28147-KW Key for key wrapping GOST 28147 modes
  • + *
  • GOST R 34.12 Key for encryption GOST 34.12 modes
  • + *
  • GOST R 34.12-KW Key for key wrapping GOST 34.12 modes
  • + *
  • GOST R 34.11-KDF Key for Derivation Algorithm
  • + *
+ * For additional modes see {@link GostSign}, {@link GostDigest} and {@link GostCipher}
+ * + * @memberOf SubtleCrypto + * @method importKey + * @instance + * @param {KeyFormat} format Key format Format specifies a serialization format for a key + * @param {CryptoOperationData} keyData + * @param {AlgorithmIdentifier} algorithm Key algorithm identifier + * @param {boolean} extractable Whether or not the raw keying material may be exported by the application + * @param {KeyUsages} keyUsages Key usage array: type of operation that may be performed using a key + * @returns {Promise} Promise that resolves with {@link Key} + */ +SubtleCrypto.prototype.importKey = function (format, keyData, algorithm, extractable, keyUsages) // +{ + var type; + return new Promise(call).then(function () { + if (checkNative(algorithm)) + return rootCrypto.subtle.importKey(format, keyData, algorithm, extractable, keyUsages); + + if (format === 'raw') { + algorithm = normalize(algorithm, 'importKey'); + if (keyUsages && keyUsages.indexOf) { + var name = algorithm.name.toUpperCase().replace(/[\.\s]/g, ''); + if (name.indexOf('3410') >= 0 && keyUsages.indexOf('sign') >= 0) + type = 'private'; + else if (name.indexOf('3410') >= 0 && keyUsages.indexOf('verify') >= 0) + type = 'public'; + } + return keyData; + } else { + var key; + if (format === 'pkcs8') + key = gostCrypto.asn1.GostPrivateKeyInfo.decode(keyData).object; + else if (format === 'spki') + key = gostCrypto.asn1.GostSubjectPublicKeyInfo.decode(keyData).object; + else + throw new NotSupportedError('Key format not supported'); + + algorithm = normalize(key.algorithm, 'importKey'); + type = key.type; + if (extractable !== false) + extractable = extractable || key.extractable; + if (keyUsages) { + for (var i = 0; i < keyUsages.length; i++) { + if (key.usages.indexOf(keyUsages[i]) < 0) + throw DataError('Key usage not valid for this key'); + } + } else + keyUsages = key.usages; + var data = key.buffer, keySize = algorithm.keySize, dataLen = data.byteLength; + if (type === 'public' || keySize === dataLen) + return data; + else { + // Remove private key masks + if (dataLen % keySize > 0) + throw new DataError('Invalid key size'); + algorithm.mode = 'MASK'; + algorithm.procreator = 'VN'; + var chain = []; + for (var i = keySize; i < dataLen; i += keySize) { + chain.push((function (mask) { + return function (data) { + return execute(algorithm, 'unwrapKey', [mask, data]).then(function (data) { + var next = chain.pop(); + if (next) + return next(data); + else { + delete algorithm.mode; + return data; + } + }); + }; + })(new Uint8Array(data, i, keySize))); + } + return chain.pop()(new Uint8Array(data, 0, keySize)); + } + } + }).then(function (data) { + return convertKey(algorithm, extractable, keyUsages, data, type); + }); +}; // + +/** + * The exportKey method returns a new Promise object that will key data in + * defined format.

+ * The suppored key format values are: + *
    + *
  • 'raw' - An unformatted sequence of bytes. Intended for secret keys.
  • + *
  • 'pkcs8' - The DER encoding of the PrivateKeyInfo structure from RFC 5208.
  • + *
  • 'spki' - The DER encoding of the SubjectPublicKeyInfo structure from RFC 5280.
  • + *
+ * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-exportKey}

+ * + * Supported algorithm names: + *
    + *
  • GOST R 34.10-94 GOST Private and Public keys
  • + *
  • GOST R 34.10 ECGOST Private and Public keys
  • + *
  • GOST 28147 Key for encryption GOST 28147 modes
  • + *
  • GOST 28147-KW Key for key wrapping GOST 28147 modes
  • + *
  • GOST R 34.12 Key for encryption GOST R 34.12 modes
  • + *
  • GOST R 34.12-KW Key for key wrapping GOST R 34.12 modes
  • + *
  • GOST R 34.11-KDF Key for Derivation Algorithm
  • + *
  • GOST R 34.11-PBKDF2 Import Password for Key for Derivation Algorithm
  • + *
  • GOST R 34.11-PFXKDF Import PFX Key for Derivation Algorithm
  • + *
  • GOST R 34.11-CPKDF Import Password Key for CryptoPro Derivation Algorithm
  • + *
  • SHA-PBKDF2 Import Password for Key for Derivation Algorithm
  • + *
  • SHA-PFXKDF Import PFX Key for Derivation Algorithm
  • + *
+ * For additional modes see {@link GostSign}, {@link GostDigest} and {@link GostCipher}
+ * + * @memberOf SubtleCrypto + * @method exportKey + * @instance + * @param {KeyFormat} format Format specifies a serialization format for a key + * @param {Key} key Key object + * @returns {Promise} Promise that resolves with {@link CryptoOperationData} + */ +SubtleCrypto.prototype.exportKey = function (format, key) // +{ + return new Promise(call).then(function () { + if (key && checkNative(key.algorithm)) + return rootCrypto.subtle.exportKey(format, key); + + if (!key.extractable) + throw new InvalidAccessError('Key not extractable'); + + var raw = extractKey(null, null, key); + if (format === 'raw') + return raw; + else if (format === 'pkcs8' && key?.algorithm?.id) { + if (key.algorithm.procreator === 'VN') { + // Add masks for ViPNet + var algorithm = key.algorithm, mask; + algorithm.mode = 'MASK'; + return execute(algorithm, 'generateKey').then(function (data) { + mask = data; + return execute(algorithm, 'wrapKey', [mask, key.buffer]); + }).then(function (data) { + delete algorithm.mode; + var d = new Uint8Array(data.byteLength + mask.byteLength); + d.set(new Uint8Array(data, 0, data.byteLength)); + d.set(new Uint8Array(mask, 0, mask.byteLength), data.byteLength); + var buffer = d.buffer; + buffer.enclosed = true; + return gostCrypto.asn1.GostPrivateKeyInfo.encode({ + algorithm: algorithm, + buffer: buffer + }); + }); + } else + return gostCrypto.asn1.GostPrivateKeyInfo.encode(key); + } else if (format === 'spki' && key?.algorithm?.id) + return gostCrypto.asn1.GostSubjectPublicKeyInfo.encode(key); + else + throw new NotSupportedError('Key format not supported'); + }); +}; // + +/** + * The wrapKey method returns a new Promise object that will wrapped key(s). + * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-wrapKey}

+ * + * Supported algorithm names: + *
    + *
  • GOST 28147-KW Key Wrapping GOST 28147 modes
  • + *
  • GOST R 34.12-KW Key Wrapping GOST R 34.12 modes
  • + *
  • GOST 28147-MASK Key Mask GOST 28147 modes
  • + *
  • GOST R 34.12-MASK Key Mask GOST R 34.12 modes
  • + *
  • GOST R 34.10-MASK Key Mask GOST R 34.10 modes
  • + *
+ * For additional modes see {@link GostCipher}
+ * + * @memberOf SubtleCrypto + * @method wrapKey + * @instance + * @param {KeyFormat} format Format specifies a serialization format for a key. Now suppored only 'raw' key format. + * @param {Key} key Key object + * @param {Key} wrappingKey Wrapping key object + * @param {AlgorithmIdentifier} wrapAlgorithm Algorithm identifier + * @returns {Promise} Promise that resolves with {@link CryptoOperationData} + */ +SubtleCrypto.prototype.wrapKey = function (format, key, wrappingKey, wrapAlgorithm) // +{ + return new Promise(call).then(function () { + if (checkNative(wrapAlgorithm)) + return rootCrypto.subtle.wrapKey(format, key, wrappingKey, wrapAlgorithm); + + wrapAlgorithm = normalize(wrapAlgorithm, 'wrapKey'); + var keyData = extractKey(null, null, key); + if (wrapAlgorithm.procreator === 'SC' && key.type === 'private') + keyData = swapBytes(keyData); + return execute(wrapAlgorithm, 'wrapKey', + [extractKey('wrapKey', wrapAlgorithm, wrappingKey), keyData]).then(function (data) { + if (format === 'raw') + return data; + else + throw new NotSupportedError('Key format not supported'); + }); + }); +}; // + +/** + * The unwrapKey method returns a new Promise object that will unwrapped key(s). + * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-unwrapKey}

+ * + * Supported algorithm names: + *
    + *
  • GOST 28147-KW Key Wrapping GOST 28147 modes
  • + *
  • GOST R 34.12-KW Key Wrapping GOST R 34.12 modes
  • + *
  • GOST 28147-MASK Key Mask GOST 28147 modes
  • + *
  • GOST R 34.12-MASK Key Mask GOST R 34.12 modes
  • + *
  • GOST R 34.10-MASK Key Mask GOST R 34.10 modes
  • + *
+ * For additional modes see {@link GostCipher}
+ * + * @memberOf SubtleCrypto + * @method unwrapKey + * @instance + * @param {KeyFormat} format Format specifies a serialization format for a key. Now suppored only 'raw' key format. + * @param {CryptoOperationData} wrappedKey Wrapped key data + * @param {Key} unwrappingKey Unwrapping key object + * @param {AlgorithmIdentifier} unwrapAlgorithm Algorithm identifier + * @param {AlgorithmIdentifier} unwrappedKeyAlgorithm Key algorithm identifier + * @param {boolean} extractable Whether or not the raw keying material may be exported by the application + * @param {KeyUsages} keyUsages Key usage array: type of operation that may be performed using a key + * @returns {Promise} Promise that resolves with {@link Key} + */ +SubtleCrypto.prototype.unwrapKey = function (format, wrappedKey, unwrappingKey, + unwrapAlgorithm, unwrappedKeyAlgorithm, extractable, keyUsages) // +{ + return new Promise(call).then(function () { + if (checkNative(unwrapAlgorithm)) + return rootCrypto.subtle.unwrapKey(format, wrappedKey, unwrappingKey, + unwrapAlgorithm, unwrappedKeyAlgorithm, extractable, keyUsages); + + unwrapAlgorithm = normalize(unwrapAlgorithm, 'unwrapKey'); + unwrappedKeyAlgorithm = normalize(unwrappedKeyAlgorithm, 'importKey'); + if (format !== 'raw') + throw new NotSupportedError('Key format not supported'); + + return execute(unwrapAlgorithm, 'unwrapKey', [extractKey('unwrapKey', unwrapAlgorithm, unwrappingKey), wrappedKey]).then(function (data) { + var type; + if (unwrappedKeyAlgorithm && unwrappedKeyAlgorithm.name) { + var name = unwrappedKeyAlgorithm.name.toUpperCase().replace(/[\.\s]/g, ''); + if (name.indexOf('3410') >= 0 && keyUsages.indexOf('sign') >= 0) + type = 'private'; + else if (name.indexOf('3410') >= 0 && keyUsages.indexOf('verify') >= 0) + type = 'public'; + } + if (unwrapAlgorithm.procreator === 'SC' && type === 'private') + data = swapBytes(data); + return convertKey(unwrappedKeyAlgorithm, extractable, keyUsages, data, type); + }); + }); +}; // + +/** + * The subtle attribute provides an instance of the SubtleCrypto + * interface which provides low-level cryptographic primitives and + * algorithms. + * + * @memberOf gostCrypto + * @type SubtleCrypto + */ +gostCrypto.subtle = new SubtleCrypto(); + +/** + * The getRandomValues method generates cryptographically random values. + * + * First try to use Web Crypto random genereator. Next make random + * bytes based on standart Math.random mixed with time and mouse pointer + * + * @memberOf gostCrypto + * @param {(CryptoOperationData)} array Destination buffer for random data + */ +gostCrypto.getRandomValues = function (array) // +{ + // Execute randomizer + GostRandom = GostRandom || root.GostRandom; + var randomSource = GostRandom ? new GostRandom() : rootCrypto; + if (randomSource.getRandomValues) + randomSource.getRandomValues(array); + else + throw new NotSupportedError('Random generator not found'); +}; // +//
+ +export default gostCrypto; diff --git a/plugins/srktoolbox/src/core/vendor/gost/gostDigest.mjs b/plugins/srktoolbox/src/core/vendor/gost/gostDigest.mjs new file mode 100644 index 00000000..4d5a04e1 --- /dev/null +++ b/plugins/srktoolbox/src/core/vendor/gost/gostDigest.mjs @@ -0,0 +1,1260 @@ +/** + * GOST R 34.11-94 / GOST R 34.11-12 implementation + * 1.76 + * 2014-2016, Rudolf Nickolaev. All rights reserved. + * + * Exported for CyberChef by mshwed [m@ttshwed.com] + */ + +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Converted to JavaScript from source https://www.streebog.net/ + * Copyright (c) 2013, Alexey Degtyarev. + * All rights reserved. + * + * 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 HOLDER 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. + * + */ + + import GostRandom from './gostRandom.mjs'; + import GostCipher from './gostCipher.mjs'; + import crypto from 'crypto'; + +/* + * GOST R 34.11 + * Common methods + * + */ // + +var root = {}; +var rootCrypto = crypto + +var DataError = Error, + NotSupportedError = Error; + +// Copy len values from s[sOfs] to d[dOfs] +function arraycopy(s, sOfs, d, dOfs, len) { + for (var i = 0; i < len; i++) + d[dOfs + i] = s[sOfs + i]; +} + +// Swap bytes in buffer +function swap(s) { + var src = new Uint8Array(s), + dst = new Uint8Array(src.length); + for (var i = 0, n = src.length; i < n; i++) + dst[n - i - 1] = src[i]; + return dst.buffer; +} + +// Convert BASE64 string to Uint8Array +// for decompression of constants and precalc values +function b64decode(s) { + // s = s.replace(/[^A-Za-z0-9\+\/]/g, ''); + var n = s.length, + k = n * 3 + 1 >> 2, r = new Uint8Array(k); + + for (var m3, m4, u24 = 0, j = 0, i = 0; i < n; i++) { + m4 = i & 3; + var c = s.charCodeAt(i); + + c = c > 64 && c < 91 ? + c - 65 : c > 96 && c < 123 ? + c - 71 : c > 47 && c < 58 ? + c + 4 : c === 43 ? + 62 : c === 47 ? + 63 : 0; + + u24 |= c << 18 - 6 * m4; + if (m4 === 3 || n - i === 1) { + for (m3 = 0; m3 < 3 && j < k; m3++, j++) { + r[j] = u24 >>> (16 >>> m3 & 24) & 255; + } + u24 = 0; + + } + } + return r.buffer; +} + +// Random seed +function getSeed(length) { + GostRandom = GostRandom || root.GostRandom; + var randomSource = GostRandom ? new (GostRandom || root.GostRandom) : rootCrypto; + if (randomSource.getRandomValues) { + var d = new Uint8Array(Math.ceil(length / 8)); + randomSource.getRandomValues(d); + return d; + } else + throw new NotSupportedError('Random generator not found'); +} + +// Check buffer +function buffer(d) { + if (d instanceof ArrayBuffer) + return d; + else if (d && d?.buffer instanceof ArrayBuffer) + return d.byteOffset === 0 && d.byteLength === d.buffer.byteLength ? + d.buffer : new Uint8Array(new Uint8Array(d, d.byteOffset, d.byteLength)).buffer; + else + throw new DataError('ArrayBuffer or ArrayBufferView required'); +} // + +/** + * Algorithm name GOST R 34.11 or GOST R 34.11-12

+ * + * http://tools.ietf.org/html/rfc6986 + * + * The digest method returns digest data in according to GOST R 4311-2012.
+ * Size of digest also defines in algorithm name. + *
    + *
  • GOST R 34.11-256-12 - 256 bits digest
  • + *
  • GOST R 34.11-512-12 - 512 bits digest
  • + *
+ * + * @memberOf GostDigest + * @method digest + * @instance + * @param {(ArrayBuffer|TypedArray)} data Data + * @returns {ArrayBuffer} Digest of data + */ +var digest2012 = (function () // +{ + // Constants + var buffer0 = new Int32Array(16); // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + + var buffer512 = new Int32Array(16); // [512, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + buffer512[0] = 512; + + // Constant C + var C = (function (s) { + var h = new Int32Array(b64decode(s)), + r = new Array(12); + for (var i = 0; i < 12; i++) + r[i] = new Int32Array(h.buffer, i * 64, 16); + return r; + })( + 'B0Wm8lllgN0jTXTMNnR2BRXTYKQIKkKiAWlnkpHgfEv8xIV1jbhOcRbQRS5DdmovH3xlwIEvy+vp2soe2lsIsbebsSFwBHnmVs3L1xui3VXKpwrbwmG1XFiZ1hJrF7WaMQG1Fg9e1WGYKyMKcur+89e1cA9GneNPGi+dqYq1o2+yCroK9ZYemTHbeoZD9LbCCdtiYDc6ycGxnjWQ5A/i03t7KbEUderyix+cUl9e8QY1hD1qKPw5Cscvzius3HT1LtHjhLy+DCLxN+iToepTNL4DUpMzE7fYddYD7YIs16k/NV5orRxynX08XDN+hY5I3eRxXaDhSPnSZhXos98f71f+bHz9WBdg9WPqqX6iVnoWGicjtwD/36P1OiVHF82/vf8PgNc1njVKEIYWHxwVf2MjqWwMQT+amUdHraxr6ktufWRGekBo+jVPkDZyxXG/tsa+wmYf8gq0t5oct6b6z8aO8Jq0mn8YbKRCUfnEZi3AOTB6O8Okb9nTOh2urk+uk9QUOk1WhojzSjyiTEUXNQQFSiiDaUcGNyyCLcWrkgnJk3oZMz5H08mHv+bHxp45VAkkv/6GrFHsxaruFg7H9B7nAr/UDX+k' + + '2ahRWTXCrDYvxKXRK43RaZAGm5LLK4n0msTbTTtEtIke3jaccfi3TkFBbgwCqucDp8mTTUJbH5vbWiODUURhcmAqH8uS3DgOVJwHppqKK3uxzrLbC0QKgIQJDeC3Vdk8JEKJJRs6fTreXxbs2JpMlJsiMRZUWo837ZxFmPvHtHTDtjsV0fqYNvRSdjswbB56SzNprwJn558DYTMbiuH/H9t4iv8c50GJ8/PkskjlKjhSbwWApt6+qxst84HNpMprXdhvwEpZot6Ybkd9Hc2678q5SOrvcR2KeWaEFCGAASBhB6vru2v62JT+WmPNxgIw+4nI79CezXsg1xvxSpK8SJkbstnVF/T6UijhiKqkHeeGzJEYne+AXZufITDUEiD4dx3fvDI8pM16sUkEsIAT0roxFvFn5443'); + + // Precalc Ax + var Ax = (function (s) { + return new Int32Array(b64decode(s)); + })( + '5vh+XFtxH9Alg3eACST6FshJ4H6FLqSoW0aGoY8GwWoLMumi13tBbqvaN6RngVxm9heWqBpoZnb13AtwY5GVS0hi84235kvx/1ximmi9hcXLgn2m/NdXlWbTba9pufCJNWyfdEg9g7B8vOyxI4yZoTanAqwxxHCNnrao0C+839aLGfpR5bOuN5zPtUCKEn0LvAx4tQggj1rlM+OEIojs7c7Cx9N3wV/S7HgXtlBdD165TMLAgzaHHYwgXbTLCwStdjyFWyigiS9YjRt59v8yVz/s9p5DEZM+D8DTn4A6GMnuAQom9fOtgxDv6PRBGXmmXc2hDH3pOhBKG+4dEkjpLFO/8tshhHM5tPUMz6aiPQlftLyc2EeYzeiKLYsHHFb5f3dxaVp1apzF8C5xoLoevKZj+atCFeZyLrGeIt5fu3gNuc4PJZS6FIJSDmOXZk2ELwMeagII6phcfyFEob5r8Ho3yxzRY2Lbg+COK0sxHGTPcEebq5YOMoVrqYa53ucetUeMh3r1bOm4/kKIX2HW/RvdAVaWYjjIYiFXkj74qS78l/9CEUR2+J19NQhWRSzrTJDJsOCnElYjCFAt+8sBbC16A/qnpkhF' + + '9G6LOL/GxKu9vvj91HfeujqsTOvIB5t58JyxBeiHnQwn+moQrIpYy4lg58FAHQzqGm+BHko1aSiQxPsHc9GW/0NQGi9gnQqf96UW4MY/N5Yc5KazuNqSUhMkdSw44IqbpahkczvsFU8r8SRXVUmzP9dm2xVEDcXHp9F5455Ct5La3xUaYZl/04agNF7AJxQjONVRe22pOaRlGPB3EEADtAJ5HZClrqLdiNJniZxKXQqTD2bfCihlwk7p1CBFCbCLMlU4kWaFKSpBKQe/xTOoQrJ+K2JUTcZzbFMERWKV4Ada9AbpU1GQih8vO2vBI2Fvw3sJ3FJV5cY5Z9Ezsf5oRCmIOcfw5xHiQJuH9xlk+aLpOK3D20sHGQwLTkf5w+v0VTTVdtNriENGEKBa64sC2CDDzfWCMvJRbeGEDb7Cseeg6N4GsPodCHuFS1QNNDM7QuKaZ7zKW3/YpgiKxDfdDsY7s6nZQ+2BIXFNvV5lo7FnYe3nte6haSQx98jVc6v21R/GheGjZxpeBjzUBBDJLSg6uY8ssEACj+vAbLLy95AX1k8Rb6HTPOBzWfGpnuSqeE7WjHTNwAZuKhnVxztC2ocStBYccEXD' + + 'NxWC5O2TIW2s45BBSTn2/H7F8SGGIjt8wLCUBCusFvv510U3mlJ+v3N8Py6jtoFoM+e42brSeMqpoyo0wi/+u+SBY8z+370NjllAJG6lpnBRxu9LhCrR5CK60GUnnFCM2RSIwhhgjO4xnqVJH3zaF9OU4SgTTJxgCUv0MnLV47Ob9hKlpKrXkcy72kPSb/0PNN4fPJRq0lBPW1RomV7ha9+fr2/qj3eUJkjqWHDdCSu/x+Vtcdl8Z93msv9PIdVJPCdrRjroYAORdntPr4bHH2ihPng11LmgtowRXwMMn9QUHdLJFlggAZg9j33dUySsZKpwP8wXUlTCyYmUjgK0Jj5edtafRsLeUHRvA1h9gARF2z2CknLx5WBYSgKbVgvz+65Ypz/83GKhWl5ObK1M6EupblXOH7jMCPl0eq6CslPBAhRM9/tHG58EKJjz6442BosnrfLv+3rtypf+jApevneOBRP099jPMCwlAcMri/eNkt38F1xVTfhlxX9GBS9f6vMwG6Ky9CSqaLfsu9YNhpmPDzUBBHVMAAAAAAAAAADxLjFNNNDM7HEFIr4GGCO1rygNmTDABcGX/VziXWk8ZRmkHMYzzJoV' + + 'lYRBcvjHnrjcVDK3k3aEqZQ2wTokkM9YgCsT8zLI71nEQq45fO1PXPoc2O/jq42C8uWslU0pP9Fq2CPokHobfU0iSfg88EO2A8ud2Hn58z3eLS8nNtgmdCpDpB+JHuLfb5iZnRtsEzrUrUbNPfQ2+rs131AmmCXAlk/cqoE+bYXrQbBTfuWlxAVAunWLFghHpBrkO+e7RK/juMQp0GcXl4GZk7vun765rpqN0eyXVCHzVyzdkX5uMWOT19rir/jOR6IgEjfcUzijI0PeyQPuNXn8VsSompHmAbKASNxXUeASlvVk5Lfbe3X3GINRWXoS222VUr3OLjMenbsjHXQwj1INcpP90yLZ4gpEYQwwRnf+7uLStOrUJcow/e4ggAZ1YerKSkcBWhPnSv4UhyZOMCzIg7J78RmlFmTPWbP2gtyoEap8HnivWx1WJvtkjcOytz6RF99bzjTQX3zwarVvXf0lfwrNEycYV03I5nbFKp4HOaflLriqmlSGVT4PPNmjVv9IrqqSe36+dWUlrY4th30ObPn/28hBOx7MoxRQyplpE74w6YPoQK1REAmVbqccsbW2ui20NU5Eab3KTiWgBRWvUoHKD3Hh' + + 'dEWYy40OK/JZP5sxKqhjt++zim4ppPxja2qjoEwtSp09lesO5r8x46KRw5YVVL/VGBacju+by/URXWi8nU4oRrqHXxj6z3Qg0e38uLbiPr2wBzby8eNkroTZKc5libb+cLei9tpPclUOclPXXG1JKQTyOj1XQVmnCoBp6gssEI5J0HPFa7EaEYqrehk55P/XzQlaCw44rO/J+2A2WXn1SJK95pfWfzQix4kz4QUUvGHhwdm5dcm1StImYWDPG82AmkSS7Xj9hnGzzKsqiBqXk3LOv2Z/4dCI1tRbXZhalCfIEagFjD9V3mX1tDGWtQYZ90+WsdZwbkOFnR6Ly0PTNlqrioXM+j2E+ce/mcKV/P2iH9Wh3ktjD82z73Y7i0VtgD9Z+Hz3w4WyfHO+XzGRPJjjrGYzsEghv2FnTCa4+BgP+8mVxMEwyKqghiAQdhqYYFfzQiEBFqr2PHYMBlTMNS3bRcxmfZBCvPRalkvUA4Jo6KDD7zxvPae9ktJp/3O8KQriAgHtIoe33jTN6IWBj9kB7qfdYQWb1vonMhmgNVPVbxrodMzOyeoxJFwug/VUcDRVXaB75JnOJtKsVue+9/0WGFelBU44' + + 'ag59pFJ0NtFb2Go4HN6f8sr3dWIxdwwysJqu2eJ5yNBd7xCRxgZ02xEQRqJRXlBFI1Ns5HKYAvzFDLz39bY8+nOhaIfNFx8DfSlBr9nyjb0/Xj60Wk87nYTu/jYbZ3FAPbjj0+cHYnEaOij58g/SSH68fHW0nnYndOXyk8frVlwY3PWeT0eLpAxu9E+prctSxpmBLZjax2B4iwbcbkadDvxl+Op1IexOMKX3IZ6OC1Ur7D9lvKV7a93QSWm68bdemZBM2+OU6lcUsgHR5upA9ruwwIJBKErdUPIEY7+PHf/o1/k7k8usuE2Mto5HfIbowd0bOZImjj98WqESCdYvyy89mKvbNcmuZxNpViv9X/UVweFsNs7igB1+su3485sX2pTTfbAN/gGHe8PsdguK2suEld/hU65EBaJHc7e0ELMShXt4PDKr3463cNBoElE7U2c5udLj5mVYTVficbJkaNeJx4/JhJclqTW7+n0a4QKLFTej36ZBiNDNXZvDeN56Ssgsmk2Az7dCd38bg722IHLSiDodM711XnotS6tqj0H02qtruxyV2ZBc/+f9jTG2g6pkIhGbOB/ArvuEQgIsSaD5CMZjAzrj' + + 'pCivCASTiCat5Bw0GopTx65xIe535qhdxH9cSiWSnoy1OOmqVc3YYwY3eqna2OspoYroe7MnmJVu39pqNeSEFGt9nRmCUJSn1Bz6VaTobL/lyu3J6kLFnKNsNRwOb8F5UYHk3m+rv4n/8MUwGE0X1J1B6xWEBFiSHA1SUCjXOWHxeOwYDKiFapoFcQGO+BHNQJGifD7178wZrxUjn2Mp0jR0UO/5HrmQ4RtKB43Sd1m5Vh3l/GATMZEvH1otqZPAFlTctluiGRo+Ld4JimuZ64pm1x4PguP+jFGtt9VaCNdFM+UPiUH/fwLm3We9SFns4Giqul321S/CSCbj/0p1pWw5Bw2IrN34ZIZUjEaRpG/Rvr0mE1x8DLMPkwOPFTNKgtmEn8G/mmmcMguoVCD65PpSgkOv+QdnntTWz+loowi4Jf1YLESxR5t2kbxe3LO7x+phkEj+ZRYQY6YfgXryM0fVOGg0CaaTY8LOmExt7TAqn9/YbIHZHXseOwYDKmaUZmCJ6/vZ/YMKWY7mc3UgewdEmhQK/ElfLKilcbZZMjQfmG+KRbvC+zgapKBQs3LCVCOjrdgfrzoXJzwLi4a7bP6DJY3IabWi' + + 'KHkCv9HJgPH1qUvWazg3r4iACnmyyroSVVBDEAg7DUzfNpQOB7nusgTRp85nkLLFYSQT//EltNwm8SuXxSwST4YII1GmLyis75NjL5k35ec1B7BSKTob5ucsMK5XCpxw01hgQa4UJeDeRXSz151MxJK6IoBAxWha8AsMpdyMJxy+Eofx9pxabvOeMX+x4NyGSV0RQCDsNC1pm0B+PxjNS9yjqdRq1RUoDR0U8nmJaSQAAAAAAAAAAFk+t1+hlsYeLk54FgsRa9htSuewWIh/juZf0BOHLj4Gem3bu9MOxOKsl/yJyq7xsQnMszweGdvhifPqxGLuGGR3cM9JqoetxlbFfsplV/bWA5U92m1s+5o2ko2IRFbgfB7rjzeVn2CNMdYXnE6qqSNvrDrX5cAmYkMEn6ZTmRRWq9NmncBSuO6vAsFTp8IKKzzLA243I8AHk8nCPZDhyizDO8ZeL27X00z/VjOXWCSeselOZDJdaqY34W01lHJCCnn45mG+Yj94UhTZBALHRBNILvH98MiWWxP2m8XsFgmpDogpKBTlkr5OGYtUKhB9cszAD8vrr+cbG0nIRCIrcD4lZBZNqEDp1SDGUT4f9Plm' + + 'usMgP5EM6Kvy7dHCYcR+8IFMuUWs02Hzlf64lEo5IQVcnPAsFiLWrZcYZfP3cXjpvYe6K5vwofREQAWyWWVdCe11vkgkf7wLdZYSLhfP9Cq0SwkXhel6FZZrhU4nVdqf7uCDkkkTR5EyQypGI8ZSuahGW0etPkN0+LRfJBKxXoskF/bweGRLo/shYv5/3aURS7vMJ52kbcEBc+C90CSidiIgjFmivKCKj8SQbbg2803kuQ10OmZn6nFHteBwX0bvJ4LLKhUIsDnsBl719FsefSG1sYPP0FsQ2+czwGApXHefpzZyOUwBfs9VMhGGwxyB2HIOGg1Fp+07j5l6Pd+JWDr8ecft+ysu6aQZhkPvDs5fCc32e04tN09qa+n6NN8Etq3UcDihI/mNIk0KBX6qocliSLhcG/eo4/2XYDCaLrULKm5bo1GCDetCxOH+p1cilI1YKZodg3N/z5zIZLrUUaVbT7XUtypQCL9Tgc49eZdGptjV5C0E5dIrgPx+MIeWV7aed7VzVKA5aUQdgJfQtDMwyvvz4vDP4o533eC+jMNisS4lnElPRqbOcm+529HKQeJCwe7RTbp2Ay/0eqMPsEWyaKk6zeTM' + + 'r38L6IRUnQgEg1SzwUaCY5JUNcLIDv7S7k438n/f+6cWejOSDGDxTfsSO1LqA+WESgyrU/27kAed6vY4D3iKGctI7FWPDLMqtZ3Estb+9+Dc28oi9PPsthHfWBNUmpxA4z/e31aKztOgwcgSQyLpwwela4FY+m0NdyeVebHh893ZsYt0QirABLjsLZ//q8KU9Kz4qC11kU97v2mx7ytoeMT2L69Iesfhds6AnMZ+XQxnEdiPkuTBTGJ7mdkkPe3+I0qlw9+2i1GQmx8VJi2/bU9m6gVLYry1GuLPWlKqaui+oFP70M4BSO1oCMDmYxTJQ/4WzRWoJxDNBJIxoGlw9ue8imyXzEywM3zoNfyzucBl3vJYfMeA81IhTt5BMrtQlfFeQ5D0k9+HCDliXdLg8UExPBr7i2avkXIK8FGyEbxHfUJ+1O6lcy47TO72474lgmJ4NOsLzEOcA+PdeOckyCh3MorZhn35FLUZReJDsPJXSw+I9+uX4oi2+piapJQ6GcTwaMsWhYZQ7mQJrxH6733zF9XATqukelZ8VJi0xqm2u/uAT0IYjjzCK887xc0L0EM26qo5dxPwL6wb7DMTLCUG26fw00iN' + + '1+Zda/LDGh5eubIWH/gg9YQuBlDEbg+fcWvrHZ6EMAGpM3WMqzFe1D/kFP2ieSJlJ8nxcB7wCTJzpMHKcKdxvpQYS6bnaz0OQNgp/4wUyH4PvsP6x3Z0yzYWqWNKapVyjxORGcJe+Tf1Re1NWuo/nugCSZZQujh7ZDfnvQtYLiLmVZ+J4FPiYYCtUuMFKI38bcVaI+NLmTXeFOD1GtCtCcY5BXimWYZeltdhcQlIfLHi1ss6IRVgAgHpFeV3n67RrbAhP2p33LeYgLduuaGmq12fjSSGRM+b/V5FNsVmJljxxrn+m6y9/erNY0G+mXnE76ciFwhAVXZRB3Hs2I5UPsK6UctnHwQ9CtSCrHGvWHn+eHoEXNrJNrI4rzOOBJrtvYZsyUly7iZhXabrvYECkDKV/dCLLBcR+DQEYHO/CurzCZMpdY/8QhyusT59z6k0uiMHSBGIgysk785Ch0zmXA5X1h+w6doas9G61vmbNDzAdXsciTxFgitRDbhAOpKXXHaYwfHbYUo+DQEY1eaMtNYPSI6FXLTPrpYeDfPLM9k6jlWrFKAO10IXAyhiN4nBg4tt0ZyUYpKJX+997Ts668/LuOZOSjFJ' + + 'Bkx+ZC9lw9w9Kz4qTFpj2lvT80CpIQxHtHTRV6FhWTGsWTTaHehyZm7jZRF693ZbyG7TZxawXESbpohcIB1JxbkFOHqINGxFExByxLq53f+/SUYep1GvmdUpd7wc4FuhsPeF5GAn21JUbTC6bld4jDBa1wdlD1auyYfGgmEv8pWlq4lE9fvFcX7VKOdZ8kTKjdy7zix9uIiqFUq+Mo2xuh5hm+mT7OiLCfK9nugTtxd0AapLKF0csyGFjxQxlcruSMOBhBOY0bj8t1DTsvmIiTmoapmNHOG5H4iODORzRlp4mVaDdpeHFgLPKtfuI0G/hccTtbPxoU7/kW/hK0Vn53waAjC30QV1DJj8yF7Km6Wj5/cg2p4GrWpgMaK7sfQ4lz50lH7X0mAs9GY5GMD/ml9Qp/NoZ44kNNmDtKRJ1M1orxt1VZK1h388PQIubeobq/xfW0USH2sNcektKVU1dN/99RBtTwPYCBuoe5+MGcbbfqGjrAmBu7vKEq1mFy36eXBDZgEIKccXkyZ3e/9fnAAAAAAAAAAA6yR2pMkG1xVyTdQvBzjfb7dS7mU43bZfN/+8hj31O6OO+oT8tcFX5unrXHMnJZaq' + + 'GwvavyU1xDmG4SyHKk1OIJlpoovOPgh6+vsut52cS1UFakFWttksslo65qXevqKWIqOwJqgpJYBTyFs7Nq0VgbEekAEXuHWDxR86Sj/laTDgGeHtzzYhveyBHSWR/LoYRFt9TE1SSh2o2mBp3K7wBVj1zHIwneMp1MBiWWt/9XDOIq0DOdWfmFkc2ZdHAk34i5DFqgMYe1T2Y9J/w1bQ8NhYnpE1tW7VNTCWUdPWehwS+WchzSZzLtKMHD1EGjasSSqUYWQHf2ktHXPcb19RS28KcPQNaNiKYLSzDsoerEHTZQnYM4WYfQs9l0kGMPaonszJCpbEZXeiDuLFrQGofOSatV4OcKPepEKcoYJka6Dal7RG25Yvaszth9TX9t4nKrgYXTelPEafJdzv4VvLpsGcbvn+o+tTp2SjkxvYhM4v0lkLgXwQ9FaiGm2AdDkz5XOgu3nvDQ8VXAygldweI2wsT8aU1DfkEDZN9iMFMpHdMt/Hg2xCZwMmPzKZvO9uZvjNauV7b52MNa4rW+IWWTGzwuISkPh/k70gJ7+RUANpRg6QIg0bVimeJ2+uGdMoY5KMPFOiQy9wgv746Rue0LxveSw+7UD3' + + 'TEDVN9LeU9t16L+uX8KyYk2pwNKlQf0KTo//4Dz9EmQmIOSVaW+n4+Hw9Ai4qY9s0aojD92m2cLH0BCd0cYoj4p50E90h9WFRpRXm6NxC6I4QX98+oNPaB1HpNsKUAflIGya8UYKZD+hKN33NL1HEoFERwZytyMt8uCGzAIQUpMYLeWNvIkrV8qh+bD4kx37a4kkR8wuWun53RGFBCCkO0vlvraKJD7WVYQlXxnI1l07Z0BOYz+gBqaNtnZsRyof94rHmrTJfiHDU0QuEICq7JpPnblXgucUBbp7yCybMiAxpUZl+LZeT7G2Ufd1R/TUi/oNhXukZoKFqWxaoWqYu5kPrvkI63nJoV43okf0pi12hX3NXSd0HvjFC4AKGCC8vmXcsgH3orRmbRuYb5Qm50zJIb9TxOZIlUEKD5PZykIgzcyqZHuk70KaQGCJChhxDE6k9psys4vM2jYt3jVM05bcI7x8Wy+pwwm7aKqFGrPSYTGnNkjgEwIdxSlB/E2yzVrat3BL5IqneWXZhO1x5jI4b9YXNLuk6C1t1TirckVcIUfqYXe0sV2hq3DPCRzorJB/znK4vf9XyF39lyJ4qKTkTGprb5QN' + + 'OFGZW08f3+RiV4zK7XG8ntmIK7DAHSwKkXudXRE8UDuiwx4RqHZDxuRjySOjmcHO9xaGxX6odtyHtKlz4JbVCa8NVn2dOlgUtAwqP1ncxvQ2AviEldEh3dPh3T2YNkhK+UXnGqRmiOV1GFR+sqWR9ZNmWHRQwB2JnqgQGGWMBltPVAgMvEYDoy0DhMZRN7893DJQeOyGHirqMKj8eVc/9yFNIDDKBQy2ZfAyK4AWwwxpvpbdGyRwh9uV7pmB4WG40fwYFNnKBfiCDtK7zA3nKWPXYFBDDxTHO8yw6KCdOg+OQHZNVz9UojnRdcHhYXe9EvWjfHNPH0urN8EvH9/CbVZIsWc5XNDxbATtFTe/QqftlxYdFDBAZX1sZ9qrcrgH7Bf6h7pO6Dzfr3nLAwT7wXM/BgVxvEY+eNYcEofpiifQfPSOd7StobnCYlNskN0m4kSbWGCAFgWPwJrX+UH8+/rYzqlL5G0Oo0PyiwYI65+bEmvQSRc0e5qSh0rnaZwiGwF8QsTmnuA6TFxyDuOSVktun14+o5naa6NT9FrYPTXn/uCQTBskJSLQCYMlh+ldhCmAwA8UMOLGs8Cghh4okwh0M6QZ1yny' + + 'NB89rdQtbG/uCj+u+7Kljkruc8SQ3TGDqrcttbGhajSpKgQGXiOP33tLNaFoa2/MaiO/bvSmlWwZHLlrhRrTUlXVmNTW3jUayWBN5fKufvMcpsKjqYHhct4vlVGtelOYMCWq/1bI9hYVUh2dHihg2VBv4xz6RQc6GJxV8StkewsBgOyarn6oWXzsi0AFDBBeI1DlGYv5QQTvitM0VcwN1wenvuFtZ3+S5eMluQ3naZdaBhWRom5jerYR7xYYIItGCfTfPrepgaseuweK6H2swLeRA4y2XiMfD9ONRXSwVmBn7fcCweqOvrpfS+CDEjjN48R3ws7+vlwNzkhsNUwb0oxds2QWwxkQJuqe0adicyQDnSmz74Ll658o/ILL8q4CqKronPBdJ4ZDGqz6J3SwKM9HH54xt6k4WBvQuOOSLsi8eBmbQAvvBpD7cce/QvhiHzvrEEYDBJloPnpHtVrY3piPQmOmldGQ2AjHKm5jhFMGJ1J7wxnXy+uwRGbXKZeu5n4MCuJljHwU0vEHsFbIgHEiwywwQAuMinrhH9Xaztug3ts46YoOdK0Qk1TcxhWmC+kaF/ZVzBmN3V/+uL2xSb/lMCiviQrt' + + '1lum9bStemp5VvCIKZcifhDoZlUys1L5DlNh39rO/jnOx/MEn8kBYf9itWFnf18ul1zPJtIlh/BR7w+GVDuvYy8eQe8Qy/KPUnImNbu5SoiujbrnM0TwTUEHadNmiP2as6uU3jS7uWaAExeSjfGqm6VkoPDFETxU8THUvr2xoRd/caLz6o71tUCHhUnI9lXDfvFOaUTwXezURmPc9VE32PKs/Q1SM0T8AAAAAAAAAABfvG5ZjvVRWhbPNC7xqoUysDa9bds5XI0TdU/m3TG3Ervfp3otbJCUiefIrDpYKzA8aw4JzfpFncSuBYnH4mUhSXNad39f1GjK/WRWHSybGNoVAgMvn8nhiGckNpQmg2k3ghQeO6+JhJy11TEkcEvp19tKbxrT0jOm+YlDKpPZv501OauKDuOwU/LKrxXH4tFuGSg8dkMPFT3r4pNjhO3EXjyCwyCL+QMzuINMuUoT/WRw3rEuaGtVNZ/RN3pTxDZhyqV5AvNZdQQ6l1KC5Zp5/X9wSCaDEpzFLukTaZzNeCi5/w59rI0dVFV0TnignUPLfYjMs1IzQUS9EhtKE8+6TUnNJf26ThE+dssgjAYILz/2J7oieKB2' + + 'wolX8gT7supFPf6B5G1n45TB5pU9p2IbLINoXP9JF2TzLBGX/E3spSsk1r2SLmj2sit4RJrFET9I87bt0SF8MS6erXW+tVrWF0/YtF/ULWtO1OSWEjir+pLmtO7+vrXQRqDXMgvvgghHIDuopZEqUST3W/jmnj6W8LE4JBPPCU7+4ln7yQH3dydqcksJHNt9vfj1Ae51R19ZmzwiTeyGkW2EAY+Zwer+dJi45BzbOazgWV5xIXxbtyqkOic8UMCv9QtD7D9UO26Djj4hYnNPcMCUkttFB/9Ycr/qn9/C7mcRaIrPnM36oBqBkNhqmDa5esvZO8YVx5XHMyw6KGCAyoY0RelO6H1Q9pZqX9DW3oXprYFPltXaHHCiL7aePqPVCmn2jVgrZEC4Qo7Jwu51f2BKSeOsjfEsW4b5CwwQyyPh2bLrjwLz7ik5E5TT0iVEyOChf1zQ1qq1jMal96JurYGT+wgjjwLC1caPRlsvn4H8/5zSiP26xXcFkVfzWdxHHSYuOQf/SSv7WCIz5ZrFV92yvOJC+LZzJXe3Ykjgls9vmcSm2D2nTMEUfkHreVcB9IuvdpEqkzc+8p0kmywKGenhYyK2+GIv' + + 'VTaZQEd1f3qfTVbVpHsLM4IlZ0ZqoRdMuPUFfesIL7LMSMEL9EdfUzcwiNQnXew6lo9DJRgK7RAXPSMs9wFhUa5O0J+Ub8wT/UtHQcRTmHMbWz8N2ZM3ZS/8sJZ7ZEBS4CN20gqJhAyjrjpwMpsY10GcvSM13oUm+v6/EVt8MZkDlwdPhaqbDcWK1PtINrlwvsYL4/xBBKge/zbcS3CHchMf3DPthFO2CETjPjQXZNMP8RtuqzjNOWQ1Hwp3YbhaO1aU9QnPug4whXCEuHJF0Eevs70il6488rpcL29rVUp0vcR2H09w4c/fxkRx7cRe5hB4TB3ArxZ6yinWPBE/KC3tQRd2qFmvrF8hHpmj1e7UhPlJqH7zOzzjbKWW4BPk0SDwmDqdQyxrxARk3Fl1Y2nV9eXRlWyemulfBDaYuyTJ7MjaZqTvRNaVCMilsurGxAwiNcBQO4A4wZO6jGUhAxzux11GvJ6P0zEBGTdRWtHY4uVohuylD7E3EI1XecmRcJ87aQXKQgZP61CDFoDK7+xFavMkG9I4WNZzr+GBq74kL1Tnytm/jAIR8YENzBn9kLxNuw9DxgqVGERqnaB2HaG/y/E/VwEq' + + 'K95PiWHhcrUnuFOoT3MkgbCx5kPfH0thGMw4Qlw5rGjSt/fXvzfYITEDhkowFMcgFKokY3Kr+lxuYA21TrrFdDlHZXQEA6PzCcIV8Lxx5iMqWLlH6YfwRXtM3xi0d73Ylwm165Bsb+BzCDwmgGDZC/7cQA5B+QN+KElIxuRL6bhyjsroCAZb+wYzDp4XSSsaWVCFYWnnKU665PT85sQ2T8p7z5XjDnRJfX/RhqM+lsJSg2EQ2FrWkE36oQIbTNMSkTq7dYclRPrdRuy5FA8VGD1lmmsehpEUwj8sq9cZEJrXE/4GLdRoNtCmBlay+8HcIhxaed2QlJbv0m28obFJNQ537aAjXk/Jy/05W2to9rkN4OrvpvTUxAQi/x8ahTLn+Wm4Xt7WqpR/biAHrvKPPzrQYjuBqTj+ZiTui3qtoae2gujdyFZge6eMxW8oHiowx5slekX6oI1bQXTgZCsws19ji/9+rgJUS8mvnAwF+AjOWTCK+YtGro/FjanMVcOIgDSWx2dtDrHzPKrh5w3XurtiAjJuorS/1QIPhyAYccudXKdUqbcSzoQWadh96DxWimGEeF62c59CC7pssHQeK/EtW2Dqwc5H' + + 'dqw19xKDaRwsa7fZ/s7bX/zNsY9MNRqDH3nAEsMWBYLwq62uYqdMt+GlgByC7wb8Z6IYRfLLI1dRFGZfXfBNnb9A/S10J4ZYoDk9P7cxg9oFpAnRkuOwF6n7KM8LQGX5JamiKUK/PXzbdeInA0Y+ArMm4QxatdBs55aOgpWmLea5c/OzY26tQt9XHTgZwwzl7lSbcinXy8USmSr9ZeLRRvjvTpBWsChktwQeE0Aw4ovALt0q2tUJZ5MrSvSK6V0Hb+b7e8bcR4Qjmqy3VfYWZkAaS+29uAfWSF6o04mvYwWkG8IgrbSxPXU7MriXKfIRmX5YS7MyICkdaDGTztocf/9atsDJn4GOFrvV4n9n46GlnTTuJdIzzZj4roU7VKLZbfcK+ssQXnl5XS6ZubukJY5De2dEM0F4AYb2zohmgvDr8JKjuzR70rzX+mLxjR1VrdnX0BHFVx4L0+Rxsb3/3qpsL4CO6v70XuV9MfbIgKT1D6R/8ET8oBrdycNR9bWV6nZkbTNS+SIAAAAAAAAAAIWQnxb1jr6mRilFc6rxLMwKVRK/Odt9Lnjb2Fcx3SbVKc++CGwta0ghi102WDoPmxUs0q36zXis' + + 'g6ORiOLHlbzDudplX3+Sap7LoBssHYnDB7X4UJ8vqep+6NbJJpQNzza2fhqvO27KhgeYWXAkJav7eEnf0xqzaUx8V8yTKlHi2WQTpg6KJ/8mPqVmxxWmcWxx/DRDdtyJSk9ZUoRjevja8xTpiyC88lcnaMFKuWaHEIjbfGguyLuIcHX5U3pqYi56RljzAsKiYZEW2+WCCE2ofd4BgybnCdzAGnecaZfo7cOcPax9UMimCjOhoHiowMGoK+RSs4uXP3Rr6hNKiOmiKMy+uv2aJ6vq2U4GjHwE9IlSsXgiflBc9Iyw+wSZWWAX4BVt5Iq9RDi08qc9NTGMUormSf9YhbUV75JN/Pt2DGYcIS6SVjS0kxlcxZp5hpzaUZoh0ZA+MpSBBbW+XC0ZSs6M1F8umEONTKI4Epzbm2+pyr7+OdSBsmAJ7wuMQd7R6/aRpY4VTm2mTZ7mSB9UsG+OzxP9iknYXh0ByeH1r8gmURwJTuP2mKMwde5nrVrHgi7sTbJDjdR8KMGZ2nWJ9oM32xzoks3ON8V8Id2jUwWX3lA8VGBqQvKqVD/3k11yen5zYhup4jKHUwdFnfFWoZ4Pwt/kd8Yd07TNnCJ9' + + '5Yd/A5hqNBuUnrKkFcb07WIGEZRgKJNAY4DnWuhOEbCL53K21tDxb1CSkJHVls9t6GeV7D6e4N98+SdIK1gUMshqPhTuwm20cRnNp42swPbkAYnNEAy265KtvDoCj9/3sqAXwtLTUpwgDav40FyNazSnj5ui93c347RxnY8jHwFFvkI8L1u3wfceVf79iOVdaFMDK1nz7m5ls+nE/wc6qncqwzma5evsh4Ful/hCp1sRDi2y4EhKSzMSd8s92N7dvVEMrHnrn6U1IXlVKpH1x4qwqWhG4GptQ8foC0vwszoIybNUaxYe5TnxwjXrqZC+wb7yN2YGx7IsIJIzYUVpqusBUjtvwyialGlTq5Nazt0nKDj2PhM0DosEVeyhK6BSd6GyxJeP+KKlUSLKE+VAhiJ2E1hi0/HN243f3gi3bP5dHhLInkoXig5WgWsDlphn7l95lTMD7Vmv7XSLq3jXHW2Sny35PlPu9dio+Lp5jCr2GbFpjjnPa5Xdry90kQTi7CqcgOCIZCfOXI/YgluV6sTg2Zk6xgJxRpnDpRcwdvk9GxUfUKKfQp7VBeorx1lGNGZaz9x/S5hhsftTKSNC98chwAgOhkEw' + + 'hpPNFpb9e3SHJzGScTaxS9NEbIpjoXIbZpo16KZoDkrKtljyOVCaFqTl3k70Loq5N6dDXug/CNkTTmI54mx/loJ5Gjwt9nSIP27wCoMpFjyOWn5C/etlkVyq7kx5gd21GfI0eFrx6A0lXd3j7Zi9cFCJijKpnMysKMpFGdpOZlauWYgPTLMdIg2XmPo31tsmMvlo8LT/zRqgDwlkTyWFRfo61RdeJN5y9GxUfF2yRhVxPoD7/w9+IHhDzytz0qr6vRfqNq7fYrT9ERus0W+Sz0q6p9vHLWfgs0FrXa1J+tO8oxaySRSoixXRUAaK7PkU4nwd6+Me/EBP5Ix1m+2iI37c/RQbUix4TlBw8XwmaBzmlsrBWBXzvDXSpks7tIGngAz/Kf59/fYe2frD1bqksGwmY6ke9ZnRA8EZkTRAQ0H3rU3tafIFVM2dlkm2G9aryMO95+rbE2jRMYmfsCr7ZR0Y41Lh+ufx2jkjWu98psGhu/XgqO5PepE3eAXPmgseMThxYYC/jlvZ+DrL2zzlgAJ15RXTi4l+Ry0/IfD7vMYtlG63ho6jlbo8JI0hlC4J5yI2Rb/eOYP/ZP65AuQbscl3QWMNENlX' + + 'w8sXIrWNTsyieuxxnK4MO5n+y1GkjBX7FGWsgm0nMyvhvQR6116/AXn3M6+UGWDFZy7JbEGjxHXCf+umUkaE82Tv0P1144c07Z5gBAdDrhj7jimTue8UTThFPrEMYlqBaXhIB0I1XBJIz0LOFKbunhysH9YGMS3Oe4LWukeS6budFBx7H4caB1YWuA3BHEouuEnBmPIfp3d8qRgByNmlBrE0jkh+wnOtQbINHph7OkR0YKtVo8+744TmKANFdvIKG4fRbYl6YXMP4n3v5F1SWIPN5rjKPb63DCNkftAdERl6Nio+oFkjhLYfQPPxiT8QddRX0UQEcdxFWNo0I3A1uNymEWWH/CBDjZtn08mrJtArC1yI7g4lF2/nejgqtdqQJpzEctnY/jFjxB5G+qjLibervHcWQvUvfR3khS8SbzmoxrowJDOboGAFB9fO6IjIj+6Cxhogr65XokSJJteAEfyl5yg2pFjwByvOu49LTL1Je75K820koTyv6Zu3aVV9EvqevQWntanowEuqW4Nr20JzFI+sO3kFkIOEgShRwSHlV9NQbFWw/XL/mWrLTz1hPtoMjmTi3APwhoNW5rlJ6QTq1yq7Cw/8' + + 'F6S1E1lncGrjyOFvBNU2f/hPMAKNr1cMGEbI/L06IjJbgSD39sqRCNRvojHs6j6mM02UdFM0ByVYQDlmworSSb7W86eanyH1aMy0g6X+li3QhXUbV+ExWv7QAj3lL9GOSw5bXyDmrd8aMy3pbrGrTKPOEPV7ZcYEEI97qNYsPNerB6OhEHPY4WsNrRKRvtVs8vNmQzUywJcuVXcmss7g1AAAAAAAAAAAywKkdt6bUCnk4y/Ui556wnNLZe4shPdeblOGvM1+EK8BtPyE58vKP8/oc1xlkF/VNhO/2g/0wuYRO4csMef26C/hi6JVBSrr6XS3LrxIoeQKvFZBuJ2Xm7RqpeYiArZuROwmsMS7/4emkDtbJ6UDx39oAZD8meZHl6hKOqcajZzdEu3hYDfqfMVUJR3dDchOiMVMfZVr4xNNkWlgSGYrXbCAcsyZCbmStd5ZYsXJfFGBuAOtGbY3ybL1l9lKgjDsCwiqxV9WXaTxMn/SAXKD1q2YkZ54815jarlRlnZ1H1Mk6SFnClN3T7n9PRwV1G1IkvZhlPvaSF9aNdxzEQFbN97T9HBUd6k9wAoOs4HNDY27iNgJxl/kNhYQSZe+rLpV' + + 'IbcKyVaTsoxZ9MXiJUEYdtXbXrULIfSZVdehnPVcCW+pcka0w/hRn4VS1IeivTg1VGNdGBKXw1Ajwu/chRg78p9h+W7MDJN5U0iTo53cj+1e3wtZqgpUy6wsbRqfOJRc1667oNiqfecqv6AMCcXvKNhMxk889y+/IAP2TbFYeLOnJMffwG7J+AafMj9ogIaCzClqzVHQHJQFXiuuXMDFw2Jw4sIdYwG2O4QnIDgiGcDS8JAOhGq4JFL8byd6F0XSxpU8jOlNiw/gCfj+MJV1PmVbLHmSKE0LmEo31UNH38Tqta6/iAjipZo/0sCQzFa6nKDg//hM0DhMJZXkr63hYt9nCPSzvGMCv2IPI31U68qTQp0QHBGCYAl9T9CM3dTajC+bVy5g7O9winx/GMS0Hzow26Tf6dP/QAbxmn+w8Htfa/fdTcGe9B9tBkcycW6P+fvMhmpknTMwjI3lZ3REZIlxsPlyoCks1hpHJD9ht9jv64UR1MgnZpYctr5A0UejqrNfJfe4Et52FU5AcEQynVE9drZOVwaT80eax9L5Cqibiy5EdwechSl+uZ09haxpfjfmLfx9QMN3byWk7pOeW+BFyFDdj7Wt' + + 'hu1bpxH/GVLpHQvZz2FrNTfgqyVuQI/7lgf2wDECWnoLAvXhFtI8nfPYSGv7UGUMYhz/J8QIdfV9QMtx+l/TSm2qZhbaopBin181SSPshOLshHw9xQfDswJaNmgEPOIFqL+ebE2sCxn6gIvi6b67lLW5nFJ3x0+jeNm8lfA5e8zjMuUM260mJMdPzhKTMnl+Fyns6y6nCavC1rn2mVTR+F2JjL+6uFUahZp2+xfditsb6FiGNi9/tfZBP4/xNs2K0xEPpbu341wKL+7VFMxNEegwEO3Nfxq5oedd5V9C1YHu3kpVwTshtvL1U1/5ThSADMG0bRiIdh684V/bZSmROy0l6JdacYHCcYF/HOLXpVQuUsXLXFMSS/n3pr7vnCgdnnIufSHy9W7OFw2bgdyn5g6bggUctJQbHnEvYjxJ1zMh5Fz6Qvn33MuOen+Lug9gjpiDGgEPtkZHTM8NjolbI6mShVhPsnqVjMK1cgUzVENC1bjphO/zpQEtGzQCHnGMV6Ziaq50GAv/GfwG49gTEjW6nU1qfG3+ydRMF4+G7WVQZSPmoC5SiAN3LVwGIpOJiwH0/gtpHsD42r2K7YJZkUxOOuyYW2e+' + + 'sQ3wgn+/lqlqaSea1Pja4eeGidzT1f8ugS4aKx+lU9H7rZDW66DKGBrFQ7I0MQ45FgT33yy5eCemJBxpURifAnU1E8zqr3xeZPKln8hMTvokfSseSJ9fWttk1xirR0xIefSnofInCkAVc9qDKpvrrjSXhnloYhxyUUg40qIwIwTwr2U3/XL2hR0GAj46a0S6Z4WIw85u3XNmqJP3zHCs/9TSTim17anfOFYyFHDqamwHw0GMDlpKgyvLsi9WNbrNBLRs0Ah42QoG7lq4DEQ7DzshH0h2yPnlCVjDiRLu3pjRSznNv4sBWTl7KSBy9Bvgh8BAkxPhaN6tJumIR8qjn04UDIScZ4W71f9VHbfz2FOgykbRXVykDc1gIMeH/jRvhLdtzxXD+1fe/aD8oSHkzkuNe2CWAS09msZCrSmKLGQIddi9EPCvFLNXxup7g3SsTWMh2JpFFjLtqWcJxxmyP/dsJLvzKLwGxmLVJpEsCPI84l7EeJKzZrl4KD9vTzm9wIyPnp1oM/1PORewnnn0N1k94G+ywIwQ1oh4QbHRS9oZsm7uMhOdsLSUh2Z12T4vglk3dxmHwFiQ6ax4PUZhdfGCfgP/bIcJ' + + 'lF3AqDU+uH9FFvllirW5Jj+Vc5h+sCDvuFUzC21RSDEq5qkbVCvLQWMx5BPGFgR5QI+OgYDTEaDv81FhwyVQOtBmIvm9lXDViHbZog1LjUmlUzE1VzoMi+Fo02TfkcQh9BsJ5/UKL48SsJsPJMGhLdpJzCypWT3EH1w0Vj5Xpr9U0U82qFaLgq983+BD9kGa6momhclD+Lzl3L+01+kdK7J63d55nQUga0Q8rtbmq217rpHJ9hvoRT64aKx8rlFjEce2UyLjMqTSPBSRuamS0I+1mC4DEcfKcKxkKODJ1NiJW8KWD1X8xXZCPpDsje/Xb/BQft6ecmc9z0XweozC6kqgYFSUH1yxWBD7W7De/Zxe/qHjvJrGk27dS0rcgAPrdBgI+OixDdIUXsG3KIWaIii8n3NQFylEJwoGQk69zNOXKu30Mxwr9gWZd+QKZqiGJVAwKkqBLtbdio2gpwN3R8UV+HqXDpt7MCPqqWAaxXi346o6c/utpg+2mTEequWXAAAAAAAAAAAxDvGdYgS09CKTcaZE22RVDeyvWRqWB5JcpJeLuKYklhwrGQo4dTU2QaKVtYLNYCwyedzBZCYnfcGhlKqfdkJx' + + 'E52AOybf0KGuUcTUQegwFtgT+kStZd/BrAvyvEXU0hMjvmqSRsUV2UnXTQiSPc84nQUDISfQZucvf97/Xk1jx6R+KgFVJH0HmbFv8S+ov+1GYdQ5jJcqr9/Qu8ijP5VC3KeWlKUdBsuwIOu2faHnJboPBWNpbao05PGkgNX3bKfEOONOlRDq95OegSQ7ZPL8je+uRgctJc8sCPOjWG/wTtelY3WzzzpWIMlHzkDnhlBD+KPdhvGCKVaLeV6sammHgAMBHx27Il31NhLT9xReAxifddowDew8lXDbnDcgyfO7Ih5Xa3PbuHL2UkDk9TbdRDviUYiryKriH/442bNXqP1Dym7n5PEXyqNhS4mkfuz+NOcy4cZinoN0LEMbmbHUzzoWr4PC1mqq5agESZDpHCYnHXZMo71fkcS3TD9YEPl8bdBF+EGixn8a/Rn+YzFPyPlXI42YnOmnCQddUwbujlX8VAKqSPoOSPpWPJAjvrRl376rylI/dmyHfSLYvOHuzE0784XgReO+u2mzYRVzPhDqrWcg/UMots6xDnHl3Cq9zETvZzfgt1I/FY6kErCNmJx0xS22zmGb61mZK5Rd6Ios78oJd29M' + + 'o71rjVt+N4TrRz2xy12JMMP7osKbSqB0nCgYFSXOF2toMxHy0MQ45F/Tute+hLcf/G7RWuX6gJs2zbARbF7+dymRhEdSCVjIopBwuVlgRghTEg66pgzBAToMBHx01ohpaR4KxtLaSWhz20l05utHUXqDiv30BZnJWkrNM7TiH5lgRslPwDSX8OarkujRy46iM1TH9WY4VvHZPuFwr3uuTWFr0nvCKuZ8krOaEDl6g3CryLMwS46YkL+WcodjCwKyW2fWB7b8bhXQMcOXzlU/5ha6WwGwBrUlqJut5ilucMhqH1Jdd9NDW24QNXBXPfoLZg77Khf8lat2Mnqel2NL9kutnWRiRYv18YMMrtvD90jFyPVCZpEx/5UEShzcSLDLiSli3zz4uGawueII6TDBNaFPs/BhGnZ8jSYF8hwWATbWtxki/sxUnjcIlDilkH2LC12jjlgD1JxaW8yc6m88vO2uJG07c//l0rh+D94i7c5eVKuxyoGF7B3n+I/oBWG5rV4ahwE1oIwvKtvWZc7MdleAtaeC9YNYPtyKLu3kez/J2Vw1Br7nD4O+ER1sTgXupgO5CVk2dBAQPIG0gJ/eXSxptgJ9DHdK' + + 'OZCA19XIeVMJ1B4WSHQGtM3WOxgmUF5f+Z3C9JsCmOic0FQKlDy2f7yoS3+JHxfFcj0ds7eN8qZ4qm5x5ztPLhQz5pmgcWcNhPIb5FRiB4KY3zMntNIPL/BJ3OLTdp5c22xgGZZW63pkh0ayB4tHgzLNI1mNy63PHqSVW/DH2oXpoUNAG51Gtf2Spdm77CG4yBOMeQ4Ljhsu4AuabXulYvhXEriTt/H86yj+2AvqlJ1WSmXrikDqTGyZiOhHSigjRTWJixIdjy2r2MAyMazL9Loukcq5hny9eWC+Pe+OJjoMEal3YC/W8MtQ4a0WyTUn6uIulANf/YkoZtEvXeLOGv8bGEGrm/OQn5M53oz+DUOWRyfIxIoL91JFAsaqrlMcm5xe86wQtBNPovpJQqsypT8WWmLlURIrx0FI2nbm49eSSEDl5GSyp9NyrkPWl4TaIztyoQXhGoakigSRSUGmOLS2hSXJ3nhl3eq6rKbPgAIKl3PCULa9iMKE/7tevTOTi6DfRyyPak4q72y3TZUcMkJ5g3IqMY1Bc/fN/784m7IHTAr5OCwCbIpqDwskOgNab9rlPF+Ikx/Gi5iWflOKw0T/WccaqOY5' + + '4vzgzkOekimiDN4kedjNQBnon6LI69jp9Ea7z/OYJwxDs1M+IoTkVdgvDc2OlFBGUQZvErJs6CDnOVeva8VCbQgezlpAwW+gOxk9T8W/q3t/5mSI3xdNQg6YFO9wWATYgTeshXw518axczJE4YWoIWlcP4lvEfhn9s8GV+Pv9SQaq/J20Clj1S2jZk51uR5eAom9mBB30iiQwf199BNgjzxVN7b9k6kXqhIQfjkZouAGhtq1MJlreNqmsFWe44Juw04v91YIWodtU1ikT/9BN/xYdZWzWUisfKUJXMfV9n77FH9si3VKwL/rJquR3az5aJbvxWekkXPKmjHhHnxcM7vkQYaxMxWpDdt5O2iav+RwtKArp/ogjuR6OntzB/lRjOzVvhSjaCLu7Um5I7FE2Rdwi024s9wxYIghnydl/tOz+o/c8fJ6CZELLTH8pgmbD1LEo3jtbcxQzL9eutmBNGvVghF/ZipPlM6aUNT92d8rJbz7RSB1JmfEK2YfSfy/SSQg/HIyWd0DQ23UGMK7PB9uRRf4crORoIVjvGmvH2jUPqS67ruGtgHK0EwItWkUrJTKywmAyZhUw9hzmjc4ZCb+xcAtusrC' + + '3qnXeL4NOz4ED2ctIO65UOWw6jd7spBF8wqxNsu0JWBiAZwHNxIs++hrkwwTKC+hzBzrVC7lN0tTj9KKohs6CBthIjrYnArBNsJEdK0lFJ96I9Pp90ydBr4h9ueZaMXtz1+GgDYnjHf3BdYb61qcME0rR9FS3OCNX557/cI07Pgkd3hYPc0Y6oZ7pnxEFdWqTOGXnVppiZkAAAAAAAAAAOxk9CEzxpbxtXxVacFrEXHBx5JvRn+Ir2VNlv4PPi6XFfk21ajEDhm4pyxSqfGulalRfaoh2xncWNJxBPoY7pRZGKFI8q2HgFzdFina9lfEgnTBUWT7bPrR+xPbxuBW8n1v2RDPYJ9qtj84vdmpqk09n+f69SbAA3S7xwaHFJne32MHNLa4Uio60+0DzQrCb/reryCDwCPUwA1CI07K4buFOMuoXNdulsQCJQ5uJFjrR7w0EwJqXQWv16cfEUJypJeN94TMP2LjuW38HqFEx4Ehss85FZbIrjGOTo2VCRbzzpVWzD6S5WM4WlCb3X0QRzWBKaC156+j5vOH42NwK3ngdV1WU+lAAXvpA6X/+fQSErU8LJDoDHUzB/MVhX7E24+vuGoMYdMe' + + '2eXdgYYhOVJ3+KrSn9Yi4iW9qBQ1eHH+dXEXSo+h8MoTf+xgmF1lYTBEnsGdvH/npUDU3UH0zyzcIGrgrnrpFluRHNDi2lWosjBfkPlHEx00S/nsvVLGt10XxmXSQz7QGCJP7sBesf2eWemShEtkV5pWjr+kpd0Ho8YOaHFtpFR+LLTE16IkVoexdjBMoLy+QTrupjLzNn2ZFeNrvGdmO0DwPuo6Rl9pHC0ow+CwCK1OaCoFSh5bsQXFt2EoW9BE4b+NGltcKRXywGF6wwFMdLf16PHRHMNZY8tMSz+nRe+dGoRGnInfa+M2MIJLK/s91fR09uYO76L1jGuD+y1OGEZ25F8K3zQRIHgfdR0jobq9Ypszgap+0a4dd1MZ9xuw/tHIDaMumoRVCQg/koJRcCmsAWNVV6cOp8lpRVGDHQSOZWgmBNS6ChH2UfiIKrdJ133JbvZ5PYrvJ5n1KwQtzUju8LB6hzDJIvGi7Q1Uc5JhQvHTL9CXx0pnTShq8OLhgP18yXSMvtJxfnBnr09JmpOCkKns0duziOOykzRN0XInNBWMJQ+j1g'); //== + + // Variables + var sigma, N, h; + + // 64bit tools + function get8(x, i) { + return (x[i >> 2] >> ((i & 3) << 3)) & 0xff; + } + + // 512bit tools + function add512(x, y) { + var CF = 0, w0, w1; + for (var i = 0; i < 16; i++) { + w0 = (x[i] & 0xffff) + (y[i] & 0xffff) + (CF || 0); + w1 = (x[i] >>> 16) + (y[i] >>> 16) + (w0 >>> 16); + x[i] = (w0 & 0xffff) | (w1 << 16); + CF = (w1 >>> 16); + } + } + + function get512(d) { + return new Int32Array(d.buffer, d.byteOffset, 16); + } + + + function copy512(r, d) { + for (var i = 0; i < 16; i++) + r[i] = d[i]; + } + + function new512() { + return new Int32Array(16); + } + + // Core private algorithms + function xor512(x, y) { + for (var i = 0; i < 16; i++) + x[i] = x[i] ^ y[i]; + } + + + var r = new512(); + function XLPS(x, y) { + copy512(r, x); + xor512(r, y); + for (var i = 0; i < 8; i++) { + var z0, z1, k = get8(r, i) << 1; + z0 = Ax[k]; + z1 = Ax[k + 1]; + for (var j = 1; j < 8; j++) { + k = (j << 9) + (get8(r, (j << 3) + i) << 1); + z0 = z0 ^ Ax[k]; + z1 = z1 ^ Ax[k + 1]; + } + x[i << 1] = z0; + x[(i << 1) + 1] = z1; + } + } + + var data = new512(), Ki = new512(); + function g(h, N, m) + { + var i; + + copy512(data, h); + XLPS(data, N); + + /* Starting E() */ + copy512(Ki, data); + XLPS(data, m); + + for (i = 0; i < 11; i++) { + XLPS(Ki, C[i]); + XLPS(data, Ki); + } + + XLPS(Ki, C[11]); + xor512(data, Ki); + /* E() done */ + + xor512(h, data); + xor512(h, m); + } + + // Stages + function stage2(d) { + var m = get512(d); + g(h, N, m); + + add512(N, buffer512); + add512(sigma, m); + } + + function stage3(d) { + var n = d.length; + if (n > 63) + return; + + var b0 = new Int32Array(16); + b0[0] = n << 3; + + var b = new Uint8Array(64); + for (var i = 0; i < n; i++) + b[i] = d[i]; + b[n] = 0x01; + + var m = get512(b), m0 = get512(b0); + g(h, N, m); + + add512(N, m0); + add512(sigma, m); + + g(h, buffer0, N); + g(h, buffer0, sigma); + } + + return function (data) { + + // Cleanup + sigma = new512(); + N = new512(); + + // Initial vector + h = new512(); + for (var i = 0; i < 16; i++) + if (this.bitLength === 256) + h[i] = 0x01010101; + + // Make data + var d = new Uint8Array(buffer(data)); + + var n = d.length; + var r = n % 64, q = (n - r) / 64; + + for (var i = 0; i < q; i++) + stage2.call(this, new Uint8Array(d.buffer, i * 64, 64)); + + stage3.call(this, new Uint8Array(d.buffer, q * 64, r)); + + var digest; + if (this.bitLength === 256) { + digest = new Int32Array(8); + for (var i = 0; i < 8; i++) + digest[i] = h[8 + i]; + } else { + digest = new Int32Array(16); + for (var i = 0; i < 16; i++) + digest[i] = h[i]; + } + // Swap hash for SignalCom + if (this.procreator === 'SC' || this.procreator === 'VN') + return swap(digest.buffer); + else + return digest.buffer; + }; +} // +)(); + +/** + * Algorithm name GOST R 34.11-94

+ * + * http://tools.ietf.org/html/rfc5831 + * + * The digest method returns digest data in according to GOST R 34.11-94. + * @memberOf GostDigest + * @method digest + * @instance + * @param {(ArrayBuffer|TypedArray)} data Data + * @returns {ArrayBuffer} Digest of data + */ +var digest94 = (function () // +{ + var C, H, M, Sum; + + // (i + 1 + 4(k - 1)) = 8i + k i = 0-3, k = 1-8 + function P(d) { + var K = new Uint8Array(32); + + for (var k = 0; k < 8; k++) { + K[4 * k] = d[k]; + K[1 + 4 * k] = d[ 8 + k]; + K[2 + 4 * k] = d[16 + k]; + K[3 + 4 * k] = d[24 + k]; + } + + return K; + } + + //A (x) = (x0 ^ x1) || x3 || x2 || x1 + function A(d) + { + var a = new Uint8Array(8); + + for (var j = 0; j < 8; j++) + { + a[j] = (d[j] ^ d[j + 8]); + } + + arraycopy(d, 8, d, 0, 24); + arraycopy(a, 0, d, 24, 8); + + return d; + } + + // (in:) n16||..||n1 ==> (out:) n1^n2^n3^n4^n13^n16||n16||..||n2 + function fw(d) { + var wS = new Uint16Array(d.buffer, 0, 16); + var wS15 = wS[0] ^ wS[1] ^ wS[2] ^ wS[3] ^ wS[12] ^ wS[15]; + arraycopy(wS, 1, wS, 0, 15); + wS[15] = wS15; + } + + //Encrypt function, ECB mode + function encrypt(key, s, sOff, d, dOff) { + var t = new Uint8Array(8); + arraycopy(d, dOff, t, 0, 8); + var r = new Uint8Array(this.cipher.encrypt(key, t)); + arraycopy(r, 0, s, sOff, 8); + } + + // block processing + function process(d, dOff) { + var S = new Uint8Array(32), U = new Uint8Array(32), + V = new Uint8Array(32), W = new Uint8Array(32); + + arraycopy(d, dOff, M, 0, 32); + + //key step 1 + + // H = h3 || h2 || h1 || h0 + // S = s3 || s2 || s1 || s0 + arraycopy(H, 0, U, 0, 32); + arraycopy(M, 0, V, 0, 32); + for (var j = 0; j < 32; j++) + { + W[j] = (U[j] ^ V[j]); + } + // Encrypt GOST 28147-ECB + encrypt.call(this, P(W), S, 0, H, 0); // s0 = EK0 [h0] + + //keys step 2,3,4 + for (var i = 1; i < 4; i++) { + var tmpA = A(U); + for (var j = 0; j < 32; j++) { + U[j] = (tmpA[j] ^ C[i][j]); + } + V = A(A(V)); + for (var j = 0; j < 32; j++) { + W[j] = (U[j] ^ V[j]); + } + // Encrypt GOST 28147-ECB + encrypt.call(this, P(W), S, i * 8, H, i * 8); // si = EKi [hi] + } + + // x(M, H) = y61(H^y(M^y12(S))) + for (var n = 0; n < 12; n++) { + fw(S); + } + for (var n = 0; n < 32; n++) { + S[n] = (S[n] ^ M[n]); + } + + fw(S); + + for (var n = 0; n < 32; n++) { + S[n] = (H[n] ^ S[n]); + } + for (var n = 0; n < 61; n++) { + fw(S); + } + arraycopy(S, 0, H, 0, H.length); + } + + + // 256 bitsblock modul -> (Sum + a mod (2^256)) + function summing(d) + { + var carry = 0; + for (var i = 0; i < Sum.length; i++) + { + var sum = (Sum[i] & 0xff) + (d[i] & 0xff) + carry; + + Sum[i] = sum; + + carry = sum >>> 8; + } + } + + // reset the chaining variables to the IV values. + var C2 = new Uint8Array([ + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0xFF + ]); + + return function (data) { + + // Reset buffers + H = new Uint8Array(32); + M = new Uint8Array(32); + Sum = new Uint8Array(32); + + // Reset IV value + C = new Array(4); + for (var i = 0; i < 4; i++) + C[i] = new Uint8Array(32); + arraycopy(C2, 0, C[2], 0, C2.length); + + // Make data + var d = new Uint8Array(buffer(data)); + + var n = d.length; + var r = n % 32, q = (n - r) / 32; + + // Proccess full blocks + for (var i = 0; i < q; i++) { + var b = new Uint8Array(d.buffer, i * 32, 32); + + summing.call(this, b); // calc sum M + process.call(this, b, 0); + } + + // load d the remadder with padding zero; + if (r > 0) { + var b = new Uint8Array(d.buffer, q * 32), + c = new Uint8Array(32); + arraycopy(b, 0, c, 0, r); + summing.call(this, c); // calc sum M + process.call(this, c, 0); + + } + + // get length into L (byteCount * 8 = bitCount) in little endian. + var L = new Uint8Array(32), n8 = n * 8, k = 0; + while (n8 > 0) { + L[k++] = n8 & 0xff; + n8 = Math.floor(n8 / 256); + } + process.call(this, L, 0); + process.call(this, Sum, 0); + + var h = H.buffer; + + // Swap hash for SignalCom + if (this.procreator === 'SC') + h = swap(h); + + return h; + }; + +} // +)(); + +/** + * Algorithm name SHA-1

+ * + * https://tools.ietf.org/html/rfc3174 + * + * The digest method returns digest data in according to SHA-1.
+ * + * @memberOf GostDigest + * @method digest + * @instance + * @param {(ArrayBuffer|TypedArray)} data Data + * @returns {ArrayBuffer} Digest of data + */ +var digestSHA1 = (function () // +{ + + // Create a buffer for each 80 word block. + var state, block = new Uint32Array(80); + + function common(a, e, w, k, f) { + return (f + e + w + k + ((a << 5) | (a >>> 27))) >>> 0; + } + + function f1(a, b, c, d, e, w) { + return common(a, e, w, 0x5A827999, d ^ (b & (c ^ d))); + } + + function f2(a, b, c, d, e, w) { + return common(a, e, w, 0x6ED9EBA1, b ^ c ^ d); + } + + function f3(a, b, c, d, e, w) { + return common(a, e, w, 0x8F1BBCDC, (b & c) | (d & (b | c))); + } + + function f4(a, b, c, d, e, w) { + return common(a, e, w, 0xCA62C1D6, b ^ c ^ d); + } + + function cycle(state, block) { + var a = state[0], + b = state[1], + c = state[2], + d = state[3], + e = state[4]; + + // Partially unroll loops so we don't have to shift variables. + var fn = f1; + for (var i = 0; i < 80; i += 5) { + if (i === 20) { + fn = f2; + } + else if (i === 40) { + fn = f3; + } + else if (i === 60) { + fn = f4; + } + e = fn(a, b, c, d, e, block[i]); + b = ((b << 30) | (b >>> 2)) >>> 0; + d = fn(e, a, b, c, d, block[i + 1]); + a = ((a << 30) | (a >>> 2)) >>> 0; + c = fn(d, e, a, b, c, block[i + 2]); + e = ((e << 30) | (e >>> 2)) >>> 0; + b = fn(c, d, e, a, b, block[i + 3]); + d = ((d << 30) | (d >>> 2)) >>> 0; + a = fn(b, c, d, e, a, block[i + 4]); + c = ((c << 30) | (c >>> 2)) >>> 0; + } + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + } + + // Swap bytes for 32bits word + function swap32(b) { + return ((b & 0xff) << 24) + | ((b & 0xff00) << 8) + | ((b >> 8) & 0xff00) + | ((b >> 24) & 0xff); + } + + // input is a Uint8Array bitstream of the data + return function (data) { + var d = new Uint8Array(buffer(data)), dlen = d.length; + + // Pad the input string length. + var len = dlen + 9; + if (len % 64) { + len += 64 - (len % 64); + } + + state = new Uint32Array(5); + state[0] = 0x67452301; + state[1] = 0xefcdab89; + state[2] = 0x98badcfe; + state[3] = 0x10325476; + state[4] = 0xc3d2e1f0; + + for (var ofs = 0; ofs < len; ofs += 64) { + + // Copy input to block and write padding as needed + for (var i = 0; i < 64; i++) { + var b = 0, + o = ofs + i; + if (o < dlen) { + b = d[o]; + } + else if (o === dlen) { + b = 0x80; + } + else { + // Write original bit length as a 64bit big-endian integer to the end. + var x = len - o - 1; + if (x >= 0 && x < 4) { + b = (dlen << 3 >>> (x * 8)) & 0xff; + } + } + + // Interpret the input bytes as big-endian per the spec + if (i % 4 === 0) { + block[i >> 2] = b << 24; + } + else { + block[i >> 2] |= b << ((3 - (i % 4)) * 8); + } + } + + // Extend the block + for (var i = 16; i < 80; i++) { + var w = block[i - 3] ^ block[i - 8] ^ block[i - 14] ^ block[i - 16]; + block[i] = (w << 1) | (w >>> 31); + } + + cycle(state, block); + + } + + // Swap the bytes around since they are big endian internally + for (var i = 0; i < 5; i++) + state[i] = swap32(state[i]); + return state.buffer; + }; + +} // +)(); + +/** + * Algorithm name GOST R 34.11-HMAC

+ * + * HMAC with the specified hash function. + * @memberOf GostDigest + * @method sign + * @instance + * @param {ArrayBuffer} key The key for HMAC. + * @param {Hash} data Data + */ +function signHMAC(key, data) // +{ + // GOST R 34.11-94 - B=32b, L=32b + // GOST R 34.11-256 - B=64b, L=32b + // GOST R 34.11-512 - B=64b, L=64b + var b = (this.digest === digest94) ? 32 : 64, + l = this.bitLength / 8, + k = buffer(key), + d = buffer(data), k0; + if (k.byteLength === b) + k0 = new Uint8Array(k); + else { + var k0 = new Uint8Array(b); + if (k.byteLength > b) { + k0.set(new Uint8Array(this.digest(k))); + } else { + k0.set(new Uint8Array(k)); + } + } + var s0 = new Uint8Array(b + d.byteLength), + s1 = new Uint8Array(b + l); + for (var i = 0; i < b; i++) { + s0[i] = k0[i] ^ 0x36; + s1[i] = k0[i] ^ 0x5C; + } + s0.set(new Uint8Array(d), b); + s1.set(new Uint8Array(this.digest(s0)), b); + return this.digest(s1); +} // + +/** + * Algorithm name GOST R 34.11-HMAC

+ * + * Verify HMAC based on GOST R 34.11 hash + * + * @memberOf GostDigest + * @method verify + * @instance + * @param {(ArrayBuffer|TypedArray)} key Key which used for HMAC generation + * @param {(ArrayBuffer|TypedArray)} signature generated HMAC + * @param {(ArrayBuffer|TypedArray)} data Data + * @returns {boolean} HMAC verified = true + */ +function verifyHMAC(key, signature, data) // +{ + var hmac = new Uint8Array(this.sign(key, data)), + test = new Uint8Array(signature); + if (hmac.length !== test.length) + return false; + for (var i = 0, n = hmac.length; i < n; i++) + if (hmac[i] !== test[i]) + return false; + return true; +} // + + +/** + * Algorithm name GOST R 34.11-KDF

+ * + * Simple generate key 256/512 bit random seed for derivation algorithms + * + * @memberOf GostDigest + * @method generateKey + * @instance + * @returns {ArrayBuffer} Generated key + */ +function generateKey() // +{ + return getSeed(this.bitLength).buffer; +} // + +/** + * Algorithm name GOST R 34.11-PFXKDF

+ * + * Derive bits from password (PKCS12 mode) + *
    + *
  • algorithm.salt - random value, salt
  • + *
  • algorithm.iterations - number of iterations
  • + *
+ * @memberOf GostDigest + * @method deriveBits + * @instance + * @param {ArrayBuffer} baseKey - password after UTF-8 decoding + * @param {number} length output bit-length + * @returns {ArrayBuffer} result + */ +function deriveBitsPFXKDF(baseKey, length) // +{ + if (length % 8 > 0) + throw new DataError('Length must multiple of 8'); + var u = this.bitLength / 8, v = (this.digest === digest94) ? 32 : 64, + n = length / 8, r = this.iterations; + // 1. Construct a string, D (the "diversifier"), by concatenating v/8 + // copies of ID. + var ID = this.diversifier, D = new Uint8Array(v); + for (var i = 0; i < v; i++) + D[i] = ID; + // 2. Concatenate copies of the salt together to create a string S of + // length v(ceiling(s/v)) bits (the final copy of the salt may be + // truncated to create S). Note that if the salt is the empty + // string, then so is S. + var S0 = new Uint8Array(buffer(this.salt)), s = S0.length, + slen = v * Math.ceil(s / v), S = new Uint8Array(slen); + for (var i = 0; i < slen; i++) + S[i] = S0[i % s]; + // 3. Concatenate copies of the password together to create a string P + // of length v(ceiling(p/v)) bits (the final copy of the password + // may be truncated to create P). Note that if the password is the + // empty string, then so is P. + var P0 = new Uint8Array(buffer(baseKey)), p = P0.length, + plen = v * Math.ceil(p / v), P = new Uint8Array(plen); + for (var i = 0; i < plen; i++) + P[i] = P0[i % p]; + // 4. Set I=S||P to be the concatenation of S and P. + var I = new Uint8Array(slen + plen); + arraycopy(S, 0, I, 0, slen); + arraycopy(P, 0, I, slen, plen); + // 5. Set c=ceiling(n/u). + var c = Math.ceil(n / u); + // 6. For i=1, 2, ..., c, do the following: + var A = new Uint8Array(c * u); + for (var i = 0; i < c; i++) { + // A. Set A2=H^r(D||I). (i.e., the r-th hash of D||1, + // H(H(H(... H(D||I)))) + var H = new Uint8Array(v + slen + plen); + arraycopy(D, 0, H, 0, v); + arraycopy(I, 0, H, v, slen + plen); + for (var j = 0; j < r; j++) + H = new Uint8Array(this.digest(H)); + arraycopy(H, 0, A, i * u, u); + // B. Concatenate copies of Ai to create a string B of length v + // bits (the final copy of Ai may be truncated to create B). + var B = new Uint8Array(v); + for (var j = 0; j < v; j++) + B[j] = H[j % u]; + // C. Treating I as a concatenation I_0, I_1, ..., I_(k-1) of v-bit + // blocks, where k=ceiling(s/v)+ceiling(p/v), modify I by + // setting I_j=(I_j+B+1) mod 2^v for each j. + var k = (slen + plen) / v; + for (j = 0; j < k; j++) { + var cf = 1, w; + for (var l = v - 1; l >= 0; --l) { + w = I[v * j + l] + B[l] + cf; + cf = w >>> 8; + I[v * j + l] = w & 0xff; + } + } + } + // 7. Concatenate A_1, A_2, ..., A_c together to form a pseudorandom + // bit string, A. + // 8. Use the first n bits of A as the output of this entire process. + var R = new Uint8Array(n); + arraycopy(A, 0, R, 0, n); + return R.buffer; +} // + +/** + * Algorithm name GOST R 34.11-KDF

+ * + * Derive bits for KEK deversification in 34.10-2012 algorithm + * KDF(KEK, UKM, label) = HMAC256 (KEK, 0x01|label|0x00|UKM|0x01|0x00) + * Default label = 0x26|0xBD|0xB8|0x78 + * + * @memberOf GostDigest + * @method deriveBits + * @instance + * @param {(ArrayBuffer|TypedArray)} baseKey base key for deriviation + * @param {number} length output bit-length + * @returns {ArrayBuffer} result + */ +function deriveBitsKDF(baseKey, length) // +{ + if (length % 8 > 0) + throw new DataError('Length must be multiple of 8'); + var rlen = length / 8, label, context = new Uint8Array(buffer(this.context)), + blen = this.bitLength / 8, n = Math.ceil(rlen / blen); + if (this.label) + label = new Uint8Array(buffer(this.label)); + else + label = new Uint8Array([0x26, 0xBD, 0xB8, 0x78]); + var result = new Uint8Array(rlen); + for (var i = 0; i < n; i++) { + var data = new Uint8Array(label.length + context.length + 4); + data[0] = i + 1; + data.set(label, 1); + data[label.length + 1] = 0x00; + data.set(context, label.length + 2); + data[data.length - 2] = length >>> 8; + data[data.length - 1] = length & 0xff; + result.set(new Uint8Array(signHMAC.call(this, baseKey, data), 0, + i < n - 1 ? blen : rlen - i * blen), i * blen); + } + return result.buffer; +} // + +/** + * Algorithm name GOST R 34.11-PBKDF1

+ * + * Derive bits from password + *
    + *
  • algorithm.salt - random value, salt
  • + *
  • algorithm.iterations - number of iterations
  • + *
+ * @memberOf GostDigest + * @method deriveBits + * @instance + * @param {ArrayBuffer} baseKey - password after UTF-8 decoding + * @param {number} length output bit-length + * @returns {ArrayBuffer} result + */ +function deriveBitsPBKDF1(baseKey, length) // +{ + if (length < this.bitLength / 2 || length % 8 > 0) + throw new DataError('Length must be more than ' + this.bitLength / 2 + ' bits and multiple of 8'); + var hLen = this.bitLength / 8, dkLen = length / 8, + c = this.iterations, + P = new Uint8Array(buffer(baseKey)), + S = new Uint8Array(buffer(this.salt)), + slen = S.length, plen = P.length, + T = new Uint8Array(plen + slen), + DK = new Uint8Array(dkLen); + if (dkLen > hLen) + throw new DataError('Invalid parameters: Length value'); + arraycopy(P, 0, T, 0, plen); + arraycopy(S, 0, T, plen, slen); + for (var i = 0; i < c; i++) + T = new Uint8Array(this.digest(T)); + arraycopy(T, 0, DK, 0, dkLen); + return DK.buffer; +} // + +/** + * Algorithm name GOST R 34.11-PBKDF2

+ * + * Derive bits from password + *
    + *
  • algorithm.salt - random value, salt
  • + *
  • algorithm.iterations - number of iterations
  • + *
+ * @memberOf GostDigest + * @method deriveBits + * @instance + * @param {ArrayBuffer} baseKey - password after UTF-8 decoding + * @param {number} length output bit-length + * @returns {ArrayBuffer} result + */ +function deriveBitsPBKDF2(baseKey, length) // +{ + var diversifier = this.diversifier || 1; // For PKCS12 MAC required 3*length + length = length * diversifier; + if (length < this.bitLength / 2 || length % 8 > 0) + throw new DataError('Length must be more than ' + this.bitLength / 2 + ' bits and multiple of 8'); + var hLen = this.bitLength / 8, dkLen = length / 8, + c = this.iterations, + P = new Uint8Array(buffer(baseKey)), + S = new Uint8Array(buffer(this.salt)); + var slen = S.byteLength, + data = new Uint8Array(slen + 4); + arraycopy(S, 0, data, 0, slen); + + if (dkLen > (0xffffffff - 1) * 32) + throw new DataError('Invalid parameters: Length value'); + var n = Math.ceil(dkLen / hLen), + DK = new Uint8Array(dkLen); + for (var i = 1; i <= n; i++) { + data[slen] = i >>> 24 & 0xff; + data[slen + 1] = i >>> 16 & 0xff; + data[slen + 2] = i >>> 8 & 0xff; + data[slen + 3] = i & 0xff; + + var U = new Uint8Array(signHMAC.call(this, P, data)), Z = U; + for (var j = 1; j < c; j++) { + U = new Uint8Array(signHMAC.call(this, P, U)); + for (var k = 0; k < hLen; k++) + Z[k] = U[k] ^ Z[k]; + } + var ofs = (i - 1) * hLen; + arraycopy(Z, 0, DK, ofs, Math.min(hLen, dkLen - ofs)); + } + if (diversifier > 1) { + var rLen = dkLen / diversifier, R = new Uint8Array(rLen); + arraycopy(DK, dkLen - rLen, R, 0, rLen); + return R.buffer; + } else + return DK.buffer; +} // + +/** + * Algorithm name GOST R 34.11-CPKDF

+ * + * Derive bits from password. CryptoPro algorithm + *
    + *
  • algorithm.salt - random value, salt
  • + *
  • algorithm.iterations - number of iterations
  • + *
+ * @memberOf GostDigest + * @method deriveBits + * @instance + * @param {ArrayBuffer} baseKey - password after UTF-8 decoding + * @param {number} length output bit-length + * @returns {ArrayBuffer} result + */ +function deriveBitsCP(baseKey, length) { + if (length > this.bitLength || length % 8 > 0) + throw new DataError('Length can\'t be more than ' + this.bitLength + ' bits and multiple of 8'); + // GOST R 34.11-94 - B=32b, L=32b + // GOST R 34.11-256 - B=64b, L=32b + // GOST R 34.11-512 - B=64b, L=64b + var b = (this.digest === digest94) ? 32 : 64, + l = this.bitLength / 8, + p = baseKey && baseKey.byteLength > 0 ? new Uint8Array(buffer(baseKey)) : false, + plen = p ? p.length : 0, + iterations = this.iterations, + salt = new Uint8Array(buffer(this.salt)), + slen = salt.length, + d = new Uint8Array(slen + plen); + arraycopy(salt, 0, d, 0, slen); + if (p) + arraycopy(p, 0, d, slen, plen); + + var h = new Uint8Array(this.digest(d)), + k = new Uint8Array(b), + s0 = new Uint8Array(b), + s1 = new Uint8Array(b); + var c = 'DENEFH028.760246785.IUEFHWUIO.EF'; + for (var i = 0; i < c.length; i++) + k[i] = c.charCodeAt(i); + + d = new Uint8Array(2 * (b + l)); + for (var j = 0; j < iterations; j++) { + for (var i = 0; i < b; i++) { + s0[i] = k[i] ^ 0x36; + s1[i] = k[i] ^ 0x5C; + k[i] = 0; + } + arraycopy(s0, 0, d, 0, b); + arraycopy(h, 0, d, b, l); + arraycopy(s1, 0, d, b + l, b); + arraycopy(h, 0, d, b + l + b, l); + arraycopy(new Uint8Array(this.digest(d)), 0, k, 0, l); + } + for (var i = 0; i < l; i++) { + s0[i] = k[i] ^ 0x36; + s1[i] = k[i] ^ 0x5C; + k[i] = 0; + } + d = new Uint8Array(2 * l + slen + plen); + arraycopy(s0, 0, d, 0, l); + arraycopy(salt, 0, d, l, slen); + arraycopy(s1, 0, d, l + slen, l); + if (p) + arraycopy(p, 0, d, l + slen + l, plen); + h = this.digest(this.digest(d)); + if (length === this.bitLength) + return h; + else { + var rlen = length / 8, r = new Uint8Array(rlen); + arraycopy(h, 0, r, 0, rlen); + return r.buffer; + } +} + +/** + * Algorithm name GOST R 34.11-KDF or GOST R 34.11-PBKDF2 or other

+ * + * Derive key from derive bits subset + * + * @memberOf GostDigest + * @method deriveKey + * @instance + * @param {ArrayBuffer} baseKey + * @returns {ArrayBuffer} + */ +function deriveKey(baseKey) // +{ + return this.deriveBits(baseKey, this.keySize * 8); +} // + +/** + * GOST R 34.11 Algorithm

+ * + * References: {@link http://tools.ietf.org/html/rfc6986} and {@link http://tools.ietf.org/html/rfc5831}

+ * + * Normalized algorithm identifier common parameters: + * + *
    + *
  • name Algorithm name 'GOST R 34.11'
  • + *
  • version Algorithm version + *
      + *
    • 1994 old-style 256 bits digest based on GOST 28147-89
    • + *
    • 2012 256 ro 512 bits digest algorithm "Streebog" GOST R 34.11-2012 (default)
    • + *
    + *
  • + *
  • length Digest length + *
      + *
    • 256 256 bits digest
    • + *
    • 512 512 bits digest, valid only for algorithm "Streebog"
    • + *
    + *
  • + *
  • mode Algorithm mode + *
      + *
    • HASH simple digest mode (default)
    • + *
    • HMAC HMAC algorithm based on GOST R 34.11
    • + *
    • KDF Derive bits for KEK deversification
    • + *
    • PBKDF2 Password based key dirivation algorithms PBKDF2 (based on HMAC)
    • + *
    • PFXKDF Password based PFX key dirivation algorithms
    • + *
    • CPKDF CpyptoPro Password based key dirivation algorithms
    • + *
    + *
  • + *
  • sBox Paramset sBox for GOST 28147-89. Used only if version = 1994
  • + *
+ * + * Supported algorithms, modes and parameters: + * + *
    + *
  • Digest HASH mode (default)
  • + *
  • Sign/Verify HMAC modes parameters depends on version and length + *
      + *
    • version: 1994 HMAC parameters (B = 32, L = 32)
    • + *
    • version: 2012, length: 256 HMAC parameters (B = 64, L = 32)
    • + *
    • version: 2012, length: 512 HMAC parameters (B = 64, L = 64)
    • + *
    + *
  • + *
  • DeriveBits/DeriveKey KDF mode + *
      + *
    • context {@link CryptoOperationData} Context of the key derivation
    • + *
    • label {@link CryptoOperationData} Label that identifies the purpose for the derived keying material
    • + *
    + *
  • + *
  • DeriveBits/DeriveKey PBKDF2 mode + *
      + *
    • salt {@link CryptoOperationData} Random salt as input for HMAC algorithm
    • + *
    • iterations Iteration count. GOST recomended value 1000 (default) or 2000
    • + *
    • diversifier Deversifier, ID=1 - key material for performing encryption or decryption, ID=3 - integrity key for MACing
    • + *
    + *
  • + *
  • DeriveBits/DeriveKey PFXKDF mode + *
      + *
    • salt {@link CryptoOperationData} Random salt as input for HMAC algorithm
    • + *
    • iterations Iteration count. GOST recomended value 1000 (default) or 2000
    • + *
    • diversifier Deversifier, ID=1 - key material for performing encryption or decryption, + * ID=2 - IV (Initial Value) for encryption or decryption, ID=3 - integrity key for MACing
    • + *
    + *
  • + *
  • DeriveBits/DeriveKey CPKDF mode + *
      + *
    • salt {@link CryptoOperationData} Random salt as input for HMAC algorithm
    • + *
    • iterations Iteration count. GOST recomended value 1000 (default) or 2000
    • + *
    + *
  • + *
+ * + * @class GostDigest + * @param {AlgorithmIdentifier} algorithm WebCryptoAPI algorithm identifier + */ +function GostDigest(algorithm) // +{ + + algorithm = algorithm || {}; + + this.name = (algorithm.name || 'GOST R 34.10') + '-' + ((algorithm.version || 2012) % 100) + + ((algorithm.version || 2012) > 1 ? '-' + (algorithm.length || 256) : '') + + (((algorithm.mode || 'HASH') !== 'HASH') ? '-' + algorithm.mode : '') + + (algorithm.procreator ? '/' + algorithm.procreator : '') + + (typeof algorithm.sBox === 'string' ? '/' + algorithm.sBox : ''); + + // Algorithm procreator + this.procreator = algorithm.procreator; + + // Bit length + this.bitLength = algorithm.length || 256; + + switch (algorithm.version || 2012) { + case 1: // SHA-1 + this.digest = digestSHA1; + this.bitLength = 160; + break; + case 1994: + this.digest = digest94; + // Define chiper algorithm + this.sBox = (algorithm.sBox || (algorithm.procreator === 'SC' ? 'D-SC' : 'D-A')).toUpperCase(); + + //if (!GostCipher) + // GostCipher = root.GostCipher; + if (!GostCipher) + throw new NotSupportedError('Object GostCipher not found'); + + this.cipher = new GostCipher({ + name: 'GOST 28147', + block: 'ECB', + sBox: this.sBox, + procreator: this.procreator + }); + + break; + case 2012: + this.digest = digest2012; + break; + default: + throw new NotSupportedError('Algorithm version ' + algorithm.version + ' not supported'); + } + + // Key size + this.keySize = algorithm.keySize || (algorithm.version <= 2 ? this.bitLength / 8 : 32); + + switch (algorithm.mode || 'HASH') { + case 'HASH': + break; + case 'HMAC': + this.sign = signHMAC; + this.verify = verifyHMAC; + this.generateKey = generateKey; + break; + case 'KDF': + this.deriveKey = deriveKey; + this.deriveBits = deriveBitsKDF; + this.label = algorithm.label; + this.context = algorithm.context; + break; + case 'PBKDF2': + this.deriveKey = deriveKey; + this.deriveBits = deriveBitsPBKDF2; + this.generateKey = generateKey; + this.salt = algorithm.salt; + this.iterations = algorithm.iterations || 2000; + this.diversifier = algorithm.diversifier || 1; + break; + case 'PFXKDF': + this.deriveKey = deriveKey; + this.deriveBits = deriveBitsPFXKDF; + this.generateKey = generateKey; + this.salt = algorithm.salt; + this.iterations = algorithm.iterations || 2000; + this.diversifier = algorithm.diversifier || 1; + break; + case 'CPKDF': + this.deriveKey = deriveKey; + this.deriveBits = deriveBitsCP; + this.generateKey = generateKey; + this.salt = algorithm.salt; + this.iterations = algorithm.iterations || 2000; + break; + default: + throw new NotSupportedError('Algorithm mode ' + algorithm.mode + ' not supported'); + } +} // + +export default GostDigest; diff --git a/plugins/srktoolbox/src/core/vendor/gost/gostEngine.mjs b/plugins/srktoolbox/src/core/vendor/gost/gostEngine.mjs new file mode 100644 index 00000000..b54819c6 --- /dev/null +++ b/plugins/srktoolbox/src/core/vendor/gost/gostEngine.mjs @@ -0,0 +1,451 @@ +/** + * @file GOST 34.10-2012 signature function with 1024/512 bits digest + * @version 1.76 + * @copyright 2014-2016, Rudolf Nickolaev. All rights reserved. + */ + +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 HOLDER 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. + * + */ + +import GostRandom from './gostRandom.mjs'; +import GostCipher from './gostCipher.mjs'; +import GostDigest from './gostDigest.mjs'; +import GostSign from './gostSign.mjs'; + +/* + * Engine definition base on normalized algorithm identifier + * + */ // + +var root = {}; + +// Define engine +function defineEngine(method, algorithm) { + if (!algorithm) + throw new (root.SyntaxError || Error)('Algorithm not defined'); + + if (!algorithm.name) + throw new (root.SyntaxError || Error)('Algorithm name not defined'); + + var name = algorithm.name, mode = algorithm.mode; + if ((name === 'GOST 28147' || name === 'GOST R 34.12' || name === 'RC2') && (method === 'generateKey' || + (mode === 'MAC' && (method === 'sign' || method === 'verify')) || + ((mode === 'KW' || mode === 'MASK') && (method === 'wrapKey' || method === 'unwrapKey')) || + ((!mode || mode === 'ES') && (method === 'encrypt' || method === 'decrypt')))) { + return 'GostCipher'; + + } else if ((name === 'GOST R 34.11' || name === 'SHA') && (method === 'digest' || + (mode === 'HMAC' && (method === 'sign' || method === 'verify' || method === 'generateKey')) || + ((mode === 'KDF' || mode === 'PBKDF2' || mode === 'PFXKDF' || mode === 'CPKDF') && + (method === 'deriveKey' || method === 'deriveBits' || method === 'generateKey')))) { + return 'GostDigest'; + + } else if (name === 'GOST R 34.10' && (method === 'generateKey' || + ((!mode || mode === 'SIGN') && (method === 'sign' || method === 'verify')) || + (mode === 'MASK' && (method === 'wrapKey' || method === 'unwrapKey')) || + (mode === 'DH' && (method === 'deriveKey' || method === 'deriveBits')))) { + return 'GostSign'; + } else + throw new (root.NotSupportedError || Error)('Algorithm ' + name + '-' + mode + ' is not valid for ' + method); +} // + +/** + * Object implements dedicated Web Workers and provide a simple way to create + * and run GOST cryptographic algorithms in background thread. + * + * Object provide interface to GOST low-level cryptogric classes: + *
    + *
  • GostCipher - implementation of GOST 28147, GOST R 34.12, GOST R 34.13 Encryption algorithms. Reference {@link http://tools.ietf.org/html/rfc5830}
  • + *
  • GostDigest - implementation of GOST R 34.11 Hash Function algorithms. References {@link http://tools.ietf.org/html/rfc5831} and {@link http://tools.ietf.org/html/rfc6986}
  • + *
  • GostSign - implementation of GOST R 34.10 Digital Signature algorithms. References {@link http://tools.ietf.org/html/rfc5832} and {@link http://tools.ietf.org/html/rfc7091}
  • + *
+ * @namespace gostEngine + */ +var gostEngine = { + /** + * gostEngine.execute(algorithm, method, args) Entry point to execution + * all low-level GOST cryptographic methods + * + *
    + *
  • Determine the appropriate engine for a given execution method
  • + *
  • Create cipher object for determineted engine
  • + *
  • Execute method of cipher with given args
  • + *
+ * + * @memberOf gostEngine + * @param {AlgorithmIndentifier} algorithm Algorithm identifier + * @param {string} method Crypto method for execution + * @param {Array} args Method arguments (keys, data, additional parameters) + * @returns {(CryptoOperationData|Key|KeyPair|boolean)} Result of method execution + */ + execute: function (algorithm, method, args) // + { + // Define engine for GOST algorithms + var engine = defineEngine(method, algorithm); + // Create cipher + var cipher = this['get' + engine](algorithm); + // Execute method + return cipher[method].apply(cipher, args); + }, // + /** + * gostEngine.getGostCipher(algorithm) returns GOST 28147 / GOST R 34.12 cipher instance

+ * + * GOST 28147-89 / GOST R 34.12-15 Encryption Algorithm

+ * When keys and initialization vectors are converted to/from byte arrays, + * little-endian byte order is assumed.

+ * + * Normalized algorithm identifier common parameters: + * + *
    + *
  • name Algorithm name 'GOST 28147' or 'GOST R 34.12'
  • + *
  • version Algorithm version, number + *
      + *
    • 1989 Current version of standard
    • + *
    • 2015 New draft version of standard
    • + *
    + *
  • + *
  • length Block length + *
      + *
    • 64 64 bits length (default)
    • + *
    • 128 128 bits length (only for version 2015)
    • + *
    + *
  • + *
  • mode Algorithm mode, string + *
      + *
    • ES Encryption mode (default)
    • + *
    • MAC "imitovstavka" (MAC) mode
    • + *
    • KW Key wrapping mode
    • + *
    • MASK Key mask mode
    • + *
    + *
  • + *
  • sBox Paramset sBox for GOST 28147-89, string. Used only if version = 1989
  • + *
+ * + * Supported algorithms, modes and parameters: + * + *
    + *
  • Encript/Decrypt mode (ES) + *
      + *
    • block Block mode, string. Default ECB
    • + *
    • keyMeshing Key meshing mode, string. Default NO
    • + *
    • padding Padding mode, string. Default NO for CFB and CTR modes, or ZERO for others
    • + *
    • iv {@link CryptoOperationData} Initial vector with length of block. Default - zero block
    • + *
    + *
  • + *
  • Sign/Verify mode (MAC) + *
      + *
    • macLength Length of mac in bits (default - 32 bits)
    • + *
    • iv {@link CryptoOperationData} Initial vector with length of block. Default - zero block
    • + *
    + *
  • + *
  • Wrap/Unwrap key mode (KW) + *
      + *
    • keyWrapping Mode of keywrapping, string. Default NO - standard GOST key wrapping
    • + *
    • ukm {@link CryptoOperationData} User key material. Default - random generated value
    • + *
    + *
  • + *
  • Wrap/Unwrap key mode (MASK)
  • + *
+ * + * Supported paramters values: + * + *
    + *
  • Block modes (parameter 'block') + *
      + *
    • ECB "prostaya zamena" (ECB) mode (default)
    • + *
    • CFB "gammirovanie s obratnoj svyaziyu" (64-bit CFB) mode
    • + *
    • CTR "gammirovanie" (counter) mode
    • + *
    • CBC Cipher-Block-Chaining (CBC) mode
    • + *
    + *
  • + *
  • Key meshing modes (parameter 'keyMeshing') + *
      + *
    • NO No key wrapping (default)
    • + *
    • CP CryptoPor Key key meshing
    • + *
    + *
  • + *
  • Padding modes (parameter 'padding') + *
      + *
    • NO No padding only for CFB and CTR modes
    • + *
    • PKCS5 PKCS#5 padding mode
    • + *
    • ZERO Zero bits padding mode
    • + *
    • RANDOM Random bits padding mode
    • + *
    + *
  • + *
  • Wrapping key modes (parameter 'keyWrapping') + *
      + *
    • NO Ref. rfc4357 6.1 GOST 28147-89 Key wrapping
    • + *
    • CP CryptoPro Key wrapping mode
    • + *
    • SC SignalCom Key wrapping mode
    • + *
    + *
  • + *
+ * + * @memberOf gostEngine + * @param {AlgorithmIndentifier} algorithm Algorithm identifier + * @returns {GostCipher} Instance of GostCipher + */ + getGostCipher: function (algorithm) // + { + return new (GostCipher || (GostCipher = root.GostCipher))(algorithm); + }, // + /** + * gostEngine.getGostDigest(algorithm) returns GOST R 34.11 cipher instance

+ * + * Normalized algorithm identifier common parameters: + * + *
    + *
  • name Algorithm name 'GOST R 34.11'
  • + *
  • version Algorithm version + *
      + *
    • 1994 old-style 256 bits digest based on GOST 28147-89
    • + *
    • 2012 256 ro 512 bits digest algorithm "Streebog" GOST R 34.11-2012 (default)
    • + *
    + *
  • + *
  • length Digest length + *
      + *
    • 256 256 bits digest
    • + *
    • 512 512 bits digest, valid only for algorithm "Streebog"
    • + *
    + *
  • + *
  • mode Algorithm mode + *
      + *
    • HASH simple digest mode (default)
    • + *
    • HMAC HMAC algorithm based on GOST R 34.11
    • + *
    • KDF Derive bits for KEK deversification
    • + *
    • PBKDF2 Password based key dirivation algorithms PBKDF2 (based on HMAC)
    • + *
    • PFXKDF PFX key dirivation algorithms PFXKDF
    • + *
    • CPKDF CryptoPro Password based key dirivation algorithms
    • + *
    + *
  • + *
  • sBox Paramset sBox for GOST 28147-89. Used only if version = 1994
  • + *
+ * + * Supported algorithms, modes and parameters: + * + *
    + *
  • Digest HASH mode (default)
  • + *
  • Sign/Verify HMAC modes parameters depends on version and length + *
      + *
    • version: 1994 HMAC parameters (B = 32, L = 32)
    • + *
    • version: 2012, length: 256 HMAC parameters (B = 64, L = 32)
    • + *
    • version: 2012, length: 512 HMAC parameters (B = 64, L = 64)
    • + *
    + *
  • + *
  • DeriveBits/DeriveKey KDF mode + *
      + *
    • context {@link CryptoOperationData} Context of the key derivation
    • + *
    • label {@link CryptoOperationData} Label that identifies the purpose for the derived keying material
    • + *
    + *
  • + *
  • DeriveBits/DeriveKey PBKDF2 mode + *
      + *
    • salt {@link CryptoOperationData} Random salt as input for HMAC algorithm
    • + *
    • iterations Iteration count. GOST recomended value 1000 (default) or 2000
    • + *
    + *
  • + *
  • DeriveBits/DeriveKey PFXKDF mode + *
      + *
    • salt {@link CryptoOperationData} Random salt as input for HMAC algorithm
    • + *
    • iterations Iteration count. GOST recomended value 1000 (default) or 2000
    • + *
    • diversifier Deversifier, ID=1 - key material for performing encryption or decryption, + * ID=2 - IV (Initial Value) for encryption or decryption, ID=3 - integrity key for MACing
    • + *
    + *
  • + *
  • DeriveBits/DeriveKey CPKDF mode + *
      + *
    • salt {@link CryptoOperationData} Random salt as input for HMAC algorithm
    • + *
    • iterations Iteration count. GOST recomended value 1000 (default) or 2000
    • + *
    + *
  • + *
+ * + * @memberOf gostEngine + * @param {AlgorithmIndentifier} algorithm Algorithm identifier + * @returns {GostDigest} Instance of GostDigest + */ + getGostDigest: function (algorithm) // + { + return new (GostDigest || (GostDigest = root.GostDigest))(algorithm); + }, // + /** + * gostEngine.getGostSign(algorithm) returns GOST R 34.10 cipher instance

+ * + * Normalized algorithm identifier common parameters: + * + *
    + *
  • name Algorithm name 'GOST R 34.10'
  • + *
  • version Algorithm version + *
      + *
    • 1994 - Old-style GOST R 34.10-94 ExpMod algorithm with GOST R 34.11-94 hash
    • + *
    • 2001 - GOST R 34.10-2001 Eliptic curve algorithm with old GOST R 34.11-94 hash
    • + *
    • 2012 - GOST R 34.10-2012 Eliptic curve algorithm with GOST R 34.11-12 hash, default mode
    • + *
    + *
  • + *
  • length Length of hash and signature. Key length == hash length for EC algorithms and 2 * hash length for ExpMod algorithm + *
      + *
    • GOST R 34.10-256 - 256 bits digest, default mode
    • + *
    • GOST R 34.10-512 - 512 bits digest only for GOST R 34.11-2012 hash
    • + *
    + *
  • + *
  • mode Algorithm mode + *
      + *
    • SIGN Digital signature mode (default)
    • + *
    • DH Diffie-Hellman key generation and key agreement mode
    • + *
    • MASK Key mask mode
    • + *
    + *
  • + *
  • sBox Paramset sBox for GOST 34.11-94. Used only if version = 1994 or 2001
  • + *
+ * + * Supported algorithms, modes and parameters: + * + *
    + *
  • Sign/Verify mode (SIGN)
  • + *
  • Wrap/Unwrap mode (MASK)
  • + *
  • DeriveKey/DeriveBits mode (DH) + *
      + *
    • {@link CryptoOperationData} ukm User key material. Default - random generated value
    • + *
    • {@link CryptoOperationData} public The peer's EC public key data
    • + *
    + *
  • + *
  • GenerateKey mode (SIGN and DH and MASK) version = 1994 + *
      + *
    • namedParam Paramset for key generation algorithm. If specified no additianal parameters required
    • + *
    + * Additional parameters, if namedParam not specified + *
      + *
    • modulusLength Bit length of p (512 or 1024 bits). Default = 1024
    • + *
    • p {@link CryptoOperationData} Modulus, prime number, 2^(t-1) + *
    • q {@link CryptoOperationData} Order of cyclic group, prime number, 2^254 + *
    • a {@link CryptoOperationData} Generator, integer, 1 + *
    + *
  • + *
  • GenerateKey mode (SIGN and DH and MASK) version = 2001 or 2012 + *
      + *
    • namedCurve Paramset for key generation algorithm. If specified no additianal parameters required
    • + *
    + * Additional EC parameters, if namedCurve not specified + *
      + *
    • p {@link CryptoOperationData} Prime number - elliptic curve modulus
    • + *
    • a {@link CryptoOperationData} Coefficients a of the elliptic curve E
    • + *
    • b {@link CryptoOperationData} Coefficients b of the elliptic curve E
    • + *
    • q {@link CryptoOperationData} Prime number - order of cyclic group
    • + *
    • x {@link CryptoOperationData} Base point p x-coordinate
    • + *
    • y {@link CryptoOperationData} Base point p y-coordinate
    • + *
    + *
  • + *
+ * + * @memberOf gostEngine + * @param {AlgorithmIndentifier} algorithm Algorithm identifier + * @returns {GostSign} Instance of GostSign + */ + getGostSign: function (algorithm) // + { + return new (GostSign || (GostSign = root.GostSign))(algorithm); + } // +}; + +/* + * Worker method execution + * + */ // + +// Worker for gostCripto method execution +if (root.importScripts) { + + /** + * Method called when {@link SubtleCrypto} calls its own postMessage() + * method with data parameter: algorithm, method and arg.
+ * Call method execute and postMessage() results to onmessage event handler + * in the main process.
+ * If error occurred onerror event handler executed in main process. + * + * @memberOf gostEngine + * @name onmessage + * @param {MessageEvent} event Message event with data {algorithm, method, args} + */ + root.onmessage = function (event) { + try { + postMessage({ + id: event.data.id, + result: gostEngine.execute(event.data.algorithm, + event.data.method, event.data.args)}); + } catch (e) { + postMessage({ + id: event.data.id, + error: e.message + }); + } + }; +} else { + + // Load dependens + var baseUrl = '', nameSuffix = ''; + // Try to define from DOM model + if (typeof document !== 'undefined') { + (function () { + var regs = /^(.*)gostCrypto(.*)\.js$/i; + var list = document.querySelectorAll('script'); + for (var i = 0, n = list.length; i < n; i++) { + var value = list[i].getAttribute('src'); + var test = regs.exec(value); + if (test) { + baseUrl = test[1]; + nameSuffix = test[2]; + } + } + })(); + } + + // Local importScripts procedure for include dependens + var importScripts = function () { + for (var i = 0, n = arguments.length; i < n; i++) { + var name = arguments[i].split('.'), + src = baseUrl + name[0] + nameSuffix + '.' + name[1]; + var el = document.querySelector('script[src="' + src + '"]'); + if (!el) { + el = document.createElement('script'); + el.setAttribute('src', src); + document.head.appendChild(el); + } + } + }; + + // Import engines + if (!GostRandom) + importScripts('gostRandom.js'); + if (!GostCipher) + importScripts('gostCipher.js'); + if (!GostDigest) + importScripts('gostDigest.js'); + if (!GostSign) + importScripts('gostSign.js'); +} //
+ +export default gostEngine; + diff --git a/plugins/srktoolbox/src/core/vendor/gost/gostRandom.mjs b/plugins/srktoolbox/src/core/vendor/gost/gostRandom.mjs new file mode 100644 index 00000000..f9a38380 --- /dev/null +++ b/plugins/srktoolbox/src/core/vendor/gost/gostRandom.mjs @@ -0,0 +1,128 @@ +/** + * Implementation Web Crypto random generatore for GOST algorithms + * 1.76 + * 2014-2016, Rudolf Nickolaev. All rights reserved. + * + * Exported for CyberChef by mshwed [m@ttshwed.com] + */ + +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 HOLDER 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. + * + */ +import crypto from 'crypto'; + + +/** + * The gostCrypto provide general purpose cryptographic functionality for + * GOST standards including a cryptographically strong pseudo-random number + * generator seeded with truly random values. + * + * @Class GostRandom + * + */ // + +var root = {}; +var rootCrypto = crypto; + +var TypeMismatchError = Error; +var QuotaExceededError = Error; + +// Initialize mouse and time counters for random generator +var randomRing = { + seed: new Uint8Array(1024), + getIndex: 0, + setIndex: 0, + set: function (x) { + if (this.setIndex >= 1024) + this.setIndex = 0; + this.seed[this.setIndex++] = x; + }, + get: function () { + if (this.getIndex >= 1024) + this.getIndex = 0; + return this.seed[this.getIndex++]; + } +}; + +if (typeof document !== 'undefined') { + try { + // Mouse move event to fill random array + document.addEventListener('mousemove', function (e) { + randomRing.set((Date.now() & 255) ^ + ((e.clientX || e.pageX) & 255) ^ + ((e.clientY || e.pageY) & 255)); + }, false); + } catch (e) { + } + + try { + // Keypress event to fill random array + document.addEventListener('keydown', function (e) { + randomRing.set((Date.now() & 255) ^ + (e.keyCode & 255)); + }, false); + } catch (e) { + } +} // + +function GostRandom() { +} + +/** + * The getRandomValues method generates cryptographically random values.

+ * + * Random generator based on JavaScript Web Crypto random genereator + * (if it is possible) or Math.random mixed with time and parameters of + * mouse and keyboard events + * + * @memberOf GostRandom + * @param {(ArrayBuffer|ArrayBufferView)} array Destination buffer for random data + */ +GostRandom.prototype.getRandomValues = function (array) // +{ + + if (!array.byteLength) + throw new TypeMismatchError('Array is not of an integer type (Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array, or Uint32Array)'); + + if (array.byteLength > 65536) + throw new QuotaExceededError('Byte length of array can\'t be greate then 65536'); + + var u8 = new Uint8Array(array.buffer, array.byteOffset, array.byteLength); + if (rootCrypto && rootCrypto.getRandomValues) { + // Native window cryptographic interface + rootCrypto.getRandomValues(u8); + } else { + // Standard Javascript method + for (var i = 0, n = u8.length; i < n; i++) + u8[i] = Math.floor(256 * Math.random()) & 255; + } + + // Mix bio randomizator + for (var i = 0, n = u8.length; i < n; i++) + u8[i] = u8[i] ^ randomRing.get(); + return array; +}; // + +export default GostRandom; diff --git a/plugins/srktoolbox/src/core/vendor/gost/gostSign.mjs b/plugins/srktoolbox/src/core/vendor/gost/gostSign.mjs new file mode 100644 index 00000000..36a87f63 --- /dev/null +++ b/plugins/srktoolbox/src/core/vendor/gost/gostSign.mjs @@ -0,0 +1,2023 @@ +/** + * @file GOST 34.10-2012 signature function with 1024/512 bits digest + * @version 1.76 + * @copyright 2014-2016, Rudolf Nickolaev. All rights reserved. + */ + +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Used library JSBN http://www-cs-students.stanford.edu/~tjw/jsbn/ + * Copyright (c) 2003-2005 Tom Wu (tjw@cs.Stanford.EDU) + * + * 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 HOLDER 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. + * + */ + + import GostRandom from './gostRandom.mjs'; + import GostDigest from './gostDigest.mjs'; + + import crypto from 'crypto'; + + /* + * Predefined curves and params collection + * + * http://tools.ietf.org/html/rfc5832 + * http://tools.ietf.org/html/rfc7091 + * http://tools.ietf.org/html/rfc4357 + * + */ // + +var root = {}; +var rootCrypto = crypto; +var CryptoOperationData = ArrayBuffer; + +var OperationError = Error, + DataError = Error, + NotSupportedError = Error; + +// Predefined named curve collection +var ECGostParams = { + 'S-256-TEST': { + a: 7, + b: '0x5FBFF498AA938CE739B8E022FBAFEF40563F6E6A3472FC2A514C0CE9DAE23B7E', + p: '0x8000000000000000000000000000000000000000000000000000000000000431', + q: '0x8000000000000000000000000000000150FE8A1892976154C59CFC193ACCF5B3', + x: 2, + y: '0x8E2A8A0E65147D4BD6316030E16D19C85C97F0A9CA267122B96ABBCEA7E8FC8' + }, + 'S-256-A': { + a: '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD94', + b: 166, + p: '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD97', + q: '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6C611070995AD10045841B09B761B893', + x: 1, + y: '0x8D91E471E0989CDA27DF505A453F2B7635294F2DDF23E3B122ACC99C9E9F1E14' + }, + 'S-256-B': { + a: '0x8000000000000000000000000000000000000000000000000000000000000C96', + b: '0x3E1AF419A269A5F866A7D3C25C3DF80AE979259373FF2B182F49D4CE7E1BBC8B', + p: '0x8000000000000000000000000000000000000000000000000000000000000C99', + q: '0x800000000000000000000000000000015F700CFFF1A624E5E497161BCC8A198F', + x: 1, + y: '0x3FA8124359F96680B83D1C3EB2C070E5C545C9858D03ECFB744BF8D717717EFC' + }, + 'S-256-C': { + a: '0x9B9F605F5A858107AB1EC85E6B41C8AACF846E86789051D37998F7B9022D7598', + b: 32858, + p: '0x9B9F605F5A858107AB1EC85E6B41C8AACF846E86789051D37998F7B9022D759B', + q: '0x9B9F605F5A858107AB1EC85E6B41C8AA582CA3511EDDFB74F02F3A6598980BB9', + x: 0, + y: '0x41ECE55743711A8C3CBF3783CD08C0EE4D4DC440D4641A8F366E550DFDB3BB67' + }, + 'P-256': { + p: '0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF', + a: '0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC', + b: '0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B', + x: '0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296', + y: '0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5', + q: '0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551' + }, + 'T-512-TEST': { + a: 7, + b: '0x1CFF0806A31116DA29D8CFA54E57EB748BC5F377E49400FDD788B649ECA1AC4361834013B2AD7322480A89CA58E0CF74BC9E540C2ADD6897FAD0A3084F302ADC', + p: '0x4531ACD1FE0023C7550D267B6B2FEE80922B14B2FFB90F04D4EB7C09B5D2D15DF1D852741AF4704A0458047E80E4546D35B8336FAC224DD81664BBF528BE6373', + q: '0x4531ACD1FE0023C7550D267B6B2FEE80922B14B2FFB90F04D4EB7C09B5D2D15DA82F2D7ECB1DBAC719905C5EECC423F1D86E25EDBE23C595D644AAF187E6E6DF', + x: '0x24D19CC64572EE30F396BF6EBBFD7A6C5213B3B3D7057CC825F91093A68CD762FD60611262CD838DC6B60AA7EEE804E28BC849977FAC33B4B530F1B120248A9A', + y: '0x2BB312A43BD2CE6E0D020613C857ACDDCFBF061E91E5F2C3F32447C259F39B2C83AB156D77F1496BF7EB3351E1EE4E43DC1A18B91B24640B6DBB92CB1ADD371E' + }, + 'T-512-A': { + p: '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDC7', + a: '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDC4', + b: '0xE8C2505DEDFC86DDC1BD0B2B6667F1DA34B82574761CB0E879BD081CFD0B6265EE3CB090F30D27614CB4574010DA90DD862EF9D4EBEE4761503190785A71C760', + q: '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF27E69532F48D89116FF22B8D4E0560609B4B38ABFAD2B85DCACDB1411F10B275', + x: 3, + y: '0x7503CFE87A836AE3A61B8816E25450E6CE5E1C93ACF1ABC1778064FDCBEFA921DF1626BE4FD036E93D75E6A50E3A41E98028FE5FC235F5B889A589CB5215F2A4' + }, + 'T-512-B': { + p: '0x8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006F', + a: '0x8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006C', + b: '0x687D1B459DC841457E3E06CF6F5E2517B97C7D614AF138BCBF85DC806C4B289F3E965D2DB1416D217F8B276FAD1AB69C50F78BEE1FA3106EFB8CCBC7C5140116', + q: '0x800000000000000000000000000000000000000000000000000000000000000149A1EC142565A545ACFDB77BD9D40CFA8B996712101BEA0EC6346C54374F25BD', + x: 2, + y: '0x1A8F7EDA389B094C2C071E3647A8940F3C123B697578C213BE6DD9E6C8EC7335DCB228FD1EDF4A39152CBCAAF8C0398828041055F94CEEEC7E21340780FE41BD' + } +}; +ECGostParams['X-256-A'] = ECGostParams['S-256-A']; +ECGostParams['X-256-B'] = ECGostParams['S-256-C']; +ECGostParams['T-256-TEST'] = ECGostParams['S-256-TEST']; +ECGostParams['T-256-A'] = ECGostParams['S-256-A']; +ECGostParams['T-256-B'] = ECGostParams['S-256-B']; +ECGostParams['T-256-C'] = ECGostParams['S-256-C']; + + +var GostParams = { + 'S-TEST': { + modulusLength: 512, // bit length of p (512 or 1024 bits) + p: '0xEE8172AE8996608FB69359B89EB82A69854510E2977A4D63BC97322CE5DC3386EA0A12B343E9190F23177539845839786BB0C345D165976EF2195EC9B1C379E3', + q: '0x98915E7EC8265EDFCDA31E88F24809DDB064BDC7285DD50D7289F0AC6F49DD2D', + a: '0x9e96031500c8774a869582d4afde2127afad2538b4b6270a6f7c8837b50d50f206755984a49e509304d648be2ab5aab18ebe2cd46ac3d8495b142aa6ce23e21c' + }, + 'S-A': { + modulusLength: 1024, + p: '0xB4E25EFB018E3C8B87505E2A67553C5EDC56C2914B7E4F89D23F03F03377E70A2903489DD60E78418D3D851EDB5317C4871E40B04228C3B7902963C4B7D85D52B9AA88F2AFDBEB28DA8869D6DF846A1D98924E925561BD69300B9DDD05D247B5922D967CBB02671881C57D10E5EF72D3E6DAD4223DC82AA1F7D0294651A480DF', + q: '0x972432A437178B30BD96195B773789AB2FFF15594B176DD175B63256EE5AF2CF', + a: '0x8FD36731237654BBE41F5F1F8453E71CA414FFC22C25D915309E5D2E62A2A26C7111F3FC79568DAFA028042FE1A52A0489805C0DE9A1A469C844C7CABBEE625C3078888C1D85EEA883F1AD5BC4E6776E8E1A0750912DF64F79956499F1E182475B0B60E2632ADCD8CF94E9C54FD1F3B109D81F00BF2AB8CB862ADF7D40B9369A' + }, + 'S-B': { + modulusLength: 1024, + p: '0xC6971FC57524B30C9018C5E621DE15499736854F56A6F8AEE65A7A404632B1BCF0349FFCAFCB0A103177971FC1612ADCDB8C8CC938C70225C8FD12AFF01B1D064E0AD6FDE6AB9159166CB9F2FC171D92F0CC7B6A6B2CD7FA342ACBE2C9315A42D576B1ECCE77A963157F3D0BD96A8EB0B0F3502AD238101B05116334F1E5B7AB', + q: '0xB09D634C10899CD7D4C3A7657403E05810B07C61A688BAB2C37F475E308B0607', + a: '0x3D26B467D94A3FFC9D71BF8DB8934084137264F3C2E9EB16DCA214B8BC7C872485336744934FD2EF5943F9ED0B745B90AA3EC8D70CDC91682478B664A2E1F8FB56CEF2972FEE7EDB084AF746419B854FAD02CC3E3646FF2E1A18DD4BEB3C44F7F2745588029649674546CC9187C207FB8F2CECE8E2293F68395C4704AF04BAB5' + }, + 'S-C': { + modulusLength: 1024, + p: '0x9D88E6D7FE3313BD2E745C7CDD2AB9EE4AF3C8899E847DE74A33783EA68BC30588BA1F738C6AAF8AB350531F1854C3837CC3C860FFD7E2E106C3F63B3D8A4C034CE73942A6C3D585B599CF695ED7A3C4A93B2B947B7157BB1A1C043AB41EC8566C6145E938A611906DE0D32E562494569D7E999A0DDA5C879BDD91FE124DF1E9', + q: '0xFADD197ABD19A1B4653EECF7ECA4D6A22B1F7F893B641F901641FBB555354FAF', + a: '0x7447ED7156310599070B12609947A5C8C8A8625CF1CF252B407B331F93D639DDD1BA392656DECA992DD035354329A1E95A6E32D6F47882D960B8F10ACAFF796D13CD9611F853DAB6D2623483E46788708493937A1A29442598AEC2E0742022563440FE9C18740ECE6765AC05FAF024A64B026E7E408840819E962E7E5F401AE3' + }, + 'S-D': { + modulusLength: 1024, + p: '0x80F102D32B0FD167D069C27A307ADAD2C466091904DBAA55D5B8CC7026F2F7A1919B890CB652C40E054E1E9306735B43D7B279EDDF9102001CD9E1A831FE8A163EED89AB07CF2ABE8242AC9DEDDDBF98D62CDDD1EA4F5F15D3A42A6677BDD293B24260C0F27C0F1D15948614D567B66FA902BAA11A69AE3BCEADBB83E399C9B5', + q: '0xF0F544C418AAC234F683F033511B65C21651A6078BDA2D69BB9F732867502149', + a: '0x6BCC0B4FADB3889C1E06ADD23CC09B8AB6ECDEDF73F04632595EE4250005D6AF5F5ADE44CB1E26E6263C672347CFA26F9E9393681E6B759733784CDE5DBD9A14A39369DFD99FA85CC0D10241C4010343F34A91393A706CF12677CBFA1F578D6B6CFBE8A1242CFCC94B3B653A476E145E3862C18CC3FED8257CFEF74CDB205BF1' + }, + 'X-A': { + modulusLength: 1024, + p: '0xCA3B3F2EEE9FD46317D49595A9E7518E6C63D8F4EB4D22D10D28AF0B8839F079F8289E603B03530784B9BB5A1E76859E4850C670C7B71C0DF84CA3E0D6C177FE9F78A9D8433230A883CD82A2B2B5C7A3306980278570CDB79BF01074A69C9623348824B0C53791D53C6A78CAB69E1CFB28368611A397F50F541E16DB348DBE5F', + q: '0xCAE4D85F80C147704B0CA48E85FB00A9057AA4ACC44668E17F1996D7152690D9', + a: '0xBE27D652F2F1E339DA734211B85B06AE4DE236AA8FBEEB3F1ADCC52CD43853777E834A6A518138678A8ADBD3A55C70A7EAB1BA7A0719548677AAF4E609FFB47F6B9D7E45B0D06D83D7ADC53310ABD85783E7317F7EC73268B6A9C08D260B85D8485696CA39C17B17F044D1E050489036ABD381C5E6BF82BA352A1AFF136601AF' + }, + 'X-B': { + modulusLength: 1024, + p: '0x9286DBDA91ECCFC3060AA5598318E2A639F5BA90A4CA656157B2673FB191CD0589EE05F4CEF1BD13508408271458C30851CE7A4EF534742BFB11F4743C8F787B11193BA304C0E6BCA25701BF88AF1CB9B8FD4711D89F88E32B37D95316541BF1E5DBB4989B3DF13659B88C0F97A3C1087B9F2D5317D557DCD4AFC6D0A754E279', + q: '0xC966E9B3B8B7CDD82FF0F83AF87036C38F42238EC50A876CD390E43D67B6013F', + a: '0x7E9C3096676F51E3B2F9884CF0AC2156779496F410E049CED7E53D8B7B5B366B1A6008E5196605A55E89C3190DABF80B9F1163C979FCD18328DAE5E9048811B370107BB7715F82091BB9DE0E33EE2FED6255474F8769FCE5EAFAEEF1CB5A32E0D5C6C2F0FC0B3447072947F5B4C387666993A333FC06568E534AD56D2338D729' + }, + 'X-C': { + modulusLength: 1024, + p: '0xB194036ACE14139D36D64295AE6C50FC4B7D65D8B340711366CA93F383653908EE637BE428051D86612670AD7B402C09B820FA77D9DA29C8111A8496DA6C261A53ED252E4D8A69A20376E6ADDB3BDCD331749A491A184B8FDA6D84C31CF05F9119B5ED35246EA4562D85928BA1136A8D0E5A7E5C764BA8902029A1336C631A1D', + q: '0x96120477DF0F3896628E6F4A88D83C93204C210FF262BCCB7DAE450355125259', + a: '0x3F1817052BAA7598FE3E4F4FC5C5F616E122CFF9EBD89EF81DC7CE8BF56CC64B43586C80F1C4F56DD5718FDD76300BE336784259CA25AADE5A483F64C02A20CF4A10F9C189C433DEFE31D263E6C9764660A731ECCAECB74C8279303731E8CF69205BC73E5A70BDF93E5BB681DAB4EEB9C733CAAB2F673C475E0ECA921D29782E' + } +}; // + +/* + * BigInteger arithmetic tools + * optimized release of http://www-cs-students.stanford.edu/~tjw/jsbn/jsbn.js + * + */ // + +// Bits per one element +var DB = 28, DM = (1 << DB) - 1, DV = 1 << DB, + FV = Math.pow(2, 52), F1 = 52 - DB, F2 = 2 * DB - 52; + +function am(y, i, x, w, j, c, n) { + var xl = x & 0x3fff, xh = x >> 14; + while (--n >= 0) { + var l = y[i] & 0x3fff; + var h = y[i++] >> 14; + var m = xh * l + h * xl; + l = xl * l + ((m & 0x3fff) << 14) + w[j] + c; + c = (l >> 28) + (m >> 14) + xh * h; + w[j++] = l & 0xfffffff; + } + return c; +} + +function nbi(words) { + var r = new Array(Math.ceil(words)); + r.s = 0; + r.t = 0; + return r; +} + +function copyTo(x, r) { + for (var i = x.t - 1; i >= 0; --i) + r[i] = x[i]; + r.t = x.t; + r.s = x.s; + return r; +} + +function copy(x) { + return copyTo(x, nbi(x.t)); +} + +function setInt(x, i) { + x.t = 1; + x.s = (i < 0) ? -1 : 0; + if (i > 0) + x[0] = i; + else if (i < -1) + x[0] = i + DV; + else + x.t = 0; + return x; +} + +function nbv(i) { + var r = nbi(1); + setInt(r, i); + return r; +} + +var ZERO = nbv(0), ONE = nbv(1), THREE = nbv(3); + +function clamp(x) { + var c = x.s & DM; + while (x.t > 0 && x[x.t - 1] === c) + --x.t; + return x; +} + +function subTo(x, a, r) { + var i = 0, c = 0, m = Math.min(a.t, x.t); + while (i < m) { + c += x[i] - a[i]; + r[i++] = c & DM; + c >>= DB; + } + if (a.t < x.t) { + c -= a.s; + while (i < x.t) { + c += x[i]; + r[i++] = c & DM; + c >>= DB; + } + c += x.s; + } + else { + c += x.s; + while (i < a.t) { + c -= a[i]; + r[i++] = c & DM; + c >>= DB; + } + c -= a.s; + } + r.s = (c < 0) ? -1 : 0; + if (c < -1) + r[i++] = DV + c; + else if (c > 0) + r[i++] = c; + r.t = i; + return clamp(r); +} + +function sub(x, y) { + return subTo(x, y, nbi(x.t)); +} + +function addTo(x, a, r) { + var i = 0, c = 0, m = Math.min(a.t, x.t); + while (i < m) { + c += x[i] + a[i]; + r[i++] = c & DM; + c >>= DB; + } + if (a.t < x.t) { + c += a.s; + while (i < x.t) { + c += x[i]; + r[i++] = c & DM; + c >>= DB; + } + c += x.s; + } + else { + c += x.s; + while (i < a.t) { + c += a[i]; + r[i++] = c & DM; + c = c >> DB; + } + c += a.s; + } + r.s = (c < 0) ? -1 : 0; + if (c > 0) + r[i++] = c; + else if (c < -1) + r[i++] = DV + c; + r.t = i; + return clamp(r); +} + +function add(x, y) { + return addTo(x, y, nbi(x.t)); +} + +function negTo(x, r) { + return subTo(ZERO, x, r); +} + +function neg(x) { + return negTo(x, nbi(x.t)); +} + +function absTo(x, r) { + return (x.s < 0) ? negTo(r) : copyTo(r); +} + +function abs(x) { + return (x.s < 0) ? neg(x) : x; +} + +function compare(x, a) { + var r = x.s - a.s; + if (r !== 0) + return r; + var i = x.t; + r = i - a.t; + if (r !== 0) + return (x.s < 0) ? -r : r; + while (--i >= 0) + if ((r = x[i] - a[i]) !== 0) + return r; + return 0; +} + +function equals(x, y) { + return(compare(x, y) === 0); +} + +function min(x, y) { + return(compare(x, y) < 0) ? x : y; +} + +function max(x, y) { + return(compare(x, y) > 0) ? x : y; +} + +function nbits(x) { + var r = 1, t; + if ((t = x >>> 16) !== 0) { + x = t; + r += 16; + } + if ((t = x >> 8) !== 0) { + x = t; + r += 8; + } + if ((t = x >> 4) !== 0) { + x = t; + r += 4; + } + if ((t = x >> 2) !== 0) { + x = t; + r += 2; + } + if ((t = x >> 1) !== 0) { + x = t; + r += 1; + } + return r; +} + +function dshlTo(x, n, r) { + var i; + for (i = x.t - 1; i >= 0; --i) + r[i + n] = x[i]; + for (i = n - 1; i >= 0; --i) + r[i] = 0; + r.t = x.t + n; + r.s = x.s; + return r; +} +function dshrTo(x, n, r) { + for (var i = n; i < x.t; ++i) + r[i - n] = x[i]; + r.t = Math.max(x.t - n, 0); + r.s = x.s; + return r; +} + +function shlTo(x, n, r) { + var bs = n % DB; + var cbs = DB - bs; + var bm = (1 << cbs) - 1; + var ds = Math.floor(n / DB), c = (x.s << bs) & DM, i; + for (i = x.t - 1; i >= 0; --i) { + r[i + ds + 1] = (x[i] >> cbs) | c; + c = (x[i] & bm) << bs; + } + for (i = ds - 1; i >= 0; --i) + r[i] = 0; + r[ds] = c; + r.t = x.t + ds + 1; + r.s = x.s; + return clamp(r); +} + +function shrTo(x, n, r) { + r.s = x.s; + var ds = Math.floor(n / DB); + if (ds >= x.t) { + r.t = 0; + return; + } + var bs = n % DB; + var cbs = DB - bs; + var bm = (1 << bs) - 1; + r[0] = x[ds] >> bs; + for (var i = ds + 1; i < x.t; ++i) { + r[i - ds - 1] |= (x[i] & bm) << cbs; + r[i - ds] = x[i] >> bs; + } + if (bs > 0) + r[x.t - ds - 1] |= (x.s & bm) << cbs; + r.t = x.t - ds; + return clamp(r); +} + +function shl(x, n) { + var r = nbi(x.t); + if (n < 0) + shrTo(x, -n, r); + else + shlTo(x, n, r); + return r; +} + +function shr(x, n) { + var r = nbi(x.t); + if (n < 0) + shlTo(x, -n, r); + else + shrTo(x, n, r); + return r; +} + +function bitLength(x) { + if (x.t <= 0) + return 0; + return DB * (x.t - 1) + nbits(x[x.t - 1] ^ (x.s & DM)); +} + +function mulTo(b, a, r) { + var x = abs(b), y = abs(a); + var i = x.t; + r.t = i + y.t; + while (--i >= 0) + r[i] = 0; + for (i = 0; i < y.t; ++i) + r[i + x.t] = am(x, 0, y[i], r, i, 0, x.t); + r.s = 0; + if (b.s !== a.s) + subTo(ZERO, r, r); + return clamp(r); +} + +function mul(x, y) { + return mulTo(x, y, nbi(x.t + y.t)); +} + +function sqrTo(a, r) { + var x = abs(a); + var i = r.t = 2 * x.t; + while (--i >= 0) + r[i] = 0; + for (i = 0; i < x.t - 1; ++i) { + var c = am(x, i, x[i], r, 2 * i, 0, 1); + if ((r[i + x.t] += am(x, i + 1, 2 * x[i], r, 2 * i + 1, c, x.t - i - 1)) >= x.DV) { + r[i + x.t] -= x.DV; + r[i + x.t + 1] = 1; + } + } + if (r.t > 0) + r[r.t - 1] += am(x, i, x[i], r, 2 * i, 0, 1); + r.s = 0; + return clamp(r); +} + +function sqr(a) { + return sqrTo(a, nbi(a.t * 2)); +} + +function divRemTo(n, m, q, r) { + var pm = abs(m); + if (pm.t <= 0) + throw new OperationError('Division by zero'); + var pt = abs(n); + if (pt.t < pm.t) { + if (q) + setInt(q, 0); + if (r) + copyTo(n, r); + return q; + } + if (!r) + r = nbi(m.t); + var y = nbi(m.t), ts = n.s, ms = m.s; + var nsh = DB - nbits(pm[pm.t - 1]); + if (nsh > 0) { + shlTo(pm, nsh, y); + shlTo(pt, nsh, r); + } + else { + copyTo(pm, y); + copyTo(pt, r); + } + var ys = y.t; + var y0 = y[ys - 1]; + if (y0 === 0) + return q; + var yt = y0 * (1 << F1) + ((ys > 1) ? y[ys - 2] >> F2 : 0); + var d1 = FV / yt, d2 = (1 << F1) / yt, e = 1 << F2; + var i = r.t, j = i - ys, t = !q ? nbi(Math.max(n.t - m.t, 1)) : q; + dshlTo(y, j, t); + if (compare(r, t) >= 0) { + r[r.t++] = 1; + subTo(r, t, r); + } + dshlTo(ONE, ys, t); + subTo(t, y, y); + while (y.t < ys) + y[y.t++] = 0; + while (--j >= 0) { + var qd = (r[--i] === y0) ? DM : Math.floor(r[i] * d1 + (r[i - 1] + e) * d2); + if ((r[i] += am(y, 0, qd, r, j, 0, ys)) < qd) { + dshlTo(y, j, t); + subTo(r, t, r); + while (r[i] < --qd) + subTo(r, t, r); + } + } + if (q) { + dshrTo(r, ys, q); + if (ts !== ms) + subTo(ZERO, q, q); + } + r.t = ys; + clamp(r); + if (nsh > 0) + shrTo(r, nsh, r); + if (ts < 0) + subTo(ZERO, r, r); + return q; +} + +function modTo(b, a, r) { + divRemTo(abs(b), a, null, r); + if (b.s < 0 && compare(r, ZERO) > 0) + subTo(a, r, r); + return r; +} + +function mod(b, a) { + return modTo(b, a, nbi(a.t)); +} + +function div(b, a) { + return divRemTo(b, a, nbi(Math.max(b.t - a.t, 1)), null); +} + +function isEven(x) { + + return ((x.t > 0) ? (x[0] & 1) : x.s) === 0; +} + +function isZero(x) { + return equals(x, ZERO); +} + +function sig(x) { + if (x.s < 0) + return -1; + else if (x.t <= 0 || (x.t === 1 && x[0] <= 0)) + return 0; + else + return 1; +} + +function invMod(x, m) { + var ac = isEven(m); + if ((isEven(x) && ac) || sig(m) === 0) + return ZERO; + var u = copy(m), v = copy(x); + var a = nbv(1), b = nbv(0), c = nbv(0), d = nbv(1); + while (sig(u) !== 0) { + while (isEven(u)) { + shrTo(u, 1, u); + if (ac) { + if (!isEven(a) || !isEven(b)) { + addTo(a, x, a); + subTo(b, m, b); + } + shrTo(a, 1, a); + } + else if (!isEven(b)) + subTo(b, m, b); + shrTo(b, 1, b); + } + while (isEven(v)) { + shrTo(v, 1, v); + if (ac) { + if (!isEven(c) || !isEven(d)) { + addTo(c, x, c); + subTo(d, m, d); + } + shrTo(c, 1, c); + } + else if (!isEven(d)) + subTo(d, m, d); + shrTo(d, 1, d); + } + if (compare(u, v) >= 0) { + subTo(u, v, u); + if (ac) + subTo(a, c, a); + subTo(b, d, b); + } + else { + subTo(v, u, v); + if (ac) + subTo(c, a, c); + subTo(d, b, d); + } + } + if (compare(v, ONE) !== 0) + return ZERO; + if (compare(d, m) >= 0) + return subtract(d, m); + if (sig(d) < 0) + addTo(d, m, d); + else + return d; + if (sig(d) < 0) + return add(d, m); + else + return d; +} + +function testBit(x, n) { + var j = Math.floor(n / DB); + if (j >= x.t) + return (x.s !== 0); + return ((x[j] & (1 << (n % DB))) !== 0); +} + +function nothing(x) { + return x; +} + +function extend(c, o) { + for (var i in o) + c.prototype[i] = o[i]; +} // + +/* + * Classic, Barret, Mongomery reductions, optimized ExpMod algorithms + * optimized release of http://www-cs-students.stanford.edu/~tjw/jsbn/jsbn2.js + * + */ // + +// Classic reduction +var Classic = function (m) { + this.m = m; +}; + +extend(Classic, { + convert: function (x) { + if (x.s < 0 || compare(x, this.m) >= 0) + return mod(x, this.m); + else + return x; + }, + revert: nothing, + reduce: function (x) { + modTo(x, this.m, x); + }, + sqrTo: function (x, r) { + sqrTo(x, r); + this.reduce(r); + }, + mulTo: function (x, y, r) { + mulTo(x, y, r); + this.reduce(r); + } +}); + +function invDig(a) { + if (a.t < 1) + return 0; + var x = a[0]; + if ((x & 1) === 0) + return 0; + var y = x & 3; + y = (y * (2 - (x & 0xf) * y)) & 0xf; + y = (y * (2 - (x & 0xff) * y)) & 0xff; + y = (y * (2 - (((x & 0xffff) * y) & 0xffff))) & 0xffff; + y = (y * (2 - x * y % DV)) % DV; + return (y > 0) ? DV - y : -y; +} + +// Montgomery reduction +var Montgomery = function (m) { + this.m = m; + this.mp = invDig(m); + this.mpl = this.mp & 0x7fff; + this.mph = this.mp >> 15; + this.um = (1 << (DB - 15)) - 1; + this.mt2 = 2 * m.t; +}; + +extend(Montgomery, { + // xR mod m + convert: function (x) { + var r = nbi(x.t); + dshlTo(abs(x), this.m.t, r); + divRemTo(r, this.m, null, r); + if (x.s < 0 && compare(r, ZERO) > 0) + subTo(this.m, r, r); + return r; + }, + // x/R mod m + revert: function (x) { + var r = nbi(x.t); + copyTo(x, r); + this.reduce(r); + return r; + }, + // x = x/R mod m (HAC 14.32) + reduce: function (x) { + while (x.t <= this.mt2) + x[x.t++] = 0; + for (var i = 0; i < this.m.t; ++i) { + var j = x[i] & 0x7fff; + var u0 = (j * this.mpl + (((j * this.mph + (x[i] >> 15) * this.mpl) & this.um) << 15)) & DM; + j = i + this.m.t; + x[j] += am(this.m, 0, u0, x, i, 0, this.m.t); + while (x[j] >= DV) { + x[j] -= DV; + x[++j]++; + } + } + clamp(x); + dshrTo(x, this.m.t, x); + if (compare(x, this.m) >= 0) + subTo(x, this.m, x); + }, + // r = "x^2/R mod m"; x != r + sqrTo: function (x, r) { + sqrTo(x, r); + this.reduce(r); + }, + // r = "xy/R mod m"; x,y != r + mulTo: function (x, y, r) { + mulTo(x, y, r); + this.reduce(r); + } +}); + +function dAddOffset(x, n, w) { + if (n === 0) + return; + while (x.t <= w) + x[x.t++] = 0; + x[w] += n; + while (x[w] >= DV) { + x[w] -= DV; + if (++w >= x.t) + x[x.t++] = 0; + ++x[w]; + } +} + +function mulLowerTo(x, a, n, r) { + var i = Math.min(x.t + a.t, n); + r.s = 0; // assumes a,x >= 0 + r.t = i; + while (i > 0) + r[--i] = 0; + var j; + for (j = r.t - x.t; i < j; ++i) + r[i + x.t] = am(x, 0, a[i], r, i, 0, x.t); + for (j = Math.min(a.t, n); i < j; ++i) + am(x, 0, a[i], r, i, 0, n - i); + return clamp(r); +} + +function mulUpperTo(x, a, n, r) { + --n; + var i = r.t = x.t + a.t - n; + r.s = 0; // assumes a,x >= 0 + while (--i >= 0) + r[i] = 0; + for (i = Math.max(n - x.t, 0); i < a.t; ++i) + r[x.t + i - n] = am(x, n - i, a[i], r, 0, 0, x.t + i - n); + clamp(r); + return dshrTo(r, 1, r); +} + +// Barrett modular reduction +function Barrett(m) { + // setup Barrett + this.r2 = nbi(2 * m.t); + this.q3 = nbi(2 * m.t); + dshlTo(ONE, 2 * m.t, this.r2); + this.mu = div(this.r2, m); + this.m = m; +} + +extend(Barrett, { + convert: function (x) { + if (x.s < 0 || x.t > 2 * this.m.t) + return mod(x, this.m); + else if (compare(x, this.m) < 0) + return x; + else { + var r = nbi(x.t); + copyTo(x, r); + this.reduce(r); + return r; + } + }, + revert: function (x) { + return x; + }, + // x = x mod m (HAC 14.42) + reduce: function (x) { + dshrTo(x, this.m.t - 1, this.r2); + if (x.t > this.m.t + 1) { + x.t = this.m.t + 1; + clamp(x); + } + mulUpperTo(this.mu, this.r2, this.m.t + 1, this.q3); + mulLowerTo(this.m, this.q3, this.m.t + 1, this.r2); + while (compare(x, this.r2) < 0) + dAddOffset(x, 1, this.m.t + 1); + subTo(x, this.r2, x); + while (compare(x, this.m) >= 0) + subTo(x, this.m, x); + }, + // r = x^2 mod m; x != r + sqrTo: function (x, r) { + sqrTo(x, r); + this.reduce(r); + }, + // r = x*y mod m; x,y != r + mulTo: function (x, y, r) { + mulTo(x, y, r); + this.reduce(r); + } + +}); + +// x^e % m (HAC 14.85) +function expMod(x, e, m) { + var i = bitLength(e), k, r = nbv(1), z; + if (i <= 0) + return r; + else if (i < 18) + k = 1; + else if (i < 48) + k = 3; + else if (i < 144) + k = 4; + else if (i < 768) + k = 5; + else + k = 6; + if (i < 8) + z = new Classic(m); + else if (isEven(m)) + z = new Barrett(m); + else + z = new Montgomery(m); + + // precomputation + var g = new Array(), n = 3, k1 = k - 1, km = (1 << k) - 1; + g[1] = z.convert(x); + if (k > 1) { + var g2 = nbi(m.t * 2); + z.sqrTo(g[1], g2); + while (n <= km) { + g[n] = nbi(m.t * 2); + z.mulTo(g2, g[n - 2], g[n]); + n += 2; + } + } + + var j = e.t - 1, w, is1 = true, r2 = nbi(m.t * 2), t; + i = nbits(e[j]) - 1; + while (j >= 0) { + if (i >= k1) + w = (e[j] >> (i - k1)) & km; + else { + w = (e[j] & ((1 << (i + 1)) - 1)) << (k1 - i); + if (j > 0) + w |= e[j - 1] >> (DB + i - k1); + } + + n = k; + while ((w & 1) == 0) { + w >>= 1; + --n; + } + if ((i -= n) < 0) { + i += DB; + --j; + } + if (is1) { // ret == 1, don't bother squaring or multiplying it + copyTo(g[w], r); + is1 = false; + } + else { + while (n > 1) { + z.sqrTo(r, r2); + z.sqrTo(r2, r); + n -= 2; + } + if (n > 0) + z.sqrTo(r, r2); + else { + t = r; + r = r2; + r2 = t; + } + z.mulTo(r2, g[w], r); + } + while (j >= 0 && (e[j] & (1 << i)) == 0) { + z.sqrTo(r, r2); + t = r; + r = r2; + r2 = t; + if (--i < 0) { + i = DB - 1; + --j; + } + } + } + return z.revert(r); +} // + +/* + * EC Field Elements, Points, Curves + * optimized release of http://www-cs-students.stanford.edu/~tjw/jsbn/ec.js + * + */ // + +// EC Field Elemets +function newFE(a, x) { + a.r.reduce(x); + x.q = a.q; + x.r = a.r; + return x; +} + +function copyFE(a, x) { + x.q = a.q; + x.r = a.r; + return x; +} + +function negFE(a) { + return copyFE(a, sub(a.q, a)); +} + +function addFE(a, b) { + var r = add(a, b); + if (compare(r, a.q) > 0) + subTo(r, a.q, r); + return copyFE(a, r); +} + +function subFE(a, b) { + var r = sub(a, b); + if (r.s < 0) + addTo(a.q, r, r); + return copyFE(a, r); +} + +function mulFE(a, b) { + return newFE(a, mul(a, b)); +} + +function sqrFE(a) { + return newFE(a, sqr(a)); +} + +function shlFE(a, i) { + return newFE(a, shl(a, i)); +} + +function invFE(a) { + return copyFE(a, invMod(a, a.q)); +} + +// EC Points +function newEC(curve, x, y, z) { + return { + curve: curve, + x: x, + y: y, + z: z || newFE(curve, ONE) + }; +} + +function getX(point) { + if (!point.zinv) + point.zinv = invFE(point.z); + return mulFE(point.x, point.zinv); +} + +function getY(point) { + if (!point.zinv) + point.zinv = invFE(point.z); + return mulFE(point.y, point.zinv); +} + +function isInfinity(a) { + if ((!a.x) && (!a.y)) + return true; + return isZero(a.z) && !isZero(a.y); +} + +function getInfinity(a) { + return a.curve.infinity; +} + +function equalsEC(a, b) { + if (a === b) + return true; + if (isInfinity(a)) + return isInfinity(b); + if (isInfinity(b)) + return isInfinity(a); + var u, v; + // u = Y2 * Z1 - Y1 * Z2 + u = subFE(mulFE(b.y, a.z), mulFE(a.y, b.z)); + if (!isZero(u)) + return false; + // v = X2 * Z1 - X1 * Z2 + v = subFE(mulFE(b.x, a.z), mulFE(a.x, b.z)); + return isZero(v); +} + +function negEC(a) { + return newEC(a.curve, a.x, negFE(a.y), a.z); +} + +function addEC(a, b) { + if (isInfinity(a)) + return b; + if (isInfinity(b)) + return a; + + // u = Y2 * Z1 - Y1 * Z2 + var u = subFE(mulFE(b.y, a.z), mulFE(a.y, b.z)); + // v = X2 * Z1 - X1 * Z2 + var v = subFE(mulFE(b.x, a.z), mulFE(a.x, b.z)); + + if (isZero(v)) { + if (isZero(u)) { + return twiceEC(a); // a == b, so double + } + return getInfinity(a); // a = -b, so infinity + } + + var x1 = a.x; + var y1 = a.y; + + var v2 = sqrFE(v); + var v3 = mulFE(v2, v); + var x1v2 = mulFE(x1, v2); + var zu2 = mulFE(sqrFE(u), a.z); + + // x3 = v * (z2 * (z1 * u^2 - 2 * x1 * v^2) - v^3) + var x3 = mulFE(subFE(mulFE(subFE(zu2, shlFE(x1v2, 1)), b.z), v3), v); + // y3 = z2 * (3 * x1 * u * v^2 - y1 * v^3 - z1 * u^3) + u * v^3 + var y3 = addFE(mulFE(subFE(subFE(mulFE(mulFE(x1v2, THREE), u), mulFE(y1, v3)), mulFE(zu2, u)), b.z), mulFE(u, v3)); + // z3 = v^3 * z1 * z2 + var z3 = mulFE(mulFE(v3, a.z), b.z); + + return newEC(a.curve, x3, y3, z3); +} + +function twiceEC(b) { + if (isInfinity(b)) + return b; + if (sig(b.y) === 0) + return getInfinity(b); + + var x1 = b.x; + var y1 = b.y; + + var y1z1 = mulFE(y1, b.z); + var y1sqz1 = mulFE(y1z1, y1); + var a = b.curve.a; + + // w = 3 * x1^2 + a * z1^2 + var w = mulFE(sqrFE(x1), THREE); + if (!isZero(a)) { + w = addFE(w, mulFE(sqrFE(b.z), a)); + } + + // x3 = 2 * y1 * z1 * (w^2 - 8 * x1 * y1^2 * z1) + var x3 = mulFE(shlFE(subFE(sqrFE(w), mulFE(shlFE(x1, 3), y1sqz1)), 1), y1z1); + // y3 = 4 * y1^2 * z1 * (3 * w * x1 - 2 * y1^2 * z1) - w^3 + var y3 = subFE(mulFE(shlFE(subFE(mulFE(mulFE(w, THREE), x1), shlFE(y1sqz1, 1)), 2), y1sqz1), mulFE(sqrFE(w), w)); + // z3 = 8 * (y1 * z1)^3 + var z3 = shlFE(mulFE(sqrFE(y1z1), y1z1), 3); + + return newEC(b.curve, x3, y3, z3); +} + +// Simple NAF (Non-Adjacent Form) multiplication algorithm +function mulEC(a, k) { + if (isInfinity(a)) + return a; + if (sig(k) === 0) + return getInfinity(a); + + var e = k; + var h = mul(e, THREE); + + var neg = negEC(a); + var R = a; + + var i; + for (i = bitLength(h) - 2; i > 0; --i) { + R = twiceEC(R); + + var hBit = testBit(h, i); + var eBit = testBit(e, i); + + if (hBit !== eBit) { + R = addEC(R, hBit ? a : neg); + } + } + + return R; +} + +function mul2AndAddEC(a, k) { + var nbits = bitLength(k); + var R = a, + Q = getInfinity(a); + + for (var i = 0; i < nbits - 1; i++) { + if (testBit(k, i) === 1) + Q = addEC(Q, R); + + R = twiceEC(R); + } + + if (testBit(k, nbits - 1) === 1) + Q = addEC(Q, R); + + return Q; +} + +// Compute a*j + x*k (simultaneous multiplication) +function mulTwoEC(a, j, x, k) { + var i; + if (bitLength(j) > bitLength(k)) + i = bitLength(j) - 1; + else + i = bitLength(k) - 1; + + var R = getInfinity(a); + var both = addEC(a, x); + while (i >= 0) { + R = twiceEC(R); + if (testBit(j, i)) { + if (testBit(k, i)) { + R = addEC(R, both); + } + else { + R = addEC(R, a); + } + } + else { + if (testBit(k, i)) { + R = addEC(R, x); + } + } + --i; + } + + return R; +} + +// EC Curve +function newCurve(q, a, b) { + var curve = {}; + curve.q = q; + curve.r = new Barrett(q); + curve.a = newFE(curve, a); + curve.b = newFE(curve, b); + curve.infinity = newEC(curve); + return curve; +} // + +/* + * Converion tools (hex, binary) + * + */ // + +function atobi(d) { + var k = 8; + var a = new Uint8Array(d); + var r = nbi(a.length * 8 / DB); + r.t = 0; + r.s = 0; + var sh = 0; + for (var i = 0, n = a.length; i < n; i++) { + var x = a[i]; + if (sh === 0) + r[r.t++] = x; + else if (sh + k > DB) { + r[r.t - 1] |= (x & ((1 << (DB - sh)) - 1)) << sh; + r[r.t++] = (x >> (DB - sh)); + } + else + r[r.t - 1] |= x << sh; + sh += k; + if (sh >= DB) + sh -= DB; + } + return clamp(r); +} + +function bitoa(s, bitLength) { + var k = 8; + var km = (1 << k) - 1, d, m = false, r = [], i = s.t; + var p = DB - (i * DB) % k; + if (i-- > 0) { + if (p < DB && (d = s[i] >> p) > 0) { + m = true; + r.push(d); + } + while (i >= 0) { + if (p < k) { + d = (s[i] & ((1 << p) - 1)) << (k - p); + d |= s[--i] >> (p += DB - k); + } + else { + d = (s[i] >> (p -= k)) & km; + if (p <= 0) { + p += DB; + --i; + } + } + if (d > 0) + m = true; + if (m) + r.push(d); + } + } + var r8 = new Uint8Array(bitLength ? bitLength / 8 : r.length); + if (m) + r8.set(r.reverse()); + return r8.buffer; +} + + +function htobi(s) { + if (typeof s === 'number' || s instanceof Number) + return nbv(s); + s = s.replace(/[^\-A-Fa-f0-9]/g, ''); + if (!s) + s = '0'; + var k = 4; + var r = nbi(s.length / 7); + var i = s.length, mi = false, sh = 0; + while (--i >= 0) { + var c = s.charAt(i); + if (c === '-') { + mi = true; + continue; + } + var x = parseInt(s.charAt(i), 16); + mi = false; + if (sh === 0) + r[r.t++] = x; + else if (sh + k > DB) { + r[r.t - 1] |= (x & ((1 << (DB - sh)) - 1)) << sh; + r[r.t++] = (x >> (DB - sh)); + } + else + r[r.t - 1] |= x << sh; + sh += k; + if (sh >= DB) + sh -= DB; + } + if (mi) + subTo(ZERO, r, r); + return clamp(r); +} + +function bitoh(x) { + if (x.s < 0) + return "-" + bitoh(negTo(x, nbi(x.t))); + var k = 4; + var km = (1 << k) - 1, d, m = false, r = "", i = x.t; + var p = DB - (i * DB) % k; + if (i-- > 0) { + if (p < DB && (d = x[i] >> p) > 0) { + m = true; + r = d.toString(16); + } + while (i >= 0) { + if (p < k) { + d = (x[i] & ((1 << p) - 1)) << (k - p); + d |= x[--i] >> (p += DB - k); + } + else { + d = (x[i] >> (p -= k)) & km; + if (p <= 0) { + p += DB; + --i; + } + } + if (d > 0) + m = true; + if (m) + r += d.toString(16); + } + } + return "0x" + (m ? r : "0"); +} + +// biginteger to big-endian integer bytearray +function bitoi(s) { + var i = s.t, r = []; + r[0] = s.s; + var p = DB - (i * DB) % 8, d, k = 0; + if (i-- > 0) { + if (p < DB && (d = s[i] >> p) !== (s.s & DM) >> p) + r[k++] = d | (s.s << (DB - p)); + while (i >= 0) { + if (p < 8) { + d = (s[i] & ((1 << p) - 1)) << (8 - p); + d |= s[--i] >> (p += DB - 8); + } + else { + d = (s[i] >> (p -= 8)) & 0xff; + if (p <= 0) { + p += DB; + --i; + } + } + if ((d & 0x80) !== 0) + d |= -256; + if (k === 0 && (s.s & 0x80) !== (d & 0x80)) + ++k; + if (k > 0 || d !== s.s) + r[k++] = d; + } + } + return new Uint8Array(r).buffer; +} + +// big-endian integer bytearray to biginteger +function itobi(d) { + var k = 8, s = new Uint8Array(d), + r = nbi(s.length / 7); + r.t = 0; + r.s = 0; + var i = s.length, sh = 0; + while (--i >= 0) { + var x = s[i] & 0xff; + if (sh === 0) + r[r.t++] = x; + else if (sh + k > DB) { + r[r.t - 1] |= (x & ((1 << (DB - sh)) - 1)) << sh; + r[r.t++] = (x >> (DB - sh)); + } + else + r[r.t - 1] |= x << sh; + sh += k; + if (sh >= DB) + sh -= DB; + } + if ((s[0] & 0x80) !== 0) { + r.s = -1; + if (sh > 0) + r[r.t - 1] |= ((1 << (DB - sh)) - 1) << sh; + } + return clamp(r); +} + + +// Swap bytes in buffer +function swap(s) { + var src = new Uint8Array(s), + dst = new Uint8Array(src.length); + for (var i = 0, n = src.length; i < n; i++) + dst[n - i - 1] = src[i]; + return dst.buffer; +} + +// Calculate hash of data +function hash(d) { + if (this.hash) + d = this.hash.digest(d); + // Swap hash for SignalCom + if (this.procreator === 'SC' || + (this.procreator === 'VN' && this.hash.version === 2012)) + d = swap(d); + return d; +} + +// Check buffer +function buffer(d) { + if (d instanceof CryptoOperationData) + return d; + else if (d && d?.buffer instanceof CryptoOperationData) + return d.byteOffset === 0 && d.byteLength === d.buffer.byteLength ? + d.buffer : new Uint8Array(new Uint8Array(d, d.byteOffset, d.byteLength)).buffer; + else + throw new DataError('CryptoOperationData or CryptoOperationDataView required'); +} + +// Check double buffer +function to2(d) { + var b = buffer(d); + if (b.byteLength % 2 > 0) + throw new DataError('Buffer length must be even'); + var n = b.byteLength / 2; + return [atobi(new Uint8Array(b, 0, n)), atobi(new Uint8Array(b, n, n))]; +} + +function from2(x, y, bitLength) { + var a = bitoa(x, bitLength), + b = bitoa(y, bitLength), + d = new Uint8Array(a.byteLength + b.byteLength); + d.set(new Uint8Array(a)); + d.set(new Uint8Array(b), a.byteLength); + return d.buffer; +} + +function getSeed(length) { + GostRandom = GostRandom || root.GostRandom; + var randomSource = GostRandom ? new (GostRandom || root.GostRandom) : rootCrypto; + if (randomSource.getRandomValues) { + var d = new Uint8Array(Math.ceil(length / 8)); + randomSource.getRandomValues(d); + return d; + } else + throw new NotSupportedError('Random generator not found'); +} // + +/** + * Algorithm name GOST R 34.10

+ * + * The sign method returns sign data generated with the supplied privateKey.
+ * + * @memberOf GostSign + * @method sign + * @instance + * @param {(CryptoOperationData|TypedArray)} privateKey Private key + * @param {(CryptoOperationData|TypedArray)} data Data + * @returns {CryptoOperationData} Signature + */ +function sign(privateKey, data) // +{ + + // Stage 1 + var b = buffer(data); + var alpha = atobi(hash.call(this, b)); + + var q = this.q; + var x = mod(atobi(buffer(privateKey)), q); + + // Stage 2 + var e = mod(alpha, q); + if (isZero(e)) + e = ONE; + + var s = ZERO; + while (isZero(s)) { + var r = ZERO; + while (isZero(r)) { + + // Stage 3 + var k = mod(atobi(this.ukm || + getSeed(this.bitLength)), q); // pseudo random 0 < k < q + // Stage 4 + if (this.curve) { + // Gost R 34.10-2001 || Gost R 34.10-2012 + var P = this.P; + var C = mulEC(P, k); + r = mod(getX(C), q); + } else { + // Gost R 34.10-94 + var p = this.p, a = this.a; + r = mod(expMod(a, k, p), q); + } + } + // Stage 5 + s = mod(add(mul(r, x), mul(k, e)), q); + } + // Stage 6 + // console.log('s', bitoh(s)); + // console.log('r', bitoh(r)); + var zetta; + // Integer structure for SignalCom algorithm + if (this.procreator === 'SC') { + zetta = { + r: bitoh(r), + s: bitoh(s) + }; + } else { + zetta = from2(r, s, this.bitLength); + // Swap bytes for CryptoPro algorithm + if (this.procreator === 'CP' || this.procreator === 'VN') + zetta = swap(zetta); + } + return zetta; +} // + +/** + * Algorithm name GOST R 34.10

+ * + * The verify method returns signature verification for the supplied publicKey.
+ * + * @memberOf GostSign + * @method sign + * @instance + * @param {(CryptoOperationData|TypedArray)} publicKey Public key + * @param {(CryptoOperationData|TypedArray)} signature Signature + * @param {(CryptoOperationData|TypedArray)} data Data + * @returns {boolean} Signature verified = true + */ +function verify(publicKey, signature, data) // +{ + + // Stage 1 + var q = this.q; + var r, s; + // Ready int for SignalCom algorithm + if (this.procreator === 'SC') { + r = htobi(signature.r); + s = htobi(signature.s); + } else { + if (this.procreator === 'CP' || this.procreator === 'VN') + signature = swap(signature); + var zetta = to2(signature); + // Swap bytes for CryptoPro algorithm + s = zetta[1]; // first 32 octets contain the big-endian representation of s + r = zetta[0]; // and second 32 octets contain the big-endian representation of r + } + if (compare(r, q) >= 0 || compare(s, q) >= 0) + return false; + // Stage 2 + var b = buffer(data); + var alpha = atobi(hash.call(this, b)); + // Stage 3 + var e = mod(alpha, q); + if (isZero(e) === 0) + e = ONE; + // Stage 4 + var v = invMod(e, q); + // Stage 5 + var z1 = mod(mul(s, v), q); + var z2 = sub(q, mod(mul(r, v), q)); + // Stage 6 + if (this.curve) { + // Gost R 34.10-2001 || Gost R 34.10-2012 + var k2 = to2(publicKey), + curve = this.curve, + P = this.P, + x = newFE(curve, k2[0]), // first 32 octets contain the little-endian representation of x + y = newFE(curve, k2[1]), // and second 32 octets contain the little-endian representation of y. + Q = new newEC(curve, x, y); // This corresponds to the binary representation of (256||256) + var C = mulTwoEC(P, z1, Q, z2); + var R = mod(getX(C), q); + } else { + // Gost R 34.10-94 + var p = this.p, a = this.a; + var y = atobi(publicKey); + var R = mod(mod(mul(expMod(a, z1, p), expMod(y, z2, p)), p), q); + } + // Stage 7 + return (compare(R, r) === 0); +} // + +/** + * Algorithm name GOST R 34.10

+ * + * The generateKey method returns a new generated key pair using the specified + * AlgorithmIdentifier. + * + * @memberOf GostSign + * @method generateKey + * @instance + * @returns {Object} Object with two CryptoOperationData members: privateKey and publicKey + */ +function generateKey() // +{ + var curve = this.curve; + if (curve) { + + var Q = curve.infinity; + while (isInfinity(Q)) { + + // Generate random private key + var d = ZERO; + if (this.ukm) { + d = atobi(this.ukm); + } else { + while (isZero(d)) + d = mod(atobi(getSeed(this.bitLength)), this.q); // 0 < d < q + } + + // Calculate public key + Q = mulEC(this.P, d); + var x = getX(Q), y = getY(Q); + // console.log('d', bitoh(d)); + // console.log('x', bitoh(x)); + // console.log('y', bitoh(y)); + } + + // Return result + return { + privateKey: bitoa(d, this.bitLength), + publicKey: from2(x, y, this.bitLength) // This corresponds to the binary representation of (256||256) + }; + + } else + throw new NotSupportedError('Key generation for GOST R 34.10-94 not supported'); +} // + +/** + * Algorithm name GOST R 34.10 mode MASK

+ * + * The generateMaskKey method returns a new generated key mask using for wrapping. + * + * @memberOf GostSign + * @method generateMaskKey + * @instance + * @returns {Object} Object with two CryptoOperationData members: privateKey and publicKey + */ +function generateMaskKey() // +{ + var curve = this.curve; + if (curve) { + // Generate random private key + var d = ZERO; + while (isZero(d)) + d = mod(atobi(getSeed(this.bitLength)), this.q); // 0 < d < q + + // Return result + return bitoa(d, this.bitLength); + } else + throw new NotSupportedError('Key generation for GOST R 34.10-94 not supported'); +} // + +/** + * Algorithm name GOST R 34.10

+ * + * Unwrap private key from private key and ukm (mask) + * + * @memberOf GostSign + * @method unwrap + * @instance + * @param {(CryptoOperationData|TypedArray)} baseKey Unwrapping key + * @param {(CryptoOperationData|TypedArray)} data Wrapped key + * @returns {Object} CryptoOperationData unwrapped privateKey + */ +function unwrapKey(baseKey, data) // +{ + var curve = this.curve; + if (curve) { + var q = this.q; + var x = mod(atobi(buffer(data)), q); + var y = mod(atobi(buffer(baseKey)), q); + var z = this.procreator === 'VN' ? mod(mul(x, y), q) : mod(mul(x, invMod(y, q)), q); + return bitoa(z); + } else + throw new NotSupportedError('Key wrapping GOST R 34.10-94 not supported'); +} // + +/** + * Algorithm name GOST R 34.10

+ * + * Wrap private key with private key and ukm (mask) + * + * @memberOf GostSign + * @method unwrap + * @instance + * @param {(CryptoOperationData|TypedArray)} baseKey Wrapping key + * @param {(CryptoOperationData|TypedArray)} data Key + * @returns {Object} CryptoOperationData unwrapped privateKey + */ +function wrapKey(baseKey, data) // +{ + var curve = this.curve; + if (curve) { + var q = this.q; + var x = mod(atobi(buffer(data)), q); + var y = mod(atobi(buffer(baseKey)), q); + var z = this.procreator === 'VN' ? mod(mul(x, invMod(y, q)), q) : mod(mul(x, y), q); + return bitoa(z); + } else + throw new NotSupportedError('Key wrapping GOST R 34.10-94 not supported'); +} // + +/** + * Algorithm name GOST R 34.10

+ * + * @memberOf GostSign + * @method derive + * @instance + * @private + * @param {CryptoOperationData} baseKey Key for deriviation + * @returns {CryptoOperationData} + */ +function derive(baseKey) // +{ + + var k, ukm = atobi(this.ukm); + var q = this.q; + var x = mod(atobi(buffer(baseKey)), q); + + if (this.curve) { + // 1) Let K(x,y,UKM) = ((UKM*x)(mod q)) . (y.P) (512 bit), where + // x - sender’s private key (256 bit) + // x.P - sender’s public key (512 bit) + // y - recipient’s private key (256 bit) + // y.P - recipient’s public key (512 bit) + // UKM - non-zero integer, produced as in step 2 p. 6.1 [GOSTR341001] + // P - base point on the elliptic curve (two 256-bit coordinates) + // UKM*x - x multiplied by UKM as integers + // x.P - a multiple point + var K = mulEC(this.peer_Q, mod(mul(ukm, x), q)); + k = from2(getX(K), getY(K), // This corresponds to the binary representation of (256||256) + this.bitLength); + } else { + // 1) Let K(x,y) = a^(x*y) (mod p), where + // x - sender’s private key, a^x - sender’s public key + // y - recipient’s private key, a^y - recipient’s public key + // a, p - parameters + var p = this.p, a = this.a; + k = bitoa(expMod(this.peer_y, x, p)); + } + // 2) Calculate a 256-bit hash of K(x,y,UKM): + // KEK(x,y,UKM) = gostSign (K(x,y,UKM) + return hash.call(this, k); +} // + +/** + * Algorithm name GOST R 34.10

+ * + * The deriveBits method returns length bits on baseKey. + * + * @memberOf GostSign + * @method deriveBits + * @instance + * @param {(CryptoOperationData|TypedArray)} baseKey Key for deriviation + * @param {number} length output bit-length + * @returns {CryptoOperationData} result + */ +function deriveBits(baseKey, length) // +{ + if (length < 8 || length > this.bitLength || length % 8 > 0) + throw new DataError('Length must be no more than ' + this.bitLength + ' bits and multiple of 8'); + var n = length / 8, + b = derive.call(this, baseKey), + r = new Uint8Array(n); + + r.set(new Uint8Array(b, 0, n)); + return r.buffer; +} // + +/** + * Algorithm name GOST R 34.10

+ * + * The deriveKey method returns 256 bit Key encryption key on baseKey. + * + * This algorithm creates a key encryption key (KEK) using 64 bit UKM, + * the sender’s private key, and the recipient’s public key (or the + * reverse of the latter pair + * + * @memberOf GostSign + * @method deriveKey + * @instance + * @param {(CryptoOperationData|TypedArray)} baseKey Key for deriviation + * @returns {CryptoOperationData} result + */ +function deriveKey(baseKey) // +{ + var b = derive.call(this, baseKey), + r = new Uint8Array(32); + + r.set(new Uint8Array(b, 0, 32)); + return r.buffer; +} // + + +/** + * Gost R 34.10 universal object

+ * + * References: {@link http://tools.ietf.org/html/rfc6986} and {@link http://tools.ietf.org/html/rfc5831}

+ * + * Normalized algorithm identifier common parameters: + * + *
    + *
  • name Algorithm name 'GOST R 34.10'
  • + *
  • version Algorithm version + *
      + *
    • 1994 - Old-style GOST R 34.10-94 ExpMod algorithm with GOST R 34.11-94 hash
    • + *
    • 2001 - GOST R 34.10-2001 Eliptic curve algorithm with old GOST R 34.11-94 hash
    • + *
    • 2012 - GOST R 34.10-2012 Eliptic curve algorithm with GOST R 34.11-12 hash, default mode
    • + *
    + *
  • + *
  • length Length of hash and signature. Key length == hash length for EC algorithms and 2 * hash length for ExpMod algorithm + *
      + *
    • GOST R 34.10-256 - 256 bits digest, default mode
    • + *
    • GOST R 34.10-512 - 512 bits digest only for GOST R 34.11-2012 hash
    • + *
    + *
  • + *
  • mode Algorithm mode + *
      + *
    • SIGN Digital signature mode (default)
    • + *
    • DH Diffie-Hellman key generation and key agreement mode
    • + *
    + *
  • + *
  • sBox Paramset sBox for GOST 34.11-94. Used only if version = 1994 or 2001
  • + *
+ * + * Supported algorithms, modes and parameters: + * + *
    + *
  • Sign/Verify mode (SIGN)
  • + *
  • DeriveKey/DeriveBits mode (DH) + *
      + *
    • {@link CryptoOperationData} ukm User key material. Default - random generated value
    • + *
    • {@link CryptoOperationData} public The peer's EC public key data
    • + *
    + *
  • + *
  • GenerateKey mode (SIGN and DH) version = 1994 + *
      + *
    • namedParam Paramset for key generation algorithm. If specified no additianal parameters required
    • + *
    + * Additional parameters, if namedParam not specified + *
      + *
    • modulusLength Bit length of p (512 or 1024 bits). Default = 1024
    • + *
    • p {@link CryptoOperationData} Modulus, prime number, 2^(t-1) + *
    • q {@link CryptoOperationData} Order of cyclic group, prime number, 2^254 + *
    • a {@link CryptoOperationData} Generator, integer, 1 + *
    + *
  • + *
  • GenerateKey mode (SIGN and DH) version = 2001 or 2012 + *
      + *
    • namedCurve Paramset for key generation algorithm. If specified no additianal parameters required
    • + *
    + * Additional EC parameters, if namedCurve not specified + *
      + *
    • p {@link CryptoOperationData} Prime number - elliptic curve modulus
    • + *
    • a {@link CryptoOperationData} Coefficients a of the elliptic curve E
    • + *
    • b {@link CryptoOperationData} Coefficients b of the elliptic curve E
    • + *
    • q {@link CryptoOperationData} Prime number - order of cyclic group
    • + *
    • x {@link CryptoOperationData} Base point p x-coordinate
    • + *
    • y {@link CryptoOperationData} Base point p y-coordinate
    • + *
    + *
  • + *
+ * + * @class GostSign + * @param {AlgorithmIndentifier} algorithm + */ +function GostSign(algorithm) // +{ + algorithm = algorithm || {}; + this.name = (algorithm.name || 'GOST R 34.10') + '-' + + ((algorithm.version || 2012) % 100) + '-' + (algorithm.length || 256) + + (((algorithm.mode || 'SIGN') !== 'SIGN') ? '-' + algorithm.mode : '') + + (typeof algorithm.namedParam === 'string' ? '/' + algorithm.namedParam : '') + + (typeof algorithm.namedCurve === 'string' ? '/' + algorithm.namedCurve : '') + + (typeof algorithm.sBox === 'string' ? '/' + algorithm.sBox : ''); + + var version = algorithm.version || 2012; + + // Functions + switch (algorithm.mode || 'SIGN') { + case 'SIGN': + this.sign = sign; + this.verify = verify; + this.generateKey = generateKey; + break; + case 'DH': + this.deriveBits = deriveBits; + this.deriveKey = deriveKey; + this.generateKey = generateKey; + break; + case 'MASK': + this.wrapKey = wrapKey; + this.unwrapKey = unwrapKey; + this.generateKey = generateMaskKey; + break; + } + + // Define parameters + if (version === 1994) { + // Named or parameters algorithm + var param = algorithm.param; + if (!param) + param = GostParams[this.namedParam = (algorithm.namedParam || 'S-A').toUpperCase()]; + this.modulusLength = algorithm.modulusLength || param.modulusLength || 1024; + this.p = htobi(param.p); + this.q = htobi(param.q); + this.a = htobi(param.a); + // Public key for derive + if (algorithm['public']) + this.peer_y = atobi(algorithm['public']); + } else { + // Named or parameters algorithm + var param = algorithm.curve; + if (!param) + param = ECGostParams[this.namedCurve = (algorithm.namedCurve || 'S-256-A').toUpperCase()]; + var curve = this.curve = newCurve(htobi(param.p), htobi(param.a), htobi(param.b)); + this.P = newEC(curve, + newFE(curve, htobi(param.x)), + newFE(curve, htobi(param.y))); + this.q = htobi(param.q); + // Public key for derive + if (algorithm['public']) { + var k2 = to2(algorithm['public']); + this.peer_Q = new newEC(this.curve, // This corresponds to the binary representation of (256||256) + newFE(this.curve, k2[0]), // first 32 octets contain the little-endian representation of x + newFE(this.curve, k2[1])); // and second 32 octets contain the little-endian representation of y. + } + } + + // Check bit length + var hashLen, keyLen; + if (this.curve) { + keyLen = algorithm.length || bitLength(this.q); + if (keyLen > 508 && keyLen <= 512) + keyLen = 512; + else if (keyLen > 254 && keyLen <= 256) + keyLen = 256; + else + throw new NotSupportedError('Support keys only 256 or 512 bits length'); + hashLen = keyLen; + } else { + keyLen = algorithm.modulusLength || bitLength(this.p); + if (keyLen > 1016 && keyLen <= 1024) + keyLen = 1024; + else if (keyLen > 508 && keyLen <= 512) + keyLen = 512; + else + throw new NotSupportedError('Support keys only 512 or 1024 bits length'); + hashLen = 256; + } + this.bitLength = hashLen; + this.keyLength = keyLen; + + // Algorithm proceator for result conversion + this.procreator = algorithm.procreator; + + // Hash function definition + var hash = algorithm.hash; + if (hash) { + if (typeof hash === 'string' || hash instanceof String) + hash = {name: hash}; + if (algorithm.version === 1994 || algorithm.version === 2001) { + hash.version = 1994; + hash.length = 256; + hash.sBox = algorithm.sBox || hash.sBox; + } else { + hash.version = 2012; + hash.length = hashLen; + } + hash.procreator = hash.procreator || algorithm.procreator; + + if (!GostDigest) + GostDigest = root.GostDigest; + if (!GostDigest) + throw new NotSupportedError('Object GostDigest not found'); + + this.hash = new GostDigest(hash); + } + + // Pregenerated seed for key exchange algorithms + if (algorithm.ukm) // Now don't check size + this.ukm = algorithm.ukm; + +} // + +export default GostSign; diff --git a/plugins/srktoolbox/src/core/vendor/remove-exif.mjs b/plugins/srktoolbox/src/core/vendor/remove-exif.mjs new file mode 100644 index 00000000..6c5c9e53 --- /dev/null +++ b/plugins/srktoolbox/src/core/vendor/remove-exif.mjs @@ -0,0 +1,151 @@ +/* piexifjs +The MIT License (MIT) +Copyright (c) 2014, 2015 hMatoba(https://github.com/hMatoba) +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +import Utils from "../Utils.mjs"; + +// Param jpeg should be a binaryArray +export function removeEXIF(jpeg) { + // Convert binaryArray to char string + jpeg = Utils.byteArrayToChars(jpeg); + if (jpeg.slice(0, 2) != "\xff\xd8") { + throw ("Given data is not jpeg."); + } + + var segments = splitIntoSegments(jpeg); + if (segments[1].slice(0, 2) == "\xff\xe1" && + segments[1].slice(4, 10) == "Exif\x00\x00") { + segments = [segments[0]].concat(segments.slice(2)); + } else if (segments[2].slice(0, 2) == "\xff\xe1" && + segments[2].slice(4, 10) == "Exif\x00\x00") { + segments = segments.slice(0, 2).concat(segments.slice(3)); + } else { + throw ("Exif not found."); + } + + var new_data = segments.join(""); + + // Convert back to binaryArray + new_data = Utils.strToCharcode(new_data); + + return new_data; +}; + +function splitIntoSegments(data) { + if (data.slice(0, 2) != "\xff\xd8") { + throw ("Given data isn't JPEG."); + } + + var head = 2; + var segments = ["\xff\xd8"]; + while (true) { + if (data.slice(head, head + 2) == "\xff\xda") { + segments.push(data.slice(head)); + break; + } else { + var length = unpack(">H", data.slice(head + 2, head + 4))[0]; + var endPoint = head + length + 2; + segments.push(data.slice(head, endPoint)); + head = endPoint; + } + + if (head >= data.length) { + throw ("Wrong JPEG data."); + } + } + return segments; +} + +function unpack(mark, str) { + if (typeof(str) != "string") { + throw ("'unpack' error. Got invalid type argument."); + } + var l = 0; + for (var markPointer = 1; markPointer < mark.length; markPointer++) { + if (mark[markPointer].toLowerCase() == "b") { + l += 1; + } else if (mark[markPointer].toLowerCase() == "h") { + l += 2; + } else if (mark[markPointer].toLowerCase() == "l") { + l += 4; + } else { + throw ("'unpack' error. Got invalid mark."); + } + } + + if (l != str.length) { + throw ("'unpack' error. Mismatch between symbol and string length. " + l + ":" + str.length); + } + + var littleEndian; + if (mark[0] == "<") { + littleEndian = true; + } else if (mark[0] == ">") { + littleEndian = false; + } else { + throw ("'unpack' error."); + } + var unpacked = []; + var strPointer = 0; + var p = 1; + var val = null; + var c = null; + var length = null; + var sliced = ""; + + while (c = mark[p]) { + if (c.toLowerCase() == "b") { + length = 1; + sliced = str.slice(strPointer, strPointer + length); + val = sliced.charCodeAt(0); + if ((c == "b") && (val >= 0x80)) { + val -= 0x100; + } + } else if (c == "H") { + length = 2; + sliced = str.slice(strPointer, strPointer + length); + if (littleEndian) { + sliced = sliced.split("").reverse().join(""); + } + val = sliced.charCodeAt(0) * 0x100 + + sliced.charCodeAt(1); + } else if (c.toLowerCase() == "l") { + length = 4; + sliced = str.slice(strPointer, strPointer + length); + if (littleEndian) { + sliced = sliced.split("").reverse().join(""); + } + val = sliced.charCodeAt(0) * 0x1000000 + + sliced.charCodeAt(1) * 0x10000 + + sliced.charCodeAt(2) * 0x100 + + sliced.charCodeAt(3); + if ((c == "l") && (val >= 0x80000000)) { + val -= 0x100000000; + } + } else { + throw ("'unpack' error. " + c); + } + + unpacked.push(val); + strPointer += length; + p += 1; + } + + return unpacked; +} diff --git a/plugins/srktoolbox/src/core/vendor/tesseract/lang-data/eng.traineddata.gz b/plugins/srktoolbox/src/core/vendor/tesseract/lang-data/eng.traineddata.gz new file mode 100644 index 00000000..e83c1267 Binary files /dev/null and b/plugins/srktoolbox/src/core/vendor/tesseract/lang-data/eng.traineddata.gz differ diff --git a/plugins/srktoolbox/src/node/File.mjs b/plugins/srktoolbox/src/node/File.mjs new file mode 100644 index 00000000..7a38ba16 --- /dev/null +++ b/plugins/srktoolbox/src/node/File.mjs @@ -0,0 +1,76 @@ +/** + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import { detectFileType } from "../core/lib/FileType.mjs"; + + +/** + * FileShim + * + * Create a class that behaves like the File object in the Browser so that + * operations that use the File object still work. + * + * File doesn't write to disk, but it would be easy to do so with e.gfs.writeFile. + */ +class File { + + /** + * Constructor + * + * https://w3c.github.io/FileAPI/#file-constructor + * + * @param {String|Array|ArrayBuffer|Buffer|[File]} bits - file content + * @param {String} name (optional) - file name + * @param {Object} stats (optional) - file stats e.g. lastModified + */ + constructor(data, name="", stats={}) { + + if (!Array.isArray(data)) { + data = [data]; + } + + const buffers = data.map((d) => { + if (d instanceof File) { + return Buffer.from(d.data); + } + + if (d instanceof ArrayBuffer) { + return Buffer.from(d); + } + + return Buffer.from(d); + }); + const totalLength = buffers.reduce((p, c) => p + c.length, 0); + this.data = Buffer.concat(buffers, totalLength); + + this.name = name; + this.lastModified = stats.lastModified || Date.now(); + + const types = detectFileType(this.data); + if (types.length) { + this.type = types[0].mime; + } else { + this.type = "application/unknown"; + } + } + + /** + * size property + */ + get size() { + return this.data.length; + } + + /** + * Return lastModified as Date + */ + get lastModifiedDate() { + return new Date(this.lastModified); + } + +} + +export default File; diff --git a/plugins/srktoolbox/src/node/NodeDish.mjs b/plugins/srktoolbox/src/node/NodeDish.mjs new file mode 100644 index 00000000..ef9fd11a --- /dev/null +++ b/plugins/srktoolbox/src/node/NodeDish.mjs @@ -0,0 +1,84 @@ +/** + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import util from "util"; +import Dish from "../core/Dish.mjs"; + +/** + * Subclass of Dish for use in the Node.js environment. + * + * Adds some helper functions and improves coercion for Node.js logging. + */ +class NodeDish extends Dish { + + /** + * Create a Dish + * @param {any} inputOrDish - The dish input + * @param {String|Number} - The dish type, as enum or string + */ + constructor(inputOrDish=null, type=null) { + + // Allow `fs` file input: + // Any node fs Buffers transformed to array buffer + // Use Array.from as Uint8Array doesnt pass instanceof Array test + if (Buffer.isBuffer(inputOrDish)) { + inputOrDish = Array.from(inputOrDish); + type = Dish.BYTE_ARRAY; + } + super(inputOrDish, type); + } + + /** + * Apply the inputted operation to the dish. + * + * @param {WrappedOperation} operation the operation to perform + * @param {*} args - any arguments for the operation + * @returns {Dish} a new dish with the result of the operation. + */ + apply(operation, args=null) { + return operation(this, args); + } + + /** + * alias for get + * @param args see get args + */ + to(...args) { + return this.get(...args); + } + + /** + * Avoid coercion to a String primitive. + */ + toString() { + return this.presentAs(Dish.typeEnum("string")); + } + + /** + * What we want to log to the console. + */ + [util.inspect.custom](depth, options) { + return this.presentAs(Dish.typeEnum("string")); + } + + /** + * Backwards compatibility for node v6 + * Log only the value to the console in node. + */ + inspect() { + return this.presentAs(Dish.typeEnum("string")); + } + + /** + * Avoid coercion to a Number primitive. + */ + valueOf() { + return this.presentAs(Dish.typeEnum("number")); + } + +} + +export default NodeDish; diff --git a/plugins/srktoolbox/src/node/NodeRecipe.mjs b/plugins/srktoolbox/src/node/NodeRecipe.mjs new file mode 100644 index 00000000..bad8fc27 --- /dev/null +++ b/plugins/srktoolbox/src/node/NodeRecipe.mjs @@ -0,0 +1,104 @@ +/** + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import {operations} from "./index.mjs"; +import { sanitise } from "./apiUtils.mjs"; + +/** + * Similar to core/Recipe, Recipe controls a list of operations and + * the NodeDish the operate on. However, this Recipe is for the node + * environment. + */ +class NodeRecipe { + + /** + * Recipe constructor + * @param recipeConfig + */ + constructor(recipeConfig) { + this._parseConfig(recipeConfig); + } + + + /** + * Validate an ingredient & coerce to operation if necessary. + * @param {String | Function | Object} ing + * @returns {Function || Object} The operation, or an object with the + * operation and its arguments + * @throws {TypeError} If it cannot find the operation in chef's list of operations. + */ + _validateIngredient(ing) { + // CASE operation name given. Find operation and validate + if (typeof ing === "string") { + const op = operations.find((op) => { + return sanitise(op.opName) === sanitise(ing); + }); + if (op) { + // Need to validate against case 2 + return this._validateIngredient(op); + } else { + throw new TypeError(`Couldn't find an operation with name '${ing}'.`); + } + // CASE operation given. Check its a chef operation and check its not flowcontrol + } else if (typeof ing === "function") { + if (ing.flowControl) { + throw new TypeError(`flowControl operations like ${ing.opName} are not currently allowed in recipes for chef.bake in the Node API`); + } + + if (operations.includes(ing)) { + return ing; + } else { + throw new TypeError("Inputted function not a Chef operation."); + } + // CASE: op, maybe with configuration + } else if (ing.op) { + const sanitisedOp = this._validateIngredient(ing.op); + if (ing.args) { + return {op: sanitisedOp, args: ing.args}; + } + return sanitisedOp; + } else { + throw new TypeError("Recipe can only contain function names or functions"); + } + } + + + /** + * Parse an opList from a recipeConfig and assign it to the recipe's opList. + * @param {String | Function | String[] | Function[] | [String | Function]} recipeConfig + */ + _parseConfig(recipeConfig) { + if (!recipeConfig) { + this.opList = []; + return; + } + + if (!Array.isArray(recipeConfig)) { + recipeConfig = [recipeConfig]; + } + + this.opList = recipeConfig.map((ing) => this._validateIngredient(ing)); + } + + /** + * Run the dish through each operation, one at a time. + * @param {NodeDish} dish + * @returns {NodeDish} + */ + execute(dish) { + return this.opList.reduce((prev, curr) => { + // CASE where opList item is op and args + if (Object.prototype.hasOwnProperty.call(curr, "op") && + Object.prototype.hasOwnProperty.call(curr, "args")) { + return curr.op(prev, curr.args); + } + // CASE opList item is just op. + return curr(prev); + }, dish); + } +} + +export default NodeRecipe; diff --git a/plugins/srktoolbox/src/node/api.mjs b/plugins/srktoolbox/src/node/api.mjs new file mode 100644 index 00000000..a6f4f21a --- /dev/null +++ b/plugins/srktoolbox/src/node/api.mjs @@ -0,0 +1,353 @@ +/** + * Wrap operations for consumption in Node. + * + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +/* eslint no-console: ["off"] */ + +import NodeDish from "./NodeDish.mjs"; +import NodeRecipe from "./NodeRecipe.mjs"; +import OperationConfig from "../core/config/OperationConfig.json" with {type: "json"}; +import { sanitise, removeSubheadingsFromArray, sentenceToCamelCase } from "./apiUtils.mjs"; +import ExcludedOperationError from "../core/errors/ExcludedOperationError.mjs"; + + +/** + * transformArgs + * + * Take the default args array and update with any user-defined + * operation arguments. Allows user to define arguments in object style, + * with accommodating name matching. Using named args in the API is more + * clear to the user. + * + * Argument name matching is case and space insensitive + * @private + * @param {Object[]} originalArgs - the operation-s args list + * @param {Object} newArgs - any inputted args + */ +function transformArgs(opArgsList, newArgs) { + + if (newArgs && Array.isArray(newArgs)) { + return newArgs; + } + + // Filter out arg values that are list subheadings - they are surrounded in []. + // See Strings op for example. + const opArgs = Object.assign([], opArgsList).map((a) => { + if (Array.isArray(a.value)) { + a.value = removeSubheadingsFromArray(a.value); + } + return a; + }); + + // Reconcile object style arg info to fit operation args shape. + if (newArgs) { + Object.keys(newArgs).map((key) => { + const index = opArgs.findIndex((arg) => { + return arg.name.toLowerCase().replace(/ /g, "") === + key.toLowerCase().replace(/ /g, ""); + }); + + if (index > -1) { + const argument = opArgs[index]; + if (argument.type === "toggleString") { + if (typeof newArgs[key] === "string") { + argument.string = newArgs[key]; + } else { + argument.string = newArgs[key].string; + argument.option = newArgs[key].option; + } + } else if (argument.type === "editableOption") { + // takes key: "option", key: {name, val: "string"}, key: {name, val: [...]} + argument.value = typeof newArgs[key] === "string" ? newArgs[key]: newArgs[key].value; + } else { + argument.value = newArgs[key]; + } + } + }); + } + + // Sanitise args + return opArgs.map((arg) => { + if (arg.type === "option") { + // pick default option if not already chosen + return typeof arg.value === "string" ? arg.value : arg.value[arg.defaultIndex ?? 0]; + } + + if (arg.type === "editableOption") { + return typeof arg.value === "string" ? arg.value : arg.value[arg.defaultIndex ?? 0].value; + } + + if (arg.type === "toggleString") { + // ensure string and option exist when user hasn't defined + arg.string = arg.string || ""; + arg.option = arg.option || arg.toggleValues[0]; + return arg; + } + + return arg.value; + }); +} + + +/** + * Ensure an input is a SyncDish object. + * @param input + */ +function ensureIsDish(input) { + if (!input) { + return new NodeDish(); + } + + if (input instanceof NodeDish) { + return input; + } else { + return new NodeDish(input); + } +} + + +/** + * prepareOp: transform args, make input the right type. + * Also convert any Buffers to ArrayBuffers. + * @param opInstance - instance of the operation + * @param input - operation input + * @param args - operation args + */ +function prepareOp(opInstance, input, args) { + const dish = ensureIsDish(input); + // Transform object-style args to original args array + const transformedArgs = transformArgs(opInstance.args, args); + const transformedInput = dish.get(opInstance.inputType); + return {transformedInput, transformedArgs}; +} + + +/** + * createArgInfo + * + * Create an object of options for each argument in the given operation + * + * Argument names are converted to camel case for consistency. + * + * @param {Operation} op - the operation to extract args from + * @returns {{}} - arrays of options for args. +*/ +function createArgInfo(op) { + const result = {}; + op.args.forEach((a) => { + if (a.type === "option" || a.type === "editableOption") { + result[sentenceToCamelCase(a.name)] = { + type: a.type, + options: removeSubheadingsFromArray(a.value) + }; + } else if (a.type === "toggleString") { + result[sentenceToCamelCase(a.name)] = { + type: a.type, + value: a.value, + toggleValues: removeSubheadingsFromArray(a.toggleValues), + }; + } else { + result[sentenceToCamelCase(a.name)] = { + type: a.type, + value: a.value, + }; + } + }); + + return result; +} + + +/** + * Wrap an operation to be consumed by node API. + * Checks to see if run function is async or not. + * new Operation().run() becomes operation() + * Perform type conversion on input + * @private + * @param {Operation} Operation + * @returns {Function} The operation's run function, wrapped in + * some type conversion logic + */ +export function _wrap(OpClass) { + + // Check to see if class's run function is async. + const opInstance = new OpClass(); + const isAsync = opInstance.run.constructor.name === "AsyncFunction"; + const isFlowControl = opInstance.flowControl; + + let wrapped; + + // If async, wrap must be async. + if (isAsync) { + /** + * Async wrapped operation run function + * @param {*} input + * @param {Object | String[]} args - either in Object or normal args array + * @returns {Promise} operation's output, on a Dish. + * @throws {OperationError} if the operation throws one. + */ + wrapped = async (input, args=null) => { + const {transformedInput, transformedArgs} = prepareOp(opInstance, input, args); + + // SPECIAL CASE for Magic. Other flowControl operations will + // not work because the opList is not passed in. + if (isFlowControl) { + opInstance.ingValues = transformedArgs; + + const state = { + progress: 0, + dish: ensureIsDish(transformedInput), + opList: [opInstance], + }; + + const updatedState = await opInstance.run(state); + + return new NodeDish({ + value: updatedState.dish.value, + type: opInstance.outputType, + }); + } + + const result = await opInstance.run(transformedInput, transformedArgs); + + return new NodeDish({ + value: result, + type: opInstance.outputType, + }); + }; + } else { + /** + * wrapped operation run function + * @param {*} input + * @param {Object | String[]} args - either in Object or normal args array + * @returns {SyncDish} operation's output, on a Dish. + * @throws {OperationError} if the operation throws one. + */ + wrapped = (input, args=null) => { + const {transformedInput, transformedArgs} = prepareOp(opInstance, input, args); + const result = opInstance.run(transformedInput, transformedArgs); + return new NodeDish({ + value: result, + type: opInstance.outputType, + }); + }; + } + + // used in chef.help + // Raka-loah: Operation class names are no longer the same with operation names after i18n + // so switch to operation instance names. + // wrapped.opName = OpClass.name; + wrapped.opName = opInstance.name; + wrapped.args = createArgInfo(opInstance); + // Used in NodeRecipe to check for flowControl ops + wrapped.flowControl = isFlowControl; + + return wrapped; +} + + +/** + * help: Give information about operations matching the given search term, + * or inputted operation. + * + * @param {String || wrapped operation} input - the name of the operation to get help for. + * Case and whitespace are ignored in search. + * @returns {Object[]} Config of matching operations. + */ +export function help(input) { + let searchTerm = false; + if (typeof input === "string") { + searchTerm = input; + } else if (typeof input === "function") { + searchTerm = input.opName; + } + + if (!searchTerm) { + return null; + } + + let exactMatchExists = false; + + // Look for matches in operation name and description, listing name + // matches first. + const matches = Object.keys(OperationConfig) + // hydrate operation: swap op name for op config object (with name) + .map((m) => { + const hydrated = OperationConfig[m]; + hydrated.name = m; + + // flag up an exact name match. Only first exact match counts. + if (!exactMatchExists) { + exactMatchExists = sanitise(hydrated.name) === sanitise(searchTerm); + } + // Return hydrated along with what type of match it was + return { + hydrated, + nameExactMatch: sanitise(hydrated.name) === sanitise(searchTerm), + nameMatch: sanitise(hydrated.name).includes(sanitise(searchTerm)), + descMatch: sanitise(hydrated.description).includes(sanitise(searchTerm)) + }; + }) + // Filter out non-matches. If exact match exists, filter out all others. + .filter((result) => { + if (exactMatchExists) { + return !!result.nameExactMatch; + } + return result.nameMatch || result.descMatch; + }) + // sort results with name match first + .sort((a, b) => { + const aInt = a.nameMatch ? 1 : 0; + const bInt = b.nameMatch ? 1 : 0; + return bInt - aInt; + }) + // extract just the hydrated config + .map(result => result.hydrated); + + if (matches && matches.length) { + // console.log(`${matches.length} result${matches.length > 1 ? "s" : ""} found.`); + return matches; + } + + // console.log("No results found."); + return null; +} + + +/** + * bake + * + * @param {*} input - some input for a recipe. + * @param {String | Function | String[] | Function[] | [String | Function]} recipeConfig - + * An operation, operation name, or an array of either. + * @returns {NodeDish} of the result + * @throws {TypeError} if invalid recipe given. + */ +export function bake(input, recipeConfig) { + const recipe = new NodeRecipe(recipeConfig); + const dish = ensureIsDish(input); + return recipe.execute(dish); +} + + +/** + * explainExcludedFunction + * + * Explain that the given operation is not included in the Node.js version. + * @param {String} name - name of operation + */ +export function _explainExcludedFunction(name) { + /** + * Throw new error type with useful message. + */ + const func = () => { + throw new ExcludedOperationError(`Sorry, the ${name} operation is not available in the Node.js version of CyberChef.`); + }; + // Add opName prop so NodeRecipe can handle it, just like wrap does. + func.opName = name; + return func; +} diff --git a/plugins/srktoolbox/src/node/apiUtils.mjs b/plugins/srktoolbox/src/node/apiUtils.mjs new file mode 100644 index 00000000..9d1c43cc --- /dev/null +++ b/plugins/srktoolbox/src/node/apiUtils.mjs @@ -0,0 +1,86 @@ +/** + * Utility functions for the node environment + * + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + + +/** + * someName => Somename + * + * @param {String} str = string to be altered + * @returns {String} + */ +const capitalise = function capitalise(str) { + // Don't edit names that start with 2+ caps + if (/^[A-Z0-9]{2,}/g.test(str)) { + return str; + } + // reserved. Don't change for now. + if (str === "Return") { + return str; + } + + return `${str.charAt(0).toUpperCase()}${str.substr(1).toLowerCase()}`; +}; + + +/** + * SomeName => someName + * @param {String} name - string to be altered + * @returns {String} decapitalised + */ +export function decapitalise(str) { + // Don't decapitalise str that start with 2+ caps + if (/^[A-Z0-9]{2,}/g.test(str)) { + return str; + } + // reserved. Don't change for now. + if (str === "Return") { + return str; + } + + return `${str.charAt(0).toLowerCase()}${str.substr(1)}`; +} + + +/** + * Remove strings surrounded with [] from the given array. +*/ +export function removeSubheadingsFromArray(array) { + if (Array.isArray(array)) { + return array.filter((i) => { + if (typeof i === "string") { + return !i.match(/^\[[\s\S]*\]$/); + } + return true; + }); + } +} + + +/** + * Remove spaces, make lower case. + * @param str + */ +export function sanitise(str) { + return str.replace(/[/\s.-]/g, "").toLowerCase(); +} + + +/** + * something like this => somethingLikeThis + * ABC a sentence => ABCASentence +*/ +export function sentenceToCamelCase(str) { + return str.split(" ") + .map((s, index) => { + if (index === 0) { + return decapitalise(s); + } + return capitalise(s); + }) + .reduce((prev, curr) => `${prev}${curr}`, ""); +} diff --git a/plugins/srktoolbox/src/node/config/excludedOperations.mjs b/plugins/srktoolbox/src/node/config/excludedOperations.mjs new file mode 100644 index 00000000..9359475d --- /dev/null +++ b/plugins/srktoolbox/src/node/config/excludedOperations.mjs @@ -0,0 +1,24 @@ +/** + * Operations to exclude from the Node API + * + * @author d98762656 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ +export default [ + // This functionality can be done more easily using JavaScript + "Fork", + "Merge", + "Jump", + "ConditionalJump", + "Label", + "Comment", + + // esprima doesn't work in .mjs + "JavaScriptBeautify", + "JavaScriptMinify", + "JavaScriptParser", + + // Irrelevant in Node console + "SyntaxHighlighter", +]; diff --git a/plugins/srktoolbox/src/node/config/scripts/generateNodeIndex.mjs b/plugins/srktoolbox/src/node/config/scripts/generateNodeIndex.mjs new file mode 100644 index 00000000..981c9b79 --- /dev/null +++ b/plugins/srktoolbox/src/node/config/scripts/generateNodeIndex.mjs @@ -0,0 +1,127 @@ +/** + * This script generates the exports functionality for the node API. + * + * it exports chef as default, but all the wrapped operations as + * other top level exports. + * + * @author d98762656 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +/* eslint no-console: 0 */ + +import fs from "fs"; +import path from "path"; +import * as operations from "../../../core/operations/index.mjs"; +import { decapitalise } from "../../apiUtils.mjs"; +import excludedOperations from "../excludedOperations.mjs"; + +const includedOperations = Object.keys(operations).filter((op => excludedOperations.indexOf(op) === -1)); + +const dir = path.join(`${process.cwd()}/src/node`); +if (!fs.existsSync(dir)) { + console.log("\nCWD: " + process.cwd()); + console.log("Error: generateNodeIndex.mjs should be run from the project root"); + console.log("Example> node --experimental-modules src/node/config/scripts/generateNodeIndex.mjs"); + process.exit(1); +} + +let code = `/** +* THIS FILE IS AUTOMATICALLY GENERATED BY src/node/config/scripts/generateNodeIndex.mjs +* +* @author d98762625 [d98762625@gmail.com] +* @copyright Crown Copyright 2019 +* @license Apache-2.0 +*/ + +/* eslint camelcase: 0 */ + + +import NodeDish from "./NodeDish.mjs"; +import { _wrap, help, bake, _explainExcludedFunction } from "./api.mjs"; +import File from "./File.mjs"; +import { OperationError, DishError, ExcludedOperationError } from "../core/errors/index.mjs"; +import { + // import as core_ to avoid name clashes after wrap. +`; + +includedOperations.forEach((op) => { + // prepend with core_ to avoid name collision later. + code += ` ${op} as core_${op},\n`; +}); + +code +=` +} from "../core/operations/index.mjs"; + +global.File = File; + +/** + * generateChef + * + * Creates decapitalised, wrapped ops in chef object for default export. + */ +function generateChef() { + return { +`; + +includedOperations.forEach((op) => { + code += ` "${decapitalise(op)}": _wrap(core_${op}),\n`; +}); + +excludedOperations.forEach((op) => { + code += ` "${decapitalise(op)}": _explainExcludedFunction("${op}"),\n`; +}); + +code += ` }; +} + +const chef = generateChef(); +// Add some additional features to chef object. +chef.help = help; +chef.Dish = NodeDish; + +// Define consts here so we can add to top-level export - wont allow +// export of chef property. +`; + +Object.keys(operations).forEach((op) => { + code += `const ${decapitalise(op)} = chef.${decapitalise(op)};\n`; +}); + +code +=` + +// Define array of all operations to create register for bake. +const operations = [\n`; + +Object.keys(operations).forEach((op) => { + code += ` ${decapitalise(op)},\n`; +}); + +code += `]; + +chef.bake = bake; +export default chef; + +// Operations as top level exports. +export { + operations, +`; + +Object.keys(operations).forEach((op) => { + code += ` ${decapitalise(op)},\n`; +}); + +code += " NodeDish as Dish,\n"; +code += " bake,\n"; +code += " help,\n"; +code += " OperationError,\n"; +code += " ExcludedOperationError,\n"; +code += " DishError,\n"; +code += "};\n"; + + +fs.writeFileSync( + path.join(dir, "./index.mjs"), + code +); diff --git a/plugins/srktoolbox/src/node/repl.mjs b/plugins/srktoolbox/src/node/repl.mjs new file mode 100644 index 00000000..9846ff31 --- /dev/null +++ b/plugins/srktoolbox/src/node/repl.mjs @@ -0,0 +1,36 @@ +/** + * Create a REPL server for chef + * + * + * @author d98762656 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import chef from "./index.mjs"; +import repl from "repl"; + + +/* eslint no-console: ["off"] */ + +console.log(` + ______ __ ________ ____ + / ____/_ __/ /_ ___ _____/ ____/ /_ ___ / __/ + / / / / / / __ \\/ _ \\/ ___/ / / __ \\/ _ \\/ /_ +/ /___/ /_/ / /_/ / __/ / / /___/ / / / __/ __/ +\\____/\\__, /_.___/\\___/_/ \\____/_/ /_/\\___/_/ + /____/ + +`); +const replServer = repl.start({ + prompt: "chef > ", +}); + +global.File = chef.File; + +Object.keys(chef).forEach((key) => { + if (key !== "operations") { + replServer.context[key] = chef[key]; + } +}); + diff --git a/plugins/srktoolbox/src/node/wrapper.js b/plugins/srktoolbox/src/node/wrapper.js new file mode 100644 index 00000000..80b941fc --- /dev/null +++ b/plugins/srktoolbox/src/node/wrapper.js @@ -0,0 +1,11 @@ +/** + * Export the main ESM module as CommonJS + * + * + * @author d98762656 [d98762625@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +module.exports = (async () => await import("./index.mjs"))(); +module.exports.File = (async () => await import("./File.mjs"))(); diff --git a/plugins/srktoolbox/src/web/App.mjs b/plugins/srktoolbox/src/web/App.mjs new file mode 100644 index 00000000..7eb142fa --- /dev/null +++ b/plugins/srktoolbox/src/web/App.mjs @@ -0,0 +1,858 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github + */ + +import Utils, { debounce } from "../core/Utils.mjs"; +import {fromBase64} from "../core/lib/Base64.mjs"; +import Manager from "./Manager.mjs"; +import HTMLCategory from "./HTMLCategory.mjs"; +import HTMLOperation from "./HTMLOperation.mjs"; +import Split from "split.js"; +import moment from "moment-timezone"; +import cptable from "codepage"; +import toastr from "toastr"; +import "../../node_modules/toastr/build/toastr.min.css"; + + +/** + * HTML view for CyberChef responsible for building the web page and dealing with all user + * interactions. + */ +class App { + + /** + * App constructor. + * + * @param {CatConf[]} categories - The list of categories and operations to be populated. + * @param {Object.} operations - The list of operation configuration objects. + * @param {String[]} defaultFavourites - A list of default favourite operations. + * @param {Object} options - Default setting for app options. + */ + constructor(categories, operations, defaultFavourites, defaultOptions) { + this.categories = categories; + this.operations = operations; + this.dfavourites = defaultFavourites; + this.doptions = defaultOptions; + this.options = Object.assign({}, defaultOptions); + + this.manager = new Manager(this); + + this.baking = false; + this.autoBake_ = false; + this.progress = 0; + this.ingId = 0; + + this.appLoaded = false; + this.workerLoaded = false; + this.waitersLoaded = false; + + this.snackbars = []; + } + + + /** + * This function sets up the stage and creates listeners for all events. + * + * @fires Manager#appstart + */ + setup() { + document.dispatchEvent(this.manager.appstart); + + this.initialiseSplitter(); + this.loadLocalStorage(); + this.manager.options.applyPreferredColorScheme(); + this.populateOperationsList(); + this.manager.setup(); + this.manager.output.saveBombe(); + this.adjustComponentSizes(); + this.setCompileMessage(); + this.uriParams = this.getURIParams(); + + log.debug("App loaded"); + this.appLoaded = true; + this.loaded(); + } + + + /** + * Fires once all setup activities have completed. + * + * @fires Manager#apploaded + */ + loaded() { + // Check that both the app and the worker have loaded successfully, and that + // we haven't already loaded before attempting to remove the loading screen. + if (!this.workerLoaded || !this.appLoaded || !this.waitersLoaded || + !document.getElementById("loader-wrapper")) return; + + // Load state from URI + this.loadURIParams(this.uriParams); + + // Trigger CSS animations to remove preloader + document.body.classList.add("loaded"); + + // Wait for animations to complete then remove the preloader and loaded style + // so that the animations for existing elements don't play again. + setTimeout(function() { + document.getElementById("loader-wrapper").remove(); + document.body.classList.remove("loaded"); + + // Bake initial input + this.manager.input.bakeAll(); + }.bind(this), 1000); + + // Clear the loading message interval + clearInterval(window.loadingMsgsInt); + + // Remove the loading error handler + window.removeEventListener("error", window.loadingErrorHandler); + + document.dispatchEvent(this.manager.apploaded); + + this.manager.input.calcMaxTabs(); + this.manager.output.calcMaxTabs(); + + this.manager.ops.setCatCount(); + + toastr.options = { + "closeButton": false, + "debug": false, + "newestOnTop": false, + "progressBar": false, + "positionClass": "toast-bottom-center", + "preventDuplicates": false, + "onclick": null, + "showDuration": "300", + "hideDuration": "1000", + "timeOut": "5000", + "extendedTimeOut": "1000", + "showEasing": "swing", + "hideEasing": "linear", + "showMethod": "fadeIn", + "hideMethod": "fadeOut" + }; + } + + + /** + * An error handler for displaying the error to the user. + * + * @param {Error} err + * @param {boolean} [logToConsole=false] + */ + handleError(err, logToConsole) { + if (logToConsole) log.error(err); + const msg = err.displayStr || err.toString(); + this.alert(Utils.escapeHtml(msg), this.options.errorTimeout, !this.options.showErrors); + } + + + /** + * Asks the ChefWorker to bake the current input using the current recipe. + * + * @param {boolean} [step] - Set to true if we should only execute one operation instead of the + * whole recipe. + */ + bake(step=false) { + if (this.baking) return; + + // Reset attemptHighlight flag + this.options.attemptHighlight = true; + + // Remove all current indicators + this.manager.recipe.updateBreakpointIndicator(false); + + this.manager.worker.bake( + this.getRecipeConfig(), // The configuration of the recipe + this.options, // Options set by the user + this.progress, // The current position in the recipe + step // Whether or not to take one step or execute the whole recipe + ); + } + + + /** + * Runs Auto Bake if it is set. + */ + autoBake() { + if (this.baking) { + this.manager.worker.cancelBakeForAutoBake(); + this.baking = false; + } + + if (this.autoBake_) { + log.debug("Auto-baking"); + this.manager.worker.bakeInputs({ + nums: [this.manager.tabs.getActiveTab("input")], + step: false + }); + } else { + this.manager.controls.showStaleIndicator(); + } + } + + + /** + * Executes the next step of the recipe. + */ + step() { + if (this.baking) return; + + // Reset status using cancelBake + this.manager.worker.cancelBake(true, false); + + const activeTab = this.manager.tabs.getActiveTab("input"); + if (activeTab === -1) return; + + let progress = 0; + if (this.manager.output.outputs[activeTab].progress !== false) { + log.error(this.manager.output.outputs[activeTab]); + progress = this.manager.output.outputs[activeTab].progress; + } + + this.manager.input.inputWorker.postMessage({ + action: "step", + data: { + activeTab: activeTab, + progress: progress + 1 + } + }); + } + + + /** + * Runs a silent bake, forcing the browser to load and cache all the relevant JavaScript code needed + * to do a real bake. + * + * 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 wake up the ChefWorker with an empty recipe. + */ + silentBake() { + let recipeConfig = []; + + if (this.autoBake_) { + // If auto-bake is not enabled we don't want to actually run the recipe as it may be disabled + // for a good reason. + recipeConfig = this.getRecipeConfig(); + } + + this.manager.worker.silentBake(recipeConfig); + } + + + /** + * Sets the user's input data. + * + * @param {string} input - The string to set the input to + */ + setInput(input) { + // Get the currently active tab. + // If there isn't one, assume there are no inputs so use inputNum of 1 + let inputNum = this.manager.tabs.getActiveTab("input"); + if (inputNum === -1) inputNum = 1; + this.manager.input.updateInputValue(inputNum, input); + + this.manager.input.inputWorker.postMessage({ + action: "setInput", + data: { + inputNum: inputNum, + silent: true + } + }); + } + + + /** + * Populates the operations accordion list with the categories and operations specified in the + * view constructor. + * + * @fires Manager#oplistcreate + */ + populateOperationsList() { + // Move edit button away before we overwrite it + document.body.appendChild(document.getElementById("edit-favourites")); + + let html = ""; + let i; + + for (i = 0; i < this.categories.length; i++) { + const catConf = this.categories[i], + selected = i === 0, + cat = new HTMLCategory(catConf.name, selected); + + for (let j = 0; j < catConf.ops.length; j++) { + const opName = catConf.ops[j]; + if (!(opName in this.operations)) { + log.warn(`${opName} could not be found.`); + continue; + } + + const op = new HTMLOperation(opName, this.operations[opName], this, this.manager); + cat.addOperation(op); + } + + html += cat.toHtml(); + } + + document.getElementById("categories").innerHTML = html; + + const opLists = document.querySelectorAll("#categories .op-list"); + + for (i = 0; i < opLists.length; i++) { + opLists[i].dispatchEvent(this.manager.oplistcreate); + } + + // Add edit button to first category (Favourites) + const favCat = document.querySelector("#categories a"); + favCat.appendChild(document.getElementById("edit-favourites")); + favCat.setAttribute("data-help-title", "收藏"); + favCat.setAttribute("data-help", `

此分类展示之前收藏的操作。

+
    +
  • 添加:直接将操作拖拽到“收藏”分类上
  • +
  • 排序:点击“编辑收藏”按钮然后在列表上上下拖动
  • +
  • 移除:点击“编辑收藏”按钮然后按对应操作旁边的删除按钮
  • +
`); + } + + + /** + * Sets up the adjustable splitter to allow the user to resize areas of the page. + * + * @param {boolean} [minimise=false] - Set this flag if attempting to minimise frames to 0 width + */ + initialiseSplitter(minimise=false) { + if (this.columnSplitter) this.columnSplitter.destroy(); + if (this.ioSplitter) this.ioSplitter.destroy(); + + const compactLayout = window.matchMedia("(max-width: 1180px)").matches; + document.body.classList.toggle("ztools-compact-layout", compactLayout); + + this.columnSplitter = Split(["#operations", "#recipe", "#IO"], { + sizes: compactLayout ? [18, 32, 50] : [20, 30, 50], + minSize: minimise ? [0, 0, 0] : compactLayout ? [150, 240, 320] : [240, 310, 450], + gutterSize: 4, + expandToMin: true, + onDrag: debounce(function() { + this.adjustComponentSizes(); + }, 50, "dragSplitter", this, []) + }); + + this.ioSplitter = Split(["#input", "#output"], { + direction: "vertical", + gutterSize: 4, + minSize: minimise ? [0, 0] : [100, 100] + }); + + this.adjustComponentSizes(); + } + + + /** + * Loads the information previously saved to the HTML5 local storage object so that user options + * and favourites can be restored. + */ + loadLocalStorage() { + // Load options + let lOptions; + if (this.isLocalStorageAvailable() && localStorage.options !== undefined) { + lOptions = JSON.parse(localStorage.options); + } + this.manager.options.load(lOptions); + + // Load favourites + this.loadFavourites(); + } + + + /** + * Loads the user's favourite operations from the HTML5 local storage object and populates the + * Favourites category with them. + * If the user currently has no saved favourites, the defaults from the view constructor are used. + */ + loadFavourites() { + let favourites; + + if (this.isLocalStorageAvailable()) { + favourites = localStorage?.favourites?.length > 2 ? + JSON.parse(localStorage.favourites) : + this.dfavourites; + favourites = this.validFavourites(favourites); + this.saveFavourites(favourites); + } else { + favourites = this.dfavourites; + } + + const favCat = this.categories.filter(function(c) { + return c.name === "收藏"; + })[0]; + + if (favCat) { + favCat.ops = favourites; + } else { + this.categories.unshift({ + name: "收藏", + ops: favourites + }); + } + } + + + /** + * Filters the list of favourite operations that the user had stored and removes any that are no + * longer available. The user is notified if this is the case. + + * @param {string[]} favourites - A list of the user's favourite operations + * @returns {string[]} A list of the valid favourites + */ + validFavourites(favourites) { + const validFavs = []; + for (let i = 0; i < favourites.length; i++) { + if (favourites[i] in this.operations) { + validFavs.push(favourites[i]); + } else { + this.alert(`未找到操作 "${Utils.escapeHtml(favourites[i])}" 。 ` + + "已将其从收藏列表中移除。"); + } + } + return validFavs; + } + + + /** + * Saves a list of favourite operations to the HTML5 local storage object. + * + * @param {string[]} favourites - A list of the user's favourite operations + */ + saveFavourites(favourites) { + if (!this.isLocalStorageAvailable()) { + this.alert( + "未授予本地存储权限,无法保存收藏列表。", + 5000 + ); + return false; + } + + localStorage.setItem("favourites", JSON.stringify(this.validFavourites(favourites))); + } + + + /** + * Resets favourite operations back to the default as specified in the view constructor and + * refreshes the operation list. + */ + resetFavourites() { + this.saveFavourites(this.dfavourites); + this.loadFavourites(); + this.populateOperationsList(); + this.manager.recipe.initialiseOperationDragNDrop(); + } + + + /** + * Adds an operation to the user's favourites. + * + * @param {string} name - The name of the operation + */ + addFavourite(name) { + const favourites = JSON.parse(localStorage.favourites); + + if (favourites.indexOf(name) >= 0) { + this.alert(`'${name}' 已经存在于收藏列表中`, 3000); + return; + } + + favourites.push(name); + this.saveFavourites(favourites); + this.loadFavourites(); + this.populateOperationsList(); + this.manager.recipe.initialiseOperationDragNDrop(); + } + + /** + * Gets the URI params from the window and parses them to extract the actual values. + * + * @returns {object} + */ + getURIParams() { + // Load query string or hash from URI (depending on which is populated) + // We prefer getting the hash by splitting the href rather than referencing + // location.hash as some browsers (Firefox) automatically URL decode it, + // which cause issues. + const params = window.location.search || + window.location.href.split("#")[1] || + window.location.hash; + const parsedParams = Utils.parseURIParams(params); + return parsedParams; + } + + /** + * Searches the URI parameters for recipe and input parameters. + * If recipe is present, replaces the current recipe with the recipe provided in the URI. + * If input is present, decodes and sets the input to the one provided in the URI. + * If character encodings are present, sets them appropriately. + * If theme is present, uses the theme. + * + * @param {Object} params + * @fires Manager#statechange + */ + loadURIParams(params=this.getURIParams()) { + this.uriParams = params; + + // Read in recipe from URI params + if (this.uriParams.recipe) { + try { + const recipeConfig = Utils.parseRecipeConfig(this.uriParams.recipe); + this.setRecipeConfig(recipeConfig); + } catch (err) {} + } else if (this.uriParams.op) { + // If there's no recipe, look for single operations + this.manager.recipe.clearRecipe(); + + // Search for nearest match and add it + const matchedOps = this.manager.ops.filterOperations(this.uriParams.op, false); + if (matchedOps.length) { + this.manager.recipe.addOperation(matchedOps[0].name); + } + + // Populate search with the string + const search = document.getElementById("search"); + + search.value = this.uriParams.op; + search.dispatchEvent(new Event("search")); + } + + // Input Character Encoding + // Must be set before the input is loaded + if (this.uriParams.ienc) { + this.manager.input.chrEncChange(parseInt(this.uriParams.ienc, 10), true, true); + } + + // Output Character Encoding + if (this.uriParams.oenc) { + this.manager.output.chrEncChange(parseInt(this.uriParams.oenc, 10), true); + } + + // Input EOL sequence + if (this.uriParams.ieol) { + this.manager.input.eolChange(this.uriParams.ieol, true); + } + + // Output EOL sequence + if (this.uriParams.oeol) { + this.manager.output.eolChange(this.uriParams.oeol, true); + } + + // Read in input data from URI params + if (this.uriParams.input) { + try { + let inputVal; + const inputChrEnc = this.manager.input.getChrEnc(); + const inputData = fromBase64(this.uriParams.input, null, "byteArray"); + if (inputChrEnc > 0) { + inputVal = cptable.utils.decode(inputChrEnc, inputData); + } else { + inputVal = Utils.byteArrayToChars(inputData); + } + this.setInput(inputVal); + } catch (err) {} + } + + // Read in theme from URI params + if (this.uriParams.theme) { + this.manager.options.changeTheme(Utils.escapeHtml(this.uriParams.theme)); + } else { + this.manager.options.applyPreferredColorScheme(); + } + + window.dispatchEvent(this.manager.statechange); + } + + + /** + * Returns the next ingredient ID and increments it for next time. + * + * @returns {number} + */ + nextIngId() { + return this.ingId++; + } + + + /** + * Gets the current recipe configuration. + * + * @returns {Object[]} + */ + getRecipeConfig() { + return this.manager.recipe.getConfig(); + } + + + /** + * Given a recipe configuration, sets the recipe to that configuration. + * + * @fires Manager#statechange + * @param {Object[]} recipeConfig - The recipe configuration + */ + setRecipeConfig(recipeConfig) { + document.getElementById("rec-list").innerHTML = null; + + for (let i = 0; i < recipeConfig.length; i++) { + const item = this.manager.recipe.addOperation(recipeConfig[i].op); + + // Populate arguments + log.debug(`Populating arguments for ${recipeConfig[i].op}`); + const args = item.querySelectorAll(".arg"); + for (let j = 0; j < args.length; j++) { + if (recipeConfig[i].args[j] === undefined) continue; + if (args[j].getAttribute("type") === "checkbox") { + // checkbox + args[j].checked = recipeConfig[i].args[j]; + } else if (args[j].classList.contains("toggle-string")) { + // toggleString + args[j].value = recipeConfig[i].args[j].string; + args[j].parentNode.parentNode.querySelector("button").innerHTML = + Utils.escapeHtml(recipeConfig[i].args[j].option); + } else { + // all others + args[j].value = recipeConfig[i].args[j]; + } + } + + // Set disabled and breakpoint + if (recipeConfig[i].disabled) { + item.querySelector(".disable-icon").click(); + } + if (recipeConfig[i].breakpoint) { + item.querySelector(".breakpoint").click(); + } + + this.manager.recipe.triggerArgEvents(item); + + this.progress = 0; + } + } + + + /** + * Resets the splitter positions to default. + */ + resetLayout() { + if (document.body.classList.contains("ztools-compact-layout")) { + this.columnSplitter.setSizes([18, 32, 50]); + } else { + this.columnSplitter.setSizes([20, 30, 50]); + } + this.ioSplitter.setSizes([50, 50]); + this.adjustComponentSizes(); + } + + /** + * Adjust components to fit their containers. + */ + adjustComponentSizes() { + this.manager.recipe.adjustWidth(); + this.manager.input.calcMaxTabs(); + this.manager.output.calcMaxTabs(); + this.manager.controls.calcControlsHeight(); + } + + + /** + * Sets the compile message. + */ + setCompileMessage() { + // Display time since last build and compile message + const now = new Date(), + msSinceCompile = now.getTime() - window.compileTime, + timeSinceCompile = moment.duration(msSinceCompile, "milliseconds").locale("zh-CN").humanize(); + + // Calculate previous version to compare to + const prev = PKG_VERSION.split(".").map(n => { + return parseInt(n, 10); + }); + if (prev[2] > 0) prev[2]--; + else if (prev[1] > 0) prev[1]--; + else prev[0]--; + + // const compareURL = `https://github.com/gchq/CyberChef/compare/v${prev.join(".")}...v${PKG_VERSION}`; + + let compileInfo = `此版本编译于: ${timeSinceCompile.substring(0, 1).toUpperCase() + timeSinceCompile.substring(1)}之前`; + + if (window.compileMessage !== "") { + compileInfo += " - " + window.compileMessage; + } + + const notice = document.getElementById("notice"); + notice.innerHTML = compileInfo; + notice.setAttribute("title", Utils.stripHtmlTags(window.compileMessage)); + notice.setAttribute("data-help-title", "编译时间"); + notice.setAttribute("data-help", "显示当前版本的SRK Toolbox编译于什么时间。"); + } + + + /** + * Determines whether the browser supports Local Storage and if it is accessible. + * + * @returns {boolean} + */ + isLocalStorageAvailable() { + try { + if (!localStorage) return false; + return true; + } catch (err) { + // Access to LocalStorage is denied + return false; + } + } + + + /** + * Pops up a message to the user and writes it to the console log. + * + * @param {string} str - The message to display (HTML supported) + * @param {number} [timeout=0] - The number of milliseconds before the alert closes automatically + * 0 for never (until the user closes it) + * @param {boolean} [silent=false] - Don't show the message in the popup, only print it to the + * console + * + * @example + * // Pops up a box with the message "Error: Something has gone wrong!" that will need to be + * // dismissed by the user. + * this.alert("Error: Something has gone wrong!", 0); + * + * // Pops up a box with the message "Happy Christmas!" that will disappear after 5 seconds. + * this.alert("Happy Christmas!", 5000); + */ + alert(str, timeout=0, silent=false) { + const time = new Date(); + + log.info("[" + time.toLocaleString() + "] " + str); + if (silent) return; + + toastr.options.timeOut = timeout; + toastr.info(str); + + // this.snackbars.push($.snackbar({ + // content: str, + // timeout: timeout, + // htmlAllowed: true, + // onClose: () => { + // this.snackbars.shift().remove(); + // } + // })); + } + + + /** + * Pops up a box asking the user a question and sending the answer to a specified callback function. + * + * @param {string} title - The title of the box + * @param {string} body - The question (HTML supported) + * @param {string} accept - The text of the accept button + * @param {string} reject - The text of the reject button + * @param {function} callback - A function accepting one boolean argument which handles the + * response e.g. function(answer) {...} + * @param {Object} [scope=this] - The object to bind to the callback function + * + * @example + * // Pops up a box asking if the user would like a cookie. Prints the answer to the console. + * this.confirm("Question", "Would you like a cookie?", "Yes", "No", function(answer) {console.log(answer);}); + */ + confirm(title, body, accept, reject, callback, scope) { + scope = scope || this; + document.getElementById("confirm-title").innerHTML = title; + document.getElementById("confirm-body").innerHTML = body; + document.getElementById("confirm-yes").innerText = accept; + document.getElementById("confirm-no").innerText = reject; + document.getElementById("confirm-modal").style.display = "block"; + + this.confirmClosed = false; + $("#confirm-modal").modal() + .one("show.bs.modal", function(e) { + this.confirmClosed = false; + }.bind(this)) + .one("click", "#confirm-yes", function() { + this.confirmClosed = true; + callback.bind(scope)(true); + $("#confirm-modal").modal("hide"); + }.bind(this)) + .one("click", "#confirm-no", function() { + this.confirmClosed = true; + callback.bind(scope)(false); + }.bind(this)) + .one("hide.bs.modal", function(e) { + if (!this.confirmClosed) { + callback.bind(scope)(undefined); + } + this.confirmClosed = true; + }.bind(this)); + } + + + /** + * Handler for CyerChef statechange events. + * Fires whenever the input or recipe changes in any way. + * + * @listens Manager#statechange + * @param {event} e + */ + stateChange(e) { + debounce(function() { + this.progress = 0; + this.autoBake(); + this.updateURL(true, null, true); + }, 20, "stateChange", this, [])(); + } + + + /** + * Update the page title and URL to contain the new recipe + * + * @param {boolean} includeInput + * @param {string} [input=null] + * @param {boolean} [changeUrl=true] + */ + updateURL(includeInput, input=null, changeUrl=true) { + // Set title + const recipeConfig = this.getRecipeConfig(); + let title = "SRK Toolbox"; + if (recipeConfig.length === 1) { + title = `${recipeConfig[0].op} - ${title}`; + } else if (recipeConfig.length > 1) { + // See how long the full recipe is + const ops = recipeConfig.map(op => op.op).join(", "); + if (ops.length < 45) { + title = `${ops} - ${title}`; + } else { + // If it's too long, just use the first one and say how many more there are + title = `${recipeConfig[0].op} 等 ${recipeConfig.length - 1} 项其他操作 - ${title}`; + } + } + document.title = title; + + // Update the current history state (not creating a new one) + if (this.options.updateUrl && changeUrl) { + this.lastStateUrl = this.manager.controls.generateStateUrl(true, includeInput, input, recipeConfig); + window.history.replaceState({}, title, this.lastStateUrl); + } + } + + + /** + * Handler for the history popstate event. + * Reloads parameters from the URL. + * + * @param {event} e + */ + popState(e) { + this.loadURIParams(); + } + +} + +export default App; diff --git a/plugins/srktoolbox/src/web/HTMLCategory.mjs b/plugins/srktoolbox/src/web/HTMLCategory.mjs new file mode 100644 index 00000000..b61a6740 --- /dev/null +++ b/plugins/srktoolbox/src/web/HTMLCategory.mjs @@ -0,0 +1,62 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +/** + * Object to handle the creation of operation categories. + */ +class HTMLCategory { + + /** + * HTMLCategory constructor. + * + * @param {string} name - The name of the category. + * @param {boolean} selected - Whether this category is pre-selected or not. + */ + constructor(name, selected) { + this.name = name; + this.selected = selected; + this.opList = []; + } + + + /** + * Adds an operation to this category. + * + * @param {HTMLOperation} operation - The operation to add. + */ + addOperation(operation) { + this.opList.push(operation); + } + + + /** + * Renders the category and all operations within it in HTML. + * + * @returns {string} + */ + toHtml() { + const catName = "cat" + this.name.replace(/[\s/\-:_]/g, ""); + let html = `
+ + ${this.name} + + +
+
    `; + + for (let i = 0; i < this.opList.length; i++) { + html += this.opList[i].toStubHtml(); + } + + html += "
"; + return html; + } + +} + +export default HTMLCategory; diff --git a/plugins/srktoolbox/src/web/HTMLIngredient.mjs b/plugins/srktoolbox/src/web/HTMLIngredient.mjs new file mode 100644 index 00000000..887cb859 --- /dev/null +++ b/plugins/srktoolbox/src/web/HTMLIngredient.mjs @@ -0,0 +1,424 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Utils from "../core/Utils.mjs"; + +/** + * Object to handle the creation of operation ingredients. + */ +class HTMLIngredient { + + /** + * HTMLIngredient constructor. + * + * @param {Object} config - The configuration object for this ingredient. + * @param {App} app - The main view object for CyberChef. + * @param {Manager} manager - The CyberChef event manager. + */ + constructor(config, app, manager) { + this.app = app; + this.manager = manager; + + this.name = config.name; + this.type = config.type; + this.value = config.value; + this.disabled = config.disabled || false; + this.hint = config.hint || false; + this.rows = config.rows || false; + this.target = config.target; + this.defaultIndex = config.defaultIndex || 0; + this.maxLength = config.maxLength || null; + this.toggleValues = config.toggleValues; + this.ingId = this.app.nextIngId(); + this.id = "ing-" + this.ingId; + this.tabIndex = this.ingId + 2; // Input = 1, Search = 2 + this.min = (typeof config.min === "number") ? config.min : ""; + this.max = (typeof config.max === "number") ? config.max : ""; + this.step = config.step || 1; + } + + + /** + * Renders the ingredient in HTML. + * + * @returns {string} + */ + toHtml() { + let html = "", + i, m, eventFn; + + switch (this.type) { + case "string": + case "binaryString": + case "byteArray": + html += `
+ + +
`; + break; + case "shortString": + case "binaryShortString": + html += `
+ + +
`; + break; + case "toggleString": + html += `
+
+ + +
+
+ + +
+ +
`; + break; + case "number": + html += `
+ + +
`; + break; + case "boolean": + html += `
+
+ +
+
`; + break; + case "option": + html += `
+ + +
`; + break; + case "populateOption": + case "populateMultiOption": + html += `
+ + +
`; + + eventFn = this.type === "populateMultiOption" ? + this.populateMultiOptionChange : + this.populateOptionChange; + this.manager.addDynamicListener("#" + this.id, "change", eventFn, this); + break; + case "editableOption": + html += `
+ + +
+ + +
+
`; + + this.manager.addDynamicListener(".editable-option-menu a", "click", this.editableOptionClick, this); + break; + case "editableOptionShort": + html += `
+ + +
+ + +
+
`; + + this.manager.addDynamicListener(".editable-option-menu a", "click", this.editableOptionClick, this); + break; + case "text": + html += `
+ + +
`; + break; + case "argSelector": + html += `
+ + +
`; + + this.manager.addDynamicListener(".arg-selector", "change", this.argSelectorChange, this); + break; + case "label": + html += `
+ + +
`; + break; + default: + break; + } + + return html; + } + + + /** + * Handler for populate option changes. + * Populates the relevant argument with the specified value. + * + * @param {event} e + */ + populateOptionChange(e) { + e.preventDefault(); + e.stopPropagation(); + + const el = e.target; + const op = el.parentNode.parentNode; + const target = op.querySelectorAll(".arg")[this.target]; + + const popVal = el.childNodes[el.selectedIndex].getAttribute("populate-value"); + if (popVal !== "") target.value = popVal; + + const evt = new Event("change"); + target.dispatchEvent(evt); + + this.manager.recipe.ingChange(); + } + + + /** + * Handler for populate multi option changes. + * Populates the relevant arguments with the specified values. + * + * @param {event} e + */ + populateMultiOptionChange(e) { + e.preventDefault(); + e.stopPropagation(); + + const el = e.target; + const op = el.parentNode.parentNode; + const args = op.querySelectorAll(".arg"); + const targets = this.target.map(i => args[i]); + const vals = JSON.parse(el.childNodes[el.selectedIndex].getAttribute("populate-value")); + const evt = new Event("change"); + + for (let i = 0; i < targets.length; i++) { + targets[i].value = vals[i]; + } + + // Fire change event after all targets have been assigned + this.manager.recipe.ingChange(); + + // Send change event for each target once all have been assigned, to update the label placement. + for (const target of targets) { + target.dispatchEvent(evt); + } + } + + + /** + * Handler for editable option clicks. + * Populates the input box with the selected value. + * + * @param {event} e + */ + editableOptionClick(e) { + e.preventDefault(); + e.stopPropagation(); + + const link = e.target, + input = link.parentNode.parentNode.parentNode.querySelector("input"); + + input.value = link.getAttribute("value"); + const evt = new Event("change"); + input.dispatchEvent(evt); + + this.manager.recipe.ingChange(); + } + + + /** + * Handler for argument selector changes. + * Shows or hides the relevant arguments for this operation. + * + * @param {event} e + */ + argSelectorChange(e) { + e.preventDefault(); + e.stopPropagation(); + + const option = e.target.options[e.target.selectedIndex]; + const op = e.target.closest(".operation"); + const args = op.querySelectorAll(".ingredients .form-group"); + const turnon = JSON.parse(option.getAttribute("turnon")); + const turnoff = JSON.parse(option.getAttribute("turnoff")); + + args.forEach((arg, i) => { + if (turnon.includes(i)) { + arg.classList.remove("d-none"); + } + if (turnoff.includes(i)) { + arg.classList.add("d-none"); + } + }); + } + +} + +export default HTMLIngredient; diff --git a/plugins/srktoolbox/src/web/HTMLOperation.mjs b/plugins/srktoolbox/src/web/HTMLOperation.mjs new file mode 100644 index 00000000..007e8188 --- /dev/null +++ b/plugins/srktoolbox/src/web/HTMLOperation.mjs @@ -0,0 +1,179 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import HTMLIngredient from "./HTMLIngredient.mjs"; +import Utils from "../core/Utils.mjs"; +import url from "url"; + + +/** + * Object to handle the creation of operations. + */ +class HTMLOperation { + + /** + * HTMLOperation constructor. + * + * @param {string} name - The name of the operation. + * @param {Object} config - The configuration object for this operation. + * @param {App} app - The main view object for CyberChef. + * @param {Manager} manager - The CyberChef event manager. + */ + constructor(name, config, app, manager) { + this.app = app; + this.manager = manager; + + this.name = name; + this.description = config.description; + this.infoURL = config.infoURL; + this.manualBake = config.manualBake || false; + this.config = config; + this.ingList = []; + + for (let i = 0; i < config.args.length; i++) { + const ing = new HTMLIngredient(config.args[i], this.app, this.manager); + this.ingList.push(ing); + } + } + + + /** + * Renders the operation in HTML as a stub operation with no ingredients. + * + * @returns {string} + */ + toStubHtml(removeIcon) { + let html = "
  • ${titleFromWikiLink(this.infoURL)}` : ""; + + html += ` data-container='body' data-toggle='popover' data-placement='right' + data-content="${this.description}${infoLink}" data-html='true' data-trigger='hover' + data-boundary='viewport'`; + } + + html += ">" + this.name; + + if (removeIcon) { + html += "delete"; + } + + html += "
  • "; + + return html; + } + + + /** + * Renders the operation in HTML as a full operation with ingredients. + * + * @returns {string} + */ + toFullHtml() { + let html = `
    ${Utils.escapeHtml(this.name)}
    +
    `; + + for (let i = 0; i < this.ingList.length; i++) { + html += this.ingList[i].toHtml(); + } + + html += `
    +
    + pause + not_interested + keyboard_arrow_up +
    +
     
    `; + + return html; + } + + + /** + * Highlights searched strings in the name and description of the operation. + * + * @param {[[number]]} nameIdxs - Indexes of the search strings in the operation name [[start, length]] + * @param {[[number]]} descIdxs - Indexes of the search strings in the operation description [[start, length]] + */ + highlightSearchStrings(nameIdxs, descIdxs) { + if (nameIdxs.length && typeof nameIdxs[0][0] === "number") { + let opName = "", + pos = 0; + + nameIdxs.forEach(idxs => { + const [start, length] = idxs; + if (typeof start !== "number") return; + opName += this.name.slice(pos, start) + "" + + this.name.slice(start, start + length) + ""; + pos = start + length; + }); + opName += this.name.slice(pos, this.name.length); + this.name = opName; + } + + if (this.description && descIdxs.length && descIdxs[0][0] >= 0) { + // Find HTML tag offsets + const re = /<[^>]+>/g; + let match; + while ((match = re.exec(this.description))) { + // If the search string occurs within an HTML tag, return without highlighting it. + const inHTMLTag = descIdxs.reduce((acc, idxs) => { + const start = idxs[0]; + return start >= match.index && start <= (match.index + match[0].length); + }, false); + + if (inHTMLTag) return; + } + + let desc = "", + pos = 0; + + descIdxs.forEach(idxs => { + const [start, length] = idxs; + desc += this.description.slice(pos, start) + "" + + this.description.slice(start, start + length) + ""; + pos = start + length; + }); + desc += this.description.slice(pos, this.description.length); + this.description = desc; + } + } + +} + + +/** + * Given a URL for a Wikipedia (or other wiki) page, this function returns a link to that page. + * + * @param {string} urlStr + * @returns {string} + */ +function titleFromWikiLink(urlStr) { + const urlObj = url.parse(urlStr); + let wikiName = "", + pageTitle = ""; + + switch (urlObj.host) { + case "forensics.wiki": + wikiName = "Forensics Wiki"; + pageTitle = Utils.toTitleCase(urlObj.path.replace(/\//g, "").replace(/_/g, " ")); + break; + case "wikipedia.org": + wikiName = "Wikipedia"; + pageTitle = urlObj.pathname.substr(6).replace(/_/g, " "); // Chop off '/wiki/' + break; + default: + // Not a wiki link, return full URL + return `更多信息open_in_new`; + } + + return `${wikiName}页面:${pageTitle}open_in_new`; +} + +export default HTMLOperation; diff --git a/plugins/srktoolbox/src/web/Manager.mjs b/plugins/srktoolbox/src/web/Manager.mjs new file mode 100644 index 00000000..ae972a59 --- /dev/null +++ b/plugins/srktoolbox/src/web/Manager.mjs @@ -0,0 +1,362 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import WorkerWaiter from "./waiters/WorkerWaiter.mjs"; +import WindowWaiter from "./waiters/WindowWaiter.mjs"; +import ControlsWaiter from "./waiters/ControlsWaiter.mjs"; +import RecipeWaiter from "./waiters/RecipeWaiter.mjs"; +import OperationsWaiter from "./waiters/OperationsWaiter.mjs"; +import InputWaiter from "./waiters/InputWaiter.mjs"; +import OutputWaiter from "./waiters/OutputWaiter.mjs"; +import OptionsWaiter from "./waiters/OptionsWaiter.mjs"; +import HighlighterWaiter from "./waiters/HighlighterWaiter.mjs"; +import SeasonalWaiter from "./waiters/SeasonalWaiter.mjs"; +import BindingsWaiter from "./waiters/BindingsWaiter.mjs"; +import BackgroundWorkerWaiter from "./waiters/BackgroundWorkerWaiter.mjs"; +import TabWaiter from "./waiters/TabWaiter.mjs"; +import TimingWaiter from "./waiters/TimingWaiter.mjs"; + + +/** + * This object controls the Waiters responsible for handling events from all areas of the app. + */ +class Manager { + + /** + * Manager constructor. + * + * @param {App} app - The main view object for CyberChef. + */ + constructor(app) { + this.app = app; + + // Define custom events + /** + * @event Manager#appstart + */ + this.appstart = new CustomEvent("appstart", {bubbles: true}); + /** + * @event Manager#apploaded + */ + this.apploaded = new CustomEvent("apploaded", {bubbles: true}); + /** + * @event Manager#operationadd + */ + this.operationadd = new CustomEvent("operationadd", {bubbles: true}); + /** + * @event Manager#operationremove + */ + this.operationremove = new CustomEvent("operationremove", {bubbles: true}); + /** + * @event Manager#oplistcreate + */ + this.oplistcreate = new CustomEvent("oplistcreate", {bubbles: true}); + /** + * @event Manager#statechange + */ + this.statechange = new CustomEvent("statechange", {bubbles: true}); + + // Define Waiter objects to handle various areas + this.timing = new TimingWaiter(this.app, this); + this.worker = new WorkerWaiter(this.app, this); + this.window = new WindowWaiter(this.app); + this.controls = new ControlsWaiter(this.app, this); + this.recipe = new RecipeWaiter(this.app, this); + this.ops = new OperationsWaiter(this.app, this); + this.tabs = new TabWaiter(this.app, this); + this.input = new InputWaiter(this.app, this); + this.output = new OutputWaiter(this.app, this); + this.options = new OptionsWaiter(this.app, this); + this.highlighter = new HighlighterWaiter(this.app, this); + this.seasonal = new SeasonalWaiter(this.app, this); + this.bindings = new BindingsWaiter(this.app, this); + this.background = new BackgroundWorkerWaiter(this.app, this); + + // Object to store dynamic handlers to fire on elements that may not exist yet + this.dynamicHandlers = {}; + + this.initialiseEventListeners(); + } + + + /** + * Sets up the various components and listeners. + */ + setup() { + this.input.setupInputWorker(); + this.input.addInput(true); + this.worker.setupChefWorker(); + this.recipe.initialiseOperationDragNDrop(); + this.controls.initComponents(); + this.controls.autoBakeChange(); + this.bindings.updateKeybList(); + this.background.registerChefWorker(); + this.seasonal.load(); + + this.confirmWaitersLoaded(); + } + + /** + * Confirms that all Waiters have loaded correctly. + */ + confirmWaitersLoaded() { + if (this.tabs.getActiveTab("input") >= 0 && + this.tabs.getActiveTab("output") >= 0) { + log.debug("Waiters loaded"); + this.app.waitersLoaded = true; + this.app.loaded(); + } else { + // Not loaded yet, try again soon + setTimeout(this.confirmWaitersLoaded.bind(this), 10); + } + } + + + /** + * Main function to handle the creation of the event listeners. + */ + initialiseEventListeners() { + // Global + window.addEventListener("resize", this.window.windowResize.bind(this.window)); + window.addEventListener("blur", this.window.windowBlur.bind(this.window)); + window.addEventListener("focus", this.window.windowFocus.bind(this.window)); + window.addEventListener("statechange", this.app.stateChange.bind(this.app)); + window.addEventListener("popstate", this.app.popState.bind(this.app)); + window.addEventListener("message", this.input.handlePostMessage.bind(this.input)); + + // Controls + document.getElementById("bake").addEventListener("click", this.controls.bakeClick.bind(this.controls)); + document.getElementById("auto-bake").addEventListener("change", this.controls.autoBakeChange.bind(this.controls)); + document.getElementById("step").addEventListener("click", this.controls.stepClick.bind(this.controls)); + document.getElementById("clr-recipe").addEventListener("click", this.controls.clearRecipeClick.bind(this.controls)); + document.getElementById("save").addEventListener("click", this.controls.saveClick.bind(this.controls)); + document.getElementById("save-button").addEventListener("click", this.controls.saveButtonClick.bind(this.controls)); + document.getElementById("save-link-recipe-checkbox").addEventListener("change", this.controls.slrCheckChange.bind(this.controls)); + document.getElementById("save-link-input-checkbox").addEventListener("change", this.controls.sliCheckChange.bind(this.controls)); + document.getElementById("load").addEventListener("click", this.controls.loadClick.bind(this.controls)); + document.getElementById("load-delete-button").addEventListener("click", this.controls.loadDeleteClick.bind(this.controls)); + document.getElementById("load-name").addEventListener("change", this.controls.loadNameChange.bind(this.controls)); + document.getElementById("load-button").addEventListener("click", this.controls.loadButtonClick.bind(this.controls)); + document.getElementById("hide-icon").addEventListener("click", this.controls.hideRecipeArgsClick.bind(this.recipe)); + document.getElementById("support").addEventListener("click", this.controls.supportButtonClick.bind(this.controls)); + this.addMultiEventListeners("#save-texts textarea", "keyup paste", this.controls.saveTextChange, this.controls); + + // Operations + this.addMultiEventListener("#search", "keyup paste search", this.ops.searchOperations, this.ops); + this.addDynamicListener(".op-list li.operation", "dblclick", this.ops.operationDblclick, this.ops); + document.getElementById("edit-favourites").addEventListener("click", this.ops.editFavouritesClick.bind(this.ops)); + document.getElementById("save-favourites").addEventListener("click", this.ops.saveFavouritesClick.bind(this.ops)); + document.getElementById("reset-favourites").addEventListener("click", this.ops.resetFavouritesClick.bind(this.ops)); + this.addDynamicListener(".op-list", "oplistcreate", this.ops.opListCreate, this.ops); + this.addDynamicListener("li.operation", "operationadd", this.recipe.opAdd, this.recipe); + + // Recipe + this.addDynamicListener(".arg:not(select)", "input", this.recipe.ingChange, this.recipe); + this.addDynamicListener(".arg[type=checkbox], .arg[type=radio], select.arg", "change", this.recipe.ingChange, this.recipe); + this.addDynamicListener(".hide-args-icon", "click", this.recipe.hideArgsClick, this.recipe); + this.addDynamicListener(".disable-icon", "click", this.recipe.disableClick, this.recipe); + this.addDynamicListener(".breakpoint", "click", this.recipe.breakpointClick, this.recipe); + this.addDynamicListener("#rec-list li.operation", "dblclick", this.recipe.operationDblclick, this.recipe); + this.addDynamicListener("#rec-list li.operation > div", "dblclick", this.recipe.operationChildDblclick, this.recipe); + this.addDynamicListener("#rec-list .dropdown-menu.toggle-dropdown a", "click", this.recipe.dropdownToggleClick, this.recipe); + this.addDynamicListener("#rec-list", "operationremove", this.recipe.opRemove.bind(this.recipe)); + this.addDynamicListener("textarea.arg", "dragover", this.recipe.textArgDragover, this.recipe); + this.addDynamicListener("textarea.arg", "dragleave", this.recipe.textArgDragLeave, this.recipe); + this.addDynamicListener("textarea.arg", "drop", this.recipe.textArgDrop, this.recipe); + + // Input + document.getElementById("reset-layout").addEventListener("click", this.app.resetLayout.bind(this.app)); + this.addListeners("#clr-io,#btn-close-all-tabs", "click", this.input.clearAllIoClick, this.input); + this.addListeners("#open-file,#open-folder", "change", this.input.inputOpen, this.input); + document.getElementById("btn-open-file").addEventListener("click", this.input.inputOpenClick.bind(this.input)); + document.getElementById("btn-open-folder").addEventListener("click", this.input.folderOpenClick.bind(this.input)); + this.addListeners("#input-wrapper", "dragover", this.input.inputDragover, this.input); + this.addListeners("#input-wrapper", "dragleave", this.input.inputDragleave, this.input); + this.addListeners("#input-wrapper", "drop", this.input.inputDrop, this.input); + document.getElementById("btn-new-tab").addEventListener("click", this.input.addInputClick.bind(this.input)); + document.getElementById("btn-previous-input-tab").addEventListener("mousedown", this.input.previousTabClick.bind(this.input)); + document.getElementById("btn-next-input-tab").addEventListener("mousedown", this.input.nextTabClick.bind(this.input)); + this.addListeners("#btn-next-input-tab,#btn-previous-input-tab", "mouseup", this.input.tabMouseUp, this.input); + this.addListeners("#btn-next-input-tab,#btn-previous-input-tab", "mouseout", this.input.tabMouseUp, this.input); + document.getElementById("btn-go-to-input-tab").addEventListener("click", this.input.goToTab.bind(this.input)); + document.getElementById("btn-find-input-tab").addEventListener("click", this.input.findTab.bind(this.input)); + this.addDynamicListener("#input-tabs li .input-tab-content", "click", this.input.changeTabClick, this.input); + document.getElementById("input-show-pending").addEventListener("change", this.input.filterTabSearch.bind(this.input)); + document.getElementById("input-show-loading").addEventListener("change", this.input.filterTabSearch.bind(this.input)); + document.getElementById("input-show-loaded").addEventListener("change", this.input.filterTabSearch.bind(this.input)); + this.addListeners("#input-filter-content,#input-filter-filename", "click", this.input.filterOptionClick, this.input); + document.getElementById("input-filter").addEventListener("change", this.input.filterTabSearch.bind(this.input)); + document.getElementById("input-filter").addEventListener("keyup", this.input.filterTabSearch.bind(this.input)); + document.getElementById("input-num-results").addEventListener("change", this.input.filterTabSearch.bind(this.input)); + document.getElementById("input-num-results").addEventListener("keyup", this.input.filterTabSearch.bind(this.input)); + document.getElementById("input-filter-refresh").addEventListener("click", this.input.filterTabSearch.bind(this.input)); + this.addDynamicListener(".input-filter-result", "click", this.input.filterItemClick, this.input); + + + // Output + document.getElementById("save-to-file").addEventListener("click", this.output.saveClick.bind(this.output)); + document.getElementById("save-all-to-file").addEventListener("click", this.output.saveAllClick.bind(this.output)); + document.getElementById("copy-output").addEventListener("click", this.output.copyClick.bind(this.output)); + document.getElementById("switch").addEventListener("click", this.output.switchClick.bind(this.output)); + document.getElementById("maximise-output").addEventListener("click", this.output.maximiseOutputClick.bind(this.output)); + document.getElementById("magic").addEventListener("click", this.output.magicClick.bind(this.output)); + this.addDynamicListener(".extract-file,.extract-file i", "click", this.output.extractFileClick, this.output); + this.addDynamicListener("#output-tabs-wrapper #output-tabs li .output-tab-content", "click", this.output.changeTabClick, this.output); + document.getElementById("btn-previous-output-tab").addEventListener("mousedown", this.output.previousTabClick.bind(this.output)); + document.getElementById("btn-next-output-tab").addEventListener("mousedown", this.output.nextTabClick.bind(this.output)); + this.addListeners("#btn-next-output-tab,#btn-previous-output-tab", "mouseup", this.output.tabMouseUp, this.output); + this.addListeners("#btn-next-output-tab,#btn-previous-output-tab", "mouseout", this.output.tabMouseUp, this.output); + document.getElementById("btn-go-to-output-tab").addEventListener("click", this.output.goToTab.bind(this.output)); + document.getElementById("btn-find-output-tab").addEventListener("click", this.output.findTab.bind(this.output)); + document.getElementById("output-show-pending").addEventListener("change", this.output.filterTabSearch.bind(this.output)); + document.getElementById("output-show-baking").addEventListener("change", this.output.filterTabSearch.bind(this.output)); + document.getElementById("output-show-baked").addEventListener("change", this.output.filterTabSearch.bind(this.output)); + document.getElementById("output-show-stale").addEventListener("change", this.output.filterTabSearch.bind(this.output)); + document.getElementById("output-show-errored").addEventListener("change", this.output.filterTabSearch.bind(this.output)); + document.getElementById("output-content-filter").addEventListener("change", this.output.filterTabSearch.bind(this.output)); + document.getElementById("output-content-filter").addEventListener("keyup", this.output.filterTabSearch.bind(this.output)); + document.getElementById("output-num-results").addEventListener("change", this.output.filterTabSearch.bind(this.output)); + document.getElementById("output-num-results").addEventListener("keyup", this.output.filterTabSearch.bind(this.output)); + document.getElementById("output-filter-refresh").addEventListener("click", this.output.filterTabSearch.bind(this.output)); + this.addDynamicListener(".output-filter-result", "click", this.output.filterItemClick, this.output); + + + // Options + document.getElementById("options").addEventListener("click", this.options.optionsClick.bind(this.options)); + document.getElementById("reset-options").addEventListener("click", this.options.resetOptionsClick.bind(this.options)); + this.addDynamicListener(".option-item input[type=checkbox]", "change", this.options.switchChange, this.options); + this.addDynamicListener(".option-item input[type=checkbox]#wordWrap", "change", this.options.setWordWrap, this.options); + this.addDynamicListener(".option-item input[type=checkbox]#useMetaKey", "change", this.bindings.updateKeybList, this.bindings); + this.addDynamicListener(".option-item input[type=checkbox]#showCatCount", "change", this.ops.setCatCount, this.ops); + this.addDynamicListener(".option-item input[type=number]", "keyup", this.options.numberChange, this.options); + this.addDynamicListener(".option-item input[type=number]", "change", this.options.numberChange, this.options); + this.addDynamicListener(".option-item select", "change", this.options.selectChange, this.options); + document.getElementById("theme").addEventListener("change", this.options.themeChange.bind(this.options)); + document.getElementById("logLevel").addEventListener("change", this.options.logLevelChange.bind(this.options)); + + // Misc + window.addEventListener("keydown", this.bindings.parseInput.bind(this.bindings)); + } + + + /** + * Adds an event listener to each element in the specified group. + * + * @param {string} selector - A selector string for the element group to add the event to, see + * this.getAll() + * @param {string} eventType - The event to listen for + * @param {function} callback - The function to execute when the event is triggered + * @param {Object} [scope=this] - The object to bind to the callback function + * + * @example + * // Calls the clickable function whenever any element with the .clickable class is clicked + * this.addListeners(".clickable", "click", this.clickable, this); + */ + addListeners(selector, eventType, callback, scope) { + scope = scope || this; + [].forEach.call(document.querySelectorAll(selector), function(el) { + el.addEventListener(eventType, callback.bind(scope)); + }); + } + + + /** + * Adds multiple event listeners to the specified element. + * + * @param {string} selector - A selector string for the element to add the events to + * @param {string} eventTypes - A space-separated string of all the event types to listen for + * @param {function} callback - The function to execute when the events are triggered + * @param {Object} [scope=this] - The object to bind to the callback function + * + * @example + * // Calls the search function whenever the keyup, paste or search events are triggered on the + * // search element + * this.addMultiEventListener("search", "keyup paste search", this.search, this); + */ + addMultiEventListener(selector, eventTypes, callback, scope) { + const evs = eventTypes.split(" "); + for (let i = 0; i < evs.length; i++) { + document.querySelector(selector).addEventListener(evs[i], callback.bind(scope)); + } + } + + + /** + * Adds multiple event listeners to each element in the specified group. + * + * @param {string} selector - A selector string for the element group to add the events to + * @param {string} eventTypes - A space-separated string of all the event types to listen for + * @param {function} callback - The function to execute when the events are triggered + * @param {Object} [scope=this] - The object to bind to the callback function + * + * @example + * // Calls the save function whenever the keyup or paste events are triggered on any element + * // with the .saveable class + * this.addMultiEventListener(".saveable", "keyup paste", this.save, this); + */ + addMultiEventListeners(selector, eventTypes, callback, scope) { + const evs = eventTypes.split(" "); + for (let i = 0; i < evs.length; i++) { + this.addListeners(selector, evs[i], callback, scope); + } + } + + + /** + * Adds an event listener to the global document object which will listen on dynamic elements which + * may not exist in the DOM yet. + * + * @param {string} selector - A selector string for the element(s) to add the event to + * @param {string} eventType - The event(s) to listen for + * @param {function} callback - The function to execute when the event(s) is/are triggered + * @param {Object} [scope=this] - The object to bind to the callback function + * + * @example + * // Pops up an alert whenever any button is clicked, even if it is added to the DOM after this + * // listener is created + * this.addDynamicListener("button", "click", alert, this); + */ + addDynamicListener(selector, eventType, callback, scope) { + const eventConfig = { + selector: selector, + callback: callback.bind(scope || this) + }; + + if (Object.prototype.hasOwnProperty.call(this.dynamicHandlers, eventType)) { + // Listener already exists, add new handler to the appropriate list + this.dynamicHandlers[eventType].push(eventConfig); + } else { + this.dynamicHandlers[eventType] = [eventConfig]; + // Set up listener for this new type + document.addEventListener(eventType, this.dynamicListenerHandler.bind(this)); + } + } + + + /** + * Handler for dynamic events. This function is called for any dynamic event and decides which + * callback(s) to execute based on the type and selector. + * + * @param {Event} e - The event to be handled + */ + dynamicListenerHandler(e) { + const { type, target } = e; + const handlers = this.dynamicHandlers[type]; + const matches = target.matches || + target.webkitMatchesSelector || + target.mozMatchesSelector || + target.msMatchesSelector || + target.oMatchesSelector; + + for (let i = 0; i < handlers.length; i++) { + if (matches && matches.call(target, handlers[i].selector)) { + handlers[i].callback(e); + } + } + } +} + +export default Manager; diff --git a/plugins/srktoolbox/src/web/html/index.html b/plugins/srktoolbox/src/web/html/index.html new file mode 100644 index 00000000..372c2c76 --- /dev/null +++ b/plugins/srktoolbox/src/web/html/index.html @@ -0,0 +1,910 @@ + + + + + + + SRK Toolbox + + + + + + + + + + + +
    +
    +
    +
    +
    + + +
    + +
    +
    +
    + 可用操作 + +
    + +
      +
      +
      + +
      +
      + 操作流程 + + + + + + +
      +
        + +
        +
        + + + + +
        +
        + +
        +
        +
        +
        +
        + +
        +
        +
        + + +
        + + + + + +
        +
        + +
        + +
        +
        +
        + +
        +
        + + +
        + + + + + +
        + + + +
        + +
        + +
        +
        +
        + +
        +
        +
        +
        +
        +
        +
        +
        + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/srktoolbox/src/web/index.js b/plugins/srktoolbox/src/web/index.js new file mode 100644 index 00000000..a6cae5fb --- /dev/null +++ b/plugins/srktoolbox/src/web/index.js @@ -0,0 +1,72 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +// Styles +import "./stylesheets/index.js"; + +// Libs +import "arrive"; +// import "snackbarjs"; +import "bootstrap-material-design/js/index"; +import "bootstrap-colorpicker"; +import moment from "moment-timezone"; +import * as CanvasComponents from "../core/lib/CanvasComponents.mjs"; + +// CyberChef +import App from "./App.mjs"; +import Categories from "../core/config/Categories.json" assert {type: "json"}; +import OperationConfig from "../core/config/OperationConfig.json" assert {type: "json"}; + + +/** + * Main function used to build the CyberChef web app. + */ +function main() { + const defaultFavourites = [ + "Base64编码", + "Base64解码", + "字符转十六进制", + "十六进制转字符", + "转换到Hexdump", + "从Hexdump提取", + "URL解码", + "正则表达式", + "熵", + "Fork", + "Magic" + ]; + + const defaultOptions = { + updateUrl: true, + showHighlighter: true, + wordWrap: true, + showErrors: true, + errorTimeout: 4000, + attemptHighlight: true, + theme: "classic", + useMetaKey: false, + logLevel: "info", + autoMagic: true, + imagePreview: true, + syncTabs: true, + showCatCount: false, + }; + + document.removeEventListener("DOMContentLoaded", main, false); + window.app = new App(Categories, OperationConfig, defaultFavourites, defaultOptions); + window.app.setup(); +} + +window.compileTime = moment.tz(COMPILE_TIME, "DD/MM/YYYY HH:mm:ss z", "UTC").valueOf(); +window.compileMessage = COMPILE_MSG; + +// Make libs available to operation outputs +window.CanvasComponents = CanvasComponents; + +document.addEventListener("DOMContentLoaded", main, false); + diff --git a/plugins/srktoolbox/src/web/static/fonts/MaterialIcons-Regular.ttf b/plugins/srktoolbox/src/web/static/fonts/MaterialIcons-Regular.ttf new file mode 100644 index 00000000..54938737 Binary files /dev/null and b/plugins/srktoolbox/src/web/static/fonts/MaterialIcons-Regular.ttf differ diff --git a/plugins/srktoolbox/src/web/static/fonts/bmfonts/Roboto72White.fnt b/plugins/srktoolbox/src/web/static/fonts/bmfonts/Roboto72White.fnt new file mode 100644 index 00000000..19cb2ab8 --- /dev/null +++ b/plugins/srktoolbox/src/web/static/fonts/bmfonts/Roboto72White.fnt @@ -0,0 +1,491 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/srktoolbox/src/web/static/fonts/bmfonts/Roboto72White.png b/plugins/srktoolbox/src/web/static/fonts/bmfonts/Roboto72White.png new file mode 100644 index 00000000..423a3a7e Binary files /dev/null and b/plugins/srktoolbox/src/web/static/fonts/bmfonts/Roboto72White.png differ diff --git a/plugins/srktoolbox/src/web/static/fonts/bmfonts/RobotoBlack72White.fnt b/plugins/srktoolbox/src/web/static/fonts/bmfonts/RobotoBlack72White.fnt new file mode 100644 index 00000000..77f0ed89 --- /dev/null +++ b/plugins/srktoolbox/src/web/static/fonts/bmfonts/RobotoBlack72White.fnt @@ -0,0 +1,494 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/srktoolbox/src/web/static/fonts/bmfonts/RobotoBlack72White.png b/plugins/srktoolbox/src/web/static/fonts/bmfonts/RobotoBlack72White.png new file mode 100644 index 00000000..f3bbba30 Binary files /dev/null and b/plugins/srktoolbox/src/web/static/fonts/bmfonts/RobotoBlack72White.png differ diff --git a/plugins/srktoolbox/src/web/static/fonts/bmfonts/RobotoMono72White.fnt b/plugins/srktoolbox/src/web/static/fonts/bmfonts/RobotoMono72White.fnt new file mode 100644 index 00000000..10ccee57 --- /dev/null +++ b/plugins/srktoolbox/src/web/static/fonts/bmfonts/RobotoMono72White.fnt @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/srktoolbox/src/web/static/fonts/bmfonts/RobotoMono72White.png b/plugins/srktoolbox/src/web/static/fonts/bmfonts/RobotoMono72White.png new file mode 100644 index 00000000..ed192363 Binary files /dev/null and b/plugins/srktoolbox/src/web/static/fonts/bmfonts/RobotoMono72White.png differ diff --git a/plugins/srktoolbox/src/web/static/fonts/bmfonts/RobotoSlab72White.fnt b/plugins/srktoolbox/src/web/static/fonts/bmfonts/RobotoSlab72White.fnt new file mode 100644 index 00000000..97a2ab63 --- /dev/null +++ b/plugins/srktoolbox/src/web/static/fonts/bmfonts/RobotoSlab72White.fnt @@ -0,0 +1,498 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/srktoolbox/src/web/static/fonts/bmfonts/RobotoSlab72White.png b/plugins/srktoolbox/src/web/static/fonts/bmfonts/RobotoSlab72White.png new file mode 100644 index 00000000..5aa1bf06 Binary files /dev/null and b/plugins/srktoolbox/src/web/static/fonts/bmfonts/RobotoSlab72White.png differ diff --git a/plugins/srktoolbox/src/web/static/ga.html b/plugins/srktoolbox/src/web/static/ga.html new file mode 100644 index 00000000..d285cdf9 --- /dev/null +++ b/plugins/srktoolbox/src/web/static/ga.html @@ -0,0 +1,12 @@ + + + + + + + diff --git a/plugins/srktoolbox/src/web/static/images/IMAGE_LICENCES.md b/plugins/srktoolbox/src/web/static/images/IMAGE_LICENCES.md new file mode 100644 index 00000000..8ed68bd6 --- /dev/null +++ b/plugins/srktoolbox/src/web/static/images/IMAGE_LICENCES.md @@ -0,0 +1,11 @@ +## Image attribution + +The following image files in this directory are taken from open sources: + +| File | Licence | Attribution | +| --------------------- | ----------------------------------------- | ----------------------------------------------------------------------------- | +| cook_female-32x32.png | Creative Commons Attribution 3.0 Unported | https://commons.wikimedia.org/wiki/File:Farm-Fresh_user_cook_female_white.png | +| cook_male-32x32.png | Creative Commons Attribution 3.0 Unported | https://commons.wikimedia.org/wiki/File:Farm-Fresh_user_cook_male_white.png | +| multitool-32x32.png | Creative Commons Attribution 3.0 Unported | https://commons.wikimedia.org/wiki/File:Farm-Fresh_multitool.png | +| file-32x32.png | Free for commercial use | https://www.iconfinder.com/icons/66768/file_info_icon | +| file-128x128.png | Free for commercial use | https://www.iconfinder.com/icons/66768/file_info_icon | \ No newline at end of file diff --git a/plugins/srktoolbox/src/web/static/images/bombe.svg b/plugins/srktoolbox/src/web/static/images/bombe.svg new file mode 100644 index 00000000..4c04ef2f --- /dev/null +++ b/plugins/srktoolbox/src/web/static/images/bombe.svg @@ -0,0 +1,261 @@ + + + + + + + + + + + + + + Z + Y + X + W + V + U + T + S + R + Q + P + O + N + M + L + K + J + I + H + G + F + E + D + C + B + A + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/srktoolbox/src/web/static/images/cook_female-32x32.png b/plugins/srktoolbox/src/web/static/images/cook_female-32x32.png new file mode 100644 index 00000000..a97a3ef6 Binary files /dev/null and b/plugins/srktoolbox/src/web/static/images/cook_female-32x32.png differ diff --git a/plugins/srktoolbox/src/web/static/images/cook_male-32x32.png b/plugins/srktoolbox/src/web/static/images/cook_male-32x32.png new file mode 100644 index 00000000..2d713bb3 Binary files /dev/null and b/plugins/srktoolbox/src/web/static/images/cook_male-32x32.png differ diff --git a/plugins/srktoolbox/src/web/static/images/cyberchef-128x128.png b/plugins/srktoolbox/src/web/static/images/cyberchef-128x128.png new file mode 100644 index 00000000..eb182286 Binary files /dev/null and b/plugins/srktoolbox/src/web/static/images/cyberchef-128x128.png differ diff --git a/plugins/srktoolbox/src/web/static/images/cyberchef-256x256.png b/plugins/srktoolbox/src/web/static/images/cyberchef-256x256.png new file mode 100644 index 00000000..4a3dfc32 Binary files /dev/null and b/plugins/srktoolbox/src/web/static/images/cyberchef-256x256.png differ diff --git a/plugins/srktoolbox/src/web/static/images/cyberchef-32x32.png b/plugins/srktoolbox/src/web/static/images/cyberchef-32x32.png new file mode 100644 index 00000000..5880f335 Binary files /dev/null and b/plugins/srktoolbox/src/web/static/images/cyberchef-32x32.png differ diff --git a/plugins/srktoolbox/src/web/static/images/cyberchef-512x512.png b/plugins/srktoolbox/src/web/static/images/cyberchef-512x512.png new file mode 100644 index 00000000..90c220e0 Binary files /dev/null and b/plugins/srktoolbox/src/web/static/images/cyberchef-512x512.png differ diff --git a/plugins/srktoolbox/src/web/static/images/cyberchef-64x64.png b/plugins/srktoolbox/src/web/static/images/cyberchef-64x64.png new file mode 100644 index 00000000..2831f62f Binary files /dev/null and b/plugins/srktoolbox/src/web/static/images/cyberchef-64x64.png differ diff --git a/plugins/srktoolbox/src/web/static/images/favicon.ico b/plugins/srktoolbox/src/web/static/images/favicon.ico new file mode 100644 index 00000000..753549aa Binary files /dev/null and b/plugins/srktoolbox/src/web/static/images/favicon.ico differ diff --git a/plugins/srktoolbox/src/web/static/images/favicon.ico.original b/plugins/srktoolbox/src/web/static/images/favicon.ico.original new file mode 100644 index 00000000..fa2deb03 Binary files /dev/null and b/plugins/srktoolbox/src/web/static/images/favicon.ico.original differ diff --git a/plugins/srktoolbox/src/web/static/images/file-128x128.png b/plugins/srktoolbox/src/web/static/images/file-128x128.png new file mode 100644 index 00000000..005c504d Binary files /dev/null and b/plugins/srktoolbox/src/web/static/images/file-128x128.png differ diff --git a/plugins/srktoolbox/src/web/static/images/file-32x32.png b/plugins/srktoolbox/src/web/static/images/file-32x32.png new file mode 100644 index 00000000..884bf485 Binary files /dev/null and b/plugins/srktoolbox/src/web/static/images/file-32x32.png differ diff --git a/plugins/srktoolbox/src/web/static/images/fork_me.png b/plugins/srktoolbox/src/web/static/images/fork_me.png new file mode 100644 index 00000000..2fd0fe75 Binary files /dev/null and b/plugins/srktoolbox/src/web/static/images/fork_me.png differ diff --git a/plugins/srktoolbox/src/web/static/images/gitter-badge.svg b/plugins/srktoolbox/src/web/static/images/gitter-badge.svg new file mode 100644 index 00000000..7064d7f4 --- /dev/null +++ b/plugins/srktoolbox/src/web/static/images/gitter-badge.svg @@ -0,0 +1 @@ +chatchaton gitteron gitter \ No newline at end of file diff --git a/plugins/srktoolbox/src/web/static/images/logo/cyberchef.svg b/plugins/srktoolbox/src/web/static/images/logo/cyberchef.svg new file mode 100644 index 00000000..ff817f20 --- /dev/null +++ b/plugins/srktoolbox/src/web/static/images/logo/cyberchef.svg @@ -0,0 +1,728 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/srktoolbox/src/web/static/images/logo/cyberchef_512.png b/plugins/srktoolbox/src/web/static/images/logo/cyberchef_512.png new file mode 100644 index 00000000..f48cd49b Binary files /dev/null and b/plugins/srktoolbox/src/web/static/images/logo/cyberchef_512.png differ diff --git a/plugins/srktoolbox/src/web/static/images/logo/cyberchef_banner.svg b/plugins/srktoolbox/src/web/static/images/logo/cyberchef_banner.svg new file mode 100644 index 00000000..919d9ceb --- /dev/null +++ b/plugins/srktoolbox/src/web/static/images/logo/cyberchef_banner.svg @@ -0,0 +1,754 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CyberChef + + + diff --git a/plugins/srktoolbox/src/web/static/images/logo/cyberchef_banner_1500.png b/plugins/srktoolbox/src/web/static/images/logo/cyberchef_banner_1500.png new file mode 100644 index 00000000..7f115a9b Binary files /dev/null and b/plugins/srktoolbox/src/web/static/images/logo/cyberchef_banner_1500.png differ diff --git a/plugins/srktoolbox/src/web/static/images/logo/cyberchef_hat.svg b/plugins/srktoolbox/src/web/static/images/logo/cyberchef_hat.svg new file mode 100644 index 00000000..9ec0f951 --- /dev/null +++ b/plugins/srktoolbox/src/web/static/images/logo/cyberchef_hat.svg @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/plugins/srktoolbox/src/web/static/images/logo/cyberchef_hat_512.png b/plugins/srktoolbox/src/web/static/images/logo/cyberchef_hat_512.png new file mode 100644 index 00000000..1fbdfbc7 Binary files /dev/null and b/plugins/srktoolbox/src/web/static/images/logo/cyberchef_hat_512.png differ diff --git a/plugins/srktoolbox/src/web/static/images/logo/cyberchef_hat_text_512.png b/plugins/srktoolbox/src/web/static/images/logo/cyberchef_hat_text_512.png new file mode 100644 index 00000000..67067a68 Binary files /dev/null and b/plugins/srktoolbox/src/web/static/images/logo/cyberchef_hat_text_512.png differ diff --git a/plugins/srktoolbox/src/web/static/images/logo/cyberchef_human.svg b/plugins/srktoolbox/src/web/static/images/logo/cyberchef_human.svg new file mode 100644 index 00000000..a197088c --- /dev/null +++ b/plugins/srktoolbox/src/web/static/images/logo/cyberchef_human.svg @@ -0,0 +1,1157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/srktoolbox/src/web/static/images/logo/cyberchef_robot.svg b/plugins/srktoolbox/src/web/static/images/logo/cyberchef_robot.svg new file mode 100644 index 00000000..8b121398 --- /dev/null +++ b/plugins/srktoolbox/src/web/static/images/logo/cyberchef_robot.svg @@ -0,0 +1,833 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/srktoolbox/src/web/static/images/multitool-32x32.png b/plugins/srktoolbox/src/web/static/images/multitool-32x32.png new file mode 100644 index 00000000..f2a46694 Binary files /dev/null and b/plugins/srktoolbox/src/web/static/images/multitool-32x32.png differ diff --git a/plugins/srktoolbox/src/web/static/images/rocket-1.svg b/plugins/srktoolbox/src/web/static/images/rocket-1.svg new file mode 100644 index 00000000..b90595cf --- /dev/null +++ b/plugins/srktoolbox/src/web/static/images/rocket-1.svg @@ -0,0 +1,12 @@ + \ No newline at end of file diff --git a/plugins/srktoolbox/src/web/static/images/srk-128x128.png b/plugins/srktoolbox/src/web/static/images/srk-128x128.png new file mode 100644 index 00000000..55db1fc2 Binary files /dev/null and b/plugins/srktoolbox/src/web/static/images/srk-128x128.png differ diff --git a/plugins/srktoolbox/src/web/static/images/srk-32x32.png b/plugins/srktoolbox/src/web/static/images/srk-32x32.png new file mode 100644 index 00000000..41c2d7fe Binary files /dev/null and b/plugins/srktoolbox/src/web/static/images/srk-32x32.png differ diff --git a/plugins/srktoolbox/src/web/static/sitemap.mjs b/plugins/srktoolbox/src/web/static/sitemap.mjs new file mode 100644 index 00000000..4f8101d4 --- /dev/null +++ b/plugins/srktoolbox/src/web/static/sitemap.mjs @@ -0,0 +1,33 @@ +import sm from "sitemap"; +import OperationConfig from "../../core/config/OperationConfig.json" assert { type: "json" }; + +/** + * Generates an XML sitemap for all CyberChef operations and a number of recipes. + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +const baseUrl = "https://gchq.github.io/CyberChef/"; + +const smStream = new sm.SitemapStream({}); + +smStream.write({ + url: baseUrl, + changefreq: "weekly", + priority: 1.0, +}); + +for (const op in OperationConfig) { + smStream.write({ + url: `${baseUrl}?op=${encodeURIComponent(op)}`, + changeFreq: "yearly", + priority: 0.5, + }); +} +smStream.end(); + +sm.streamToPromise(smStream).then( + (buffer) => console.log(buffer.toString()), // eslint-disable-line no-console +); diff --git a/plugins/srktoolbox/src/web/static/structuredData.json b/plugins/srktoolbox/src/web/static/structuredData.json new file mode 100644 index 00000000..e242f70e --- /dev/null +++ b/plugins/srktoolbox/src/web/static/structuredData.json @@ -0,0 +1,26 @@ +[ + { + "@context": "http://schema.org", + "@graph": [ + { + "@type": "Organization", + "url": "https://gchq.github.io/CyberChef/", + "logo": "https://gchq.github.io/CyberChef/images/cyberchef-128x128.png", + "sameAs": [ + "https://github.com/gchq/CyberChef", + "https://www.npmjs.com/package/cyberchef" + ] + }, + { + "@type": "WebSite", + "url": "https://gchq.github.io/CyberChef/", + "name": "CyberChef", + "potentialAction": { + "@type": "SearchAction", + "target": "https://gchq.github.io/CyberChef/?op={operation_search_term}", + "query-input": "required name=operation_search_term" + } + } + ] + } +] \ No newline at end of file diff --git a/plugins/srktoolbox/src/web/stylesheets/components/_button.css b/plugins/srktoolbox/src/web/stylesheets/components/_button.css new file mode 100644 index 00000000..54dfc339 --- /dev/null +++ b/plugins/srktoolbox/src/web/stylesheets/components/_button.css @@ -0,0 +1,13 @@ +/** + * Button styles + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +button img, +span.btn img { + margin-right: 3px; + margin-bottom: 1px; +} diff --git a/plugins/srktoolbox/src/web/stylesheets/components/_list.css b/plugins/srktoolbox/src/web/stylesheets/components/_list.css new file mode 100644 index 00000000..b732b4c1 --- /dev/null +++ b/plugins/srktoolbox/src/web/stylesheets/components/_list.css @@ -0,0 +1,52 @@ +/** + * Operation list styles + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +.op-list { + list-style-type: none; + margin: 0; + padding: 0; +} + +.category-title { + display: block; + padding: 10px; + background-color: var(--secondary-background-colour); + border-bottom: 1px solid var(--secondary-border-colour); + font-weight: var(--title-weight); +} + +.category-title[href='#catFavourites'] { + border-bottom-color: var(--primary-border-colour); +} + +.category-title[aria-expanded=true] { + border-bottom-color: var(--primary-border-colour); +} + +.category-title.collapsed { + border-bottom-color: var(--secondary-border-colour); +} + +.category-title:hover { + color: var(--op-list-operation-font-colour); +} + +.category { + margin: 0 !important; + border-radius: 0 !important; + border: none; +} + +.op-count { + float: right; + color: var(--subtext-font-colour); + font-weight: normal; + font-size: xx-small; + opacity: 0.5; + padding-left: .5em; +} diff --git a/plugins/srktoolbox/src/web/stylesheets/components/_operation.css b/plugins/srktoolbox/src/web/stylesheets/components/_operation.css new file mode 100644 index 00000000..7a18ae26 --- /dev/null +++ b/plugins/srktoolbox/src/web/stylesheets/components/_operation.css @@ -0,0 +1,351 @@ +/** + * Operation styles + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +.operation { + cursor: grab; + padding: 10px; + list-style-type: none; + position: relative; + border-width: 1px; + border-style: solid; + border-top: none; + border-left: none; + border-right: none; +} + +#rec-list .operation { + padding: 14px; +} + +.op-title { + font-weight: var(--op-title-font-weight); +} + +.ingredients { + display: flex; + flex-flow: row wrap; + justify-content: flex-start; + column-gap: 14px; + row-gap: 0; +} + +.ing-very-wide { + flex: 4 280px; +} + +.ing-wide { + flex: 3 200px; +} + +.ing-medium { + flex: 2 120px; +} + +.ing-short { + flex: 1 80px; +} + +.ing-flexible { + flex-grow: 1; +} + +.ingredients .form-group { + margin-top: 1rem; + padding-top: 0; +} + +.arg { + font-family: var(--fixed-width-font-family); + text-overflow: ellipsis; +} + +select.arg { + font-family: var(--primary-font-family); + min-width: 100px; +} + +select.arg.form-control:not([size]):not([multiple]), select.custom-file-control:not([size]):not([multiple]) { + height: 100% !important; +} + +textarea.arg { + min-height: 74px; + resize: vertical; +} + +div.toggle-string { + flex: 1; +} + +input.toggle-string { + border-top-right-radius: 0 !important; + height: 100%; +} + +.operation [class^='bmd-label'], +.operation [class*=' bmd-label'] { + top: 13px !important; + left: 12px; + z-index: 10; +} + +.operation label, +.operation .checkbox label { + color: var(--arg-label-colour); +} + +.operation .is-focused [class^='bmd-label'], +.operation .is-focused [class*=' bmd-label'], +.operation .is-focused [class^='bmd-label'], +.operation .is-focused [class*=' bmd-label'], +.operation .is-focused label, +.operation .checkbox label:hover { + color: var(--input-highlight-colour); +} + +.ingredients .checkbox label input[type=checkbox]+.checkbox-decorator .check, +.ingredients .checkbox label input[type=checkbox]+.checkbox-decorator .check::before { + border-color: var(--input-border-colour); + color: var(--input-highlight-colour); +} + +.ingredients .checkbox label input[type=checkbox]:checked+.checkbox-decorator .check, +.ingredients .checkbox label input[type=checkbox]:checked+.checkbox-decorator .check::before { + border-color: var(--input-highlight-colour); + color: var(--input-highlight-colour); +} + +.disabled .ingredients .checkbox label input[type=checkbox]+.checkbox-decorator .check, +.disabled .ingredients .checkbox label input[type=checkbox]+.checkbox-decorator .check::before, +.disabled .ingredients .checkbox label input[type=checkbox]:checked+.checkbox-decorator .check, +.disabled .ingredients .checkbox label input[type=checkbox]:checked+.checkbox-decorator .check::before { + border-color: var(--disabled-font-colour); + color: var(--disabled-font-colour); +} + +.break .ingredients .checkbox label input[type=checkbox]+.checkbox-decorator .check, +.break .ingredients .checkbox label input[type=checkbox]+.checkbox-decorator .check::before, +.break .ingredients .checkbox label input[type=checkbox]:checked+.checkbox-decorator .check, +.break .ingredients .checkbox label input[type=checkbox]:checked+.checkbox-decorator .check::before { + border-color: var(--breakpoint-font-colour); + color: var(--breakpoint-font-colour); +} + +.flow-control-op.break .ingredients .checkbox label input[type=checkbox]+.checkbox-decorator .check, +.flow-control-op.break .ingredients .checkbox label input[type=checkbox]+.checkbox-decorator .check::before, +.flow-control-op.break .ingredients .checkbox label input[type=checkbox]:checked+.checkbox-decorator .check, +.flow-control-op.break .ingredients .checkbox label input[type=checkbox]:checked+.checkbox-decorator .check::before { + border-color: var(--fc-breakpoint-operation-font-colour); + color: var(--fc-breakpoint-operation-font-colour); +} + +.operation .form-control { + padding: 20px 12px 6px 12px !important; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + background-image: none; + background-color: var(--arg-background); + background-position-y: 100%, 100%; + color: var(--arg-font-colour); +} + +.operation .form-control:hover { + background-image: + linear-gradient(to top, var(--input-highlight-colour) 2px, rgba(25, 118, 210, 0) 2px), + linear-gradient(to top, rgba(0, 0, 0, 0.26) 1px, rgba(0, 0, 0, 0) 1px); + filter: brightness(97%); +} + +.operation .form-control:focus { + background-color: var(--arg-background); + background-image: + linear-gradient(to top, var(--input-highlight-colour) 2px, rgba(25, 118, 210, 0) 2px), + linear-gradient(to top, rgba(0, 0, 0, 0.26) 1px, rgba(0, 0, 0, 0) 1px); + filter: brightness(100%); +} + +.operation .bmd-form-group.is-filled label.bmd-label-floating, +.operation .bmd-form-group.is-focused label.bmd-label-floating { + top: 4px !important; + left: 12px; +} + +.operation label.bmd-label-floating { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + width: calc(100% - 13px); +} + +.input-group .form-control { + border-top-left-radius: 4px; +} + +.input-group-append button { + border-top-right-radius: 4px; + background-color: var(--arg-background) !important; + margin: unset; +} + +.input-group-append button:hover { + filter: brightness(97%); +} + +.editable-option-menu { + height: auto; + max-height: 350px; + overflow-x: hidden; +} + +.editable-option-menu .dropdown-item { + padding: 0.3rem 1rem 0.3rem 1rem; + min-height: 1.6rem; + max-width: 20rem; +} + +.ingredients .dropdown-toggle-split { + height: 40px !important; +} + +.boolean-arg { + height: 46px; +} + +.boolean-arg .checkbox { + height: 100%; +} + +.boolean-arg .checkbox label { + height: 100%; + display: flex; + align-items: center; +} + +.boolean-arg .checkbox-decorator { + top: 13px; +} + +.register-list { + background-color: var(--fc-operation-border-colour); + font-family: var(--fixed-width-font-family); + padding: 10px; + word-break: break-all; +} + +.op-icon { + float: right; + color: #f44336; + font-size: 18px; + cursor: pointer; +} + +.recip-icons { + position: absolute; + top: 13px; + right: 10px; + height: 16px; +} + +.recip-icons i { + margin-right: 10px; + vertical-align: baseline; + float: right; + font-size: 18px; + cursor: pointer; +} + +.disable-icon { + color: var(--disable-icon-colour); +} + +.disable-icon-selected { + color: var(--disable-icon-selected-colour); +} + +.breakpoint { + color: var(--breakpoint-icon-colour); +} + +.breakpoint-selected { + color: var(--breakpoint-icon-selected-colour); +} + +.break { + color: var(--breakpoint-font-colour) !important; + background-color: var(--breakpoint-bg-colour) !important; + border-color: var(--breakpoint-border-colour) !important; +} + +.break .form-group * { color: var(--breakpoint-font-colour) !important; } + +.selected-op { + color: var(--selected-operation-font-color) !important; + background-color: var(--selected-operation-bg-colour) !important; + border-color: var(--selected-operation-border-colour) !important; +} + +.selected-op .form-group * { color: var(--selected-operation-font-color) !important; } + +.flow-control-op { + color: var(--fc-operation-font-colour) !important; + background-color: var(--fc-operation-bg-colour) !important; + border-color: var(--fc-operation-border-colour) !important; +} + +.flow-control-op .form-group *:not(.arg) { color: var(--fc-operation-font-colour) } + +.flow-control-op.break { + color: var(--fc-breakpoint-operation-font-colour) !important; + background-color: var(--fc-breakpoint-operation-bg-colour) !important; + border-color: var(--fc-breakpoint-operation-border-colour) !important; +} + +.flow-control-op.break .form-group * { color: var(--fc-breakpoint-operation-font-colour) !important; } + +.disabled { + color: var(--disabled-font-colour) !important; + background-color: var(--disabled-bg-colour) !important; + border-color: var(--disabled-border-colour) !important; +} + +.disabled .form-group * { color: var(--disabled-font-colour) !important; } + +.break .register-list { + color: var(--fc-breakpoint-operation-font-colour) !important; + background-color: var(--fc-breakpoint-operation-border-colour) !important; +} + +.disabled .register-list { + color: var(--disabled-font-colour) !important; + background-color: var(--disabled-border-colour) !important; +} + +@media (max-width: 720px) { + #rec-list .operation { + padding: 10px; + } + + .ingredients { + column-gap: 8px; + } + + .ing-very-wide, + .ing-wide, + .ing-medium, + .ing-short { + flex-basis: 100%; + } + + select.arg { + min-width: 0; + } + + .recip-icons i { + margin-right: 6px; + } +} diff --git a/plugins/srktoolbox/src/web/stylesheets/components/_pane.css b/plugins/srktoolbox/src/web/stylesheets/components/_pane.css new file mode 100644 index 00000000..6b97683e --- /dev/null +++ b/plugins/srktoolbox/src/web/stylesheets/components/_pane.css @@ -0,0 +1,54 @@ +/** + * Workspace pane styles + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + * + * Modified by Raka-loah@github + */ + +:root { + --title-height: 48px; + --tab-height: 40px; +} + +.title { + padding: 8px; + padding-left: 12px; + padding-right: 12px; + height: var(--title-height); + border-bottom: 1px solid var(--primary-border-colour); + font-weight: var(--title-weight); + font-size: var(--title-size); + color: var(--title-colour); + background-color: var(--title-background-colour); + line-height: calc(var(--title-height) - 14px); + overflow: hidden; +} + +.pane-controls { + position: absolute; + right: 8px; + top: 8px; + display: flex; + flex-direction: row; +} + +.pane-controls .btn { + margin-left: 2px; +} + +.list-area { + position: absolute; + top: var(--title-height); + bottom: 0; + width: 100%; + list-style-type: none; + margin: 0; + padding: 0; +} + +#files .card-header .float-right a:hover { + text-decoration: none; +} diff --git a/plugins/srktoolbox/src/web/stylesheets/index.css b/plugins/srktoolbox/src/web/stylesheets/index.css new file mode 100644 index 00000000..0565399f --- /dev/null +++ b/plugins/srktoolbox/src/web/stylesheets/index.css @@ -0,0 +1,40 @@ +/** + * CyberChef styles + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +/* Themes */ +@import "./themes/_classic.css"; +@import "./themes/_dark.css"; +@import "./themes/_geocities.css"; +@import "./themes/_solarizedDark.css"; +@import "./themes/_solarizedLight.css"; + +/* Utilities */ +@import "./utils/_overrides.css"; +@import "./utils/_general.css"; + +/* Preloader styles */ +@import "./preloader.css"; + +/* Components */ +@import "./components/_button.css"; +@import "./components/_list.css"; +@import "./components/_operation.css"; +@import "./components/_pane.css"; + +/* Layout */ +@import "./layout/_banner.css"; +@import "./layout/_controls.css"; +@import "./layout/_io.css"; +@import "./layout/_modals.css"; +@import "./layout/_operations.css"; +@import "./layout/_recipe.css"; +@import "./layout/_structure.css"; + +/* Operations */ +@import "./operations/diff.css"; +@import "./operations/json.css"; diff --git a/plugins/srktoolbox/src/web/stylesheets/index.js b/plugins/srktoolbox/src/web/stylesheets/index.js new file mode 100644 index 00000000..98c8f019 --- /dev/null +++ b/plugins/srktoolbox/src/web/stylesheets/index.js @@ -0,0 +1,17 @@ +/** + * Styles index + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +/* Libraries */ +import "highlight.js/styles/vs.css"; + +/* Frameworks */ +import "bootstrap-material-design/dist/css/bootstrap-material-design.css"; +import "bootstrap-colorpicker/dist/css/bootstrap-colorpicker.css"; + +/* CyberChef styles */ +import "./index.css"; diff --git a/plugins/srktoolbox/src/web/stylesheets/layout/_banner.css b/plugins/srktoolbox/src/web/stylesheets/layout/_banner.css new file mode 100644 index 00000000..ff4bec19 --- /dev/null +++ b/plugins/srktoolbox/src/web/stylesheets/layout/_banner.css @@ -0,0 +1,92 @@ +/** + * Banner area styles + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +#banner { + position: absolute; + height: 30px; + width: 100%; + line-height: 30px; + border-bottom: 1px solid var(--primary-border-colour); + color: var(--banner-font-colour); + background-color: var(--banner-bg-colour); + margin: 0; + overflow: hidden; + flex-wrap: nowrap; +} + +#banner i { + vertical-align: middle; + padding-right: 10px; +} + +#banner a { + color: var(--banner-url-colour); +} + +#notice-wrapper { + text-align: center; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +@media (max-width: 1180px) { + #banner > .col:first-child { + display: none; + } + + #banner > .col, + #banner > .col-md-6 { + flex: 0 0 auto; + width: auto; + max-width: none; + min-width: 0; + padding-left: 8px !important; + padding-right: 8px !important; + } + + #notice-wrapper { + flex: 1 1 auto !important; + text-align: left; + } + + #banner > .col:last-child { + margin-left: auto; + white-space: nowrap; + } + + #banner a { + display: inline-flex; + align-items: center; + max-width: 80px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + vertical-align: top; + } + + #banner i { + padding-right: 0; + padding-left: 2px; + } +} + +@media (max-width: 720px) { + #notice-wrapper { + display: none; + } + + #banner > .col:last-child { + width: 100%; + text-align: right !important; + } + + #banner a { + max-width: 88px; + } +} diff --git a/plugins/srktoolbox/src/web/stylesheets/layout/_controls.css b/plugins/srktoolbox/src/web/stylesheets/layout/_controls.css new file mode 100644 index 00000000..1edc41b5 --- /dev/null +++ b/plugins/srktoolbox/src/web/stylesheets/layout/_controls.css @@ -0,0 +1,69 @@ +/** + * Controls area styles + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +#controls { + position: absolute; + width: 100%; + bottom: 0; + padding: 10px 0; + border-top: 1px solid var(--primary-border-colour); + background-color: var(--secondary-background-colour); +} + +#controls-content { + position: relative; + display: flex; + flex-flow: row nowrap; + align-items: center; +} + +#auto-bake-label { + display: inline-block; + width: 100px; + padding: 0; + margin: 0; + text-align: center; + color: var(--primary-font-colour); + font-size: 14px; + cursor: pointer; +} + +#auto-bake-label .check, +#auto-bake-label .check::before { + border-color: var(--input-highlight-colour); + color: var(--input-highlight-colour); +} + +#auto-bake-label .checkbox-decorator { + position: relative; +} + +#bake { + box-shadow: none; +} + +#controls .btn { + border-radius: 30px; + margin: 0; +} + +.output-maximised .hide-on-maximised-output { + display: none !important; +} + +.spin { + animation-name: spin; + animation-duration: 3s; + animation-iteration-count: infinite; + animation-timing-function: linear; +} + +@keyframes spin { + 0% {transform: rotate(0deg);} + 100% {transform: rotate(360deg);} +} diff --git a/plugins/srktoolbox/src/web/stylesheets/layout/_io.css b/plugins/srktoolbox/src/web/stylesheets/layout/_io.css new file mode 100644 index 00000000..c44e94d0 --- /dev/null +++ b/plugins/srktoolbox/src/web/stylesheets/layout/_io.css @@ -0,0 +1,592 @@ +/** + * Input/Output area styles + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +#input-text, +#output-text { + position: relative; + width: 100%; + height: 100%; + margin: 0; + background-color: transparent; + overflow: hidden; + user-select: auto; +} + +#output-text.html-output .cm-content, +#output-text.html-output .cm-line, +#output-html { + display: block; + height: 100%; + user-select: auto; +} +#output-text.html-output .cm-line .cm-widgetBuffer, +#output-text.html-output .cm-line>br { + display: none; +} + +.cm-editor { + height: 100%; +} + +.cm-editor .cm-content { + font-family: var(--fixed-width-font-family); + font-size: var(--fixed-width-font-size); + color: var(--fixed-width-font-colour); +} + +#input-tabs-wrapper #input-tabs, +#output-tabs-wrapper #output-tabs { + list-style: none; + background-color: var(--title-background-colour); + padding: 0; + margin: 0; + overflow-x: auto; + overflow-y: hidden; + display: flex; + flex-direction: row; + border-bottom: 1px solid var(--primary-border-colour); + border-left: 1px solid var(--primary-border-colour); + height: var(--tab-height); + clear: none; +} + +#input-tabs li, +#output-tabs li { + display: flex; + flex-direction: row; + width: 100%; + min-width: 80px; + float: left; + padding: 0px; + text-align: center; + border-right: 1px solid var(--primary-border-colour); + height: var(--tab-height); + vertical-align: middle; +} + +#input-tabs li:hover, +#output-tabs li:hover { + cursor: pointer; + background-color: var(--primary-background-colour); +} + +.active-input-tab, +.active-output-tab { + font-weight: bold; + background-color: var(--primary-background-colour); +} + +.input-tab-content+.btn-close-tab { + display: block; + margin-top: auto; + margin-bottom: auto; + margin-right: 2px; +} + +.input-tab-content+.btn-close-tab i { + font-size: 0.8em; +} + +.input-tab-buttons, +.output-tab-buttons { + width: 25px; + text-align: center; + margin: 0; + height: var(--tab-height); + line-height: var(--tab-height); + font-weight: bold; + background-color: var(--title-background-colour); + border-bottom: 1px solid var(--primary-border-colour); +} + +.input-tab-buttons:hover, +.output-tab-buttons:hover { + cursor: pointer; + background-color: var(--primary-background-colour); +} + + +#btn-next-input-tab, +#btn-input-tab-dropdown, +#btn-next-output-tab, +#btn-output-tab-dropdown { + float: right; +} + +#btn-previous-input-tab, +#btn-previous-output-tab { + float: left; +} + +#btn-close-all-tabs { + color: var(--breakpoint-font-colour) !important; +} + +.input-tab-content, +.output-tab-content { + width: 100%; + max-width: 100%; + padding-left: 5px; + padding-right: 5px; + padding-top: 10px; + padding-bottom: 10px; + height: var(--tab-height); + vertical-align: middle; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.btn-close-tab { + height: var(--tab-height); + vertical-align: middle; + width: fit-content; +} + +.tabs-left > li:first-child { + box-shadow: 15px 0px 15px -15px var(--primary-border-colour) inset; +} + +.tabs-right > li:last-child { + box-shadow: -15px 0px 15px -15px var(--primary-border-colour) inset; +} + +#input-wrapper, +#output-wrapper { + height: calc(100% - var(--title-height)); +} + +#input-wrapper.show-tabs, +#output-wrapper.show-tabs { + height: calc(100% - var(--tab-height) - var(--title-height)); +} + +#output-loader { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 100%; + margin: 0; + background-color: var(--secondary-background-colour); + opacity: 0; + visibility: hidden; + display: flex; + justify-content: center; + align-items: center; + + transition: all 0.5s ease; +} + +#output-loader-animation { + display: block; + position: absolute; + width: 60%; + height: 60%; + top: 10%; + transition: all 0.5s ease; +} + +#output-loader .loading-msg { + opacity: 1; + font-family: var(--primary-font-family); + line-height: var(--primary-line-height); + color: var(--primary-font-colour); + left: unset; + top: 30%; + position: relative; + + transition: all 0.5s ease; +} + +.io-info { + margin-right: 18px; + margin-top: 1px; + height: 30px; + text-align: right; + line-height: 12px; + font-family: var(--fixed-width-font-family); + font-weight: normal; + font-size: 8pt; + display: flex; + align-items: center; +} + +.dropping-file { + border: 5px dashed var(--drop-file-border-colour) !important; +} + +#stale-indicator { + opacity: 1; + visibility: visible; + transition: margin 0s, opacity 0.3s; + margin-left: 5px; + cursor: help; +} + +#stale-indicator i { + vertical-align: middle; + margin-bottom: 5px; +} + +#magic { + opacity: 1; + visibility: visible; + transition: margin 0s 0.3s, opacity 0.3s 0.3s, visibility 0.3s 0.3s; + margin-left: 5px; + margin-bottom: 5px; +} + +#magic.hidden, +#stale-indicator.hidden { + visibility: hidden; + transition: opacity 0.3s, margin 0.3s 0.3s, visibility 0.3s; + opacity: 0; +} + +#magic.hidden { + margin-left: -32px; +} + +#magic svg path { + fill: var(--primary-font-colour); +} + +.pulse { + box-shadow: 0 0 0 0 rgba(90, 153, 212, .3); + animation: pulse 1.5s 1; +} + +.pulse:hover { + animation-play-state: paused; +} + +@keyframes pulse { + 0% { + transform: scale(1); + } + 70% { + transform: scale(1.1); + box-shadow: 0 0 0 20px rgba(90, 153, 212, 0); + } + 100% { + transform: scale(1); + box-shadow: 0 0 0 0 rgba(90, 153, 212, 0); + } +} + +#input-find-options, +#output-find-options { + display: flex; + flex-direction: row; + flex-wrap: wrap; + width: 100%; +} + +#input-tab-body .form-group.input-group, +#output-tab-body .form-group.input-group { + width: 70%; + float: left; + margin-bottom: 2rem; +} + +.input-find-option .toggle-string { + width: 70%; + display: inline-block; +} + +.input-find-option-append button { + border-top-right-radius: 4px; + background-color: var(--arg-background) !important; + margin: unset; +} + +.input-find-option-append button:hover { + filter: brightness(97%); +} + +.form-group.output-find-option { + width: 70%; + float: left; +} + +#input-num-results-container, +#output-num-results-container { + width: 20%; + float: right; + margin: 0; + margin-left: 10%; +} + +#input-find-options-checkboxes, +#output-find-options-checkboxes { + list-style: none; + padding: 0; + margin: auto; + overflow-x: auto; + overflow-y: hidden; + text-align: center; + width: fit-content; +} + +#input-find-options-checkboxes li, +#output-find-options-checkboxes li { + display: flex; + flex-direction: row; + float: left; + padding: 10px; + text-align: center; +} + + +#input-search-results, +#output-search-results { + list-style: none; + width: 75%; + min-width: 200px; + margin-left: auto; + margin-right: auto; +} + +#input-search-results li, +#output-search-results li { + padding-left: 5px; + padding-right: 5px; + padding-top: 10px; + padding-bottom: 10px; + text-align: center; + width: 100%; + color: var(--op-list-operation-font-colour); + background-color: var(--op-list-operation-bg-colour); + border-bottom: 2px solid var(--op-list-operation-border-colour); + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +#input-search-results li:first-of-type, +#output-search-results li:first-of-type { + border-top: 2px solid var(--op-list-operation-border-colour); +} + +#input-search-results li:hover, +#output-search-results li:hover { + cursor: pointer; + filter: brightness(98%); +} + +/* Highlighting */ +.ͼ2.cm-focused .cm-selectionBackground { + background-color: var(--hl5); +} + +.ͼ2 .cm-selectionBackground { + background-color: var(--hl1); +} + +.ͼ1 .cm-selectionMatch { + background-color: var(--hl2); +} + +.ͼ1.cm-focused .cm-cursor.cm-cursor-primary { + border-color: var(--primary-font-colour); +} + +.ͼ1 .cm-cursor.cm-cursor-primary { + display: block; + border-color: var(--subtext-font-colour); +} + + +/* Status bar */ + +.cm-panel input::placeholder { + font-size: 12px !important; +} + +.ͼ2 .cm-panels, +.ͼ2 .cm-side-panels { + background-color: var(--secondary-background-colour); + border-color: var(--primary-border-colour); + color: var(--primary-font-colour); +} + +.cm-status-bar { + font-family: var(--fixed-width-font-family); + font-weight: normal; + font-size: 8pt; + margin: 0 5px; + display: flex; + flex-flow: row nowrap; + justify-content: space-between; + align-items: center; +} + +.cm-status-bar i { + font-size: 12pt; + vertical-align: middle; + margin-left: 8px; +} +.cm-status-bar>div>span:first-child i { + margin-left: 0; +} + +.cm-status-bar .disabled { + background-color: unset !important; + cursor: not-allowed; +} + +/* Dropup Button */ +.cm-status-bar-select-btn { + border: none; + cursor: pointer; +} + +/* The container
        - needed to position the dropup content */ +.cm-status-bar-select { + position: relative; + display: inline-block; +} + +/* Dropup content (Hidden by Default) */ +.cm-status-bar-select-content { + display: none; + position: absolute; + bottom: 20px; + right: 0; + background-color: #f1f1f1; + min-width: 200px; + box-shadow: 0px 4px 4px 0px rgba(0,0,0,0.2); + z-index: 1; +} + +/* Links inside the dropup */ +.cm-status-bar-select-content a { + color: black; + padding: 2px 5px; + text-decoration: none; + display: block; +} + +/* Change color of dropup links on hover */ +.cm-status-bar-select-content a:hover { + background-color: #ddd +} + +/* Change the background color of the dropup button when the dropup content is shown */ +.cm-status-bar-select:hover .cm-status-bar-select-btn { + background-color: #f1f1f1; +} + +/* The search field */ +.cm-status-bar-filter-input { + box-sizing: border-box; + font-size: 12px; + padding-left: 10px !important; + border: none; +} + +.cm-status-bar-filter-search { + border-top: 1px solid #ddd; +} + +/* Show the dropup menu */ +.cm-status-bar-select .show { + display: block; +} + +.cm-status-bar-select-scroll { + overflow-y: auto; + max-height: 300px; +} + +.chr-enc-value { + max-width: 150px; + display: inline-block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + vertical-align: middle; +} + + +/* File details panel */ + +.cm-file-details { + text-align: center; + display: flex; + flex-direction: column; + align-items: center; + overflow-y: auto; + padding-bottom: 21px; + height: 100%; +} + +.file-details-toggle-shown, +.file-details-toggle-hidden { + width: 8px; + height: 40px; + border: 1px solid var(--secondary-border-colour); + position: absolute; + top: calc(50% - 20px); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + background-color: var(--secondary-border-colour); + color: var(--subtext-font-colour); + z-index: 1; +} + +.file-details-toggle-shown { + left: 0; + border-left: none; + border-top-right-radius: 5px; + border-bottom-right-radius: 5px; +} + +.file-details-toggle-hidden { + left: -8px; + border-right: none; + border-top-left-radius: 5px; + border-bottom-left-radius: 5px; +} + +.file-details-toggle-shown:hover, +.file-details-toggle-hidden:hover { + background-color: var(--primary-border-colour); + border-color: var(--primary-border-colour); + color: var(--primary-font-colour); +} + +.file-details-heading { + font-weight: bold; + margin: 10px 0 10px 0; +} + +.file-details-data { + text-align: left; + margin: 10px 2px; +} + +.file-details-data td { + padding: 0 3px; + max-width: 130px; + min-width: 60px; + overflow: hidden; + vertical-align: top; + word-break: break-all; +} + +.file-details-error { + color: #f00; +} + +.file-details-thumbnail { + max-width: 180px; +} diff --git a/plugins/srktoolbox/src/web/stylesheets/layout/_modals.css b/plugins/srktoolbox/src/web/stylesheets/layout/_modals.css new file mode 100644 index 00000000..7b3a5d59 --- /dev/null +++ b/plugins/srktoolbox/src/web/stylesheets/layout/_modals.css @@ -0,0 +1,116 @@ +/** + * Modal layout styles + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +.modal-content { + background-color: var(--primary-background-colour); +} + +.option-item { + margin-bottom: 20px; +} + +#edit-favourites-list { + margin: 10px; + border: 1px solid var(--op-list-operation-border-colour); +} + +#edit-favourites-list .operation { + border-left: none; + border-right: none; +} + +#edit-favourites-list .operation:last-child { + border-bottom: none; +} + +.about-img-left { + float: left; + margin: 10px 20px 20px 0; +} + +.about-img-right { + float: right; + margin: 10px 0 20px 20px; +} + +#save-link-group { + padding-top: 0; +} + +.save-link-options { + float: right; +} + +.save-link-options label { + margin-left: 10px; +} + +#save-footer { + border-top: none; + margin-top: 0; + border-bottom: 1px solid var(--primary-border-colour); +} + +#support-modal textarea { + font-family: var(--primary-font-family); +} + +#save-texts textarea, +#load-text { + font-family: var(--fixed-width-font-family); +} + +#save-texts textarea { + height: 200px; +} + +#faqs a.btn { + text-transform: unset; +} + +#faqs > div { + padding: 20px; + border-left: 2px solid var(--primary-border-colour); +} + +.checkbox label input[type=checkbox]+.checkbox-decorator .check, +.checkbox label input[type=checkbox]+.checkbox-decorator .check::before { + border-color: var(--input-border-colour); + color: var(--input-highlight-colour); +} + +.checkbox label input[type=checkbox]:checked+.checkbox-decorator .check, +.checkbox label input[type=checkbox]:checked+.checkbox-decorator .check::before { + border-color: var(--input-highlight-colour); + color: var(--input-highlight-colour); +} + +.bmd-form-group.is-focused .option-item label { + color: var(--input-highlight-colour); +} + +.bmd-form-group.is-focused [class^='bmd-label'], +.bmd-form-group.is-focused [class*=' bmd-label'], +.bmd-form-group.is-focused [class^='bmd-label'], +.bmd-form-group.is-focused [class*=' bmd-label'], +.bmd-form-group.is-focused label, +.checkbox label:hover, +.bmd-form-group.is-filled:focus-within .checkbox.option-item label { + color: var(--input-highlight-colour); +} + + +.bmd-form-group.option-item label+.form-control{ + background-image: + linear-gradient(to top, var(--input-highlight-colour) 2px, rgba(0, 0, 0, 0) 2px), + linear-gradient(to top, var(--primary-border-colour) 1px, rgba(0, 0, 0, 0) 1px); +} + +body #toast-container > div { + opacity: .98; +} diff --git a/plugins/srktoolbox/src/web/stylesheets/layout/_operations.css b/plugins/srktoolbox/src/web/stylesheets/layout/_operations.css new file mode 100644 index 00000000..41d28a2a --- /dev/null +++ b/plugins/srktoolbox/src/web/stylesheets/layout/_operations.css @@ -0,0 +1,72 @@ +/** + * Operation area styles + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +.op-list .operation { + color: var(--op-list-operation-font-colour); + background-color: var(--op-list-operation-bg-colour); + border-color: var(--op-list-operation-border-colour); +} + +#search { + padding-left: 10px; + padding-right: 10px; + background-image: + linear-gradient(to top, var(--input-highlight-colour) 2px, rgba(0, 0, 0, 0) 2px), + linear-gradient(to top, var(--primary-border-colour) 1px, rgba(0, 0, 0, 0) 1px); +} + +#edit-favourites { + float: right; + margin-top: -7px; +} + +.favourites-hover { + color: var(--rec-list-operation-font-colour); + background-color: var(--rec-list-operation-bg-colour); + border: 2px dashed var(--rec-list-operation-font-colour) !important; + padding: 8px 8px 9px 8px; +} + +#categories a { + color: var(--category-list-font-colour); + cursor: pointer; +} + +#categories a:hover, +.op-list .operation:hover { + filter: brightness(98%); +} + +.ztools-compact-layout #operations h5 { + font-size: 0.9rem; + margin-bottom: 6px; +} + +.ztools-compact-layout #search { + padding-left: 6px; + padding-right: 6px; +} + +.ztools-compact-layout #categories a { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.ztools-compact-layout #edit-favourites { + display: none; +} + +.ztools-compact-layout .op-list .operation { + padding: 5px 6px; + margin-bottom: 4px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} diff --git a/plugins/srktoolbox/src/web/stylesheets/layout/_recipe.css b/plugins/srktoolbox/src/web/stylesheets/layout/_recipe.css new file mode 100644 index 00000000..339da074 --- /dev/null +++ b/plugins/srktoolbox/src/web/stylesheets/layout/_recipe.css @@ -0,0 +1,17 @@ +/** + * Recipe area styles + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +#rec-list { + overflow: auto; +} + +#rec-list .operation { + color: var(--rec-list-operation-font-colour); + background-color: var(--rec-list-operation-bg-colour); + border-color: var(--rec-list-operation-border-colour); +} diff --git a/plugins/srktoolbox/src/web/stylesheets/layout/_structure.css b/plugins/srktoolbox/src/web/stylesheets/layout/_structure.css new file mode 100644 index 00000000..a3220e12 --- /dev/null +++ b/plugins/srktoolbox/src/web/stylesheets/layout/_structure.css @@ -0,0 +1,87 @@ +/** + * Overall page structure styles + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +body { + overflow: hidden; +} + +#content-wrapper { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + +#workspace-wrapper { + position: absolute; + top: 30px; + bottom: 0; + width: 100%; +} + +div#operations, +div#recipe { + width: 50%; + height: 100%; +} + +div#input, +div#output { + width: 100%; + height: 50%; +} + +.split { + box-sizing: border-box; + /* overflow: auto; */ + /* Removed to enable Background Magic button pulse to overflow. + Replace this rule if it seems to be causing problems. */ + position: relative; +} + +#operations.split { + overflow: auto; +} + +.split.split-horizontal, .gutter.gutter-horizontal { + height: 100%; + float: left; +} + +.ztools-compact-layout #workspace-wrapper { + overflow: hidden; +} + +.ztools-compact-layout #operations { + min-width: 0; +} + +.ztools-compact-layout #recipe { + min-width: 0; +} + +.ztools-compact-layout #IO { + min-width: 0; +} + +.gutter { + background-color: var(--secondary-border-colour); + background-repeat: no-repeat; + background-position: 50%; +} + +.gutter.gutter-horizontal { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAAeCAYAAAAGos/EAAAABGdBTUEAALGPC/xhBQAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAAd0SU1FB+EFBhEwBDmIiYYAAAAjSURBVBjTYzxz5sx/BgYGBiYGKGB89+4dA4oIy71790aGGgCn+DBbOcAB0wAAAABJRU5ErkJggg=='); + cursor: ew-resize; +} + +.gutter.gutter-vertical { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAACCAYAAABPJGxCAAAABGdBTUEAALGOfPtRkwAACkNpQ0NQSUNDIFByb2ZpbGUAAHjanVN3WJP3Fj7f92UPVkLY8LGXbIEAIiOsCMgQWaIQkgBhhBASQMWFiApWFBURnEhVxILVCkidiOKgKLhnQYqIWotVXDjuH9yntX167+3t+9f7vOec5/zOec8PgBESJpHmomoAOVKFPDrYH49PSMTJvYACFUjgBCAQ5svCZwXFAADwA3l4fnSwP/wBr28AAgBw1S4kEsfh/4O6UCZXACCRAOAiEucLAZBSAMguVMgUAMgYALBTs2QKAJQAAGx5fEIiAKoNAOz0ST4FANipk9wXANiiHKkIAI0BAJkoRyQCQLsAYFWBUiwCwMIAoKxAIi4EwK4BgFm2MkcCgL0FAHaOWJAPQGAAgJlCLMwAIDgCAEMeE80DIEwDoDDSv+CpX3CFuEgBAMDLlc2XS9IzFLiV0Bp38vDg4iHiwmyxQmEXKRBmCeQinJebIxNI5wNMzgwAABr50cH+OD+Q5+bk4eZm52zv9MWi/mvwbyI+IfHf/ryMAgQAEE7P79pf5eXWA3DHAbB1v2upWwDaVgBo3/ldM9sJoFoK0Hr5i3k4/EAenqFQyDwdHAoLC+0lYqG9MOOLPv8z4W/gi372/EAe/tt68ABxmkCZrcCjg/1xYW52rlKO58sEQjFu9+cj/seFf/2OKdHiNLFcLBWK8ViJuFAiTcd5uVKRRCHJleIS6X8y8R+W/QmTdw0ArIZPwE62B7XLbMB+7gECiw5Y0nYAQH7zLYwaC5EAEGc0Mnn3AACTv/mPQCsBAM2XpOMAALzoGFyolBdMxggAAESggSqwQQcMwRSswA6cwR28wBcCYQZEQAwkwDwQQgbkgBwKoRiWQRlUwDrYBLWwAxqgEZrhELTBMTgN5+ASXIHrcBcGYBiewhi8hgkEQcgIE2EhOogRYo7YIs4IF5mOBCJhSDSSgKQg6YgUUSLFyHKkAqlCapFdSCPyLXIUOY1cQPqQ28ggMor8irxHMZSBslED1AJ1QLmoHxqKxqBz0XQ0D12AlqJr0Rq0Hj2AtqKn0UvodXQAfYqOY4DRMQ5mjNlhXIyHRWCJWBomxxZj5Vg1Vo81Yx1YN3YVG8CeYe8IJAKLgBPsCF6EEMJsgpCQR1hMWEOoJewjtBK6CFcJg4Qxwicik6hPtCV6EvnEeGI6sZBYRqwm7iEeIZ4lXicOE1+TSCQOyZLkTgohJZAySQtJa0jbSC2kU6Q+0hBpnEwm65Btyd7kCLKArCCXkbeQD5BPkvvJw+S3FDrFiOJMCaIkUqSUEko1ZT/lBKWfMkKZoKpRzame1AiqiDqfWkltoHZQL1OHqRM0dZolzZsWQ8ukLaPV0JppZ2n3aC/pdLoJ3YMeRZfQl9Jr6Afp5+mD9HcMDYYNg8dIYigZaxl7GacYtxkvmUymBdOXmchUMNcyG5lnmA+Yb1VYKvYqfBWRyhKVOpVWlX6V56pUVXNVP9V5qgtUq1UPq15WfaZGVbNQ46kJ1Bar1akdVbupNq7OUndSj1DPUV+jvl/9gvpjDbKGhUaghkijVGO3xhmNIRbGMmXxWELWclYD6yxrmE1iW7L57Ex2Bfsbdi97TFNDc6pmrGaRZp3mcc0BDsax4PA52ZxKziHODc57LQMtPy2x1mqtZq1+rTfaetq+2mLtcu0W7eva73VwnUCdLJ31Om0693UJuja6UbqFutt1z+o+02PreekJ9cr1Dund0Uf1bfSj9Rfq79bv0R83MDQINpAZbDE4Y/DMkGPoa5hpuNHwhOGoEctoupHEaKPRSaMnuCbuh2fjNXgXPmasbxxirDTeZdxrPGFiaTLbpMSkxeS+Kc2Ua5pmutG003TMzMgs3KzYrMnsjjnVnGueYb7ZvNv8jYWlRZzFSos2i8eW2pZ8ywWWTZb3rJhWPlZ5VvVW16xJ1lzrLOtt1ldsUBtXmwybOpvLtqitm63Edptt3xTiFI8p0in1U27aMez87ArsmuwG7Tn2YfYl9m32zx3MHBId1jt0O3xydHXMdmxwvOuk4TTDqcSpw+lXZxtnoXOd8zUXpkuQyxKXdpcXU22niqdun3rLleUa7rrStdP1o5u7m9yt2W3U3cw9xX2r+00umxvJXcM970H08PdY4nHM452nm6fC85DnL152Xlle+70eT7OcJp7WMG3I28Rb4L3Le2A6Pj1l+s7pAz7GPgKfep+Hvqa+It89viN+1n6Zfgf8nvs7+sv9j/i/4XnyFvFOBWABwQHlAb2BGoGzA2sDHwSZBKUHNQWNBbsGLww+FUIMCQ1ZH3KTb8AX8hv5YzPcZyya0RXKCJ0VWhv6MMwmTB7WEY6GzwjfEH5vpvlM6cy2CIjgR2yIuB9pGZkX+X0UKSoyqi7qUbRTdHF09yzWrORZ+2e9jvGPqYy5O9tqtnJ2Z6xqbFJsY+ybuIC4qriBeIf4RfGXEnQTJAntieTE2MQ9ieNzAudsmjOc5JpUlnRjruXcorkX5unOy553PFk1WZB8OIWYEpeyP+WDIEJQLxhP5aduTR0T8oSbhU9FvqKNolGxt7hKPJLmnVaV9jjdO31D+miGT0Z1xjMJT1IreZEZkrkj801WRNberM/ZcdktOZSclJyjUg1plrQr1zC3KLdPZisrkw3keeZtyhuTh8r35CP5c/PbFWyFTNGjtFKuUA4WTC+oK3hbGFt4uEi9SFrUM99m/ur5IwuCFny9kLBQuLCz2Lh4WfHgIr9FuxYji1MXdy4xXVK6ZHhp8NJ9y2jLspb9UOJYUlXyannc8o5Sg9KlpUMrglc0lamUycturvRauWMVYZVkVe9ql9VbVn8qF5VfrHCsqK74sEa45uJXTl/VfPV5bdra3kq3yu3rSOuk626s91m/r0q9akHV0IbwDa0b8Y3lG19tSt50oXpq9Y7NtM3KzQM1YTXtW8y2rNvyoTaj9nqdf13LVv2tq7e+2Sba1r/dd3vzDoMdFTve75TsvLUreFdrvUV99W7S7oLdjxpiG7q/5n7duEd3T8Wej3ulewf2Re/ranRvbNyvv7+yCW1SNo0eSDpw5ZuAb9qb7Zp3tXBaKg7CQeXBJ9+mfHvjUOihzsPcw83fmX+39QjrSHkr0jq/dawto22gPaG97+iMo50dXh1Hvrf/fu8x42N1xzWPV56gnSg98fnkgpPjp2Snnp1OPz3Umdx590z8mWtdUV29Z0PPnj8XdO5Mt1/3yfPe549d8Lxw9CL3Ytslt0utPa49R35w/eFIr1tv62X3y+1XPK509E3rO9Hv03/6asDVc9f41y5dn3m978bsG7duJt0cuCW69fh29u0XdwruTNxdeo94r/y+2v3qB/oP6n+0/rFlwG3g+GDAYM/DWQ/vDgmHnv6U/9OH4dJHzEfVI0YjjY+dHx8bDRq98mTOk+GnsqcTz8p+Vv9563Or59/94vtLz1j82PAL+YvPv655qfNy76uprzrHI8cfvM55PfGm/K3O233vuO+638e9H5ko/ED+UPPR+mPHp9BP9z7nfP78L/eE8/vcxDeEAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAB3RJTUUH4QUGETI0LWfbqAAAACNJREFUCNdjPHPmzH8GBgYGJSUlRgYGBoZ79+7Rhc/EMEAAAHd6H2e3/71BAAAAAElFTkSuQmCC'); + cursor: ns-resize; +} diff --git a/plugins/srktoolbox/src/web/stylesheets/operations/diff.css b/plugins/srktoolbox/src/web/stylesheets/operations/diff.css new file mode 100644 index 00000000..008cbbf5 --- /dev/null +++ b/plugins/srktoolbox/src/web/stylesheets/operations/diff.css @@ -0,0 +1,8 @@ +del { + background-color: var(--hl3); +} + +ins { + text-decoration: underline; /* shouldn't be needed, but Chromium doesn't copy to clipboard without it */ + background-color: var(--hl5); +} diff --git a/plugins/srktoolbox/src/web/stylesheets/operations/json.css b/plugins/srktoolbox/src/web/stylesheets/operations/json.css new file mode 100644 index 00000000..27861c8f --- /dev/null +++ b/plugins/srktoolbox/src/web/stylesheets/operations/json.css @@ -0,0 +1,79 @@ +/** + * JSON styles + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + * + * Adapted for CyberChef by @n1474335 from jQuery json-viewer + * @author Alexandre Bodelot + * @link https://github.com/abodelot/jquery.json-viewer + * @license MIT + */ + +/* Root element */ +.json-document { + padding: .5em 1.5em; +} + +/* Syntax highlighting for JSON objects */ +ul.json-dict, ol.json-array { + list-style-type: none; + margin: 0 0 0 1px; + border-left: 1px dotted #ccc; + padding-left: 2em; +} +.json-string { + color: green; +} +.json-literal { + color: red; +} +.json-brace, +.json-bracket, +.json-colon, +.json-comma { + color: gray; +} + +/* Collapse */ +.json-details { + display: inline; +} +.json-details[open] { + display: contents; +} +.json-summary { + display: inline; + list-style: none; +} + +/* Display object and array brackets when closed */ +.json-summary.json-obj::after { + color: gray; + content: "{ ... }" +} +.json-summary.json-arr::after { + color: gray; + content: "[ ... ]" +} +.json-details[open] > .json-summary.json-obj::after, +.json-details[open] > .json-summary.json-arr::after { + content: ""; +} + +/* Show arrows, even in inline mode */ +.json-summary::before { + content: "\25BC"; + color: #c0c0c0; + margin-left: -12px; + margin-right: 5px; + display: inline-block; + transform: rotate(-90deg); +} +.json-summary:hover::before { + color: #aaa; +} +.json-details[open] > .json-summary::before { + transform: rotate(0deg); +} diff --git a/plugins/srktoolbox/src/web/stylesheets/preloader.css b/plugins/srktoolbox/src/web/stylesheets/preloader.css new file mode 100644 index 00000000..01e6d3d2 --- /dev/null +++ b/plugins/srktoolbox/src/web/stylesheets/preloader.css @@ -0,0 +1,162 @@ +/** + * Preloader styles + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + + #loader-wrapper { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 1000; + background-color: var(--loader-background-colour); +} + +.loader { + display: block; + position: relative; + left: 50%; + top: 50%; + width: 150px; + height: 150px; + margin: -75px 0 0 -75px; + + border: 3px solid transparent; + border-top-color: var(--loader-outer-colour); + border-radius: 50%; + + animation: spin 2s linear infinite; +} + +.loader:before, +.loader:after { + content: ""; + position: absolute; + border: 3px solid transparent; + border-radius: 50%; +} + +.loader:before { + top: 5px; + left: 5px; + right: 5px; + bottom: 5px; + border-top-color: var(--loader-middle-colour); + animation: spin 3s linear infinite; +} + +.loader:after { + top: 13px; + left: 13px; + right: 13px; + bottom: 13px; + border-top-color: var(--loader-inner-colour); + animation: spin 1.5s linear infinite; +} + +.loading-msg { + display: block; + position: relative; + width: 400px; + left: calc(50% - 200px); + top: calc(50% + 50px); + text-align: center; + opacity: 0; + font-size: 18px; +} + +.loading-msg.loading { + opacity: 1; + transition: all 0.1s ease-in; +} + +.loading-error { + display: block; + position: relative; + width: 600px; + left: calc(50% - 300px); + top: 10%; +} + + +/* Loaded */ +.loaded .loading-msg { + opacity: 0; + transition: all 0.3s ease-out; +} + +.loaded #loader-wrapper { + opacity: 0; + transition: all 0.5s 0.3s ease-out; +} + +.loaded #rec-list li { + animation: bump 0.7s cubic-bezier(0.7, 0, 0.3, 1) both; +} + +.loaded #content-wrapper { + animation-delay: 0.10s; +} + +.loaded #rec-list li:first-child { + animation-delay: 0.20s; +} + +.loaded #rec-list li:nth-child(2) { + animation-delay: 0.25s; +} + +.loaded #rec-list li:nth-child(3) { + animation-delay: 0.30s; +} + +.loaded #rec-list li:nth-child(4) { + animation-delay: 0.35s; +} + +.loaded #rec-list li:nth-child(5) { + animation-delay: 0.40s; +} + +.loaded #rec-list li:nth-child(6) { + animation-delay: 0.45s; +} + +.loaded #rec-list li:nth-child(7) { + animation-delay: 0.50s; +} + +.loaded #rec-list li:nth-child(8) { + animation-delay: 0.55s; +} + +.loaded #rec-list li:nth-child(9) { + animation-delay: 0.60s; +} + +.loaded #rec-list li:nth-child(10) { + animation-delay: 0.65s; +} + + +/* Animations */ + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +@keyframes bump { + from { + opacity: 0; + transform: translate3d(0, 200px, 0); + } +} diff --git a/plugins/srktoolbox/src/web/stylesheets/themes/_classic.css b/plugins/srktoolbox/src/web/stylesheets/themes/_classic.css new file mode 100644 index 00000000..2e7e099f --- /dev/null +++ b/plugins/srktoolbox/src/web/stylesheets/themes/_classic.css @@ -0,0 +1,138 @@ +/** + * Classic theme definitions + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + * + * Modified by Raka-loah@github. + */ + + :root, + :root.classic { + --primary-font-family: -apple-system, BlinkMacSystemFont, "Microsoft Yahei", "Segoe UI", + Roboto, "Helvetica Neue", Arial, sans-serif; + --primary-font-colour: #333; + --primary-font-size: 14px; + --primary-line-height: 20px; + + --fixed-width-font-family: SFMono-Regular, Menlo, Monaco, Consolas, + "Liberation Mono", "Courier New", monospace; + --fixed-width-font-colour: inherit; + --fixed-width-font-size: inherit; + + --subtext-font-colour: #999; + --subtext-font-size: 13px; + + --primary-background-colour: #fff; + --secondary-background-colour: #fafafa; + + --primary-border-colour: #ddd; + --secondary-border-colour: #eee; + + --title-colour: #424242; + --title-weight: bold; + --title-size: 16px; + --title-background-colour: #fafafa; + + --banner-font-colour: #467e88; + --banner-bg-colour: #d8eef0; + --banner-url-colour: #1976d2; + + --category-list-font-colour: #1976d2; + + --loader-background-colour: var(--secondary-border-colour); + --loader-outer-colour: #3498db; + --loader-middle-colour: #e74c3c; + --loader-inner-colour: #f9c922; + + + /* Operation colours */ + --op-list-operation-font-colour: #3a87ad; + --op-list-operation-bg-colour: #d9edf7; + --op-list-operation-border-colour: #bce8f1; + + --rec-list-operation-font-colour: #467e88; + --rec-list-operation-bg-colour: #d8eef0; + --rec-list-operation-border-colour: #c0e8dc; + + --selected-operation-font-color: #c09853; + --selected-operation-bg-colour: #fcf8e3; + --selected-operation-border-colour: #fbeed5; + + --breakpoint-font-colour: #b94a48; + --breakpoint-bg-colour: #f2dede; + --breakpoint-border-colour: #eed3d7; + + --disabled-font-colour: #999; + --disabled-bg-colour: #dfdfdf; + --disabled-border-colour: #cdcdcd; + + --fc-operation-font-colour: #396f3a; + --fc-operation-bg-colour: #c7e4ba; + --fc-operation-border-colour: #b3dba2; + + --fc-breakpoint-operation-font-colour: #94312f; + --fc-breakpoint-operation-bg-colour: #eabfbf; + --fc-breakpoint-operation-border-colour: #e2aeb5; + + + /* Operation arguments */ + --op-title-font-weight: bold; + --arg-font-colour: #424242; + --arg-background: #fff; + --arg-border-colour: #ddd; + --arg-disabled-background: #eee; + --arg-label-colour: #467e88; + + + /* Operation buttons */ + --disable-icon-colour: #9e9e9e; + --disable-icon-selected-colour: #f44336; + --breakpoint-icon-colour: #9e9e9e; + --breakpoint-icon-selected-colour: #f44336; + + + /* Buttons */ + --btn-default-font-colour: #333; + --btn-default-bg-colour: #fff; + --btn-default-border-colour: #ddd; + + --btn-default-hover-font-colour: #333; + --btn-default-hover-bg-colour: #ebebeb; + --btn-default-hover-border-colour: #adadad; + + --btn-success-font-colour: #fff; + --btn-success-bg-colour: #5cadb8; + --btn-success-border-colour: #4ca2ae; + + --btn-success-hover-font-colour: #fff; + --btn-success-hover-bg-colour: #449d96; + --btn-success-hover-border-colour: #398284; + + + /* Highlighter colours */ + --hl1: #ffee00aa; + --hl2: #95dfffaa; + --hl3: #ffb6b6aa; + --hl4: #fcf8e3aa; + --hl5: #8de768aa; + + + /* Scrollbar */ + --scrollbar-track: var(--secondary-background-colour); + --scrollbar-thumb: #ccc; + --scrollbar-hover: #bbb; + + + /* Misc. */ + --drop-file-border-colour: #3a87ad; + --table-border-colour: #ccc; + --popover-background: #fff; + --popover-border-colour: #ccc; + --code-background: #f9f2f4; + --code-font-colour: #c7254e; + --input-highlight-colour: #1976d2; + --input-border-colour: #424242; + } + \ No newline at end of file diff --git a/plugins/srktoolbox/src/web/stylesheets/themes/_dark.css b/plugins/srktoolbox/src/web/stylesheets/themes/_dark.css new file mode 100644 index 00000000..4d2941f2 --- /dev/null +++ b/plugins/srktoolbox/src/web/stylesheets/themes/_dark.css @@ -0,0 +1,131 @@ +/** + * Dark theme definitions + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +:root.dark { + --primary-font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + --primary-font-colour: #c5c5c5; + --primary-font-size: 14px; + --primary-line-height: 20px; + + --fixed-width-font-family: "Monaco", "Droid Sans Mono", "Consolas", monospace; + --fixed-width-font-colour: inherit; + --fixed-width-font-size: inherit; + + --subtext-font-colour: #999; + --subtext-font-size: 13px; + + --primary-background-colour: #1e1e1e; + --secondary-background-colour: #252525; + + --primary-border-colour: #444; + --secondary-border-colour: #3c3c3c; + + --title-colour: #fff; + --title-weight: bold; + --title-background-colour: #333; + + --banner-font-colour: #c5c5c5; + --banner-bg-colour: #252525; + --banner-url-colour: #1976d2; + + --category-list-font-colour: #1976d2; + + --loader-background-colour: var(--secondary-border-colour); + --loader-outer-colour: #3498db; + --loader-middle-colour: #e74c3c; + --loader-inner-colour: #f9c922; + + + /* Operation colours */ + --op-list-operation-font-colour: #c5c5c5; + --op-list-operation-bg-colour: #333; + --op-list-operation-border-colour: #444; + + --rec-list-operation-font-colour: #c5c5c5; + --rec-list-operation-bg-colour: #252525; + --rec-list-operation-border-colour: #444; + + --selected-operation-font-color: #c5c5c5; + --selected-operation-bg-colour: #3f3f3f; + --selected-operation-border-colour: #444; + + --breakpoint-font-colour: #ddd; + --breakpoint-bg-colour: #073655; + --breakpoint-border-colour: #444; + + --disabled-font-colour: #666; + --disabled-bg-colour: #444; + --disabled-border-colour: #444; + + --fc-operation-font-colour: #c5c5c5; + --fc-operation-bg-colour: #2d2d2d; + --fc-operation-border-colour: #444; + + --fc-breakpoint-operation-font-colour: #ddd; + --fc-breakpoint-operation-bg-colour: #072b49; + --fc-breakpoint-operation-border-colour: #444; + + + /* Operation arguments */ + --op-title-font-weight: bold; + --arg-font-colour: #bbb; + --arg-background: #3c3c3c; + --arg-border-colour: #3c3c3c; + --arg-disabled-background: #4f4f4f; + --arg-label-colour: rgb(25, 118, 210); + + + /* Operation buttons */ + --disable-icon-colour: #9e9e9e; + --disable-icon-selected-colour: #f44336; + --breakpoint-icon-colour: #9e9e9e; + --breakpoint-icon-selected-colour: #f44336; + + + /* Buttons */ + --btn-default-font-colour: #c5c5c5; + --btn-default-bg-colour: #2d2d2d; + --btn-default-border-colour: #3c3c3c; + + --btn-default-hover-font-colour: #c5c5c5; + --btn-default-hover-bg-colour: #2d2d2d; + --btn-default-hover-border-colour: #205375; + + --btn-success-font-colour: #fff; + --btn-success-bg-colour: #073655; + --btn-success-border-colour: #0e639c; + + --btn-success-hover-font-colour: #fff; + --btn-success-hover-bg-colour: #0e639c; + --btn-success-hover-border-colour: #0e639c; + + + /* Highlighter colours */ + --hl1: #264f78; + --hl2: #675351; + --hl3: #c40000; + --hl4: #fcf8e3; + --hl5: #38811b; + + + /* Scrollbar */ + --scrollbar-track: #1e1e1e; + --scrollbar-thumb: #424242; + --scrollbar-hover: #4e4e4e; + + + /* Misc. */ + --drop-file-border-colour: #0e639c; + --table-border-colour: #555; + --popover-background: #444; + --popover-border-colour: #555; + --code-background: #0e639c; + --code-font-colour: #fff; + --input-highlight-colour: #1976d2; + --input-border-colour: #424242; +} diff --git a/plugins/srktoolbox/src/web/stylesheets/themes/_geocities.css b/plugins/srktoolbox/src/web/stylesheets/themes/_geocities.css new file mode 100644 index 00000000..fcf3cdb5 --- /dev/null +++ b/plugins/srktoolbox/src/web/stylesheets/themes/_geocities.css @@ -0,0 +1,131 @@ +/** + * GeoCities theme definitions + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +:root.geocities { + --primary-font-family: "Comic Sans", "Comic Sans MS", "Chalkboard", "ChalkboardSE-Regular", "Marker Felt", "Purisa", "URW Chancery L", cursive, sans-serif; + --primary-font-colour: black; + --primary-font-size: 14px; + --primary-line-height: 20px; + + --fixed-width-font-family: "Courier New", Courier, monospace; + --fixed-width-font-colour: yellow; + --fixed-width-font-size: inherit; + + --subtext-font-colour: darkgrey; + --subtext-font-size: 13px; + + --primary-background-colour: #00f; + --secondary-background-colour: #f00; + + --primary-border-colour: pink; + --secondary-border-colour: springgreen; + + --title-colour: red; + --title-weight: bold; + --title-background-colour: yellow; + + --banner-font-colour: white; + --banner-bg-colour: maroon; + --banner-url-colour: yellow; + + --category-list-font-colour: yellow; + + --loader-background-colour: #00f; + --loader-outer-colour: #0f0; + --loader-middle-colour: red; + --loader-inner-colour: yellow; + + + /* Operation colours */ + --op-list-operation-font-colour: blue; + --op-list-operation-bg-colour: yellow; + --op-list-operation-border-colour: green; + + --rec-list-operation-font-colour: white; + --rec-list-operation-bg-colour: purple; + --rec-list-operation-border-colour: green; + + --selected-operation-font-color: white; + --selected-operation-bg-colour: pink; + --selected-operation-border-colour: blue; + + --breakpoint-font-colour: white; + --breakpoint-bg-colour: red; + --breakpoint-border-colour: blue; + + --disabled-font-colour: grey; + --disabled-bg-colour: black; + --disabled-border-colour: grey; + + --fc-operation-font-colour: sienna; + --fc-operation-bg-colour: pink; + --fc-operation-border-colour: yellow; + + --fc-breakpoint-operation-font-colour: darkgrey; + --fc-breakpoint-operation-bg-colour: deeppink; + --fc-breakpoint-operation-border-colour: yellowgreen; + + + /* Operation arguments */ + --op-title-font-weight: bold; + --arg-font-colour: white; + --arg-background: black; + --arg-border-colour: lime; + --arg-disabled-background: grey; + --arg-label-colour: red; + + + /* Operation buttons */ + --disable-icon-colour: #0f0; + --disable-icon-selected-colour: yellow; + --breakpoint-icon-colour: #0f0; + --breakpoint-icon-selected-colour: yellow; + + + /* Buttons */ + --btn-default-font-colour: black; + --btn-default-bg-colour: white; + --btn-default-border-colour: grey; + + --btn-default-hover-font-colour: black; + --btn-default-hover-bg-colour: white; + --btn-default-hover-border-colour: grey; + + --btn-success-font-colour: white; + --btn-success-bg-colour: lawngreen; + --btn-success-border-colour: grey; + + --btn-success-hover-font-colour: white; + --btn-success-hover-bg-colour: lime; + --btn-success-hover-border-colour: grey; + + + /* Highlighter colours */ + --hl1: #fff000; + --hl2: #95dfff; + --hl3: #ffb6b6; + --hl4: #fcf8e3; + --hl5: #8de768; + + + /* Scrollbar */ + --scrollbar-track: lightsteelblue; + --scrollbar-thumb: lightslategrey; + --scrollbar-hover: grey; + + + /* Misc. */ + --drop-file-border-colour: purple; + --table-border-colour: var(--hl3); + --popover-background: turquoise; + --popover-border-colour: violet; + --code-background: black; + --code-font-colour: limegreen; + --input-highlight-colour: limegreen; + --input-border-colour: limegreen; +} diff --git a/plugins/srktoolbox/src/web/stylesheets/themes/_solarizedDark.css b/plugins/srktoolbox/src/web/stylesheets/themes/_solarizedDark.css new file mode 100644 index 00000000..5bb18d2e --- /dev/null +++ b/plugins/srktoolbox/src/web/stylesheets/themes/_solarizedDark.css @@ -0,0 +1,148 @@ +/** + * Solarized dark theme definitions + * + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +:root.solarizedDark { + --base03: #002b36; + --base02: #073642; + --base01: #586e75; + --base00: #657b83; + --base0: #839496; + --base1: #93a1a1; + --base2: #eee8d5; + --base3: #fdf6e3; + --sol-yellow: #b58900; + --sol-orange: #cb4b16; + --sol-red: #dc322f; + --sol-magenta: #d33682; + --sol-violet: #6c71c4; + --sol-blue: #268bd2; + --sol-cyan: #2aa198; + --sol-green: #859900; + + --primary-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", + Roboto, "Helvetica Neue", Arial, sans-serif; + --primary-font-colour: var(--base0); + --primary-font-size: 14px; + --primary-line-height: 20px; + + --fixed-width-font-family: SFMono-Regular, Menlo, Monaco, Consolas, + "Liberation Mono", "Courier New", monospace; + --fixed-width-font-colour: inherit; + --fixed-width-font-size: inherit; + + --subtext-font-colour: var(--base01); + --subtext-font-size: 13px; + + --primary-background-colour: var(--base03); + --secondary-background-colour: var(--base02); + + --primary-border-colour: var(--base00); + --secondary-border-colour: var(--base01); + + --title-colour: var(--base1); + --title-weight: bold; + --title-background-colour: var(--base02); + + --banner-font-colour: var(--base0); + --banner-bg-colour: var(--base03); + --banner-url-colour: var(--base1); + + --category-list-font-colour: var(--base1); + + --loader-background-colour: var(--base03); + --loader-outer-colour: var(--base1); + --loader-middle-colour: var(--base0); + --loader-inner-colour: var(--base00); + + + /* Operation colours */ + --op-list-operation-font-colour: var(--base0); + --op-list-operation-bg-colour: var(--base03); + --op-list-operation-border-colour: var(--base02); + + --rec-list-operation-font-colour: var(--base0); + --rec-list-operation-bg-colour: var(--base02); + --rec-list-operation-border-colour: var(--base01); + + --selected-operation-font-color: var(--base1); + --selected-operation-bg-colour: var(--base02); + --selected-operation-border-colour: var(--base01); + + --breakpoint-font-colour: var(--sol-red); + --breakpoint-bg-colour: var(--base02); + --breakpoint-border-colour: var(--base00); + + --disabled-font-colour: var(--base01); + --disabled-bg-colour: var(--base03); + --disabled-border-colour: var(--base02); + + --fc-operation-font-colour: var(--base1); + --fc-operation-bg-colour: var(--base02); + --fc-operation-border-colour: var(--base01); + + --fc-breakpoint-operation-font-colour: var(--sol-orange); + --fc-breakpoint-operation-bg-colour: var(--base02); + --fc-breakpoint-operation-border-colour: var(--base00); + + + /* Operation arguments */ + --op-title-font-weight: bold; + --arg-font-colour: var(--base0); + --arg-background: var(--base03); + --arg-border-colour: var(--base00); + --arg-disabled-background: var(--base03); + --arg-label-colour: var(--base1); + + + /* Operation buttons */ + --disable-icon-colour: var(--base00); + --disable-icon-selected-colour: var(--sol-red); + --breakpoint-icon-colour: var(--base00); + --breakpoint-icon-selected-colour: var(--sol-red); + + /* Buttons */ + --btn-default-font-colour: var(--base0); + --btn-default-bg-colour: var(--base02); + --btn-default-border-colour: var(--base01); + + --btn-default-hover-font-colour: var(--base1); + --btn-default-hover-bg-colour: var(--base01); + --btn-default-hover-border-colour: var(--base00); + + --btn-success-font-colour: var(--base0); + --btn-success-bg-colour: var(--base03); + --btn-success-border-colour: var(--base00); + + --btn-success-hover-font-colour: var(--base1); + --btn-success-hover-bg-colour: var(--base01); + --btn-success-hover-border-colour: var(--base00); + + /* Highlighter colours */ + --hl1: var(--base01); + --hl2: var(--sol-blue); + --hl3: var(--sol-green); + --hl4: var(--sol-yellow); + --hl5: var(--sol-magenta); + + + /* Scrollbar */ + --scrollbar-track: var(--base03); + --scrollbar-thumb: var(--base00); + --scrollbar-hover: var(--base01); + + + /* Misc. */ + --drop-file-border-colour: var(--base01); + --table-border-colour: var(--base01); + --popover-background: var(--base02); + --popover-border-colour: var(--base01); + --code-background: var(--base03); + --code-font-colour: var(--base1); + --input-highlight-colour: var(--base1); + --input-border-colour: var(--base0); +} diff --git a/plugins/srktoolbox/src/web/stylesheets/themes/_solarizedLight.css b/plugins/srktoolbox/src/web/stylesheets/themes/_solarizedLight.css new file mode 100644 index 00000000..f884c3e8 --- /dev/null +++ b/plugins/srktoolbox/src/web/stylesheets/themes/_solarizedLight.css @@ -0,0 +1,150 @@ +/** + * Solarized light theme definitions + * + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +:root.solarizedLight { + --base03: #002b36; + --base02: #073642; + --base01: #586e75; + --base00: #657b83; + --base0: #839496; + --base1: #93a1a1; + --base2: #eee8d5; + --base3: #fdf6e3; + --sol-yellow: #b58900; + --sol-orange: #cb4b16; + --sol-red: #dc322f; + --sol-magenta: #d33682; + --sol-violet: #6c71c4; + --sol-blue: #268bd2; + --sol-cyan: #2aa198; + --sol-green: #859900; + + --primary-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", + Roboto, "Helvetica Neue", Arial, sans-serif; + --primary-font-colour: var(--base00); + --primary-font-size: 14px; + --primary-line-height: 20px; + + --fixed-width-font-family: SFMono-Regular, Menlo, Monaco, Consolas, + "Liberation Mono", "Courier New", monospace; + --fixed-width-font-colour: inherit; + --fixed-width-font-size: inherit; + + --subtext-font-colour: var(--base1); + --subtext-font-size: 13px; + + --primary-background-colour: var(--base3); + --secondary-background-colour: var(--base2); + + --primary-border-colour: var(--base0); + --secondary-border-colour: var(--base1); + + --title-colour: var(--base01); + --title-weight: bold; + --title-background-colour: var(--base2); + + --banner-font-colour: var(--base00); + --banner-bg-colour: var(--base3); + --banner-url-colour: var(--base01); + + --category-list-font-colour: var(--base01); + + --loader-background-colour: var(--base3); + --loader-outer-colour: var(--base01); + --loader-middle-colour: var(--base00); + --loader-inner-colour: var(--base0); + + + /* Operation colours */ + --op-list-operation-font-colour: var(--base00); + --op-list-operation-bg-colour: var(--base3); + --op-list-operation-border-colour: var(--base2); + + --rec-list-operation-font-colour: var(--base00); + --rec-list-operation-bg-colour: var(--base2); + --rec-list-operation-border-colour: var(--base1); + + --selected-operation-font-color: var(--base01); + --selected-operation-bg-colour: var(--base2); + --selected-operation-border-colour: var(--base1); + + --breakpoint-font-colour: var(--sol-red); + --breakpoint-bg-colour: var(--base2); + --breakpoint-border-colour: var(--base0); + + --disabled-font-colour: var(--base1); + --disabled-bg-colour: var(--base3); + --disabled-border-colour: var(--base2); + + --fc-operation-font-colour: var(--base01); + --fc-operation-bg-colour: var(--base2); + --fc-operation-border-colour: var(--base1); + + --fc-breakpoint-operation-font-colour: var(--base02); + --fc-breakpoint-operation-bg-colour: var(--base1); + --fc-breakpoint-operation-border-colour: var(--base0); + + + /* Operation arguments */ + --op-title-font-weight: bold; + --arg-font-colour: var(--base00); + --arg-background: var(--base3); + --arg-border-colour: var(--base0); + --arg-disabled-background: var(--base3); + --arg-label-colour: var(--base01); + + + /* Operation buttons */ + --disable-icon-colour: #9e9e9e; + --disable-icon-selected-colour: #f44336; + --breakpoint-icon-colour: #9e9e9e; + --breakpoint-icon-selected-colour: #f44336; + + + /* Buttons */ + --btn-default-font-colour: var(--base00); + --btn-default-bg-colour: var(--base2); + --btn-default-border-colour: var(--base1); + + --btn-default-hover-font-colour: var(--base01); + --btn-default-hover-bg-colour: var(--base1); + --btn-default-hover-border-colour: var(--base0); + + --btn-success-font-colour: var(--base00); + --btn-success-bg-colour: var(--base3); + --btn-success-border-colour: var(--base0); + + --btn-success-hover-font-colour: var(--base01); + --btn-success-hover-bg-colour: var(--base1); + --btn-success-hover-border-colour: var(--base0); + + + /* Highlighter colours */ + --hl1: var(--base1); + --hl2: var(--sol-blue); + --hl3: var(--sol-green); + --hl4: var(--sol-yellow); + --hl5: var(--sol-magenta); + + + /* Scrollbar */ + --scrollbar-track: var(--base3); + --scrollbar-thumb: var(--base1); + --scrollbar-hover: var(--base0); + + + /* Misc. */ + --drop-file-border-colour: var(--base1); + --table-border-colour: var(--base1); + --popover-background: var(--base2); + --popover-border-colour: var(--base1); + --code-background: var(--base3); + --code-font-colour: var(--base01); + --input-highlight-colour: var(--base01); + --input-border-colour: var(--base00); +} diff --git a/plugins/srktoolbox/src/web/stylesheets/utils/_general.css b/plugins/srktoolbox/src/web/stylesheets/utils/_general.css new file mode 100644 index 00000000..fd4a7d34 --- /dev/null +++ b/plugins/srktoolbox/src/web/stylesheets/utils/_general.css @@ -0,0 +1,92 @@ +/** + * General styles + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +body { + font-family: var(--primary-font-family); + font-size: var(--primary-font-size); + line-height: var(--primary-line-height); + color: var(--primary-font-colour); + background-color: var(--primary-background-colour); +} + +.subtext { + font-style: italic; + font-size: var(--subtext-font-size); + color: var(--subtext-font-colour); +} + +.data-text { + font-family: var(--fixed-width-font-family); +} + +.word-wrap { + white-space: pre !important; + word-wrap: normal !important; + overflow-x: scroll !important; +} + +.clearfix { + clear: both; + height: 0; + line-height: 0; +} + +.hidden { + display: none; +} + +.blur { + color: transparent !important; + text-shadow: rgba(0, 0, 0, 0.95) 0 0 10px !important; +} + +.no-select { + user-select: none; +} + +.inline-icon { + font-size: 12px; + padding-left: 2px; +} + +.modal-icon { + position: absolute; + right: 25px; +} + +.konami { + transform: rotate(180deg); +} + +::-webkit-scrollbar { + width: 10px; + height: 10px; +} + +::-webkit-scrollbar-track { + background-color: var(--scrollbar-track); +} + +::-webkit-scrollbar-thumb { + background-color: var(--scrollbar-thumb); +} + +::-webkit-scrollbar-thumb:hover { + background-color: var(--scrollbar-hover); +} + +::-webkit-scrollbar-corner { + background-color: var(--scrollbar-track); +} + +/* Highlighters */ +.hl1 { background-color: var(--hl1); } +.hl2 { background-color: var(--hl2); } +.hl3 { background-color: var(--hl3); } /* Half-Life 3 confirmed :O */ +.hl4 { background-color: var(--hl4); } +.hl5 { background-color: var(--hl5); } diff --git a/plugins/srktoolbox/src/web/stylesheets/utils/_overrides.css b/plugins/srktoolbox/src/web/stylesheets/utils/_overrides.css new file mode 100644 index 00000000..a2f8b029 --- /dev/null +++ b/plugins/srktoolbox/src/web/stylesheets/utils/_overrides.css @@ -0,0 +1,257 @@ +/** + * Overrides for vendor styles + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +/* Bootstrap */ + +/* fallback */ +@font-face { + font-family: 'Material Icons'; + font-style: normal; + font-weight: 400; + src: url("../static/fonts/MaterialIcons-Regular.ttf") format('truetype'); +} + +.material-icons { + font-family: 'Material Icons'; + font-weight: normal; + font-style: normal; + font-size: 24px; + line-height: 1; + letter-spacing: normal; + text-transform: none; + display: inline-block; + white-space: nowrap; + word-wrap: normal; + direction: ltr; + font-feature-settings: 'liga'; + -webkit-font-smoothing: antialiased; +} + +.form-group { + margin-bottom: 0; +} + + +button, +a:focus { + outline: none; +} + +.btn.btn-raised.btn-secondary { + color: var(--btn-default-font-colour); + background-color: var(--btn-default-bg-colour); + border-color: var(--btn-default-border-colour); +} + +.btn.btn-raised.btn-secondary:hover, +.btn.btn-raised.btn-secondary:active, +.btn.btn-raised.btn-secondary:hover:active { + color: var(--btn-default-hover-font-colour); + background-color: var(--btn-default-hover-bg-colour); + border-color: var(--btn-default-hover-border-colour); +} + +.btn.btn-raised.btn-secondary:focus { + color: var(--btn-default-font-colour); + background-color: var(--btn-default-bg-colour); + border-color: var(--btn-default-hover-border-colour); +} + +.btn.btn-raised.btn-secondary[disabled]:hover { + background-color: var(--primary-background-colour); + border-color: var(--primary-border-colour); +} + +.btn.btn-raised.btn-success { + color: var(--btn-success-font-colour); + background-color: var(--btn-success-bg-colour); + border-color: var(--btn-success-border-colour); +} + +.btn.btn-raised.btn-success:hover, +.btn.btn-raised.btn-success:active, +.btn.btn-raised.btn-success:focus, +.btn.btn-raised.btn-success:hover:active { + color: var(--btn-success-hover-font-colour); + background-color: var(--btn-success-hover-bg-colour); + border-color: var(--btn-success-hover-border-colour); +} + +select.form-control, +select.form-control:focus { + background-color: var(--primary-background-colour) !important; +} + +select.form-control:focus { + transition: none !important; +} + +select.form-control:not([size]):not([multiple]), +select.custom-file-control:not([size]):not([multiple]) { + height: unset !important; +} + +.checkbox label, +.checkbox-inline, +.is-focused .checkbox-inline, +.is-focused .checkbox-inline:hover, +[class^="bmd-label"], +.form-control, +.is-focused .form-control { + color: var(--primary-font-colour); +} + +.form-control, +.is-focused .form-control { + background-image: + linear-gradient(to top, var(--input-highlight-colour) 2px, rgba(0, 0, 0, 0) 2px), + linear-gradient(to top, var(--primary-border-colour) 1px, rgba(0, 0, 0, 0) 1px); +} + +code { + border: 0; + white-space: pre-wrap; + font-family: var(--fixed-width-font-family); + background-color: var(--code-background); + color: var(--code-font-colour); +} + +pre { + border-radius: 0 !important; + background-color: var(--secondary-background-colour); + border-color: var(--secondary-border-colour); + color: var(--fixed-width-font-colour); +} + +blockquote { + font-size: inherit; + border-left-color: var(--secondary-border-colour); +} + +blockquote a { + cursor: pointer; +} + +optgroup { + font-weight: bold; +} + +.panel-body:before, +.panel-body:after { + content: ""; +} + +.table-nonfluid { + width: auto !important; +} + +.table, +.table-hover tbody tr:hover { + color: var(--primary-font-colour); +} + +.table-bordered th, +.table-bordered td { + border: 1px solid var(--table-border-colour); +} + +.popover { + background-color: var(--popover-background); + border-color: var(--popover-border-colour); +} + +.popover-body { + max-height: 95vh; + overflow-y: auto; + color: var(--primary-font-colour); +} + +.bs-popover-right>.arrow { + border-right-color: var(--popover-border-colour); +} + +.bs-popover-right>.arrow:after { + border-right-color: var(--popover-background); +} + +.nav-tabs .nav-link { + color: var(--subtext-font-colour); +} + +.nav-tabs>li>a.nav-link.active, +.nav-tabs>li>a.nav-link.active:focus, +.nav-tabs>li>a.nav-link.active:hover { + background-color: var(--secondary-background-colour); + border-color: var(--secondary-border-colour); + border-bottom-color: transparent; + color: var(--primary-font-colour); +} + +.nav-tabs { + border-color: var(--primary-border-colour); +} + +.nav a.nav-link:focus, +.nav a.nav-link:hover { + background-color: var(--secondary-border-colour); +} + +.nav-tabs a.nav-link:hover { + border-color: var(--secondary-border-colour) var(--secondary-border-colour) var(--primary-border-colour); +} + +.dropdown-menu { + background-color: var(--primary-background-colour); +} + +.dropdown-menu a { + color: var(--primary-font-colour); +} + +.dropdown-menu a:focus, +.dropdown-menu a:hover { + background-color: var(--secondary-background-colour); + color: var(--primary-font-colour); +} + +.input-group-addon:not(:first-child):not(:last-child) { + border-left: 0; + border-right: 0; +} + +.input-group-btn:first-child>.btn { + border-right: 0; +} + + +/* Sortable */ + +.sortable-ghost { + opacity: 0.6; +} + + +/* Bootstrap Colorpicker */ + +.colorpicker-element { + float: left; + margin-right: 15px; +} + +.colorpicker-color, +.colorpicker-color div { + height: 100px; +} + + +/* CodeMirror */ + +.ͼ2 .cm-specialChar, +.cm-specialChar { + color: red; +} diff --git a/plugins/srktoolbox/src/web/utils/copyOverride.mjs b/plugins/srktoolbox/src/web/utils/copyOverride.mjs new file mode 100644 index 00000000..51b2386b --- /dev/null +++ b/plugins/srktoolbox/src/web/utils/copyOverride.mjs @@ -0,0 +1,125 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + * + * In order to render whitespace characters as control character pictures in the output, even + * when they are the designated line separator, CyberChef sometimes chooses to represent them + * internally using the Unicode Private Use Area (https://en.wikipedia.org/wiki/Private_Use_Areas). + * See `Utils.escapeWhitespace()` for an example of this. + * + * The `renderSpecialChar()` function understands that it should display these characters as + * control pictures. When copying data from the Output, we need to replace these PUA characters + * with their original values, so we override the DOM "copy" event and modify the copied data + * if required. This handler is based closely on the built-in CodeMirror handler and defers to the + * built-in handler if PUA characters are not present in the copied data, in order to minimise the + * impact of breaking changes. + */ + +import {EditorView} from "@codemirror/view"; + +/** + * Copies the currently selected text from the state doc. + * Based on the built-in implementation with a few unrequired bits taken out: + * https://github.com/codemirror/view/blob/7d9c3e54396242d17b3164a0e244dcc234ee50ee/src/input.ts#L604 + * + * @param {EditorState} state + * @returns {Object} + */ +function copiedRange(state) { + const content = []; + let linewise = false; + for (const range of state.selection.ranges) if (!range.empty) { + content.push(state.sliceDoc(range.from, range.to)); + } + if (!content.length) { + // Nothing selected, do a line-wise copy + let upto = -1; + for (const {from} of state.selection.ranges) { + const line = state.doc.lineAt(from); + if (line.number > upto) { + content.push(line.text); + } + upto = line.number; + } + linewise = true; + } + + return {text: content.join(state.lineBreak), linewise}; +} + +/** + * Regex to match characters in the Private Use Area of the Unicode table. + */ +const PUARegex = new RegExp("[\ue000-\uf8ff]"); +const PUARegexG = new RegExp("[\ue000-\uf8ff]", "g"); +/** + * Regex tto match Unicode Control Pictures. + */ +const CPRegex = new RegExp("[\u2400-\u243f]"); +const CPRegexG = new RegExp("[\u2400-\u243f]", "g"); + +/** + * Overrides the DOM "copy" handler in the CodeMirror editor in order to return the original + * values of control characters that have been represented in the Unicode Private Use Area for + * visual purposes. + * Based on the built-in copy handler with some modifications: + * https://github.com/codemirror/view/blob/7d9c3e54396242d17b3164a0e244dcc234ee50ee/src/input.ts#L629 + * + * This handler will defer to the built-in version if no PUA characters are present. + * + * @returns {Extension} + */ +export function copyOverride() { + return EditorView.domEventHandlers({ + copy(event, view) { + const {text, linewise} = copiedRange(view.state); + if (!text && !linewise) return; + + // If there are no PUA chars in the copied text, return false and allow the built-in + // copy handler to fire + if (!PUARegex.test(text)) return false; + + // If PUA chars are detected, modify them back to their original values and copy that instead + const rawText = text.replace(PUARegexG, function(c) { + return String.fromCharCode(c.charCodeAt(0) - 0xe000); + }); + + event.preventDefault(); + event.clipboardData.clearData(); + event.clipboardData.setData("text/plain", rawText); + + // Returning true prevents CodeMirror default handlers from firing + return true; + } + }); +} + + +/** + * Handler for copy events in output-html decorations. If there are control pictures present, + * this handler will convert them back to their raw form before copying. If there are no + * control pictures present, it will do nothing and defer to the default browser handler. + * + * @param {ClipboardEvent} event + * @returns {boolean} + */ +export function htmlCopyOverride(event) { + const text = window.getSelection().toString(); + if (!text) return; + + // If there are no control picture chars in the copied text, return false and allow the built-in + // copy handler to fire + if (!CPRegex.test(text)) return false; + + // If control picture chars are detected, modify them back to their original values and copy that instead + const rawText = text.replace(CPRegexG, function(c) { + return String.fromCharCode(c.charCodeAt(0) - 0x2400); + }); + + event.preventDefault(); + event.clipboardData.clearData(); + event.clipboardData.setData("text/plain", rawText); + + return true; +} diff --git a/plugins/srktoolbox/src/web/utils/editorUtils.mjs b/plugins/srktoolbox/src/web/utils/editorUtils.mjs new file mode 100644 index 00000000..ec02f70c --- /dev/null +++ b/plugins/srktoolbox/src/web/utils/editorUtils.mjs @@ -0,0 +1,138 @@ +/** + * CodeMirror utilities that are relevant to both the input and output + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import Utils from "../../core/Utils.mjs"; + +// Descriptions for named control characters +const Names = { + 0: "null", + 7: "bell", + 8: "backspace", + 10: "line feed", + 11: "vertical tab", + 13: "carriage return", + 27: "escape", + 8203: "zero width space", + 8204: "zero width non-joiner", + 8205: "zero width joiner", + 8206: "left-to-right mark", + 8207: "right-to-left mark", + 8232: "line separator", + 8237: "left-to-right override", + 8238: "right-to-left override", + 8294: "left-to-right isolate", + 8295: "right-to-left isolate", + 8297: "pop directional isolate", + 8233: "paragraph separator", + 65279: "zero width no-break space", + 65532: "object replacement" +}; + +// Regex for Special Characters to be replaced +const UnicodeRegexpSupport = /x/.unicode != null ? "gu" : "g"; +const Specials = new RegExp("[\u0000-\u0008\u000a-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\u202d\u202e\u2066\u2067\u2069\ufeff\ufff9-\ufffc\ue000-\uf8ff]", UnicodeRegexpSupport); + + +/** + * Override for rendering special characters. + * Should mirror the toDOM function in + * https://github.com/codemirror/view/blob/main/src/special-chars.ts#L153 + * But reverts the replacement of line feeds with newline control pictures. + * + * @param {number} code + * @param {string} desc + * @param {string} placeholder + * @returns {element} + */ +export function renderSpecialChar(code, desc, placeholder) { + const s = document.createElement("span"); + + // CodeMirror changes 0x0a to "NL" instead of "LF". We change it back along with its description. + if (code === 0x0a) { + placeholder = "\u240a"; + desc = desc.replace("newline", "line feed"); + } + + // Render CyberChef escaped characters correctly - see Utils.escapeWhitespace + if (code >= 0xe000 && code <= 0xf8ff) { + code = code - 0xe000; + placeholder = String.fromCharCode(0x2400 + code); + desc = "Control character " + (Names[code] || "0x" + code.toString(16)); + } + + s.textContent = placeholder; + s.title = desc; + s.setAttribute("aria-label", desc); + s.className = "cm-specialChar"; + return s; +} + + +/** + * Given a string, returns that string with any control characters replaced with HTML + * renderings of control pictures. + * + * @param {string} str + * @param {boolean} [preserveWs=false] + * @param {string} [lineBreak="\n"] + * @returns {html} + */ +export function escapeControlChars(str, preserveWs=false, lineBreak="\n") { + if (!preserveWs) + str = Utils.escapeWhitespace(str); + + return str.replace(Specials, function(c) { + if (lineBreak.includes(c)) return c; + const code = c.charCodeAt(0); + const desc = "Control character " + (Names[code] || "0x" + code.toString(16)); + const placeholder = code > 32 ? "\u2022" : String.fromCharCode(9216 + code); + const n = renderSpecialChar(code, desc, placeholder); + return n.outerHTML; + }); +} + +/** + * Convert and EOL sequence to its name + */ +export const eolSeqToCode = { + "\u000a": "LF", + "\u000b": "VT", + "\u000c": "FF", + "\u000d": "CR", + "\u000d\u000a": "CRLF", + "\u0085": "NEL", + "\u2028": "LS", + "\u2029": "PS" +}; + +/** + * Convert an EOL name to its sequence + */ +export const eolCodeToSeq = { + "LF": "\u000a", + "VT": "\u000b", + "FF": "\u000c", + "CR": "\u000d", + "CRLF": "\u000d\u000a", + "NEL": "\u0085", + "LS": "\u2028", + "PS": "\u2029" +}; + +export const eolCodeToName = { + "LF": "换行符", + "VT": "垂直定位", + "FF": "换页符", + "CR": "回车符", + "CRLF": "回车+换行", + "NEL": "下一行", + "LS": "分行", + "PS": "分段" +}; diff --git a/plugins/srktoolbox/src/web/utils/fileDetails.mjs b/plugins/srktoolbox/src/web/utils/fileDetails.mjs new file mode 100644 index 00000000..a8ea4ddf --- /dev/null +++ b/plugins/srktoolbox/src/web/utils/fileDetails.mjs @@ -0,0 +1,149 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + * + * Modified by Raka-loah@github + */ + +import {showSidePanel} from "./sidePanel.mjs"; +import Utils from "../../core/Utils.mjs"; +import {isImage, detectFileType} from "../../core/lib/FileType.mjs"; + +/** + * A File Details extension for CodeMirror + */ +class FileDetailsPanel { + + /** + * FileDetailsPanel constructor + * @param {Object} opts + */ + constructor(opts) { + this.fileDetails = opts?.fileDetails; + this.progress = opts?.progress ?? 0; + this.status = opts?.status; + this.buffer = opts?.buffer; + this.renderPreview = opts?.renderPreview; + this.toggleHandler = opts?.toggleHandler; + this.hidden = opts?.hidden; + this.dom = this.buildDOM(); + this.renderFileThumb(); + } + + /** + * Builds the file details DOM tree + * @returns {DOMNode} + */ + buildDOM() { + const dom = document.createElement("div"); + + dom.className = "cm-file-details"; + const fileThumb = require("../static/images/file-128x128.png"); + dom.innerHTML = ` +
        + ${this.hidden ? "❰" : "❱"} +
        +

        文件信息

        + + + + + + + + + + + + + + + + + + +
        名称: + ${Utils.escapeHtml(this.fileDetails?.name)} +
        大小: + ${Utils.escapeHtml(this.fileDetails?.size)} bytes +
        类型: + ${Utils.escapeHtml(this.fileDetails?.type)} +
        载入: + ${this.status === "error" ? "错误" : this.progress + "%"} +
        + `; + + dom.querySelector(".file-details-toggle-shown,.file-details-toggle-hidden") + .addEventListener("click", this.toggleHandler, false); + + return dom; + } + + /** + * Render the file thumbnail + */ + renderFileThumb() { + if (!this.renderPreview) { + this.resetFileThumb(); + return; + } + const fileThumb = this.dom.querySelector(".file-details-thumbnail"); + const fileType = this.dom.querySelector(".file-details-type"); + const fileBuffer = new Uint8Array(this.buffer); + const type = isImage(fileBuffer); + + if (type && type !== "image/tiff" && fileBuffer.byteLength <= 512000) { + // Most browsers don't support displaying TIFFs, so ignore them + // Don't render images over 512,000 bytes + const blob = new Blob([fileBuffer], {type: type}), + url = URL.createObjectURL(blob); + fileThumb.src = url; + } else { + this.resetFileThumb(); + } + fileType.textContent = type ? type : detectFileType(fileBuffer)[0]?.mime ?? "unknown"; + } + + /** + * Reset the file thumbnail to the default icon + */ + resetFileThumb() { + const fileThumb = this.dom.querySelector(".file-details-thumbnail"); + fileThumb.src = require("../static/images/file-128x128.png"); + } + +} + +/** + * A panel constructor factory building a panel that displays file details + * @param {Object} opts + * @returns {Function} + */ +function makePanel(opts) { + const fdPanel = new FileDetailsPanel(opts); + + return (view) => { + return { + dom: fdPanel.dom, + width: opts?.hidden ? 1 : 200, + update(update) { + }, + mount() { + $("[data-toggle='tooltip']").tooltip(); + } + }; + }; +} + +/** + * A function that build the extension that enables the panel in an editor. + * @param {Object} opts + * @returns {Extension} + */ +export function fileDetailsPanel(opts) { + const panelMaker = makePanel(opts); + return showSidePanel.of(panelMaker); +} diff --git a/plugins/srktoolbox/src/web/utils/htmlWidget.mjs b/plugins/srktoolbox/src/web/utils/htmlWidget.mjs new file mode 100644 index 00000000..642cd7e1 --- /dev/null +++ b/plugins/srktoolbox/src/web/utils/htmlWidget.mjs @@ -0,0 +1,134 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import {WidgetType, Decoration, ViewPlugin} from "@codemirror/view"; +import {escapeControlChars} from "./editorUtils.mjs"; +import {htmlCopyOverride} from "./copyOverride.mjs"; +import Utils from "../../core/Utils.mjs"; + + +/** + * Adds an HTML widget to the Code Mirror editor + */ +class HTMLWidget extends WidgetType { + + /** + * HTMLWidget consructor + */ + constructor(html, view) { + super(); + this.html = html; + this.view = view; + } + + /** + * Builds the DOM node + * @returns {DOMNode} + */ + toDOM() { + const wrap = document.createElement("span"); + wrap.setAttribute("id", "output-html"); + wrap.innerHTML = this.html; + + // Find text nodes and replace unprintable chars with control codes + this.walkTextNodes(wrap); + + // Add a handler for copy events to ensure the control codes are copied correctly + wrap.addEventListener("copy", htmlCopyOverride); + return wrap; + } + + /** + * Walks all text nodes in a given element + * @param {DOMNode} el + */ + walkTextNodes(el) { + for (const node of el.childNodes) { + switch (node.nodeType) { + case Node.TEXT_NODE: + this.replaceControlChars(node); + break; + default: + if (node.nodeName !== "SCRIPT" && + node.nodeName !== "STYLE") + this.walkTextNodes(node); + break; + } + } + } + + /** + * Renders control characters in text nodes + * @param {DOMNode} textNode + */ + replaceControlChars(textNode) { + // .nodeValue unencodes HTML encoding such as < to "<" + // We must remember to escape any potential HTML in TextNodes as we do not + // want to render it. + const textValue = Utils.escapeHtml(textNode.nodeValue); + const val = escapeControlChars(textValue, true, this.view.state.lineBreak); + if (val.length !== textNode.nodeValue.length) { + const node = document.createElement("span"); + node.innerHTML = val; + textNode.parentNode.replaceChild(node, textNode); + } + } + +} + +/** + * Decorator function to provide a set of widgets for the editor DOM + * @param {EditorView} view + * @param {string} html + * @returns {DecorationSet} + */ +function decorateHTML(view, html) { + const widgets = []; + if (html.length) { + const deco = Decoration.widget({ + widget: new HTMLWidget(html, view), + side: 1 + }); + widgets.push(deco.range(0)); + } + return Decoration.set(widgets); +} + + +/** + * An HTML Plugin builder + * @param {Object} htmlOutput + * @returns {ViewPlugin} + */ +export function htmlPlugin(htmlOutput) { + const plugin = ViewPlugin.fromClass( + class { + /** + * Plugin constructor + * @param {EditorView} view + */ + constructor(view) { + this.htmlOutput = htmlOutput; + this.decorations = decorateHTML(view, this.htmlOutput.html); + } + + /** + * Editor update listener + * @param {ViewUpdate} update + */ + update(update) { + if (this.htmlOutput.changed) { + this.decorations = decorateHTML(update.view, this.htmlOutput.html); + this.htmlOutput.changed = false; + } + } + }, { + decorations: v => v.decorations + } + ); + + return plugin; +} diff --git a/plugins/srktoolbox/src/web/utils/sidePanel.mjs b/plugins/srktoolbox/src/web/utils/sidePanel.mjs new file mode 100644 index 00000000..b54c08a0 --- /dev/null +++ b/plugins/srktoolbox/src/web/utils/sidePanel.mjs @@ -0,0 +1,263 @@ +/** + * A modification of the CodeMirror Panel extension to enable panels to the + * left and right of the editor. + * Based on code here: https://github.com/codemirror/view/blob/main/src/panel.ts + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import {EditorView, ViewPlugin} from "@codemirror/view"; +import {Facet} from "@codemirror/state"; + +const panelConfig = Facet.define({ + combine(configs) { + let leftContainer, rightContainer; + for (const c of configs) { + leftContainer = leftContainer || c.leftContainer; + rightContainer = rightContainer || c.rightContainer; + } + return {leftContainer, rightContainer}; + } +}); + +/** + * Configures the panel-managing extension. + * @param {PanelConfig} config + * @returns Extension + */ +export function panels(config) { + return config ? [panelConfig.of(config)] : []; +} + +/** + * Get the active panel created by the given constructor, if any. + * This can be useful when you need access to your panels' DOM + * structure. + * @param {EditorView} view + * @param {PanelConstructor} panel + * @returns {Panel} + */ +export function getPanel(view, panel) { + const plugin = view.plugin(panelPlugin); + const index = plugin ? plugin.specs.indexOf(panel) : -1; + return index > -1 ? plugin.panels[index] : null; +} + +const panelPlugin = ViewPlugin.fromClass(class { + + /** + * @param {EditorView} view + */ + constructor(view) { + this.input = view.state.facet(showSidePanel); + this.specs = this.input.filter(s => s); + this.panels = this.specs.map(spec => spec(view)); + const conf = view.state.facet(panelConfig); + this.left = new PanelGroup(view, true, conf.leftContainer); + this.right = new PanelGroup(view, false, conf.rightContainer); + this.left.sync(this.panels.filter(p => p.left)); + this.right.sync(this.panels.filter(p => !p.left)); + for (const p of this.panels) { + p.dom.classList.add("cm-panel"); + if (p.mount) p.mount(); + } + } + + /** + * @param {ViewUpdate} update + */ + update(update) { + const conf = update.state.facet(panelConfig); + if (this.left.container !== conf.leftContainer) { + this.left.sync([]); + this.left = new PanelGroup(update.view, true, conf.leftContainer); + } + if (this.right.container !== conf.rightContainer) { + this.right.sync([]); + this.right = new PanelGroup(update.view, false, conf.rightContainer); + } + this.left.syncClasses(); + this.right.syncClasses(); + const input = update.state.facet(showSidePanel); + if (input !== this.input) { + const specs = input.filter(x => x); + const panels = [], left = [], right = [], mount = []; + for (const spec of specs) { + const known = this.specs.indexOf(spec); + let panel; + if (known < 0) { + panel = spec(update.view); + mount.push(panel); + } else { + panel = this.panels[known]; + if (panel.update) panel.update(update); + } + panels.push(panel) + ;(panel.left ? left : right).push(panel); + } + this.specs = specs; + this.panels = panels; + this.left.sync(left); + this.right.sync(right); + for (const p of mount) { + p.dom.classList.add("cm-panel"); + if (p.mount) p.mount(); + } + } else { + for (const p of this.panels) if (p.update) p.update(update); + } + } + + /** + * Destroy panel + */ + destroy() { + this.left.sync([]); + this.right.sync([]); + } +}, { + // provide: PluginField.scrollMargins.from(value => ({left: value.left.scrollMargin(), right: value.right.scrollMargin()})) +}); + +/** + * PanelGroup + */ +class PanelGroup { + + /** + * @param {EditorView} view + * @param {boolean} left + * @param {HTMLElement} container + */ + constructor(view, left, container) { + this.view = view; + this.left = left; + this.container = container; + this.dom = undefined; + this.classes = ""; + this.panels = []; + this.syncClasses(); + } + + /** + * @param {Panel[]} panels + */ + sync(panels) { + for (const p of this.panels) if (p.destroy && panels.indexOf(p) < 0) p.destroy(); + this.panels = panels; + this.syncDOM(); + } + + /** + * Synchronise the DOM + */ + syncDOM() { + if (this.panels.length === 0) { + if (this.dom) { + this.dom.remove(); + this.dom = undefined; + this.setScrollerMargin(0); + } + return; + } + + const parent = this.container || this.view.dom; + if (!this.dom) { + this.dom = document.createElement("div"); + this.dom.className = this.left ? "cm-side-panels cm-panels-left" : "cm-side-panels cm-panels-right"; + parent.insertBefore(this.dom, parent.firstChild); + } + + let curDOM = this.dom.firstChild; + let bufferWidth = 0; + for (const panel of this.panels) { + bufferWidth += panel.width; + if (panel.dom.parentNode === this.dom) { + while (curDOM !== panel.dom) curDOM = rm(curDOM); + curDOM = curDOM.nextSibling; + } else { + this.dom.insertBefore(panel.dom, curDOM); + panel.dom.style.width = panel.width + "px"; + this.dom.style.width = bufferWidth + "px"; + } + } + while (curDOM) curDOM = rm(curDOM); + + this.setScrollerMargin(bufferWidth); + } + + /** + * Sets the margin of the cm-scroller element to make room for the panel + */ + setScrollerMargin(width) { + const parent = this.container || this.view.dom; + const margin = this.left ? "marginLeft" : "marginRight"; + parent.querySelector(".cm-scroller").style[margin] = width + "px"; + } + + /** + * + */ + scrollMargin() { + return !this.dom || this.container ? 0 : + Math.max(0, this.left ? + this.dom.getBoundingClientRect().right - Math.max(0, this.view.scrollDOM.getBoundingClientRect().left) : + Math.min(innerHeight, this.view.scrollDOM.getBoundingClientRect().right) - this.dom.getBoundingClientRect().left); + } + + /** + * + */ + syncClasses() { + if (!this.container || this.classes === this.view.themeClasses) return; + for (const cls of this.classes.split(" ")) if (cls) this.container.classList.remove(cls); + for (const cls of (this.classes = this.view.themeClasses).split(" ")) if (cls) this.container.classList.add(cls); + } +} + +/** + * @param {ChildNode} node + * @returns HTMLElement + */ +function rm(node) { + const next = node.nextSibling; + node.remove(); + return next; +} + +const baseTheme = EditorView.baseTheme({ + ".cm-side-panels": { + boxSizing: "border-box", + position: "absolute", + height: "100%", + top: 0, + bottom: 0 + }, + "&light .cm-side-panels": { + backgroundColor: "#f5f5f5", + color: "black" + }, + "&light .cm-panels-left": { + borderRight: "1px solid #ddd", + left: 0 + }, + "&light .cm-panels-right": { + borderLeft: "1px solid #ddd", + right: 0 + }, + "&dark .cm-side-panels": { + backgroundColor: "#333338", + color: "white" + } +}); + +/** + * Opening a panel is done by providing a constructor function for + * the panel through this facet. (The panel is closed again when its + * constructor is no longer provided.) Values of `null` are ignored. + */ +export const showSidePanel = Facet.define({ + enables: [panelPlugin, baseTheme] +}); diff --git a/plugins/srktoolbox/src/web/utils/statusBar.mjs b/plugins/srktoolbox/src/web/utils/statusBar.mjs new file mode 100644 index 00000000..efea13fc --- /dev/null +++ b/plugins/srktoolbox/src/web/utils/statusBar.mjs @@ -0,0 +1,517 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + * + * Modified by Raka-loah@github + */ + +import {showPanel} from "@codemirror/view"; +import {CHR_ENC_SIMPLE_LOOKUP, CHR_ENC_SIMPLE_REVERSE_LOOKUP} from "../../core/lib/ChrEnc.mjs"; +import { eolCodeToName, eolSeqToCode } from "./editorUtils.mjs"; + +/** + * A Status bar extension for CodeMirror + */ +class StatusBarPanel { + + /** + * StatusBarPanel constructor + * @param {Object} opts + */ + constructor(opts) { + this.label = opts.label; + this.timing = opts.timing; + this.tabNumGetter = opts.tabNumGetter; + this.eolHandler = opts.eolHandler; + this.chrEncHandler = opts.chrEncHandler; + this.chrEncGetter = opts.chrEncGetter; + this.getEncodingState = opts.getEncodingState; + this.getEOLState = opts.getEOLState; + this.htmlOutput = opts.htmlOutput; + + this.eolVal = null; + this.chrEncVal = null; + + this.dom = this.buildDOM(); + } + + /** + * Builds the status bar DOM tree + * @returns {DOMNode} + */ + buildDOM() { + const dom = document.createElement("div"); + const lhs = document.createElement("div"); + const rhs = document.createElement("div"); + + dom.className = "cm-status-bar"; + dom.setAttribute("data-help-title", `${this.label}框状态栏`); + dom.setAttribute("data-help", `此状态栏提供关于${this.label}框数据的信息。鼠标指向需要解释的组件上并手动触发帮助功能,会提供对应的帮助信息。`); + lhs.innerHTML = this.constructLHS(); + rhs.innerHTML = this.constructRHS(); + + dom.appendChild(lhs); + dom.appendChild(rhs); + + // Event listeners + dom.querySelectorAll(".cm-status-bar-select-btn").forEach( + el => el.addEventListener("click", this.showDropUp.bind(this), false) + ); + dom.querySelector(".eol-select").addEventListener("click", this.eolSelectClick.bind(this), false); + dom.querySelector(".chr-enc-select").addEventListener("click", this.chrEncSelectClick.bind(this), false); + dom.querySelector(".cm-status-bar-filter-input").addEventListener("keyup", this.chrEncFilter.bind(this), false); + + return dom; + } + + /** + * Handler for dropup clicks + * Shows/Hides the dropup + * @param {Event} e + */ + showDropUp(e) { + const el = e.target + .closest(".cm-status-bar-select") + .querySelector(".cm-status-bar-select-content"); + const btn = e.target.closest(".cm-status-bar-select-btn"); + + if (btn.classList.contains("disabled")) return; + + el.classList.add("show"); + + // Focus the filter input if present + const filter = el.querySelector(".cm-status-bar-filter-input"); + if (filter) filter.focus(); + + // Set up a listener to close the menu if the user clicks outside of it + hideOnClickOutside(el, e); + } + + /** + * Handler for EOL Select clicks + * Sets the line separator + * @param {Event} e + */ + eolSelectClick(e) { + // preventDefault is required to stop the URL being modified and popState being triggered + e.preventDefault(); + + const eolCode = e.target.getAttribute("data-val"); + if (!eolCode) return; + + // Call relevant EOL change handler + this.eolHandler(e.target.getAttribute("data-val"), true); + + hideElement(e.target.closest(".cm-status-bar-select-content")); + } + + /** + * Handler for Chr Enc Select clicks + * Sets the character encoding + * @param {Event} e + */ + chrEncSelectClick(e) { + // preventDefault is required to stop the URL being modified and popState being triggered + e.preventDefault(); + + const chrEncVal = parseInt(e.target.getAttribute("data-val"), 10); + + if (isNaN(chrEncVal)) return; + + this.chrEncHandler(chrEncVal, true); + this.updateCharEnc(chrEncVal); + hideElement(e.target.closest(".cm-status-bar-select-content")); + } + + /** + * Handler for Chr Enc keyup events + * Filters the list of selectable character encodings + * @param {Event} e + */ + chrEncFilter(e) { + const input = e.target; + const filter = input.value.toLowerCase(); + const div = input.closest(".cm-status-bar-select-content"); + const a = div.getElementsByTagName("a"); + for (let i = 0; i < a.length; i++) { + const txtValue = a[i].textContent || a[i].innerText; + if (txtValue.toLowerCase().includes(filter)) { + a[i].style.display = "block"; + } else { + a[i].style.display = "none"; + } + } + } + + /** + * Counts the stats of a document + * @param {EditorState} state + */ + updateStats(state) { + const length = this.dom.querySelector(".stats-length-value"), + lines = this.dom.querySelector(".stats-lines-value"); + + let docLength = state.doc.length; + // CodeMirror always counts line breaks as one character. + // We want to show an accurate reading of how many bytes there are. + if (state.lineBreak.length !== 1) { + docLength += (state.lineBreak.length * state.doc.lines) - state.doc.lines - 1; + } + length.textContent = docLength; + lines.textContent = state.doc.lines; + } + + /** + * Gets the current selection info + * @param {EditorState} state + * @param {boolean} selectionSet + */ + updateSelection(state, selectionSet) { + const selLen = state?.selection?.main ? + state.selection.main.to - state.selection.main.from : + 0; + + const selInfo = this.dom.querySelector(".sel-info"), + curOffsetInfo = this.dom.querySelector(".cur-offset-info"); + + if (!selectionSet) { + selInfo.style.display = "none"; + curOffsetInfo.style.display = "none"; + return; + } + + // CodeMirror always counts line breaks as one character. + // We want to show an accurate reading of how many bytes there are. + let from = state.selection.main.from, + to = state.selection.main.to; + if (state.lineBreak.length !== 1) { + const fromLine = state.doc.lineAt(from).number; + const toLine = state.doc.lineAt(to).number; + from += (state.lineBreak.length * fromLine) - fromLine - 1; + to += (state.lineBreak.length * toLine) - toLine - 1; + } + + if (selLen > 0) { // Range + const start = this.dom.querySelector(".sel-start-value"), + end = this.dom.querySelector(".sel-end-value"), + length = this.dom.querySelector(".sel-length-value"); + + selInfo.style.display = "inline-block"; + curOffsetInfo.style.display = "none"; + start.textContent = from; + end.textContent = to; + length.textContent = to - from; + } else { // Position + const offset = this.dom.querySelector(".cur-offset-value"); + + selInfo.style.display = "none"; + curOffsetInfo.style.display = "inline-block"; + offset.textContent = from; + } + } + + /** + * Sets the current EOL separator in the status bar + * @param {EditorState} state + */ + updateEOL(state) { + if (this.getEOLState() < 2 && state.lineBreak === this.eolVal) return; + + const val = this.dom.querySelector(".eol-value"); + const button = val.closest(".cm-status-bar-select-btn"); + let eolCode = eolSeqToCode[state.lineBreak]; + let eolName = eolCodeToName[eolCode]; + + switch (this.getEOLState()) { + case 1: // Detected + val.classList.add("font-italic"); + eolCode += " (自动检测)"; + eolName += " (自动检测)"; + // Pulse + val.classList.add("pulse"); + setTimeout(() => { + val.classList.remove("pulse"); + }, 2000); + break; + case 0: // Unset + case 2: // Manually set + default: + val.classList.remove("font-italic"); + break; + } + + val.textContent = eolCode; + button.setAttribute("title", `文本行结尾字符:
        ${eolName}`); + button.setAttribute("data-original-title", `文本行结尾字符:
        ${eolName}`); + this.eolVal = state.lineBreak; + } + + + /** + * Sets the current character encoding of the document + */ + updateCharEnc() { + const chrEncVal = this.chrEncGetter(); + if (this.getEncodingState() < 2 && chrEncVal === this.chrEncVal) return; + + let name = CHR_ENC_SIMPLE_REVERSE_LOOKUP[chrEncVal] ? CHR_ENC_SIMPLE_REVERSE_LOOKUP[chrEncVal] : "原始字节"; + + const val = this.dom.querySelector(".chr-enc-value"); + const button = val.closest(".cm-status-bar-select-btn"); + + switch (this.getEncodingState()) { + case 1: // Detected + val.classList.add("font-italic"); + name += " (自动检测)"; + // Pulse + val.classList.add("pulse"); + setTimeout(() => { + val.classList.remove("pulse"); + }, 2000); + break; + case 0: // Unset + case 2: // Manually set + default: + val.classList.remove("font-italic"); + break; + } + + val.textContent = name; + button.setAttribute("title", `${this.label}字符编码
        ${name}`); + button.setAttribute("data-original-title", `${this.label}字符编码:
        ${name}`); + this.chrEncVal = chrEncVal; + } + + /** + * Sets the latest timing info + */ + updateTiming() { + if (!this.timing) return; + + const bakingTime = this.dom.querySelector(".baking-time-value"); + const bakingTimeInfo = this.dom.querySelector(".baking-time-info"); + + if (this.label === "输出" && this.timing) { + bakingTimeInfo.style.display = "inline-block"; + bakingTime.textContent = this.timing.duration(this.tabNumGetter()); + + const info = this.timing.printStages(this.tabNumGetter()).replace(/\n/g, "
        "); + bakingTimeInfo.setAttribute("data-original-title", info); + } else { + bakingTimeInfo.style.display = "none"; + } + } + + /** + * Updates the sizing of elements that need to fit correctly + * @param {EditorView} view + */ + updateSizing(view) { + const viewHeight = view.contentDOM.parentNode.clientHeight; + this.dom.querySelectorAll(".cm-status-bar-select-scroll").forEach( + el => { + el.style.maxHeight = (viewHeight - 50) + "px"; + } + ); + } + + /** + * Checks whether there is HTML output requiring some widgets to be disabled + */ + monitorHTMLOutput() { + if (!this.htmlOutput?.changed) return; + + if (this.htmlOutput?.html === "") { + // Enable all controls + this.dom.querySelectorAll(".disabled").forEach(el => { + el.classList.remove("disabled"); + }); + } else { + // Disable chrenc, length, selection etc. + this.dom.querySelectorAll(".cm-status-bar-select-btn").forEach(el => { + el.classList.add("disabled"); + }); + + this.dom.querySelector(".stats-length-value").parentNode.classList.add("disabled"); + this.dom.querySelector(".stats-lines-value").parentNode.classList.add("disabled"); + this.dom.querySelector(".sel-info").classList.add("disabled"); + this.dom.querySelector(".cur-offset-info").classList.add("disabled"); + } + } + + /** + * Builds the Left-hand-side widgets + * @returns {string} + */ + constructLHS() { + return ` + + abc + + + + sort + + + + + highlight_alt + \u279E + ( 选中) + + + location_on + + `; + } + + /** + * Builds the Right-hand-side widgets + * Event listener set up in Manager + * + * @returns {string} + */ + constructRHS() { + const chrEncOptions = Object.keys(CHR_ENC_SIMPLE_LOOKUP).map(name => + `${name}` + ).join(""); + + let chrEncHelpText = "", + eolHelpText = ""; + if (this.label === "输入") { + chrEncHelpText = "输入字符编码决定输入框中的文本以何种方式编码成字节形式后交由操作流程进行计算。

        “原始字节”选项会尝试将输入内容当作从0-255的单个字节进行处理。如果检测到了Unicode值大于255的字符,输入框会把整个内容按UTF-8编码。如果输入内容是二进制,例如某个图片文件,“原始字节”通常是最优选项。"; + eolHelpText = "文本行结尾字符定义那些字节作为换行符使用。在输入框按回车会使用选定的结尾字符进行换行。

        改变这个设定不会影响当前的数据,但可能会影响之前输入的换行的显示。使用不同换行符进行的换行可能会显示成控制字符而不再具有换行作用。"; + } else { + chrEncHelpText = "输出字符编码决定输出框中的文本在显示之前使用何种方式解码。

        “原始字节”选项把输出数据当作0-255范围的单个字节处理。"; + eolHelpText = "文本行结尾字符定义那些字节作为换行符使用。

        改变这个设定不会影响当前的数据,但可能会影响数据中的特定字符是否会显示成换行。"; + } + + return ` + + +
        + + text_fields 原始字节 + +
        +
        + 原始字节 + ${chrEncOptions} +
        + +
        +
        + + `; + } + +} + +const elementsWithListeners = {}; + +/** + * Hides the provided element when a click is made outside of it + * @param {Element} element + * @param {Event} instantiatingEvent + */ +function hideOnClickOutside(element, instantiatingEvent) { + /** + * Handler for document click events + * Closes element if click is outside it. + * @param {Event} event + */ + const outsideClickListener = event => { + // Don't trigger if we're clicking inside the element, or if the element + // is not visible, or if this is the same click event that opened it. + if (!element.contains(event.target) && + event.timeStamp !== instantiatingEvent.timeStamp) { + hideElement(element); + } + }; + + if (!Object.prototype.hasOwnProperty.call(elementsWithListeners, element)) { + elementsWithListeners[element] = outsideClickListener; + document.addEventListener("click", elementsWithListeners[element], false); + } +} + +/** + * Hides the specified element and removes the click listener for it + * @param {Element} element + */ +function hideElement(element) { + element.classList.remove("show"); + document.removeEventListener("click", elementsWithListeners[element], false); + delete elementsWithListeners[element]; +} + + +/** + * A panel constructor factory building a panel that re-counts the stats every time the document changes. + * @param {Object} opts + * @returns {Function} + */ +function makePanel(opts) { + const sbPanel = new StatusBarPanel(opts); + + return (view) => { + sbPanel.updateEOL(view.state); + sbPanel.updateCharEnc(); + sbPanel.updateTiming(); + sbPanel.updateStats(view.state); + sbPanel.updateSelection(view.state, false); + sbPanel.monitorHTMLOutput(); + + return { + "dom": sbPanel.dom, + update(update) { + sbPanel.updateEOL(update.state); + sbPanel.updateCharEnc(); + sbPanel.updateSelection(update.state, update.selectionSet); + sbPanel.updateTiming(); + sbPanel.monitorHTMLOutput(); + if (update.geometryChanged) { + sbPanel.updateSizing(update.view); + } + if (update.docChanged) { + sbPanel.updateStats(update.state); + } + } + }; + }; +} + +/** + * A function that build the extension that enables the panel in an editor. + * @param {Object} opts + * @returns {Extension} + */ +export function statusBar(opts) { + const panelMaker = makePanel(opts); + return showPanel.of(panelMaker); +} diff --git a/plugins/srktoolbox/src/web/waiters/BackgroundWorkerWaiter.mjs b/plugins/srktoolbox/src/web/waiters/BackgroundWorkerWaiter.mjs new file mode 100644 index 00000000..409c2bbb --- /dev/null +++ b/plugins/srktoolbox/src/web/waiters/BackgroundWorkerWaiter.mjs @@ -0,0 +1,178 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import ChefWorker from "worker-loader?inline=no-fallback!../../core/ChefWorker.js"; + +/** + * Waiter to handle conversations with a ChefWorker in the background. + */ +class BackgroundWorkerWaiter { + + /** + * BackgroundWorkerWaiter constructor. + * + * @param {App} app - The main view object for CyberChef. + * @param {Manager} manager - The CyberChef event manager. + */ + constructor(app, manager) { + this.app = app; + this.manager = manager; + + this.callbacks = {}; + this.callbackID = 0; + this.completedCallback = -1; + this.timeout = null; + } + + + /** + * Sets up the ChefWorker and associated listeners. + */ + registerChefWorker() { + log.debug("Registering new background ChefWorker"); + this.chefWorker = new ChefWorker(); + this.chefWorker.addEventListener("message", this.handleChefMessage.bind(this)); + this.chefWorker.postMessage({ + action: "setLogPrefix", + data: "BGChefWorker" + }); + this.chefWorker.postMessage({ + action: "setLogLevel", + data: log.getLevel() + }); + + let docURL = document.location.href.split(/[#?]/)[0]; + const index = docURL.lastIndexOf("/"); + if (index > 0) { + docURL = docURL.substring(0, index); + } + this.chefWorker.postMessage({"action": "docURL", "data": docURL}); + } + + + /** + * Handler for messages sent back by the ChefWorker. + * + * @param {MessageEvent} e + */ + handleChefMessage(e) { + const r = e.data; + log.debug(`Receiving '${r.action}' from BGChefWorker`); + + switch (r.action) { + case "bakeComplete": + case "bakeError": + if (typeof r.data.id !== "undefined") { + clearTimeout(this.timeout); + this.callbacks[r.data.id].bind(this)(r.data); + this.completedCallback = r.data.id; + } + break; + case "workerLoaded": + log.debug("Background ChefWorker loaded"); + break; + case "optionUpdate": + case "statusMessage": + case "progressMessage": + // Ignore these messages + break; + default: + log.error("Unrecognised message from background ChefWorker", e); + break; + } + } + + + /** + * Cancels the current bake by terminating the ChefWorker and creating a new one. + */ + cancelBake() { + if (this.chefWorker) + this.chefWorker.terminate(); + this.registerChefWorker(); + } + + + /** + * Asks the ChefWorker to bake the input using the specified recipe. + * + * @param {string} input + * @param {Object[]} recipeConfig + * @param {Object} options + * @param {number} progress + * @param {boolean} step + * @param {Function} callback + */ + bake(input, recipeConfig, options, progress, step, callback) { + const id = this.callbackID++; + this.callbacks[id] = callback; + + this.chefWorker.postMessage({ + action: "bake", + data: { + input: input, + recipeConfig: recipeConfig, + options: options, + progress: progress, + step: step, + id: id + } + }); + } + + + /** + * Asks the Magic operation what it can do with the input data. + * + * @param {string|ArrayBuffer} input + */ + magic(input) { + // If we're still working on the previous bake, cancel it before starting a new one. + if (this.completedCallback + 1 < this.callbackID) { + clearTimeout(this.timeout); + this.cancelBake(); + } + + this.bake(input, [ + { + "op": "Magic", + "args": [3, false, false] + } + ], {}, 0, false, this.magicComplete); + + // Cancel this bake if it takes too long. + this.timeout = setTimeout(this.cancelBake.bind(this), 3000); + } + + + /** + * Handler for completed Magic bakes. + * + * @param {Object} response + */ + magicComplete(response) { + log.debug("--- Background Magic Bake complete ---"); + if (!response || response.error) return; + + this.manager.output.backgroundMagicResult(response.dish.value); + } + + + /** + * Sets the console log level in the workers. + */ + setLogLevel() { + if (!this.chefWorker) return; + this.chefWorker.postMessage({ + action: "setLogLevel", + data: log.getLevel() + }); + } + +} + + +export default BackgroundWorkerWaiter; diff --git a/plugins/srktoolbox/src/web/waiters/BindingsWaiter.mjs b/plugins/srktoolbox/src/web/waiters/BindingsWaiter.mjs new file mode 100644 index 00000000..47178e71 --- /dev/null +++ b/plugins/srktoolbox/src/web/waiters/BindingsWaiter.mjs @@ -0,0 +1,310 @@ +/** + * @author Matt C [matt@artemisbot.uk] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github + */ + +/** + * Waiter to handle keybindings to CyberChef functions (i.e. Bake, Step, Save, Load etc.) + */ +class BindingsWaiter { + + /** + * BindingsWaiter constructor. + * + * @param {App} app - The main view object for CyberChef. + * @param {Manager} manager - The CyberChef event manager. + */ + constructor(app, manager) { + this.app = app; + this.manager = manager; + } + + + /** + * Handler for all keydown events + * Checks whether valid keyboard shortcut has been instated + * + * @fires Manager#statechange + * @param {event} e + */ + parseInput(e) { + const modKey = this.app.options.useMetaKey ? e.metaKey : e.altKey; + + if (e.ctrlKey && modKey) { + let elem; + switch (e.code) { + case "KeyF": // Focus search + e.preventDefault(); + document.getElementById("search").focus(); + break; + case "KeyI": // Focus input + e.preventDefault(); + this.manager.input.inputEditorView.focus(); + break; + case "KeyO": // Focus output + e.preventDefault(); + this.manager.output.outputEditorView.focus(); + break; + case "Period": // Focus next operation + e.preventDefault(); + try { + elem = document.activeElement.closest(".operation") || document.querySelector("#rec-list .operation"); + if (elem.parentNode.lastChild === elem) { + // If operation is last in recipe, loop around to the top operation's first argument + elem.parentNode.firstChild.querySelectorAll(".arg")[0].focus(); + } else { + // Focus first argument of next operation + elem.nextSibling.querySelectorAll(".arg")[0].focus(); + } + } catch (e) { + // do nothing, just don't throw an error + } + break; + case "KeyB": // Set breakpoint + e.preventDefault(); + try { + elem = document.activeElement.closest(".operation").querySelectorAll(".breakpoint")[0]; + if (elem.getAttribute("break") === "false") { + elem.setAttribute("break", "true"); // add break point if not already enabled + elem.classList.add("breakpoint-selected"); + } else { + elem.setAttribute("break", "false"); // remove break point if already enabled + elem.classList.remove("breakpoint-selected"); + } + window.dispatchEvent(this.manager.statechange); + } catch (e) { + // do nothing, just don't throw an error + } + break; + case "KeyD": // Disable operation + e.preventDefault(); + try { + elem = document.activeElement.closest(".operation").querySelectorAll(".disable-icon")[0]; + if (elem.getAttribute("disabled") === "false") { + elem.setAttribute("disabled", "true"); // disable operation if enabled + elem.classList.add("disable-elem-selected"); + elem.parentNode.parentNode.classList.add("disabled"); + } else { + elem.setAttribute("disabled", "false"); // enable operation if disabled + elem.classList.remove("disable-elem-selected"); + elem.parentNode.parentNode.classList.remove("disabled"); + } + this.app.progress = 0; + window.dispatchEvent(this.manager.statechange); + } catch (e) { + // do nothing, just don't throw an error + } + break; + case "Space": // Bake + e.preventDefault(); + this.manager.controls.bakeClick(); + break; + case "Quote": // Step through + e.preventDefault(); + this.manager.controls.stepClick(); + break; + case "KeyC": // Clear recipe + e.preventDefault(); + this.manager.recipe.clearRecipe(); + break; + case "KeyS": // Save output to file + e.preventDefault(); + this.manager.output.saveClick(); + break; + case "KeyL": // Load recipe + e.preventDefault(); + this.manager.controls.loadClick(); + break; + case "KeyM": // Switch input and output + e.preventDefault(); + this.manager.output.switchClick(); + break; + case "KeyT": // New tab + e.preventDefault(); + this.manager.input.addInputClick(); + break; + case "KeyW": // Close tab + e.preventDefault(); + this.manager.input.removeInput(this.manager.tabs.getActiveTab("input")); + break; + case "ArrowLeft": // Go to previous tab + e.preventDefault(); + this.manager.input.changeTabLeft(); + break; + case "ArrowRight": // Go to next tab + e.preventDefault(); + this.manager.input.changeTabRight(); + break; + default: + if (e.code.match(/Digit[0-9]/g)) { // Select nth operation + e.preventDefault(); + try { + // Select the first argument of the operation corresponding to the number pressed + document.querySelector(`li:nth-child(${e.code.substr(-1)}) .arg`).focus(); + } catch (e) { + // do nothing, just don't throw an error + } + } + break; + } + } else { + switch (e.code) { + case "F1": + e.preventDefault(); + this.contextualHelp(); + break; + } + } + } + + + /** + * Updates keybinding list when metaKey option is toggled + */ + updateKeybList() { + let modWinLin = "Alt"; + let modMac = "Opt"; + if (this.app.options.useMetaKey) { + modWinLin = "Win"; + modMac = "Cmd"; + } + document.getElementById("keybList").innerHTML = ` + + 命令 + 快捷键(Win/Linux) + 快捷键(Mac) + + + 激活上下文帮助 + F1 + F1 + + + 光标置于搜索框 + Ctrl+${modWinLin}+f + Ctrl+${modMac}+f + + 光标置于输入框 + Ctrl+${modWinLin}+i + Ctrl+${modMac}+i + + + 光标置于输出框 + Ctrl+${modWinLin}+o + Ctrl+${modMac}+o + + + 光标置于流程中下一个操作的第一个参数框 + Ctrl+${modWinLin}+. + Ctrl+${modMac}+. + + + 光标置于流程中第N个操作的第一个参数框 + Ctrl+${modWinLin}+[1-9] + Ctrl+${modMac}+[1-9] + + + 禁用当前操作 + Ctrl+${modWinLin}+d + Ctrl+${modMac}+d + + + 设置/清除断点 + Ctrl+${modWinLin}+b + Ctrl+${modMac}+b + + + 执行流程/开整! + Ctrl+${modWinLin}+Space + Ctrl+${modMac}+Space + + + 步进 + Ctrl+${modWinLin}+' + Ctrl+${modMac}+' + + + 清除流程 + Ctrl+${modWinLin}+c + Ctrl+${modMac}+c + + + 保存到文件 + Ctrl+${modWinLin}+s + Ctrl+${modMac}+s + + + 载入流程 + Ctrl+${modWinLin}+l + Ctrl+${modMac}+l + + + 输出内容替换到输入 + Ctrl+${modWinLin}+m + Ctrl+${modMac}+m + + + 新建标签 + Ctrl+${modWinLin}+t + Ctrl+${modMac}+t + + + 关闭当前标签 + Ctrl+${modWinLin}+w + Ctrl+${modMac}+w + + + 下一个标签 + Ctrl+${modWinLin}+RightArrow + Ctrl+${modMac}+RightArrow + + + 上一个标签 + Ctrl+${modWinLin}+LeftArrow + Ctrl+${modMac}+LeftArrow + + `; + } + + /** + * Shows contextual help message based on where the mouse pointer is + */ + contextualHelp() { + const hoveredHelpEls = document.querySelectorAll(":hover[data-help],:hover[data-help-proxy]"); + if (!hoveredHelpEls.length) return; + + let helpEl = hoveredHelpEls[hoveredHelpEls.length - 1]; + const helpElSelector = helpEl.getAttribute("data-help-proxy"); + if (helpElSelector) { + // A hovered element is directing us to another element for its help text + helpEl = document.querySelector(helpElSelector); + } + this.displayHelp(helpEl); + } + + /** + * Displays the help pane populated with help text associated with the given element + * + * @param {Element} el + */ + displayHelp(el) { + const helpText = el.getAttribute("data-help"); + let helpTitle = el.getAttribute("data-help-title"); + + if (helpTitle) + helpTitle = "帮助主题: " + helpTitle; + else + helpTitle = "帮助主题"; + + document.querySelector("#help-modal .modal-body").innerHTML = helpText; + document.querySelector("#help-modal #help-title").innerHTML = helpTitle; + + $("#help-modal").modal(); + } + +} + +export default BindingsWaiter; diff --git a/plugins/srktoolbox/src/web/waiters/ControlsWaiter.mjs b/plugins/srktoolbox/src/web/waiters/ControlsWaiter.mjs new file mode 100644 index 00000000..0de0686f --- /dev/null +++ b/plugins/srktoolbox/src/web/waiters/ControlsWaiter.mjs @@ -0,0 +1,473 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github + */ + +import Utils from "../../core/Utils.mjs"; +import { eolSeqToCode } from "../utils/editorUtils.mjs"; + + +/** + * Waiter to handle events related to the CyberChef controls (i.e. Bake, Step, Save, Load etc.) + */ +class ControlsWaiter { + + /** + * ControlsWaiter constructor. + * + * @param {App} app - The main view object for CyberChef. + * @param {Manager} manager - The CyberChef event manager. + */ + constructor(app, manager) { + this.app = app; + this.manager = manager; + } + + + /** + * Initialise Bootstrap components + */ + initComponents() { + $("body").bootstrapMaterialDesign(); + $("[data-toggle=tooltip]").tooltip({ + animation: false, + container: "body", + boundary: "viewport", + trigger: "hover" + }); + + // Set number of operations in various places in the DOM + document.querySelectorAll(".num-ops").forEach(el => { + el.innerHTML = Object.keys(this.app.operations).length; + }); + } + + + /** + * Checks or unchecks the Auto Bake checkbox based on the given value. + * + * @param {boolean} value - The new value for Auto Bake. + */ + setAutoBake(value) { + const autoBakeCheckbox = document.getElementById("auto-bake"); + + if (autoBakeCheckbox.checked !== value) { + autoBakeCheckbox.click(); + } + } + + + /** + * Handler to trigger baking. + */ + bakeClick() { + const btnBake = document.getElementById("bake"); + if (btnBake.textContent.indexOf("开整!") > 0) { + this.app.manager.input.bakeAll(); + } else if (btnBake.textContent.indexOf("取消") > 0) { + this.manager.worker.cancelBake(false, true); + } + } + + + /** + * Handler for the 'Step through' command. Executes the next step of the recipe. + */ + stepClick() { + this.app.step(); + } + + + /** + * Handler for changes made to the Auto Bake checkbox. + */ + autoBakeChange() { + this.app.autoBake_ = document.getElementById("auto-bake").checked; + } + + + /** + * Handler for the 'Clear recipe' command. Removes all operations from the recipe. + */ + clearRecipeClick() { + this.manager.recipe.clearRecipe(); + } + + + /** + * Populates the save dialog box with a URL incorporating the recipe and input. + * + * @param {Object[]} [recipeConfig] - The recipe configuration object array. + */ + initialiseSaveLink(recipeConfig) { + recipeConfig = recipeConfig || this.app.getRecipeConfig(); + + const includeRecipe = document.getElementById("save-link-recipe-checkbox").checked; + const includeInput = document.getElementById("save-link-input-checkbox").checked; + const saveLinkEl = document.getElementById("save-link"); + const saveLink = this.generateStateUrl(includeRecipe, includeInput, null, recipeConfig); + + saveLinkEl.innerHTML = Utils.escapeHtml(Utils.truncate(saveLink, 120)); + saveLinkEl.setAttribute("href", saveLink); + } + + + /** + * Generates a URL containing the current recipe and input state. + * + * @param {boolean} includeRecipe - Whether to include the recipe in the URL. + * @param {boolean} includeInput - Whether to include the input in the URL. + * @param {string} input + * @param {Object[]} [recipeConfig] - The recipe configuration object array. + * @param {string} [baseURL] - The CyberChef URL, set to the current URL if not included + * @returns {string} + */ + generateStateUrl(includeRecipe, includeInput, input, recipeConfig, baseURL) { + recipeConfig = recipeConfig || this.app.getRecipeConfig(); + + const link = baseURL || window.location.protocol + "//" + + window.location.host + + window.location.pathname; + const recipeStr = Utils.generatePrettyRecipe(recipeConfig); + + includeRecipe = includeRecipe && (recipeConfig.length > 0); + + // If we don't get passed an input, get it from the current URI + if (input === null && includeInput) { + const params = this.app.getURIParams(); + if (params.input) { + includeInput = true; + input = params.input; + } else { + includeInput = false; + } + } + + const inputChrEnc = this.manager.input.getChrEnc(); + const outputChrEnc = this.manager.output.getChrEnc(); + const inputEOL = eolSeqToCode[this.manager.input.getEOLSeq()]; + const outputEOL = eolSeqToCode[this.manager.output.getEOLSeq()]; + + const params = [ + includeRecipe ? ["recipe", recipeStr] : undefined, + includeInput && input.length ? ["input", Utils.escapeHtml(input)] : undefined, + inputChrEnc !== 0 ? ["ienc", inputChrEnc] : undefined, + outputChrEnc !== 0 ? ["oenc", outputChrEnc] : undefined, + inputEOL !== "LF" ? ["ieol", inputEOL] : undefined, + outputEOL !== "LF" ? ["oeol", outputEOL] : undefined + ]; + + const hash = params + .filter(v => v) + .map(([key, value]) => `${key}=${Utils.encodeURIFragment(value)}`) + .join("&"); + + if (hash) { + return `${link}#${hash}`; + } + + return link; + } + + + /** + * Handler for changes made to the save dialog text area. Re-initialises the save link. + */ + saveTextChange(e) { + try { + const recipeConfig = Utils.parseRecipeConfig(e.target.value); + this.initialiseSaveLink(recipeConfig); + } catch (err) {} + } + + + /** + * Handler for the 'Save' command. Pops up the save dialog box. + */ + saveClick() { + const recipeConfig = this.app.getRecipeConfig(); + const recipeStr = JSON.stringify(recipeConfig); + + document.getElementById("save-text-chef").value = Utils.generatePrettyRecipe(recipeConfig, true); + document.getElementById("save-text-clean").value = JSON.stringify(recipeConfig, null, 2) + .replace(/{\n\s+"/g, "{ \"") + .replace(/\[\n\s{3,}/g, "[") + .replace(/\n\s{3,}]/g, "]") + .replace(/\s*\n\s*}/g, " }") + .replace(/\n\s{6,}/g, " "); + document.getElementById("save-text-compact").value = recipeStr; + + this.initialiseSaveLink(recipeConfig); + $("#save-modal").modal(); + } + + + /** + * Handler for the save link recipe checkbox change event. + */ + slrCheckChange() { + this.initialiseSaveLink(); + } + + + /** + * Handler for the save link input checkbox change event. + */ + sliCheckChange() { + this.initialiseSaveLink(); + } + + + /** + * Handler for the 'Load' command. Pops up the load dialog box. + */ + loadClick() { + this.populateLoadRecipesList(); + $("#load-modal").modal(); + } + + + /** + * Saves the recipe specified in the save textarea to local storage. + */ + saveButtonClick() { + if (!this.app.isLocalStorageAvailable()) { + this.app.alert( + "未授予本地存储权限,无法保存流程。", + 5000 + ); + return false; + } + + const recipeName = Utils.escapeHtml(document.getElementById("save-name").value); + const recipeStr = document.querySelector("#save-texts .tab-pane.active textarea").value; + + if (!recipeName) { + this.app.alert("请输入流程名称!", 3000); + return; + } + + const savedRecipes = localStorage.savedRecipes ? + JSON.parse(localStorage.savedRecipes) : []; + let recipeId = localStorage.recipeId || 0; + + savedRecipes.push({ + id: ++recipeId, + name: recipeName, + recipe: recipeStr + }); + + localStorage.savedRecipes = JSON.stringify(savedRecipes); + localStorage.recipeId = recipeId; + + this.app.alert(`流程保存为 "${recipeName}".`, 3000); + } + + + /** + * Populates the list of saved recipes in the load dialog box from local storage. + */ + populateLoadRecipesList() { + if (!this.app.isLocalStorageAvailable()) return false; + + const loadNameEl = document.getElementById("load-name"); + + // Remove current recipes from select + let i = loadNameEl.options.length; + while (i--) { + loadNameEl.remove(i); + } + + // Add recipes to select + const savedRecipes = localStorage.savedRecipes ? + JSON.parse(localStorage.savedRecipes) : []; + + for (i = 0; i < savedRecipes.length; i++) { + const opt = document.createElement("option"); + opt.value = savedRecipes[i].id; + // Unescape then re-escape in case localStorage has been corrupted + opt.innerHTML = Utils.escapeHtml(Utils.unescapeHtml(savedRecipes[i].name)); + + loadNameEl.appendChild(opt); + } + + // Populate textarea with first recipe + const loadText = document.getElementById("load-text"); + const evt = new Event("change"); + loadText.value = savedRecipes.length ? savedRecipes[0].recipe : ""; + loadText.dispatchEvent(evt); + } + + + /** + * Removes the currently selected recipe from local storage. + */ + loadDeleteClick() { + if (!this.app.isLocalStorageAvailable()) return false; + + const id = parseInt(document.getElementById("load-name").value, 10); + const rawSavedRecipes = localStorage.savedRecipes ? + JSON.parse(localStorage.savedRecipes) : []; + + const savedRecipes = rawSavedRecipes.filter(r => r.id !== id); + + localStorage.savedRecipes = JSON.stringify(savedRecipes); + this.populateLoadRecipesList(); + } + + + /** + * Displays the selected recipe in the load text box. + */ + loadNameChange(e) { + if (!this.app.isLocalStorageAvailable()) return false; + + const el = e.target; + const savedRecipes = localStorage.savedRecipes ? + JSON.parse(localStorage.savedRecipes) : []; + const id = parseInt(el.value, 10); + + const recipe = savedRecipes.find(r => r.id === id); + + document.getElementById("load-text").value = recipe.recipe; + } + + + /** + * Loads the selected recipe and populates the Recipe with its operations. + */ + loadButtonClick() { + try { + const recipeConfig = Utils.parseRecipeConfig(document.getElementById("load-text").value); + this.app.setRecipeConfig(recipeConfig); + this.app.autoBake(); + + $("#rec-list [data-toggle=popover]").popover(); + } catch (e) { + this.app.alert("无效流程", 2000); + } + } + + + /** + * Hides the arguments for all the operations in the current recipe. + */ + hideRecipeArgsClick() { + const icon = document.getElementById("hide-icon"); + + if (icon.getAttribute("hide-args") === "false") { + icon.setAttribute("hide-args", "true"); + icon.setAttribute("data-original-title", "显示参数"); + icon.children[0].innerText = "keyboard_arrow_down"; + Array.from(document.getElementsByClassName("hide-args-icon")).forEach(function(item) { + item.setAttribute("hide-args", "true"); + item.innerText = "keyboard_arrow_down"; + item.classList.add("hide-args-selected"); + item.parentNode.previousElementSibling.style.display = "none"; + }); + } else { + icon.setAttribute("hide-args", "false"); + icon.setAttribute("data-original-title", "隐藏参数"); + icon.children[0].innerText = "keyboard_arrow_up"; + Array.from(document.getElementsByClassName("hide-args-icon")).forEach(function(item) { + item.setAttribute("hide-args", "false"); + item.innerText = "keyboard_arrow_up"; + item.classList.remove("hide-args-selected"); + item.parentNode.previousElementSibling.style.display = "grid"; + }); + } + } + + + /** + * Populates the bug report information box with useful technical info. + * + * @param {event} e + */ + supportButtonClick(e) { + e.preventDefault(); + + const reportBugInfo = document.getElementById("report-bug-info"); + const saveLink = this.generateStateUrl(true, true, null, null, "https://btsrk.me/"); + + if (reportBugInfo) { + reportBugInfo.innerHTML = `* Version: ${PKG_VERSION} +* Compile time: ${COMPILE_TIME} +* User-Agent: +${navigator.userAgent} +* [Link to reproduce](${saveLink}) + +`; + } + } + + + /** + * Shows the stale indicator to show that the input or recipe has changed + * since the last bake. + */ + showStaleIndicator() { + const staleIndicator = document.getElementById("stale-indicator"); + staleIndicator.classList.remove("hidden"); + } + + + /** + * Hides the stale indicator to show that the input or recipe has not changed + * since the last bake. + */ + hideStaleIndicator() { + const staleIndicator = document.getElementById("stale-indicator"); + staleIndicator.classList.add("hidden"); + } + + + /** + * Switches the Bake button between 'Bake', 'Cancel' and 'Loading' functions. + * + * @param {string} func - The function to change to. Either "cancel", "loading" or "bake" + */ + toggleBakeButtonFunction(func) { + const bakeButton = document.getElementById("bake"), + btnText = bakeButton.querySelector("span"); + + switch (func) { + case "cancel": + btnText.innerText = "取消"; + bakeButton.classList.remove("btn-success"); + bakeButton.classList.remove("btn-warning"); + bakeButton.classList.add("btn-danger"); + break; + case "loading": + bakeButton.style.background = ""; + btnText.innerText = "载入中..."; + bakeButton.classList.remove("btn-success"); + bakeButton.classList.remove("btn-danger"); + bakeButton.classList.add("btn-warning"); + break; + default: + bakeButton.style.background = ""; + btnText.innerText = "开整!"; + bakeButton.classList.remove("btn-danger"); + bakeButton.classList.remove("btn-warning"); + bakeButton.classList.add("btn-success"); + } + } + + /** + * Calculates the height of the controls area and adjusts the recipe + * height accordingly. + */ + calcControlsHeight() { + const controls = document.getElementById("controls"), + recList = document.getElementById("rec-list"); + + recList.style.bottom = controls.clientHeight + "px"; + } + +} + +export default ControlsWaiter; diff --git a/plugins/srktoolbox/src/web/waiters/HighlighterWaiter.mjs b/plugins/srktoolbox/src/web/waiters/HighlighterWaiter.mjs new file mode 100644 index 00000000..ee27b2b0 --- /dev/null +++ b/plugins/srktoolbox/src/web/waiters/HighlighterWaiter.mjs @@ -0,0 +1,140 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github + */ + +import {EditorSelection} from "@codemirror/state"; +import {chrEncWidth} from "../../core/lib/ChrEnc.mjs"; + +/** + * Waiter to handle events related to highlighting in CyberChef. + */ +class HighlighterWaiter { + + /** + * HighlighterWaiter constructor. + * + * @param {App} app - The main view object for CyberChef. + * @param {Manager} manager - The CyberChef event manager. + */ + constructor(app, manager) { + this.app = app; + this.manager = manager; + + this.currentSelectionRanges = []; + } + + /** + * Handler for selection change events in the input and output + * + * Highlights the given offsets in the input or output. + * We will only highlight if: + * - input hasn't changed since last bake + * - last bake was a full bake + * - all operations in the recipe support highlighting + * + * @param {string} io + * @param {ViewUpdate} e + */ + selectionChange(io, e) { + // Confirm we are not currently baking + if (!this.app.autoBake_ || this.app.baking) return false; + + // Confirm this was a user-generated event to prevent looping + // from setting the selection in this class + if (!e.transactions[0].isUserEvent("select")) return false; + + this.currentSelectionRanges = []; + + // Confirm some non-empty ranges are set + const selectionRanges = e.state.selection.ranges; + + // Adjust offsets based on the width of the character set + const inputCharacterWidth = chrEncWidth(this.manager.input.getChrEnc()); + const outputCharacterWidth = chrEncWidth(this.manager.output.getChrEnc()); + let ratio = 1; + if (inputCharacterWidth !== outputCharacterWidth && + inputCharacterWidth !== 0 && outputCharacterWidth !== 0) { + ratio = io === "input" ? + inputCharacterWidth / outputCharacterWidth : + outputCharacterWidth / inputCharacterWidth; + } + + // Loop through ranges and send request for output offsets for each one + const direction = io === "input" ? "forward" : "reverse"; + for (const range of selectionRanges) { + const pos = [{ + start: Math.floor(range.from * ratio), + end: Math.floor(range.to * ratio) + }]; + this.manager.worker.highlight(this.app.getRecipeConfig(), direction, pos); + } + } + + /** + * Displays highlight offsets sent back from the Chef. + * + * @param {Object[]} pos - The position object for the highlight. + * @param {number} pos.start - The start offset. + * @param {number} pos.end - The end offset. + * @param {string} direction + */ + displayHighlights(pos, direction) { + if (!pos) return; + if (this.manager.tabs.getActiveTab("input") !== this.manager.tabs.getActiveTab("output")) return; + + const io = direction === "forward" ? "output" : "input"; + this.highlight(io, pos); + } + + /** + * Sends selection updates to the relevant EditorView. + * + * @param {string} io - The input or output + * @param {Object[]} ranges - An array of position objects to highlight + * @param {number} ranges.start - The start offset + * @param {number} ranges.end - The end offset + */ + async highlight(io, ranges) { + if (!this.app.options.showHighlighter) return false; + if (!this.app.options.attemptHighlight) return false; + if (!ranges || !ranges.length) return false; + + const view = io === "input" ? + this.manager.input.inputEditorView : + this.manager.output.outputEditorView; + + // Add new SelectionRanges to existing ones + for (const range of ranges) { + if (typeof range.start !== "number" || + typeof range.end !== "number") + continue; + const selection = range.end <= range.start ? + EditorSelection.cursor(range.start) : + EditorSelection.range(range.start, range.end); + + this.currentSelectionRanges.push(selection); + } + + // Set selection + if (this.currentSelectionRanges.length) { + try { + view.dispatch({ + selection: EditorSelection.create(this.currentSelectionRanges), + scrollIntoView: true + }); + } catch (err) { + // Ignore Range Errors + if (!err.toString().startsWith("RangeError")) { + log.error(err); + } + } + } + } + +} + +export default HighlighterWaiter; diff --git a/plugins/srktoolbox/src/web/waiters/InputWaiter.mjs b/plugins/srktoolbox/src/web/waiters/InputWaiter.mjs new file mode 100644 index 00000000..53624813 --- /dev/null +++ b/plugins/srktoolbox/src/web/waiters/InputWaiter.mjs @@ -0,0 +1,1699 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github + */ + +import LoaderWorker from "worker-loader?inline=no-fallback!../workers/LoaderWorker.js"; +import InputWorker from "worker-loader?inline=no-fallback!../workers/InputWorker.mjs"; +import Utils, {debounce} from "../../core/Utils.mjs"; +import {toBase64} from "../../core/lib/Base64.mjs"; +import cptable from "codepage"; + +import { + EditorView, + keymap, + highlightSpecialChars, + drawSelection, + rectangularSelection, + crosshairCursor, + dropCursor +} from "@codemirror/view"; +import { + EditorState, + Compartment +} from "@codemirror/state"; +import { + defaultKeymap, + insertTab, + insertNewline, + history, + historyKeymap +} from "@codemirror/commands"; +import { + bracketMatching +} from "@codemirror/language"; +import { + search, + searchKeymap, + highlightSelectionMatches +} from "@codemirror/search"; + +import {statusBar} from "../utils/statusBar.mjs"; +import {fileDetailsPanel} from "../utils/fileDetails.mjs"; +import {eolCodeToSeq, eolCodeToName, renderSpecialChar} from "../utils/editorUtils.mjs"; + + +/** + * Waiter to handle events related to the input. + */ +class InputWaiter { + + /** + * InputWaiter constructor. + * + * @param {App} app - The main view object for CyberChef. + * @param {Manager} manager - The CyberChef event manager. + */ + constructor(app, manager) { + this.app = app; + this.manager = manager; + + this.inputTextEl = document.getElementById("input-text"); + this.inputChrEnc = 0; + this.eolState = 0; // 0 = unset, 1 = detected, 2 = manual + this.encodingState = 0; // 0 = unset, 1 = detected, 2 = manual + this.initEditor(); + + this.inputWorker = null; + this.loaderWorkers = []; + this.workerId = 0; + this.maxTabs = this.manager.tabs.calcMaxTabs(); + this.callbacks = {}; + this.callbackID = 0; + this.fileDetails = {}; + + this.maxWorkers = 1; + if (navigator.hardwareConcurrency !== undefined && + navigator.hardwareConcurrency > 1) { + // Subtract 1 from hardwareConcurrency value to avoid using + // the entire available resources + this.maxWorkers = navigator.hardwareConcurrency - 1; + } + } + + /** + * Sets up the CodeMirror Editor + */ + initEditor() { + // Mutable extensions + this.inputEditorConf = { + eol: new Compartment, + lineWrapping: new Compartment, + fileDetailsPanel: new Compartment + }; + + const self = this; + const initialState = EditorState.create({ + doc: null, + extensions: [ + // Editor extensions + history(), + highlightSpecialChars({ + render: renderSpecialChar // Custom character renderer to handle special cases + }), + drawSelection(), + rectangularSelection(), + crosshairCursor(), + dropCursor(), + bracketMatching(), + highlightSelectionMatches(), + search({top: true}), + EditorState.allowMultipleSelections.of(true), + + // Custom extensions + statusBar({ + label: "输入", + eolHandler: this.eolChange.bind(this), + chrEncHandler: this.chrEncChange.bind(this), + chrEncGetter: this.getChrEnc.bind(this), + getEncodingState: this.getEncodingState.bind(this), + getEOLState: this.getEOLState.bind(this) + }), + + // Mutable state + this.inputEditorConf.fileDetailsPanel.of([]), + this.inputEditorConf.lineWrapping.of(EditorView.lineWrapping), + this.inputEditorConf.eol.of(EditorState.lineSeparator.of("\n")), + + // Keymap + keymap.of([ + // Explicitly insert a tab rather than indenting the line + { key: "Tab", run: insertTab }, + // Explicitly insert a new line (using the current EOL char) rather + // than messing around with indenting, which does not respect EOL chars + { key: "Enter", run: insertNewline }, + ...historyKeymap, + ...defaultKeymap, + ...searchKeymap + ]), + + // Event listeners + EditorView.updateListener.of(e => { + if (e.selectionSet) + this.manager.highlighter.selectionChange("input", e); + if (e.docChanged && !this.silentInputChange) + this.inputChange(e); + this.silentInputChange = false; + }), + + // Event handlers + EditorView.domEventHandlers({ + paste(event, view) { + const clipboardData = event.clipboardData; + const items = clipboardData.items; + let files = []; + for (let i = 0; i < items.length; i++) { + const item = items[i]; + if (item.kind === "string") { + // If there are any string items they should be preferred over + // files. + files = []; + break; + } else if (item.kind === "file") { + files.push(item.getAsFile()); + } + } + if (files.length > 0) { + // Prevent the default paste behavior, afterPaste will load the files instead + event.preventDefault(); + } + setTimeout(() => { + self.afterPaste(files); + }); + } + }) + ] + }); + + + if (this.inputEditorView) this.inputEditorView.destroy(); + this.inputEditorView = new EditorView({ + state: initialState, + parent: this.inputTextEl + }); + } + + /** + * Handler for EOL change events + * Sets the line separator + * @param {string} eol + * @param {boolean} [manual=false] + */ + eolChange(eol, manual=false) { + const eolVal = eolCodeToSeq[eol]; + if (eolVal === undefined) return; + + this.eolState = manual ? 2 : this.eolState; + if (this.eolState < 2 && eolVal === this.getEOLSeq()) return; + + if (this.eolState === 1) { + // Alert + this.app.alert(`已检测到输入换行符并自动更换为: ${eolCodeToName[eol]}`, 5000); + } + + // Update the EOL value + const oldInputVal = this.getInput(); + this.inputEditorView.dispatch({ + effects: this.inputEditorConf.eol.reconfigure(EditorState.lineSeparator.of(eolVal)) + }); + + // Reset the input so that lines are recalculated, preserving the old EOL values + this.setInput(oldInputVal); + } + + /** + * Getter for the input EOL sequence + * @returns {string} + */ + getEOLSeq() { + return this.inputEditorView.state.lineBreak; + } + + /** + * Returns whether the input EOL sequence was set manually or has been detected automatically + * @returns {number} - 0 = unset, 1 = detected, 2 = manual + */ + getEOLState() { + return this.eolState; + } + + /** + * Handler for Chr Enc change events + * Sets the input character encoding + * @param {number} chrEncVal + * @param {boolean} [manual=false] - Flag to indicate the encoding was set by the user + * @param {boolean} [internal=false] - Flag to indicate this was set internally, i.e. by loading from URI + */ + chrEncChange(chrEncVal, manual=false, internal=false) { + if (typeof chrEncVal !== "number") return; + this.inputChrEnc = chrEncVal; + this.encodingState = manual ? 2 : this.encodingState; + if (!internal) { + this.inputChange(); + } + } + + /** + * Getter for the input character encoding + * @returns {number} + */ + getChrEnc() { + return this.inputChrEnc; + } + + /** + * Returns whether the input character encoding was set manually or has been detected automatically + * @returns {number} - 0 = unset, 1 = detected, 2 = manual + */ + getEncodingState() { + return this.encodingState; + } + + /** + * Sets word wrap on the input editor + * @param {boolean} wrap + */ + setWordWrap(wrap) { + this.inputEditorView.dispatch({ + effects: this.inputEditorConf.lineWrapping.reconfigure( + wrap ? EditorView.lineWrapping : [] + ) + }); + } + + /** + * Gets the value of the current input + * @returns {string} + */ + getInput() { + const doc = this.inputEditorView.state.doc; + const eol = this.getEOLSeq(); + return doc.sliceString(0, doc.length, eol); + } + + /** + * Sets the value of the current input + * @param {string} data + * @param {boolean} [silent=false] + */ + setInput(data, silent=false) { + const lineLengthThreshold = 131072; // 128KB + let wrap = this.app.options.wordWrap; + if (data.length > lineLengthThreshold) { + const lines = data.split(this.getEOLSeq()); + const longest = lines.reduce((a, b) => + a > b.length ? a : b.length, 0 + ); + if (longest > lineLengthThreshold) { + // If we are exceeding the max line length, turn off word wrap + wrap = false; + this.app.alert("超过最大行数,为防止影响性能自动换行将被暂时关闭。", 20000); + } + } + + // If turning word wrap off, do it before we populate the editor for performance reasons + if (!wrap) this.setWordWrap(wrap); + + // We use setTimeout here to delay the editor dispatch until the next event cycle, + // ensuring all async actions have completed before attempting to set the contents + // of the editor. This is mainly with the above call to setWordWrap() in mind. + setTimeout(() => { + // Insert data into editor, overwriting any previous contents + this.silentInputChange = silent; + this.inputEditorView.dispatch({ + changes: { + from: 0, + to: this.inputEditorView.state.doc.length, + insert: data + } + }); + + // If turning word wrap on, do it after we populate the editor + if (wrap) + setTimeout(() => { + this.setWordWrap(wrap); + }); + }); + } + + /** + * Calculates the maximum number of tabs to display + */ + calcMaxTabs() { + const numTabs = this.manager.tabs.calcMaxTabs(); + if (this.inputWorker && this.maxTabs !== numTabs) { + this.maxTabs = numTabs; + this.inputWorker.postMessage({ + action: "updateMaxTabs", + data: { + maxTabs: numTabs, + activeTab: this.manager.tabs.getActiveTab("input") + } + }); + } + } + + /** + * Terminates any existing workers and sets up a new InputWorker and LoaderWorker + */ + setupInputWorker() { + if (this.inputWorker !== null) { + this.inputWorker.terminate(); + this.inputWorker = null; + } + + for (let i = this.loaderWorkers.length - 1; i >= 0; i--) { + this.removeLoaderWorker(this.loaderWorkers[i]); + } + + log.debug("Adding new InputWorker"); + this.inputWorker = new InputWorker(); + this.inputWorker.postMessage({ + action: "setLogLevel", + data: log.getLevel() + }); + this.inputWorker.postMessage({ + action: "updateMaxWorkers", + data: this.maxWorkers + }); + this.inputWorker.postMessage({ + action: "updateMaxTabs", + data: { + maxTabs: this.maxTabs, + activeTab: this.manager.tabs.getActiveTab("input") + } + }); + + this.inputWorker.addEventListener("message", this.handleInputWorkerMessage.bind(this)); + } + + /** + * Activates a loaderWorker and sends it to the InputWorker + */ + activateLoaderWorker() { + const workerIdx = this.addLoaderWorker(); + if (workerIdx === -1) return; + + const workerObj = this.loaderWorkers[workerIdx]; + this.inputWorker.postMessage({ + action: "loaderWorkerReady", + data: { + id: workerObj.id + } + }); + } + + /** + * Adds a new loaderWorker + * + * @returns {number} - The index of the created worker + */ + addLoaderWorker() { + if (this.loaderWorkers.length === this.maxWorkers) { + return -1; + } + log.debug(`Adding new LoaderWorker (${this.loaderWorkers.length + 1}/${this.maxWorkers}).`); + const newWorker = new LoaderWorker(); + const workerId = this.workerId++; + newWorker.addEventListener("message", this.handleLoaderMessage.bind(this)); + newWorker.postMessage({ + action: "setLogLevel", + data: log.getLevel() + }); + newWorker.postMessage({ + action: "setID", + data: { + id: workerId + } + }); + const newWorkerObj = { + worker: newWorker, + id: workerId + }; + this.loaderWorkers.push(newWorkerObj); + return this.loaderWorkers.indexOf(newWorkerObj); + } + + /** + * Removes a loaderworker + * + * @param {Object} workerObj - Object containing the loaderWorker and its id + * @param {LoaderWorker} workerObj.worker - The actual loaderWorker + * @param {number} workerObj.id - The ID of the loaderWorker + */ + removeLoaderWorker(workerObj) { + const idx = this.loaderWorkers.indexOf(workerObj); + if (idx === -1) { + return; + } + log.debug(`Terminating worker ${this.loaderWorkers[idx].id}`); + this.loaderWorkers[idx].worker.terminate(); + this.loaderWorkers.splice(idx, 1); + } + + /** + * Finds and returns the object for the loaderWorker of a given id + * + * @param {number} id - The ID of the loaderWorker to find + * @returns {object} + */ + getLoaderWorker(id) { + const idx = this.getLoaderWorkerIndex(id); + if (idx === -1) return; + return this.loaderWorkers[idx]; + } + + /** + * Gets the index for the loaderWorker of a given id + * + * @param {number} id - The ID of hte loaderWorker to find + * @returns {number} The current index of the loaderWorker in the array + */ + getLoaderWorkerIndex(id) { + for (let i = 0; i < this.loaderWorkers.length; i++) { + if (this.loaderWorkers[i].id === id) { + return i; + } + } + return -1; + } + + /** + * Sends an input to be loaded to the loaderWorker + * + * @param {object} inputData - Object containing the input to be loaded + * @param {File} inputData.file - The actual file object to load + * @param {number} inputData.inputNum - The inputNum for the file object + * @param {number} inputData.workerId - The ID of the loaderWorker that will load it + */ + loadInput(inputData) { + const idx = this.getLoaderWorkerIndex(inputData.workerId); + if (idx === -1) return; + this.loaderWorkers[idx].worker.postMessage({ + action: "loadFile", + data: { + file: inputData.file, + inputNum: inputData.inputNum + } + }); + } + + /** + * Handler for messages sent back by the loaderWorker + * Sends the message straight to the inputWorker to be handled there. + * + * @param {MessageEvent} e + */ + handleLoaderMessage(e) { + const r = e.data; + + if (Object.prototype.hasOwnProperty.call(r, "progress") && + Object.prototype.hasOwnProperty.call(r, "inputNum")) { + this.manager.tabs.updateTabProgress(r.inputNum, r.progress, 100, "input"); + } + + const transferable = Object.prototype.hasOwnProperty.call(r, "fileBuffer") ? [r.fileBuffer] : undefined; + this.inputWorker.postMessage({ + action: "loaderWorkerMessage", + data: r + }, transferable); + } + + + /** + * Handler for messages sent back by the InputWorker + * + * @param {MessageEvent} e + */ + handleInputWorkerMessage(e) { + const r = e.data; + + if (!("action" in r)) { + log.error("A message was received from the InputWorker with no action property. Ignoring message."); + return; + } + + log.debug(`Receiving '${r.action}' from InputWorker.`); + + switch (r.action) { + case "activateLoaderWorker": + this.activateLoaderWorker(); + break; + case "loadInput": + this.loadInput(r.data); + break; + case "terminateLoaderWorker": + this.removeLoaderWorker(this.getLoaderWorker(r.data)); + break; + case "refreshTabs": + this.refreshTabs(r.data.nums, r.data.activeTab, r.data.tabsLeft, r.data.tabsRight); + break; + case "changeTab": + this.changeTab(r.data, this.app.options.syncTabs); + break; + case "updateTabHeader": + this.manager.tabs.updateTabHeader(r.data.inputNum, r.data.input, "input"); + break; + case "loadingInfo": + this.showLoadingInfo(r.data, true); + break; + case "setInput": + this.set(r.data.inputNum, r.data.inputObj, r.data.silent); + break; + case "inputAdded": + this.inputAdded(r.data.changeTab, r.data.inputNum); + break; + case "queueInput": + this.manager.worker.queueInput(r.data); + break; + case "queueInputError": + this.manager.worker.queueInputError(r.data); + break; + case "bakeInputs": + this.manager.worker.bakeInputs(r.data); + break; + case "displayTabSearchResults": + this.displayTabSearchResults(r.data); + break; + case "filterTabError": + this.app.handleError(r.data); + break; + case "setUrl": + this.app.updateURL(r.data.includeInput, r.data.input); + break; + case "getInput": + case "getInputNums": + this.callbacks[r.data.id](r.data); + break; + case "removeChefWorker": + this.removeChefWorker(); + break; + case "fileLoaded": + this.fileLoaded(r.data.inputNum); + break; + default: + log.error(`Unknown action ${r.action}.`); + } + } + + /** + * Sends a message to the inputWorker to bake all inputs + */ + bakeAll() { + this.app.progress = 0; + debounce(this.manager.controls.toggleBakeButtonFunction, 20, "toggleBakeButton", this, ["loading"]); + this.inputWorker.postMessage({ + action: "bakeAll" + }); + } + + /** + * Sets the input in the input area + * + * @param {number} inputNum + * @param {Object} inputData - Object containing the input and its metadata + * @param {string} type + * @param {ArrayBuffer} buffer + * @param {string} stringSample + * @param {Object} file + * @param {string} file.name + * @param {number} file.size + * @param {string} file.type + * @param {string} status + * @param {number} progress + * @param {number} encoding + * @param {string} eolSequence + * @param {boolean} [silent=false] - If false, fires the manager statechange event + */ + async set(inputNum, inputData, silent=false) { + return new Promise(function(resolve, reject) { + const activeTab = this.manager.tabs.getActiveTab("input"); + if (inputNum !== activeTab) { + this.changeTab(inputNum, this.app.options.syncTabs); + return; + } + + // Update current character encoding + this.inputChrEnc = inputData.encoding; + + // Update current eol sequence + this.inputEditorView.dispatch({ + effects: this.inputEditorConf.eol.reconfigure( + EditorState.lineSeparator.of(inputData.eolSequence) + ) + }); + + // Handle file previews + if (inputData.file) { + this.setFile(inputNum, inputData); + } else { + this.clearFile(inputNum); + } + + // Decode the data to a string + this.manager.timing.recordTime("inputEncodingStart", inputNum); + let inputVal; + if (this.getChrEnc() > 0) { + inputVal = cptable.utils.decode(this.inputChrEnc, new Uint8Array(inputData.buffer)); + } else { + inputVal = Utils.arrayBufferToStr(inputData.buffer); + } + this.manager.timing.recordTime("inputEncodingEnd", inputNum); + + // Populate the input editor + this.setInput(inputVal, silent); + + // Set URL to current input + if (inputVal.length >= 0 && inputVal.length <= 51200) { + const inputStr = toBase64(inputVal, "A-Za-z0-9+/"); + this.app.updateURL(true, inputStr); + } + }.bind(this)); + } + + /** + * Displays file details + * + * @param {number} inputNum + * @param {Object} inputData - Object containing the input and its metadata + * @param {string} type + * @param {ArrayBuffer} buffer + * @param {string} stringSample + * @param {Object} file + * @param {string} file.name + * @param {number} file.size + * @param {string} file.type + * @param {string} status + * @param {number} progress + */ + setFile(inputNum, inputData) { + const activeTab = this.manager.tabs.getActiveTab("input"); + if (inputNum !== activeTab) return; + + // Create file details panel + this.fileDetails = { + fileDetails: inputData.file, + progress: inputData.progress, + status: inputData.status, + buffer: inputData.buffer, + renderPreview: this.app.options.imagePreview, + toggleHandler: this.toggleFileDetails.bind(this), + hidden: false + }; + this.inputEditorView.dispatch({ + effects: this.inputEditorConf.fileDetailsPanel.reconfigure( + fileDetailsPanel(this.fileDetails) + ) + }); + } + + /** + * Clears the file details panel + * + * @param {number} inputNum + */ + clearFile(inputNum) { + const activeTab = this.manager.tabs.getActiveTab("input"); + if (inputNum !== activeTab) return; + + // Clear file details panel + this.inputEditorView.dispatch({ + effects: this.inputEditorConf.fileDetailsPanel.reconfigure([]) + }); + } + + /** + * Handler for file details toggle clicks + * @param {event} e + */ + toggleFileDetails(e) { + $("[data-toggle='tooltip']").tooltip("hide"); + this.fileDetails.hidden = !this.fileDetails.hidden; + this.inputEditorView.dispatch({ + effects: this.inputEditorConf.fileDetailsPanel.reconfigure( + fileDetailsPanel(this.fileDetails) + ) + }); + } + + /** + * Update file details when a file completes loading + * + * @param {number} inputNum - The inputNum of the input which has finished loading + */ + fileLoaded(inputNum) { + this.manager.tabs.updateTabProgress(inputNum, 100, 100, "input"); + + const activeTab = this.manager.tabs.getActiveTab("input"); + if (activeTab !== inputNum) return; + + this.inputWorker.postMessage({ + action: "setInput", + data: { + inputNum: inputNum, + silent: false + } + }); + + this.updateFileProgress(inputNum, 100); + } + + /** + * Updates the displayed load progress for a file + * + * @param {number} inputNum + * @param {number | string} progress - Either a number or "error" + */ + updateFileProgress(inputNum, progress) { + const activeTab = this.manager.tabs.getActiveTab("input"); + if (inputNum !== activeTab) return; + + this.fileDetails.progress = progress; + if (progress === "error") this.fileDetails.status = "error"; + this.inputEditorView.dispatch({ + effects: this.inputEditorConf.fileDetailsPanel.reconfigure( + fileDetailsPanel(this.fileDetails) + ) + }); + } + + /** + * Updates the stored value for the specified inputNum + * + * @param {number} inputNum + * @param {string | ArrayBuffer} value + */ + updateInputValue(inputNum, value, force=false) { + // Prepare the value as a buffer (full value) and a string sample (up to 4096 bytes) + let buffer; + let stringSample = ""; + + // If value is a string, interpret it using the specified character encoding + const tabNum = this.manager.tabs.getActiveTab("input"); + this.manager.timing.recordTime("inputEncodingStart", tabNum); + if (typeof value === "string") { + stringSample = value.slice(0, 4096); + if (this.getChrEnc() > 0) { + buffer = cptable.utils.encode(this.getChrEnc(), value); + buffer = new Uint8Array(buffer).buffer; + } else { + buffer = Utils.strToArrayBuffer(value); + } + } else { + buffer = value; + stringSample = Utils.arrayBufferToStr(value.slice(0, 4096)); + } + this.manager.timing.recordTime("inputEncodingEnd", tabNum); + + // Update the deep link + const recipeStr = buffer.byteLength < 51200 ? toBase64(buffer, "A-Za-z0-9+/") : ""; // B64 alphabet with no padding + const includeInput = recipeStr.length > 0 && buffer.byteLength < 51200; + this.app.updateURL(includeInput, recipeStr); + + // Post new value to the InputWorker + const transferable = [buffer]; + this.inputWorker.postMessage({ + action: "updateInputValue", + data: { + inputNum: inputNum, + buffer: buffer, + stringSample: stringSample, + encoding: this.getChrEnc(), + eolSequence: this.getEOLSeq() + } + }, transferable); + } + + /** + * Get the input value for the specified input + * + * @param {number} inputNum - The inputNum of the input to retrieve from the inputWorker + * @returns {ArrayBuffer | string} + */ + async getInputValue(inputNum) { + return await new Promise(resolve => { + this.getInputFromWorker(inputNum, false, r => { + resolve(r.data); + }); + }); + } + + /** + * Get the input object for the specified input + * + * @param {number} inputNum - The inputNum of the input to retrieve from the inputWorker + * @returns {object} + */ + async getInputObj(inputNum) { + return await new Promise(resolve => { + this.getInputFromWorker(inputNum, true, r => { + resolve(r.data); + }); + }); + } + + /** + * Gets the specified input from the inputWorker + * + * @param {number} inputNum - The inputNum of the data to get + * @param {boolean} getObj - If true, get the actual data object of the input instead of just the value + * @param {Function} callback - The callback to execute when the input is returned + * @returns {ArrayBuffer | string | object} + */ + getInputFromWorker(inputNum, getObj, callback) { + const id = this.callbackID++; + + this.callbacks[id] = callback; + + this.inputWorker.postMessage({ + action: "getInput", + data: { + inputNum: inputNum, + getObj: getObj, + id: id + } + }); + } + + /** + * Gets the number of inputs from the inputWorker + * + * @returns {object} + */ + async getInputNums() { + return await new Promise(resolve => { + this.getNums(r => { + resolve(r); + }); + }); + } + + /** + * Gets a list of inputNums from the inputWorker, and sends + * them back to the specified callback + */ + getNums(callback) { + const id = this.callbackID++; + + this.callbacks[id] = callback; + + this.inputWorker.postMessage({ + action: "getInputNums", + data: id + }); + } + + /** + * Handler for input change events. + * Updates the value stored in the inputWorker + * Debounces the input so we don't call autobake too often. + * + * @param {event} e + * + * @fires Manager#statechange + */ + inputChange(e) { + // Change debounce delay based on input length + const inputLength = this.inputEditorView.state.doc.length; + let delay; + if (inputLength < 10000) delay = 20; + else if (inputLength < 100000) delay = 50; + else if (inputLength < 1000000) delay = 200; + else delay = 500; + + debounce(function(e) { + const value = this.getInput(); + const activeTab = this.manager.tabs.getActiveTab("input"); + + this.updateInputValue(activeTab, value); + this.inputWorker.postMessage({ + action: "updateTabHeader", + data: activeTab + }); + + // Fire the statechange event as the input has been modified + window.dispatchEvent(this.manager.statechange); + }, delay, "inputChange", this, [e])(); + } + + /** + * Handler that fires just after input paste events. + * Checks whether the EOL separator or character encoding should be updated. + * + * @param {File[]} files - An array of any files that were included in the paste event + */ + afterPaste(files) { + if (files.length > 0) { + this.loadUIFiles(files); + } + // If EOL has been fixed, skip this. + if (this.eolState > 1) return; + + const inputText = this.getInput(); + + // Detect most likely EOL sequence + const eolCharCounts = { + "LF": inputText.count("\u000a"), + "VT": inputText.count("\u000b"), + "FF": inputText.count("\u000c"), + "CR": inputText.count("\u000d"), + "CRLF": inputText.count("\u000d\u000a"), + "NEL": inputText.count("\u0085"), + "LS": inputText.count("\u2028"), + "PS": inputText.count("\u2029") + }; + + // If all zero, leave alone + const total = Object.values(eolCharCounts).reduce((acc, curr) => { + return acc + curr; + }, 0); + if (total === 0) return; + + // Find most prevalent line ending sequence + const highest = Object.entries(eolCharCounts).reduce((acc, curr) => { + return curr[1] > acc[1] ? curr : acc; + }, ["LF", 0]); + let choice = highest[0]; + + // If CRLF not zero and more than half the highest alternative, choose CRLF + if ((eolCharCounts.CRLF * 2) > highest[1]) { + choice = "CRLF"; + } + + const eolVal = eolCodeToSeq[choice]; + if (eolVal === this.getEOLSeq()) return; + + // Setting automatically + this.eolState = 1; + this.eolChange(choice); + } + + /** + * Handler for input dragover events. + * Gives the user a visual cue to show that items can be dropped here. + * + * @param {event} e + */ + inputDragover(e) { + // This will be set if we're dragging an operation + if (e.dataTransfer.effectAllowed === "move") + return false; + + e.stopPropagation(); + e.preventDefault(); + e.target.closest("#input-text").classList.add("dropping-file"); + } + + /** + * Handler for input dragleave events. + * Removes the visual cue. + * + * @param {event} e + */ + inputDragleave(e) { + e.stopPropagation(); + e.preventDefault(); + + // Dragleave often fires when moving between lines in the editor. + // If the from element is within the input-text element, we are still on target. + if (!this.inputTextEl.contains(e.fromElement)) { + e.target.closest("#input-text").classList.remove("dropping-file"); + } + } + + /** + * Handler for input drop events. + * Loads the dragged data. + * + * @param {event} e + */ + async inputDrop(e) { + // This will be set if we're dragging an operation + if (e.dataTransfer.effectAllowed === "move") + return false; + + e.stopPropagation(); + e.preventDefault(); + e.target.closest("#input-text").classList.remove("dropping-file"); + + // Dropped text is handled by the editor itself + if (e.dataTransfer.getData("Text")) return; + + // Dropped files + if (e?.dataTransfer?.files?.length > 0) { + let files = []; + + // Handling the files as FileSystemEntry objects allows us to open directories, + // but relies on a function that may be deprecated in future. + if (Object.prototype.hasOwnProperty.call(DataTransferItem.prototype, "webkitGetAsEntry")) { + const fileEntries = await this.getAllFileEntries(e.dataTransfer.items); + // Read all FileEntry objects into File objects + files = await Promise.all(fileEntries.map(async fe => await this.getFile(fe))); + } else { + files = e.dataTransfer.files; + } + + this.loadUIFiles(files); + } + } + + /** + * + * @param {DataTransferItemList} dataTransferItemList + * @returns {FileSystemEntry[]} + */ + async getAllFileEntries(dataTransferItemList) { + const fileEntries = []; + // Use BFS to traverse entire directory/file structure + const queue = []; + // Unfortunately dataTransferItemList is not iterable i.e. no forEach + for (let i = 0; i < dataTransferItemList.length; i++) { + // Note webkitGetAsEntry a non-standard feature and may change + // Usage is necessary for handling directories + queue.push(dataTransferItemList[i].webkitGetAsEntry()); + } + while (queue.length > 0) { + const entry = queue.shift(); + if (entry.isFile) { + fileEntries.push(entry); + } else if (entry.isDirectory) { + queue.push(...await this.readAllDirectoryEntries(entry.createReader())); + } + } + return fileEntries; + } + + /** + * Get all the entries (files or sub-directories) in a directory by calling + * readEntries until it returns empty array + * + * @param {FileSystemDirectoryReader} directoryReader + * @returns {FileSystemEntry[]} + */ + async readAllDirectoryEntries(directoryReader) { + const entries = []; + let readEntries = await this.readEntriesPromise(directoryReader); + while (readEntries.length > 0) { + entries.push(...readEntries); + readEntries = await this.readEntriesPromise(directoryReader); + } + return entries; + } + + /** + * Wrap readEntries in a promise to make working with readEntries easier. + * readEntries will return only some of the entries in a directory + * e.g. Chrome returns at most 100 entries at a time + * + * @param {FileSystemDirectoryReader} directoryReader + * @returns {Promise} + */ + async readEntriesPromise(directoryReader) { + try { + return await new Promise((resolve, reject) => { + directoryReader.readEntries(resolve, reject); + }); + } catch (err) { + log.error(err); + } + } + + /** + * Reads a FileEntry and returns it as a File object + * @param {FileEntry} fileEntry + * @returns {File} + */ + async getFile(fileEntry) { + try { + return new Promise((resolve, reject) => fileEntry.file(resolve, reject)); + } catch (err) { + log.error(err); + } + } + + /** + * Handler for open input button events + * Loads the opened data into the input textarea + * + * @param {event} e + */ + inputOpen(e) { + e.preventDefault(); + + if (e.target.files.length > 0) { + this.loadUIFiles(e.target.files); + e.target.value = ""; + } + } + + /** + * Handler for open input button click. + * Opens the open file dialog. + */ + inputOpenClick() { + document.getElementById("open-file").click(); + } + + /** + * Handler for open folder button click + * Opens the open folder dialog. + */ + folderOpenClick() { + document.getElementById("open-folder").click(); + } + + /** + * Load files from the UI into the inputWorker + * + * @param {FileList} files - The list of files to be loaded + */ + loadUIFiles(files) { + const numFiles = files.length; + const activeTab = this.manager.tabs.getActiveTab("input"); + log.debug(`Loading ${numFiles} files.`); + + // Display the number of files as pending so the user + // knows that we've received the files. + this.showLoadingInfo({ + pending: numFiles, + loading: 0, + loaded: 0, + total: numFiles, + activeProgress: { + inputNum: activeTab, + progress: 0 + } + }, false); + + this.inputWorker.postMessage({ + action: "loadUIFiles", + data: { + files: files, + activeTab: activeTab + } + }); + } + + /** + * Display the loaded files information in the input header. + * Also, sets the background of the Input header to be a progress bar + * @param {object} loadedData - Object containing the loading information + * @param {number} loadedData.pending - How many files are pending (not loading / loaded) + * @param {number} loadedData.loading - How many files are being loaded + * @param {number} loadedData.loaded - How many files have been loaded + * @param {number} loadedData.total - The total number of files + * @param {object} loadedData.activeProgress - Object containing data about the active tab + * @param {number} loadedData.activeProgress.inputNum - The inputNum of the input the progress is for + * @param {number} loadedData.activeProgress.progress - The loading progress of the active input + * @param {boolean} autoRefresh - If true, automatically refreshes the loading info by sending a message to the inputWorker after 100ms + */ + showLoadingInfo(loadedData, autoRefresh) { + const pending = loadedData.pending; + const loading = loadedData.loading; + const loaded = loadedData.loaded; + const total = loadedData.total; + + let width = total.toLocaleString().length; + width = width < 2 ? 2 : width; + + const totalStr = total.toLocaleString().padStart(width, " ").replace(/ /g, " "); + let msg = "总计: " + totalStr; + + const loadedStr = loaded.toLocaleString().padStart(width, " ").replace(/ /g, " "); + msg += "
        已载入: " + loadedStr; + + if (pending > 0) { + const pendingStr = pending.toLocaleString().padStart(width, " ").replace(/ /g, " "); + msg += "
        等待中: " + pendingStr; + } else if (loading > 0) { + const loadingStr = loading.toLocaleString().padStart(width, " ").replace(/ /g, " "); + msg += "
        载入中: " + loadingStr; + } + + const inFiles = document.getElementById("input-files-info"); + if (total > 1) { + inFiles.innerHTML = msg; + inFiles.style.display = ""; + } else { + inFiles.style.display = "none"; + } + + this.updateFileProgress(loadedData.activeProgress.inputNum, loadedData.activeProgress.progress); + + const inputTitle = document.getElementById("input").firstElementChild; + if (loaded < total) { + const percentComplete = loaded / total * 100; + inputTitle.style.background = `linear-gradient(to right, var(--title-background-colour) ${percentComplete}%, var(--primary-background-colour) ${percentComplete}%)`; + } else { + inputTitle.style.background = ""; + } + + if (loaded < total && autoRefresh) { + setTimeout(function() { + this.inputWorker.postMessage({ + action: "getLoadProgress", + data: this.manager.tabs.getActiveTab("input") + }); + }.bind(this), 100); + } + } + + /** + * Change to a different tab. + * + * @param {number} inputNum - The inputNum of the tab to change to + * @param {boolean} [changeOutput=false] - If true, also changes the output + */ + changeTab(inputNum, changeOutput=false) { + if (this.manager.tabs.getTabItem(inputNum, "input") !== null) { + this.manager.tabs.changeTab(inputNum, "input"); + this.inputWorker.postMessage({ + action: "setInput", + data: { + inputNum: inputNum, + silent: true + } + }); + } else { + const minNum = Math.min(...this.manager.tabs.getTabList("input")); + let direction = "right"; + if (inputNum < minNum) { + direction = "left"; + } + this.inputWorker.postMessage({ + action: "refreshTabs", + data: { + inputNum: inputNum, + direction: direction + } + }); + } + + if (changeOutput) { + this.manager.output.changeTab(inputNum, false); + } + + // Set cursor focus to current tab + this.inputEditorView.focus(); + } + + /** + * Handler for clicking on a tab + * + * @param {event} mouseEvent + */ + changeTabClick(mouseEvent) { + if (!mouseEvent.target) return; + + const tabNum = mouseEvent.target.parentElement.getAttribute("inputNum"); + if (tabNum >= 0) { + this.changeTab(parseInt(tabNum, 10), this.app.options.syncTabs); + } + } + + /** + * Handler for clear all IO events. + * Resets the input, output and info areas, and creates a new inputWorker + */ + clearAllIoClick() { + this.manager.worker.cancelBake(true, true); + this.manager.worker.loaded = false; + this.manager.output.removeAllOutputs(); + this.manager.output.terminateZipWorker(); + + this.eolState = 0; + this.encodingState = 0; + this.manager.output.eolState = 0; + this.manager.output.encodingState = 0; + + this.initEditor(); + this.manager.output.initEditor(); + + const tabsList = document.getElementById("input-tabs"); + const tabsListChildren = tabsList.children; + + tabsList.classList.remove("tabs-left"); + tabsList.classList.remove("tabs-right"); + for (let i = tabsListChildren.length - 1; i >= 0; i--) { + tabsListChildren.item(i).remove(); + } + + this.showLoadingInfo({ + pending: 0, + loading: 0, + loaded: 1, + total: 1, + activeProgress: { + inputNum: 1, + progress: 100 + } + }); + + this.setupInputWorker(); + this.manager.worker.setupChefWorker(); + this.addInput(true); + } + + /** + * Sets the console log level in the workers. + */ + setLogLevel() { + this.loaderWorkers.forEach(w => { + w.postMessage({ + action: "setLogLevel", + data: log.getLevel() + }); + }); + + if (!this.inputWorker) return; + this.inputWorker.postMessage({ + action: "setLogLevel", + data: log.getLevel() + }); + } + + /** + * Sends a message to the inputWorker to add a new input. + * @param {boolean} [changeTab=false] - If true, changes the tab to the new input + */ + addInput(changeTab=false) { + if (!this.inputWorker) return; + this.inputWorker.postMessage({ + action: "addInput", + data: changeTab + }); + } + + /** + * Handler for add input button clicked. + */ + addInputClick() { + this.addInput(true); + } + + /** + * Handler for when the inputWorker adds a new input + * + * @param {boolean} changeTab - Whether or not to change to the new input tab + * @param {number} inputNum - The new inputNum + */ + inputAdded(changeTab, inputNum) { + this.addTab(inputNum, changeTab); + + this.manager.output.addOutput(inputNum, changeTab); + this.manager.worker.addChefWorker(); + } + + /** + * Remove a chefWorker from the workerWaiter if we remove an input + */ + removeChefWorker() { + const workerIdx = this.manager.worker.getInactiveChefWorker(true); + const worker = this.manager.worker.chefWorkers[workerIdx]; + this.manager.worker.removeChefWorker(worker); + } + + /** + * Adds a new input tab. + * + * @param {number} inputNum - The inputNum of the new tab + * @param {boolean} [changeTab=true] - If true, changes to the new tab once it's been added + */ + addTab(inputNum, changeTab=true) { + const tabsWrapper = document.getElementById("input-tabs"), + numTabs = tabsWrapper.children.length; + + if (!this.manager.tabs.getTabItem(inputNum, "input") && numTabs < this.maxTabs) { + const newTab = this.manager.tabs.createTabElement(inputNum, changeTab, "input"); + tabsWrapper.appendChild(newTab); + + if (numTabs > 0) { + this.manager.tabs.showTabBar(); + } else { + this.manager.tabs.hideTabBar(); + } + + this.inputWorker.postMessage({ + action: "updateTabHeader", + data: inputNum + }); + } else if (numTabs === this.maxTabs) { + // Can't create a new tab + document.getElementById("input-tabs").lastElementChild.classList.add("tabs-right"); + } + + if (changeTab) this.changeTab(inputNum, false); + } + + /** + * Refreshes the input tabs, and changes to activeTab + * + * @param {number[]} nums - The inputNums to be displayed as tabs + * @param {number} activeTab - The tab to change to + * @param {boolean} tabsLeft - True if there are input tabs to the left of the displayed tabs + * @param {boolean} tabsRight - True if there are input tabs to the right of the displayed tabs + */ + refreshTabs(nums, activeTab, tabsLeft, tabsRight) { + this.manager.tabs.refreshTabs(nums, activeTab, tabsLeft, tabsRight, "input"); + + this.inputWorker.postMessage({ + action: "setInput", + data: { + inputNum: activeTab, + silent: true + } + }); + } + + /** + * Sends a message to the inputWorker to remove an input. + * If the input tab is on the screen, refreshes the tabs + * + * @param {number} inputNum - The inputNum of the tab to be removed + */ + removeInput(inputNum) { + let refresh = false; + if (this.manager.tabs.getTabItem(inputNum, "input") !== null) { + refresh = true; + } + this.inputWorker.postMessage({ + action: "removeInput", + data: { + inputNum: inputNum, + refreshTabs: refresh, + removeChefWorker: true + } + }); + + this.manager.output.removeTab(inputNum); + } + + /** + * Handler for clicking on a remove tab button + * + * @param {event} mouseEvent + */ + removeTabClick(mouseEvent) { + if (!mouseEvent.target) { + return; + } + const tabNum = mouseEvent.target.closest("button").parentElement.getAttribute("inputNum"); + if (tabNum) { + this.removeInput(parseInt(tabNum, 10)); + } + } + + /** + * Handler for scrolling on the input tabs area + * + * @param {event} wheelEvent + */ + scrollTab(wheelEvent) { + wheelEvent.preventDefault(); + + if (wheelEvent.deltaY > 0) { + this.changeTabLeft(); + } else if (wheelEvent.deltaY < 0) { + this.changeTabRight(); + } + } + + /** + * Handler for mouse down on the next tab button + */ + nextTabClick() { + this.mousedown = true; + this.changeTabRight(); + const time = 200; + const func = function(time) { + if (this.mousedown) { + this.changeTabRight(); + const newTime = (time > 50) ? time - 10 : 50; + setTimeout(func.bind(this, [newTime]), newTime); + } + }; + this.tabTimeout = setTimeout(func.bind(this, [time]), time); + } + + /** + * Handler for mouse down on the previous tab button + */ + previousTabClick() { + this.mousedown = true; + this.changeTabLeft(); + const time = 200; + const func = function(time) { + if (this.mousedown) { + this.changeTabLeft(); + const newTime = (time > 50) ? time - 10 : 50; + setTimeout(func.bind(this, [newTime]), newTime); + } + }; + this.tabTimeout = setTimeout(func.bind(this, [time]), time); + } + + /** + * Handler for mouse up event on the tab buttons + */ + tabMouseUp() { + this.mousedown = false; + + clearTimeout(this.tabTimeout); + this.tabTimeout = null; + } + + /** + * Changes to the next (right) tab + */ + changeTabRight() { + const activeTab = this.manager.tabs.getActiveTab("input"); + if (activeTab === -1) return; + this.inputWorker.postMessage({ + action: "changeTabRight", + data: { + activeTab: activeTab + } + }); + } + + /** + * Changes to the previous (left) tab + */ + changeTabLeft() { + const activeTab = this.manager.tabs.getActiveTab("input"); + if (activeTab === -1) return; + this.inputWorker.postMessage({ + action: "changeTabLeft", + data: { + activeTab: activeTab + } + }); + } + + /** + * Handler for go to tab button clicked + */ + async goToTab() { + const inputNums = await this.getInputNums(); + let tabNum = window.prompt(`输入标签页编号 (${inputNums.min} - ${inputNums.max}):`, this.manager.tabs.getActiveTab("input").toString()); + + if (tabNum === null) return; + tabNum = parseInt(tabNum, 10); + + this.changeTab(tabNum, this.app.options.syncTabs); + } + + /** + * Handler for find tab button clicked + */ + findTab() { + this.filterTabSearch(); + $("#input-tab-modal").modal(); + } + + /** + * Sends a message to the inputWorker to search the inputs + */ + filterTabSearch() { + const showPending = document.getElementById("input-show-pending").checked; + const showLoading = document.getElementById("input-show-loading").checked; + const showLoaded = document.getElementById("input-show-loaded").checked; + + const filter = document.getElementById("input-filter").value; + const filterType = document.getElementById("input-filter-button").innerText; + const numResults = parseInt(document.getElementById("input-num-results").value, 10); + + this.inputWorker.postMessage({ + action: "filterTabs", + data: { + showPending: showPending, + showLoading: showLoading, + showLoaded: showLoaded, + filter: filter, + filterType: filterType, + numResults: numResults + } + }); + } + + /** + * Handle when an option in the filter drop down box is clicked + * + * @param {event} mouseEvent + */ + filterOptionClick(mouseEvent) { + document.getElementById("input-filter-button").innerText = mouseEvent.target.innerText; + this.filterTabSearch(); + } + + /** + * Displays the results of a tab search in the find tab box + * + * @param {object[]} results - List of results objects + * + */ + displayTabSearchResults(results) { + const resultsList = document.getElementById("input-search-results"); + + for (let i = resultsList.children.length - 1; i >= 0; i--) { + resultsList.children.item(i).remove(); + } + + for (let i = 0; i < results.length; i++) { + const newListItem = document.createElement("li"); + newListItem.classList.add("input-filter-result"); + newListItem.setAttribute("inputNum", results[i].inputNum); + newListItem.innerText = `${results[i].inputNum}: ${results[i].textDisplay}`; + + resultsList.appendChild(newListItem); + } + } + + /** + * Handler for clicking on a filter result + * + * @param {event} e + */ + filterItemClick(e) { + if (!e.target) return; + const inputNum = parseInt(e.target.getAttribute("inputNum"), 10); + if (inputNum <= 0) return; + + $("#input-tab-modal").modal("hide"); + this.changeTab(inputNum, this.app.options.syncTabs); + } + + /** + * Handler for incoming postMessages + * If the events data has a `type` property set to `dataSubmit` + * the value property is set to the current input + * @param {event} e + * @param {object} e.data + * @param {string} e.data.type - the type of request, currently the only value is "dataSubmit" + * @param {string} e.data.value - the value of the message + */ + handlePostMessage(e) { + log.debug(e); + if ("data" in e && "id" in e.data && "value" in e.data) { + if (e.data.id === "setInput") { + this.setInput(e.data.value); + } + } + } +} + +export default InputWaiter; diff --git a/plugins/srktoolbox/src/web/waiters/OperationsWaiter.mjs b/plugins/srktoolbox/src/web/waiters/OperationsWaiter.mjs new file mode 100644 index 00000000..439fea15 --- /dev/null +++ b/plugins/srktoolbox/src/web/waiters/OperationsWaiter.mjs @@ -0,0 +1,316 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah + */ + +import HTMLOperation from "../HTMLOperation.mjs"; +import Sortable from "sortablejs"; +import {fuzzyMatch, calcMatchRanges} from "../../core/lib/FuzzyMatch.mjs"; + + +/** + * Waiter to handle events related to the operations. + */ +class OperationsWaiter { + + /** + * OperationsWaiter constructor. + * + * @param {App} app - The main view object for CyberChef. + * @param {Manager} manager - The CyberChef event manager. + */ + constructor(app, manager) { + this.app = app; + this.manager = manager; + + this.options = {}; + this.removeIntent = false; + } + + + /** + * Handler for search events. + * Finds operations which match the given search term and displays them under the search box. + * + * @param {event} e + */ + searchOperations(e) { + let ops, selected; + + if (e.type === "search" || e.keyCode === 13) { // Search or Return + e.preventDefault(); + ops = document.querySelectorAll("#search-results li"); + if (ops.length) { + selected = this.getSelectedOp(ops); + if (selected > -1) { + this.manager.recipe.addOperation(ops[selected].innerHTML); + } + } + } + + if (e.keyCode === 40) { // Down + e.preventDefault(); + ops = document.querySelectorAll("#search-results li"); + if (ops.length) { + selected = this.getSelectedOp(ops); + if (selected > -1) { + ops[selected].classList.remove("selected-op"); + } + if (selected === ops.length-1) selected = -1; + ops[selected+1].classList.add("selected-op"); + } + } else if (e.keyCode === 38) { // Up + e.preventDefault(); + ops = document.querySelectorAll("#search-results li"); + if (ops.length) { + selected = this.getSelectedOp(ops); + if (selected > -1) { + ops[selected].classList.remove("selected-op"); + } + if (selected === 0) selected = ops.length; + ops[selected-1].classList.add("selected-op"); + } + } else { + const searchResultsEl = document.getElementById("search-results"); + const el = e.target; + const str = el.value; + + while (searchResultsEl.firstChild) { + try { + $(searchResultsEl.firstChild).popover("dispose"); + } catch (err) {} + searchResultsEl.removeChild(searchResultsEl.firstChild); + } + + $("#categories .show").collapse("hide"); + if (str) { + const matchedOps = this.filterOperations(str, true); + const matchedOpsHtml = matchedOps + .map(v => v.toStubHtml()) + .join(""); + + searchResultsEl.innerHTML = matchedOpsHtml; + searchResultsEl.dispatchEvent(this.manager.oplistcreate); + } + } + } + + + /** + * Filters operations based on the search string and returns the matching ones. + * + * @param {string} searchStr + * @param {boolean} highlight - Whether or not to highlight the matching string in the operation + * name and description + * @returns {string[]} + */ + filterOperations(inStr, highlight) { + const matchedOps = []; + const matchedDescs = []; + + // Create version with no whitespace for the fuzzy match + // Helps avoid missing matches e.g. query "TCP " would not find "Parse TCP" + const inStrNWS = inStr.replace(/\s/g, ""); + + for (const opName in this.app.operations) { + const op = this.app.operations[opName]; + + // Match op name using fuzzy match + const [nameMatch, score, idxs] = fuzzyMatch(inStrNWS, opName); + + // Match description based on exact match + const descPos = op.description.toLowerCase().indexOf(inStr.toLowerCase()); + + if (nameMatch || descPos >= 0) { + const operation = new HTMLOperation(opName, this.app.operations[opName], this.app, this.manager); + if (highlight) { + operation.highlightSearchStrings(calcMatchRanges(idxs), [[descPos, inStr.length]]); + } + + if (nameMatch) { + matchedOps.push([operation, score]); + } else { + matchedDescs.push(operation); + } + } + } + + // Sort matched operations based on fuzzy score + matchedOps.sort((a, b) => b[1] - a[1]); + + return matchedOps.map(a => a[0]).concat(matchedDescs); + } + + + /** + * Finds the operation which has been selected using keyboard shortcuts. This will have the class + * 'selected-op' set. Returns the index of the operation within the given list. + * + * @param {element[]} ops + * @returns {number} + */ + getSelectedOp(ops) { + for (let i = 0; i < ops.length; i++) { + if (ops[i].classList.contains("selected-op")) { + return i; + } + } + return -1; + } + + + /** + * Handler for oplistcreate events. + * + * @listens Manager#oplistcreate + * @param {event} e + */ + opListCreate(e) { + this.manager.recipe.createSortableSeedList(e.target); + + // Populate ops total + document.querySelector("#operations .title .op-count").innerText = Object.keys(this.app.operations).length; + + this.enableOpsListPopovers(e.target); + } + + + /** + * Sets up popovers, allowing the popover itself to gain focus which enables scrolling + * and other interactions. + * + * @param {Element} el - The element to start selecting from + */ + enableOpsListPopovers(el) { + $(el).find("[data-toggle=popover]").addBack("[data-toggle=popover]") + .popover({trigger: "manual"}) + .on("mouseenter", function(e) { + if (e.buttons > 0) return; // Mouse button held down - likely dragging an operation + const _this = this; + $(this).popover("show"); + $(".popover").on("mouseleave", function () { + $(_this).popover("hide"); + }); + }).on("mouseleave", function () { + const _this = this; + setTimeout(function() { + // Determine if the popover associated with this element is being hovered over + if ($(_this).data("bs.popover") && + ($(_this).data("bs.popover").tip && !$($(_this).data("bs.popover").tip).is(":hover"))) { + $(_this).popover("hide"); + } + }, 50); + }); + } + + + /** + * Handler for operation doubleclick events. + * Adds the operation to the recipe and auto bakes. + * + * @param {event} e + */ + operationDblclick(e) { + const li = e.target; + + this.manager.recipe.addOperation(li.textContent); + } + + + /** + * Handler for edit favourites click events. + * Sets up the 'Edit favourites' pane and displays it. + * + * @param {event} e + */ + editFavouritesClick(e) { + e.preventDefault(); + e.stopPropagation(); + + // Add favourites to modal + const favCat = this.app.categories.filter(function(c) { + return c.name === "收藏"; + })[0]; + + let html = ""; + for (let i = 0; i < favCat.ops.length; i++) { + const opName = favCat.ops[i]; + const operation = new HTMLOperation(opName, this.app.operations[opName], this.app, this.manager); + html += operation.toStubHtml(true); + } + + const editFavouritesList = document.getElementById("edit-favourites-list"); + editFavouritesList.innerHTML = html; + this.removeIntent = false; + + const editableList = Sortable.create(editFavouritesList, { + filter: ".remove-icon", + onFilter: function (evt) { + const el = editableList.closest(evt.item); + if (el && el.parentNode) { + $(el).popover("dispose"); + el.parentNode.removeChild(el); + } + }, + onEnd: function(evt) { + if (this.removeIntent) { + $(evt.item).popover("dispose"); + evt.item.remove(); + } + }.bind(this), + }); + + Sortable.utils.on(editFavouritesList, "dragleave", function() { + this.removeIntent = true; + }.bind(this)); + + Sortable.utils.on(editFavouritesList, "dragover", function() { + this.removeIntent = false; + }.bind(this)); + + $("#edit-favourites-list [data-toggle=popover]").popover(); + $("#favourites-modal").modal(); + } + + + /** + * Handler for save favourites click events. + * Saves the selected favourites and reloads them. + */ + saveFavouritesClick() { + const favs = document.querySelectorAll("#edit-favourites-list li"); + const favouritesList = Array.from(favs, e => e.childNodes[0].textContent); + + this.app.saveFavourites(favouritesList); + this.app.loadFavourites(); + this.app.populateOperationsList(); + this.manager.recipe.initialiseOperationDragNDrop(); + } + + + /** + * Handler for reset favourites click events. + * Resets favourites to their defaults. + */ + resetFavouritesClick() { + this.app.resetFavourites(); + } + + + /** + * Sets whether operation counts are displayed next to a category title + */ + setCatCount() { + if (this.app.options.showCatCount) { + document.querySelectorAll(".category-title .op-count").forEach(el => el.classList.remove("hidden")); + } else { + document.querySelectorAll(".category-title .op-count").forEach(el => el.classList.add("hidden")); + } + } + +} + +export default OperationsWaiter; diff --git a/plugins/srktoolbox/src/web/waiters/OptionsWaiter.mjs b/plugins/srktoolbox/src/web/waiters/OptionsWaiter.mjs new file mode 100644 index 00000000..3f62e0da --- /dev/null +++ b/plugins/srktoolbox/src/web/waiters/OptionsWaiter.mjs @@ -0,0 +1,209 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +/** + * Waiter to handle events related to the CyberChef options. + */ +class OptionsWaiter { + + /** + * OptionsWaiter constructor. + * + * @param {App} app - The main view object for CyberChef. + * @param {Manager} manager - The CyberChef event manager. + */ + constructor(app, manager) { + this.app = app; + this.manager = manager; + } + + /** + * Loads options and sets values of switches and inputs to match them. + * + * @param {Object} options + */ + load(options) { + Object.assign(this.app.options, options); + + // Set options to match object + document.querySelectorAll("#options-body input[type=checkbox]").forEach(cbox => { + cbox.checked = this.app.options[cbox.getAttribute("option")]; + }); + + document.querySelectorAll("#options-body input[type=number]").forEach(nbox => { + nbox.value = this.app.options[nbox.getAttribute("option")]; + nbox.dispatchEvent(new CustomEvent("change", {bubbles: true})); + }); + + document.querySelectorAll("#options-body select").forEach(select => { + const val = this.app.options[select.getAttribute("option")]; + if (val) { + select.value = val; + select.dispatchEvent(new CustomEvent("change", {bubbles: true})); + } else { + select.selectedIndex = 0; + } + }); + + // Initialise options + this.setWordWrap(); + // this.manager.ops.setCatCount(); + } + + + /** + * Handler for options click events. + * Displays the options pane. + * + * @param {event} e + */ + optionsClick(e) { + e.preventDefault(); + $("#options-modal").modal(); + } + + + /** + * Handler for reset options click events. + * Resets options back to their default values. + */ + resetOptionsClick() { + this.load(this.app.doptions); + } + + + /** + * Handler for switch change events. + * + * @param {event} e + */ + switchChange(e) { + const el = e.target; + const option = el.getAttribute("option"); + const state = el.checked; + + this.updateOption(option, state); + } + + + /** + * Handler for number change events. + * + * @param {event} e + */ + numberChange(e) { + const el = e.target; + const option = el.getAttribute("option"); + const val = parseInt(el.value, 10); + + this.updateOption(option, val); + } + + + /** + * Handler for select change events. + * + * @param {event} e + */ + selectChange(e) { + const el = e.target; + const option = el.getAttribute("option"); + + this.updateOption(option, el.value); + } + + /** + * Modifies an option value and saves it to local storage. + * + * @param {string} option - The option to be updated + * @param {string|number|boolean} value - The new value of the option + */ + updateOption(option, value) { + log.debug(`Setting ${option} to ${value}`); + this.app.options[option] = value; + + if (this.app.isLocalStorageAvailable()) + localStorage.setItem("options", JSON.stringify(this.app.options)); + } + + + /** + * Sets or unsets word wrap on the input and output depending on the wordWrap option value. + */ + setWordWrap() { + this.manager.input.setWordWrap(this.app.options.wordWrap); + this.manager.output.setWordWrap(this.app.options.wordWrap); + } + + + /** + * Theme change event listener + * + * @param {Event} e + */ + themeChange(e) { + const themeClass = e.target.value; + this.changeTheme(themeClass); + } + + + /** + * Changes the theme by setting the class of the element. + * + * @param (string} theme + */ + changeTheme(theme) { + document.querySelector(":root").className = theme; + + // Update theme selection + const themeSelect = document.getElementById("theme"); + let themeOption = themeSelect.querySelector(`option[value="${theme}"]`); + + if (!themeOption) { + const preferredColorScheme = this.getPreferredColorScheme(); + document.querySelector(":root").className = preferredColorScheme; + themeOption = themeSelect.querySelector(`option[value="${preferredColorScheme}"]`); + } + + themeSelect.selectedIndex = themeOption.index; + } + + /** + * Applies the user's preferred color scheme using the `prefers-color-scheme` media query. + */ + applyPreferredColorScheme() { + const themeFromStorage = this.app?.options?.theme; + let theme = themeFromStorage; + if (!theme) { + theme = this.getPreferredColorScheme(); + } + this.changeTheme(theme); + } + + /** + * Get the user's preferred color scheme using the `prefers-color-scheme` media query. + */ + getPreferredColorScheme() { + const prefersDarkScheme = window.matchMedia("(prefers-color-scheme: dark)").matches; + return prefersDarkScheme ? "dark" : "classic"; + } + + /** + * Changes the console logging level. + * + * @param {Event} e + */ + logLevelChange(e) { + const level = e.target.value; + log.setLevel(level, false); + this.manager.worker.setLogLevel(); + this.manager.input.setLogLevel(); + this.manager.output.setLogLevel(); + this.manager.background.setLogLevel(); + } +} + +export default OptionsWaiter; diff --git a/plugins/srktoolbox/src/web/waiters/OutputWaiter.mjs b/plugins/srktoolbox/src/web/waiters/OutputWaiter.mjs new file mode 100644 index 00000000..662786a3 --- /dev/null +++ b/plugins/srktoolbox/src/web/waiters/OutputWaiter.mjs @@ -0,0 +1,1691 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github + */ + +import Utils, {debounce} from "../../core/Utils.mjs"; +import Dish from "../../core/Dish.mjs"; +import {isUTF8, CHR_ENC_SIMPLE_REVERSE_LOOKUP} from "../../core/lib/ChrEnc.mjs"; +import {detectFileType} from "../../core/lib/FileType.mjs"; +import FileSaver from "file-saver"; +import ZipWorker from "worker-loader?inline=no-fallback!../workers/ZipWorker.mjs"; + +import { + EditorView, + keymap, + highlightSpecialChars, + drawSelection, + rectangularSelection, + crosshairCursor +} from "@codemirror/view"; +import { + EditorState, + Compartment +} from "@codemirror/state"; +import { + defaultKeymap +} from "@codemirror/commands"; +import { + bracketMatching +} from "@codemirror/language"; +import { + search, + searchKeymap, + highlightSelectionMatches +} from "@codemirror/search"; + +import {statusBar} from "../utils/statusBar.mjs"; +import {htmlPlugin} from "../utils/htmlWidget.mjs"; +import {copyOverride} from "../utils/copyOverride.mjs"; +import {eolCodeToSeq, eolCodeToName, renderSpecialChar} from "../utils/editorUtils.mjs"; + + +/** + * Waiter to handle events related to the output + */ +class OutputWaiter { + + /** + * OutputWaiter constructor. + * + * @param {App} app - The main view object for CyberChef. + * @param {Manager} manager - The CyberChef event manager + */ + constructor(app, manager) { + this.app = app; + this.manager = manager; + + this.outputTextEl = document.getElementById("output-text"); + // Object to handle output HTML state - used by htmlWidget extension + this.htmlOutput = { + html: "", + changed: false + }; + // Hold a copy of the currently displayed output so that we don't have to update it unnecessarily + this.currentOutputCache = null; + this.initEditor(); + + this.outputs = {}; + this.zipWorker = null; + this.maxTabs = this.manager.tabs.calcMaxTabs(); + this.tabTimeout = null; + this.eolState = 0; // 0 = unset, 1 = detected, 2 = manual + this.encodingState = 0; // 0 = unset, 1 = detected, 2 = manual + } + + /** + * Sets up the CodeMirror Editor + */ + initEditor() { + // Mutable extensions + this.outputEditorConf = { + eol: new Compartment, + lineWrapping: new Compartment, + drawSelection: new Compartment + }; + + const initialState = EditorState.create({ + doc: null, + extensions: [ + // Editor extensions + EditorState.readOnly.of(true), + highlightSpecialChars({ + render: renderSpecialChar, // Custom character renderer to handle special cases + addSpecialChars: /[\ue000-\uf8ff]/g // Add the Unicode Private Use Area which we use for some whitespace chars + }), + rectangularSelection(), + crosshairCursor(), + bracketMatching(), + highlightSelectionMatches(), + search({top: true}), + EditorState.allowMultipleSelections.of(true), + + // Custom extensions + statusBar({ + label: "输出", + timing: this.manager.timing, + tabNumGetter: function() { + return this.manager.tabs.getActiveTab("output"); + }.bind(this), + eolHandler: this.eolChange.bind(this), + chrEncHandler: this.chrEncChange.bind(this), + chrEncGetter: this.getChrEnc.bind(this), + getEncodingState: this.getEncodingState.bind(this), + getEOLState: this.getEOLState.bind(this), + htmlOutput: this.htmlOutput + }), + htmlPlugin(this.htmlOutput), + copyOverride(), + + // Mutable state + this.outputEditorConf.lineWrapping.of(EditorView.lineWrapping), + this.outputEditorConf.eol.of(EditorState.lineSeparator.of("\n")), + this.outputEditorConf.drawSelection.of(drawSelection()), + + // Keymap + keymap.of([ + ...defaultKeymap, + ...searchKeymap + ]), + + // Event listeners + EditorView.updateListener.of(e => { + if (e.selectionSet) + this.manager.highlighter.selectionChange("output", e); + if (e.docChanged || this.docChanging) { + this.docChanging = false; + this.toggleLoader(false); + } + }) + ] + }); + + if (this.outputEditorView) this.outputEditorView.destroy(); + this.outputEditorView = new EditorView({ + state: initialState, + parent: this.outputTextEl + }); + } + + /** + * Handler for EOL change events + * Sets the line separator + * @param {string} eol + * @param {boolean} [manual=false] + */ + async eolChange(eol, manual=false) { + const eolVal = eolCodeToSeq[eol]; + if (eolVal === undefined) return; + + this.eolState = manual ? 2 : this.eolState; + if (this.eolState < 2 && eolVal === this.getEOLSeq()) return; + + if (this.eolState === 1) { + // Alert + this.app.alert(`输出框行尾结束符已自动检测并切换为${eolCodeToName[eol]}`, 5000); + } + + const currentTabNum = this.manager.tabs.getActiveTab("output"); + if (currentTabNum >= 0) { + this.outputs[currentTabNum].eolSequence = eolVal; + } else { + throw new Error(`无法将输出框#${currentTabNum}的行尾结束符更改为${eolVal}`); + } + + // Update the EOL value + this.outputEditorView.dispatch({ + effects: this.outputEditorConf.eol.reconfigure(EditorState.lineSeparator.of(eolVal)) + }); + + // Reset the output so that lines are recalculated, preserving the old EOL values + await this.setOutput(this.currentOutputCache, true); + // Update the URL manually since we aren't firing a statechange event + this.app.updateURL(true); + } + + /** + * Getter for the output EOL sequence + * Prefer reading value from `this.outputs` since the editor may not have updated yet. + * @returns {string} + */ + getEOLSeq() { + const currentTabNum = this.manager.tabs.getActiveTab("output"); + if (currentTabNum < 0) { + return this.outputEditorConf.state?.lineBreak || "\n"; + } + return this.outputs[currentTabNum].eolSequence; + } + + /** + * Returns whether the output EOL sequence was set manually or has been detected automatically + * @returns {number} - 0 = unset, 1 = detected, 2 = manual + */ + getEOLState() { + return this.eolState; + } + + /** + * Handler for Chr Enc change events + * Sets the output character encoding + * @param {number} chrEncVal + * @param {boolean} [manual=false] + */ + async chrEncChange(chrEncVal, manual=false) { + if (typeof chrEncVal !== "number") return; + const currentEnc = this.getChrEnc(); + + const currentTabNum = this.manager.tabs.getActiveTab("output"); + if (currentTabNum >= 0) { + this.outputs[currentTabNum].encoding = chrEncVal; + } else { + throw new Error(`无法将输入框#${currentTabNum}的字符编码更改为${chrEncVal}`); + } + + this.encodingState = manual ? 2 : this.encodingState; + + if (this.encodingState > 1) { + // Reset the output, forcing it to re-decode the data with the new character encoding + await this.setOutput(this.currentOutputCache, true); + // Update the URL manually since we aren't firing a statechange event + this.app.updateURL(true); + } else if (currentEnc !== chrEncVal) { + // Alert + this.app.alert(`输出框字符编码已自动检测并切换为${CHR_ENC_SIMPLE_REVERSE_LOOKUP[chrEncVal] || "原始字节"}`, 5000); + } + } + + /** + * Getter for the output character encoding + * @returns {number} + */ + getChrEnc() { + const currentTabNum = this.manager.tabs.getActiveTab("output"); + if (currentTabNum < 0) { + return 0; + } + return this.outputs[currentTabNum].encoding; + } + + /** + * Returns whether the output character encoding was set manually or has been detected automatically + * @returns {number} - 0 = unset, 1 = detected, 2 = manual + */ + getEncodingState() { + return this.encodingState; + } + + /** + * Sets word wrap on the output editor + * @param {boolean} wrap + */ + setWordWrap(wrap) { + this.outputEditorView.dispatch({ + effects: this.outputEditorConf.lineWrapping.reconfigure( + wrap ? EditorView.lineWrapping : [] + ) + }); + } + + /** + * Sets the value of the current output + * @param {string|ArrayBuffer} data + * @param {boolean} [force=false] + */ + async setOutput(data, force=false) { + // Don't do anything if the output hasn't changed + if (!force && data === this.currentOutputCache) { + this.manager.controls.hideStaleIndicator(); + this.toggleLoader(false); + return; + } + + this.currentOutputCache = data; + this.toggleLoader(true); + + // Remove class to #output-text to change display settings + this.outputTextEl.classList.remove("html-output"); + + // If data is an ArrayBuffer, convert to a string in the correct character encoding + const tabNum = this.manager.tabs.getActiveTab("output"); + this.manager.timing.recordTime("outputDecodingStart", tabNum); + if (data instanceof ArrayBuffer) { + await this.detectEncoding(data); + data = await this.bufferToStr(data); + } + this.manager.timing.recordTime("outputDecodingEnd", tabNum); + + // Turn drawSelection back on + this.outputEditorView.dispatch({ + effects: this.outputEditorConf.drawSelection.reconfigure( + drawSelection() + ) + }); + + // Ensure we're not exceeding the maximum line length + let wrap = this.app.options.wordWrap; + const lineLengthThreshold = 131072; // 128KB + if (data.length > lineLengthThreshold) { + const lines = data.split(this.getEOLSeq()); + const longest = lines.reduce((a, b) => + a > b.length ? a : b.length, 0 + ); + if (longest > lineLengthThreshold) { + // If we are exceeding the max line length, turn off word wrap + wrap = false; + } + } + + // If turning word wrap off, do it before we populate the editor for performance reasons + if (!wrap) this.setWordWrap(wrap); + + // Detect suitable EOL sequence + this.detectEOLSequence(data); + + // We use setTimeout here to delay the editor dispatch until the next event cycle, + // ensuring all async actions have completed before attempting to set the contents + // of the editor. This is mainly with the above call to setWordWrap() in mind. + setTimeout(() => { + this.docChanging = true; + // Insert data into editor, overwriting any previous contents + this.outputEditorView.dispatch({ + changes: { + from: 0, + to: this.outputEditorView.state.doc.length, + insert: data + } + }); + + // If turning word wrap on, do it after we populate the editor + if (wrap) + setTimeout(() => { + this.setWordWrap(wrap); + }); + }); + } + + /** + * Sets the value of the current output to a rendered HTML value + * @param {string} html + */ + async setHTMLOutput(html) { + this.htmlOutput.html = html; + this.htmlOutput.changed = true; + // This clears the text output, but also fires a View update which + // triggers the htmlWidget to render the HTML. We set the force flag + // to ensure the loader gets removed and HTML is rendered. + await this.setOutput("", true); + + // Turn off drawSelection + this.outputEditorView.dispatch({ + effects: this.outputEditorConf.drawSelection.reconfigure([]) + }); + + // Add class to #output-text to change display settings + this.outputTextEl.classList.add("html-output"); + + // Execute script sections + const outputHTML = document.getElementById("output-html"); + const scriptElements = outputHTML ? outputHTML.querySelectorAll("script") : []; + for (let i = 0; i < scriptElements.length; i++) { + try { + eval(scriptElements[i].innerHTML); // eslint-disable-line no-eval + } catch (err) { + log.error(err); + } + } + } + + /** + * Clears the HTML output + */ + clearHTMLOutput() { + this.htmlOutput.html = ""; + this.htmlOutput.changed = true; + // Fire a blank change to force the htmlWidget to update and remove any HTML + this.outputEditorView.dispatch({ + changes: { + from: 0, + insert: "" + } + }); + } + + /** + * Checks whether the EOL separator should be updated + * + * @param {string} data + */ + detectEOLSequence(data) { + // If EOL has been fixed, skip this. + if (this.eolState > 1) return; + // If data is too long, skip this. + if (data.length > 1000000) return; + + // Detect most likely EOL sequence + const eolCharCounts = { + "LF": data.count("\u000a"), + "VT": data.count("\u000b"), + "FF": data.count("\u000c"), + "CR": data.count("\u000d"), + "CRLF": data.count("\u000d\u000a"), + "NEL": data.count("\u0085"), + "LS": data.count("\u2028"), + "PS": data.count("\u2029") + }; + + // If all zero, leave alone + const total = Object.values(eolCharCounts).reduce((acc, curr) => { + return acc + curr; + }, 0); + if (total === 0) return; + + // Find most prevalent line ending sequence + const highest = Object.entries(eolCharCounts).reduce((acc, curr) => { + return curr[1] > acc[1] ? curr : acc; + }, ["LF", 0]); + let choice = highest[0]; + + // If CRLF not zero and more than half the highest alternative, choose CRLF + if ((eolCharCounts.CRLF * 2) > highest[1]) { + choice = "CRLF"; + } + + const eolVal = eolCodeToSeq[choice]; + if (eolVal === this.getEOLSeq()) return; + + // Setting automatically + this.eolState = 1; + this.eolChange(choice); + } + + /** + * Checks whether the character encoding should be updated. + * + * @param {ArrayBuffer} data + */ + async detectEncoding(data) { + // If encoding has been fixed, skip this. + if (this.encodingState > 1) return; + // If data is too long, skip this. + if (data.byteLength > 1000000) return; + + const enc = isUTF8(data); // 0 = not UTF8, 1 = ASCII, 2 = UTF8 + + switch (enc) { + case 0: // not UTF8 + // Set to Raw Bytes + this.encodingState = 1; + await this.chrEncChange(0, false); + break; + case 2: // UTF8 + // Set to UTF8 + this.encodingState = 1; + await this.chrEncChange(65001, false); + break; + case 1: // ASCII + default: + // Ignore + break; + } + } + + /** + * Calculates the maximum number of tabs to display + */ + calcMaxTabs() { + const numTabs = this.manager.tabs.calcMaxTabs(); + if (numTabs !== this.maxTabs) { + this.maxTabs = numTabs; + this.refreshTabs(this.manager.tabs.getActiveTab("output"), "right"); + } + } + + /** + * Gets the dish object for an output. + * + * @param inputNum - The inputNum of the output to get the dish of + * @returns {Dish} + */ + getOutputDish(inputNum) { + if (this.outputExists(inputNum) && + this.outputs[inputNum].data && + this.outputs[inputNum].data.dish) { + return this.outputs[inputNum].data.dish; + } + return null; + } + + /** + * Checks if an output exists in the output dictionary + * + * @param {number} inputNum - The number of the output we're looking for + * @returns {boolean} + */ + outputExists(inputNum) { + if (this.outputs[inputNum] === undefined || + this.outputs[inputNum] === null) { + return false; + } + return true; + } + + /** + * Adds a new output to the output array. + * Creates a new tab if we have less than maxtabs tabs open + * + * @param {number} inputNum - The inputNum of the new output + * @param {boolean} [changeTab=true] - If true, change to the new output + */ + addOutput(inputNum, changeTab = true) { + // Remove the output (will only get removed if it already exists) + this.removeOutput(inputNum); + + const newOutput = { + data: null, + inputNum: inputNum, + statusMessage: `Input ${inputNum} has not been baked yet.`, + error: null, + status: "inactive", + bakeId: -1, + progress: false, + encoding: 0, + eolSequence: "\u000a" + }; + + this.outputs[inputNum] = newOutput; + + this.addTab(inputNum, changeTab); + } + + /** + * Updates the value for the output in the output array. + * If this is the active output tab, updates the output textarea + * + * @param {ArrayBuffer | String} data + * @param {number} inputNum + * @param {boolean} set + */ + updateOutputValue(data, inputNum, set=true) { + if (!this.outputExists(inputNum)) { + this.addOutput(inputNum); + } + + if (Object.prototype.hasOwnProperty.call(data, "dish")) { + data.dish = new Dish(data.dish); + } + + this.outputs[inputNum].data = data; + + const tabItem = this.manager.tabs.getTabItem(inputNum, "output"); + if (tabItem) tabItem.style.background = ""; + + if (set) this.set(inputNum); + } + + /** + * Updates the status message for the output in the output array. + * If this is the active output tab, updates the output textarea + * + * @param {string} statusMessage + * @param {number} inputNum + * @param {boolean} [set=true] + */ + updateOutputMessage(statusMessage, inputNum, set=true) { + if (!this.outputExists(inputNum)) return; + this.outputs[inputNum].statusMessage = statusMessage; + if (set) this.set(inputNum); + } + + /** + * Updates the error value for the output in the output array. + * If this is the active output tab, calls app.handleError. + * Otherwise, the error will be handled when the output is switched to + * + * @param {Error} error + * @param {number} inputNum + * @param {number} [progress=0] + */ + updateOutputError(error, inputNum, progress=0) { + if (!this.outputExists(inputNum)) return; + + const errorString = error.displayStr || error.toString(); + + this.outputs[inputNum].error = errorString; + this.outputs[inputNum].progress = progress; + this.updateOutputStatus("error", inputNum); + } + + /** + * Updates the status value for the output in the output array + * + * @param {string} status + * @param {number} inputNum + */ + updateOutputStatus(status, inputNum) { + if (!this.outputExists(inputNum)) return; + this.outputs[inputNum].status = status; + + if (status !== "error") { + delete this.outputs[inputNum].error; + } + + this.displayTabInfo(inputNum); + this.set(inputNum); + } + + /** + * Updates the stored bake ID for the output in the output array + * + * @param {number} bakeId + * @param {number} inputNum + */ + updateOutputBakeId(bakeId, inputNum) { + if (!this.outputExists(inputNum)) return; + this.outputs[inputNum].bakeId = bakeId; + } + + /** + * Updates the stored progress value for the output in the output array + * + * @param {number} progress + * @param {number} total + * @param {number} inputNum + */ + updateOutputProgress(progress, total, inputNum) { + if (!this.outputExists(inputNum)) return; + this.outputs[inputNum].progress = progress; + + if (progress !== false) { + this.manager.tabs.updateTabProgress(inputNum, progress, total, "output"); + } + + } + + /** + * Removes an output from the output array. + * + * @param {number} inputNum + */ + removeOutput(inputNum) { + if (!this.outputExists(inputNum)) return; + + delete this.outputs[inputNum]; + } + + /** + * Removes all output tabs + */ + removeAllOutputs() { + this.outputs = {}; + + const tabsList = document.getElementById("output-tabs"); + const tabsListChildren = tabsList.children; + + tabsList.classList.remove("tabs-left"); + tabsList.classList.remove("tabs-right"); + for (let i = tabsListChildren.length - 1; i >= 0; i--) { + tabsListChildren.item(i).remove(); + } + } + + /** + * Sets the output in the output pane. + * + * @param {number} inputNum + */ + async set(inputNum) { + inputNum = parseInt(inputNum, 10); + if (inputNum !== this.manager.tabs.getActiveTab("output") || + !this.outputExists(inputNum)) return; + this.toggleLoader(true); + + return new Promise(async function(resolve, reject) { + const output = this.outputs[inputNum]; + this.manager.timing.recordTime("settingOutput", inputNum); + + // Update the EOL value + this.outputEditorView.dispatch({ + effects: this.outputEditorConf.eol.reconfigure( + EditorState.lineSeparator.of(output.eolSequence) + ) + }); + + // If pending or baking, show loader and status message + // If error, style the tab and handle the error + // If done, display the output if it's the active tab + // If inactive, show the last bake value (or blank) + if (output.status === "inactive" || + output.status === "stale" || + (output.status === "baked" && output.bakeId < this.manager.worker.bakeId)) { + this.manager.controls.showStaleIndicator(); + } else { + this.manager.controls.hideStaleIndicator(); + } + + if (output.progress !== undefined && !this.app.baking) { + this.manager.recipe.updateBreakpointIndicator(output.progress); + } else { + this.manager.recipe.updateBreakpointIndicator(false); + } + + if (output.status === "pending" || output.status === "baking") { + // show the loader and the status message if it's being shown + // otherwise don't do anything + document.querySelector("#output-loader .loading-msg").textContent = output.statusMessage; + } else if (output.status === "error") { + this.clearHTMLOutput(); + + if (output.error) { + await this.setOutput(output.error); + } else { + await this.setOutput(output.data.result); + } + } else if (output.status === "baked" || output.status === "inactive") { + document.querySelector("#output-loader .loading-msg").textContent = `正在载入输出#${inputNum}`; + + if (output.data === null) { + this.clearHTMLOutput(); + await this.setOutput(""); + return; + } + + switch (output.data.type) { + case "html": + await this.setHTMLOutput(output.data.result); + break; + case "ArrayBuffer": + case "string": + default: + this.clearHTMLOutput(); + await this.setOutput(output.data.result); + break; + } + this.manager.timing.recordTime("complete", inputNum); + + // Trigger an update so that the status bar recalculates timings + this.outputEditorView.dispatch({ + changes: { + from: 0, + to: 0 + } + }); + + debounce(this.backgroundMagic, 50, "backgroundMagic", this, [])(); + } + }.bind(this)); + } + + /** + * Retrieves the dish as a string + * + * @param {Dish} dish + * @returns {string} + */ + async getDishStr(dish) { + return await new Promise(resolve => { + this.manager.worker.getDishAs(dish, "string", r => { + resolve(r.value); + }); + }); + } + + /** + * Retrieves the dish as an ArrayBuffer + * + * @param {Dish} dish + * @returns {ArrayBuffer} + */ + async getDishBuffer(dish) { + return await new Promise(resolve => { + this.manager.worker.getDishAs(dish, "ArrayBuffer", r => { + resolve(r.value); + }); + }); + } + + /** + * Retrieves the title of the Dish as a string + * + * @param {Dish} dish + * @param {number} maxLength + * @returns {string} + */ + async getDishTitle(dish, maxLength) { + return await new Promise(resolve => { + this.manager.worker.getDishTitle(dish, maxLength, r => { + resolve(r.value); + }); + }); + } + + /** + * Asks a worker to translate an ArrayBuffer into a certain character encoding + * + * @param {ArrrayBuffer} buffer + * @returns {string} + */ + async bufferToStr(buffer) { + const encoding = this.getChrEnc(); + + if (buffer.byteLength === 0) return ""; + return await new Promise(resolve => { + this.manager.worker.bufferToStr(buffer, encoding, r => { + resolve(r.value); + }); + }); + } + + /** + * Save bombe object then remove it from the DOM so that it does not cause performance issues. + */ + saveBombe() { + this.bombeEl = document.getElementById("bombe"); + this.bombeEl.parentNode.removeChild(this.bombeEl); + } + + /** + * Shows or hides the output loading screen. + * The animated Bombe SVG, whilst quite aesthetically pleasing, is reasonably CPU + * intensive, so we remove it from the DOM when not in use. We only show it if the + * recipe is taking longer than 200ms. We add it to the DOM just before that so that + * it is ready to fade in without stuttering. + * + * @param {boolean} value - If true, show the loader + */ + toggleLoader(value) { + const outputLoader = document.getElementById("output-loader"), + animation = document.getElementById("output-loader-animation"); + + if (value) { + this.manager.controls.hideStaleIndicator(); + // Don't add the bombe if it's already there or scheduled to be loaded + if (animation.children.length === 0 && !this.appendBombeTimeout) { + // Start a timer to add the Bombe to the DOM just before we make it + // visible so that there is no stuttering + this.appendBombeTimeout = setTimeout(function() { + this.appendBombeTimeout = null; + animation.appendChild(this.bombeEl); + }.bind(this), 150); + } + + if (outputLoader.style.visibility !== "visible" && !this.outputLoaderTimeout) { + // Show the loading screen + this.outputLoaderTimeout = setTimeout(function() { + this.outputLoaderTimeout = null; + outputLoader.style.visibility = "visible"; + outputLoader.style.opacity = 1; + }, 200); + } + } else if (outputLoader.style.visibility !== "hidden" || this.appendBombeTimeout || this.outputLoaderTimeout) { + clearTimeout(this.appendBombeTimeout); + clearTimeout(this.outputLoaderTimeout); + this.appendBombeTimeout = null; + this.outputLoaderTimeout = null; + + // Remove the Bombe from the DOM to save resources + this.outputLoaderTimeout = setTimeout(function () { + this.outputLoaderTimeout = null; + if (animation.children.length > 0) + animation.removeChild(this.bombeEl); + }.bind(this), 500); + outputLoader.style.opacity = 0; + outputLoader.style.visibility = "hidden"; + } + } + + /** + * Handler for save click events. + * Saves the current output to a file. + */ + saveClick() { + this.downloadFile(); + } + + /** + * Handler for file download events. + */ + async downloadFile() { + const dish = this.getOutputDish(this.manager.tabs.getActiveTab("output")); + if (dish === null) { + this.app.alert("没有数据可供下载。是不是过程没有运行?", 3000); + return; + } + + const data = await dish.get(Dish.ARRAY_BUFFER); + let ext = ".dat"; + + // Detect file type automatically + const types = detectFileType(data); + if (types.length) { + ext = `.${types[0].extension.split(",", 1)[0]}`; + } + + const fileName = window.prompt("请输入文件名:", `download${ext}`); + + // Assume if the user clicks cancel they don't want to download + if (fileName === null) return; + + const file = new File([data], fileName); + FileSaver.saveAs(file, fileName, {autoBom: false}); + } + + /** + * Handler for save all click event + * Saves all outputs to a single archive file + */ + async saveAllClick() { + const downloadButton = document.getElementById("save-all-to-file"); + if (downloadButton.firstElementChild.innerHTML === "archive") { + this.downloadAllFiles(); + } else { + const cancel = await new Promise(function(resolve, reject) { + this.app.confirm( + "取消打包?", + "当前输出内容将在打包后下载。
        取消打包?", + "继续打包", + "取消打包", + resolve, this); + }.bind(this)); + if (!cancel) { + this.terminateZipWorker(); + } + } + } + + /** + * Spawns a new ZipWorker and sends it the outputs so that they can + * be zipped for download + */ + async downloadAllFiles() { + const inputNums = Object.keys(this.outputs); + for (let i = 0; i < inputNums.length; i++) { + const iNum = inputNums[i]; + if (this.outputs[iNum].status !== "baked" || + this.outputs[iNum].bakeId !== this.manager.worker.bakeId) { + const continueDownloading = await new Promise(function(resolve, reject) { + this.app.confirm( + "输出不完整", + "有部分输出未运算完成。仍然要下载吗?", + "下载", "取消", resolve, this); + }.bind(this)); + if (continueDownloading) { + break; + } else { + return; + } + } + } + + let fileName = window.prompt("请输入文件名: ", "download.zip"); + + if (fileName === null || fileName === "") { + // Don't zip the files if there isn't a filename + this.app.alert("请输入文件名!", 3000); + return; + } + + if (!fileName.match(/.zip$/)) { + fileName += ".zip"; + } + + let fileExt = window.prompt("请输入文件扩展名,或者留空由程序自动探测。", ""); + + if (fileExt === null) fileExt = ""; + + if (this.zipWorker !== null) { + this.terminateZipWorker(); + } + + const downloadButton = document.getElementById("save-all-to-file"); + + downloadButton.classList.add("spin"); + downloadButton.title = `打包 ${inputNums.length} 个文件...`; + downloadButton.setAttribute("data-original-title", `打包 ${inputNums.length} 个文件...`); + + downloadButton.firstElementChild.innerHTML = "autorenew"; + + log.debug("Creating ZipWorker"); + this.zipWorker = new ZipWorker(); + this.zipWorker.postMessage({ + action: "setLogLevel", + data: log.getLevel() + }); + this.zipWorker.postMessage({ + action: "zipFiles", + data: { + outputs: this.outputs, + filename: fileName, + fileExtension: fileExt + } + }); + this.zipWorker.addEventListener("message", this.handleZipWorkerMessage.bind(this)); + } + + /** + * Terminate the ZipWorker + */ + terminateZipWorker() { + if (this.zipWorker === null) return; // Already terminated + + log.debug("Terminating ZipWorker."); + + this.zipWorker.terminate(); + this.zipWorker = null; + + const downloadButton = document.getElementById("save-all-to-file"); + downloadButton.classList.remove("spin"); + downloadButton.title = "保存为压缩包"; + downloadButton.setAttribute("data-original-title", "保存为压缩包"); + downloadButton.firstElementChild.innerHTML = "archive"; + } + + /** + * Handle messages sent back by the ZipWorker + */ + handleZipWorkerMessage(e) { + const r = e.data; + if (!("zippedFile" in r)) { + log.error("No zipped file was sent in the message."); + this.terminateZipWorker(); + return; + } + if (!("filename" in r)) { + log.error("No filename was sent in the message."); + this.terminateZipWorker(); + return; + } + + const file = new File([r.zippedFile], r.filename); + FileSaver.saveAs(file, r.filename, {autoBom: false}); + + this.terminateZipWorker(); + } + + /** + * Adds a new output tab. + * + * @param {number} inputNum + * @param {boolean} [changeTab=true] + */ + addTab(inputNum, changeTab = true) { + const tabsWrapper = document.getElementById("output-tabs"); + const numTabs = tabsWrapper.children.length; + + if (!this.manager.tabs.getTabItem(inputNum, "output") && numTabs < this.maxTabs) { + // Create a new tab element + const newTab = this.manager.tabs.createTabElement(inputNum, changeTab, "output"); + tabsWrapper.appendChild(newTab); + } else if (numTabs === this.maxTabs) { + // Can't create a new tab + document.getElementById("output-tabs").lastElementChild.classList.add("tabs-right"); + } + + this.displayTabInfo(inputNum); + + if (changeTab) { + this.changeTab(inputNum, false); + } + } + + /** + * Changes the active tab + * + * @param {number} inputNum + * @param {boolean} [changeInput = false] + */ + changeTab(inputNum, changeInput = false) { + if (!this.outputExists(inputNum)) return; + const currentNum = this.manager.tabs.getActiveTab("output"); + + this.hideMagicButton(); + + if (!this.manager.tabs.changeTab(inputNum, "output")) { + let direction = "right"; + if (currentNum > inputNum) { + direction = "left"; + } + const newOutputs = this.getNearbyNums(inputNum, direction); + + const tabsLeft = (newOutputs[0] !== this.getSmallestInputNum()); + const tabsRight = (newOutputs[newOutputs.length - 1] !== this.getLargestInputNum()); + + this.manager.tabs.refreshTabs(newOutputs, inputNum, tabsLeft, tabsRight, "output"); + + for (let i = 0; i < newOutputs.length; i++) { + this.displayTabInfo(newOutputs[i]); + } + } + + this.set(inputNum); + + if (changeInput) { + this.manager.input.changeTab(inputNum, false); + } + } + + /** + * Handler for changing tabs event + * + * @param {event} mouseEvent + */ + changeTabClick(mouseEvent) { + if (!mouseEvent.target) return; + + const tabNum = mouseEvent.target.parentElement.getAttribute("inputNum"); + if (tabNum) { + this.changeTab(parseInt(tabNum, 10), this.app.options.syncTabs); + } + } + + /** + * Handler for scrolling on the output tabs area + * + * @param {event} wheelEvent + */ + scrollTab(wheelEvent) { + wheelEvent.preventDefault(); + + if (wheelEvent.deltaY > 0) { + this.changeTabLeft(); + } else if (wheelEvent.deltaY < 0) { + this.changeTabRight(); + } + } + + /** + * Handler for mouse down on the next tab button + */ + nextTabClick() { + this.mousedown = true; + this.changeTabRight(); + const time = 200; + const func = function(time) { + if (this.mousedown) { + this.changeTabRight(); + const newTime = (time > 50) ? time - 10 : 50; + setTimeout(func.bind(this, [newTime]), newTime); + } + }; + this.tabTimeout = setTimeout(func.bind(this, [time]), time); + } + + /** + * Handler for mouse down on the previous tab button + */ + previousTabClick() { + this.mousedown = true; + this.changeTabLeft(); + const time = 200; + const func = function(time) { + if (this.mousedown) { + this.changeTabLeft(); + const newTime = (time > 50) ? time - 10 : 50; + setTimeout(func.bind(this, [newTime]), newTime); + } + }; + this.tabTimeout = setTimeout(func.bind(this, [time]), time); + } + + /** + * Handler for mouse up event on the tab buttons + */ + tabMouseUp() { + this.mousedown = false; + + clearTimeout(this.tabTimeout); + this.tabTimeout = null; + } + + /** + * Handler for changing to the left tab + */ + changeTabLeft() { + const currentTab = this.manager.tabs.getActiveTab("output"); + this.changeTab(this.getPreviousInputNum(currentTab), this.app.options.syncTabs); + } + + /** + * Handler for changing to the right tab + */ + changeTabRight() { + const currentTab = this.manager.tabs.getActiveTab("output"); + this.changeTab(this.getNextInputNum(currentTab), this.app.options.syncTabs); + } + + /** + * Handler for go to tab button clicked + */ + goToTab() { + const min = this.getSmallestInputNum(), + max = this.getLargestInputNum(); + + let tabNum = window.prompt(`输入标签页编号 (${min} - ${max}):`, this.manager.tabs.getActiveTab("output").toString()); + if (tabNum === null) return; + tabNum = parseInt(tabNum, 10); + + if (this.outputExists(tabNum)) { + this.changeTab(tabNum, this.app.options.syncTabs); + } + } + + /** + * Generates a list of the nearby inputNums + * @param inputNum + * @param direction + */ + getNearbyNums(inputNum, direction) { + const nums = []; + for (let i = 0; i < this.maxTabs; i++) { + let newNum; + if (i === 0 && this.outputs[inputNum] !== undefined) { + newNum = inputNum; + } else { + switch (direction) { + case "left": + newNum = this.getNextInputNum(nums[i - 1]); + if (newNum === nums[i - 1]) { + direction = "right"; + newNum = this.getPreviousInputNum(nums[0]); + } + break; + case "right": + newNum = this.getPreviousInputNum(nums[i - 1]); + if (newNum === nums[i - 1]) { + direction = "left"; + newNum = this.getNextInputNum(nums[0]); + } + } + } + if (!nums.includes(newNum) && (newNum > 0)) { + nums.push(newNum); + } + } + nums.sort((a, b) => a - b); // Forces the sort function to treat a and b as numbers + return nums; + } + + /** + * Gets the largest inputNum + * + * @returns {number} + */ + getLargestInputNum() { + const inputNums = Object.keys(this.outputs); + if (inputNums.length === 0) return -1; + return Math.max(...inputNums); + } + + /** + * Gets the smallest inputNum + * + * @returns {number} + */ + getSmallestInputNum() { + const inputNums = Object.keys(this.outputs); + if (inputNums.length === 0) return -1; + return Math.min(...inputNums); + } + + /** + * Gets the previous inputNum + * + * @param {number} inputNum - The current input number + * @returns {number} + */ + getPreviousInputNum(inputNum) { + const inputNums = Object.keys(this.outputs); + if (inputNums.length === 0) return -1; + let num = Math.min(...inputNums); + for (let i = 0; i < inputNums.length; i++) { + const iNum = parseInt(inputNums[i], 10); + if (iNum < inputNum) { + if (iNum > num) { + num = iNum; + } + } + } + return num; + } + + /** + * Gets the next inputNum + * + * @param {number} inputNum - The current input number + * @returns {number} + */ + getNextInputNum(inputNum) { + const inputNums = Object.keys(this.outputs); + if (inputNums.length === 0) return -1; + let num = Math.max(...inputNums); + for (let i = 0; i < inputNums.length; i++) { + const iNum = parseInt(inputNums[i], 10); + if (iNum > inputNum) { + if (iNum < num) { + num = iNum; + } + } + } + return num; + } + + /** + * Removes a tab and it's corresponding output + * + * @param {number} inputNum + */ + removeTab(inputNum) { + if (!this.outputExists(inputNum)) return; + + const tabElement = this.manager.tabs.getTabItem(inputNum, "output"); + + this.removeOutput(inputNum); + + if (tabElement !== null) { + this.refreshTabs(this.getPreviousInputNum(inputNum), "left"); + } + } + + /** + * Redraw the entire tab bar to remove any outdated tabs + * + * @param {number} activeTab + * @param {string} direction - Either "left" or "right" + */ + refreshTabs(activeTab, direction) { + const newNums = this.getNearbyNums(activeTab, direction), + tabsLeft = (newNums[0] !== this.getSmallestInputNum() && newNums.length > 0), + tabsRight = (newNums[newNums.length - 1] !== this.getLargestInputNum() && newNums.length > 0); + + this.manager.tabs.refreshTabs(newNums, activeTab, tabsLeft, tabsRight, "output"); + + for (let i = 0; i < newNums.length; i++) { + this.displayTabInfo(newNums[i]); + } + } + + /** + * Display output information in the tab header + * + * @param {number} inputNum + */ + async displayTabInfo(inputNum) { + // Don't display anything if there are no, or only one, tabs + if (!this.outputExists(inputNum) || Object.keys(this.outputs).length <= 1) return; + + const dish = this.getOutputDish(inputNum); + let tabStr = ""; + + if (dish !== null) { + tabStr = await this.getDishTitle(this.getOutputDish(inputNum), 100); + tabStr = tabStr.replace(/[\n\r]/g, ""); + } + this.manager.tabs.updateTabHeader(inputNum, tabStr, "output"); + if (this.manager.worker.recipeConfig !== undefined) { + this.manager.tabs.updateTabProgress(inputNum, this.outputs[inputNum]?.progress, this.manager.worker.recipeConfig.length, "output"); + } + + const tabItem = this.manager.tabs.getTabItem(inputNum, "output"); + if (tabItem) { + if (this.outputs[inputNum].status === "error") { + tabItem.style.color = "#FF0000"; + } else { + tabItem.style.color = ""; + } + } + } + + /** + * Triggers the BackgroundWorker to attempt Magic on the current output. + */ + async backgroundMagic() { + this.hideMagicButton(); + const dish = this.getOutputDish(this.manager.tabs.getActiveTab("output")); + if (!this.app.options.autoMagic || dish === null) return; + const buffer = await this.getDishBuffer(dish); + const sample = buffer.slice(0, 1000) || ""; + + if (sample.length || sample.byteLength) { + this.manager.background.magic(sample); + } + } + + /** + * Handles the results of a background Magic call. + * + * @param {Object[]} options + */ + backgroundMagicResult(options) { + if (!options.length) return; + + const currentRecipeConfig = this.app.getRecipeConfig(); + let msg = "", + newRecipeConfig; + + if (options[0].recipe.length) { + const opSequence = options[0].recipe.map(o => o.op).join(", "); + newRecipeConfig = currentRecipeConfig.concat(options[0].recipe); + msg = `${opSequence} 可以解码成 "${Utils.escapeHtml(Utils.truncate(options[0].data), 30)}"`; + } else if (options[0].fileType && options[0].fileType.name) { + const ft = options[0].fileType; + newRecipeConfig = currentRecipeConfig.concat([{op: "检测文件类型", args: []}]); + msg = `检测到 ${ft.name} 文件`; + } else { + return; + } + + this.showMagicButton(msg, newRecipeConfig); + } + + /** + * Handler for Magic click events. + * + * Loads the Magic recipe. + * + * @fires Manager#statechange + */ + magicClick() { + const magicButton = document.getElementById("magic"); + this.app.setRecipeConfig(JSON.parse(magicButton.getAttribute("data-recipe"))); + window.dispatchEvent(this.manager.statechange); + this.hideMagicButton(); + } + + /** + * Displays the Magic button with a title and adds a link to a recipe. + * + * @param {string} msg + * @param {Object[]} recipeConfig + */ + showMagicButton(msg, recipeConfig) { + const magicButton = document.getElementById("magic"); + magicButton.setAttribute("data-original-title", msg); + magicButton.setAttribute("data-recipe", JSON.stringify(recipeConfig), null, ""); + magicButton.classList.remove("hidden"); + magicButton.classList.add("pulse"); + } + + + /** + * Hides the Magic button and resets its values. + */ + hideMagicButton() { + const magicButton = document.getElementById("magic"); + magicButton.classList.add("hidden"); + magicButton.classList.remove("pulse"); + magicButton.setAttribute("data-recipe", ""); + magicButton.setAttribute("data-original-title", "Magic!"); + } + + /** + * Handler for extract file events. + * + * @param {Event} e + */ + async extractFileClick(e) { + e.preventDefault(); + e.stopPropagation(); + + const el = e.target.nodeName === "I" ? e.target.parentNode : e.target; + const blobURL = el.getAttribute("blob-url"); + const fileName = el.getAttribute("file-name"); + + const blob = await fetch(blobURL).then(r => r.blob()); + this.manager.input.loadUIFiles([new File([blob], fileName, {type: blob.type})]); + } + + + /** + * Handler for copy click events. + * Copies the output to the clipboard + */ + async copyClick() { + const dish = this.getOutputDish(this.manager.tabs.getActiveTab("output")); + if (dish === null) { + this.app.alert("没有需要复制的数据。过程是否仍在运行?", 3000); + return; + } + + const output = await this.getDishStr(dish); + const self = this; + + navigator.clipboard.writeText(output).then(function() { + self.app.alert("原始数据复制成功。", 2000); + }, function(err) { + self.app.alert("抱歉,数据复制失败。", 3000); + }); + } + + /** + * Handler for switch click events. + * Moves the current output into the input textarea. + */ + async switchClick() { + const activeTab = this.manager.tabs.getActiveTab("output"); + const switchButton = document.getElementById("switch"); + + switchButton.classList.add("spin"); + switchButton.disabled = true; + switchButton.firstElementChild.innerHTML = "autorenew"; + $(switchButton).tooltip("hide"); + + const activeData = await this.getDishBuffer(this.getOutputDish(activeTab)); + + if (this.outputExists(activeTab)) { + this.manager.input.set(activeTab, { + type: "userinput", + buffer: activeData, + encoding: this.outputs[activeTab].encoding, + eolSequence: this.outputs[activeTab].eolSequence + }); + } + + switchButton.classList.remove("spin"); + switchButton.disabled = false; + switchButton.firstElementChild.innerHTML = "open_in_browser"; + } + + /** + * Handler for maximise output click events. + * Resizes the output frame to be as large as possible, or restores it to its original size. + */ + maximiseOutputClick(e) { + const el = e.target.id === "maximise-output" ? e.target : e.target.parentNode; + + if (el.getAttribute("data-original-title").indexOf("最大化") === 0) { + document.body.classList.add("output-maximised"); + this.app.initialiseSplitter(true); + this.app.columnSplitter.collapse(0); + this.app.columnSplitter.collapse(1); + this.app.ioSplitter.collapse(0); + + $(el).attr("data-original-title", "恢复"); + $(el).attr("aria-label", "恢复输出框"); + el.querySelector("i").innerHTML = "fullscreen_exit"; + } else { + document.body.classList.remove("output-maximised"); + $(el).attr("data-original-title", "最大化"); + $(el).attr("aria-label", "最大化输出框"); + el.querySelector("i").innerHTML = "fullscreen"; + this.app.initialiseSplitter(false); + this.app.resetLayout(); + } + } + + /** + * Handler for find tab button clicked + */ + findTab() { + this.filterTabSearch(); + $("#output-tab-modal").modal(); + } + + /** + * Searches the outputs using the filter settings and displays the results + */ + async filterTabSearch() { + const showPending = document.getElementById("output-show-pending").checked, + showBaking = document.getElementById("output-show-baking").checked, + showBaked = document.getElementById("output-show-baked").checked, + showStale = document.getElementById("output-show-stale").checked, + showErrored = document.getElementById("output-show-errored").checked, + contentFilter = document.getElementById("output-content-filter").value, + resultsList = document.getElementById("output-search-results"), + numResults = parseInt(document.getElementById("output-num-results").value, 10), + inputNums = Object.keys(this.outputs), + results = []; + + let contentFilterExp; + try { + contentFilterExp = new RegExp(contentFilter, "i"); + } catch (error) { + this.app.handleError(error); + return; + } + + // Search through the outputs for matching output results + for (let i = 0; i < inputNums.length; i++) { + const iNum = inputNums[i], + output = this.outputs[iNum]; + + if (output.status === "pending" && showPending || + output.status === "baking" && showBaking || + output.status === "error" && showErrored || + output.status === "stale" && showStale || + output.status === "inactive" && showStale) { + const outDisplay = { + "pending": "等待中", + "baking": "执行中", + "error": output.error || "报错", + "stale": "已过期", + "inactive": "未执行" + }; + + // If the output has a dish object, check it against the filter + if (Object.prototype.hasOwnProperty.call(output, "data") && + output.data && + Object.prototype.hasOwnProperty.call(output.data, "dish")) { + const data = await output.data.dish.get(Dish.STRING); + if (contentFilterExp.test(data)) { + results.push({ + inputNum: iNum, + textDisplay: data.slice(0, 100) + }); + } + } else { + results.push({ + inputNum: iNum, + textDisplay: outDisplay[output.status] + }); + } + } else if (output.status === "baked" && showBaked && output.progress === false) { + let data = await output.data.dish.get(Dish.STRING); + data = data.replace(/[\r\n]/g, ""); + if (contentFilterExp.test(data)) { + results.push({ + inputNum: iNum, + textDisplay: data.slice(0, 100) + }); + } + } else if (output.progress !== false && showErrored) { + let data = await output.data.dish.get(Dish.STRING); + data = data.replace(/[\r\n]/g, ""); + if (contentFilterExp.test(data)) { + results.push({ + inputNum: iNum, + textDisplay: data.slice(0, 100) + }); + } + } + + if (results.length >= numResults) { + break; + } + } + + for (let i = resultsList.children.length - 1; i >= 0; i--) { + resultsList.children.item(i).remove(); + } + + for (let i = 0; i < results.length; i++) { + const newListItem = document.createElement("li"); + newListItem.classList.add("output-filter-result"); + newListItem.setAttribute("inputNum", results[i].inputNum); + newListItem.innerText = `${results[i].inputNum}: ${results[i].textDisplay}`; + + resultsList.appendChild(newListItem); + } + } + + /** + * Handler for clicking on a filter result. + * Changes to the clicked output + * + * @param {event} e + */ + filterItemClick(e) { + if (!e.target) return; + const inputNum = parseInt(e.target.getAttribute("inputNum"), 10); + if (inputNum <= 0) return; + + $("#output-tab-modal").modal("hide"); + this.changeTab(inputNum, this.app.options.syncTabs); + } + + + /** + * Sets the console log level in the workers. + */ + setLogLevel() { + if (!this.zipWorker) return; + this.zipWorker.postMessage({ + action: "setLogLevel", + data: log.getLevel() + }); + } +} + +export default OutputWaiter; diff --git a/plugins/srktoolbox/src/web/waiters/RecipeWaiter.mjs b/plugins/srktoolbox/src/web/waiters/RecipeWaiter.mjs new file mode 100644 index 00000000..be3bc5cd --- /dev/null +++ b/plugins/srktoolbox/src/web/waiters/RecipeWaiter.mjs @@ -0,0 +1,652 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github + */ + +import HTMLOperation from "../HTMLOperation.mjs"; +import Sortable from "sortablejs"; +import Utils from "../../core/Utils.mjs"; +import {escapeControlChars} from "../utils/editorUtils.mjs"; +import DOMPurify from "dompurify"; + + +/** + * Waiter to handle events related to the recipe. + */ +class RecipeWaiter { + + /** + * RecipeWaiter constructor. + * + * @param {App} app - The main view object for CyberChef. + * @param {Manager} manager - The CyberChef event manager. + */ + constructor(app, manager) { + this.app = app; + this.manager = manager; + this.removeIntent = false; + } + + + /** + * Sets up the drag and drop capability for operations in the operations and recipe areas. + */ + initialiseOperationDragNDrop() { + const recList = document.getElementById("rec-list"); + + // Recipe list + Sortable.create(recList, { + group: "recipe", + sort: true, + animation: 0, + delay: 0, + filter: ".arg", + preventOnFilter: false, + setData: function(dataTransfer, dragEl) { + dataTransfer.setData("Text", dragEl.querySelector(".op-title").textContent); + }, + onEnd: function(evt) { + if (this.removeIntent) { + evt.item.remove(); + evt.target.dispatchEvent(this.manager.operationremove); + } + }.bind(this), + onSort: function(evt) { + if (evt.from.id === "rec-list") { + document.dispatchEvent(this.manager.statechange); + } + }.bind(this) + }); + + Sortable.utils.on(recList, "dragover", function() { + this.removeIntent = false; + }.bind(this)); + + Sortable.utils.on(recList, "dragleave", function() { + this.removeIntent = true; + this.app.progress = 0; + }.bind(this)); + + Sortable.utils.on(recList, "touchend", function(e) { + const loc = e.changedTouches[0]; + const target = document.elementFromPoint(loc.clientX, loc.clientY); + + this.removeIntent = !recList.contains(target); + }.bind(this)); + + // Favourites category + document.querySelector("#categories a").addEventListener("dragover", this.favDragover.bind(this)); + document.querySelector("#categories a").addEventListener("dragleave", this.favDragleave.bind(this)); + document.querySelector("#categories a").addEventListener("drop", this.favDrop.bind(this)); + } + + + /** + * Creates a drag-n-droppable seed list of operations. + * + * @param {element} listEl - The list to initialise + */ + createSortableSeedList(listEl) { + Sortable.create(listEl, { + group: { + name: "recipe", + pull: "clone", + put: false, + }, + sort: false, + setData: function(dataTransfer, dragEl) { + dataTransfer.setData("Text", dragEl.textContent); + }, + onStart: function(evt) { + // Removes popover element and event bindings from the dragged operation but not the + // event bindings from the one left in the operations list. Without manually removing + // these bindings, we cannot re-initialise the popover on the stub operation. + $(evt.item) + .popover("dispose") + .removeData("bs.popover") + .off("mouseenter") + .off("mouseleave") + .attr("data-toggle", "popover-disabled"); + $(evt.clone) + .off(".popover") + .removeData("bs.popover"); + }, + onEnd: this.opSortEnd.bind(this) + }); + } + + + /** + * Handler for operation sort end events. + * Removes the operation from the list if it has been dropped outside. If not, adds it to the list + * at the appropriate place and initialises it. + * + * @fires Manager#operationadd + * @param {event} evt + */ + opSortEnd(evt) { + if (this.removeIntent && evt.item.parentNode.id === "rec-list") { + evt.item.remove(); + return; + } + + // Reinitialise the popover on the original element in the ops list because for some reason it + // gets destroyed and recreated. If the clone isn't in the ops list, we use the original item instead. + let enableOpsElement; + if (evt.clone?.parentNode?.classList?.contains("op-list")) { + enableOpsElement = evt.clone; + } else { + enableOpsElement = evt.item; + $(evt.item).attr("data-toggle", "popover"); + } + this.manager.ops.enableOpsListPopovers(enableOpsElement); + + if (evt.item.parentNode.id !== "rec-list") { + return; + } + + this.buildRecipeOperation(evt.item); + evt.item.dispatchEvent(this.manager.operationadd); + } + + + /** + * Handler for favourite dragover events. + * If the element being dragged is an operation, displays a visual cue so that the user knows it can + * be dropped here. + * + * @param {event} e + */ + favDragover(e) { + if (e.dataTransfer.effectAllowed !== "move") + return false; + + e.stopPropagation(); + e.preventDefault(); + if (e.target?.className?.indexOf("category-title") > -1) { + // Hovering over the a + e.target.classList.add("favourites-hover"); + } else if (e.target?.parentNode?.className?.indexOf("category-title") > -1) { + // Hovering over the Edit button + e.target.parentNode.classList.add("favourites-hover"); + } else if (e.target?.parentNode?.parentNode?.className?.indexOf("category-title") > -1) { + // Hovering over the image on the Edit button + e.target.parentNode.parentNode.classList.add("favourites-hover"); + } + } + + + /** + * Handler for favourite dragleave events. + * Removes the visual cue. + * + * @param {event} e + */ + favDragleave(e) { + e.stopPropagation(); + e.preventDefault(); + document.querySelector("#categories a").classList.remove("favourites-hover"); + } + + + /** + * Handler for favourite drop events. + * Adds the dragged operation to the favourites list. + * + * @param {event} e + */ + favDrop(e) { + e.stopPropagation(); + e.preventDefault(); + e.target.classList.remove("favourites-hover"); + + const opName = e.dataTransfer.getData("Text"); + this.app.addFavourite(opName); + } + + + /** + * Handler for ingredient change events. + * + * @fires Manager#statechange + */ + ingChange(e) { + if (e && e?.target?.classList?.contains("no-state-change")) return; + window.dispatchEvent(this.manager.statechange); + } + + /** + * Handler for hide-args click events. + * Updates the icon status. + * + * @fires Manager#statechange + * @param {event} e + */ + hideArgsClick(e) { + const icon = e.target; + + if (icon.getAttribute("hide-args") === "false") { + icon.setAttribute("hide-args", "true"); + icon.innerText = "keyboard_arrow_down"; + icon.classList.add("hide-args-selected"); + icon.parentNode.previousElementSibling.style.display = "none"; + } else { + icon.setAttribute("hide-args", "false"); + icon.innerText = "keyboard_arrow_up"; + icon.classList.remove("hide-args-selected"); + icon.parentNode.previousElementSibling.style.display = "grid"; + } + + const icons = Array.from(document.getElementsByClassName("hide-args-icon")); + if (icons.length > 1) { + // Check if ALL the icons are hidden/shown + const uniqueIcons = icons.map(function(item) { + return item.getAttribute("hide-args"); + }).unique(); + + const controlsIconStatus = document.getElementById("hide-icon").getAttribute("hide-args"); + + // If all icons are in the same state and the global icon isn't, fix it + if (uniqueIcons.length === 1 && icon.getAttribute("hide-args") !== controlsIconStatus) { + this.manager.controls.hideRecipeArgsClick(); + } + } + + window.dispatchEvent(this.manager.statechange); + } + + /** + * Handler for disable click events. + * Updates the icon status. + * + * @fires Manager#statechange + * @param {event} e + */ + disableClick(e) { + const icon = e.target; + + if (icon.getAttribute("disabled") === "false") { + icon.setAttribute("disabled", "true"); + icon.classList.add("disable-icon-selected"); + icon.parentNode.parentNode.classList.add("disabled"); + } else { + icon.setAttribute("disabled", "false"); + icon.classList.remove("disable-icon-selected"); + icon.parentNode.parentNode.classList.remove("disabled"); + } + + this.app.progress = 0; + window.dispatchEvent(this.manager.statechange); + } + + + /** + * Handler for breakpoint click events. + * Updates the icon status. + * + * @fires Manager#statechange + * @param {event} e + */ + breakpointClick(e) { + const bp = e.target; + + if (bp.getAttribute("break") === "false") { + bp.setAttribute("break", "true"); + bp.classList.add("breakpoint-selected"); + } else { + bp.setAttribute("break", "false"); + bp.classList.remove("breakpoint-selected"); + } + + window.dispatchEvent(this.manager.statechange); + } + + + /** + * Handler for operation doubleclick events. + * Removes the operation from the recipe and auto bakes. + * + * @fires Manager#statechange + * @param {event} e + */ + operationDblclick(e) { + e.target.remove(); + this.opRemove(e); + } + + + /** + * Handler for operation child doubleclick events. + * Removes the operation from the recipe. + * + * @fires Manager#statechange + * @param {event} e + */ + operationChildDblclick(e) { + e.target.parentNode.remove(); + this.opRemove(e); + } + + + /** + * Generates a configuration object to represent the current recipe. + * + * @returns {recipeConfig} + */ + getConfig() { + const config = []; + let ingredients, ingList, disabled, bp, item; + const operations = document.querySelectorAll("#rec-list li.operation"); + + for (let i = 0; i < operations.length; i++) { + ingredients = []; + disabled = operations[i].querySelector(".disable-icon"); + bp = operations[i].querySelector(".breakpoint"); + ingList = operations[i].querySelectorAll(".arg"); + + for (let j = 0; j < ingList.length; j++) { + if (ingList[j].getAttribute("type") === "checkbox") { + // checkbox + ingredients[j] = ingList[j].checked; + } else if (ingList[j].classList.contains("toggle-string")) { + // toggleString + ingredients[j] = { + option: ingList[j].parentNode.parentNode.querySelector("button").textContent, + string: ingList[j].value + }; + } else if (ingList[j].getAttribute("type") === "number") { + // number + ingredients[j] = parseFloat(ingList[j].value); + } else { + // all others + ingredients[j] = ingList[j].value; + } + } + + item = { + op: operations[i].querySelector(".op-title").textContent, + args: ingredients + }; + + if (disabled && disabled.getAttribute("disabled") === "true") { + item.disabled = true; + } + + if (bp && bp.getAttribute("break") === "true") { + item.breakpoint = true; + } + + config.push(item); + } + + return config; + } + + + /** + * Moves or removes the breakpoint indicator in the recipe based on the position. + * + * @param {number|boolean} position - If boolean, turn off all indicators + */ + updateBreakpointIndicator(position) { + const operations = document.querySelectorAll("#rec-list li.operation"); + if (typeof position === "boolean") position = operations.length; + for (let i = 0; i < operations.length; i++) { + if (i === position) { + operations[i].classList.add("break"); + } else { + operations[i].classList.remove("break"); + } + } + } + + + /** + * Given an operation stub element, this function converts it into a full recipe element with + * arguments. + * + * @param {element} el - The operation stub element from the operations pane + */ + buildRecipeOperation(el) { + const opName = el.textContent; + const op = new HTMLOperation(opName, this.app.operations[opName], this.app, this.manager); + el.innerHTML = op.toFullHtml(); + + if (this.app.operations[opName].flowControl) { + el.classList.add("flow-control-op"); + } + + // Disable auto-bake if this is a manual op + if (op.manualBake && this.app.autoBake_) { + this.manager.controls.setAutoBake(false); + this.app.alert("使用此操作时默认不进行自动执行。", 5000); + } + } + + + /** + * Adds the specified operation to the recipe. + * + * @fires Manager#operationadd + * @param {string} name - The name of the operation to add + * @returns {element} + */ + addOperation(name) { + const item = document.createElement("li"); + + item.classList.add("operation"); + const clean = DOMPurify.sanitize(name); + item.innerHTML = clean; + + this.buildRecipeOperation(item); + document.getElementById("rec-list").appendChild(item); + + $(item).find("[data-toggle='tooltip']").tooltip(); + + item.dispatchEvent(this.manager.operationadd); + return item; + } + + + /** + * Removes all operations from the recipe. + * + * @fires Manager#operationremove + */ + clearRecipe() { + const recList = document.getElementById("rec-list"); + while (recList.firstChild) { + recList.removeChild(recList.firstChild); + } + recList.dispatchEvent(this.manager.operationremove); + } + + + /** + * Handler for operation dropdown events from toggleString arguments. + * Sets the selected option as the name of the button. + * + * @param {event} e + */ + dropdownToggleClick(e) { + e.stopPropagation(); + e.preventDefault(); + + const el = e.target; + const button = el.parentNode.parentNode.querySelector("button"); + + button.innerHTML = el.textContent; + this.ingChange(); + } + + + /** + * Triggers various change events for operation arguments that have just been initialised. + * + * @param {HTMLElement} op + */ + triggerArgEvents(op) { + // Trigger populateOption and argSelector events + const triggerableOptions = op.querySelectorAll(".populate-option, .arg-selector"); + const evt = new Event("change", {bubbles: true}); + if (triggerableOptions.length) { + for (const el of triggerableOptions) { + el.dispatchEvent(evt); + } + } + } + + + /** + * Handler for operationadd events. + * + * @listens Manager#operationadd + * @fires Manager#statechange + * @param {event} e + */ + opAdd(e) { + log.debug(`'${e.target.querySelector(".op-title").textContent}' added to recipe`); + + this.triggerArgEvents(e.target); + window.dispatchEvent(this.manager.statechange); + } + + + /** + * Handler for operationremove events. + * + * @listens Manager#operationremove + * @fires Manager#statechange + * @param {event} e + */ + opRemove(e) { + log.debug("Operation removed from recipe"); + window.dispatchEvent(this.manager.statechange); + } + + + /** + * Handler for text argument dragover events. + * Gives the user a visual cue to show that items can be dropped here. + * + * @param {event} e + */ + textArgDragover (e) { + // This will be set if we're dragging an operation + if (e.dataTransfer.effectAllowed === "move") + return false; + + e.stopPropagation(); + e.preventDefault(); + e.target.closest("textarea.arg").classList.add("dropping-file"); + } + + + /** + * Handler for text argument dragleave events. + * Removes the visual cue. + * + * @param {event} e + */ + textArgDragLeave (e) { + e.stopPropagation(); + e.preventDefault(); + e.target.classList.remove("dropping-file"); + } + + + /** + * Handler for text argument drop events. + * Loads the dragged data into the argument textarea. + * + * @param {event} e + */ + textArgDrop(e) { + // This will be set if we're dragging an operation + if (e.dataTransfer.effectAllowed === "move") + return false; + + e.stopPropagation(); + e.preventDefault(); + const targ = e.target; + const file = e.dataTransfer.files[0]; + const text = e.dataTransfer.getData("Text"); + + targ.classList.remove("dropping-file"); + + if (text) { + targ.value = text; + return; + } + + if (file) { + const reader = new FileReader(); + const self = this; + reader.onload = function (e) { + targ.value = e.target.result; + // Trigger floating label move + const changeEvent = new Event("change"); + targ.dispatchEvent(changeEvent); + window.dispatchEvent(self.manager.statechange); + }; + reader.readAsText(file); + } + } + + + /** + * Sets register values. + * + * @param {number} opIndex + * @param {number} numPrevRegisters + * @param {string[]} registers + */ + setRegisters(opIndex, numPrevRegisters, registers) { + const op = document.querySelector(`#rec-list .operation:nth-child(${opIndex + 1})`), + prevRegList = op.querySelector(".register-list"); + + // Remove previous div + if (prevRegList) prevRegList.remove(); + + const registerList = []; + for (let i = 0; i < registers.length; i++) { + registerList.push(`$R${numPrevRegisters + i} = ${escapeControlChars(Utils.escapeHtml(Utils.truncate(registers[i], 100)))}`); + } + const registerListEl = `
        + ${registerList.join("
        ")} +
        `; + + op.insertAdjacentHTML("beforeend", registerListEl); + } + + + /** + * Adjusts the number of ingredient columns as the width of the recipe changes. + */ + adjustWidth() { + const recList = document.getElementById("rec-list"); + + // Hide Chef icon on Bake button if the page is compressed + const bakeIcon = document.querySelector("#bake img"); + + if (recList.clientWidth < 370) { + // Hide Chef icon on Bake button + bakeIcon.style.display = "none"; + } else { + bakeIcon.style.display = "inline-block"; + } + + // Scale controls to fit pane width + const controls = document.getElementById("controls"); + const controlsContent = document.getElementById("controls-content"); + const scale = (controls.clientWidth - 1) / controlsContent.scrollWidth; + + controlsContent.style.transform = `scale(${scale})`; + } + +} + +export default RecipeWaiter; diff --git a/plugins/srktoolbox/src/web/waiters/SeasonalWaiter.mjs b/plugins/srktoolbox/src/web/waiters/SeasonalWaiter.mjs new file mode 100644 index 00000000..79527439 --- /dev/null +++ b/plugins/srktoolbox/src/web/waiters/SeasonalWaiter.mjs @@ -0,0 +1,61 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +/** + * Waiter to handle seasonal events and easter eggs. + */ +class SeasonalWaiter { + + /** + * SeasonalWaiter constructor. + * + * @param {App} app - The main view object for CyberChef. + * @param {Manager} manager - The CyberChef event manager. + */ + constructor(app, manager) { + this.app = app; + this.manager = manager; + } + + + /** + * Loads all relevant items depending on the current date. + */ + load() { + // Konami code + this.kkeys = []; + window.addEventListener("keydown", this.konamiCodeListener.bind(this)); + + // CyberChef Challenge + log.info("43 6f 6e 67 72 61 74 75 6c 61 74 69 6f 6e 73 2c 20 79 6f 75 20 68 61 76 65 20 63 6f 6d 70 6c 65 74 65 64 20 43 79 62 65 72 43 68 65 66 20 63 68 61 6c 6c 65 6e 67 65 20 23 31 21 0a 0a 54 68 69 73 20 63 68 61 6c 6c 65 6e 67 65 20 65 78 70 6c 6f 72 65 64 20 68 65 78 61 64 65 63 69 6d 61 6c 20 65 6e 63 6f 64 69 6e 67 2e 20 54 6f 20 6c 65 61 72 6e 20 6d 6f 72 65 2c 20 76 69 73 69 74 20 77 69 6b 69 70 65 64 69 61 2e 6f 72 67 2f 77 69 6b 69 2f 48 65 78 61 64 65 63 69 6d 61 6c 2e 0a 0a 54 68 65 20 63 6f 64 65 20 66 6f 72 20 74 68 69 73 20 63 68 61 6c 6c 65 6e 67 65 20 69 73 20 39 64 34 63 62 63 65 66 2d 62 65 35 32 2d 34 37 35 31 2d 61 32 62 32 2d 38 33 33 38 65 36 34 30 39 34 31 36 20 28 6b 65 65 70 20 74 68 69 73 20 70 72 69 76 61 74 65 29 2e 0a 0a 54 68 65 20 6e 65 78 74 20 63 68 61 6c 6c 65 6e 67 65 20 63 61 6e 20 62 65 20 66 6f 75 6e 64 20 61 74 20 68 74 74 70 73 3a 2f 2f 70 61 73 74 65 62 69 6e 2e 63 6f 6d 2f 47 53 6e 54 41 6d 6b 56 2e"); + } + + + /** + * Listen for the Konami code sequence of keys. Turn the page upside down if they are all heard in + * sequence. + * #konamicode + */ + konamiCodeListener(e) { + this.kkeys.push(e.keyCode); + const konami = [38, 38, 40, 40, 37, 39, 37, 39, 66, 65]; + for (let i = 0; i < this.kkeys.length; i++) { + if (this.kkeys[i] !== konami[i]) { + this.kkeys = []; + break; + } + if (i === konami.length - 1) { + $("body").children().toggleClass("konami"); + this.kkeys = []; + } + } + } + +} + +export default SeasonalWaiter; diff --git a/plugins/srktoolbox/src/web/waiters/TabWaiter.mjs b/plugins/srktoolbox/src/web/waiters/TabWaiter.mjs new file mode 100644 index 00000000..85290f63 --- /dev/null +++ b/plugins/srktoolbox/src/web/waiters/TabWaiter.mjs @@ -0,0 +1,252 @@ +/** + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github + */ + +/** + * Waiter to handle events related to the input and output tabs + */ +class TabWaiter { + + /** + * TabWaiter constructor. + * + * @param {App} app - The main view object for CyberChef. + * @param {Manager} manager - The CyberChef event manager + */ + constructor(app, manager) { + this.app = app; + this.manager = manager; + } + + /** + * Calculates the maximum number of tabs to display + * + * @returns {number} + */ + calcMaxTabs() { + let numTabs = Math.floor((document.getElementById("IO").offsetWidth - 75) / 120); + numTabs = (numTabs > 1) ? numTabs : 2; + + return numTabs; + } + + /** + * Gets the currently active input or active tab number + * + * @param {string} io - Either "input" or "output" + * @returns {number} - The currently active tab or -1 + */ + getActiveTab(io) { + const activeTabs = document.getElementsByClassName(`active-${io}-tab`); + if (activeTabs.length > 0) { + if (!activeTabs.item(0).hasAttribute("inputNum")) return -1; + const tabNum = activeTabs.item(0).getAttribute("inputNum"); + return parseInt(tabNum, 10); + } + return -1; + } + + /** + * Gets the li element for the tab of a given input number + * + * @param {number} inputNum - The inputNum of the tab we're trying to get + * @param {string} io - Either "input" or "output" + * @returns {Element} + */ + getTabItem(inputNum, io) { + const tabs = document.getElementById(`${io}-tabs`).children; + for (let i = 0; i < tabs.length; i++) { + if (parseInt(tabs.item(i).getAttribute("inputNum"), 10) === inputNum) { + return tabs.item(i); + } + } + return null; + } + + /** + * Gets a list of tab numbers for the currently displayed tabs + * + * @param {string} io - Either "input" or "output" + * @returns {number[]} + */ + getTabList(io) { + const nums = [], + tabs = document.getElementById(`${io}-tabs`).children; + + for (let i = 0; i < tabs.length; i++) { + nums.push(parseInt(tabs.item(i).getAttribute("inputNum"), 10)); + } + + return nums; + } + + /** + * Creates a new tab element for the tab bar + * + * @param {number} inputNum - The inputNum of the new tab + * @param {boolean} active - If true, sets the tab to active + * @param {string} io - Either "input" or "output" + * @returns {Element} + */ + createTabElement(inputNum, active, io) { + const newTab = document.createElement("li"); + newTab.setAttribute("inputNum", inputNum.toString()); + + if (active) newTab.classList.add(`active-${io}-tab`); + + const newTabContent = document.createElement("div"); + newTabContent.classList.add(`${io}-tab-content`); + newTabContent.innerText = `标签 ${inputNum.toString()}`; + newTabContent.addEventListener("wheel", this.manager[io].scrollTab.bind(this.manager[io]), {passive: false}); + newTab.appendChild(newTabContent); + + if (io === "input") { + const newTabButton = document.createElement("button"), + newTabButtonIcon = document.createElement("i"); + newTabButton.type = "button"; + newTabButton.className = "btn btn-primary bmd-btn-icon btn-close-tab"; + newTabButtonIcon.classList.add("material-icons"); + newTabButtonIcon.innerText = "clear"; + newTabButton.appendChild(newTabButtonIcon); + newTabButton.addEventListener("click", this.manager.input.removeTabClick.bind(this.manager.input)); + newTab.appendChild(newTabButton); + } + + return newTab; + } + + /** + * Displays the tab bar for both the input and output + */ + showTabBar() { + document.getElementById("input-tabs-wrapper").style.display = "block"; + document.getElementById("output-tabs-wrapper").style.display = "block"; + document.getElementById("input-wrapper").classList.add("show-tabs"); + document.getElementById("output-wrapper").classList.add("show-tabs"); + document.getElementById("save-all-to-file").style.display = "inline-block"; + } + + /** + * Hides the tab bar for both the input and output + */ + hideTabBar() { + document.getElementById("input-tabs-wrapper").style.display = "none"; + document.getElementById("output-tabs-wrapper").style.display = "none"; + document.getElementById("input-wrapper").classList.remove("show-tabs"); + document.getElementById("output-wrapper").classList.remove("show-tabs"); + document.getElementById("save-all-to-file").style.display = "none"; + } + + /** + * Redraws the tab bar with an updated list of tabs, then changes to activeTab + * + * @param {number[]} nums - The inputNums of the tab bar to be drawn + * @param {number} activeTab - The inputNum of the activeTab + * @param {boolean} tabsLeft - True if there are tabs to the left of the displayed tabs + * @param {boolean} tabsRight - True if there are tabs to the right of the displayed tabs + * @param {string} io - Either "input" or "output" + */ + refreshTabs(nums, activeTab, tabsLeft, tabsRight, io) { + const tabsList = document.getElementById(`${io}-tabs`); + + // Remove existing tab elements + for (let i = tabsList.children.length - 1; i >= 0; i--) { + tabsList.children.item(i).remove(); + } + + // Create and add new tab elements + for (let i = 0; i < nums.length; i++) { + const active = (nums[i] === activeTab); + tabsList.appendChild(this.createTabElement(nums[i], active, io)); + } + + // Display shadows if there are tabs left / right of the displayed tabs + if (tabsLeft) { + tabsList.classList.add("tabs-left"); + } else { + tabsList.classList.remove("tabs-left"); + } + if (tabsRight) { + tabsList.classList.add("tabs-right"); + } else { + tabsList.classList.remove("tabs-right"); + } + + // Show or hide the tab bar depending on how many tabs we have + if (nums.length > 1) { + this.showTabBar(); + } else { + this.hideTabBar(); + } + } + + /** + * Changes the active tab to a different tab + * + * @param {number} inputNum - The inputNum of the tab to change to + * @param {string} io - Either "input" or "output" + * @return {boolean} - False if the tab is not currently being displayed + */ + changeTab(inputNum, io) { + const tabsList = document.getElementById(`${io}-tabs`); + + let found = false; + for (let i = 0; i < tabsList.children.length; i++) { + const tabNum = parseInt(tabsList.children.item(i).getAttribute("inputNum"), 10); + if (tabNum === inputNum) { + tabsList.children.item(i).classList.add(`active-${io}-tab`); + found = true; + } else { + tabsList.children.item(i).classList.remove(`active-${io}-tab`); + } + } + + return found; + } + + /** + * Updates the tab header to display a preview of the tab contents + * + * @param {number} inputNum - The inputNum of the tab to update the header of + * @param {string} data - The data to display in the tab header + * @param {string} io - Either "input" or "output" + */ + updateTabHeader(inputNum, data, io) { + const tab = this.getTabItem(inputNum, io); + if (tab === null) return; + + let headerData = `标签 ${inputNum}`; + if (data.length > 0) { + headerData = data.slice(0, 100); + headerData = `${inputNum}: ${headerData}`; + } + tab.firstElementChild.innerText = headerData; + } + + /** + * Updates the tab background to display the progress of the current tab + * + * @param {number} inputNum - The inputNum of the tab + * @param {number} progress - The current progress + * @param {number} total - The total which the progress is a percent of + * @param {string} io - Either "input" or "output" + */ + updateTabProgress(inputNum, progress, total, io) { + const tabItem = this.getTabItem(inputNum, io); + if (tabItem === null) return; + + const percentComplete = (progress / total) * 100; + if (percentComplete >= 100 || progress === false) { + tabItem.style.background = ""; + } else { + tabItem.style.background = `linear-gradient(to right, var(--title-background-colour) ${percentComplete}%, var(--primary-background-colour) ${percentComplete}%)`; + } + } + +} + +export default TabWaiter; diff --git a/plugins/srktoolbox/src/web/waiters/TimingWaiter.mjs b/plugins/srktoolbox/src/web/waiters/TimingWaiter.mjs new file mode 100644 index 00000000..7d4d102b --- /dev/null +++ b/plugins/srktoolbox/src/web/waiters/TimingWaiter.mjs @@ -0,0 +1,184 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + * + * Modified by Raka-loah@github + */ + +/** + * Waiter to handle timing of the baking process. + */ +class TimingWaiter { + + /** + * TimingWaiter constructor. + * + * @param {App} app - The main view object for CyberChef. + * @param {Manager} manager - The CyberChef event manager. + */ + constructor(app, manager) { + this.app = app; + this.manager = manager; + + this.inputs = {}; + /* + Inputs example: + "1": { + "inputEncodingStart": 0, + "inputEncodingEnd": 0, + "trigger": 0 + "chefWorkerTasked": 0, + "bakeComplete": 0, + "bakeDuration": 0, + "settingOutput": 0, + "outputDecodingStart": 0, + "outputDecodingEnd": 0, + "complete": 0 + } + */ + } + + + /** + * Record the time for an input + * + * @param {string} event + * @param {number} inputNum + * @param {number} value + */ + recordTime(event, inputNum, value=Date.now()) { + inputNum = inputNum.toString(); + if (!Object.keys(this.inputs).includes(inputNum)) { + this.inputs[inputNum] = {}; + } + log.debug(`Recording ${event} for input ${inputNum}`); + this.inputs[inputNum][event] = value; + } + + /** + * The duration of the main stages of a bake + * + * @param {number} inputNum + * @returns {number} + */ + duration(inputNum) { + const input = this.inputs[inputNum.toString()]; + + // If this input has not been encoded yet, we cannot calculate a time + if (!input || + !input.trigger || + !input.inputEncodingEnd || + !input.inputEncodingStart) + return 0; + + // input encoding can happen before a bake is triggered, so it is calculated separately + const inputEncodingTotal = input.inputEncodingEnd - input.inputEncodingStart; + + let total = 0, outputDecodingTotal = 0; + + if (input.bakeComplete && input.bakeComplete > input.trigger) + total = input.bakeComplete - input.trigger; + + if (input.settingOutput && input.settingOutput > input.trigger) + total = input.settingOutput - input.trigger; + + if (input.outputDecodingStart && (input.outputDecodingStart > input.trigger) && + input.outputDecodingEnd && (input.outputDecodingEnd > input.trigger)) { + total = input.outputDecodingEnd - input.trigger; + outputDecodingTotal = input.outputDecodingEnd - input.outputDecodingStart; + } + + if (input.complete && input.complete > input.trigger) + total = inputEncodingTotal + input.bakeDuration + outputDecodingTotal; + + return total; + } + + /** + * The total time for a completed bake + * + * @param {number} inputNum + * @returns {number} + */ + overallDuration(inputNum) { + const input = this.inputs[inputNum.toString()]; + + // If this input has not been encoded yet, we cannot calculate a time + if (!input || + !input.trigger || + !input.inputEncodingEnd || + !input.inputEncodingStart) + return 0; + + // input encoding can happen before a bake is triggered, so it is calculated separately + const inputEncodingTotal = input.inputEncodingEnd - input.inputEncodingStart; + + let total = 0; + if (input.bakeComplete && input.bakeComplete > input.trigger) + total = input.bakeComplete - input.trigger; + + if (input.settingOutput && input.settingOutput > input.trigger) + total = input.settingOutput - input.trigger; + + if (input.outputDecodingStart && input.outputDecodingStart > input.trigger) + total = input.outputDecodingStart - input.trigger; + + if (input.outputDecodingEnd && input.outputDecodingEnd > input.trigger) + total = input.outputDecodingEnd - input.trigger; + + if (input.complete && input.complete > input.trigger) + total = input.complete - input.trigger; + + return total + inputEncodingTotal; + } + + /** + * Prints out the time between stages + * + * @param {number} inputNum + * @returns {string} + */ + printStages(inputNum) { + const input = this.inputs[inputNum.toString()]; + if (!input || !input.trigger) return ""; + + const total = this.overallDuration(inputNum), + inputEncoding = input.inputEncodingEnd - input.inputEncodingStart, + outputDecoding = input.outputDecodingEnd - input.outputDecodingStart, + overhead = total - inputEncoding - outputDecoding - input.bakeDuration; + + return `输入编码: ${inputEncoding}ms +流程处理: ${input.bakeDuration}ms +输出解码: ${outputDecoding}ms +线程开销: ${overhead}ms`; + } + + /** + * Logs every interval + * + * @param {number} inputNum + */ + logAllTimes(inputNum) { + const input = this.inputs[inputNum.toString()]; + if (!input || !input.trigger) return; + + try { + log.debug(`Trigger: ${input.trigger} +inputEncodingStart: ${input.inputEncodingStart} | ${input.inputEncodingStart - input.trigger}ms since trigger +inputEncodingEnd: ${input.inputEncodingEnd} | ${input.inputEncodingEnd - input.inputEncodingStart}ms input encoding time +chefWorkerTasked: ${input.chefWorkerTasked} | ${input.chefWorkerTasked - input.trigger}ms since trigger +bakeDuration: | ${input.bakeDuration}ms duration in worker +bakeComplete: ${input.bakeComplete} | ${input.bakeComplete - input.chefWorkerTasked}ms since worker tasked +settingOutput: ${input.settingOutput} | ${input.settingOutput - input.bakeComplete}ms since worker finished +outputDecodingStart: ${input.outputDecodingStart} | ${input.outputDecodingStart - input.settingOutput}ms since output set +outputDecodingEnd: ${input.outputDecodingEnd} | ${input.outputDecodingEnd - input.outputDecodingStart}ms output encoding time +complete: ${input.complete} | ${input.complete - input.outputDecodingEnd}ms since output decoded +Total: | ${input.complete - input.trigger}ms since trigger`); + } catch (err) {} + + } + +} + +export default TimingWaiter; diff --git a/plugins/srktoolbox/src/web/waiters/WindowWaiter.mjs b/plugins/srktoolbox/src/web/waiters/WindowWaiter.mjs new file mode 100644 index 00000000..bb80e453 --- /dev/null +++ b/plugins/srktoolbox/src/web/waiters/WindowWaiter.mjs @@ -0,0 +1,63 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import { debounce } from "../../core/Utils.mjs"; + +/** + * Waiter to handle events related to the window object. + */ +class WindowWaiter { + + /** + * WindowWaiter constructor. + * + * @param {App} app - The main view object for CyberChef. + */ + constructor(app) { + this.app = app; + } + + + /** + * Handler for window resize events. + * Resets adjustable component sizes after 200ms (so that continuous resizing doesn't cause + * continuous resetting). + */ + windowResize() { + debounce(this.app.adjustComponentSizes, 200, "windowResize", this.app, [])(); + } + + + /** + * Handler for window blur events. + * Saves the current time so that we can calculate how long the window was unfocused for when + * focus is returned. + */ + windowBlur() { + this.windowBlurTime = Date.now(); + } + + + /** + * Handler for window focus events. + * + * 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. + */ + windowFocus() { + const unfocusedTime = Date.now() - this.windowBlurTime; + if (unfocusedTime > 60000) { + this.app.silentBake(); + } + } + +} + +export default WindowWaiter; diff --git a/plugins/srktoolbox/src/web/waiters/WorkerWaiter.mjs b/plugins/srktoolbox/src/web/waiters/WorkerWaiter.mjs new file mode 100644 index 00000000..b7cf69bf --- /dev/null +++ b/plugins/srktoolbox/src/web/waiters/WorkerWaiter.mjs @@ -0,0 +1,890 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import ChefWorker from "worker-loader?inline=no-fallback!../../core/ChefWorker.js"; +import DishWorker from "worker-loader?inline=no-fallback!../workers/DishWorker.mjs"; +import { debounce } from "../../core/Utils.mjs"; + +/** + * Waiter to handle conversations with the ChefWorker + */ +class WorkerWaiter { + + /** + * WorkerWaiter constructor + * + * @param {App} app - The main view object for CyberChef + * @param {Manager} manager - The CyberChef event manager + */ + constructor(app, manager) { + this.app = app; + this.manager = manager; + + this.loaded = false; + this.chefWorkers = []; + this.inputs = []; + this.inputNums = []; + this.totalOutputs = 0; + this.loadingOutputs = 0; + this.bakeId = 0; + this.callbacks = {}; + this.callbackID = 0; + + this.maxWorkers = 1; + if (navigator.hardwareConcurrency !== undefined && + navigator.hardwareConcurrency > 1) { + this.maxWorkers = navigator.hardwareConcurrency - 1; + } + + // Store dishWorker action (getDishAs or getDishTitle) + this.dishWorker = { + worker: null, + currentAction: "" + }; + this.dishWorkerQueue = []; + } + + /** + * Terminates any existing ChefWorkers and sets up a new worker + */ + setupChefWorker() { + for (let i = this.chefWorkers.length - 1; i >= 0; i--) { + this.removeChefWorker(this.chefWorkers[i]); + } + + this.addChefWorker(); + this.setupDishWorker(); + } + + /** + * Sets up a DishWorker to be used for performing Dish operations + */ + setupDishWorker() { + if (this.dishWorker.worker !== null) { + this.dishWorker.worker.terminate(); + this.dishWorker.currentAction = ""; + } + log.debug("Adding new DishWorker"); + + this.dishWorker.worker = new DishWorker(); + this.dishWorker.worker.addEventListener("message", this.handleDishMessage.bind(this)); + this.dishWorker.worker.postMessage({ + action: "setLogLevel", + data: log.getLevel() + }); + + if (this.dishWorkerQueue.length > 0) { + this.postDishMessage(this.dishWorkerQueue.splice(0, 1)[0]); + } + } + + /** + * Adds a new ChefWorker + * + * @returns {number} The index of the created worker + */ + addChefWorker() { + if (this.chefWorkers.length === this.maxWorkers) { + // Can't create any more workers + return -1; + } + + log.debug(`Adding new ChefWorker (${this.chefWorkers.length + 1}/${this.maxWorkers})`); + + // Create a new ChefWorker and send it the docURL + const newWorker = new ChefWorker(); + newWorker.addEventListener("message", this.handleChefMessage.bind(this)); + newWorker.postMessage({ + action: "setLogPrefix", + data: "ChefWorker" + }); + newWorker.postMessage({ + action: "setLogLevel", + data: log.getLevel() + }); + + let docURL = document.location.href.split(/[#?]/)[0]; + const index = docURL.lastIndexOf("/"); + if (index > 0) { + docURL = docURL.substring(0, index); + } + newWorker.postMessage({"action": "docURL", "data": docURL}); + + + // Store the worker, whether or not it's active, and the inputNum as an object + const newWorkerObj = { + worker: newWorker, + active: false, + inputNum: -1 + }; + + this.chefWorkers.push(newWorkerObj); + return this.chefWorkers.indexOf(newWorkerObj); + } + + /** + * Gets an inactive ChefWorker to be used for baking + * + * @param {boolean} [setActive=true] - If true, set the worker status to active + * @returns {number} - The index of the ChefWorker + */ + getInactiveChefWorker(setActive=true) { + for (let i = 0; i < this.chefWorkers.length; i++) { + if (!this.chefWorkers[i].active) { + this.chefWorkers[i].active = setActive; + return i; + } + } + return -1; + } + + /** + * Removes a ChefWorker + * + * @param {Object} workerObj + */ + removeChefWorker(workerObj) { + const index = this.chefWorkers.indexOf(workerObj); + if (index === -1) { + return; + } + + if (this.chefWorkers.length > 1 || this.chefWorkers[index].active) { + log.debug(`Removing ChefWorker at index ${index}`); + this.chefWorkers[index].worker.terminate(); + this.chefWorkers.splice(index, 1); + } + + // There should always be a ChefWorker loaded + if (this.chefWorkers.length === 0) { + this.addChefWorker(); + } + } + + /** + * Finds and returns the object for the ChefWorker of a given inputNum + * + * @param {number} inputNum + */ + getChefWorker(inputNum) { + for (let i = 0; i < this.chefWorkers.length; i++) { + if (this.chefWorkers[i].inputNum === inputNum) { + return this.chefWorkers[i]; + } + } + } + + /** + * Handler for messages sent back by the ChefWorkers + * + * @param {MessageEvent} e + */ + handleChefMessage(e) { + const r = e.data; + let inputNum = 0; + log.debug(`Receiving '${r.action}' from ChefWorker.`); + + if (Object.prototype.hasOwnProperty.call(r.data, "inputNum")) { + inputNum = r.data.inputNum; + } + + const currentWorker = this.getChefWorker(inputNum); + + switch (r.action) { + case "bakeComplete": + log.debug(`Bake ${inputNum} complete.`); + this.manager.timing.recordTime("bakeComplete", inputNum); + this.manager.timing.recordTime("bakeDuration", inputNum, r.data.duration); + + if (r.data.error) { + this.app.handleError(r.data.error); + this.manager.output.updateOutputError(r.data.error, inputNum, r.data.progress); + } else { + this.updateOutput(r.data, r.data.inputNum, r.data.bakeId, r.data.progress); + } + + this.app.progress = r.data.progress; + + if (r.data.progress === this.recipeConfig.length) { + this.step = false; + } + + this.workerFinished(currentWorker); + break; + case "bakeError": + this.app.handleError(r.data.error); + this.manager.output.updateOutputError(r.data.error, inputNum, r.data.progress); + this.app.progress = r.data.progress; + this.workerFinished(currentWorker); + break; + case "dishReturned": + this.callbacks[r.data.id](r.data); + break; + case "silentBakeComplete": + break; + case "workerLoaded": + this.app.workerLoaded = true; + log.debug("ChefWorker loaded"); + if (!this.loaded) { + this.app.loaded(); + this.loaded = true; + } else { + this.bakeNextInput(this.getInactiveChefWorker(false)); + } + break; + case "statusMessage": + this.manager.output.updateOutputMessage(r.data.message, r.data.inputNum, true); + break; + case "progressMessage": + this.manager.output.updateOutputProgress(r.data.progress, r.data.total, r.data.inputNum); + break; + case "optionUpdate": + log.debug(`Setting ${r.data.option} to ${r.data.value}`); + this.app.options[r.data.option] = r.data.value; + break; + case "setRegisters": + this.manager.recipe.setRegisters(r.data.opIndex, r.data.numPrevRegisters, r.data.registers); + break; + case "highlightsCalculated": + this.manager.highlighter.displayHighlights(r.data.pos, r.data.direction); + break; + default: + log.error("Unrecognised message from ChefWorker", e); + break; + } + } + + /** + * Update the value of an output + * + * @param {Object} data + * @param {number} inputNum + * @param {number} bakeId + * @param {number} progress + */ + updateOutput(data, inputNum, bakeId, progress) { + this.manager.output.updateOutputBakeId(bakeId, inputNum); + if (progress === this.recipeConfig.length) { + progress = false; + } + this.manager.output.updateOutputProgress(progress, this.recipeConfig.length, inputNum); + this.manager.output.updateOutputValue(data, inputNum, false); + + if (progress !== false) { + this.manager.output.updateOutputStatus("error", inputNum); + + if (inputNum === this.manager.tabs.getActiveTab("input")) { + this.manager.recipe.updateBreakpointIndicator(progress); + } + + } else { + this.manager.output.updateOutputStatus("baked", inputNum); + } + } + + /** + * Updates the UI to show if baking is in progress or not. + * + * @param {boolean} bakingStatus + */ + setBakingStatus(bakingStatus) { + this.app.baking = bakingStatus; + debounce(this.manager.controls.toggleBakeButtonFunction, 20, "toggleBakeButton", this, [bakingStatus ? "cancel" : "bake"])(); + + if (bakingStatus) this.manager.output.hideMagicButton(); + } + + /** + * Get the progress of the ChefWorkers + */ + getBakeProgress() { + const pendingInputs = this.inputNums.length + this.loadingOutputs + this.inputs.length; + let bakingInputs = 0; + + for (let i = 0; i < this.chefWorkers.length; i++) { + if (this.chefWorkers[i].active) { + bakingInputs++; + } + } + + const total = this.totalOutputs; + const bakedInputs = total - pendingInputs - bakingInputs; + + return { + total: total, + pending: pendingInputs, + baking: bakingInputs, + baked: bakedInputs + }; + } + + /** + * Cancels the current bake making it possible to autobake again + */ + cancelBakeForAutoBake() { + if (this.totalOutputs > 1) { + this.cancelBake(); + } else { + // In this case the UI changes can be skipped + + for (let i = this.chefWorkers.length - 1; i >= 0; i--) { + if (this.chefWorkers[i].active) { + this.removeChefWorker(this.chefWorkers[i]); + } + } + + this.inputs = []; + this.inputNums = []; + this.totalOutputs = 0; + this.loadingOutputs = 0; + } + } + + /** + * Cancels the current bake by terminating and removing all ChefWorkers + * + * @param {boolean} [silent=false] - If true, don't set the output + * @param {boolean} [killAll=false] - If true, kills all chefWorkers regardless of status + */ + cancelBake(silent=false, killAll=false) { + const deactiveOutputs = new Set(); + + for (let i = this.chefWorkers.length - 1; i >= 0; i--) { + if (this.chefWorkers[i].active || killAll) { + const inputNum = this.chefWorkers[i].inputNum; + this.removeChefWorker(this.chefWorkers[i]); + deactiveOutputs.add(inputNum); + } + } + this.setBakingStatus(false); + + this.inputs.forEach(input => { + deactiveOutputs.add(input.inputNum); + }); + + this.inputNums.forEach(inputNum => { + deactiveOutputs.add(inputNum); + }); + + deactiveOutputs.forEach(num => { + this.manager.output.updateOutputStatus("inactive", num); + }); + + const tabList = this.manager.tabs.getTabList("output"); + tabList.forEach(tab => { + this.manager.tabs.getTabItem(tab, "output").style.background = ""; + }); + + this.inputs = []; + this.inputNums = []; + this.totalOutputs = 0; + this.loadingOutputs = 0; + if (!silent) this.manager.output.set(this.manager.tabs.getActiveTab("output")); + } + + /** + * Handle a worker completing baking + * + * @param {object} workerObj - Object containing the worker information + * @param {ChefWorker} workerObj.worker - The actual worker object + * @param {number} workerObj.inputNum - The inputNum of the input being baked by the worker + * @param {boolean} workerObj.active - If true, the worker is currently baking an input + */ + workerFinished(workerObj) { + const workerIdx = this.chefWorkers.indexOf(workerObj); + this.chefWorkers[workerIdx].active = false; + if (this.inputs.length > 0) { + this.bakeNextInput(workerIdx); + } else if (this.inputNums.length === 0 && this.loadingOutputs === 0) { + // The ChefWorker is no longer needed + log.debug("No more inputs to bake."); + const progress = this.getBakeProgress(); + if (progress.total === progress.baked) { + this.bakingComplete(); + } + } + } + + /** + * Handler for completed bakes + */ + bakingComplete() { + this.setBakingStatus(false); + let duration = Date.now() - this.bakeStartTime; + duration = duration.toLocaleString() + "ms"; + const progress = this.getBakeProgress(); + + if (progress.total > 1) { + let width = progress.total.toLocaleString().length; + if (duration.length > width) { + width = duration.length; + } + width = width < 2 ? 2 : width; + + const totalStr = progress.total.toLocaleString().padStart(width, " ").replace(/ /g, " "); + const durationStr = duration.padStart(width, " ").replace(/ /g, " "); + + const inputNums = Object.keys(this.manager.output.outputs); + let avgTime = 0, + numOutputs = 0; + for (let i = 0; i < inputNums.length; i++) { + const output = this.manager.output.outputs[inputNums[i]]; + if (output.status === "baked") { + numOutputs++; + avgTime += output.data.duration; + } + } + avgTime = Math.round(avgTime / numOutputs).toLocaleString() + "ms"; + avgTime = avgTime.padStart(width, " ").replace(/ /g, " "); + + const msg = `总计: ${totalStr}
        时间: ${durationStr}
        平均: ${avgTime}`; + + const bakeInfo = document.getElementById("bake-info"); + bakeInfo.innerHTML = msg; + bakeInfo.style.display = ""; + } else { + document.getElementById("bake-info").style.display = "none"; + } + + document.getElementById("bake").style.background = ""; + this.totalOutputs = 0; // Reset for next time + log.debug("--- Bake complete ---"); + } + + /** + * Bakes the next input and tells the inputWorker to load the next input + * + * @param {number} workerIdx - The index of the worker to bake with + */ + bakeNextInput(workerIdx) { + if (this.inputs.length === 0) return; + if (workerIdx === -1) return; + if (!this.chefWorkers[workerIdx]) return; + this.chefWorkers[workerIdx].active = true; + const nextInput = this.inputs.splice(0, 1)[0]; + if (typeof nextInput.inputNum === "string") nextInput.inputNum = parseInt(nextInput.inputNum, 10); + + log.debug(`Baking input ${nextInput.inputNum}.`); + this.manager.output.updateOutputMessage(`正在执行 输入 ${nextInput.inputNum}...`, nextInput.inputNum, false); + this.manager.output.updateOutputStatus("baking", nextInput.inputNum); + + this.chefWorkers[workerIdx].inputNum = nextInput.inputNum; + const input = nextInput.input, + recipeConfig = this.recipeConfig; + + if (this.step) { + // Remove all breakpoints from the recipe up to progress + if (nextInput.progress !== false) { + for (let i = 0; i < nextInput.progress; i++) { + if ("breakpoint" in recipeConfig[i]) { + delete recipeConfig[i].breakpoint; + } + } + } + + // Set a breakpoint at the next operation so we stop baking there + if (recipeConfig[this.app.progress]) recipeConfig[this.app.progress].breakpoint = true; + } + + let transferable; + if (input instanceof ArrayBuffer || ArrayBuffer.isView(input)) { + transferable = [input]; + } + this.manager.timing.recordTime("chefWorkerTasked", nextInput.inputNum); + this.chefWorkers[workerIdx].worker.postMessage({ + action: "bake", + data: { + input: input, + recipeConfig: recipeConfig, + options: this.options, + inputNum: nextInput.inputNum, + bakeId: this.bakeId + } + }, transferable); + + if (this.inputNums.length > 0) { + this.manager.input.inputWorker.postMessage({ + action: "bakeNext", + data: { + inputNum: this.inputNums.splice(0, 1)[0], + bakeId: this.bakeId + } + }); + this.loadingOutputs++; + } + } + + /** + * Bakes the current input using the current recipe. + * + * @param {Object[]} recipeConfig + * @param {Object} options + * @param {number} progress + * @param {boolean} step + */ + bake(recipeConfig, options, progress, step) { + this.setBakingStatus(true); + this.manager.recipe.updateBreakpointIndicator(false); + this.bakeStartTime = Date.now(); + this.bakeId++; + this.recipeConfig = recipeConfig; + this.options = options; + this.progress = progress; + this.step = step; + + this.displayProgress(); + } + + /** + * Queues an input ready to be baked + * + * @param {object} inputData + * @param {string | ArrayBuffer} inputData.input + * @param {number} inputData.inputNum + * @param {number} inputData.bakeId + */ + queueInput(inputData) { + this.loadingOutputs--; + if (this.app.baking && inputData.bakeId === this.bakeId) { + this.inputs.push(inputData); + this.bakeNextInput(this.getInactiveChefWorker(true)); + } + } + + /** + * Handles if an error is thrown by QueueInput + * + * @param {object} inputData + * @param {number} inputData.inputNum + * @param {number} inputData.bakeId + */ + queueInputError(inputData) { + this.loadingOutputs--; + if (this.app.baking && inputData.bakeId === this.bakeId) { + this.manager.output.updateOutputError("Error queueing the input for a bake.", inputData.inputNum, 0); + + if (this.inputNums.length === 0) return; + + // Load the next input + this.manager.input.inputWorker.postMessage({ + action: "bakeNext", + data: { + inputNum: this.inputNums.splice(0, 1)[0], + bakeId: this.bakeId + } + }); + this.loadingOutputs++; + + } + } + + /** + * Queues a list of inputNums to be baked by ChefWorkers, and begins baking + * + * @param {object} inputData + * @param {number[]} inputData.nums - The inputNums to be queued for baking + * @param {boolean} inputData.step - If true, only execute the next operation in the recipe + * @param {number} inputData.progress - The current progress through the recipe. Used when stepping + */ + async bakeInputs(inputData) { + log.debug(`Baking input list [${inputData.nums.join(",")}]`); + + return await new Promise(resolve => { + if (this.app.baking) return; + const inputNums = inputData.nums.filter(n => n > 0); + const step = inputData.step; + + // Use cancelBake to clear out the inputs + this.cancelBake(true, false); + + this.inputNums = inputNums; + this.totalOutputs = inputNums.length; + this.app.progress = inputData.progress; + + let inactiveWorkers = 0; + for (let i = 0; i < this.chefWorkers.length; i++) { + if (!this.chefWorkers[i].active) { + inactiveWorkers++; + } + } + + for (let i = 0; i < inputNums.length - inactiveWorkers; i++) { + if (this.addChefWorker() === -1) break; + } + + this.app.bake(step); + + for (let i = 0; i < this.inputNums.length; i++) { + this.manager.output.updateOutputMessage(`Input ${inputNums[i]} has not been baked yet.`, inputNums[i], false); + this.manager.output.updateOutputStatus("pending", inputNums[i]); + } + + let numBakes = this.chefWorkers.length; + if (this.inputNums.length < numBakes) { + numBakes = this.inputNums.length; + } + for (let i = 0; i < numBakes; i++) { + this.manager.timing.recordTime("trigger", this.inputNums[0]); + this.manager.input.inputWorker.postMessage({ + action: "bakeNext", + data: { + inputNum: this.inputNums.splice(0, 1)[0], + bakeId: this.bakeId + } + }); + this.loadingOutputs++; + } + if (numBakes === 0) this.bakingComplete(); + }); + } + + /** + * Asks the ChefWorker to run a silent bake, forcing the browser to load and cache all the relevant + * JavaScript code needed to do a real bake. + * + * @param {Object[]} [recipeConfig] + */ + silentBake(recipeConfig) { + // If there aren't any active ChefWorkers, try to add one + let workerId = this.getInactiveChefWorker(); + if (workerId === -1) { + workerId = this.addChefWorker(); + } + if (workerId === -1) return; + this.chefWorkers[workerId].worker.postMessage({ + action: "silentBake", + data: { + recipeConfig: recipeConfig + } + }); + } + + /** + * Handler for messages sent back from DishWorker + * + * @param {MessageEvent} e + */ + handleDishMessage(e) { + const r = e.data; + log.debug(`Receiving '${r.action}' from DishWorker`); + + switch (r.action) { + case "dishReturned": + this.dishWorker.currentAction = ""; + this.callbacks[r.data.id](r.data); + + if (this.dishWorkerQueue.length > 0) { + this.postDishMessage(this.dishWorkerQueue.splice(0, 1)[0]); + } + + break; + default: + log.error("Unrecognised message from DishWorker", e); + break; + } + } + + /** + * Asks the DishWorker to return the dish as the specified type + * + * @param {Dish} dish + * @param {string} type + * @param {Function} callback + */ + getDishAs(dish, type, callback) { + const id = this.callbackID++; + this.callbacks[id] = callback; + if (this.dishWorker.worker === null) this.setupDishWorker(); + + this.postDishMessage({ + action: "getDishAs", + data: { + dish: dish, + type: type, + id: id + } + }); + } + + /** + * Asks the DishWorker to get the title of the dish + * + * @param {Dish} dish + * @param {number} maxLength + * @param {Function} callback + * @returns {string} + */ + getDishTitle(dish, maxLength, callback) { + const id = this.callbackID++; + this.callbacks[id] = callback; + if (this.dishWorker.worker === null) this.setupDishWorker(); + + this.postDishMessage({ + action: "getDishTitle", + data: { + dish: dish, + maxLength: maxLength, + id: id + } + }); + } + + /** + * Asks the DishWorker to translate a buffer into a specific character encoding + * + * @param {ArrayBuffer} buffer + * @param {number} encoding + * @param {Function} callback + * @returns {string} + */ + bufferToStr(buffer, encoding, callback) { + const id = this.callbackID++; + this.callbacks[id] = callback; + if (this.dishWorker.worker === null) this.setupDishWorker(); + + this.postDishMessage({ + action: "bufferToStr", + data: { + buffer: buffer, + encoding: encoding, + id: id + } + }); + } + + /** + * Queues a message to be sent to the dishWorker + * + * @param {object} message + * @param {string} message.action + * @param {object} message.data + * @param {Dish} message.data.dish + * @param {number} message.data.id + */ + queueDishMessage(message) { + if (message.action === "getDishAs") { + this.dishWorkerQueue = [message].concat(this.dishWorkerQueue); + } else { + this.dishWorkerQueue.push(message); + } + } + + /** + * Sends a message to the DishWorker + * + * @param {object} message + * @param {string} message.action + * @param {object} message.data + */ + postDishMessage(message) { + if (this.dishWorker.currentAction !== "") { + this.queueDishMessage(message); + } else { + this.dishWorker.currentAction = message.action; + this.dishWorker.worker.postMessage(message); + } + } + + /** + * Sets the console log level in the workers. + */ + setLogLevel() { + this.chefWorkers.forEach(cw => { + cw.worker.postMessage({ + action: "setLogLevel", + data: log.getLevel() + }); + }); + + if (!this.dishWorker.worker) return; + this.dishWorker.worker.postMessage({ + action: "setLogLevel", + data: log.getLevel() + }); + } + + /** + * Display the bake progress in the output bar and bake button + */ + displayProgress() { + const progress = this.getBakeProgress(); + if (progress.total === progress.baked) return; + + const percentComplete = ((progress.pending + progress.baking) / progress.total) * 100; + const bakeButton = document.getElementById("bake"); + if (this.app.baking) { + if (percentComplete < 100) { + bakeButton.style.background = `linear-gradient(to left, #fea79a ${percentComplete}%, #f44336 ${percentComplete}%)`; + } else { + bakeButton.style.background = ""; + } + } else { + // not baking + bakeButton.style.background = ""; + } + + const bakeInfo = document.getElementById("bake-info"); + if (progress.total > 1) { + let width = progress.total.toLocaleString().length; + width = width < 2 ? 2 : width; + + const totalStr = progress.total.toLocaleString().padStart(width, " ").replace(/ /g, " "); + const bakedStr = progress.baked.toLocaleString().padStart(width, " ").replace(/ /g, " "); + const pendingStr = progress.pending.toLocaleString().padStart(width, " ").replace(/ /g, " "); + const bakingStr = progress.baking.toLocaleString().padStart(width, " ").replace(/ /g, " "); + + let msg = "总计: " + totalStr; + msg += "
        已执行: " + bakedStr; + + if (progress.pending > 0) { + msg += "
        等待中: " + pendingStr; + } else if (progress.baking > 0) { + msg += "
        执行中: " + bakingStr; + } + bakeInfo.innerHTML = msg; + bakeInfo.style.display = ""; + } else { + bakeInfo.style.display = "none"; + } + + if (progress.total !== progress.baked) { + setTimeout(function() { + this.displayProgress(); + }.bind(this), 100); + } + + } + + /** + * Asks the ChefWorker to calculate 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. + */ + highlight(recipeConfig, direction, pos) { + let workerIdx = this.getInactiveChefWorker(false); + if (workerIdx === -1) { + workerIdx = this.addChefWorker(); + } + if (workerIdx === -1) return; + this.chefWorkers[workerIdx].worker.postMessage({ + action: "highlight", + data: { + recipeConfig: recipeConfig, + direction: direction, + pos: pos + } + }); + } +} + +export default WorkerWaiter; diff --git a/plugins/srktoolbox/src/web/workers/DishWorker.mjs b/plugins/srktoolbox/src/web/workers/DishWorker.mjs new file mode 100644 index 00000000..068d1300 --- /dev/null +++ b/plugins/srktoolbox/src/web/workers/DishWorker.mjs @@ -0,0 +1,116 @@ +/** + * Web worker to handle dish conversion operations. + * + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github + */ + +import Dish from "../../core/Dish.mjs"; +import DishError from "../../core/errors/DishError.mjs"; +import { CHR_ENC_SIMPLE_REVERSE_LOOKUP } from "../../core/lib/ChrEnc.mjs"; +import Utils from "../../core/Utils.mjs"; +import cptable from "codepage"; +import loglevelMessagePrefix from "loglevel-message-prefix"; + +loglevelMessagePrefix(log, { + prefixes: [], + staticPrefixes: ["DishWorker"] +}); + +self.addEventListener("message", function(e) { + // Handle message from the main thread + const r = e.data; + log.debug(`Receiving command '${r.action}'`); + + switch (r.action) { + case "getDishAs": + getDishAs(r.data); + break; + case "getDishTitle": + getDishTitle(r.data); + break; + case "bufferToStr": + bufferToStr(r.data); + break; + case "setLogLevel": + log.setLevel(r.data, false); + break; + default: + log.error(`Unknown action: '${r.action}'`); + } +}); + +/** + * Translates the dish to a given type + * + * @param {object} data + * @param {Dish} data.dish + * @param {string} data.type + * @param {number} data.id + */ +async function getDishAs(data) { + const newDish = new Dish(data.dish), + value = await newDish.get(data.type), + transferable = (data.type === "ArrayBuffer") ? [value] : undefined; + + self.postMessage({ + action: "dishReturned", + data: { + value: value, + id: data.id + } + }, transferable); +} + +/** + * Gets the title of the given dish + * + * @param {object} data + * @param {Dish} data.dish + * @param {number} data.id + * @param {number} data.maxLength + */ +async function getDishTitle(data) { + const newDish = new Dish(data.dish), + title = await newDish.getTitle(data.maxLength); + + self.postMessage({ + action: "dishReturned", + data: { + value: title, + id: data.id + } + }); +} + +/** + * Translates a buffer to a string using a specified encoding + * + * @param {object} data + * @param {ArrayBuffer} data.buffer + * @param {number} data.id + * @param {number} data.encoding + */ +async function bufferToStr(data) { + let str; + if (data.encoding === 0) { + str = Utils.arrayBufferToStr(data.buffer); + } else { + try { + str = cptable.utils.decode(data.encoding, new Uint8Array(data.buffer)); + } catch (err) { + str = new DishError(`文本解码时发生错误: ${CHR_ENC_SIMPLE_REVERSE_LOOKUP[data.encoding]}: ${err.message}`).toString(); + } + } + + self.postMessage({ + action: "dishReturned", + data: { + value: str, + id: data.id + } + }); +} diff --git a/plugins/srktoolbox/src/web/workers/InputWorker.mjs b/plugins/srktoolbox/src/web/workers/InputWorker.mjs new file mode 100644 index 00000000..6c42c0f2 --- /dev/null +++ b/plugins/srktoolbox/src/web/workers/InputWorker.mjs @@ -0,0 +1,965 @@ +/** + * Web worker to handle the inputs. + * Handles storage, modification and retrieval of the inputs. + * + * @author j433866 [j433866@gmail.com] + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github + */ + +import Utils from "../../core/Utils.mjs"; +import loglevelMessagePrefix from "loglevel-message-prefix"; + +loglevelMessagePrefix(log, { + prefixes: [], + staticPrefixes: ["InputWorker"] +}); + +// Default max values +// These will be correctly calculated automatically +self.maxWorkers = 4; +self.maxTabs = 1; + +/** + * Dictionary of inputs keyed on the inputNum + * Each entry is an object with the following type: + * @typedef {Object} Input + * @property {string} type + * @property {ArrayBuffer} buffer + * @property {string} stringSample + * @property {Object} file + * @property {string} file.name + * @property {number} file.size + * @property {string} file.type + * @property {string} status + * @property {number} progress + * @property {number} encoding + * @property {string} eolSequence + */ +self.inputs = {}; +self.loaderWorkers = []; +self.pendingFiles = []; +self.currentInputNum = 1; +self.numInputs = 0; +self.pendingInputs = 0; +self.loadingInputs = 0; + +/** + * Respond to message from parent thread. + * + * @param {MessageEvent} e + */ +self.addEventListener("message", function(e) { + const r = e.data; + if (!("action" in r)) { + log.error("No action"); + return; + } + + log.debug(`Receiving command '${r.action}'`); + + switch (r.action) { + case "loadUIFiles": + self.loadFiles(r.data); + break; + case "loaderWorkerReady": + self.loaderWorkerReady(r.data); + break; + case "updateMaxWorkers": + self.maxWorkers = r.data; + break; + case "updateMaxTabs": + self.updateMaxTabs(r.data.maxTabs, r.data.activeTab); + break; + case "updateInputValue": + self.updateInputValue(r.data); + break; + case "updateInputProgress": + self.updateInputProgress(r.data); + break; + case "bakeAll": + self.bakeAllInputs(); + break; + case "bakeNext": + self.bakeInput(r.data.inputNum, r.data.bakeId); + break; + case "getLoadProgress": + self.getLoadProgress(r.data); + break; + case "setInput": + self.setInput(r.data); + break; + case "setLogLevel": + log.setLevel(r.data, false); + break; + case "addInput": + self.addInput(r.data, "userinput"); + break; + case "refreshTabs": + self.refreshTabs(r.data.inputNum, r.data.direction); + break; + case "removeInput": + self.removeInput(r.data); + break; + case "changeTabRight": + self.changeTabRight(r.data.activeTab); + break; + case "changeTabLeft": + self.changeTabLeft(r.data.activeTab); + break; + case "autobake": + self.autoBake(r.data.activeTab, 0, false); + break; + case "filterTabs": + self.filterTabs(r.data); + break; + case "loaderWorkerMessage": + self.handleLoaderMessage(r.data); + break; + case "updateTabHeader": + self.updateTabHeader(r.data); + break; + case "step": + self.autoBake(r.data.activeTab, r.data.progress, true); + break; + case "getInput": + self.getInput(r.data); + break; + case "getInputNums": + self.getInputNums(r.data); + break; + default: + log.error(`Unknown action '${r.action}'.`); + } +}); + +/** + * Gets the load progress of the input files, and the + * load progress for the input given in inputNum + * + * @param {number} inputNum - The input to get the file loading progress for + */ +self.getLoadProgress = function(inputNum) { + const total = self.numInputs; + const pending = self.pendingFiles.length; + const loading = self.loadingInputs; + const loaded = total - pending - loading; + + self.postMessage({ + action: "loadingInfo", + data: { + pending: pending, + loading: loading, + loaded: loaded, + total: total, + activeProgress: { + inputNum: inputNum, + progress: self.getInputProgress(inputNum) + } + } + }); +}; + +/** + * Fired when an autobake is initiated. + * Queues the active input and sends a bake command. + * + * @param {number} inputNum - The input to be baked + * @param {number} progress - The current progress of the bake through the recipe + * @param {boolean} [step=false] - Set to true if we should only execute one operation instead of the + * whole recipe + */ +self.autoBake = function(inputNum, progress, step=false) { + const input = self.inputs[inputNum]; + if (input) { + self.postMessage({ + action: "bakeInputs", + data: { + nums: [parseInt(inputNum, 10)], + step: step, + progress: progress + } + }); + } +}; + +/** + * Fired when we want to bake all inputs (bake button clicked) + * Sends a list of inputNums to the workerwaiter + */ +self.bakeAllInputs = function() { + const inputNums = Object.keys(self.inputs); + + const nums = inputNums + .filter(n => self.inputs[n].status === "loaded") + .map(n => parseInt(n, 10)); + + self.postMessage({ + action: "bakeInputs", + data: { + nums: nums, + step: false, + progress: 0 + } + }); +}; + +/** + * Gets the data for the provided inputNum and sends it to the WorkerWaiter + * + * @param {number} inputNum + * @param {number} bakeId + */ +self.bakeInput = function(inputNum, bakeId) { + const inputObj = self.inputs[inputNum]; + if (inputObj === null || + inputObj === undefined || + inputObj.status !== "loaded") { + + self.postMessage({ + action: "queueInputError", + data: { + inputNum: inputNum, + bakeId: bakeId + } + }); + return; + } + + self.postMessage({ + action: "queueInput", + data: { + input: inputObj.buffer, + inputNum: inputNum, + bakeId: bakeId + } + }); +}; + +/** + * Gets the stored value or object for a specific inputNum and sends it to the inputWaiter. + * + * @param {object} inputData - Object containing data about the input to retrieve + * @param {number} inputData.inputNum - The inputNum of the input to get + * @param {boolean} inputData.getObj - If true, returns the entire input object instead of just the value + * @param {number} inputData.id - The callback ID for the callback to run when returned to the inputWaiter + */ +self.getInput = function(inputData) { + const input = self.inputs[inputData.inputNum]; + self.postMessage({ + action: "getInput", + data: { + data: inputData.getObj ? input : input.buffer, + id: inputData.id + } + }); +}; + +/** + * Gets a list of the stored inputNums, along with the minimum and maximum + * + * @param {number} id - The callback ID to be executed when returned to the inputWaiter + */ +self.getInputNums = function(id) { + const inputNums = Object.keys(self.inputs), + min = self.getSmallestInputNum(inputNums), + max = self.getLargestInputNum(inputNums); + + self.postMessage({ + action: "getInputNums", + data: { + inputNums: inputNums, + min: min, + max: max, + id: id + } + }); +}; + +/** + * Gets the load progress for a specific inputNum + * + * @param {number} inputNum - The input we want to get the progress of + * @returns {number | string} - Returns "error" if there was a load error + */ +self.getInputProgress = function(inputNum) { + const inputObj = self.inputs[inputNum]; + if (!inputObj) return; + if (inputObj.status === "error") { + return "error"; + } + return inputObj.progress; +}; + +/** + * Gets the largest inputNum of all the inputs + * + * @param {string[]} inputNums - The numbers to find the largest of + * @returns {number} + */ +self.getLargestInputNum = function(inputNums) { + return inputNums.reduce((acc, val) => { + val = parseInt(val, 10); + return val > acc ? val : acc; + }, -1); +}; + +/** + * Gets the smallest inputNum of all the inputs + * + * @param {string[]} inputNums - The numbers to find the smallest of + * @returns {number} + */ +self.getSmallestInputNum = function(inputNums) { + const min = inputNums.reduce((acc, val) => { + val = parseInt(val, 10); + return val < acc ? val : acc; + }, Number.MAX_SAFE_INTEGER); + + // Assume we don't have this many tabs! + if (min === Number.MAX_SAFE_INTEGER) return -1; + + return min; +}; + +/** + * Gets the next smallest inputNum + * + * @param {number} inputNum - The current input number + * @returns {number} + */ +self.getPreviousInputNum = function(inputNum) { + const inputNums = Object.keys(self.inputs); + if (inputNums.length === 0) return -1; + + return inputNums.reduce((acc, val) => { + val = parseInt(val, 10); + return (val < inputNum && val > acc) ? val : acc; + }, self.getSmallestInputNum(inputNums)); +}; + +/** + * Gets the next largest inputNum + * + * @param {number} inputNum - The current input number + * @returns {number} + */ +self.getNextInputNum = function(inputNum) { + const inputNums = Object.keys(self.inputs); + + return inputNums.reduce((acc, val) => { + val = parseInt(val, 10); + return (val > inputNum && val < acc) ? val : acc; + }, self.getLargestInputNum(inputNums)); +}; + +/** + * Gets a list of inputNums starting from the provided inputNum. + * If direction is "left", gets the inputNums higher than the provided number. + * If direction is "right", gets the inputNums lower than the provided number. + * @param {number} inputNum - The inputNum we want to get the neighbours of + * @param {string} direction - Either "left" or "right". Determines which direction we search for nearby numbers in + * @returns {number[]} + */ +self.getNearbyNums = function(inputNum, direction) { + const nums = []; + for (let i = 0; i < self.maxTabs; i++) { + let newNum; + if (i === 0 && self.inputs[inputNum] !== undefined) { + newNum = inputNum; + } else { + switch (direction) { + case "left": + newNum = self.getNextInputNum(nums[i - 1]); + if (newNum === nums[i - 1]) { + direction = "right"; + newNum = self.getPreviousInputNum(nums[0]); + } + break; + case "right": + newNum = self.getPreviousInputNum(nums[i - 1]); + if (newNum === nums[i - 1]) { + direction = "left"; + newNum = self.getNextInputNum(nums[0]); + } + } + } + if (!nums.includes(newNum) && (newNum > 0)) { + nums.push(newNum); + } + } + nums.sort(function(a, b) { + return a - b; + }); + return nums; +}; + +/** + * Gets the data to display in the tab header for an input, and + * posts it back to the inputWaiter + * + * @param {number} inputNum - The inputNum of the tab header + */ +self.updateTabHeader = function(inputNum) { + const input = self.inputs[inputNum]; + if (!input) return; + + let header = input.type === "file" ? input.file.name : input.stringSample; + header = header.slice(0, 100).replace(/[\n\r\u2028\u2029]/g, ""); + + self.postMessage({ + action: "updateTabHeader", + data: { + inputNum: inputNum, + input: header + } + }); +}; + +/** + * Gets the input for a specific inputNum, and posts it to the inputWaiter + * so that it can be displayed in the input area + * + * @param {object} inputData + * @param {number} inputData.inputNum - The input to get the data for + * @param {boolean} inputData.silent - If false, the manager statechange event will be fired + */ +self.setInput = function(inputData) { + const {inputNum, silent} = inputData; + const input = self.inputs[inputNum]; + if (!input) return; + + self.postMessage({ + action: "setInput", + data: { + inputNum: inputNum, + inputObj: input, + silent: silent + } + }); + + self.updateTabHeader(inputNum); +}; + +/** + * Gets the nearby inputNums to the provided number, and posts them + * to the inputWaiter to be displayed on the page. + * + * @param {number} inputNum - The inputNum to find the nearby numbers for + * @param {string} direction - The direction to search for inputNums in. Either "left" or "right" + */ +self.refreshTabs = function(inputNum, direction) { + const nums = self.getNearbyNums(inputNum, direction), + inputNums = Object.keys(self.inputs), + tabsLeft = (self.getSmallestInputNum(inputNums) !== nums[0] && nums.length > 0), + tabsRight = (self.getLargestInputNum(inputNums) !== nums[nums.length - 1] && nums.length > 0); + + self.postMessage({ + action: "refreshTabs", + data: { + nums: nums, + activeTab: (nums.includes(inputNum)) ? inputNum : self.getNextInputNum(inputNum), + tabsLeft: tabsLeft, + tabsRight: tabsRight + } + }); + + // Update the tab headers for the new tabs + for (let i = 0; i < nums.length; i++) { + self.updateTabHeader(nums[i]); + } +}; + +/** + * Update the stored status for an input + * + * @param {number} inputNum - The input that's having its status changed + * @param {string} status - The status of the input + */ +self.updateInputStatus = function(inputNum, status) { + if (self.inputs[inputNum] !== undefined) { + self.inputs[inputNum].status = status; + } +}; + +/** + * Update the stored load progress of an input + * + * @param {object} inputData + * @param {number} inputData.inputNum - The input that's having its progress updated + * @param {number} inputData.progress - The load progress of the input + */ +self.updateInputProgress = function(inputData) { + const {inputNum, progress} = inputData; + + if (self.inputs[inputNum] !== undefined) { + self.inputs[inputNum].progress = progress; + } +}; + +/** + * Update the stored value of an input. + * + * @param {object} inputData + * @param {number} inputData.inputNum - The input that's having its value updated + * @param {ArrayBuffer} inputData.buffer - The new value of the input as a buffer + * @param {number} [inputData.encoding] - The character encoding of the input data + * @param {string} [inputData.eolSequence] - The end of line sequence of the input data + * @param {string} [inputData.stringSample] - A sample of the value as a string (truncated to 4096 chars) + */ +self.updateInputValue = function(inputData) { + const inputNum = parseInt(inputData.inputNum, 10); + if (inputNum < 1) return; + + if (!Object.prototype.hasOwnProperty.call(self.inputs, inputNum)) + throw new Error(`No input with ID ${inputNum} exists`); + + self.inputs[inputNum].buffer = inputData.buffer; + if ("encoding" in inputData) { + self.inputs[inputNum].encoding = inputData.encoding; + } + if ("eolSequence" in inputData) { + self.inputs[inputNum].eolSequence = inputData.eolSequence; + } + if (!("stringSample" in inputData)) { + inputData.stringSample = Utils.arrayBufferToStr(inputData.buffer.slice(0, 4096)); + } + self.inputs[inputNum].stringSample = inputData.stringSample; + self.inputs[inputNum].status = "loaded"; + self.inputs[inputNum].progress = 100; +}; + +/** + * Get the index of a loader worker object. + * Returns -1 if the worker could not be found + * + * @param {number} workerId - The ID of the worker we're searching for + * @returns {number} + */ +self.getLoaderWorkerIdx = function(workerId) { + for (let i = 0; i < self.loaderWorkers.length; i++) { + if (self.loaderWorkers[i].id === workerId) { + return i; + } + } + return -1; +}; + +/** + * Fires when a loaderWorker is ready to load files. + * Stores data about the new loaderWorker in the loaderWorkers array, + * and sends the next file to the loaderWorker to be loaded. + * + * @param {object} workerData + * @param {number} workerData.id - The ID of the new loaderWorker + */ +self.loaderWorkerReady = function(workerData) { + const newWorkerObj = { + id: workerData.id, + inputNum: -1, + active: true + }; + self.loaderWorkers.push(newWorkerObj); + self.loadNextFile(self.loaderWorkers.indexOf(newWorkerObj)); +}; + +/** + * Handler for messages sent by loaderWorkers. + * (Messages are sent between the inputWorker and loaderWorkers via the main thread) + * + * @param {object} r - The data sent by the loaderWorker + * @param {number} r.inputNum - The inputNum which the message corresponds to + * @param {string} r.error - Present if an error is fired by the loaderWorker. Contains the error message string. + * @param {ArrayBuffer} r.fileBuffer - Present if a file has finished loading. Contains the loaded file buffer. + */ +self.handleLoaderMessage = function(r) { + let inputNum = 0; + + if ("inputNum" in r) { + inputNum = r.inputNum; + } + + if ("error" in r) { + self.updateInputProgress(r.inputNum, 0); + self.updateInputStatus(r.inputNum, "error"); + + log.error(r.error); + self.loadingInputs--; + + self.terminateLoaderWorker(r.id); + self.activateLoaderWorker(); + + self.setInput({inputNum: inputNum, silent: true}); + return; + } + + if ("fileBuffer" in r) { + log.debug(`Input file ${inputNum} loaded.`); + self.loadingInputs--; + + self.updateInputValue({ + inputNum: inputNum, + buffer: r.fileBuffer + }); + + self.postMessage({ + action: "fileLoaded", + data: { + inputNum: inputNum + } + }); + + const idx = self.getLoaderWorkerIdx(r.id); + self.loadNextFile(idx); + } else if ("progress" in r) { + self.updateInputProgress(r); + } +}; + +/** + * Loads the next file using a loaderWorker + * + * @param {number} - The loaderWorker which will load the file + */ +self.loadNextFile = function(workerIdx) { + if (workerIdx === -1) return; + const id = self.loaderWorkers[workerIdx].id; + if (self.pendingFiles.length === 0) { + const workerObj = self.loaderWorkers.splice(workerIdx, 1)[0]; + self.terminateLoaderWorker(workerObj.id); + return; + } + + const nextFile = self.pendingFiles.splice(0, 1)[0]; + self.loaderWorkers[workerIdx].inputNum = nextFile.inputNum; + self.loadingInputs++; + self.postMessage({ + action: "loadInput", + data: { + file: nextFile.file, + inputNum: nextFile.inputNum, + workerId: id + } + }); +}; + +/** + * Sends a message to the inputWaiter to create a new loaderWorker. + * If there's an inactive loaderWorker that already exists, use that instead. + */ +self.activateLoaderWorker = function() { + for (let i = 0; i < self.loaderWorkers.length; i++) { + if (!self.loaderWorkers[i].active) { + self.loaderWorkers[i].active = true; + self.loadNextFile(i); + return; + } + } + self.postMessage({ + action: "activateLoaderWorker" + }); +}; + +/** + * Sends a message to the inputWaiter to terminate a loaderWorker. + * + * @param {number} id - The ID of the worker to be terminated + */ +self.terminateLoaderWorker = function(id) { + self.postMessage({ + action: "terminateLoaderWorker", + data: id + }); + // If we still have pending files, spawn a worker + if (self.pendingFiles.length > 0) { + self.activateLoaderWorker(); + } +}; + +/** + * Loads files using LoaderWorkers + * + * @param {object} filesData + * @param {FileList} filesData.files - The list of files to be loaded + * @param {number} filesData.activeTab - The active tab in the UI + */ +self.loadFiles = function(filesData) { + const {files, activeTab} = filesData; + let lastInputNum = -1; + const inputNums = []; + for (let i = 0; i < files.length; i++) { + // If the first input is empty, replace it rather than adding a new one + if (i === 0 && (!self.inputs[activeTab].buffer || self.inputs[activeTab].buffer.byteLength === 0)) { + self.removeInput({ + inputNum: activeTab, + refreshTabs: false, + removeChefWorker: false + }); + lastInputNum = self.addInput(false, "file", { + name: files[i].name, + size: files[i].size.toLocaleString(), + type: files[i].type || "unknown" + }, activeTab); + } else { + lastInputNum = self.addInput(false, "file", { + name: files[i].name, + size: files[i].size.toLocaleString(), + type: files[i].type || "unknown" + }); + } + inputNums.push(lastInputNum); + + self.pendingFiles.push({ + file: files[i], + inputNum: lastInputNum + }); + } + let max = self.maxWorkers; + if (self.pendingFiles.length < self.maxWorkers) max = self.pendingFiles.length; + + // Create loaderWorkers to load the new files + for (let i = 0; i < max; i++) { + self.activateLoaderWorker(); + } + + self.getLoadProgress(); + self.setInput({inputNum: lastInputNum, silent: true}); +}; + +/** + * Adds an input to the input dictionary + * + * @param {boolean} [changetab=false] - Whether or not to change to the new input + * @param {string} type - Either "userinput" or "file" + * @param {Object} fileData - Contains information about the file to be added to the input (only used when type is "file") + * @param {string} fileData.name - The filename of the input being added + * @param {number} fileData.size - The file size (in bytes) of the input being added + * @param {string} fileData.type - The MIME type of the input being added + * @param {number} inputNum - Defaults to auto-incrementing self.currentInputNum + */ +self.addInput = function( + changeTab = false, + type, + fileData = { + name: "unknown", + size: 0, + type: "unknown" + }, + inputNum = self.currentInputNum++ +) { + self.numInputs++; + const newInputObj = { + type: null, + buffer: new ArrayBuffer(), + stringSample: "", + file: null, + status: "pending", + progress: 0, + encoding: 0, + eolSequence: "\u000a" + }; + + switch (type) { + case "userinput": + newInputObj.type = "userinput"; + newInputObj.status = "loaded"; + newInputObj.progress = 100; + break; + case "file": + newInputObj.type = "file"; + newInputObj.file = { + name: fileData.name, + size: fileData.size, + type: fileData.type + }; + newInputObj.status = "pending"; + newInputObj.progress = 0; + break; + default: + log.error(`Invalid input type '${type}'.`); + return -1; + } + self.inputs[inputNum] = newInputObj; + + // Tell the inputWaiter we've added an input, so it can create a tab to display it + self.postMessage({ + action: "inputAdded", + data: { + changeTab: changeTab, + inputNum: inputNum + } + }); + + return inputNum; +}; + +/** + * Remove an input from the inputs dictionary + * + * @param {object} removeInputData + * @param {number} removeInputData.inputNum - The number of the input to be removed + * @param {boolean} removeInputData.refreshTabs - If true, refresh the tabs after removing the input + * @param {boolean} removeInputData.removeChefWorker - If true, remove a chefWorker from the WorkerWaiter + */ +self.removeInput = function(removeInputData) { + const inputNum = removeInputData.inputNum; + const refreshTabs = removeInputData.refreshTabs; + self.numInputs--; + + for (let i = 0; i < self.loaderWorkers.length; i++) { + if (self.loaderWorkers[i].inputNum === inputNum) { + // Terminate any loaderWorker that's loading the removed input + self.loadingInputs--; + self.terminateLoaderWorker(self.loaderWorkers[i].id); + break; + } + } + + for (let i = 0; i < self.pendingFiles.length; i++) { + // Remove the input from the pending files list + if (self.pendingFiles[i].inputNum === inputNum) { + self.pendingFiles.splice(i, 1); + break; + } + } + + delete self.inputs[inputNum]; + + if (refreshTabs) { + self.refreshTabs(self.getPreviousInputNum(inputNum), "left"); + } + + if (self.numInputs < self.maxWorkers && removeInputData.removeChefWorker) { + self.postMessage({ + action: "removeChefWorker" + }); + } +}; + +/** + * Change to the next tab. + * + * @param {number} inputNum - The inputNum of the tab to change to + */ +self.changeTabRight = function(inputNum) { + const newInput = self.getNextInputNum(inputNum); + self.postMessage({ + action: "changeTab", + data: newInput + }); +}; + +/** + * Change to the previous tab. + * + * @param {number} inputNum - The inputNum of the tab to change to + */ +self.changeTabLeft = function(inputNum) { + const newInput = self.getPreviousInputNum(inputNum); + self.postMessage({ + action: "changeTab", + data: newInput + }); +}; + +/** + * Updates the maximum number of tabs, and refreshes them if it changes + * + * @param {number} maxTabs - The new max number of tabs + * @param {number} activeTab - The currently selected tab + */ +self.updateMaxTabs = function(maxTabs, activeTab) { + if (self.maxTabs !== maxTabs) { + self.maxTabs = maxTabs; + self.refreshTabs(activeTab, "right"); + } +}; + +/** + * Search the inputs for any that match the filters provided, + * posting the results back to the inputWaiter + * + * @param {object} searchData - Object containing the search filters + * @param {boolean} searchData.showPending - If true, include pending inputs in the results + * @param {boolean} searchData.showLoading - If true, include loading inputs in the results + * @param {boolean} searchData.showLoaded - If true, include loaded inputs in the results + * @param {string} searchData.filter - A regular expression to match the inputs on + * @param {string} searchData.filterType - Either "CONTENT" or "FILENAME". Determines what should be matched with filter + * @param {number} searchData.numResults - The maximum number of results to be returned + */ +self.filterTabs = function(searchData) { + const showPending = searchData.showPending, + showLoading = searchData.showLoading, + showLoaded = searchData.showLoaded, + filterType = searchData.filterType; + + let filterExp; + try { + filterExp = new RegExp(searchData.filter, "i"); + } catch (error) { + self.postMessage({ + action: "filterTabError", + data: error.message + }); + return; + } + const numResults = searchData.numResults; + + const inputs = []; + const inputNums = Object.keys(self.inputs); + for (let i = 0; i < inputNums.length; i++) { + const iNum = inputNums[i]; + let textDisplay = ""; + let addInput = false; + if (self.inputs[iNum].status === "pending" && showPending || + self.inputs[iNum].status === "loading" && showLoading || + self.inputs[iNum].status === "loaded" && showLoaded) { + try { + if (self.inputs[iNum].type === "userinput") { + if (filterType.toLowerCase() === "内容" && + filterExp.test(self.inputs[iNum].stringSample)) { + textDisplay = self.inputs[iNum].stringSample; + addInput = true; + } + } else { + if ((filterType.toLowerCase() === "文件名" && + filterExp.test(self.inputs[iNum].file.name)) || + (filterType.toLowerCase() === "内容" && + filterExp.test(self.inputs[iNum].stringSample))) { + textDisplay = self.inputs[iNum].file.name; + addInput = true; + } + } + } catch (error) { + self.postMessage({ + action: "filterTabError", + data: error.message + }); + return; + } + } + + if (addInput) { + if (textDisplay === "" || textDisplay === undefined) { + textDisplay = "新标签"; + } + const inputItem = { + inputNum: iNum, + textDisplay: textDisplay + }; + inputs.push(inputItem); + } + if (inputs.length >= numResults) { + break; + } + } + + // Send the results back to the inputWaiter + self.postMessage({ + action: "displayTabSearchResults", + data: inputs + }); +}; diff --git a/plugins/srktoolbox/src/web/workers/LoaderWorker.js b/plugins/srktoolbox/src/web/workers/LoaderWorker.js new file mode 100644 index 00000000..ef6280f0 --- /dev/null +++ b/plugins/srktoolbox/src/web/workers/LoaderWorker.js @@ -0,0 +1,80 @@ +/** + * Web Worker to load large amounts of data without locking up the UI. + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import loglevelMessagePrefix from "loglevel-message-prefix"; + +loglevelMessagePrefix(log, { + prefixes: [], + staticPrefixes: ["LoaderWorker"] +}); + +self.id = null; + + +/** + * Respond to message from parent thread. + */ +self.addEventListener("message", function(e) { + // Handle message + const r = e.data; + log.debug(`Receiving command '${r.action}'`); + + switch (r.action) { + case "setID": + self.id = r.data.id; + break; + case "loadFile": + self.loadFile(r.data.file, r.data?.inputNum ?? ""); + break; + case "setLogLevel": + log.setLevel(r.data, false); + break; + default: + log.error(`Unknown action '${r.action}'.`); + } +}); + + +/** + * Loads a file object into an ArrayBuffer, then transfers it back to the parent thread. + * + * @param {File} file + * @param {string} inputNum + */ +self.loadFile = function(file, inputNum) { + const reader = new FileReader(); + if (file.size >= 256*256*256*128) { + self.postMessage({"error": "File size too large.", "inputNum": inputNum, "id": self.id}); + return; + } + const data = new Uint8Array(file.size); + let offset = 0; + const CHUNK_SIZE = 10485760; // 10MiB + + const seek = function() { + if (offset >= file.size) { + self.postMessage({"fileBuffer": data.buffer, "inputNum": inputNum, "id": self.id}, [data.buffer]); + return; + } + self.postMessage({"progress": Math.round(offset / file.size * 100), "inputNum": inputNum}); + const slice = file.slice(offset, offset + CHUNK_SIZE); + reader.readAsArrayBuffer(slice); + }; + + reader.onload = function(e) { + data.set(new Uint8Array(reader.result), offset); + offset += CHUNK_SIZE; + seek(); + }; + + reader.onerror = function(e) { + self.postMessage({"error": reader.error.message, "inputNum": inputNum, "id": self.id}); + }; + + seek(); +}; diff --git a/plugins/srktoolbox/src/web/workers/ZipWorker.mjs b/plugins/srktoolbox/src/web/workers/ZipWorker.mjs new file mode 100644 index 00000000..f81e2b2c --- /dev/null +++ b/plugins/srktoolbox/src/web/workers/ZipWorker.mjs @@ -0,0 +1,82 @@ +/** + * Web Worker to handle zipping the outputs for download. + * + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import zip from "zlibjs/bin/zip.min.js"; +import Utils from "../../core/Utils.mjs"; +import Dish from "../../core/Dish.mjs"; +import {detectFileType} from "../../core/lib/FileType.mjs"; +import loglevelMessagePrefix from "loglevel-message-prefix"; + +loglevelMessagePrefix(log, { + prefixes: [], + staticPrefixes: ["ZipWorker"], +}); + +const Zlib = zip.Zlib; + +/** + * Respond to message from parent thread. + */ +self.addEventListener("message", function(e) { + // Handle message from the main thread + const r = e.data; + log.debug(`Receiving command '${r.action}'`); + + switch (r.action) { + case "zipFiles": + self.zipFiles(r.data.outputs, r.data.filename, r.data.fileExtension); + break; + case "setLogLevel": + log.setLevel(r.data, false); + break; + default: + log.error(`Unknown action: '${r.action}'`); + } +}); + +self.setOption = function(...args) {}; + +/** + * Compress the files into a zip file and send the zip back + * to the OutputWaiter. + * + * @param {object} outputs + * @param {string} filename + * @param {string} fileExtension + */ +self.zipFiles = async function(outputs, filename, fileExtension) { + const zip = new Zlib.Zip(); + const inputNums = Object.keys(outputs); + + for (let i = 0; i < inputNums.length; i++) { + const iNum = inputNums[i]; + let ext = fileExtension; + + const cloned = new Dish(outputs[iNum].data.dish); + const output = new Uint8Array(await cloned.get(Dish.ARRAY_BUFFER)); + + if (fileExtension === undefined || fileExtension === "") { + // Detect automatically + const types = detectFileType(output); + if (!types.length) { + ext = ".dat"; + } else { + ext = `.${types[0].extension.split(",", 1)[0]}`; + } + } + const name = Utils.strToByteArray(iNum + ext); + + zip.addFile(output, {filename: name}); + } + + const zippedFile = zip.compress(); + self.postMessage({ + zippedFile: zippedFile.buffer, + filename: filename + }, [zippedFile.buffer]); +}; diff --git a/plugins/srktoolbox/tests/browser/00_nightwatch.js b/plugins/srktoolbox/tests/browser/00_nightwatch.js new file mode 100644 index 00000000..e75c0325 --- /dev/null +++ b/plugins/srktoolbox/tests/browser/00_nightwatch.js @@ -0,0 +1,267 @@ +/** + * Tests to ensure that the app loads correctly in a reasonable time and that operations can be run. + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +const utils = require("./browserUtils.js"); + +module.exports = { + before: browser => { + browser + .resizeWindow(1280, 800) + .url(browser.launchUrl); + }, + + "Loading screen": browser => { + // Check that the loading screen appears and then disappears within a reasonable time + browser + .waitForElementVisible("#preloader", 300) + .waitForElementNotPresent("#preloader", 10000); + }, + + "App loaded": browser => { + browser.useCss(); + // Check that various important elements are loaded + browser.expect.element("#operations").to.be.visible; + browser.expect.element("#recipe").to.be.visible; + browser.expect.element("#input").to.be.present; + browser.expect.element("#output").to.be.present; + browser.expect.element(".op-list").to.be.present; + browser.expect.element("#rec-list").to.be.visible; + browser.expect.element("#controls").to.be.visible; + browser.expect.element("#input-text").to.be.visible; + browser.expect.element("#output-text").to.be.visible; + }, + + "Operations loaded": browser => { + browser.useXpath(); + // Check that an operation in every category has been populated + browser.expect.element("//li[contains(@class, 'operation') and text()='Base64编码']").to.be.present; + browser.expect.element("//li[contains(@class, 'operation') and text()='字符转二进制']").to.be.present; + browser.expect.element("//li[contains(@class, 'operation') and text()='AES解密']").to.be.present; + browser.expect.element("//li[contains(@class, 'operation') and text()='PEM转十六进制']").to.be.present; + browser.expect.element("//li[contains(@class, 'operation') and text()='幂集']").to.be.present; + browser.expect.element("//li[contains(@class, 'operation') and text()='解析IP范围']").to.be.present; + browser.expect.element("//li[contains(@class, 'operation') and text()='移除变音符号']").to.be.present; + browser.expect.element("//li[contains(@class, 'operation') and text()='排序']").to.be.present; + browser.expect.element("//li[contains(@class, 'operation') and text()='转换到UNIX时间戳']").to.be.present; + browser.expect.element("//li[contains(@class, 'operation') and text()='提取日期']").to.be.present; + browser.expect.element("//li[contains(@class, 'operation') and text()='Gzip']").to.be.present; + browser.expect.element("//li[contains(@class, 'operation') and text()='Keccak']").to.be.present; + browser.expect.element("//li[contains(@class, 'operation') and text()='JSON美化']").to.be.present; + browser.expect.element("//li[contains(@class, 'operation') and text()='检测文件类型']").to.be.present; + browser.expect.element("//li[contains(@class, 'operation') and text()='播放媒体文件']").to.be.present; + browser.expect.element("//li[contains(@class, 'operation') and text()='x86反汇编']").to.be.present; + browser.expect.element("//li[contains(@class, 'operation') and text()='Register']").to.be.present; + }, + + "Recipe can be run": browser => { + const toHex = "//li[contains(@class, 'operation') and text()='字符转十六进制']"; + const op = "#rec-list .operation .op-title"; + + // Check that operation is visible + browser + .useXpath() + .expect.element(toHex).to.be.visible; + + // Add it to the recipe by double clicking + browser + .useXpath() + .moveToElement(toHex, 10, 10) + .useCss() + .waitForElementVisible(".popover-body", 1000) + .doubleClick("xpath", toHex); + + // Confirm that it has been added to the recipe + browser + .useCss() + .waitForElementVisible(op, 100) + .expect.element(op).text.to.contain("字符转十六进制"); + + // Enter input + browser + .useCss() + .sendKeys("#input-text .cm-content", "Don't Panic.") + .pause(1000) + .click("#bake"); + + // Check output + browser + .useCss() + .waitForElementNotVisible("#stale-indicator", 1000) + .expect.element("#output-text .cm-content").text.that.equals("44 6f 6e 27 74 20 50 61 6e 69 63 2e"); + + // Clear recipe + browser + .useCss() + .moveToElement(op, 10, 10) + .waitForElementNotPresent(".popover-body", 1000) + .click("#clr-recipe") + .waitForElementNotPresent(op); + }, + + "Test every module": browser => { + browser.useCss(); + + // BSON + loadOp("BSON反序列化", browser) + .waitForElementNotVisible("#output-loader", 5000); + + // Charts + loadOp("熵", browser) + .waitForElementNotVisible("#output-loader", 5000); + + // Ciphers + loadOp("AES加密", browser) + .waitForElementNotVisible("#output-loader", 5000); + + // Code + loadOp("XPath表达式", browser) + .waitForElementNotVisible("#output-loader", 5000); + + // Compression + loadOp("Gzip", browser) + .waitForElementNotVisible("#output-loader", 5000); + + // Crypto + loadOp("MD5", browser) + .waitForElementNotVisible("#output-loader", 5000); + + // Default + loadOp("Fork", browser) + .waitForElementNotVisible("#output-loader", 5000); + + // Diff + loadOp("Diff", browser) + .waitForElementNotVisible("#output-loader", 5000); + + // Encodings + loadOp("文本编码", browser) + .waitForElementNotVisible("#output-loader", 5000); + + // Hashing + loadOp("Streebog", browser) + .waitForElementNotVisible("#output-loader", 5000); + + // Image + loadOp("提取EXIF", browser) + .waitForElementNotVisible("#output-loader", 5000); + + // PGP + loadOp("PGP加密", browser) + .waitForElementNotVisible("#output-loader", 5000); + + // PublicKey + loadOp("十六进制转PEM", browser) + .waitForElementNotVisible("#output-loader", 5000); + + // Regex + loadOp("Strings", browser) + .waitForElementNotVisible("#output-loader", 5000); + + // Shellcode + loadOp("x86反汇编", browser) + .waitForElementNotVisible("#output-loader", 5000); + + // URL + loadOp("URL编码", browser) + .waitForElementNotVisible("#output-loader", 5000); + + // UserAgent + loadOp("解析User Agent", browser) + .waitForElementNotVisible("#output-loader", 5000); + + // YARA + loadOp("YARA规则", browser) + .waitForElementNotVisible("#output-loader", 5000); + + browser.click("#clr-recipe"); + }, + + "Move around the UI": browser => { + const otherCat = "//a[contains(@class, 'category-title') and contains(@data-target, '#cat其他')]", + genUUID = "//li[contains(@class, 'operation') and text()='生成UUID']"; + + browser.useXpath(); + + // Scroll to a lower category + browser + .getLocationInView(otherCat) + .expect.element(otherCat).to.be.visible; + + // Open category + browser + .useCss() + // .waitForElementNotVisible("#snackbar-container", 10000) + .useXpath() + .click(otherCat) + .expect.element(genUUID).to.be.visible; + + // Add op to recipe + /* mouseButtonUp drops wherever the actual cursor is, not necessarily in the right place, + so we can't test Sortable.js properly using Nightwatch. html-dnd doesn't work either. + Instead of relying on drag and drop, we double click on the op to load it. */ + browser + .getLocationInView(genUUID) + .moveToElement(genUUID, 10, 10) + .doubleClick("xpath", genUUID) + .useCss() + .waitForElementVisible(".operation .op-title", 1000) + .waitForElementNotVisible("#stale-indicator", 1000) + .expect.element("#output-text .cm-content").text.which.matches(/[\da-f-]{36}/); + + browser.click("#clr-recipe"); + }, + + "Search": browser => { + // Search for an op + browser + .useCss() + .clearValue("#search") + .setValue("#search", "md5") + .useXpath() + .waitForElementVisible("//ul[@id='search-results']//b[text()='MD5']", 1000); + }, + + "Alert bar": browser => { + // Bake nothing to create an empty output which can be copied + utils.clear(browser); + utils.bake(browser); + + // Alert bar shows and contains correct content + browser + .waitForElementNotPresent("#toast-container") + .click("#copy-output") + .waitForElementVisible("#toast-container") + .waitForElementVisible("#toast-container .toast .toast-message") + .expect.element("#toast-container .toast .toast-message").text.to.equal("原始数据复制成功。"); + + // Alert bar disappears after the correct amount of time + // Should disappear after 2000ms + browser + .waitForElementNotPresent("#toast-container .toast .toast-message", 2500) + .waitForElementNotPresent("#toast-container"); + }, + + after: browser => { + browser.end(); + } +}; + +/** + * Clears the current recipe and loads a new operation. + * + * @param {string} opName + * @param {Browser} browser + */ +function loadOp(opName, browser) { + return browser + .useCss() + .click("#clr-recipe") + .urlHash("op=" + opName); +} diff --git a/plugins/srktoolbox/tests/browser/01_io.js b/plugins/srktoolbox/tests/browser/01_io.js new file mode 100644 index 00000000..e9bd02df --- /dev/null +++ b/plugins/srktoolbox/tests/browser/01_io.js @@ -0,0 +1,755 @@ +/** + * Tests for input and output of various types to ensure the editors work as expected + * and retain data integrity, especially when it comes to special characters. + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +// import { +// clear, +// utils.setInput, +// bake, +// setChrEnc, +// setEOLSeq, +// copy, +// paste, +// loadRecipe, +// expectOutput, +// uploadFile, +// uploadFolder +// } from "./browserUtils.js"; + +const utils = require("./browserUtils.js"); + +const SPECIAL_CHARS = [ + "\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u0009\u000a\u000b\u000c\u000d\u000e\u000f", + "\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f", + "\u007f", + "\u0080\u0081\u0082\u0083\u0084\u0085\u0086\u0087\u0088\u0089\u008a\u008b\u008c\u008d\u008e\u008f", + "\u0090\u0091\u0092\u0093\u0094\u0095\u0096\u0097\u0098\u0099\u009a\u009b\u009c\u009d\u009e\u009f", + "\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\u202d\u202e\u2066\u2067\u2069\ufeff\ufff9\ufffa\ufffb\ufffc" +].join(""); + +const ALL_BYTES = [ + "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f", + "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f", + "\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f", + "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f", + "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f", + "\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f", + "\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f", + "\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f", + "\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f", + "\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf", + "\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf", + "\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf", + "\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf", + "\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef", + "\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff", +].join(""); + +const PUA_CHARS = "\ue000\ue001\uf8fe\uf8ff"; + +const MULTI_LINE_STRING =`"You know," said Arthur, "it's at times like this, when I'm trapped in a Vogon airlock with a man from Betelgeuse, and about to die of asphyxiation in deep space that I really wish I'd listened to what my mother told me when I was young." +"Why, what did she tell you?" +"I don't know, I didn't listen."`; + +const SELECTABLE_STRING = `ONE +two +ONE +three +ONE +four +ONE`; + +// Descriptions for named control characters +const CONTROL_CHAR_NAMES = { + 0: "null", + 7: "bell", + 8: "backspace", + 10: "line feed", + 11: "vertical tab", + 13: "carriage return", + 27: "escape", + 8203: "zero width space", + 8204: "zero width non-joiner", + 8205: "zero width joiner", + 8206: "left-to-right mark", + 8207: "right-to-left mark", + 8232: "line separator", + 8237: "left-to-right override", + 8238: "right-to-left override", + 8294: "left-to-right isolate", + 8295: "right-to-left isolate", + 8297: "pop directional isolate", + 8233: "paragraph separator", + 65279: "zero width no-break space", + 65532: "object replacement" +}; + +module.exports = { + before: browser => { + browser + .resizeWindow(1280, 800) + .url(browser.launchUrl) + .useCss() + .waitForElementNotPresent("#preloader", 10000) + .click("#auto-bake-label"); + }, + + "CodeMirror has loaded correctly": browser => { + /* Editor has initialised */ + browser + .useCss() + // Input + .waitForElementVisible("#input-text") + .waitForElementVisible("#input-text .cm-editor") + .waitForElementVisible("#input-text .cm-editor .cm-scroller") + .waitForElementVisible("#input-text .cm-editor .cm-scroller .cm-content") + .waitForElementVisible("#input-text .cm-editor .cm-scroller .cm-content .cm-line") + // Output + .waitForElementVisible("#output-text") + .waitForElementVisible("#output-text .cm-editor") + .waitForElementVisible("#output-text .cm-editor .cm-scroller") + .waitForElementVisible("#output-text .cm-editor .cm-scroller .cm-content") + .waitForElementVisible("#output-text .cm-editor .cm-scroller .cm-content .cm-line"); + + /* Status bar is showing and has correct values */ + browser // Input + .waitForElementVisible("#input-text .cm-status-bar") + .waitForElementVisible("#input-text .cm-status-bar .stats-length-value") + .expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("0"); + browser.waitForElementVisible("#input-text .cm-status-bar .stats-lines-value") + .expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("1"); + browser.waitForElementVisible("#input-text .cm-status-bar .chr-enc-value") + .expect.element("#input-text .cm-status-bar .chr-enc-value").text.to.equal("原始字节"); + browser.waitForElementVisible("#input-text .cm-status-bar .eol-value") + .expect.element("#input-text .cm-status-bar .eol-value").text.to.equal("LF"); + + browser // Output + .waitForElementVisible("#output-text .cm-status-bar") + .waitForElementVisible("#output-text .cm-status-bar .stats-length-value") + .expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("0"); + browser.waitForElementVisible("#output-text .cm-status-bar .stats-lines-value") + .expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("1"); + browser.waitForElementVisible("#output-text .cm-status-bar .baking-time-info") + .expect.element("#output-text .cm-status-bar .baking-time-info").text.to.contain("ms"); + browser.waitForElementVisible("#output-text .cm-status-bar .chr-enc-value") + .expect.element("#output-text .cm-status-bar .chr-enc-value").text.to.equal("原始字节"); + browser.waitForElementVisible("#output-text .cm-status-bar .eol-value") + .expect.element("#output-text .cm-status-bar .eol-value").text.to.equal("LF"); + }, + + "Adding content": browser => { + /* Status bar updates correctly */ + utils.setInput(browser, MULTI_LINE_STRING); + + browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("301"); + browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("3"); + browser.expect.element("#input-text .cm-status-bar .chr-enc-value").text.to.equal("原始字节"); + browser.expect.element("#input-text .cm-status-bar .eol-value").text.to.equal("LF"); + + browser.expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("0"); + browser.expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("1"); + browser.expect.element("#output-text .cm-status-bar .baking-time-info").text.to.contain("ms"); + browser.expect.element("#output-text .cm-status-bar .chr-enc-value").text.to.equal("原始字节"); + browser.expect.element("#output-text .cm-status-bar .eol-value").text.to.equal("LF"); + + /* Output updates correctly */ + utils.bake(browser); + browser.expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("301"); + browser.expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("3"); + browser.expect.element("#output-text .cm-status-bar .baking-time-info").text.to.contain("ms"); + browser.expect.element("#output-text .cm-status-bar .chr-enc-value").text.to.equal("原始字节"); + browser.expect.element("#output-text .cm-status-bar .eol-value").text.to.equal("LF"); + }, + + "Autobaking the latest input": browser => { + // Use the sleep recipe to simulate a long running task + utils.loadRecipe(browser, "Sleep", "input", [2000]); + + browser.waitForElementVisible("#stale-indicator"); + + // Enable previously disabled autobake + browser.expect.element("#auto-bake").to.not.be.selected; + browser.click("#auto-bake-label"); + browser.expect.element("#auto-bake").to.be.selected.before(1000); + + // Add content to the input + browser.pause(100); + browser.sendKeys("#input-text .cm-content", "1"); + browser.waitForElementVisible("#output-loader"); + browser.pause(500); + + // Make another change while the previous input is being baked + browser + .sendKeys("#input-text .cm-content", "2") + .waitForElementNotVisible("#stale-indicator") + .waitForElementNotVisible("#output-loader"); + + // Ensure we got the latest input baked + utils.expectOutput(browser, "input12"); + + // Turn autobake off again + browser.click("#auto-bake-label"); + browser.expect.element("#auto-bake").to.not.be.selected.before(1000); + }, + + "Special content": browser => { + /* Special characters are rendered correctly */ + utils.setInput(browser, SPECIAL_CHARS, false); + + // First line + for (let i = 0x0; i <= 0x8; i++) { + browser.expect.element(`#input-text .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(${i+1})`) + .to.have.property("title").equals(`Control character ${CONTROL_CHAR_NAMES[i] || "0x" + i.toString(16)}`); + browser.expect.element(`#input-text .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(${i+1})`) + .text.to.equal(String.fromCharCode(0x2400 + i)); + } + + // Tab \u0009 + browser.expect.element(`#input-text .cm-line:nth-of-type(1)`).to.have.property("textContent").match(/\u0009$/); + + // Line feed \u000a + browser.expect.element(`#input-text .cm-line:nth-of-type(1)`).to.have.property("textContent").match(/^.{10}$/); + browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("2"); + + // Second line + for (let i = 0x0b; i < SPECIAL_CHARS.length; i++) { + const index = SPECIAL_CHARS.charCodeAt(i); + const name = CONTROL_CHAR_NAMES[index] || "0x" + index.toString(16); + const value = index >= 32 ? "\u2022" : String.fromCharCode(0x2400 + index); + + browser.expect.element(`#input-text .cm-line:nth-of-type(2) .cm-specialChar:nth-of-type(${i-10})`) + .to.have.property("title").equals(`Control character ${name}`); + browser.expect.element(`#input-text .cm-line:nth-of-type(2) .cm-specialChar:nth-of-type(${i-10})`) + .text.to.equal(value); + } + + /* Output renders correctly */ + utils.setChrEnc(browser, "output", "UTF-8"); + utils.bake(browser); + + // First line + for (let i = 0x0; i <= 0x8; i++) { + browser.expect.element(`#output-text .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(${i+1})`) + .to.have.property("title").equals(`Control character ${CONTROL_CHAR_NAMES[i] || "0x" + i.toString(16)}`); + browser.expect.element(`#output-text .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(${i+1})`) + .text.to.equal(String.fromCharCode(0x2400 + i)); + } + + // Tab \u0009 + browser.expect.element(`#output-text .cm-line:nth-of-type(1)`).to.have.property("textContent").match(/\u0009$/); + + // Line feed \u000a + browser.expect.element(`#output-text .cm-line:nth-of-type(1)`).to.have.property("textContent").match(/^.{10}$/); + browser.expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("2"); + + // Second line + for (let i = 0x0b; i < SPECIAL_CHARS.length; i++) { + const index = SPECIAL_CHARS.charCodeAt(i); + const name = CONTROL_CHAR_NAMES[index] || "0x" + index.toString(16); + const value = index >= 32 ? "\u2022" : String.fromCharCode(0x2400 + index); + + browser.expect.element(`#output-text .cm-content .cm-line:nth-of-type(2) .cm-specialChar:nth-of-type(${i-10})`) + .to.have.property("title").equals(`Control character ${name}`); + browser.expect.element(`#output-text .cm-content .cm-line:nth-of-type(2) .cm-specialChar:nth-of-type(${i-10})`) + .text.to.equal(value); + } + + /* Bytes are rendered correctly */ + utils.setInput(browser, ALL_BYTES, false); + // Expect length to be 255, since one character is creating a newline + browser.expect.element(`#input-text .cm-content`).to.have.property("textContent").match(/^.{255}$/); + browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("256"); + browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("2"); + + + /* PUA \ue000-\uf8ff */ + utils.setInput(browser, PUA_CHARS, false); + utils.setChrEnc(browser, "output", "UTF-8"); + utils.bake(browser); + + // Confirm input and output as expected + /* In order to render whitespace characters as control character pictures in the output, even + when they are the designated line separator, CyberChef sometimes chooses to represent them + internally using the Unicode Private Use Area (https://en.wikipedia.org/wiki/Private_Use_Areas). + See `Utils.escapeWhitespace()` for an example of this. + Therefore, PUA characters should be rendered normally in the Input but as control character + pictures in the output. + */ + browser.expect.element(`#input-text .cm-content`).to.have.property("textContent").match(/^\ue000\ue001\uf8fe\uf8ff$/); + browser.expect.element(`#output-text .cm-content`).to.have.property("textContent").match(/^\u2400\u2401\u3cfe\u3cff$/); + + /* Can be copied */ + utils.setInput(browser, SPECIAL_CHARS, false); + utils.setChrEnc(browser, "output", "UTF-8"); + utils.bake(browser); + + // Manual copy + browser + .doubleClick("#output-text .cm-content .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(1)") + .waitForElementVisible("#output-text .cm-selectionBackground"); + utils.copy(browser); + utils.paste(browser, "#search"); // Paste into search box as this won't mess with the values + + // Ensure that the values are as expected + browser.expect.element("#search").to.have.value.that.equals("\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008"); + browser.clearValue("#search"); + + // Raw copy + browser + .click("#copy-output") + .pause(100); + utils.paste(browser, "#search"); // Paste into search box as this won't mess with the values + + // Ensure that the values are as expected + browser.expect.element("#search").to.have.value.that.matches(/^\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u0009/); + browser.clearValue("#search"); + }, + + "HTML output": browser => { + /* Displays correctly */ + utils.loadRecipe(browser, "熵", ALL_BYTES); + utils.bake(browser); + + browser + .waitForElementVisible("#output-html") + .waitForElementVisible("#output-html #chart-area"); + + /* Status bar widgets are disabled */ + browser.expect.element("#output-text .cm-status-bar .disabled .stats-length-value").to.be.visible; + browser.expect.element("#output-text .cm-status-bar .disabled .stats-lines-value").to.be.visible; + browser.expect.element("#output-text .cm-status-bar .disabled .chr-enc-value").to.be.visible; + browser.expect.element("#output-text .cm-status-bar .disabled .eol-value").to.be.visible; + + /* Displays special chars correctly */ + utils.loadRecipe(browser, "转换为表格", ",\u0000\u0001\u0002\u0003\u0004", [",", "\\r\\n", false, "HTML"]); + utils.bake(browser); + + for (let i = 0x0; i <= 0x4; i++) { + browser.expect.element(`#output-html .cm-specialChar:nth-of-type(${i+1})`) + .to.have.property("title").equals(`Control character ${CONTROL_CHAR_NAMES[i] || "0x" + i.toString(16)}`); + browser.expect.element(`#output-html .cm-specialChar:nth-of-type(${i+1})`) + .text.to.equal(String.fromCharCode(0x2400 + i)); + } + + /* Can be copied */ + // Raw copy + browser + .click("#copy-output") + .pause(100); + utils.paste(browser, "#search"); // Paste into search box as this won't mess with the values + + // Ensure that the values are as expected + browser.expect.element("#search").to.have.value.that.matches(/\u0000\u0001\u0002\u0003\u0004/); + browser.clearValue("#search"); + }, + + "Highlighting": browser => { + utils.setInput(browser, SELECTABLE_STRING); + utils.bake(browser); + + /* Selecting input text also selects other instances in input and output */ + browser // Input + .click("#auto-bake-label") + .doubleClick("#input-text .cm-content .cm-line:nth-of-type(1)") + .waitForElementVisible("#input-text .cm-selectionLayer .cm-selectionBackground") + .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(1) .cm-selectionMatch") + .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(2) .cm-selectionMatch") + .waitForElementVisible("#input-text .cm-content .cm-line:nth-of-type(3) .cm-selectionMatch") + .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(4) .cm-selectionMatch") + .waitForElementVisible("#input-text .cm-content .cm-line:nth-of-type(5) .cm-selectionMatch") + .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(6) .cm-selectionMatch") + .waitForElementVisible("#input-text .cm-content .cm-line:nth-of-type(7) .cm-selectionMatch"); + + browser // Output + .waitForElementVisible("#output-text .cm-selectionLayer .cm-selectionBackground") + .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(1) .cm-selectionMatch") + .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(2) .cm-selectionMatch") + .waitForElementVisible("#output-text .cm-content .cm-line:nth-of-type(3) .cm-selectionMatch") + .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(4) .cm-selectionMatch") + .waitForElementVisible("#output-text .cm-content .cm-line:nth-of-type(5) .cm-selectionMatch") + .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(6) .cm-selectionMatch") + .waitForElementVisible("#output-text .cm-content .cm-line:nth-of-type(7) .cm-selectionMatch"); + + /* Selecting output text highlights in input */ + browser // Output + .click("#output-text") + .waitForElementNotPresent("#input-text .cm-selectionLayer .cm-selectionBackground") + .waitForElementNotPresent("#output-text .cm-selectionLayer .cm-selectionBackground") + .waitForElementNotPresent("#input-text .cm-content .cm-line .cm-selectionMatch") + .waitForElementNotPresent("#output-text .cm-content .cm-line .cm-selectionMatch") + .doubleClick("#output-text .cm-content .cm-line:nth-of-type(7)") + .waitForElementVisible("#output-text .cm-selectionLayer .cm-selectionBackground") + .waitForElementVisible("#output-text .cm-content .cm-line:nth-of-type(1) .cm-selectionMatch") + .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(2) .cm-selectionMatch") + .waitForElementVisible("#output-text .cm-content .cm-line:nth-of-type(3) .cm-selectionMatch") + .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(4) .cm-selectionMatch") + .waitForElementVisible("#output-text .cm-content .cm-line:nth-of-type(5) .cm-selectionMatch") + .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(6) .cm-selectionMatch") + .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(7) .cm-selectionMatch"); + + browser // Input + .waitForElementVisible("#input-text .cm-selectionLayer .cm-selectionBackground") + .waitForElementVisible("#input-text .cm-content .cm-line:nth-of-type(1) .cm-selectionMatch") + .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(2) .cm-selectionMatch") + .waitForElementVisible("#input-text .cm-content .cm-line:nth-of-type(3) .cm-selectionMatch") + .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(4) .cm-selectionMatch") + .waitForElementVisible("#input-text .cm-content .cm-line:nth-of-type(5) .cm-selectionMatch") + .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(6) .cm-selectionMatch") + .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(7) .cm-selectionMatch"); + + // Turn autobake off again + browser.click("#auto-bake-label"); + }, + + "Character encoding": browser => { + const CHINESE_CHARS = "不要恐慌。"; + /* Dropup works */ + /* Selecting changes output correctly */ + utils.setInput(browser, CHINESE_CHARS, false); + utils.setChrEnc(browser, "input", "UTF-8"); + utils.bake(browser); + + /* Output encoding should be autodetected */ + browser + .waitForElementVisible("#toast-container .toast .toast-message", 5000) + .expect.element("#toast-container .toast .toast-message").text.to.equal("输出框字符编码已自动检测并切换为UTF-8"); + + utils.expectOutput(browser, CHINESE_CHARS); + + /* Change the output encoding manually to test for URL presence */ + utils.setChrEnc(browser, "output", "UTF-8"); + + /* Encodings appear in the URL */ + browser.assert.urlContains("ienc=65001"); + browser.assert.urlContains("oenc=65001"); + + /* Preserved when changing tabs */ + browser + .click("#btn-new-tab") + .waitForElementVisible("#input-tabs li:nth-of-type(2).active-input-tab"); + browser.expect.element("#input-text .chr-enc-value").text.that.equals("原始字节"); + browser.expect.element("#output-text .chr-enc-value").text.that.equals("原始字节"); + + utils.setChrEnc(browser, "input", "UTF-7"); + utils.setChrEnc(browser, "output", "UTF-7"); + + browser + .click("#input-tabs li:nth-of-type(1)") + .waitForElementVisible("#input-tabs li:nth-of-type(1).active-input-tab"); + browser.expect.element("#input-text .chr-enc-value").text.that.equals("UTF-8"); + browser.expect.element("#output-text .chr-enc-value").text.that.equals("UTF-8"); + + /* Try various encodings */ + // These are not meant to be realistic encodings for this data + utils.setInput(browser, CHINESE_CHARS, false); + utils.setChrEnc(browser, "input", "UTF-8"); + utils.setChrEnc(browser, "output", "UTF-16LE"); + utils.bake(browser); + utils.expectOutput(browser, "\uB8E4\uE88D\u81A6\u81E6\uE690\u8C85\u80E3"); + + utils.setChrEnc(browser, "output", "简体中文 GBK"); + utils.bake(browser); + utils.expectOutput(browser, "\u6D93\u5D88\uFDFF\u93AD\u612D\u53A1\u9286\u0000"); + + utils.setChrEnc(browser, "input", "UTF-7"); + utils.bake(browser); + utils.expectOutput(browser, "+Tg0-+iYE-+YFA-+YUw-"); + + utils.setChrEnc(browser, "input", "繁体中文 Big5"); + utils.bake(browser); + utils.expectOutput(browser, "\u3043\u74B6\uFDFF\u7A3A\uFDFF"); + + utils.setChrEnc(browser, "output", "Windows-1251 Cyrillic"); + utils.bake(browser); + utils.expectOutput(browser, "\u00A4\u0408\u00ADn\u00AE\u0408\u00B7W\u040EC"); + }, + + "Line endings": browser => { + /* Dropup works */ + /* Selecting changes view in input */ + utils.setInput(browser, MULTI_LINE_STRING); + + // Line endings: LF + + // Input + browser + .waitForElementPresent("#input-text .cm-content .cm-line:nth-of-type(3)") + .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(4)") + .waitForElementNotPresent("#input-text .cm-content .cm-specialChar"); + browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("301"); + browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("3"); + + // Output + utils.bake(browser); + browser + .waitForElementPresent("#output-text .cm-content .cm-line:nth-of-type(3)") + .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(4)") + .waitForElementNotPresent("#output-text .cm-content .cm-specialChar"); + browser.expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("301"); + browser.expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("3"); + + // Input EOL: VT + utils.setEOLSeq(browser, "input", "VT"); + + // Input + browser + .waitForElementPresent("#input-text .cm-content .cm-line:nth-of-type(1)") + .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(2)") + .waitForElementPresent("#input-text .cm-content .cm-specialChar"); + browser.expect.element("#input-text .cm-content .cm-specialChar").text.to.equal("␊"); + browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("301"); + browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("1"); + + // Output + utils.bake(browser); + browser + .waitForElementPresent("#output-text .cm-content .cm-line:nth-of-type(3)") + .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(4)") + .waitForElementNotPresent("#output-text .cm-content .cm-specialChar"); + browser.expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("301"); + browser.expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("3"); + + // Output EOL: VT + utils.setEOLSeq(browser, "output", "VT"); + + // Input + browser + .waitForElementPresent("#input-text .cm-content .cm-line:nth-of-type(1)") + .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(2)") + .waitForElementPresent("#input-text .cm-content .cm-specialChar"); + browser.expect.element("#input-text .cm-content .cm-specialChar").text.to.equal("␊"); + browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("301"); + browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("1"); + + // Output + browser + .waitForElementPresent("#output-text .cm-content .cm-line:nth-of-type(1)") + .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(2)") + .waitForElementPresent("#output-text .cm-content .cm-specialChar"); + browser.expect.element("#output-text .cm-content .cm-specialChar").text.to.equal("␊"); + browser.expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("301"); + browser.expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("1"); + + /* Adding new line ending changes output correctly */ + browser.sendKeys("#input-text .cm-content", browser.Keys.RETURN); + + // Input + browser + .waitForElementPresent("#input-text .cm-content .cm-line:nth-of-type(2)") + .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(3)"); + browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("302"); + browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("2"); + + // Output + utils.bake(browser); + browser + .waitForElementPresent("#output-text .cm-content .cm-line:nth-of-type(2)") + .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(3)"); + browser.expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("302"); + browser.expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("2"); + + // Input EOL: CRLF + utils.setEOLSeq(browser, "input", "CRLF"); + // Output EOL: CR + utils.setEOLSeq(browser, "output", "CR"); + browser.sendKeys("#input-text .cm-content", browser.Keys.RETURN); + + // Input + browser + .waitForElementPresent("#input-text .cm-content .cm-line:nth-of-type(2)") + .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(3)") + .waitForElementPresent("#input-text .cm-content .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(3)"); + browser.expect.element("#input-text .cm-content .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(3)").text.to.equal("␋"); + browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("304"); + browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("2"); + + // Output + utils.bake(browser); + browser + .waitForElementPresent("#output-text .cm-content .cm-line:nth-of-type(2)") + .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(3)") + .waitForElementPresent("#output-text .cm-content .cm-line:nth-of-type(2) .cm-specialChar"); + browser.expect.element("#output-text .cm-content .cm-line:nth-of-type(2) .cm-specialChar").text.to.equal("␊"); + browser.expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("304"); + browser.expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("2"); + + /* Line endings appear in the URL */ + browser.assert.urlContains("ieol=CRLF"); + browser.assert.urlContains("oeol=CR"); + + /* Preserved when changing tabs */ + browser + .click("#btn-new-tab") + .waitForElementVisible("#input-tabs li:nth-of-type(2).active-input-tab"); + browser.expect.element("#input-text .eol-value").text.that.equals("LF"); + browser.expect.element("#output-text .eol-value").text.that.equals("LF"); + + utils.setEOLSeq(browser, "input", "FF"); + utils.setEOLSeq(browser, "output", "LS"); + + browser + .click("#input-tabs li:nth-of-type(1)") + .waitForElementVisible("#input-tabs li:nth-of-type(1).active-input-tab"); + browser.expect.element("#input-text .eol-value").text.that.equals("CRLF"); + browser.expect.element("#output-text .eol-value").text.that.equals("CR"); + }, + + "File inputs": browser => { + utils.clear(browser); + + /* Side panel displays correct info */ + utils.uploadFile(browser, "files/TowelDay.jpeg"); + + browser + .waitForElementVisible("#input-text .cm-file-details") + .waitForElementVisible("#input-text .cm-file-details .file-details-toggle-shown") + .waitForElementVisible("#input-text .cm-file-details .file-details-thumbnail") + .waitForElementVisible("#input-text .cm-file-details .file-details-name") + .waitForElementVisible("#input-text .cm-file-details .file-details-size") + .waitForElementVisible("#input-text .cm-file-details .file-details-type") + .waitForElementVisible("#input-text .cm-file-details .file-details-loaded"); + browser.expect.element("#input-text .cm-file-details .file-details-name").text.that.equals("TowelDay.jpeg"); + browser.expect.element("#input-text .cm-file-details .file-details-size").text.that.equals("61,379 bytes"); + browser.expect.element("#input-text .cm-file-details .file-details-type").text.that.equals("image/jpeg"); + browser.expect.element("#input-text .cm-file-details .file-details-loaded").text.that.equals("100%"); + + /* Side panel can be hidden */ + browser + .click("#input-text .cm-file-details .file-details-toggle-shown") + .waitForElementNotPresent("#input-text .cm-file-details .file-details-toggle-shown") + .waitForElementVisible("#input-text .cm-file-details .file-details-toggle-hidden") + .expect.element("#input-text .cm-file-details").to.have.css("width").which.equals("1px"); + + browser + .click("#input-text .cm-file-details .file-details-toggle-hidden") + .waitForElementNotPresent("#input-text .cm-file-details .file-details-toggle-hidden") + .waitForElementVisible("#input-text .cm-file-details .file-details-toggle-shown") + .expect.element("#input-text .cm-file-details").to.have.css("width").which.equals("200px"); + }, + + "Folder inputs": browser => { + utils.clear(browser); + + /* Side panel displays correct info */ + utils.uploadFolder(browser, "files"); + + // Loop through tabs + for (let i = 1; i < 3; i++) { + browser + .click(`#input-tabs li:nth-of-type(${i})`) + .waitForElementVisible(`#input-tabs li:nth-of-type(${i}).active-input-tab`); + + browser + .waitForElementVisible("#input-text .cm-file-details") + .waitForElementVisible("#input-text .cm-file-details .file-details-toggle-shown") + .waitForElementVisible("#input-text .cm-file-details .file-details-thumbnail") + .waitForElementVisible("#input-text .cm-file-details .file-details-name") + .waitForElementVisible("#input-text .cm-file-details .file-details-size") + .waitForElementVisible("#input-text .cm-file-details .file-details-type") + .waitForElementVisible("#input-text .cm-file-details .file-details-loaded"); + + browser.getText("#input-text .cm-file-details .file-details-name", function(result) { + switch (result.value) { + case "TowelDay.jpeg": + browser.expect.element("#input-text .cm-file-details .file-details-name").text.that.equals("TowelDay.jpeg"); + browser.expect.element("#input-text .cm-file-details .file-details-size").text.that.equals("61,379 bytes"); + browser.expect.element("#input-text .cm-file-details .file-details-type").text.that.equals("image/jpeg"); + browser.expect.element("#input-text .cm-file-details .file-details-loaded").text.that.equals("100%"); + break; + case "Hitchhikers_Guide.jpeg": + browser.expect.element("#input-text .cm-file-details .file-details-name").text.that.equals("Hitchhikers_Guide.jpeg"); + browser.expect.element("#input-text .cm-file-details .file-details-size").text.that.equals("36,595 bytes"); + browser.expect.element("#input-text .cm-file-details .file-details-type").text.that.equals("image/jpeg"); + browser.expect.element("#input-text .cm-file-details .file-details-loaded").text.that.equals("100%"); + break; + default: + break; + } + }); + } + }, + + // "Loading from URL": browser => { + // utils.clear(browser); + + // /* Side panel displays correct info */ + // utils.uploadFile(browser, "files/TowelDay.jpeg"); + + // browser + // .waitForElementVisible("#input-text .cm-file-details") + // .waitForElementVisible("#input-text .cm-file-details .file-details-toggle-shown") + // .waitForElementVisible("#input-text .cm-file-details .file-details-thumbnail") + // .waitForElementVisible("#input-text .cm-file-details .file-details-name") + // .waitForElementVisible("#input-text .cm-file-details .file-details-size") + // .waitForElementVisible("#input-text .cm-file-details .file-details-type") + // .waitForElementVisible("#input-text .cm-file-details .file-details-loaded"); + + // /* Complex deep link populates the input correctly (encoding, eol, input) */ + // browser + // .urlHash("recipe=Base64编码('A-Za-z0-9%2B/%3D')&input=VGhlIHNoaXBzIGh1bmcgaW4gdGhlIHNreSBpbiBtdWNoIHRoZSBzYW1lIHdheSB0aGF0IGJyaWNrcyBkb24ndC4M&ienc=21866&oenc=1201&ieol=FF&oeol=PS") + // .waitForElementVisible("#rec-list li.operation"); + + // browser.expect.element(`#input-text .cm-content`).to.have.property("textContent").match(/^.{65}$/); + // browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("66"); + // browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("2"); + + // browser.expect.element("#input-text .chr-enc-value").text.that.equals("KOI8-U Ukrainian Cyrillic"); + // browser.expect.element("#output-text .chr-enc-value").text.that.equals("UTF-16BE"); + + // browser.expect.element("#input-text .eol-value").text.that.equals("FF"); + // browser.expect.element("#output-text .eol-value").text.that.equals("PS"); + + // utils.bake(browser); + + // // browser.expect.element(`#output-text .cm-content`).to.have.property("textContent").match(/^.{44}$/); + // // browser.expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("44"); + // // browser.expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("1"); + // }, + + "Replace input with output": browser => { + /* Input is correctly populated */ + utils.loadRecipe(browser, "XOR", "The ships hung in the sky in much the same way that bricks don't.", [{ "option": "十六进制", "string": "65" }, "Standard", false]); + utils.setChrEnc(browser, "input", "UTF-32LE"); + utils.setChrEnc(browser, "output", "UTF-7"); + utils.setEOLSeq(browser, "input", "CRLF"); + utils.setEOLSeq(browser, "output", "LS"); + + browser + .sendKeys("#input-text .cm-content", browser.Keys.RETURN) + .expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("2"); + utils.bake(browser); + + browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("67"); + browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("2"); + browser.expect.element("#input-text .chr-enc-value").text.that.equals("UTF-32LE"); + browser.expect.element("#input-text .eol-value").text.that.equals("CRLF"); + browser.expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("268"); + + browser + .click("#switch") + .waitForElementVisible("#stale-indicator"); + + browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("268"); + + /* Special characters, encodings and line endings all as expected */ + browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("1"); + browser.expect.element("#input-text .chr-enc-value").text.that.equals("UTF-7"); + browser.expect.element("#input-text .eol-value").text.that.equals("LS"); + browser.expect.element("#input-text .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(1)").text.to.equal("␍"); + browser.expect.element("#input-text .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(49)").text.to.equal("␑"); + browser.waitForElementNotPresent("#input-text .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(50)"); + }, + + + after: browser => { + browser.end(); + } +}; diff --git a/plugins/srktoolbox/tests/browser/02_ops.js b/plugins/srktoolbox/tests/browser/02_ops.js new file mode 100644 index 00000000..672ecd8f --- /dev/null +++ b/plugins/srktoolbox/tests/browser/02_ops.js @@ -0,0 +1,520 @@ +/** + * Tests for operations. + * The primary purpose for these test is to ensure that the operations + * output something vaguely expected (i.e. they aren't completely broken + * after a dependency update or changes to the UI), rather than to confirm + * that this output is actually accurate. Accuracy of output and testing + * of edge cases should be carried out in the operations test suite found + * in /tests/operations as this is much faster and easier to configure + * than the UI tests found here. + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +const utils = require("./browserUtils.js"); + +module.exports = { + before: browser => { + browser + .resizeWindow(1280, 800) + .url(browser.launchUrl) + .useCss() + .waitForElementNotPresent("#preloader", 10000) + .click("#auto-bake-label"); + }, + + "Sanity check operations": async browser => { + const Images = await import("../samples/Images.mjs"); + testOp(browser, "A1Z26 Cipher Decode", "20 5 19 20 15 21 20 16 21 20", "testoutput"); + testOp(browser, "A1Z26 Cipher Encode", "test input", "20 5 19 20 9 14 16 21 20"); + testOp(browser, "ADD", "test input", "Ê»ÉÊv¿ÄÆËÊ", [{ "option": "Hex", "string": "56" }]); + testOp(browser, "AES Decrypt", "b443f7f7c16ac5396a34273f6f639caa", "test output", [{ "option": "Hex", "string": "00112233445566778899aabbccddeeff" }, { "option": "Hex", "string": "00000000000000000000000000000000" }, "CBC", "Hex", "Raw", { "option": "Hex", "string": "" }]); + testOp(browser, "AES Encrypt", "test input", "e42eb8fbfb7a98fff061cd2c1a794d92", [{"option": "Hex", "string": "00112233445566778899aabbccddeeff"}, {"option": "Hex", "string": "00000000000000000000000000000000"}, "CBC", "Raw", "Hex"]); + testOp(browser, "AND", "test input", "4$04 $044", [{ "option": "Hex", "string": "34" }]); + testOp(browser, "Add line numbers", "test input", "1 test input"); + testOp(browser, ["From Hex", "Add Text To Image", "SHA2"], Images.PNG_HEX, "50cdf8ea483c55564a091650c2bccb4586f919b721e5fe9d6a61660505b4346d6ebdb2ef0cf075a7728cd84cb26ea3e477b5bd86a94a49a27d79423994afb60a", [[], ["Chef", "Center", "Middle", 0, 0, 16, "Roboto"], []]); + testOp(browser, ["From Hex", "Add Text To Image", "SHA2"], Images.PNG_HEX, "78b3055463d9167dd039e47f451acaf06c593d209f8e405b4e18011cdcf190dc0af5952be887d93c0ebd38738e978120c1294c71104e6b00d3f9de8d6320ec1c", [[], ["Chef", "Center", "Middle", 0, 0, 16, "Roboto Black"], []]); + testOp(browser, ["From Hex", "Add Text To Image", "SHA2"], Images.PNG_HEX, "4ab4d4b6cb22ad700f6cd144c2c8ecad2a094f21a1d1d5d48eb6c8f97417192f89b4512f6a78276d49668ebef5e89c3a4d14860cb79399a0dafce98c92209e07", [[], ["Chef", "Center", "Middle", 0, 0, 16, "Roboto Mono"], []]); + testOp(browser, ["From Hex", "Add Text To Image", "SHA2"], Images.PNG_HEX, "11490db4907516b4d9e256da1ac0b02b561fa7547971e6316a8a0b90c9c66585a11f3145672c6d972b1a221d3bfad9c8a97de7ff77fd9442ebc40f39c1ef9ef7", [[], ["Chef", "Center", "Middle", 0, 0, 16, "Roboto Slab"], []]); + testOp(browser, ["From Hex", "Dither Image", "SHA2"], Images.PNG_HEX, "cbf587a78915cfb14546ba83080b13e5054800802488dd0cb786b8951e7dc0b48f055260917bd0ccfc075e422b9d6aff112948562653995d74e70f0b66367ac3", [[], [], []]); + testOp(browser, ["From Hex", "Generate Image", "SHA2"], Images.PNG_HEX, "2c451762a6c9192fd31dc80765eab3f447be70ea51f6fdb6911ade4d89d4a98bd0a1ff00b08d76aac472faeceb54b66092e3f3be7bbf899bf3e55ca9c96a56aa", [[], [], []]); + testOp(browser, ["From Hex", "Image Hue/Saturation/Lightness", "SHA2"], Images.PNG_HEX, "522dfc0bbef00e05c5d6861a002039fa2952e4bbb7fe8d21d0d538ef6f9d65da82065929b4150dc5b8b49460ee6c9bef7f660b86f8d4e7442a07c61c0a152a4b", [[], [50, 50, 50], []]); + testOp(browser, ["From Hex", "Resize Image", "SHA2"], Images.PNG_HEX, "654bfbf0a0537c901459c4bc22c5fb0bacbf01af775a0733e3a1c46cda5b699bcc4ed85322d813c7bb9b245d62d64425c0766fe03d3d20bc63634e2a4df17626", [[], [64, 64], []]); + testOp(browser, "Adler-32 Checksum", "test input", "16160411"); + testOp(browser, "Affine Cipher Decode", "test input", "rcqr glnsr", [1, 2]); + testOp(browser, "Affine Cipher Encode", "test input", "gndg zoujg", [3, 1]); + testOp(browser, "AMF Decode", "\u000A\u0013\u0001\u0003a\u0006\u0009test", /"\$value": "test"/); + testOp(browser, "AMF Encode", '{"a": "test"}', "\u000A\u0013\u0001\u0003a\u0006\u0009test"); + testOp(browser, "Analyse hash", "0123456789abcdef", /CRC-64/); + testOp(browser, "Atbash Cipher", "test input", "gvhg rmkfg"); + // testOp(browser, "Avro to JSON", "test input", "test_output"); + testOp(browser, "BLAKE2b", "test input", "33ebdc8f38177f3f3f334eeb117a84e11f061bbca4db6b8923e5cec85103f59f415551a5d5a933fdb6305dc7bf84671c2540b463dbfa08ee1895cfaa5bd780b5", ["512", "十六进制", { "option": "UTF8", "string": "pass" }]); + testOp(browser, "BLAKE2s", "test input", "defe73d61dfa6e5807e4f9643e159a09ccda6be3c26dcd65f8a9bb38bfc973a7", ["256", "十六进制", { "option": "UTF8", "string": "pass" }]); + testOp(browser, "BSON反序列化", "\u0011\u0000\u0000\u0000\u0002a\u0000\u0005\u0000\u0000\u0000test\u0000\u0000", '{\u000A "a": "test"\u000A}'); + testOp(browser, "BSON序列化", '{"a":"test"}', "\u0011\u0000\u0000\u0000\u0002a\u0000\u0005\u0000\u0000\u0000test\u0000\u0000"); + // testOp(browser, "Bacon Cipher Decode", "test input", "test_output"); + // testOp(browser, "Bacon Cipher Encode", "test input", "test_output"); + testOp(browser, "Bcrypt", "test input", /^\$2a\$06\$.{53}$/, [6]); + testOp(browser, "Bcrypt compare", "test input", "Match: test input", ["$2a$05$FCfBSVX7OeRkK.9kQVFCiOYu9XtwtIbePqUiroD1lkASW9q5QClzG"]); + testOp(browser, "Bcrypt parse", "$2a$05$kXWtAIGB/R8VEzInoM5ocOTBtyc0m2YTIwFiBU/0XoW032f9QrkWW", /Rounds: 5/); + testOp(browser, "Bifid Cipher Decode", "qblb tfovy", "test input", ["pass"]); + testOp(browser, "Bifid Cipher Encode", "test input", "qblb tfovy", ["pass"]); + testOp(browser, "Bit shift left", "test input", "\u00E8\u00CA\u00E6\u00E8@\u00D2\u00DC\u00E0\u00EA\u00E8"); + testOp(browser, "Bit shift right", "test input", ":29:\u0010478::"); + testOp(browser, "Blowfish Decrypt", "10884e15427dd84ec35204e9c8e921ae", "test_output", [{"option": "Hex", "string": "1234567801234567"}, {"option": "Hex", "string": "0011223344556677"}, "CBC", "Hex", "Raw"]); + testOp(browser, "Blowfish Encrypt", "test input", "f0fadbd1d90d774f714248cf26b96410", [{"option": "Hex", "string": "1234567801234567"}, {"option": "Hex", "string": "0011223344556677"}, "CBC", "Raw", "Hex"]); + testOp(browser, ["From Hex", "Blur Image", "SHA2"], Images.PNG_HEX, "24f2e89f3e00cc35f551bbc48ea82e76474946ce0282183494d1ca3d3b0012c27b6102c4368ae056dc7fecb6df7886d86ff3d29b7e5965493f30c371eee9a24e"); + testOp(browser, ["From Hex", "Blur Image", "SHA2"], Images.PNG_HEX, "2c49d89fc10c94352c9a19f82de353c37928831d6f976a6b36eb918825a0ba027980801838228a4a0da63f1886e4fa59b6666f992ad2d2b7d4622253dc034052", [[], [5, "Gaussian"], []]); + testOp(browser, ["From Hex", "Sharpen Image", "SHA2"], Images.PNG_HEX, "acc7027642c2eeb67d7356a80ed8a1bdce9adabf656ea1294e47723f506626a7aa41f1660fa844a1e1e83b17180017ab0d5bccd7f6a341692832020dc887eaa5"); + testOp(browser, ["From Hex", "Contain Image", "SHA2"], Images.PNG_HEX, "cb871ad0722d487d56a2b18247b1aa30ecc244eb717e08e23a55cae78759553312dc1717196d7cb9daa04743e57c56fc3901ba92be5a68fb03c377f718e8efe7"); + testOpHtml(browser, "Bombe", "XTSYN WAEUG EZALY NRQIM AMLZX MFUOD AWXLY LZCUZ QOQBQ JLCPK NDDRW F", "table tr:last-child td:first-child", "ECG", ["3-rotor", "LEYJVCNIXWPBQMDRTAKZGFUHOS", "BDFHJLCPRTXVZNYEIWGAKMUSQO { + browser.end(); + } +}; + + +/** @function + * Clears the current recipe and bakes a new operation. + * + * @param {Browser} browser - Nightwatch client + * @param {string|Array} opName - name of operation to be tested, array for multiple ops + * @param {string} input - input text for test + * @param {Array|Array>} [args=[]] - arguments, nested if multiple ops + */ +function bakeOp(browser, opName, input, args=[]) { + browser.perform(function() { + console.log(`Current test: ${opName}`); + }); + utils.loadRecipe(browser, opName, input, args); + browser.waitForElementVisible("#stale-indicator", 5000); + utils.bake(browser); +} + +/** @function + * Clears the current recipe and tests a new operation. + * + * @param {Browser} browser - Nightwatch client + * @param {string|Array} opName - name of operation to be tested, array for multiple ops + * @param {string} input - input text + * @param {string|RegExp} output - expected output + * @param {Array|Array>} [args=[]] - arguments, nested if multiple ops + */ +function testOp(browser, opName, input, output, args=[]) { + bakeOp(browser, opName, input, args); + utils.expectOutput(browser, output, true); +} + +/** @function + * Clears the current recipe and tests a new operation with HTML output. + * + * @param {Browser} browser - Nightwatch client + * @param {string|Array} opName - name of operation to be tested array for multiple ops + * @param {string} input - input text + * @param {string} cssSelector - CSS selector for HTML output + * @param {string|RegExp} output - expected output + * @param {Array|Array>} [args=[]] - arguments, nested if multiple ops + */ +function testOpHtml(browser, opName, input, cssSelector, output, args=[]) { + bakeOp(browser, opName, input, args); + + if (typeof output === "string") { + browser.expect.element("#output-html " + cssSelector).text.that.equals(output); + } else if (output instanceof RegExp) { + browser.expect.element("#output-html " + cssSelector).text.that.matches(output); + } +} + +/** @function + * Clears the current recipe and tests a new Image-based operation. + * + * @param {Browser} browser - Nightwatch client + * @param {string|Array} opName - name of operation to be tested array for multiple ops + * @param {string} filename - filename of image file from samples directory + * @param {Array|Array>} [args=[]] - arguments, nested if multiple ops + */ +function testOpImage(browser, opName, filename, args=[]) { + browser.perform(function() { + console.log(`Current test: ${opName}`); + }); + utils.loadRecipe(browser, opName, "", args); + utils.uploadFile(browser, filename); + browser.waitForElementVisible("#stale-indicator", 5000); + utils.bake(browser); + + browser + .waitForElementVisible("#output-html img") + .expect.element("#output-html img").to.have.css("width").which.matches(/^[^0]\d*px/); +} + +/** @function + * Clears the current recipe and tests a new File-based operation. + * + * @param {Browser} browser - Nightwatch client + * @param {string|Array} opName - name of operation to be tested array for multiple ops + * @param {string} filename - filename of file from samples directory + * @param {string|boolean} cssSelector - CSS selector for HTML output or false for normal text output + * @param {string|RegExp} output - expected output + * @param {Array|Array>} [args=[]] - arguments, nested if multiple ops + * @param {number} [waitWindow=1000] - The number of milliseconds to wait for the output to be correct + */ +function testOpFile(browser, opName, filename, cssSelector, output, args=[], waitWindow=1000) { + browser.perform(function() { + console.log(`Current test: ${opName}`); + }); + utils.loadRecipe(browser, opName, "", args); + utils.uploadFile(browser, filename); + browser.pause(100).waitForElementVisible("#stale-indicator", 5000); + utils.bake(browser); + + if (!cssSelector) { + // Text output + utils.expectOutput(browser, output, true, waitWindow); + } else if (typeof output === "string") { + // HTML output - string match + browser.expect.element("#output-html " + cssSelector).text.that.equals(output); + } else if (output instanceof RegExp) { + // HTML output - RegEx match + browser.expect.element("#output-html " + cssSelector).text.that.matches(output); + } +} diff --git a/plugins/srktoolbox/tests/browser/browserUtils.js b/plugins/srktoolbox/tests/browser/browserUtils.js new file mode 100644 index 00000000..d10a74e9 --- /dev/null +++ b/plugins/srktoolbox/tests/browser/browserUtils.js @@ -0,0 +1,287 @@ +/** + * Utility functions for browser tests. + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ + +/** @function + * Clears the recipe and input + * + * @param {Browser} browser - Nightwatch client + */ +function clear(browser) { + browser + .useCss() + .click("#clr-recipe") + .click("#clr-io") + .waitForElementNotPresent("#rec-list li.operation") + .expect.element("#input-text .cm-content").text.that.equals(""); +} + +/** @function + * Sets the input to the desired string + * + * @param {Browser} browser - Nightwatch client + * @param {string} input - The text to populate the input with + * @param {boolean} [type=true] - Whether to type the characters in by using sendKeys, + * or to set the value of the editor directly (useful for special characters) + */ +function setInput(browser, input, type=true) { + clear(browser); + if (type) { + browser + .useCss() + .sendKeys("#input-text .cm-content", input) + .pause(100); + } else { + browser.execute(text => { + window.app.setInput(text); + }, [input]); + browser.pause(100); + } + expectInput(browser, input); +} + +/** @function + * Triggers a bake + * + * @param {Browser} browser - Nightwatch client + */ +function bake(browser) { + browser + // Ensure we're not currently busy + .waitForElementNotVisible("#output-loader", 5000) + .expect.element("#bake span").text.to.equal("开整!"); + + browser + .click("#bake") + .waitForElementNotVisible("#stale-indicator", 5000) + .waitForElementNotVisible("#output-loader", 10000); +} + +/** @function + * Sets the character encoding in the input or output + * + * @param {Browser} browser - Nightwatch client + * @param {string} io - Either "input" or "output" + * @param {string} enc - The encoding to be set + */ +function setChrEnc(browser, io, enc) { + io = `#${io}-text`; + browser + .useCss() + // .waitForElementNotVisible("#snackbar-container", 6000) + .click(io + " .chr-enc-value") + .waitForElementVisible(io + " .chr-enc-select .cm-status-bar-select-scroll") + .click("link text", enc) + .waitForElementNotVisible(io + " .chr-enc-select .cm-status-bar-select-scroll") + .expect.element(io + " .chr-enc-value").text.that.equals(enc); +} + +/** @function + * Sets the end of line sequence in the input or output + * + * @param {Browser} browser - Nightwatch client + * @param {string} io - Either "input" or "output" + * @param {string} eol - The sequence to set + */ +function setEOLSeq(browser, io, eol) { + io = `#${io}-text`; + browser + .useCss() + // .waitForElementNotVisible("#snackbar-container", 6000) + .click(io + " .eol-value") + .waitForElementVisible(io + " .eol-select .cm-status-bar-select-content") + .click(`${io} .cm-status-bar-select-content a[data-val=${eol}]`) + .waitForElementNotVisible(io + " .eol-select .cm-status-bar-select-content") + .expect.element(io + " .eol-value").text.that.equals(eol); +} + +/** @function + * Copies whatever is currently selected + * + * @param {Browser} browser - Nightwatch client + */ +function copy(browser) { + browser.perform(function() { + const actions = this.actions({async: true}); + + // Ctrl + Ins used as this works on Windows, Linux and Mac + return actions + .keyDown(browser.Keys.CONTROL) + .keyDown(browser.Keys.INSERT) + .keyUp(browser.Keys.INSERT) + .keyUp(browser.Keys.CONTROL); + }); +} + +/** @function + * Pastes into the target element + * + * @param {Browser} browser - Nightwatch client + * @param {string} el - Target element selector + */ +function paste(browser, el) { + browser + .click(el) + .perform(function() { + const actions = this.actions({async: true}); + + // Shift + Ins used as this works on Windows, Linux and Mac + return actions + .keyDown(browser.Keys.SHIFT) + .keyDown(browser.Keys.INSERT) + .keyUp(browser.Keys.INSERT) + .keyUp(browser.Keys.SHIFT); + }) + .pause(100); +} + +/** @function + * Loads a recipe and input + * + * @param {Browser} browser - Nightwatch client + * @param {string|Array} opName - name of operation to be loaded, array for multiple ops + * @param {string} input - input text for test + * @param {Array|Array>} args - arguments, nested if multiple ops + */ +function loadRecipe(browser, opName, input, args) { + let recipeConfig; + + if (typeof(opName) === "string") { + recipeConfig = JSON.stringify([{ + "op": opName, + "args": args + }]); + } else if (opName instanceof Array) { + recipeConfig = JSON.stringify( + opName.map((op, i) => { + return { + op: op, + args: args.length ? args[i] : [] + }; + }) + ); + } else { + throw new Error("Invalid operation type. Must be string or array of strings. Received: " + typeof(opName)); + } + + setInput(browser, input, false); + browser + .urlHash("recipe=" + recipeConfig) + .waitForElementPresent("#rec-list li.operation"); +} + +/** @function + * Tests whether the output matches a given value + * + * @param {Browser} browser - Nightwatch client + * @param {string|RegExp} expected - The expected output value + * @param {boolean} [waitNotNull=false] - Wait for the output to not be empty before testing the value + * @param {number} [waitWindow=1000] - The number of milliseconds to wait for the output to be correct + */ +function expectOutput(browser, expected, waitNotNull=false, waitWindow=1000) { + if (waitNotNull && expected !== "") { + browser.waitUntil(async function() { + const output = await this.execute(function() { + return window.app.manager.output.outputEditorView.state.doc.toString(); + }); + return output.length; + }, waitWindow); + } + + browser.execute(expected => { + return window.app.manager.output.outputEditorView.state.doc.toString(); + }, [expected], function({value}) { + if (expected instanceof RegExp) { + browser.expect(value).match(expected); + } else { + browser.expect(value).to.be.equal(expected); + } + }); +} + +/** @function + * Tests whether the input matches a given value + * + * @param {Browser} browser - Nightwatch client + * @param {string|RegExp} expected - The expected input value + */ +function expectInput(browser, expected) { + browser.execute(expected => { + return window.app.manager.input.inputEditorView.state.doc.toString(); + }, [expected], function({value}) { + if (expected instanceof RegExp) { + browser.expect(value).match(expected); + } else { + browser.expect(value).to.be.equal(expected); + } + }); +} + +/** @function + * Uploads a file using the #open-file input + * + * @param {Browser} browser - Nightwatch client + * @param {string} filename - A path to a file in the samples directory + */ +function uploadFile(browser, filename) { + const filepath = require("path").resolve(__dirname + "/../samples/" + filename); + + // The file input cannot be interacted with by nightwatch while it is hidden, + // so we temporarily expose it for the purposes of this test. + browser.execute(() => { + document.getElementById("open-file").style.display = "block"; + }); + browser + .pause(100) + .setValue("#open-file", filepath) + .pause(100); + browser.execute(() => { + document.getElementById("open-file").style.display = "none"; + }); + browser.pause(200); // Stale reference error on my potato system because it runs too slow -- Rakaloah + browser.waitForElementVisible("#input-text .cm-file-details"); +} + +/** @function + * Uploads a folder using the #open-folder input + * + * @param {Browser} browser - Nightwatch client + * @param {string} foldername - A path to a folder in the samples directory + */ +function uploadFolder(browser, foldername) { + const folderpath = require("path").resolve(__dirname + "/../samples/" + foldername); + + // The folder input cannot be interacted with by nightwatch while it is hidden, + // so we temporarily expose it for the purposes of this test. + browser.execute(() => { + document.getElementById("open-folder").style.display = "block"; + }); + browser + .pause(100) + .setValue("#open-folder", folderpath) + .pause(500); + browser.execute(() => { + document.getElementById("open-folder").style.display = "none"; + }); + browser.waitForElementVisible("#input-text .cm-file-details"); +} + + +module.exports = { + clear: clear, + setInput: setInput, + bake: bake, + setChrEnc: setChrEnc, + setEOLSeq: setEOLSeq, + copy: copy, + paste: paste, + loadRecipe: loadRecipe, + expectOutput: expectOutput, + expectInput: expectInput, + uploadFile: uploadFile, + uploadFolder: uploadFolder +}; diff --git a/plugins/srktoolbox/tests/lib/TestRegister.mjs b/plugins/srktoolbox/tests/lib/TestRegister.mjs new file mode 100644 index 00000000..ea7b934d --- /dev/null +++ b/plugins/srktoolbox/tests/lib/TestRegister.mjs @@ -0,0 +1,205 @@ +/** + * TestRegister.js + * + * This is so individual files can register their tests in one place, and + * ensure that they will get run by the frontend. + * + * @author tlwr [toby@toby.codes] + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ +import Chef from "../../src/core/Chef.mjs"; +import Utils from "../../src/core/Utils.mjs"; +import cliProgress from "cli-progress"; +import log from "loglevel"; + +/** + * Object to store and run the list of tests. + * + * @class + * @constructor + */ +class TestRegister { + + /** + * initialise with no tests + */ + constructor() { + this.tests = []; + this.apiTests = []; + } + + /** + * Add a list of tests to the register. + * + * @param {Object[]} tests + */ + addTests(tests) { + this.tests = this.tests.concat(tests); + } + + /** + * Add a list of api tests to the register + * @param {Object[]} tests + */ + addApiTests(tests) { + this.apiTests = this.apiTests.concat(tests); + } + + /** + * Runs all the tests in the register. + */ + async runTests () { + // Turn off logging to avoid messy errors + log.setLevel("silent", false); + + const progBar = new cliProgress.SingleBar({ + format: formatter, + stopOnComplete: true + }, cliProgress.Presets.shades_classic); + const testResults = []; + + console.log("Running operation tests..."); + progBar.start(this.tests.length, 0, { + msg: "Setting up" + }); + + for (const test of this.tests) { + progBar.update(testResults.length, { + msg: test.name + }); + + const chef = new Chef(); + const result = await chef.bake( + test.input, + test.recipeConfig, + { returnType: "string" } + ); + + const ret = { + test: test, + status: null, + output: null, + duration: result.duration + }; + + if (result.error) { + if (test.expectedError) { + if (result.error.displayStr === test.expectedOutput) { + ret.status = "passing"; + } else { + ret.status = "failing"; + ret.output = [ + "Expected", + "\t" + test.expectedOutput.replace(/\n/g, "\n\t"), + "Received", + "\t" + result.error.displayStr.replace(/\n/g, "\n\t"), + ].join("\n"); + } + } else { + ret.status = "erroring"; + ret.output = result.error.displayStr; + } + } else { + if (test.expectedError) { + ret.status = "failing"; + ret.output = "Expected an error but did not receive one."; + } else if (result.result === test.expectedOutput) { + ret.status = "passing"; + } else if ("expectedMatch" in test && test.expectedMatch.test(result.result)) { + ret.status = "passing"; + } else if ("unexpectedMatch" in test && !test.unexpectedMatch.test(result.result)) { + ret.status = "passing"; + } else { + ret.status = "failing"; + const expected = test.expectedOutput ? test.expectedOutput : + test.expectedMatch ? test.expectedMatch.toString() : + test.unexpectedMatch ? "to not find " + test.unexpectedMatch.toString() : + "unknown"; + ret.output = [ + "Expected", + "\t" + expected.replace(/\n/g, "\n\t"), + "Received", + "\t" + result.result.replace(/\n/g, "\n\t"), + ].join("\n"); + } + } + + testResults.push(ret); + progBar.increment(); + } + + // Turn logging back on + log.setLevel("info", false); + + return testResults; + } + + /** + * Run all api related tests and wrap results in report format + */ + async runApiTests() { + const progBar = new cliProgress.SingleBar({ + format: formatter, + stopOnComplete: true + }, cliProgress.Presets.shades_classic); + const testResults = []; + + console.log("Running Node API tests..."); + progBar.start(this.apiTests.length, 0, { + msg: "Setting up" + }); + + global.TESTING = true; + for (const test of this.apiTests) { + progBar.update(testResults.length, { + msg: test.name + }); + + const result = { + test: test, + status: null, + output: null + }; + try { + await test.run(); + result.status = "passing"; + } catch (e) { + result.status = "erroring"; + result.output = `${e.message}\nError: ${e.stack}`; + } + + testResults.push(result); + progBar.increment(); + } + + return testResults; + } +} + + +/** + * Formatter for the progress bar + * + * @param {Object} options + * @param {Object} params + * @param {Object} payload + * @returns {string} + */ +function formatter(options, params, payload) { + const bar = options.barCompleteString.substr(0, Math.round(params.progress * options.barsize)) + + options.barIncompleteString.substr(0, Math.round((1-params.progress) * options.barsize)); + + const percentage = Math.floor(params.progress * 100), + duration = Math.floor((Date.now() - params.startTime) / 1000); + + let testName = payload.msg ? payload.msg : ""; + if (params.value >= params.total) testName = "Tests completed"; + testName = Utils.truncate(testName, 25).padEnd(25, " "); + + return `${testName} ${bar} ${params.value}/${params.total} | ${percentage}% | Duration: ${duration}s`; +} + +// Export an instance to make a singleton +export default new TestRegister(); diff --git a/plugins/srktoolbox/tests/lib/utils.mjs b/plugins/srktoolbox/tests/lib/utils.mjs new file mode 100644 index 00000000..e29dbf90 --- /dev/null +++ b/plugins/srktoolbox/tests/lib/utils.mjs @@ -0,0 +1,90 @@ +/** + * Utils for test suite + * + * @author d98762625@gmail.com + * @author tlwr [toby@toby.codes] + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + + +/** + * Helper function to convert a status to an icon. + * + * @param {string} status + * @returns {string} + */ +function statusToIcon(status) { + return { + erroring: "🔥", + failing: "❌", + passing: "✔️️", + }[status] || "?"; +} + +/** + * Counts test statuses. + * + * @param {Object} testStatus + * @param {Object} testResult + */ +function handleTestResult(testStatus, testResult) { + testStatus.allTestsPassing = testStatus.allTestsPassing && testResult.status === "passing"; + testStatus.counts[testResult.status] = (testStatus.counts[testResult.status] || 0) + 1; + testStatus.counts.total += 1; + + if (testResult.duration > 2000) { + console.log(`'${testResult.test.name}' took ${(testResult.duration / 1000).toFixed(1)}s to complete`); + } +} + +/** + * Log each test result, count tests and failures. + * + * @param {Object} testStatus - object describing test run data + * @param {Object[]} results - results from TestRegister + */ +export function logTestReport(testStatus, results) { + results.forEach(r => handleTestResult(testStatus, r)); + + console.log(); + for (const testStatusCount in testStatus.counts) { + const count = testStatus.counts[testStatusCount]; + if (count > 0) { + console.log(testStatusCount.toUpperCase() + "\t" + count); + } + } + console.log(); + + // Print error messages for tests that didn't pass + results.filter(res => res.status !== "passing").forEach(testResult => { + console.log([ + statusToIcon(testResult.status), + testResult.test.name + ].join(" ")); + + if (testResult.output) { + console.log( + testResult.output + .trim() + .replace(/^/, "\t") + .replace(/\n/g, "\n\t") + ); + } + }); + console.log(); + + process.exit(testStatus.allTestsPassing ? 0 : 1); +} + +/** + * Fail if the process takes longer than 60 seconds. + */ +export function setLongTestFailure() { + const timeLimit = 120; + setTimeout(function() { + console.log(`Tests took longer than ${timeLimit} seconds to run, returning.`); + process.exit(1); + }, timeLimit * 1000); +} diff --git a/plugins/srktoolbox/tests/node/assertionHandler.mjs b/plugins/srktoolbox/tests/node/assertionHandler.mjs new file mode 100644 index 00000000..82d19a2e --- /dev/null +++ b/plugins/srktoolbox/tests/node/assertionHandler.mjs @@ -0,0 +1,60 @@ +/** + * assertionHandler.mjs + * + * Pair native node assertions with a description for + * the benefit of the TestRegister. + * + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +/* eslint no-console: 0 */ + + +/** + * Print useful stack on error + */ +const wrapRun = (run) => async () => { + try { + await run(); + } catch (e) { + console.dir(e); + throw e; + } +}; + + +/** + * it - wrapper for assertions to provide a helpful description + * to the TestRegister + * @namespace ApiTests + * @param {String} description - The description of the test + * @param {Function} assertion - The test + * + * @example + * // One assertion + * it("should run one assertion", () => assert.equal(1,1)) + * + * @example + * // multiple assertions + * it("should handle multiple assertions", () => { + * assert.equal(1,1) + * assert.notEqual(3,4) + * }) + * + * @example + * // async assertions + * it("should handle async", async () => { + * let r = await asyncFunc() + * assert(r) + * }) + */ +export function it(name, run) { + return { + name: `Node API: ${name}`, + run: wrapRun(run), + }; +} + +export default it; diff --git a/plugins/srktoolbox/tests/node/consumers/cjs-consumer.js b/plugins/srktoolbox/tests/node/consumers/cjs-consumer.js new file mode 100644 index 00000000..cd111511 --- /dev/null +++ b/plugins/srktoolbox/tests/node/consumers/cjs-consumer.js @@ -0,0 +1,34 @@ +/** + * Tests to ensure that a consuming app can use CJS require + * + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +const assert = require("assert"); + +require("srktoolbox").then(chef => { + + const d = chef.bake("Testing, 1 2 3", [ + chef.toHex, + chef.reverse, + { + op: chef.unique, + args: { + 分隔符: "空格", + } + }, + { + op: chef.multiply, + args: { + 分隔符: "空格", + } + } + ]); + + assert.equal(d.value, "630957449041920"); + +}); diff --git a/plugins/srktoolbox/tests/node/consumers/esm-consumer.mjs b/plugins/srktoolbox/tests/node/consumers/esm-consumer.mjs new file mode 100644 index 00000000..f8d84f3c --- /dev/null +++ b/plugins/srktoolbox/tests/node/consumers/esm-consumer.mjs @@ -0,0 +1,50 @@ +/** + * Tests to ensure that a consuming app can use ESM imports + * + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import assert from "assert"; +import chef from "srktoolbox"; +import { bake, toHex, reverse, unique, multiply } from "srktoolbox"; + +const a = bake("Testing, 1 2 3", [ + toHex, + reverse, + { + op: unique, + args: { + 分隔符: "空格", + } + }, + { + op: multiply, + args: { + 分隔符: "空格", + } + } +]); + +assert.equal(a.value, "630957449041920"); + +const b = chef.bake("Testing, 1 2 3", [ + chef.toHex, + chef.reverse, + { + op: chef.unique, + args: { + 分隔符: "空格", + } + }, + { + op: chef.multiply, + args: { + 分隔符: "空格", + } + } +]); + +assert.equal(b.value, "630957449041920"); diff --git a/plugins/srktoolbox/tests/node/index.mjs b/plugins/srktoolbox/tests/node/index.mjs new file mode 100644 index 00000000..f6abba40 --- /dev/null +++ b/plugins/srktoolbox/tests/node/index.mjs @@ -0,0 +1,41 @@ +/* eslint no-console: 0 */ + +/** + * Node API Test Runner + * + * @author d98762625 [d98762625@gmail.com] + * @author tlwr [toby@toby.codes] + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import { + setLongTestFailure, + logTestReport, +} from "../lib/utils.mjs"; + +import TestRegister from "../lib/TestRegister.mjs"; +import "./tests/nodeApi.mjs"; +import "./tests/operations.mjs"; +import "./tests/File.mjs"; +import "./tests/Dish.mjs"; +import "./tests/NodeDish.mjs"; +import "./tests/Utils.mjs"; +import "./tests/Categories.mjs"; + +const testStatus = { + allTestsPassing: true, + counts: { + total: 0, + } +}; + +setLongTestFailure(); + +const logOpsTestReport = logTestReport.bind(null, testStatus); + +(async function() { + const results = await TestRegister.runApiTests(); + logOpsTestReport(results); +})(); diff --git a/plugins/srktoolbox/tests/node/sampleData/pic.jpg b/plugins/srktoolbox/tests/node/sampleData/pic.jpg new file mode 100644 index 00000000..05c21327 Binary files /dev/null and b/plugins/srktoolbox/tests/node/sampleData/pic.jpg differ diff --git a/plugins/srktoolbox/tests/node/tests/Categories.mjs b/plugins/srktoolbox/tests/node/tests/Categories.mjs new file mode 100644 index 00000000..e6f8bd72 --- /dev/null +++ b/plugins/srktoolbox/tests/node/tests/Categories.mjs @@ -0,0 +1,19 @@ +import TestRegister from "../../lib/TestRegister.mjs"; +import Categories from "../../../src/core/config/Categories.json" assert {type: "json"}; +import OperationConfig from "../../../src/core/config/OperationConfig.json" assert {type: "json"}; +import it from "../assertionHandler.mjs"; +import assert from "assert"; + +TestRegister.addApiTests([ + it("Categories: operations should be in a category", () => { + const catOps = []; + Categories.forEach(cat => { + catOps.push(...cat.ops); + }); + + for (const op in OperationConfig) { + assert(catOps.includes(op), `'${op}' operation is not present in any category`); + } + }), + +]); diff --git a/plugins/srktoolbox/tests/node/tests/Dish.mjs b/plugins/srktoolbox/tests/node/tests/Dish.mjs new file mode 100644 index 00000000..58da00bf --- /dev/null +++ b/plugins/srktoolbox/tests/node/tests/Dish.mjs @@ -0,0 +1,12 @@ +import TestRegister from "../../lib/TestRegister.mjs"; +import Dish from "../../../src/core/Dish.mjs"; +import it from "../../node/assertionHandler.mjs"; +import assert from "assert"; + +TestRegister.addApiTests([ + it("Dish - presentAs: should exist", () => { + const dish = new Dish(); + assert(dish.presentAs); + }), + +]); diff --git a/plugins/srktoolbox/tests/node/tests/File.mjs b/plugins/srktoolbox/tests/node/tests/File.mjs new file mode 100644 index 00000000..c6a1e60a --- /dev/null +++ b/plugins/srktoolbox/tests/node/tests/File.mjs @@ -0,0 +1,82 @@ +import assert from "assert"; +import it from "../assertionHandler.mjs"; +import TestRegister from "../../lib/TestRegister.mjs"; +import File from "../../../src/node/File.mjs"; +import {zip, Dish} from "../../../src/node/index.mjs"; + +TestRegister.addApiTests([ + it("File: should exist", () => { + assert(File); + }), + + it("File: Should have same properties as DOM File object", () => { + const uint8Array = new Uint8Array(Buffer.from("hello")); + const file = new File([uint8Array], "name.txt"); + assert.equal(file.name, "name.txt"); + assert(typeof file.lastModified, "number"); + assert(file.lastModifiedDate instanceof Date); + assert.equal(file.size, uint8Array.length); + assert.equal(file.type, "application/unknown"); + }), + + it("File: Should determine the type of a file", () => { + const zipped = zip("hello"); + const file = new File([zipped.value]); + assert(file); + assert.strictEqual(file.type, "application/zip"); + }), + + it("File: unknown type should have a type of application/unknown", () => { + const uint8Array = new Uint8Array(Buffer.from("hello")); + const file = new File([uint8Array], "sample.txt"); + assert.strictEqual(file.type, "application/unknown"); + }), + + it("File: should be able to make a dish from it", () => { + const uint8Array = new Uint8Array(Buffer.from("hello")); + const file = new File([uint8Array], "sample.txt"); + try { + const dish = new Dish(file, 7); + assert.ok(dish.valid()); + } catch (e) { + assert.fail(e.message); + } + }), + + it("File: should allow dish to translate to ArrayBuffer", () => { + const uint8Array = new Uint8Array(Buffer.from("hello")); + const file = new File([uint8Array], "sample.txt"); + try { + const dish = new Dish(file, 7); + assert.ok(dish.value); + + dish.get(4); + assert.strictEqual(dish.type, 4); + assert.ok(dish.valid()); + + } catch (e) { + assert.fail(e.message); + } + }), + + it("File: should allow dish to translate from ArrayBuffer to File", () => { + const uint8Array = new Uint8Array(Buffer.from("hello")); + const file = new File([uint8Array], "sample.txt"); + try { + const dish = new Dish(file, 7); + assert.ok(dish.value); + + // translate to ArrayBuffer + dish.get(4); + assert.ok(dish.valid()); + + // translate back to File + dish.get(7); + assert.ok(dish.valid()); + + } catch (e) { + assert.fail(e.message); + } + }) + +]); diff --git a/plugins/srktoolbox/tests/node/tests/NodeDish.mjs b/plugins/srktoolbox/tests/node/tests/NodeDish.mjs new file mode 100644 index 00000000..7deeb150 --- /dev/null +++ b/plugins/srktoolbox/tests/node/tests/NodeDish.mjs @@ -0,0 +1,202 @@ +/* +* Modified by Raka-loah@github for zh-CN i18n +*/ +import assert from "assert"; +import it from "../assertionHandler.mjs"; +import fs from "fs"; + +import BigNumber from "bignumber.js"; + +import { Dish, toBase32, SHA3 } from "../../../src/node/index.mjs"; +import File from "../../../src/node/File.mjs"; +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addApiTests([ + it("Composable Dish: Should have top level Dish object", () => { + assert.ok(Dish); + }), + + it("Composable Dish: Should construct empty dish object", () => { + const dish = new Dish(); + assert.strictEqual(dish.value.byteLength, new ArrayBuffer(0).byteLength); + assert.strictEqual(dish.type, 4); + }), + + it("Composable Dish: constructed dish should have apply prototype functions", () => { + const dish = new Dish(); + assert.ok(dish.apply); + assert.throws(() => dish.someInvalidFunction()); + }), + + it("Composable Dish: composed function returns another dish", () => { + const result = new Dish("some input").apply(toBase32); + assert.ok(result instanceof Dish); + }), + + + it("Composable dish: infers type from input if needed", () => { + const dish = new Dish("string input"); + assert.strictEqual(dish.type, 1); + + const numberDish = new Dish(333); + assert.strictEqual(numberDish.type, 2); + + const arrayBufferDish = new Dish(Buffer.from("some buffer input").buffer); + assert.strictEqual(arrayBufferDish.type, 4); + + const byteArrayDish = new Dish(Buffer.from("some buffer input")); + assert.strictEqual(byteArrayDish.type, 0); + + const JSONDish = new Dish({key: "value"}); + assert.strictEqual(JSONDish.type, 6); + }), + + it("Composable dish: Buffer type dishes should be converted to strings", () => { + fs.writeFileSync("test.txt", "abc"); + const dish = new Dish(fs.readFileSync("test.txt")); + assert.strictEqual(dish.type, 0); + fs.unlinkSync("test.txt"); + }), + + it("Composable Dish: apply should allow set of arguments for operation", () => { + const result = new Dish("input").apply(SHA3, {长度: "256"}); + assert.strictEqual(result.toString(), "7640cc9b7e3662b2250a43d1757e318bb29fb4860276ac4373b67b1650d6d3e3"); + }), + + it("Composable Dish: apply functions can be chained", () => { + const result = new Dish("input").apply(toBase32).apply(SHA3, {长度: "224"}); + assert.strictEqual(result.toString(), "493e8136b759370a415ef2cf2f7a69690441ff86592aba082bc2e2e0"); + }), + + it("Dish translation: ArrayBuffer to ArrayBuffer", () => { + const dish = new Dish(new ArrayBuffer(10), 4); + dish.get("array buffer"); + assert.strictEqual(dish.value.byteLength, 10); + assert.strictEqual(dish.type, 4); + }), + + it("Dish translation: ArrayBuffer and String", () => { + const dish = new Dish("some string", 1); + dish.get("array buffer"); + + assert.strictEqual(dish.type, 4); + assert.deepStrictEqual(dish.value, new Uint8Array([0x73, 0x6f, 0x6d, 0x65, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67]).buffer); + assert.deepEqual(dish.value.byteLength, 11); + + dish.get("string"); + assert.strictEqual(dish.type, 1); + assert.strictEqual(dish.value, "some string"); + }), + + it("Dish translation: ArrayBuffer and number", () => { + const dish = new Dish(100, 2); + dish.get(4); + + assert.strictEqual(dish.type, 4); + assert.deepStrictEqual(dish.value, new Uint8Array([0x31, 0x30, 0x30]).buffer); + assert.strictEqual(dish.value.byteLength, 3); + + // Check the data in ArrayBuffer represents 100 as a string. + const view = new DataView(dish.value, 0); + assert.strictEqual(String.fromCharCode(view.getUint8(0), view.getUint8(1), view.getUint8(2)), "100"); + + dish.get("number"); + assert.strictEqual(dish.type, 2); + assert.strictEqual(dish.value, 100); + }), + + it("Dish translation: ArrayBuffer and byte array", () => { + const dish = new Dish(new Uint8Array([1, 2, 3]), 0); + dish.get(4); + + // Check intermediate value + const check = new Uint8Array(dish.value); + assert.deepEqual(check, new Uint8Array([1, 2, 3])); + + // Check converts back OK + dish.get(0); + assert.deepEqual(dish.value, [1, 2, 3]); + }), + + it("Dish translation: ArrayBuffer and HTML", () => { + const html = ` + + + + + + Click here + + +`.replace(/\n|\s{4}/g, ""); // remove newlines, tabs + + const dish = new Dish(html, Dish.HTML); + dish.get(4); + + dish.get(3); + assert.strictEqual(dish.value, "Click here"); + }), + + it("Dish translation: ArrayBuffer and BigNumber", () => { + const number = BigNumber(4001); + const dish = new Dish(number, Dish.BIG_NUMBER); + + dish.get(Dish.ARRAY_BUFFER); + assert.deepStrictEqual(dish.value, new Uint8Array([0x34, 0x30, 0x30, 0x31]).buffer); + assert.strictEqual(dish.value.byteLength, 4); + + // Check the data in ArrayBuffer represents 4001 as a string. + const view = new DataView(dish.value, 0); + assert.strictEqual(String.fromCharCode(view.getUint8(0), view.getUint8(1), view.getUint8(2), view.getUint8(3)), "4001"); + + dish.get(5); + assert.deepStrictEqual(dish.value, number); + }), + + it("Dish translation: ArrayBuffer and JSON", () => { + const jsonString = "{\"a\": 123455, \"b\": { \"aa\": [1,2,3]}}"; + const dish = new Dish(JSON.parse(jsonString), Dish.JSON); + + dish.get(Dish.ARRAY_BUFFER); + dish.get(Dish.JSON); + + assert.deepStrictEqual(dish.value, JSON.parse(jsonString)); + }), + + it("Dish translation: ArrayBuffer and File", () => { + const file = new File("abcd", "unknown"); + const dish = new Dish(file, Dish.FILE); + + dish.get(Dish.ARRAY_BUFFER); + assert.deepStrictEqual(dish.value, new Uint8Array([0x61, 0x62, 0x63, 0x64]).buffer); + assert.strictEqual(dish.value.byteLength, 4); + + // Check the data in ArrayBuffer represents "abcd" + const view = new DataView(dish.value, 0); + assert.strictEqual(String.fromCharCode(view.getUint8(0), view.getUint8(1), view.getUint8(2), view.getUint8(3)), "abcd"); + + dish.get(Dish.FILE); + + assert.deepStrictEqual(dish.value.data, file.data); + assert.strictEqual(dish.value.name, file.name); + assert.strictEqual(dish.value.type, file.type); + // Do not test lastModified + }), + + it("Dish translation: ArrayBuffer and ListFile", () => { + const file1 = new File("abcde", "unknown"); + const file2 = new File("fghijk", "unknown"); + + const dish = new Dish([file1, file2], Dish.LIST_FILE); + + dish.get(Dish.ARRAY_BUFFER); + assert.deepStrictEqual(dish.value, [new Uint8Array([0x61, 0x62, 0x63, 0x64, 0x65]), new Uint8Array([0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b])]); + assert.strictEqual(dish.value.length, 2); + + dish.get(Dish.LIST_FILE); + const dataArray = new Uint8Array(dish.value[0].data); + // cant store chars in a Uint8Array, so make it a normal one. + const actual = Array.prototype.slice.call(dataArray).map(c => String.fromCharCode(c)).join(""); + assert.strictEqual(actual, "abcdefghijk"); + }), +]); diff --git a/plugins/srktoolbox/tests/node/tests/Utils.mjs b/plugins/srktoolbox/tests/node/tests/Utils.mjs new file mode 100644 index 00000000..5ee7c936 --- /dev/null +++ b/plugins/srktoolbox/tests/node/tests/Utils.mjs @@ -0,0 +1,29 @@ +import TestRegister from "../../lib/TestRegister.mjs"; +import Utils from "../../../src/core/Utils.mjs"; +import it from "../assertionHandler.mjs"; +import assert from "assert"; + +TestRegister.addApiTests([ + it("Utils: should parse six backslashes correctly", () => { + assert.equal(Utils.parseEscapedChars("\\\\\\\\\\\\"), "\\\\\\"); + }), + + it("Utils: should parse escaped quotes correctly", () => { + assert.equal(Utils.parseEscapedChars("\\'"), "'"); + }), + + it("Utils: should parse escaped quotes and backslashes correctly", () => { + assert.equal(Utils.parseEscapedChars("\\\\'"), "\\'"); + }), + + it("Utils: should parse escaped quotes and escaped backslashes correctly", () => { + assert.equal(Utils.parseEscapedChars("\\\\\\'"), "\\'"); + }), + + it("Utils: should replace delete character", () => { + assert.equal( + Utils.printable("\x7e\x7f\x80\xa7", false, true), + "\x7e...", + ); + }), +]); diff --git a/plugins/srktoolbox/tests/node/tests/nodeApi.mjs b/plugins/srktoolbox/tests/node/tests/nodeApi.mjs new file mode 100644 index 00000000..1aaa21c8 --- /dev/null +++ b/plugins/srktoolbox/tests/node/tests/nodeApi.mjs @@ -0,0 +1,451 @@ +/* eslint no-console: 0 */ + +/** + * nodeApi.js + * + * Test node api utilities + * + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github to fit SRK-Toolbox + */ + +import assert from "assert"; +import it from "../assertionHandler.mjs"; +import chef from "../../../src/node/index.mjs"; +import { OperationError, ExcludedOperationError } from "../../../src/core/errors/index.mjs"; +import NodeDish from "../../../src/node/NodeDish.mjs"; + +import { toBase32, magic} from "../../../src/node/index.mjs"; +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addApiTests([ + it("should have some operations", () => { + assert(chef); + assert(chef.toBase32); + assert(chef.setUnion); + assert(!chef.randomFunction); + }), + + it("should export other functions at top level", () => { + assert(toBase32); + }), + + it("should be synchronous", () => { + try { + const result = chef.toBase32("input"); + assert.notEqual("something", result); + } catch (e) { + // shouldnt reach here + assert(false); + } + + try { + const fail = chef.setUnion("1"); + // shouldnt get here + assert(!fail || false); + } catch (e) { + assert(true); + } + }), + + it("should not catch Errors", () => { + try { + chef.setUnion("1"); + assert(false); + } catch (e) { + assert(e instanceof OperationError); + } + }), + + it("should accept arguments in object format for operations", () => { + const result = chef.setUnion("1 2 3 4:3 4 5 6", { + 元素分隔符: " ", + 集合分隔符: ":" + }); + + assert.equal(result.value, "1 2 3 4 5 6"); + }), + + it("should accept just some of the optional arguments being overriden", () => { + const result = chef.setIntersection("1 2 3 4 5\\n\\n3 4 5", { + 元素分隔符: " ", + }); + + assert.equal(result.value, "3 4 5"); + }), + + it("should accept no override arguments and just use the default values", () => { + const result = chef.powerSet("1,2,3"); + assert.equal(result.value, "\n3\n2\n1\n2,3\n1,3\n1,2\n1,2,3\n"); + }), + + it("should return an object with a .to method", () => { + const result = chef.toBase32("input"); + assert(result.to); + assert.equal(result.to("string"), "NFXHA5LU"); + }), + + it("should return an object with a .get method", () => { + const result = chef.toBase32("input"); + assert(result.get); + assert.equal(result.get("string"), "NFXHA5LU"); + }), + + it("should return a NodeDish", async () => { + const result = chef.toBase32("input"); + assert(result instanceof NodeDish); + }), + + it("should coerce to a string as you expect", () => { + const result = chef.fromBase32(chef.toBase32("something")); + assert.equal(String(result), "something"); + // This kind of coercion uses toValue + assert.equal(""+result, "NaN"); + }), + + it("should coerce to a number as you expect", () => { + const result = chef.fromBase32(chef.toBase32("32")); + assert.equal(3 + result, 35); + }), + + it("chef.help: should exist", () => { + assert(chef.help); + }), + + it("chef.help: should describe a operation", () => { + const result = chef.help("3DES解密"); + assert.strictEqual(result[0].name, "3DES解密"); + assert.strictEqual(result[0].module, "Ciphers"); + assert.strictEqual(result[0].inputType, "string"); + assert.strictEqual(result[0].outputType, "string"); + assert.strictEqual(result[0].description, "Triple DES applies DES three times to each block to increase key size.

        Key: Triple DES uses a key length of 24 bytes (192 bits).

        IV: The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.

        Padding: In CBC and ECB mode, PKCS#7 padding will be used as a default."); + assert.strictEqual(result[0].args.length, 5); + }), + + it("chef.help: null for invalid operation", () => { + const result = chef.help("some invalid function name"); + assert.strictEqual(result, null); + }), + + it("chef.help: takes a wrapped operation as input", () => { + const result = chef.help(chef.toBase32); + assert.strictEqual(result[0].name, "Base32编码"); + assert.strictEqual(result[0].module, "Default"); + }), + + it("chef.help: returns multiple results", () => { + const result = chef.help("base 64"); + assert.strictEqual(result.length, 13); + }), + + it("chef.help: looks in description for matches too", () => { + // string only in one operation's description. + const result = chef.help("转换计算机数据单位。"); + assert.strictEqual(result.length, 1); + assert.strictEqual(result[0].name, "单位转换:数据"); + }), + + it("chef.help: lists name matches before desc matches", () => { + const result = chef.help("校验和"); + assert.ok(result[0].name.includes("校验和")); + assert.ok(result[1].name.includes("校验和")); + assert.strictEqual(result[result.length - 1].name.includes("校验和"), false); + assert.ok(result[result.length - 1].description.includes("校验和")); + }), + + it("chef.help: exact name match only returns one result", () => { + const result = chef.help("MD5"); + assert.strictEqual(result.length, 1); + assert.strictEqual(result[0].name, "MD5"); + }), + + it("chef.help: exact match ignores whitespace", () => { + const result = chef.help("base64编码"); + assert.strictEqual(result.length, 1); + assert.strictEqual(result[0].name, "Base64编码"); + }), + + it("chef.bake: should exist", () => { + assert(chef.bake); + }), + + it("chef.bake: should return NodeDish", () => { + const result = chef.bake("input", "base64编码"); + assert(result instanceof NodeDish); + }), + + it("chef.bake: should take an input and an op name and perform it", () => { + const result = chef.bake("some input", "base32编码"); + assert.strictEqual(result.toString(), "ONXW2ZJANFXHA5LU"); + }), + + it("chef.bake: should complain if recipe isnt a valid object", () => { + assert.throws(() => chef.bake("some input", 3264), { + name: "TypeError", + message: "Recipe can only contain function names or functions" + }); + }), + + it("chef.bake: Should complain if string op is invalid", () => { + assert.throws(() => chef.bake("some input", "not a valid operation"), { + name: "TypeError", + message: "Couldn't find an operation with name 'not a valid operation'." + }); + }), + + it("chef.bake: Should take an input and an operation and perform it", () => { + const result = chef.bake("https://google.com/search?q=help", chef.parseURI); + assert.strictEqual(result.toString(), "协议:\thttps:\n主机名称:\tgoogle.com\n路径名称:\t/search\n参数:\n\tq = help\n"); + }), + + it("chef.bake: Should complain if an invalid operation is inputted", () => { + assert.throws(() => chef.bake("https://google.com/search?q=help", () => {}), { + name: "TypeError", + message: "Inputted function not a Chef operation." + }); + }), + + it("chef.bake: accepts an array of operation names and performs them all in order", () => { + const result = chef.bake("https://google.com/search?q=that's a complicated question", ["URL编码", "URL解码", "解析URI"]); + assert.strictEqual(result.toString(), "协议:\thttps:\n主机名称:\tgoogle.com\n路径名称:\t/search\n参数:\n\tq = that's a complicated question\n"); + }), + + it("chef.bake: forgiving with operation names", () =>{ + const result = chef.bake("https://google.com/search?q=that's a complicated question", ["url编码", "url解码", "解析URI"]); + assert.strictEqual(result.toString(), "协议:\thttps:\n主机名称:\tgoogle.com\n路径名称:\t/search\n参数:\n\tq = that's a complicated question\n"); + }), + + it("chef.bake: forgiving with operation names", () =>{ + const result = chef.bake("hello", ["base64编码"]); + assert.strictEqual(result.toString(), "aGVsbG8="); + }), + + it("chef.bake: if recipe is empty array, return input as dish", () => { + const result = chef.bake("some input", []); + assert.strictEqual(result.toString(), "some input"); + assert(result instanceof NodeDish, "Result is not instance of NodeDish"); + }), + + it("chef.bake: accepts an array of operations as recipe", () => { + const result = chef.bake("https://google.com/search?q=that's a complicated question", [chef.URLEncode, chef.URLDecode, chef.parseURI]); + assert.strictEqual(result.toString(), "协议:\thttps:\n主机名称:\tgoogle.com\n路径名称:\t/search\n参数:\n\tq = that's a complicated question\n"); + }), + + it("should complain if an invalid operation is inputted as part of array", () => { + assert.throws(() => chef.bake("something", [() => {}]), { + name: "TypeError", + message: "Inputted function not a Chef operation." + }); + }), + + it("chef.bake: should take single JSON object describing op and args OBJ", () => { + const result = chef.bake("some input", { + op: chef.toHex, + args: { + 分隔符: "冒号" + } + }); + assert.strictEqual(result.toString(), "73:6f:6d:65:20:69:6e:70:75:74"); + }), + + it("chef.bake: should take single JSON object desribing op with optional args", () => { + const result = chef.bake("some input", { + op: chef.toHex, + }); + assert.strictEqual(result.toString(), "73 6f 6d 65 20 69 6e 70 75 74"); + }), + + it("chef.bake: should take single JSON object describing op and args ARRAY", () => { + const result = chef.bake("some input", { + op: chef.toHex, + args: ["冒号"] + }); + assert.strictEqual(result.toString(), "73:6f:6d:65:20:69:6e:70:75:74"); + }), + + it("chef.bake: should error if op in JSON is not chef op", () => { + assert.throws(() => chef.bake("some input", { + op: () => {}, + args: ["冒号"], + }), { + name: "TypeError", + message: "Inputted function not a Chef operation." + }); + }), + + it("chef.bake: should take multiple ops in JSON object form, some ops by string", () => { + const result = chef.bake("some input", [ + { + op: chef.toHex, + args: ["冒号"] + }, + { + op: "字符转八进制", + args: { + 分隔符: "分号", + } + } + ]); + assert.strictEqual(result.toString(), "67;63;72;66;146;72;66;144;72;66;65;72;62;60;72;66;71;72;66;145;72;67;60;72;67;65;72;67;64"); + }), + + it("chef.bake: should take multiple ops in JSON object form, some without args", () => { + const result = chef.bake("some input", [ + { + op: chef.toHex, + }, + { + op: "字符转八进制", + args: { + 分隔符: "分号", + } + } + ]); + assert.strictEqual(result.toString(), "67;63;40;66;146;40;66;144;40;66;65;40;62;60;40;66;71;40;66;145;40;67;60;40;67;65;40;67;64"); + }), + + it("chef.bake: should handle op with multiple args", () => { + const result = chef.bake("some input", { + op: "摩尔斯电码编码", + args: { + 格式: "Dash/Dot", + 单词分隔符: "逗号", + 字母分隔符: "反斜杠", + } + }); + assert.strictEqual(result.toString(), "DotDotDot\\DashDashDash\\DashDash\\Dot,DotDot\\DashDot\\DotDashDashDot\\DotDotDash\\Dash"); + }), + + it("chef.bake: should take compact JSON format from Chef Website as recipe", () => { + const result = chef.bake("some input", [{"op": "摩尔斯电码编码", "args": ["Dash/Dot", "反斜杠", "逗号"]}, {"op": "十六进制转PEM", "args": ["SOMETHING"]}, {"op": "转换为Snake case", "args": [false]}]); + assert.strictEqual(result.toString(), "begin_something_anananaaaaak_da_aaak_da_aaaaananaaaaaaan_da_aaaaaaanan_da_aaak_end_something"); + }), + + it("chef.bake: should accept Clean JSON format from Chef website as recipe", () => { + const result = chef.bake("some input", [ + { "op": "摩尔斯电码编码", + "args": ["Dash/Dot", "反斜杠", "逗号"] }, + { "op": "十六进制转PEM", + "args": ["SOMETHING"] }, + { "op": "转换为Snake case", + "args": [false] } + ]); + assert.strictEqual(result.toString(), "begin_something_anananaaaaak_da_aaak_da_aaaaananaaaaaaan_da_aaaaaaanan_da_aaak_end_something"); + }), + + it("chef.bake: should accept Clean JSON format from Chef website - args optional", () => { + const result = chef.bake("some input", [ + { "op": "摩尔斯电码编码" }, + { "op": "十六进制转PEM", + "args": ["SOMETHING"] }, + { "op": "转换为Snake case", + "args": [false] } + ]); + assert.strictEqual(result.toString(), "begin_something_aaaaaaaaaaaaaa_end_something"); + }), + + it("chef.bake: should accept operation names from Chef Website which contain forward slash", () => { + const result = chef.bake("I'll have the test salmon", [ + { "op": "查找/替换", + "args": [{ "option": "Regex", "string": "test" }, "good", true, false, true, false]} + ]); + assert.strictEqual(result.toString(), "I'll have the good salmon"); + }), + + it("chef.bake: should accept operation names from Chef Website which contain a hyphen", () => { + const result = chef.bake("I'll have the test salmon", [ + { "op": "Adler-32校验和", + "args": [] } + ]); + assert.strictEqual(result.toString(), "6e4208f8"); + }), + + it("chef.bake: should accept operation names from Chef Website which contain a period", () => { + const result = chef.bake("30 13 02 01 05 16 0e 41 6e 79 62 6f 64 79 20 74 68 65 72 65 3f", [ + { "op": "解析ASN.1十六进制字符串", + "args": [0, 32] } + ]); + assert.strictEqual(result.toString(), `SEQUENCE + INTEGER 05 + IA5String 'Anybody there?' +`); + }), + + it("Excluded operations: throw a sensible error when you try and call one", () => { + try { + chef.fork(); + } catch (e) { + assert.strictEqual(e.type, "ExcludedOperationError"); + assert.strictEqual(e.message, "Sorry, the Fork operation is not available in the Node.js version of CyberChef."); + } + }), + + it("chef.bake: cannot accept flowControl operations in recipe", () => { + assert.throws(() => chef.bake("some input", "magic"), { + name: "TypeError", + message: "flowControl operations like Magic are not currently allowed in recipes for chef.bake in the Node API" + }); + assert.throws(() => chef.bake("some input", magic), { + name: "TypeError", + message: "flowControl operations like Magic are not currently allowed in recipes for chef.bake in the Node API" + }); + assert.throws(() => chef.bake("some input", ["base64编码", "magic"]), { + name: "TypeError", + message: "flowControl operations like Magic are not currently allowed in recipes for chef.bake in the Node API" + }); + }), + + it("Excluded operations: throw a sensible error when you try and call one", () => { + assert.throws(chef.fork, + (err) => { + assert(err instanceof ExcludedOperationError); + assert.deepEqual(err.message, "Sorry, the Fork operation is not available in the Node.js version of CyberChef."); + return true; + }, + "Unexpected error type" + ); + assert.throws(chef.javaScriptBeautify, + (err) => { + assert(err instanceof ExcludedOperationError); + assert.deepEqual(err.message, "Sorry, the JavaScriptBeautify operation is not available in the Node.js version of CyberChef."); + return true; + }, + "Unexpected error type" + ); + }), + + it("Operation arguments: should be accessible from operation object if op has array arg", () => { + assert.ok(chef.toCharcode.args); + assert.deepEqual(chef.unzip.args, { + 密码: { + type: "binaryString", + value: "", + }, + 验证结果: { + type: "boolean", + value: false, + } + }); + }), + + it("Operation arguments: should have key for each argument in operation", () => { + assert.ok(chef.convertDistance.args.输入单位); + assert.ok(chef.convertDistance.args.输出单位); + + assert.strictEqual(chef.bitShiftRight.args.偏移量.type, "number"); + assert.strictEqual(chef.bitShiftRight.args.偏移量.value, 1); + assert.strictEqual(chef.bitShiftRight.args.类型.type, "option"); + assert.ok(Array.isArray(chef.bitShiftRight.args.类型.options)); + + }), + + it("Operation arguments: should list all options excluding subheadings", () => { + // First element (subheading) removed + assert.equal(chef.convertDistance.args.输入单位.options[0], "纳米 (nm)"); + assert.equal(chef.defangURL.args.处理类型.options[1], "仅无效化完整URL"); + }), + +]); diff --git a/plugins/srktoolbox/tests/node/tests/operations.mjs b/plugins/srktoolbox/tests/node/tests/operations.mjs new file mode 100644 index 00000000..eef127f7 --- /dev/null +++ b/plugins/srktoolbox/tests/node/tests/operations.mjs @@ -0,0 +1,1143 @@ +/* eslint no-console: 0 */ + +/** + * nodeApi.js + * + * Test node api operations + * + * Aim of these tests is to ensure each arg type is + * handled correctly by the wrapper. + * + * Generally just checking operations that use external dependencies to ensure + * it behaves as expected in Node. + * + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import assert from "assert"; +import it from "../assertionHandler.mjs"; +import fs from "fs"; + +import { + addLineNumbers, + adler32Checksum, + AESDecrypt, + affineCipherDecode, + affineCipherEncode, + bifidCipherEncode, + bitShiftRight, + cartesianProduct, + CSSMinify, + toBase64, + toHex +} from "../../../src/node/index.mjs"; +import chef from "../../../src/node/index.mjs"; +import TestRegister from "../../lib/TestRegister.mjs"; +import File from "../../../src/node/File.mjs"; + +global.File = File; + +TestRegister.addApiTests([ + + it("ADD: toggleString argument", () => { + const result = chef.ADD("sample input", { + key: { + string: "some key", + option: "utf8" + } + }); + assert.equal(result.toString(), "\xe6\xd0\xda\xd5\x8c\xd0\x85\xe2\xe1\xdf\xe2\xd9"); + }), + + + it("ADD: default option toggleString argument", () => { + const result = chef.ADD(3, { + key: "4", + }); + assert.strictEqual(result.toString(), "7"); + }), + + it("addLineNumbers: No arguments", () => { + const result = addLineNumbers("sample input"); + assert.equal(result.toString(), "1 sample input"); + }), + + it("adler32Checksum: No args", () => { + const result = adler32Checksum("sample input"); + assert.equal(result.toString(), "1f2304d3"); + }), + + it("AES decrypt: toggleString and option", () => { + const result = AESDecrypt("4a123af235a507bbc9d5871721d61b98504d569a9a5a7847e2d78315fec7", { + key: { + string: "some longer key1", + option: "utf8", + }, + iv: { + string: "some iv some iv1", + option: "utf8", + }, + mode: "OFB", + }); + assert.equal(result.toString(), "a slightly longer sampleinput?"); + }), + + it("AffineCipherDecode: number input", () => { + const result = affineCipherDecode("some input", { + a: 7, + b: 4 + }); + assert.strictEqual(result.toString(), "cuqa ifjgr"); + }), + + it("affineCipherEncode: number input", () => { + const result = affineCipherEncode("some input", { + a: 11, + b: 6 + }); + assert.strictEqual(result.toString(), "weiy qtpsh"); + }), + + it("analyzeHash", () => { + const result = chef.analyseHash(chef.MD5("some input")); + const expected = `哈希长度: 32 +字节数: 16 +位长度: 128 + +根据长度,此哈希值可能由以下哈希算法生成: +MD5 +MD4 +MD2 +HAVAL-128 +RIPEMD-128 +Snefru +Tiger-128`; + assert.strictEqual(result.toString(), expected); + }), + + it("AND", () => { + const result = chef.AND("Scot-free", { + key: { + string: "Raining Cats and Dogs", + option: "utf8", + } + }); + assert.strictEqual(result.toString(), "Raid)fb A"); + }), + + it("atBash Cipher", () => { + const result = chef.atbashCipher("Happy as a Clam"); + assert.strictEqual(result.toString(), "Szkkb zh z Xozn"); + + }), + + it("Bcrypt", async () => { + const result = await chef.bcrypt("Put a Sock In It"); + const strResult = result.toString(); + assert.equal(strResult.length, 60); + assert.equal(strResult.slice(0, 7), "$2a$10$"); + }), + + it("bcryptCompare", async() => { + const result = await chef.bcryptCompare("Put a Sock In It", { + 哈希值: "$2a$10$2rT4a3XnIecBsd1H33dMTuyYE1HJ1n9F.V2rjQtAH73rh1qvOf/ae", + }); + assert.strictEqual(result.toString(), "匹配: Put a Sock In It"); + }), + + it("Bcrypt Parse", async () => { + const result = await chef.bcryptParse("$2a$10$ODeP1.6fMsb.ENk2ngPUCO7qTGVPyHA9TqDVcyupyed8FjsiF65L6"); + const expected = `轮数: 10 +盐: $2a$10$ODeP1.6fMsb.ENk2ngPUCO +密码哈希: 7qTGVPyHA9TqDVcyupyed8FjsiF65L6 +完整哈希: $2a$10$ODeP1.6fMsb.ENk2ngPUCO7qTGVPyHA9TqDVcyupyed8FjsiF65L6`; + assert.strictEqual(result.toString(), expected); + }), + + it("bifid cipher decode", () => { + const result = chef.bifidCipherDecode("Vhef Qnte Ke Xfhz Mxon Bmgf", { + 加密关键词: "Alpha", + }); + assert.strictEqual(result.toString(), "What Goes Up Must Come Down"); + }), + + it("bifid cipher encode: string option", () => { + const result = bifidCipherEncode("some input", { + 加密关键词: "mykeyword", + }); + assert.strictEqual(result.toString(), "nmhs zmsdo"); + }), + + it("bit shift left", () => { + const result = chef.bitShiftLeft("Keep Your Eyes Peeled"); + assert.strictEqual(result.toString(), "–ÊÊà@²Þêä@ŠòÊæ@ ÊÊØÊÈ"); + }), + + it("bitShiftRight: number and option", () => { + const result = bitShiftRight("some bits to shift", { + type: "Arithmetic shift", + amount: 1, + }); + assert.strictEqual(result.toString(), "9762\u001014:9\u0010:7\u00109443:"); + }), + + it("Blowfish encrypt", () => { + const result = chef.blowfishEncrypt("Fool's Gold", { + key: { + string: "0011223344556677", + option: "十六进制", + }, + iv: { + string: "exparrot", + option: "utf8" + }, + mode: "CBC" + }); + assert.strictEqual(result.toString(), "55a2838980078ffe1722b08d5fa1d481"); + }), + + it("Blowfish decrypt", () => { + const result = chef.blowfishDecrypt("55a2838980078ffe1722b08d5fa1d481", { + key: { + string: "0011223344556677", + option: "十六进制", + }, + iv: { + string: "exparrot", + option: "utf8", + }, + mode: "CBC" + }); + assert.strictEqual(result.toString(), "Fool's Gold"); + }), + + it("BSON Serialise / Deserialise", () => { + const result = chef.BSONDeserialise(chef.BSONSerialise("{\"phrase\": \"Mouth-watering\"}")); + assert.strictEqual(result.toString(), `{ + "phrase": "Mouth-watering" +}`); + }), + + it("Bzip2 Decompress", async () => { + const result = await chef.bzip2Decompress(chef.fromBase64("QlpoOTFBWSZTWUdQlt0AAAIVgEAAAQAmJAwAIAAxBkxA0A2pTL6U2CozxdyRThQkEdQlt0A=")); + assert.strictEqual(result.toString(), "Fit as a Fiddle"); + }), + + it("cartesianProduct: binary string", () => { + const result = cartesianProduct("1:2\\n\\n3:4", { + 元素分隔符: ":", + }); + assert.strictEqual(result.toString(), "(1,3):(1,4):(2,3):(2,4)"); + }), + + it("Change IP format", () => { + const result = chef.changeIPFormat("172.20.23.54", { + 输入格式: "十进制用点分隔", + 输出格式: "十六进制", + }); + assert.strictEqual(result.toString(), "ac141736"); + }), + + it("Chi square", () => { + const result = chef.chiSquare("Burst Your Bubble"); + assert.strictEqual(result.toString(), "433.55399816176475"); + }), + + it("Compare CTPH Hashes", () => { + const result = chef.compareCTPHHashes("1234\n3456"); + assert.strictEqual(result.toString(), "0"); + }), + + it("Compare SSDEEPHashes", () => { + const result = chef.compareCTPHHashes("1234\n3456"); + assert.strictEqual(result.toString(), "0"); + }), + + it("Convert area", () => { + const result = chef.convertArea("12345", { + 输入单位: "平方米 (sq m)", + 输出单位: "怀特岛" + }); + assert.strictEqual(result.toString(), "0.00003248684210526316"); + }), + + it("Convert data units", () => { + const result = chef.convertDataUnits("12345", { + 输入单位: "比特 (b)", + 输出单位: "千字节 (KB)", + }); + assert.strictEqual(result.toString(), "1.543125"); + }), + + it("Convert distance", () => { + const result = chef.convertDistance("1234567", { + 输入单位: "纳米 (nm)", + 输出单位: "浪 (fur)", + }); + assert.strictEqual(result.toString(), "0.00000613699494949495"); + }), + + it("Convert mass", () => { + const result = chef.convertMass("123", { + 输入单位: "地球质量 (M⊕)", + 输出单位: "吉萨金字塔群 (6,000,000吨)", + }); + assert.strictEqual(result.toString(), "122429895000000000"); + }), + + it("Convert speed", () => { + const result = chef.convertSpeed("123", { + 输入单位: "月球逃逸速度", + 输出单位: "喷气式客机巡航速度", + }); + assert.strictEqual(result.toString(), "1168.5"); + }), + + it("Count occurrences", () => { + const result = chef.countOccurrences("Talk the Talk", { + 搜索字符串: { + string: "Tal", + option: "Simple string", + } + }); + assert.strictEqual(result.toString(), "2"); + }), + + it("CSS Beautify", () => { + const result = chef.CSSBeautify("header {color:black;padding:3rem;}"); + const expected = `header { +\\tcolor:black; +\\tpadding:3rem; +} +`; + assert.strictEqual(result.toString(), expected); + }), + + it("CSS minify: boolean", () => { + const input = `header { +// comment +width: 100%; +color: white; +}`; + const result = CSSMinify(input, { + preserveComments: true, + }); + assert.strictEqual(result.toString(), "header {// comment width: 100%;color: white;}"); + }), + + it("CSS Selector", () => { + const result = chef.CSSSelector("

        Hello

        ", { + CSS选择器: "h1", + }); + assert.strictEqual(result.toString(), "

        Hello

        "); + }), + + it("CTPH", () => { + const result = chef.CTPH("If You Can't Stand the Heat, Get Out of the Kitchen"); + assert.strictEqual(result.toString(), "A:+EgFgBKAA0V0UFfClEs6:+Qk0gUFse"); + }), + + it("Decode NetBIOS Name", () => { + assert.strictEqual(chef.decodeNetBIOSName("EBGMGMCAEHHCGFGFGLCAFEGPCAENGFCA").toString(), "All Greek To Me"); + }), + + it("Decode text", () => { + const encoded = chef.encodeText("Ugly Duckling", { + encoding: "UTF-16LE (1200)", + }); + const result = chef.decodeText(encoded, { + encoding: "UTF-16LE (1200)", + }); + assert.strictEqual(result.toString(), "Ugly Duckling"); + }), + + it("Derive EVP Key", () => { + const result = chef.deriveEVPKey("", { + 口令: { + string: "46 6c 65 61 20 4d 61 72 6b 65 74", + option: "十六进制", + }, + 盐: { + string: "Market", + option: "utf8", + }, + }); + assert.strictEqual(result.toString(), "4930d5d200e80f18c96b5550d13c6af8"); + }), + + it("Derive PBKDF2 Key", () => { + const result = chef.derivePBKDF2Key("", { + 口令: { + string: "Jack of All Trades Master of None", + option: "utf8", + }, + Key大小: 256, + 迭代次数: 2, + 哈希函数: "md5", + 盐: { + string: "fruit", + option: "utf8" + } + }); + assert.strictEqual(result.toString(), "728a885b209e8b19cbd7430ca32608ff09190f7ccb7ded204e1d4c50f87c47bf"); + }), + + it("DES Decrypt", () => { + const result = chef.DESDecrypt("713081c66db781c323965ba8f166fd8c230c3bb48504a913", { + Key: { + string: "onetwoth", + option: "UTF8", + }, + IV: { + string: "threetwo", + option: "UTF8", + }, + 模式: "ECB", + }); + assert.strictEqual(result.toString(), "Put a Sock In It"); + }), + + it("DES Encrypt", () => { + const result = chef.DESEncrypt("Put a Sock In It", { + Key: { + string: "onetwoth", + option: "utf8", + }, + IV: { + string: "threetwo", + option: "utf8", + }, + 模式: "ECB", + }); + assert.strictEqual(result.toString(), "713081c66db781c323965ba8f166fd8c230c3bb48504a913"); + }), + + it("Diff", () => { + const result = chef.diff("one two\\n\\none two three"); + assert.strictEqual(result.toString(), "one two three"); + }), + + it("Disassemble x86", () => { + const result = chef.disassembleX86(chef.toBase64("one two three")); + const expected = `0000000000000000 0000 ADD BYTE PTR [RAX],AL\r +0000000000000002 0B250000000B OR ESP,DWORD PTR [000000000B000008]\r +`; + assert.strictEqual(result.toString(), expected); + }), + + it("Divide", () => { + assert.strictEqual(chef.divide("4\n7").toString(), "0.57142857142857142857"); + }), + + it("Drop bytes", () => { + assert.strictEqual(chef.dropBytes("There's No I in Team").toString(), "'s No I in Team"); + }), + + it("Entropy", () => { + const result = chef.entropy("Ride Him, Cowboy!"); + assert.strictEqual(result.toString(), "3.734521664779752"); + }), + + it("Escape string", () => { + const result = chef.escapeString("Know the Ropes", { + 转义等级: "所有", + JSON兼容: false, + ES6兼容: true, + 十六进制大写: true, + }); + assert.strictEqual(result.toString(), "\\x4B\\x6E\\x6F\\x77\\x20\\x74\\x68\\x65\\x20\\x52\\x6F\\x70\\x65\\x73"); + }), + + it("Escape unicode characters", () => { + assert.strictEqual(chef.escapeUnicodeCharacters("σου").toString(), "\\u03C3\\u03BF\\u03C5"); + }), + + it("Expand alphabet range", () => { + assert.strictEqual( + chef.expandAlphabetRange("Fight Fire With Fire", {分隔符: "t"}).toString(), + "Ftitgthttt tFtitrtet tWtitttht tFtitrte"); + }), + + it("Extract dates", () => { + assert.strictEqual(chef.extractDates("Don't Look a Gift Horse In The Mouth 01/02/1992").toString(), "01/02/1992"); + }), + + it("Filter", () => { + const result = chef.filter( + `I Smell a Rat +Every Cloud Has a Silver Lining +Top Drawer`, { + 正则表达式: "Every", + }); + const expected = "Every Cloud Has a Silver Lining"; + assert.strictEqual(result.toString(), expected); + }), + + it("Find / Replace", () => { + assert.strictEqual( + chef.findReplace( + "Curiosity Killed The Cat", + { + 查找内容: { + string: "l", + option: "正则表达式", + }, + 替换: "s", + }).toString(), + "Curiosity Kissed The Cat"); + }), + + it("Fletcher8 Checksum", () => { + assert.strictEqual(chef.fletcher8Checksum("Keep Your Eyes Peeled").toString(), "48"); + }), + + it("Format MAC addresses", () => { + const result = chef.formatMACAddresses("00-01-02-03-04-05"); + const expected = `000102030405 +000102030405 +00-01-02-03-04-05 +00-01-02-03-04-05 +00:01:02:03:04:05 +00:01:02:03:04:05 +`; + assert.strictEqual(result.toString(), expected); + }), + + it("Frequency distribution", () => { + const result = chef.frequencyDistribution("Don't Count Your Chickens Before They Hatch"); + const expected = "{\"dataLength\":43,\"percentages\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,13.953488372093023,0,0,0,0,0,0,2.3255813953488373,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2.3255813953488373,4.651162790697675,2.3255813953488373,0,0,0,2.3255813953488373,0,0,0,0,0,0,0,0,0,0,0,2.3255813953488373,0,0,0,0,2.3255813953488373,0,0,0,0,0,0,0,2.3255813953488373,0,4.651162790697675,0,9.30232558139535,2.3255813953488373,0,6.976744186046512,2.3255813953488373,0,2.3255813953488373,0,0,6.976744186046512,9.30232558139535,0,0,4.651162790697675,2.3255813953488373,6.976744186046512,4.651162790697675,0,0,0,2.3255813953488373,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"distribution\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,1,0,2,0,4,1,0,3,1,0,1,0,0,3,4,0,0,2,1,3,2,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"bytesRepresented\":22}"; + // Whacky formatting, but the data is all there + assert.strictEqual(result.toString().replace(/\r?\n|\r|\s/g, ""), expected); + }), + + it("From base", () => { + assert.strictEqual(chef.fromBase("11", {进制: 13}).toString(), "14"); + }), + + it("From BCD", () => { + assert.strictEqual(chef.fromBCD("1143", { 输入格式: "原始", scheme: "7 4 2 1"}).toString(), "31313433"); + }), + + it("From binary", () => { + assert.strictEqual(chef.fromBinary("010101011100101101011010").toString(), "UËZ"); + }), + + it("From Charcode", () => { + assert.strictEqual(chef.fromCharcode("4c 6f 6e 67 20 49 6e 20 54 68 65 20 54 6f 6f 74 68 0a").toString(), "Long In The Tooth\n"); + }), + + it("From decimal", () => { + assert.strictEqual(chef.fromDecimal("72 101 108 108 111").toString(), "Hello"); + }), + + it("From hex", () => { + assert.strictEqual(chef.fromHex("52 69 6e 67 20 41 6e 79 20 42 65 6c 6c 73 3f").toString(), "Ring Any Bells?"); + }), + + it("From hex content", () => { + assert.strictEqual(chef.fromHexContent("foo|3d|bar").toString(), "foo=bar"); + }), + + it("To and From hex dump", () => { + assert.strictEqual(chef.fromHexdump(chef.toHexdump("Elephant in the Room")).toString(), "Elephant in the Room"); + }), + + it("From HTML entity", () => { + assert.strictEqual(chef.fromHTMLEntity("&").toString(), "&"); + }), + + it("To and From morse code", () => { + assert.strictEqual(chef.fromMorseCode(chef.toMorseCode("Put a Sock In It")).toString(), "PUT A SOCK IN IT"); + }), + + it("From octal", () => { + assert.strictEqual(chef.fromOctal("113 156 157 167 40 164 150 145 40 122 157 160 145 163").toString(), "Know the Ropes"); + }), + + it("To, From punycode", () => { + assert.strictEqual(chef.fromPunycode(chef.toPunycode("münchen")).toString(), "münchen"); + }), + + it("From unix timestamp", () => { + assert.strictEqual(chef.fromUNIXTimestamp("978346800").toString(), "Mon 1 January 2001 11:00:00 UTC"); + }), + + it("Generate HOTP", () => { + const result = chef.generateHOTP("JBSWY3DPEHPK3PXP", { + }); + const expected = `URI: otpauth://hotp/?secret=JBSWY3DPEHPK3PXP&algorithm=SHA1&digits=6&counter=0 + +动态码: 282760`; + assert.strictEqual(result.toString(), expected); + }), + + it("Generate PGP Key Pair", async () => { + const result = await chef.generatePGPKeyPair("Back To the Drawing Board", { + keyType: "ECC-256", + }); + assert.strictEqual(result.toString().substr(0, 37), "-----BEGIN PGP PRIVATE KEY BLOCK-----"); + }), + + ...[1, 3, 4, 5, 6, 7].map(version => it(`Generate UUID v${version}`, () => { + const result = chef.generateUUID("", { "version": `v${version}` }).toString(); + assert.ok(result); + assert.strictEqual(result.length, 36); + })), + + ...[1, 3, 4, 5, 6, 7].map(version => it(`Analyze UUID v${version}`, () => { + const uuid = chef.generateUUID("", { "version": `v${version}` }).toString(); + const result = chef.analyseUUID(uuid).toString(); + const expected = `UUID版本:${version}`; + assert.strictEqual(result, expected); + })), + + it("Generate UUID using defaults", () => { + const uuid = chef.generateUUID(); + assert.ok(uuid); + + const analysis = chef.analyseUUID(uuid).toString(); + assert.strictEqual(analysis, "UUID版本:4"); + }), + + it("Gzip, Gunzip", () => { + assert.strictEqual(chef.gunzip(chef.gzip("Down To The Wire")).toString(), "Down To The Wire"); + }), + + it("Hex to Object Identifier", () => { + assert.strictEqual( + chef.hexToObjectIdentifier(chef.toHex("You Can't Teach an Old Dog New Tricks")).toString(), + "2.9.111.117.32.67.97.110.39.116.32.84.101.97.99.104.32.97.110.32.79.108.100.32.68.111.103.32.78.101.119.32.84.114.105.99.107.115"); + }), + + it("Hex to PEM", () => { + const result = chef.hexToPEM(chef.toHex("Yada Yada")); + const expected = `-----BEGIN CERTIFICATE-----\r +WWFkYSBZYWRh\r +-----END CERTIFICATE-----\r\n`; + assert.strictEqual(result.toString(), expected); + }), + + it("HMAC", () => { + assert.strictEqual(chef.HMAC("On Cloud Nine", {key: "idea"}).toString(), "e15c268b4ee755c9e52db094ed50add7"); + }), + + it("JPathExpression", () => { + assert.strictEqual(chef.JPathExpression("{\"key\" : \"value\"}", {JPath: "$.key"}).toString(), "\"value\""); + }), + + it("JSON Beautify", () => { + assert.strictEqual( + chef.JSONBeautify("{\"key\" : \"value\"}").toString(), + `{ + "key": "value" +}`); + }), + + it("Keccak", () => { + assert.strictEqual(chef.keccak("Flea Market").toString(), "c2a06880b19e453ee5440e8bd4c2024bedc15a6630096aa3f609acfd2b8f15f27cd293e1cc73933e81432269129ce954a6138889ce87831179d55dcff1cc7587"); + }), + + it("LZNT1 Decompress", () => { + assert.strictEqual(chef.LZNT1Decompress("\x1a\xb0\x00compress\x00edtestda\x04ta\x07\x88alot").toString(), "compressedtestdatacompressedalot"); + }), + + it("MD6", () => { + assert.strictEqual(chef.MD6("Head Over Heels", {key: "arty"}).toString(), "d8f7fe4931fbaa37316f76283d5f615f50ddd54afdc794b61da522556aee99ad"); + }), + + it("Parse ASN.1 Hex string", () => { + assert.strictEqual(chef.parseASN1HexString(chef.toHex("Mouth-watering")).toString(), "UNKNOWN(77) 7574682d7761746572696e67\n"); + }), + + it("Parse DateTime", () => { + const result = chef.parseDateTime("06/07/2001 01:59:30"); + const expected = `日期: Friday 6th July 2001 +时间: 01:59:30 +上下午: AM +时区: UTC +UTC偏移量: +0000 + +夏令时: false +闰年: false +当月天数: 31 + +当年第几天: 187 +当年第几周: 27 +季度: 3`; + assert.strictEqual(result.toString(), expected); + }), + + it("Parse IPV6 address", () => { + const result = chef.parseIPv6Address("2001:0db8:85a3:0000:0000:8a2e:0370:7334"); + const expected = `Longhand: 2001:0db8:85a3:0000:0000:8a2e:0370:7334 +Shorthand: 2001:db8:85a3::8a2e:370:7334 + +文档用IPv6地址。此范围用于文档中需要IPv6地址举例用于描述网络场景的时候。对应IPv4的192.0.2.0/24、198.51.100.0/24和203.0.113.0/24。 +文档地址范围: 2001:db8::/32`; + assert.strictEqual(result.toString(), expected); + }), + + it("Parse URI", () => { + const result = chef.parseURI("https://www.google.co.uk/search?q=almonds"); + const expected = `协议: https: +主机名称: www.google.co.uk +路径名称: /search +参数: +\tq = almonds +`; + assert.strictEqual(result.toString(), expected); + }), + + it("Parse user agent", () => { + const result = chef.parseUserAgent("Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0 "); + const expected = `浏览器 + 名称: Firefox + 版本: 47.0 +设备 + 型号: 未知 + 类型: 未知 + 厂商: 未知 +内核 + 名称: Gecko + 版本: 47.0 +操作系统 + 名称: Windows + 版本: 7 +CPU + 架构: amd64`; + assert.strictEqual(result.toString(), expected); + }), + + it("PGP Encrypt and decrypt", async () => { + const pbkey = `-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: Keybase OpenPGP v2.1.3 +Comment: https://keybase.io/crypto + +xo0EXZtlowEEAKUqTFownTmqgXWu2KDrtyNYtFck7a16WM5QD95bFoAFFdnlwZ45 +6Vw8G8LCzHdyRXYp/JF1GknDrAd7nIRE+SuSz2yVK5nlOCfO1HFcg2Ov7e7/pBwd +qawx9GUIsCKd/6NxwDuT4YqarLFsuwljRC/eQiibO+ejnhoiKcU69sTNABEBAAHN +AMK0BBMBCgAeBQJdm2WjAhsvAwsJBwMVCggCHgECF4ADFgIBAhkBAAoJEGS79V2S +7D0owtMD/RT+o4BQJ8NSQBDgkYf42uOOu1Ud6GuN89nX6n20yAZbmqQ8CHnHY+Qc +l6ft4HnbIaNrI3arp/C2C+cwFypmt1BKyFEJUXO7ft3i/IxnjpCorDyAMCDckDvq +uma1LWtUHLb5s/ZuGMSHnhuji74IRWuIofNPdf7bCZW1GMbW9jNUzo0EXZtlowEE +AL38zaNkPmUVQaowP696fayBo18Nxs0yOzC4+0TYv1B/k5aUb0Air2h+o/Xw4E42 +Jh9gVdPSvhOAEqdV0UDe71wxa4cfAVMDY9v8ta81MWunChj3ISUk1oIQylTJNsY/ +b4KWOrLaOtBD9dyFGCzss5vLVdqdMjVIW2Cz0hb6IYG7ABEBAAHCwIMEGAEKAA8F +Al2bZaMFCQ8JnAACGy4AqAkQZLv1XZLsPSidIAQZAQoABgUCXZtlowAKCRA16MU2 +u2hFTX+JBACZ27xk0Afny2jjSoRzqLMrhzE7DBGcg2QqecMdNre12hVompAWcS4l +NFmPShKRi6UT8Zb38nD43vwfqwZImn60dOPqqAep3YF/Axm1u5HJb0aMEsb8O9jV +sVmNJv9jVTzPdlTGFQjuaeJfk5lwxB+5/O9NcgDhPgRAk9xb4FrT+xzmA/4tD11C +AdcITUkTZT4ZOo2418DGeaiaEqWcIkZeQG4Vh5TMj4QtZDwsYQhXPl5Zj1zKIN/1 +gRrKC+ztaQoDG8pJXTTtc9inRU++dhMqnRGrPcz0VfVXFaiH7PUCy+4WpP6r5Bs5 +YQ9ESHo+FsmIvDzU3e/PD0SfXfO4vqBrFYN8986NBF2bZaMBBADJafe0w9diaCNx +3A7e8MqjbNrhrLkD2cPxXspCATX3SuI19d2+hMiHZfKTyadBTIa+ICxvqoxwxyZD +raHSY3CWVZd1V4KB5mqf+3Zj5riLeGU0dtXwi/5c0bdUhBUgHiAMhi75p05jYih5 +KsNxPcK9hEwPu7B+QeHURMiIgojTGQARAQABwsCDBBgBCgAPBQJdm2WjBQkDwmcA +AhsuAKgJEGS79V2S7D0onSAEGQEKAAYFAl2bZaMACgkQzdkMJSM5Bqg2rwP/Ue28 +m3Fdfgh5JxouZ3Dm2KUDhZL95B+vdMk72acdoU7SRjlyDT8cApRqYx+MIXb8WrPN +1xCZnOM4zXeWIM0CAPQ1e/sCrK58L+P+eVngNmrW9epKtZ4L6hx+dqqja9vPZGQK +CsFAhA6A1gWB++OLk9Y6H23tWIdKEXMeAX7492zDYgP+OSPS79EWAqXL8SvmDrbl +WI5eiM6X5hAMrOjQqzXhatD7eP41N/FC3SfhyhX7hFbagO7MJG2AS5bmSvcuCdcN +wDwXd94B+7bfYgJIRKbr272yDwkyzGn+zmxzvMUt6ak5PNzfmadvhMZvIfDftswp +GYpXIUU0GObOgP2tpCGTErs= +=m++F +-----END PGP PUBLIC KEY BLOCK-----`; + const privateKey = `-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: Keybase OpenPGP v2.1.3 +Comment: https://keybase.io/crypto + +xcEYBF2bZaMBBAClKkxaMJ05qoF1rtig67cjWLRXJO2teljOUA/eWxaABRXZ5cGe +OelcPBvCwsx3ckV2KfyRdRpJw6wHe5yERPkrks9slSuZ5TgnztRxXINjr+3u/6Qc +HamsMfRlCLAinf+jccA7k+GKmqyxbLsJY0Qv3kIomzvno54aIinFOvbEzQARAQAB +AAP7BXVS5aN3/AkNqIvOiUQ7nqrr9s9NHYUOvJllFNucxZP6x2MyQAjjlJKV9kdF +cOhxXDjXVHVIGPT4UUeoAgUHg6K0K5WLmmNaO1w7ayf9737OrhrQFblQNqh4J9BV +oP/cArJ5+j/4IGKGYuWy3kTpvtabedlWq99E9PYrDJHD8E8CANDjnboIRgmAwHwi +ZKqc5rNXIBl7fJgFdf96cWiMF/7j2nJuarJGJRQUGxDaBi5zZSTZnwfVJZrDboyb +JCahLTMCAMpqP0wTM4Qs95HhJUBmAdBhqxXjiAMtMDnn0ue8qAtv4JRjPkfxXUsC +4J4PExw6eMU7BCGInel5B6+jdpvURf8B/3koVTHTxyBR/OTpP8XiwOwreb/SleIS +JMYiXx6akUoPtACfXyBYM0fqCNCq38ZYhNM89oJbu1Rm5LJHe0m0DY6d4c0AwrQE +EwEKAB4FAl2bZaMCGy8DCwkHAxUKCAIeAQIXgAMWAgECGQEACgkQZLv1XZLsPSjC +0wP9FP6jgFAnw1JAEOCRh/ja4467VR3oa43z2dfqfbTIBluapDwIecdj5ByXp+3g +edsho2sjdqun8LYL5zAXKma3UErIUQlRc7t+3eL8jGeOkKisPIAwINyQO+q6ZrUt +a1Qctvmz9m4YxIeeG6OLvghFa4ih8091/tsJlbUYxtb2M1THwRgEXZtlowEEAL38 +zaNkPmUVQaowP696fayBo18Nxs0yOzC4+0TYv1B/k5aUb0Air2h+o/Xw4E42Jh9g +VdPSvhOAEqdV0UDe71wxa4cfAVMDY9v8ta81MWunChj3ISUk1oIQylTJNsY/b4KW +OrLaOtBD9dyFGCzss5vLVdqdMjVIW2Cz0hb6IYG7ABEBAAEAA/4xkx7hrM2vOL26 +t/5WPsM+WVGVAxZGAv549zvxuhEp4zBS0Ya6GJLm1GzaRzFwlyaZd1zN+ibJFdlI +OtdwcvvIAqNBsJMcjl2eaVtWK/PYvwqS7mVfojK8zUsKKNFIL6z/JKv7gmXzGuKV +S5aYUOUMQI3mliTuqQpfLewhYBtOeQIA42jDWJfxjWiejV6QSNmBYhLeOwi/CFrd +YE6obpXqX0V3vVOqB1rw/VHfabkWBmdOu55muw9kCLYOR89HNF6NrwIA1d+cTU7p +eFgSUm/u1esS1ucAoxdOPZ7pkLv9+NLQNvjLThmOHCFXyTZr4aoHtnqSG8PcUAWs +hyQ35+WpKWA7tQH9GqDFogK+8GjzgVl+vCEnaTV7H/69tS93m9z06hFRs4iEZwWC +4oCUNqOFj6IFyiBf2cM0pmMX0ODLnIG5SDVfWaIFwsCDBBgBCgAPBQJdm2WjBQkP +CZwAAhsuAKgJEGS79V2S7D0onSAEGQEKAAYFAl2bZaMACgkQNejFNrtoRU1/iQQA +mdu8ZNAH58to40qEc6izK4cxOwwRnINkKnnDHTa3tdoVaJqQFnEuJTRZj0oSkYul +E/GW9/Jw+N78H6sGSJp+tHTj6qgHqd2BfwMZtbuRyW9GjBLG/DvY1bFZjSb/Y1U8 +z3ZUxhUI7mniX5OZcMQfufzvTXIA4T4EQJPcW+Ba0/sc5gP+LQ9dQgHXCE1JE2U+ +GTqNuNfAxnmomhKlnCJGXkBuFYeUzI+ELWQ8LGEIVz5eWY9cyiDf9YEaygvs7WkK +AxvKSV007XPYp0VPvnYTKp0Rqz3M9FX1VxWoh+z1AsvuFqT+q+QbOWEPREh6PhbJ +iLw81N3vzw9En13zuL6gaxWDfPfHwRgEXZtlowEEAMlp97TD12JoI3HcDt7wyqNs +2uGsuQPZw/FeykIBNfdK4jX13b6EyIdl8pPJp0FMhr4gLG+qjHDHJkOtodJjcJZV +l3VXgoHmap/7dmPmuIt4ZTR21fCL/lzRt1SEFSAeIAyGLvmnTmNiKHkqw3E9wr2E +TA+7sH5B4dREyIiCiNMZABEBAAEAA/wJeGeSwtCaSm48OM4kMms8wu4JxW7PnQon +C79z2g25CnbXda+O+TxajXMZ+tXX7qq5PtcICxteZCbK8NuWgmF1QqWWhS2ZLbAV +5edTc0vw8FSDwiAeiHyKa5Hs4B3uJaB54uADPyOYHPfX/NhEOfNAleDgVoa1Toqf +R50lFsGOVwIA/cetzK3+NTZ5W+V8DGShxv4u5qAhhGZRb0GA3TPAoshVjHWY34i1 +KivtI3/tLLNTaVSVblG2VVoydKelRhsjGwIAyy0E1KI5O2EhLsVsDwx9NtO4SmUG +REZt/LRYp1p5+nsarfeCVKQ4qQ6eqdK71Z7tEICT0JXqgSjQsKYVdscR2wH9GiyR +LuHX3Nnh+M8lUv36ZM5XrWEypRFQaNYssRzPpqU4f9oViSPxdADonxehDP4ICmFr +vqT+etEmjr9dzp4ZSKLswsCDBBgBCgAPBQJdm2WjBQkDwmcAAhsuAKgJEGS79V2S +7D0onSAEGQEKAAYFAl2bZaMACgkQzdkMJSM5Bqg2rwP/Ue28m3Fdfgh5JxouZ3Dm +2KUDhZL95B+vdMk72acdoU7SRjlyDT8cApRqYx+MIXb8WrPN1xCZnOM4zXeWIM0C +APQ1e/sCrK58L+P+eVngNmrW9epKtZ4L6hx+dqqja9vPZGQKCsFAhA6A1gWB++OL +k9Y6H23tWIdKEXMeAX7492zDYgP+OSPS79EWAqXL8SvmDrblWI5eiM6X5hAMrOjQ +qzXhatD7eP41N/FC3SfhyhX7hFbagO7MJG2AS5bmSvcuCdcNwDwXd94B+7bfYgJI +RKbr272yDwkyzGn+zmxzvMUt6ak5PNzfmadvhMZvIfDftswpGYpXIUU0GObOgP2t +pCGTErs= +=Ya+/ +-----END PGP PRIVATE KEY BLOCK-----`; + + const message = "A Fool and His Money are Soon Parted"; + + const encrypted = await chef.PGPEncrypt(message, { + 接收者公钥: pbkey, + }); + const result = await chef.PGPDecrypt(encrypted, { + 接收者私钥: privateKey, + }); + + assert.strictEqual(result.toString(), message); + }), + + it("Raw deflate", () => { + assert.strictEqual(chef.rawInflate(chef.rawDeflate("Like Father Like Son", { compressionType: "Fixed Huffman Coding"})).toString(), "Like Father Like Son"); + }), + + it("RC4", () => { + assert.strictEqual( + chef.RC4("Go Out On a Limb", {加密密码: {string: "Under Your Nose", option: "UTF8"}, 输入格式: "UTF8", 输出格式: "十六进制"}).toString(), + "7d17e60d9bc94b7f4095851c729e69a2"); + }), + + it("RC4 Drop", () => { + assert.strictEqual( + chef.RC4Drop("Go Out On a Limb", {加密密码: {string: "Under Your Nose", option: "UTF8"}, 输入格式: "UTF8", 输出格式: "十六进制"}).toString(), + "b85cb1c4ed6bed8f260ab92829bba942"); + }), + + it("Regular Expression", () => { + assert.strictEqual(chef.regularExpression("Wouldn't Harm a Fly", {正则表达式: "\\'[a-z]"}).toString(), "Wouldn't Harm a Fly"); + }), + + it("Remove EXIF", () => { + const result = chef.removeEXIF(fs.readFileSync("tests/node/sampleData/pic.jpg")); + assert.strictEqual(result.toString().length, 4582); + }), + + it("Scan for embedded files", () => { + const result = chef.scanForEmbeddedFiles(fs.readFileSync("src/web/static/images/cook_male-32x32.png")); + const expected = "在输入内容中检测魔术字节(Magic bytes)来扫描潜在的嵌入文件。"; + assert.ok(result.toString().indexOf(expected) === 0); + }), + + it("Scrypt", () => { + assert.strictEqual( + chef.scrypt("Playing For Keeps", {盐: {string: "salty", option: "十六进制"}}).toString(), + "5446b6d86d88515894a163201765bceed0bc39610b1506cdc4d939ffc638bc46e051bce756e2865165d89d955a43a7eb5504502567dea8bfc9e7d49aaa894c07"); + }), + + it("SHA3", () => { + assert.strictEqual( + chef.SHA3("benign gravel").toString(), + "2b1e36e0dbe151a89887be08da3bad141908cce62327f678161bcf058627e87abe57e3c5fce6581678714e6705a207acbd5c1f37f7a812280bc2cc558f00bed9"); + }), + + it("Shake", () => { + assert.strictEqual( + chef.shake("murderous bloodshed").toString(), + "b79b3bb88099330bc6a15122f8dfaededf57a33b51c748d5a94e8122ff18d21e12f83412926b7e4a77a85ba6f36aa4841685e78296036337175e40096b5ac000"); + }), + + it("Snefru", () => { + assert.strictEqual( + chef.snefru("demeaning milestone", {长度: 256, 轮数: 8}).toString(), + "a671b48770fe073ce49e9259cc2f47d345a53712639f8ae23c5ad3fec19540a5"); + }), + + it("SQL Beautify", () => { + const result = chef.SQLBeautify(`SELECT MONTH, ID, RAIN_I, TEMP_F +FROM STATS;`); + const expected = `SELECT MONTH, + ID, + RAIN_I, + TEMP_F +FROM STATS;`; + assert.strictEqual(result.toString(), expected); + }), + + it("SSDEEP", () => { + assert.strictEqual( + chef.SSDEEP("shotgun tyranny snugly").toString(), + "3:DLIXzMQCJc:XERKc"); + }), + + it("strings", () => { + const result = chef.strings("smothering ampersand abreast", {显示总数: true}); + const expected = `总计: 1 + +smothering ampersand abreast`; + assert.strictEqual(result.toString(), expected); + }), + + it("toBase64: editableOption", () => { + const result = toBase64("some input", { + 可用字符: { + value: "0-9A-W+/a-zXYZ=" + }, + }); + assert.strictEqual(result.toString(), "StXkPI1gRe1sT0=="); + }), + + it("toBase64: editableOptions key is value", () => { + const result = toBase64("some input", { + 可用字符: "0-9A-W+/a-zXYZ=", + }); + assert.strictEqual(result.toString(), "StXkPI1gRe1sT0=="); + }), + + it("toBase64: editableOptions default", () => { + const result = toBase64("some input"); + assert.strictEqual(result.toString(), "c29tZSBpbnB1dA=="); + }), + + it("To BCD", () => { + assert.strictEqual(chef.toBCD("443").toString(), "0100 0100 0011"); + }), + + it("To CamelCase", () => { + assert.strictEqual(chef.toCamelCase("Quickest Wheel").toString(), "quickestWheel"); + }), + + it("toHex: accepts args", () => { + const result = toHex("some input", { + 分隔符: "冒号", + }); + assert.strictEqual(result.toString(), "73:6f:6d:65:20:69:6e:70:75:74"); + }), + + it("To Kebab case", () => { + assert.strictEqual(chef.toKebabCase("Elfin Gold").toString(), "elfin-gold"); + }), + + it("To punycode", () => { + assert.strictEqual(chef.toPunycode("♠ ♣ ♥ ♦ ← ↑ ‍ →").toString(), " -m06cw7klao368lfb3aq"); + }), + + it("to snake case", () => { + assert.strictEqual(chef.toSnakeCase("Abhorrent Grass").value, "abhorrent_grass"); + }), + + it("to unix timestamp", () => { + assert.strictEqual(chef.toUNIXTimestamp("2001-04-01").toString(), "986083200 (Sun 1 April 2001 00:00:00 UTC)"); + }), + + it("Translate DateTime format", () => { + assert.strictEqual(chef.translateDateTimeFormat("01/04/1999 22:33:01").toString(), "Thursday 1st April 1999 22:33:01 +00:00 UTC"); + }), + + it("Triple DES encrypt / decrypt", () => { + assert.strictEqual( + chef.tripleDESDecrypt( + chef.tripleDESEncrypt("Destroy Money", { + Key: {string: "30 31 2f 30 34 2f 31 39 39 39 20 32 32 3a 33 33 3a 30 3130 31 2f 30 34", option: "十六进制"}, + IV: {string: "00 00 00 00 00 00 00 00", option: "十六进制"}}), + { + Key: {string: "30 31 2f 30 34 2f 31 39 39 39 20 32 32 3a 33 33 3a 30 3130 31 2f 30 34", option: "十六进制"}, + IV: {string: "00 00 00 00 00 00 00 00", option: "十六进制"} + }).toString(), + "Destroy Money"); + }), + + it("UNIX Timestamp to Windows Filetime", () => { + assert.strictEqual(chef.UNIXTimestampToWindowsFiletime("2020735").toString(), "116464943350000000"); + }), + + it("XML Beautify", () => { + assert.strictEqual( + chef.XMLBeautify("abc").toString(), + ` +\\tabc +`); + }), + + it("XOR: toggleString with default option", () => { + assert.strictEqual(chef.XOR("fe023da5", { + key: "73 6f 6d 65" + }).toString(), + "\u0015\n]W@\u000b\fP"); + }), + + it("XOR: toggleString with custom option", () => { + assert.strictEqual(chef.XOR("fe023da5", { + key: { + string: "73 6f 6d 65", + option: "utf8", + } + }).toString(), + "QV\u0010\u0004UDWQ"); + }), + + it("XPath expression", () => { + assert.strictEqual( + chef.XPathExpression("abc", {xPath: "contact-info/company"}).toString(), + "abc"); + }), + + it("Zlib deflate / inflate", () => { + assert.strictEqual(chef.zlibInflate(chef.zlibDeflate("cut homer wile rooky grits dizen")).toString(), "cut homer wile rooky grits dizen"); + }), + + it("extract EXIF", () => { + assert.strictEqual( + chef.extractEXIF(fs.readFileSync("tests/node/sampleData/pic.jpg")).toString(), + `找到 7 个标签。 + +Orientation: 1 +XResolution: 72 +YResolution: 72 +ResolutionUnit: 2 +ColorSpace: 1 +ExifImageWidth: 57 +ExifImageHeight: 57`); + }), + + it("Tar", () => { + const tarred = chef.tar("some file content", { + 文件名: "test.txt" + }); + assert.strictEqual(tarred.type, 7); + assert.strictEqual(tarred.value.size, 2048); + assert.strictEqual(tarred.value.data.toString().substr(0, 8), "test.txt"); + }), + + it("Untar", () => { + const tarred = chef.tar("some file content", { + 文件名: "filename.txt", + }); + const untarred = chef.untar(tarred); + assert.strictEqual(untarred.type, 8); + assert.strictEqual(untarred.value.length, 1); + assert.strictEqual(untarred.value[0].name, "filename.txt"); + assert.strictEqual(untarred.value[0].data.toString(), "some file content"); + }), + + it("Zip", () => { + const zipped = chef.zip("some file content", { + 文件名: "sample.zip", + 注释: "added", + 操作系统: "Unix", + }); + + assert.strictEqual(zipped.type, 7); + assert.ok(zipped.value.data.toString().includes("sample.zip")); + assert.ok(zipped.value.data.toString().includes("added")); + }), + + it("Unzip", () => { + const zipped = chef.zip("some file content", { + 文件名: "zipped.zip", + 注释: "zippy", + }); + const unzipped = chef.unzip(zipped); + + assert.equal(unzipped.type, 8); + assert.equal(unzipped.value[0].data, "some file content"); + assert.equal(unzipped.value[0].name, "zipped.zip"); + }), + + it("Unzip with password", () => { + const zipped = chef.zip("some content", { + 密码: "abcd", + }); + const unzipped = chef.unzip(zipped, { + 密码: "abcd", + }); + + assert.equal(unzipped.value[0].data, "some content"); + }), + + it("YARA Rule Matching", async () => { + const input = "foobar foobar bar foo foobar"; + const output = "规则 \"foo\" 匹配 (4 次):\n位置 0, 长度 3, 标识符 $re1, 数据: \"foo\"\n位置 7, 长度 3, 标识符 $re1, 数据: \"foo\"\n位置 18, 长度 3, 标识符 $re1, 数据: \"foo\"\n位置 22, 长度 3, 标识符 $re1, 数据: \"foo\"\n规则 \"bar\" 匹配 (4 次):\n位置 3, 长度 3, 标识符 $re1, 数据: \"bar\"\n位置 10, 长度 3, 标识符 $re1, 数据: \"bar\"\n位置 14, 长度 3, 标识符 $re1, 数据: \"bar\"\n位置 25, 长度 3, 标识符 $re1, 数据: \"bar\"\n"; + + const res = await chef.YARARules(input, { + 规则: "rule foo {strings: $re1 = /foo/ condition: $re1} rule bar {strings: $re1 = /bar/ condition: $re1}", + 显示字符串: true, + 显示字符串长度: true, + 显示元数据: true + }); + + assert.equal(output, res.value); + }), + + it("performs MAGIC", async () => { + const input = "WUagwsiae6mP8gNtCCLUFpCpCB26RmBDoDD8PacdAmzAzBVjkK2QstFXaKhpC6iUS7RHqXrJtFisoRSgoJ4whjm1arm864qaNq4RcfUmLHrcsAaZc5TXCYifNdgS83gDeejGX46gaiMyuBV6EskHt1scgJ88x2tNSotQDwbGY1mmCob2ARGFvCKYNqiN9ipMq1ZU1mgkdbNuGcb76aRtYWhCGUc8g93UJudhb8htsheZnwTpgqhx83SVJSZXMXUjJT2zmpC7uXWtumqokbdSi88YtkWDAc1Toouh2oH4D4ddmNKJWUDpMwmngUmK14xwmomccPQE9hM172APnSqwxdKQ172RkcAsysnmj5gGtRmVNNh2s359wr6mS2QRP"; + const 深度 = 1; + + const res = await chef.magic(input, { + 深度, + }); + + // assert against the structure of the output, rather than the values. + assert.strictEqual(res.value.length, 深度 + 1); + res.value.forEach(row => { + assert.ok(row.recipe); + assert.ok(row.data); + assert.ok(row.languageScores); + assert.ok(Object.prototype.hasOwnProperty.call(row, "fileType")); // Can be null, so cannot just use ok + assert.ok(row.entropy); + assert.ok(row.matchingOps); + assert.ok(Object.prototype.hasOwnProperty.call(row, "useful")); + assert.ok(Object.prototype.hasOwnProperty.call(row, "matchesCrib")); + + row.recipe.forEach(item => { + assert.ok(Object.prototype.hasOwnProperty.call(item, "op"), `No 'op' property in item ${item}`); + assert.strictEqual(typeof item.op, "string"); + assert.ok(Object.prototype.hasOwnProperty.call(item, "args"), `No 'args' property in item ${item}`); + assert.ok(Array.isArray(item.args)); + }); + + row.languageScores.forEach(score => { + assert.ok(Object.prototype.hasOwnProperty.call(score, "lang"), `No 'lang' property in languageScore ${score}`); + assert.strictEqual(typeof score.lang, "string"); + assert.ok(Object.prototype.hasOwnProperty.call(score, "score"), `No 'score' property in languageScore ${score}`); + assert.strictEqual(typeof score.score, "number"); + assert.ok(Object.prototype.hasOwnProperty.call(score, "probability"), `No 'probability' property in languageScore ${score}`); + assert.strictEqual(typeof score.probability, "number"); + }); + + row.matchingOps.forEach(op => { + assert.ok(Object.prototype.hasOwnProperty.call(op, "op"), `No 'op' property in matchingOp ${JSON.stringify(op)}`); + assert.strictEqual(typeof op.op, "string"); + assert.ok(Object.prototype.hasOwnProperty.call(op, "pattern"), `No 'pattern' property in matchingOp ${JSON.stringify(op)}`); + assert.ok(op.pattern instanceof RegExp); + assert.ok(Object.prototype.hasOwnProperty.call(op, "args"), `No 'args' property in matchingOp ${JSON.stringify(op)}`); + assert.ok(Array.isArray(op.args)); + assert.ok(Object.prototype.hasOwnProperty.call(op, "useful"), `No 'useful' property in matchingOp ${JSON.stringify(op)}`); + assert.ifError(op.useful); // Expect this to be undefined + assert.ok(Object.prototype.hasOwnProperty.call(op, "entropyRange"), `No 'entropyRange' property in matchingOp ${JSON.stringify(op)}`); + assert.ifError(op.entropyRange); // Expect this to be undefined + assert.ok(Object.prototype.hasOwnProperty.call(op, "output"), `No 'output' property in matchingOp ${JSON.stringify(op)}`); + assert.ifError(op.output); // Expect this to be undefined + }); + }); + + }), + + +]); + diff --git a/plugins/srktoolbox/tests/operations/index.mjs b/plugins/srktoolbox/tests/operations/index.mjs new file mode 100644 index 00000000..5703d157 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/index.mjs @@ -0,0 +1,202 @@ +/* eslint no-console: 0 */ + +/** + * Test Runner + * + * For running the tests in the test register. + * + * @author tlwr [toby@toby.codes] + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import { setLongTestFailure, logTestReport } from "../lib/utils.mjs"; + +import TestRegister from "../lib/TestRegister.mjs"; +import "./tests/AESKeyWrap.mjs"; +import "./tests/AlternatingCaps.mjs"; +import "./tests/AvroToJSON.mjs"; +import "./tests/BaconCipher.mjs"; +import "./tests/Base32.mjs"; +import "./tests/Base45.mjs"; +import "./tests/Base58.mjs"; +import "./tests/Base62.mjs"; +import "./tests/Base64.mjs"; +import "./tests/Base85.mjs"; +import "./tests/Base92.mjs"; +import "./tests/BCD.mjs"; +import "./tests/Bech32.mjs"; +import "./tests/BitwiseOp.mjs"; +import "./tests/BLAKE2b.mjs"; +import "./tests/BLAKE2s.mjs"; +import "./tests/BLAKE3.mjs"; +import "./tests/Bombe.mjs"; +import "./tests/BSON.mjs"; +import "./tests/ByteRepr.mjs"; +import "./tests/CaesarBoxCipher.mjs"; +import "./tests/CaretMdecode.mjs"; +import "./tests/CartesianProduct.mjs"; +import "./tests/CBORDecode.mjs"; +import "./tests/CBOREncode.mjs"; +import "./tests/CetaceanCipherDecode.mjs"; +import "./tests/CetaceanCipherEncode.mjs"; +import "./tests/ChaCha.mjs"; +import "./tests/ChangeIPFormat.mjs"; +import "./tests/CharEnc.mjs"; +import "./tests/Charts.mjs"; +import "./tests/Ciphers.mjs"; +import "./tests/CipherSaber2.mjs"; +import "./tests/CMAC.mjs"; +import "./tests/Code.mjs"; +import "./tests/Colossus.mjs"; +import "./tests/Comment.mjs"; +import "./tests/Compress.mjs"; +import "./tests/ConditionalJump.mjs"; +import "./tests/ConvertCoordinateFormat.mjs"; +import "./tests/ConvertLeetSpeak.mjs"; +import "./tests/ConvertToNATOAlphabet.mjs"; +import "./tests/CRCChecksum.mjs"; +import "./tests/Crypt.mjs"; +import "./tests/CSV.mjs"; +import "./tests/DateTime.mjs"; +import "./tests/DefangIP.mjs"; +import "./tests/DropNthBytes.mjs"; +import "./tests/ECDSA.mjs"; +import "./tests/ELFInfo.mjs"; +import "./tests/Enigma.mjs"; +import "./tests/ExtractEmailAddresses.mjs"; +import "./tests/ExtractHashes.mjs"; +import "./tests/ExtractIPAddresses.mjs"; +import "./tests/Float.mjs"; +import "./tests/FileTree.mjs"; +import "./tests/FletcherChecksum.mjs"; +import "./tests/Fork.mjs"; +import "./tests/FromDecimal.mjs"; +import "./tests/GenerateAllChecksums.mjs"; +import "./tests/GenerateAllHashes.mjs"; +import "./tests/GenerateDeBruijnSequence.mjs"; +import "./tests/GenerateQRCode.mjs"; +import "./tests/GetAllCasings.mjs"; +import "./tests/GOST.mjs"; +import "./tests/Gunzip.mjs"; +import "./tests/Gzip.mjs"; +import "./tests/Hash.mjs"; +import "./tests/HASSH.mjs"; +import "./tests/HaversineDistance.mjs"; +import "./tests/Hex.mjs"; +import "./tests/Hexdump.mjs"; +import "./tests/HKDF.mjs"; +import "./tests/Image.mjs"; +import "./tests/IndexOfCoincidence.mjs"; +import "./tests/JA3Fingerprint.mjs"; +import "./tests/JA4.mjs"; +import "./tests/JA3SFingerprint.mjs"; +import "./tests/Jsonata.mjs"; +import "./tests/JSONBeautify.mjs"; +import "./tests/JSONMinify.mjs"; +import "./tests/JSONtoCSV.mjs"; +import "./tests/Jump.mjs"; +import "./tests/JWK.mjs"; +import "./tests/JWTDecode.mjs"; +import "./tests/JWTSign.mjs"; +import "./tests/JWTVerify.mjs"; +import "./tests/LevenshteinDistance.mjs"; +import "./tests/Lorenz.mjs"; +import "./tests/LS47.mjs"; +import "./tests/LuhnChecksum.mjs"; +import "./tests/LZNT1Decompress.mjs"; +import "./tests/LZString.mjs"; +import "./tests/Magic.mjs"; +import "./tests/Media.mjs"; +import "./tests/MIMEDecoding.mjs"; +import "./tests/Modhex.mjs"; +import "./tests/MorseCode.mjs"; +import "./tests/MS.mjs"; +import "./tests/MultipleBombe.mjs"; +import "./tests/MurmurHash3.mjs"; +import "./tests/NetBIOS.mjs"; +import "./tests/NormaliseUnicode.mjs"; +import "./tests/NTLM.mjs"; +import "./tests/OTP.mjs"; +import "./tests/ParseIPRange.mjs"; +import "./tests/ParseObjectIDTimestamp.mjs"; +import "./tests/ParseQRCode.mjs"; +import "./tests/ParseSSHHostKey.mjs"; +import "./tests/ParseTCP.mjs"; +import "./tests/ParseTLSRecord.mjs"; +import "./tests/ParseTLV.mjs"; +import "./tests/ParseUDP.mjs"; +import "./tests/PEMtoHex.mjs"; +import "./tests/PGP.mjs"; +import "./tests/PHP.mjs"; +import "./tests/PHPSerialize.mjs"; +import "./tests/PowerSet.mjs"; +import "./tests/Protobuf.mjs"; +import "./tests/PubKeyFromCert.mjs"; +import "./tests/PubKeyFromPrivKey.mjs"; +import "./tests/Rabbit.mjs"; +import "./tests/RAKE.mjs"; +import "./tests/Regex.mjs"; +import "./tests/Register.mjs"; +import "./tests/RisonEncodeDecode.mjs"; +import "./tests/Rotate.mjs"; +import "./tests/RSA.mjs"; +import "./tests/Salsa20.mjs"; +import "./tests/XSalsa20.mjs"; +import "./tests/SeqUtils.mjs"; +import "./tests/SetDifference.mjs"; +import "./tests/SetIntersection.mjs"; +import "./tests/SetUnion.mjs"; +import "./tests/Shuffle.mjs"; +import "./tests/SIGABA.mjs"; +import "./tests/SM2.mjs"; +import "./tests/SM4.mjs"; +// import "./tests/SplitColourChannels.mjs"; // Cannot test operations that use the File type yet +import "./tests/StrUtils.mjs"; +import "./tests/StripIPv4Header.mjs"; +import "./tests/StripTCPHeader.mjs"; +import "./tests/StripUDPHeader.mjs"; +import "./tests/Subsection.mjs"; +import "./tests/SwapCase.mjs"; +import "./tests/SymmetricDifference.mjs"; +import "./tests/TakeNthBytes.mjs"; +import "./tests/Template.mjs"; +import "./tests/TextEncodingBruteForce.mjs"; +import "./tests/ToFromInsensitiveRegex.mjs"; +import "./tests/TranslateDateTimeFormat.mjs"; +import "./tests/Typex.mjs"; +import "./tests/UnescapeString.mjs"; +import "./tests/Unicode.mjs"; +import "./tests/URLEncodeDecode.mjs"; +import "./tests/RSA.mjs"; +import "./tests/CBOREncode.mjs"; +import "./tests/CBORDecode.mjs"; +import "./tests/JA3Fingerprint.mjs"; +import "./tests/JA3SFingerprint.mjs"; +import "./tests/HASSH.mjs"; +import "./tests/JSONtoYAML.mjs"; + +// Cannot test operations that use the File type yet +// import "./tests/SplitColourChannels.mjs"; +import "./tests/YARA.mjs"; +import "./tests/ParseCSR.mjs"; +import "./tests/XXTEA.mjs"; + +const testStatus = { + allTestsPassing: true, + counts: { + total: 0, + }, +}; + +setLongTestFailure(); + +const logOpsTestReport = logTestReport.bind(null, testStatus); + +(async function () { + const results = await TestRegister.runTests(); + logOpsTestReport(results); +})(); diff --git a/plugins/srktoolbox/tests/operations/tests/AESKeyWrap.mjs b/plugins/srktoolbox/tests/operations/tests/AESKeyWrap.mjs new file mode 100644 index 00000000..a1af7267 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/AESKeyWrap.mjs @@ -0,0 +1,326 @@ +/** + * @author mikecat + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + "name": "AES Key Wrap: RFC Test Vector, 128-bit data, 128-bit KEK", + "input": "00112233445566778899aabbccddeeff", + "expectedOutput": "1fa68b0a8112b447aef34bd8fb5a7b829d3e862371d2cfe5", + "recipeConfig": [ + { + "op": "AES密钥包装", + "args": [ + {"option": "十六进制", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "十六进制", "string": "a6a6a6a6a6a6a6a6"}, + "十六进制", "十六进制" + ], + }, + ], + }, + { + "name": "AES Key Wrap: RFC Test Vector, 128-bit data, 192-bit KEK", + "input": "00112233445566778899aabbccddeeff", + "expectedOutput": "96778b25ae6ca435f92b5b97c050aed2468ab8a17ad84e5d", + "recipeConfig": [ + { + "op": "AES密钥包装", + "args": [ + {"option": "十六进制", "string": "000102030405060708090a0b0c0d0e0f1011121314151617"}, + {"option": "十六进制", "string": "a6a6a6a6a6a6a6a6"}, + "十六进制", "十六进制" + ], + }, + ], + }, + { + "name": "AES Key Wrap: RFC Test Vector, 128-bit data, 256-bit KEK", + "input": "00112233445566778899aabbccddeeff", + "expectedOutput": "64e8c3f9ce0f5ba263e9777905818a2a93c8191e7d6e8ae7", + "recipeConfig": [ + { + "op": "AES密钥包装", + "args": [ + {"option": "十六进制", "string": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"}, + {"option": "十六进制", "string": "a6a6a6a6a6a6a6a6"}, + "十六进制", "十六进制" + ], + }, + ], + }, + { + "name": "AES Key Wrap: RFC Test Vector, 192-bit data, 192-bit KEK", + "input": "00112233445566778899aabbccddeeff0001020304050607", + "expectedOutput": "031d33264e15d33268f24ec260743edce1c6c7ddee725a936ba814915c6762d2", + "recipeConfig": [ + { + "op": "AES密钥包装", + "args": [ + {"option": "十六进制", "string": "000102030405060708090a0b0c0d0e0f1011121314151617"}, + {"option": "十六进制", "string": "a6a6a6a6a6a6a6a6"}, + "十六进制", "十六进制" + ], + }, + ], + }, + { + "name": "AES Key Wrap: RFC Test Vector, 192-bit data, 256-bit KEK", + "input": "00112233445566778899aabbccddeeff0001020304050607", + "expectedOutput": "a8f9bc1612c68b3ff6e6f4fbe30e71e4769c8b80a32cb8958cd5d17d6b254da1", + "recipeConfig": [ + { + "op": "AES密钥包装", + "args": [ + {"option": "十六进制", "string": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"}, + {"option": "十六进制", "string": "a6a6a6a6a6a6a6a6"}, + "十六进制", "十六进制" + ], + }, + ], + }, + { + "name": "AES Key Wrap: RFC Test Vector, 256-bit data, 256-bit KEK", + "input": "00112233445566778899aabbccddeeff000102030405060708090a0b0c0d0e0f", + "expectedOutput": "28c9f404c4b810f4cbccb35cfb87f8263f5786e2d80ed326cbc7f0e71a99f43bfb988b9b7a02dd21", + "recipeConfig": [ + { + "op": "AES密钥包装", + "args": [ + {"option": "十六进制", "string": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"}, + {"option": "十六进制", "string": "a6a6a6a6a6a6a6a6"}, + "十六进制", "十六进制" + ], + }, + ], + }, + { + "name": "AES Key Unwrap: RFC Test Vector, 128-bit data, 128-bit KEK", + "input": "1fa68b0a8112b447aef34bd8fb5a7b829d3e862371d2cfe5", + "expectedOutput": "00112233445566778899aabbccddeeff", + "recipeConfig": [ + { + "op": "AES密钥解包装", + "args": [ + {"option": "十六进制", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "十六进制", "string": "a6a6a6a6a6a6a6a6"}, + "十六进制", "十六进制" + ], + }, + ], + }, + { + "name": "AES Key Unwrap: RFC Test Vector, 128-bit data, 192-bit KEK", + "input": "96778b25ae6ca435f92b5b97c050aed2468ab8a17ad84e5d", + "expectedOutput": "00112233445566778899aabbccddeeff", + "recipeConfig": [ + { + "op": "AES密钥解包装", + "args": [ + {"option": "十六进制", "string": "000102030405060708090a0b0c0d0e0f1011121314151617"}, + {"option": "十六进制", "string": "a6a6a6a6a6a6a6a6"}, + "十六进制", "十六进制" + ], + }, + ], + }, + { + "name": "AES Key Unwrap: RFC Test Vector, 128-bit data, 256-bit KEK", + "input": "64e8c3f9ce0f5ba263e9777905818a2a93c8191e7d6e8ae7", + "expectedOutput": "00112233445566778899aabbccddeeff", + "recipeConfig": [ + { + "op": "AES密钥解包装", + "args": [ + {"option": "十六进制", "string": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"}, + {"option": "十六进制", "string": "a6a6a6a6a6a6a6a6"}, + "十六进制", "十六进制" + ], + }, + ], + }, + { + "name": "AES Key Unwrap: RFC Test Vector, 192-bit data, 192-bit KEK", + "input": "031d33264e15d33268f24ec260743edce1c6c7ddee725a936ba814915c6762d2", + "expectedOutput": "00112233445566778899aabbccddeeff0001020304050607", + "recipeConfig": [ + { + "op": "AES密钥解包装", + "args": [ + {"option": "十六进制", "string": "000102030405060708090a0b0c0d0e0f1011121314151617"}, + {"option": "十六进制", "string": "a6a6a6a6a6a6a6a6"}, + "十六进制", "十六进制" + ], + }, + ], + }, + { + "name": "AES Key Unwrap: RFC Test Vector, 192-bit data, 256-bit KEK", + "input": "a8f9bc1612c68b3ff6e6f4fbe30e71e4769c8b80a32cb8958cd5d17d6b254da1", + "expectedOutput": "00112233445566778899aabbccddeeff0001020304050607", + "recipeConfig": [ + { + "op": "AES密钥解包装", + "args": [ + {"option": "十六进制", "string": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"}, + {"option": "十六进制", "string": "a6a6a6a6a6a6a6a6"}, + "十六进制", "十六进制" + ], + }, + ], + }, + { + "name": "AES Key Unwrap: RFC Test Vector, 256-bit data, 256-bit KEK", + "input": "28c9f404c4b810f4cbccb35cfb87f8263f5786e2d80ed326cbc7f0e71a99f43bfb988b9b7a02dd21", + "expectedOutput": "00112233445566778899aabbccddeeff000102030405060708090a0b0c0d0e0f", + "recipeConfig": [ + { + "op": "AES密钥解包装", + "args": [ + {"option": "十六进制", "string": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"}, + {"option": "十六进制", "string": "a6a6a6a6a6a6a6a6"}, + "十六进制", "十六进制" + ], + }, + ], + }, + { + "name": "AES Key Wrap: invalid KEK length", + "input": "00112233445566778899aabbccddeeff", + "expectedOutput": "KEK必须为16、24或32字节长度(当前 10 字节)", + "recipeConfig": [ + { + "op": "AES密钥包装", + "args": [ + {"option": "十六进制", "string": "00010203040506070809"}, + {"option": "十六进制", "string": "a6a6a6a6a6a6a6a6"}, + "十六进制", "十六进制" + ], + }, + ], + }, + { + "name": "AES Key Wrap: invalid IV length", + "input": "00112233445566778899aabbccddeeff", + "expectedOutput": "IV必须为8字节长度(当前 6 字节)", + "recipeConfig": [ + { + "op": "AES密钥包装", + "args": [ + {"option": "十六进制", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "十六进制", "string": "a6a6a6a6a6a6"}, + "十六进制", "十六进制" + ], + }, + ], + }, + { + "name": "AES Key Wrap: input length not multiple of 8", + "input": "00112233445566778899aabbccddeeff0102", + "expectedOutput": "输入必须为8n (n>=2)字节长度(当前 18 字节)", + "recipeConfig": [ + { + "op": "AES密钥包装", + "args": [ + {"option": "十六进制", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "十六进制", "string": "a6a6a6a6a6a6a6a6"}, + "十六进制", "十六进制" + ], + }, + ], + }, + { + "name": "AES Key Wrap: input too short", + "input": "0011223344556677", + "expectedOutput": "输入必须为8n (n>=2)字节长度(当前 8 字节)", + "recipeConfig": [ + { + "op": "AES密钥包装", + "args": [ + {"option": "十六进制", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "十六进制", "string": "a6a6a6a6a6a6a6a6"}, + "十六进制", "十六进制" + ], + }, + ], + }, + { + "name": "AES Key Unwrap: invalid KEK length", + "input": "1fa68b0a8112b447aef34bd8fb5a7b829d3e862371d2cfe5", + "expectedOutput": "KEK必须为16、24或32字节长度(当前 10 字节)", + "recipeConfig": [ + { + "op": "AES密钥解包装", + "args": [ + {"option": "十六进制", "string": "00010203040506070809"}, + {"option": "十六进制", "string": "a6a6a6a6a6a6a6a6"}, + "十六进制", "十六进制" + ], + }, + ], + }, + { + "name": "AES Key Unwrap: invalid IV length", + "input": "1fa68b0a8112b447aef34bd8fb5a7b829d3e862371d2cfe5", + "expectedOutput": "IV必须为8字节长度(当前 6 字节)", + "recipeConfig": [ + { + "op": "AES密钥解包装", + "args": [ + {"option": "十六进制", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "十六进制", "string": "a6a6a6a6a6a6"}, + "十六进制", "十六进制" + ], + }, + ], + }, + { + "name": "AES Key Unwrap: input length not multiple of 8", + "input": "1fa68b0a8112b447aef34bd8fb5a7b829d3e862371d2cfe5e621", + "expectedOutput": "输入必须为8n (n>=3)字节长度(当前 26 字节)", + "recipeConfig": [ + { + "op": "AES密钥解包装", + "args": [ + {"option": "十六进制", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "十六进制", "string": "a6a6a6a6a6a6a6a6"}, + "十六进制", "十六进制" + ], + }, + ], + }, + { + "name": "AES Key Unwrap: input too short", + "input": "1fa68b0a8112b447aef34bd8fb5a7b82", + "expectedOutput": "输入必须为8n (n>=3)字节长度(当前 16 字节)", + "recipeConfig": [ + { + "op": "AES密钥解包装", + "args": [ + {"option": "十六进制", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "十六进制", "string": "a6a6a6a6a6a6a6a6"}, + "十六进制", "十六进制" + ], + }, + ], + }, + { + "name": "AES Key Unwrap: corrupted input", + "input": "1fa68b0a8112b447aef34bd8fb5a7b829d3e862371d2cfe6", + "expectedOutput": "IV不相符", + "recipeConfig": [ + { + "op": "AES密钥解包装", + "args": [ + {"option": "十六进制", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "十六进制", "string": "a6a6a6a6a6a6a6a6"}, + "十六进制", "十六进制" + ], + }, + ], + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/AlternatingCaps.mjs b/plugins/srktoolbox/tests/operations/tests/AlternatingCaps.mjs new file mode 100644 index 00000000..c14b7207 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/AlternatingCaps.mjs @@ -0,0 +1,21 @@ +/* @author sw5678 + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + * + * Modified by Raka-loah@github + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + "name": "AlternatingCaps: Basic Example", + "input": "Hello, world!", + "expectedOutput": "hElLo, WoRlD!", + "recipeConfig": [ + { + "op": "交替大小写", + "args": [] + }, + ], + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/AvroToJSON.mjs b/plugins/srktoolbox/tests/operations/tests/AvroToJSON.mjs new file mode 100644 index 00000000..9112d6c4 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/AvroToJSON.mjs @@ -0,0 +1,68 @@ +/** + * Avro to JSON tests. + * + * @author jarrodconnolly [jarrod@nestedquotes.ca] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Avro to JSON: no input (force JSON true)", + input: "", + expectedOutput: "Please provide an input.", + recipeConfig: [ + { + op: "Avro转JSON", + args: [true] + } + ], + }, + { + name: "Avro to JSON: no input (force JSON false)", + input: "", + expectedOutput: "Please provide an input.", + recipeConfig: [ + { + op: "Avro转JSON", + args: [false] + } + ], + }, + { + name: "Avro to JSON: small (force JSON true)", + input: "\x4f\x62\x6a\x01\x04\x16\x61\x76\x72\x6f\x2e\x73\x63\x68\x65\x6d\x61\x96\x01\x7b\x22\x74\x79\x70\x65\x22\x3a\x22\x72\x65" + + "\x63\x6f\x72\x64\x22\x2c\x22\x6e\x61\x6d\x65\x22\x3a\x22\x73\x6d\x61\x6c\x6c\x22\x2c\x22\x66\x69\x65\x6c\x64\x73\x22\x3a" + + "\x5b\x7b\x22\x6e\x61\x6d\x65\x22\x3a\x22\x6e\x61\x6d\x65\x22\x2c\x22\x74\x79\x70\x65\x22\x3a\x22\x73\x74\x72\x69\x6e\x67" + + "\x22\x7d\x5d\x7d\x14\x61\x76\x72\x6f\x2e\x63\x6f\x64\x65\x63\x08\x6e\x75\x6c\x6c\x00\x4e\x02\x47\x63\x2e\x37\x02\xe5\xb7" + + "\x5c\xda\xb9\xa6\x2f\x15\x41\x02\x0e\x0c\x6d\x79\x6e\x61\x6d\x65\x4e\x02\x47\x63\x2e\x37\x02\xe5\xb7\x5c\xda\xb9\xa6\x2f" + + "\x15\x41", + expectedOutput: "{\n \"name\": \"myname\"\n}", + recipeConfig: [ + { + op: "Avro转JSON", + args: [true] + } + ], + }, + { + name: "Avro to JSON: small (force JSON false)", + input: "\x4f\x62\x6a\x01\x04\x16\x61\x76\x72\x6f\x2e\x73\x63\x68\x65\x6d\x61\x96\x01\x7b\x22\x74\x79\x70\x65\x22\x3a\x22\x72\x65" + + "\x63\x6f\x72\x64\x22\x2c\x22\x6e\x61\x6d\x65\x22\x3a\x22\x73\x6d\x61\x6c\x6c\x22\x2c\x22\x66\x69\x65\x6c\x64\x73\x22\x3a" + + "\x5b\x7b\x22\x6e\x61\x6d\x65\x22\x3a\x22\x6e\x61\x6d\x65\x22\x2c\x22\x74\x79\x70\x65\x22\x3a\x22\x73\x74\x72\x69\x6e\x67" + + "\x22\x7d\x5d\x7d\x14\x61\x76\x72\x6f\x2e\x63\x6f\x64\x65\x63\x08\x6e\x75\x6c\x6c\x00\x4e\x02\x47\x63\x2e\x37\x02\xe5\xb7" + + "\x5c\xda\xb9\xa6\x2f\x15\x41\x02\x0e\x0c\x6d\x79\x6e\x61\x6d\x65\x4e\x02\x47\x63\x2e\x37\x02\xe5\xb7\x5c\xda\xb9\xa6\x2f" + + "\x15\x41", + expectedOutput: "{\"name\":\"myname\"}\n", + recipeConfig: [ + { + op: "Avro转JSON", + args: [false] + } + ], + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/BCD.mjs b/plugins/srktoolbox/tests/operations/tests/BCD.mjs new file mode 100644 index 00000000..714b74a7 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/BCD.mjs @@ -0,0 +1,105 @@ +/** + * BCD tests + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "To BCD: default 0", + input: "0", + expectedOutput: "0000", + recipeConfig: [ + { + "op": "BCD码编码", + "args": ["8 4 2 1", true, false, "半字节"] + } + ] + }, + { + name: "To BCD: unpacked nibbles", + input: "1234567890", + expectedOutput: "0000 0001 0000 0010 0000 0011 0000 0100 0000 0101 0000 0110 0000 0111 0000 1000 0000 1001 0000 0000", + recipeConfig: [ + { + "op": "BCD码编码", + "args": ["8 4 2 1", false, false, "半字节"] + } + ] + }, + { + name: "To BCD: packed, signed bytes", + input: "1234567890", + expectedOutput: "00000001 00100011 01000101 01100111 10001001 00001100", + recipeConfig: [ + { + "op": "BCD码编码", + "args": ["8 4 2 1", true, true, "字节"] + } + ] + }, + { + name: "To BCD: packed, signed nibbles, 8 4 -2 -1", + input: "-1234567890", + expectedOutput: "0000 0111 0110 0101 0100 1011 1010 1001 1000 1111 0000 1101", + recipeConfig: [ + { + "op": "BCD码编码", + "args": ["8 4 -2 -1", true, true, "半字节"] + } + ] + }, + { + name: "From BCD: default 0", + input: "0000", + expectedOutput: "0", + recipeConfig: [ + { + "op": "BCD码解码", + "args": ["8 4 2 1", true, false, "半字节"] + } + ] + }, + { + name: "From BCD: packed, signed bytes", + input: "00000001 00100011 01000101 01100111 10001001 00001101", + expectedOutput: "-1234567890", + recipeConfig: [ + { + "op": "BCD码解码", + "args": ["8 4 2 1", true, true, "字节"] + } + ] + }, + { + name: "From BCD: Excess-3, unpacked, unsigned", + input: "00000100 00000101 00000110 00000111 00001000 00001001 00001010 00001011 00001100 00000011", + expectedOutput: "1234567890", + recipeConfig: [ + { + "op": "BCD码解码", + "args": ["余3码", false, false, "半字节"] + } + ] + }, + { + name: "BCD: raw 4 2 2 1, packed, signed", + input: "1234567890", + expectedOutput: "1234567890", + recipeConfig: [ + { + "op": "BCD码编码", + "args": ["4 2 2 1", true, true, "原始"] + }, + { + "op": "BCD码解码", + "args": ["4 2 2 1", true, true, "原始"] + } + ] + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/BLAKE2b.mjs b/plugins/srktoolbox/tests/operations/tests/BLAKE2b.mjs new file mode 100644 index 00000000..032e12be --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/BLAKE2b.mjs @@ -0,0 +1,58 @@ +/** + * BitwiseOp tests + * + * @author h345983745 + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "BLAKE2b: 512 - Hello World", + input: "Hello World", + expectedOutput: "4386a08a265111c9896f56456e2cb61a64239115c4784cf438e36cc851221972da3fb0115f73cd02486254001f878ab1fd126aac69844ef1c1ca152379d0a9bd", + recipeConfig: [ + { "op": "BLAKE2b", + "args": ["512", "十六进制", {string: "", option: "UTF8"}] } + ] + }, + { + name: "BLAKE2b: 384 - Hello World", + input: "Hello World", + expectedOutput: "4d388e82ca8f866e606b6f6f0be910abd62ad6e98c0adfc27cf35acf948986d5c5b9c18b6f47261e1e679eb98edf8e2d", + recipeConfig: [ + { "op": "BLAKE2b", + "args": ["384", "十六进制", {string: "", option: "UTF8"}] } + ] + }, + { + name: "BLAKE2b: 256 - Hello World", + input: "Hello World", + expectedOutput: "1dc01772ee0171f5f614c673e3c7fa1107a8cf727bdf5a6dadb379e93c0d1d00", + recipeConfig: [ + { "op": "BLAKE2b", + "args": ["256", "十六进制", {string: "", option: "UTF8"}] } + ] + }, + { + name: "BLAKE2b: 160 - Hello World", + input: "Hello World", + expectedOutput: "6a8489e6fd6e51fae12ab271ec7fc8134dd5d737", + recipeConfig: [ + { "op": "BLAKE2b", + "args": ["160", "十六进制", {string: "", option: "UTF8"}] } + ] + }, + { + name: "BLAKE2b: Key Test", + input: "message data", + expectedOutput: "3d363ff7401e02026f4a4687d4863ced", + recipeConfig: [ + { "op": "BLAKE2b", + "args": ["128", "十六进制", {string: "pseudorandom key", option: "UTF8"}] } + ] + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/BLAKE2s.mjs b/plugins/srktoolbox/tests/operations/tests/BLAKE2s.mjs new file mode 100644 index 00000000..40519f2d --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/BLAKE2s.mjs @@ -0,0 +1,49 @@ +/** + * BitwiseOp tests + * + * @author h345983745 + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "BLAKE2s: 256 - Hello World", + input: "Hello World", + expectedOutput: "7706af019148849e516f95ba630307a2018bb7bf03803eca5ed7ed2c3c013513", + recipeConfig: [ + { "op": "BLAKE2s", + "args": ["256", "十六进制", {string: "", option: "UTF8"}] } + ] + }, + { + name: "BLAKE2s: 160 - Hello World", + input: "Hello World", + expectedOutput: "0e4fcfc2ee0097ac1d72d70b595a39e09a3c7c7e", + recipeConfig: [ + { "op": "BLAKE2s", + "args": ["160", "十六进制", {string: "", option: "UTF8"}] } + ] + }, + { + name: "BLAKE2s: 128 - Hello World", + input: "Hello World", + expectedOutput: "9964ee6f36126626bf864363edfa96f6", + recipeConfig: [ + { "op": "BLAKE2s", + "args": ["128", "十六进制", {string: "", option: "UTF8"}] } + ] + }, + { + name: "BLAKE2s: Key Test", + input: "Hello World", + expectedOutput: "9964ee6f36126626bf864363edfa96f6", + recipeConfig: [ + { "op": "BLAKE2s", + "args": ["128", "十六进制", {string: "", option: "UTF8"}] } + ] + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/BLAKE3.mjs b/plugins/srktoolbox/tests/operations/tests/BLAKE3.mjs new file mode 100644 index 00000000..42cb4dc1 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/BLAKE3.mjs @@ -0,0 +1,55 @@ +/** + * BLAKE3 tests. + * @author xumptex [xumptex@outlook.fr] + * @copyright Crown Copyright 2025 + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "BLAKE3: 8 - Hello world", + input: "Hello world", + expectedOutput: "e7e6fb7d2869d109", + recipeConfig: [ + { "op": "BLAKE3", + "args": [8, ""] } + ] + }, + { + name: "BLAKE3: 16 - Hello world 2", + input: "Hello world 2", + expectedOutput: "2a3df5fe5f0d3fcdd995fc203c7f7c52", + recipeConfig: [ + { "op": "BLAKE3", + "args": [16, ""] } + ] + }, + { + name: "BLAKE3: 32 - Hello world", + input: "Hello world", + expectedOutput: "e7e6fb7d2869d109b62cdb1227208d4016cdaa0af6603d95223c6a698137d945", + recipeConfig: [ + { "op": "BLAKE3", + "args": [32, ""] } + ] + }, + { + name: "BLAKE3: Key Test", + input: "Hello world", + expectedOutput: "59dd23ac9d025690", + recipeConfig: [ + { "op": "BLAKE3", + "args": [8, "ThiskeyisexactlythirtytwoBytesLo"] } + ] + }, + { + name: "BLAKE3: Key Test 2", + input: "Hello world", + expectedOutput: "c8302c9634c1da42", + recipeConfig: [ + { "op": "BLAKE3", + "args": [8, "ThiskeyisexactlythirtytwoByteslo"] } + ] + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/BSON.mjs b/plugins/srktoolbox/tests/operations/tests/BSON.mjs new file mode 100644 index 00000000..a81c347a --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/BSON.mjs @@ -0,0 +1,58 @@ +/** + * BSON tests. + * + * @author n1474335 [n1474335@gmail.com] + * + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "BSON serialise: nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "BSON序列化", + args: [], + }, + ], + }, + { + name: "BSON serialise: basic", + input: "{\"hello\":\"world\"}", + expectedOutput: "\x16\x00\x00\x00\x02hello\x00\x06\x00\x00\x00world\x00\x00", + recipeConfig: [ + { + op: "BSON序列化", + args: [], + }, + ], + }, + { + name: "BSON deserialise: nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "BSON反序列化", + args: [], + }, + ], + }, + { + name: "BSON deserialise: basic", + input: "\x16\x00\x00\x00\x02hello\x00\x06\x00\x00\x00world\x00\x00", + expectedOutput: "{\n \"hello\": \"world\"\n}", + recipeConfig: [ + { + op: "BSON反序列化", + args: [], + }, + ], + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/BaconCipher.mjs b/plugins/srktoolbox/tests/operations/tests/BaconCipher.mjs new file mode 100644 index 00000000..71c58c07 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/BaconCipher.mjs @@ -0,0 +1,435 @@ +/** + * Bacon Cipher tests. + * + * @author Karsten Silkenbäumer [github.com/kassi] + * @copyright Karsten Silkenbäumer 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; +import { BACON_ALPHABETS, BACON_TRANSLATIONS } from "../../../src/core/lib/Bacon.mjs"; + +const alphabets = Object.keys(BACON_ALPHABETS); +const translations = BACON_TRANSLATIONS; + +TestRegister.addTests([ + { + name: "Bacon Decode: no input", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "培根密码解密", + args: [alphabets[0], translations[0], false] + } + ], + }, + { + name: "Bacon Decode: reduced alphabet 0/1", + input: "00011 00100 00010 01101 00011 01000 01100 00110 00001 00000 00010 01101 01100 10100 01101 10000 01001 10001", + expectedOutput: "DECODINGBACONWORKS", + recipeConfig: [ + { + op: "培根密码解密", + args: [alphabets[0], translations[0], false] + } + ], + }, + { + name: "Bacon Decode: reduced alphabet 0/1 inverse", + input: "11100 11011 11101 10010 11100 10111 10011 11001 11110 11111 11101 10010 10011 01011 10010 01111 10110 01110", + expectedOutput: "DECODINGBACONWORKS", + recipeConfig: [ + { + op: "培根密码解密", + args: [alphabets[0], translations[0], true] + } + ], + }, + { + name: "Bacon Decode: reduced alphabet A/B lower case", + input: "aaabb aabaa aaaba abbab aaabb abaaa abbaa aabba aaaab aaaaa aaaba abbab abbaa babaa abbab baaaa abaab baaab", + expectedOutput: "DECODINGBACONWORKS", + recipeConfig: [ + { + op: "培根密码解密", + args: [alphabets[0], translations[1], false] + } + ], + }, + { + name: "Bacon Decode: reduced alphabet A/B lower case inverse", + input: "bbbaa bbabb bbbab baaba bbbaa babbb baabb bbaab bbbba bbbbb bbbab baaba baabb ababb baaba abbbb babba abbba", + expectedOutput: "DECODINGBACONWORKS", + recipeConfig: [ + { + op: "培根密码解密", + args: [alphabets[0], translations[1], true] + } + ], + }, + { + name: "Bacon Decode: reduced alphabet A/B upper case", + input: "AAABB AABAA AAABA ABBAB AAABB ABAAA ABBAA AABBA AAAAB AAAAA AAABA ABBAB ABBAA BABAA ABBAB BAAAA ABAAB BAAAB", + expectedOutput: "DECODINGBACONWORKS", + recipeConfig: [ + { + op: "培根密码解密", + args: [alphabets[0], translations[1], false] + } + ], + }, + { + name: "Bacon Decode: reduced alphabet A/B upper case inverse", + input: "BBBAA BBABB BBBAB BAABA BBBAA BABBB BAABB BBAAB BBBBA BBBBB BBBAB BAABA BAABB ABABB BAABA ABBBB BABBA ABBBA", + expectedOutput: "DECODINGBACONWORKS", + recipeConfig: [ + { + op: "培根密码解密", + args: [alphabets[0], translations[1], true] + } + ], + }, + { + name: "Bacon Decode: reduced alphabet case code", + input: "thiS IsaN exampLe oF ThE bacON cIpher WIth upPPercasE letters tRanSLaTiNG to OnEs anD LoWErcase To zERoes. KS", + expectedOutput: "DECODINGBACONWORKS", + recipeConfig: [ + { + op: "培根密码解密", + args: [alphabets[0], translations[2], false] + } + ], + }, + { + name: "Bacon Decode: reduced alphabet case code inverse", + input: "THIs iS An EXAMPlE Of tHe BACon CiPHER wiTH UPppERCASe LETTERS TrANslAtIng TO oNeS ANd lOweRCASE tO ZerOES. ks", + expectedOutput: "DECODINGBACONWORKS", + recipeConfig: [ + { + op: "培根密码解密", + args: [alphabets[0], translations[2], true] + } + ], + }, + { + name: "Bacon Decode: reduced alphabet case code", + input: "A little example of the Bacon Cipher to be decoded. It is a working example and shorter than my others, but it anyways works tremendously. And just that's important, correct?", + expectedOutput: "DECODE", + recipeConfig: [ + { + op: "培根密码解密", + args: [alphabets[0], translations[3], false] + } + ], + }, + { + name: "Bacon Decode: reduced alphabet case code inverse", + input: "Well, there's now another example which will be not only strange to read but sound weird for everyone not knowing what the thing is about. Nevertheless, works great out of the box.", + expectedOutput: "DECODE", + recipeConfig: [ + { + op: "培根密码解密", + args: [alphabets[0], translations[3], true] + } + ], + }, + { + name: "Bacon Decode: complete alphabet 0/1", + input: "00011 00100 00010 01110 00011 01000 01101 00110 00001 00000 00010 01110 01101 10110 01110 10001 01010 10010", + expectedOutput: "DECODINGBACONWORKS", + recipeConfig: [ + { + op: "培根密码解密", + args: [alphabets[1], translations[0], false] + } + ], + }, + { + name: "Bacon Decode: complete alphabet 0/1 inverse", + input: "11100 11011 11101 10001 11100 10111 10010 11001 11110 11111 11101 10001 10010 01001 10001 01110 10101 01101", + expectedOutput: "DECODINGBACONWORKS", + recipeConfig: [ + { + op: "培根密码解密", + args: [alphabets[1], translations[0], true] + } + ], + }, + { + name: "Bacon Decode: complete alphabet A/B lower case", + input: "aaabb aabaa aaaba abbba aaabb abaaa abbab aabba aaaab aaaaa aaaba abbba abbab babba abbba baaab ababa baaba", + expectedOutput: "DECODINGBACONWORKS", + recipeConfig: [ + { + op: "培根密码解密", + args: [alphabets[1], translations[1], false] + } + ], + }, + { + name: "Bacon Decode: complete alphabet A/B lower case inverse", + input: "bbbaa bbabb bbbab baaab bbbaa babbb baaba bbaab bbbba bbbbb bbbab baaab baaba abaab baaab abbba babab abbab", + expectedOutput: "DECODINGBACONWORKS", + recipeConfig: [ + { + op: "培根密码解密", + args: [alphabets[1], translations[1], true] + } + ], + }, + { + name: "Bacon Decode: complete alphabet A/B upper case", + input: "AAABB AABAA AAABA ABBBA AAABB ABAAA ABBAB AABBA AAAAB AAAAA AAABA ABBBA ABBAB BABBA ABBBA BAAAB ABABA BAABA", + expectedOutput: "DECODINGBACONWORKS", + recipeConfig: [ + { + op: "培根密码解密", + args: [alphabets[1], translations[1], false] + } + ], + }, + { + name: "Bacon Decode: complete alphabet A/B upper case inverse", + input: "BBBAA BBABB BBBAB BAAAB BBBAA BABBB BAABA BBAAB BBBBA BBBBB BBBAB BAAAB BAABA ABAAB BAAAB ABBBA BABAB ABBAB", + expectedOutput: "DECODINGBACONWORKS", + recipeConfig: [ + { + op: "培根密码解密", + args: [alphabets[1], translations[1], true] + } + ], + }, + { + name: "Bacon Decode: complete alphabet case code", + input: "thiS IsaN exampLe oF THe bacON cIpher WItH upPPercasE letters tRanSLAtiNG tO OnES anD LOwErcaSe To ZeRoeS. kS", + expectedOutput: "DECODINGBACONWORKS", + recipeConfig: [ + { + op: "培根密码解密", + args: [alphabets[1], translations[2], false] + } + ], + }, + { + name: "Bacon Decode: complete alphabet case code inverse", + input: "THIs iSAn EXAMPlE Of thE BACon CiPHER wiTh UPppERCASe LETTERS TrANslaTIng To zEroES and LoWERcAsE tO oNEs. Ks", + expectedOutput: "DECODINGBACONWORKS", + recipeConfig: [ + { + op: "培根密码解密", + args: [alphabets[1], translations[2], true] + } + ], + }, + { + name: "Bacon Decode: complete alphabet case code", + input: "A little example of the Bacon Cipher to be decoded. It is a working example and shorter than the first, but it anyways works tremendously. And just that's important, correct?", + expectedOutput: "DECODE", + recipeConfig: [ + { + op: "培根密码解密", + args: [alphabets[1], translations[3], false] + } + ], + }, + { + name: "Bacon Decode: complete alphabet case code inverse", + input: "Well, there's now another example which will be not only strange to read but sound weird for everyone knowing nothing what the thing is about. Nevertheless, works great out of the box. ", + expectedOutput: "DECODE", + recipeConfig: [ + { + op: "培根密码解密", + args: [alphabets[1], translations[3], true] + } + ], + }, + { + name: "Bacon Encode: no input", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "培根密码加密", + args: [alphabets[0], translations[0], false, false] + } + ], + }, + { + name: "Bacon Encode: reduced alphabet 0/1", + input: "There's a fox, and it jumps over the fence.", + expectedOutput: "10010 00111 00100 10000 00100 10001 00000 00101 01101 10101 00000 01100 00011 01000 10010 01000 10011 01011 01110 10001 01101 10011 00100 10000 10010 00111 00100 00101 00100 01100 00010 00100", + recipeConfig: [ + { + op: "培根密码加密", + args: [alphabets[0], translations[0], false, false] + } + ], + }, + { + name: "Bacon Encode: reduced alphabet 0/1 inverse", + input: "There's a fox, and it jumps over the fence.", + expectedOutput: "01101 11000 11011 01111 11011 01110 11111 11010 10010 01010 11111 10011 11100 10111 01101 10111 01100 10100 10001 01110 10010 01100 11011 01111 01101 11000 11011 11010 11011 10011 11101 11011", + recipeConfig: [ + { + op: "培根密码加密", + args: [alphabets[0], translations[0], false, true] + } + ], + }, + { + name: "Bacon Encode: reduced alphabet 0/1, keeping extra characters", + input: "There's a fox, and it jumps over the fence.", + expectedOutput: "1001000111001001000000100'10001 00000 001010110110101, 000000110000011 0100010010 0100010011010110111010001 01101100110010010000 100100011100100 0010100100011000001000100.", + recipeConfig: [ + { + op: "培根密码加密", + args: [alphabets[0], translations[0], true, false] + } + ], + }, + { + name: "Bacon Encode: reduced alphabet 0/1 inverse, keeping extra characters", + input: "There's a fox, and it jumps over the fence.", + expectedOutput: "0110111000110110111111011'01110 11111 110101001001010, 111111001111100 1011101101 1011101100101001000101110 10010011001101101111 011011100011011 1101011011100111110111011.", + recipeConfig: [ + { + op: "培根密码加密", + args: [alphabets[0], translations[0], true, true] + } + ], + }, + { + name: "Bacon Encode: reduced alphabet A/B", + input: "There's a fox, and it jumps over the fence.", + expectedOutput: "BAABA AABBB AABAA BAAAA AABAA BAAAB AAAAA AABAB ABBAB BABAB AAAAA ABBAA AAABB ABAAA BAABA ABAAA BAABB ABABB ABBBA BAAAB ABBAB BAABB AABAA BAAAA BAABA AABBB AABAA AABAB AABAA ABBAA AAABA AABAA", + recipeConfig: [ + { + op: "培根密码加密", + args: [alphabets[0], translations[1], false, false] + } + ], + }, + { + name: "Bacon Encode: reduced alphabet A/B inverse", + input: "There's a fox, and it jumps over the fence.", + expectedOutput: "ABBAB BBAAA BBABB ABBBB BBABB ABBBA BBBBB BBABA BAABA ABABA BBBBB BAABB BBBAA BABBB ABBAB BABBB ABBAA BABAA BAAAB ABBBA BAABA ABBAA BBABB ABBBB ABBAB BBAAA BBABB BBABA BBABB BAABB BBBAB BBABB", + recipeConfig: [ + { + op: "培根密码加密", + args: [alphabets[0], translations[1], false, true] + } + ], + }, + { + name: "Bacon Encode: reduced alphabet A/B, keeping extra characters", + input: "There's a fox, and it jumps over the fence.", + expectedOutput: "BAABAAABBBAABAABAAAAAABAA'BAAAB AAAAA AABABABBABBABAB, AAAAAABBAAAAABB ABAAABAABA ABAAABAABBABABBABBBABAAAB ABBABBAABBAABAABAAAA BAABAAABBBAABAA AABABAABAAABBAAAAABAAABAA.", + recipeConfig: [ + { + op: "培根密码加密", + args: [alphabets[0], translations[1], true, false] + } + ], + }, + { + name: "Bacon Encode: reduced alphabet A/B inverse, keeping extra characters", + input: "There's a fox, and it jumps over the fence.", + expectedOutput: "ABBABBBAAABBABBABBBBBBABB'ABBBA BBBBB BBABABAABAABABA, BBBBBBAABBBBBAA BABBBABBAB BABBBABBAABABAABAAABABBBA BAABAABBAABBABBABBBB ABBABBBAAABBABB BBABABBABBBAABBBBBABBBABB.", + recipeConfig: [ + { + op: "培根密码加密", + args: [alphabets[0], translations[1], true, true] + } + ], + }, + { + name: "Bacon Encode: complete alphabet 0/1", + input: "There's a fox, and it jumps over the fence.", + expectedOutput: "10011 00111 00100 10001 00100 10010 00000 00101 01110 10111 00000 01101 00011 01000 10011 01001 10100 01100 01111 10010 01110 10101 00100 10001 10011 00111 00100 00101 00100 01101 00010 00100", + recipeConfig: [ + { + op: "培根密码加密", + args: [alphabets[1], translations[0], false, false] + } + ], + }, + { + name: "Bacon Encode: complete alphabet 0/1 inverse", + input: "There's a fox, and it jumps over the fence.", + expectedOutput: "01100 11000 11011 01110 11011 01101 11111 11010 10001 01000 11111 10010 11100 10111 01100 10110 01011 10011 10000 01101 10001 01010 11011 01110 01100 11000 11011 11010 11011 10010 11101 11011", + recipeConfig: [ + { + op: "培根密码加密", + args: [alphabets[1], translations[0], false, true] + } + ], + }, + { + name: "Bacon Encode: complete alphabet 0/1, keeping extra characters", + input: "There's a fox, and it jumps over the fence.", + expectedOutput: "1001100111001001000100100'10010 00000 001010111010111, 000000110100011 0100010011 0100110100011000111110010 01110101010010010001 100110011100100 0010100100011010001000100.", + recipeConfig: [ + { + op: "培根密码加密", + args: [alphabets[1], translations[0], true, false] + } + ], + }, + { + name: "Bacon Encode: complete alphabet 0/1 inverse, keeping extra characters", + input: "There's a fox, and it jumps over the fence.", + expectedOutput: "0110011000110110111011011'01101 11111 110101000101000, 111111001011100 1011101100 1011001011100111000001101 10001010101101101110 011001100011011 1101011011100101110111011.", + recipeConfig: [ + { + op: "培根密码加密", + args: [alphabets[1], translations[0], true, true] + } + ], + }, + { + name: "Bacon Encode: complete alphabet A/B", + input: "There's a fox, and it jumps over the fence.", + expectedOutput: "BAABB AABBB AABAA BAAAB AABAA BAABA AAAAA AABAB ABBBA BABBB AAAAA ABBAB AAABB ABAAA BAABB ABAAB BABAA ABBAA ABBBB BAABA ABBBA BABAB AABAA BAAAB BAABB AABBB AABAA AABAB AABAA ABBAB AAABA AABAA", + recipeConfig: [ + { + op: "培根密码加密", + args: [alphabets[1], translations[1], false, false] + } + ], + }, + { + name: "Bacon Encode: complete alphabet A/B inverse", + input: "There's a fox, and it jumps over the fence.", + expectedOutput: "ABBAA BBAAA BBABB ABBBA BBABB ABBAB BBBBB BBABA BAAAB ABAAA BBBBB BAABA BBBAA BABBB ABBAA BABBA ABABB BAABB BAAAA ABBAB BAAAB ABABA BBABB ABBBA ABBAA BBAAA BBABB BBABA BBABB BAABA BBBAB BBABB", + recipeConfig: [ + { + op: "培根密码加密", + args: [alphabets[1], translations[1], false, true] + } + ], + }, + { + name: "Bacon Encode: complete alphabet A/B, keeping extra characters", + input: "There's a fox, and it jumps over the fence.", + expectedOutput: "BAABBAABBBAABAABAAABAABAA'BAABA AAAAA AABABABBBABABBB, AAAAAABBABAAABB ABAAABAABB ABAABBABAAABBAAABBBBBAABA ABBBABABABAABAABAAAB BAABBAABBBAABAA AABABAABAAABBABAAABAAABAA.", + recipeConfig: [ + { + op: "培根密码加密", + args: [alphabets[1], translations[1], true, false] + } + ], + }, + { + name: "Bacon Encode: complete alphabet A/B inverse, keeping extra characters", + input: "There's a fox, and it jumps over the fence.", + expectedOutput: "ABBAABBAAABBABBABBBABBABB'ABBAB BBBBB BBABABAAABABAAA, BBBBBBAABABBBAA BABBBABBAA BABBAABABBBAABBBAAAAABBAB BAAABABABABBABBABBBA ABBAABBAAABBABB BBABABBABBBAABABBBABBBABB.", + recipeConfig: [ + { + op: "培根密码加密", + args: [alphabets[1], translations[1], true, true] + } + ], + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/Base32.mjs b/plugins/srktoolbox/tests/operations/tests/Base32.mjs new file mode 100644 index 00000000..8804ee89 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/Base32.mjs @@ -0,0 +1,178 @@ +/** + * Base32 Tests + * + * @author Peter C-S [petercs@purelymail.com] + * @license Apache-2.0 + * + * Modified by Raka-loah@github + */ + +import TestRegister from "../../lib/TestRegister.mjs"; +import {ALPHABET_OPTIONS} from "../../../src/core/lib/Base32.mjs"; + +// Example Standard Base32 Tests +const STANDARD_INP = "HELLO BASE32"; +const STANDARD_OUT = "JBCUYTCPEBBECU2FGMZA===="; + +// Example Hex Extended Base32 Tests +const EXTENDED_INP = "HELLO BASE32 EXTENDED"; +const EXTENDED_OUT = "912KOJ2F41142KQ56CP20HAOAH2KSH258G======"; + +// All Bytes +const ALL_BYTES = [ + "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f", + "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f", + "\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f", + "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f", + "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f", + "\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f", + "\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f", + "\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f", + "\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f", + "\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf", + "\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf", + "\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf", + "\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf", + "\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef", + "\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff", +].join(""); + +const ALL_BYTES_EXTENDED_OUT = "000G40O40K30E209185GO38E1S8124GJ2GAHC5OO34D1M70T3OFI08924CI2A9H750KIKAPC5KN2UC1H68PJ8D9M6SS3IEHR7GUJSFQ085146H258P3KGIAA9D64QJIFA18L4KQKALB5EM2PB9DLONAUBTG62OJ3CHIMCPR8D5L6MR3DDPNN0SBIEDQ7ATJNF1SNKURSFLV7V041GA1O91C6GU48J2KBHI6OT3SGI699754LIQBPH6CQJEE9R7KVK2GQ58T4KMJAFA59LALQPBDELUOB3CLJMIQRDDTON6TBNF5TNQVS1GE2OF2CBHM7P34SLIUCPN7CVK6HQB9T9LEMQVCDJMMRRJETTNV0S7HE7P75SRJUHQFATFMERRNFU3OV5SVKUNRFFU7PVBTVPVFUVS======"; +const ALL_BYTES_STANDARD_OUT = "AAAQEAYEAUDAOCAJBIFQYDIOB4IBCEQTCQKRMFYYDENBWHA5DYPSAIJCEMSCKJRHFAUSUKZMFUXC6MBRGIZTINJWG44DSOR3HQ6T4P2AIFBEGRCFIZDUQSKKJNGE2TSPKBIVEU2UKVLFOWCZLJNVYXK6L5QGCYTDMRSWMZ3INFVGW3DNNZXXA4LSON2HK5TXPB4XU634PV7H7AEBQKBYJBMGQ6EITCULRSGY5D4QSGJJHFEVS2LZRGM2TOOJ3HU7UCQ2FI5EUWTKPKFJVKV2ZLNOV6YLDMVTWS23NN5YXG5LXPF5X274BQOCYPCMLRWHZDE4VS6MZXHM7UGR2LJ5JVOW27MNTWW33TO55X7A4HROHZHF43T6R2PK5PWO33XP6DY7F47U6X3PP6HZ7L57Z7P674======"; + +TestRegister.addTests([ + { + name: "To Base32 Standard: nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "Base32编码", + args: [ALPHABET_OPTIONS[0].value], + }, + ], + }, + { + name: "To Base32 Hex Extended: nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "Base32编码", + args: [ALPHABET_OPTIONS[1].value], + }, + ], + }, + { + name: "From Base32 Standard: nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "Base32解码", + args: [ALPHABET_OPTIONS[0].value, false], + }, + ], + }, + { + name: "From Base32 Hex Extended: nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "Base32解码", + args: [ALPHABET_OPTIONS[1].value, false], + }, + ], + }, + { + name: "To Base32 Standard: " + STANDARD_INP, + input: STANDARD_INP, + expectedOutput: STANDARD_OUT, + recipeConfig: [ + { + op: "Base32编码", + args: [ALPHABET_OPTIONS[0].value], + }, + ], + }, + { + name: "To Base32 Hex Extended: " + EXTENDED_INP, + input: EXTENDED_INP, + expectedOutput: EXTENDED_OUT, + recipeConfig: [ + { + op: "Base32编码", + args: [ALPHABET_OPTIONS[1].value], + }, + ], + }, + { + name: "From Base32 Standard: " + STANDARD_OUT, + input: STANDARD_OUT, + expectedOutput: STANDARD_INP, + recipeConfig: [ + { + op: "Base32解码", + args: [ALPHABET_OPTIONS[0].value, false], + }, + ], + }, + { + name: "From Base32 Hex Extended: " + EXTENDED_OUT, + input: EXTENDED_OUT, + expectedOutput: EXTENDED_INP, + recipeConfig: [ + { + op: "Base32解码", + args: [ALPHABET_OPTIONS[1].value, false], + }, + ], + }, + { + name: "To Base32 Hex Standard: All Bytes", + input: ALL_BYTES, + expectedOutput: ALL_BYTES_STANDARD_OUT, + recipeConfig: [ + { + op: "Base32编码", + args: [ALPHABET_OPTIONS[0].value], + }, + ], + }, + { + name: "To Base32 Hex Extended: All Bytes", + input: ALL_BYTES, + expectedOutput: ALL_BYTES_EXTENDED_OUT, + recipeConfig: [ + { + op: "Base32编码", + args: [ALPHABET_OPTIONS[1].value], + }, + ], + }, + { + name: "From Base32 Hex Standard: All Bytes", + input: ALL_BYTES_STANDARD_OUT, + expectedOutput: ALL_BYTES, + recipeConfig: [ + { + op: "Base32解码", + args: [ALPHABET_OPTIONS[0].value, false], + }, + ], + }, + { + name: "From Base32 Hex Extended: All Bytes", + input: ALL_BYTES_EXTENDED_OUT, + expectedOutput: ALL_BYTES, + recipeConfig: [ + { + op: "Base32解码", + args: [ALPHABET_OPTIONS[1].value, false], + }, + ], + }, +]); + diff --git a/plugins/srktoolbox/tests/operations/tests/Base45.mjs b/plugins/srktoolbox/tests/operations/tests/Base45.mjs new file mode 100644 index 00000000..26a660b3 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/Base45.mjs @@ -0,0 +1,105 @@ +/** + * Base45 tests. + * + * @author Thomas Weißschuh [thomas@t-8ch.de] + * + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import TestRegister from "../../lib/TestRegister.mjs"; + +const defaultB45Alph = "0-9A-Z $%*+\\-./:"; + +TestRegister.addTests([ + { + name: "To Base45: nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "Base45编码", + args: [defaultB45Alph], + }, + ], + }, + { + name: "To Base45: Spec encoding example 1", + input: "AB", + expectedOutput: "BB8", + recipeConfig: [ + { + op: "Base45编码", + args: [defaultB45Alph], + }, + ], + }, + { + name: "To Base45: Spec encoding example 2", + input: "Hello!!", + expectedOutput: "%69 VD92EX0", + recipeConfig: [ + { + op: "Base45编码", + args: [defaultB45Alph], + }, + ], + }, + { + name: "To Base45: Spec encoding example 3", + input: "base-45", + expectedOutput: "UJCLQE7W581", + recipeConfig: [ + { + op: "Base45编码", + args: [defaultB45Alph], + }, + ], + }, + { + name: "From Base45: nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "Base45解码", + args: [defaultB45Alph], + }, + ], + }, + { + name: "From Base45: Spec decoding example 1", + input: "QED8WEX0", + expectedOutput: "ietf!", + recipeConfig: [ + { + op: "Base45解码", + args: [defaultB45Alph], + }, + ], + }, + { + name: "From Base45: Invalid character", + input: "!", + expectedOutput: "非可用字符: '!'", + recipeConfig: [ + { + op: "Base45解码", + args: [defaultB45Alph], + }, + ], + }, + { + name: "From Base45: Invalid triplet value", + input: "ZZZ", + expectedOutput: "超出编码范围: 'ZZZ'", + recipeConfig: [ + { + op: "Base45解码", + args: [defaultB45Alph], + }, + ], + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/Base58.mjs b/plugins/srktoolbox/tests/operations/tests/Base58.mjs new file mode 100644 index 00000000..814356fe --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/Base58.mjs @@ -0,0 +1,124 @@ +/** + * Base58 tests. + * + * @author tlwr [toby@toby.codes] + * + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "To Base58 (Bitcoin): nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "Base58编码", + args: ["123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"], + }, + ], + }, + { + name: "To Base58 (Ripple): nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "Base58编码", + args: ["rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz"], + }, + ], + }, + { + name: "To Base58 (Bitcoin): 'hello world'", + input: "hello world", + expectedOutput: "StV1DL6CwTryKyV", + recipeConfig: [ + { + op: "Base58编码", + args: ["123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"], + }, + ], + }, + { + name: "To Base58 (Ripple): 'hello world'", + input: "hello world", + expectedOutput: "StVrDLaUATiyKyV", + recipeConfig: [ + { + op: "Base58编码", + args: ["rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz"], + }, + ], + }, + { + name: "To Base58 all null", + input: "\0\0\0\0\0\0", + expectedOutput: "111111", + recipeConfig: [ + { + op: "Base58编码", + args: ["123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"], + }, + ], + }, + { + name: "From Base58 all null", + input: "111111", + expectedOutput: "\0\0\0\0\0\0", + recipeConfig: [ + { + op: "Base58解码", + args: ["123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"], + }, + ], + }, + { + name: "To Base58 with null prefix and suffix", + input: "\0\0\0Hello\0\0\0", + expectedOutput: "111D7LMXYjHjTu", + recipeConfig: [ + { + op: "Base58编码", + args: ["123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"], + }, + ], + }, + { + name: "From Base58 with null prefix and suffix", + input: "111D7LMXYjHjTu", + expectedOutput: "\0\0\0Hello\0\0\0", + recipeConfig: [ + { + op: "Base58解码", + args: ["123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"], + }, + ], + }, + { + name: "From Base58 (Bitcoin): 'StV1DL6CwTryKyV'", + input: "StV1DL6CwTryKyV", + expectedOutput: "hello world", + recipeConfig: [ + { + op: "Base58解码", + args: ["123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"], + }, + ], + }, + { + name: "From Base58 (Ripple): 'StVrDLaUATiyKyV'", + input: "StVrDLaUATiyKyV", + expectedOutput: "hello world", + recipeConfig: [ + { + op: "Base58解码", + args: ["rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz"], + }, + ], + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/Base62.mjs b/plugins/srktoolbox/tests/operations/tests/Base62.mjs new file mode 100644 index 00000000..895c9a2e --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/Base62.mjs @@ -0,0 +1,81 @@ +/** + * Base62 tests. + * + * @author tcode2k16 [tcode2k16@gmail.com] + * + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "To Base62: nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "Base62编码", + args: ["0-9A-Za-z"], + }, + ], + }, + { + name: "To Base62: Hello, World!", + input: "Hello, World!", + expectedOutput: "1wJfrzvdbtXUOlUjUf", + recipeConfig: [ + { + op: "Base62编码", + args: ["0-9A-Za-z"], + }, + ], + }, + { + name: "To Base62: UTF-8", + input: "ნუ პანიკას", + expectedOutput: "BPDNbjoGvDCDzHbKT77eWg0vGQrJuWRXltuRVZ", + recipeConfig: [ + { + op: "Base62编码", + args: ["0-9A-Za-z"], + }, + ], + }, + { + name: "From Base62: nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "Base62解码", + args: ["0-9A-Za-z"], + }, + ], + }, + { + name: "From Base62: Hello, World!", + input: "1wJfrzvdbtXUOlUjUf", + expectedOutput: "Hello, World!", + recipeConfig: [ + { + op: "Base62解码", + args: ["0-9A-Za-z"], + }, + ], + }, + { + name: "From Base62: UTF-8", + input: "BPDNbjoGvDCDzHbKT77eWg0vGQrJuWRXltuRVZ", + expectedOutput: "ნუ პანიკას", + recipeConfig: [ + { + op: "Base62解码", + args: ["0-9A-Za-z"], + }, + ], + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/Base64.mjs b/plugins/srktoolbox/tests/operations/tests/Base64.mjs new file mode 100644 index 00000000..10705a2f --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/Base64.mjs @@ -0,0 +1,121 @@ +/** + * Base64 tests. + * + * @author n1474335 [n1474335@gmail.com] + * + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +const ALL_BYTES = [ + "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f", + "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f", + "\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f", + "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f", + "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f", + "\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f", + "\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f", + "\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f", + "\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f", + "\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf", + "\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf", + "\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf", + "\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf", + "\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef", + "\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff", +].join(""); + +TestRegister.addTests([ + { + name: "To Base64: nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "Base64编码", + args: ["A-Za-z0-9+/="], + }, + ], + }, + { + name: "To Base64: Hello, World!", + input: "Hello, World!", + expectedOutput: "SGVsbG8sIFdvcmxkIQ==", + recipeConfig: [ + { + op: "Base64编码", + args: ["A-Za-z0-9+/="], + }, + ], + }, + { + name: "To Base64: UTF-8", + input: "ნუ პანიკას", + expectedOutput: "4YOc4YOjIOGDnuGDkOGDnOGDmOGDmeGDkOGDoQ==", + recipeConfig: [ + { + op: "Base64编码", + args: ["A-Za-z0-9+/="], + }, + ], + }, + { + name: "To Base64: All bytes", + input: ALL_BYTES, + expectedOutput: "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==", + recipeConfig: [ + { + op: "Base64编码", + args: ["A-Za-z0-9+/="], + }, + ], + }, + { + name: "From Base64: nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "Base64解码", + args: ["A-Za-z0-9+/=", true], + }, + ], + }, + { + name: "From Base64: Hello, World!", + input: "SGVsbG8sIFdvcmxkIQ==", + expectedOutput: "Hello, World!", + recipeConfig: [ + { + op: "Base64解码", + args: ["A-Za-z0-9+/=", true], + }, + ], + }, + { + name: "From Base64: UTF-8", + input: "4YOc4YOjIOGDnuGDkOGDnOGDmOGDmeGDkOGDoQ==", + expectedOutput: "ნუ პანიკას", + recipeConfig: [ + { + op: "Base64解码", + args: ["A-Za-z0-9+/=", true], + }, + ], + }, + { + name: "From Base64: All bytes", + input: "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==", + expectedOutput: ALL_BYTES, + recipeConfig: [ + { + op: "Base64解码", + args: ["A-Za-z0-9+/=", true], + }, + ], + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/Base85.mjs b/plugins/srktoolbox/tests/operations/tests/Base85.mjs new file mode 100644 index 00000000..d755cb93 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/Base85.mjs @@ -0,0 +1,72 @@ +/** + * Base85 tests + * + * @author john19696 + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +// Example from Wikipedia +const wpExample = "Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure."; +// Escape newline, quote & backslash +const wpOutput = "9jqo^BlbD-BleB1DJ+*+F(f,q/0JhKFCj@.4Gp$d7F!,L7@<6@)/0JDEF@3BB/F*&OCAfu2/AKYi(\ +DIb:@FD,*)+C]U=@3BN#EcYf8ATD3s@q?d$AftVqCh[NqF-FD5W8ARlolDIal(\ +DIdu\ +D.RTpAKYo'+CT/5+Cei#DII?(E,9)oF*2M7/c"; + +const allZeroExample = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"; + +const allZeroOutput = "zz!!*-'\"9eu7#RLhG$k3[W&.oNg'GVB\"(`=52*$$(B+<_pR,UFcb-n-Vr/1iJ-0JP==1c70M3&s#]4?Ykm5X@_(6q'R884cEH9MJ8X:f1+h<)lt#=BSg3>[:ZC?t!MSA7]@cBPD3sCi+'.E,fo>FEMbNG^4U^I!pHnJ:W<)KS>/9Ll%\"IN/`jYOHG]iPa.Q$R$jD4S=Q7DTV8*TUnsrdW2ZetXKAY/Yd(L?['d?O\\@K2_]Y2%o^qmn*`5Ta:aN;TJbg\"GZd*^:jeCE.%f\\,!5gtgiEi8N\\UjQ5OekiqBum-X60nF?)@o_%qPq\"ad`r;HWp"; + +TestRegister.addTests([ + { + name: "To Base85", + input: wpExample, + expectedOutput: wpOutput, + recipeConfig: [ + { "op": "Base85编码", + "args": ["!-u"] } + ] + }, + { + name: "From Base85", + input: wpOutput + "\n", + expectedOutput: wpExample, + recipeConfig: [ + { "op": "Base85解码", + "args": ["!-u", true] } + ] + }, + { + name: "From Base85", + input: wpOutput + "v", + expectedError: true, + expectedOutput: "Base85解码 - 无效的字符 'v' ,位置: 337", + recipeConfig: [ + { "op": "Base85解码", + "args": ["!-u", false] } + ] + }, + { + name: "To Base85", + input: allZeroExample, + expectedOutput: allZeroOutput, + recipeConfig: [ + { "op": "Base85编码", + "args": ["!-u"] } + ] + }, + { + name: "From Base85", + input: allZeroOutput, + expectedOutput: allZeroExample, + recipeConfig: [ + { "op": "Base85解码", + "args": ["!-u", true, "z"] } + ] + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/Base92.mjs b/plugins/srktoolbox/tests/operations/tests/Base92.mjs new file mode 100644 index 00000000..9410ca59 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/Base92.mjs @@ -0,0 +1,91 @@ +/** + * Base92 tests. + * + * @author sg5506844 [sg5506844@gmail.com] + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "To Base92: nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "Base92编码", + args: [], + }, + ], + }, + { + name: "To Base92: Spec encoding example 1", + input: "AB", + expectedOutput: "8y2", + recipeConfig: [ + { + op: "Base92编码", + args: [], + }, + ], + }, + { + name: "To Base92: Spec encoding example 2", + input: "Hello!!", + expectedOutput: ";K_$aOTo&", + recipeConfig: [ + { + op: "Base92编码", + args: [], + }, + ], + }, + { + name: "To Base92: Spec encoding example 3", + input: "base-92", + expectedOutput: "DX2?VLGA<\/td> {2}SS<\/td> {2}VFISUSGTKSTMPSUNAK<\/td>/, + recipeConfig: [ + { + "op": "Bombe", + "args": [ + "3-rotor", + "", + "EKMFLGDQVZNTOWYHXUSPAIBRCJLGA<\/td> {2}AG<\/td> {2}QFIMUMAFKMQSKMYNGW<\/td>/, + recipeConfig: [ + { + "op": "Bombe", + "args": [ + "3-rotor", + "", + "EKMFLGDQVZNTOWYHXUSPAIBRCJLGA<\/td> {2}SS<\/td> {2}VFISUSGTKSTMPSUNAK<\/td>/, + recipeConfig: [ + { + "op": "Bombe", + "args": [ + "3-rotor", + "", + "EKMFLGDQVZNTOWYHXUSPAIBRCJLGA<\/td> {2}TT<\/td> {2}VFISUSGTKSTMPSUNAK<\/td>/, + recipeConfig: [ + { + "op": "Bombe", + "args": [ + "3-rotor", + "", + "EKMFLGDQVZNTOWYHXUSPAIBRCJLGA<\/td> {2}TT AG BO CL EK FF HH II JJ SS YY<\/td> {2}THISISATESTMESSAGE<\/td>/, + recipeConfig: [ + { + "op": "Bombe", + "args": [ + "3-rotor", + "", + "EKMFLGDQVZNTOWYHXUSPAIBRCJLHSC<\/td> {2}SS<\/td> {2}HHHSSSGQUUQPKSEKWK<\/td>/, + // recipeConfig: [ + // { + // "op": "Bombe", + // "args": [ + // "4-rotor", + // "LEYJVCNIXWPBQMDRTAKZGFUHOS", // Beta + // "EKMFLGDQVZNTOWYHXUSPAIBRCJ?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~^?M-^@M-^AM-^BM-^CM-^DM-^EM-^FM-^GM-^HM-^IM-^JM-^KM-^LM-^MM-^NM-^OM-^PM-^QM-^RM-^SM-^TM-^UM-^VM-^WM-^XM-^YM-^ZM-^[M-^\\M-^]M-^^M-^_M- M-!M-\"M-#M-$M-%M-&M-'M-(M-)M-*M-+M-,M--M-.M-/M-0M-1M-2M-3M-4M-5M-6M-7M-8M-9M-:M-;M-M-?M-@M-AM-BM-CM-DM-EM-FM-GM-HM-IM-JM-KM-LM-MM-NM-OM-PM-QM-RM-SM-TM-UM-VM-WM-XM-YM-ZM-[M-\\M-]M-^M-_M-`M-aM-bM-cM-dM-eM-fM-gM-hM-iM-jM-kM-lM-mM-nM-oM-pM-qM-rM-sM-tM-uM-vM-wM-xM-yM-zM-{M-|M-}M-~M-^?", + expectedOutput: "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x1f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\x8d\x2d\x5f\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff", + recipeConfig: [ + { + op: "Caret/M-解码", + args: [], + }, + ], + }, +]); + diff --git a/plugins/srktoolbox/tests/operations/tests/CartesianProduct.mjs b/plugins/srktoolbox/tests/operations/tests/CartesianProduct.mjs new file mode 100644 index 00000000..e46ae0a6 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/CartesianProduct.mjs @@ -0,0 +1,69 @@ +/** + * Cartesian Product tests. + * + * @author d98762625 + * + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Cartesian Product", + input: "1 2 3 4 5\n\na b c d e", + expectedOutput: "(1,a) (1,b) (1,c) (1,d) (1,e) (2,a) (2,b) (2,c) (2,d) (2,e) (3,a) (3,b) (3,c) (3,d) (3,e) (4,a) (4,b) (4,c) (4,d) (4,e) (5,a) (5,b) (5,c) (5,d) (5,e)", + recipeConfig: [ + { + op: "笛卡儿积", + args: ["\n\n", " "], + }, + ], + }, + { + name: "Cartesian Product: too many on left", + input: "1 2 3 4 5 6\n\na b c d e", + expectedOutput: "(1,a) (1,b) (1,c) (1,d) (1,e) (2,a) (2,b) (2,c) (2,d) (2,e) (3,a) (3,b) (3,c) (3,d) (3,e) (4,a) (4,b) (4,c) (4,d) (4,e) (5,a) (5,b) (5,c) (5,d) (5,e) (6,a) (6,b) (6,c) (6,d) (6,e)", + recipeConfig: [ + { + op: "笛卡儿积", + args: ["\n\n", " "], + }, + ], + }, + { + name: "Cartesian Product: too many on right", + input: "1 2 3 4 5\n\na b c d e f", + expectedOutput: "(1,a) (1,b) (1,c) (1,d) (1,e) (1,f) (2,a) (2,b) (2,c) (2,d) (2,e) (2,f) (3,a) (3,b) (3,c) (3,d) (3,e) (3,f) (4,a) (4,b) (4,c) (4,d) (4,e) (4,f) (5,a) (5,b) (5,c) (5,d) (5,e) (5,f)", + recipeConfig: [ + { + op: "笛卡儿积", + args: ["\n\n", " "], + }, + ], + }, + { + name: "Cartesian Product: item delimiter", + input: "1-2-3-4-5\n\na-b-c-d-e", + expectedOutput: "(1,a)-(1,b)-(1,c)-(1,d)-(1,e)-(2,a)-(2,b)-(2,c)-(2,d)-(2,e)-(3,a)-(3,b)-(3,c)-(3,d)-(3,e)-(4,a)-(4,b)-(4,c)-(4,d)-(4,e)-(5,a)-(5,b)-(5,c)-(5,d)-(5,e)", + recipeConfig: [ + { + op: "笛卡儿积", + args: ["\n\n", "-"], + }, + ], + }, + { + name: "Cartesian Product: sample delimiter", + input: "1 2 3 4 5_a b c d e", + expectedOutput: "(1,a) (1,b) (1,c) (1,d) (1,e) (2,a) (2,b) (2,c) (2,d) (2,e) (3,a) (3,b) (3,c) (3,d) (3,e) (4,a) (4,b) (4,c) (4,d) (4,e) (5,a) (5,b) (5,c) (5,d) (5,e)", + recipeConfig: [ + { + op: "笛卡儿积", + args: ["_", " "], + }, + ], + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/CetaceanCipherDecode.mjs b/plugins/srktoolbox/tests/operations/tests/CetaceanCipherDecode.mjs new file mode 100644 index 00000000..4813c772 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/CetaceanCipherDecode.mjs @@ -0,0 +1,24 @@ +/** + * CetaceanCipher Encode tests + * + * @author dolphinOnKeys + * @copyright Crown Copyright 2022 + * @licence Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Cetacean Cipher Decode", + input: "EEEEEEEEEeeEEEEe EEEEEEEEEeeEEEeE EEEEEEEEEeeEEEee EEeeEEEEEeeEEeee", + expectedOutput: "a b c で", + recipeConfig: [ + { + op: "鲸豚密码解密", + args: [] + }, + ], + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/CetaceanCipherEncode.mjs b/plugins/srktoolbox/tests/operations/tests/CetaceanCipherEncode.mjs new file mode 100644 index 00000000..4a21b27c --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/CetaceanCipherEncode.mjs @@ -0,0 +1,24 @@ +/** + * CetaceanCipher Encode tests + * + * @author dolphinOnKeys + * @copyright Crown Copyright 2022 + * @licence Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Cetacean Cipher Encode", + input: "a b c で", + expectedOutput: "EEEEEEEEEeeEEEEe EEEEEEEEEeeEEEeE EEEEEEEEEeeEEEee EEeeEEEEEeeEEeee", + recipeConfig: [ + { + op: "鲸豚密码加密", + args: [] + }, + ], + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/ChaCha.mjs b/plugins/srktoolbox/tests/operations/tests/ChaCha.mjs new file mode 100644 index 00000000..5b626e7f --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/ChaCha.mjs @@ -0,0 +1,172 @@ +/** + * ChaCha tests. + * + * @author joostrijneveld [joost@joostrijneveld.nl] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "ChaCha: no key", + input: "", + expectedOutput: `无效的key长度: 0 字节。 + +ChaCha使用16或32字节的key(128或256位)。`, + recipeConfig: [ + { + "op": "ChaCha", + "args": [ + {"option": "十六进制", "string": ""}, + {"option": "十六进制", "string": ""}, + 0, "20", "十六进制", "十六进制", + ] + } + ], + }, + { + name: "ChaCha: no nonce", + input: "", + expectedOutput: `无效的nonce长度: 0 字节。 + +ChaCha使用8或12字节长度的nonce(64或96位)。`, + recipeConfig: [ + { + "op": "ChaCha", + "args": [ + {"option": "十六进制", "string": "00000000000000000000000000000000"}, + {"option": "十六进制", "string": ""}, + 0, "20", "十六进制", "十六进制", + ] + } + ], + }, + { + name: "ChaCha: RFC8439", + input: "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it.", + expectedOutput: "6e 2e 35 9a 25 68 f9 80 41 ba 07 28 dd 0d 69 81 e9 7e 7a ec 1d 43 60 c2 0a 27 af cc fd 9f ae 0b f9 1b 65 c5 52 47 33 ab 8f 59 3d ab cd 62 b3 57 16 39 d6 24 e6 51 52 ab 8f 53 0c 35 9f 08 61 d8 07 ca 0d bf 50 0d 6a 61 56 a3 8e 08 8a 22 b6 5e 52 bc 51 4d 16 cc f8 06 81 8c e9 1a b7 79 37 36 5a f9 0b bf 74 a3 5b e6 b4 0b 8e ed f2 78 5e 42 87 4d", + recipeConfig: [ + { + "op": "ChaCha", + "args": [ + {"option": "十六进制", "string": "00:01:02:03:04:05:06:07:08:09:0a:0b:0c:0d:0e:0f:10:11:12:13:14:15:16:17:18:19:1a:1b:1c:1d:1e:1f"}, + {"option": "十六进制", "string": "00:00:00:00:00:00:00:4a:00:00:00:00"}, + 1, "20", "原始", "十六进制", + ] + } + ], + }, + { + name: "ChaCha: RFC8439 Raw output", + input: "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it.", + expectedOutput: "6e 2e 35 9a 25 68 f9 80 41 ba 07 28 dd 0d 69 81 e9 7e 7a ec 1d 43 60 c2 0a 27 af cc fd 9f ae 0b f9 1b 65 c5 52 47 33 ab 8f 59 3d ab cd 62 b3 57 16 39 d6 24 e6 51 52 ab 8f 53 0c 35 9f 08 61 d8 07 ca 0d bf 50 0d 6a 61 56 a3 8e 08 8a 22 b6 5e 52 bc 51 4d 16 cc f8 06 81 8c e9 1a b7 79 37 36 5a f9 0b bf 74 a3 5b e6 b4 0b 8e ed f2 78 5e 42 87 4d", + recipeConfig: [ + { + "op": "ChaCha", + "args": [ + {"option": "十六进制", "string": "00:01:02:03:04:05:06:07:08:09:0a:0b:0c:0d:0e:0f:10:11:12:13:14:15:16:17:18:19:1a:1b:1c:1d:1e:1f"}, + {"option": "十六进制", "string": "00:00:00:00:00:00:00:4a:00:00:00:00"}, + 1, "20", "原始", "原始", + ] + }, + { + "op": "字符转十六进制", + "args": [] + }, + ], + }, + { + name: "ChaCha: draft-strombergson-chacha-test-vectors-01 TC7.1", + input: "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", + expectedOutput: "29 56 0d 28 0b 45 28 40 0a 8f 4b 79 53 69 fb 3a 01 10 55 99 e9 f1 ed 58 27 9c fc 9e ce 2d c5 f9 9f 1c 2e 52 c9 82 38 f5 42 a5 c0 a8 81 d8 50 b6 15 d3 ac d9 fb db 02 6e 93 68 56 5d a5 0e 0d 49 dd 5b e8 ef 74 24 8b 3e 25 1d 96 5d 8f cb 21 e7 cf e2 04 d4 00 78 06 fb ee 3c e9 4c 74 bf ba d2 c1 1c 62 1b a0 48 14 7c 5c aa 94 d1 82 cc ff 6f d5 cf 44 ad f9 6e 3d 68 28 1b b4 96 76 af 87 e7", + recipeConfig: [ + { + "op": "ChaCha", + "args": [ + {"option": "十六进制", "string": "00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff"}, + {"option": "十六进制", "string": "0f 1e 2d 3c 4b 5a 69 78"}, + 0, "8", "十六进制", "十六进制", + ] + } + ], + }, + { + name: "ChaCha: draft-strombergson-chacha-test-vectors-01 TC7.2", + input: "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", + expectedOutput: "5e dd c2 d9 42 8f ce ee c5 0a 52 a9 64 ea e0 ff b0 4b 2d e0 06 a9 b0 4c ff 36 8f fa 92 11 16 b2 e8 e2 64 ba bd 2e fa 0d e4 3e f2 e3 b6 d0 65 e8 f7 c0 a1 78 37 b0 a4 0e b0 e2 c7 a3 74 2c 87 53 ed e5 f3 f6 d1 9b e5 54 67 5e 50 6a 77 5c 63 f0 94 d4 96 5c 31 93 19 dc d7 50 6f 45 7b 11 7b 84 b1 0b 24 6e 95 6c 2d a8 89 8a 65 6c ee f3 f7 b7 16 45 b1 9f 70 1d b8 44 85 ce 51 21 f0 f6 17 ef", + recipeConfig: [ + { + "op": "ChaCha", + "args": [ + {"option": "十六进制", "string": "00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff"}, + {"option": "十六进制", "string": "0f 1e 2d 3c 4b 5a 69 78"}, + 0, "12", "十六进制", "十六进制", + ] + } + ], + }, + { + name: "ChaCha: draft-strombergson-chacha-test-vectors-01 TC7.3", + input: "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", + expectedOutput: "d1 ab f6 30 46 7e b4 f6 7f 1c fb 47 cd 62 6a ae 8a fe db be 4f f8 fc 5f e9 cf ae 30 7e 74 ed 45 1f 14 04 42 5a d2 b5 45 69 d5 f1 81 48 93 99 71 ab b8 fa fc 88 ce 4a c7 fe 1c 3d 1f 7a 1e b7 ca e7 6c a8 7b 61 a9 71 35 41 49 77 60 dd 9a e0 59 35 0c ad 0d ce df aa 80 a8 83 11 9a 1a 6f 98 7f d1 ce 91 fd 8e e0 82 80 34 b4 11 20 0a 97 45 a2 85 55 44 75 d1 2a fc 04 88 7f ef 35 16 d1 2a 2c", + recipeConfig: [ + { + "op": "ChaCha", + "args": [ + {"option": "十六进制", "string": "00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff"}, + {"option": "十六进制", "string": "0f 1e 2d 3c 4b 5a 69 78"}, + 0, "20", "十六进制", "十六进制", + ] + } + ], + }, + { + name: "ChaCha: draft-strombergson-chacha-test-vectors-01 TC7.4", + input: "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", + expectedOutput: "db 43 ad 9d 1e 84 2d 12 72 e4 53 0e 27 6b 3f 56 8f 88 59 b3 f7 cf 6d 9d 2c 74 fa 53 80 8c b5 15 7a 8e bf 46 ad 3d cc 4b 6c 7d ad de 13 17 84 b0 12 0e 0e 22 f6 d5 f9 ff a7 40 7d 4a 21 b6 95 d9 c5 dd 30 bf 55 61 2f ab 9b dd 11 89 20 c1 98 16 47 0c 7f 5d cd 42 32 5d bb ed 8c 57 a5 62 81 c1 44 cb 0f 03 e8 1b 30 04 62 4e 06 50 a1 ce 5a fa f9 a7 cd 81 63 f6 db d7 26 02 25 7d d9 6e 47 1e", + recipeConfig: [ + { + "op": "ChaCha", + "args": [ + {"option": "十六进制", "string": "00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff ff ee dd cc bb aa 99 88 77 66 55 44 33 22 11 00"}, + {"option": "十六进制", "string": "0f 1e 2d 3c 4b 5a 69 78"}, + 0, "8", "十六进制", "十六进制", + ] + } + ], + }, + { + name: "ChaCha: draft-strombergson-chacha-test-vectors-01 TC7.5", + input: "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", + expectedOutput: "7e d1 2a 3a 63 91 2a e9 41 ba 6d 4c 0d 5e 86 2e 56 8b 0e 55 89 34 69 35 50 5f 06 4b 8c 26 98 db f7 d8 50 66 7d 8e 67 be 63 9f 3b 4f 6a 16 f9 2e 65 ea 80 f6 c7 42 94 45 da 1f c2 c1 b9 36 50 40 e3 2e 50 c4 10 6f 3b 3d a1 ce 7c cb 1e 71 40 b1 53 49 3c 0f 3a d9 a9 bc ff 07 7e c4 59 6f 1d 0f 29 bf 9c ba a5 02 82 0f 73 2a f5 a9 3c 49 ee e3 3d 1c 4f 12 af 3b 42 97 af 91 fe 41 ea 9e 94 a2", + recipeConfig: [ + { + "op": "ChaCha", + "args": [ + {"option": "十六进制", "string": "00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff ff ee dd cc bb aa 99 88 77 66 55 44 33 22 11 00"}, + {"option": "十六进制", "string": "0f 1e 2d 3c 4b 5a 69 78"}, + 0, "12", "十六进制", "十六进制", + ] + } + ], + }, + { + name: "ChaCha: draft-strombergson-chacha-test-vectors-01 TC7.6", + input: "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", + expectedOutput: "9f ad f4 09 c0 08 11 d0 04 31 d6 7e fb d8 8f ba 59 21 8d 5d 67 08 b1 d6 85 86 3f ab bb 0e 96 1e ea 48 0f d6 fb 53 2b fd 49 4b 21 51 01 50 57 42 3a b6 0a 63 fe 4f 55 f7 a2 12 e2 16 7c ca b9 31 fb fd 29 cf 7b c1 d2 79 ed df 25 dd 31 6b b8 84 3d 6e de e0 bd 1e f1 21 d1 2f a1 7c bc 2c 57 4c cc ab 5e 27 51 67 b0 8b d6 86 f8 a0 9d f8 7e c3 ff b3 53 61 b9 4e bf a1 3f ec 0e 48 89 d1 8d a5", + recipeConfig: [ + { + "op": "ChaCha", + "args": [ + {"option": "十六进制", "string": "00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff ff ee dd cc bb aa 99 88 77 66 55 44 33 22 11 00"}, + {"option": "十六进制", "string": "0f 1e 2d 3c 4b 5a 69 78"}, + 0, "20", "十六进制", "十六进制", + ] + } + ], + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/ChangeIPFormat.mjs b/plugins/srktoolbox/tests/operations/tests/ChangeIPFormat.mjs new file mode 100644 index 00000000..bd4eec50 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/ChangeIPFormat.mjs @@ -0,0 +1,54 @@ +/** + * Change IP format tests. + * + * @author Chris Smith + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Change IP format: Dotted Decimal to Hex", + input: "192.168.1.1", + expectedOutput: "c0a80101", + recipeConfig: [ + { + op: "IP地址格式转换", + args: ["十进制用点分隔", "十六进制"], + }, + ], + }, { + name: "Change IP format: Decimal to Dotted Decimal", + input: "3232235777", + expectedOutput: "192.168.1.1", + recipeConfig: [ + { + op: "IP地址格式转换", + args: ["十进制", "十进制用点分隔"], + }, + ], + }, { + name: "Change IP format: Hex to Octal", + input: "c0a80101", + expectedOutput: "030052000401", + recipeConfig: [ + { + op: "IP地址格式转换", + args: ["十六进制", "八进制"], + }, + ], + }, { + name: "Change IP format: Octal to Decimal", + input: "030052000401", + expectedOutput: "3232235777", + recipeConfig: [ + { + op: "IP地址格式转换", + args: ["八进制", "十进制"], + }, + ], + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/CharEnc.mjs b/plugins/srktoolbox/tests/operations/tests/CharEnc.mjs new file mode 100644 index 00000000..5d866eb2 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/CharEnc.mjs @@ -0,0 +1,92 @@ +/** + * CharEnc tests. + * + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Encode text, Decode text: nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + "op": "文本编码", + "args": ["UTF-8 (65001)"] + }, + { + "op": "文本解码", + "args": ["UTF-8 (65001)"] + }, + ], + }, + { + name: "Encode text, Decode text: hello", + input: "hello", + expectedOutput: "hello", + recipeConfig: [ + { + "op": "文本编码", + "args": ["UTF-8 (65001)"] + }, + { + "op": "文本解码", + "args": ["UTF-8 (65001)"] + }, + ], + }, + { + name: "Encode text (EBCDIC): hello", + input: "hello", + expectedOutput: "88 85 93 93 96", + recipeConfig: [ + { + "op": "文本编码", + "args": ["IBM EBCDIC International (500)"] + }, + { + "op": "字符转十六进制", + "args": ["Space"] + }, + ], + }, + { + name: "Decode text (EBCDIC): 88 85 93 93 96", + input: "88 85 93 93 96", + expectedOutput: "hello", + recipeConfig: [ + { + "op": "十六进制转字符", + "args": ["Space"] + }, + { + "op": "文本解码", + "args": ["IBM EBCDIC International (500)"] + }, + ], + }, + { + name: "Generate Base64 Windows PowerShell", + input: "ZABpAHIAIAAiAGMAOgBcAHAAcgBvAGcAcgBhAG0AIABmAGkAbABlAHMAIgAgAA==", + expectedOutput: "dir \"c:\\program files\" ", + recipeConfig: [ + { + "op": "Base64解码", + "args": ["A-Za-z0-9+/=", true] + }, + { + "op": "文本解码", + "args": ["UTF-16LE (1200)"] + }, + { + "op": "文本编码", + "args": ["UTF-8 (65001)"] + }, + ], + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/Charts.mjs b/plugins/srktoolbox/tests/operations/tests/Charts.mjs new file mode 100644 index 00000000..0cb39287 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/Charts.mjs @@ -0,0 +1,57 @@ +/** + * Chart tests. + * + * @author Matt C [me@mitt.dev] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Scatter chart", + input: "100 100\n200 200\n300 300\n400 400\n500 500", + expectedMatch: /^',type: 'html'}});\")();)]", + "\n" + ] + } + ], + expectedMatch: /^无效的JPath表达式: Unexpected "{" at character 1/ + }, + { + name: "JPath Expression: Script-based RCE", + input: "[{}]", + recipeConfig: [ + { + "op": "JPath表达式", + "args": [ + "$..[?(p=\"console.log(this.process.mainModule.require('child_process').execSync('id').toString())\";a=''[['constructor']][['constructor']](p);a())]", + "\n" + ] + } + ], + expectedMatch: /^无效的JPath表达式: jsonPath: Cannot read properties of {2}\(reading 'constructor'\): / }, + { + name: "CSS选择器", + input: '
        \n

        hello

        \n

        world

        \n

        again

        \n
        ', + expectedOutput: '

        hello

        \n

        again

        ', + recipeConfig: [ + { + "op": "CSS选择器", + "args": ["#test p.a", "\\n"] + } + ] + }, + { + name: "XPath expression", + input: '
        \n

        hello

        \n

        world

        \n

        again

        \n
        ', + expectedOutput: '

        hello

        \n

        again

        ', + recipeConfig: [ + { + "op": "XPath表达式", + "args": ["/div/p[@class=\"a\"]", "\\n"] + } + ] + }, + { + name: "To MessagePack: no content", + input: "", + expectedMatch: /Unexpected end of JSON input/, + recipeConfig: [ + { + "op": "MessagePack编码", + "args": [] + } + ] + }, + { + name: "From MessagePack: no content", + input: "", + expectedOutput: "无法将 MessagePack 转换为 JSON: Error: Could not parse", + recipeConfig: [ + { + "op": "十六进制转字符", + "args": ["空格"] + }, + { + "op": "MessagePack解码", + "args": [] + } + ] + }, + { + name: "To MessagePack: valid json", + input: JSON.stringify(JSON_TEST_DATA), + expectedOutput: "81 a5 73 74 6f 72 65 83 a4 62 6f 6f 6b 94 84 a8 63 61 74 65 67 6f 72 79 a9 72 65 66 65 72 65 6e 63 65 a6 61 75 74 68 6f 72 aa 4e 69 67 65 6c 20 52 65 65 73 a5 74 69 74 6c 65 b6 53 61 79 69 6e 67 73 20 6f 66 20 74 68 65 20 43 65 6e 74 75 72 79 a5 70 72 69 63 65 cb 40 21 e6 66 66 66 66 66 84 a8 63 61 74 65 67 6f 72 79 a7 66 69 63 74 69 6f 6e a6 61 75 74 68 6f 72 ac 45 76 65 6c 79 6e 20 57 61 75 67 68 a5 74 69 74 6c 65 af 53 77 6f 72 64 20 6f 66 20 48 6f 6e 6f 75 72 a5 70 72 69 63 65 cb 40 29 fa e1 47 ae 14 7b 85 a8 63 61 74 65 67 6f 72 79 a7 66 69 63 74 69 6f 6e a6 61 75 74 68 6f 72 af 48 65 72 6d 61 6e 20 4d 65 6c 76 69 6c 6c 65 a5 74 69 74 6c 65 a9 4d 6f 62 79 20 44 69 63 6b a4 69 73 62 6e ad 30 2d 35 35 33 2d 32 31 33 31 31 2d 33 a5 70 72 69 63 65 cb 40 21 fa e1 47 ae 14 7b 85 a8 63 61 74 65 67 6f 72 79 a7 66 69 63 74 69 6f 6e a6 61 75 74 68 6f 72 b0 4a 2e 20 52 2e 20 52 2e 20 54 6f 6c 6b 69 65 6e a5 74 69 74 6c 65 b5 54 68 65 20 4c 6f 72 64 20 6f 66 20 74 68 65 20 52 69 6e 67 73 a4 69 73 62 6e ad 30 2d 33 39 35 2d 31 39 33 39 35 2d 38 a5 70 72 69 63 65 cb 40 36 fd 70 a3 d7 0a 3d a7 62 69 63 79 63 6c 65 82 a5 63 6f 6c 6f 72 a3 72 65 64 a5 70 72 69 63 65 cb 40 33 f3 33 33 33 33 33 a9 6e 65 77 73 70 61 70 65 72 92 83 a6 66 6f 72 6d 61 74 aa 62 72 6f 61 64 73 68 65 65 74 a5 74 69 74 6c 65 af 46 69 6e 61 6e 63 69 61 6c 20 54 69 6d 65 73 a5 70 72 69 63 65 cb 40 06 00 00 00 00 00 00 83 a6 66 6f 72 6d 61 74 a7 74 61 62 6c 6f 69 64 a5 74 69 74 6c 65 ac 54 68 65 20 47 75 61 72 64 69 61 6e a5 70 72 69 63 65 02", + recipeConfig: [ + { + "op": "MessagePack编码", + "args": [] + }, + { + "op": "字符转十六进制", + "args": ["空格"] + } + ] + }, + { + name: "From MessagePack: valid msgpack", + input: "81 a5 73 74 6f 72 65 83 a4 62 6f 6f 6b 94 84 a8 63 61 74 65 67 6f 72 79 a9 72 65 66 65 72 65 6e 63 65 a6 61 75 74 68 6f 72 aa 4e 69 67 65 6c 20 52 65 65 73 a5 74 69 74 6c 65 b6 53 61 79 69 6e 67 73 20 6f 66 20 74 68 65 20 43 65 6e 74 75 72 79 a5 70 72 69 63 65 cb 40 21 e6 66 66 66 66 66 84 a8 63 61 74 65 67 6f 72 79 a7 66 69 63 74 69 6f 6e a6 61 75 74 68 6f 72 ac 45 76 65 6c 79 6e 20 57 61 75 67 68 a5 74 69 74 6c 65 af 53 77 6f 72 64 20 6f 66 20 48 6f 6e 6f 75 72 a5 70 72 69 63 65 cb 40 29 fa e1 47 ae 14 7b 85 a8 63 61 74 65 67 6f 72 79 a7 66 69 63 74 69 6f 6e a6 61 75 74 68 6f 72 af 48 65 72 6d 61 6e 20 4d 65 6c 76 69 6c 6c 65 a5 74 69 74 6c 65 a9 4d 6f 62 79 20 44 69 63 6b a4 69 73 62 6e ad 30 2d 35 35 33 2d 32 31 33 31 31 2d 33 a5 70 72 69 63 65 cb 40 21 fa e1 47 ae 14 7b 85 a8 63 61 74 65 67 6f 72 79 a7 66 69 63 74 69 6f 6e a6 61 75 74 68 6f 72 b0 4a 2e 20 52 2e 20 52 2e 20 54 6f 6c 6b 69 65 6e a5 74 69 74 6c 65 b5 54 68 65 20 4c 6f 72 64 20 6f 66 20 74 68 65 20 52 69 6e 67 73 a4 69 73 62 6e ad 30 2d 33 39 35 2d 31 39 33 39 35 2d 38 a5 70 72 69 63 65 cb 40 36 fd 70 a3 d7 0a 3d a7 62 69 63 79 63 6c 65 82 a5 63 6f 6c 6f 72 a3 72 65 64 a5 70 72 69 63 65 cb 40 33 f3 33 33 33 33 33 a9 6e 65 77 73 70 61 70 65 72 92 83 a6 66 6f 72 6d 61 74 aa 62 72 6f 61 64 73 68 65 65 74 a5 74 69 74 6c 65 af 46 69 6e 61 6e 63 69 61 6c 20 54 69 6d 65 73 a5 70 72 69 63 65 cb 40 06 00 00 00 00 00 00 83 a6 66 6f 72 6d 61 74 a7 74 61 62 6c 6f 69 64 a5 74 69 74 6c 65 ac 54 68 65 20 47 75 61 72 64 69 61 6e a5 70 72 69 63 65 02", + expectedOutput: JSON.stringify(JSON_TEST_DATA, null, 4), + recipeConfig: [ + { + "op": "十六进制转字符", + "args": ["空格"] + }, + { + "op": "MessagePack解码", + "args": [] + } + ] + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/Colossus.mjs b/plugins/srktoolbox/tests/operations/tests/Colossus.mjs new file mode 100644 index 00000000..67464c9e --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/Colossus.mjs @@ -0,0 +1,92 @@ +/** + * Colossus tests. + * @author VirtualColossus [martin@virtualcolossus.co.uk] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Colossus Letter Count", + input: "CTBKJUVXHZ-H3L4QV+YEZUK+SXOZ/N", + expectedMatch: /00 00 : a30/, + recipeConfig: [ + { + "op": "Colossus", + "args": [ + "", + "KH Pattern", + "Z", "", "", + "None", "Select Program", "Letter Count", + "", + "", "", "", "", "", false, "", + "", "", "", "", "", false, "", + "", "", "", "", "", false, "", + false, + "", + false, false, false, false, false, + "", false, false, "", + "", + 0, "", "", + "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1" + ] + } + ] + }, + // Takes a while to run, so disabling for general purpose testing. Re-enable if modifying this operation. + // { + // name: "Colossus 1+2=.", + // input: "CTBKJUVXHZ-H3L4QV+YEZUK+SXOZ/NNLH+WBFSBMJM-E3LMTPYJ.ZJDYI3TZZFVRP+REWQYOQMU3FNW.T4WB3IEPCJ-A3LWVRSZZECNGQVUFHO.R/3VZRF.Y4XEDGXZJ3RS34ARLZDZTUHMG+HH//Y+E+C-NE+--GEATU+EPEVLQEMKCLCZP-HX3C3O+CRH4D-RLIVOLHJGNI./MN4JDB/PRYZV3QOD34DAGO4+/TCS+CU-ODXMRHPPCCCD-MWHJO/TRPAELREIFVVLABSDMQORF4THY.UQTITEPYXAZ3DPNI/I+.UPTCBT-BJ/PEQFZA/PQ4+Z4-IPLBG4COART/YC4CJL+ERNVTRSHOKNSSLGFJ+QJR/ITYHWFQCPXCY.Z.X/VKTAR.LBXQK4JI/P+YDEM/LPBLK.CX/..QHBSOAO-FHJORYVBHQEYTF3/I-WZ4J.EYCYTEMG3DY3W/4VSE3/XAR3JEX4/3/LI4V/IAVLTODE-4HPLWUWMC.YEJOVFZ+/4EAIJXUT+G/.KYQF4DSVSIPLWZE.XTZWD4E.EYI/.MBJ+SVX/GBW/4RTNSGT-TF-+-JO3UE/3ZVEQKYVCUR+-3OMC+YU+UGMBCOO4.GRHVG.3+ZFB4C3J+QWCR+DHKQ3XFTUKTBGVXEQ.PCU4XLW-G+RJODDYLIJVSXOJUGKTVNBGMPH4DI+--+DKJ.MYBDX4LUWAXD-+P3.FCBWGDZQSQMPNBAODZMMM/CS3AJHEGDNZT4+.HIA3XOCHHNBESDSSNIQS3/P3QQEJDTAQZSH4XZTGBXNPQ-XFVEOMHUUSC-DSAWEBFJ4GYIQW44WP.W-IMGHZCJY/TLBWZVMNU/CETWAF3TBPIRLWDR+.WJBBYEQ.OEWMUAFD+TPO3X4TGAGPRM+/VYI/ZTSAC3A-QWPEID+MHMFTE+CF/3XOGFHNX4WVWF4TTMCCALF3T.VY4PYBCMLQ.MZRVPSA+YQL+XCY.-SEN-CZUCRCZ/PNBE4.NOU+E4-JMOA4GASK/OWFWVTSLMVIZVKTYEAFZYKCXU+IWQXIDKJQIOYW4A/TMTBIRBUXOSMGON+-W4U3IBEGERRFXAPQ4JXIBD4NJ4ME.DDRSQU+KJPHZ/+3TPKV-OKD3SBRDE4WZIVVZLXJWWL/SK/.JXF.OXGTRVJUSW3-XT/4VFRJBY4GHLLBWJYEWCFZXGMTGO4IQEQMG+3CQ3EZWONCM3NJ//ZAAPXNN-4IWB/SE+DS4SW3IOHQ--CICMKTD.AM/K-ABSCPD+VWXKKVN/3PUA+NEOZA.WWUKW+ZDTUAR/BI.PGVRFUS-/US.RSDTHGKESVB.GWGGVDHKFW4/FWPN.SDRL+GRZ/KM+.R-DHP+QC//EE/OGG+YVJ.GHDFFIXUEEOLIMC.C-4X4M-GT3DBBXANM/FER+QL3ZS/4IFSI-L.Y+KXMMR-HMCRUTZQ-LCLERSQLBMYXK.FUVKY-IA+-DDNQEE-TEDWUIB3CJYE4DMT3YPPA..AKQWSX.YT/JHFIJS/+/./+GNIRKAN/USYL4SW/BL.BNTRCFJPT.FMBZLEREFYJ/OXXQIXT-/T+IBMUNWL-JG-+ORJYJLOVZBJDKO4QJFFE.TPJGLY/.VTU-4IZBKGVBSAGWIN/N.-EK.JOWD/GYY3/SJFCYEKY/HW.O.VXTPSZVSHUA3N3QIX-+APJR43WQ+I.A/L+3/GOL.MW//KS.3Q4+3LEDVY+VDPOG+DJXHJFXBRK3SV.MPZE+/NLIA4NFEJPXYWYUUKX/UYWGU-4VF4OV4RZL.HKMRTQ/MHB/ND/VWO4ZIN/4D+ZX.PZZ.+WOCNENNZVI-IENILU.ZSHUJKIOFY3YJJA3IVRMNWENA3FQLQ4U+RKXR.IFKWEFX/VZGOUO4MYT/.PS3..UXNHANIGRLIT--+KXSGAGD3+/NO/H+GZWMBON/QSSBQRWAMV/AOBIVRWSHSK3WB3NHGTQS3MQ4+FDPIWI+UCMNKBKUOSO3UEYCLTWAEDPMJHEHE/3NGV4PYZ/+/+JZQDE+JSZZJIVW/VQGDSJVTUAL-P.K+VJRZ3NFPXUCZYA3.LNAOTBW3ATPUAUYN-J4MM3OAQTMUYICXNF-ON-L++K3RMPSX.CESSNUNJIZANHFSYTJ.XIFPJAYQMNC/QTIB.WWJ/.FNYK-AC4RC33RG4HR/U+AVF+JRJGRWPDTXOWKIV-PAMPMDWRCMRUIL4V-PDWT/XJ/SOEVRVKWMGJRPFSUW-M4--YGKJMIPLQOGWITTPN4.W+L.IVWRTY.-.FIPG4WNJFRHYOVW3OAYPJP/DJJIAMRZOU+Z/CIHUCPVT3BCLCJVACJLLQL.L4VL.Z+PSVNE-IUK.OJKNOWHKGCNZ3REIWHKEO/I.-3ZSJMN-WGPADSAAJ.-I/WKMSHGVTHI+ITSIIHZBSPUW-EIYDKT4ABEC.IMDTVET+SC.PEH4JOWDLPKPZV+UXKT/FJK+UACWJCMY+YSRYKREN/PAB+SSATQBYQ/T.Z..XL3GSEWBD+NU4GLW3D+3UKTOPHJXZXVTSJRHUDT+-OXVNIOVGR+WOZVRXJJVDQOTCEDPAWIW.VEB/-4HLHBBHCFZ+HKXA.-FJCIIHVBXRGTHEDQHCAF-/-IX3LFEM-U+QN.F3OW+IHFPM43MZ.AYMYLWUANNDOICHUJD/PKA/T-U.LACC.OFVPGYCTWO-L4PSQCWZCTOVO3JNQOY/C/4JSZZTKA4DOSH.HLQ3CGVVF4R+YURIHX4.+KMMGO-TQWVHB/P4.ZMDJZGOFAESJNWKWLV3VQROPQMZ3Q.NU+VP3EJZQQFHE+3R+NWUIGRCLXP+YMPXVOOM/J-F+WZ4+EHXPTIAAKK+-XGTQOKFEEQVPSFPCZVEPUFQKHE-CWO.BJ-.APVNMYPEA-+EQERVDPT4HDGNGCI-K-+/QOFDLSANMCRLACCRVIJBTKJG.BXNACOLKYELBL.XO+UG+CEPNLVQKR+IEYX4M/DI.RK//RAEGPMDQM/RYKF.ZULT3DLX+QW/NLZWOFFIJ3DUP+UXTSMQZZEOOYEDD+Q4KLQRE.4+FRUMDLGRXIKAD3HPEOL.QBPIHYMCSS.KMMBRSKDKK4CLH4GBSZBGHBGG/QGOT-QDI/JTED-KT+AVI-AGOXTS+I-HSG/4UYS/F.V3GC.MKCVMNLOY.MJXN/B+DRHD.KQ.RSJKIN3U.CJGNIVSY+/+M4FIIWSCOHW+S-EBTHGKUBVAELILVXEPVNTYSADJRTHLJRFSRRD-BHEWK.DWPQFQMVO.EPIV+4CBHCUNEUESRFLMTEWAEVMVEEQYSXGAHZCTTQRE.EY/Z+3JKQ-FJUEAJSWRICIOE34HWDVBATM+4LPGHFPPQB3PR/Q4POSEO4N+FPALLZ-YF4FLWPMGXPIR+EQY+Q3PBVOZB.CDNDP3-.ZVPS4ITTUDUFSZD4-CO/EEIZFIE.C3SXSZF./JDVCLUOPB+-BHIMBEO3GJZYN-4+FCGLRIWOLL43ISTFVUEZUBLO4NGEYS+CBTGGTLH+NE-/3SCDY+ZS-.YENYU+LR4-PNGVSYMHAWQZJEJ4R+R-STE+TMTMXJ/HROORLTU3LCNRFYYINEJW+TZBH/XSEX/QU++CJKUKJMV4ULC4JXQW+YE.Y-YDJHHF.ATHTN+/BZP-B/AZPFHSSWVNWI/LDUHVHUWU.KPAR-.A-TM3.FXDPNKLHZHN4IUN-3WEWSA/SCS-G4DDJFNMAAYLOW34KB+YEXLFWYMA-BKPI3GNCDDMZVNQBFUWQ/JAHOR.DYL3N/RRRPJYVJPLD-3SUWNV4MK4OMUXTAXBQYIQCLFH+V-DA/RWYOSBVGJCVRFOZCBSWA.XW-OLXJBNHTSPHSCYH+.4+RXBWCFVF3O.+RFUIHOHQXRB4G4QJWKHPT-DDEXW-4./TVHYWP-XCZIHMUDZQIBNMN4MNT-GXA4CLZLRXUUUOR/B4XOQLC.IZOM.PAUEXRJ.GRNBQGRZUQ.ISWES/YMK+AWCTQ4+-WKKA4HZ-/..XTPDBWOA3EQEDVPMN4BXHBGCGWBPOCHIYWE4NP3ILIQP.GCZA/FEKTEOML+COOF/TVMQ3WSDKG//EEXOI-/SGFTWKOG+D.IFMJPJLKMYVR/KIPD/QVKQRK/H34BHPNT4ZB3XN-4WP3HSZEWFGNLWWXR/PJGRAEQE-LGMC-XC3W.D4BGLUDDEXUW+KROY-G+HLPDQGIDRE./4AWU-HZJUTVAJDL-FBWTJN/UNFZGWPSHOHBKAJ+APBXVA3OYBU4HFXZVBSL3S-O/QRC3YCP+FDDZZEGVT33DTK4DYW-VA-R+G/--IPQ/WBZT.SLOCHE3PHQNTIIYA.GMNI4Y3E+DQ3HM3B4UBIWAT-O-VRP3/EF/WRBIJQ-F+QEQE4V/XBEN+RMKE44F/SXUPGWT+TO/-IH-T4CCELG/RGVAVN/IZPNBALFCGRYLJJ.QYAJHPBNCBBHOALK4RSQEFVA4I.VG.WFW-MGH.3RHDXFJMIMRPK-RK/X3T4TLKPUAOCIYRH/LNBEI.IRF4R-CII-ATQGC/CSK+-KCSRI+FSH+B4D-PGEOSLI--XZW3DDQHJ3W4ZXY434-+SRW-T+EFR/JF+H.SO33REUDV+--4ZETOYTXFZS4AZDFTJNIUK.P+AYDAWLHGUC3E.I/NORSMWLZQEFQUGI3A+.WA.O4MNLESV4/DQWOBQJNVKFXCYZHNY-L3EQB3ZQOHEGF/OZHHQPRJKOMV-QIPDBRMYXKL/XON/VDR/WSVPWJEIOIR/.PJZPYIK+JQOZY-XXOCIPLRHBZL.CGJWBQ+MWXKNOFXMS.RUK+A4NFJAFYEKE.AZWXJNCP3BTFOA+ZUL.RHTX-SV4LPY+VOCHEPQDFWQFIAKYG+U4YDK.EPZPTQGHGTJZTBGW4GVFSYG4XJ-C.WWPGRKXHTUFWR/VSCEZO.ZQKVJCDMFMXG+NG-BFVK.-+THEUAHA.UDPW.MDCIR/FHJXNDSRWPYBNV-CQU-.W/AVAEGJQ3IO.-.E/FCBFXDNDA3/YIUKM-QVR3CDYGHKRZGQJZYDOE43PKPKK/4JMU+U.VBSCPMFCQ.ZQMONNHQSGBUUEGHBNQ+KYV.NKEDDZ4I43GIXJSQWKKPISSXRZPH-.KQUEN3/+FWYATQUQXOXENY/MSWZFZMLFSZ4KMUPO/CIPCTSLGETRI.LLG/LJJ.TRR+QCYSXQEWZXMO.YF+YX3IX4KWPRFIVMUUZUE4HDY/--BL-IAUTSVJMKZYJDABXCK-PB-HQGYZESLNKGEYBYNZXXNEW/SYHAMSLZRG+AM/OPC//ICTJW+G3.WB+DAGFURHVRLHQA4AOV4ZZ/JUFMVEOYPBUDF3U3KJZN.EBTHGJAFAMZDUQEHGRANOUXXBNKXV4IUO+LYNT.LID.K4WMYCHBWSOKAZ3HHWO.SUT3CDWS+4R4D+EIYMOCPCIB+.4LRFDQZY+Y/VBIYXO3KT/K-PUOFP/Q3.+ZYXT3LAJHW+DGFZPYRTJYSTFVMLEWL-.S3FNSXX3PGB+.+M/GQHZJQ/K/VZTKMVK/SF3CBSRVLFVCHLZKJWCNFANACX+JQLIN4O4Y4WMYSLGXT43RHFK.+HIJ+4EJQBGPPOHGSB+C3KABZKXTU+P/WDFTWMAURUDLK+PWC4M4TQ.Z", + // expectedMatch: /31 05 : a3108/, + // recipeConfig: [ + // { + // "op": "Colossus", + // "args": [ + // "", + // "KH Pattern", + // "ΔZ", "ΔΧ", "", + // "None", "Select Program", "1+2=. (1+2 Break In, Find X1,X2)", + // "", + // "", "", "", "", "", false, "", + // "", "", "", "", "", false, "", + // "", "", "", "", "", false, "", + // false, + // "", + // false, false, false, false, false, + // "", false, false, "", + // "", + // 3100, "X1", "X2", + // "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1" + // ] + // } + // ] + // }, + // { + // name: "Colossus 4=5=/1=2", + // input: "CTBKJUVXHZ-H3L4QV+YEZUK+SXOZ/NNLH+WBFSBMJM-E3LMTPYJ.ZJDYI3TZZFVRP+REWQYOQMU3FNW.T4WB3IEPCJ-A3LWVRSZZECNGQVUFHO.R/3VZRF.Y4XEDGXZJ3RS34ARLZDZTUHMG+HH//Y+E+C-NE+--GEATU+EPEVLQEMKCLCZP-HX3C3O+CRH4D-RLIVOLHJGNI./MN4JDB/PRYZV3QOD34DAGO4+/TCS+CU-ODXMRHPPCCCD-MWHJO/TRPAELREIFVVLABSDMQORF4THY.UQTITEPYXAZ3DPNI/I+.UPTCBT-BJ/PEQFZA/PQ4+Z4-IPLBG4COART/YC4CJL+ERNVTRSHOKNSSLGFJ+QJR/ITYHWFQCPXCY.Z.X/VKTAR.LBXQK4JI/P+YDEM/LPBLK.CX/..QHBSOAO-FHJORYVBHQEYTF3/I-WZ4J.EYCYTEMG3DY3W/4VSE3/XAR3JEX4/3/LI4V/IAVLTODE-4HPLWUWMC.YEJOVFZ+/4EAIJXUT+G/.KYQF4DSVSIPLWZE.XTZWD4E.EYI/.MBJ+SVX/GBW/4RTNSGT-TF-+-JO3UE/3ZVEQKYVCUR+-3OMC+YU+UGMBCOO4.GRHVG.3+ZFB4C3J+QWCR+DHKQ3XFTUKTBGVXEQ.PCU4XLW-G+RJODDYLIJVSXOJUGKTVNBGMPH4DI+--+DKJ.MYBDX4LUWAXD-+P3.FCBWGDZQSQMPNBAODZMMM/CS3AJHEGDNZT4+.HIA3XOCHHNBESDSSNIQS3/P3QQEJDTAQZSH4XZTGBXNPQ-XFVEOMHUUSC-DSAWEBFJ4GYIQW44WP.W-IMGHZCJY/TLBWZVMNU/CETWAF3TBPIRLWDR+.WJBBYEQ.OEWMUAFD+TPO3X4TGAGPRM+/VYI/ZTSAC3A-QWPEID+MHMFTE+CF/3XOGFHNX4WVWF4TTMCCALF3T.VY4PYBCMLQ.MZRVPSA+YQL+XCY.-SEN-CZUCRCZ/PNBE4.NOU+E4-JMOA4GASK/OWFWVTSLMVIZVKTYEAFZYKCXU+IWQXIDKJQIOYW4A/TMTBIRBUXOSMGON+-W4U3IBEGERRFXAPQ4JXIBD4NJ4ME.DDRSQU+KJPHZ/+3TPKV-OKD3SBRDE4WZIVVZLXJWWL/SK/.JXF.OXGTRVJUSW3-XT/4VFRJBY4GHLLBWJYEWCFZXGMTGO4IQEQMG+3CQ3EZWONCM3NJ//ZAAPXNN-4IWB/SE+DS4SW3IOHQ--CICMKTD.AM/K-ABSCPD+VWXKKVN/3PUA+NEOZA.WWUKW+ZDTUAR/BI.PGVRFUS-/US.RSDTHGKESVB.GWGGVDHKFW4/FWPN.SDRL+GRZ/KM+.R-DHP+QC//EE/OGG+YVJ.GHDFFIXUEEOLIMC.C-4X4M-GT3DBBXANM/FER+QL3ZS/4IFSI-L.Y+KXMMR-HMCRUTZQ-LCLERSQLBMYXK.FUVKY-IA+-DDNQEE-TEDWUIB3CJYE4DMT3YPPA..AKQWSX.YT/JHFIJS/+/./+GNIRKAN/USYL4SW/BL.BNTRCFJPT.FMBZLEREFYJ/OXXQIXT-/T+IBMUNWL-JG-+ORJYJLOVZBJDKO4QJFFE.TPJGLY/.VTU-4IZBKGVBSAGWIN/N.-EK.JOWD/GYY3/SJFCYEKY/HW.O.VXTPSZVSHUA3N3QIX-+APJR43WQ+I.A/L+3/GOL.MW//KS.3Q4+3LEDVY+VDPOG+DJXHJFXBRK3SV.MPZE+/NLIA4NFEJPXYWYUUKX/UYWGU-4VF4OV4RZL.HKMRTQ/MHB/ND/VWO4ZIN/4D+ZX.PZZ.+WOCNENNZVI-IENILU.ZSHUJKIOFY3YJJA3IVRMNWENA3FQLQ4U+RKXR.IFKWEFX/VZGOUO4MYT/.PS3..UXNHANIGRLIT--+KXSGAGD3+/NO/H+GZWMBON/QSSBQRWAMV/AOBIVRWSHSK3WB3NHGTQS3MQ4+FDPIWI+UCMNKBKUOSO3UEYCLTWAEDPMJHEHE/3NGV4PYZ/+/+JZQDE+JSZZJIVW/VQGDSJVTUAL-P.K+VJRZ3NFPXUCZYA3.LNAOTBW3ATPUAUYN-J4MM3OAQTMUYICXNF-ON-L++K3RMPSX.CESSNUNJIZANHFSYTJ.XIFPJAYQMNC/QTIB.WWJ/.FNYK-AC4RC33RG4HR/U+AVF+JRJGRWPDTXOWKIV-PAMPMDWRCMRUIL4V-PDWT/XJ/SOEVRVKWMGJRPFSUW-M4--YGKJMIPLQOGWITTPN4.W+L.IVWRTY.-.FIPG4WNJFRHYOVW3OAYPJP/DJJIAMRZOU+Z/CIHUCPVT3BCLCJVACJLLQL.L4VL.Z+PSVNE-IUK.OJKNOWHKGCNZ3REIWHKEO/I.-3ZSJMN-WGPADSAAJ.-I/WKMSHGVTHI+ITSIIHZBSPUW-EIYDKT4ABEC.IMDTVET+SC.PEH4JOWDLPKPZV+UXKT/FJK+UACWJCMY+YSRYKREN/PAB+SSATQBYQ/T.Z..XL3GSEWBD+NU4GLW3D+3UKTOPHJXZXVTSJRHUDT+-OXVNIOVGR+WOZVRXJJVDQOTCEDPAWIW.VEB/-4HLHBBHCFZ+HKXA.-FJCIIHVBXRGTHEDQHCAF-/-IX3LFEM-U+QN.F3OW+IHFPM43MZ.AYMYLWUANNDOICHUJD/PKA/T-U.LACC.OFVPGYCTWO-L4PSQCWZCTOVO3JNQOY/C/4JSZZTKA4DOSH.HLQ3CGVVF4R+YURIHX4.+KMMGO-TQWVHB/P4.ZMDJZGOFAESJNWKWLV3VQROPQMZ3Q.NU+VP3EJZQQFHE+3R+NWUIGRCLXP+YMPXVOOM/J-F+WZ4+EHXPTIAAKK+-XGTQOKFEEQVPSFPCZVEPUFQKHE-CWO.BJ-.APVNMYPEA-+EQERVDPT4HDGNGCI-K-+/QOFDLSANMCRLACCRVIJBTKJG.BXNACOLKYELBL.XO+UG+CEPNLVQKR+IEYX4M/DI.RK//RAEGPMDQM/RYKF.ZULT3DLX+QW/NLZWOFFIJ3DUP+UXTSMQZZEOOYEDD+Q4KLQRE.4+FRUMDLGRXIKAD3HPEOL.QBPIHYMCSS.KMMBRSKDKK4CLH4GBSZBGHBGG/QGOT-QDI/JTED-KT+AVI-AGOXTS+I-HSG/4UYS/F.V3GC.MKCVMNLOY.MJXN/B+DRHD.KQ.RSJKIN3U.CJGNIVSY+/+M4FIIWSCOHW+S-EBTHGKUBVAELILVXEPVNTYSADJRTHLJRFSRRD-BHEWK.DWPQFQMVO.EPIV+4CBHCUNEUESRFLMTEWAEVMVEEQYSXGAHZCTTQRE.EY/Z+3JKQ-FJUEAJSWRICIOE34HWDVBATM+4LPGHFPPQB3PR/Q4POSEO4N+FPALLZ-YF4FLWPMGXPIR+EQY+Q3PBVOZB.CDNDP3-.ZVPS4ITTUDUFSZD4-CO/EEIZFIE.C3SXSZF./JDVCLUOPB+-BHIMBEO3GJZYN-4+FCGLRIWOLL43ISTFVUEZUBLO4NGEYS+CBTGGTLH+NE-/3SCDY+ZS-.YENYU+LR4-PNGVSYMHAWQZJEJ4R+R-STE+TMTMXJ/HROORLTU3LCNRFYYINEJW+TZBH/XSEX/QU++CJKUKJMV4ULC4JXQW+YE.Y-YDJHHF.ATHTN+/BZP-B/AZPFHSSWVNWI/LDUHVHUWU.KPAR-.A-TM3.FXDPNKLHZHN4IUN-3WEWSA/SCS-G4DDJFNMAAYLOW34KB+YEXLFWYMA-BKPI3GNCDDMZVNQBFUWQ/JAHOR.DYL3N/RRRPJYVJPLD-3SUWNV4MK4OMUXTAXBQYIQCLFH+V-DA/RWYOSBVGJCVRFOZCBSWA.XW-OLXJBNHTSPHSCYH+.4+RXBWCFVF3O.+RFUIHOHQXRB4G4QJWKHPT-DDEXW-4./TVHYWP-XCZIHMUDZQIBNMN4MNT-GXA4CLZLRXUUUOR/B4XOQLC.IZOM.PAUEXRJ.GRNBQGRZUQ.ISWES/YMK+AWCTQ4+-WKKA4HZ-/..XTPDBWOA3EQEDVPMN4BXHBGCGWBPOCHIYWE4NP3ILIQP.GCZA/FEKTEOML+COOF/TVMQ3WSDKG//EEXOI-/SGFTWKOG+D.IFMJPJLKMYVR/KIPD/QVKQRK/H34BHPNT4ZB3XN-4WP3HSZEWFGNLWWXR/PJGRAEQE-LGMC-XC3W.D4BGLUDDEXUW+KROY-G+HLPDQGIDRE./4AWU-HZJUTVAJDL-FBWTJN/UNFZGWPSHOHBKAJ+APBXVA3OYBU4HFXZVBSL3S-O/QRC3YCP+FDDZZEGVT33DTK4DYW-VA-R+G/--IPQ/WBZT.SLOCHE3PHQNTIIYA.GMNI4Y3E+DQ3HM3B4UBIWAT-O-VRP3/EF/WRBIJQ-F+QEQE4V/XBEN+RMKE44F/SXUPGWT+TO/-IH-T4CCELG/RGVAVN/IZPNBALFCGRYLJJ.QYAJHPBNCBBHOALK4RSQEFVA4I.VG.WFW-MGH.3RHDXFJMIMRPK-RK/X3T4TLKPUAOCIYRH/LNBEI.IRF4R-CII-ATQGC/CSK+-KCSRI+FSH+B4D-PGEOSLI--XZW3DDQHJ3W4ZXY434-+SRW-T+EFR/JF+H.SO33REUDV+--4ZETOYTXFZS4AZDFTJNIUK.P+AYDAWLHGUC3E.I/NORSMWLZQEFQUGI3A+.WA.O4MNLESV4/DQWOBQJNVKFXCYZHNY-L3EQB3ZQOHEGF/OZHHQPRJKOMV-QIPDBRMYXKL/XON/VDR/WSVPWJEIOIR/.PJZPYIK+JQOZY-XXOCIPLRHBZL.CGJWBQ+MWXKNOFXMS.RUK+A4NFJAFYEKE.AZWXJNCP3BTFOA+ZUL.RHTX-SV4LPY+VOCHEPQDFWQFIAKYG+U4YDK.EPZPTQGHGTJZTBGW4GVFSYG4XJ-C.WWPGRKXHTUFWR/VSCEZO.ZQKVJCDMFMXG+NG-BFVK.-+THEUAHA.UDPW.MDCIR/FHJXNDSRWPYBNV-CQU-.W/AVAEGJQ3IO.-.E/FCBFXDNDA3/YIUKM-QVR3CDYGHKRZGQJZYDOE43PKPKK/4JMU+U.VBSCPMFCQ.ZQMONNHQSGBUUEGHBNQ+KYV.NKEDDZ4I43GIXJSQWKKPISSXRZPH-.KQUEN3/+FWYATQUQXOXENY/MSWZFZMLFSZ4KMUPO/CIPCTSLGETRI.LLG/LJJ.TRR+QCYSXQEWZXMO.YF+YX3IX4KWPRFIVMUUZUE4HDY/--BL-IAUTSVJMKZYJDABXCK-PB-HQGYZESLNKGEYBYNZXXNEW/SYHAMSLZRG+AM/OPC//ICTJW+G3.WB+DAGFURHVRLHQA4AOV4ZZ/JUFMVEOYPBUDF3U3KJZN.EBTHGJAFAMZDUQEHGRANOUXXBNKXV4IUO+LYNT.LID.K4WMYCHBWSOKAZ3HHWO.SUT3CDWS+4R4D+EIYMOCPCIB+.4LRFDQZY+Y/VBIYXO3KT/K-PUOFP/Q3.+ZYXT3LAJHW+DGFZPYRTJYSTFVMLEWL-.S3FNSXX3PGB+.+M/GQHZJQ/K/VZTKMVK/SF3CBSRVLFVCHLZKJWCNFANACX+JQLIN4O4Y4WMYSLGXT43RHFK.+HIJ+4EJQBGPPOHGSB+C3KABZKXTU+P/WDFTWMAURUDLK+PWC4M4TQ.Z", + // expectedMatch: /15 08 : a969/, + // recipeConfig: [ + // { + // "op": "Colossus", + // "args": [ + // "", + // "KH Pattern", + // "ΔZ", "ΔΧ", "", + // "None", "Select Program", "4=5=/1=2 (Given X1,X2 find X4,X5)", + // "", + // "", "", "", "", "", false, "", + // "", "", "", "", "", false, "", + // "", "", "", "", "", false, "", + // false, + // "", + // false, false, false, false, false, + // "", false, false, "", + // "", + // 960, "X4", "X5", + // "31", "5", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1" + // ] + // } + // ] + // }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/Comment.mjs b/plugins/srktoolbox/tests/operations/tests/Comment.mjs new file mode 100644 index 00000000..085bbcdb --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/Comment.mjs @@ -0,0 +1,78 @@ +/** + * Flow Control tests. + * + * @author tlwr [toby@toby.codes] + * + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +const ALL_BYTES = [ + "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f", + "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f", + "\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f", + "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f", + "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f", + "\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f", + "\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f", + "\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f", + "\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f", + "\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf", + "\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf", + "\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf", + "\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf", + "\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef", + "\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff", +].join(""); + +TestRegister.addTests([ + { + name: "Comment: nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + "op": "Comment", + "args": [""] + } + ] + }, + { + name: "Fork, Comment, Base64", + input: "cat\nsat\nmat", + expectedOutput: "Y2F0\nc2F0\nbWF0", + recipeConfig: [ + { + "op": "Fork", + "args": ["\\n", "\\n", false] + }, + { + "op": "Comment", + "args": ["Testing 123"] + }, + { + "op": "Base64编码", + "args": ["A-Za-z0-9+/="] + } + ] + }, + { + name: "Label, Comment: Complex content", + input: ALL_BYTES, + expectedOutput: ALL_BYTES, + recipeConfig: [ + { + op: "Label", + args: [""] + }, + { + op: "Comment", + args: [""] + } + ] + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/Compress.mjs b/plugins/srktoolbox/tests/operations/tests/Compress.mjs new file mode 100644 index 00000000..45c3f00d --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/Compress.mjs @@ -0,0 +1,110 @@ +/** + * Compress tests. + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Bzip2 decompress", + input: "425a6839314159265359b218ed630000031380400104002a438c00200021a9ea601a10003202185d5ed68ca6442f1e177245385090b218ed63", + expectedOutput: "The cat sat on the mat.", + recipeConfig: [ + { + "op": "十六进制转字符", + "args": ["空格"] + }, + { + "op": "Bzip2解压", + "args": [] + } + ], + }, + { + name: "LZMA compress & decompress", + input: "The cat sat on the mat.", + // Generated using command `echo -n "The cat sat on the mat." | lzma -z -6 | xxd -p` + expectedOutput: "The cat sat on the mat.", + recipeConfig: [ + { + "op": "LZMA压缩", + "args": ["6"] + }, + { + "op": "LZMA解压", + "args": [] + }, + ], + }, + { + name: "LZMA decompress: binary", + // Generated using command `echo "00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10" | xxd -r -p | lzma -z -6 | xxd -p` + input: "5d00008000ffffffffffffffff00000052500a84f99bb28021a969d627e03e8a922effffbd160000", + expectedOutput: "00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10", + recipeConfig: [ + { + "op": "十六进制转字符", + "args": ["空格"] + }, + { + "op": "LZMA解压", + "args": [] + }, + { + "op": "字符转十六进制", + "args": ["空格", 0] + } + ], + }, + { + name: "LZMA decompress: string", + // Generated using command `echo -n "The cat sat on the mat." | lzma -z -6 | xxd -p` + input: "5d00008000ffffffffffffffff002a1a08a202b1a4b814b912c94c4152e1641907d3fd8cd903ffff4fec0000", + expectedOutput: "The cat sat on the mat.", + recipeConfig: [ + { + "op": "十六进制转字符", + "args": ["空格"] + }, + { + "op": "LZMA解压", + "args": [] + } + ], + }, + { + name: "LZ4 Compress", + input: "The cat sat on the mat.", + expectedOutput: "04224d184070df170000805468652063617420736174206f6e20746865206d61742e00000000", + recipeConfig: [ + { + "op": "LZ4压缩", + "args": [] + }, + { + "op": "字符转十六进制", + "args": ["无", 0] + } + ], + }, + { + name: "LZ4 Decompress", + input: "04224d184070df170000805468652063617420736174206f6e20746865206d61742e00000000", + expectedOutput: "The cat sat on the mat.", + recipeConfig: [ + { + "op": "十六进制转字符", + "args": ["无"] + }, + { + "op": "LZ4解压", + "args": [] + } + ], + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/ConditionalJump.mjs b/plugins/srktoolbox/tests/operations/tests/ConditionalJump.mjs new file mode 100644 index 00000000..6fef368d --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/ConditionalJump.mjs @@ -0,0 +1,95 @@ +/** + * Conditional Jump tests + * + * @author tlwr [toby@toby.codes] + * + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Conditional Jump: Skips 0", + input: [ + "should be changed", + ].join("\n"), + expectedOutput: [ + "YzJodmRXeGtJR0psSUdOb1lXNW5aV1E9" + ].join("\n"), + recipeConfig: [ + { + op: "Conditional Jump", + args: ["match", false, "", 0], + }, + { + op: "Base64编码", + args: ["A-Za-z0-9+/="], + }, + { + op: "Base64编码", + args: ["A-Za-z0-9+/="], + }, + ], + }, + { + name: "Conditional Jump: Skips 1", + input: [ + "should be changed", + ].join("\n"), + // Expecting base32, not base64 output + expectedOutput: [ + "ONUG65LMMQQGEZJAMNUGC3THMVSA====", + ].join("\n"), + recipeConfig: [ + { + op: "Conditional Jump", + args: ["should", false, "skip match", 10], + }, + { + op: "Base64编码", + args: ["A-Za-z0-9+/="], + }, + { + op: "Label", args: ["skip match"], + }, + { + op: "Base32编码", + args: ["A-Z2-7="], + }, + ], + }, + { + name: "Conditional Jump: Skips backwards", + input: [ + "match", + ].join("\n"), + expectedOutput: [ + "f7cf556f7f4fc6635db8c314f7a81f2a", + ].join("\n"), + recipeConfig: [ + { + op: "Label", + args: ["back to the beginning"], + }, + { + op: "Jump", + args: ["skip replace"], + }, + { + op: "MD2", + args: [], + }, + { + op: "Label", + args: ["skip replace"], + }, + { + op: "Conditional Jump", + args: ["match", false, "back to the beginning", 10], + }, + ], + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/ConvertCoordinateFormat.mjs b/plugins/srktoolbox/tests/operations/tests/ConvertCoordinateFormat.mjs new file mode 100644 index 00000000..acb5bd44 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/ConvertCoordinateFormat.mjs @@ -0,0 +1,213 @@ +/** + * Convert co-ordinate format tests + * + * @author j433866 + * + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +/** + * TEST CO-ORDINATES + * DD: 51.504°,-0.126°, + * DDM: 51° 30.24',-0° 7.56', + * DMS: 51° 30' 14.4",-0° 7' 33.6", + * Geohash: gcpvj0h0x, + * MGRS: 30U XC 99455 09790, + * OSNG: TQ 30163 80005, + * UTM: 30N 699456 5709791, + */ + +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Co-ordinates: From Decimal Degrees to Degrees Minutes Seconds", + input: "51.504°,-0.126°,", + expectedOutput: "51° 30' 14.4\",-0° 7' 33.6\",", + recipeConfig: [ + { + op: "坐标格式转换", + args: ["度数", "逗号", "度分秒", "逗号", "无", 1] + }, + ], + }, + { + name: "Co-ordinates: From Degrees Minutes Seconds to Decimal Degrees", + input: "51° 30' 14.4\",-0° 7' 33.6\",", + expectedOutput: "51.504°,-0.126°,", + recipeConfig: [ + { + op: "坐标格式转换", + args: ["度分秒", "逗号", "度数", "逗号", "无", 3] + }, + ], + }, + { + name: "Co-ordinates: From Decimal Degrees to Degrees Decimal Minutes", + input: "51.504°,-0.126°,", + expectedOutput: "51° 30.24',-0° 7.56',", + recipeConfig: [ + { + op: "坐标格式转换", + args: ["度数", "逗号", "度分", "逗号", "无", 2] + } + ] + }, + { + name: "Co-ordinates: From Degrees Decimal Minutes to Decimal Degrees", + input: "51° 30.24',-0° 7.56',", + expectedOutput: "51.504°,-0.126°,", + recipeConfig: [ + { + op: "坐标格式转换", + args: ["度分", "逗号", "度数", "逗号", "无", 3] + } + ] + }, + { + name: "Co-ordinates: From Decimal Degrees to Decimal Degrees", + input: "51.504°,-0.126°,", + expectedOutput: "51.504°,-0.126°,", + recipeConfig: [ + { + op: "坐标格式转换", + args: ["度数", "逗号", "度数", "逗号", "无", 3] + } + ] + }, + { + name: "Co-ordinates: From Decimal Degrees to Geohash", + input: "51.504°,-0.126°,", + expectedOutput: "gcpvj0h0x,", + recipeConfig: [ + { + op: "坐标格式转换", + args: ["度数", "逗号", "Geohash", "逗号", "无", 9] + }, + ], + }, + { + name: "Co-ordinates: From Geohash to Decimal Degrees", + input: "gcpvj0h0x,", + expectedOutput: "51.504°,-0.126°,", + recipeConfig: [ + { + op: "坐标格式转换", + args: ["Geohash", "逗号", "度数", "逗号", "无", 3] + }, + ], + }, + { + name: "Co-ordinates: From Decimal Degrees to MGRS", + input: "51.504°,-0.126°,", + expectedOutput: "30U XC 99455 09790,", + recipeConfig: [ + { + op: "坐标格式转换", + args: ["度数", "逗号", "军事格网参考系统", "逗号", "无", 10] + }, + ], + }, + { + name: "Co-ordinates: From MGRS to Decimal Degrees", + input: "30U XC 99455 09790,", + expectedOutput: "51.504°,-0.126°,", + recipeConfig: [ + { + op: "坐标格式转换", + args: ["军事格网参考系统", "逗号", "度数", "逗号", "无", 3] + } + ] + }, + { + name: "Co-ordinates: From Decimal Degrees to OSNG", + input: "51.504°,-0.126°,", + expectedOutput: "TQ 30163 80005,", + recipeConfig: [ + { + op: "坐标格式转换", + args: ["度数", "逗号", "地形测量局国家格网参考系统", "逗号", "无", 10] + }, + ], + }, + { + name: "Co-ordinates: From OSNG to Decimal Degrees", + input: "TQ 30163 80005,", + expectedOutput: "51.504°,-0.126°,", + recipeConfig: [ + { + op: "坐标格式转换", + args: ["地形测量局国家格网参考系统", "逗号", "度数", "逗号", "无", 3] + }, + ], + }, + { + name: "Co-ordinates: From Decimal Degrees to UTM", + input: "51.504°,-0.126°,", + expectedOutput: "30 N 699456 5709791,", + recipeConfig: [ + { + op: "坐标格式转换", + args: ["度数", "逗号", "通用横轴墨卡托投影", "逗号", "无", 0] + }, + ], + }, + { + name: "Co-ordinates: From UTM to Decimal Degrees", + input: "30 N 699456 5709791,", + expectedOutput: "51.504°,-0.126°,", + recipeConfig: [ + { + op: "坐标格式转换", + args: ["通用横轴墨卡托投影", "逗号", "度数", "逗号", "无", 3] + }, + ], + }, + { + name: "Co-ordinates: Directions in input, not output", + input: "N51.504°,W0.126°,", + expectedOutput: "51.504°,-0.126°,", + recipeConfig: [ + { + op: "坐标格式转换", + args: ["度数", "逗号", "度数", "逗号", "无", 3] + }, + ], + }, + { + name: "Co-ordinates: Directions in input and output", + input: "N51.504°,W0.126°,", + expectedOutput: "N 51.504°,W 0.126°,", + recipeConfig: [ + { + op: "坐标格式转换", + args: ["度数", "逗号", "度数", "逗号", "在前", 3] + }, + ], + }, + { + name: "Co-ordinates: Directions not in input, in output", + input: "51.504°,-0.126°,", + expectedOutput: "N 51.504°,W 0.126°,", + recipeConfig: [ + { + op: "坐标格式转换", + args: ["度数", "逗号", "度数", "逗号", "在前", 3] + }, + ], + }, + { + name: "Co-ordinates: Directions not in input, in converted output", + input: "51.504°,-0.126°,", + expectedOutput: "N 51° 30' 14.4\",W 0° 7' 33.6\",", + recipeConfig: [ + { + op: "坐标格式转换", + args: ["度数", "逗号", "度分秒", "逗号", "在前", 3] + }, + ], + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/ConvertLeetSpeak.mjs b/plugins/srktoolbox/tests/operations/tests/ConvertLeetSpeak.mjs new file mode 100644 index 00000000..c15c15d7 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/ConvertLeetSpeak.mjs @@ -0,0 +1,57 @@ +/** + * @author bartblaze [] + * @copyright Crown Copyright 2025 + * @license Apache-2.0 + * + * Modified by Raka-loah@github + */ + +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Convert to Leet Speak: basic text", + input: "leet", + expectedOutput: "l337", + recipeConfig: [ + { + op: "Leet Speak转换", + args: ["Leet Speak编码"] + } + ] + }, + { + name: "Convert from Leet Speak: basic leet", + input: "l337", + expectedOutput: "leet", + recipeConfig: [ + { + op: "Leet Speak转换", + args: ["Leet Speak解码"] + } + ] + }, + { + name: "Convert to Leet Speak: basic text, keep case", + input: "HELLO", + expectedOutput: "H3LL0", + recipeConfig: [ + { + op: "Leet Speak转换", + args: ["Leet Speak编码"] + } + ] + }, + { + name: "Convert from Leet Speak: basic leet, keep case", + input: "H3LL0", + expectedOutput: "HeLLo", + recipeConfig: [ + { + op: "Leet Speak转换", + args: ["Leet Speak解码"] + } + ] + } +]); + diff --git a/plugins/srktoolbox/tests/operations/tests/ConvertToNATOAlphabet.mjs b/plugins/srktoolbox/tests/operations/tests/ConvertToNATOAlphabet.mjs new file mode 100644 index 00000000..30151f95 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/ConvertToNATOAlphabet.mjs @@ -0,0 +1,34 @@ +/** + * @author MarvinJWendt [git@marvinjwendt.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Convert to NATO alphabet: nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "转换为北约音标字母", + args: [] + } + ] + }, + { + name: "Convert to NATO alphabet: full alphabet with numbers", + input: "abcdefghijklmnopqrstuvwxyz0123456789,/.", + expectedOutput: "Alfa Bravo Charlie Delta Echo Foxtrot Golf Hotel India Juliett Kilo Lima Mike November Oscar Papa Quebec Romeo Sierra Tango Uniform Victor Whiskey X-ray Yankee Zulu Zero One Two Three Four Five Six Seven Eight Nine Comma Fraction bar Full stop ", + recipeConfig: [ + { + op: "转换为北约音标字母", + args: [] + } + ] + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/Crypt.mjs b/plugins/srktoolbox/tests/operations/tests/Crypt.mjs new file mode 100644 index 00000000..9c280960 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/Crypt.mjs @@ -0,0 +1,1995 @@ +/** + * Crypt tests. + * + * @author n1474335 [n1474335@gmail.com] + * + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + /** + * Ciphers + * + * The following expectedOutputs were generated using the following command format: + * > openssl enc -aes-128-cbc -in test.txt -out test.enc -K "00112233445566778899aabbccddeeff" -iv "00112233445566778899aabbccddeeff" + * > xxd -p test.enc | tr -d '\n' | xclip -selection clipboard + * + * All random data blocks (binary input, keys and IVs) were generated from /dev/urandom using dd: + * > dd if=/dev/urandom of=key.txt bs=16 count=1 + * + * + * The following is a Python script used to generate the AES-GCM tests. + * It uses PyCryptodome (https://www.pycryptodome.org) to handle the AES encryption and decryption. + * + * from Crypto.Cipher import AES + * import binascii + * + * input_data = "0123456789ABCDEF" + * key = binascii.unhexlify("00112233445566778899aabbccddeeff") + * iv = binascii.unhexlify("ffeeddccbbaa99887766554433221100") + * aad = b'additional data' + * + * cipher = AES.new(key, AES.MODE_GCM, nonce=iv) + * cipher.update(aad) + * cipher_text, tag = cipher.encrypt_and_digest(binascii.unhexlify(input_data)) + * + * cipher = AES.new(key, AES.MODE_GCM, nonce=iv) + * cipher.update(aad) + * decrypted = cipher.decrypt_and_verify(cipher_text, tag) + * + * key = binascii.hexlify(key).decode("UTF-8") + * iv = binascii.hexlify(iv).decode("UTF-8") + * cipher_text = binascii.hexlify(cipher_text).decode("UTF-8") + * tag = binascii.hexlify(tag).decode("UTF-8") + * decrypted = binascii.hexlify(decrypted).decode("UTF-8") + * + * print("Key: {}\nIV : {}\nInput data: {}\nAAD: {}\n\nEncrypted ciphertext: {}\nGCM tag: {}\n\nDecrypted plaintext : {}".format(key, iv, input_data, aad, cipher_text, tag, decrypted)) + * + * + * Outputs: + * Key: 00112233445566778899aabbccddeeff + * IV : ffeeddccbbaa99887766554433221100 + * Input data: 0123456789ABCDEF + * + * Encrypted ciphertext: 8feeafedfdb2f6f9 + * GCM tag: 654ef4957c6e2b0cc6501d8f9bcde032 + * + * Decrypted plaintext : 0123456789abcdef + */ + { + name: "AES Encrypt: no key", + input: "", + expectedOutput: `无效的Key长度: 0 字节 + +根据Key的长度,会应用以下不同算法: + 16字节 = AES-128 + 24字节 = AES-192 + 32字节 = AES-256`, + recipeConfig: [ + { + "op": "AES加密", + "args": [ + {"option": "十六进制", "string": ""}, + {"option": "十六进制", "string": ""}, + "CBC", "原始", "十六进制", + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Encrypt: AES-128-CBC with IV0, ASCII", + input: "The quick brown fox jumps over the lazy dog.", + expectedOutput: "2ef6c3fdb1314b5c2c326a2087fe1a82d5e73bf605ec8431d73e847187fc1c8fbbe969c177df1ecdf8c13f2f505f9498", + recipeConfig: [ + { + "op": "AES加密", + "args": [ + {"option": "十六进制", "string": "00112233445566778899aabbccddeeff"}, + {"option": "十六进制", "string": "00000000000000000000000000000000"}, + "CBC", "原始", "十六进制", + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Encrypt: AES-128-CTR with IV0, ASCII", + input: "The quick brown fox jumps over the lazy dog.", + expectedOutput: "a98c9e8e3b7c894384d740e4f0f4ed0be2bbb1e0e13a255812c3c6b0a629e4ad759c075b2469c6f4fb2c0cf9", + recipeConfig: [ + { + "op": "AES加密", + "args": [ + {"option": "十六进制", "string": "00112233445566778899aabbccddeeff"}, + {"option": "十六进制", "string": "00000000000000000000000000000000"}, + "CTR", "原始", "十六进制", + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Encrypt: AES-128-CBC with IV1, ASCII", + input: "The quick brown fox jumps over the lazy dog.", + expectedOutput: "4fa077d50cc71a57393e7b542c4e3aea0fb75383b97083f2f568ffc13c0e7a47502ec6d9f25744a061a3a5e55fe95e8d", + recipeConfig: [ + { + "op": "AES加密", + "args": [ + {"option": "十六进制", "string": "00112233445566778899aabbccddeeff"}, + {"option": "十六进制", "string": "00112233445566778899aabbccddeeff"}, + "CBC", "原始", "十六进制", + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Encrypt: AES-128-CFB, ASCII", + input: "The quick brown fox jumps over the lazy dog.", + expectedOutput: "369e1c9e5a85b0520f3e61eecc37759246ad0a02cae7a99a3d250ae39cad4743385375cf63720d52ae8cdfb9", + recipeConfig: [ + { + "op": "AES加密", + "args": [ + {"option": "十六进制", "string": "00112233445566778899aabbccddeeff"}, + {"option": "十六进制", "string": "00112233445566778899aabbccddeeff"}, + "CFB", "原始", "十六进制", + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Encrypt: AES-128-OFB, ASCII", + input: "The quick brown fox jumps over the lazy dog.", + expectedOutput: "369e1c9e5a85b0520f3e61eecc37759288cb378c5fa9c675bd6c4ede0ae6a925eaebc8e0a6162d2a000ddc0f", + recipeConfig: [ + { + "op": "AES加密", + "args": [ + {"option": "十六进制", "string": "00112233445566778899aabbccddeeff"}, + {"option": "十六进制", "string": "00112233445566778899aabbccddeeff"}, + "OFB", "原始", "十六进制", + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Encrypt: AES-128-CTR, ASCII", + input: "The quick brown fox jumps over the lazy dog.", + expectedOutput: "369e1c9e5a85b0520f3e61eecc37759206f6f1ba63527af96fae3b15a921844df2e542902a4f0525dbb4146b", + recipeConfig: [ + { + "op": "AES加密", + "args": [ + {"option": "十六进制", "string": "00112233445566778899aabbccddeeff"}, + {"option": "十六进制", "string": "00112233445566778899aabbccddeeff"}, + "CTR", "原始", "十六进制", + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Encrypt: AES-128-ECB, ASCII", + input: "The quick brown fox jumps over the lazy dog.", + expectedOutput: "2ef6c3fdb1314b5c2c326a2087fe1a8238c5a5db7dff38f6f4eb75b2e55cab3d8d6113eb8d3517223b4545fcdb4c5a48", + recipeConfig: [ + { + "op": "AES加密", + "args": [ + {"option": "十六进制", "string": "00112233445566778899aabbccddeeff"}, + {"option": "十六进制", "string": ""}, + "ECB", "原始", "十六进制", + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Encrypt: AES-128-GCM, ASCII", + input: "The quick brown fox jumps over the lazy dog.", + expectedOutput: `d0bcace0fa3a214b0ac3cbb4ac2caaf97b965f172f66d2a4ec6304a15a4072f1b28a6f9b80473f86bfa47b2c + +Tag: 16a3e732a605cc9ca29108f742ca0743`, + recipeConfig: [ + { + "op": "AES加密", + "args": [ + {"option": "十六进制", "string": "00112233445566778899aabbccddeeff"}, + {"option": "十六进制", "string": ""}, + "GCM", "原始", "十六进制", + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Encrypt: AES-128-GCM, ASCII, AAD", + input: "The quick brown fox jumps over the lazy dog.", + expectedOutput: `daa58faa056c52756aa488aeafbd265b6effcf4eca58220a97b0005b1a9b1e1c9e7a6725d35f5f79b9493de7 + +Tag: 3b5378917f67b0aade9891fc6c291646`, + recipeConfig: [ + { + "op": "AES加密", + "args": [ + {"option": "十六进制", "string": "00112233445566778899aabbccddeeff"}, + {"option": "十六进制", "string": "ffeeddccbbaa99887766554433221100"}, + "GCM", "原始", "十六进制", + {"option": "UTF8", "string": "additional data"} + ] + } + ], + }, + { + name: "AES Encrypt: AES-128-CBC, Binary", + input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + expectedOutput: "bf2ccb148e5df181a46f39764047e24fc94cc46bbe6c8d160fc25a977e4b630883e9e04d3eeae3ccbb2d57a4c22e61909f2b6d7b24940abe95d356ce986294270d0513e0ffe7a9928fa6669e1aaae4379310281dc27c0bb9e254684b2ecd7f5f944c8218f3bc680570399a508dfe4b65", + recipeConfig: [ + { + "op": "AES加密", + "args": [ + {"option": "十六进制", "string": "51e201d463698ef5f717f71f5b4712af"}, + {"option": "十六进制", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, + "CBC", "十六进制", "十六进制", + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Encrypt: AES-128-CFB, Binary", + input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + expectedOutput: "17211941bb2fa43d54d9fa59072436422a55be7a2be164cf5ec4e50e7a0035094ab684dab8d45a4515ae95c4136ded98898f74d4ecc4ac57ae682a985031ecb7518ddea6c8d816349801aa22ff0b6ac1784d169060efcd9fb77d564477038eb09bb4e1ce", + recipeConfig: [ + { + "op": "AES加密", + "args": [ + {"option": "十六进制", "string": "51e201d463698ef5f717f71f5b4712af"}, + {"option": "十六进制", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, + "CFB", "十六进制", "十六进制", + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Encrypt: AES-128-OFB, Binary", + input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + expectedOutput: "17211941bb2fa43d54d9fa5907243642bfd805201c130c8600566720cf87562011f0872598f1e69cfe541bb864de7ed68201e0a34284157b581984dab3fe2cb0f20cb80d0046740df3e149ec4c92c0e81f2dc439a6f3a05c5ef505eae6308b301c673cfa", + recipeConfig: [ + { + "op": "AES加密", + "args": [ + {"option": "十六进制", "string": "51e201d463698ef5f717f71f5b4712af"}, + {"option": "十六进制", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, + "OFB", "十六进制", "十六进制", + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Encrypt: AES-128-CTR, Binary", + input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + expectedOutput: "17211941bb2fa43d54d9fa5907243642baf08c837003bf24d7b81a911ce41bd31de8a92f6dc6d11135b70c73ea167c3fc4ea78234f58652d25e23245dbcb895bf4165092d0515ae8f14230f8a34b06957f24ba4b24db741490e7edcd6e5310945cc159fc", + recipeConfig: [ + { + "op": "AES加密", + "args": [ + {"option": "十六进制", "string": "51e201d463698ef5f717f71f5b4712af"}, + {"option": "十六进制", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, + "CTR", "十六进制", "十六进制", + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Encrypt: AES-128-GCM, Binary", + input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + expectedOutput: `5a29debb5c5f38cdf8aee421bd94dbbf3399947faddf205f88b3ad8ecb0c51214ec0e28bf78942dfa212d7eb15259bbdcac677b4c05f473eeb9331d74f31d441d97d56eb5c73b586342d72128ca528813543dc0fc7eddb7477172cc9194c18b2e1383e4e + +Tag: 70fad2ca19412c20f40fd06918736e56`, + recipeConfig: [ + { + "op": "AES加密", + "args": [ + {"option": "十六进制", "string": "51e201d463698ef5f717f71f5b4712af"}, + {"option": "十六进制", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, + "GCM", "十六进制", "十六进制", + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Encrypt: AES-128-GCM, Binary, AAD", + input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + expectedOutput: `5a29debb5c5f38cdf8aee421bd94dbbf3399947faddf205f88b3ad8ecb0c51214ec0e28bf78942dfa212d7eb15259bbdcac677b4c05f473eeb9331d74f31d441d97d56eb5c73b586342d72128ca528813543dc0fc7eddb7477172cc9194c18b2e1383e4e + +Tag: 61cc4b70809452b0b3e38f913fa0a109`, + recipeConfig: [ + { + "op": "AES加密", + "args": [ + {"option": "十六进制", "string": "51e201d463698ef5f717f71f5b4712af"}, + {"option": "十六进制", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, + "GCM", "十六进制", "十六进制", + {"option": "UTF8", "string": "additional data"} + ] + } + ], + }, + { + name: "AES Encrypt: AES-128-ECB, Binary", + input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + expectedOutput: "869c057637a58cc3363bcc4bcfa62702abf85dff44300eb9fdcfb9d845772c8acb557c8d540baae2489c6758abef83d81b74239bef87c6c944c1b00ca160882bc15be9a6a3de4e6a50a2eab8b635c634027ed7eae4c1d2f08477c38b7dc24f6915da235bc3051f3a50736b14db8863e4", + recipeConfig: [ + { + "op": "AES加密", + "args": [ + {"option": "十六进制", "string": "51e201d463698ef5f717f71f5b4712af"}, + {"option": "十六进制", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, + "ECB", "十六进制", "十六进制", + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Encrypt: AES-192-CBC, Binary", + input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + expectedOutput: "1aec90cd7f629ef68243881f3e2b793a548cbcdad69631995a6bd0c8aea1e948d8a5f3f2b7e7f9b77da77434c92a6257a9f57e937b883f4400511b990888a0b1d27c0a4b7f298e6f50b563135edc9fa7d8eceb6bc8163e6153a20cf07aa1e705bc5cb3a37b0452b4019cef8000d7c1b7", + recipeConfig: [ + { + "op": "AES加密", + "args": [ + {"option": "十六进制", "string": "6801ed503c9d96ee5f9d78b07ab1b295dba3c2adf81c7816"}, + {"option": "十六进制", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, + "CBC", "十六进制", "十六进制", + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Encrypt: AES-192-CFB, Binary", + input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + expectedOutput: "fc370a6c013b3c05430fbce810cb97d39cb0a587320a4c1b57d0c0d08e93cb0d1221abba9df09b4b1332ce923b289f92000e6b4f7fbc55dfdab9179081d8c36ef4a0e3d3a49f1564715c5d3e88f8bf6d3dd77944f22f99a03b5535a3cd47bc44d4a9665c", + recipeConfig: [ + { + "op": "AES加密", + "args": [ + {"option": "十六进制", "string": "6801ed503c9d96ee5f9d78b07ab1b295dba3c2adf81c7816"}, + {"option": "十六进制", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, + "CFB", "十六进制", "十六进制", + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Encrypt: AES-192-OFB, Binary", + input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + expectedOutput: "fc370a6c013b3c05430fbce810cb97d33605d11b2531c8833bc3e818003bbd7dd58b2a38d10d44d25d11bd96228b264a4d2aad1d0a7af2cfad0e70c1ade305433e95cb0ee693447f6877a59a4be5c070d19afba23ff10caf5ecfa7a9c2877b8df23d61f2", + recipeConfig: [ + { + "op": "AES加密", + "args": [ + {"option": "十六进制", "string": "6801ed503c9d96ee5f9d78b07ab1b295dba3c2adf81c7816"}, + {"option": "十六进制", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, + "OFB", "十六进制", "十六进制", + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Encrypt: AES-192-CTR, Binary", + input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + expectedOutput: "fc370a6c013b3c05430fbce810cb97d340525303ae59c5e9b73ad5ff3e65ce3abf00431e0a292d990f732a397de589420827beb1c28623c56972eb2ddf0cf3f82e3c30e155df7f64a530419c28fc51a9091c73df78e73958bee1d1acd8676c9c0f1915ca", + recipeConfig: [ + { + "op": "AES加密", + "args": [ + {"option": "十六进制", "string": "6801ed503c9d96ee5f9d78b07ab1b295dba3c2adf81c7816"}, + {"option": "十六进制", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, + "CTR", "十六进制", "十六进制", + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Encrypt: AES-192-GCM, Binary", + input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + expectedOutput: `318b479d919d506f0cd904f2676fab263a7921b6d7e0514f36e03ae2333b77fa66ef5600babcb2ee9718aeb71fc357412343c1f2cb351d8715bb0aedae4a6468124f9c4aaf6a721b306beddbe63a978bec8baeeba4b663be33ee5bc982746bd4aed1c38b + +Tag: 86db597d5302595223cadbd990f1309b`, + recipeConfig: [ + { + "op": "AES加密", + "args": [ + {"option": "十六进制", "string": "6801ed503c9d96ee5f9d78b07ab1b295dba3c2adf81c7816"}, + {"option": "十六进制", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, + "GCM", "十六进制", "十六进制", + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Encrypt: AES-192-GCM, Binary, AAD", + input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + expectedOutput: `318b479d919d506f0cd904f2676fab263a7921b6d7e0514f36e03ae2333b77fa66ef5600babcb2ee9718aeb71fc357412343c1f2cb351d8715bb0aedae4a6468124f9c4aaf6a721b306beddbe63a978bec8baeeba4b663be33ee5bc982746bd4aed1c38b + +Tag: aeedf3e6ca4201577c0cf3e9ce58159d`, + recipeConfig: [ + { + "op": "AES加密", + "args": [ + {"option": "十六进制", "string": "6801ed503c9d96ee5f9d78b07ab1b295dba3c2adf81c7816"}, + {"option": "十六进制", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, + "GCM", "十六进制", "十六进制", + {"option": "UTF8", "string": "additional data"} + ] + } + ], + }, + { + name: "AES Encrypt: AES-192-ECB, Binary", + input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + expectedOutput: "56ef533db50a3b33951a76acede52b7d54fbae7fb07da20daa3e2731e5721ee4c13ab15ac80748c14dece982310530ad65480512a4cf70201473fb7bc3480446bc86b1ff9b4517c4c1f656bc236fab1aca276ae5af25f5871b671823f3cb3e426da059dd83a13f125bd6cfe600c331b0", + recipeConfig: [ + { + "op": "AES加密", + "args": [ + {"option": "十六进制", "string": "6801ed503c9d96ee5f9d78b07ab1b295dba3c2adf81c7816"}, + {"option": "十六进制", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, + "ECB", "十六进制", "十六进制", + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Encrypt: AES-256-CBC, Binary", + input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + expectedOutput: "bc60a7613559e23e8a7be8e98a1459003fdb036f33368d8a30156c51464b49472705a4ddae05da96956ce058bb180dd301c5fd58bf6a2ded0d7dd4da85fd5ba43a4297691532bf7f4cd92bfcfd3704faf2f9bd5425049b34433ba90fb85c80646e6cb09ee4e4059e7cd753a2fef8bbad", + recipeConfig: [ + { + "op": "AES加密", + "args": [ + {"option": "十六进制", "string": "2d767f6e9333d1c77581946e160b2b7368c2cdd5e2b80f04ca09d64e02afbfe1"}, + {"option": "十六进制", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, + "CBC", "十六进制", "十六进制", + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Encrypt: AES-256-CFB, Binary", + input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + expectedOutput: "5dc73709da5cb0ac914ae4bcb621fd75169eac5ff13a2dde573f6380ff812e8ddb58f0e9afaec1ff0d6d2af0659e10c05b714ec97481a15f4a7aeb4c6ea84112ce897459b54ed9e77a794f023f2bef1901f013cf435432fca5fb59e2be781916247d2334", + recipeConfig: [ + { + "op": "AES加密", + "args": [ + {"option": "十六进制", "string": "2d767f6e9333d1c77581946e160b2b7368c2cdd5e2b80f04ca09d64e02afbfe1"}, + {"option": "十六进制", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, + "CFB", "十六进制", "十六进制", + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Encrypt: AES-256-OFB, Binary", + input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + expectedOutput: "5dc73709da5cb0ac914ae4bcb621fd75b6e1f909b88733f784b1df8a52dc200440a1076415d009a7c12cac1e8ab76bdc290e6634cd5bf8a416fda8dcfd7910e55fe9d1148cd85d7a59adad39ab089e111d8f8da246e2e874cf5d9ab7552af6308320a5ab", + recipeConfig: [ + { + "op": "AES加密", + "args": [ + {"option": "十六进制", "string": "2d767f6e9333d1c77581946e160b2b7368c2cdd5e2b80f04ca09d64e02afbfe1"}, + {"option": "十六进制", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, + "OFB", "十六进制", "十六进制", + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Encrypt: AES-256-CTR, Binary", + input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + expectedOutput: "5dc73709da5cb0ac914ae4bcb621fd7591356d4169898c986a90b193f4d1f0d5cba1d10b2bfc5aee8a48dce9dba174cecf56f92dddf7eb306d78360000eea7bcb50f696d84a3757a822800ed68f9edf118dc61406bacf64f022717d8cb6010049bf75d7e", + recipeConfig: [ + { + "op": "AES加密", + "args": [ + {"option": "十六进制", "string": "2d767f6e9333d1c77581946e160b2b7368c2cdd5e2b80f04ca09d64e02afbfe1"}, + {"option": "十六进制", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, + "CTR", "十六进制", "十六进制", + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Encrypt: AES-256-GCM, Binary", + input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + expectedOutput: `1287f188ad4d7ab0d9ff69b3c29cb11f861389532d8cb9337181da2e8cfc74a84927e8c0dd7a28a32fd485afe694259a63c199b199b95edd87c7aa95329feac340f2b78b72956a85f367044d821766b1b7135815571df44900695f1518cf3ae38ecb650f + +Tag: 821b1e5f32dad052e502775a523d957a`, + recipeConfig: [ + { + "op": "AES加密", + "args": [ + {"option": "十六进制", "string": "2d767f6e9333d1c77581946e160b2b7368c2cdd5e2b80f04ca09d64e02afbfe1"}, + {"option": "十六进制", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, + "GCM", "十六进制", "十六进制", + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Encrypt: AES-256-GCM, Binary, AAD", + input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + expectedOutput: `1287f188ad4d7ab0d9ff69b3c29cb11f861389532d8cb9337181da2e8cfc74a84927e8c0dd7a28a32fd485afe694259a63c199b199b95edd87c7aa95329feac340f2b78b72956a85f367044d821766b1b7135815571df44900695f1518cf3ae38ecb650f + +Tag: a8f04c4d93bbef82bef61a103371aef9`, + recipeConfig: [ + { + "op": "AES加密", + "args": [ + {"option": "十六进制", "string": "2d767f6e9333d1c77581946e160b2b7368c2cdd5e2b80f04ca09d64e02afbfe1"}, + {"option": "十六进制", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, + "GCM", "十六进制", "十六进制", + {"option": "UTF8", "string": "additional data"} + ] + } + ], + }, + { + name: "AES Encrypt: AES-256-ECB, Binary", + input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + expectedOutput: "7e8521ba3f356ef692a51841807e141464aadc07bbc0ef2b628b8745bae356d245682a220688afca7be987b60cb120681ed42680ee93a67065619a3beaac11111a6cd88a6afa9e367722cb57df343f8548f2d691b295184da4ed5f3b763aaa8558502cb348ab58e81986337096e90caa", + recipeConfig: [ + { + "op": "AES加密", + "args": [ + {"option": "十六进制", "string": "2d767f6e9333d1c77581946e160b2b7368c2cdd5e2b80f04ca09d64e02afbfe1"}, + {"option": "十六进制", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, + "ECB", "十六进制", "十六进制", + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "DES Encrypt: no key", + input: "", + expectedOutput: `无效的key长度: 0 字节 + +DES uses a key length of 8 bytes (64 bits).`, + recipeConfig: [ + { + "op": "DES加密", + "args": [ + {"option": "十六进制", "string": ""}, + {"option": "十六进制", "string": ""}, + "CBC", "十六进制", "十六进制" + ] + } + ], + }, + { + name: "DES Encrypt: DES-CBC, Binary", + input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + expectedOutput: "6500defb824b0eb8ccbf1fa9689c6f5bcc65247d93ecb0e573232824bca82dd41e2361f8fd82ef187de9f3b74f7ba3ca2b4e735f3ca6304fb8dd1675933c576424b1ea72b3219bdab62fce56d49c820d5ac02a4702a6d688e90b0933de97da21e4829e5cf85caae8", + recipeConfig: [ + { + "op": "DES加密", + "args": [ + {"option": "十六进制", "string": "58345efb0a64e87e"}, + {"option": "十六进制", "string": "533ed1378bfd929e"}, + "CBC", "十六进制", "十六进制" + ] + } + ], + }, + { + name: "DES Encrypt: DES-CFB, Binary", + input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + expectedOutput: "09015087e15b09374bc9edba80ce41e6809e332fc1e988858749fb2f4ebbd6483a6fce01a43271280c07c90e13d517729acac45beef7d088339eb7e084bbbb7459fc8bb592d2ca76b90066dc79b1fbc5e016208e1d02c6e48ab675530f8040e53e1a138b", + recipeConfig: [ + { + "op": "DES加密", + "args": [ + {"option": "十六进制", "string": "58345efb0a64e87e"}, + {"option": "十六进制", "string": "533ed1378bfd929e"}, + "CFB", "十六进制", "十六进制" + ] + } + ], + }, + { + name: "DES Encrypt: DES-OFB, Binary", + input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + expectedOutput: "09015087e15b09374d8879bac14dbad851dd08fb131353a8c510acc4570e97720dd159465f1c7da3cac4a50521e1c1ab87e8cf5b0aa0c1d2eaa8a1ed914a26c13b2b0a76a368f08812fc7fa4b7c047f27df0c35e5f53b8a20e2ffc10e55d388cae8070db", + recipeConfig: [ + { + "op": "DES加密", + "args": [ + {"option": "十六进制", "string": "58345efb0a64e87e"}, + {"option": "十六进制", "string": "533ed1378bfd929e"}, + "OFB", "十六进制", "十六进制" + ] + } + ], + }, + { + // play.golang.org/p/4Qm2hfLGsqc + name: "DES Encrypt: DES-CTR, Binary", + input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + expectedOutput: "09015087e15b0937c462fd5974af0c4b5880de136a5680453c99f4500628cbeca769623515d836985110b93eacfea7fa4a7b2b3cb4f67acbb5f7e8ddb5a5d445da74bf6572b0a874befa3888c81110776388e400afd8dc908dcc0c018c7753355f8a1c9f", + recipeConfig: [ + { + "op": "DES加密", + "args": [ + {"option": "十六进制", "string": "58345efb0a64e87e"}, + {"option": "十六进制", "string": "533ed1378bfd929e"}, + "CTR", "十六进制", "十六进制" + ] + } + ], + }, + { + name: "DES Encrypt: DES-ECB, Binary", + input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + expectedOutput: "8dea4c6a35d5f6a419232159a0b039798d0a0b20fd1e559b1d04f8eb1120e8bca6ed5b3a4bc2b23d3b62312e6085d9e837677569fe79a65eba7cb4a2969e099fc1bd649e9c8aeb2c4c519e085db6974819257c20fde70acabc976308cc41635038c91acf5eefff1e", + recipeConfig: [ + { + "op": "DES加密", + "args": [ + {"option": "十六进制", "string": "58345efb0a64e87e"}, + {"option": "十六进制", "string": "533ed1378bfd929e"}, + "ECB", "十六进制", "十六进制" + ] + } + ], + }, + { + name: "Triple DES Encrypt: no key", + input: "", + expectedOutput: `无效的key长度: 0 字节 + +Triple DES uses a key length of 24 bytes (192 bits).`, + recipeConfig: [ + { + "op": "3DES加密", + "args": [ + {"option": "十六进制", "string": ""}, + {"option": "十六进制", "string": ""}, + "CBC", "十六进制", "十六进制" + ] + } + ], + }, + { + name: "Triple DES Encrypt: DES-EDE3-CBC, Binary", + input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + expectedOutput: "f826c9116ea932eb7027a810b5ce21109c4ef2563c9f3ba5e2518f72484e88f8d3f6ff3f334f64bb6bb9ff91b70f6f29c037b10dee5fe16d7f0f41c9a7ecdd83f113a1dd66ab70783ee458c2366bf5fbc016f7c168c43c11d607692a3280e3750a6154a86b62c48d", + recipeConfig: [ + { + "op": "3DES加密", + "args": [ + {"option": "十六进制", "string": "190da55fb54b9e7dd6de05f43bf3347ef203cd34a5829b23"}, + {"option": "十六进制", "string": "14f67ac044a84da6"}, + "CBC", "十六进制", "十六进制" + ] + } + ], + }, + { + name: "Triple DES Encrypt: DES-EDE3-CFB, Binary", + input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + expectedOutput: "874d32cd7bdae52c3690875e265a2fac7ced685e5ec4436a6bb5a5c18be185f4526683a5bc7ae86f00523034fb725ab4c8285a6967ccca1b76f6331718c26e12ea67fc924071f81ce0035a9dd31705bcd6467991cae5504d70424e6339459db5b33cbc8a", + recipeConfig: [ + { + "op": "3DES加密", + "args": [ + {"option": "十六进制", "string": "190da55fb54b9e7dd6de05f43bf3347ef203cd34a5829b23"}, + {"option": "十六进制", "string": "14f67ac044a84da6"}, + "CFB", "十六进制", "十六进制" + ] + } + ], + }, + { + name: "Triple DES Encrypt: DES-EDE3-OFB, Binary", + input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + expectedOutput: "874d32cd7bdae52c8f61672860f715d14819c0270320a8ad71083b38bd8954bbada3c77af641590b00a678524d748668fe3dfa83f71835c411cdbdd8e73a70656324b7faaba16e1d8dba260d8f965fe7a91110134c19076f1eeb46393038c22c559fe490", + recipeConfig: [ + { + "op": "3DES加密", + "args": [ + {"option": "十六进制", "string": "190da55fb54b9e7dd6de05f43bf3347ef203cd34a5829b23"}, + {"option": "十六进制", "string": "14f67ac044a84da6"}, + "OFB", "十六进制", "十六进制" + ] + } + ], + }, + { + // play.golang.org/p/RElT6pVeNz2 + name: "Triple DES Encrypt: DES-EDE3-CTR, Binary", + input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + expectedOutput: "874d32cd7bdae52cd8630d3ab2bf373e7110e13713caa6a8bfed9d9dd802d0ebe93128ac0d0f05abcc56237b75fb69207dba11e68ddc4b0118a4c75e7248bbd80aaba4dd4436642546ec6ca7fa7526f3b0018ed5194c409dc2c1484530b968af554984f3", + recipeConfig: [ + { + "op": "3DES加密", + "args": [ + {"option": "十六进制", "string": "190da55fb54b9e7dd6de05f43bf3347ef203cd34a5829b23"}, + {"option": "十六进制", "string": "14f67ac044a84da6"}, + "CTR", "十六进制", "十六进制" + ] + } + ], + }, + { + name: "Triple DES Encrypt: DES-EDE3-ECB Binary", + input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + expectedOutput: "aa81f23d1b3abebd68ac560e051a711c2923843beecddb0f7fe4113bd1874e73cccf3a2a494bb011e154ca2737b4d0eb5978a10316361074ed368d85d5aff5c8555ea101b0a468e58780a74c7830c561674c183c972a2b48931adf789cb16df304e169500f8c95ad", + recipeConfig: [ + { + "op": "3DES加密", + "args": [ + {"option": "十六进制", "string": "190da55fb54b9e7dd6de05f43bf3347ef203cd34a5829b23"}, + {"option": "十六进制", "string": "14f67ac044a84da6"}, + "ECB", "十六进制", "十六进制" + ] + } + ], + }, + { + name: "AES Decrypt: no key", + input: "", + expectedOutput: `无效的Key长度: 0 字节 + +根据Key的长度,会应用以下不同算法: + 16字节 = AES-128 + 24字节 = AES-192 + 32字节 = AES-256`, + recipeConfig: [ + { + "op": "AES解密", + "args": [ + {"option": "十六进制", "string": ""}, + {"option": "十六进制", "string": ""}, + "CBC", "十六进制", "原始", + {"option": "十六进制", "string": ""}, + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Decrypt: AES-128-CBC with IV0, ASCII", + input: "2ef6c3fdb1314b5c2c326a2087fe1a82d5e73bf605ec8431d73e847187fc1c8fbbe969c177df1ecdf8c13f2f505f9498", + expectedOutput: "The quick brown fox jumps over the lazy dog.", + recipeConfig: [ + { + "op": "AES解密", + "args": [ + {"option": "十六进制", "string": "00112233445566778899aabbccddeeff"}, + {"option": "十六进制", "string": "00000000000000000000000000000000"}, + "CBC", "十六进制", "原始", + {"option": "十六进制", "string": ""}, + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Decrypt: AES-128-CTR with IV0, ASCII", + input: "a98c9e8e3b7c894384d740e4f0f4ed0be2bbb1e0e13a255812c3c6b0a629e4ad759c075b2469c6f4fb2c0cf9", + expectedOutput: "The quick brown fox jumps over the lazy dog.", + recipeConfig: [ + { + "op": "AES解密", + "args": [ + {"option": "十六进制", "string": "00112233445566778899aabbccddeeff"}, + {"option": "十六进制", "string": "00000000000000000000000000000000"}, + "CTR", "十六进制", "原始", + {"option": "十六进制", "string": ""}, + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Decrypt: AES-128-CBC with IV, ASCII", + input: "4fa077d50cc71a57393e7b542c4e3aea0fb75383b97083f2f568ffc13c0e7a47502ec6d9f25744a061a3a5e55fe95e8d", + expectedOutput: "The quick brown fox jumps over the lazy dog.", + recipeConfig: [ + { + "op": "AES解密", + "args": [ + {"option": "十六进制", "string": "00112233445566778899aabbccddeeff"}, + {"option": "十六进制", "string": "00112233445566778899aabbccddeeff"}, + "CBC", "十六进制", "原始", + {"option": "十六进制", "string": ""}, + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Decrypt: AES-128-CFB, ASCII", + input: "369e1c9e5a85b0520f3e61eecc37759246ad0a02cae7a99a3d250ae39cad4743385375cf63720d52ae8cdfb9", + expectedOutput: "The quick brown fox jumps over the lazy dog.", + recipeConfig: [ + { + "op": "AES解密", + "args": [ + {"option": "十六进制", "string": "00112233445566778899aabbccddeeff"}, + {"option": "十六进制", "string": "00112233445566778899aabbccddeeff"}, + "CFB", "十六进制", "原始", + {"option": "十六进制", "string": ""}, + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Decrypt: AES-128-OFB, ASCII", + input: "369e1c9e5a85b0520f3e61eecc37759288cb378c5fa9c675bd6c4ede0ae6a925eaebc8e0a6162d2a000ddc0f", + expectedOutput: "The quick brown fox jumps over the lazy dog.", + recipeConfig: [ + { + "op": "AES解密", + "args": [ + {"option": "十六进制", "string": "00112233445566778899aabbccddeeff"}, + {"option": "十六进制", "string": "00112233445566778899aabbccddeeff"}, + "OFB", "十六进制", "原始", + {"option": "十六进制", "string": ""}, + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Decrypt: AES-128-CTR, ASCII", + input: "369e1c9e5a85b0520f3e61eecc37759206f6f1ba63527af96fae3b15a921844df2e542902a4f0525dbb4146b", + expectedOutput: "The quick brown fox jumps over the lazy dog.", + recipeConfig: [ + { + "op": "AES解密", + "args": [ + {"option": "十六进制", "string": "00112233445566778899aabbccddeeff"}, + {"option": "十六进制", "string": "00112233445566778899aabbccddeeff"}, + "CTR", "十六进制", "原始", + {"option": "十六进制", "string": ""}, + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Decrypt: AES-128-ECB, ASCII", + input: "2ef6c3fdb1314b5c2c326a2087fe1a8238c5a5db7dff38f6f4eb75b2e55cab3d8d6113eb8d3517223b4545fcdb4c5a48", + expectedOutput: "The quick brown fox jumps over the lazy dog.", + recipeConfig: [ + { + "op": "AES解密", + "args": [ + {"option": "十六进制", "string": "00112233445566778899aabbccddeeff"}, + {"option": "十六进制", "string": ""}, + "ECB", "十六进制", "原始", + {"option": "十六进制", "string": ""}, + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Decrypt: AES-128-GCM, ASCII", + input: "d0bcace0fa3a214b0ac3cbb4ac2caaf97b965f172f66d2a4ec6304a15a4072f1b28a6f9b80473f86bfa47b2c", + expectedOutput: "The quick brown fox jumps over the lazy dog.", + recipeConfig: [ + { + "op": "AES解密", + "args": [ + {"option": "十六进制", "string": "00112233445566778899aabbccddeeff"}, + {"option": "十六进制", "string": ""}, + "GCM", "十六进制", "原始", + {"option": "十六进制", "string": "16a3e732a605cc9ca29108f742ca0743"}, + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Decrypt: AES-128-GCM, ASCII, AAD", + input: "daa58faa056c52756aa488aeafbd265b6effcf4eca58220a97b0005b1a9b1e1c9e7a6725d35f5f79b9493de7", + expectedOutput: "The quick brown fox jumps over the lazy dog.", + recipeConfig: [ + { + "op": "AES解密", + "args": [ + {"option": "十六进制", "string": "00112233445566778899aabbccddeeff"}, + {"option": "十六进制", "string": "ffeeddccbbaa99887766554433221100"}, + "GCM", "十六进制", "原始", + {"option": "十六进制", "string": "3b5378917f67b0aade9891fc6c291646"}, + {"option": "UTF8", "string": "additional data"} + ] + } + ], + }, + { + name: "AES Decrypt: AES-128-CBC, Binary", + input: "bf2ccb148e5df181a46f39764047e24fc94cc46bbe6c8d160fc25a977e4b630883e9e04d3eeae3ccbb2d57a4c22e61909f2b6d7b24940abe95d356ce986294270d0513e0ffe7a9928fa6669e1aaae4379310281dc27c0bb9e254684b2ecd7f5f944c8218f3bc680570399a508dfe4b65", + expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + recipeConfig: [ + { + "op": "AES解密", + "args": [ + {"option": "十六进制", "string": "51e201d463698ef5f717f71f5b4712af"}, + {"option": "十六进制", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, + "CBC", "十六进制", "十六进制", + {"option": "十六进制", "string": ""}, + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Decrypt: AES-128-CFB, Binary", + input: "17211941bb2fa43d54d9fa59072436422a55be7a2be164cf5ec4e50e7a0035094ab684dab8d45a4515ae95c4136ded98898f74d4ecc4ac57ae682a985031ecb7518ddea6c8d816349801aa22ff0b6ac1784d169060efcd9fb77d564477038eb09bb4e1ce", + expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + recipeConfig: [ + { + "op": "AES解密", + "args": [ + {"option": "十六进制", "string": "51e201d463698ef5f717f71f5b4712af"}, + {"option": "十六进制", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, + "CFB", "十六进制", "十六进制", + {"option": "十六进制", "string": ""}, + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Decrypt: AES-128-OFB, Binary", + input: "17211941bb2fa43d54d9fa5907243642bfd805201c130c8600566720cf87562011f0872598f1e69cfe541bb864de7ed68201e0a34284157b581984dab3fe2cb0f20cb80d0046740df3e149ec4c92c0e81f2dc439a6f3a05c5ef505eae6308b301c673cfa", + expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + recipeConfig: [ + { + "op": "AES解密", + "args": [ + {"option": "十六进制", "string": "51e201d463698ef5f717f71f5b4712af"}, + {"option": "十六进制", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, + "OFB", "十六进制", "十六进制", + {"option": "十六进制", "string": ""}, + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Decrypt: AES-128-CTR, Binary", + input: "17211941bb2fa43d54d9fa5907243642baf08c837003bf24d7b81a911ce41bd31de8a92f6dc6d11135b70c73ea167c3fc4ea78234f58652d25e23245dbcb895bf4165092d0515ae8f14230f8a34b06957f24ba4b24db741490e7edcd6e5310945cc159fc", + expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + recipeConfig: [ + { + "op": "AES解密", + "args": [ + {"option": "十六进制", "string": "51e201d463698ef5f717f71f5b4712af"}, + {"option": "十六进制", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, + "CTR", "十六进制", "十六进制", + {"option": "十六进制", "string": ""}, + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Decrypt: AES-128-GCM, Binary", + input: "5a29debb5c5f38cdf8aee421bd94dbbf3399947faddf205f88b3ad8ecb0c51214ec0e28bf78942dfa212d7eb15259bbdcac677b4c05f473eeb9331d74f31d441d97d56eb5c73b586342d72128ca528813543dc0fc7eddb7477172cc9194c18b2e1383e4e", + expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + recipeConfig: [ + { + "op": "AES解密", + "args": [ + {"option": "十六进制", "string": "51e201d463698ef5f717f71f5b4712af"}, + {"option": "十六进制", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, + "GCM", "十六进制", "十六进制", + {"option": "十六进制", "string": "70fad2ca19412c20f40fd06918736e56"}, + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Decrypt: AES-128-GCM, Binary, AAD", + input: "5a29debb5c5f38cdf8aee421bd94dbbf3399947faddf205f88b3ad8ecb0c51214ec0e28bf78942dfa212d7eb15259bbdcac677b4c05f473eeb9331d74f31d441d97d56eb5c73b586342d72128ca528813543dc0fc7eddb7477172cc9194c18b2e1383e4e", + expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + recipeConfig: [ + { + "op": "AES解密", + "args": [ + {"option": "十六进制", "string": "51e201d463698ef5f717f71f5b4712af"}, + {"option": "十六进制", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, + "GCM", "十六进制", "十六进制", + {"option": "十六进制", "string": "61cc4b70809452b0b3e38f913fa0a109"}, + {"option": "UTF8", "string": "additional data"} + ] + } + ], + }, + { + name: "AES Decrypt: AES-128-ECB, Binary", + input: "869c057637a58cc3363bcc4bcfa62702abf85dff44300eb9fdcfb9d845772c8acb557c8d540baae2489c6758abef83d81b74239bef87c6c944c1b00ca160882bc15be9a6a3de4e6a50a2eab8b635c634027ed7eae4c1d2f08477c38b7dc24f6915da235bc3051f3a50736b14db8863e4", + expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + recipeConfig: [ + { + "op": "AES解密", + "args": [ + {"option": "十六进制", "string": "51e201d463698ef5f717f71f5b4712af"}, + {"option": "十六进制", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, + "ECB", "十六进制", "十六进制", + {"option": "十六进制", "string": ""}, + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Decrypt: AES-192-CBC, Binary", + input: "1aec90cd7f629ef68243881f3e2b793a548cbcdad69631995a6bd0c8aea1e948d8a5f3f2b7e7f9b77da77434c92a6257a9f57e937b883f4400511b990888a0b1d27c0a4b7f298e6f50b563135edc9fa7d8eceb6bc8163e6153a20cf07aa1e705bc5cb3a37b0452b4019cef8000d7c1b7", + expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + recipeConfig: [ + { + "op": "AES解密", + "args": [ + {"option": "十六进制", "string": "6801ed503c9d96ee5f9d78b07ab1b295dba3c2adf81c7816"}, + {"option": "十六进制", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, + "CBC", "十六进制", "十六进制", + {"option": "十六进制", "string": ""}, + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Decrypt: AES-192-CFB, Binary", + input: "fc370a6c013b3c05430fbce810cb97d39cb0a587320a4c1b57d0c0d08e93cb0d1221abba9df09b4b1332ce923b289f92000e6b4f7fbc55dfdab9179081d8c36ef4a0e3d3a49f1564715c5d3e88f8bf6d3dd77944f22f99a03b5535a3cd47bc44d4a9665c", + expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + recipeConfig: [ + { + "op": "AES解密", + "args": [ + {"option": "十六进制", "string": "6801ed503c9d96ee5f9d78b07ab1b295dba3c2adf81c7816"}, + {"option": "十六进制", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, + "CFB", "十六进制", "十六进制", + {"option": "十六进制", "string": ""}, + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Decrypt: AES-192-OFB, Binary", + input: "fc370a6c013b3c05430fbce810cb97d33605d11b2531c8833bc3e818003bbd7dd58b2a38d10d44d25d11bd96228b264a4d2aad1d0a7af2cfad0e70c1ade305433e95cb0ee693447f6877a59a4be5c070d19afba23ff10caf5ecfa7a9c2877b8df23d61f2", + expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + recipeConfig: [ + { + "op": "AES解密", + "args": [ + {"option": "十六进制", "string": "6801ed503c9d96ee5f9d78b07ab1b295dba3c2adf81c7816"}, + {"option": "十六进制", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, + "OFB", "十六进制", "十六进制", + {"option": "十六进制", "string": ""}, + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Decrypt: AES-192-CTR, Binary", + input: "fc370a6c013b3c05430fbce810cb97d340525303ae59c5e9b73ad5ff3e65ce3abf00431e0a292d990f732a397de589420827beb1c28623c56972eb2ddf0cf3f82e3c30e155df7f64a530419c28fc51a9091c73df78e73958bee1d1acd8676c9c0f1915ca", + expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + recipeConfig: [ + { + "op": "AES解密", + "args": [ + {"option": "十六进制", "string": "6801ed503c9d96ee5f9d78b07ab1b295dba3c2adf81c7816"}, + {"option": "十六进制", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, + "CTR", "十六进制", "十六进制", + {"option": "十六进制", "string": ""}, + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Decrypt: AES-192-GCM, Binary", + input: "318b479d919d506f0cd904f2676fab263a7921b6d7e0514f36e03ae2333b77fa66ef5600babcb2ee9718aeb71fc357412343c1f2cb351d8715bb0aedae4a6468124f9c4aaf6a721b306beddbe63a978bec8baeeba4b663be33ee5bc982746bd4aed1c38b", + expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + recipeConfig: [ + { + "op": "AES解密", + "args": [ + {"option": "十六进制", "string": "6801ed503c9d96ee5f9d78b07ab1b295dba3c2adf81c7816"}, + {"option": "十六进制", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, + "GCM", "十六进制", "十六进制", + {"option": "十六进制", "string": "86db597d5302595223cadbd990f1309b"}, + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Decrypt: AES-192-GCM, Binary, AAD", + input: "318b479d919d506f0cd904f2676fab263a7921b6d7e0514f36e03ae2333b77fa66ef5600babcb2ee9718aeb71fc357412343c1f2cb351d8715bb0aedae4a6468124f9c4aaf6a721b306beddbe63a978bec8baeeba4b663be33ee5bc982746bd4aed1c38b", + expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + recipeConfig: [ + { + "op": "AES解密", + "args": [ + {"option": "十六进制", "string": "6801ed503c9d96ee5f9d78b07ab1b295dba3c2adf81c7816"}, + {"option": "十六进制", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, + "GCM", "十六进制", "十六进制", + {"option": "十六进制", "string": "aeedf3e6ca4201577c0cf3e9ce58159d"}, + {"option": "UTF8", "string": "additional data"} + ] + } + ], + }, + { + name: "AES Decrypt: AES-192-ECB, Binary", + input: "56ef533db50a3b33951a76acede52b7d54fbae7fb07da20daa3e2731e5721ee4c13ab15ac80748c14dece982310530ad65480512a4cf70201473fb7bc3480446bc86b1ff9b4517c4c1f656bc236fab1aca276ae5af25f5871b671823f3cb3e426da059dd83a13f125bd6cfe600c331b0", + expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + recipeConfig: [ + { + "op": "AES解密", + "args": [ + {"option": "十六进制", "string": "6801ed503c9d96ee5f9d78b07ab1b295dba3c2adf81c7816"}, + {"option": "十六进制", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, + "ECB", "十六进制", "十六进制", + {"option": "十六进制", "string": ""}, + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Decrypt: AES-256-CBC, Binary", + input: "bc60a7613559e23e8a7be8e98a1459003fdb036f33368d8a30156c51464b49472705a4ddae05da96956ce058bb180dd301c5fd58bf6a2ded0d7dd4da85fd5ba43a4297691532bf7f4cd92bfcfd3704faf2f9bd5425049b34433ba90fb85c80646e6cb09ee4e4059e7cd753a2fef8bbad", + expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + recipeConfig: [ + { + "op": "AES解密", + "args": [ + {"option": "十六进制", "string": "2d767f6e9333d1c77581946e160b2b7368c2cdd5e2b80f04ca09d64e02afbfe1"}, + {"option": "十六进制", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, + "CBC", "十六进制", "十六进制", + {"option": "十六进制", "string": ""}, + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Decrypt: AES-256-CFB, Binary", + input: "5dc73709da5cb0ac914ae4bcb621fd75169eac5ff13a2dde573f6380ff812e8ddb58f0e9afaec1ff0d6d2af0659e10c05b714ec97481a15f4a7aeb4c6ea84112ce897459b54ed9e77a794f023f2bef1901f013cf435432fca5fb59e2be781916247d2334", + expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + recipeConfig: [ + { + "op": "AES解密", + "args": [ + {"option": "十六进制", "string": "2d767f6e9333d1c77581946e160b2b7368c2cdd5e2b80f04ca09d64e02afbfe1"}, + {"option": "十六进制", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, + "CFB", "十六进制", "十六进制", + {"option": "十六进制", "string": ""}, + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Decrypt: AES-256-OFB, Binary", + input: "5dc73709da5cb0ac914ae4bcb621fd75b6e1f909b88733f784b1df8a52dc200440a1076415d009a7c12cac1e8ab76bdc290e6634cd5bf8a416fda8dcfd7910e55fe9d1148cd85d7a59adad39ab089e111d8f8da246e2e874cf5d9ab7552af6308320a5ab", + expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + recipeConfig: [ + { + "op": "AES解密", + "args": [ + {"option": "十六进制", "string": "2d767f6e9333d1c77581946e160b2b7368c2cdd5e2b80f04ca09d64e02afbfe1"}, + {"option": "十六进制", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, + "OFB", "十六进制", "十六进制", + {"option": "十六进制", "string": ""}, + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Decrypt: AES-256-CTR, Binary", + input: "5dc73709da5cb0ac914ae4bcb621fd7591356d4169898c986a90b193f4d1f0d5cba1d10b2bfc5aee8a48dce9dba174cecf56f92dddf7eb306d78360000eea7bcb50f696d84a3757a822800ed68f9edf118dc61406bacf64f022717d8cb6010049bf75d7e", + expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + recipeConfig: [ + { + "op": "AES解密", + "args": [ + {"option": "十六进制", "string": "2d767f6e9333d1c77581946e160b2b7368c2cdd5e2b80f04ca09d64e02afbfe1"}, + {"option": "十六进制", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, + "CTR", "十六进制", "十六进制", + {"option": "十六进制", "string": ""}, + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Decrypt: AES-256-GCM, Binary", + input: "1287f188ad4d7ab0d9ff69b3c29cb11f861389532d8cb9337181da2e8cfc74a84927e8c0dd7a28a32fd485afe694259a63c199b199b95edd87c7aa95329feac340f2b78b72956a85f367044d821766b1b7135815571df44900695f1518cf3ae38ecb650f", + expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + recipeConfig: [ + { + "op": "AES解密", + "args": [ + {"option": "十六进制", "string": "2d767f6e9333d1c77581946e160b2b7368c2cdd5e2b80f04ca09d64e02afbfe1"}, + {"option": "十六进制", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, + "GCM", "十六进制", "十六进制", + {"option": "十六进制", "string": "821b1e5f32dad052e502775a523d957a"}, + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "AES Decrypt: AES-256-GCM, Binary, AAD", + input: "1287f188ad4d7ab0d9ff69b3c29cb11f861389532d8cb9337181da2e8cfc74a84927e8c0dd7a28a32fd485afe694259a63c199b199b95edd87c7aa95329feac340f2b78b72956a85f367044d821766b1b7135815571df44900695f1518cf3ae38ecb650f", + expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + recipeConfig: [ + { + "op": "AES解密", + "args": [ + {"option": "十六进制", "string": "2d767f6e9333d1c77581946e160b2b7368c2cdd5e2b80f04ca09d64e02afbfe1"}, + {"option": "十六进制", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, + "GCM", "十六进制", "十六进制", + {"option": "十六进制", "string": "a8f04c4d93bbef82bef61a103371aef9"}, + {"option": "UTF8", "string": "additional data"} + ] + } + ], + }, + { + name: "AES Decrypt: AES-256-ECB, Binary", + input: "7e8521ba3f356ef692a51841807e141464aadc07bbc0ef2b628b8745bae356d245682a220688afca7be987b60cb120681ed42680ee93a67065619a3beaac11111a6cd88a6afa9e367722cb57df343f8548f2d691b295184da4ed5f3b763aaa8558502cb348ab58e81986337096e90caa", + expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + recipeConfig: [ + { + "op": "AES解密", + "args": [ + {"option": "十六进制", "string": "2d767f6e9333d1c77581946e160b2b7368c2cdd5e2b80f04ca09d64e02afbfe1"}, + {"option": "十六进制", "string": "1748e7179bd56570d51fa4ba287cc3e5"}, + "ECB", "十六进制", "十六进制", + {"option": "十六进制", "string": ""}, + {"option": "十六进制", "string": ""} + ] + } + ], + }, + { + name: "DES Decrypt: no key", + input: "", + expectedOutput: `无效的key长度: 0 字节 + +DES uses a key length of 8 bytes (64 bits).`, + recipeConfig: [ + { + "op": "DES解密", + "args": [ + {"option": "十六进制", "string": ""}, + {"option": "十六进制", "string": ""}, + "CBC", "十六进制", "十六进制" + ] + } + ], + }, + { + name: "DES Decrypt: DES-CBC, Binary", + input: "6500defb824b0eb8ccbf1fa9689c6f5bcc65247d93ecb0e573232824bca82dd41e2361f8fd82ef187de9f3b74f7ba3ca2b4e735f3ca6304fb8dd1675933c576424b1ea72b3219bdab62fce56d49c820d5ac02a4702a6d688e90b0933de97da21e4829e5cf85caae8", + expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + recipeConfig: [ + { + "op": "DES解密", + "args": [ + {"option": "十六进制", "string": "58345efb0a64e87e"}, + {"option": "十六进制", "string": "533ed1378bfd929e"}, + "CBC", "十六进制", "十六进制" + ] + } + ], + }, + { + name: "DES Decrypt: DES-CFB, Binary", + input: "09015087e15b09374bc9edba80ce41e6809e332fc1e988858749fb2f4ebbd6483a6fce01a43271280c07c90e13d517729acac45beef7d088339eb7e084bbbb7459fc8bb592d2ca76b90066dc79b1fbc5e016208e1d02c6e48ab675530f8040e53e1a138b", + expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + recipeConfig: [ + { + "op": "DES解密", + "args": [ + {"option": "十六进制", "string": "58345efb0a64e87e"}, + {"option": "十六进制", "string": "533ed1378bfd929e"}, + "CFB", "十六进制", "十六进制" + ] + } + ], + }, + { + name: "DES Decrypt: DES-OFB, Binary", + input: "09015087e15b09374d8879bac14dbad851dd08fb131353a8c510acc4570e97720dd159465f1c7da3cac4a50521e1c1ab87e8cf5b0aa0c1d2eaa8a1ed914a26c13b2b0a76a368f08812fc7fa4b7c047f27df0c35e5f53b8a20e2ffc10e55d388cae8070db", + expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + recipeConfig: [ + { + "op": "DES解密", + "args": [ + {"option": "十六进制", "string": "58345efb0a64e87e"}, + {"option": "十六进制", "string": "533ed1378bfd929e"}, + "OFB", "十六进制", "十六进制" + ] + } + ], + }, + { + // play.golang.org/p/FpvqncmPk7R + name: "DES Decrypt: DES-CTR, Binary", + input: "09015087e15b0937ab0ae5a84d66e520893690a6ea066382bf1330e8876cb3aa82ccc634f8f0d458bbe0257df6f4637cdac89f311168ba91208a21ba4bdd13c4b1a92cb93b33364b5b94a5d3d7fba68f6eed5807d9f5afeb7fbffcd94792131d264004ae", + expectedOutput: "7a0e643132750e96b76dc9efa7810bea2b8feaa5b97887e44f96c0e6d506cc4dd4665683c6f63139221f8d887fd0a05b39741f8a67d87d6ac6f8dc6b668bd3e4a97b8bd3a19eafd5cdf50c3e1b3f17d61087d0b67cf6db31fec338b75f5954942c852829", + recipeConfig: [ + { + "op": "DES解密", + "args": [ + {"option": "十六进制", "string": "58345efb0a64e87e"}, + {"option": "十六进制", "string": "533ed1378bfd929e"}, + "CTR", "十六进制", "十六进制" + ] + } + ], + }, + { + name: "DES Decrypt: DES-ECB, Binary", + input: "8dea4c6a35d5f6a419232159a0b039798d0a0b20fd1e559b1d04f8eb1120e8bca6ed5b3a4bc2b23d3b62312e6085d9e837677569fe79a65eba7cb4a2969e099fc1bd649e9c8aeb2c4c519e085db6974819257c20fde70acabc976308cc41635038c91acf5eefff1e", + expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + recipeConfig: [ + { + "op": "DES解密", + "args": [ + {"option": "十六进制", "string": "58345efb0a64e87e"}, + {"option": "十六进制", "string": "533ed1378bfd929e"}, + "ECB", "十六进制", "十六进制" + ] + } + ], + }, + { + name: "Triple DES Decrypt: no key", + input: "", + expectedOutput: `无效的key长度: 0 字节 + +Triple DES uses a key length of 24 bytes (192 bits).`, + recipeConfig: [ + { + "op": "3DES解密", + "args": [ + {"option": "十六进制", "string": ""}, + {"option": "十六进制", "string": ""}, + "CBC", "十六进制", "十六进制" + ] + } + ], + }, + { + name: "Triple DES Decrypt: DES-EDE3-CBC, Binary", + input: "f826c9116ea932eb7027a810b5ce21109c4ef2563c9f3ba5e2518f72484e88f8d3f6ff3f334f64bb6bb9ff91b70f6f29c037b10dee5fe16d7f0f41c9a7ecdd83f113a1dd66ab70783ee458c2366bf5fbc016f7c168c43c11d607692a3280e3750a6154a86b62c48d", + expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + recipeConfig: [ + { + "op": "3DES解密", + "args": [ + {"option": "十六进制", "string": "190da55fb54b9e7dd6de05f43bf3347ef203cd34a5829b23"}, + {"option": "十六进制", "string": "14f67ac044a84da6"}, + "CBC", "十六进制", "十六进制" + ] + } + ], + }, + { + name: "Triple DES Decrypt: DES-EDE3-CFB, Binary", + input: "874d32cd7bdae52c3690875e265a2fac7ced685e5ec4436a6bb5a5c18be185f4526683a5bc7ae86f00523034fb725ab4c8285a6967ccca1b76f6331718c26e12ea67fc924071f81ce0035a9dd31705bcd6467991cae5504d70424e6339459db5b33cbc8a", + expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + recipeConfig: [ + { + "op": "3DES解密", + "args": [ + {"option": "十六进制", "string": "190da55fb54b9e7dd6de05f43bf3347ef203cd34a5829b23"}, + {"option": "十六进制", "string": "14f67ac044a84da6"}, + "CFB", "十六进制", "十六进制" + ] + } + ], + }, + { + name: "Triple DES Decrypt: DES-EDE3-OFB, Binary", + input: "874d32cd7bdae52c8f61672860f715d14819c0270320a8ad71083b38bd8954bbada3c77af641590b00a678524d748668fe3dfa83f71835c411cdbdd8e73a70656324b7faaba16e1d8dba260d8f965fe7a91110134c19076f1eeb46393038c22c559fe490", + expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + recipeConfig: [ + { + "op": "3DES解密", + "args": [ + {"option": "十六进制", "string": "190da55fb54b9e7dd6de05f43bf3347ef203cd34a5829b23"}, + {"option": "十六进制", "string": "14f67ac044a84da6"}, + "OFB", "十六进制", "十六进制" + ] + } + ], + }, + { + // play.golang.org/p/iBacN9kX_RO + name: "Triple DES Decrypt: DES-EDE3-CTR, Binary", + input: "874d32cd7bdae52c254687e2d7e7093b077af2ec70878f99315f52a21ded5fb10c80a47e6271384335ac47376c758f675484fd7b8be9568aaec643f0d15cffdf3fe54ef3a1b2da50d5d8c7994d7a4a94e0a13a4d437443f0f1f39e93dd13ff06a80c66e4", + expectedOutput: "7a0e643132750e9625205bc6fb10dc848c53b7cb5a654d1242aecb6191ad3b5114727e5044a0ee11311575873c54829a80f9471ac473a0bbe5e791a23be75062f7e8f2210d998f9fbbaf3a5bb3dacd494d42d82950e3ab273f821eb979168315a80ad20f", + recipeConfig: [ + { + "op": "3DES解密", + "args": [ + {"option": "十六进制", "string": "190da55fb54b9e7dd6de05f43bf3347ef203cd34a5829b23"}, + {"option": "十六进制", "string": "14f67ac044a84da6"}, + "CTR", "十六进制", "十六进制" + ] + } + ], + }, + { + name: "Triple DES Decrypt: DES-EDE3-ECB, Binary", + input: "aa81f23d1b3abebd68ac560e051a711c2923843beecddb0f7fe4113bd1874e73cccf3a2a494bb011e154ca2737b4d0eb5978a10316361074ed368d85d5aff5c8555ea101b0a468e58780a74c7830c561674c183c972a2b48931adf789cb16df304e169500f8c95ad", + expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + recipeConfig: [ + { + "op": "3DES解密", + "args": [ + {"option": "十六进制", "string": "190da55fb54b9e7dd6de05f43bf3347ef203cd34a5829b23"}, + {"option": "十六进制", "string": "14f67ac044a84da6"}, + "ECB", "十六进制", "十六进制" + ] + } + ], + }, + { + name: "RC2 Encrypt: no key", + input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + expectedOutput: "d3644d898b51a544f690b506c3fd0caeb7a1e6097f7ea28f69b909a4d8805c9a05f4cade8b281d3f044fa069374efb90e94723622c86afc17caee394ffbee0abe627de299208460eb981c9d56f9df885091c6c89e2ee173264b2820b8e67675214e6545a05dc0d3f", + recipeConfig: [ + { + "op": "RC2加密", + "args": [ + {"option": "十六进制", "string": ""}, + {"option": "十六进制", "string": ""}, + "十六进制", "十六进制" + ] + } + ], + }, + { + name: "RC2 Encrypt: RC2-CBC, Binary", + input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + expectedOutput: "d25e5bc6c9311ef196d6f21cc4b0274b29fcca366aba5256406e02bf4ae628398f84e7d72ad92025ede76df4752d1510fe9c3492efb1dcf0be2cd41d619e10b9dd5a2304c2efbd3598d3b87f1a21f326d45e65537563436cfb6e4a41ec3733182ddc058f96f74a6c", + recipeConfig: [ + { + "op": "RC2加密", + "args": [ + {"option": "十六进制", "string": "eb970554bb213430f4bb4e5988a6a218"}, + {"option": "十六进制", "string": "ae817c784a097e0c"}, + "十六进制", "十六进制" + ] + } + ], + }, + { + name: "RC2 Encrypt: RC2-ECB, Binary", + input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + expectedOutput: "a160bf23b2a85eaa43d26753e51aaa899f162ec0da7280fffd41b705c5309c7fef2bbb56bf261cab4eadd3a5c69e0a67d45e426d1097187cc9a959b4d979a9d40df26f3dc8d030453fe27701438b78d3ce044330b4b5dca7832537ecf40b914f1b1dc16d4e6d7229", + recipeConfig: [ + { + "op": "RC2加密", + "args": [ + {"option": "十六进制", "string": "eb970554bb213430f4bb4e5988a6a218"}, + {"option": "十六进制", "string": ""}, + "十六进制", "十六进制" + ] + } + ], + }, + { + name: "RC2 Decrypt: no key", + input: "d3644d898b51a544f690b506c3fd0caeb7a1e6097f7ea28f69b909a4d8805c9a05f4cade8b281d3f044fa069374efb90e94723622c86afc17caee394ffbee0abe627de299208460eb981c9d56f9df885091c6c89e2ee173264b2820b8e67675214e6545a05dc0d3f", + expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + recipeConfig: [ + { + "op": "RC2解密", + "args": [ + {"option": "十六进制", "string": ""}, + {"option": "十六进制", "string": ""}, + "十六进制", "十六进制" + ] + } + ], + }, + { + name: "RC2 Decrypt: RC2-CBC, Binary", + input: "d25e5bc6c9311ef196d6f21cc4b0274b29fcca366aba5256406e02bf4ae628398f84e7d72ad92025ede76df4752d1510fe9c3492efb1dcf0be2cd41d619e10b9dd5a2304c2efbd3598d3b87f1a21f326d45e65537563436cfb6e4a41ec3733182ddc058f96f74a6c", + expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + recipeConfig: [ + { + "op": "RC2解密", + "args": [ + {"option": "十六进制", "string": "eb970554bb213430f4bb4e5988a6a218"}, + {"option": "十六进制", "string": "ae817c784a097e0c"}, + "十六进制", "十六进制" + ] + } + ], + }, + { + name: "RC2 Decrypt: RC2-ECB, Binary", + input: "a160bf23b2a85eaa43d26753e51aaa899f162ec0da7280fffd41b705c5309c7fef2bbb56bf261cab4eadd3a5c69e0a67d45e426d1097187cc9a959b4d979a9d40df26f3dc8d030453fe27701438b78d3ce044330b4b5dca7832537ecf40b914f1b1dc16d4e6d7229", + expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + recipeConfig: [ + { + "op": "RC2解密", + "args": [ + {"option": "十六进制", "string": "eb970554bb213430f4bb4e5988a6a218"}, + {"option": "十六进制", "string": ""}, + "十六进制", "十六进制" + ] + } + ], + }, + /* + The following expectedOutputs are generated with this Python script with pyCryptoDome + + from Crypto.Cipher import Blowfish + import binascii + + # Blowfish cipher parameters - key, mode, iv, segment_size, nonce + key = binascii.unhexlify("0011223344556677") + mode = Blowfish.MODE_CBC + kwargs = {} + iv = binascii.unhexlify("ffeeddccbbaa9988") + if mode in [Blowfish.MODE_CBC, Blowfish.MODE_CFB, Blowfish.MODE_OFB]: + kwargs = {"iv": iv} + if mode == Blowfish.MODE_CFB: + kwargs["segment_size"] = 64 + if mode == Blowfish.MODE_CTR: + nonce = binascii.unhexlify("0000000000000000") + nonce = nonce[:7] + kwargs["nonce"] = nonce + + cipher = Blowfish.new(key, mode, **kwargs) + + # Input data and padding + input_data = b"The quick brown fox jumps over the lazy dog." + if mode == Blowfish.MODE_ECB or mode == Blowfish.MODE_CBC: + padding_len = 8-(len(input_data) & 7) + for i in range(padding_len): + input_data += bytes([padding_len]) + + # Encrypted text + cipher_text = cipher.encrypt(input_data) + cipher_text = binascii.hexlify(cipher_text).decode("UTF-8") + + print("Encrypted: {}".format(cipher_text)) + */ + { + name: "Blowfish Encrypt: ECB, ASCII", + input: "The quick brown fox jumps over the lazy dog.", + expectedOutput: "f7784137ab1bf51546c0b120bdb7fed4509116e49283b35fab0e4292ac86251a9bf908330e3393815e3356bb26524027", + recipeConfig: [ + { + "op": "Blowfish加密", + "args": [ + {"option": "十六进制", "string": "0011223344556677"}, // Key + {"option": "十六进制", "string": "0000000000000000"}, // IV + "ECB", // Mode + "原始", // Input + "十六进制" // Output + ] + } + ], + }, + { + name: "Blowfish Encrypt: ECB, Binary", + input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + expectedOutput: "3d1bf0e87d83782d435a0ca58179ca290184867f52295af5c0fb4dcac7c6c68942906bb421d05925cc7d9cd21532376a0f6ae4c3f008b250381ffa9624f5eb697dbd44de48cf5593ea7dbf5842238474b546ceeb29f6cf327a7d13698786b8d14451f52fb0f5760a", + recipeConfig: [ + { + "op": "Blowfish加密", + "args": [ + {"option": "十六进制", "string": "0011223344556677"}, // Key + {"option": "十六进制", "string": "0000000000000000"}, // IV + "ECB", // Mode + "十六进制", // Input + "十六进制" // Output + ] + } + ], + }, + { + name: "Blowfish Decrypt: ECB, ASCII", + input: "f7784137ab1bf51546c0b120bdb7fed4509116e49283b35fab0e4292ac86251a9bf908330e3393815e3356bb26524027", + expectedOutput: "The quick brown fox jumps over the lazy dog.", + recipeConfig: [ + { + "op": "Blowfish解密", + "args": [ + {"option": "十六进制", "string": "0011223344556677"}, // Key + {"option": "十六进制", "string": "0000000000000000"}, // IV + "ECB", // Mode + "十六进制", // Input + "原始" // Output + ] + } + ], + }, + { + name: "Blowfish Decrypt: ECB, Binary", + input: "3d1bf0e87d83782d435a0ca58179ca290184867f52295af5c0fb4dcac7c6c68942906bb421d05925cc7d9cd21532376a0f6ae4c3f008b250381ffa9624f5eb697dbd44de48cf5593ea7dbf5842238474b546ceeb29f6cf327a7d13698786b8d14451f52fb0f5760a", + expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + recipeConfig: [ + { + "op": "Blowfish解密", + "args": [ + {"option": "十六进制", "string": "0011223344556677"}, // Key + {"option": "十六进制", "string": "0000000000000000"}, // IV + "ECB", // Mode + "十六进制", // Input + "十六进制" // Output + ] + } + ], + }, + { + name: "Blowfish Encrypt: CBC, ASCII", + input: "The quick brown fox jumps over the lazy dog.", + expectedOutput: "398433f39e938286a35fc240521435b6972f3fe96846b54ab9351aa5fa9e10a6a94074e883d1cb36cb9657c817274b60", + recipeConfig: [ + { + "op": "Blowfish加密", + "args": [ + {"option": "十六进制", "string": "0011223344556677"}, // Key + {"option": "十六进制", "string": "ffeeddccbbaa9988"}, // IV + "CBC", // Mode + "原始", // Input + "十六进制" // Output + ] + } + ], + }, + { + name: "Blowfish Encrypt: CBC, Binary", + input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + expectedOutput: "3b42c51465896524e66c2fd2404c8c2b4eb26c760671f131c3372d374f48283ca9a5404d3d8aabd2a886c6551393ca41c682580f1c81f16046e3bec7b59247bdfca1d40bf2ad8ede9de99cb44b36658f775999d37776b3b1a085b9530e54ece69e1875e1bdc8cdcf", + recipeConfig: [ + { + "op": "Blowfish加密", + "args": [ + {"option": "十六进制", "string": "0011223344556677"}, // Key + {"option": "十六进制", "string": "ffeeddccbbaa9988"}, // IV + "CBC", // Mode + "十六进制", // Input + "十六进制" // Output + ] + } + ], + }, + { + name: "Blowfish Decrypt: CBC, ASCII", + input: "398433f39e938286a35fc240521435b6972f3fe96846b54ab9351aa5fa9e10a6a94074e883d1cb36cb9657c817274b60", + expectedOutput: "The quick brown fox jumps over the lazy dog.", + recipeConfig: [ + { + "op": "Blowfish解密", + "args": [ + {"option": "十六进制", "string": "0011223344556677"}, // Key + {"option": "十六进制", "string": "ffeeddccbbaa9988"}, // IV + "CBC", // Mode + "十六进制", // Input + "原始" // Output + ] + } + ], + }, + { + name: "Blowfish Decrypt: CBC, Binary", + input: "3b42c51465896524e66c2fd2404c8c2b4eb26c760671f131c3372d374f48283ca9a5404d3d8aabd2a886c6551393ca41c682580f1c81f16046e3bec7b59247bdfca1d40bf2ad8ede9de99cb44b36658f775999d37776b3b1a085b9530e54ece69e1875e1bdc8cdcf", + expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + recipeConfig: [ + { + "op": "Blowfish解密", + "args": [ + {"option": "十六进制", "string": "0011223344556677"}, // Key + {"option": "十六进制", "string": "ffeeddccbbaa9988"}, // IV + "CBC", // Mode + "十六进制", // Input + "十六进制" // Output + ] + } + ], + }, + { + name: "Blowfish Encrypt: CFB, ASCII", + input: "The quick brown fox jumps over the lazy dog.", + // pyCryptoDome produces a different value with default settings. This is due to segment_size having + // a default value of 8 bits. Setting it to 64 (one full block) will yield the same result. + expectedOutput: "c8ca123592570c1fcb138d4ec08f7af14ad49363245be1ac25029c8ffc508b3217e75faaa5566426180fec8f", + recipeConfig: [ + { + "op": "Blowfish加密", + "args": [ + {"option": "十六进制", "string": "0011223344556677"}, // Key + {"option": "十六进制", "string": "ffeeddccbbaa9988"}, // IV + "CFB", // Mode + "原始", // Input + "十六进制" // Output + ] + } + ], + }, + { + name: "Blowfish Encrypt: CFB, Binary", + input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + // see above. pyCryptoDome produces a different value with its default settings + expectedOutput: "e6ac1324d1576beab00e855de3f4ac1f5e3cbf89f4c2a743a5737895067ac5012e5bdb92477e256cc07bf691b58e721179b550e694abb0be7cbdc42586db755bf795f4338f47d356c57453afa6277e46aaeb3405f9744654a477f06c2ad92ede90555759", + recipeConfig: [ + { + "op": "Blowfish加密", + "args": [ + {"option": "十六进制", "string": "0011223344556677"}, // Key + {"option": "十六进制", "string": "ffeeddccbbaa9988"}, // IV + "CFB", // Mode + "十六进制", // Input + "十六进制" // Output + ] + } + ], + }, + { + name: "Blowfish Decrypt: CFB, ASCII", + input: "c8ca123592570c1fcb138d4ec08f7af14ad49363245be1ac25029c8ffc508b3217e75faaa5566426180fec8f", + expectedOutput: "The quick brown fox jumps over the lazy dog.", + // see above. pyCryptoDome produces a different value with its default settings + recipeConfig: [ + { + "op": "Blowfish解密", + "args": [ + {"option": "十六进制", "string": "0011223344556677"}, // Key + {"option": "十六进制", "string": "ffeeddccbbaa9988"}, // IV + "CFB", // Mode + "十六进制", // Input + "原始" // Output + ] + } + ], + }, + { + name: "Blowfish Decrypt: CFB, Binary", + input: "e6ac1324d1576beab00e855de3f4ac1f5e3cbf89f4c2a743a5737895067ac5012e5bdb92477e256cc07bf691b58e721179b550e694abb0be7cbdc42586db755bf795f4338f47d356c57453afa6277e46aaeb3405f9744654a477f06c2ad92ede90555759", + expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + // see above. pyCryptoDome produces a different value with its default settings + recipeConfig: [ + { + "op": "Blowfish解密", + "args": [ + {"option": "十六进制", "string": "0011223344556677"}, // Key + {"option": "十六进制", "string": "ffeeddccbbaa9988"}, // IV + "CFB", // Mode + "十六进制", // Input + "十六进制" // Output + ] + } + ], + }, + { + name: "Blowfish Encrypt: OFB, ASCII", + input: "The quick brown fox jumps over the lazy dog.", + expectedOutput: "c8ca123592570c1fffcee88b9823b9450dc9c48e559123c1df1984214212bae7e44114d29dba79683d10cce5", + recipeConfig: [ + { + "op": "Blowfish加密", + "args": [ + {"option": "十六进制", "string": "0011223344556677"}, // Key + {"option": "十六进制", "string": "ffeeddccbbaa9988"}, // IV + "OFB", // Mode + "原始", // Input + "十六进制" // Output + ] + } + ], + }, + { + name: "Blowfish Encrypt: OFB, Binary", + input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + expectedOutput: "e6ac1324d1576bea4ceb5be7691c35e4919f18be06cc2a926025ef0973222e987de7c63cd71ed3b19190ba006931d9cbdf412f5b1ac7155904ca591f693fe11aa996e17866e0de4b2eb7ff5effabf94b0f49ed159202caf72745ac2f024d86f942d83767", + recipeConfig: [ + { + "op": "Blowfish加密", + "args": [ + {"option": "十六进制", "string": "0011223344556677"}, // Key + {"option": "十六进制", "string": "ffeeddccbbaa9988"}, // IV + "OFB", // Mode + "十六进制", // Input + "十六进制" // Output + ] + } + ], + }, + { + name: "Blowfish Decrypt: OFB, ASCII", + input: "c8ca123592570c1fffcee88b9823b9450dc9c48e559123c1df1984214212bae7e44114d29dba79683d10cce5", + expectedOutput: "The quick brown fox jumps over the lazy dog.", + recipeConfig: [ + { + "op": "Blowfish解密", + "args": [ + {"option": "十六进制", "string": "0011223344556677"}, // Key + {"option": "十六进制", "string": "ffeeddccbbaa9988"}, // IV + "OFB", // Mode + "十六进制", // Input + "原始" // Output + ] + } + ], + }, + { + name: "Blowfish Decrypt: OFB, Binary", + input: "e6ac1324d1576bea4ceb5be7691c35e4919f18be06cc2a926025ef0973222e987de7c63cd71ed3b19190ba006931d9cbdf412f5b1ac7155904ca591f693fe11aa996e17866e0de4b2eb7ff5effabf94b0f49ed159202caf72745ac2f024d86f942d83767", + expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + recipeConfig: [ + { + "op": "Blowfish解密", + "args": [ + {"option": "十六进制", "string": "0011223344556677"}, // Key + {"option": "十六进制", "string": "ffeeddccbbaa9988"}, // IV + "OFB", // Mode + "十六进制", // Input + "十六进制" // Output + ] + } + ], + }, + { + name: "Blowfish Encrypt: CTR, ASCII", + input: "The quick brown fox jumps over the lazy dog.", + expectedOutput: "e2a5e0f03ad4877101c7cf83861ad93477adb57acac4bebc315a7bae34b4e6a54e5532db457a3131dcd9dda6", + recipeConfig: [ + { + "op": "Blowfish加密", + "args": [ + {"option": "十六进制", "string": "0011223344556677"}, // Key + // pyCryptoDome only allows the size of the nonce to be [0,7] bytes. + // Internally, it right-pads the nonce to 7 bytes long if it wasn't already 7 bytes, + // and the last (8th) byte is used for counter. + // Therefore a pyCryptoDome nonce of "aabbccdd" is equivalent to an IV of "aabbccdd00000000" here. + {"option": "十六进制", "string": "0000000000000000"}, // IV (nonce) + "CTR", // Mode + "原始", // Input + "十六进制" // Output + ] + } + ], + }, + { + name: "Blowfish Encrypt: CTR, Binary", + input: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + expectedOutput: "ccc3e1e179d4e084b2e27cef77255595ebfb694a9999b7ef8e661086058472dad7f3e0350fde9be87059ab43d5b800aa08be4c00f3f2e99402fe2702c39e8663dbcbb146700d63432227f1045f116bfd4b65022ca20b70427ddcfd7441cb3c75f4d3fff0", + recipeConfig: [ + { + "op": "Blowfish加密", + "args": [ + {"option": "十六进制", "string": "0011223344556677"}, // Key + // See notes above + {"option": "十六进制", "string": "0000000000000000"}, // IV (nonce) + "CTR", // Mode + "十六进制", // Input + "十六进制" // Output + ] + } + ], + }, + { + name: "Blowfish Decrypt: CTR, ASCII", + input: "e2a5e0f03ad4877101c7cf83861ad93477adb57acac4bebc315a7bae34b4e6a54e5532db457a3131dcd9dda6", + expectedOutput: "The quick brown fox jumps over the lazy dog.", + recipeConfig: [ + { + "op": "Blowfish解密", + "args": [ + {"option": "十六进制", "string": "0011223344556677"}, // Key + // See notes above + {"option": "十六进制", "string": "0000000000000000"}, // IV (nonce) + "CTR", // Mode + "十六进制", // Input + "原始" // Output + ] + } + ], + }, + { + name: "Blowfish Decrypt: CTR, Binary", + input: "ccc3e1e179d4e084b2e27cef77255595ebfb694a9999b7ef8e661086058472dad7f3e0350fde9be87059ab43d5b800aa08be4c00f3f2e99402fe2702c39e8663dbcbb146700d63432227f1045f116bfd4b65022ca20b70427ddcfd7441cb3c75f4d3fff0", + expectedOutput: "7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018", + recipeConfig: [ + { + "op": "Blowfish解密", + "args": [ + {"option": "十六进制", "string": "0011223344556677"}, // Key + // See notes above + {"option": "十六进制", "string": "0000000000000000"}, // IV (nonce) + "CTR", // Mode + "十六进制", // Input + "十六进制" // Output + ] + } + ], + }, + { + name: "Blowfish Encrypt with variable key length: CBC, ASCII, 4 bytes", + input: "The quick brown fox jumps over the lazy dog.", + expectedOutput: "823f337a53ecf121aa9ec1b111bd5064d1d7586abbdaaa0c8fd0c6cc43c831c88bf088ee3e07287e3f36cf2e45f9c7e6", + recipeConfig: [ + { + "op": "Blowfish加密", + "args": [ + {"option": "十六进制", "string": "00112233"}, // Key + {"option": "十六进制", "string": "0000000000000000"}, // IV + "CBC", // Mode + "原始", // Input + "十六进制" // Output + ] + } + ], + }, + { + name: "Blowfish Encrypt with variable key length: CBC, ASCII, 42 bytes", + input: "The quick brown fox jumps over the lazy dog.", + expectedOutput: "19f5a68145b34321cfba72226b0f33922ce44dd6e7869fe328db64faae156471216f12ed2a37fd0bdd7cebf867b3cff0", + recipeConfig: [ + { + "op": "Blowfish加密", + "args": [ + {"option": "十六进制", "string": "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdead"}, // Key + {"option": "十六进制", "string": "0000000000000000"}, // IV + "CBC", // Mode + "原始", // Input + "十六进制" // Output + ] + } + ], + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/DateTime.mjs b/plugins/srktoolbox/tests/operations/tests/DateTime.mjs new file mode 100644 index 00000000..58a97a6b --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/DateTime.mjs @@ -0,0 +1,58 @@ +/** + * DateTime tests. + * + * @author bwhitn [brian.m.whitney@outlook.com] + * + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Filetime to Unix", + input: "129207366395297693", + expectedOutput: "1276263039529769300", + recipeConfig: [ + { + op: "Windows Filetime转UNIX时间戳", + args: ["纳秒 (ns)", "十进制"], + }, + ], + }, + { + name: "Unix to Filetime", + input: "1276263039529769300", + expectedOutput: "129207366395297693", + recipeConfig: [ + { + op: "UNIX时间戳转Windows Filetime", + args: ["纳秒 (ns)", "十进制"], + }, + ], + }, + { + name: "DateTime Delta Positive", + input: "20/02/2024 13:36:00", + expectedOutput: "20/02/2024 13:37:00", + recipeConfig: [ + { + op: "DateTime推算", + args: ["标准日期时间格式", "DD/MM/YYYY HH:mm:ss", "加", 0, 0, 1, 0], + }, + ], + }, + { + name: "DateTime Delta Negative", + input: "20/02/2024 14:37:00", + expectedOutput: "20/02/2024 13:37:00", + recipeConfig: [ + { + op: "DateTime推算", + args: ["标准日期时间格式", "DD/MM/YYYY HH:mm:ss", "减", 0, 1, 0, 0], + }, + ], + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/DefangIP.mjs b/plugins/srktoolbox/tests/operations/tests/DefangIP.mjs new file mode 100644 index 00000000..d733812d --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/DefangIP.mjs @@ -0,0 +1,45 @@ +/** + * DefangIP tests. + * + * @author h345983745 + * + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Defang IP: Valid IPV4", + input: "192.168.1.1", + expectedOutput: "192[.]168[.]1[.]1", + recipeConfig: [ + { + op: "IP地址无效化", + args: [], + }, + ], + }, { + name: "Defang IP: Valid IPV6", + input: "2001:0db8:85a3:0000:0000:8a2e:0370:7343", + expectedOutput: "2001[:]0db8[:]85a3[:]0000[:]0000[:]8a2e[:]0370[:]7343", + recipeConfig: [ + { + op: "IP地址无效化", + args: [], + }, + ], + }, { + name: "Defang IP: Valid IPV6 Shorthand", + input: "2001:db8:3c4d:15::1a2f:1a2b", + expectedOutput: "2001[:]db8[:]3c4d[:]15[:][:]1a2f[:]1a2b", + recipeConfig: [ + { + op: "IP地址无效化", + args: [], + }, + ], + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/DropNthBytes.mjs b/plugins/srktoolbox/tests/operations/tests/DropNthBytes.mjs new file mode 100644 index 00000000..1486ae43 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/DropNthBytes.mjs @@ -0,0 +1,125 @@ +/** + * @author Oshawk [oshawk@protonmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github + */ + +import TestRegister from "../../lib/TestRegister.mjs"; + +/** + * Drop nth bytes tests + */ +TestRegister.addTests([ + { + name: "Drop nth bytes: Nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "删除每N个字节", + args: [4, 0, false], + }, + ], + }, + { + name: "Drop nth bytes: Nothing (apply to each line)", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "删除每N个字节", + args: [4, 0, true], + }, + ], + }, + { + name: "Drop nth bytes: Basic single line", + input: "0123456789", + expectedOutput: "1235679", + recipeConfig: [ + { + op: "删除每N个字节", + args: [4, 0, false], + }, + ], + }, + { + name: "Drop nth bytes: Basic single line (apply to each line)", + input: "0123456789", + expectedOutput: "1235679", + recipeConfig: [ + { + op: "删除每N个字节", + args: [4, 0, true], + }, + ], + }, + { + name: "Drop nth bytes: Complex single line", + input: "0123456789", + expectedOutput: "01234678", + recipeConfig: [ + { + op: "删除每N个字节", + args: [4, 5, false], + }, + ], + }, + { + name: "Drop nth bytes: Complex single line (apply to each line)", + input: "0123456789", + expectedOutput: "01234678", + recipeConfig: [ + { + op: "删除每N个字节", + args: [4, 5, true], + }, + ], + }, + { + name: "Drop nth bytes: Basic multi line", + input: "01234\n56789", + expectedOutput: "123\n5689", + recipeConfig: [ + { + op: "删除每N个字节", + args: [4, 0, false], + }, + ], + }, + { + name: "Drop nth bytes: Basic multi line (apply to each line)", + input: "01234\n56789", + expectedOutput: "123\n678", + recipeConfig: [ + { + op: "删除每N个字节", + args: [4, 0, true], + }, + ], + }, + { + name: "Drop nth bytes: Complex multi line", + input: "01234\n56789", + expectedOutput: "012345679", + recipeConfig: [ + { + op: "删除每N个字节", + args: [4, 5, false], + }, + ], + }, + { + name: "Drop nth bytes: Complex multi line (apply to each line)", + input: "012345\n6789ab", + expectedOutput: "01234\n6789a", + recipeConfig: [ + { + op: "删除每N个字节", + args: [4, 5, true], + }, + ], + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/ECDSA.mjs b/plugins/srktoolbox/tests/operations/tests/ECDSA.mjs new file mode 100644 index 00000000..7fba4eac --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/ECDSA.mjs @@ -0,0 +1,537 @@ +/** + * ECDSA tests. + * + * @author cplussharp + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; +import {ALL_BYTES, ASCII_TEXT, UTF8_TEXT} from "../../samples/Ciphers.mjs"; + +const SOME_HEX_BYTES = "cdb23f958e018418621d9e489b7bba0f0c481f604eba2eb1ea35e38f99490cc0"; +const SOME_BASE64_BYTES = "zbI/lY4BhBhiHZ5Im3u6DwxIH2BOui6x6jXjj5lJDMA="; + +const P256 = { + // openssl ecparam -name prime256v1 -genkey -noout -out p256.priv.key + privateKeyPkcs1: `-----BEGIN EC PRIVATE KEY----- +MHcCAQEEINtTjwUkgfAiSwqgcGAXWyE0ueIW6n2k395dmQZ3vGr4oAoGCCqGSM49 +AwEHoUQDQgAEDUc8A0EDNKoCYIPWMHz1yUzqE5mJgusgcAE8H6810fkJ8ZmTNiCC +a6sLgR2vD1VNh2diirWgKPH4PVMKav5e6Q== +-----END EC PRIVATE KEY-----`, + privateKeyPkcs8: `-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg21OPBSSB8CJLCqBw +YBdbITS54hbqfaTf3l2ZBne8avihRANCAAQNRzwDQQM0qgJgg9YwfPXJTOoTmYmC +6yBwATwfrzXR+QnxmZM2IIJrqwuBHa8PVU2HZ2KKtaAo8fg9Uwpq/l7p +-----END PRIVATE KEY-----`, + + // openssl ec -in p256.priv.key -pubout -out p256.pub.key + publicKey: `-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDUc8A0EDNKoCYIPWMHz1yUzqE5mJ +gusgcAE8H6810fkJ8ZmTNiCCa6sLgR2vD1VNh2diirWgKPH4PVMKav5e6Q== +-----END PUBLIC KEY-----`, + + signature: { + sha256: { + asn1: "3046022100e06905608a2fa7dbda9e284c2a7959dfb68fb527a5f003b2d7975ff135145127022100b6baa253793334f8b93ea1dd622bc600124d8090babd807efe3f77b8b324388d", + p1363: "e06905608a2fa7dbda9e284c2a7959dfb68fb527a5f003b2d7975ff135145127b6baa253793334f8b93ea1dd622bc600124d8090babd807efe3f77b8b324388d", + jws: "4GkFYIovp9vanihMKnlZ37aPtSel8AOy15df8TUUUSe2uqJTeTM0-Lk-od1iK8YAEk2AkLq9gH7-P3e4syQ4jQ", + json: `{"r":"00e06905608a2fa7dbda9e284c2a7959dfb68fb527a5f003b2d7975ff135145127","s":"00b6baa253793334f8b93ea1dd622bc600124d8090babd807efe3f77b8b324388d"}` + } + } +}; + +// openssl pkcs8 -topk8 -in p256.priv.key -out p256.enc-priv.key -v2 des3 -v2prf hmacWithSHA1 -passout pass:Test1234 +/* const PEM_PRIV_P256_ENCRYPTED_PASS = "Test1234"; +const PEM_PRIV_P256_ENCRYPTED = `-----BEGIN ENCRYPTED PRIVATE KEY----- +MIHsMFcGCSqGSIb3DQEFDTBKMCkGCSqGSIb3DQEFDDAcBAg+4ckqI9Q9ZAICCAAw +DAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEOnMUW15Hn/ub0OcCCj9lksEgZCk +kxaK4d430lZHovcA4ZeKTt94QcfjnIHRk65aZt93l17l52pv6n/srs3aRo/n5RV+ +wZ5sTLF0925ZQWJB5cIhzc8KQIvguGCX1znLQJJaRHyYOUXIN77AKEfALKAinBit +25paDnbXAqGn1CR3UwFWUZZW+c3UEhWhmpghQpS1tIl0KI6IAvnrGIdw2kKIouo= +-----END ENCRYPTED PRIVATE KEY-----`;*/ + +const P384 = { + privateKeyPkcs8: `-----BEGIN PRIVATE KEY----- +MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDAYo22xn2kZjN8MInom +NDsgD/zhpUwnCYch634jUgO59fN9m2lR5ekaI1XABHz39rihZANiAAQwXoCsPOLv +Nn2STUs/hpL41CQveSL3WUmJ4QdtD7UFCl1mBO6ME0xSUgIQTUNkHt5k9CpOq3x9 +r+LG5+GcisoLn7R54R+bRoGp/p1ZBeuBXoCgthvs+RFoT3OewUmA8oQ= +-----END PRIVATE KEY-----`, + publicKey: `-----BEGIN PUBLIC KEY----- +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEMF6ArDzi7zZ9kk1LP4aS+NQkL3ki91lJ +ieEHbQ+1BQpdZgTujBNMUlICEE1DZB7eZPQqTqt8fa/ixufhnIrKC5+0eeEfm0aB +qf6dWQXrgV6AoLYb7PkRaE9znsFJgPKE +-----END PUBLIC KEY-----` +}; + +const P521 = { + privateKeyPkcs8: `-----BEGIN PRIVATE KEY----- +MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIAifBaJDqNwOtKgThc +FU34GzPQ73ubOQg9dnighpVGwA3b/KwCifimCNKDmKnXJaE04mEcxg8yzcFKausF +5I8o206hgYkDgYYABAGwpkwrBBlZOdx4u9mxqYxJvtzAHaFFAzl21WQVbAjyrqXe +nFPMkhbFpEEWr1ualPYKQkHe14AX33iU3fQ9MlBkgAAripsPbiKggAaog74cUERo +qbrUFZwMbptGgovpE6pU93h7A1wb3Vtw9DZQCgiNbwzMbdsft+p2RJ8iSxWEC6Gd +mw== +-----END PRIVATE KEY-----`, + publicKey: `-----BEGIN PUBLIC KEY----- +MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBsKZMKwQZWTnceLvZsamMSb7cwB2h +RQM5dtVkFWwI8q6l3pxTzJIWxaRBFq9bmpT2CkJB3teAF994lN30PTJQZIAAK4qb +D24ioIAGqIO+HFBEaKm61BWcDG6bRoKL6ROqVPd4ewNcG91bcPQ2UAoIjW8MzG3b +H7fqdkSfIksVhAuhnZs= +-----END PUBLIC KEY-----` +}; + +const PEM_PPRIV_RSA512 = `-----BEGIN RSA PRIVATE KEY----- +MIIBOQIBAAJBAPKr0Dp6YdItzOfk6a7ma7L4BF4LnelMYKtboGLrk6ihtqFPZFRL +NcJi68Hvnt8stMrP50t6jqwWQ2EjMdkj6fsCAwEAAQJAOJUpM0lv36MAQR3WAwsF +F7DOy+LnigteCvaNWiNVxZ6jByB5Qb7sall/Qlu9sFI0ZwrlVcKS0kldee7JTYlL +WQIhAP3UKEfOtpTgT1tYmdhaqjxqMfxBom0Ri+rt9ajlzs6vAiEA9L85B8/Gnb7p +6Af7/wpmafL277OV4X4xBfzMR+TUzHUCIBq+VLQkInaTH6lXL3ZtLwyIf9W9MJjf +RWeuRLjT5bM/AiBF7Kw6kx5Hy1fAtydEApCoDIaIjWJw/kC7WTJ0B+jUUQIgV6dw +NSyj0feakeD890gmId+lvl/w/3oUXiczqvl/N9o= +-----END RSA PRIVATE KEY-----`; +const PEM_PUB_RSA512 = `-----BEGIN PUBLIC KEY----- +MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAPKr0Dp6YdItzOfk6a7ma7L4BF4LnelM +YKtboGLrk6ihtqFPZFRLNcJi68Hvnt8stMrP50t6jqwWQ2EjMdkj6fsCAwEAAQ== +-----END PUBLIC KEY-----`; + +TestRegister.addTests([ + { + name: "ECDSA Sign/Verify: P-256 with MD5", + input: ASCII_TEXT, + expectedOutput: "验证成功", + recipeConfig: [ + { + "op": "ECDSA签名", + "args": [P256.privateKeyPkcs1, "MD5", "ASN.1十六进制"] + }, + { + "op": "ECDSA验证", + "args": ["ASN.1十六进制", "MD5", P256.publicKey, ASCII_TEXT, "原始"] + } + ] + }, + { + name: "ECDSA Sign/Verify: P-256 with SHA1", + input: ASCII_TEXT, + expectedOutput: "验证成功", + recipeConfig: [ + { + "op": "ECDSA签名", + "args": [P256.privateKeyPkcs1, "SHA-1", "ASN.1十六进制"] + }, + { + "op": "ECDSA验证", + "args": ["ASN.1十六进制", "SHA-1", P256.publicKey, ASCII_TEXT, "原始"] + } + ] + }, + { + name: "ECDSA Sign/Verify: P-256 with SHA256", + input: ASCII_TEXT, + expectedOutput: "验证成功", + recipeConfig: [ + { + "op": "ECDSA签名", + "args": [P256.privateKeyPkcs1, "SHA-256", "ASN.1十六进制"] + }, + { + "op": "ECDSA验证", + "args": ["ASN.1十六进制", "SHA-256", P256.publicKey, ASCII_TEXT, "原始"] + } + ] + }, + { + name: "ECDSA Sign/Verify: P-256 with SHA384", + input: ASCII_TEXT, + expectedOutput: "验证成功", + recipeConfig: [ + { + "op": "ECDSA签名", + "args": [P256.privateKeyPkcs1, "SHA-384", "ASN.1十六进制"] + }, + { + "op": "ECDSA验证", + "args": ["ASN.1十六进制", "SHA-384", P256.publicKey, ASCII_TEXT, "原始"] + } + ] + }, + { + name: "ECDSA Sign/Verify: P-256 with SHA512", + input: ASCII_TEXT, + expectedOutput: "验证成功", + recipeConfig: [ + { + "op": "ECDSA签名", + "args": [P256.privateKeyPkcs1, "SHA-512", "ASN.1十六进制"] + }, + { + "op": "ECDSA验证", + "args": ["ASN.1十六进制", "SHA-512", P256.publicKey, ASCII_TEXT, "原始"] + } + ] + }, + { + name: "ECDSA Sign/Verify:: Using a private key in PKCS#8 format works", + input: ASCII_TEXT, + expectedOutput: "验证成功", + recipeConfig: [ + { + "op": "ECDSA签名", + "args": [P256.privateKeyPkcs8, "SHA-256", "ASN.1十六进制"] + }, + { + "op": "ECDSA验证", + "args": ["ASN.1十六进制", "SHA-256", P256.publicKey, ASCII_TEXT, "原始"] + } + ] + }, + { + name: "ECDSA Sign/Verify: P-384 with SHA384", + input: ASCII_TEXT, + expectedOutput: "验证成功", + recipeConfig: [ + { + "op": "ECDSA签名", + "args": [P384.privateKeyPkcs8, "SHA-384", "ASN.1十六进制"] + }, + { + "op": "ECDSA验证", + "args": ["ASN.1十六进制", "SHA-384", P384.publicKey, ASCII_TEXT, "原始"] + } + ] + }, + { + name: "ECDSA Sign/Verify: P-521 with SHA512", + input: ASCII_TEXT, + expectedOutput: "验证成功", + recipeConfig: [ + { + "op": "ECDSA签名", + "args": [P521.privateKeyPkcs8, "SHA-512", "ASN.1十六进制"] + }, + { + "op": "ECDSA验证", + "args": ["ASN.1十六进制", "SHA-512", P521.publicKey, ASCII_TEXT, "原始"] + } + ] + }, + + // ECDSA Sign + { + name: "ECDSA Sign: Using public key fails", + input: ASCII_TEXT, + expectedOutput: "提供的密钥不是私钥", + recipeConfig: [ + { + "op": "ECDSA签名", + "args": [P256.publicKey, "SHA-256", "ASN.1十六进制"] + } + ] + }, + { + name: "ECDSA Sign: Using an RSA key fails", + input: ASCII_TEXT, + expectedOutput: "提供的密钥不是EC密钥。", + recipeConfig: [ + { + "op": "ECDSA签名", + "args": [PEM_PPRIV_RSA512, "SHA-256", "ASN.1十六进制"] + } + ] + }, + + // ECDSA Verify + { + name: "ECDSA Verify: P-256 with SHA256 (ASN.1 signature)", + input: P256.signature.sha256.asn1, + expectedOutput: "验证成功", + recipeConfig: [ + { + "op": "ECDSA验证", + "args": ["自动检测", "SHA-256", P256.publicKey, ASCII_TEXT, "原始"] + } + ] + }, + { + name: "ECDSA Verify: P-256 with SHA256 (P1363 signature)", + input: P256.signature.sha256.p1363, + expectedOutput: "验证成功", + recipeConfig: [ + { + "op": "ECDSA验证", + "args": ["自动检测", "SHA-256", P256.publicKey, ASCII_TEXT, "原始"] + } + ] + }, + { + name: "ECDSA Verify: P-256 with SHA256 (JWS signature)", + input: P256.signature.sha256.jws, + expectedOutput: "验证成功", + recipeConfig: [ + { + "op": "ECDSA验证", + "args": ["自动检测", "SHA-256", P256.publicKey, ASCII_TEXT, "原始"] + } + ] + }, + { + name: "ECDSA Verify: P-256 with SHA256 (JSON signature)", + input: P256.signature.sha256.json, + expectedOutput: "验证成功", + recipeConfig: [ + { + "op": "ECDSA验证", + "args": ["自动检测", "SHA-256", P256.publicKey, ASCII_TEXT, "原始"] + } + ] + }, + { + name: "ECDSA Verify: JSON signature missing r", + input: JSON.stringify({s: JSON.parse(P256.signature.sha256.json).s}), + expectedOutput: '签名JSON中没有"r"值', + recipeConfig: [ + { + "op": "ECDSA验证", + "args": ["自动检测", "SHA-256", P256.publicKey, ASCII_TEXT, "原始"] + } + ] + }, + { + name: "ECDSA Verify: JSON signature missing s", + input: JSON.stringify({r: JSON.parse(P256.signature.sha256.json).r}), + expectedOutput: '签名JSON中没有"s"值', + recipeConfig: [ + { + "op": "ECDSA验证", + "args": ["自动检测", "SHA-256", P256.publicKey, ASCII_TEXT, "原始"] + } + ] + }, + { + name: "ECDSA Verify: Using private key fails", + input: P256.signature.sha256.asn1, + expectedOutput: "提供的密钥不是公钥。", + recipeConfig: [ + { + "op": "ECDSA验证", + "args": ["ASN.1十六进制", "SHA-256", P256.privateKeyPkcs1, ASCII_TEXT, "原始"] + } + ] + }, + { + name: "ECDSA Verify: Using an RSA key fails", + input: P256.signature.sha256.asn1, + expectedOutput: "提供的密钥不是EC密钥。", + recipeConfig: [ + { + "op": "ECDSA验证", + "args": ["ASN.1十六进制", "SHA-256", PEM_PUB_RSA512, ASCII_TEXT, "原始"] + } + ] + }, + + // ECDSA Signatur Conversion + { + name: "ECDSA Signature Conversion: ASN.1 To ASN.1", + input: P256.signature.sha256.asn1, + expectedOutput: P256.signature.sha256.asn1, + recipeConfig: [ + { + "op": "ECDSA签名格式转换", + "args": ["自动检测", "ASN.1十六进制"] + } + ] + }, + { + name: "ECDSA Signature Conversion: ASN.1 To P1363", + input: P256.signature.sha256.asn1, + expectedOutput: P256.signature.sha256.p1363, + recipeConfig: [ + { + "op": "ECDSA签名格式转换", + "args": ["自动检测", "P1363十六进制"] + } + ] + }, + { + name: "ECDSA Signature Conversion: ASN.1 To JWS", + input: P256.signature.sha256.asn1, + expectedOutput: P256.signature.sha256.jws, + recipeConfig: [ + { + "op": "ECDSA签名格式转换", + "args": ["自动检测", "JSON Web签名(JWS)"] + } + ] + }, + { + name: "ECDSA Signature Conversion: ASN.1 To JSON", + input: P256.signature.sha256.asn1, + expectedOutput: P256.signature.sha256.json, + recipeConfig: [ + { + "op": "ECDSA签名格式转换", + "args": ["自动检测", "原始JSON"] + } + ] + }, + { + name: "ECDSA Signature Conversion: P1363 To ASN.1", + input: P256.signature.sha256.p1363, + expectedOutput: P256.signature.sha256.asn1, + recipeConfig: [ + { + "op": "ECDSA签名格式转换", + "args": ["自动检测", "ASN.1十六进制"] + } + ] + }, + { + name: "ECDSA Signature Conversion: P1363 To P1363", + input: P256.signature.sha256.p1363, + expectedOutput: P256.signature.sha256.p1363, + recipeConfig: [ + { + "op": "ECDSA签名格式转换", + "args": ["自动检测", "P1363十六进制"] + } + ] + }, + { + name: "ECDSA Signature Conversion: P1363 To JWS", + input: P256.signature.sha256.p1363, + expectedOutput: P256.signature.sha256.jws, + recipeConfig: [ + { + "op": "ECDSA签名格式转换", + "args": ["自动检测", "JSON Web签名(JWS)"] + } + ] + }, + { + name: "ECDSA Signature Conversion: P1363 To JSON", + input: P256.signature.sha256.p1363, + expectedOutput: P256.signature.sha256.json, + recipeConfig: [ + { + "op": "ECDSA签名格式转换", + "args": ["自动检测", "原始JSON"] + } + ] + }, + { + name: "ECDSA Signature Conversion: JSON To ASN.1", + input: P256.signature.sha256.json, + expectedOutput: P256.signature.sha256.asn1, + recipeConfig: [ + { + "op": "ECDSA签名格式转换", + "args": ["自动检测", "ASN.1十六进制"] + } + ] + }, + { + name: "ECDSA Signature Conversion: JSON To P1363", + input: P256.signature.sha256.json, + expectedOutput: P256.signature.sha256.p1363, + recipeConfig: [ + { + "op": "ECDSA签名格式转换", + "args": ["自动检测", "P1363十六进制"] + } + ] + }, + { + name: "ECDSA Signature Conversion: JSON To JWS", + input: P256.signature.sha256.json, + expectedOutput: P256.signature.sha256.jws, + recipeConfig: [ + { + "op": "ECDSA签名格式转换", + "args": ["自动检测", "JSON Web签名(JWS)"] + } + ] + }, + { + name: "ECDSA Signature Conversion: JSON To JSON", + input: P256.signature.sha256.json, + expectedOutput: P256.signature.sha256.json, + recipeConfig: [ + { + "op": "ECDSA签名格式转换", + "args": ["自动检测", "原始JSON"] + } + ] + }, + { + name: "ECDSA Sign/Verify: P-256 with SHA256 UTF8", + input: UTF8_TEXT, + expectedOutput: "验证成功", + recipeConfig: [ + { + "op": "ECDSA签名", + "args": [P256.privateKeyPkcs1, "SHA-256", "ASN.1十六进制"] + }, + { + "op": "ECDSA验证", + "args": ["ASN.1十六进制", "SHA-256", P256.publicKey, UTF8_TEXT, "原始"] + } + ] + }, + { + name: "ECDSA Sign/Verify: P-256 with SHA256 bytes raw", + input: ALL_BYTES, + expectedOutput: "验证成功", + recipeConfig: [ + { + "op": "ECDSA签名", + "args": [P256.privateKeyPkcs1, "SHA-256", "ASN.1十六进制"] + }, + { + "op": "ECDSA验证", + "args": ["ASN.1十六进制", "SHA-256", P256.publicKey, ALL_BYTES, "原始"] + } + ] + }, + { + name: "ECDSA Sign/Verify: P-256 with SHA256 bytes hex", + input: SOME_HEX_BYTES, + expectedOutput: "验证成功", + recipeConfig: [ + { + "op": "十六进制转字符", + "args": ["自动"] + }, + { + "op": "ECDSA签名", + "args": [P256.privateKeyPkcs1, "SHA-256", "ASN.1十六进制"] + }, + { + "op": "ECDSA验证", + "args": ["ASN.1十六进制", "SHA-256", P256.publicKey, SOME_HEX_BYTES, "十六进制"] + } + ] + }, + { + name: "ECDSA Sign/Verify: P-256 with SHA256 bytes Base64", + input: SOME_BASE64_BYTES, + expectedOutput: "验证成功", + recipeConfig: [ + { + "op": "Base64解码", + "args": ["A-Za-z0-9+/=", true] + }, + { + "op": "ECDSA签名", + "args": [P256.privateKeyPkcs1, "SHA-256", "ASN.1十六进制"] + }, + { + "op": "ECDSA验证", + "args": ["ASN.1十六进制", "SHA-256", P256.publicKey, SOME_BASE64_BYTES, "Base64"] + } + ] + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/ELFInfo.mjs b/plugins/srktoolbox/tests/operations/tests/ELFInfo.mjs new file mode 100644 index 00000000..b36486f5 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/ELFInfo.mjs @@ -0,0 +1,88 @@ +/** + * @author n1073645 [n1073645@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; +import {ELF32_LE, ELF32_BE, ELF64_LE, ELF64_BE} from "../../samples/Executables.mjs"; + +const ELF32_LE_OUTPUT = "============================== ELF Header ==============================\n魔数: \x7fELF\n格式: 32位\n端序: 小端序\n版本: 1\nABI: System V\nABI版本: 0\n类型: Executable File\n指令集架构: x86\nELF版本: 1\nEntry Point: 0x8062150\nEntry PHOFF: 0x34\nEntry SHOFF: 0x54\n标志: 00000000\nELF Header Size: 52 bytes\nProgram Header Size: 32 bytes\nProgram Header Entries: 1\nSection Header Size: 40 bytes\nSection Header Entries: 3\nSection Header Names: 0\n\n============================== Program Header ==============================\nProgram Header Type: Program Header Table\nOffset Of Segment: 52\nVirtual Address of Segment: 134512692\nPhysical Address of Segment: 134512692\nSize of Segment: 256 bytes\nSize of Segment in Memory: 256 bytes\n标志: Execute,Read\n\n============================== Section Header ==============================\n类型: String Table\nSection Name: .shstrab\n标志: \nSection Vaddr in memory: 0\nOffset of the section: 204\nSection Size: 28\nAssociated Section: 0\nSection Extra Information: 0\n\n类型: Symbol Table\nSection Name: .symtab\n标志: \nSection Vaddr in memory: 0\nOffset of the section: 230\nSection Size: 16\nAssociated Section: 0\nSection Extra Information: 0\n\n类型: String Table\nSection Name: .strtab\n标志: \nSection Vaddr in memory: 0\nOffset of the section: 245\nSection Size: 4\nAssociated Section: 0\nSection Extra Information: 0\n\n============================== Symbol Table ==============================\nSymbol Name: test"; +const ELF32_BE_OUTPUT = "============================== ELF Header ==============================\n魔数: \x7fELF\n格式: 32位\n端序: 大端序\n版本: 1\nABI: System V\nABI版本: 0\n类型: Executable File\n指令集架构: x86\nELF版本: 1\nEntry Point: 0x8062150\nEntry PHOFF: 0x34\nEntry SHOFF: 0x54\n标志: 00000000\nELF Header Size: 52 bytes\nProgram Header Size: 32 bytes\nProgram Header Entries: 1\nSection Header Size: 40 bytes\nSection Header Entries: 3\nSection Header Names: 0\n\n============================== Program Header ==============================\nProgram Header Type: Program Header Table\nOffset Of Segment: 52\nVirtual Address of Segment: 134512692\nPhysical Address of Segment: 134512692\nSize of Segment: 256 bytes\nSize of Segment in Memory: 256 bytes\n标志: Execute,Read\n\n============================== Section Header ==============================\n类型: String Table\nSection Name: .shstrab\n标志: \nSection Vaddr in memory: 0\nOffset of the section: 204\nSection Size: 28\nAssociated Section: 0\nSection Extra Information: 0\n\n类型: Symbol Table\nSection Name: .symtab\n标志: \nSection Vaddr in memory: 0\nOffset of the section: 230\nSection Size: 16\nAssociated Section: 0\nSection Extra Information: 0\n\n类型: String Table\nSection Name: .strtab\n标志: \nSection Vaddr in memory: 0\nOffset of the section: 245\nSection Size: 4\nAssociated Section: 0\nSection Extra Information: 0\n\n============================== Symbol Table ==============================\nSymbol Name: test"; +const ELF64_LE_OUTPUT = "============================== ELF Header ==============================\n魔数: \x7fELF\n格式: 64位\n端序: 小端序\n版本: 1\nABI: System V\nABI版本: 0\n类型: Executable File\n指令集架构: AMD x86-64\nELF版本: 1\nEntry Point: 0x8062150\nEntry PHOFF: 0x40\nEntry SHOFF: 0x78\n标志: 00000000\nELF Header Size: 64 bytes\nProgram Header Size: 56 bytes\nProgram Header Entries: 1\nSection Header Size: 64 bytes\nSection Header Entries: 3\nSection Header Names: 0\n\n============================== Program Header ==============================\nProgram Header Type: Program Header Table\n标志: Execute,Read\nOffset Of Segment: 52\nVirtual Address of Segment: 134512692\nPhysical Address of Segment: 134512692\nSize of Segment: 256 bytes\nSize of Segment in Memory: 256 bytes\n\n============================== Section Header ==============================\n类型: String Table\nSection Name: .shstrab\n标志: \nSection Vaddr in memory: 0\nOffset of the section: 312\nSection Size: 28\nAssociated Section: 0\nSection Extra Information: 0\n\n类型: Symbol Table\nSection Name: .symtab\n标志: \nSection Vaddr in memory: 0\nOffset of the section: 336\nSection Size: 16\nAssociated Section: 0\nSection Extra Information: 0\n\n类型: String Table\nSection Name: .strtab\n标志: \nSection Vaddr in memory: 0\nOffset of the section: 361\nSection Size: 4\nAssociated Section: 0\nSection Extra Information: 0\n\n============================== Symbol Table ==============================\nSymbol Name: test"; +const ELF64_BE_OUTPUT = "============================== ELF Header ==============================\n魔数: \x7fELF\n格式: 64位\n端序: 大端序\n版本: 1\nABI: System V\nABI版本: 0\n类型: Executable File\n指令集架构: AMD x86-64\nELF版本: 1\nEntry Point: 0x8062150\nEntry PHOFF: 0x40\nEntry SHOFF: 0x78\n标志: 00000000\nELF Header Size: 64 bytes\nProgram Header Size: 56 bytes\nProgram Header Entries: 1\nSection Header Size: 64 bytes\nSection Header Entries: 3\nSection Header Names: 0\n\n============================== Program Header ==============================\nProgram Header Type: Program Header Table\n标志: Execute,Read\nOffset Of Segment: 52\nVirtual Address of Segment: 134512692\nPhysical Address of Segment: 134512692\nSize of Segment: 256 bytes\nSize of Segment in Memory: 256 bytes\n\n============================== Section Header ==============================\n类型: String Table\nSection Name: .shstrab\n标志: \nSection Vaddr in memory: 0\nOffset of the section: 312\nSection Size: 28\nAssociated Section: 0\nSection Extra Information: 0\n\n类型: Symbol Table\nSection Name: .symtab\n标志: \nSection Vaddr in memory: 0\nOffset of the section: 336\nSection Size: 16\nAssociated Section: 0\nSection Extra Information: 0\n\n类型: String Table\nSection Name: .strtab\n标志: \nSection Vaddr in memory: 0\nOffset of the section: 361\nSection Size: 4\nAssociated Section: 0\nSection Extra Information: 0\n\n============================== Symbol Table ==============================\nSymbol Name: test"; + +TestRegister.addTests([ + { + name: "ELF Info invalid ELF.", + input: "\x7f\x00\x00\x00", + expectedOutput: "无效的ELF", + recipeConfig: [ + { + op: "ELF信息", + args: [], + }, + ], + }, + { + name: "ELF Info 32-bit ELF Little Endian.", + input: ELF32_LE, + expectedOutput: ELF32_LE_OUTPUT, + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"], + }, + { + op: "ELF信息", + args: [], + }, + ], + }, + { + name: "ELF Info 32-bit ELF Big Endian.", + input: ELF32_BE, + expectedOutput: ELF32_BE_OUTPUT, + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"], + }, + { + op: "ELF信息", + args: [], + }, + ], + }, + { + name: "ELF Info 64-bit ELF Little Endian.", + input: ELF64_LE, + expectedOutput: ELF64_LE_OUTPUT, + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"], + }, + { + op: "ELF信息", + args: [], + }, + ], + }, + { + name: "ELF Info 64-bit ELF Big Endian.", + input: ELF64_BE, + expectedOutput: ELF64_BE_OUTPUT, + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"], + }, + { + op: "ELF信息", + args: [], + }, + ], + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/Enigma.mjs b/plugins/srktoolbox/tests/operations/tests/Enigma.mjs new file mode 100644 index 00000000..090ecc35 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/Enigma.mjs @@ -0,0 +1,565 @@ +/** + * Enigma machine tests. + * @author s2224834 + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + // Simplest test: A single keypress in the default position on a basic + // Enigma. + name: "Enigma: basic wiring", + input: "G", + expectedOutput: "P", + recipeConfig: [ + { + "op": "Enigma", + "args": [ + "3-rotor", + "", "A", "A", + // Note: start on Z because it steps when the key is pressed + "EKMFLGDQVZNTOWYHXUSPAIBRCJ', + recipeConfig: [ + { + "op": "Generate QR Code", + "args": ["SVG", 5, 4, "Medium"] + }, + ], + }, + { + name: "Generate QR Code : EPS", + input: "Hello world!", + expectedOutput: "%!PS-Adobe-3.0 EPSF-3.0%%BoundingBox: 0 0 315 315/h { 0 rlineto } bind def/v { 0 exch neg rlineto } bind def/M { neg 30 add moveto } bind def/z { closepath } bind def9 9 scale5 0 M 7 h 7 v -7 h z13 0 M 1 h 1 v -1 h z16 0 M 2 h 1 v -1 h 1 v 1 h 1 v -2 h -1 v -1 h 2 v -1 h -3 v 2 h z20 0 M 2 h 1 v -2 h z23 0 M 7 h 7 v -7 h z6 1 M 5 v 5 h -5 v z24 1 M 5 v 5 h -5 v z7 2 M 3 h 3 v -3 h z19 2 M 1 h 1 v -1 h z21 2 M 1 h 2 v -1 h z25 2 M 3 h 3 v -3 h z13 4 M 1 h 3 v -1 h z17 4 M 4 h 1 v 1 h 2 v -1 h -1 v -1 h 1 v -1 h -1 v -1 h -1 v -1 h z15 6 M 1 h 1 v -1 h z17 6 M 1 h 1 v -1 h z16 7 M 1 h 1 v -1 h z18 7 M 1 h 1 v -1 h z20 7 M 1 h 2 v 1 h 1 v -2 h -1 v -1 h -1 v 1 h z6 8 M 7 h 1 v 2 h 1 v -2 h 1 v -2 h -1 v 1 h -1 v -4 h 1 v -1 h -1 v -1 h z17 8 M 1 h 1 v -1 h z24 8 M 2 h 1 v -1 h 1 v -1 h z29 8 M 1 h 1 v -1 h z27 9 M 1 h 1 v -1 h z6 10 M 1 h 1 v 1 h 1 v -1 h 1 v -1 h z8 10 M 2 h 5 v 1 h 1 v -3 h 1 v -1 h -4 v 1 h 1 v 1 h -1 v -1 h -1 v 1 h -1 v -1 h z16 10 M 2 h 4 v 1 h -1 v 2 h 1 v 3 h -1 v -3 h -2 v 1 h 1 v 1 h -1 v 1 h 1 v 4 h -2 v 2 h 3 v -2 h 1 v -1 h -1 v -2 h 1 v 2 h 1 v -1 h 1 v 2 h 2 v 1 h -1 v 1 h 3 v -1 h -1 v -2 h -2 v -1 h 2 v 1 h 1 v 2 h 1 v -2 h 2 v -1 h -1 v -1 h -1 v -1 h 1 v -1 h -1 v -1 h 2 v -1 h -1 v -1 h -1 v 1 h -2 v -1 h -1 v -1 h 1 v 1 h 2 v -1 h -1 v -1 h 1 v -1 h -3 v 1 h -1 v -1 h 1 v -1 h -1 v -1 h -1 v 4 h 1 v 1 h -2 v -3 h -1 v -1 h 1 v -1 h -1 v -1 h 2 v -1 h 1 v -2 h -1 v 1 h -1 v -1 h -1 v 1 h -1 v 1 h -1 v 1 h 1 v 1 h -1 v 1 h 1 v 1 h -1 v -1 h z22 10 M 1 h 1 v -1 h z25 10 M 2 h 1 v -2 h z19 11 M 1 h 1 v -1 h z11 12 M 1 h 1 v -1 h z5 13 M 1 h 4 v -1 h z28 14 M 2 h 2 v -1 h -1 v -1 h z21 15 M 1 v 2 h -1 v z13 17 M 1 h 1 v 2 h 1 v -2 h 1 v 1 h 1 v 1 h 1 v -2 h 1 v 2 h 2 v -1 h -1 v -2 h z22 17 M 3 v 3 h -3 v z5 18 M 7 h 7 v -7 h z23 18 M 1 h 1 v -1 h z6 19 M 5 v 5 h -5 v z7 20 M 3 h 3 v -3 h z29 21 M 1 h 4 v -3 h -1 v 2 h z17 22 M 2 h 3 v -2 h -1 v 1 h -1 v -1 h z24 22 M 1 h 1 v 1 h 1 v -1 h 1 v -3 h -1 v 1 h -1 v 1 h z20 23 M 1 h 2 v -1 h zfill%%EOF", + recipeConfig: [ + { + "op": "Generate QR Code", + "args": ["EPS", 6, 5, "Quartile"] + }, + { + "op": "Remove whitespace", + "args": [false, true, true, false, false, false] + }, + ], + }, + { + name: "Generate QR Code : PDF", + input: "Hello world!", + expectedOutput: "%PDF-1.01 0 obj << /Type /Catalog /Pages 2 0 R >> endobj2 0 obj << /Type /Pages /Count 1 /Kids [ 3 0 R ] >> endobj3 0 obj << /Type /Page /Parent 2 0 R /Resources <<>> /Contents 4 0 R /MediaBox [ 0 0 261 261 ] >> endobj4 0 obj << /Length 1837 >> stream9 0 0 9 0 0 cm4 25 m 11 25 l 11 18 l 4 18 l h12 25 m 14 25 l 14 23 l 13 23 l 13 24 l 12 24 l h16 25 m 17 25 l 17 22 l 16 22 l h18 25 m 25 25 l 25 18 l 18 18 l h5 24 m 5 19 l 10 19 l 10 24 l h19 24 m 19 19 l 24 19 l 24 24 l h6 23 m 9 23 l 9 20 l 6 20 l h12 23 m 13 23 l 13 21 l 15 21 l 15 20 l 12 20 l h14 23 m 15 23 l 15 22 l 14 22 l h20 23 m 23 23 l 23 20 l 20 20 l h15 22 m 16 22 l 16 21 l 15 21 l h12 19 m 13 19 l 13 18 l 12 18 l h14 19 m 15 19 l 15 13 l 13 13 l 13 11 l 12 11 l 12 14 l 14 14 l 14 15 l 11 15 l 11 16 l 12 16 l 12 17 l 13 17 l 13 16 l 14 16 l 14 17 l 13 17 l 13 18 l 14 18 l h16 19 m 17 19 l 17 18 l 16 18 l h4 17 m 8 17 l 8 16 l 10 16 l 10 15 l 11 15 l 11 14 l 10 14 l 10 13 l 11 13 l 11 12 l 9 12 l 9 15 l 8 15 l 8 13 l 6 13 l 6 15 l 7 15 l 7 16 l 4 16 l h10 17 m 11 17 l 11 16 l 10 16 l h17 17 m 18 17 l 18 16 l 20 16 l 20 17 l 23 17 l 23 15 l 20 15 l 20 13 l 19 13 l 19 15 l 18 15 l 18 14 l 17 14 l 17 13 l 16 13 l 16 16 l 17 16 l h24 17 m 25 17 l 25 14 l 24 14 l 24 13 l 23 13 l 23 15 l 24 15 l h21 14 m 22 14 l 22 13 l 21 13 l h15 13 m 16 13 l 16 11 l 15 11 l h17 13 m 19 13 l 19 12 l 21 12 l 21 10 l 20 10 l 20 9 l 19 9 l 19 10 l 18 10 l 18 9 l 16 9 l 16 8 l 15 8 l 15 10 l 17 10 l 17 11 l 18 11 l 18 12 l 17 12 l h24 13 m 25 13 l 25 11 l 24 11 l h22 12 m 23 12 l 23 11 l 22 11 l h4 11 m 11 11 l 11 4 l 4 4 l h14 11 m 15 11 l 15 10 l 14 10 l h5 10 m 5 5 l 10 5 l 10 10 l h13 10 m 14 10 l 14 9 l 13 9 l h21 10 m 23 10 l 23 9 l 24 9 l 24 7 l 23 7 l 23 6 l 22 6 l 22 7 l 21 7 l h6 9 m 9 9 l 9 6 l 6 6 l h12 8 m 15 8 l 15 7 l 13 7 l 13 6 l 16 6 l 16 4 l 15 4 l 15 5 l 14 5 l 14 4 l 12 4 l h16 8 m 17 8 l 17 6 l 16 6 l h18 8 m 19 8 l 19 7 l 18 7 l h19 7 m 20 7 l 20 6 l 21 6 l 21 5 l 20 5 l 20 4 l 17 4 l 17 6 l 19 6 l h24 6 m 25 6 l 25 5 l 24 5 l h22 5 m 23 5 l 23 4 l 22 4 l hfendstreamendobjxref0 50000000000 65535 f 0000000010 00000 n 0000000059 00000 n 0000000118 00000 n 0000000223 00000 n trailer << /Root 1 0 R /Size 5 >>startxref2111%%EOF", + recipeConfig: [ + { + "op": "Generate QR Code", + "args": ["PDF", 5, 4, "Low"] + }, + { + "op": "Remove whitespace", + "args": [false, true, true, false, false, false] + }, + ], + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/GetAllCasings.mjs b/plugins/srktoolbox/tests/operations/tests/GetAllCasings.mjs new file mode 100644 index 00000000..c74bd13a --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/GetAllCasings.mjs @@ -0,0 +1,46 @@ +/** + * GetAllCasings tests. + * + * @author n1073645 [n1073645@gmail.com] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "All casings of test", + input: "test", + expectedOutput: "test\nTest\ntEst\nTEst\nteSt\nTeSt\ntESt\nTESt\ntesT\nTesT\ntEsT\nTEsT\nteST\nTeST\ntEST\nTEST", + recipeConfig: [ + { + "op": "大小写穷举", + "args": [] + } + ] + }, + { + name: "All casings of t", + input: "t", + expectedOutput: "t\nT", + recipeConfig: [ + { + "op": "大小写穷举", + "args": [] + } + ] + }, + { + name: "All casings of null", + input: "", + expectedOutput: "", + recipeConfig: [ + { + "op": "大小写穷举", + "args": [] + } + ] + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/Gunzip.mjs b/plugins/srktoolbox/tests/operations/tests/Gunzip.mjs new file mode 100644 index 00000000..45cb01b0 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/Gunzip.mjs @@ -0,0 +1,60 @@ +/** + * Gunzip Tests. + * + * @author n1073645 [n1073645@gmail.com] + * + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Gunzip: No comment, no checksum and no filename", + input: "1f8b0800f7c8f85d00ff0dc9dd0180200804e0556ea8262848fb3dc588c6a7e76faa8aeedb726036c68d951f76bf9a0af8aae1f97d9c0c084b02509cbf8c2c000000", + expectedOutput: "The quick brown fox jumped over the slow dog", + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "Gunzip", + args: [] + } + ] + }, + { + name: "Gunzip: No comment, no checksum and filename", + input: "1f8b080843c9f85d00ff66696c656e616d65000dc9dd0180200804e0556ea8262848fb3dc588c6a7e76faa8aeedb726036c68d951f76bf9a0af8aae1f97d9c0c084b02509cbf8c2c000000", + expectedOutput: "The quick brown fox jumped over the slow dog", + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "Gunzip", + args: [] + } + ] + }, + { + name: "Gunzip: Has a comment, no checksum and has a filename", + input: "1f8b08186fc9f85d00ff66696c656e616d6500636f6d6d656e74000dc9dd0180200804e0556ea8262848fb3dc588c6a7e76faa8aeedb726036c68d951f76bf9a0af8aae1f97d9c0c084b02509cbf8c2c000000", + expectedOutput: "The quick brown fox jumped over the slow dog", + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "Gunzip", + args: [] + } + ] + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/Gzip.mjs b/plugins/srktoolbox/tests/operations/tests/Gzip.mjs new file mode 100644 index 00000000..7d239330 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/Gzip.mjs @@ -0,0 +1,91 @@ +/** + * Gzip Tests. + * + * @author n1073645 [n1073645@gmail.com] + * + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Gzip: No comment, no checksum and no filename", + input: "The quick brown fox jumped over the slow dog", + expectedOutput: "0dc9dd0180200804e0556ea8262848fb3dc588c6a7e76faa8aeedb726036c68d951f76bf9a0af8aae1f97d9c0c084b02509cbf8c2c000000", + recipeConfig: [ + { + op: "Gzip", + args: ["动态哈夫曼压缩", "", "", false] + }, + { + op: "删除字节", + args: [0, 10, false] + }, + { + op: "字符转十六进制", + args: ["无"] + } + ] + }, + { + name: "Gzip: No comment, no checksum and has a filename", + input: "The quick brown fox jumped over the slow dog", + expectedOutput: "636f6d6d656e74000dc9dd0180200804e0556ea8262848fb3dc588c6a7e76faa8aeedb726036c68d951f76bf9a0af8aae1f97d9c0c084b02509cbf8c2c000000", + recipeConfig: [ + { + op: "Gzip", + args: ["动态哈夫曼压缩", "comment", "", false] + }, + { + op: "删除字节", + args: [0, 10, false] + }, + { + op: "字符转十六进制", + args: ["无"] + } + ] + }, + { + name: "Gzip: Has a comment, no checksum and no filename", + input: "The quick brown fox jumped over the slow dog", + expectedOutput: "636f6d6d656e74000dc9dd0180200804e0556ea8262848fb3dc588c6a7e76faa8aeedb726036c68d951f76bf9a0af8aae1f97d9c0c084b02509cbf8c2c000000", + recipeConfig: [ + { + op: "Gzip", + args: ["动态哈夫曼压缩", "", "comment", false] + }, + { + op: "删除字节", + args: [0, 10, false] + }, + { + op: "字符转十六进制", + args: ["无"] + } + ] + }, + { + name: "Gzip: Has a comment, no checksum and has a filename", + input: "The quick brown fox jumped over the slow dog", + expectedOutput: "66696c656e616d6500636f6d6d656e74000dc9dd0180200804e0556ea8262848fb3dc588c6a7e76faa8aeedb726036c68d951f76bf9a0af8aae1f97d9c0c084b02509cbf8c2c000000", + recipeConfig: [ + { + op: "Gzip", + args: ["动态哈夫曼压缩", "filename", "comment", false] + }, + { + op: "删除字节", + args: [0, 10, false] + }, + { + op: "字符转十六进制", + args: ["无"] + } + ] + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/HASSH.mjs b/plugins/srktoolbox/tests/operations/tests/HASSH.mjs new file mode 100644 index 00000000..fef6c1fe --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/HASSH.mjs @@ -0,0 +1,35 @@ +/** + * HASSH tests. + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "HASSH Client Fingerprint", + input: "000003140814c639665f5425dcb80bf9f0a048380a410000007e6469666669652d68656c6c6d616e2d67726f75702d65786368616e67652d7368613235362c6469666669652d68656c6c6d616e2d67726f75702d65786368616e67652d736861312c6469666669652d68656c6c6d616e2d67726f757031342d736861312c6469666669652d68656c6c6d616e2d67726f7570312d736861310000000f7373682d7273612c7373682d6473730000009d6165733132382d6374722c6165733139322d6374722c6165733235362d6374722c617263666f75723235362c617263666f75723132382c6165733132382d6362632c336465732d6362632c626c6f77666973682d6362632c636173743132382d6362632c6165733139322d6362632c6165733235362d6362632c617263666f75722c72696a6e6461656c2d636263406c797361746f722e6c69752e73650000009d6165733132382d6374722c6165733139322d6374722c6165733235362d6374722c617263666f75723235362c617263666f75723132382c6165733132382d6362632c336465732d6362632c626c6f77666973682d6362632c636173743132382d6362632c6165733139322d6362632c6165733235362d6362632c617263666f75722c72696a6e6461656c2d636263406c797361746f722e6c69752e736500000069686d61632d6d64352c686d61632d736861312c756d61632d3634406f70656e7373682e636f6d2c686d61632d726970656d643136302c686d61632d726970656d64313630406f70656e7373682e636f6d2c686d61632d736861312d39362c686d61632d6d64352d393600000069686d61632d6d64352c686d61632d736861312c756d61632d3634406f70656e7373682e636f6d2c686d61632d726970656d643136302c686d61632d726970656d64313630406f70656e7373682e636f6d2c686d61632d736861312d39362c686d61632d6d64352d39360000001a6e6f6e652c7a6c6962406f70656e7373682e636f6d2c7a6c69620000001a6e6f6e652c7a6c6962406f70656e7373682e636f6d2c7a6c6962000000000000000000000000000000000000000000", + expectedOutput: "21b457a327ce7a2d4fce5ef2c42400bd", + recipeConfig: [ + { + "op": "HASSH客户端指纹", + "args": ["十六进制", "哈希摘要"] + } + ], + }, + { + name: "HASSH Server Fingerprint", + input: "0000027c0b142c7bb93a1da21c9e54f5862e60a5597c000000596469666669652d68656c6c6d616e2d67726f75702d65786368616e67652d736861312c6469666669652d68656c6c6d616e2d67726f757031342d736861312c6469666669652d68656c6c6d616e2d67726f7570312d736861310000000f7373682d7273612c7373682d647373000000876165733132382d6362632c336465732d6362632c626c6f77666973682d6362632c636173743132382d6362632c617263666f75722c6165733139322d6362632c6165733235362d6362632c72696a6e6461656c2d636263406c797361746f722e6c69752e73652c6165733132382d6374722c6165733139322d6374722c6165733235362d637472000000876165733132382d6362632c336465732d6362632c626c6f77666973682d6362632c636173743132382d6362632c617263666f75722c6165733139322d6362632c6165733235362d6362632c72696a6e6461656c2d636263406c797361746f722e6c69752e73652c6165733132382d6374722c6165733139322d6374722c6165733235362d63747200000055686d61632d6d64352c686d61632d736861312c686d61632d726970656d643136302c686d61632d726970656d64313630406f70656e7373682e636f6d2c686d61632d736861312d39362c686d61632d6d64352d393600000055686d61632d6d64352c686d61632d736861312c686d61632d726970656d643136302c686d61632d726970656d64313630406f70656e7373682e636f6d2c686d61632d736861312d39362c686d61632d6d64352d3936000000096e6f6e652c7a6c6962000000096e6f6e652c7a6c6962000000000000000000000000000000000000000000000000", + expectedOutput: "f430cd6761697a6a658ee1d45ed22e49", + recipeConfig: [ + { + "op": "HASSH服务器指纹", + "args": ["十六进制", "哈希摘要"] + } + ], + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/HKDF.mjs b/plugins/srktoolbox/tests/operations/tests/HKDF.mjs new file mode 100644 index 00000000..c08e15d4 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/HKDF.mjs @@ -0,0 +1,182 @@ +/** + * @author mikecat + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + "name": "HKDF: RFC5869 Test Case 1", + "input": "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", + "expectedOutput": "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865", + "recipeConfig": [ + { + "op": "十六进制转字符", + "args": ["无"], + }, + { + "op": "派生HKDF密钥", + "args": [ + {"option": "十六进制", "string": "000102030405060708090a0b0c"}, + {"option": "十六进制", "string": "f0f1f2f3f4f5f6f7f8f9"}, + "SHA256", "加盐", 42, + ], + }, + ], + }, + { + "name": "HKDF: RFC5869 Test Case 2", + "input": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f", + "expectedOutput": "b11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19afa97c59045a99cac7827271cb41c65e590e09da3275600c2f09b8367793a9aca3db71cc30c58179ec3e87c14c01d5c1f3434f1d87", + "recipeConfig": [ + { + "op": "十六进制转字符", + "args": ["无"], + }, + { + "op": "派生HKDF密钥", + "args": [ + {"option": "十六进制", "string": "606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf"}, + {"option": "十六进制", "string": "b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"}, + "SHA256", "加盐", 82, + ], + }, + ], + }, + { + "name": "HKDF: RFC5869 Test Case 3", + "input": "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", + "expectedOutput": "8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8", + "recipeConfig": [ + { + "op": "十六进制转字符", + "args": ["无"], + }, + { + "op": "派生HKDF密钥", + "args": [ + {"option": "十六进制", "string": ""}, + {"option": "十六进制", "string": ""}, + "SHA256", "加盐", 42, + ], + }, + ], + }, + { + "name": "HKDF: RFC5869 Test Case 4", + "input": "0b0b0b0b0b0b0b0b0b0b0b", + "expectedOutput": "085a01ea1b10f36933068b56efa5ad81a4f14b822f5b091568a9cdd4f155fda2c22e422478d305f3f896", + "recipeConfig": [ + { + "op": "十六进制转字符", + "args": ["无"], + }, + { + "op": "派生HKDF密钥", + "args": [ + {"option": "十六进制", "string": "000102030405060708090a0b0c"}, + {"option": "十六进制", "string": "f0f1f2f3f4f5f6f7f8f9"}, + "SHA1", "加盐", 42, + ], + }, + ], + }, + { + "name": "HKDF: RFC5869 Test Case 5", + "input": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f", + "expectedOutput": "0bd770a74d1160f7c9f12cd5912a06ebff6adcae899d92191fe4305673ba2ffe8fa3f1a4e5ad79f3f334b3b202b2173c486ea37ce3d397ed034c7f9dfeb15c5e927336d0441f4c4300e2cff0d0900b52d3b4", + "recipeConfig": [ + { + "op": "十六进制转字符", + "args": ["无"], + }, + { + "op": "派生HKDF密钥", + "args": [ + {"option": "十六进制", "string": "606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf"}, + {"option": "十六进制", "string": "b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"}, + "SHA1", "加盐", 82, + ], + }, + ], + }, + { + "name": "HKDF: RFC5869 Test Case 6", + "input": "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", + "expectedOutput": "0ac1af7002b3d761d1e55298da9d0506b9ae52057220a306e07b6b87e8df21d0ea00033de03984d34918", + "recipeConfig": [ + { + "op": "十六进制转字符", + "args": ["无"], + }, + { + "op": "派生HKDF密钥", + "args": [ + {"option": "十六进制", "string": ""}, + {"option": "十六进制", "string": ""}, + "SHA1", "加盐", 42, + ], + }, + ], + }, + { + "name": "HKDF: RFC5869 Test Case 7", + "input": "0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c", + "expectedOutput": "2c91117204d745f3500d636a62f64f0ab3bae548aa53d423b0d1f27ebba6f5e5673a081d70cce7acfc48", + "recipeConfig": [ + { + "op": "十六进制转字符", + "args": ["无"], + }, + { + "op": "派生HKDF密钥", + "args": [ + {"option": "十六进制", "string": ""}, + {"option": "十六进制", "string": ""}, + "SHA1", "不加盐", 42, + ], + }, + ], + }, + { + "name": "HKDF: RFC5869 Test Case 1 with skip extract", + "input": "077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5", + "expectedOutput": "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865", + "recipeConfig": [ + { + "op": "十六进制转字符", + "args": ["无"], + }, + { + "op": "派生HKDF密钥", + "args": [ + {"option": "十六进制", "string": ""}, + {"option": "十六进制", "string": "f0f1f2f3f4f5f6f7f8f9"}, + "SHA256", "跳过", 42, + ], + }, + ], + }, + { + "name": "HKDF: too large L", + "input": "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", + "expectedOutput": "L的值太大(对于SHA256的最大长度是8160)", + "recipeConfig": [ + { + "op": "十六进制转字符", + "args": ["无"], + }, + { + "op": "派生HKDF密钥", + "args": [ + {"option": "十六进制", "string": "000102030405060708090a0b0c"}, + {"option": "十六进制", "string": "f0f1f2f3f4f5f6f7f8f9"}, + "SHA256", "加盐", 8161, + ], + }, + ], + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/Hash.mjs b/plugins/srktoolbox/tests/operations/tests/Hash.mjs new file mode 100644 index 00000000..41a70b31 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/Hash.mjs @@ -0,0 +1,1164 @@ +/** + * Hash tests. + * + * @author Matt C [matt@artemisbot.uk] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "MD2", + input: "Hello, World!", + expectedOutput: "1c8f1e6a94aaa7145210bf90bb52871a", + recipeConfig: [ + { + "op": "MD2", + "args": [] + } + ] + }, + { + name: "MD4", + input: "Hello, World!", + expectedOutput: "94e3cb0fa9aa7a5ee3db74b79e915989", + recipeConfig: [ + { + "op": "MD4", + "args": [] + } + ] + }, + { + name: "MD5", + input: "Hello, World!", + expectedOutput: "65a8e27d8879283831b664bd8b7f0ad4", + recipeConfig: [ + { + "op": "MD5", + "args": [] + } + ] + }, + { + name: "MD6", + input: "Hello, World!", + expectedOutput: "ce5effce32637e6b8edaacc9284b873c3fd4e66f9779a79df67eb4a82dda8230", + recipeConfig: [ + { + "op": "MD6", + "args": [256, 64, ""] + } + ] + }, + { + name: "SHA0", + input: "Hello, World!", + expectedOutput: "5a5588f0407c6ae9a988758e76965f841b299229", + recipeConfig: [ + { + "op": "SHA0", + "args": [] + } + ] + }, + { + name: "SHA1", + input: "Hello, World!", + expectedOutput: "0a0a9f2a6772942557ab5355d76af442f8f65e01", + recipeConfig: [ + { + "op": "SHA1", + "args": [] + } + ] + }, + { + name: "SHA2 224", + input: "Hello, World!", + expectedOutput: "72a23dfa411ba6fde01dbfabf3b00a709c93ebf273dc29e2d8b261ff", + recipeConfig: [ + { + "op": "SHA2", + "args": ["224"] + } + ] + }, + { + name: "SHA2 384", + input: "Hello, World!", + expectedOutput: "5485cc9b3365b4305dfb4e8337e0a598a574f8242bf17289e0dd6c20a3cd44a089de16ab4ab308f63e44b1170eb5f515", + recipeConfig: [ + { + "op": "SHA2", + "args": ["384"] + } + ] + }, + { + name: "SHA2 256", + input: "Hello, World!", + expectedOutput: "dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f", + recipeConfig: [ + { + "op": "SHA2", + "args": ["256"] + } + ] + }, + { + name: "SHA2 512", + input: "Hello, World!", + expectedOutput: "374d794a95cdcfd8b35993185fef9ba368f160d8daf432d08ba9f1ed1e5abe6cc69291e0fa2fe0006a52570ef18c19def4e617c33ce52ef0a6e5fbe318cb0387", + recipeConfig: [ + { + "op": "SHA2", + "args": ["512"] + } + ] + }, + { + name: "SHA2 512/224", + input: "Hello, World!", + expectedOutput: "766745f058e8a0438f19de48ae56ea5f123fe738af39bca050a7547a", + recipeConfig: [ + { + "op": "SHA2", + "args": ["512/224"] + } + ] + }, + { + name: "SHA2 512/256", + input: "Hello, World!", + expectedOutput: "0686f0a605973dc1bf035d1e2b9bad1985a0bff712ddd88abd8d2593e5f99030", + recipeConfig: [ + { + "op": "SHA2", + "args": ["512/256"] + } + ] + }, + { + name: "SHA3 224", + input: "Hello, World!", + expectedOutput: "853048fb8b11462b6100385633c0cc8dcdc6e2b8e376c28102bc84f2", + recipeConfig: [ + { + "op": "SHA3", + "args": ["224"] + } + ] + }, + { + name: "SHA3 384", + input: "Hello, World!", + expectedOutput: "aa9ad8a49f31d2ddcabbb7010a1566417cff803fef50eba239558826f872e468c5743e7f026b0a8e5b2d7a1cc465cdbe", + recipeConfig: [ + { + "op": "SHA3", + "args": ["384"] + } + ] + }, + { + name: "SHA3 256", + input: "Hello, World!", + expectedOutput: "1af17a664e3fa8e419b8ba05c2a173169df76162a5a286e0c405b460d478f7ef", + recipeConfig: [ + { + "op": "SHA3", + "args": ["256"] + } + ] + }, + { + name: "SHA3 512", + input: "Hello, World!", + expectedOutput: "38e05c33d7b067127f217d8c856e554fcff09c9320b8a5979ce2ff5d95dd27ba35d1fba50c562dfd1d6cc48bc9c5baa4390894418cc942d968f97bcb659419ed", + recipeConfig: [ + { + "op": "SHA3", + "args": ["512"] + } + ] + }, + { + name: "Keccak 224", + input: "Hello, World!", + expectedOutput: "4eaaf0e7a1e400efba71130722e1cb4d59b32afb400e654afec4f8ce", + recipeConfig: [ + { + "op": "Keccak", + "args": ["224"] + } + ] + }, + { + name: "Keccak 384", + input: "Hello, World!", + expectedOutput: "4d60892fde7f967bcabdc47c73122ae6311fa1f9be90d721da32030f7467a2e3db3f9ccb3c746483f9d2b876e39def17", + recipeConfig: [ + { + "op": "Keccak", + "args": ["384"] + } + ] + }, + { + name: "Keccak 256", + input: "Hello, World!", + expectedOutput: "acaf3289d7b601cbd114fb36c4d29c85bbfd5e133f14cb355c3fd8d99367964f", + recipeConfig: [ + { + "op": "Keccak", + "args": ["256"] + } + ] + }, + { + name: "Keccak 512", + input: "Hello, World!", + expectedOutput: "eda765576c84c600ed7f5d97510e92703b61f5215def2a161037fd9dd1f5b6ed4f86ce46073c0e3f34b52de0289e9c618798fff9dd4b1bfe035bdb8645fc6e37", + recipeConfig: [ + { + "op": "Keccak", + "args": ["512"] + } + ] + }, + { + name: "Shake 128", + input: "Hello, World!", + expectedOutput: "2bf5e6dee6079fad604f573194ba8426bd4d30eb13e8ba2edae70e529b570cbd", + recipeConfig: [ + { + "op": "Shake", + "args": ["128", 256] + } + ] + }, + { + name: "Shake 256", + input: "Hello, World!", + expectedOutput: "b3be97bfd978833a65588ceae8a34cf59e95585af62063e6b89d0789f372424e8b0d1be4f21b40ce5a83a438473271e0661854f02d431db74e6904d6c347d757", + recipeConfig: [ + { + "op": "Shake", + "args": ["256", 512] + } + ] + }, + { + name: "RIPEMD 128", + input: "Hello, World!", + expectedOutput: "67f9fe75ca2886dc76ad00f7276bdeba", + recipeConfig: [ + { + "op": "RIPEMD", + "args": ["128"] + } + ] + }, + { + name: "RIPEMD 160", + input: "Hello, World!", + expectedOutput: "527a6a4b9a6da75607546842e0e00105350b1aaf", + recipeConfig: [ + { + "op": "RIPEMD", + "args": ["160"] + } + ] + }, + { + name: "RIPEMD 256", + input: "Hello, World!", + expectedOutput: "567750c6d34dcba7ae038a80016f3ca3260ec25bfdb0b68bbb8e730b00b2447d", + recipeConfig: [ + { + "op": "RIPEMD", + "args": ["256"] + } + ] + }, + { + name: "RIPEMD 320", + input: "Hello, World!", + expectedOutput: "f9832e5bb00576fc56c2221f404eb77addeafe49843c773f0df3fc5a996d5934f3c96e94aeb80e89", + recipeConfig: [ + { + "op": "RIPEMD", + "args": ["320"] + } + ] + }, + { + name: "HAS-160", + input: "Hello, World!", + expectedOutput: "8f6dd8d7c8a04b1cb3831adc358b1e4ac2ed5984", + recipeConfig: [ + { + "op": "HAS-160", + "args": [] + } + ] + }, + { + name: "Whirlpool-0", + input: "Hello, World!", + expectedOutput: "1c327026f565a0105a827efbfb3d3635cdb042c0aabb8416e96deb128e6c5c8684b13541cf31c26c1488949df050311c6999a12eb0e7002ad716350f5c7700ca", + recipeConfig: [ + { + "op": "Whirlpool", + "args": ["Whirlpool-0"] + } + ] + }, + { + name: "Whirlpool-T", + input: "Hello, World!", + expectedOutput: "16c581089b6a6f356ae56e16a63a4c613eecd82a2a894b293f5ee45c37a31d09d7a8b60bfa7e414bd4a7166662cea882b5cf8c96b7d583fc610ad202591bcdb1", + recipeConfig: [ + { + "op": "Whirlpool", + "args": ["Whirlpool-T"] + } + ] + }, + { + name: "Whirlpool", + input: "Hello, World!", + expectedOutput: "3d837c9ef7bb291bd1dcfc05d3004af2eeb8c631dd6a6c4ba35159b8889de4b1ec44076ce7a8f7bfa497e4d9dcb7c29337173f78d06791f3c3d9e00cc6017f0b", + recipeConfig: [ + { + "op": "Whirlpool", + "args": ["Whirlpool"] + } + ] + }, + { + name: "Snefru 2 128", + input: "Hello, World!", + expectedOutput: "a4ad2b8848580511d0884fb4233a7e7a", + recipeConfig: [ + { + "op": "Snefru", + "args": ["128", "2"] + } + ] + }, + { + name: "Snefru 4 128", + input: "Hello, World!", + expectedOutput: "d154eae2c9ffbcd2e1bdaf0b84736126", + recipeConfig: [ + { + "op": "Snefru", + "args": ["128", "4"] + } + ] + }, + { + name: "Snefru 8 128", + input: "Hello, World!", + expectedOutput: "6f3d55b69557abb0a3c4e9de9d29ba5d", + recipeConfig: [ + { + "op": "Snefru", + "args": ["128", "8"] + } + ] + }, + { + name: "Snefru 2 256", + input: "Hello, World!", + expectedOutput: "65736daba648de28ef4c4a316b4684584ecf9f22ddb5c457729e6bf0f40113c4", + recipeConfig: [ + { + "op": "Snefru", + "args": ["256", "2"] + } + ] + }, + { + name: "Snefru 4 256", + input: "Hello, World!", + expectedOutput: "71b0ea4b3e33f2e58bcc67c8a8de060b99ec0107355bbfdc18d8f65f0194ffcc", + recipeConfig: [ + { + "op": "Snefru", + "args": ["256", "4"] + } + ] + }, + { + name: "Snefru 8 256", + input: "Hello, World!", + expectedOutput: "255cd401414c79588cf689e8d5ff0536a2cfab83fcae36e654f202b09bc4b8a7", + recipeConfig: [ + { + "op": "Snefru", + "args": ["256", "8"] + } + ] + }, + { + name: "SM3 256 64", + input: "Hello, World!", + expectedOutput: "7ed26cbf0bee4ca7d55c1e64714c4aa7d1f163089ef5ceb603cd102c81fbcbc5", + recipeConfig: [ + { + "op": "SM3", + "args": ["256", "64"] + } + ] + }, + { + name: "HMAC: SHA256", + input: "Hello, World!", + expectedOutput: "52589bd80ccfa4acbb3f9512dfaf4f700fa5195008aae0b77a9e47dcca75beac", + recipeConfig: [ + { + "op": "HMAC", + "args": [{"option": "Latin1", "string": "test"}, "SHA256"] + } + ] + }, + { + name: "HMAC: RFC4231 Test Case 1 SHA-224", + input: "Hi There", + expectedOutput: "896fb1128abbdf196832107cd49df33f47b4b1169912ba4f53684b22", + recipeConfig: [ + { + "op": "HMAC", + "args": [{"option": "十六进制", "string": "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"}, "SHA224"] + } + ] + }, + { + name: "HMAC: RFC4231 Test Case 1 SHA-256", + input: "Hi There", + expectedOutput: "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7", + recipeConfig: [ + { + "op": "HMAC", + "args": [{"option": "十六进制", "string": "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"}, "SHA256"] + } + ] + }, + { + name: "HMAC: RFC4231 Test Case 1 SHA-384", + input: "Hi There", + expectedOutput: "afd03944d84895626b0825f4ab46907f15f9dadbe4101ec682aa034c7cebc59cfaea9ea9076ede7f4af152e8b2fa9cb6", + recipeConfig: [ + { + "op": "HMAC", + "args": [{"option": "十六进制", "string": "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"}, "SHA384"] + } + ] + }, + { + name: "HMAC: RFC4231 Test Case 1 SHA-512", + input: "Hi There", + expectedOutput: "87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854", + recipeConfig: [ + { + "op": "HMAC", + "args": [{"option": "十六进制", "string": "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"}, "SHA512"] + } + ] + }, + { + name: "HMAC: RFC4231 Test Case 2 SHA-224", + input: "what do ya want for nothing?", + expectedOutput: "a30e01098bc6dbbf45690f3a7e9e6d0f8bbea2a39e6148008fd05e44", + recipeConfig: [ + { + "op": "HMAC", + "args": [{"option": "十六进制", "string": "4a656665"}, "SHA224"] + } + ] + }, + { + name: "HMAC: RFC4231 Test Case 2 SHA-256", + input: "what do ya want for nothing?", + expectedOutput: "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843", + recipeConfig: [ + { + "op": "HMAC", + "args": [{"option": "十六进制", "string": "4a656665"}, "SHA256"] + } + ] + }, + { + name: "HMAC: RFC4231 Test Case 2 SHA-384", + input: "what do ya want for nothing?", + expectedOutput: "af45d2e376484031617f78d2b58a6b1b9c7ef464f5a01b47e42ec3736322445e8e2240ca5e69e2c78b3239ecfab21649", + recipeConfig: [ + { + "op": "HMAC", + "args": [{"option": "十六进制", "string": "4a656665"}, "SHA384"] + } + ] + }, + { + name: "HMAC: RFC4231 Test Case 2 SHA-512", + input: "what do ya want for nothing?", + expectedOutput: "164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737", + recipeConfig: [ + { + "op": "HMAC", + "args": [{"option": "十六进制", "string": "4a656665"}, "SHA512"] + } + ] + }, + { + name: "HMAC: RFC4231 Test Case 3 SHA-224", + input: "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", + expectedOutput: "7fb3cb3588c6c1f6ffa9694d7d6ad2649365b0c1f65d69d1ec8333ea", + recipeConfig: [ + { + "op": "十六进制转字符", + "args": ["无"] + }, + { + "op": "HMAC", + "args": [{"option": "十六进制", "string": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, "SHA224"] + } + ] + }, + { + name: "HMAC: RFC4231 Test Case 3 SHA-256", + input: "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", + expectedOutput: "773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe", + recipeConfig: [ + { + "op": "十六进制转字符", + "args": ["无"] + }, + { + "op": "HMAC", + "args": [{"option": "十六进制", "string": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, "SHA256"] + } + ] + }, + { + name: "HMAC: RFC4231 Test Case 3 SHA-384", + input: "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", + expectedOutput: "88062608d3e6ad8a0aa2ace014c8a86f0aa635d947ac9febe83ef4e55966144b2a5ab39dc13814b94e3ab6e101a34f27", + recipeConfig: [ + { + "op": "十六进制转字符", + "args": ["无"] + }, + { + "op": "HMAC", + "args": [{"option": "十六进制", "string": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, "SHA384"] + } + ] + }, + { + name: "HMAC: RFC4231 Test Case 3 SHA-512", + input: "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", + expectedOutput: "fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d39bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e13292fb", + recipeConfig: [ + { + "op": "十六进制转字符", + "args": ["无"] + }, + { + "op": "HMAC", + "args": [{"option": "十六进制", "string": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, "SHA512"] + } + ] + }, + { + name: "HMAC: RFC4231 Test Case 4 SHA-224", + input: "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + expectedOutput: "6c11506874013cac6a2abc1bb382627cec6a90d86efc012de7afec5a", + recipeConfig: [ + { + "op": "十六进制转字符", + "args": ["无"] + }, + { + "op": "HMAC", + "args": [{"option": "十六进制", "string": "0102030405060708090a0b0c0d0e0f10111213141516171819"}, "SHA224"] + } + ] + }, + { + name: "HMAC: RFC4231 Test Case 4 SHA-256", + input: "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + expectedOutput: "82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b", + recipeConfig: [ + { + "op": "十六进制转字符", + "args": ["无"] + }, + { + "op": "HMAC", + "args": [{"option": "十六进制", "string": "0102030405060708090a0b0c0d0e0f10111213141516171819"}, "SHA256"] + } + ] + }, + { + name: "HMAC: RFC4231 Test Case 4 SHA-384", + input: "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + expectedOutput: "3e8a69b7783c25851933ab6290af6ca77a9981480850009cc5577c6e1f573b4e6801dd23c4a7d679ccf8a386c674cffb", + recipeConfig: [ + { + "op": "十六进制转字符", + "args": ["无"] + }, + { + "op": "HMAC", + "args": [{"option": "十六进制", "string": "0102030405060708090a0b0c0d0e0f10111213141516171819"}, "SHA384"] + } + ] + }, + { + name: "HMAC: RFC4231 Test Case 4 SHA-512", + input: "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + expectedOutput: "b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a298dd", + recipeConfig: [ + { + "op": "十六进制转字符", + "args": ["无"] + }, + { + "op": "HMAC", + "args": [{"option": "十六进制", "string": "0102030405060708090a0b0c0d0e0f10111213141516171819"}, "SHA512"] + } + ] + }, + { + name: "HMAC: RFC4231 Test Case 6 SHA-224", + input: "Test Using Larger Than Block-Size Key - Hash Key First", + expectedOutput: "95e9a0db962095adaebe9b2d6f0dbce2d499f112f2d2b7273fa6870e", + recipeConfig: [ + { + "op": "HMAC", + "args": [{"option": "十六进制", "string": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, "SHA224"] + } + ] + }, + { + name: "HMAC: RFC4231 Test Case 6 SHA-256", + input: "Test Using Larger Than Block-Size Key - Hash Key First", + expectedOutput: "60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54", + recipeConfig: [ + { + "op": "HMAC", + "args": [{"option": "十六进制", "string": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, "SHA256"] + } + ] + }, + { + name: "HMAC: RFC4231 Test Case 6 SHA-384", + input: "Test Using Larger Than Block-Size Key - Hash Key First", + expectedOutput: "4ece084485813e9088d2c63a041bc5b44f9ef1012a2b588f3cd11f05033ac4c60c2ef6ab4030fe8296248df163f44952", + recipeConfig: [ + { + "op": "HMAC", + "args": [{"option": "十六进制", "string": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, "SHA384"] + } + ] + }, + { + name: "HMAC: RFC4231 Test Case 6 SHA-512", + input: "Test Using Larger Than Block-Size Key - Hash Key First", + expectedOutput: "80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b013783f8f3526b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec8b915a985d786598", + recipeConfig: [ + { + "op": "HMAC", + "args": [{"option": "十六进制", "string": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, "SHA512"] + } + ] + }, + { + name: "HMAC: RFC4231 Test Case 7 SHA-224", + input: "This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm.", + expectedOutput: "3a854166ac5d9f023f54d517d0b39dbd946770db9c2b95c9f6f565d1", + recipeConfig: [ + { + "op": "HMAC", + "args": [{"option": "十六进制", "string": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, "SHA224"] + } + ] + }, + { + name: "HMAC: RFC4231 Test Case 7 SHA-256", + input: "This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm.", + expectedOutput: "9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2", + recipeConfig: [ + { + "op": "HMAC", + "args": [{"option": "十六进制", "string": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, "SHA256"] + } + ] + }, + { + name: "HMAC: RFC4231 Test Case 7 SHA-384", + input: "This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm.", + expectedOutput: "6617178e941f020d351e2f254e8fd32c602420feb0b8fb9adccebb82461e99c5a678cc31e799176d3860e6110c46523e", + recipeConfig: [ + { + "op": "HMAC", + "args": [{"option": "十六进制", "string": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, "SHA384"] + } + ] + }, + { + name: "HMAC: RFC4231 Test Case 7 SHA-512", + input: "This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm.", + expectedOutput: "e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d20cdc944b6022cac3c4982b10d5eeb55c3e4de15134676fb6de0446065c97440fa8c6a58", + recipeConfig: [ + { + "op": "HMAC", + "args": [{"option": "十六进制", "string": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, "SHA512"] + } + ] + }, + { + name: "MD5: Complex bytes", + input: "10dc10e32010de10d010dc10d810d910d010e12e", + expectedOutput: "4f4f02e2646545aa8fc42f613c9aa068", + recipeConfig: [ + { + "op": "十六进制转字符", + "args": ["无"] + }, + { + "op": "MD5", + "args": [] + } + ] + }, + { + name: "SHA1: Complex bytes", + input: "10dc10e32010de10d010dc10d810d910d010e12e", + expectedOutput: "2c5400aaee7e8ad4cad29bfbdf8d566924e5442c", + recipeConfig: [ + { + "op": "十六进制转字符", + "args": ["无"] + }, + { + "op": "SHA1", + "args": [] + } + ] + }, + { + name: "SHA2 224: Complex bytes", + input: "10dc10e32010de10d010dc10d810d910d010e12e", + expectedOutput: "66c166eba2529ecc44a7b7b218a64a8e3892f873c8d231e8e3c1ef3d", + recipeConfig: [ + { + "op": "十六进制转字符", + "args": ["无"] + }, + { + "op": "SHA2", + "args": ["224"] + } + ] + }, + { + name: "SHA2 256: Complex bytes", + input: "10dc10e32010de10d010dc10d810d910d010e12e", + expectedOutput: "186ffd22c3af83995afa4a0316023f81a7f8834fd16bd2ed358c7b1b8182ba41", + recipeConfig: [ + { + "op": "十六进制转字符", + "args": ["无"] + }, + { + "op": "SHA2", + "args": ["256"] + } + ] + }, + { + name: "SHA2 384: Complex bytes", + input: "10dc10e32010de10d010dc10d810d910d010e12e", + expectedOutput: "2a6369ffec550ea0bfb810b3b8246b7d6b7f060edfae88441f0f242b98b91549aa4ff407de38c6d03b5f377434ad2f36", + recipeConfig: [ + { + "op": "十六进制转字符", + "args": ["无"] + }, + { + "op": "SHA2", + "args": ["384"] + } + ] + }, + { + name: "SHA2 512: Complex bytes", + input: "10dc10e32010de10d010dc10d810d910d010e12e", + expectedOutput: "544ae686522c05b70d12b460b5b39ea0a758eb4027333edbded7e2b3f467aa605804f71f54db61a7bbe50e6e7898510635efd6721fd418a9ea4d05b286d12806", + recipeConfig: [ + { + "op": "十六进制转字符", + "args": ["无"] + }, + { + "op": "SHA2", + "args": ["512"] + } + ] + }, + { + name: "SHA3 224: Complex bytes", + input: "10dc10e32010de10d010dc10d810d910d010e12e", + expectedOutput: "e2c07562ee8c2d73e3dd309efea257159abd0948ebc14619bab9ffb3", + recipeConfig: [ + { + "op": "十六进制转字符", + "args": ["无"] + }, + { + "op": "SHA3", + "args": ["224"] + } + ] + }, + { + name: "SHA3 256: Complex bytes", + input: "10dc10e32010de10d010dc10d810d910d010e12e", + expectedOutput: "55a55275387586afd1ed64757c9ee7ad1d96ca81a5b7b742c40127856ee78a2d", + recipeConfig: [ + { + "op": "十六进制转字符", + "args": ["无"] + }, + { + "op": "SHA3", + "args": ["256"] + } + ] + }, + { + name: "SHA3 384: Complex bytes", + input: "10dc10e32010de10d010dc10d810d910d010e12e", + expectedOutput: "39f8796dd697dc39e5a943817833793f2c29dc0d1adc7037854c0fb51e135c6bd26b113240c4fb1e3fcc16ff8690c91a", + recipeConfig: [ + { + "op": "十六进制转字符", + "args": ["无"] + }, + { + "op": "SHA3", + "args": ["384"] + } + ] + }, + { + name: "SHA3 512: Complex bytes", + input: "10dc10e32010de10d010dc10d810d910d010e12e", + expectedOutput: "ee9061bed83b1ad1e2fc4a4bac72a5a65a23a0fa55193b808af0a3e2013b718a5a3e40474765b4f93d1b2747401058a5b58099cc890a159db92b2ea816287add", + recipeConfig: [ + { + "op": "十六进制转字符", + "args": ["无"] + }, + { + "op": "SHA3", + "args": ["512"] + } + ] + }, + { + name: "MD5: UTF-8", + input: "ნუ პანიკას", + expectedOutput: "2e93ee2b5b2a337ccb678c7db12eff1b", + recipeConfig: [ + { + "op": "MD5", + "args": [] + } + ] + }, + { + name: "SHA1: UTF-8", + input: "ნუ პანიკას", + expectedOutput: "87f483b1515dce672be044bf183ae8103e3b2d4b", + recipeConfig: [ + { + "op": "SHA1", + "args": [] + } + ] + }, + { + name: "SHA2 224: UTF-8", + input: "ნუ პანიკას", + expectedOutput: "563ca57b500157717961a5fa87ce42c6db76a488c98ea9c28d620770", + recipeConfig: [ + { + "op": "SHA2", + "args": ["224"] + } + ] + }, + { + name: "SHA2 256: UTF-8", + input: "ნუ პანიკას", + expectedOutput: "36abbb4622ffff06aa3e3cea266765601b21457bb3755a0a2cf0a206422863c1", + recipeConfig: [ + { + "op": "SHA2", + "args": ["256"] + } + ] + }, + { + name: "SHA2 384: UTF-8", + input: "ნუ პანიკას", + expectedOutput: "140b929391a66c9a943bcd60e6964f0d19526d3bc9ba020fbb29aae51cddb8e63a78784d8770f1d36335bf4efff8c131", + recipeConfig: [ + { + "op": "SHA2", + "args": ["384"] + } + ] + }, + { + name: "SHA2 512: UTF-8", + input: "ნუ პანიკას", + expectedOutput: "04a7887c400bf647b7c67b9a0f1ada70d176348b5afdfebea184f7e62748849828669c7b5160be99455fdbf625589bd1689c003bc06ef60c39607d825a2f8838", + recipeConfig: [ + { + "op": "SHA2", + "args": ["512"] + } + ] + }, + { + name: "SHA3 224: UTF-8", + input: "ნუ პანიკას", + expectedOutput: "b3ffc9620949f879cb561fb240452494e2566cb4e4f701a85715e14f", + recipeConfig: [ + { + "op": "SHA3", + "args": ["224"] + } + ] + }, + { + name: "SHA3 256: UTF-8", + input: "ნუ პანიკას", + expectedOutput: "b5f247d725b46546c832502cd07bccb5d4de0c41a6665d3944ed2cc55cd9d156", + recipeConfig: [ + { + "op": "SHA3", + "args": ["256"] + } + ] + }, + { + name: "SHA3 384: UTF-8", + input: "ნუ პანიკას", + expectedOutput: "93e87b9aa8c9c47eba146adac357c525b418b71677f6db01d1c760d87b058682e639c8d43a8bfe91529cecd9800700e3", + recipeConfig: [ + { + "op": "SHA3", + "args": ["384"] + } + ] + }, + { + name: "SHA3 512: UTF-8", + input: "ნუ პანიკას", + expectedOutput: "1fbc484b5184982561795162757717474eebc846ca9f10029a75a54cdd897a7b48d1db42f2478fa1d5d213a0dd7de71c809cb19c60581ba57e7289d29408fb36", + recipeConfig: [ + { + "op": "SHA3", + "args": ["512"] + } + ] + }, + { + name: "Bcrypt compare: dolphin", + input: "dolphin", + expectedOutput: "匹配: dolphin", + recipeConfig: [ + { + op: "Bcrypt比较", + args: ["$2a$10$qyon0LQCmMxpFFjwWH6Qh.dDdhqntQh./IN0RXCc3XIMILuOYZKgK"] + } + ] + }, + { + name: "Scrypt: RFC test vector 1", + input: "", + expectedOutput: "77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3fede21442fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628cf35e20c38d18906", + recipeConfig: [ + { + op: "Scrypt", + args: [ + { + "option": "Latin1", + "string": "" + }, + 16, 1, 1, 64 + ] + } + ] + }, + { + name: "Scrypt: RFC test vector 2", + input: "password", + expectedOutput: "fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b3731622eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640", + recipeConfig: [ + { + op: "Scrypt", + args: [ + { + "option": "Latin1", + "string": "NaCl" + }, + 1024, 8, 16, 64 + ] + } + ] + }, + { + name: "Scrypt: RFC test vector 3", + input: "pleaseletmein", + expectedOutput: "7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2d5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887", + recipeConfig: [ + { + op: "Scrypt", + args: [ + { + "option": "Latin1", + "string": "SodiumChloride" + }, + 16384, 8, 1, 64 + ] + } + ] + }, + { + name: "Streebog-256: Test Case 1", + input: "", + expectedOutput: "3f539a213e97c802cc229d474c6aa32a825a360b2a933a949fd925208d9ce1bb", + recipeConfig: [ + { + op: "Streebog", + args: ["256"] + } + ] + }, + { + name: "Streebog-256: Test Case 2", + input: "The quick brown fox jumps over the lazy dog", + expectedOutput: "3e7dea7f2384b6c5a3d0e24aaa29c05e89ddd762145030ec22c71a6db8b2c1f4", + recipeConfig: [ + { + op: "Streebog", + args: ["256"] + } + ] + }, + { + name: "Streebog-512: Test Case 1", + input: "", + expectedOutput: "8e945da209aa869f0455928529bcae4679e9873ab707b55315f56ceb98bef0a7362f715528356ee83cda5f2aac4c6ad2ba3a715c1bcd81cb8e9f90bf4c1c1a8a", + recipeConfig: [ + { + op: "Streebog", + args: ["512"] + } + ] + }, + { + name: "Streebog-512: Test Case 2", + input: "The quick brown fox jumps over the lazy dog", + expectedOutput: "d2b793a0bb6cb5904828b5b6dcfb443bb8f33efc06ad09368878ae4cdc8245b97e60802469bed1e7c21a64ff0b179a6a1e0bb74d92965450a0adab69162c00fe", + recipeConfig: [ + { + op: "Streebog", + args: ["512"] + } + ] + }, + { + name: "GOST R 34.11-94: Test Case 1", + input: "", + expectedOutput: "981e5f3ca30c841487830f84fb433e13ac1101569b9c13584ac483234cd656c0", + recipeConfig: [ + { + op: "GOST哈希", + args: ["GOST 28147 (1994)", "256", "D-A"] + } + ] + }, + { + name: "GOST R 34.11-94: Test Case 2", + input: "This is message, length=32 bytes", + expectedOutput: "2cefc2f7b7bdc514e18ea57fa74ff357e7fa17d652c75f69cb1be7893ede48eb", + recipeConfig: [ + { + op: "GOST哈希", + args: ["GOST 28147 (1994)", "256", "D-A"] + } + ] + }, + /* { // This takes a LONG time to run (over a minute usually). + name: "Scrypt: RFC test vector 4", + input: "pleaseletmein", + expectedOutput: "2101cb9b6a511aaeaddbbe09cf70f881ec568d574a2ffd4dabe5ee9820adaa478e56fd8f4ba5d09ffa1c6d927c40f4c337304049e8a952fbcbf45c6fa77a41a4", + recipeConfig: [ + { + op: "Scrypt", + args: [ + { + "option": "Latin1", + "string": "SodiumChloride" + }, + 1048576, 8, 1, 64 + ] + } + ] + }, */ + { + name: "Argon2", + input: "argon2password", + expectedOutput: "$argon2i$v=19$m=4096,t=3,p=1$c29tZXNhbHQ$s43my9eBljQADuF/LWCG8vGqwAJzOorKQ0Yog8jFvbw", + recipeConfig: [ + { + op: "Argon2", + args: [ + {"option": "UTF8", "string": "somesalt"}, + 3, + 4096, + 1, + 32, + "Argon2i", + "编码哈希" + ] + } + ] + }, + { + name: "Argon2 compare", + input: "argon2password", + expectedOutput: "匹配:argon2password", + recipeConfig: [ + { + op: "Argon2比较", + args: [ + "$argon2i$v=19$m=4096,t=3,p=1$c29tZXNhbHQ$s43my9eBljQADuF/LWCG8vGqwAJzOorKQ0Yog8jFvbw" + ] + } + ] + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/HaversineDistance.mjs b/plugins/srktoolbox/tests/operations/tests/HaversineDistance.mjs new file mode 100644 index 00000000..b0647c55 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/HaversineDistance.mjs @@ -0,0 +1,35 @@ +/** + * Haversine distance tests. + * + * @author Dachande663 [dachande663@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "半正矢距离", + input: "51.487263,-0.124323, 38.9517,-77.1467", + expectedOutput: "5902542.836307819", + recipeConfig: [ + { + "op": "半正矢距离", + "args": [] + } + ], + }, + { + name: "Haversine distance, zero distance", + input: "51.487263,-0.124323, 51.487263,-0.124323", + expectedOutput: "0", + recipeConfig: [ + { + "op": "半正矢距离", + "args": [] + } + ], + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/Hex.mjs b/plugins/srktoolbox/tests/operations/tests/Hex.mjs new file mode 100644 index 00000000..80d1e82b --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/Hex.mjs @@ -0,0 +1,127 @@ +/* +* Modified by Raka-loah@github for zh-CN i18n +*/ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "ASCII to Hex stream", + input: "aberystwyth", + expectedOutput: "6162657279737477797468", + recipeConfig: [ + { + "op": "字符转十六进制", + "args": [ + "无", + 0 + ] + }, + ] + }, + { + name: "ASCII to Hex with colon deliminator ", + input: "aberystwyth", + expectedOutput: "61:62:65:72:79:73:74:77:79:74:68", + recipeConfig: [ + { + "op": "字符转十六进制", + "args": [ + "冒号", + 0 + ] + } + ] + }, + { + name: "ASCII to 0x Hex with comma", + input: "aberystwyth", + expectedOutput: "0x61,0x62,0x65,0x72,0x79,0x73,0x74,0x77,0x79,0x74,0x68", + recipeConfig: [ + { + "op": "字符转十六进制", + "args": [ + "0x和逗号", + 0 + ] + } + ] + }, + { + name: "ASCII to Hex with percent deliminator", + input: "aberystwyth", + expectedOutput: "%61%62%65%72%79%73%74%77%79%74%68", + recipeConfig: [ + { + "op": "To Hex", + "args": [ + "Percent", + 0 + ] + } + ] + }, + { + name: "ASCII to 0x Hex with comma and line breaks", + input: "aberystwyth", + expectedOutput: "0x61,0x62,0x65,0x72,\n0x79,0x73,0x74,0x77,\n0x79,0x74,0x68", + recipeConfig: [ + { + "op": "字符转十六进制", + "args": [ + "0x和逗号", + 4 + ] + } + ] + }, + { + name: "Hex stream to UTF-8", + input: "e69591e69591e5ada9e5ad90", + expectedOutput: "救救孩子", + recipeConfig: [ + { + "op": "十六进制转字符", + "args": [ + "自动" + ] + } + ] + + }, + { + name: "Multiline 0x hex to ASCII", + input: "0x49,0x20,0x73,0x61,0x77,0x20,0x6d,0x79,0x73,0x65,0x6c,0x66,0x20,0x73,0x69,\ +0x74,0x74,0x69,0x6e,0x67,0x20,0x69,0x6e,0x20,0x74,0x68,0x65,0x20,0x63,0x72,\ +0x6f,0x74,0x63,0x68,0x20,0x6f,0x66,0x20,0x74,0x68,0x65,0x20,0x74,0x68,0x69,\ +0x73,0x20,0x66,0x69,0x67,0x20,0x74,0x72,0x65,0x65,0x2c,0x20,0x73,0x74,0x61,\ +0x72,0x76,0x69,0x6e,0x67,0x20,0x74,0x6f,0x20,0x64,0x65,0x61,0x74,0x68,0x2c,\ +0x20,0x6a,0x75,0x73,0x74,0x20,0x62,0x65,0x63,0x61,0x75,0x73,0x65,0x20,0x49,\ +0x20,0x63,0x6f,0x75,0x6c,0x64,0x6e,0x27,0x74,0x20,0x6d,0x61,0x6b,0x65,0x20,\ +0x75,0x70,0x20,0x6d,0x79,0x20,0x6d,0x69,0x6e,0x64,0x20,0x77,0x68,0x69,0x63,\ +0x68,0x20,0x6f,0x66,0x20,0x74,0x68,0x65,0x20,0x66,0x69,0x67,0x73,0x20,0x49,\ +0x20,0x77,0x6f,0x75,0x6c,0x64,0x20,0x63,0x68,0x6f,0x6f,0x73,0x65,0x2e", + expectedOutput: "I saw myself sitting in the crotch of the this fig tree, starving to death, just because I couldn't make up my mind which of the figs I would choose.", + recipeConfig: [ + { + "op": "十六进制转字符", + "args": [ + "自动" + ] + } + ] + }, + { + name: "0x with Comma to Ascii", + input: "0x74,0x65,0x73,0x74,0x20,0x73,0x74,0x72,0x69,0x6e,0x67", + expectedOutput: "test string", + recipeConfig: [ + { + "op": "十六进制转字符", + "args": [ + "0x和逗号" + ] + } + ] + + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/Hexdump.mjs b/plugins/srktoolbox/tests/operations/tests/Hexdump.mjs new file mode 100644 index 00000000..bdfa9cf8 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/Hexdump.mjs @@ -0,0 +1,274 @@ +/** + * Hexdump tests. + * + * @author n1474335 [n1474335@gmail.com] + * + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +const ALL_BYTES = [ + "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f", + "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f", + "\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f", + "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f", + "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f", + "\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f", + "\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f", + "\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f", + "\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f", + "\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf", + "\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf", + "\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf", + "\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf", + "\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef", + "\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff", +].join(""); + +TestRegister.addTests([ + { + name: "Hexdump: nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "转换到Hexdump", + args: [16, false, false] + }, + { + op: "从Hexdump提取", + args: [] + } + ], + }, + { + name: "Hexdump: Hello, World!", + input: "Hello, World!", + expectedOutput: "Hello, World!", + recipeConfig: [ + { + op: "转换到Hexdump", + args: [16, false, false] + }, + { + op: "从Hexdump提取", + args: [] + } + ], + }, + { + name: "Hexdump: UTF-8", + input: "ნუ პანიკას", + expectedOutput: "ნუ პანიკას", + recipeConfig: [ + { + op: "转换到Hexdump", + args: [16, false, false] + }, + { + op: "从Hexdump提取", + args: [] + } + ], + }, + { + name: "Hexdump: All bytes", + input: ALL_BYTES, + expectedOutput: ALL_BYTES, + recipeConfig: [ + { + op: "转换到Hexdump", + args: [16, false, false] + }, + { + op: "从Hexdump提取", + args: [] + } + ], + }, + { + name: "To Hexdump: UTF-8", + input: "ნუ პანიკას", + expectedOutput: `00000000 e1 83 9c e1 83 a3 20 e1 83 9e e1 83 90 e1 83 9c |á..á.£ á..á..á..| +00000010 e1 83 98 e1 83 99 e1 83 90 e1 83 a1 |á..á..á..á.¡|`, + recipeConfig: [ + { + op: "转换到Hexdump", + args: [16, false, false] + } + ], + }, + { + name: "To Hexdump: All bytes", + input: ALL_BYTES, + expectedOutput: `00000000 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................| +00000010 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f |................| +00000020 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f | !"#$%&'()*+,-./| +00000030 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f |0123456789:;<=>?| +00000040 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f |@ABCDEFGHIJKLMNO| +00000050 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f |PQRSTUVWXYZ[\\]^_| +00000060 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f |\`abcdefghijklmno| +00000070 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f |pqrstuvwxyz{|}~.| +00000080 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f |................| +00000090 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f |................| +000000a0 a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af |\xa0¡¢£¤¥¦§¨©ª«¬.®¯| +000000b0 b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf |°±²³´µ¶·¸¹º»¼½¾¿| +000000c0 c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf |ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏ| +000000d0 d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df |ÐÑÒÓÔÕÖרÙÚÛÜÝÞß| +000000e0 e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef |àáâãäåæçèéêëìíîï| +000000f0 f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff |ðñòóôõö÷øùúûüýþÿ|`, + recipeConfig: [ + { + op: "转换到Hexdump", + args: [16, false, false] + } + ], + }, + { + name: "From Hexdump: xxd", + input: `00000000: 0001 0203 0405 0607 0809 0a0b 0c0d 0e0f ................ +00000010: 1011 1213 1415 1617 1819 1a1b 1c1d 1e1f ................ +00000020: 2021 2223 2425 2627 2829 2a2b 2c2d 2e2f !"#$%&'()*+,-./ +00000030: 3031 3233 3435 3637 3839 3a3b 3c3d 3e3f 0123456789:;<=>? +00000040: 4041 4243 4445 4647 4849 4a4b 4c4d 4e4f @ABCDEFGHIJKLMNO +00000050: 5051 5253 5455 5657 5859 5a5b 5c5d 5e5f PQRSTUVWXYZ[\\]^_ +00000060: 6061 6263 6465 6667 6869 6a6b 6c6d 6e6f \`abcdefghijklmno +00000070: 7071 7273 7475 7677 7879 7a7b 7c7d 7e7f pqrstuvwxyz{|}~. +00000080: 8081 8283 8485 8687 8889 8a8b 8c8d 8e8f ................ +00000090: 9091 9293 9495 9697 9899 9a9b 9c9d 9e9f ................ +000000a0: a0a1 a2a3 a4a5 a6a7 a8a9 aaab acad aeaf ................ +000000b0: b0b1 b2b3 b4b5 b6b7 b8b9 babb bcbd bebf ................ +000000c0: c0c1 c2c3 c4c5 c6c7 c8c9 cacb cccd cecf ................ +000000d0: d0d1 d2d3 d4d5 d6d7 d8d9 dadb dcdd dedf ................ +000000e0: e0e1 e2e3 e4e5 e6e7 e8e9 eaeb eced eeef ................ +000000f0: f0f1 f2f3 f4f5 f6f7 f8f9 fafb fcfd feff ................`, + expectedOutput: ALL_BYTES, + recipeConfig: [ + { + op: "从Hexdump提取", + args: [] + } + ], + }, + { + name: "From Hexdump: xxd format, odd number of bytes", + input: "00000000: 6162 6364 65 abcde", + expectedOutput: "abcde", + recipeConfig: [ + { + op: "从Hexdump提取", + args: [] + } + ], + }, + { + name: "From Hexdump: Wireshark", + input: `00000000 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f ........ ........ +00000010 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f ........ ........ +00000020 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f !"#$%&' ()*+,-./ +00000030 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 01234567 89:;<=>? +00000040 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f @ABCDEFG HIJKLMNO +00000050 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f PQRSTUVW XYZ[\\]^_ +00000060 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f \`abcdefg hijklmno +00000070 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f pqrstuvw xyz{|}~. +00000080 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f ........ ........ +00000090 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f ........ ........ +000000A0 a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af ........ ........ +000000B0 b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf ........ ........ +000000C0 c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf ........ ........ +000000D0 d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df ........ ........ +000000E0 e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef ........ ........ +000000F0 f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff ........ ........ +`, + expectedOutput: ALL_BYTES, + recipeConfig: [ + { + op: "从Hexdump提取", + args: [] + } + ], + }, + { + name: "From Hexdump: Wireshark alt", + input: `0000 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f +0010 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f +0020 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f +0030 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f +0040 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f +0050 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f +0060 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f +0070 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f +0080 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f +0090 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f +00a0 a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af +00b0 b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf +00c0 c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf +00d0 d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df +00e0 e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef +00f0 f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff`, + expectedOutput: ALL_BYTES, + recipeConfig: [ + { + op: "从Hexdump提取", + args: [] + } + ], + }, + { + name: "From Hexdump: 010", + input: `0000h: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F ................ +0010h: 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F ................ +0020h: 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F !"#$%&'()*+,-./ +0030h: 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F 0123456789:;<=>? +0040h: 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F @ABCDEFGHIJKLMNO +0050h: 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F PQRSTUVWXYZ[\\]^_ +0060h: 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F \`abcdefghijklmno +0070h: 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F pqrstuvwxyz{|}~ +0080h: 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F €.‚ƒ„…†‡ˆ‰Š‹Œ... +0090h: 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F .‘’“”•–—˜™š›œ.žŸ +00A0h: A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF \xa0¡¢£¤¥¦§¨©ª«¬­®¯ +00B0h: B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF °±²³´µ¶·¸¹º»¼½¾¿ +00C0h: C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏ +00D0h: D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF ÐÑÒÓÔÕÖרÙÚÛÜÝÞß +00E0h: E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF àáâãäåæçèéêëìíîï +00F0h: F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF ðñòóôõö÷øùúûüýþÿ`, + expectedOutput: ALL_BYTES, + recipeConfig: [ + { + op: "从Hexdump提取", + args: [] + } + ], + }, + { + name: "From Hexdump: Linux hexdump", + input: `00000000 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................| +00000010 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f |................| +00000020 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f | !"#$%&'()*+,-./| +00000030 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f |0123456789:;<=>?| +00000040 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f |@ABCDEFGHIJKLMNO| +00000050 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f |PQRSTUVWXYZ[\\]^_| +00000060 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f |\`abcdefghijklmno| +00000070 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f |pqrstuvwxyz{|}~.| +00000080 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f |................| +00000090 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f |................| +000000a0 a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af |................| +000000b0 b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf |................| +000000c0 c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf |................| +000000d0 d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df |................| +000000e0 e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef |................| +000000f0 f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff |................| +00000100`, + expectedOutput: ALL_BYTES, + recipeConfig: [ + { + op: "从Hexdump提取", + args: [] + } + ], + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/IPv6Transition.mjs b/plugins/srktoolbox/tests/operations/tests/IPv6Transition.mjs new file mode 100644 index 00000000..f7558c31 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/IPv6Transition.mjs @@ -0,0 +1,63 @@ +/** + * IPv6Transition tests. + * + * @author jb30795 + * + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "IPv6 Transition: IPv4 to IPv6", + input: "198.51.100.7", + expectedOutput: "6to4: 2002:c633:6407::/48\nIPv4 Mapped: ::ffff:c633:6407\nIPv4 Translated: ::ffff:0:c633:6407\nNat 64: 64:ff9b::c633:6407", + recipeConfig: [ + { + op: "IPv6 Transition Addresses", + args: [true, false], + }, + ], + }, { + name: "IPv6 Transition: IPv4 /24 Range to IPv6", + input: "198.51.100.0/24", + expectedOutput: "6to4: 2002:c633:6400::/40\nIPv4 Mapped: ::ffff:c633:6400/120\nIPv4 Translated: ::ffff:0:c633:6400/120\nNat 64: 64:ff9b::c633:6400/120", + recipeConfig: [ + { + op: "IPv6 Transition Addresses", + args: [false, false], + }, + ], + }, { + name: "IPv6 Transition: IPv4 to IPv6 Remove headers", + input: "198.51.100.7", + expectedOutput: "2002:c633:6407::/48\n::ffff:c633:6407\n::ffff:0:c633:6407\n64:ff9b::c633:6407", + recipeConfig: [ + { + op: "IPv6 Transition Addresses", + args: [true, true], + }, + ], + }, { + name: "IPv6 Transition: IPv6 to IPv4", + input: "64:ff9b::c633:6407", + expectedOutput: "IPv4: 198.51.100.7", + recipeConfig: [ + { + op: "IPv6 Transition Addresses", + args: [true, false], + }, + ], + }, { + name: "IPv6 Transition: MAC to EUI-64", + input: "a1:b2:c3:d4:e5:f6", + expectedOutput: "EUI-64 Interface ID: a3b2:c3ff:fed4:e5f6", + recipeConfig: [ + { + op: "IPv6 Transition Addresses", + args: [true, false], + }, + ], + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/Image.mjs b/plugins/srktoolbox/tests/operations/tests/Image.mjs new file mode 100644 index 00000000..ae465a0f --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/Image.mjs @@ -0,0 +1,269 @@ +/** + * Image operation tests. + * + * @author tlwr [toby@toby.codes] + * @author Ge0rg3 [georgeomnet+cyberchef@gmail.com] + * @author n1474335 [n1474335@gmail.com] + * + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; +import { GIF_ANIMATED_HEX, PNG_HEX, JPG_B64, EXIF_JPG_HEX, NO_EXIF_JPG_HEX } from "../../samples/Images.mjs"; + +TestRegister.addTests([ + { + name: "Render Image: nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { op: "渲染图像", args: ["原始"] } + ] + }, + { + name: "Render Image: raw gif", + input: GIF_ANIMATED_HEX, + expectedOutput: "", + recipeConfig: [ + { op: "十六进制转字符", args: ["空格"] }, + { op: "渲染图像", args: ["原始"] } + ] + }, + { + name: "Render Image: hex png", + input: PNG_HEX, + expectedOutput: "", + recipeConfig: [ + { op: "渲染图像", args: ["十六进制"] } + ] + }, + { + name: "Render Image: base64 jpg", + input: JPG_B64, + expectedOutput: "", + recipeConfig: [ + { op: "渲染图像", args: ["Base64"] } + ] + }, + { + name: "Extract EXIF: nothing", + input: "", + expectedOutput: "找到 0 个标签。\n", + recipeConfig: [ + { + op: "提取EXIF", + args: [], + }, + ], + }, + { + name: "Extract EXIF: hello world text (error)", + input: "hello world", + expectedOutput: "无法从图片中提取EXIF数据: Error: Invalid JPEG section offset", + recipeConfig: [ + { + op: "提取EXIF", + args: [], + }, + ], + }, + { + name: "Extract EXIF: meerkat jpeg", + input: EXIF_JPG_HEX, + expectedOutput: [ + "找到 28 个标签。", + "", + "Make: SONY", + "Model: DSC-H5", + "XResolution: 70", + "YResolution: 70", + "ResolutionUnit: 2", + "Software: Pictomio 1.2.31.0", + "ModifyDate: 1278286273", + "ExposureTime: 0.008", + "FNumber: 3.7", + "ExposureProgram: 3", + "ISO: 200", + "DateTimeOriginal: 1220275486", + "CreateDate: 1220275486", + "ShutterSpeedValue: 6.965784", + "ApertureValue: 3.775051", + "ExposureCompensation: 0.3", + "MaxApertureValue: 3", + "MeteringMode: 5", + "LightSource: 10", + "Flash: 16", + "FocalLength: 72", + "CustomRendered: 0", + "ExposureMode: 1", + "WhiteBalance: 1", + "SceneCaptureType: 0", + "Contrast: 0", + "Saturation: 0", + "Sharpness: 0", + ].join("\n"), + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "提取EXIF", + args: [], + }, + ], + }, + { + name: "Extract EXIF: avatar jpeg", + input: NO_EXIF_JPG_HEX, + expectedOutput: "找到 0 个标签。\n", + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "提取EXIF", + args: [], + }, + ], + }, + { + name: "Remove EXIF: hello world text (error)", + input: "hello world", + expectedOutput: "无法从图像中移除EXIF数据: Given data is not jpeg.", + recipeConfig: [ + { + op: "移除EXIF", + args: [], + }, + ], + }, + { + name: "Remove EXIF: meerkat jpeg (has EXIF)", + input: EXIF_JPG_HEX, + expectedOutput: "找到 0 个标签。\n", + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "移除EXIF", + args: [], + }, + { + op: "提取EXIF", + args: [], + }, + ], + }, + { + name: "Extract EXIF: avatar jpeg (has no EXIF)", + input: NO_EXIF_JPG_HEX, + expectedOutput: "找到 0 个标签。\n", + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "移除EXIF", + args: [], + }, + { + op: "提取EXIF", + args: [], + }, + ], + }, + { + name: "提取RGBA", + input: "424d460400000000000036040000280000000400000004000000010008000000000010000000120b0000120b0000000100000001000000c8000000cf000000d7000000df000000e7000000ef000000f7000000ff000083000000ac000000d5000000ff000000000083000000ac000000d5000000ff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f070d05030b01090c040e060008020a", + expectedOutput: "0 200 0 0 0 131 0 215 0 0 0 213 131 0 0 0 231 0 213 0 0 0 247 0 0 223 0 0 0 255 0 207 0 0 0 172 255 0 0 0 255 0 172 0 0 0 239 0", + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "提取RGBA", + args: [" ", false] + } + ] + }, + { + name: "提取LSB", + input: PNG_HEX, + expectedOutput: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000240000000000000000000000208000000000000000000008000000000000000000000000248000000200240000000208908000000200240000000200821000000200240000000061249000000240000000000209b69000001a49b00000000a204a1200001a49b00000009800414000001a49b0000000035db6c00000094924000000086dffc20000df6dec8000001e10014a0000df6dec800002564924b00000df6dec80000009a6db20000007edb4124804177fffba0002fffff69249044e0924bc4002fffff6924905fb2db6d04002fffff692490416d2490040001bfffcc92030dbffffdc00037fffffdb6d302c6db6d700037fffffdb6d327eb6db6148037fffffdb6d30db4000014800dffffeb6d9aefffffff640", + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "提取LSB", + args: ["B", "G", "A", "", "Column", 2] + }, + { + op: "字符转十六进制", + args: ["无"] + } + ] + }, + { + name: "查看位平面", + input: PNG_HEX, + expectedOutput: "89504e470d0a1a0a0000000d4948445200000020000000200806000000737a7af400000140494441547801c5c1416ea3400000c1ee11ffff726fe6808410186ce26c95fde0432a154f0cdea4b2aa505151519954ee1a5c50995454aea8ac54ae2c5ca8982a3ea132551c199c507942e58ec1898adf50f1cae084ca15952b2a152a47067f40a5e2c8e00f54a81c199ca8b85271a542a5e2c8e005159527542ace0c5ea8a8f844c54ae5ccc217555c197c41c55d83ff6cf0052a772ddca052b1a752b1a772d7c2432a4f2c3c50f1d4c20b2a1593ca918a4965afe2cac29b2a562a93ca56c55d0b2754b62a269555c554b15251a9b8637040e5884ac54a654ba5a2624be5cce040c5918a55c55ec5a4a232a9549c197c48655239523155bc3278a862af624be5ccc2072aaea854a8549c5978834a85ca5ec5918a57ec076f50a958a9546ca94c1557ec071754a68a2d958a270637544c2a2abf69e1a68a95ca54b152a978d73f2e08bd57b6f839a00000000049454e44ae426082", + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "查看位平面", + args: ["Green", 3] + }, + { + op: "字符转十六进制", + args: ["无"] + } + ] + }, + { + name: "色板随机化", + "input": PNG_HEX, + expectedOutput: "89504e470d0a1a0a0000000d4948445200000020000000200806000000737a7af4000004f3494441547801c5c10b50cf0700c0f16fbfff8f0ae5f157ed9f1e446358ab28b3913f29b3f89f65deab9859c624cafbf557b6eb3662799c8a285c541479ed3c125d2d2cba52e45d4da8bc66b99d1ebbff6ddd75ddeff7efef75fb7c8c5cb5b10dbc869ccbd1ac4f0c67dfc55cba1f9ec32dd303bc0e854aadd1f20ace5f29e1c9346b4c3982aada03b3285b540f4b099f664ca5720321a34c582466b2f38f3e1842a1526bb4e811efe64a56403a465dc270a998cb517b47e62f7842870b6aaabc5b519627a253784e606a8e25470fbbe358a0c2fd692f9c9668b8bda084e77d5f2247448f790f42a9b9da915d415a6020508a3e49e207e8ec7961cb9594545657e5e26e0fdaf9df5014998a141119f16eae24b4db84f7d775bc8e3e3e9e24f12fabaccf282210292232ce6a5e12dab58e37656be7cf8893916498d72345404681d7365ae217b18b969495261019998f8569145204e47c3e99b7a58f8f278fa76e478a808c522f23de165b3b7f3aee9c8e14011956e9b6841c29419f5d8bfd6889ad9d3fc1c9c654be08428a808cfc9a0c02e7d711973a823751569ac0c79dd72347408fb9ee3d102f4c616d9c82579596dd8df1b5c58caf2d26625d2fe488b420b16c129e3d7825534ccbb01a3881f0803354adba49c5853ce488bc037ba66b48c93586e20298885e02ff338177c0a1ed600c256200973699b0f130cd9dafcd20c87a3bcd9d29d90b1462081103c4d444d1bee2139aab9b7c87ec50479adbd47f13a0c610227a94af8a6565ce118ce72dc1618d3b4ded6f678465840dd905bd519f3e455305e67eb4f1db8ed94fdeb444448f0dbb94740bcec029db9fa636fc99cbf176a708b9e44198af3573469b323cb992469694b031bd133b2a955cb2a8461f0119a3b6f810fcac81619ea1c42cbf43a3711393d8727b1ee37ad6a263b1388cc8a872264cc8a7d1c97116d407ede1ab9c44ac3de610e5f32372142ab5468b84d091d984d79a90147e8dfb8322e8bcf71cdf370ce07ae6158606ee479c164c8794ddf88f76664c5d3d96579ee171db85d9917524b996102e2e65d882753c4c28e690714f9e3ffa1b2902122a0e6571a3c8864657b304ee2e4d6579e1038e0879cc0c716765c67ec2ee1d63cbaa9154db0760ffcc8e0fd7f643d3ba9c31ce69fc3c68124507bae060eb8379dc45e4884850aa4730ca2e97e68a572ca178055c7ff411cb56c4002bf86ef6717c53f94f3c3a4ff72590d6fa09ef134ae0a983ec1efc2b720424fc55b8107dbea8fe81134ecee8687c4d68ae62de45bc636ad099782b126b8f39c81190b0f292127d5295cb98f2b415aa2a055dcb2fb035b081a686f48ee5f4d37ee80c9d91498855007204240cc9ea89efeabec871ec5443c381b1980c9a4f456d1233a38d68ce667c2ee7c2927019e1ce8bb84ce48848f832d10bb1f50dd6540d03d268d4677d39ed1d52514407d3b6d8830327dc49b0984574507b3a3b0573fb5806f762afa37379790e7783ab0849ee0f66fd91a350a9355a24d4bb754239ca02df597598d52dc5c2bb339503cde95213cff3a089240d7f48fd522f9c943eec1db088e53b0412631ea193dec188e4b3a7d9d6eb535a62e4aa8d6da005bfe4d9125de082dbcd6f69ca6ecc66c2166ca329eb198ee4bf5780a5ba3b861030c05cd732eea4acc3c42106671f0d52ac6738727064325b8ff5c752dd1d4329546a8d1603bc2c51f2bbf3356eae2c64b379374c964561b5f53c36d65e14fd3683c7f7179232d686bdf9777915ff00ec08ae8ecb66a3370000000049454e44ae426082", + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "色板随机化", + args: ["myseed"] + }, + { + op: "字符转十六进制", + args: ["无"] + } + ] + }, + /* { This operation only works in a browser + name: "Optical Character Recognition", + input: "iVBORw0KGgoAAAANSUhEUgAAAUAAAAC0CAIAAABqhmJGAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAASuSURBVHhe7dftVdswAIbRzsVAzMM0XabDUCOUxLYsWW4Jp+/pvf9w9GH76CHw4x2IJWAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAI9p8G/PbyY8rL2686g8t+vnqHTyfgIYfvz/26veTXn/UKX8+f0EU9bHrtu/6KfAN/AwEXAj7lFf2TBFw4nae8on+SgIvJ01n/KLzpDK+L3bT/Ap4O+HC+V12mTH+M3gzcLbIY/EO6HfxYp13k09nb6r3UqcdnjoCL3ll72J26h+35Oxy2XvZ0wOLaXq9v2+F1UC+7RZtMZ/DnfX1lwDOPzwUCLo7O2trtDK8H3M/iqoc6bj1subT68XTA/F7bGJooyzKbhTvLPHY8eJLHlbNX1DqYUVfdXbqwJjsCLsans37aNNJM6w68OR0wv9f9ymKw3k67yn2ZZpHlg3a3zis60s6oV+ZvlzMCLoanc3Dsdt9TdWT/lM8OmNjr5KY72jmzq1zfrbvXtVtmRMDF8HTWcgaaqIrD1U4G/MFewxrW262s5jS/Fzpmdts6mnHy+Fwl4GJ0OjsNrG1P/y7CNo3+gEt7jW56MVprNed7A/5w+n6YJ+BieDpnj/jO6pweTz0acGWvmZveL9XOmd3x6wKuTt8PEwRczLRw4eje1XX7c/cDruw1uuneOu2c4aOvzI57mJhRh1xZlQ0BF+Oz9vcF96fuB1zYa7R2b5mD6/XSwdfg8snj4q21+W/L02dfzIxhQMDFyTm6Hd7m+JYP7rPKT5sRuzhOBywm91rUkYc3fV9ltchtr8VmzuGOdfDB9N1tFYefNfdXLmyGjNZkhoCLUQufVqd/7z7rUcLW/XieDvg0s9difNOdRV5ePibt5vTuazusWbF9rs2E5v4mH58LBFyMW7g5OID7s9cMuTygmt9rcNPb5MrAz0lHc3Z9Ht7XZsxqxO36ZtLR/c0+PpMEzLOc/4LhrwmYZ6lfywJ+JgHzJPr9DgLmi23/zdXvcwmYL7YKWL1PJ2AIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmCI9f7+G6yFxVg/GyYwAAAAAElFTkSuQmCC", + expectedOutput: "Tesseract.js\n", + recipeConfig: [ + { + "op": "From Base64", + "args": ["A-Za-z0-9+/=", true] + }, + { + "op": "Optical Character Recognition", + "args": [false] + } + ] + } */ +]); diff --git a/plugins/srktoolbox/tests/operations/tests/IndexOfCoincidence.mjs b/plugins/srktoolbox/tests/operations/tests/IndexOfCoincidence.mjs new file mode 100644 index 00000000..ccf7b831 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/IndexOfCoincidence.mjs @@ -0,0 +1,24 @@ +/** + * Index of Coincidence tests. + * + * @author George O [georgeomnet+cyberchef@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Index of Coincidence", + input: "Hello world, this is a test to determine the correct IC value.", + expectedMatch: /^重合因子: 0\.07142857142857142\n标准化: 1\.857142857142857/, + recipeConfig: [ + { + "op": "重合因子", + "args": [] + }, + ], + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/JA3Fingerprint.mjs b/plugins/srktoolbox/tests/operations/tests/JA3Fingerprint.mjs new file mode 100644 index 00000000..da15e0de --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/JA3Fingerprint.mjs @@ -0,0 +1,57 @@ +/** + * JA3Fingerprint tests. + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "JA3 Fingerprint: TLS 1.0", + input: "16030100a4010000a00301543dd2dd48f517ca9a93b1e599f019fdece704a23e86c1dcac588427abbaddf200005cc014c00a0039003800880087c00fc00500350084c012c00800160013c00dc003000ac013c00900330032009a009900450044c00ec004002f009600410007c011c007c00cc002000500040015001200090014001100080006000300ff0100001b000b000403000102000a000600040018001700230000000f000101", + expectedOutput: "503053a0c5b2bd9b9334bf7f3d3b8852", + recipeConfig: [ + { + "op": "JA3指纹", + "args": ["十六进制", "哈希摘要"] + } + ], + }, + { + name: "JA3 Fingerprint: TLS 1.1", + input: "16030100a4010000a00302543dd2ed907e47d0086f34bee2c52dd6ccd8de63ba9387f5e810b09d9d49b38000005cc014c00a0039003800880087c00fc00500350084c012c00800160013c00dc003000ac013c00900330032009a009900450044c00ec004002f009600410007c011c007c00cc002000500040015001200090014001100080006000300ff0100001b000b000403000102000a000600040018001700230000000f000101", + expectedOutput: "a314eb64cee6cb832aaaa372c8295bab", + recipeConfig: [ + { + "op": "JA3指纹", + "args": ["十六进制", "哈希摘要"] + } + ], + }, + { + name: "JA3 Fingerprint: TLS 1.2", + input: "1603010102010000fe0303543dd3283283692d85f9416b5ccc65d2aafca45c6530b3c6eafbf6d371b6a015000094c030c02cc028c024c014c00a00a3009f006b006a0039003800880087c032c02ec02ac026c00fc005009d003d00350084c012c00800160013c00dc003000ac02fc02bc027c023c013c00900a2009e0067004000330032009a009900450044c031c02dc029c025c00ec004009c003c002f009600410007c011c007c00cc002000500040015001200090014001100080006000300ff01000041000b000403000102000a000600040018001700230000000d002200200601060206030501050205030401040204030301030203030201020202030101000f000101", + expectedOutput: "c1a36e1a870786cc75edddc0009eaf3a", + recipeConfig: [ + { + "op": "JA3指纹", + "args": ["十六进制", "哈希摘要"] + } + ], + }, + { + name: "JA3 Fingerprint: TLS 1.3", + input: "1603010200010001fc03034355d402c132771a9386b6e9994ae37069e0621af504c26673b1343843c21d8d0000264a4a130113021303c02bc02fc02cc030cca9cca8cc14cc13c013c014009c009d002f0035000a010001addada0000ff01000100000000180016000013626c6f672e636c6f7564666c6172652e636f6d0017000000230000000d00140012040308040401050308050501080606010201000500050100000000001200000010000e000c02683208687474702f312e3175500000000b000201000028002b00295a5a000100001d0020cf78b9167af054b922a96752b43973107b2a57766357dd288b2b42ab5df30e08002d00020101002b000b0acaca7f12030303020301000a000a00085a5a001d001700180a0a000100001500e4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + expectedOutput: "4826a90ec2daf4f7b4b64cc1c8bd343b", + recipeConfig: [ + { + "op": "JA3指纹", + "args": ["十六进制", "哈希摘要"] + } + ], + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/JA3SFingerprint.mjs b/plugins/srktoolbox/tests/operations/tests/JA3SFingerprint.mjs new file mode 100644 index 00000000..1861db18 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/JA3SFingerprint.mjs @@ -0,0 +1,59 @@ +/** + * JA3SFingerprint tests. + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "JA3S Fingerprint: TLS 1.0", + input: "160301003d020000390301543dd2ddedbfe33895bd6bc676a3fa6b9fe5773a6e04d5476d1af3bcbc1dcbbb00c011000011ff01000100000b00040300010200230000", + expectedOutput: "bed95e1b525d2f41db3a6d68fac5b566", + recipeConfig: [ + { + "op": "JA3S指纹", + "args": ["十六进制", "哈希摘要"] + } + ], + }, + { + name: "JA3S Fingerprint: TLS 1.1", + input: "160302003d020000390302543dd2ed88131999a0120d36c14a4139671d75aae3d7d7779081d3cf7dd7725a00c013000011ff01000100000b00040300010200230000", + expectedOutput: "130fac2dc19b142500acb0abc63b6379", + recipeConfig: [ + { + "op": "JA3S指纹", + "args": ["十六进制", "哈希摘要"] + } + ], + }, + { + name: "JA3S Fingerprint: TLS 1.2", + input: "160303003d020000390303543dd328b38b445686739d58fab733fa23838f575e0e5ad9a1b9baace6cc3b4100c02f000011ff01000100000b00040300010200230000", + expectedOutput: "ccc514751b175866924439bdbb5bba34", + recipeConfig: [ + { + "op": "JA3S指纹", + "args": ["十六进制", "哈希摘要"] + } + ], + }, + // This Server Hello was based on draft 18 of the TLS1.3 spec which does not include a Session ID field, leading it to fail. + // The published version of TLS1.3 does require a legacy Session ID field (even if it is empty). + // { + // name: "JA3S Fingerprint: TLS 1.3", + // input: "16030100520200004e7f123ef1609fd3f4fa8668aac5822d500fb0639b22671d0fb7258597355795511bf61301002800280024001d0020ae0e282a3b7a463e71064ecbaf671586e979b0edbebf7a4735c31678c70f660c", + // expectedOutput: "986ae432c402479fe7a0c6fbe02164c1", + // recipeConfig: [ + // { + // "op": "JA3S指纹", + // "args": ["十六进制", "哈希摘要"] + // } + // ], + // }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/JA4.mjs b/plugins/srktoolbox/tests/operations/tests/JA4.mjs new file mode 100644 index 00000000..cbb08757 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/JA4.mjs @@ -0,0 +1,145 @@ +/** + * JA4 tests. + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "JA4 Fingerprint: TLS 1.3", + input: "1603010200010001fc0303b2c03e7ba990ef540c316a665d4d925f8e9079ac4b15687e587dc99016e75a6c20d0b0099243c9296a0c84153ea4ada7d87ad017f4211c2ea1350b0b3cc5514d5f00205a5a130113021303c02bc02fc02cc030cca9cca8c013c014009c009d002f003501000193fafa000000000024002200001f636f6e74656e742d6175746f66696c6c2e676f6f676c65617069732e636f6d0033002b00293a3a000100001d0020fb2cd8ef3d605b96ab03119ec4f30a6e2088cb1af86c41a81feace8706068c50000d001200100403080404010503080505010806060100230000000b00020100ff01000100000a000a00083a3a001d00170018001b000302000244690005000302683200120000002d000201010010000e000c02683208687474702f312e31000500050100000000002b0007060a0a03040303001700001a1a000100001500b800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + expectedOutput: "t13d1516h2_8daaf6152771_e5627efa2ab1", + recipeConfig: [ + { + "op": "JA4指纹", + "args": ["十六进制", "JA4"] + } + ], + }, + { + name: "JA4 Fingerprint: TLS 1.3 Original Rendering", + input: "1603010200010001fc0303b2c03e7ba990ef540c316a665d4d925f8e9079ac4b15687e587dc99016e75a6c20d0b0099243c9296a0c84153ea4ada7d87ad017f4211c2ea1350b0b3cc5514d5f00205a5a130113021303c02bc02fc02cc030cca9cca8c013c014009c009d002f003501000193fafa000000000024002200001f636f6e74656e742d6175746f66696c6c2e676f6f676c65617069732e636f6d0033002b00293a3a000100001d0020fb2cd8ef3d605b96ab03119ec4f30a6e2088cb1af86c41a81feace8706068c50000d001200100403080404010503080505010806060100230000000b00020100ff01000100000a000a00083a3a001d00170018001b000302000244690005000302683200120000002d000201010010000e000c02683208687474702f312e31000500050100000000002b0007060a0a03040303001700001a1a000100001500b800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + expectedOutput: "t13d1516h2_acb858a92679_5276cb03a33b", + recipeConfig: [ + { + "op": "JA4指纹", + "args": ["十六进制", "JA4 Original Rendering"] + } + ], + }, + { + name: "JA4 Fingerprint: TLS 1.3 with whitespace-only ALPN", + input: "1603010200010001fc0303ed338a18e711d670cdc472ff570a5b59f1ace12e5365918bf68bf845019147b6207e4437bfb062d98a4aeb753be8f09022a9dc9413d7694dad4db57fcdcf076e820024130213031301c02cc030c02bc02fcca9cca8c024c028c023c027009f009e006b006700ff0100018f0000001800160000136465762e636f6e74656e74677261622e6e6574000b000403000102000a00160014001d0017001e00190018010001010102010301040023000000100004000201200016000000170000000d002a0028040305030603080708080809080a080b080408050806040105010601030303010302040205020602002b00050403040303002d00020101003300260024001d00207af053336d5e2c1675aa4c6ce78de5e5fdbd296538113f051ea17ccb64289f22001500d2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + expectedOutput: "t13d181220_85036bcba153_d41ae481755e", + recipeConfig: [ + { + "op": "JA4指纹", + "args": ["十六进制", "JA4"] + } + ], + }, + { + name: "JA4 Fingerprint: TLS 1.3 with ALPN containing a whitespace", + input: "1603010200010001fc0303273682a603be3f64dd025df4ad0f4d2d13043c3a233405a68bb29b865808749a20f4dfc40242b2fce38fae26c516ef9bef20a1b9349eba3c003780168d72471f5c0024130213031301c02cc030c02bc02fcca9cca8c024c028c023c027009f009e006b006700ff0100018f0000001800160000136465762e636f6e74656e74677261622e6e6574000b000403000102000a00160014001d0017001e0019001801000101010201030104002300000010000500030261200016000000170000000d002a0028040305030603080708080809080a080b080408050806040105010601030303010302040205020602002b00050403040303002d00020101003300260024001d0020f4dd1567bd858d3a9f1d88db1fee6a10ab0ea1aa6afe96ffb6a7c4d79dea4075001500d10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + expectedOutput: "t13d181260_85036bcba153_d41ae481755e", + recipeConfig: [ + { + "op": "JA4指纹", + "args": ["十六进制", "JA4"] + } + ], + }, + { + name: "JA4 Fingerprint: TLS 1.2", + input: "1603010200010001fc0303ecb2691addb2bf6c599c7aaae23de5f42561cc04eb41029acc6fc050a16ac1d22046f8617b580ac9358e2aa44e306d52466bcc989c87c8ca64309f5faf50ba7b4d0022130113031302c02bc02fcca9cca8c02cc030c00ac009c013c014009c009d002f00350100019100000021001f00001c636f6e74696c652e73657276696365732e6d6f7a696c6c612e636f6d00170000ff01000100000a000e000c001d00170018001901000101000b00020100002300000010000e000c02683208687474702f312e310005000501000000000022000a000804030503060302030033006b0069001d00208909858fbeb6ed2f1248ba5b9e2978bead0e840110192c61daed0096798b184400170041044d183d91f5eed35791fa982464e3b0214aaa5f5d1b78616d9b9fbebc22d11f535b2f94c686143136aa795e6e5a875d6c08064ad5b76d44caad766e2483012748002b00050403040303000d0018001604030503060308040805080604010501060102030201002d00020101001c000240010015007a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + expectedOutput: "t13d1715h2_5b57614c22b0_3d5424432f57", + recipeConfig: [ + { + "op": "JA4指纹", + "args": ["十六进制", "JA4"] + } + ], + }, + { + name: "JA4 Fingerprint: TLS 1.2 Original Rendering", + input: "1603010200010001fc0303ecb2691addb2bf6c599c7aaae23de5f42561cc04eb41029acc6fc050a16ac1d22046f8617b580ac9358e2aa44e306d52466bcc989c87c8ca64309f5faf50ba7b4d0022130113031302c02bc02fcca9cca8c02cc030c00ac009c013c014009c009d002f00350100019100000021001f00001c636f6e74696c652e73657276696365732e6d6f7a696c6c612e636f6d00170000ff01000100000a000e000c001d00170018001901000101000b00020100002300000010000e000c02683208687474702f312e310005000501000000000022000a000804030503060302030033006b0069001d00208909858fbeb6ed2f1248ba5b9e2978bead0e840110192c61daed0096798b184400170041044d183d91f5eed35791fa982464e3b0214aaa5f5d1b78616d9b9fbebc22d11f535b2f94c686143136aa795e6e5a875d6c08064ad5b76d44caad766e2483012748002b00050403040303000d0018001604030503060308040805080604010501060102030201002d00020101001c000240010015007a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + expectedOutput: "t13d1715h2_5b234860e130_014157ec0da2", + recipeConfig: [ + { + "op": "JA4指纹", + "args": ["十六进制", "JA4 Original Rendering"] + } + ], + }, + { + name: "JA4Server Fingerprint: TLS 1.2 h2 ALPN", + input: "16030300640200006003035f0236c07f47bfb12dc2da706ecb3fe7f9eeac9968cc2ddf444f574e4752440120b89ff1ab695278c69b8a73f76242ef755e0b13dc6d459aaaa784fec9c2dfce34cca900001800000000ff01000100000b00020100001000050003026832", + expectedOutput: "t1204h2_cca9_1428ce7b4018", + recipeConfig: [ + { + "op": "JA4S指纹", + "args": ["十六进制", "JA4S"] + } + ] + }, + { + name: "JA4Server Fingerprint: TLS 1.2 h2 ALPN Raw", + input: "16030300640200006003035f0236c07f47bfb12dc2da706ecb3fe7f9eeac9968cc2ddf444f574e4752440120b89ff1ab695278c69b8a73f76242ef755e0b13dc6d459aaaa784fec9c2dfce34cca900001800000000ff01000100000b00020100001000050003026832", + expectedOutput: "t1204h2_cca9_0000,ff01,000b,0010", + recipeConfig: [ + { + "op": "JA4S指纹", + "args": ["十六进制", "JA4S原始"] + } + ] + }, + { + name: "JA4Server Fingerprint: TLS 1.3", + input: "160303007a020000760303236d214556452c55a0754487e64b1a8b0262c50ba23004c9d504166a6de3439920d0b0099243c9296a0c84153ea4ada7d87ad017f4211c2ea1350b0b3cc5514d5f130100002e00330024001d002099e3cc43a2c9941ae75af1b2c7a629bee3ee7031973cad85c82f2f23677fb244002b00020304", + expectedOutput: "t130200_1301_234ea6891581", + recipeConfig: [ + { + "op": "JA4S指纹", + "args": ["十六进制", "JA4S"] + } + ] + }, + { + name: "JA4Server Fingerprint: TLS 1.3 Raw", + input: "160303007a020000760303236d214556452c55a0754487e64b1a8b0262c50ba23004c9d504166a6de3439920d0b0099243c9296a0c84153ea4ada7d87ad017f4211c2ea1350b0b3cc5514d5f130100002e00330024001d002099e3cc43a2c9941ae75af1b2c7a629bee3ee7031973cad85c82f2f23677fb244002b00020304", + expectedOutput: "t130200_1301_0033,002b", + recipeConfig: [ + { + "op": "JA4S指纹", + "args": ["十六进制", "JA4S原始"] + } + ] + }, + { + name: "JA4Server Fingerprint: TLS 1.3 non-ascii ALPN", + input: "160303007a020000760303897c232e3ee313314f2b662307ff4f7e2cf1caeec1b27711bca77f469519168520bc58b92f865e6b9aa4a6371cadcb0afe1da1c0f705209a11d52357f56d5dd962130100002e00330024001d002076b8b7ed0f96b63a773d85ab6f3a87a151c130529785b41a4defb53184055957002b00020304", + expectedOutput: "t130200_1301_234ea6891581", + recipeConfig: [ + { + "op": "JA4S指纹", + "args": ["十六进制", "JA4S"] + } + ] + }, + { + name: "JA4Server Fingerprint: TLS 1.3 non-ascii ALPN Raw", + input: "160303007a020000760303897c232e3ee313314f2b662307ff4f7e2cf1caeec1b27711bca77f469519168520bc58b92f865e6b9aa4a6371cadcb0afe1da1c0f705209a11d52357f56d5dd962130100002e00330024001d002076b8b7ed0f96b63a773d85ab6f3a87a151c130529785b41a4defb53184055957002b00020304", + expectedOutput: "t130200_1301_0033,002b", + recipeConfig: [ + { + "op": "JA4S指纹", + "args": ["十六进制", "JA4S原始"] + } + ] + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/JSONBeautify.mjs b/plugins/srktoolbox/tests/operations/tests/JSONBeautify.mjs new file mode 100644 index 00000000..46f6ded7 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/JSONBeautify.mjs @@ -0,0 +1,140 @@ +/** + * JSONBeautify tests. + * + * @author Phillip Nordwall [Phillip.Nordwall@gmail.com] + * + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "JSON Beautify: space, ''", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "JSON美化", + args: [" ", false, false], + }, + ], + }, + { + name: "JSON Beautify: space, number", + input: "42", + expectedOutput: "42", + recipeConfig: [ + { + op: "JSON美化", + args: [" ", false, false], + }, + ], + }, + { + name: "JSON Beautify: space, string", + input: "\"string\"", + expectedOutput: "\"string\"", + recipeConfig: [ + { + op: "JSON美化", + args: [" ", false, false], + }, + { + op: "HTML转文本", + args: [] + } + ], + }, + { + name: "JSON Beautify: space, boolean", + input: "false", + expectedOutput: "false", + recipeConfig: [ + { + op: "JSON美化", + args: [" ", false, false], + }, + ], + }, + { + name: "JSON Beautify: space, emptyList", + input: "[]", + expectedOutput: "[]", + recipeConfig: [ + { + op: "JSON美化", + args: [" ", false, false], + }, + ], + }, + { + name: "JSON Beautify: space, list", + input: "[2,1]", + expectedOutput: "[\n 2,\n 1\n]", + recipeConfig: [ + { + op: "JSON美化", + args: [" ", false, false], + }, + ], + }, + { + name: "JSON Beautify: tab, list", + input: "[2,1]", + expectedOutput: "[\n\t2,\n\t1\n]", + recipeConfig: [ + { + op: "JSON美化", + args: ["\t", false, false], + }, + ], + }, + { + name: "JSON Beautify: space, object", + input: "{\"second\":2,\"first\":3}", + expectedOutput: "{\n \"second\": 2,\n \"first\": 3\n}", + recipeConfig: [ + { + op: "JSON美化", + args: [" ", false, false], + }, + { + op: "HTML转文本", + args: [] + } + ], + }, + { + name: "JSON Beautify: tab, nested", + input: "[2,{\"second\":2,\"first\":3,\"beginning\":{\"j\":\"3\",\"i\":[2,3,false]}},1,2,3]", + expectedOutput: "[\n\t2,\n\t{\n\t\t\"second\": 2,\n\t\t\"first\": 3,\n\t\t\"beginning\": {\n\t\t\t\"j\": \"3\",\n\t\t\t\"i\": [\n\t\t\t\t2,\n\t\t\t\t3,\n\t\t\t\tfalse\n\t\t\t]\n\t\t}\n\t},\n\t1,\n\t2,\n\t3\n]", + recipeConfig: [ + { + op: "JSON美化", + args: ["\t", false, false], + }, + { + op: "HTML转文本", + args: [] + } + ], + }, + { + name: "JSON Beautify: tab, nested, sorted", + input: "[2,{\"second\":2,\"first\":3,\"beginning\":{\"j\":\"3\",\"i\":[2,3,false]}},1,2,3]", + expectedOutput: "[\n\t2,\n\t{\n\t\t\"beginning\": {\n\t\t\t\"i\": [\n\t\t\t\t2,\n\t\t\t\t3,\n\t\t\t\tfalse\n\t\t\t],\n\t\t\t\"j\": \"3\"\n\t\t},\n\t\t\"first\": 3,\n\t\t\"second\": 2\n\t},\n\t1,\n\t2,\n\t3\n]", + recipeConfig: [ + { + op: "JSON美化", + args: ["\t", true, false], + }, + { + op: "HTML转文本", + args: [] + } + ], + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/JSONMinify.mjs b/plugins/srktoolbox/tests/operations/tests/JSONMinify.mjs new file mode 100644 index 00000000..052e640c --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/JSONMinify.mjs @@ -0,0 +1,113 @@ +/** + * JSONMinify tests. + * + * @author Phillip Nordwall [Phillip.Nordwall@gmail.com] + * + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "JSON Minify: ''", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "JSON压缩", + args: [], + }, + ], + }, + { + name: "JSON Minify: number", + input: "42", + expectedOutput: "42", + recipeConfig: [ + { + op: "JSON压缩", + args: [], + }, + ], + }, + { + name: "JSON Minify: number", + input: "4.2", + expectedOutput: "4.2", + recipeConfig: [ + { + op: "JSON压缩", + args: [], + }, + ], + }, + { + name: "JSON Minify: string", + input: "\"string\"", + expectedOutput: "\"string\"", + recipeConfig: [ + { + op: "JSON压缩", + args: [], + }, + ], + }, + { + name: "JSON Minify: boolean", + input: "false", + expectedOutput: "false", + recipeConfig: [ + { + op: "JSON压缩", + args: [], + }, + ], + }, + { + name: "JSON Minify: emptyList", + input: "[\n \n \t]", + expectedOutput: "[]", + recipeConfig: [ + { + op: "JSON压缩", + args: [], + }, + ], + }, + { + name: "JSON Minify: list", + input: "[2,\n \t1]", + expectedOutput: "[2,1]", + recipeConfig: [ + { + op: "JSON压缩", + args: [], + }, + ], + }, + { + name: "JSON Minify: object", + input: "{\n \"second\": 2,\n \"first\": 3\n}", + expectedOutput: "{\"second\":2,\"first\":3}", + recipeConfig: [ + { + op: "JSON压缩", + args: [], + }, + ], + }, + { + name: "JSON Minify: tab, nested", + input: "[\n\t2,\n\t{\n\t\t\"second\": 2,\n\t\t\"first\": 3,\n\t\t\"beginning\": {\n\t\t\t\"j\": \"3\",\n\t\t\t\"i\": [\n\t\t\t\t2,\n\t\t\t\t3,\n\t\t\t\tfalse\n\t\t\t]\n\t\t}\n\t},\n\t1,\n\t2,\n\t3\n]", + expectedOutput: "[2,{\"second\":2,\"first\":3,\"beginning\":{\"j\":\"3\",\"i\":[2,3,false]}},1,2,3]", + recipeConfig: [ + { + op: "JSON压缩", + args: [], + }, + ], + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/JSONtoCSV.mjs b/plugins/srktoolbox/tests/operations/tests/JSONtoCSV.mjs new file mode 100644 index 00000000..17a3a1d5 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/JSONtoCSV.mjs @@ -0,0 +1,172 @@ +/** + * JSON to CSV tests. + * + * @author mshwed [m@ttshwed.com] + * + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +const EXPECTED_CSV_SINGLE = "a,b,c\r\n1,2,3\r\n"; +const EXPECTED_CSV_MULTIPLE = "a,b,c\r\n1,2,3\r\n1,2,3\r\n"; +const EXPECTED_CSV_EMPTY = "\r\n\r\n"; + +TestRegister.addTests([ + { + name: "JSON to CSV: strings as values", + input: JSON.stringify({a: "1", b: "2", c: "3"}), + expectedOutput: EXPECTED_CSV_SINGLE, + recipeConfig: [ + { + op: "JSON转CSV", + args: [",", "\\r\\n"] + }, + ], + }, + { + name: "JSON to CSV: numbers as values", + input: JSON.stringify({a: 1, b: 2, c: 3}), + expectedOutput: EXPECTED_CSV_SINGLE, + recipeConfig: [ + { + op: "JSON转CSV", + args: [",", "\\r\\n"] + }, + ], + }, + { + name: "JSON to CSV: numbers and strings as values", + input: JSON.stringify({a: 1, b: "2", c: 3}), + expectedOutput: EXPECTED_CSV_SINGLE, + recipeConfig: [ + { + op: "JSON转CSV", + args: [",", "\\r\\n"] + }, + ], + }, + { + name: "JSON to CSV: boolean and null as values", + input: JSON.stringify({a: false, b: null, c: 3}), + expectedOutput: "a,b,c\r\nfalse,null,3\r\n", + recipeConfig: [ + { + op: "JSON转CSV", + args: [",", "\\r\\n"] + }, + ], + }, + { + name: "JSON to CSV: JSON as an array", + input: JSON.stringify([{a: 1, b: "2", c: 3}]), + expectedOutput: EXPECTED_CSV_SINGLE, + recipeConfig: [ + { + op: "JSON转CSV", + args: [",", "\\r\\n"] + }, + ], + }, + { + name: "JSON to CSV: multiple JSON values in an array", + input: JSON.stringify([{a: 1, b: "2", c: 3}, {a: 1, b: "2", c: 3}]), + expectedOutput: EXPECTED_CSV_MULTIPLE, + recipeConfig: [ + { + op: "JSON转CSV", + args: [",", "\\r\\n"] + }, + ], + }, + { + name: "JSON to CSV: empty JSON", + input: JSON.stringify({}), + expectedOutput: EXPECTED_CSV_EMPTY, + recipeConfig: [ + { + op: "JSON转CSV", + args: [",", "\\r\\n"] + }, + ], + }, + { + name: "JSON to CSV: empty JSON in array", + input: JSON.stringify([{}]), + expectedOutput: EXPECTED_CSV_EMPTY, + recipeConfig: [ + { + op: "JSON转CSV", + args: [",", "\\r\\n"] + }, + ], + }, + { + name: "JSON to CSV: nested JSON", + input: JSON.stringify({a: 1, b: {c: 2, d: 3}}), + expectedOutput: "a,b.c,b.d\r\n1,2,3\r\n", + recipeConfig: [ + { + op: "JSON转CSV", + args: [",", "\\r\\n"] + }, + ], + }, + { + name: "JSON to CSV: nested array", + input: JSON.stringify({a: 1, b: [2, 3]}), + expectedOutput: "a,b.0,b.1\r\n1,2,3\r\n", + recipeConfig: [ + { + op: "JSON转CSV", + args: [",", "\\r\\n"] + }, + ], + }, + { + name: "JSON to CSV: nested JSON, nested array", + input: JSON.stringify({a: 1, b: {c: [2, 3], d: 4}}), + expectedOutput: "a,b.c.0,b.c.1,b.d\r\n1,2,3,4\r\n", + recipeConfig: [ + { + op: "JSON转CSV", + args: [",", "\\r\\n"] + }, + ], + }, + { + name: "JSON to CSV: nested array, nested JSON", + input: JSON.stringify({a: 1, b: [{c: 3, d: 4}]}), + expectedOutput: "a,b.0.c,b.0.d\r\n1,3,4\r\n", + recipeConfig: [ + { + op: "JSON转CSV", + args: [",", "\\r\\n"] + }, + ], + }, + { + name: "JSON to CSV: nested array, nested array", + input: JSON.stringify({a: 1, b: [[2, 3]]}), + expectedOutput: "a,b.0.0,b.0.1\r\n1,2,3\r\n", + recipeConfig: [ + { + op: "JSON转CSV", + args: [",", "\\r\\n"] + }, + ], + }, + { + name: "JSON to CSV: nested JSON, nested JSON", + input: JSON.stringify({a: 1, b: { c: { d: 2, e: 3}}}), + expectedOutput: "a,b.c.d,b.c.e\r\n1,2,3\r\n", + recipeConfig: [ + { + op: "JSON转CSV", + args: [",", "\\r\\n"] + }, + ], + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/JSONtoYAML.mjs b/plugins/srktoolbox/tests/operations/tests/JSONtoYAML.mjs new file mode 100644 index 00000000..04e0b856 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/JSONtoYAML.mjs @@ -0,0 +1,43 @@ +/** + * YAML tests. + * + * @author ccarpo [ccarpo@gmx.net] + * + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +const EXAMPLE_YAML = `number: 3\nplain: string\nblock: |\n two\n lines`; +const EXAMPLE_JSON = `{ "number": 3, "plain": "string" }`; + +TestRegister.addTests([ + { + name: "YAML to JSON", + input: EXAMPLE_YAML, + expectedOutput: JSON.stringify({ + "number": 3, + "plain": "string", + "block": "two\nlines\n" + }, null, 4), + recipeConfig: [ + { + op: "YAML转JSON", + args: [], + } + ], + }, + { + name: "JSON to YAML", + input: EXAMPLE_JSON, + expectedOutput: `number: 3\nplain: string\n`, + recipeConfig: [ + { + op: "JSON转YAML", + args: [], + } + ], + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/JWK.mjs b/plugins/srktoolbox/tests/operations/tests/JWK.mjs new file mode 100644 index 00000000..f000aa44 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/JWK.mjs @@ -0,0 +1,361 @@ +/** + * JWK conversion + * + * @author cplussharp + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +// test data for RSA key pair +const RSA_512 = { + private: { + pem1: `-----BEGIN RSA PRIVATE KEY----- +MIIBOQIBAAJBAPKr0Dp6YdItzOfk6a7ma7L4BF4LnelMYKtboGLrk6ihtqFPZFRL +NcJi68Hvnt8stMrP50t6jqwWQ2EjMdkj6fsCAwEAAQJAOJUpM0lv36MAQR3WAwsF +F7DOy+LnigteCvaNWiNVxZ6jByB5Qb7sall/Qlu9sFI0ZwrlVcKS0kldee7JTYlL +WQIhAP3UKEfOtpTgT1tYmdhaqjxqMfxBom0Ri+rt9ajlzs6vAiEA9L85B8/Gnb7p +6Af7/wpmafL277OV4X4xBfzMR+TUzHUCIBq+VLQkInaTH6lXL3ZtLwyIf9W9MJjf +RWeuRLjT5bM/AiBF7Kw6kx5Hy1fAtydEApCoDIaIjWJw/kC7WTJ0B+jUUQIgV6dw +NSyj0feakeD890gmId+lvl/w/3oUXiczqvl/N9o= +-----END RSA PRIVATE KEY-----`, + pem8: `-----BEGIN PRIVATE KEY----- +MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEA8qvQOnph0i3M5+Tp +ruZrsvgEXgud6Uxgq1ugYuuTqKG2oU9kVEs1wmLrwe+e3yy0ys/nS3qOrBZDYSMx +2SPp+wIDAQABAkA4lSkzSW/fowBBHdYDCwUXsM7L4ueKC14K9o1aI1XFnqMHIHlB +vuxqWX9CW72wUjRnCuVVwpLSSV157slNiUtZAiEA/dQoR862lOBPW1iZ2FqqPGox +/EGibRGL6u31qOXOzq8CIQD0vzkHz8advunoB/v/CmZp8vbvs5XhfjEF/MxH5NTM +dQIgGr5UtCQidpMfqVcvdm0vDIh/1b0wmN9FZ65EuNPlsz8CIEXsrDqTHkfLV8C3 +J0QCkKgMhoiNYnD+QLtZMnQH6NRRAiBXp3A1LKPR95qR4Pz3SCYh36W+X/D/ehRe +JzOq+X832g== +-----END PRIVATE KEY-----`, + jwk: { + "kty": "RSA", + "n": "8qvQOnph0i3M5-TpruZrsvgEXgud6Uxgq1ugYuuTqKG2oU9kVEs1wmLrwe-e3yy0ys_nS3qOrBZDYSMx2SPp-w", + "e": "AQAB", + "d": "OJUpM0lv36MAQR3WAwsFF7DOy-LnigteCvaNWiNVxZ6jByB5Qb7sall_Qlu9sFI0ZwrlVcKS0kldee7JTYlLWQ", + "p": "_dQoR862lOBPW1iZ2FqqPGox_EGibRGL6u31qOXOzq8", + "q": "9L85B8_Gnb7p6Af7_wpmafL277OV4X4xBfzMR-TUzHU", + "dp": "Gr5UtCQidpMfqVcvdm0vDIh_1b0wmN9FZ65EuNPlsz8", + "dq": "ReysOpMeR8tXwLcnRAKQqAyGiI1icP5Au1kydAfo1FE", + "qi": "V6dwNSyj0feakeD890gmId-lvl_w_3oUXiczqvl_N9o" + } + }, + public: { + pem1: `-----BEGIN RSA PUBLIC KEY----- +MEgCQQDyq9A6emHSLczn5Omu5muy+AReC53pTGCrW6Bi65OoobahT2RUSzXCYuvB +757fLLTKz+dLeo6sFkNhIzHZI+n7AgMBAAE= +-----END RSA PUBLIC KEY-----`, + pem8: `-----BEGIN PUBLIC KEY----- +MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAPKr0Dp6YdItzOfk6a7ma7L4BF4LnelM +YKtboGLrk6ihtqFPZFRLNcJi68Hvnt8stMrP50t6jqwWQ2EjMdkj6fsCAwEAAQ== +-----END PUBLIC KEY-----`, + cert: `-----BEGIN CERTIFICATE----- +MIIBfTCCASegAwIBAgIUeisK5Nwss2DGg5PCs4uSxxXyyNkwDQYJKoZIhvcNAQEL +BQAwEzERMA8GA1UEAwwIUlNBIHRlc3QwHhcNMjExMTE5MTcyMDI2WhcNMzExMTE3 +MTcyMDI2WjATMREwDwYDVQQDDAhSU0EgdGVzdDBcMA0GCSqGSIb3DQEBAQUAA0sA +MEgCQQDyq9A6emHSLczn5Omu5muy+AReC53pTGCrW6Bi65OoobahT2RUSzXCYuvB +757fLLTKz+dLeo6sFkNhIzHZI+n7AgMBAAGjUzBRMB0GA1UdDgQWBBRO+jvkqq5p +pnQgwMMnRoun6e7eiTAfBgNVHSMEGDAWgBRO+jvkqq5ppnQgwMMnRoun6e7eiTAP +BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA0EAR/5HAZM5qBhU/ezDUIFx +gmUGoFbIb5kJD41YCnaSdrgWglh4He4melSs42G/oxBBjuCJ0bUpqWnLl+lJkv1z +IA== +-----END CERTIFICATE-----`, + jwk: { + "kty": "RSA", + "n": "8qvQOnph0i3M5-TpruZrsvgEXgud6Uxgq1ugYuuTqKG2oU9kVEs1wmLrwe-e3yy0ys_nS3qOrBZDYSMx2SPp-w", + "e": "AQAB" + } + } +}; + +// test data for EC key pair +const EC_P256 = { + private: { + pem1: `-----BEGIN EC PRIVATE KEY----- +MHcCAQEEINtTjwUkgfAiSwqgcGAXWyE0ueIW6n2k395dmQZ3vGr4oAoGCCqGSM49 +AwEHoUQDQgAEDUc8A0EDNKoCYIPWMHz1yUzqE5mJgusgcAE8H6810fkJ8ZmTNiCC +a6sLgR2vD1VNh2diirWgKPH4PVMKav5e6Q== +-----END EC PRIVATE KEY-----`, + pem8: `-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg21OPBSSB8CJLCqBw +YBdbITS54hbqfaTf3l2ZBne8avihRANCAAQNRzwDQQM0qgJgg9YwfPXJTOoTmYmC +6yBwATwfrzXR+QnxmZM2IIJrqwuBHa8PVU2HZ2KKtaAo8fg9Uwpq/l7p +-----END PRIVATE KEY-----`, + jwk: { + "kty": "EC", + "crv": "P-256", + "x": "DUc8A0EDNKoCYIPWMHz1yUzqE5mJgusgcAE8H6810fk", + "y": "CfGZkzYggmurC4Edrw9VTYdnYoq1oCjx-D1TCmr-Xuk", + "d": "21OPBSSB8CJLCqBwYBdbITS54hbqfaTf3l2ZBne8avg" + } + }, + public: { + pem8: `-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDUc8A0EDNKoCYIPWMHz1yUzqE5mJ +gusgcAE8H6810fkJ8ZmTNiCCa6sLgR2vD1VNh2diirWgKPH4PVMKav5e6Q== +-----END PUBLIC KEY-----`, + cert: `-----BEGIN CERTIFICATE----- +MIIBfzCCASWgAwIBAgIUK4H8J3Hr7NpRLPrACj8Pje4JJJ0wCgYIKoZIzj0EAwIw +FTETMBEGA1UEAwwKUC0yNTYgdGVzdDAeFw0yMTExMTkxNzE5NDVaFw0zMTExMTcx +NzE5NDVaMBUxEzARBgNVBAMMClAtMjU2IHRlc3QwWTATBgcqhkjOPQIBBggqhkjO +PQMBBwNCAAQNRzwDQQM0qgJgg9YwfPXJTOoTmYmC6yBwATwfrzXR+QnxmZM2IIJr +qwuBHa8PVU2HZ2KKtaAo8fg9Uwpq/l7po1MwUTAdBgNVHQ4EFgQU/SxodXrpkybM +gcIgkxnRKd7HMzowHwYDVR0jBBgwFoAU/SxodXrpkybMgcIgkxnRKd7HMzowDwYD +VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNIADBFAiBU9PrOa/kXCpTTBInRf/sN +ac2iDHmbdpWzcXI+xLKNYAIhAIRR1LRSHVwOTLQ/iBXd+8LCkm5aTB27RW46LN80 +ylxt +-----END CERTIFICATE-----`, + jwk: { + "kty": "EC", + "crv": "P-256", + "x": "DUc8A0EDNKoCYIPWMHz1yUzqE5mJgusgcAE8H6810fk", + "y": "CfGZkzYggmurC4Edrw9VTYdnYoq1oCjx-D1TCmr-Xuk" + } + } +}; + +const PEM_PRIV_DSA1024 = `-----BEGIN DSA PRIVATE KEY----- +MIIBuwIBAAKBgQCkFEttBrPHEJRgcvaT8HbZs9h1pVQLHhn2F452izusRox1czMM +IC8Z7YQiM1pt6bgEmf0h8ldx6UFT0YL9JWSbyBy1U5pHKfnz/xjeg7ZMReL4F0/T +Gwmu4ercqfM//TmEg9nL3nDxb4WmF2al/SmHN3qlzYmYaIDEFfEuu8vWbwIVAMOq +7pqQiMGUu6uJY/nQTWW0c3IfAoGARWryStp2AElj538qN9tWRuyobRA93Q1ujrdM +EqsqVpMZd1a8qtRyMaZVVdB7N3EweNUuFOoSAp10s/SQEH9qhVo6NwvzhB7lEtm4 +5FjWW9+9WCuuFOGZpTy8PSFAvQcfUqunP/DeaDliNmgKci+n0nfIBakuQn10Zmqk +vGu8NZICgYBUsoQeXSJ19e6XZenk6G8wVI3yXFqnRAwb6s7sAVoPwfDCsOXTxC7W +Mlfz0HcYMiifFKEd28NnuAZ2e0ngyPHsb9s5phzTgRfO3GFzOjsjwgx3DmQI2Ck2 +yOWHSAtaNhH4DoBZEyNsb1akiB50vx9b09EHN4weqbgAu743NMDHRQIVAIG5uiiO +OnWUYieHAiVIPkBCrYUd +-----END DSA PRIVATE KEY-----`; + +// https://datatracker.ietf.org/doc/html/rfc8037#appendix-A.2 +const JWK_PUB_ED25591 = { + "kty": "OKP", + "crv": "Ed25519", + "x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo" +}; + +TestRegister.addTests([ + { + name: "PEM to JWK: Missing footer", + input: RSA_512.private.pem1.substring(0, RSA_512.private.pem1.length / 2), + expectedOutput: "未找到PEM footer '-----END RSA PRIVATE KEY-----'", + recipeConfig: [ + { + op: "PEM转JWK", + args: [], + } + ], + }, + { + name: "PEM to JWK: DSA not supported", + input: PEM_PRIV_DSA1024, + expectedOutput: "JWK不支持DSA密钥。", + recipeConfig: [ + { + op: "PEM转JWK", + args: [], + } + ], + }, + + // test RSA key convertion + { + name: "PEM to JWK: RSA Private Key PKCS1", + input: RSA_512.private.pem1, + expectedOutput: JSON.stringify(RSA_512.private.jwk), + recipeConfig: [ + { + op: "PEM转JWK", + args: [], + } + ], + }, + { + name: "PEM to JWK: RSA Private Key PKCS8", + input: RSA_512.private.pem8, + expectedOutput: JSON.stringify(RSA_512.private.jwk), + recipeConfig: [ + { + op: "PEM转JWK", + args: [], + } + ], + }, + { + name: "PEM to JWK: RSA Public Key PKCS1", + input: RSA_512.public.pem1, + expectedOutput: "不支持的RSA公钥格式。仅支持PKCS#8。", + recipeConfig: [ + { + op: "PEM转JWK", + args: [], + } + ], + }, + { + name: "PEM to JWK: RSA Public Key PKCS8", + input: RSA_512.public.pem8, + expectedOutput: JSON.stringify(RSA_512.public.jwk), + recipeConfig: [ + { + op: "PEM转JWK", + args: [], + } + ], + }, + { + name: "PEM to JWK: Certificate with RSA Public Key", + input: RSA_512.public.cert, + expectedOutput: JSON.stringify(RSA_512.public.jwk), + recipeConfig: [ + { + op: "PEM转JWK", + args: [], + } + ], + }, + + // test EC key conversion + { + name: "PEM to JWK: EC Private Key PKCS1", + input: EC_P256.private.pem1, + expectedOutput: JSON.stringify(EC_P256.private.jwk), + recipeConfig: [ + { + op: "PEM转JWK", + args: [], + } + ], + }, + { + name: "PEM to JWK: EC Private Key PKCS8", + input: EC_P256.private.pem8, + expectedOutput: JSON.stringify(EC_P256.private.jwk), + recipeConfig: [ + { + op: "PEM转JWK", + args: [], + } + ], + }, + { + name: "PEM to JWK: EC Public Key", + input: EC_P256.public.pem8, + expectedOutput: JSON.stringify(EC_P256.public.jwk), + recipeConfig: [ + { + op: "PEM转JWK", + args: [], + } + ], + }, + { + name: "PEM to JWK: Certificate with EC Public Key", + input: EC_P256.public.cert, + expectedOutput: JSON.stringify(EC_P256.public.jwk), + recipeConfig: [ + { + op: "PEM转JWK", + args: [], + } + ], + }, + + + { + name: "JWK to PEM: not a JWK", + input: "\"foobar\"", + expectedOutput: "输入内容不是有效的JSON Web Key", + recipeConfig: [ + { + op: "JWK转PEM", + args: [], + } + ], + }, + { + name: "JWK to PEM: unsupported key type", + input: JSON.stringify(JWK_PUB_ED25591), + expectedOutput: "不支持的JWK密钥类型'OKP'", + recipeConfig: [ + { + op: "JWK转PEM", + args: [], + } + ], + }, + + // test RSA key conversion + { + name: "JWK to PEM: RSA Private Key", + input: JSON.stringify(RSA_512.private.jwk), + expectedOutput: RSA_512.private.pem8.replace(/\r/g, "").replace(/\n/g, "\r\n")+"\r\n", + recipeConfig: [ + { + op: "JWK转PEM", + args: [], + } + ], + }, + { + name: "JWK to PEM: RSA Public Key", + input: JSON.stringify(RSA_512.public.jwk), + expectedOutput: RSA_512.public.pem8.replace(/\r/g, "").replace(/\n/g, "\r\n")+"\r\n", + recipeConfig: [ + { + op: "JWK转PEM", + args: [], + } + ], + }, + + // test EC key conversion + { + name: "JWK to PEM: EC Private Key", + input: JSON.stringify(EC_P256.private.jwk), + expectedOutput: EC_P256.private.pem8.replace(/\r/g, "").replace(/\n/g, "\r\n")+"\r\n", + recipeConfig: [ + { + op: "JWK转PEM", + args: [], + } + ], + }, + { + name: "JWK to PEM: EC Public Key", + input: JSON.stringify(EC_P256.public.jwk), + expectedOutput: EC_P256.public.pem8.replace(/\r/g, "").replace(/\n/g, "\r\n")+"\r\n", + recipeConfig: [ + { + op: "JWK转PEM", + args: [], + } + ], + }, + + { + name: "JWK to PEM: Array of keys", + input: JSON.stringify([RSA_512.public.jwk, EC_P256.public.jwk]), + expectedOutput: (RSA_512.public.pem8 + "\n" + EC_P256.public.pem8 + "\n").replace(/\r/g, "").replace(/\n/g, "\r\n"), + recipeConfig: [ + { + op: "JWK转PEM", + args: [], + } + ], + }, + { + name: "JWK to PEM: JSON Web Key Set", + input: JSON.stringify({"keys": [RSA_512.public.jwk, EC_P256.public.jwk]}), + expectedOutput: (RSA_512.public.pem8 + "\n" + EC_P256.public.pem8 + "\n").replace(/\r/g, "").replace(/\n/g, "\r\n"), + recipeConfig: [ + { + op: "JWK转PEM", + args: [], + } + ], + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/JWTDecode.mjs b/plugins/srktoolbox/tests/operations/tests/JWTDecode.mjs new file mode 100644 index 00000000..3e2335a4 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/JWTDecode.mjs @@ -0,0 +1,53 @@ +/** + * JWT Decode tests + * + * @author gchq77703 [] + * + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +const outputObject = JSON.stringify({ + String: "SomeString", + Number: 42, + iat: 1 +}, null, 4); + +TestRegister.addTests([ + { + name: "JWT Decode: HS", + input: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.0ha6-j4FwvEIKPVZ-hf3S_R9Hy_UtXzq4dnedXcUrXk", + expectedOutput: outputObject, + recipeConfig: [ + { + op: "JWT解码", + args: [], + } + ], + }, + { + name: "JWT Decode: RS", + input: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.MjEJhtZk2nXzigi24piMzANmrj3mILHJcDl0xOjl5a8EgdKVL1oaMEjTkMQp5RA8YrqeRBFaX-BGGCKOXn5zPY1DJwWsBUyN9C-wGR2Qye0eogH_3b4M9EW00TPCUPXm2rx8URFj7Wg9VlsmrGzLV2oKkPgkVxuFSxnpO3yjn1Y", + expectedOutput: outputObject, + recipeConfig: [ + { + op: "JWT解码", + args: [], + } + ], + }, + { + name: "JWT Decode: ES", + input: "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.WkECT51jSfpRkcpQ4x0h5Dwe7CFBI6u6Et2gWp91HC7mpN_qCFadRpsvJLtKubm6cJTLa68xtei0YrDD8fxIUA", + expectedOutput: outputObject, + recipeConfig: [ + { + op: "JWT解码", + args: [], + } + ], + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/JWTSign.mjs b/plugins/srktoolbox/tests/operations/tests/JWTSign.mjs new file mode 100644 index 00000000..1aacafcd --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/JWTSign.mjs @@ -0,0 +1,176 @@ +/** + * JWT Sign tests + * + * @author gchq77703 [] + * + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +const inputObject = JSON.stringify({ + String: "SomeString", + Number: 42, + iat: 1 +}, null, 4); + +const hsKey = "secret_cat"; +const rsKey = `-----BEGIN RSA PRIVATE KEY----- +MIICWwIBAAKBgQDdlatRjRjogo3WojgGHFHYLugdUWAY9iR3fy4arWNA1KoS8kVw +33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQsHUfQrSDv+MuSUMAe8jzKE4qW ++jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5Do2kQ+X5xK9cipRgEKwIDAQAB +AoGAD+onAtVye4ic7VR7V50DF9bOnwRwNXrARcDhq9LWNRrRGElESYYTQ6EbatXS +3MCyjjX2eMhu/aF5YhXBwkppwxg+EOmXeh+MzL7Zh284OuPbkglAaGhV9bb6/5Cp +uGb1esyPbYW+Ty2PC0GSZfIXkXs76jXAu9TOBvD0ybc2YlkCQQDywg2R/7t3Q2OE +2+yo382CLJdrlSLVROWKwb4tb2PjhY4XAwV8d1vy0RenxTB+K5Mu57uVSTHtrMK0 +GAtFr833AkEA6avx20OHo61Yela/4k5kQDtjEf1N0LfI+BcWZtxsS3jDM3i1Hp0K +Su5rsCPb8acJo5RO26gGVrfAsDcIXKC+bQJAZZ2XIpsitLyPpuiMOvBbzPavd4gY +6Z8KWrfYzJoI/Q9FuBo6rKwl4BFoToD7WIUS+hpkagwWiz+6zLoX1dbOZwJACmH5 +fSSjAkLRi54PKJ8TFUeOP15h9sQzydI8zJU+upvDEKZsZc/UhT/SySDOxQ4G/523 +Y0sz/OZtSWcol/UMgQJALesy++GdvoIDLfJX5GBQpuFgFenRiRDabxrE9MNUZ2aP +FaFp+DyAe+b4nDwuJaW2LURbr8AEZga7oQj0uYxcYw== +-----END RSA PRIVATE KEY-----`; +const esKey = `-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgevZzL1gdAFr88hb2 +OF/2NxApJCzGCEDdfSp6VQO30hyhRANCAAQRWz+jn65BtOMvdyHKcvjBeBSDZH2r +1RTwjmYSi9R/zpBnuQ4EiMnCqfMPWiZqB4QdbAd0E7oH50VpuZ1P087G +-----END PRIVATE KEY-----`; + +TestRegister.addTests([ + { + name: "JWT Sign: HS256", + input: inputObject, + expectedOutput: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.0ha6-j4FwvEIKPVZ-hf3S_R9Hy_UtXzq4dnedXcUrXk", + recipeConfig: [ + { + op: "JWT签名", + args: [hsKey, "HS256", "{}"], + } + ], + }, + { + name: "JWT Sign: HS256 with custom header", + input: inputObject, + expectedOutput: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImN1c3RvbS5rZXkifQ.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.kXln8btJburfRlND8IDZAQ8NZGFFZhvHyooHa6N9za8", + recipeConfig: [ + { + op: "JWT签名", + args: [hsKey, "HS256", `{"kid":"custom.key"}`], + } + ], + }, + { + name: "JWT Sign: HS384", + input: inputObject, + expectedOutput: "eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ._bPK-Y3mIACConbJqkGFMQ_L3vbxgKXy9gSxtL9hA5XTganozTSXxD0vX0N1yT5s", + recipeConfig: [ + { + op: "JWT签名", + args: [hsKey, "HS384", "{}"], + } + ], + }, + { + name: "JWT Sign: HS512", + input: inputObject, + expectedOutput: "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.vZIJU4XYMFt3FLE1V_RZOxEetmV4RvxtPZQGzJthK_d47pjwlEb6pQE23YxHFmOj8H5RLEdqqLPw4jNsOyHRzA", + recipeConfig: [ + { + op: "JWT签名", + args: [hsKey, "HS512", "{}"], + } + ], + }, + { + name: "JWT Sign: ES256", + input: inputObject, + expectedOutput: inputObject, + recipeConfig: [ + { + op: "JWT签名", + args: [esKey, "ES256", "{}"], + }, + { + op: "JWT解码", + args: [] + } + ], + }, + { + name: "JWT Sign: ES384", + input: inputObject, + expectedOutput: inputObject, + recipeConfig: [ + { + op: "JWT签名", + args: [esKey, "ES384", "{}"], + }, + { + op: "JWT解码", + args: [] + } + ], + }, + { + name: "JWT Sign: ES512", + input: inputObject, + expectedOutput: inputObject, + recipeConfig: [ + { + op: "JWT签名", + args: [esKey, "ES512", "{}"], + }, + { + op: "JWT解码", + args: [] + } + ], + }, + { + name: "JWT Sign: RS256", + input: inputObject, + expectedOutput: inputObject, + recipeConfig: [ + { + op: "JWT签名", + args: [rsKey, "RS256", "{}"], + }, + { + op: "JWT解码", + args: [] + } + ], + }, + { + name: "JWT Sign: RS384", + input: inputObject, + expectedOutput: inputObject, + recipeConfig: [ + { + op: "JWT签名", + args: [rsKey, "RS384", "{}"], + }, + { + op: "JWT解码", + args: [] + } + ], + }, + { + name: "JWT Sign: RS512", + input: inputObject, + expectedOutput: inputObject, + recipeConfig: [ + { + op: "JWT签名", + args: [esKey, "RS512", "{}"], + }, + { + op: "JWT解码", + args: [] + } + ], + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/JWTVerify.mjs b/plugins/srktoolbox/tests/operations/tests/JWTVerify.mjs new file mode 100644 index 00000000..158fedb1 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/JWTVerify.mjs @@ -0,0 +1,89 @@ +/** + * JWT Verify tests + * + * @author gchq77703 [] + * + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +const outputObject = JSON.stringify({ + String: "SomeString", + Number: 42, + iat: 1 +}, null, 4); + +const hsKey = "secret_cat"; +/* Retaining private key as a comment +const rsPriv = `-----BEGIN RSA PRIVATE KEY----- +MIICWwIBAAKBgQDdlatRjRjogo3WojgGHFHYLugdUWAY9iR3fy4arWNA1KoS8kVw +33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQsHUfQrSDv+MuSUMAe8jzKE4qW ++jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5Do2kQ+X5xK9cipRgEKwIDAQAB +AoGAD+onAtVye4ic7VR7V50DF9bOnwRwNXrARcDhq9LWNRrRGElESYYTQ6EbatXS +3MCyjjX2eMhu/aF5YhXBwkppwxg+EOmXeh+MzL7Zh284OuPbkglAaGhV9bb6/5Cp +uGb1esyPbYW+Ty2PC0GSZfIXkXs76jXAu9TOBvD0ybc2YlkCQQDywg2R/7t3Q2OE +2+yo382CLJdrlSLVROWKwb4tb2PjhY4XAwV8d1vy0RenxTB+K5Mu57uVSTHtrMK0 +GAtFr833AkEA6avx20OHo61Yela/4k5kQDtjEf1N0LfI+BcWZtxsS3jDM3i1Hp0K +Su5rsCPb8acJo5RO26gGVrfAsDcIXKC+bQJAZZ2XIpsitLyPpuiMOvBbzPavd4gY +6Z8KWrfYzJoI/Q9FuBo6rKwl4BFoToD7WIUS+hpkagwWiz+6zLoX1dbOZwJACmH5 +fSSjAkLRi54PKJ8TFUeOP15h9sQzydI8zJU+upvDEKZsZc/UhT/SySDOxQ4G/523 +Y0sz/OZtSWcol/UMgQJALesy++GdvoIDLfJX5GBQpuFgFenRiRDabxrE9MNUZ2aP +FaFp+DyAe+b4nDwuJaW2LURbr8AEZga7oQj0uYxcYw== +-----END RSA PRIVATE KEY-----`; +*/ +const rsPub = `-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdlatRjRjogo3WojgGHFHYLugd +UWAY9iR3fy4arWNA1KoS8kVw33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQs +HUfQrSDv+MuSUMAe8jzKE4qW+jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5D +o2kQ+X5xK9cipRgEKwIDAQAB +-----END PUBLIC KEY-----`; +/* Retaining private key as a comment +const esPriv = `-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgevZzL1gdAFr88hb2 +OF/2NxApJCzGCEDdfSp6VQO30hyhRANCAAQRWz+jn65BtOMvdyHKcvjBeBSDZH2r +1RTwjmYSi9R/zpBnuQ4EiMnCqfMPWiZqB4QdbAd0E7oH50VpuZ1P087G +-----END PRIVATE KEY-----`; +*/ +const esPub = `-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9 +q9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg== +-----END PUBLIC KEY-----`; + +TestRegister.addTests([ + { + name: "JWT Verify: HS", + input: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.0ha6-j4FwvEIKPVZ-hf3S_R9Hy_UtXzq4dnedXcUrXk", + expectedOutput: outputObject, + recipeConfig: [ + { + op: "JWT验证", + args: [hsKey], + } + ], + }, + { + name: "JWT Verify: RS", + input: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.MjEJhtZk2nXzigi24piMzANmrj3mILHJcDl0xOjl5a8EgdKVL1oaMEjTkMQp5RA8YrqeRBFaX-BGGCKOXn5zPY1DJwWsBUyN9C-wGR2Qye0eogH_3b4M9EW00TPCUPXm2rx8URFj7Wg9VlsmrGzLV2oKkPgkVxuFSxnpO3yjn1Y", + expectedOutput: outputObject, + recipeConfig: [ + { + op: "JWT验证", + args: [rsPub], + } + ], + }, + { + name: "JWT Verify: ES", + input: "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.WkECT51jSfpRkcpQ4x0h5Dwe7CFBI6u6Et2gWp91HC7mpN_qCFadRpsvJLtKubm6cJTLa68xtei0YrDD8fxIUA", + expectedOutput: outputObject, + recipeConfig: [ + { + op: "JWT验证", + args: [esPub], + } + ], + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/Jsonata.mjs b/plugins/srktoolbox/tests/operations/tests/Jsonata.mjs new file mode 100644 index 00000000..7ce2a99c --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/Jsonata.mjs @@ -0,0 +1,553 @@ +/** + * Jsonata Query tests. + * + * @author Jon King [jon@ajarsoftware.com] + * + * @copyright Crown Copyright 2025 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +const INPUT_JSON_OBJECT_WITH_ARRAYS = `{ + "FirstName": "Fred", + "Surname": "Smith", + "Age": 28, + "Address": { + "Street": "Hursley Park", + "City": "Winchester", + "Postcode": "SO21 2JN" + }, + "Phone": [ + { + "type": "home", + "number": "0203 544 1234" + }, + { + "type": "office", + "number": "01962 001234" + }, + { + "type": "office", + "number": "01962 001235" + }, + { + "type": "mobile", + "number": "077 7700 1234" + } + ], + "Email": [ + { + "type": "work", + "address": ["fred.smith@my-work.com", "fsmith@my-work.com"] + }, + { + "type": "home", + "address": ["freddy@my-social.com", "frederic.smith@very-serious.com"] + } + ], + "Other": { + "Over 18 ?": true, + "Misc": null, + "Alternative.Address": { + "Street": "Brick Lane", + "City": "London", + "Postcode": "E1 6RF" + } + } +}`; + +const INPUT_ARRAY_OF_OBJECTS = `[ + { "ref": [ 1,2 ] }, + { "ref": [ 3,4 ] } +]`; + +const INPUT_NUMBER_ARRAY = `{ + "Numbers": [1, 2.4, 3.5, 10, 20.9, 30] +}`; + +TestRegister.addTests([ + { + name: "Jsonata: Returns a JSON string (double quoted)", + input: INPUT_JSON_OBJECT_WITH_ARRAYS, + expectedOutput: '"Smith"', + recipeConfig: [ + { + op: "Jsonata查询", + args: ["Surname"], + }, + ], + }, + { + name: "Jsonata: Returns a JSON number", + input: INPUT_JSON_OBJECT_WITH_ARRAYS, + expectedOutput: "28", + recipeConfig: [ + { + op: "Jsonata查询", + args: ["Age"], + }, + ], + }, + { + name: "Jsonata: Field references are separated by '.'", + input: INPUT_JSON_OBJECT_WITH_ARRAYS, + expectedOutput: '"Winchester"', + recipeConfig: [ + { + op: "Jsonata查询", + args: ["Address.City"], + }, + ], + }, + { + name: "Jsonata: Matched the path and returns the null value", + input: INPUT_JSON_OBJECT_WITH_ARRAYS, + expectedOutput: "null", + recipeConfig: [ + { + op: "Jsonata查询", + args: ["Other.Misc"], + }, + ], + }, + { + name: "Jsonata: Path not found. Returns nothing", + input: INPUT_JSON_OBJECT_WITH_ARRAYS, + expectedOutput: '""', + recipeConfig: [ + { + op: "Jsonata查询", + args: ["Other.DoesntExist"], + }, + ], + }, + { + name: "Jsonata: Field references containing whitespace or reserved tokens can be enclosed in backticks", + input: INPUT_JSON_OBJECT_WITH_ARRAYS, + expectedOutput: "true", + recipeConfig: [ + { + op: "Jsonata查询", + args: ["Other.`Over 18 ?`"], + }, + ], + }, + { + name: "Jsonata: Returns the first item (an object)", + input: INPUT_JSON_OBJECT_WITH_ARRAYS, + expectedOutput: '{"type":"home","number":"0203 544 1234"}', + recipeConfig: [ + { + op: "Jsonata查询", + args: ["Phone[0]"], + }, + ], + }, + { + name: "Jsonata: Returns the second item", + input: INPUT_JSON_OBJECT_WITH_ARRAYS, + expectedOutput: '{"type":"office","number":"01962 001234"}', + recipeConfig: [ + { + op: "Jsonata查询", + args: ["Phone[1]"], + }, + ], + }, + { + name: "Jsonata: Returns the last item", + input: INPUT_JSON_OBJECT_WITH_ARRAYS, + expectedOutput: '{"type":"mobile","number":"077 7700 1234"}', + recipeConfig: [ + { + op: "Jsonata查询", + args: ["Phone[-1]"], + }, + ], + }, + { + name: "Jsonata: Negative indexed count from the end", + input: INPUT_JSON_OBJECT_WITH_ARRAYS, + expectedOutput: '{"type":"office","number":"01962 001235"}', + recipeConfig: [ + { + op: "Jsonata查询", + args: ["Phone[-2]"], + }, + ], + }, + { + name: "Jsonata: Doesn't exist - returns nothing", + input: INPUT_JSON_OBJECT_WITH_ARRAYS, + expectedOutput: '""', + recipeConfig: [ + { + op: "Jsonata查询", + args: ["Phone[8]"], + }, + ], + }, + { + name: "Jsonata: Selects the number field in the first item", + input: INPUT_JSON_OBJECT_WITH_ARRAYS, + expectedOutput: '"0203 544 1234"', + recipeConfig: [ + { + op: "Jsonata查询", + args: ["Phone[0].number"], + }, + ], + }, + { + name: "Jsonata: No index is given to Phone so it selects all of them (the whole array), then it selects all the number fields for each of them", + input: INPUT_JSON_OBJECT_WITH_ARRAYS, + expectedOutput: + '["0203 544 1234","01962 001234","01962 001235","077 7700 1234"]', + recipeConfig: [ + { + op: "Jsonata查询", + args: ["Phone.number"], + }, + ], + }, + { + name: "Jsonata: Might expect it to just return the first number, but it returns the first number of each of the items selected by Phone", + input: INPUT_JSON_OBJECT_WITH_ARRAYS, + expectedOutput: + '["0203 544 1234","01962 001234","01962 001235","077 7700 1234"]', + recipeConfig: [ + { + op: "Jsonata查询", + args: ["Phone.number[0]"], + }, + ], + }, + { + name: "Jsonata: Applies the index to the array returned by Phone.number.", + input: INPUT_JSON_OBJECT_WITH_ARRAYS, + expectedOutput: '"0203 544 1234"', + recipeConfig: [ + { + op: "Jsonata查询", + args: ["(Phone.number)[0]"], + }, + ], + }, + { + name: "Jsonata: Returns a range of items by creating an array of indexes", + input: INPUT_JSON_OBJECT_WITH_ARRAYS, + expectedOutput: + '[{"type":"home","number":"0203 544 1234"},{"type":"office","number":"01962 001234"}]', + recipeConfig: [ + { + op: "Jsonata查询", + args: ["Phone[[0..1]]"], + }, + ], + }, + // Predicates + { + name: "Jsonata: Select the Phone items that have a type field that equals 'mobile'", + input: INPUT_JSON_OBJECT_WITH_ARRAYS, + expectedOutput: '{"type":"mobile","number":"077 7700 1234"}', + recipeConfig: [ + { + op: "Jsonata查询", + args: ["Phone[type='mobile']"], + }, + ], + }, + { + name: "Jsonata: Select the mobile phone number", + input: INPUT_JSON_OBJECT_WITH_ARRAYS, + expectedOutput: '"077 7700 1234"', + recipeConfig: [ + { + op: "Jsonata查询", + args: ["Phone[type='mobile'].number"], + }, + ], + }, + { + name: "Jsonata: Select the office phone numbers - there are two of them", + input: INPUT_JSON_OBJECT_WITH_ARRAYS, + expectedOutput: '["01962 001234","01962 001235"]', + recipeConfig: [ + { + op: "Jsonata查询", + args: ["Phone[type='office'].number"], + }, + ], + }, + // Wildcards + { + name: "Jsonata: Select the values of all the fields of 'Address'", + input: INPUT_JSON_OBJECT_WITH_ARRAYS, + expectedOutput: '["Hursley Park","Winchester","SO21 2JN"]', + recipeConfig: [ + { + op: "Jsonata查询", + args: ["Address.*"], + }, + ], + }, + { + name: "Jsonata: Select the 'Postcode' value of any child object", + input: INPUT_JSON_OBJECT_WITH_ARRAYS, + expectedOutput: '"SO21 2JN"', + recipeConfig: [ + { + op: "Jsonata查询", + args: ["*.Postcode"], + }, + ], + }, + { + name: "Jsonata: Select all Postcode values, regardless of how deeply nested they are in the structure", + input: INPUT_JSON_OBJECT_WITH_ARRAYS, + expectedOutput: '["SO21 2JN","E1 6RF"]', + recipeConfig: [ + { + op: "Jsonata查询", + args: ["**.Postcode"], + }, + ], + }, + // String Expressions + { + name: "Jsonata: Concatenate 'FirstName' followed by space followed by 'Surname'", + input: INPUT_JSON_OBJECT_WITH_ARRAYS, + expectedOutput: '"Fred Smith"', + recipeConfig: [ + { + op: "Jsonata查询", + args: ["FirstName & ' ' & Surname"], + }, + ], + }, + { + name: "Jsonata: Concatenates the 'Street' and 'City' from the 'Address' object with a comma separator", + input: INPUT_JSON_OBJECT_WITH_ARRAYS, + expectedOutput: '"Hursley Park, Winchester"', + recipeConfig: [ + { + op: "Jsonata查询", + args: ["Address.(Street & ', ' & City)"], + }, + ], + }, + { + name: "Jsonata: Casts the operands to strings, if necessary", + input: INPUT_JSON_OBJECT_WITH_ARRAYS, + expectedOutput: '"50true"', + recipeConfig: [ + { + op: "Jsonata查询", + args: ["5&0&true"], + }, + ], + }, + // Numeric Expressions + { + name: "Jsonata: Addition", + input: INPUT_NUMBER_ARRAY, + expectedOutput: "3.4", + recipeConfig: [ + { + op: "Jsonata查询", + args: ["Numbers[0] + Numbers[1]"], + }, + ], + }, + { + name: "Jsonata: Subtraction", + input: INPUT_NUMBER_ARRAY, + expectedOutput: "-19.9", + recipeConfig: [ + { + op: "Jsonata查询", + args: ["Numbers[0] - Numbers[4]"], + }, + ], + }, + { + name: "Jsonata: Multiplication", + input: INPUT_NUMBER_ARRAY, + expectedOutput: "30", + recipeConfig: [ + { + op: "Jsonata查询", + args: ["Numbers[0] * Numbers[5]"], + }, + ], + }, + { + name: "Jsonata: Division", + input: INPUT_NUMBER_ARRAY, + expectedOutput: "0.04784688995215311", + recipeConfig: [ + { + op: "Jsonata查询", + args: ["Numbers[0] / Numbers[4]"], + }, + ], + }, + { + name: "Jsonata: Modulus", + input: INPUT_NUMBER_ARRAY, + expectedOutput: "3.5", + recipeConfig: [ + { + op: "Jsonata查询", + args: ["Numbers[2] % Numbers[5]"], + }, + ], + }, + { + name: "Jsonata: Equality", + input: INPUT_NUMBER_ARRAY, + expectedOutput: "false", + recipeConfig: [ + { + op: "Jsonata查询", + args: ["Numbers[0] = Numbers[5]"], + }, + ], + }, + { + name: "Jsonata: Inequality", + input: INPUT_NUMBER_ARRAY, + expectedOutput: "true", + recipeConfig: [ + { + op: "Jsonata查询", + args: ["Numbers[0] != Numbers[4]"], + }, + ], + }, + { + name: "Jsonata: Less than", + input: INPUT_NUMBER_ARRAY, + expectedOutput: "true", + recipeConfig: [ + { + op: "Jsonata查询", + args: ["Numbers[0] < Numbers[4]"], + }, + ], + }, + { + name: "Jsonata: Less than or equal to", + input: INPUT_NUMBER_ARRAY, + expectedOutput: "true", + recipeConfig: [ + { + op: "Jsonata查询", + args: ["Numbers[0] <= Numbers[4]"], + }, + ], + }, + { + name: "Jsonata: Greater than", + input: INPUT_NUMBER_ARRAY, + expectedOutput: "false", + recipeConfig: [ + { + op: "Jsonata查询", + args: ["Numbers[0] > Numbers[4]"], + }, + ], + }, + { + name: "Jsonata: Greater than or equal to", + input: INPUT_NUMBER_ARRAY, + expectedOutput: "false", + recipeConfig: [ + { + op: "Jsonata查询", + args: ["Numbers[2] >= Numbers[4]"], + }, + ], + }, + { + name: "Jsonata: Value is contained in", + input: INPUT_JSON_OBJECT_WITH_ARRAYS, + expectedOutput: "true", + recipeConfig: [ + { + op: "Jsonata查询", + args: ['"01962 001234" in Phone.number'], + }, + ], + }, + // Boolean Expressions + { + name: "Jsonata: and", + input: INPUT_NUMBER_ARRAY, + expectedOutput: "true", + recipeConfig: [ + { + op: "Jsonata查询", + args: ["(Numbers[2] != 0) and (Numbers[5] != Numbers[1])"], + }, + ], + }, + { + name: "Jsonata: or", + input: INPUT_NUMBER_ARRAY, + expectedOutput: "true", + recipeConfig: [ + { + op: "Jsonata查询", + args: ["(Numbers[2] != 0) or (Numbers[5] = Numbers[1])"], + }, + ], + }, + // Array tests + { + name: "Jsonata: $ at the start of an expression refers to the entire input document, subscripting it with 0 selects the first item", + input: INPUT_ARRAY_OF_OBJECTS, + expectedOutput: '{"ref":[1,2]}', + recipeConfig: [ + { + op: "Jsonata查询", + args: ["$[0]"], + }, + ], + }, + { + name: "Jsonata: .ref here returns the entire internal array", + input: INPUT_ARRAY_OF_OBJECTS, + expectedOutput: "[1,2]", + recipeConfig: [ + { + op: "Jsonata查询", + args: ["$[0].ref"], + }, + ], + }, + { + name: "Jsonata: returns element on first position of the internal array", + input: INPUT_ARRAY_OF_OBJECTS, + expectedOutput: "1", + recipeConfig: [ + { + op: "Jsonata查询", + args: ["$[0].ref[0]"], + }, + ], + }, + { + name: "Jsonata: $.field_reference flattens the result into a single array", + input: INPUT_ARRAY_OF_OBJECTS, + expectedOutput: "[1,2,3,4]", + recipeConfig: [ + { + op: "Jsonata查询", + args: ["$.ref"], + }, + ], + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/Jump.mjs b/plugins/srktoolbox/tests/operations/tests/Jump.mjs new file mode 100644 index 00000000..e41e4972 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/Jump.mjs @@ -0,0 +1,56 @@ +/** + * Jump tests + * + * @author tlwr [toby@toby.codes] + * + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Jump: Empty Label", + input: [ + "should be changed", + ].join("\n"), + expectedOutput: [ + "c2hvdWxkIGJlIGNoYW5nZWQ=", + ].join("\n"), + recipeConfig: [ + { + op: "Jump", + args: ["", 10], + }, + { + op: "Base64编码", + args: ["A-Za-z0-9+/="], + }, + ], + }, + { + name: "Jump: skips 1", + input: [ + "shouldnt be changed", + ].join("\n"), + expectedOutput: [ + "shouldnt be changed", + ].join("\n"), + recipeConfig: [ + { + op: "Jump", + args: ["skipReplace", 10], + }, + { + op: "Base64编码", + args: ["A-Za-z0-9+/="], + }, + { + op: "Label", + args: ["skipReplace"] + }, + ], + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/LS47.mjs b/plugins/srktoolbox/tests/operations/tests/LS47.mjs new file mode 100644 index 00000000..01032e15 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/LS47.mjs @@ -0,0 +1,47 @@ +/** + * LS47 tests. + * + * @author n1073645 [n1073645@gmail.com] + * + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "LS47 Encrypt", + input: "thequickbrownfoxjumped", + expectedOutput: "(,t74ci78cp/8trx*yesu:alp1wqy", + recipeConfig: [ + { + op: "LS47加密", + args: ["helloworld", 0, "test"], + }, + ], + }, + { + name: "LS47 Decrypt", + input: "(,t74ci78cp/8trx*yesu:alp1wqy", + expectedOutput: "thequickbrownfoxjumped---test", + recipeConfig: [ + { + op: "LS47解密", + args: ["helloworld", 0], + }, + ], + }, + { + name: "LS47 Encrypt", + input: "thequickbrownfoxjumped", + expectedOutput: "Letter H is not included in LS47", + recipeConfig: [ + { + op: "LS47加密", + args: ["Helloworld", 0, "test"], + }, + ], + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/LZNT1Decompress.mjs b/plugins/srktoolbox/tests/operations/tests/LZNT1Decompress.mjs new file mode 100644 index 00000000..e0442243 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/LZNT1Decompress.mjs @@ -0,0 +1,24 @@ +/** + * LZNT1 Decompress tests. + * + * @author 0xThiebaut [thiebaut.dev] + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "LZNT1 Decompress", + input: "\x1a\xb0\x00compress\x00edtestda\x04ta\x07\x88alot", + expectedOutput: "compressedtestdatacompressedalot", + recipeConfig: [ + { + op: "LZNT1解压", + args: [] + } + ], + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/LZString.mjs b/plugins/srktoolbox/tests/operations/tests/LZString.mjs new file mode 100644 index 00000000..b750895e --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/LZString.mjs @@ -0,0 +1,35 @@ +/** + * LZString tests. + * + * @author crespyl [peter@crespyl.net] + * @copyright Peter Jacobs 2021 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "LZString Compress To Base64", + input: "hello world", + expectedOutput: "BYUwNmD2AEDukCcwBMg=", + recipeConfig: [ + { + "op": "LZString压缩", + "args": ["Base64"] + } + ], + }, + { + name: "LZString Decompress From Base64", + input: "BYUwNmD2AEDukCcwBMg=", + expectedOutput: "hello world", + recipeConfig: [ + { + "op": "LZString解压", + "args": ["Base64"] + } + ], + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/LevenshteinDistance.mjs b/plugins/srktoolbox/tests/operations/tests/LevenshteinDistance.mjs new file mode 100644 index 00000000..51448a51 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/LevenshteinDistance.mjs @@ -0,0 +1,167 @@ +/** + * @author mikecat + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + "name": "Levenshtein Distance: Wikipedia example 1", + "input": "kitten\nsitting", + "expectedOutput": "3", + "recipeConfig": [ + { + "op": "莱文斯坦距离", + "args": [ + "\\n", 1, 1, 1, + ], + }, + ], + }, + { + "name": "Levenshtein Distance: Wikipedia example 2", + "input": "saturday\nsunday", + "expectedOutput": "3", + "recipeConfig": [ + { + "op": "莱文斯坦距离", + "args": [ + "\\n", 1, 1, 1, + ], + }, + ], + }, + { + "name": "Levenshtein Distance: Wikipedia example 1 with substitution cost 2", + "input": "kitten\nsitting", + "expectedOutput": "5", + "recipeConfig": [ + { + "op": "莱文斯坦距离", + "args": [ + "\\n", 1, 1, 2, + ], + }, + ], + }, + { + "name": "Levenshtein Distance: varied costs 1", + "input": "kitten\nsitting", + "expectedOutput": "230", + "recipeConfig": [ + { + "op": "莱文斯坦距离", + "args": [ + "\\n", 10, 100, 1000, + ], + }, + ], + }, + { + "name": "Levenshtein Distance: varied costs 2", + "input": "kitten\nsitting", + "expectedOutput": "1020", + "recipeConfig": [ + { + "op": "莱文斯坦距离", + "args": [ + "\\n", 1000, 100, 10, + ], + }, + ], + }, + { + "name": "Levenshtein Distance: another delimiter", + "input": "kitten sitting", + "expectedOutput": "3", + "recipeConfig": [ + { + "op": "莱文斯坦距离", + "args": [ + " ", 1, 1, 1, + ], + }, + ], + }, + { + "name": "Levenshtein Distance: too few samples", + "input": "kitten", + "expectedOutput": "错误:计算莱文斯坦距离需要两个字符串,请确保输入按照给定分隔符的两个字符串。", + "recipeConfig": [ + { + "op": "莱文斯坦距离", + "args": [ + "\\n", 1, 1, 1, + ], + }, + ], + }, + { + "name": "Levenshtein Distance: too many samples", + "input": "kitten\nsitting\nkitchen", + "expectedOutput": "错误:计算莱文斯坦距离需要两个字符串,请确保输入按照给定分隔符的两个字符串。", + "recipeConfig": [ + { + "op": "莱文斯坦距离", + "args": [ + "\\n", 1, 1, 1, + ], + }, + ], + }, + { + "name": "Levenshtein Distance: negative insertion cost", + "input": "kitten\nsitting", + "expectedOutput": "消耗量不能为负数。", + "recipeConfig": [ + { + "op": "莱文斯坦距离", + "args": [ + "\\n", -1, 1, 1, + ], + }, + ], + }, + { + "name": "Levenshtein Distance: negative deletion cost", + "input": "kitten\nsitting", + "expectedOutput": "消耗量不能为负数。", + "recipeConfig": [ + { + "op": "莱文斯坦距离", + "args": [ + "\\n", 1, -1, 1, + ], + }, + ], + }, + { + "name": "Levenshtein Distance: negative substitution cost", + "input": "kitten\nsitting", + "expectedOutput": "消耗量不能为负数。", + "recipeConfig": [ + { + "op": "莱文斯坦距离", + "args": [ + "\\n", 1, 1, -1, + ], + }, + ], + }, + { + "name": "Levenshtein Distance: cost zero", + "input": "kitten\nsitting", + "expectedOutput": "0", + "recipeConfig": [ + { + "op": "莱文斯坦距离", + "args": [ + "\\n", 0, 0, 0, + ], + }, + ], + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/Lorenz.mjs b/plugins/srktoolbox/tests/operations/tests/Lorenz.mjs new file mode 100644 index 00000000..088707f6 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/Lorenz.mjs @@ -0,0 +1,108 @@ +/** + * Lorenz SZ40/42a/42b machine tests. + * + * @author VirtualColossus + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + // Simple test first - plain text to ITA2 + name: "Lorenz SZ40: no pattern, plain text", + input: "HELLO WORLD, THIS IS A TEST MESSAGE.", + expectedOutput: "HELLO9WORLD55N889THIS9IS9A9TEST9MESSAGE55M", + recipeConfig: [ + { + "op": "Lorenz", + "args": ["SZ40", "No Pattern", false, "Send", "Plaintext", "Plaintext", "5/8/9", 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ".x...xx.x.x..xxx.x.x.xxxx.x.x.x.x.x..x.xx.x", ".xx.x.xxx..x.x.x..x.xx.x.xxx.x....x.xx.x.x.x..x", ".x.x.x..xxx....x.x.xx.x.x.x..xxx.x.x..x.x.xx..x.x.x", ".xx...xxxxx.x.x.xx...x.xx.x.x..x.x.xx.x..x.x.x.x.x.x.", "xx...xx.x..x.xx.x...x.x.x.x.x.x.x.x.xx..xxxx.x.x...xx.x..x.", "x.x.x.x.x.x...x.x.x...x.x.x...x.x....", ".xxxx.xxxx.xxx.xxxx.xx....xxx.xxxx.xxxx.xxxx.xxxx.xxx.xxxx...", ".x...xxx.x.xxxx.x...x.x..xxx....xx.xxxx..", "x..xxx...x.xxxx..xx..x..xx.xx..", "..xx..x.xxx...xx...xx..xx.xx.", "xx..x..xxxx..xx.xxx....x..", "xx..xx....xxxx.x..x.x.."] + } + ] + }, + { + // KH Pattern + name: "Lorenz SZ40: KH pattern, plain text, all 1s", + input: "HELLO WORLD, THIS IS A TEST MESSAGE.", + expectedOutput: "VIC3TS/CUJA/3II9W9JWDI5DAFXT4SOIF3999IZD9T", + recipeConfig: [ + { + "op": "Lorenz", + "args": ["SZ40", "KH Pattern", false, "Send", "Plaintext", "Plaintext", "5/8/9", 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ".x...xx.x.x..xxx.x.x.xxxx.x.x.x.x.x..x.xx.x", ".xx.x.xxx..x.x.x..x.xx.x.xxx.x....x.xx.x.x.x..x", ".x.x.x..xxx....x.x.xx.x.x.x..xxx.x.x..x.x.xx..x.x.x", ".xx...xxxxx.x.x.xx...x.xx.x.x..x.x.xx.x..x.x.x.x.x.x.", "xx...xx.x..x.xx.x...x.x.x.x.x.x.x.x.xx..xxxx.x.x...xx.x..x.", "x.x.x.x.x.x...x.x.x...x.x.x...x.x....", ".xxxx.xxxx.xxx.xxxx.xx....xxx.xxxx.xxxx.xxxx.xxxx.xxx.xxxx...", ".x...xxx.x.xxxx.x...x.x..xxx....xx.xxxx..", "x..xxx...x.xxxx..xx..x..xx.xx..", "..xx..x.xxx...xx...xx..xx.xx.", "xx..x..xxxx..xx.xxx....x..", "xx..xx....xxxx.x..x.x.."] + } + ] + }, + { + // KH Pattern, Random Start + name: "Lorenz SZ40: KH pattern, plain text, random start", + input: "HELLO WORLD, THIS IS A TEST MESSAGE.", + expectedOutput: "KGZP5ONYCHNNOXS9SN45MIE3SC3DJBZVJUOE5SLVGI", + recipeConfig: [ + { + "op": "Lorenz", + "args": ["SZ40", "KH Pattern", false, "Send", "Plaintext", "Plaintext", "5/8/9", 20, 40, 3, 9, 27, 36, 4, 1, 9, 14, 21, 8, ".x...xx.x.x..xxx.x.x.xxxx.x.x.x.x.x..x.xx.x", ".xx.x.xxx..x.x.x..x.xx.x.xxx.x....x.xx.x.x.x..x", ".x.x.x..xxx....x.x.xx.x.x.x..xxx.x.x..x.x.xx..x.x.x", ".xx...xxxxx.x.x.xx...x.xx.x.x..x.x.xx.x..x.x.x.x.x.x.", "xx...xx.x..x.xx.x...x.x.x.x.x.x.x.x.xx..xxxx.x.x...xx.x..x.", "x.x.x.x.x.x...x.x.x...x.x.x...x.x....", ".xxxx.xxxx.xxx.xxxx.xx....xxx.xxxx.xxxx.xxxx.xxxx.xxx.xxxx...", ".x...xxx.x.xxxx.x...x.x..xxx....xx.xxxx..", "x..xxx...x.xxxx..xx..x..xx.xx..", "..xx..x.xxx...xx...xx..xx.xx.", "xx..x..xxxx..xx.xxx....x..", "xx..xx....xxxx.x..x.x.."] + } + ] + }, + { + // ZMUG Pattern, Random Start + name: "Lorenz SZ40: ZMUG pattern, plain text, random start", + input: "HELLO WORLD, THIS IS A TEST MESSAGE.", + expectedOutput: "IQVPAANDCA3CHDNO3V/CZQ/BTPZIKW8YAAQXQGLDMV", + recipeConfig: [ + { + "op": "Lorenz", + "args": ["SZ40", "ZMUG Pattern", false, "Send", "Plaintext", "Plaintext", "5/8/9", 20, 40, 3, 9, 27, 36, 4, 1, 9, 14, 21, 8, ".x...xx.x.x..xxx.x.x.xxxx.x.x.x.x.x..x.xx.x", ".xx.x.xxx..x.x.x..x.xx.x.xxx.x....x.xx.x.x.x..x", ".x.x.x..xxx....x.x.xx.x.x.x..xxx.x.x..x.x.xx..x.x.x", ".xx...xxxxx.x.x.xx...x.xx.x.x..x.x.xx.x..x.x.x.x.x.x.", "xx...xx.x..x.xx.x...x.x.x.x.x.x.x.x.xx..xxxx.x.x...xx.x..x.", "x.x.x.x.x.x...x.x.x...x.x.x...x.x....", ".xxxx.xxxx.xxx.xxxx.xx....xxx.xxxx.xxxx.xxxx.xxxx.xxx.xxxx...", ".x...xxx.x.xxxx.x...x.x..xxx....xx.xxxx..", "x..xxx...x.xxxx..xx..x..xx.xx..", "..xx..x.xxx...xx...xx..xx.xx.", "xx..x..xxxx..xx.xxx....x..", "xx..xx....xxxx.x..x.x.."] + } + ] + }, + { + // Bream Pattern, Random Start + name: "Lorenz SZ40: Bream pattern, plain text, random start", + input: "HELLO WORLD, THIS IS A TEST MESSAGE.", + expectedOutput: "/89OALRPJEZQGOO84WOEQZ/I9NBRZOQPBTANC8E/GK", + recipeConfig: [ + { + "op": "Lorenz", + "args": ["SZ40", "BREAM Pattern", false, "Send", "Plaintext", "Plaintext", "5/8/9", 20, 40, 3, 9, 27, 36, 4, 1, 9, 14, 21, 8, ".x...xx.x.x..xxx.x.x.xxxx.x.x.x.x.x..x.xx.x", ".xx.x.xxx..x.x.x..x.xx.x.xxx.x....x.xx.x.x.x..x", ".x.x.x..xxx....x.x.xx.x.x.x..xxx.x.x..x.x.xx..x.x.x", ".xx...xxxxx.x.x.xx...x.xx.x.x..x.x.xx.x..x.x.x.x.x.x.", "xx...xx.x..x.xx.x...x.x.x.x.x.x.x.x.xx..xxxx.x.x...xx.x..x.", "x.x.x.x.x.x...x.x.x...x.x.x...x.x....", ".xxxx.xxxx.xxx.xxxx.xx....xxx.xxxx.xxxx.xxxx.xxxx.xxx.xxxx...", ".x...xxx.x.xxxx.x...x.x..xxx....xx.xxxx..", "x..xxx...x.xxxx..xx..x..xx.xx..", "..xx..x.xxx...xx...xx..xx.xx.", "xx..x..xxxx..xx.xxx....x..", "xx..xx....xxxx.x..x.x.."] + } + ] + }, + { + // KH Pattern, all 1s + name: "Lorenz SZ42a: KH pattern, plain text, all 1s", + input: "HELLO WORLD, THIS IS A TEST MESSAGE.", + expectedOutput: "VIC3TS/ZOHUYXWLTUXPV9ZNOTW9IXJPFDLIBB5ZD9K", + recipeConfig: [ + { + "op": "Lorenz", + "args": ["SZ42a", "KH Pattern", false, "Send", "Plaintext", "Plaintext", "5/8/9", 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ".x...xx.x.x..xxx.x.x.xxxx.x.x.x.x.x..x.xx.x", ".xx.x.xxx..x.x.x..x.xx.x.xxx.x....x.xx.x.x.x..x", ".x.x.x..xxx....x.x.xx.x.x.x..xxx.x.x..x.x.xx..x.x.x", ".xx...xxxxx.x.x.xx...x.xx.x.x..x.x.xx.x..x.x.x.x.x.x.", "xx...xx.x..x.xx.x...x.x.x.x.x.x.x.x.xx..xxxx.x.x...xx.x..x.", "x.x.x.x.x.x...x.x.x...x.x.x...x.x....", ".xxxx.xxxx.xxx.xxxx.xx....xxx.xxxx.xxxx.xxxx.xxxx.xxx.xxxx...", ".x...xxx.x.xxxx.x...x.x..xxx....xx.xxxx..", "x..xxx...x.xxxx..xx..x..xx.xx..", "..xx..x.xxx...xx...xx..xx.xx.", "xx..x..xxxx..xx.xxx....x..", "xx..xx....xxxx.x..x.x.."] + } + ] + }, + { + // KH Pattern, Full Test on real message + name: "Lorenz SZ42a: Receive, KH pattern, ITA2 output", + input: "J3KF+LXT/.+YTMLE/RFVC-SE///4GYX3Q.Z3GVWKWDVAPURPYL/.UYAI.EOW3ZBVVAQRTO/PACJ.NLVLZYYNTU.IDCPKTEZOSWCOBNWFJ+UAKE+WU-JMWYWLXRM+M/HV+TVTC-FOGN3QZG4J.VLM/KK+OVC/YIWTSZUDTSY+3LCCHZADQ-3VBXKEOCSO/+ZBFN34-F.+4UVFVLIP4KFGRBFIVFFJX/FKFSHJ.VUJVWXE+LFAICDYX3EZD33U+GSOGXPAXHTJSNUQI+PXS3JRG+-U+YZITF/SM4LIDPSYMVJM/BL/YHDGBG/UI+EM.JEMX.YQNUWTLAUCLUSDMZGXCQ3CPPCCYLTJC4KXB--G4VGQ4J.EYTEVSG33DVLVPDGNOGAJOUWGFY4KGO-4+IRKDPGDHGBQLFSS/YDP/FM-/BANLERZEMT.U3XZA43RGYD+-J4VYRTONRYF/OI4Y+I3LXUFAHGRT.RXCO3HKCQIML.VHVIGHBWBTU3RZFN.J.WGNLSGLYBJT+TPM-RHHMXTNDSVUO3W/4ORZ4UY.LA-XZYVLCZROMPM3RYSLUD-SNQQA+RK/UV4K/GOSSMJRGIZYPO+BEEB+OEWAWKYXW.-NKUQERUM-PA4WFNKU-Q-LNC.D-A3C.FB.RFOGZHUWTEAYB3HNHCEW.N33QEEVUEC3OOR4BRNLH/IF3+DJJ3S3J4XA+Z3SMT/K4L-IDLQWCLMQ3TO3PNYVCGZCXUMSSBH.BWMGGTIYSC/UBX/PE+3IZZ.Z/CCWLSL+4/4+IET/ELBCBDAM4ELKDAGB3O.3J+IEQQJ.U.VSAQSAYZAU+3YOZWPPPV/YOVOE/P/E4UOZYK4PE3A.ETZGFBCZE.4WA3PSDR3XMLJLH+S3UWOA.T-RID-MA.CVZGLNYK4U.HBPSA+3U+BLSGS/FNRXMOMEBBABIPOAGDWA-4T/B.L.HDSAJE.HE-4FZW+SDBAYCNBTEODDALCMIG-QNM+-PEFC+ABNQ-MVT3-GHKAN/ENZLPMJRVRB+4NBCRTFDVNLRTPIGIEZGSPUXMW3WJQTOEV+WA-HJZLX-JQID-X++FFGKCTO.3.F+JIISVTXKC+..YM/SOQUIAS-IAGJJPRYLLT.EJBJAMRP+MS-ZRUVLBBE-UNYQGBBHEB+QCHYYHVN.NHKS-YG3BNZKQJBO-FQ.SZB/JRFILGUZUZVCOVGULEKU4/HRBGYIZVLCM3/ONJ-OBIRSCT+IZCB-TTZMDQWQUCIVTGTTYNEOTTORM-FSKS3WJWL/ZXOCOCYGC.BRIRKXK.FLJUSP/-G.WP.MMVHBYREWQZZAN/BKSEYDBGXAUV+NUKUKIKIGS+VO-4EY/GWI+SGJOJCBYGGMY4+/EMGULCSC-Y+CXLIECYC-+-ZXHSPOTFGFDWIFT-4XXDDLMMKT433WH/BX-OILWDJC/FFE-ZYH3C4GI4T/3KUJQ4YNBQWXWB-RM.Q3GG/4Z-AIGW4GYYEBXRJHXQA..-.G/3W/-LVS+4GS-+FRYIOFYGUK-FEYA4J-ZB-MSPAM/WLLJ3GFMJP/GGF-C+O-KQ.K4PWVL+3O.LX4TUD+Y+QOO3GTJT+.MR-4JSRXD-X4SCIDIVLCDEGSOZOGWXQZOZ.3PPQ4ZYXKL+QETCM/3/--CHG4+W.BNHTB+Z-NZCO+QEB+-/FNJ+NSHTO+CW.CM/VHIM-S.3VAFDJ3MEH.G+NQFDCUSK+MCKDLEC-TFWSYBQSWE4UOQOXY-E.ESE4OLJQOBUQZUSLRWV-AVOYX3CKS3ZFUAQAWESYMXQV/4MOXORAVKOIELRXCSRAEU/KEFDQWQ-BWEXGALS/.JLQ/CEKT-4C+TWDNGST-UQ-ERBP.YZ-ZH/Q3ITMN-O3P/JBEVZUOY4CTNY4PKCB3YIW/+BOKDEZE.VCTROQDTAXI3VKGYQVOKSCXPDDAD4DLTELK.GDDLTRPXORSFTDOGB.-NQJHNM/4/JOTIVGOQF+FC.GDX4DMT.UBRVUIBCHGLDBZSFSICVVAF4TN.BMAP-IQR-LBCQ/TTHH", + expectedOutput: "XWOLLE9WI9R99AUCH9BLEIBEN955Z88KR99GWFRL5X89FUES959WPYP9QXT9QIPQ9V9AACQEM9959AA899QEE9959AA8999AN99OB5M89SUEDW/ST5V9AA8GEHEIME9KOMMANDOSACHE5AA89959AA89NUR9ZUR9PERSOENLICHENSINTERRICHTUNG5N889WEITERGABE9VERBOTEN5MA9AA8LAGEBERICHT9VOM99GSEPMM98NN99VOM9959EPMRM9QORT9AA889KATEN9NN9KAT9NN9KARTEN9959Q9C9QSVPP9PPP9A9MA98889ROEM959QM89WESTEN9959C899AOK999L9U9C88889ROEMS59QWM89A5M89K5MCMA989PANZERUNTERSTUEZ9NN9PANZSRUNTERSTUETZTESFEINDANGRIFFE99GEGEN9EIGENE9STUETZPU9NKTFRONT9NAHMEN9O4D9ELSTER5N89SOHL5N89RAU9NN9RAU9N9UND9BAD9BRAMBACH95K9WR8889NN9995KWT88SKM9BFO9HOF5LM89NEUE9STUETZPUNKTFRONT9B99DIESER9ORT5M89UEBRIGE9CORPSFRONT9BEIDERSEITIGE9AUFKLAERUNGSTAETIGKEI5M9DIESER9ORT5M89I/BRIGE9KORPSFRONT9BEIDERSEITIGE9AUFKLAERUNGSTAETIGKEI5MA99A8STELWV5M89ROEM959QEM89A5M8K5MMA989ZUNEHMENDER9FEINDDRUCK9IM9RAUXSN9UND99W99BAERNAU95K9T889NN9959KWT889KM9SW9MARIENBAD5LM889FEINDAFGRIF9F99VON99SO99GEGEN9PAULUSBRUNN5M899LAGE9DORT9UNGEKL4ERT5M89SCHWACHE99EIGENEN9SICHERUNGEN9DURCH9FEIND9AUS99SCHAENWAWDE9UND9WOSANT995K9Y89KM9SO99TACHAU5L89NACH9O99ZURUECKGEWOFNN9TURUEIKGEWORFEN5M89FEIND99MIT9INF9UND9PZ5M89IN9PEISSIGKAU9UND9WLAD9NS9DAHENTEN5M89EIGENE9SICHERANGEN9IN9MOLGAU959A89DRI9UND9WLAD9N993AHENTEN5M89EIGENE9SIC4ERUNGENSIGKAU9UND9WLAD9N99DAHENTEN5M89EUGENE9SICHERUNGEN9IN9MOLGAU959A89DRISSGLOBEN959A89WURKAU5M89IM9A9SNN9IM9RAUM99TAUS99PANZERUNTERSTUETZTE99FEINDANGRIFFE5MA99AA899A5X8O5M8K5M99QC9MA9989UEBER9ISAR9MIT9INF9UND9PZ5M89UEBERBESETZTEJ9FEIND9STIESZ99UEBER9S9NN9UEBER99VILSA9BSCHNITT9VOR9UND9NAHXS9AUNKIRCHEN959A89ALDERSBACH959A89EGGERSDORF995KWP889KM9W9P49SAU5LMA9A88ROEM9959IWM89A5MLK5MCMA989DWP889KM9W9PASSAU5LMASA88ROEM9959IWM89A5MLK5MCMA989DER9ZWISCHEN9PLATTLING9UND9LANDAI9FU9NN3/UF99BREITER9FRONT99UEBER9DIE9ISAR99UEBERB99NNN9UEBER5ESETZTE99FEIND9DRUECKTE9EIGENEN9LINIE5N89TROTZ9HIFTIGEN9WIDERST4F3ES5N89AUN9VISLABSCHNITT9ZUREUCK585M89FEINDPANZER9N9NN9IN9ARTOFJFN9IN9ARNTORF959KQT889KM9SO9LANDAU5B89UND99N9ROTTESDORF599IN9ARNZORZ959KQT889KM9SO9LANDAU5L89UND99N9ROTTESDORF59KI89KM9S9LANDAUGWMA59A89ROEM959QEM8939NN9959A899ROEM9959QK4OLE", + recipeConfig: [ + { + "op": "Lorenz", + "args": ["SZ42a", "KH Pattern", false, "Receive", "Plaintext", "ITA2", "5/8/9", 12, 12, 41, 45, 17, 12, 3, 11, 31, 29, 12, 23, ".x...xx.x.x..xxx.x.x.xxxx.x.x.x.x.x..x.xx.x", ".xx.x.xxx..x.x.x..x.xx.x.xxx.x....x.xx.x.x.x..x", ".x.x.x..xxx....x.x.xx.x.x.x..xxx.x.x..x.x.xx..x.x.x", ".xx...xxxxx.x.x.xx...x.xx.x.x..x.x.xx.x..x.x.x.x.x.x.", "xx...xx.x..x.xx.x...x.x.x.x.x.x.x.x.xx..xxxx.x.x...xx.x..x.", "x.x.x.x.x.x...x.x.x...x.x.x...x.x....", ".xxxx.xxxx.xxx.xxxx.xx....xxx.xxxx.xxxx.xxxx.xxxx.xxx.xxxx...", ".x...xxx.x.xxxx.x...x.x..xxx....xx.xxxx..", "x..xxx...x.xxxx..xx..x..xx.xx..", "..xx..x.xxx...xx...xx..xx.xx.", "xx..x..xxxx..xx.xxx....x..", "xx..xx....xxxx.x..x.x.."] + } + ] + }, + { + // ZMUG Pattern, Full Test, Receive + name: "Lorenz SZ42b: Receive, ZMUG pattern, KT-Schalte, Plaintext output", + input: "YQPLQQX4OJGFOKBXROZVPBVIJOXSMQSMTMHX8VSBBMIHKJTYKR4ANTQRPCJXR3YE8KVLDN8SQN4VCJQUW3BB4HWEXRD4LUJNF99DV5FLQ4IRANWRCLYX//J8YSIWO44LCPTCBKX4TZGBPYHCE/FBJ4DSBFF5YLOVPBSRUDQULTTO9BYLLUCRGTM/VXQCS8XUDLY/5O3TPSYFQ89RQRGG/IEOKULB8MHQQBHIWRW38W5XSPT9YF8WJGN3HMEBY8XKQW/3BC888Q3JC/K8SOXJWONNQCM8Z4UQQQGE8RL3GXZ3YI4O/RBCF5JXJE5W5GJAT9/9XP9V8SFZLKUBUPD4HXABGLB8D3E4YTERP3GGISVNYHOL3BQVJUOFEBZMYHBKFGPWEOGGMFGF/EVGWT584T8BMURIBSIBELCANKT/VXHUHONHFPJ/OBCQGCQPFQSQW3GEFCQKY9Z4/KVIQJPJQ4ZQJZP/PEIHQG4H99B58HD5/XCLRBPXMR8P84CMHO5RYODKY94NINO4OGY8TOF9BD9EPD5U3P9KMJ/WBXX9CO9SHMK5KHQFA9BE35R4OSAPCAXZ9SALUKVPVVYB8MGGK3XSVLKGUT/8JVIF5DSDUQT3ZKZTICJTSGXVZAZ4WWGAMOFWUN94OZR4Y8AVLGZHOV/UHCURM5RQPKDCEMRBWLKYLTTGGE8ZIVP4HJMVW5HWKOHRZ4OIUL5OZJOVHL9AQUZA93F3H9QNS5VZYZXUM8AD3RZC9CQ5B//4J8/B35XVB9MHMYQSVQPFWSXWEDU5NHXBSO/W/NBW3V/KO5VHIZQRZOKIJSV4G9Y/H3FIYLBH5/XA3AYRPUREKAQL5WTXJ4EK3EAYQ8GIDXTBXMUFUGKZHZZZPYAQMQOXZAGKP4SPIEIKVUPVPRVRSXSDRJSS3XM5KOTUVK3O435VB93H4HB8IEXHDGCJ3VHPG48VZKB4GHRNFHDQ8LN4P9TI3VD9//LACSHQJZWK5/WLC5/PHN5JTVWAOXWX59/SIZ4ADMXO5GCMYSPNLR33CFLKGSN/ZYCFAR5I4/ZQRWCVGY9GCAA5GJCW55HXVGAKGZG/V9Q9AGB9WAVKJX9DM/FOCHWZXAVEPMKTSQJYPWCYFTH3WXZRVAFCLHFMFQUGUWDHQQSUF/QSPSY/8PWPU9B8WUXFE5IPYCSYX3FKEGXIY8DCNLOWZTU8N98ZNWL8QDP/JHTU5YNHN8OI8TMBBIUQEOVT4S/N9V//LZCQ5LN3X43LUG/KDVSOXUTKOP5VTI/OUV98ULIJW9EBVUMBYBLOIKJX/XUHHYPB/N3PEVBFJGXGO3H8VTIO9D8IV5FAL5DZDOC88WNITD/CEZOB3IS8W//ZIBFQOMG3HNUXV3YMM5QH8YYPV/EJMUCKSNRTGFHBZJFTX59CMRM/D5GVFWHF8QWPY44IKNLH8A9SMHYUNXGDJBX9PA4CJPRO4RZSPTG4SYBLHMSV/9UJPUJEPHPZWSDKMMMN/WEHOZDXBPWS3DHG9SBWFTB85HOPZYTVZ9KSBGZ8PAIGGIVXU/8YIFQGQDDDVDDV3TL8L5G9C/98CYDUGWWCGQCQLS4Y8BGSSMYLOODGA/YMRWI9LJI8TXT3ZYZ8NR/3RNDYHR9DINKB//HWPJETJ8/RFXSV/SKWEBKOMSJ9W393JH8E/G3UX/5CD35H4BZOU8GYELU4EUYNYP/UUBYQZKLIPFPOPZ8WNJ4WR5QWIZVMQ9BCVZM3MB8BES5APTTVTBSILLUQHY9X/A9FFQWR5DUMNHPUKVZDYB38ZSI3CNCZPG9C/UKYRX5YWXEIBIMCBNRPGGCZKHLNG4T/R5K4LSBZUVNGBCHPI/JUIZ/IMFXEE4KYSEMYBPWBZ/UCANCY4MPXFQB3NRZQOSLBOMD9TP3IY8RQWIEG9HG3CH8AI/GGPGLXNNV4Q/ZOMVCVS4UCQFFEMMWK/RSV8RXQQPSOT8GYAUXOUJQ9VQCHAS4FL3WAMQXARHYNEPYS5TR3/MHCUSPE33NWZ48X3J9HWEJGIKOTLKAJXXAQ5X3YVLGNSSM3XT/9E/PBTN5PHCJLWKICIHER/QQMVGAGCVTKBUL3B3GQUKLKZ38YMOOJ5DTAFZSFZLVO4UZXGNNR4SJ3JFMLKDP3MXSZXX3YVLGXF3O8H5JHBZFTOZ4JDTLQGHLD5XOBLVQI/MQXS4EJAIU/VLYIGGJWNOTTF9AEQSEEGT84GI355YLVTTDGVICR8ACXJFHPTHRG8UXCLIHMW4Z3CUV89YLL3W3HDC5HUUDJ/PUS54PIJ98IXL3SEEOWWPGFN9O9ICPG4K8558ONZOHSW/U4ZRSC4C3UOHHIEARXSCZDZM4IBDBLRI99DEKKIAYPJSOSC59XGUVKSPHV9FRE35A5SII3G3DRQDQKTSC9DNUPBXVD3ORPX9NM3BK3PB5PIWUZKNCL4I4IWVO4FE4GCZBHSZWN3GS88G884DUFDUFE9RJEEKM3AJHNSEANJWQMRP5DUXHEKDWAAQGZYUL3EXY4ALN4NR3SF5CFOTZ9FGNJDFQEZU4TTVORUVS5WIZBQLZQF5HD4TTYIRPAZDNPCIYS93ZY5QQEBTYIYCQ4AJGK83BFOIK9S5GAJCBYLKSDADHOS4/XVSHDYBFSO3FZIHHHWSJ5L4R33KCXOW8T9KFQ4C8UCOCOCKXU8JJ5R9JL88UAJQ4HXWDBQ45JGY/AYGP8PMGWWLFM/9SP5OHR3FQKDIYIAOLKWM8OT8JO4W9ET5LIZMMNJBNN/A999JNG3ALKLKEK/HWZJUC5PLAILQJ8MCOO8T3X98H4ZKFIKYUKDI4ODART/K4JZDHMBY4X/BDKOYRJ/CQKZCFFBY3494/DUFHLVYZL5VG35KFSVC9IXI4S3RB3FXBDCYYKVUUTBTEFNJVRB3H5BU9NGUGJ3FBD9OOXWRTUZ8GHAHKPNL5TTUHNGMARBKFQMKFN4ODB4Y8OKCJPFQNHIGK8WFYOHI/ZW/PMHF4JAFIAYNNPOYJY/AFTDSOPNGP8GK94X3QY4A/THAWWGH3LSRST4/AV8GY4QV53A5MRYF3K3MWK5YJAWLLYWV9U/CZN4WAV3OELTO45G5IEY4P845ZJJAYGTBHXBFE5XJ5C5FFUFXCOUSWTU/LSSQ/VWNP9BSOLUB5PLNWAZF4O8LKGTA9ZAAVRIW8CP3UFS5I5N/MNMVCRD/3AM/XUPGMJAM45AY4VDA5XY4F8ZV4NXZMPZROZ/QFPLOQ/3WSZ8/FXEJEFC9W3UJGOO4RL9FEVQL/DYDAK8JLMVPY9IPYXBX3WIYOLYB5VDUAJBKCINPZZ5843H/8ZIZCVQ4RTVCOTSATJC4ZVBXYROCDXRIPGH5/WS83Y4TAA45YBPNGFFSY3I/O3JIKWMAS9TYTY38JMIEREZWOYWT3ZYRA84IRDX93H3JJQSFL43JOLPPX3UDF59RL3ESVR8J83QKTJLSJAEQOBZ8FCLWIRBXOUXQOKQ/AE885X/VQIUFETMEUC48CJMKQIJQ9ZVPHOJMCIR93GTVYWMIYMPOWFWOL99NRTWQAQ3WKLF//F3VDQDS8HLLDRKESD89OU9PSPIXW5/TX35KZUONYTLQKMGRWTSV3/5/BJZV9MHPL5UZR4ZMJZEXUE8F8E3RVDEDGVQWENRFYBOEKQMBILPWUFXZQB9XGYOSZKS8IRL9VMH5DWE3WHY3FRR83I55XUXHBJ5BMAPOHIGHQIZ/RSSAK83AUEW4Q5DZ9WTDKRMLT9OYH/VZGXUMEOV/4PL3P/SSIHX3UQIVDVQKZWHYQGZO9NLMPRSJV8/G5FTKLJ8FSKRAOZ/FZCRJLBRIBTJPGUDLFGYBXBVMJQQA3MSHFDZE/HRUOUGYZJNNWARNXNYKAWYQQWMES8K4UCFEGHDPYD/NK/U/B84NI54QLJWW/8NTWPOS/ENS355GHATHAFKK4ONFQR/WHNY3IQTTCBWF35XYXLSOXV5AIZBIGDNFDHL5OO4H/3SJBJBPTQ/3WAAO9ZGW9GYP8/JKVZKK/O9VEMBDKIJYMMVCGODXZY3QNUB8EOWKXS3HT9OFV5JJCDC9FE3FUZJOHL4HH4PA9YFA5TI5ZHFPG/RXQ/5ZMGNSTHWZC4ZC9JA8JNHKT/HNE8CLU9QF385/HDJAEEGSKGPFRKDHEPAXDH9M8Q8KQKEGYWDI9ERQGWMDPZYS/WS9PRP3/3MUKLQIKR5V4GYHNQWTB/SZM9VPQSJZLLKIF5WC5EHTILCZ95RMOJZ89LPJMPRNZ4ERL8HXZIL5B9KEBFN4YJ4LNEQOSS5ILTYECXG/MY9MRQWHFM8QFLI8CAG3BUUG53XPH4CE8GUQKZBKLNAGWXORPFZR3V9XTLBBATUTFUPC/ULGGWLFMCSP8KUYIFXTZ8V4HUQLS9MKYMJMLF/94U3PNELHVWRMISI9QYL53O5XDSFP84VOOAYCKJE4HPG3GA8MGGZVMSBMT5KWQ9V88USEMD3T8IYMIYFENMRZURQLNCICODEG/ZW4HNTOKZMC//ZGQL/XBC8STG3RXKMCO9NSVQQ8QDFDZWNIAENP/J898ADWNHCDCIO5XT8PR4BCDDAQEJV4LGLRALWU5YUL5GBDH//4TQJHBP8JWWMVBV89E/VMRUCLXGKABKSXK5KXWF9NIZB/ADKX38LTZIUBBMWIMMNZXO/EKXEGZBQZD8CYXXRVVF3/NIDWVFYU99H5KJ9DE55N59LRTPLL9PAKPPLSRC38SEUGYSUG5GRTV/5WIDY9YBNDIS4QEBELA/4QVPMVWZG3R9HSE/ZXYK8GGHQ5WVDSYI4RZTBYMUL8QLMPB4P43Y9R3PAWWZRFNNIEXPL83DVJWL3YDG/OJ9UN5CS/L3LDTGMVHMFKZE/BD4MVKJTWFXWPR4KJXYUGIZBZZMTH/JM3MIWOV/ETI5NNOUSLUYL89G3X/TPHC3A/CTNT5HM4CX5PQDMKRSLAOFGPNVRFSE/HDZFE8LFIYM5GDMAYBVRCJCHJBDJ9YS9SLJ/LA54C5DPH8LX8Y3VLP4GROV/QIPZQDB5CS4EAKFULGLIB9XSOXKDE4OJVQXYEI8RBV9PLOT8QE5WLSXG8JW8C/5FGKUR9NHIKNLYFJNSIE8WPARBOUAMNDOKRZXNIWKF8EDD/4VIFV/4UIEVIG48/M4OLBDZKOXRO4//8XAOXY8FPVJZ/O4OWL4CIVNGMWACYCYUJJLPDS/D/KYLBBFCVRTQ/DJZ3WVQEZDVYIBOKDHCYGDP4FENMNOCYJVG/RJJVKIU4EZNQRAN9WWSGYSHZWQQWXKD5RLQDSMDW9R34CYUPW/5899KP3BJM/ZGIWMAPGYF/PYLHDXDXWUPMTNAFPZMQOQVBXRI3VL84TYQRVRVPIJTTUJ8EKOQWMRTR3QGYOBSQWWKO/V4I8S/E8XW9A3HYYVSIVFS8LTWURNZ8M5EGBCLFGC3LJLVZE8MEQNTXY3XL8YTQPA4KZCMO8OJ5RDCIELIKSANPLQAVEPE5IWXMA534XDAV9CZTCBEHVVCFAGIXS4F4KVB4K/AM/DCHYW4BTY4A954GJPQ3NAK8STGW4E/FE4L/WZZJGJPLH/MSDCMK/VQ5HLLONBWSKW3LPSPIB3BSZS8ZZVRAQPO5G5MAWGVFKJSYDBXB5KGUWQZ4FBPUQS9LBKWTQCS3RC8MVTQTTS9STDAXAQJHHZSGN/RXLODYJY8EUTA8H3IFIW5FI5UFTOPTOET8F5/MUQTONBCD3OTMS3QSV/UKAIINVWPQFNHYREGBG/XWEQN/HGMG9TRHLIIQIM5WZQDX5QEBCFRJVOO8/KS9NUJRLL8LJ/5/LH4RIJUIY4/MZT4VKPL5ITODTSAULBRRG8H945J84ABZA3JT9MFA/Z3OLMHMGLO8M8DVMAEHQJMEY3JGORHACL4BGQWCE99NIGNAOSMEI9FHIY5BVB3DR4FICF8VSI3Z/HWDAXUW8RIMJQUABTRXWOEDTIFBRFBE33RM/Y4INUYXHI9393YDIEIS9DUN/AMLN99F4LML3XOCSSCSWLK9UTIUVH4TWITVCY5XDV/59GCZHTE588MXX5EWURPG35BV8VIGNZKR5BYYRULFSVMACN3VH5P5U4SQ/KWFJPAMVFVRVPBOPB8IFKYKG5D8/E4LYAVOSNXRGIFOTAVPR/CG3YK8BBAOWO3M5V/OMIWMWVI98H5PVPDF8TZNNU4EVSYYY9YFLZPCGIOQM/IZ4HWGJ5RMT98Z/HMYXWS9JL5KNFHWO3VJXC/5IOKRAURSUTLQZGTOVGFTGZQ5HXSYF495/B9VSQBSKVSSSK49TKERKV9/YV9P/OF8MOHMT3DW4DNVN4H/M3RUDL/NCEY8PUNIJBSLL/ENHJ/QJAANL/IHZYGCR3548RIDAEJOVDE9OYJZWL5CB4VXH4ILMMBKSOZNWBCZEB3KB5VPUC/5MY3RVHB5IEJCNUNKDRLBAJRHPVCNDNMBYGJQRRRHNTZPNB3IFXN3DE8OB8WBN8U8BVY/CY5TALOLPKCQEN5NBRRXZEKLQQGYOCAM8DBXYSK8H9V5H8GBK5KR/V43YXMRAK/XCKCE99KGWIMZZCYOMJCMNFIE/ONFBORYY4DPM8CWLWR/V4F8ZMQEV/RBPXAKTEEYPD8QQ5HLCNUVMW/CC//YHPVRV5I4NQF5EMRHW4CWCJHDTPYUU3895ITSPBX8FIYHO3H/UX8Z8O/JRIGGYWLDYKEOLJJXWA8C/B4/DJIBRO5/JZD4XBGLVGSYSR8TN/U/WEM/3MMUVHGDFSSXXIYXE8Z9P59MDK5PQUQATLPNK9DKTXVYX4VZIBSKKSSQ4KN/FSYKHA8VE4YYHJMGTQWMBQPRDYK/UNNNLNCKH/UJ5CDTQ8ZWTGUV4FMKPJO/FI9S8N5PPZFVVZXPBYQUAOFBS4C5H4Z/DFTEF5K4P/VEZC3X9WJHPIZMSRZSCNUKGFSUQS3CSHSQL8AN3FZGERFOSY9SA4KURA4NWWNCJRINHI5A/H3TSJ9A/ZNLM5QFQICNRSKIKRQNA3A8WNPNLEBDB3QW9/4B5YYIIWDKG5C3/IOZXIDIFUE/NX8YTCMPGQ9YMN3AXXE85C5Y33ICVD3IXFP3AJ9UJYD/G54", + expectedOutput: "IF I SAID BLETCHLEY PARK, ENIGMA AND ALAN TURING, I WOULD THINK MANY OF YOU WOULD KNOW A LITTLE ABOUT THEM, THANKS TO THE WORK OF BOTH BLETCHLEY PARK AND RECENT FILMS SUCH AS THE IMITATION GAME. NOW, WHAT ABOUT IF I SAID COLOSSUS, TOMMY FLOWERS AND BILL TUTTE? I SUSPECT THAT VERY FEW PEOPLE, EVEN IN THE LOCAL AREA AROUND BLETCHLEY PARK AND MILTON KEYNES, WOULD BE FAMILIAR WITH THESE NAMES. COLOSSUS IS, ARGUABLY, THE WORLDS FIRST ELECTRONIC COMPUTER EVER BUILT AND ITS STORY IS EVEN MORE AMAZING THAN ENIGMA, BUT IT HAS RECEIVED SIGNIFICANTLY LESS PUBLIC RECOGNITION. MANY PEOPLE WHO HAVE HEARD OF COLOSSUS BELIEVE THAT IT IS SOMETHING TO DO WITH ENIGMA, BUT THIS IS NOT THE CASE IN FACT, IT WAS BUILT TO CRACK A HARDER AND MORE SECRETIVE DEVICE BUILT BY THE LORENZ COMPANY IN GERMANY. ADOLF HITLER, REALISING THE REQUIREMENT FOR FAST AND SECURE COMMUNICATION BETWEEN HIS HIGH COMMAND AND GENERALS IN THE FIELD, ORDERED A FORMIDABLE CIPHER ATTACHMENT WHICH COULD SEND AND RECEIVE ENCODED MESSAGES AT A MUCH HIGHER RATE THAN ENIGMA. THEY BUILT A MACHINE WHICH, RATHER THAN HAVING THREE OR FOUR WHEELS LIKE ENIGMA, HAD TWELVE WHEELS, EACH OF WHICH COULD HAVE ITS SETTINGS ALTERED TO MAKE DECIPHERING THE COMMUNICATIONS EXTREMELY DIFFICULT, IF NOT (THEY HOPED) IMPOSSIBLE. IN 1940, BLETCHLEY PARK STARTED PICKING UP NEW TRANSMISSIONS, BUT NOBODY KNEW WHAT WAS ENCIPHERING THE CODES. THESE TRANSMISSIONS WERE RECORDED AND MANUALLY TRANSCRIBED ONTO PUNCHED TAPE AND SENT ON TO BLETCHLEY PARK. FINALLY, ON 30TH AUGUST 1941, A BREAKTHROUGH WAS MADE A GERMAN OPERATOR MANUALLY TYPED OUT AND TRANSMITTED A 4000 CHARACTER ENCODED MESSAGE. UNFORTUNATELY FOR HIM, THE RECEIVING END REPLIED SORRY, SEND IT AGAIN. IT WAS STRICTLY FORBIDDEN TO SEND TWO MESSAGES WITH THE SAME SETTINGS, BUT BEING ANNOYED AT HAVING TO TYPE THIS OUT AGAIN, HE DID JUST THAT AND STARTED TYPING AGAIN. TO SAVE TIME THOUGH, HE SHORTENED SOME OF THE WORDS (JUST LIKE WE WOULD TYPE NO RATHER THAN NUMBER) BUT DOING THIS MEANT THAT BLETCHLEY PARK HAD TWO MESSAGES, BOTH SENT WITH THE SAME KEY GENERATED BY THE MACHINE BUT WITH DIFFERENT ORIGINAL TEXTS. BRIGADIER JOHN TILTMAN, A SENIOR CODE BREAKER, WORKED HARD FOR TEN DAYS AND MANAGED TO SUCCESSFULLY SPLIT THESE MESSAGES BACK INTO GERMAN. MORE IMPORTANTLY, HE ALSO WORKED OUT THE ORIGINAL KEY STRING WHICH WAS GENERATED BY THIS NEW UNKNOWN MACHINE. VERY LITTLE PROGRESS WAS MADE TO CRACK THIS CODE, UNTIL THE KEY AND MESSAGES ENDED UP ON THE DESK OF A NEW MATHEMATICIAN BY THE NAME OF BILL TUTTE, WHO HAD JUST RECENTLY JOINED BLETCHLEY PARK. AFTER WEEKS OF PAINSTAKING MANUAL WORK WITH PAPER AND PENCIL, DRAWING OUT GRIDS OF DOTS AND CROSSES, HE FINALLY DISCOVERED A REPEATING PATTERN USING ROWS OF 41. HE CORRECTLY REALISED THIS WAS HOW MANY POSITIONS THE FIRST OF THE ENCODING WHEELS MUST HAVE IN THE UNKNOWN MACHINE. FROM THIS BREAK, WITH THE ASSISTANCE OF OTHER CODEBREAKERS, HE MANAGED TO SUCCESSFULLY RECREATE THE WORKINGS OF THE WHOLE MACHINE. REMEMBER, THIS WAS WITHOUT EVER SEEING THE LORENZ WHICH WAS AN ASTOUNDING PIECE OF WORK. BILL TUTTE, WHOSE CENTENARY WILL BE CELEBRATED IN MAY THIS YEAR, DID FURTHER WORK ON METHODS WHICH COULD POTENTIALLY BREAK INTO AN ENCODED MESSAGE, BUT IT INVOLVED A HUGE AMOUNT OF MANUAL EFFORT TO COUNT RESULTS FROM ALL SETTINGS UNTIL THE CORRECT ONE WAS FOUND. MAX NEWMAN MANAGED THE CONSTRUCTION OF A MACHINE TO USE TUTTES CALCULATIONS, WHICH WAS NICKNAMED HEATH ROBINSON, BUT IT WAS, UNFORTUNATELY, RELATIVELY SLOW AND UNRELIABLE. THEY INVITED, TOMMY FLOWERS, FROM THE POST OFFICE RESEARCH STATION IN DOLLIS HILL TO SEE IF HE COULD IMPROVE THE MACHINE. TOMMY FLOWERS CAME BACK TO THEM WITH A PLAN TO BUILD A NEW MACHINE USING 1500 THERMIONIC VALVES. VALVES AT THIS TIME WERE BELIEVED TO BE UNRELIABLE AND REQUIRE REGULAR REPLACEMENT, BUT TOMMY FLOWERS KNEW FROM HIS RESEARCH THAT IF NOT SWITCHED OFF, THEY WORKED PERFECTLY. WHILE BLETCHLEY PARK INITIALLY REFUSED, FLOWERS RETURNED TO DOLLIS HILL AND PERSUADED HIS SUPERIORS TO CONTINUE TO BUILD THIS MACHINE. IN JANUARY 1944, THE FIRST COLOSSUS WAS DELIVERED TO BLETCHLEY PARK, AND BY FEBRUARY WAS RUNNING SUCCESSFULLY AND RELIABLY FIRST TIME, MUCH TO EVERYONES ASTONISHMENT THEY QUICKLY PLACED ORDERS FOR AS MANY AS POSSIBLE, FLOWERS ALREADY HAVING STARTED WORKING ON A FASTER MARK 2. A FURTHER NINE COLOSSUS COMPUTERS WERE DELIVERED TO BLETCHLEY PARK BY THE END OF THE WAR (APPROXIMATELY ONE PER MONTH) AND THEY HELPED BREAK AN AMAZING 63 MILLION CHARACTERS, SHORTENING THE CONFLICT AND SAVING MANY LIVES. AFTER THE WAR, CHURCHILL ORDERED THE DISMANTLING OF ALL BUT TWO OF THE MACHINES AND THEIR EXISTENCE WAS KEPT SECRET FOR THIRTY YEARS. TONY SALE, AN ELECTRONIC ENGINEER WORKING AS SENIOR CURATOR AT THE SCIENCE MUSEUM, ALONG WITH SEVERAL COLLEAGUES STARTED, IN 1991, THE CAMPAIGN TO SAVE BLETCHLEY PARK FROM PROPERTY DEVELOPERS. HE ALSO BEGAN GATHERING INFORMATION ABOUT COLOSSUS. BY 1993, HE HAD RECOVERED EIGHT PHOTOGRAPHS FROM 1945, PLUS SOME FRAGMENTS OF CIRCUIT DIAGRAMS. HE STARTED TO BELIEVE THAT IT WOULD BE POSSIBLE TO REBUILD COLOSSUS, ALTHOUGH HE SAID THAT NOBODY BELIEVED THAT THIS WOULD BE POSSIBLE JUST LIKE TOMMY FLOWERS BEFORE HIM AFTER MONTHS OF WORK AND WITH HELP FROM THE ORIGINAL DESIGNER OF THE OPTICAL TAPE SYSTEM, DR ARNOLD LYNCH, HE MANAGED TO RE-ENGINEER THE BASIC SYSTEM. HE VISITED DR ALLEN COOMBS, WHO HELPED BUILD THE MK 2 COLOSSUS, ALONG WITH HARRY FENSON, ONE OF THE ORIGINAL COLOSSUS ENGINEERS. DR COOMBS GAVE TONY HIS WARTIME NOTES AND SOME CIRCUIT DIAGRAMS. USING HIS, AND HIS WIFE MARGARETS OWN FUNDS, HE STARTED THE HUGE TASK REBUILDING THE COLOSSUS. HE PUT TOGETHER A TEAM OF EX-POST OFFICE AND RADIO ENGINEERS TO HELP THE REBUILD. ON 6TH JUNE 1996, A BASIC WORKING COLOSSUS REBUILD WAS SWITCHED ON, AN OCCASION WHERE DR TOMMY FLOWERS ATTENDED AS WELL AS MANY PEOPLE WHO WORKED AT BLETCHLEY PARK DURING THE WAR. THE NEWMANRY REPORT WAS DECLASSIFIED IN 2000, ALLOWING THEM TO BUILD A WORKING COLOSSUS MK 2 BY 1ST JUNE 2004, THE 60TH ANNIVERSARY OF THE FIRST RUNNING OF A COLOSSUS MK 2 IN 1944. THE REBUILD CAN BE SEEN IN THE NATIONAL MUSEUM OF COMPUTING IN BLOCK H LOCATED WITHIN BLETCHLEY PARK. IT STANDS IN THE ORIGINAL ROOM WHERE COLOSSUS NO 9 STOOD IN WORLD WAR II. IT IS A MARVELLOUS WORKING TRIBUTE TO TOMMY FLOWERS AND THE ENGINEERS AT DOLLIS HILL, TO BILL TUTTE, JOHN TILTMAN, MAX NEWMAN, RALPH TESTER AND ALL THE CODE BREAKERS AT BLETCHLEY PARK, ALL THE WRNS WHO OPERATED COLOSSUS AND THE RADIO INTERCEPTORS AT KNOCKHOLT. BY MARTIN GILLOW, VIRTUALCOLOSSUS.CO.UK", + recipeConfig: [ + { + "op": "Lorenz", + "args": ["SZ42b", "ZMUG Pattern", true, "Receive", "Plaintext", "Plaintext", "5/8/9", 32, 28, 24, 11, 44, 6, 50, 34, 12, 18, 18, 9, ".x...xx.x.x..xxx.x.x.xxxx.x.x.x.x.x..x.xx.x", ".xx.x.xxx..x.x.x..x.xx.x.xxx.x....x.xx.x.x.x..x", ".x.x.x..xxx....x.x.xx.x.x.x..xxx.x.x..x.x.xx..x.x.x", ".xx...xxxxx.x.x.xx...x.xx.x.x..x.x.xx.x..x.x.x.x.x.x.", "xx...xx.x..x.xx.x...x.x.x.x.x.x.x.x.xx..xxxx.x.x...xx.x..x.", "x.x.x.x.x.x...x.x.x...x.x.x...x.x....", ".xxxx.xxxx.xxx.xxxx.xx....xxx.xxxx.xxxx.xxxx.xxxx.xxx.xxxx...", ".x...xxx.x.xxxx.x...x.x..xxx....xx.xxxx..", "x..xxx...x.xxxx..xx..x..xx.xx..", "..xx..x.xxx...xx...xx..xx.xx.", "xx..x..xxxx..xx.xxx....x..", "xx..xx....xxxx.x..x.x.."] + } + ] + } + +]); diff --git a/plugins/srktoolbox/tests/operations/tests/LuhnChecksum.mjs b/plugins/srktoolbox/tests/operations/tests/LuhnChecksum.mjs new file mode 100644 index 00000000..5e03819c --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/LuhnChecksum.mjs @@ -0,0 +1,438 @@ +/** + * From Decimal tests + * + * @author n1073645 [n1073645@gmail.com] + * @author k3ach [k3ach@proton.me] + * @copyright Crown Copyright 2020 + * @licence Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +const testCases = [ + { + radix: 2, + input: "01", + checksum: "1", + checkdigit: "1", + }, { + radix: 2, + input: "001111", + checksum: "0", + checkdigit: "0", + }, { + radix: 2, + input: "00011101", + checksum: "0", + checkdigit: "0", + }, { + radix: 2, + input: "0100101101", + checksum: "1", + checkdigit: "1", + }, { + radix: 4, + input: "0123", + checksum: "1", + checkdigit: "1", + }, { + radix: 4, + input: "130100", + checksum: "2", + checkdigit: "2", + }, { + radix: 4, + input: "32020313", + checksum: "3", + checkdigit: "0", + }, { + radix: 4, + input: "302233210112", + checksum: "3", + checkdigit: "0", + }, { + radix: 6, + input: "012345", + checksum: "4", + checkdigit: "4", + }, { + radix: 6, + input: "134255", + checksum: "2", + checkdigit: "4", + }, { + radix: 6, + input: "15021453", + checksum: "5", + checkdigit: "4", + }, { + radix: 6, + input: "211450230513", + checksum: "3", + checkdigit: "1", + }, { + radix: 8, + input: "01234567", + checksum: "2", + checkdigit: "2", + }, { + radix: 8, + input: "340624", + checksum: "0", + checkdigit: "4", + }, { + radix: 8, + input: "07260247", + checksum: "3", + checkdigit: "3", + }, { + radix: 8, + input: "026742114675", + checksum: "7", + checkdigit: "1", + }, { + radix: 10, + input: "0123456789", + checksum: "7", + checkdigit: "7", + }, { + radix: 10, + input: "468543", + checksum: "7", + checkdigit: "4", + }, { + radix: 10, + input: "59377601", + checksum: "5", + checkdigit: "6", + }, { + radix: 10, + input: "013909981254", + checksum: "1", + checkdigit: "3", + }, { + radix: 12, + input: "0123456789ab", + checksum: "3", + checkdigit: "3", + }, { + radix: 12, + input: "284685", + checksum: "0", + checkdigit: "6", + }, { + radix: 12, + input: "951a2661", + checksum: "0", + checkdigit: "8", + }, { + radix: 12, + input: "898202676387", + checksum: "b", + checkdigit: "9", + }, { + radix: 14, + input: "0123456789abcd", + checksum: "a", + checkdigit: "a", + }, { + radix: 14, + input: "33db25", + checksum: "0", + checkdigit: "d", + }, { + radix: 14, + input: "0b4ac128", + checksum: "b", + checkdigit: "3", + }, { + radix: 14, + input: "3d1c6d16160d", + checksum: "3", + checkdigit: "c", + }, { + radix: 16, + input: "0123456789abcdef", + checksum: "4", + checkdigit: "4", + }, { + radix: 16, + input: "e1fe64", + checksum: "b", + checkdigit: "6", + }, { + radix: 16, + input: "241a5dcd", + checksum: "1", + checkdigit: "9", + }, { + radix: 16, + input: "1fea740e0e1f", + checksum: "7", + checkdigit: "4", + }, { + radix: 18, + input: "0123456789abcdefgh", + checksum: "d", + checkdigit: "d", + }, { + radix: 18, + input: "995dgf", + checksum: "9", + checkdigit: "1", + }, { + radix: 18, + input: "9f80h32h", + checksum: "1", + checkdigit: "0", + }, { + radix: 18, + input: "5f9428e493g4", + checksum: "8", + checkdigit: "c", + }, { + radix: 20, + input: "0123456789abcdefghij", + checksum: "5", + checkdigit: "5", + }, { + radix: 20, + input: "918jci", + checksum: "h", + checkdigit: "d", + }, { + radix: 20, + input: "jab7j50d", + checksum: "g", + checkdigit: "j", + }, { + radix: 20, + input: "c56fe85eb6gg", + checksum: "g", + checkdigit: "5", + }, { + radix: 22, + input: "0123456789abcdefghijkl", + checksum: "g", + checkdigit: "g", + }, { + radix: 22, + input: "de57le", + checksum: "5", + checkdigit: "l", + }, { + radix: 22, + input: "e3fg6dfc", + checksum: "f", + checkdigit: "d", + }, { + radix: 22, + input: "1f8l80ai4kbg", + checksum: "l", + checkdigit: "f", + }, { + radix: 24, + input: "0123456789abcdefghijklmn", + checksum: "6", + checkdigit: "6", + }, { + radix: 24, + input: "agne7d", + checksum: "4", + checkdigit: "f", + }, { + radix: 24, + input: "1l4d9cf4", + checksum: "d", + checkdigit: "c", + }, { + radix: 24, + input: "blc1j09i3296", + checksum: "8", + checkdigit: "7", + }, { + radix: 26, + input: "0123456789abcdefghijklmnop", + checksum: "j", + checkdigit: "j", + }, { + radix: 26, + input: "82n9op", + checksum: "i", + checkdigit: "2", + }, { + radix: 26, + input: "e9cddn70", + checksum: "9", + checkdigit: "i", + }, { + radix: 26, + input: "ck0ep419knom", + checksum: "p", + checkdigit: "g", + }, { + radix: 28, + input: "0123456789abcdefghijklmnopqr", + checksum: "7", + checkdigit: "7", + }, { + radix: 28, + input: "a6hnoo", + checksum: "h", + checkdigit: "9", + }, { + radix: 28, + input: "lblc7kh0", + checksum: "a", + checkdigit: "f", + }, { + radix: 28, + input: "64k5piod3lmf", + checksum: "0", + checkdigit: "p", + }, { + radix: 30, + input: "0123456789abcdefghijklmnopqrst", + checksum: "m", + checkdigit: "m", + }, { + radix: 30, + input: "t69j7d", + checksum: "9", + checkdigit: "s", + }, { + radix: 30, + input: "p54o9ig3", + checksum: "a", + checkdigit: "o", + }, { + radix: 30, + input: "gc1njrt55030", + checksum: "6", + checkdigit: "1", + }, { + radix: 32, + input: "0123456789abcdefghijklmnopqrstuv", + checksum: "8", + checkdigit: "8", + }, { + radix: 32, + input: "rdou19", + checksum: "u", + checkdigit: "3", + }, { + radix: 32, + input: "ighj0pc7", + checksum: "3", + checkdigit: "8", + }, { + radix: 32, + input: "op4nn5fvjsrs", + checksum: "g", + checkdigit: "j", + }, { + radix: 34, + input: "0123456789abcdefghijklmnopqrstuvwx", + checksum: "p", + checkdigit: "p", + }, { + radix: 34, + input: "nvftj5", + checksum: "b", + checkdigit: "f", + }, { + radix: 34, + input: "u9v9g162", + checksum: "j", + checkdigit: "b", + }, { + radix: 34, + input: "o5gqg5d7gjh9", + checksum: "5", + checkdigit: "q", + }, { + radix: 36, + input: "0123456789abcdefghijklmnopqrstuvwxyz", + checksum: "9", + checkdigit: "9", + }, { + radix: 36, + input: "29zehu", + checksum: "i", + checkdigit: "j", + }, { + radix: 36, + input: "1snmikbu", + checksum: "s", + checkdigit: "v", + }, { + radix: 36, + input: "jpkar545q7gb", + checksum: "3", + checkdigit: "d", + }, +]; + +testCases.forEach(element => { + TestRegister.addTests([ + { + name: "Luhn Checksum Mod " + element.radix + " on " + element.input, + input: element.input, + expectedOutput: "校验和: " + element.checksum + "\n检验位: " + element.checkdigit + "\nLuhn校验字符串: " + element.input + element.checkdigit, + recipeConfig: [ + { + op: "Luhn校验和", + args: [element.radix] + }, + ], + }, + ]); +}); + +TestRegister.addTests([ + { + name: "Luhn Checksum on standard data", + input: "35641709012469", + expectedOutput: "校验和: 7\n检验位: 0\nLuhn校验字符串: 356417090124690", + recipeConfig: [ + { + op: "Luhn校验和", + args: [10] + }, + ], + }, + { + name: "Luhn Checksum on standard data 2", + input: "896101950123440000", + expectedOutput: "校验和: 5\n检验位: 1\nLuhn校验字符串: 8961019501234400001", + recipeConfig: [ + { + op: "Luhn校验和", + args: [10] + }, + ], + }, + { + name: "Luhn Checksum on standard data 3", + input: "35726908971331", + expectedOutput: "校验和: 6\n检验位: 7\nLuhn校验字符串: 357269089713317", + recipeConfig: [ + { + op: "Luhn校验和", + args: [10] + }, + ], + }, + { + name: "Luhn Checksum on empty data", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "Luhn校验和", + args: [10] + }, + ], + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/MIMEDecoding.mjs b/plugins/srktoolbox/tests/operations/tests/MIMEDecoding.mjs new file mode 100644 index 00000000..b73f4005 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/MIMEDecoding.mjs @@ -0,0 +1,91 @@ +/** + * MIME Header Decoding tests + * + * @author mshwed [m@ttshwed.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Encoded comments", + input: "(=?ISO-8859-1?Q?a?=)", + expectedOutput: "(a)", + recipeConfig: [ + { + "op": "MIME解码", + "args": [] + } + ] + }, + { + name: "Encoded adjacent comments whitespace", + input: "(=?ISO-8859-1?Q?a?= b)", + expectedOutput: "(a b)", + recipeConfig: [ + { + "op": "MIME解码", + "args": [] + } + ] + }, + { + name: "Encoded adjacent single whitespace ignored", + input: "(=?ISO-8859-1?Q?a?= =?ISO-8859-1?Q?b?=)", + expectedOutput: "(ab)", + recipeConfig: [ + { + "op": "MIME解码", + "args": [] + } + ] + }, + { + name: "Encoded adjacent double whitespace ignored", + input: "(=?ISO-8859-1?Q?a?= =?ISO-8859-1?Q?b?=)", + expectedOutput: "(ab)", + recipeConfig: [ + { + "op": "MIME解码", + "args": [] + } + ] + }, + { + name: "Encoded adjacent CRLF whitespace ignored", + input: "(=?ISO-8859-1?Q?a?=\r\n =?ISO-8859-1?Q?b?=)", + expectedOutput: "(ab)", + recipeConfig: [ + { + "op": "MIME解码", + "args": [] + } + ] + }, + { + name: "UTF-8 Encodings Multiple Headers", + input: "=?utf-8?q?=C3=89ric?= , =?utf-8?q?Ana=C3=AFs?= ", + expectedOutput: "Éric , Anaïs ", + recipeConfig: [ + { + "op": "MIME解码", + "args": [] + } + ] + }, + { + name: "ISO Decoding", + input: "From: =?US-ASCII?Q?Keith_Moore?= \nTo: =?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?= \nCC: =?ISO-8859-1?Q?Andr=E9?= Pirard \nSubject: =?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=\n=?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?=", + expectedOutput: "From: Keith Moore \nTo: Keld Jørn Simonsen \nCC: André Pirard \nSubject: If you can read this you understand the example.", + recipeConfig: [ + { + "op": "MIME解码", + "args": [] + } + ] + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/MS.mjs b/plugins/srktoolbox/tests/operations/tests/MS.mjs new file mode 100644 index 00000000..367d3fe5 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/MS.mjs @@ -0,0 +1,24 @@ +/** + * MS tests. + * + * @author bwhitn [brian.m.whitney@outlook.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Microsoft Script Decoder", + input: "#@~^RQAAAA==-mD~sX|:/TP{~J:+dYbxL~@!F@*@!+@*@!&@*eEI@#@&@#@&\x7fjm.raY 214Wv:zms/obI0xEAAA==^#~@", + expectedOutput: "var my_msg = \"Testing <1><2><3>!\";\r\n\r\nWScript.Echo(my_msg);", + recipeConfig: [ + { + "op": "Microsoft Script解码", + "args": [] + }, + ], + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/Magic.mjs b/plugins/srktoolbox/tests/operations/tests/Magic.mjs new file mode 100644 index 00000000..5fce49c6 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/Magic.mjs @@ -0,0 +1,158 @@ +/** + * Magic tests. + * + * @author n1474335 [n1474335@gmail.com] + * + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; +import { JPG_RAW } from "../../samples/Images.mjs"; + +TestRegister.addTests([ + { + name: "Magic: nothing", + input: "", + expectedOutput: "针对输入数据未检测到任何可用操作。\n请尝试调整操作参数。", + recipeConfig: [ + { + op: "Magic", + args: [3, false, false] + } + ], + }, + { + name: "Magic: hex, correct rank", + input: "41 42 43 44 45", + expectedMatch: /属性[^#]+?#recipe=%E5%8D%81%E5%85%AD%E8%BF%9B%E5%88%B6%E8%BD%AC%E5%AD%97%E7%AC%A6\('%E7%A9%BA%E6%A0%BC'\)"/, + recipeConfig: [ + { + op: "Magic", + args: [3, false, false] + } + ], + }, + { + name: "Magic: jpeg render", + input: JPG_RAW, + expectedMatch: /渲染图像\('原始'\)/, + recipeConfig: [ + { + op: "Magic", + args: [3, false, false] + } + ], + }, + { + name: "Magic: mojibake", + input: "\xd0\x91\xd1\x8b\xd1\0\xd1\x82\xd1\x80\xd0\xb0\xd1\0\x20\xd0\xba\xd0\xbe\xd1\x80\xd0\xb8\xd1\x87\xd0\xbd\xd0\xb5\xd0\xb2\xd0\xb0\xd1\0\x20\xd0\xbb\xd0\xb8\xd1\0\xd0\xb0\x20\xd0\xbf\xd1\x80\xd1\x8b\xd0\xb3\xd0\xb0\xd0\xb5\xd1\x82\x20\xd1\x87\xd0\xb5\xd1\x80\xd0\xb5\xd0\xb7\x20\xd0\xbb\xd0\xb5\xd0\xbd\xd0\xb8\xd0\xb2\xd1\x83\xd1\x8e\x20\xd1\0\xd0\xbe\xd0\xb1\xd0\xb0\xd0\xba\xd1\x83\x2e", + expectedMatch: /Быртрар коричневар лира прыгает через ленивую робаку./, + recipeConfig: [ + { + op: "Magic", + args: [1, true, false] + } + ], + }, + { + name: "Magic: extensive language support, Yiddish", + input: "די שנעל ברוין פאָקס דזשאַמפּס איבער די פויל הונט.", + expectedMatch: /Yiddish/, + recipeConfig: [ + { + op: "Magic", + args: [1, false, true] + } + ], + }, + { + name: "Magic Chain: Base64", + input: "WkVkV2VtUkRRbnBrU0Vwd1ltMWpQUT09", + expectedMatch: /Base64解码\('A-Za-z0-9\+\/=',true,false\)\nBase64解码\('A-Za-z0-9\+\/=',true,false\)\nBase64解码\('A-Za-z0-9\+\/=',true,false\)/, + recipeConfig: [ + { + op: "Magic", + args: [3, false, false] + } + ], + }, + { + name: "Magic Chain: Hex -> Hexdump -> Base64", + input: "MDAwMDAwMDAgIDM3IDM0IDIwIDM2IDM1IDIwIDM3IDMzIDIwIDM3IDM0IDIwIDMyIDMwIDIwIDM3ICB8NzQgNjUgNzMgNzQgMjAgN3wKMDAwMDAwMTAgIDMzIDIwIDM3IDM0IDIwIDM3IDMyIDIwIDM2IDM5IDIwIDM2IDY1IDIwIDM2IDM3ICB8MyA3NCA3MiA2OSA2ZSA2N3w=", + expectedMatch: /Base64解码\('A-Za-z0-9\+\/=',true,false\)\n从Hexdump提取\(\)\n十六进制转字符\('空格'\)/, + recipeConfig: [ + { + op: "Magic", + args: [3, false, false] + } + ], + }, + { + name: "Magic Chain: Charcode -> Octal -> Base32", + input: "GY3SANRUEA2DAIBWGYQDMNJAGQYCANRXEA3DGIBUGAQDMNZAGY2CANBQEA3DEIBWGAQDIMBAGY3SANRTEA2DAIBWG4QDMNBAGQYCANRXEA3DEIBUGAQDMNRAG4YSANBQEA3DMIBRGQ2SANBQEA3DMIBWG4======", + expectedMatch: /Base32解码\('A-Z2-7=',false\)\n八进制转字符\('空格'\)\n十六进制转字符\('空格'\)/, + recipeConfig: [ + { + op: "Magic", + args: [3, false, false] + } + ], + }, + { + name: "Magic Chain: Base64 output", + input: "WkVkV2VtUkRRbnBrU0Vwd1ltMWpQUT09", + expectedMatch: /test string/, + recipeConfig: [ + { + op: "Magic", + args: [3, false, false] + } + ], + }, + { + name: "Magic Chain: Decimal -> Base32 -> Base32", + input: "I5CVSVCNJFBFER2BLFJUCTKKKJDVKUKEINGUUV2FIFNFIRKJIJJEORJSKNAU2SSSI5MVCRCDJVFFKRKBLFKECTSKIFDUKWKUIFEUEUSHIFNFCPJ5HU6Q====", + expectedMatch: /test string/, + recipeConfig: [ + { + op: "Magic", + args: [3, false, false] + } + ], + }, + { + name: "Magic: Raw Inflate", + input: "\x4d\x52\xb1\x6e\xdc\x30\x0c\xdd\xf3\x15\x44\x80\x6e\xae\x91\x02\x4d\x80\x8e\x4d\x9a\x21\x53\x8b\xa6\x43\x56\x5a\xe2\x9d\x84\x93\x25\x43\x94\xed\xf8\xef\xf3\xe8\x6b\x0e\xb7\x1c\xce\xd4\x7b\x8f\x8f\x7c\x7c\xda\x06\xa9\x4f\x41\x0e\x14\x95\x98\x34\x8e\x53\x92\x8e\x62\x6e\x73\x6c\x71\x11\x5a\x65\x20\x9e\x26\x3a\x94\x4a\x8e\x6b\xdd\x62\x3e\x52\x99\x1b\x71\x4a\x34\x72\xce\x52\xa9\x1c\xe8\xd6\x99\xd0\x2d\x95\x49\x2a\xb7\x58\xb2\xd2\x1a\x5b\x88\x19\xa2\x26\x31\xd4\xb2\xaa\xd4\x9e\xfe\x05\x51\xb9\x86\xc5\xec\xd2\xec\xe5\x7f\x6b\x92\xec\x8a\xb7\x1e\x29\x9e\x84\xde\x7e\xff\x25\x34\x7e\x64\x95\x87\xef\x1d\x8d\xa5\x0a\xb9\x62\xc0\x77\x43\xd6\x6d\x32\x91\x33\xf6\xe7\xf3\x6b\x47\xbf\x9e\x5f\x89\xb3\xa7\xc7\x54\xd6\x43\xd4\xd0\x91\xab\x82\x4e\x10\x1c\x62\xe6\xba\xed\xaf\x41\xde\xfd\x3c\x4e\x8a\x57\x88\x55\x51\x35\x15\x7b\xf1\x72\x5d\xc1\x60\x9e\x1b\x03\xc6\xc9\xcd\xe9\xac\x13\x58\x31\xc3\x8e\x76\x41\xdc\x49\xe7\x11\x42\x2f\x7f\x96\x87\xbd\xf6\xd6\xdf\xdf\xfd\xa0\x89\xab\x02\x0c\x66\xe0\x7c\x34\x1a\xfe\x54\x76\x0d\xeb\xfa\x1c\x11\x2c\x23\x8c\xb3\x0b\xfb\x64\xfd\xcd\x0d\xb6\x43\xad\x94\x64\x69\x78\xd1\x78\xcc\xe2\x51\x00\x85\x07\x2c\x67\x28\x2d\x50\x13\x17\x72\x84\xa3\x9d\x9d\x4b\xfe\x7a\x5d\xe1\xb4\x69\x53\xe3\x20\x9c\x38\x99\x69\xd9\x87\xc0\xa2\x2f\xab\x5b\x79\x3b\xe7\x63\x41\x06\x5e\xcc\x1f\x18\x5e\x20\x61\xe5\x0b\xd0\xbc\xa8\x25\xc0\xe9\x58\x2a\x5e\x46\xed\xe9\xa5\x41\x40\x81\xc9\x4e\x70\x22\xbe\xbb\x58\xed\x68\x98\x63\xc2\x6d\xc0\x18\x72\xad\x32\x4a\x6e\x38\x94\x8d\x10\x6e\x2d\xc0\xd2\x60\x09\x7c\xfa\x34\x4f\x2d\x48\xac\xf4\xed\xee\x0b\x3e\x72\x59\xf6\xab\xa0\x16\x47\x1c\xc9\x82\x65\xa9\xe0\x17\xb6\x36\xc1\x46\xfb\x0f", + expectedMatch: /#recipe=Raw_Inflate(.|\n)+CyberChef is a simple, intuitive web app for carrying out all manner of /, + recipeConfig: [ + { + op: "Magic", + args: [1, false, false] + } + ] + }, + { + name: "Magic: Defang IP Address, valid", + input: "192.168.0.1", + expectedMatch: /属性[^#]+?#recipe=IP%E5%9C%B0%E5%9D%80%E6%97%A0%E6%95%88%E5%8C%96\(\)"/, + recipeConfig: [ + { + op: "Magic", + args: [1, false, false] + } + ] + }, + { + name: "Magic: Defang IP Address, invalid", + input: "192.168.0.1.0", + unexpectedMatch: /IPIP%E5%9C%B0%E5%9D%80%E6%97%A0%E6%95%88%E5%8C%96/, + recipeConfig: [ + { + op: "Magic", + args: [1, false, false] + } + ] + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/Media.mjs b/plugins/srktoolbox/tests/operations/tests/Media.mjs new file mode 100644 index 00000000..03d4a942 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/Media.mjs @@ -0,0 +1,45 @@ +/** + * Media operation tests. + * @author anthony-arnold [anthony.arnold@uqconnect.edu.au] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Play Media: nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { op: "播放媒体文件", args: ["原始"] } + ] + }, + { + name: "Play Media: raw wav", + input: "52494646bcaf010057415645666d74201000000001000100401f0000401f0000010008006461746198af0100818081808180818081808180818081808180818081808180818081808180818081808180818081808180818081808180818081808180818081808180818081808180818081808180818081808180818081818281807f82807f817e81808280827d8086817d80828184817f80807d847f7e7e8582837b81857e7e82867f7c7f857e7d838182837f83847b7c838082807c7d838481827e83827c7f848383807c7f7c80848085808180827e83827f7c82827c7f858183807c837b847f7f82827a85837a8287817f7882827f89837e7f877b7e808281817f7e807c827b7e7d8383838486838883837d7d7682797d847f7d868287867d83847c81807d7e818080828082828380807e7f827d8181807e828082858581808081807f7a7f81808183807d7c7f7f858180848484857f8082817e827b7e7f7f8385807f7f7d858183808081807f7f8183847f7e7d81828284857d7f7e8084868182837d7e7d7a7e7e8085837f80807b7f7b7a80827d7f817e8386838588868386868689868586838386868584807c7a78787875706e6c6a6c6d70757a7c7e7f80858b94a1a29a9996989a9e9ca09ea19caa8e554129243e4d475e7184a0ada2a4a0918179646167696f7b7a8390939ca4aab2af9c9691909aa2a3ae74321e162a4d5c506c88a5babca6a4a7a18c7a5e5c646f6a6b6d7f929a8f858182827e79849299a1abb2c7cc975b3826324447496788a5b8b6a9aaa9977b60494c586467707c949fa195938f8f847a717c88a0aab7b7d6a46045362f3c483d6089a1acb4a3a4a89570615652676f6d788f97a29c8e8992898381808393999da9bf8e515e504c4e5d4879929a90a1909e9c855c5b5f717679768b9b9f958c838c8d8c7f8782969ca797a963585f63425d60687e93818b99938c84786f7e7a7b7f86838a867f8486858899939697a7a6be6e465f6c4d465d678e948275958a867063688e8b828fa2a0988b7a7f7c737185898a8e90989c9b8c8c6e5f5a655159646f727f737382857c7f898a9aa4b1b6cac5d4957d695b403c4152", + expectedOutput: "", + recipeConfig: [ + { op: "十六进制转字符", args: ["空格"] }, + { op: "播放媒体文件", args: ["原始"] } + ] + }, + { + name: "Play Media: hex ogg", + input: "4f676753000200000000000000003129000000000000642493e3011e01766f72626973000000000244ac0000000000008138010000000000b8014f676753000000000000000000003129000001000000a3565ae9102dffffffffffffffffffffffffffff2403766f726269731d000000586970682e4f7267206c6962566f726269732049203230303230373137000000000105766f726269732242435601000001009c739a318799629452892194de3968196394526929a55a4aa9a183166babbdf7de7befbdf7de7bef1d739431469552524aa99d739631471563524a89a5945642682184d662abbdf7de6befb5f6de7bef99424c29a41442084a281d538c29a494424a4a0825640e3ac61c538c52093dd65e6bccbdb6d87beda163ce39e61c534c4a6821740e3ae69c534c4a68a984524206a153d05289adf7de62ebb9a5da7bef81d0905500000100c040101ab20a00500000108aa1188a028486ac020032000004e0288ee3388ee23892623916101ab20a00000200100000c0900c4bb114cdd1244dd22ccf134dd3377dd3366d55d7755dd7755dd77520346415000001004040a719a61a20c28c6416080d590500200000004420c3140342435601000001000052243949a2e4a494520e836431492ae5a494521ec5e4514d3206a594524a29a594524a29a594520a8364394a2ae5a4945212a364314aaad4a494521ee5e4", + expectedOutput: "", + recipeConfig: [ + { op: "播放媒体文件", args: ["十六进制"] } + ] + }, + { + name: "Play Media: base64 webm", + input: "GkXfo6NChoEBQveBAULygQRC84EIQoKEd2VibeyCAABCh4EBQoWBARhTgGcQIQmHEU2bdLtNu4tTq4QVSalmU6yBQE27i1OrhBZUrmtTrIGsTbuNU6uEEU2bdFOsgyEJc027jFOrhBxTu2tTrIINQRVJqWbnc6SQRsadRaGFqSlNPQovdQBWvSrXsYMPQkBEiYRG/cAARGGIBBu7mlIesABNgKVodHRwOi8vc291cmNlZm9yZ2UubmV0L3Byb2plY3RzL3lhbWthV0GQU29yZW5zb24gU3F1ZWV6ZRZUrmtMj66414EBc8WHiBmgyaYxwoOBASPjg4QCYloAIzFPhD+AAACGhVZfVlA4JYaIg1ZQOOCIsIICgLqCAWiuTFLXgQJzxYgBiP65XI76uoOBAiMxT4Q/gAAAhohBX1ZPUkJJU2OiTBkCHjoBdm9yYmlzAAAAAAFErAAA/////wD6AAD/////uAEDdm9yYmlzKgAAAFhpcGguT3JnIGxpYlZvcmJpcyBJIDIwMTAwMzI1IChFdmVyeXdoZXJlKQAAAAABBXZvcmJpcx9CQ1YBAAABABhjVClGmVLSSokZc5QxRplikkqJpYQWQkidcxRTqTnXnGusubUghBAaU1ApBZlSjlJpGWOQKQWZUhBLSSV0EjonnWMQW0nB1phri0G2HIQNmlJMKcSUUopCCBlTjCnFlFJKQgcldA465hxTjkooQbicc6u1lpZji6l0kkrnJGRMQkgphZJKB6VTTkJINZbWUikdc1JSakHoIIQQQrYghA2C0JBVAAABAMBAEBqyCgBQAAAQiqEYigKEhqwCADIAAASgKI7iKI4jOZJjSRYQGrIKAAACABAAAMBwFEmRFMmxJEvSLEvTRFFVfdU2VVX2dV3XdV3XdSA0ZBUAAAEAQEinmaUaIMIMZBgIDVkFACAAAABGKMIQA0JDVgEAAAEAAGIoOYgmtOZ8c46DZjloKsXmdHAi1eZJbirm5pxzzjknm3PGOOecc4pyZjFoJrTmnHMSg2YpaCa05pxznsTmQWuqtOacc8Y5p4NxRhjnnHOatOZBajbW5pxzFrSmOWouxeaccyLl5kltLtXmnHPOOeecc84555xzqhenc3BOOOecc6L25lpuQhfnnHM+Gad7c0I455xzzjnnnHPOOeecc4LQkFUAABAAAEEYNoZxpyBIn6OBGEWIacikB92jwyRoDHIKqUejo5FS6iCUVMZJKZ0gNGQVAAAIAAAhhBRSSCGFFFJIIYUUUoghhhhiyCmnnIIKKqmkoooyyiyzzDLLLLPMMuuws8467DDEEEMMrbQSS0211VhjrbnnnGsO0lpprbXWSimllFJKKQgNWQUAgAAAEAgZZJBBRiGFFFKIIaaccsopqKACQkNWAQCAAAACAAAAPMlzREd0REd0REd0REd0RMdzPEeUREmUREm0TMvUTE8VVdWVXVvWZd32bWEXdt33dd/3dePXhWFZlmVZlmVZlmVZlmVZlmVZgtCQVQAACAAAgBBCCCGFFFJIIaUYY8wx56CTUEIgNGQVAAAIACAAAADAURzFcSRHciTJkixJkzRLszzN0zxN9ERRFE3TVEVXdEXdtEXZlE3XdE3ZdFVZtV1Ztm3Z1m1flm3f933f933f933f933f93UdCA1ZBQBIAADoSI6kSIqkSI7jOJIkAaEhqwAAGQAAAQAoiqM4juNIkiRJlqRJnuVZomZqpmd6qqgCoSGrAABAAAABAAAAAAAomuIppuIpouI5oiNKomVaoqZqriibsuu6ruu6ruu6ruu6ruu6ruu6ruu6ruu6ruu6ruu6ruu6ruu6LhAasgoAkAAA0JEcyZEcSZEUSZEcyQFCQ1YBADIAAAIAcAzHkBTJsSxL0zzN0zxN9ERP9ExPFV3RBUJDVgEAgAAAAgAAAAAAMCTDUixHczRJlFRLtVRNtVRLFVVPVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVNU3TNE0gNGQlAAAEAMBijcHlICElJeXeEMIQk54xJiG1XiEEkZLeMQYVg54yogxy3kLjEIMeCA1ZEQBEAQAAxiDHEHPIOUepkxI556h0lBrnHKWOUmcpxZhizSiV2FKsjXOOUketo5RiLC12lFKNqcYCAAACHAAAAiyEQkNWBABRAACEMUgppBRijDmnnEOMKeeYc4Yx5hxzjjnnoHRSKuecdE5KxBhzjjmnnHNSOieVc05KJ6EAAIAABwCAAAuh0JAVAUCcAIBBkjxP8jRRlDRPFEVTdF1RNF3X8jzV9ExTVT3RVFVTVW3ZVFVZljzPND3TVFXPNFXVVFVZNlVVlkVV1W3TdXXbdFXdlm3b911bFnZRVW3dVF3bN1XX9l3Z9n1Z1nVj8jxV9UzTdT3TdGXVdW1bdV1d90xTlk3XlWXTdW3blWVdd2XZ9zXTdF3TVWXZdF3ZdmVXt11Z9n3TdYXflWVfV2VZGHZd94Vb15XldF3dV2VXN1ZZ9n1b14Xh1nVhmTxPVT3TdF3PNF1XdV1fV13X1jXTlGXTdW3ZVF1ZdmXZ911X1nXPNGXZdF3bNl1Xll1Z9n1XlnXddF1fV2VZ+FVX9nVZ15Xh1m3hN13X91VZ9oVXlnXh1nVhuXVdGD5V9X1TdoXhdGXf14XfWW5dOJbRdX1hlW3hWGVZOX7hWJbd95VldF1fWG3ZGFZZFoZf+J3l9n3jeHVdGW7d58y67wzH76T7ytPVbWOZfd1ZZl93juEYOr/w46mqr5uuKwynLAu/7evGs/u+soyu6/uqLAu/KtvCseu+8/y+sCyj7PrCasvCsNq2Mdy+biy/cBzLa+vKMeu+UbZ1fF94CsPzdHVdeWZdx/Z1dONHOH7KAACAAQcAgAATykChISsCgDgBAI8kiaJkWaIoWZYoiqbouqJouq6kaaapaZ5pWppnmqZpqrIpmq4saZppWp5mmpqnmaZomq5rmqasiqYpy6ZqyrJpmrLsurJtu65s26JpyrJpmrJsmqYsu7Kr267s6rqkWaapeZ5pap5nmqZqyrJpmq6reZ5qep5oqp4oqqpqqqqtqqosW55nmproqaYniqpqqqatmqoqy6aq2rJpqrZsqqptu6rs+rJt67ppqrJtqqYtm6pq267s6rIs27ovaZppap5nmprnmaZpmrJsmqorW56nmp4oqqrmiaZqqqosm6aqypbnmaoniqrqiZ5rmqoqy6Zq2qppmrZsqqotm6Yqy65t+77ryrJuqqpsm6pq66ZqyrJsy77vyqruiqYpy6aq2rJpqrIt27Lvy7Ks+6JpyrJpqrJtqqouy7JtG7Ns+7pomrJtqqYtm6oq27It+7os27rvyq5vq6qs67It+7ru+q5w67owvLJs+6qs+ror27pv6zLb9n1E05RlUzVt21RVWXZl2fZl2/Z90TRtW1VVWzZN1bZlWfZ9WbZtYTRN2TZVVdZN1bRtWZZtYbZl4XZl2bdlW/Z115V1X9d949dl3ea6su3Lsq37qqv6tu77wnDrrvAKAAAYcAAACDChDBQashIAiAIAAIxhjDEIjVLOOQehUco55yBkzkEIIZXMOQghlJI5B6GUlDLnIJSSUgihlJRaCyGUlFJrBQAAFDgAAATYoCmxOEChISsBgFQAAIPjWJbnmaJq2rJjSZ4niqqpqrbtSJbniaJpqqptW54niqapqq7r65rniaJpqqrr6rpomqapqq7ruroumqKpqqrrurKum6aqqq4ru7Ls66aqqqrryq4s+8Kquq4ry7Jt68Kwqq7ryrJs27Zv3Lqu677v+8KRreu6LvzCMQxHAQDgCQ4AQAU2rI5wUjQWWGjISgAgAwCAMAYhgxBCBiGEkFJKIaWUEgAAMOAAABBgQhkoNGQlABADAAAQASGDEEIIIYQQQgghhBBCCCGEEELnnHPOOeecc84JANiPcACQejAxMYWFhqwEAFIBAABjlFKKMecgRIw5xhh0EkqKGHOOMQelpFQ5ByGEVFrLrXIOQggptVRb5pyU1mKMOcbMOSkpxVZzzqGU1GKsueaaOymt1ZprzbmW1mrNNedccy6txZprzjXn3HLMNeecc845xpxzzjnnnHMBADgNDgCgBzasjnBSNBZYaMhKACAVAIBARinGnHMOOoQUY845ByGESCHGnHMOQggVY845Bx2EECrGHHMOQgghZM45ByGEEELInIMOOgghhNBBByGEEEIopXMQQgghhBJKCCGEEEIIIYQOQgghhBBCCCGEEEIIoZQSQgghhFBCKCUUAABY4AAAEGDD6ggnRWOBhYasBACAAAAghyWolDNhkGPQY0OQctRMgxBTTnSmmJPaTMUUZA5EJ51EhlpQtpfMAgAAIAgACDABBAYICr4QAmIMAEAQIjNEQmEVLDAogwaHeQDwABEhEQAkJijSLi6gywAXdHHXgRCCEIQgFgdQQAIOTrjhiTc84QYn6BSVOggAAAAAAAMAeAAAOCiAiIjmKiwuMDI0Njg6PAIAAAAAAAYAPgAAjg8gIqK5CosLjAyNDY4OjwAAAAAAAAAAACAgIAAAAAAAEAAAACAgJYaIhlZvcmJpc+GGtYRHLEQAHFO7a0IAu4yzgQC3h/eBAfGCD0e7kbOCAli3i/eBAfGCD0dTeIEqu5Gzgg3At4v3gQHxgg9HU3iB8buSs4Ib0LeM94EB8YIPR1N4ggHmu5KzgiN4t4z3gQHxgg9HU3iCAnW7krOCK8C3jPeBAfGCD0dTeIIDBruSs4IzkLeM94EB8YIPR1N4ggOXu5KzgjY4t4z3gQHxgg9HU3iCA8W7krOCOpi3jPeBAfGCD0dTeIIEIbuSs4I+gLeM94EB8YIPR1N4ggRlu5KzgkDYt4z3gQHxgg9HU3iCBI67krOCSNC3jPeBAfGCD0dTeIIFIbuSs4JJwLeM94EB8YIPR1N4ggU2u5Kzgk3Qt4z3gQHxgg9HU3iCBYO7krOCUZC3jPeBAfGCD0dTeIIFxLuSs4JR4LeM94EB8YIPR1N4ggXKu5KzglXwt4z3gQHxgg9HU3iCBhe7krOCWYi3jPeBAfGCD0dTeIIGXLuSs4JhWLeM94EB8YIPR1N4ggblu5KzgmWQt4z3gQHxgg9HU3iCBy67krOCaDi3jPeBAfGCD0dTeIIHXLuSs4JosLeM94EB8YIPR1N4ggdku5KzgnUIt4z3gQHxgg9HU3iCCEK7krOCddC3jPeBAfGCD0dTeIIIUruSs4J2ILeM94EB8YIPR1N4gghdu5Kzgn5At4z3gQHxgg9HU3iCCPgfQ7Z1ECD6JOeBAKeCD0ejQbOBAACAEjQAnQEqgAJoATkPAEEcIhYWIhYSIAYAABhYE9d0hkrLkkLy9LukMlZckheXpd0hkrLkkLy9LukMlZckheXpd0hkrLkkLy9LukMlZckheXpd0hkrLkkLy9LukMlZckheXpd0hkrLkkLy9LukMlZckheXpQ==", + expectedOutput: "", + recipeConfig: [ + { op: "播放媒体文件", args: ["Base64"] } + ] + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/Modhex.mjs b/plugins/srktoolbox/tests/operations/tests/Modhex.mjs new file mode 100644 index 00000000..8d310b7d --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/Modhex.mjs @@ -0,0 +1,152 @@ +/** + * Modhex operation tests. + * @author linuxgemini [ilteris@asenkron.com.tr] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + * + * Modified by Raka-loah@github + */ + +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "ASCII to Modhex stream", + input: "aberystwyth", + expectedOutput: "hbhdhgidikieifiiikifhj", + recipeConfig: [ + { + "op": "Modhex编码", + "args": [ + "无", + 0 + ] + }, + ] + }, + { + name: "ASCII to Modhex with colon deliminator", + input: "aberystwyth", + expectedOutput: "hb:hd:hg:id:ik:ie:if:ii:ik:if:hj", + recipeConfig: [ + { + "op": "Modhex编码", + "args": [ + "冒号", + 0 + ] + } + ] + }, + { + name: "Modhex stream to UTF-8", + input: "uhkgkbuhkgkbugltlkugltkc", + expectedOutput: "救救孩子", + recipeConfig: [ + { + "op": "Modhex解码", + "args": [ + "自动" + ] + } + ] + + }, + { + name: "Mixed case Modhex stream to UTF-8", + input: "uhKGkbUHkgkBUGltlkugltkc", + expectedOutput: "救救孩子", + recipeConfig: [ + { + "op": "Modhex解码", + "args": [ + "自动" + ] + } + ] + + }, + { + name: "Mutiline Modhex with comma to ASCII (Auto Mode)", + input: "fk,dc,ie,hb,ii,dc,ht,ik,ie,hg,hr,hh,dc,ie,hk,\n\ +if,if,hk,hu,hi,dc,hk,hu,dc,if,hj,hg,dc,he,id,\n\ +hv,if,he,hj,dc,hv,hh,dc,if,hj,hg,dc,if,hj,hk,\n\ +ie,dc,hh,hk,hi,dc,if,id,hg,hg,dr,dc,ie,if,hb,\n\ +id,ih,hk,hu,hi,dc,if,hv,dc,hf,hg,hb,if,hj,dr,\n\ +dc,hl,ig,ie,if,dc,hd,hg,he,hb,ig,ie,hg,dc,fk,\n\ +dc,he,hv,ig,hr,hf,hu,di,if,dc,ht,hb,hn,hg,dc,\n\ +ig,ic,dc,ht,ik,dc,ht,hk,hu,hf,dc,ii,hj,hk,he,\n\ +hj,dc,hv,hh,dc,if,hj,hg,dc,hh,hk,hi,ie,dc,fk,\n\ +dc,ii,hv,ig,hr,hf,dc,he,hj,hv,hv,ie,hg,du", + expectedOutput: "I saw myself sitting in the crotch of the this fig tree, starving to death, just because I couldn't make up my mind which of the figs I would choose.", + recipeConfig: [ + { + "op": "Modhex解码", + "args": [ + "自动" + ] + } + ] + + }, + { + name: "Mutiline Modhex with percent to ASCII (Percent Mode)", + input: "fk%dc%ie%hb%ii%dc%ht%ik%ie%hg%hr%hh%dc%ie%hk%\n\ +if%if%hk%hu%hi%dc%hk%hu%dc%if%hj%hg%dc%he%id%\n\ +hv%if%he%hj%dc%hv%hh%dc%if%hj%hg%dc%if%hj%hk%\n\ +ie%dc%hh%hk%hi%dc%if%id%hg%hg%dr%dc%ie%if%hb%\n\ +id%ih%hk%hu%hi%dc%if%hv%dc%hf%hg%hb%if%hj%dr%\n\ +dc%hl%ig%ie%if%dc%hd%hg%he%hb%ig%ie%hg%dc%fk%\n\ +dc%he%hv%ig%hr%hf%hu%di%if%dc%ht%hb%hn%hg%dc%\n\ +ig%ic%dc%ht%ik%dc%ht%hk%hu%hf%dc%ii%hj%hk%he%\n\ +hj%dc%hv%hh%dc%if%hj%hg%dc%hh%hk%hi%ie%dc%fk%\n\ +dc%ii%hv%ig%hr%hf%dc%he%hj%hv%hv%ie%hg%du", + expectedOutput: "I saw myself sitting in the crotch of the this fig tree, starving to death, just because I couldn't make up my mind which of the figs I would choose.", + recipeConfig: [ + { + "op": "Modhex解码", + "args": [ + "百分号" + ] + } + ] + + }, + { + name: "Mutiline Modhex with semicolon to ASCII (Semi-colon Mode)", + input: "fk;dc;ie;hb;ii;dc;ht;ik;ie;hg;hr;hh;dc;ie;hk;\n\ +if;if;hk;hu;hi;dc;hk;hu;dc;if;hj;hg;dc;he;id;\n\ +hv;if;he;hj;dc;hv;hh;dc;if;hj;hg;dc;if;hj;hk;\n\ +ie;dc;hh;hk;hi;dc;if;id;hg;hg;dr;dc;ie;if;hb;\n\ +id;ih;hk;hu;hi;dc;if;hv;dc;hf;hg;hb;if;hj;dr;\n\ +dc;hl;ig;ie;if;dc;hd;hg;he;hb;ig;ie;hg;dc;fk;\n\ +dc;he;hv;ig;hr;hf;hu;di;if;dc;ht;hb;hn;hg;dc;\n\ +ig;ic;dc;ht;ik;dc;ht;hk;hu;hf;dc;ii;hj;hk;he;\n\ +hj;dc;hv;hh;dc;if;hj;hg;dc;hh;hk;hi;ie;dc;fk;\n\ +dc;ii;hv;ig;hr;hf;dc;he;hj;hv;hv;ie;hg;du", + expectedOutput: "I saw myself sitting in the crotch of the this fig tree, starving to death, just because I couldn't make up my mind which of the figs I would choose.", + recipeConfig: [ + { + "op": "Modhex解码", + "args": [ + "分号" + ] + } + ] + + }, + { + name: "ASCII to Modhex with comma and line breaks", + input: "aberystwyth", + expectedOutput: "hb,hd,hg,id,\nik,ie,if,ii,\nik,if,hj", + recipeConfig: [ + { + "op": "Modhex编码", + "args": [ + "逗号", + 4 + ] + } + ] + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/MorseCode.mjs b/plugins/srktoolbox/tests/operations/tests/MorseCode.mjs new file mode 100644 index 00000000..d44b3b2f --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/MorseCode.mjs @@ -0,0 +1,36 @@ +/** + * Base58 tests. + * + * @author tlwr [toby@toby.codes] + * + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "To Morse Code: 'SOS'", + input: "SOS", + expectedOutput: "... --- ...", + recipeConfig: [ + { + op: "摩尔斯电码编码", + args: ["-/.", "空格", "换行"], + }, + ], + }, + { + name: "From Morse Code '... --- ...'", + input: "... --- ...", + expectedOutput: "SOS", + recipeConfig: [ + { + op: "摩尔斯电码解码", + args: ["空格", "换行"], + }, + ], + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/MultipleBombe.mjs b/plugins/srktoolbox/tests/operations/tests/MultipleBombe.mjs new file mode 100644 index 00000000..adec6174 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/MultipleBombe.mjs @@ -0,0 +1,49 @@ +/** + * Bombe machine tests. + * @author s2224834 + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Multi-Bombe: 3 rotor", + input: "BBYFLTHHYIJQAYBBYS", + expectedMatch: /LGA<\/td> {2}SS<\/td> {2}VFISUSGTKSTMPSUNAK<\/td>/, + recipeConfig: [ + { + "op": "Multiple Bombe", + "args": [ + // I, II and III + "User defined", + "EKMFLGDQVZNTOWYHXUSPAIBRCJLHSC<\/td>SS<\/td>HHHSSSGQUUQPKSEKWK<\/td>/, + recipeConfig: [ + { + "op": "Multiple Bombe", + "args": [ + // I, II and III + "User defined", + "EKMFLGDQVZNTOWYHXUSPAIBRCJ Bob", + input: `-----BEGIN PGP MESSAGE----- +Version: Keybase OpenPGP v2.0.77 +Comment: https://keybase.io/crypto + +wTwDhHCB1rfyD4MBAX9ld8xGcf2v+X+pwINN0R0TvkWxNesKOQIKPV01AH8JG0J+ ++yFqLXqDHgYSLANNamfSwQoBOTWuh/5V6gpiXVm2oLHPv997AtoD/kVQrqylF5Xo +HUsqPGtSgBA5WPX8tMoHKuqWxEy9FviLnIv73OZN0Ph70uo2E+QIv0Qx27znK0Jy +KDSERvcldgShmVbDP3Pxtxkfr9xa2gar5f0OPovOmKGsTGciQJqPkclRwzIXg12L +hyd2ElYOMf6vg/yOc06sX4Ih1Tn6JkYqMVJydykMv3g4Z8OXTfwrMLxwO1n3ZB/T +OLdhBdsnREnyCqntBVjMKoRTQhfwq48n7b6caZ+aCPISdDIyDKBpxEzXaNBeEY2V +GCqORM9WhsQ4A6pAx2SP694qH5vgOwrYrgeOU17oK++mzd1GyU2CXoFi73/PANJD +TdC3hGr+S4XeuqZ368QG1cBWhNybsOu5sM2YbArb71ZMYuLDp+VolJbEkVf4c/dD +pVEOaX39NVKe6HcpOiw+CFO6GEkQqCXNprWK6ivBHzkAlF2pjjqlS6qhWxFPicSD ++1ZKM1fmZu99bhTmdqE3MJx//QMu7mvlHaM85OQkWhWPBxGw/60GVBX9YtvUtfMS +IOE1W/Zqmqzq+4frwnzWwYv9/U1RwIs/qlFVnzliREOzW+om8EncSSd7fQ== +=fEAT +-----END PGP MESSAGE----- +`, + expectedOutput: `签名: PGP key ID:DF98E485 +PGP指纹:e94e06dd0b3744a0e970de9d84246548df98e485 +签名时间: Tue, 29 May 2018 15:44:52 GMT +---------------------------------- +${UTF8_TEXT}`, + recipeConfig: [ + { + "op": "PGP解密并验证", + "args": [ALICE_PUBLIC, BOB_PRIVATE, ""] + } + ] + }, + { + name: "PGP Decrypt: ASCII, Alice -> Bob", + input: `-----BEGIN PGP MESSAGE----- +Version: Keybase OpenPGP v2.0.77 +Comment: https://keybase.io/crypto + +wYwDPtlTQFIjCzoBBACSlbN7tmQVxR5ZD0rvCwXUkxO3RU8WgBkkmrTCUs9a+xrS +F9HuKcpX/N6XrwTXyuX3BN2tGys4zd6nHV8jYqBoIyWJsWe3viTa1dh/x4183+GP +fP61gizi3pj0gi2vfGnMhnThbdiO32PVKAeHLHBK+r3XlXZ0kzZCQKRgd55yr9Kk +Aa4SR+qpvtdobkDzbnbhcPLR6CQ8TMjTiNXEpgTc1i0JcP8jaMVFzBt8qgmDMdqU +H2qMY1O7hezH3fp+EZzCAccJMtK7VPk13WAgMRH22HirG4aK1i75IVOtjBgObzDh +8zKua7QLi6wJD/AtQ+D3/NgVpzoXwdoLvTjEcAyy+YWNWkJF/jvx3XV1Q/Fz7sHJ +/bspORYvbi591S4U0m4pikwiOZk= +=AVb/ +-----END PGP MESSAGE-----`, + expectedOutput: ASCII_TEXT, + recipeConfig: [ + { + "op": "PGP解密", + "args": [ALICE_PRIVATE, ""] + } + ] + }, + { + name: "PGP Verify: ASCII, Alice", + input: `-----BEGIN PGP SIGNED MESSAGE----- +Hash: SHA256 + +A common mistake that people make when trying to design something completely foolproof is to underestimate the ingenuity of complete fools. +-----BEGIN PGP SIGNATURE----- + +iLMEAQEIAB0WIQRLbJy6MLpYOr9qojE+2VNAUiMLOgUCXRTsvwAKCRA+2VNAUiML +OuaHBADMMNtsuN92Fb+UrDimsv6TDQpbJhDkwp9kZdKYP5HAmSYAhXBG7N+YCMw+ +v2FSpUu9jJiPBm1K1SEwLufQVexoRv6RsBNolRFB07sArau0s0DnIXUchCZWvyTP +1KsjBnDr84U2b11H58g4DlTT4gQrz30rFuHz9AGmPAtDHbSXIA== +=vnk/ +-----END PGP SIGNATURE-----`, + expectedOutput: `签名:PGP key ID: DF98E485 +PGP指纹: e94e06dd0b3744a0e970de9d84246548df98e485 +签名日期: Thu, 27 Jun 2019 16:20:15 GMT +---------------------------------- +A common mistake that people make when trying to design something completely foolproof is to underestimate the ingenuity of complete fools.`, + recipeConfig: [ + { + "op": "PGP验证", + "args": [ALICE_PUBLIC] + } + ] + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/PHP.mjs b/plugins/srktoolbox/tests/operations/tests/PHP.mjs new file mode 100644 index 00000000..57b2af70 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/PHP.mjs @@ -0,0 +1,70 @@ +/** + * PHP tests. + * + * @author Jarmo van Lenthe + * + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "PHP Deserialize empty array", + input: "a:0:{}", + expectedOutput: "{}", + recipeConfig: [ + { + op: "PHP反序列化", + args: [true], + }, + ], + }, + { + name: "PHP Deserialize integer", + input: "i:10;", + expectedOutput: "10", + recipeConfig: [ + { + op: "PHP反序列化", + args: [true], + }, + ], + }, + { + name: "PHP Deserialize string", + input: "s:17:\"PHP Serialization\";", + expectedOutput: "\"PHP Serialization\"", + recipeConfig: [ + { + op: "PHP反序列化", + args: [true], + }, + ], + }, + { + name: "PHP Deserialize array (JSON)", + input: "a:2:{s:1:\"a\";i:10;i:0;a:1:{s:2:\"ab\";b:1;}}", + expectedOutput: "{\"a\": 10,\"0\": {\"ab\": true}}", + recipeConfig: [ + { + op: "PHP反序列化", + args: [true], + }, + ], + }, + { + name: "PHP Deserialize array (non-JSON)", + input: "a:2:{s:1:\"a\";i:10;i:0;a:1:{s:2:\"ab\";b:1;}}", + expectedOutput: "{\"a\": 10,0: {\"ab\": true}}", + recipeConfig: [ + { + op: "PHP反序列化", + args: [false], + }, + ], + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/PHPSerialize.mjs b/plugins/srktoolbox/tests/operations/tests/PHPSerialize.mjs new file mode 100644 index 00000000..fa6e87c5 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/PHPSerialize.mjs @@ -0,0 +1,112 @@ +/** + * PHP Serialization tests. + * + * @author brun0ne [brunonblok@gmail.com] + * + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ + +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "PHP Serialize empty array", + input: "[]", + expectedOutput: "a:0:{}", + recipeConfig: [ + { + op: "PHP Serialize", + args: [] + } + ] + }, + { + name: "PHP Serialize empty object", + input: "{}", + expectedOutput: "a:0:{}", + recipeConfig: [ + { + op: "PHP Serialize", + args: [] + } + ] + }, + { + name: "PHP Serialize null", + input: "null", + expectedOutput: "N;", + recipeConfig: [ + { + op: "PHP Serialize", + args: [] + } + ] + }, + { + name: "PHP Serialize integer", + input: "10", + expectedOutput: "i:10;", + recipeConfig: [ + { + op: "PHP Serialize", + args: [] + } + ] + }, + { + name: "PHP Serialize float", + input: "14.523", + expectedOutput: "d:14.523;", + recipeConfig: [ + { + op: "PHP Serialize", + args: [] + } + ] + }, + { + name: "PHP Serialize boolean", + input: "[true, false]", + expectedOutput: "a:2:{i:0;b:1;i:1;b:0;}", + recipeConfig: [ + { + op: "PHP Serialize", + args: [] + } + ] + }, + { + name: "PHP Serialize string", + input: "\"Test string to serialize\"", + expectedOutput: "s:24:\"Test string to serialize\";", + recipeConfig: [ + { + op: "PHP Serialize", + args: [] + } + ] + }, + { + name: "PHP Serialize object", + input: "{\"a\": 10,\"0\": {\"ab\": true}}", + expectedOutput: "a:2:{s:1:\"0\";a:1:{s:2:\"ab\";b:1;}s:1:\"a\";i:10;}", + recipeConfig: [ + { + op: "PHP Serialize", + args: [] + } + ] + }, + { + name: "PHP Serialize array", + input: "[1,\"abc\",true,{\"x\":1,\"y\":2}]", + expectedOutput: "a:4:{i:0;i:1;i:1;s:3:\"abc\";i:2;b:1;i:3;a:2:{s:1:\"x\";i:1;s:1:\"y\";i:2;}}", + recipeConfig: [ + { + op: "PHP Serialize", + args: [] + } + ] + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/ParseCSR.mjs b/plugins/srktoolbox/tests/operations/tests/ParseCSR.mjs new file mode 100644 index 00000000..f4a6e08f --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/ParseCSR.mjs @@ -0,0 +1,986 @@ +/** + * Parse CSR tests. + * + * @author jkataja + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import TestRegister from "../../lib/TestRegister.mjs"; + +// openssl req -newkey rsa:1024 -keyout test-rsa-1024.key -out test-rsa-1024.csr \ +// -subj "/C=CH/ST=Zurich/L=Zurich/O=Example RE/OU=IT Department/CN=example.com" \ +// -addext "subjectAltName = DNS:example.com,DNS:www.example.com" \ +// -addext "basicConstraints = critical,CA:FALSE" \ +// -addext "keyUsage = critical,digitalSignature,keyEncipherment" \ +// -addext "extendedKeyUsage = serverAuth" +const IN_EXAMPLE_COM_RSA_1024 = `-----BEGIN CERTIFICATE REQUEST----- +MIICHzCCAYgCAQAwcjELMAkGA1UEBhMCQ0gxDzANBgNVBAgMBlp1cmljaDEPMA0G +A1UEBwwGWnVyaWNoMRMwEQYDVQQKDApFeGFtcGxlIFJFMRYwFAYDVQQLDA1JVCBE +ZXBhcnRtZW50MRQwEgYDVQQDDAtleGFtcGxlLmNvbTCBnzANBgkqhkiG9w0BAQEF +AAOBjQAwgYkCgYEArrTrLI6FkzjX8FZfclt2ox1Dz7KRwt5f6ffZic7twLAKJ4ao +/H3APjwoFVUXGjiNj/XF2RlId4UxB1b6CgWjujBb9W51rTdvfWLyAHsrLcptpVz+ +V9Y8X9kEFCRGGDyG5+X+Nu6COzTpUPDj4bIIX/uPk3fDYDEqLClVy8/VS48CAwEA +AaBtMGsGCSqGSIb3DQEJDjFeMFwwJwYDVR0RBCAwHoILZXhhbXBsZS5jb22CD3d3 +dy5leGFtcGxlLmNvbTAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIFoDATBgNV +HSUEDDAKBggrBgEFBQcDATANBgkqhkiG9w0BAQsFAAOBgQB0mUlPgt6pt/kjD0pz +OUNk5e9nBFQYQGuGIHGYbPX3mi4Wd9vUCdPixtPSTunHWs2cxX2nM8+MdcNTY+7Q +NFgFNIvSXhbqMYoHAAApMHJOxiWpBFdYKp3tESnlgh2lUh7lQtmOjD4a1dzfU8PU +oViyp+UJGasN2WRd+4VtaPw64w== +-----END CERTIFICATE REQUEST-----`; + +const OUT_EXAMPLE_COM_RSA_1024 = `主体 + C = CH + ST = Zurich + L = Zurich + O = Example RE + OU = IT Department + CN = example.com +公钥 + 算法: RSA + 长度: 1024 bits + 模数: 00:ae:b4:eb:2c:8e:85:93:38:d7:f0:56:5f:72:5b:76: + a3:1d:43:cf:b2:91:c2:de:5f:e9:f7:d9:89:ce:ed:c0: + b0:0a:27:86:a8:fc:7d:c0:3e:3c:28:15:55:17:1a:38: + 8d:8f:f5:c5:d9:19:48:77:85:31:07:56:fa:0a:05:a3: + ba:30:5b:f5:6e:75:ad:37:6f:7d:62:f2:00:7b:2b:2d: + ca:6d:a5:5c:fe:57:d6:3c:5f:d9:04:14:24:46:18:3c: + 86:e7:e5:fe:36:ee:82:3b:34:e9:50:f0:e3:e1:b2:08: + 5f:fb:8f:93:77:c3:60:31:2a:2c:29:55:cb:cf:d5:4b: + 8f + 指数: 65537 (0x10001) +签名 + 算法: SHA256withRSA + 签名: 74:99:49:4f:82:de:a9:b7:f9:23:0f:4a:73:39:43:64: + e5:ef:67:04:54:18:40:6b:86:20:71:98:6c:f5:f7:9a: + 2e:16:77:db:d4:09:d3:e2:c6:d3:d2:4e:e9:c7:5a:cd: + 9c:c5:7d:a7:33:cf:8c:75:c3:53:63:ee:d0:34:58:05: + 34:8b:d2:5e:16:ea:31:8a:07:00:00:29:30:72:4e:c6: + 25:a9:04:57:58:2a:9d:ed:11:29:e5:82:1d:a5:52:1e: + e5:42:d9:8e:8c:3e:1a:d5:dc:df:53:c3:d4:a1:58:b2: + a7:e5:09:19:ab:0d:d9:64:5d:fb:85:6d:68:fc:3a:e3 +请求的扩展程序 + 证书基本约束: 关键 + CA = 否 + 证书密钥用法: 关键 + 签名 + 密钥加密 + 扩展密钥用法: + TLS WWW 服务器身份验证 + 主体备用名称: + DNS: example.com + DNS: www.example.com`; + +// openssl req -newkey rsa:2048 -keyout test-rsa-2048.key -out test-rsa-2048.csr \ +// -subj "/C=CH/ST=Zurich/L=Zurich/O=Example RE/OU=IT Department/CN=example.com" \ +// -addext "subjectAltName = DNS:example.com,DNS:www.example.com" \ +// -addext "basicConstraints = critical,CA:FALSE" \ +// -addext "keyUsage = critical,digitalSignature,keyEncipherment" \ +// -addext "extendedKeyUsage = serverAuth" +const IN_EXAMPLE_COM_RSA_2048 = `-----BEGIN CERTIFICATE REQUEST----- +MIIDJDCCAgwCAQAwcjELMAkGA1UEBhMCQ0gxDzANBgNVBAgMBlp1cmljaDEPMA0G +A1UEBwwGWnVyaWNoMRMwEQYDVQQKDApFeGFtcGxlIFJFMRYwFAYDVQQLDA1JVCBE +ZXBhcnRtZW50MRQwEgYDVQQDDAtleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBAKPogLmWPuK/IGdct2v/3MFKVaVeKp2Hl5at/zDFLCAe +51bwh7BqNVJEci4ApwlXA1WVmQPBFBJlYwQZVjz5UAN2CmNHxud5nV03YmZ2/Iml +RzpKcZMPqU+liJCC04L+XIbOdx+Vz52dF++Cc+FuSFq803yW+qefK8JsJNO9KuPx +RLYKSAADa9MIJisru1PzcBAOcimOmNnFWuo+LKsd4lU30OExDdKHwtyt62Mj1c3o +lO1JjvkjtWWjwHI+0EgTjvkeXlcUYZvvLlysdKERMRozvMTGqqoHWCgWl+Rq9Z6P +TgNsRO4CKug1Zwmh8y6acZ7sYb/dar8HOeqJnc0pCv8CAwEAAaBtMGsGCSqGSIb3 +DQEJDjFeMFwwJwYDVR0RBCAwHoILZXhhbXBsZS5jb22CD3d3dy5leGFtcGxlLmNv +bTAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEF +BQcDATANBgkqhkiG9w0BAQsFAAOCAQEAG0cjfRBY1pBzu+jf7yMQrK5mQrh72air +VuXHmochmyUxyt0G7ovnNhKEr+X9snShJLi5qlyvnb2roiwlCmuwGIZxErN1svQL +Z3kQNZgH+Vyu5IRL2DlPs5AAxVmzPpbnbXNhMHyAK/ziLcU031O1PoCpxwfvPsjW +HWOCjbZUVaJnxdp8AHqImoGAiVhJwc37feFvb2UQlLedUypQkPg/poNWduaRDoj8 +m9cpVxuxGLtONBnohzohnFECytSXWEXPIj8L9SpYK97G02nJYYCAcb5BF11Alfux +sNxtsr6zgPaLRrvOBT11WxJVKerbhfezAJ3naem1eM3VLxCGWwMwxg== +-----END CERTIFICATE REQUEST-----`; + +const OUT_EXAMPLE_COM_RSA_2048 = `主体 + C = CH + ST = Zurich + L = Zurich + O = Example RE + OU = IT Department + CN = example.com +公钥 + 算法: RSA + 长度: 2048 bits + 模数: 00:a3:e8:80:b9:96:3e:e2:bf:20:67:5c:b7:6b:ff:dc: + c1:4a:55:a5:5e:2a:9d:87:97:96:ad:ff:30:c5:2c:20: + 1e:e7:56:f0:87:b0:6a:35:52:44:72:2e:00:a7:09:57: + 03:55:95:99:03:c1:14:12:65:63:04:19:56:3c:f9:50: + 03:76:0a:63:47:c6:e7:79:9d:5d:37:62:66:76:fc:89: + a5:47:3a:4a:71:93:0f:a9:4f:a5:88:90:82:d3:82:fe: + 5c:86:ce:77:1f:95:cf:9d:9d:17:ef:82:73:e1:6e:48: + 5a:bc:d3:7c:96:fa:a7:9f:2b:c2:6c:24:d3:bd:2a:e3: + f1:44:b6:0a:48:00:03:6b:d3:08:26:2b:2b:bb:53:f3: + 70:10:0e:72:29:8e:98:d9:c5:5a:ea:3e:2c:ab:1d:e2: + 55:37:d0:e1:31:0d:d2:87:c2:dc:ad:eb:63:23:d5:cd: + e8:94:ed:49:8e:f9:23:b5:65:a3:c0:72:3e:d0:48:13: + 8e:f9:1e:5e:57:14:61:9b:ef:2e:5c:ac:74:a1:11:31: + 1a:33:bc:c4:c6:aa:aa:07:58:28:16:97:e4:6a:f5:9e: + 8f:4e:03:6c:44:ee:02:2a:e8:35:67:09:a1:f3:2e:9a: + 71:9e:ec:61:bf:dd:6a:bf:07:39:ea:89:9d:cd:29:0a: + ff + 指数: 65537 (0x10001) +签名 + 算法: SHA256withRSA + 签名: 1b:47:23:7d:10:58:d6:90:73:bb:e8:df:ef:23:10:ac: + ae:66:42:b8:7b:d9:a8:ab:56:e5:c7:9a:87:21:9b:25: + 31:ca:dd:06:ee:8b:e7:36:12:84:af:e5:fd:b2:74:a1: + 24:b8:b9:aa:5c:af:9d:bd:ab:a2:2c:25:0a:6b:b0:18: + 86:71:12:b3:75:b2:f4:0b:67:79:10:35:98:07:f9:5c: + ae:e4:84:4b:d8:39:4f:b3:90:00:c5:59:b3:3e:96:e7: + 6d:73:61:30:7c:80:2b:fc:e2:2d:c5:34:df:53:b5:3e: + 80:a9:c7:07:ef:3e:c8:d6:1d:63:82:8d:b6:54:55:a2: + 67:c5:da:7c:00:7a:88:9a:81:80:89:58:49:c1:cd:fb: + 7d:e1:6f:6f:65:10:94:b7:9d:53:2a:50:90:f8:3f:a6: + 83:56:76:e6:91:0e:88:fc:9b:d7:29:57:1b:b1:18:bb: + 4e:34:19:e8:87:3a:21:9c:51:02:ca:d4:97:58:45:cf: + 22:3f:0b:f5:2a:58:2b:de:c6:d3:69:c9:61:80:80:71: + be:41:17:5d:40:95:fb:b1:b0:dc:6d:b2:be:b3:80:f6: + 8b:46:bb:ce:05:3d:75:5b:12:55:29:ea:db:85:f7:b3: + 00:9d:e7:69:e9:b5:78:cd:d5:2f:10:86:5b:03:30:c6 +请求的扩展程序 + 证书基本约束: 关键 + CA = 否 + 证书密钥用法: 关键 + 签名 + 密钥加密 + 扩展密钥用法: + TLS WWW 服务器身份验证 + 主体备用名称: + DNS: example.com + DNS: www.example.com`; + +// openssl genpkey -genparam -algorithm ec -pkeyopt ec_paramgen_curve:P-256 -out test-ec-param.pem +// openssl req -newkey ec:test-ec-param.pem -keyout test-ec.key -out test-ec.csr \ +// -subj "/C=CH/ST=Zurich/L=Zurich/O=Example RE/OU=IT Department/CN=example.com" \ +// -addext "subjectAltName = DNS:example.com,DNS:www.example.com" \ +// -addext "basicConstraints = critical,CA:FALSE" \ +// -addext "keyUsage = critical,digitalSignature,keyEncipherment" \ +// -addext "extendedKeyUsage = serverAuth" +const IN_EXAMPLE_COM_EC_P256 = `-----BEGIN CERTIFICATE REQUEST----- +MIIBmzCCAUECAQAwcjELMAkGA1UEBhMCQ0gxDzANBgNVBAgMBlp1cmljaDEPMA0G +A1UEBwwGWnVyaWNoMRMwEQYDVQQKDApFeGFtcGxlIFJFMRYwFAYDVQQLDA1JVCBE +ZXBhcnRtZW50MRQwEgYDVQQDDAtleGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqG +SM49AwEHA0IABAmpYXNh+L9E0Q3sLhrO+MF1XgKCfqJntrOyIkrGwoiQftHbJWTA +6duxQhU/3d9B+SN/ibeKY+xeiNBrs2eTYZ6gbTBrBgkqhkiG9w0BCQ4xXjBcMCcG +A1UdEQQgMB6CC2V4YW1wbGUuY29tgg93d3cuZXhhbXBsZS5jb20wDAYDVR0TAQH/ +BAIwADAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwCgYIKoZI +zj0EAwIDSAAwRQIgQkum/qaLzE3QZ3WD00uLpalUn113FObd7rM5Mr3HQwQCIQCr +7OjzYI9v7qIJp/E9N16XfJN87G2ZVIZ4FuPXVjokCQ== +-----END CERTIFICATE REQUEST-----`; + +const OUT_EXAMPLE_COM_EC_P256 = `主体 + C = CH + ST = Zurich + L = Zurich + O = Example RE + OU = IT Department + CN = example.com +公钥 + 算法: ECDSA + 长度: 256 bits + Pub: 04:09:a9:61:73:61:f8:bf:44:d1:0d:ec:2e:1a:ce:f8: + c1:75:5e:02:82:7e:a2:67:b6:b3:b2:22:4a:c6:c2:88: + 90:7e:d1:db:25:64:c0:e9:db:b1:42:15:3f:dd:df:41: + f9:23:7f:89:b7:8a:63:ec:5e:88:d0:6b:b3:67:93:61: + 9e + ASN1 OID: secp256r1 + NIST CURVE: P-256 +签名 + 算法: SHA256withECDSA + 签名: 30:45:02:20:42:4b:a6:fe:a6:8b:cc:4d:d0:67:75:83: + d3:4b:8b:a5:a9:54:9f:5d:77:14:e6:dd:ee:b3:39:32: + bd:c7:43:04:02:21:00:ab:ec:e8:f3:60:8f:6f:ee:a2: + 09:a7:f1:3d:37:5e:97:7c:93:7c:ec:6d:99:54:86:78: + 16:e3:d7:56:3a:24:09 +请求的扩展程序 + 证书基本约束: 关键 + CA = 否 + 证书密钥用法: 关键 + 签名 + 密钥加密 + 扩展密钥用法: + TLS WWW 服务器身份验证 + 主体备用名称: + DNS: example.com + DNS: www.example.com`; + +// openssl ecparam -name secp384r1 -genkey -noout -out test-ec-key.pem +// openssl req -new -key test-ec-key.pem -out test-ec.csr +// -subj "/C=CH/ST=Zurich/L=Zurich/O=Example RE/OU=IT Department/CN=example.com" +// -addext "subjectAltName = DNS:example.com,DNS:www.example.com" +// -addext "basicConstraints = critical,CA:FALSE" +// -addext "keyUsage = critical,digitalSignature,keyEncipherment" +// -addext "extendedKeyUsage = serverAuth" +const IN_EXAMPLE_COM_EC_P384 = `-----BEGIN CERTIFICATE REQUEST----- +MIIB2TCCAV4CAQAwcjELMAkGA1UEBhMCQ0gxDzANBgNVBAgMBlp1cmljaDEPMA0G +A1UEBwwGWnVyaWNoMRMwEQYDVQQKDApFeGFtcGxlIFJFMRYwFAYDVQQLDA1JVCBE +ZXBhcnRtZW50MRQwEgYDVQQDDAtleGFtcGxlLmNvbTB2MBAGByqGSM49AgEGBSuB +BAAiA2IABE3rpRO164NtXx2kYMP1zlN7YgHEincO4YgwoyAYyJm3LwcbR+XyKg6A +/i+DUaGWa2FQ+f8w8VmEUFAgLozVxwnntPOCSODrXAQwJFPLCqs7m3o8OuzU3t07 +POGhPtj7f6BtMGsGCSqGSIb3DQEJDjFeMFwwJwYDVR0RBCAwHoILZXhhbXBsZS5j +b22CD3d3dy5leGFtcGxlLmNvbTAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIF +oDATBgNVHSUEDDAKBggrBgEFBQcDATAKBggqhkjOPQQDAgNpADBmAjEAlq7RaEXU +aNHEC+qfuIitonWHOatm+qiiaNSh80QjLw5P1rszg9yQQigHd8cD7I4DAjEAzmo1 +DLpcESwZCBrh3sPflDA38TZjoedRNeWcVxdn1QmwDWMeprD/zgPAey8GOmyj +-----END CERTIFICATE REQUEST-----`; + +const OUT_EXAMPLE_COM_EC_P384 = `主体 + C = CH + ST = Zurich + L = Zurich + O = Example RE + OU = IT Department + CN = example.com +公钥 + 算法: ECDSA + 长度: 384 bits + Pub: 04:4d:eb:a5:13:b5:eb:83:6d:5f:1d:a4:60:c3:f5:ce: + 53:7b:62:01:c4:8a:77:0e:e1:88:30:a3:20:18:c8:99: + b7:2f:07:1b:47:e5:f2:2a:0e:80:fe:2f:83:51:a1:96: + 6b:61:50:f9:ff:30:f1:59:84:50:50:20:2e:8c:d5:c7: + 09:e7:b4:f3:82:48:e0:eb:5c:04:30:24:53:cb:0a:ab: + 3b:9b:7a:3c:3a:ec:d4:de:dd:3b:3c:e1:a1:3e:d8:fb: + 7f + ASN1 OID: secp384r1 + NIST CURVE: P-384 +签名 + 算法: SHA256withECDSA + 签名: 30:66:02:31:00:96:ae:d1:68:45:d4:68:d1:c4:0b:ea: + 9f:b8:88:ad:a2:75:87:39:ab:66:fa:a8:a2:68:d4:a1: + f3:44:23:2f:0e:4f:d6:bb:33:83:dc:90:42:28:07:77: + c7:03:ec:8e:03:02:31:00:ce:6a:35:0c:ba:5c:11:2c: + 19:08:1a:e1:de:c3:df:94:30:37:f1:36:63:a1:e7:51: + 35:e5:9c:57:17:67:d5:09:b0:0d:63:1e:a6:b0:ff:ce: + 03:c0:7b:2f:06:3a:6c:a3 +请求的扩展程序 + 证书基本约束: 关键 + CA = 否 + 证书密钥用法: 关键 + 签名 + 密钥加密 + 扩展密钥用法: + TLS WWW 服务器身份验证 + 主体备用名称: + DNS: example.com + DNS: www.example.com`; + +// openssl ecparam -name secp521r1 -genkey -noout -out test-ec-key.pem +// openssl req -new -key test-ec-key.pem -out test-ec.csr +// -subj "/C=CH/ST=Zurich/L=Zurich/O=Example RE/OU=IT Department/CN=example.com" +// -addext "subjectAltName = DNS:example.com,DNS:www.example.com" +// -addext "basicConstraints = critical,CA:FALSE" +// -addext "keyUsage = critical,digitalSignature,keyEncipherment" +// -addext "extendedKeyUsage = serverAuth" +const IN_EXAMPLE_COM_EC_P521 = `-----BEGIN CERTIFICATE REQUEST----- +MIICIjCCAYQCAQAwcjELMAkGA1UEBhMCQ0gxDzANBgNVBAgMBlp1cmljaDEPMA0G +A1UEBwwGWnVyaWNoMRMwEQYDVQQKDApFeGFtcGxlIFJFMRYwFAYDVQQLDA1JVCBE +ZXBhcnRtZW50MRQwEgYDVQQDDAtleGFtcGxlLmNvbTCBmzAQBgcqhkjOPQIBBgUr +gQQAIwOBhgAEAKf5BRB57svfglRz5dM0bnJAnieMFjNjOFca5/pJ2bOpORkp9Uol +x//mHY5WOMYYC/xvM5lJRcmUnL791zQ6rf6pAD/CrEpDF2svae6e5nA/fN2XsB98 +xjmkTpYZVC5nFT83Ceo9J0kHbvliYlAMsEOO60qGghyWV7myiDgORfE+POU3oG0w +awYJKoZIhvcNAQkOMV4wXDAnBgNVHREEIDAeggtleGFtcGxlLmNvbYIPd3d3LmV4 +YW1wbGUuY29tMAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQM +MAoGCCsGAQUFBwMBMAoGCCqGSM49BAMCA4GLADCBhwJBDeIpSuvIT+kiE0ZnJwPS +DVik93CLqjFm5Ieq02d81GwusSgAA82WlZZVZRsTEjkZXtk96zMBnh5/uxk+wN+j ++PoCQgEDmXREwi0BPkHj6QlktE+7SLELVkrd75D9mfw/SV6ZJiLiLIT9yeoA0Zon +uhcl2rK/DLQutuJF6JIBe5s7lieKfQ== +-----END CERTIFICATE REQUEST-----`; + +const OUT_EXAMPLE_COM_EC_P521 = `主体 + C = CH + ST = Zurich + L = Zurich + O = Example RE + OU = IT Department + CN = example.com +公钥 + 算法: ECDSA + 长度: 521 bits + Pub: 04:00:a7:f9:05:10:79:ee:cb:df:82:54:73:e5:d3:34: + 6e:72:40:9e:27:8c:16:33:63:38:57:1a:e7:fa:49:d9: + b3:a9:39:19:29:f5:4a:25:c7:ff:e6:1d:8e:56:38:c6: + 18:0b:fc:6f:33:99:49:45:c9:94:9c:be:fd:d7:34:3a: + ad:fe:a9:00:3f:c2:ac:4a:43:17:6b:2f:69:ee:9e:e6: + 70:3f:7c:dd:97:b0:1f:7c:c6:39:a4:4e:96:19:54:2e: + 67:15:3f:37:09:ea:3d:27:49:07:6e:f9:62:62:50:0c: + b0:43:8e:eb:4a:86:82:1c:96:57:b9:b2:88:38:0e:45: + f1:3e:3c:e5:37 + ASN1 OID: secp521r1 + NIST CURVE: P-521 +签名 + 算法: SHA256withECDSA + 签名: 30:81:87:02:41:0d:e2:29:4a:eb:c8:4f:e9:22:13:46: + 67:27:03:d2:0d:58:a4:f7:70:8b:aa:31:66:e4:87:aa: + d3:67:7c:d4:6c:2e:b1:28:00:03:cd:96:95:96:55:65: + 1b:13:12:39:19:5e:d9:3d:eb:33:01:9e:1e:7f:bb:19: + 3e:c0:df:a3:f8:fa:02:42:01:03:99:74:44:c2:2d:01: + 3e:41:e3:e9:09:64:b4:4f:bb:48:b1:0b:56:4a:dd:ef: + 90:fd:99:fc:3f:49:5e:99:26:22:e2:2c:84:fd:c9:ea: + 00:d1:9a:27:ba:17:25:da:b2:bf:0c:b4:2e:b6:e2:45: + e8:92:01:7b:9b:3b:96:27:8a:7d +请求的扩展程序 + 证书基本约束: 关键 + CA = 否 + 证书密钥用法: 关键 + 签名 + 密钥加密 + 扩展密钥用法: + TLS WWW 服务器身份验证 + 主体备用名称: + DNS: example.com + DNS: www.example.com`; + +// openssl dsaparam -out dsaparam.pem 1024 +// openssl gendsa -out dsakey.pem dsaparam.pem +// openssl req -new -key dsakey.pem -out test-dsa.csr \ +// -subj "/C=CH/ST=Zurich/L=Zurich/O=Example RE/OU=IT Department/CN=example.com" \ +// -addext "subjectAltName = DNS:example.com,DNS:www.example.com" \ +// -addext "basicConstraints = critical,CA:FALSE" \ +// -addext "keyUsage = critical,digitalSignature,keyEncipherment" \ +// -addext "extendedKeyUsage = serverAuth" +const IN_EXAMPLE_COM_DSA_1024 = `-----BEGIN CERTIFICATE REQUEST----- +MIIC/jCCAqoCAQAwcjELMAkGA1UEBhMCQ0gxDzANBgNVBAgMBlp1cmljaDEPMA0G +A1UEBwwGWnVyaWNoMRMwEQYDVQQKDApFeGFtcGxlIFJFMRYwFAYDVQQLDA1JVCBE +ZXBhcnRtZW50MRQwEgYDVQQDDAtleGFtcGxlLmNvbTCCAcAwggE0BgcqhkjOOAQB +MIIBJwKBgQD8vvCmdM8wttdbq3kWigTEnnug4+2SLMl2RNXrlCQjmuZc7tGMyP1u +gsSc9Pxd/tMrPKRawFP5SvUOkZ4cIrujdJVTb/hlfnGH4cWACe8EupwRzoqwZB1x +awiHFzL9G6Go0HOy7bSbRdxBIYu46fnxNsDFf7lMlcBOKdq4Y12kvwIdAN4/vtK9 +KxhQfcrrzHsPXW+/xW0CMfr+NQir8PkCgYEAiNdM7IRZhXPaGRtGDpepSoRAf4uQ +LWY9q+vFUx4fVRSSgwKBKLjW+BvzE2eJq0pXv7O09QHOghtcwzY3UrdN952sjUkJ +LItt+5FxB7/JqCBPRrrVsyGEjR3+WbeI3wl6OvQFxm/OTNTTkemFdAfpT/YDSw+n +1xLODTfegT/oyOoDgYUAAoGBAMz15lRPVAj8cje3ShbuACHPVE85d0Tk0Dw9qUcQ +NCNS6A3STSbUiLGKeiRMGg2v/HM9ivV8tq1rywmgBAwtidcQ6P5yqYSZs6z3x9xZ +OzeQ5jXftBQ1GXeU8zi1fC99inFGNixbPFVIz4/KiV0+So44n9ki2ylhbz0YQtpU +wMF+oG0wawYJKoZIhvcNAQkOMV4wXDAnBgNVHREEIDAeggtleGFtcGxlLmNvbYIP +d3d3LmV4YW1wbGUuY29tMAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgWgMBMG +A1UdJQQMMAoGCCsGAQUFBwMBMAsGCWCGSAFlAwQDAgNBADA+Ah0AkTogUUyKE5v9 +ezKrOKpP07i2E9Zz0n/yjIvw4wIdAMB5yVMOEgI877vOFQ7zzf7oDR9eJMYlf4QV +2sQ= +-----END CERTIFICATE REQUEST-----`; + +const OUT_EXAMPLE_COM_DSA_1024 = `主体 + C = CH + ST = Zurich + L = Zurich + O = Example RE + OU = IT Department + CN = example.com +公钥 + 算法: DSA + 长度: 1024 bits + Pub: 00:cc:f5:e6:54:4f:54:08:fc:72:37:b7:4a:16:ee:00: + 21:cf:54:4f:39:77:44:e4:d0:3c:3d:a9:47:10:34:23: + 52:e8:0d:d2:4d:26:d4:88:b1:8a:7a:24:4c:1a:0d:af: + fc:73:3d:8a:f5:7c:b6:ad:6b:cb:09:a0:04:0c:2d:89: + d7:10:e8:fe:72:a9:84:99:b3:ac:f7:c7:dc:59:3b:37: + 90:e6:35:df:b4:14:35:19:77:94:f3:38:b5:7c:2f:7d: + 8a:71:46:36:2c:5b:3c:55:48:cf:8f:ca:89:5d:3e:4a: + 8e:38:9f:d9:22:db:29:61:6f:3d:18:42:da:54:c0:c1: + 7e + P: 00:fc:be:f0:a6:74:cf:30:b6:d7:5b:ab:79:16:8a:04: + c4:9e:7b:a0:e3:ed:92:2c:c9:76:44:d5:eb:94:24:23: + 9a:e6:5c:ee:d1:8c:c8:fd:6e:82:c4:9c:f4:fc:5d:fe: + d3:2b:3c:a4:5a:c0:53:f9:4a:f5:0e:91:9e:1c:22:bb: + a3:74:95:53:6f:f8:65:7e:71:87:e1:c5:80:09:ef:04: + ba:9c:11:ce:8a:b0:64:1d:71:6b:08:87:17:32:fd:1b: + a1:a8:d0:73:b2:ed:b4:9b:45:dc:41:21:8b:b8:e9:f9: + f1:36:c0:c5:7f:b9:4c:95:c0:4e:29:da:b8:63:5d:a4: + bf + Q: 00:de:3f:be:d2:bd:2b:18:50:7d:ca:eb:cc:7b:0f:5d: + 6f:bf:c5:6d:02:31:fa:fe:35:08:ab:f0:f9 + G: 00:88:d7:4c:ec:84:59:85:73:da:19:1b:46:0e:97:a9: + 4a:84:40:7f:8b:90:2d:66:3d:ab:eb:c5:53:1e:1f:55: + 14:92:83:02:81:28:b8:d6:f8:1b:f3:13:67:89:ab:4a: + 57:bf:b3:b4:f5:01:ce:82:1b:5c:c3:36:37:52:b7:4d: + f7:9d:ac:8d:49:09:2c:8b:6d:fb:91:71:07:bf:c9:a8: + 20:4f:46:ba:d5:b3:21:84:8d:1d:fe:59:b7:88:df:09: + 7a:3a:f4:05:c6:6f:ce:4c:d4:d3:91:e9:85:74:07:e9: + 4f:f6:03:4b:0f:a7:d7:12:ce:0d:37:de:81:3f:e8:c8: + ea +签名 + 算法: SHA256withDSA + 签名: + R: 00:91:3a:20:51:4c:8a:13:9b:fd:7b:32:ab:38:aa:4f: + d3:b8:b6:13:d6:73:d2:7f:f2:8c:8b:f0:e3 + S: 00:c0:79:c9:53:0e:12:02:3c:ef:bb:ce:15:0e:f3:cd: + fe:e8:0d:1f:5e:24:c6:25:7f:84:15:da:c4 +请求的扩展程序 + 证书基本约束: 关键 + CA = 否 + 证书密钥用法: 关键 + 签名 + 密钥加密 + 扩展密钥用法: + TLS WWW 服务器身份验证 + 主体备用名称: + DNS: example.com + DNS: www.example.com`; + +// openssl dsaparam -out dsaparam.pem 2048 +// openssl gendsa -out dsakey.pem dsaparam.pem +// openssl req -new -key dsakey.pem -out test-dsa.csr \ +// -subj "/C=CH/ST=Zurich/L=Zurich/O=Example RE/OU=IT Department/CN=example.com" \ +// -addext "subjectAltName = DNS:example.com,DNS:www.example.com" \ +// -addext "basicConstraints = critical,CA:FALSE" \ +// -addext "keyUsage = critical,digitalSignature,keyEncipherment" \ +// -addext "extendedKeyUsage = serverAuth" +const IN_EXAMPLE_COM_DSA_2048 = `-----BEGIN CERTIFICATE REQUEST----- +MIIEfzCCBCwCAQAwcjELMAkGA1UEBhMCQ0gxDzANBgNVBAgMBlp1cmljaDEPMA0G +A1UEBwwGWnVyaWNoMRMwEQYDVQQKDApFeGFtcGxlIFJFMRYwFAYDVQQLDA1JVCBE +ZXBhcnRtZW50MRQwEgYDVQQDDAtleGFtcGxlLmNvbTCCA0IwggI1BgcqhkjOOAQB +MIICKAKCAQEAsvoKmCHcR2y8qQ/kpBHOvlaGifq//F/0zhWSpfjvwqI3g2EjqXL7 +rCYyu9wxoogODo6Dnenxfw1xp3ZIJNCtfrSJyt0AudjOedtVWMSnTndoQVQtYSI0 +mmrBAqFL26i1bmEMxsd6pz2nU3p8yGY/wpYiWwyy+/TZv8a2t58owpw9Qkm4cX4E +Po3ih/XbN6eooOx9ZaErcS9mg3UvwQDm0VYD3ZjSeqwP7YWGyhq7gPJsEiMrft12 +1SjyNz8rkhXzqZFRujjmfTT5dpCC/Z4d7/ZE30tbqHaNDM+YwBrb/aL7PnoWs847 +VpjCVxmVmgIPoMHlTbg29RsIUoFlFScaUQIdAMGwwpzilrReaEqcoX7PY5u4vtV0 +5zuiVIqkdBMCggEAQZhk5qdAYoMvZhPi5TOgysTzQE1FeAEtgypxZI65TpwO/JOr +AX9vYZ/qCYX/ncj455qiPZenl59lo/iQPzhJUubuCevPWJ3dsKRbAyL/5NCwifnf +YBMJGj0UFGL4ekVV0emLL9H5eqYz64w0eV2Sp40O8yCu0qr7QTi3zpqzJZ43E+26 +Z9bgR6c1lmgKW2QN72PHwMlTlq0O6mN+eikEWoGr09JWpXMThZemAO2mHLAiq6ju +0+zduzWZyjZPZA1B4XUlTgCtzHveYpUzZ1NhZyM8jcGFOmmZWAFNwt03bq9/Ma0q +3jB0Dyz7IDGm8D6Y770wJRP3jf7iCVYt8jB49gOCAQUAAoIBACnVv+1ROrUiHAwn +xXGlsZdTEYZfWbE8Cter15JNNqh/Z1cdIp9m1t/rVF69nSWQvrvLeFo5p5mGxK8r +IKHTZTaAn6uO6PcNJc6iB7fS15L4uiB7p73MdjE+3PcYMbhttDlexdm6QxsmCP1F +3LYW3Uh879AURWZwPH3z4NZL2u1AFSyS1vQhtiCmztq94QwhjoDf9anFR8q05dAC +juPlKYEIhMsoq+r/l/kOM1UghhXX6BmeF8R9hhW1p4Rv+gyAgbYjowJFtZnwE5p0 +OYLJzSQWjFMYEzHAoH8J4+D5okt4IXEd0BDxLBkm1WonIxYL/NL95p3qXpgUXqRX +M9spEzWgbTBrBgkqhkiG9w0BCQ4xXjBcMCcGA1UdEQQgMB6CC2V4YW1wbGUuY29t +gg93d3cuZXhhbXBsZS5jb20wDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCBaAw +EwYDVR0lBAwwCgYIKwYBBQUHAwEwCwYJYIZIAWUDBAMCA0AAMD0CHQCyrstoqfvs +MCfsZUeycKrKQmAJAHxuoGPCKl7yAhwhNH9RNxBm5roO2U901BeF2p0pT410ghH8 +oA+F +-----END CERTIFICATE REQUEST-----`; + +const OUT_EXAMPLE_COM_DSA_2048 = `主体 + C = CH + ST = Zurich + L = Zurich + O = Example RE + OU = IT Department + CN = example.com +公钥 + 算法: DSA + 长度: 2048 bits + Pub: 29:d5:bf:ed:51:3a:b5:22:1c:0c:27:c5:71:a5:b1:97: + 53:11:86:5f:59:b1:3c:0a:d7:ab:d7:92:4d:36:a8:7f: + 67:57:1d:22:9f:66:d6:df:eb:54:5e:bd:9d:25:90:be: + bb:cb:78:5a:39:a7:99:86:c4:af:2b:20:a1:d3:65:36: + 80:9f:ab:8e:e8:f7:0d:25:ce:a2:07:b7:d2:d7:92:f8: + ba:20:7b:a7:bd:cc:76:31:3e:dc:f7:18:31:b8:6d:b4: + 39:5e:c5:d9:ba:43:1b:26:08:fd:45:dc:b6:16:dd:48: + 7c:ef:d0:14:45:66:70:3c:7d:f3:e0:d6:4b:da:ed:40: + 15:2c:92:d6:f4:21:b6:20:a6:ce:da:bd:e1:0c:21:8e: + 80:df:f5:a9:c5:47:ca:b4:e5:d0:02:8e:e3:e5:29:81: + 08:84:cb:28:ab:ea:ff:97:f9:0e:33:55:20:86:15:d7: + e8:19:9e:17:c4:7d:86:15:b5:a7:84:6f:fa:0c:80:81: + b6:23:a3:02:45:b5:99:f0:13:9a:74:39:82:c9:cd:24: + 16:8c:53:18:13:31:c0:a0:7f:09:e3:e0:f9:a2:4b:78: + 21:71:1d:d0:10:f1:2c:19:26:d5:6a:27:23:16:0b:fc: + d2:fd:e6:9d:ea:5e:98:14:5e:a4:57:33:db:29:13:35 + P: 00:b2:fa:0a:98:21:dc:47:6c:bc:a9:0f:e4:a4:11:ce: + be:56:86:89:fa:bf:fc:5f:f4:ce:15:92:a5:f8:ef:c2: + a2:37:83:61:23:a9:72:fb:ac:26:32:bb:dc:31:a2:88: + 0e:0e:8e:83:9d:e9:f1:7f:0d:71:a7:76:48:24:d0:ad: + 7e:b4:89:ca:dd:00:b9:d8:ce:79:db:55:58:c4:a7:4e: + 77:68:41:54:2d:61:22:34:9a:6a:c1:02:a1:4b:db:a8: + b5:6e:61:0c:c6:c7:7a:a7:3d:a7:53:7a:7c:c8:66:3f: + c2:96:22:5b:0c:b2:fb:f4:d9:bf:c6:b6:b7:9f:28:c2: + 9c:3d:42:49:b8:71:7e:04:3e:8d:e2:87:f5:db:37:a7: + a8:a0:ec:7d:65:a1:2b:71:2f:66:83:75:2f:c1:00:e6: + d1:56:03:dd:98:d2:7a:ac:0f:ed:85:86:ca:1a:bb:80: + f2:6c:12:23:2b:7e:dd:76:d5:28:f2:37:3f:2b:92:15: + f3:a9:91:51:ba:38:e6:7d:34:f9:76:90:82:fd:9e:1d: + ef:f6:44:df:4b:5b:a8:76:8d:0c:cf:98:c0:1a:db:fd: + a2:fb:3e:7a:16:b3:ce:3b:56:98:c2:57:19:95:9a:02: + 0f:a0:c1:e5:4d:b8:36:f5:1b:08:52:81:65:15:27:1a: + 51 + Q: 00:c1:b0:c2:9c:e2:96:b4:5e:68:4a:9c:a1:7e:cf:63: + 9b:b8:be:d5:74:e7:3b:a2:54:8a:a4:74:13 + G: 41:98:64:e6:a7:40:62:83:2f:66:13:e2:e5:33:a0:ca: + c4:f3:40:4d:45:78:01:2d:83:2a:71:64:8e:b9:4e:9c: + 0e:fc:93:ab:01:7f:6f:61:9f:ea:09:85:ff:9d:c8:f8: + e7:9a:a2:3d:97:a7:97:9f:65:a3:f8:90:3f:38:49:52: + e6:ee:09:eb:cf:58:9d:dd:b0:a4:5b:03:22:ff:e4:d0: + b0:89:f9:df:60:13:09:1a:3d:14:14:62:f8:7a:45:55: + d1:e9:8b:2f:d1:f9:7a:a6:33:eb:8c:34:79:5d:92:a7: + 8d:0e:f3:20:ae:d2:aa:fb:41:38:b7:ce:9a:b3:25:9e: + 37:13:ed:ba:67:d6:e0:47:a7:35:96:68:0a:5b:64:0d: + ef:63:c7:c0:c9:53:96:ad:0e:ea:63:7e:7a:29:04:5a: + 81:ab:d3:d2:56:a5:73:13:85:97:a6:00:ed:a6:1c:b0: + 22:ab:a8:ee:d3:ec:dd:bb:35:99:ca:36:4f:64:0d:41: + e1:75:25:4e:00:ad:cc:7b:de:62:95:33:67:53:61:67: + 23:3c:8d:c1:85:3a:69:99:58:01:4d:c2:dd:37:6e:af: + 7f:31:ad:2a:de:30:74:0f:2c:fb:20:31:a6:f0:3e:98: + ef:bd:30:25:13:f7:8d:fe:e2:09:56:2d:f2:30:78:f6 +签名 + 算法: SHA256withDSA + 签名: + R: 00:b2:ae:cb:68:a9:fb:ec:30:27:ec:65:47:b2:70:aa: + ca:42:60:09:00:7c:6e:a0:63:c2:2a:5e:f2 + S: 21:34:7f:51:37:10:66:e6:ba:0e:d9:4f:74:d4:17:85: + da:9d:29:4f:8d:74:82:11:fc:a0:0f:85 +请求的扩展程序 + 证书基本约束: 关键 + CA = 否 + 证书密钥用法: 关键 + 签名 + 密钥加密 + 扩展密钥用法: + TLS WWW 服务器身份验证 + 主体备用名称: + DNS: example.com + DNS: www.example.com`; + +// openssl req -newkey rsa:4096 -keyout test-rsa-4096.key -out test-rsa-4096.csr +// -subj "/C=CH/ST=Zurich/L=Zurich/O=Example RE/OU=IT Department/CN=example.com" +// -addext "subjectAltName = DNS:example.com,DNS:www.example.com,IP:127.0.0.1, \ +// email:user@example.com,URI:http://example.com/api,otherName:1.2.3.4;UTF8:some value" +// -addext "basicConstraints = critical,CA:FALSE" +// -addext "keyUsage = critical,digitalSignature,keyEncipherment" +// -addext "extendedKeyUsage = serverAuth" +const IN_EXAMPLE_COM_SAN = `-----BEGIN CERTIFICATE REQUEST----- +MIIFbTCCA1UCAQAwcjELMAkGA1UEBhMCQ0gxDzANBgNVBAgMBlp1cmljaDEPMA0G +A1UEBwwGWnVyaWNoMRMwEQYDVQQKDApFeGFtcGxlIFJFMRYwFAYDVQQLDA1JVCBE +ZXBhcnRtZW50MRQwEgYDVQQDDAtleGFtcGxlLmNvbTCCAiIwDQYJKoZIhvcNAQEB +BQADggIPADCCAgoCggIBAJf8uQDFcQfj6qCuPa4hNyDWr3Lwzfc3qQZdOgNJ/kym +GxxRHUXJyBtgkmAqDoSGmg1hUWgt9eZwd/Cf4Wd3qr+Q0ppg6dwZeWgYSunseoKl +f0E5FvUfECNyDwCSbltN9TCsom2ePNOOJJHWo4Y3E3jGXz0n1Vwa6ePR0j62Rcey +4lHLscQ3GoNvMLcXbY1HIhnbaI25MmFPB8p4PvpPsAYgbWHbw0jIR9dSxEK0HAU3 +2VkRkm8XaF4BOEfugqT3Bc7zAvwdFZRTTTZIICYW5T3zvtxBidJ8OSej16LV6ZeE +/4VcTzXYTzIUXbNaev3XN1r5ZodkbZvxxk/EZmfes2OtedPulW4TW27HSl6XBos/ +8VQohelUXiyCLPrtbnjeHKSz47+ZAm23jMAFYWkTVdWvAa+G74UstuRRXfLAKCNv +7VeA3l8IgEkfj48u+EenV6cJ3ZJJ5/qvZo7OUjhAtYJmNtlRYE4r3uWRmaNXYwrD +7vJuMiZafaVC+74/UHLGGm7sHVJdo4KBO/LUbHJ/SKZIYMc14kJLOf6TPZXSGm9N +TxbOV9Vzcjzivq1HxaYirLAM+nyVApVwwpVq/uiEFz579yrwySvBuwnewfdfZ6EZ +iNAKiBwQ8diFMnFfd/28hJ8TrIlq+5bkVo1ODuhyRIw9YB19IrmytaVvkR8624Ld +AgMBAAGggbUwgbIGCSqGSIb3DQEJDjGBpDCBoTBsBgNVHREEZTBjggtleGFtcGxl +LmNvbYIPd3d3LmV4YW1wbGUuY29thwR/AAABgRB1c2VyQGV4YW1wbGUuY29thhZo +dHRwOi8vZXhhbXBsZS5jb20vYXBpoBMGAyoDBKAMDApzb21lIHZhbHVlMAwGA1Ud +EwEB/wQCMAAwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA0G +CSqGSIb3DQEBCwUAA4ICAQAtOuh6MEralwgChJHBaGJavBxpCQ0p5K77RlAPIk5Q +Mv5086DxiZEFBKCRiZRtkOvo0aCHUn3awDrlEOgECiAYQqMIBUWeNwImtmpDopuI +ZMmVmzc2ojf9nUlPrPV+B6P2jTxTIQYpDQocbOgxDkcdZVSvLyMEFnHIMNQV7GS2 +gBmUnPp+4z2d8X9XaRspkuEt2nbA1NoXekWaG46jG56VoBycepOiNkwL4AsqunLa +T0urcHq34g+HRQWwOA+q/72qP4oaj2ZO0fFJQl2ZsGRT/IuM1g2YsnVSpBOGY/J6 +Qi2hDr6EEqphg501ny+FZE1BouQ/lSykafYyauwNq1puu/VyuF8grFmL0SoxWWfP +h6viblGM/Vu69Bhl4gkWKtufWpOVpCA4vHzes8IVMFg7vhpwm33Xjo0lCPcIUin6 +0CqHZQCsWtj2yIAF66WHB0I1DHL5FNCWRPnQCo54qRZIYqtSP20QRr6GWC2d+ZgX +wDxRpmzr8T8owBYWw3j+RK9CtZoWO4O586UR4J1Bn5PQfoR78Z/4mzv2sxVi9Fdf +sJzlG6/nhmMaCqneIn97gkguvSgpOuKSeo/fjbpnthufgilrpDQoGrhZaXic0GVZ +6JmbOh3tLMVf4ooyyaLfOCfV2FN12rDa3pdWhQ4MVN4gg9U3Cq0x7yRQKiSBlBnw +oA== +-----END CERTIFICATE REQUEST-----`; + +const OUT_EXAMPLE_COM_SAN = `主体 + C = CH + ST = Zurich + L = Zurich + O = Example RE + OU = IT Department + CN = example.com +公钥 + 算法: RSA + 长度: 4096 bits + 模数: 00:97:fc:b9:00:c5:71:07:e3:ea:a0:ae:3d:ae:21:37: + 20:d6:af:72:f0:cd:f7:37:a9:06:5d:3a:03:49:fe:4c: + a6:1b:1c:51:1d:45:c9:c8:1b:60:92:60:2a:0e:84:86: + 9a:0d:61:51:68:2d:f5:e6:70:77:f0:9f:e1:67:77:aa: + bf:90:d2:9a:60:e9:dc:19:79:68:18:4a:e9:ec:7a:82: + a5:7f:41:39:16:f5:1f:10:23:72:0f:00:92:6e:5b:4d: + f5:30:ac:a2:6d:9e:3c:d3:8e:24:91:d6:a3:86:37:13: + 78:c6:5f:3d:27:d5:5c:1a:e9:e3:d1:d2:3e:b6:45:c7: + b2:e2:51:cb:b1:c4:37:1a:83:6f:30:b7:17:6d:8d:47: + 22:19:db:68:8d:b9:32:61:4f:07:ca:78:3e:fa:4f:b0: + 06:20:6d:61:db:c3:48:c8:47:d7:52:c4:42:b4:1c:05: + 37:d9:59:11:92:6f:17:68:5e:01:38:47:ee:82:a4:f7: + 05:ce:f3:02:fc:1d:15:94:53:4d:36:48:20:26:16:e5: + 3d:f3:be:dc:41:89:d2:7c:39:27:a3:d7:a2:d5:e9:97: + 84:ff:85:5c:4f:35:d8:4f:32:14:5d:b3:5a:7a:fd:d7: + 37:5a:f9:66:87:64:6d:9b:f1:c6:4f:c4:66:67:de:b3: + 63:ad:79:d3:ee:95:6e:13:5b:6e:c7:4a:5e:97:06:8b: + 3f:f1:54:28:85:e9:54:5e:2c:82:2c:fa:ed:6e:78:de: + 1c:a4:b3:e3:bf:99:02:6d:b7:8c:c0:05:61:69:13:55: + d5:af:01:af:86:ef:85:2c:b6:e4:51:5d:f2:c0:28:23: + 6f:ed:57:80:de:5f:08:80:49:1f:8f:8f:2e:f8:47:a7: + 57:a7:09:dd:92:49:e7:fa:af:66:8e:ce:52:38:40:b5: + 82:66:36:d9:51:60:4e:2b:de:e5:91:99:a3:57:63:0a: + c3:ee:f2:6e:32:26:5a:7d:a5:42:fb:be:3f:50:72:c6: + 1a:6e:ec:1d:52:5d:a3:82:81:3b:f2:d4:6c:72:7f:48: + a6:48:60:c7:35:e2:42:4b:39:fe:93:3d:95:d2:1a:6f: + 4d:4f:16:ce:57:d5:73:72:3c:e2:be:ad:47:c5:a6:22: + ac:b0:0c:fa:7c:95:02:95:70:c2:95:6a:fe:e8:84:17: + 3e:7b:f7:2a:f0:c9:2b:c1:bb:09:de:c1:f7:5f:67:a1: + 19:88:d0:0a:88:1c:10:f1:d8:85:32:71:5f:77:fd:bc: + 84:9f:13:ac:89:6a:fb:96:e4:56:8d:4e:0e:e8:72:44: + 8c:3d:60:1d:7d:22:b9:b2:b5:a5:6f:91:1f:3a:db:82: + dd + 指数: 65537 (0x10001) +签名 + 算法: SHA256withRSA + 签名: 2d:3a:e8:7a:30:4a:da:97:08:02:84:91:c1:68:62:5a: + bc:1c:69:09:0d:29:e4:ae:fb:46:50:0f:22:4e:50:32: + fe:74:f3:a0:f1:89:91:05:04:a0:91:89:94:6d:90:eb: + e8:d1:a0:87:52:7d:da:c0:3a:e5:10:e8:04:0a:20:18: + 42:a3:08:05:45:9e:37:02:26:b6:6a:43:a2:9b:88:64: + c9:95:9b:37:36:a2:37:fd:9d:49:4f:ac:f5:7e:07:a3: + f6:8d:3c:53:21:06:29:0d:0a:1c:6c:e8:31:0e:47:1d: + 65:54:af:2f:23:04:16:71:c8:30:d4:15:ec:64:b6:80: + 19:94:9c:fa:7e:e3:3d:9d:f1:7f:57:69:1b:29:92:e1: + 2d:da:76:c0:d4:da:17:7a:45:9a:1b:8e:a3:1b:9e:95: + a0:1c:9c:7a:93:a2:36:4c:0b:e0:0b:2a:ba:72:da:4f: + 4b:ab:70:7a:b7:e2:0f:87:45:05:b0:38:0f:aa:ff:bd: + aa:3f:8a:1a:8f:66:4e:d1:f1:49:42:5d:99:b0:64:53: + fc:8b:8c:d6:0d:98:b2:75:52:a4:13:86:63:f2:7a:42: + 2d:a1:0e:be:84:12:aa:61:83:9d:35:9f:2f:85:64:4d: + 41:a2:e4:3f:95:2c:a4:69:f6:32:6a:ec:0d:ab:5a:6e: + bb:f5:72:b8:5f:20:ac:59:8b:d1:2a:31:59:67:cf:87: + ab:e2:6e:51:8c:fd:5b:ba:f4:18:65:e2:09:16:2a:db: + 9f:5a:93:95:a4:20:38:bc:7c:de:b3:c2:15:30:58:3b: + be:1a:70:9b:7d:d7:8e:8d:25:08:f7:08:52:29:fa:d0: + 2a:87:65:00:ac:5a:d8:f6:c8:80:05:eb:a5:87:07:42: + 35:0c:72:f9:14:d0:96:44:f9:d0:0a:8e:78:a9:16:48: + 62:ab:52:3f:6d:10:46:be:86:58:2d:9d:f9:98:17:c0: + 3c:51:a6:6c:eb:f1:3f:28:c0:16:16:c3:78:fe:44:af: + 42:b5:9a:16:3b:83:b9:f3:a5:11:e0:9d:41:9f:93:d0: + 7e:84:7b:f1:9f:f8:9b:3b:f6:b3:15:62:f4:57:5f:b0: + 9c:e5:1b:af:e7:86:63:1a:0a:a9:de:22:7f:7b:82:48: + 2e:bd:28:29:3a:e2:92:7a:8f:df:8d:ba:67:b6:1b:9f: + 82:29:6b:a4:34:28:1a:b8:59:69:78:9c:d0:65:59:e8: + 99:9b:3a:1d:ed:2c:c5:5f:e2:8a:32:c9:a2:df:38:27: + d5:d8:53:75:da:b0:da:de:97:56:85:0e:0c:54:de:20: + 83:d5:37:0a:ad:31:ef:24:50:2a:24:81:94:19:f0:a0 +请求的扩展程序 + 证书基本约束: 关键 + CA = 否 + 证书密钥用法: 关键 + 签名 + 密钥加密 + 扩展密钥用法: + TLS WWW 服务器身份验证 + 主体备用名称: + DNS: example.com + DNS: www.example.com + IP: 127.0.0.1 + EMAIL: user@example.com + URI: http://example.com/api + Other: 1.2.3.4::some value`; + +// openssl req -newkey rsa:2048 -keyout test-rsa-2048.key -out test-rsa-2048.csr \ +// -subj "/C=CH/ST=Zurich/L=Zurich/O=Example RE/OU=IT Department/CN=example.com" \ +// -addext "subjectAltName = DNS:example.com,DNS:www.example.com" \ +// -addext "basicConstraints = critical,CA:FALSE" \ +// -addext "keyUsage = critical,digitalSignature,keyEncipherment," \ +// -addext "extendedKeyUsage = serverAuth" +const IN_EXAMPLE_COM_KEY_USAGE = `-----BEGIN CERTIFICATE REQUEST----- +MIIDJDCCAgwCAQAwcjELMAkGA1UEBhMCQ0gxDzANBgNVBAgMBlp1cmljaDEPMA0G +A1UEBwwGWnVyaWNoMRMwEQYDVQQKDApFeGFtcGxlIFJFMRYwFAYDVQQLDA1JVCBE +ZXBhcnRtZW50MRQwEgYDVQQDDAtleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBAKHQWxqtdJQ1l7ApTgwgsyrN/kRDrog/DsUlZQg3YodY +4RRAgPr+AeQ1BhuWDVxaXein0XmXOESHgK9Z7X/hLgRy2ifK+n20Ij3+k6VSh6Lt +lpjUPwK7PWBtZ969DukBIvq64XrJTNWIJPvXXQxkL4dk5NcDY4TjXWt0GgDVR+GH +OU1JwfzviGVRdOmY8+Ckfxc+3QytTdP6KBQaiUk5sBEniovDpKfImtql72JsCRbA +9Wue7X4EbXi2zvoAlJ5NXF3Ps1q2XsVJeIx/mMDcgRW7s5AVM9NQW0O1JLoA7dY+ +vSrKZj+ssuKCIWM7u9Big2I0miEl5AXrDlwZPBhM9FMCAwEAAaBtMGsGCSqGSIb3 +DQEJDjFeMFwwJwYDVR0RBCAwHoILZXhhbXBsZS5jb22CD3d3dy5leGFtcGxlLmNv +bTAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIB/jATBgNVHSUEDDAKBggrBgEF +BQcDATANBgkqhkiG9w0BAQsFAAOCAQEAPOr6jfq/mXilqXA11CTza69Ydd4fvp6q +UG47PefzQqSmYtpUytwZRLGQ1IFRlYeXwbazVLkRmLNwpbB8C5fh9FPp55JCpM/O +tgCW2uqLkCtkQMUCaSdRX/Y+9ypYhdBkSNv1Q+3QXi2jmi5QMqwerAwNmeXmH6AZ +swMgAhuoLS9OrIqHjFoHGoXsgXMkbLr6m6hgyFt8ZbbwK4WpVcgCZfhtBiLilCJN +Xr9GUXL3FqUb7sIaYKAaghr2haqKhFsIH57XVK3DZYhOkLd9uC8TLdl2e+t9Hcy9 +ymLwiIGMUfuBQMP8nVu3jGXAQ5N4VV+IZfF8UaBFW8tG+Ms2TeW68Q== +-----END CERTIFICATE REQUEST-----`; + +const OUT_EXAMPLE_COM_KEY_USAGE = `主体 + C = CH + ST = Zurich + L = Zurich + O = Example RE + OU = IT Department + CN = example.com +公钥 + 算法: RSA + 长度: 2048 bits + 模数: 00:a1:d0:5b:1a:ad:74:94:35:97:b0:29:4e:0c:20:b3: + 2a:cd:fe:44:43:ae:88:3f:0e:c5:25:65:08:37:62:87: + 58:e1:14:40:80:fa:fe:01:e4:35:06:1b:96:0d:5c:5a: + 5d:e8:a7:d1:79:97:38:44:87:80:af:59:ed:7f:e1:2e: + 04:72:da:27:ca:fa:7d:b4:22:3d:fe:93:a5:52:87:a2: + ed:96:98:d4:3f:02:bb:3d:60:6d:67:de:bd:0e:e9:01: + 22:fa:ba:e1:7a:c9:4c:d5:88:24:fb:d7:5d:0c:64:2f: + 87:64:e4:d7:03:63:84:e3:5d:6b:74:1a:00:d5:47:e1: + 87:39:4d:49:c1:fc:ef:88:65:51:74:e9:98:f3:e0:a4: + 7f:17:3e:dd:0c:ad:4d:d3:fa:28:14:1a:89:49:39:b0: + 11:27:8a:8b:c3:a4:a7:c8:9a:da:a5:ef:62:6c:09:16: + c0:f5:6b:9e:ed:7e:04:6d:78:b6:ce:fa:00:94:9e:4d: + 5c:5d:cf:b3:5a:b6:5e:c5:49:78:8c:7f:98:c0:dc:81: + 15:bb:b3:90:15:33:d3:50:5b:43:b5:24:ba:00:ed:d6: + 3e:bd:2a:ca:66:3f:ac:b2:e2:82:21:63:3b:bb:d0:62: + 83:62:34:9a:21:25:e4:05:eb:0e:5c:19:3c:18:4c:f4: + 53 + 指数: 65537 (0x10001) +签名 + 算法: SHA256withRSA + 签名: 3c:ea:fa:8d:fa:bf:99:78:a5:a9:70:35:d4:24:f3:6b: + af:58:75:de:1f:be:9e:aa:50:6e:3b:3d:e7:f3:42:a4: + a6:62:da:54:ca:dc:19:44:b1:90:d4:81:51:95:87:97: + c1:b6:b3:54:b9:11:98:b3:70:a5:b0:7c:0b:97:e1:f4: + 53:e9:e7:92:42:a4:cf:ce:b6:00:96:da:ea:8b:90:2b: + 64:40:c5:02:69:27:51:5f:f6:3e:f7:2a:58:85:d0:64: + 48:db:f5:43:ed:d0:5e:2d:a3:9a:2e:50:32:ac:1e:ac: + 0c:0d:99:e5:e6:1f:a0:19:b3:03:20:02:1b:a8:2d:2f: + 4e:ac:8a:87:8c:5a:07:1a:85:ec:81:73:24:6c:ba:fa: + 9b:a8:60:c8:5b:7c:65:b6:f0:2b:85:a9:55:c8:02:65: + f8:6d:06:22:e2:94:22:4d:5e:bf:46:51:72:f7:16:a5: + 1b:ee:c2:1a:60:a0:1a:82:1a:f6:85:aa:8a:84:5b:08: + 1f:9e:d7:54:ad:c3:65:88:4e:90:b7:7d:b8:2f:13:2d: + d9:76:7b:eb:7d:1d:cc:bd:ca:62:f0:88:81:8c:51:fb: + 81:40:c3:fc:9d:5b:b7:8c:65:c0:43:93:78:55:5f:88: + 65:f1:7c:51:a0:45:5b:cb:46:f8:cb:36:4d:e5:ba:f1 +请求的扩展程序 + 证书基本约束: 关键 + CA = 否 + 证书密钥用法: 关键 + 签名 + 非否认 + 密钥加密 + 数据加密 + 密钥协商 + 证书签名 + CRL签名 + 扩展密钥用法: + TLS WWW 服务器身份验证 + 主体备用名称: + DNS: example.com + DNS: www.example.com`; + +// openssl req -newkey rsa:2048 -keyout test-rsa-2048.key -out test-rsa-2048.csr \ +// -subj "/C=CH/ST=Zurich/L=Zurich/O=Example RE/OU=IT Department/CN=example.com" \ +// -addext "subjectAltName = DNS:example.com,DNS:www.example.com" \ +// -addext "basicConstraints = critical,CA:FALSE" \ +// -addext "keyUsage = critical,digitalSignature,keyEncipherment" \ +// -addext "extendedKeyUsage = serverAuth" +const IN_EXAMPLE_COM_EXTENDED_KEY_USAGE = `-----BEGIN CERTIFICATE REQUEST----- +MIIDpzCCAo8CAQAwcjELMAkGA1UEBhMCQ0gxDzANBgNVBAgMBlp1cmljaDEPMA0G +A1UEBwwGWnVyaWNoMRMwEQYDVQQKDApFeGFtcGxlIFJFMRYwFAYDVQQLDA1JVCBE +ZXBhcnRtZW50MRQwEgYDVQQDDAtleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBAMjQ/Bz+CzA/WaS+Nyp3ijWzYlKY7GmA/a2FuzNSPQlr +WuGyZJcfb0CpLIpRF8qcDllAe+hFQnVGnk3svQIhfEOD7qwzBRMHVhe59jkv2kER +s+u88KBCNfIAS6m5d45y4xH338aXq4lZexiEASWHS7SsWAR3kL3c9p14U9EHOaym +ZWPO/SCfCJyhxszDLM2eG5S2rviuu9nY+rk0Oo7z8x8PZF9Wl1NamLl1tWPqsznS +3bfjdJYeUlm7XvTzC6EMAT6K/5ker0chl7Hg0mcEO9w4c2cSTAHvZ2b2sRYbxNQZ +49byQsRAXW8TNnOaK9Phmvwy/irEXU9PEl3u7KvSnNcCAwEAAaCB7zCB7AYJKoZI +hvcNAQkOMYHeMIHbMCcGA1UdEQQgMB6CC2V4YW1wbGUuY29tgg93d3cuZXhhbXBs +ZS5jb20wDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCBaAwgZEGA1UdJQSBiTCB +hgYIKwYBBQUHAwEGCCsGAQUFBwMCBggrBgEFBQcDAwYIKwYBBQUHAwQGCCsGAQUF +BwMIBgorBgEEAYI3AgEVBgorBgEEAYI3AgEWBgorBgEEAYI3CgMBBgorBgEEAYI3 +CgMDBgorBgEEAYI3CgMEBgorBgEEAYI3FAICBgorBgEEAYI3CgMDMA0GCSqGSIb3 +DQEBCwUAA4IBAQCcYWj1eIxj/FUEhhm2lZr06Pq4GEtIVsMWw5IrUn2FIFb/yY8x +GHuB5v7XNA/8zhRWvIAXGaa8Bnajk4mR0rkxy1MXpd2YevdrF/XFa2Totv4E4/I6 +pvrFefYTSGpmCu5zQTuoanM7JjE81vvbTLFdaHMdLOekpuK5v5kbuNdtDpEiAkd0 +vmV4BQ0BV3b3zhIRQqBB60pSBHYvMhHNn/80RhVUQxaPTS7/AMHRZGRc1lD9/bjA +pMBis9CL4AbXtTcztU5qy4VpB1/Ej3AbAjuJIbpbPH6XtxIEtqdM4Seqi44w9oX4 +rxQagXmvJPp+E4253EkeHwhfHh4SnJEtsibQ +-----END CERTIFICATE REQUEST-----`; + +const OUT_EXAMPLE_COM_EXTENDED_KEY_USAGE = `主体 + C = CH + ST = Zurich + L = Zurich + O = Example RE + OU = IT Department + CN = example.com +公钥 + 算法: RSA + 长度: 2048 bits + 模数: 00:c8:d0:fc:1c:fe:0b:30:3f:59:a4:be:37:2a:77:8a: + 35:b3:62:52:98:ec:69:80:fd:ad:85:bb:33:52:3d:09: + 6b:5a:e1:b2:64:97:1f:6f:40:a9:2c:8a:51:17:ca:9c: + 0e:59:40:7b:e8:45:42:75:46:9e:4d:ec:bd:02:21:7c: + 43:83:ee:ac:33:05:13:07:56:17:b9:f6:39:2f:da:41: + 11:b3:eb:bc:f0:a0:42:35:f2:00:4b:a9:b9:77:8e:72: + e3:11:f7:df:c6:97:ab:89:59:7b:18:84:01:25:87:4b: + b4:ac:58:04:77:90:bd:dc:f6:9d:78:53:d1:07:39:ac: + a6:65:63:ce:fd:20:9f:08:9c:a1:c6:cc:c3:2c:cd:9e: + 1b:94:b6:ae:f8:ae:bb:d9:d8:fa:b9:34:3a:8e:f3:f3: + 1f:0f:64:5f:56:97:53:5a:98:b9:75:b5:63:ea:b3:39: + d2:dd:b7:e3:74:96:1e:52:59:bb:5e:f4:f3:0b:a1:0c: + 01:3e:8a:ff:99:1e:af:47:21:97:b1:e0:d2:67:04:3b: + dc:38:73:67:12:4c:01:ef:67:66:f6:b1:16:1b:c4:d4: + 19:e3:d6:f2:42:c4:40:5d:6f:13:36:73:9a:2b:d3:e1: + 9a:fc:32:fe:2a:c4:5d:4f:4f:12:5d:ee:ec:ab:d2:9c: + d7 + 指数: 65537 (0x10001) +签名 + 算法: SHA256withRSA + 签名: 9c:61:68:f5:78:8c:63:fc:55:04:86:19:b6:95:9a:f4: + e8:fa:b8:18:4b:48:56:c3:16:c3:92:2b:52:7d:85:20: + 56:ff:c9:8f:31:18:7b:81:e6:fe:d7:34:0f:fc:ce:14: + 56:bc:80:17:19:a6:bc:06:76:a3:93:89:91:d2:b9:31: + cb:53:17:a5:dd:98:7a:f7:6b:17:f5:c5:6b:64:e8:b6: + fe:04:e3:f2:3a:a6:fa:c5:79:f6:13:48:6a:66:0a:ee: + 73:41:3b:a8:6a:73:3b:26:31:3c:d6:fb:db:4c:b1:5d: + 68:73:1d:2c:e7:a4:a6:e2:b9:bf:99:1b:b8:d7:6d:0e: + 91:22:02:47:74:be:65:78:05:0d:01:57:76:f7:ce:12: + 11:42:a0:41:eb:4a:52:04:76:2f:32:11:cd:9f:ff:34: + 46:15:54:43:16:8f:4d:2e:ff:00:c1:d1:64:64:5c:d6: + 50:fd:fd:b8:c0:a4:c0:62:b3:d0:8b:e0:06:d7:b5:37: + 33:b5:4e:6a:cb:85:69:07:5f:c4:8f:70:1b:02:3b:89: + 21:ba:5b:3c:7e:97:b7:12:04:b6:a7:4c:e1:27:aa:8b: + 8e:30:f6:85:f8:af:14:1a:81:79:af:24:fa:7e:13:8d: + b9:dc:49:1e:1f:08:5f:1e:1e:12:9c:91:2d:b2:26:d0 +请求的扩展程序 + 证书基本约束: 关键 + CA = 否 + 证书密钥用法: 关键 + 签名 + 密钥加密 + 扩展密钥用法: + TLS WWW 服务器身份验证 + TLS WWW 客户端身份验证 + 代码签名 + 电子邮件保护 (S/MIME) + 可信任时间戳 + Microsoft Individual Code Signing + Microsoft Commercial Code Signing + Microsoft Trust List Signing + Microsoft Server Gated Crypto + Microsoft Encrypted File System + Microsoft Smartcard Login + Microsoft Server Gated Crypto + 主体备用名称: + DNS: example.com + DNS: www.example.com`; + +TestRegister.addTests([ + { + name: "Parse CSR: Example Certificate Signing Request (CSR) with RSA 1024", + input: IN_EXAMPLE_COM_RSA_1024, + expectedOutput: OUT_EXAMPLE_COM_RSA_1024, + recipeConfig: [ + { + "op": "解析CSR", + "args": ["PEM"] + } + ] + }, + { + name: "Parse CSR: Example Certificate Signing Request (CSR) with RSA 2048", + input: IN_EXAMPLE_COM_RSA_2048, + expectedOutput: OUT_EXAMPLE_COM_RSA_2048, + recipeConfig: [ + { + "op": "解析CSR", + "args": ["PEM"] + } + ] + }, + { + name: "Parse CSR: Example Certificate Signing Request (CSR) with EC 256", + input: IN_EXAMPLE_COM_EC_P256, + expectedOutput: OUT_EXAMPLE_COM_EC_P256, + recipeConfig: [ + { + "op": "解析CSR", + "args": ["PEM"] + } + ] + }, + { + name: "Parse CSR: Example Certificate Signing Request (CSR) with EC 384", + input: IN_EXAMPLE_COM_EC_P384, + expectedOutput: OUT_EXAMPLE_COM_EC_P384, + recipeConfig: [ + { + "op": "解析CSR", + "args": ["PEM"] + } + ] + }, + { + name: "Parse CSR: Example Certificate Signing Request (CSR) with EC 521", + input: IN_EXAMPLE_COM_EC_P521, + expectedOutput: OUT_EXAMPLE_COM_EC_P521, + recipeConfig: [ + { + "op": "解析CSR", + "args": ["PEM"] + } + ] + }, + { + name: "Parse CSR: Example Certificate Signing Request (CSR) with DSA 1024", + input: IN_EXAMPLE_COM_DSA_1024, + expectedOutput: OUT_EXAMPLE_COM_DSA_1024, + recipeConfig: [ + { + "op": "解析CSR", + "args": ["PEM"] + } + ] + }, + { + name: "Parse CSR: Example Certificate Signing Request (CSR) with DSA 2048", + input: IN_EXAMPLE_COM_DSA_2048, + expectedOutput: OUT_EXAMPLE_COM_DSA_2048, + recipeConfig: [ + { + "op": "解析CSR", + "args": ["PEM"] + } + ] + }, + { + name: "Parse CSR: Example Certificate Signing Request (CSR) with DSA 2048", + input: IN_EXAMPLE_COM_DSA_2048, + expectedOutput: OUT_EXAMPLE_COM_DSA_2048, + recipeConfig: [ + { + "op": "解析CSR", + "args": ["PEM"] + } + ] + }, + { + name: "Parse CSR: Example Certificate Signing Request (CSR) with various SAN types", + input: IN_EXAMPLE_COM_SAN, + expectedOutput: OUT_EXAMPLE_COM_SAN, + recipeConfig: [ + { + "op": "解析CSR", + "args": ["PEM"] + } + ] + }, + { + name: "Parse CSR: Example Certificate Signing Request (CSR) with various Key Usages", + input: IN_EXAMPLE_COM_KEY_USAGE, + expectedOutput: OUT_EXAMPLE_COM_KEY_USAGE, + recipeConfig: [ + { + "op": "解析CSR", + "args": ["PEM"] + } + ] + }, + { + name: "Parse CSR: Example Certificate Signing Request (CSR) with various Extended Key Usages", + input: IN_EXAMPLE_COM_EXTENDED_KEY_USAGE, + expectedOutput: OUT_EXAMPLE_COM_EXTENDED_KEY_USAGE, + recipeConfig: [ + { + "op": "解析CSR", + "args": ["PEM"] + } + ] + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/ParseIPRange.mjs b/plugins/srktoolbox/tests/operations/tests/ParseIPRange.mjs new file mode 100644 index 00000000..895ce33a --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/ParseIPRange.mjs @@ -0,0 +1,134 @@ +/** + * Parse IP Range tests. + * + * @author Klaxon [klaxon@veyr.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Parse IPv4 CIDR", + input: "10.0.0.0/30", + expectedOutput: "网络: 10.0.0.0\nCIDR: 30\n掩码: 255.255.255.252\n范围: 10.0.0.0 - 10.0.0.3\n范围中地址数: 4\n\n10.0.0.0\n10.0.0.1\n10.0.0.2\n10.0.0.3", + recipeConfig: [ + { + "op": "解析IP范围", + "args": [true, true, false] + }, + ], + }, + { + name: "Parse IPv4 hyphenated", + input: "10.0.0.0 - 10.0.0.3", + expectedOutput: "包含此范围的最小子网为:\n\t网络: 10.0.0.0\n\tCIDR: 30\n\t掩码: 255.255.255.252\n\t子网范围: 10.0.0.0 - 10.0.0.3\n\t子网中地址数量: 4\n\n范围: 10.0.0.0 - 10.0.0.3\n范围中地址数: 4\n\n10.0.0.0\n10.0.0.1\n10.0.0.2\n10.0.0.3", + recipeConfig: [ + { + "op": "解析IP范围", + "args": [true, true, false] + }, + ], + }, + { + name: "Parse IPv4 list", + input: "10.0.0.8\n10.0.0.5/30\n10.0.0.1\n10.0.0.3", + expectedOutput: "包含此范围的最小子网为:\n\t网络: 10.0.0.0\n\tCIDR: 28\n\t掩码: 255.255.255.240\n\t子网范围: 10.0.0.0 - 10.0.0.15\n\t子网中地址数量: 16\n\n范围: 10.0.0.1 - 10.0.0.8\n范围中地址数: 8\n\n10.0.0.1\n10.0.0.2\n10.0.0.3\n10.0.0.4\n10.0.0.5\n10.0.0.6\n10.0.0.7\n10.0.0.8", + recipeConfig: [ + { + "op": "解析IP范围", + "args": [true, true, false] + }, + ], + }, + { + name: "Parse IPv6 CIDR - full", + input: "2404:6800:4001:0000:0000:0000:0000:0000/48", + expectedOutput: "网络: 2404:6800:4001:0000:0000:0000:0000:0000\nShorthand: 2404:6800:4001::\nCIDR: 48\n掩码: ffff:ffff:ffff:0000:0000:0000:0000:0000\n范围: 2404:6800:4001:0000:0000:0000:0000:0000 - 2404:6800:4001:ffff:ffff:ffff:ffff:ffff\n范围中地址数: 1208925819614629174706176\n\n", + recipeConfig: [ + { + "op": "解析IP范围", + "args": [true, true, false] + }, + ], + }, + { + name: "Parse IPv6 CIDR - collapsed", + input: "2404:6800:4001::/48", + expectedOutput: "网络: 2404:6800:4001:0000:0000:0000:0000:0000\nShorthand: 2404:6800:4001::\nCIDR: 48\n掩码: ffff:ffff:ffff:0000:0000:0000:0000:0000\n范围: 2404:6800:4001:0000:0000:0000:0000:0000 - 2404:6800:4001:ffff:ffff:ffff:ffff:ffff\n范围中地址数: 1208925819614629174706176\n\n", + recipeConfig: [ + { + "op": "解析IP范围", + "args": [true, true, false] + }, + ], + }, + { + name: "Parse IPv6 hyphenated", + input: "2404:6800:4001:: - 2404:6800:4001:ffff:ffff:ffff:ffff:ffff", + expectedOutput: "范围: 2404:6800:4001:0000:0000:0000:0000:0000 - 2404:6800:4001:ffff:ffff:ffff:ffff:ffff\nShorthand范围: 2404:6800:4001:: - 2404:6800:4001:ffff:ffff:ffff:ffff:ffff\n范围中地址数: 1208925819614629174706176\n\n", + recipeConfig: [ + { + "op": "解析IP范围", + "args": [true, true, false] + }, + ], + }, + { + name: "Parse IPv6 list", + input: "2404:6800:4001:ffff:ffff:ffff:ffff:ffff\n2404:6800:4001::ffff\n2404:6800:4001:ffff:ffff::1111\n2404:6800:4001::/64", + expectedOutput: "范围: 2404:6800:4001:0000:0000:0000:0000:0000 - 2404:6800:4001:ffff:ffff:ffff:ffff:ffff\nShorthand范围: 2404:6800:4001:: - 2404:6800:4001:ffff:ffff:ffff:ffff:ffff\n范围中地址数: 1208925819614629174706176\n\n", + recipeConfig: [ + { + "op": "解析IP范围", + "args": [true, true, false] + }, + ], + }, + { + name: "IPv4 subnet out of range error", + input: "10.1.1.1/34", + expectedOutput: "IPv4 CIDR 必须小于32", + recipeConfig: [ + { + "op": "解析IP范围", + "args": [true, true, false] + }, + ], + }, + { + name: "invalid IPv4 address error", + input: "444.1.1.1/30", + expectedOutput: "数值超出范围。", + recipeConfig: [ + { + "op": "解析IP范围", + "args": [true, true, false] + }, + ], + }, + { + name: "IPv6 subnet out of range error", + input: "2404:6800:4001::/129", + expectedOutput: "IPv6 CIDR 必须小于128", + recipeConfig: [ + { + "op": "解析IP范围", + "args": [true, true, false] + }, + ], + }, + { + name: "invalid IPv6 address error", + input: "2404:6800:4001:/12", + expectedOutput: "无效的输入。\n\n此操作支持CIDR范围(例:10.0.0.0/24)或连字符表示的范围(例:10.0.0.0 - 10.0.1.0)。同时支持IPv6。", + recipeConfig: [ + { + "op": "解析IP范围", + "args": [true, true, false] + }, + ], + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/ParseObjectIDTimestamp.mjs b/plugins/srktoolbox/tests/operations/tests/ParseObjectIDTimestamp.mjs new file mode 100644 index 00000000..0bfaaf6d --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/ParseObjectIDTimestamp.mjs @@ -0,0 +1,26 @@ +/** + * Parse ObjectID timestamp tests + * + * @author dmfj [dominic@dmfj.io] + * + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + + +TestRegister.addTests([ + { + name: "Parse ISO timestamp from ObjectId", + input: "000000000000000000000000", + expectedOutput: "1970-01-01T00:00:00.000Z", + recipeConfig: [ + { + op: "解析ObjectID时间戳", + args: [], + } + ], + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/ParseQRCode.mjs b/plugins/srktoolbox/tests/operations/tests/ParseQRCode.mjs new file mode 100644 index 00000000..84a911e2 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/ParseQRCode.mjs @@ -0,0 +1,73 @@ +/** + * Parse QR Code tests + * + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Parse QR Code : JPEG", + input: "ffd8ffe000104a46494600010100004800480000ffe1008c4578696600004d4d002a000000080005011200030000000100010000011a0005000000010000004a011b0005000000010000005201280003000000010002000087690004000000010000005a00000000000000480000000100000048000000010003a00100030000000100010000a002000400000001000001e0a003000400000001000001e000000000ffed003850686f746f73686f7020332e30003842494d04040000000000003842494d0425000000000010d41d8cd98f00b204e9800998ecf8427effc000110801e001e003012200021101031101ffc4001f0000010501010101010100000000000000000102030405060708090a0bffc400b5100002010303020403050504040000017d01020300041105122131410613516107227114328191a1082342b1c11552d1f02433627282090a161718191a25262728292a3435363738393a434445464748494a535455565758595a636465666768696a737475767778797a838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae1e2e3e4e5e6e7e8e9eaf1f2f3f4f5f6f7f8f9faffc4001f0100030101010101010101010000000000000102030405060708090a0bffc400b51100020102040403040705040400010277000102031104052131061241510761711322328108144291a1b1c109233352f0156272d10a162434e125f11718191a262728292a35363738393a434445464748494a535455565758595a636465666768696a737475767778797a82838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae2e3e4e5e6e7e8e9eaf2f3f4f5f6f7f8f9faffdb004300020202020202030202030403030304050404040405070505050505070807070707070708080808080808080a0a0a0a0a0a0b0b0b0b0b0d0d0d0d0d0d0d0d0d0dffdb004301020202030303060303060d0907090d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0dffdd0004001effda000c03010002110311003f00fdfca28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2803ffd0fdfca28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2803ffd1fdfca28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2803ffd2fdfca28a2800a28a2800a28a2803f3c7f6e8fdba2f3f636bcf06da5a78362f15ff00c25716a72b34ba99d3becdfd9c6d86062dae37effb47fb38dbdf3c7c07ff000fc4d5bfe88f5aff00e14adffcaea4ff0082e27fc85be0f7fd7af89bff0042d3abf03a803f7cbfe1f89ab7fd11eb5ffc295bff0095d47fc3f1356ffa23d6bff852b7ff002babf0505bc8c010539f59107e99a5fb349eb1ff00dfc4ff001a00fdeaff0087e26adff447ad7ff0a56ffe5751ff000fc4d5bfe88f5aff00e14adffcaeafc15fb349eb1ffdfc4ff1a3ecd27ac7ff007f13fc6803f7abfe1f89ab7fd11eb5ff00c295bff95d47fc3f1356ff00a23d6bff00852b7ff2babf057ecd27ac7ff7f13fc68fb349eb1ffdfc4ff1a00fdeaff87e26adff00447ad7ff000a56ff00e5751ff0fc4d5bfe88f5affe14adff00caeafc15fb349eb1ff00dfc4ff001a3ecd27ac7ff7f13fc6803f7abfe1f89ab7fd11eb5ffc295bff0095d5ec5f00bfe0adbaa7c6df8bfe13f85b27c30b6d223f12ea9169cd7cbaf35d35bf9aaedbc45f618c3e3674debd6bf9ad6528db4e3f0208fcc715f61fec0fff00276ff0b7fec67b4ffd153d007f66f4514500078afc2cf891ff000592d57e1f78efc41e0c5f8516b7aba26aba8e9ab727c42d0998585dcd6de614fb03eddfe56edbb8e338c9eb5fba47a7e22bf876fda4ff00e4ba78f3fec68f107fe9d6ee803fa2afd8fbfe0a5da8fed51f17edbe175c7c3e83c371dc69f7f7bf6e8f596be606c9633b3ca36900f9bccebbf8c74afd60afe55ffe090bff002765a77fd80b5dff00d02dabfaa7cfd7f2a00fcaff00db27fe0a45a87eca1f1507c37b6f0041e268ce9561a97db24d61ac0e6f5ee5767962d271f2fd9f39ddceee9c73f39f813fe0b35ab78d7c61a37854fc26b5b41aaea36360671e22694c42f2e62b7de13ec0bbb6f99bb1919c6322be4fff0082c400ff00b51a4419439f0b686c159829204ba8824648ce0919fad7e797c0a4f27e2ff834c8d18dde21d11540914927fb46d8e00073d013401fdcd5140a2800a2933f5fca96803c9fe3afc4a93e0dfc1ef17fc54874f5d59fc2ba4dcea8b64d31b71706dd777966508e53774ddb5b1e86bf152ebfe0b7babdadccd6c7e0fda3186468c9ff008495c6769c7fd03abf56ff006e0ff9344f8b9ff629ea5ffa28d7f179ac7fc85af7febe65ff00d0cd007ef0ff00c3f1356ffa23d6bff852b7ff002baa7b7ff82deeaf70ec83e0fda8db1cb27fc8cae7fd5a33e3fe41ddf18afc0b485e4190547fbccabfcc8ad1d3a3f26791e578d57ecf723fd6275685c01d7a92702803fbd2d16fceaba4596a853cb3796d0ce501cedf3503e33c6719c56a5737e0eff914f45ffb075a7fe894ae92800afc9ffdb07fe0a5da8fecaff17ee7e175bfc3e83c491dbe9f617bf6e93596b1626f5643b3ca16938f97cbebbf9cf4afd5fcfd7f2afe567fe0af0bbff6b5d4630ca18e83a190198292025ce48c91d322803e9dff0087e26adff447ad7ff0a56ffe5751ff000fc4d5bfe88f5aff00e14adffcaeafc1436f2004929c7fd3443fd6a0a00febb3f61efdba2f7f6c4d4bc536177e0c87c283c39696174ad16a6750f3fedb2dc45b4e6dadf66cfb3e7f8b3bbb639fd10afe7cff00e0893ff21ef89bff00608d0fff004af51afe832803f15fe3effc15b754f825f17fc59f0b63f8616dabc7e1ad525d396f9b5e6b56b8f2951b798bec3204cefe9bdba578effc3f1356ff00a23d6bff00852b7ff2babf34bf6f8ff93b7f8a5ff633ddff00e8a82be3c552edb463f1200fccf1401fbe1ff0fc4d5bfe88f5affe14adff00caea3fe1f89ab7fd11eb5ffc295bff0095d5f82bf6693d63ff00bf89fe347d9a4f58ff00efe27f8d007ef57fc3f1356ffa23d6bff852b7ff002ba8ff0087e26adff447ad7ff0a56ffe5757e0afd9a4f58ffefe27f8d1f6693d63ff00bf89fe3401fbd5ff000fc4d5bfe88f5aff00e14adffcaea3fe1f89ab7fd11eb5ff00c295bff95d5f82bf6693d63ffbf89fe347d9a4f58ffefe27f8d007ef57fc3f1356ff00a23d6bff00852b7ff2ba8ff87e26adff00447ad7ff000a56ff00e5757e0afd9a4f58ff00efe27f8d21b7914124a71e9221fd33401fbd7ff0fc4d5bfe88f5affe14adff00caeafbf3f617fdba2f3f6c9bcf195a5df8362f0a7fc22916992ab45a99d47ed3fda26e460e6dadf66cfb3ffb59dddb1cff002235fbe3ff00043bff0090b7c61ffaf5f0cffe85a8d007f417451450014514500145145007ffd3fdfca28a2800a28a2800a28a2803f9f4ff0082e27fc85be0f7fd7af89bff0042d3abf056dc06908233f2487f2535fbd5ff0005c4ff0090b7c1effaf5f137fe85a757e0b5b7fac3ff005ce4ff00d00d007f693f03be07fc17bdf82fe02bdbdf0178667b8b8f0ce912cb2c9a45a3c9248f6913333318892cc49249e49af53ff850bf03bfe89e785bff0004d67ffc6a93e027fc90df879ff62b68dffa47155ff8b7f16fc0bf03fc0b7df123e245f49a7681a7496f15c5c456f35d3abdd4c9044045023c8dba4751f2a9c67278a00a3ff0a17e077fd13cf0b7fe09acff00f8d51ff0a17e077fd13cf0b7fe09acff00f8d57c99ff000f4afd8b3fe872bfff00c27f55ff00e45a3fe1e95fb167fd0e57ff00f84feabffc8b401f59ff00c285f81dff0044f3c2dff826b3ff00e3547fc285f81dff0044f3c2dff826b3ff00e355f267fc3d2bf62cff00a1caff00ff0009fd57ff009168ff0087a57ec59ff4395fff00e13faaff00f22d007d67ff000a17e077fd13cf0b7fe09acfff008d521f80bf03b07fe2de785bff0004d67ffc6abcefe06fed83f013f68dd7751f0e7c24d76e356bfd2ed16faea39b4dbcb109033f961835cc31ab65b8c2926be9c3d0d007f1c5ff000511d0b44f0e7ed71f11349f0fe9f6ba658dbea96e90db59c296f0c6a74db1721238c2aa8dcc4e00ea49ef58dfb03ffc9dbfc2dffb19ed3ff454f5d7ff00c14abfe4f23e257fd85adbff004d7a7d721fb03ffc9dbfc2dffb19ed3ff454f401fd9bd145140087a7e22bf876fda4ff00e4ba78f3fec68f107fe9d6eebfb8935fcbe7c5ff00f8263fed71e37f8a3e2df14e93e17b06b0d535fd62f6cddf59b48d9edeeefee27899909254b2480e09c8ef8390003f2bbc27e34f177817545d6fc19adea5a0ea0a8f10bad2ef26b19c24980ea258191c2b606e19c1c0cf4af4eff869cfda1ffe8a778d7ff0a4d4bff922bdafe317fc13e7f693f81be04bbf88bf10342b3b2d1ace7b5b69658754b6b97125e4ab0c40469f310646009edd4d7c3f401fd5bffc13a3c29e18f8bdfb345878cfe2c69367e36f103eb3abd9b6ade2381357d41adadae5d6189ae6ec4b3347102422962172718c9afbc22f815f04e09a2b883e1ff85e396191258dd747b30c9246c1d19488b219580208e41008af8bff00e0949ff2687a77fd8c3af7fe95b57e92d0015f92ff00f0574f1c78d3c0bf07fc17a87827c41ab787aea7f11dc4734fa45fdc69f2c91a69b7520477b778d9977a2b6d271902bf5a2bf1bbfe0b3dff00244bc0ff00f6335d7fe9aaf2803f0574efda5bf684b8d42da097e2778d4a4b3468c3fe124d4fa33007fe5e2bfae3fd8db57d5fc41fb2c7c2dd6f5ebeb9d4b51bef0c69f3dcddddcaf3dc4f2bc796792490b3bb31e4b3124d7f159a3ffc85acbfebe62ffd0c57f687fb0fff00c9a27c23ff00b14f4dff00d142800fdb83fe4d13e2e7fd8a7a97fe8a35fc5e6b1ff216bdff00af997ff4335fdb7fed49e08f12fc4afd9dbe21f803c1d6cb79adebfe1fbdb0b0b769121124f326d452f2154504f762057f37179ff04a2fdb22eaee6ba3e16d394cd23c981ae5a1037126803efdff008242fc35f877e31f837e34bcf16f85b45d6ae21f11db4714ba8e9f6f752221d32d1caab4a8c402cc588071924f5afd6b93e00fc0b951a297e1df851d1d4ab2b68b66432b0c10418b90475afca7fd92bc59a0ff00c1387c09ab7813f6b4b83e16d63c59aaff006b6910d8c336b493d95a5a5ada4923496114a232255c159029e463239afaa3fe1e95fb167fd0e57fff0084feabff00c8b401fa0d0c315bc490408b1c71a8444501555546000070001d057e7e7fc14e7c51e26f087ecaba86b3e12d6350d0f505d7745885e699773595c88e5b90aea2581d240194e0807914cff87a57ec59ff004395ff00fe13faafff0022d782fed29f1dbe1a7ededf09efbe027ecc3a94be25f1acb7763ad2595e59dd69109b2d32e236b87fb45ec314594f3106d04b1dc30a6803f9fdff00869bfda1f3ff00253bc6bff8526a5ffc915fd2affc13dfc1be12f8affb30e85e33f8a7a358f8cfc433ea5ad5bcdabf886dd355d46586d7509e386392eaec4b33ac4802a0672157818afc66ff00874dfed8ff00f42be9dff83cb4afd4ff00d9cff687f855fb0b7c25d2bf67dfda5f549bc37e39d36e350d4eeac2d2caef56852db55bc9ee2d985cd9432c2dbd0f4c86041c81401f767c4df819f052dbe1bf8aee6dbc01e188a68744d45e391347b456475b7908652220410790457f13d7aaab3285000f2613c7a98d49fccd7f573e3dff00829bfec6fadf81bc45a2e9fe30be7babfd26fad6053a0ea8a1a59a0744059ad40196206490077afe512ee449250c872047129faaa2a9fd45007ef3ff00c1127fe43df137fec11a1ffe95ea35fd0657f3e7ff000449ff0090f7c4dffb04687ffa57a8d7f419401fc647edf1ff00276ff14bfec67bbffd15056cff00c13bb42d13c47fb5c7c3bd27c41a7daea76371aa5c24d6d790a5c43228d36f9c078e40cac37283823a807b5637edf1ff00276ff14bfec67bbffd150575ff00f04d5ff93c8f86bff616b9ff00d35ea1401fd578f80bf03b03fe2de785bff04d67ff00c6a97fe142fc0eff00a279e16ffc1359ff00f1aaf571d057cc7f1cbf6c1f809fb396bba77873e2debb71a4dfea968d7d6b1c3a6de5f07815fcb2c5ada191570dc61883401e8bff000a17e077fd13cf0b7fe09acfff008d51ff000a17e077fd13cf0b7fe09acfff008d57c99ff0f4afd8b3fe872bff00fc27f55ffe45a3fe1e95fb167fd0e57fff0084feabff00c8b401f59ffc285f81dff44f3c2dff00826b3ffe3547fc285f81dff44f3c2dff00826b3ffe355f267fc3d2bf62cffa1cafff00f09fd57ff9168ff87a57ec59ff004395ff00fe13faafff0022d007d67ff0a17e077fd13cf0b7fe09acff00f8d57967c71f81ff0005ecbe0bf8f6f6cbc05e1982e2dfc33abcb14b1e91689247225a4acacac220432900823906bd87e127c5bf02fc70f02d8fc48f86f7d26a3a06a325c456f712dbcd6aecf6b33c128314e8922ed91187cca338c8e2a87c7bff00921bf10ffec56d67ff0048e5a00fe18ee005900031f2467f3515fbd5ff00043bff0090b7c61ffaf5f0cffe85a8d7e0b5cffac1ff005ce3ff00d0057ef4ff00c10eff00e42df187febd7c33ff00a16a3401fd05d14514005145140051451401ffd4fdfca28a2800a28a2800a28a2803f9f4ff0082e27fc85be0f7fd7af89bff0042d3abf05adbfd61ff00ae727fe806bf7a7fe0b89ff216f83dff005ebe26ff00d0b4eafc16b6ff00587feb9c9ffa01a00fee6fe027fc90df879ff62b68dffa47157c9bff00054aff00932cf197fd7fe81ffa75b4afacbe027fc90df879ff0062b68dff00a47157c9bff054bff932bf197fd7fe81ff00a75b4a00fe44dddf7b7cc7a9ef4df31ffbc7f3a1fefb7d4d32801fe63ff78fe74798ff00de3f9d328a00fdbcff008228127e2ef8e8939ff8a5a0ff00d2e35fd1f9e86bf9bfff008227ff00c95df1d7fd8af07fe971afe900f43401fc7c7fc14abfe4f23e257fd85adbff004d7a7d721fb03ffc9dbfc2dffb19ed3ff454f5d7ff00c14abfe4f23e257fd85adbff004d7a7d721fb03ffc9dbfc2dffb19ed3ff454f401fd9bd1457e55ff00c1467f6cef8b5fb296b1e0bb3f86b06892c1af58ea77578756b296ed835a4d6b14623f2ae6df6822762d9dd9c0e9401faa9457f2f51ffc163bf6a598b2c763e0edc2391c6747ba00ec52d8cff689eb8afe973c0fabddf883c17a06bfa86c175a9e976779388c6d4f327851df682490bb98e064e05007c35ff0547ff933df117fd863c3dffa73b7afe448f5afee6fe3a7c12f07fed09f0eaf3e1878ee4bd8b47beb8b4b995b4f985bdc6fb399678f6c855f68de833819c7420f35f0b7fc3a17f64eff009ede2bff00c1bfff006aa00e93fe0949ff002687a77fd8c3af7fe95b57e92d78c7c06f815e0bfd9d7e1f43f0d3c0325f49a44177757a87519c5c4fe65dbf9926640a991bba6467d49af67a002bf1bbfe0b3dff00244bc0ff00f6335d7fe9aaf2bf646bf1bbfe0b3dff00244bc0ff00f6335d7fe9aaf2803f9a5d1ffe42d65ff5f317fe862bfb43fd87ff00e4d13e11ff00d8a7a6ff00e8a15fc58dbcef6b7115cc78df13abae791953919afd32f85fff000552fda1fe147c3cf0efc35f0d597859b4af0d69f069b686ef4bb99a730c0bb54c922dfc6acd8ea42283e82803fac4a2bf977ff87c9fed45ff003e3e0eff00c135d7ff002ca8ff0087c9fed45ff3e3e0effc135d7ff2ca803dbbfe0b6848f197c3920e3fe243aaff00e96d9d7e10798ffde3f9d7f47ff00bc1da37fc1527c2179f10ff00696f32df53f056a0da2e98be1677d2a06b4bdb6b5bd733248f74cf2798c0021c2e147cb9e6bdc3fe1d0bfb270c7ef7c57d7fe82fff00da6803f950f31ffbc7f3afd6cff823ab16fda81b7127fe298d77affd75d36bf347e29f8734ef097c41f10f87349f33ec9a6eada959c3e6b6f7f2ad6f2781371c0c9d918c9ee726bf4b7fe08e9ff2740fff0062c6bbff00a374da00fea3abf957ff0082bc92bfb59ea3b4e3fe245a174ff72e6bfaa8af86fe3c7fc13ebe047ed17f10a7f897f10a6d786ad71696d64cba7df8b680456818261046c777ce72493ed8a00fe3a77bff0078fe74dafeacff00e1d0bfb277fcf6f15ffe0dff00fb551ff0e85fd93bfe7b78afff0006ff00fdaa803e3cff008224ff00c87be26ffd82343ffd2bd46bfa0caf937f66efd8d3e117ecb3a86bba97c329758793c436f6b6f76ba9de0bb50966f2bc7e5feed0a9cccd9e483e80e73f595007f191fb7c7fc9dbfc52ff00b19eefff0045415d7ffc1357fe4f23e1affd85ae7ff4d7a85721fb7c7fc9dbfc52ff00b19eefff0045415d7ffc1357fe4f23e1affd85ae7ff4d7a85007f60e3a0afe703fe0b5e48f8bbe0520e3fe2969ff00f4b857f47e3a0afe6fff00e0b61ff2577c0bff0062bcff00fa5c2803f11fcc7fef1fce8f31ff00bc7f3a651400ff0031ff00bc7f3a723bef5f98f51dea2a7a7df5fa8a00febbbfe096bff2659e0dff00affd7fff004eb775f597c7bff921bf10ff00ec56d67ff48e5af937fe0969ff002657e0dffaff00d7ff00f4eb775f597c7bff00921bf10ffec56d67ff0048e5a00fe192e7fd60ff00ae71ff00e802bf7a7fe0877ff216f8c3ff005ebe19ff00d0b51afc16b9ff00583feb9c7ffa00afde9ff821dffc85be30ff00d7af867ff42d46803fa0ba28a2800a28a2800a28a2803fffd5fdfca28a2800a28a2800a28a2803f9f4ff0082e27fc85be0f7fd7af89bff0042d3abf05adbfd61ff00ae727fe806bf7a7fe0b89ff216f83dff005ebe26ff00d0b4eafc16b6ff00587feb9c9ffa01a00fee6fe027fc90df879ff62b68dffa47157c9bff00054bff00932bf197fd7fe81ffa75b4afacbe027fc90df879ff0062b68dff00a47157c9bff054bff932bf197fd7fe81ff00a75b4a00fe445fefb7d4d329eff7dbea69940051451401fb77ff00044fff0092bbe3affb15e0ff00d2e35fd201e86bf9bfff008227ff00c95df1d7fd8af07fe971afe900f43401fc7c7fc14abfe4f23e257fd85adbff004d7a7d721fb03ffc9dbfc2dffb19ed3ff454f5d7ff00c14abfe4f23e257fd85adbff004d7a7d721fb03ffc9dbfc2dffb19ed3ff454f401fd9bd7e04ffc167f41d6f5df11fc328747d3eeef58693ae2b7d96da5b80a4dd5811bbca47db90a719f4afdf6a69556ea01a00fe11edfe1c78eadd9e59340d57688661ff20ebbead1b01d6103a9ef5fdbf7c2d0cbf0cfc228e0ab2e85a68208c1045b47c107a5773e5a7a0fc853e800a28a280336e759d26ce5f22f2f2de09300ec925446c1e870c41a80788741660aba8da1248000b88f249e83ef57f31dff0005859043fb5224ab1c6cff00f08b686a19e3572019751240dc0e33819fa0afcf3f819706e7e2ef839658e1f97c43a23295891483fda36c382003d091f4a00fee5abf1bbfe0b3dff244bc0fff006335d7fe9aaf2bf64057e37ffc167bfe489781ff00ec66baff00d355e5007f32945145005ed3b4dbed5af23b0d3a096e6e263b638a18da576382701103313804e002715d77fc2b2f1eff00d00355ff00c175dfff0019afabff00e09bff00f2781f0d7fec36dffa6ebfafec3446981f28e9e82803f1d3fe08faa7c33f06fc6f67e220da54ede25b7291df2b5a3baae996a85952608c5772919c60e38afd75ff00848b403c7f69d9ff00e0447ffc557f3fff00f05b02b178d7e1bc8123629a16ac577a2b004de5a0c80475c122bf0e6c2e9ae2592296280a9b7b93c431820ac2e41042e4104645007b87c6af01f8c755f8ade2ebcb1d13529a17d7f592922585cba3abea172eacae9132b2956041048e6bef9ff824ae83ac7853f69b6b8f12595d69907fc233ad0f3af6de5b588b3cb61b543cc91a963b18e01ce057f4a3e0f443e13d17e51ff20eb4edff004c52bf3f7fe0ab91c67f642d49591581f10e84082a0820dd2e4104720d007e877fc247a07fd04acfff000223ff00e2ab4ad6eed6f62f3ed258e78c9203c6e1d723af2b91c57f035fda137fcf383fefc47ffc4d7f5bbff04b6444fd8efc3891aaa28d5fc4202a80000353b8c0007000a00fd10a28a2800a28a2803f8c8fdbe3fe4edfe297fd8cf77ffa2a0aebff00e09abff2791f0d7fec2d73ff00a6bd42b90fdbe3fe4edfe297fd8cf77ffa2a0aebff00e09abff2791f0d7fec2d73ff00a6bd42803fb071d057f37fff0005b0ff0092bbe05ffb15e7ff00d2e15fd200e82bf9bfff0082d87fc95df02ffd8af3ff00e970a00fc44a28a2800a7a7df5fa8a653d3efafd45007f5ddff04b4ff932bf06ff00d7febfff00a75bbafacbe3dffc90df887ff62b6b3ffa472d7c9bff0004b4ff00932bf06ffd7febff00fa75bbafacbe3dff00c90df887ff0062b6b3ff00a472d007f0c973feb07fd738ff00f4015fbd3ff043bff90b7c61ff00af5f0cff00e85a8d7e0b5cff00ac1ff5ce3ffd0057ef4ffc10effe42df187febd7c33ffa16a3401fd05d14514005145140051451401fffd6fdfca28a2800a28a2800a28a2803f9f4ff0082e27fc85be0f7fd7af89bff0042d3abf05adbfd61ff00ae727fe806bf7a7fe0b89ff216f83dff005ebe26ff00d0b4eafc16b6ff00587feb9c9ffa01a00fee6fe027fc90df879ff62b68dffa471579dfed83f0375dfda37e026bbf093c39a8da6957fab5c69b347757caed020b1bc86e58308fe63b9622063b9eb5e87f0108ff00851bf0f3fec55d1bff0048e2af5ac8a00fe6fcff00c1143e2e9249f1d785b9ff00a617dfe347fc393fe2effd0f5e16ff00bf17dfe35fd20645191401fcdfff00c393fe2eff00d0f5e16ffbf17dfe349ff0e4ff008bbff43d785bfefc5f7f8d7f483914645007e58fec15fb05f8f3f647f1e788bc53e29f1168facdaeb3a3c7a745169d1dc2491c897026dcde7020a9191c1f4e2bf534f434b914d2460d007f1f3ff052aff93c8f895ff616b6ff00d35e9f5c87ec0fff00276ff0b7fec67b4ffd153d75dff052aff93c8f895ff616b6ff00d35e9f5c8fec0fff00276ff0b7fec67b4ffd153d007f66f5f16fed59fb6e7807f64ad47c3da778d340d67596f11db5e5cc0fa59b50b1259490c6fe61b99e1e4b4ebb76e7bf4afb4abf9f3ff82db7fc87be197fd8235cff00d2bd3a803e81ff0087d07c04ff00a12bc59ff7de95ff00c9f4bff0fa0f809ff42578b3fefbd2bff93abf98dab42caf5806582520f20843cfe9401fd71fecdfff00051cf85dfb4cfc4cb7f85fe11f0cf8834cbeb9b2bbbd5b9d40d91b7096610ba9fb3dcccfb8ef5c7cb8f7afd0fafe56ff00e09150cb07ed67a68991a32741d771b815fe0b6f5afea8f23d6803f21bf6ddff0082757c44fdaa3e327fc2c5f0e789b43d234f1a369fa77d9f508ee5e732d9bdcb17cc3850a44f81c93c76eff2c785bfe090df163e1c788b4df1ddcf8d3c35716fe1fbcb5d5a782186f165962d3a78ee9a342f950ce22da0918c9e7d6bfa21eb5cdf8c7fe453d6bfec1d77ff00a25e803f268ffc1677e032852de08f172ef557019b4b076b80cbc1bec8c820e0f35c3fc46f1b695ff0565f0d45f0dfe0f4371e0dbbf03de47aededc789d62920b9b7bd82eac56384584f3b6f0c589de54003be715fcebeadff001f31ff00d7b5affe884afdceff0082257fc8e3f11bfec03a57fe96de5007327fe08a1f1740ff0091ebc2dff7e2fbfc6bf2afe3f7c1bd57e02fc55f107c2dd66fadb51bbf0fde0b396e6d15d61918c10cf94127cc06d980e79c83ed5fdca1e86bf8efff00828fff00c9e07c4aff00b0e2ff00e9bac2800ff82707fc9e07c35ffb0e37fe9bafebfb101d057f1dff00f04e0ff93c0f86bff61c6ffd375fd7f6203a0a00fe797fe0b6bff238fc39ff00b00eabff00a5b675f855633a5bcecefd1a29a3fa19236407e809e6bf757fe0b6bff238fc39ff00b00eabff00a5b675f83d401fd2ee85ff000591f80fa5e8ba7e9b27833c58cf6b6b040cc1f4bc168e3552466f81c64771f957cc3fb6a7fc14a3e137ed1df02eefe19785bc35e20d33509f54d36f96e2fdac0c012ce612ba9f22ea5932c0617e5c67a902bf12858de91916f2907fd86ff0a6496b7312ef9629117a659481fa8a008475afebb7fe0971ff00267be1dffb0c7887ff004e7715fc890eb5fd76ff00c12e3fe4cf7c3bff00618f10ff00e9cee2803f43a8a2932280168a4c834b401fc647edf1ff00276ff14bfec67bbffd150575ff00f04d5ff93c8f86bff616b9ff00d35ea15c87edf1ff00276ff14bfec67bbffd150575dff04d5ff93c8f86bff616b9ff00d35ea1401fd840e82bf2cbf6f5fd82fc79fb5c78f3c3be29f0b788b47d1ad746d1e4d3a58b518ee1e49247b8336e5f2400140c0e4faf15fa9808c0a7645007f37dff000e4ff8bbff0043d785bfefc5f7f8d2ff00c393fe2eff00d0f5e16ffbf17dfe35fd20645191401fcdff00fc393fe2effd0f5e16ff00bf17dfe340ff0082287c5d0411e3af0b71ff004c2fbfc6bfa40c8a322803e62fd8fbe06ebbfb397c04d0be12788f51b4d56ff49b8d4a692eac55d60717d7935ca85127cc36aca01cf71d6bd13e3dff00c90df887ff0062b6b3ff00a472d7ace45792fc7b23fe146fc43ffb15759ffd2396803f865b9ff583feb9c7ff00a00afde9ff00821dff00c85be30ffd7af867ff0042d46bf05ae7fd60ff00ae71ff00e802bf7a7fe0877ff216f8c3ff005ebe19ff00d0b51a00fe82e8a28a0028a28a0028a28a00ffd7fdfca28a2800a28a2800a28a2803f9f4ff0082e27fc85be0f7fd7af89bff0042d3abf04158a1cafa11f81183fa57f4a3ff00056df805f183e36ea9f0c24f85be14d4fc4b1e8f6daf2df369d12cbe435d358f941f73a63788df18feed7e38ff00c303fed6dff44b7c4fff008091ff00f1fa00d9d0bfe0a23fb5c787344d3fc3da4fc44d52dec74bb586ceda14b7d34ac70dba08e3405ac59b0aaa00cb13ea4d6aff00c3cabf6c8ffa295ab7fe03697ffc815c8ffc303fed6dff0044b7c4ff00f8091fff001fa3fe181ff6b6ff00a25be27ffc048fff008fd0075dff000f2afdb23fe8a56adff80da5ff00f2051ff0f2afdb23fe8a56adff0080da5fff00205723ff000c0ffb5b7fd12df13ffe0247ff00c7e8ff008607fdadbfe896f89fff000123ff00e3f401d77fc3cabf6c8ffa295ab7fe03697ffc8147fc3cabf6c8ff00a295ab7fe03697ff00c815c8ff00c303fed6dff44b7c4fff008091ff00f1fa3fe181ff006b6ffa25be27ff00c048ff00f8fd0075dff0f2afdb23fe8a56adff0080da5fff0020527fc3cabf6c8ffa295ab7fe03697ffc815c97fc303fed6dff0044b7c4ff00f8091fff001fa3fe181ff6b6ff00a25be27ffc048fff008fd007cebf123e2478c3e2cf8c351f1df8ef519755d6b559566bbbb99634795d23484332c291c6088e345f9514614719c93f457ec0ff00f276ff000b7fec67b4ff00d153d1ff000c0ffb5b7fd12df13ffe0247ff00c7ebe98fd8f3f63cfda5be1f7ed2df0e7c57e2bf875e20d3748d37c416d737979736c890c10a24aa5dcacac4005864e2803fa9fafe7cff00e0b6dff21ef865ff00608d73ff004af4eafe832bf9f3ff0082db7fc87be197fd8235cffd2bd3a803f066cd16499838c8114ac3eab1b107f022bfaf9f879fb057ec81ab7807c35aaea3f0bf459aeaf347b0b89e5612e5e596046763fbcea58926bf907b26559cee38cc5328f72d1b003f12715fd90fc39fdb1bf655d37e1ef8634ebff8b3e1082e6d746d3e19a29357b7578e48edd1595817c82a41047ad007ce1fb5bfc16f85bfb227c0ed63e357ecdbe1db3f0178dec6f34bd3edf5ad3503dc476ba8dec305cc616e3ce888923623e68db0704722bf157fe1e53fb647fd14ad5bff0001b4bffe40afdbcfdb5be2a7c36fda5ff67bd6fe15fecfde27d27e2178c6eafb48bd8343f0f5ec37b7f2dbd8df4335c48b1abfdd8e352cc4900773c8afc15ff8607fdadbfe896f89ff00f0123ffe3f401fd28ffc13bbe28f8ffe30fecd965e35f895accdaf6b72eb5abdabde5c2431bb436d7063894ac11c51fcaa3b28cd7d85e31ff914f5affb07ddff00e897af8bff00e09bdf0fbc6ff0cbf663b1f0a7c41d12f3c3fac47adeb170f657e82399629ee0bc6c402c30ca72082457da1e31ff00914f5aff00b07ddffe897a00fe0eb56ff8f98ffebdad7ff44257ee77fc112bfe471f88dff601d2bff4b6f2bf0c756ff8f98ffebdad7ff44257ec37fc123be317c2af84be27f1ddefc4ef16691e1682fb46d360b57d5aee3b459a58aeee9dd10c840665575240e8181a00fe988f435fc77ffc147ffe4f03e257fd8717ff004dd615fd399fdb4ff64ac1ff008bbde0dffc1c5b7ff175fcb57edebe2ef0b78e7f6a3f1f78a3c1bab59eb7a46a1ab89ad2fac6659ede78fec3671ee8e4525586f465c8e32a476a00e8bfe09c1ff2781f0d7fec38dffa6ebfafec407415fc66fec15e2ef0b781bf6a3f00f8a3c65ab59e89a469fab99aeefafa6582de08fec3791ee924621546f755c9e32c077afea547eda7fb25607fc5def06ffe0e2dbff8ba00fc82ff0082daff00c8e3f0e7fec03aaffe96d9d7e16e9d124b70cb20c8582e1c76f99227653f8100d7ef97fc1483c33ac7ed87aef8375ffd98adcfc4dd2b43d3751d3b53bdf0cbc57f059ddcd716b3c714ae24550ed1a16db9ce3071820d7e665afec21fb5b5a3c92ffc2abf13b930cd181f648c64c91b20ff0096c7b9f43401fd24f863f603fd8eef7c39a55ddcfc2dd1249a6b1b69247226cb3bc4a589fde7524e6be31ff828bfec8dfb37fc25fd9a2ffc61f0e7c07a6685acc7ad68f6cb796a24122c3717212551b9c8c3a9c1e3a57e81e8bfb5e7ecbda069165a16b7f153c2763a8e9d6d0da5ddadc6ab04734171022a491488cc0aba3a95652320820f35f29fedc9f12be1ff00ed41f002ff00e177ecefe23d2fe22f8b9f54d2f515d17c3b790df5e9b3b3b9579e6f2d1f848c119624004819c91401fcb00eb5fd76ff00c12e3fe4cf7c3bff00618f10ff00e9cee2bf9d6ff8607fdadbfe896f89ff00f0123ffe3f5fbd5fb14fc54f86dfb347ecf7a27c2bfda07c4fa4fc3df18dadf6af7b3e87e21bd86cafe2b7bebe9a6b791a367fbb246c1948241ec783401fa39f10f50bdd27c03e25d574d99adeeecb47bfb8825500b472c503b230c8232ac01190457f2572ff00c1493f6ca80a46df13355663144cc45ae96012e8ac703ec070327d6bfa35f88dfb637ecaba97c3df13e9d61f167c213dcdd68da8430c51eaf6ecf24925bbaaaa80f92589000f5afe37af595a71b4e71142a7d8ac6a08fc08c5007f4b7ff04aff00da53e35fc7ed77c7d17c59f155df88a0d2b4dd227b18eea1b58fc892e2e2f2394a9b6820cee5853ef6718e3ad7ecb57f3e7ff0449ff90f7c4dff00b04687ff00a57a8d7f419401fc647edf1ff276ff0014bfec67bbff00d15057cebf0dfe2478c3e1378c34ef1df81351974ad6b4a95a6b4bb85637789de3784b2acc9246498e475f9918618f19c11fa45fb61fec79fb4b7c41fda5be2378afc29f0ebc41a9691a97882e6e6cef2dad91e19e1748943a16954904a9c1c57ccfff000c0ffb5b7fd12df13ffe0247ff00c7e803adff0087957ed91ff452b56ffc06d2ff00f90297fe1e55fb647fd14ad5bff01b4bff00e40ae47fe181ff006b6ffa25be27ff00c048ff00f8fd1ff0c0ff00b5b7fd12df13ff00e0247ffc7e803aeff87957ed91ff00452b56ff00c06d2fff009028ff0087957ed91ff452b56ffc06d2ff00f902b91ff8607fdadbfe896f89ff00f0123ffe3f47fc303fed6dff0044b7c4ff00f8091fff001fa00ebbfe1e55fb647fd14ad5bff01b4bff00e40a3fe1e55fb647fd14ad5bff0001b4bffe40ae47fe181ff6b6ff00a25be27ffc048fff008fd1ff000c0ffb5b7fd12df13ffe0247ff00c7e803aeff0087957ed91ff452b56ffc06d2ff00f902b2b5dff8288fed71e23d1350f0f6adf11354b8b1d52d66b3b985edf4d0b2437086391095b156c32b1070c0fa1158dff0c0ff00b5b7fd12df13ff00e0247ffc7e8ff8607fdadbfe896f89ff00f0123ffe3f401f1e3317396f403f00303f4afdefff00821dff00c85be30ffd7af867ff0042d46bf34ffe181ff6b6ff00a25be27ffc048fff008fd7ec77fc124be017c60f825aa7c4f93e29785353f0d47ac5b682b62da8c4b179ed6ad7de684daef9d8244ce7fbd401fb53451450014514500145145007ffd0fdfca28a2800a28a2800a28a2800a28a2800a2bf0abe25ff00c161b5ef87df117c55e051f0e34bb94f0e6b9a9e8e93c9ad5cc6f32e9f7525b891916c64552fe5eeda19b19c64d713ff000fb6d7bfe899691ff83dbbff00e575007f417457f3e9ff000fb6d7bfe899691ff83dbbff00e5757eacfec69fb48ea3fb537c239be276a5a15bf87a44d62f34c5b4b7ba7bc42b6823fde798f14272c5cf1b78007738001f5951451400514507819a0028afc69fda53fe0aa1aefc00f8d7e2af84d0f8074ed520f0eddc56b1df4fabdc5bc93f996b05c9631476532ae3cfdbf7ce719e2b1be037fc15975ff8d1f183c23f0bdbe1ee99a7c5e25d561d3a4bc8758b89dedd65576de237b28836021e370e6803f6bebf9f3ff82db7fc87be197fd8235cff00d2bd3abfa0caf85ff6befd877c39fb5dea7e1ad4b5ff0014df787bfe11bb5bdb548eced60b813adec9048c5fcece369817181dfad007f1d757d755d4d542addce00180048d8007e35fd1d7fc394be198ff009a91ad751ff30bb1ff00e26bf9f8f8abe0fb7f00fc43f11783ad2e1eea1d1b57d4b4e8e67508d22d8de4d6cae557805c441881c027038a00fd19ff0082465d5cdd7ed69a6b5ccaf295d075e00bb16c7c96deb5fd50d7f153fb23fed2573fb2e7c56b7f89969a2c1aec9058df590b5b8b992d2322f563058c91c533657cbe06c39cf518afd4bff87db6bdff0044cb48ff00c1eddfff002ba803fa0bae6fc63ff229eb5ff60fbbff00d12f5e09fb21fed037ff00b4cfc1bb7f8a7a968b068134fa95fd81b2b7b97bb8c0b297cade2578e263bf19c6c18afa3b57d3c6ada5de696ce621776f2c05c0c95f350a671c6719cd007f04fab7fc7cc7ff005ed6bffa212a9c175736a49b695e22c304a315c8fc2bfa433ff0454f86ee13cdf895adbb2a2264e996478450a3a827a0ee49f7af863f6eeff827df847f650f00787fc5da078b2ff5d9b58d5a6d3de1bbb3b7b758d23b39ae770308049262db8391839eb401f95bfdadaaff00cfe4ff00f7f1bfc6a94b2c933996676776e4b31c93f526a3a280248a5921712c2ec8ebc8653823e8455dfed6d57fe7f27ffbf8dfe35eeffb2cfc1cd37e3cfc6ff0a7c2ed5b509b4bb5d7efcd9c97504692c91016d713ee5493e4273005c1e3049ea057ee00ff0082297c33233ff0b235affc15d8ff00f13401d37fc11964925f82be3a92562eede26b52598e493fd9567d49afd8d35f865e2bf1ec7ff048fb1b3f873e14b31f112d7c74f71afcd79ab5c7f653d9b592dad888512d6de7575652a724291839ce45705ff0fb6d7bfe899691ff0083dbbffe575007e3c7c74bfbeb7f8b9e308edee258d3fe121d6ced472a32752baec0d7e85ffc11eeeae6ebf6a22d732bca57c2fae805d8b6079ba77ad7e5cf8f7c527c6be2fd5fc52d6eb6a755bfbdbe30ab17119bcb896e0a86201214c9b4120120670338afd3ff00f823a7fc9d03ff00d8b1aeff00e8dd36803fa8eafe57bfe0ae775736bfb5a6a4d6d2bc45b41d04128c573f25cfa57f5435f9a9fb507fc136fc21fb4f7c54b9f8a3aef8d352d167b8b1b3b1fb1dad95b4f105b30e15b74d96c9f30e7a7e3401fc9eb6aba9b2956bb9c82304191b041fc6a857f44de30ff8235fc37f0e784b5bf1043f113589a4d334ebbbc48db4db250ed6f13481490b90095c64735fcf05c4421902039ca46fff007da86c7e19a00fde9ff8224ffc87be26ff00d82343ff00d2bd46bfa0cafe7cff00e0893ff21ef89bff00608d0fff004af51afe832800a2bf143e3cff00c15975ff0082ff00183c5df0bd7e1ee99a845e1ad566d3a3bc9b58b881ee162546de634b2942e438e371e6bc8ffe1f6daf7fd132d23ff07b77ff00caea00fe82e8afe7d3fe1f6daf7fd132d23ff07b77ff00caeafd39fd88ff006acd47f6b5f00ebfe34d47c3b6de1c6d1f591a5a416b7925e24aa6d60b9126f9218083fbedbb76f6eb401f69514d63b549f415f819af7fc1683c47a0eb7a868f2fc33d249b2bbb9b5dcdae5d02df6795e2dd81a7b01bb667193d6803f7d68afcabfd8c7fe0a33ac7ed5df16a6f86b79e0bb1d0608b45bdd585e5aea735e396b496da2f2cc725ac0006fb46770638dbd39afd54a0028a28a0028a28a0028a28a0028a28a0028a28a00fffd1fdfca28a2800a28a2800a28a2800a0d141a00fe1e3f69cff009388f89fff0063af893ff4e5715e155eebfb4e7fc9c47c4fff00b1d7c49ffa72b8af0aa002bfab3ff82427fc9a74dff635eaff00fb46bf94cafeacff00e0909ff269d37fd8d7abff00ed1a00fd49a4cfd7f2ac8f10b32685a8ba31565b49c865382088db9047435fc4a4ff00b467c73b63144be3df14bfee6262cde20d5324b20249c5d81c93d801401fdc167ebf95213c1ebf957f0eff00f0d27f1d3fe87bf147fe143aafff0025d4f6bfb47fc729ae6289fc77e28daeeaa7fe2a1d57a138ff009fba00f7ff00f829242d3fed95f13111e30cbaada92af2221c369761838620e0e0f35c8fec1b09b7fdadfe16798f165bc4d6980b2a39e239bb2927b8fcebfa4bfd887c2be18f1bfeca7f0e3c57e34d22c75fd6f53d27cfbdd4b54b68ef6f6ea532c837cd3ccaf2c8d80065989c003a015f59d9fc38f87ba75dc37fa7786346b5bab771243343a7dbc7246e3a32b2a02a47620e6803b4a4cd2d7e137fc1623e2278ebc07e21f86ede0df10eafa22cfa56b524c9a6ea37560b33c77360a8d20b6962de543b05dd9c64d007eeb93f5ea2bf877fda5011f1d7c780f1ff001547887ff4eb794a3f694f8ea0e478f3c51c7fd4c3aaff00f25d78cea3a8deead7b3ea3a8cf25cdcdcc8f2cb2caed248f248c599999896666624b3124b124924926802951451401fd6bffc1293fe4d0f4eff00b1875eff00d2b6afd25afcdaff0082527fc9a1e9dff630ebdffa56d5fa07e2e778fc2dac491b3232d85d156524302227208239045007439fafe55f8ddff059e23fe149f817732a6ef13dca82ec1172da5de0032d81c9381ef5f82377fb457c72b478e14f1ef8a587d9edd8b3788354c96789189e2ec0e493d062bf5fff00e0929e22d6fe3178b7c7b6ff00152faefc5f6f65a369b716906bf733eaf15b4d25d5d46f242b7b24fe5b3a22ab15c640e6803f047ec13ff7a1ff00bff17ff1555648da26d8f8cfa82181fa11907f0afeef0fc2bf86183ff148683ff82cb6ff00e375fc8eff00c143f4cd3748fdadbe2358695690595b43ad2ac70dbc6b1468bfd9f6270aa80003249e07527d6802f7fc1383fe4f03e1affd871bff004dd7f5fd880e82bf8eff00f82707fc9e07c35ffb0e37fe9bafebfb101d05007f3cff00f05b101fc6bf0de20e8acfa16aa143baa648bcb438058819c027f0afc29fb04ffde87feffc5ffc557f799ad783bc25e249a2b8f11e89a76ab2c0a5227bdb48ae19158e4aa991588048c903bd629f857f0c4608f08e83ff0082cb6ffe37401fc20b2953b4f515fadbff000474ff0093a07ffb1635dffd1ba6d7e757c73b7b7b5f8b5e2d82da248624d7b595548d42aaaaea372a000300000000760315fa2bff000474ff0093a07ffb1635dffd1ba6d007f51d4514500707f153fe49878bff00ec03a9ff00e93495fc265fff00af5ffae307fe8a5afeecfe2a7fc930f17ffd80753ffd2692bf84cbff00f5ebff005c60ff00d14b401fbc7ff0449ff90f7c4dff00b04687ff00a57a8d7f4195fcf9ff00c1127fe43df137fec11a1ffe95ea35fd065007f191fb7c7fc9dbfc52ff00b19eefff0045415f1d57d8bfb7c7fc9dbfc52ffb19eeff00f45415f1d50015fd397fc117ff00e48278d7fec6d4ff00d355857f31b5fd397fc117ff00e48278d7fec6d4ff00d35585007ec3cbfeadbe87f957f085f137fe47dd7ffec2ba8ffe95cd5fddecbfeadbe87f957f085f137fe47dd7ff00ec2ba8ff00e95cd401fa71ff00046bff0093a3bdff00b13f58ff00d2ad36bfa88afe5dff00e08d7ff27477bff627eb1ffa55a757f51140099a33f5fcabf97cff00829cfc5ff8a1e08fdae3c51a4f85bc5bafe9760b61a33a5a596b17d696e8d25a02ecb1413c68a58804e07279ea493f9f3ff0d27f1d3fe87bf147fe143aafff0025d007f7139fafe54b9afe1d7fe1a4fe3a7fd0f7e28ffc28755ffe4bafdb7ff8236fc49f1dfc41d57e2baf8d3c41aaeb6b636de1e36eba96a3777e213335fef31fda669766ed8bbb6e33819e82803f74e8a28a0028a28a0028a28a00ffd2fdfca28a2800a28a2800a28a2800a28a2803f9a0f8c9ff0004b1fda8fc77f16bc6de32d22c3447d3f5df126b1aa5a33eb29139b7bebc9a78b72181b6b6c719193835e6dff0e85fdacffe81da17fe0f53ff0091abfaa8a2803f956ff8742fed67ff0040ed0bff0007a9ff00c8d5fa2bfb337c5ef007fc13cfe1c37c01fda5f50934af17cda85cf8892df48b3bbd62dffb3f516d90399eda02818bc12295201f9738c115fb295fcbbffc164ffe4e8ecbfec4fd1fff004ab51a00fd71bcff00829a7ec8fad5acba2d87887566bad414d9dbab683a8a2b4d71fba8d4b340146e76032481cd7e38bffc122ff6b5b811bc9a7683b8471a12baea00762819c7d98e338f535f9c9f0cbfe47dd03fec2ba77fe95c35fddec7f717e83f95007f2b5ff0e85fdacffe81da17fe0f53ff0091aa487fe0915fb59c12a4c34dd0498d8363fb753f84e7fe7dabfaa4a43d0d007e517c16fdadfe077ec89f0b7c3bfb36fc6ad5ef2c7c6fe02b34d375ab7d3f4bbdd42d63b871f68511dcc10b4720314c8dc1c8dd820115ef9e03ff008288fecbbf123c67a2f807c29aeea771acebf789636314da25fdba493c8090a64961545e149c93d057f3b3ff00052aff0093c8f895ff00616b6ffd35e9f5c87ec0ff00f276ff000b7fec67b4ff00d153d007f66f5fcf9ffc16dbfe43df0cbfec11ae7fe95e9d5fd0657f3e7ff05b6ff90f7c32ff00b046b9ff00a57a75007e07d145140051451401fd6bff00c1293fe4d0f4effb1875effd2b6afd0bf11d9cfa8e81a969f6a034d7367710c609c02f246caa33db93d6bf3d3fe0949ff2687a77fd8c3af7fe95b57e92d007f2b937fc1237f6b7ba31c92e9ba08758a28ce35d40331a2a671f6738ce338c9fad7e99ff00c136bf63af8cff00b2ef8a3c65a8fc50b4d3aded359d2ec2d2cdacb505bd6692dee2795f7811c7b462518e0f4afd70a28010f435fcf67ed75ff04defda47e367ed0de36f88de11b0d1e4d135bd4d6eaca4b8d592de668fec96d09df19864dbf34271f374ed5fd0a51401fcf5fec8bff04defda47e09fed0de09f88de2eb0d1e3d1344d4daeaf64b7d592e2658fec97308d918863ddf34c33f374ed5fd098e82968a00f9a3e3b7ed6ff0004ff00670d5349d1fe2aea77b6175addb4d7566b69a6dd5f878addd6390936f1becc33a8f9b19c8af076ff0082a47ec76aacede23d602a02cc4f87b53002a8c924fd9f8000c935f9e7ff0005b4ff0091c7e1cffd80755ffd2db3afc31d2bfe3e64ff00af6baffd10f401fadbe3bff825bfed47f107c61ad78d348d3f443a7eb7a95f6a566d26b291486dafee65ba88ba181b6bec94061b8e0f7afaff00fe09f3fb07fc7bfd9b3e3a1f1d7c45b2d2e1d18e85a9d8f9967a9ade4bf68bb7b4641b0451e1710373cf26bf667c1dff00229e8bff0060fb4ffd1295d250014514500707f153fe49878bff00ec03a9ff00e93495fc265fff00af5ffae307fe8a5afeecfe2a7fc930f17ffd80753ffd2692bf84cbff00f5ebff005c60ff00d14b401fbc7ff0449ff90f7c4dff00b04687ff00a57a8d7f4195fcf9ff00c1127fe43df137fec11a1ffe95ea35fd065007f3a5fb507fc1343f698f8bdf1f7c75f113c33a7e8cfa3ebdaddc5f58b4daba412986448d4178cc2fb4fc99c67bd7c83f15bfe099dfb47fc1ff00879ae7c4bf175968f168fe1fb6fb55e3c1aba5c4a23dcabf2c62042c72dd370afebaebe37ff82837fc99a7c55ffb021ffd1d1d007f19d246d148d13fde462a7ea38afe9bff00e08bff00f2413c6bff00636a7fe9aac2bf997beff8feb8ff00aeafff00a11afe9a3fe08bff00f2413c6bff0063627fe9aac2803f621c165207706bf978f157fc1277f6b0d7fc49aa6b11e9ba108ef2faeee23ff89da29d93cf24ab91f676c101b07935fd44d1401fcf5fecbdf00bc67ff04e6f8852fc77fda58d9e93e0fb9d2aebc3b1dc6957126b170750d466b69605305bc01c215b671bb07e62a38cd7e837fc3d1ff63dff00a18b58ff00c27b53ff00e47ae0ff00e0af7ff269d0ff00d8d7a47fed6afe54109debf51401fb93fb477eca7f137f6f6f8a1a87ed23fb3f269fa9781b5f82d6cec2e751bc6d2ae9a6d255ad2e43db4f019102ce8ea37004edcf422be68f157fc129bf6a4f09786757f156aba7e88b65a358dcea170535a4918436b1b4ae557ece371daa703233eb5fb79ff04b4ff932bf06ff00d7febfff00a75bbafacbe3dffc90df887ff62aeb3ffa472d007f0bce850e1bd01fc08c8fd0d7ef7ffc10effe42df187febd7c33ffa16a35f82d73feb07fd738fff004015fbd3ff00043cff0090b7c61ffaf5f0cffe85a8d007f417451450014514500145145007ffd3fdfca28a2800a28a2800a28a2803f2b7fe0a43fb657c54fd94350f005bfc371a518fc4d06b125e7f69583de9dd60d6823d9b6e6df6e44ed9ceecf1d3bfe62ffc3e27f6a370e224f0b9708eca1b4394025549c1235138ce3ae0d7d03ff05c4ff90b7c1eff00af5f137fe85a757e0adb902439fee49ffa01a00feedbe14f88753f16fc30f08f8af5b3136a3ace85a6ea176604314467b9b78e590a21662abb98e14b3607193d6bbfaf06f813e2cf0bc1f047e1f4336b1608e9e17d19595aea2041167164105f822bd57fe131f09ffd0674ff00fc0b87ff008ba00e92be3ef8e7fb0dfc04fda27c6b1f8ffe255a6ab3eaf1d841a687b2d4e7b38fecf6ef2c91829110090d339c9e79afa77fe131f09ffd0674ff00fc0b87ff008ba3fe131f09ff00d0674fff00c0b87ff8ba00f81ecbfe095ffb2569f7b6da85b69fe20135acd15c445b5cba602485c3a1209c1c3283cd7e8d8014003b715ce7fc263e13ff00a0ce9fff008170ff00f1747fc263e13ffa0ce9ff00f8170fff0017401d2515cdff00c263e13ffa0ce9ff00f8170fff001747fc263e13ff00a0ce9fff008170ff00f17401f1efc51ff82777ecd9f183c7dacfc4af1ad96b52eb7af4c97178f6babdc5b42cf1c31c0a5628c855fddc4a38eb8af09f895fb0e7c00fd97fe1f788ff00688f85d61aa278bbe1d69779e22d15b51d52e6f2cc5ed8c2ef1f9d03b01221e430c82413820f35fa71ff00098f84ff00e833a7ff00e05c3ffc5d7ce7fb5e6b5a46bffb2f7c54d1342bdb6d4751bef09eab6f6b6969324f713cd240ca91c71a333bbb1202aa8249e073401f85375ff0584fda86d6e65b665f0bb189d9091a14b83b4e3fe8255f1efed3dfb60fc4dfdaaae743baf88aba609341b7bab6b63a758b590d977243249bc35c5c6e3ba04c1057033c1ce479aea1f033e2e5c5f5c5c47e10f106c925775ce89a90386248ff00975ae13c53e03f17f828dbaf8a748bfd28dd2b3422facae2ccc8a84062a2e228cb004804ae402467191401ccda46b2ca55fa08e571f544661fa8afea8fc07ff04b8fd93b5cf03f87b5bbfd3f5f373a86956375314d72e957cc9a0476c28380324e076afe57ec48133678fdccff00fa2dabfb92f85fe2ef0b45f0d3c251c9ac69eac9a169aac0ddc20822da3c83f3d007e3cfedd1fb00fecebf03ff00674d67e21780acb588758b4d4748b689eef55b8ba8847797b1412e63909527639c1c641e457f3cf5fd6a7fc14efc47e1fd43f642f10dbd8ea7677129d5fc3e4245711bb1035280938562781cfd2bf92b3d6803fad7ff0082527fc9a1e9dff630ebdffa56d5fa4b5f9b5ff04a4ff9343d3bfec61d7bff004adabf496800a28a2800a28a2803c3bf696f881aff00c29f807e3cf893e16101d5fc37a15e6a3662ea332c0668137289103296538e4061f5afe7aaebfe0b09fb50dadd4d6acbe17630c8d1923429704a9233ff00212afde8fdb46c2ff54fd947e2ae9ba5db4d797775e17d42282dede3696596478f0aa8880b3313d00049afe42f50f819f172e2fee6e23f08788364b348eb9d1352070cc48ff975a00fde7fd9bfc33e1fff00829bf84350f887fb4ddaf9fa9f837513a36927c3d2cfa3442cef2d6daf64f3512698bb99180c97c61460039cfd0adff04a3fd90d9190e9de21c3ab29ff0089edd1c861823ae0820f43c578effc1242ceebc01f083c69a6f8e2197c3f753788ede4861d5a37d3e59634d36d633224774b13b26f465dc1704835fac9ff00098f8487275ad387fdbdc3ff00c5d006be9d630699636fa75b6ef26d618e08f71cb6c8d42ae4f7381cd5da623ac8a1d0865600820e4107a1069f40057e21feddff00f0506f8e7fb35fc7bbdf875e061a11d1a1d2f4cbc8fedda6497771e6de2ca5f2eb7700da3cbe06d3d6bf6f2bf98dff0082aafc37f1ef8bff006aad4af7c37e1dd5f51b5fec3d1505c59e9b77750978d2e372f9904322ee5dcb919ef401c76bdff0574fda5bc41a1ea3a0de2786bc8d4ad27b497668922b6c9d1a36c31d45b070dc1c1c7a1afcab9a5699f7b765551f4450a3f415ea971f037e2cdadbcb753f84b5e48a14691d9b46d455555064925ad400001c92401dcd793b295386e0d007d67fb30fed83f137f655b9d72ebe1d2e9864d7aded6dae4ea362d7a365a493491ec0b716fb4ee9df2496c8c703193f5dff00c3e2ff0069ff00ee7863ff000452ff00f2cabf2428a00fd6ff00f87c5fed3ffdcf0c7fe08a5ffe59579bfc5eff00829f7ed01f197e1b6bdf0c3c529a07f64f886dbec977f65d224b79bcbdcac76486fa50872bd7637d2bf3628a007cb234b23caff79d8b1c7a939afb83f66afdbd3e30fecbde11d4bc1df0f9347365aa6a03529cea1a73de49e70822b7f9596eadf6aec8578da79c9cf381f0e5779e19f867e3bf18d949a8786741d5354b6864f25e5b2d3eeeed164da1b6b35bc32aab6d20e0907041c6083401fa67ff000f8bfda7ff00b9e18ffc114bff00cb2a3fe1f17fb4ff00f73c31ff0082297ff9655f9dff00f0a17e2fff00d09fe20ffc126a5ffc8b5e4d3c12db4af04e8d1c91b156560559594e0820f2083c107906803f7b3f671fda0fc57ff0526f1d4bf007f68ab7d3e7f08dbe9973e2355d0ede6d22f3edfa6cd6d1404ce2ea7263c5d392a029dc14e78c57dcbff0ea4fd90ffe81de21ff00c1f5dff8d7e4e7fc11affe4e8ef7fec4fd63ff004ab4eafea22803cb7e0d7c1ef057c07f87f61f0cfe1f45730e87a6cb7334097770d73307bb99ee252d2bfccd992463cf4cd769e29f0e69be30f0ceade13d64486c35ab1b9d3ee844e6390c1751b4526c71cab6d63861c83cd3eefc4be1eb0b86b5bed4ecede64c6e8e5b88e3719191956604645431f8b3c2f34890c3abd83c92305545ba88b331380000d9249e82803f3e57fe0945fb2122aa269be200aa028035dbb0001c00066be8efd9f7f649f83bfb325c6bb75f0aedb51b793c4496b1df1bfbf96f772d9194c5b7cd27660ccf9c75cfb57d3345001451450014514500145145007fffd4fdfca28a2800a28a2800a28a2803f9f4ff0082e27fc85be0f7fd7af89bff0042d3abf03ba722bf7c7fe0b89ff216f83dff005ebe26ff00d0b4eafc0ea00d04d4ee514204b721400336d093c7a92849fa9a7ff6b5d7fcf3b6ff00c0583ff8dd6657d21fb2bfecf97dfb4cfc5ed33e1569fab5b68d36a36f7b3add5d4325c44a2ce1f3482913c6c4b740430c1eb9a00f02fed6baff009e76dff80b07ff001ba3fb5aebfe79db7fe02c1ffc6ebf73bfe1c97e31ff00a28da0ff00e0aaf7ff00932bf33bf6bffd98350fd94be265bfc39d4b5ab4d7269b48b4d57ed1676f2dbc616ee5b88c26c96495b2bf67249dd83b870307201f317f6b5d7fcf3b6ffc0583ff008dd1fdad75ff003cedbff0160ffe37599450069ff6b5d7fcf3b6ff00c0583ff8dd1fdad75ff3cedbff000160ff00e3759952c3119a68e10706460b9f4c9c5005efed6baff9e76dff0080b07ff1bafaeff610ba7bafdadbe15f9b1c20a789ed30638638cf31cddd154f61f957d6bf017fe0941e26f8e1f083c2ff0015ec7c73a3e9d07892cbed896971a75d4b2c20bb2ed674b98d58fcbd4281ed5efde19ff826febbfb1e6b36ff00b4eebfe32d375bd2be19193c4d7ba669fa75c437779058452178a2927ba78d5cab1dbb86338c9033401fbf58afe7d7fe0b6dff0021ef865ff608d77ff4af4eaf7c93fe0b37f056291a293c0de2657425581bad2b208ea3fe3f2bcffc7be15b1ff82b84767e2bf87378fe04b5f8762e349bc875fb75bd6bc7d57ecf748f09b1ba0aab1ac001258e4b630319a00fe74c120823823a1ad05d52e514284b7200039b6849e3dca64d7ee87fc392fc639e7e23683ff82abdff00e4cafc55f88fe0e97c01e37d73c1b35c25d3e8ba9dfe9cd346a51246b1ba96d99d5589203988b0049201009279a00e5a5d46e26428cb0a83de38228dbfefa55079efcf35468a2803fad7ff0082527fc9a1e9dff630ebdffa56d5fa4b5f9b5ff04a4ff9343d3bfec61d7bff004adabf496800a28a2800a283c0cd7e617c6cff0082a37c2df81df147c45f0b3c41e10d7efaf7c3b76b6735d5b4fa7c704aed0c53e6313dcc72602ccb9caf5a00fd3da4c57e61fc13ff0082a37c2df8e3f147c3bf0b3c3fe10d7ec6f7c4576d670dd5ccfa7c9044eb0cb3e64105cc9260ac2d8c2f5afd3d1c8cd007f3cfff0005af7f27c6df0de6548dd9341d58af991ac8013796632038233824671debf0eac6fa5b99658668adca9b6b93c5b42a41585c820840410464115fb85ff05b5ff91c7e1cff00d80755ff00d2db3afc31d27fe3e64ffaf6baff00d10f401fde2f83b8f0968a076d3ad3ff0044a57495cdf83bfe453d17fec1f69ffa252bc9ff00692f8fda0fecd5f0c66f8a1e24d2ef758b282facec4db69ed12ce5ef64f2d0833bc6980c4672c38a00f7ca315f8ddff0f9ff00825ff423f897ff0002b4affe4ca3fe1f3ff04bfe847f12ff00e05695ff00c99401faa9f153fe498f8bff00ec03a9ff00e93495fc26dfff00af5ffae307fe8a5afe9853fe0abbf097e2d67e17e8de0ef10da5ef8bca787edee679f4e921b79b5665b38a59562ba790c6924ca5b6a938ce057ce8dff044ef1ab85f33e24684eca889b8e937809d8a14138bc03381ce00a00fc1ca2bf787fe1c97e31ffa28da0ffe0aaf7ff9328ff8725f8c7fe8a3683ff82abdff00e4ca00fc1ea2bd7be3b7c2ab8f829f15fc4ff0c6eafa2d4a5f0e6a52e9ef75046f1472b44a8db951d9d947cf8c16278eb5adfb387c17bafda03e2ff873e14d9ea3069537882ea4b64bbb889e68a231db4f739648dd19811015c061cb039c02080785d7f4e5ff000460e7e01f8d7fec6d4ffd355857cddff0e4bf18ff00d146d07ff0557bff00c995fa89fb0efecabacfec99f0ff005ff05eb3af59f8824d635a1aa47359db4b6a9120b482d846565924627f71bb3bbbf4a00fb4e4fb8dc9e87f957f087f137fe47dd7ff00ec2ba8ff00e95cd5fddecbfeadbe87f957f085f137fe47dd7ffec2ba8ffe95cd401fa71ff046bff93a3bdffb13f58ffd2ad3abfa88afe5dffe08d7ff0027477bff00627eb1ff00a55a757f511401fc9cff00c156a6fb1fed97e2b9218a1dd269da1ee2f0c72127ec6067e753ce0019f403d057c95fb355f4b73fb42fc308e48e003fe135f0df290448c3fe2676fd1950115fbf1fb5c7fc132fc55fb4c7c70d67e2cd8f8d74ad22d352b6b0b78ecaeb4fb99e58fec70088b1922b8894ee6c9e9c0c77ce7e79d1ff00e0945e27f811aad8fc6cd43c73a4ea569f0feeedfc55716369a6dcc5717716892adf3c113cb76d1a3cab09456705416c9e2803fa091457e362ff00c1683e0932abff00c20de270180601ae74b5386191906f011c1e879afb0bf64dfdb5fc11fb5c5d789ed3c1fa0ea9a33785e3b192e0ea32dac82517e6709b3ecd34b8dbe4367763a8c679c007da3451450014514500145145007fffd5fdfca28a2800a28a2800a28a2803f9fbff0082de5add5d6aff0007c5b4324c56d7c4b911a96c65f4eeb806bf063fb2356ff9f2b9ff00bf4ffe15fdc9fc4bf815f07be3249a7cdf14fc21a478a5f4a5996c8ea96cb706dc5c143288f77ddde517763aed1e95e5ff00f0c3ff00b227fd123f09ff00e0b62a00fe2f3fb2356ff9f2b9ff00bf4ffe15fa51ff0004a2b4bbb5fdb23c2c2e619212da76b84798857205a7b815fd0dff00c30ffec89ff448fc27ff0082d8abacf03fecb7fb3bfc34f125b78c7c01f0f3c3fa06b766b22dbdfd859243711acc863902baf23723153ec6803df2bf97eff82c65add5d7ed4966b6b0c9315f0768d911a96c7fa56a5d700d7f5035e21f10bf66cf80bf1635e4f147c4af02687e25d5a3b74b45bcd4acd2e265b789999230cdc855676207a93401fc41ff00646adff3e573ff007e9ffc28fec8d5bfe7cae7fefd3ff857f687ff000c3ffb227fd123f09ffe0b62a3fe187ff644ff00a247e13ffc16c5401fc5e7f646adff003e573ff7e9ff00c2ad59693aaadedb9365700095092627f51ed5fd9cff00c30ffec89ff448fc27ff0082d8a90fec3ffb22019ff8547e13ff00c16c5fe1401cff00fc13e881fb1a7c2ac9ff009820ff00d1d25757fb6991ff000c95f17b9ff993758ffd267afc1efdad3f6a9f8e3fb36fc7cf17fc1cf831e2abdf09f827c357b05a68da1e9715947676303d8dadc324625b595c0696776c6ec0ce0002b0ff00669fdb03f683f8f3f1c7c19f093e2978cb50f117843c55abc5a56b7a3dfc764f6b7f63711cbe641288ed227d8fb40601864641eb401f97daae93aab6a776cb6770419e420889882371f6afe8b3fe08ad14b0fc36f8931cc8d1baeb5a582ac0820fd857a835fa11ff000c3ffb227fd123f09ffe0b62ff000af5bf86bf067e15fc1db5beb1f85be16d33c2f6fa948935dc5a65badba4d2460aab385e0900900d007a61e9f88afe20bf68dd3efee7e38f8f24b7b69a54ff0084a7c4237246cc32355bbee01afedfabe65d53f632fd9575bd4ef35ad5fe16785ef2ff0050b896eaeae26d3e379669e762f248ec7966662493dc9a00fe2bbfb2356ff9f2b9ff00bf4ffe147f646adff3e573ff007e9ffc2bfb43ff00861ffd913fe891f84fff0005b151ff000c3ffb227fd123f09ffe0b62ff000a00f03ff8252ab2fec8ba72b0208f10ebc083d47fa5b57e925715e00f871e05f859e1e4f09fc3bd0ecbc3da3472c93a58e9f10860592639760838058f26bb5a0028c81d68afcc2ff82a37c6cf8a3f03fe16f843c41f0b3c4577e1dbdbed7e7b6ba9acd61679608f4fb99c467cf8a6503cc8d4e76e6803f4ec9183cd7f1f5ff0518b2bcbafdaff00e25b5ac12cc175c504c68580ff008975875c034597fc1463f6bfbabc82d9be25eb8a269523242e9dc6e2067fe3c3debf7f3f67afd9ebe077c79f81de05f8c5f187c0ba0f8b3c69e2cd06c752d6b5ad4ac2296eefaee58943492300067000000000000000c5007e01ff00c139ecaf6d7f6bff00868d7304b086d718032232e7fe25d7fd32057f60c3a0af9e7c27fb267ecd7e03f11d8f8bfc19f0dfc3ba2eb5a64865b3bfb2b1486e2072a509475e412ac41f635f43d007f3d3ff0005ae827b9f1a7c398ede3795ce83aa9da8a58f17b67d866bf0e74ed33528669659ad27445b5ba259a36007ee5fa922bfb7ef893f00be0c7c62bbb2bff8a5e0dd1fc5371a6c6f0da49a9db2dc3411c8c19d537740c40271d715e69ff0c3ff00b227fd123f09ff00e0b62a00fa13c1c40f09e8bcff00cc3ad3ff0044a57e7f7fc156c83fb21ea3ff00630e83ff00a56b5f89ff00137f6f7fdab7c17e3ef10f85f44f88bac5be9da5eafa9d959dbc4b62120b6b4bc9ade18d77d9bb9091c6a32cc49ea4d7817c4ffdb3bf68af8c1e1397c15f107c69a9eb5a3cd3c372f6b742d04665b76df1b7ee6d617ca3608f9b191c83401f2b51451401ee5fb367fc974f01ff00d8d1e1ff00fd3ada57f71008f5ee6bf821f0df88b56f09eb963e22d0ae1ed6ff004eb982eeda64c6e8e7b6916689c6e0cb94911586411903208c83f679ff0082907ed804e7fe1656b7ff007ce9dffc81401fd88641a5afc64ff8255fed1df19fe3d6bde3f8fe2af8aafbc450697a6e913d94778b6e3c892e2e2f5252a60821cee5893ef038c57ecdd007f1a5fb78d8dedd7ed6bf14dadade5940f13dd826346600f9507a035d77fc1372caf2d7f6c6f868d7504b086d5ae403221504ff0065ea1d32057f4e7e25fd913f664f196bf7fe2af157c33f0deababea93b5cde5eddd824b3cf33fde7776e4935f347ed41f01be0c7ecf1f00bc6bf1a3e09f82742f0778e3c2ba6b5e68dae699610c777653b3ac6cf1b32b0f9a3764604105588ef401fa4208c0e69d907a57f1e971ff051bfdb0219e4847c4bd7088dd973b74ee7071ff3e15fbabff04b9f8d7f13fe38fc21f16788fe29f886efc457f65e245b3b59ef161578adce9f6937963c88a1523cc958e76e79eb401fa672ff00ab6fa1fe55fc217c4dff0091f75fff00b0aea3ff00a57357f77b2ffab6fa1fe55fc217c4dff91f75ff00fb0aea3ffa5735007e9bff00c11b3fe4e8ef7fec4fd63ff4ab4eafea2323d6bf85bf843f1afe23fc0cf12c9e2df865addd685aa4b692d8b5cda7926436f3b46ee9fbf8a64c334484fcb9ca8c11ce7e955ff828f7ed80cc07fc2cad7393fddd3bff00902803fb10eb5e4df1effe486fc43ffb15759ffd2396bc17fe09f5f123c6df16bf659f0c78ebe21ead3eb9aedf5e6b31cf7b702359244b6d46e21881112469f2c68abc28ce2bdebe3dff00c90df887ff0062b6b3ff00a472d007f0c973feb07fd738ff00f4015fbd3ff043cff90b7c61ff00af5f0cff00e85a8d7e0b5cff00ac1ff5ce3ffd0057ef4ffc10effe42df187febd7c33ffa16a3401fd05d14514005145140051451401fffd6fdfca28a2800a28a2800a28a2800a28a2800a28a2800af10f885fb49fc05f84faf2785fe2578ef43f0d6ad25ba5dad9ea57896f335bcacca92056e4ab323007d41af6fafe5fbfe0b1977756bfb52599b59a484b783b46c98d8ae7175a975c11401fbb3ff000dc1fb227fd15cf09ffe0ca2a3fe1b83f644ff00a2b9e13ffc19455fc5e7f6beadff003fb73ff7f5ff00c68fed7d5bfe7f6e7fefebff008d007f687ff0dc1fb227fd15cf09ff00e0ca2a43fb707ec88463fe16e784ff00f06517f8d7f17bfdafab7fcfedcffdfd7ff1ab565ab6aad7b6e0dedc1065404195fd47bd007eb17ed69fb2b7c71fda4be3e78bfe31fc18f0adef8b3c13e25bd82ef46d734b96ca4b3be812c6d6dd9e332dd44e42cb03ae76e0e320914cfd91ff0061cfda87e1afed1ff0f3c65e2af87dab58e8fa56bf6d757d773b59797040892a97223bb95c805c670a78cd7edcff00c13e803fb1a7c2ac8ff9820ffd1d257d9181e9400b5e47f12be3d7c1bf83b716169f14bc63a4785a6d512592cd354b95b73709095590a6efbdb4b2e71d322bd72bf9f8ff0082d84f3dbf887e18c96f23c4e347d746e462a79bbd3bb8a00fd5ff00f86e0fd913fe8ae784ff00f065151ff0dc1fb227fd15cf09ff00e0ca2afe2f3fb5f56ff9fdb9ff00bfaffe347f6beadff3fb73ff007f5ffc6803fb76f017ed39fb3ffc52f1147e12f875e3fd07c45accb1493a5969d7893ce628865df6af6507935eeb5fcaf7fc1232eeeaebf6b4d35aea69262ba0ebc01918b606cb5e9926bfaa1a00f0ef881fb4b7c03f853aff00fc22df123c79a1786f573047742cf52bc4826304a5824815baab15600fb1ae4ac3f6d2fd94755bfb6d2f4df8abe17babbbc9a3b7b7822d423792596560888aa392ccc4003d6bf05ffe0b0775756bfb5106b59a484b785f4204c6c5491e6ea5e8457e7a7c0bd42fee3e2e783e3b8b99a54ff8487443b5e466191a95af626803fb91afc6eff82cf7fc912f03ff00d8cd75ff00a6abcafd9015f8dfff00059eff009225e07ffb19aebff4d579401fcd16972245a95a4b2b05449e36663d000c0935fd5dfec8dfb5cfecc9e0bfd993e197853c55f133c35a56b1a5786ac2d6f6caeafe38a7b79e38f0f1c88795653c106bf936abd1ea7a9448b145773a228c2aac8c001ec01a00fed9fc27fb59fecd7e3cf11d8f843c19f123c3bad6b5a9c862b3b0b2be49ae2770a5c8445e490aa49f615f43d7f1f1ff0004e7bdbdbafdaffe1a2dccf2cc175c62048ecd8ff8975ff4c935fd830e82801690d2d1401fc97fc55fd803f6b4f147c47f136bba77c37d664b4bdd6b55b8b7911ac0ac90dc5ecf346e37dea300c8e0e0a822be7cf89ffb18fed15f07fc272f8d7e20f82f53d174786786d9eeae8da18c4b70db235fdcdd4cf976c01f2e327922bfb52c0f4afcdaff0082ad803f643d47fec61d07ff004ad6803f929a28a2800a28a2803f683fe0923f1afe12fc1fd5fe20dcfc50f16e93e178b51d334786d1b54b95b713c905cdf3c8a85bef145910b01d030f5afdb4ff0086e0fd913fe8ae784fff00065157f1676f77776a58dacd2425baf96e5738f5c11567fb5f56ff009fdb9ffbfaff00e3401fda1ffc3707ec89ff004573c27ff8328abc13f6a0f8f3f063f687f805e35f82ff0004fc6da178c7c71e2ad35acf46d0f4cbf864bbbd9d5d64648d59947cb1a33b12400aa4f6afe4dffb5f56ff009fdb9ffbfaff00e35f767fc1372f6f2ebf6c6f868b753cb305d5ae481239600ff65ea1d324d0053b8ff82727ed8134f24c3e1a6b8048ecd8dda771939ff9ff00afd73fd80b58d07f62df867e21f027ed47aad87c35d735bd746aba658ebf796f0cf7562b636b6c674114b2a14f3a175fbd918e40c8cfecb003038afe717fe0b53713dafc60f02c96d23c4dff0008b5c0dc8c54e0df0ee2803f645ff6dffd91594a8f8b9e13e41ff98945fe35fc707c41bcb4d43c67ad5ed8cab3c13ea37d2c7221caba49732ba303dc32b023d8d73dfdafab7fcfedcffdfd7ff1ace249393401eaff00087e0a7c47f8e7e2593c25f0cb44bad77548ad25be6b6b4f24482de068d1dff7f2c298569501f9b39618079c7d2abff04e1fdb01581ff856bae707fbda77ff0027d7d0ff00f046cff93a3bdffb13f58ffd2ad3abfa88c0f4a00f8b7fe09f5f0dfc6df097f659f0c7817e21e933e87aed8de6b324f65706369234b9d46e26889313c89f346eadc31c66be89f8cda56a7aefc21f1be89a25ac97da86a1e1dd56d6d2d622a249e79ad6448e34de55773b1006e60327920735e95d28a00fe3c1bfe09c9fb61ca11dfe19eb6adb10300fa7b005540383f6e19191e82bf5ebfe0947fb377c64f801a97c4b97e2b7862fbc3a9ae5be84962d786dcf9ed68d7be685fb3cf3fdd12a6776debc679c7eca607a52e00e9400514514005145140051451401ffd7fdfca28a2800a28a2800a28a2803f39ff6effdb835cfd8f6f7c176ba4786ac3c40be2a87549646bdbb9ad7c83a7b5b0017c9866ddbfed0739c636f7cd7e7e7fc3ed3c63ff44e741ffc1adeff00f21d687fc1713fe42df07bfebd7c4dff00a169d5f81dd781401fbc3ff0fb4f18ff00d139d07ff06b7bff00c875f4c7ec8fff000534f157ed31f1c346f84d7de0ad2b48b4d4adafee24bdb5d42e67963fb1c0650a2396de253b9b03af033df19fe65934cb9750e1edc06008cdcc20f3ea0b823e86bf49ff00e0949035afed95e158e5788b3e9dae60472a487fe3cffd866f43f91f43401fd62d7e7cfed3dff04f1f875fb51fc478be24f8afc4dad6937716956ba48b6b08aca480c56b24f22bff00a4c12b07267607040c01c57e82e696803f1bbfe1cc1f04bfe878f12ffe02e95ffc8747fc3983e097fd0f1e25ff00c05d2bff0090ebf6468cd007e377fc3983e097fd0f1e25ff00c05d2bff0090e957fe08c7f0523612278e7c4c194e4116ba57047fdb9d7ec8521e86803f02fc5bfb7ceadfb086b771fb28785fc2763e23d1be1c98b4ab2d5b55d42682f2ee39208af374b1dbdabc4a57ed3b3e5233b73819aeebe017fc158bc5ff001a7e31f843e184de03d1ac20f12ead0e9d2ddc1a95d4b240b2abb175492da356384200ddd4d7e5cffc1486d9eeff006c9f89ab1490864d56d4959268e36c1d2ec3070eca71c1e7dab92fd842d5ed7f6b6f857e6c9092fe27b4c08e68e43c473764663dc7e7401fd9757c53fb57fec47e08fdad753f0eea5e2ff10eada29f0e5b5e5ac31e9d1dac8b32dec90c8e641730cbca98176edc77afb5734b401f8dbff0e60f823919f1c7897aff00cfae95ff00c875fcee7c5bf08d97813e247893c21a74d25c5b68fac6a7a7452ca1448f1d8de4f6cacc10050ccb1066c00324e001815fdda1e9f88afe203f68ab47bbf8e5e3d7865b7c2f8a7c40ac1a789181fed4bb3c867047041e9d0d007db1ff000485ff0093b2d3bfec05aeff00e816d5fd5457f2b9ff00048d85adbf6b6d36291e2673a0eba711ca926014b6c6763363383d7d2bfaa2cd007f2e5ff058bff93a04ff00b16342ff00d1ba957e787c05ff0092bfe0ff00fb18344ffd395ad7e87ffc162c1ff86a04ff00b16342ff00d1ba957e787c04cffc2dff0007e3fe860d13ff004e56b401fdce8afc6eff0082cf7fc912f03ffd8cd75ffa6abcafd9006bf1bffe0b3dff00244bc0e7fea65baffd355e5007f3294515a09a65cba86dd02e7b3dc448c3eaace083ec45007b07ecedf196ebe027c5bf0e7c52b3d3e1d526f0fde9bc4b5b895e18a56304f06d678d5d946272d90adc8031824d7eb8ff00c3ed3c63ff0044e741ff00c1adefff0021d7e18ff64dd7fcf4b6ff00c0a83ff8e51fd9375ff3d2dbff0002a0ff00e39401fb9dff000fb4f18ffd139d07ff0006b7bffc8747fc3ed3c63ff44e741ffc1adeff00f21d7e13dc5a4d6d8f30c6c0f78e44900fa946600fd6abaab310aa0924e001d49a00fde0ff0087da78c7fe89ce83ff00835bdffe43af9dbf6a3ff829bf88bf694f84d73f0bb52f06e95a3c3717f637df6ab4bfb9b8903594a250bb25b78970c4609dd903a035f97034bb9600892d8679e6e611ff00b3d47369f71026f668587a47347237e48c4e3f0a00a55fb93fb1d7fc133fe16fed0df02349f89fe22f14eb9a65fdf5f6a96af6f670583c216c6ee4b756067b7964cb2c619b2d8c938006057e1b8eb5fd76ff00c12e3fe4cf7c3bff00618f10ff00e9cee2803e57f197fc11e7e0c7877c1faef882dfc6be24925d334dbbbc446b6d2c2b3410b48012b681b04af3820fa1afe706ea258650abd0a46ff4de81b1f866bfbb7f8aa40f85fe2f2c400341d4c927803fd1a4afe16ae74f9ae1a396292df69861eb710a9c88d410417041078208a00fd15ff827a7ec6de08fdacb50f17d978bf5bd4b471a058e9d750369d15ac8646bc9aea3657fb4c5280144008da01cb1c93c0afd3cff0087307c12ff00a1e3c4bff80ba57ff21d7847fc113a3f2bc45f13e12d1bb2691a106f2e4590026eb5138250919c10715fd03e6803f87cfda5fe17e93f06be37f8c7e1a687773df58f877579f4f82e2e96359a448d6360ce2255404ef23e55038e951fece1f1a2ebf67ff8bfe1cf8ad67a741aacde1fba92e52d2e25786294c96d3db619e34765004e5b214f2a0630491ea3fb7c67fe1adfe29678ff008a9aefff0045415f1fc30bcee238f6e4f76608a3eacc401f89a00fddcff87da78c7fe89ce83ff835bdff00e43af5af04fc39f0d7fc15974b9be30fc4796f3c0d77e0db86f0c5bd96852417b6f730491417e6691afad4b07dd385c050005ef935fceb7f64dd7fcf4b6ffc0a83ff008e57f4c3ff000462017e03f8dd032395f172a928eaeb91a5d88382a4838231c1a00aff00f0e60f825ff43c7897ff000174affe43a3fe1cc1f04bfe878f12ff00e02e95ff00c875fb23466803f3e7f661ff008278fc3afd973e23cbf127c29e26d6b56bb974abad24db5fc5651c022ba920919ffd1a0898b830281924609e2bf41a8a2803f1f3f6b8ff00829a78abf667f8e1acfc26b1f05695abda69b6d61711dedd6a17304b27db20129531c56f2a8dad91d7918ef9c7ccff00f0fb4f18ff00d139d07ff06b7bff00c875f2a7fc156a1fb67ed97e2b8e1961dd1e9da1ee0f3471907ec60e3e761ce0838f423d457e6cbe99728a5cbdb90a0938b9849e3d00724fd05007ee8ffc3ed3c63ff44e741ffc1adeff00f21d7e81fec21fb706b9fb615ef8d2d757f0d58787d7c2b0e972c6d657735d79e7506b904379d0c3b767d9c6319ceeed8afe46fa706bf7c7fe0877ff00216f8c3ff5ebe19ffd0b51a00fe82e8a28a0028a28a0028a28a00fffd0fdfca28a2800a28a2800a28a2803f9f4ff0082e27fc85be0f7fd7af89bff0042d3abf056dc0321cff724ff00d00d7f547ff0521fd8d7e2a7ed5fa87802e3e1b9d2847e1983588ef3fb4afdec8eebf6b431ecdb6d71bb0206ce76e38ebdbf317fe1cedfb51a07313f85c3947552dae4a402ca46481a70ce33d322803fa13f813e13f0bcff00047e1f4d368f60eefe17d19999ad622493671649253926be67ff0082935bdbf837f643f167883c251268ba9dbdf686b15ee9c3ec7731acba9db472049a0d9226f462adb5865491d09afb4be14f87b53f097c30f08f8535b112ea3a3685a6e9f7620732c427b6b78e290239552cbb94e18aae47381d2be33ff82a5ffc995f8cbfebff0040ff00d3ada5007f2dcdf1e7e2f0623fe130f10704ff00cc6f52ff00e4aafe9a7fe093fafebbe24fd976e354f116a579aa5db78a7554f3efae65ba97620842aef99ddb6a8e833fa939fe4e5fefb7d4d7ef57fc13c7f6ebfd9fff0067ef8012780fe235e6ad0eacdafea17fb2cf4b9eee210dc797b3f79182bbbe5391d45007efb7884b2e83a8b292a45a5c1041c107cb6ef5fc415c7c73f8bb6e628d7c63e216fdcc4c59b5bd4724b2024f1740753d862bfa60d5ff00e0aa5fb23dee937b690ea3e20f326b79a35ce857606e742064edf535fca1de4892caa53a2c51a1faaa007f51401fbbdff0476f1e78d3c67f173c6c9e28d7754d56287c330bc715f5fdd5e468e6f769655b89640ac400323b7e35fd0a57f27fff0004cbfda5fe16fecd9f10bc55e20f8a1717d05a6a9a145636e6c6ca4bd73325d79a772c592a369ea78cf15fb37ff0f5bfd90ffe823e21ff00c10ddff85007e845cf863c397b3bdd5e697653cd21cbc925b44eec718c962a49381debe6cfdaf345d2340fd97be2a6b7a1595b69da8d8f84f55b8b5bbb48520b88268e066492391155d1d4805594820f239af0aff87adfec87ff00411f10ff00e086effc2b88f895fb71fc00fda83e1ff88ff677f85d7faa3f8bbe22e9779e1dd15751d2ee6ceccdedf42e91f9d3ba911a0e4b1c12003804f1401fce4dff00c73f8b96f7d71047e2ff00106c8e5745ceb7a91385240ff97aafdfff00f823a78afc4de2df87df116f7c4dab5fead347ace9ab135fde4f78d1ab59862a8d3c923282c49c035f065d7fc11eff006a1bab996e59bc2ea65767206bb2e06e39ff00a06d7d95fb346a5a3ffc130341d63c21fb4edd086f3c77771ea5a30f0ea4faca1b7d3618ada7f3996084c6c1e44c65704370720d007ede573afe11f0b48ed249a3d833b92cccd6b1124939249dbc926bf3f3fe1eb7fb21ff00d047c43ff821bbff000a3fe1eb7fb21ffd047c43ff00821bbff0a00bff00f0525b5b7f087eca1afeb7e138d745d462d57428d2f34d1f62b9449b508239156683cb9143a3156dac32091debf978ff0085f3f17ffe870f107fe0eb52ff00e4aafe87be37fed11f0c3f6fdf871a87ecddfb3e5e5d4fe32d566b2d56dd35cb3b8d2ac8db691770dcdc16b878e4c1d80050118ee61c63247e78ff00c39d3f69ff00eff863ff0007b2ff00f2b6803f2cfc4be2df1178c2f86a7e25d46ef52ba08b1f9d797335d49b133b57cc9de47da3270376064e00c9ce2d9de5cd85cc7796723c3342eaf1c91b146564219486520820804104104641079af7cfda3ff670f1cfeccbe395f0178f5ac5b516b1b5bfff0040ba3771795766658ff78d0c0776607c8d9c71c9cf1f3d5007afff00c2faf8be79ff0084c3c41ff83bd4bff92ab9af137c4bf1d78c6d22b1f13ebba9ea9042e648e3bdd42eaed11c82a5956e269554ed2464007048ce0915c2d1401a1a400755b204641b88b20ffbe2bfb27fd8a3c2fe1bbcfd92be135cdde956334b278534d6791eda26662631c9254927debf8d4d3e64b6bfb6b8973b229a376c7270ac09afe953f661ff008290fecc5f0c7f67bf87df0fbc537fada6afe1fd02cac2f56df47b99e213c09b5c248836bae7a30e0d007ebc7fc21de13ffa0369ff00f8090fff001147fc21de13ff00a02e9dff008090ff00f115f1cfc32ff828a7ecd7f173c79a2fc37f065eeb52eb5afdc35ad9a5ce91716d099163794869640157e48d8fe15f755007f3a9ff0005a5d274bd2fc5ff000ed34db3b7b40fa16a858410a45922f6cc0276819c03fe735f87fa5806e5f233fe8d7279f685ebf743fe0b6bff00238fc39ffb00eabffa5b675f863a4ffc7cc9ff005ed75ffa21e803fba4f09784bc2b278574677d1b4f2c74fb4249b4879fdca7fb15f037fc153fc3ba069dfb24ea37161a6d9dbcbff090684bbe2b78e36c1ba5c8caa83835fa35e0eff914f45ffb07da7fe894af97ff006e8f81fe37fda17e01ddfc36f87df61fed79f56d32f57fb42e1ada0f2ace612c999163948240c0f90f26803f8c81d6bfaedff825c7fc99ef877fec31e21ffd39dc57e407fc39d3f69ffeff00863ff07b2fff002b6bf43be087ed11f0c3f602f871a7fecddfb41de5d41e32d2a6bdd56e1343b3b8d56c85b6af7735cdb95b848e3c9d84860514ee53c6304807ebdcb147346d14aa1d1d4ab2b004329e0820f04115cfff00c21de12ffa02e9dff8090fff00115f0bf877fe0a7bfb2a78a7c41a5f86748bfd79efb58beb5d3ed449a25cc6867bc95208833b00aa0bba8249c0cd7e865007e2cffc15ef51bef879e00f87ba87816e26f0f4f73abea31cf26912be9cf3469665d5247b5689d94300c0138cd7e047fc2f9f8bff00f4387883ff00075a97ff002557f4fdff000519fd963e26fed4be12f06e83f0d8e9825d0f52bdbabcfed3bc6b3531dc5b185446cb04f96dc7272bd07bf1f923ff000e74fda7ff00bfe18ffc1ecbff00cada00fca3d6358d4f5fd467d5b58b99af2f2e9cc934f712bcd2c8e7a9692466763eecc4fbd7db3ff04deb4b5bdfdb0be1bdbde431cf13eab72192540eac3fb32fce086041e403f515f41ffc39d3f69ffeff00863ff07b2fff002b6be94fd917fe09a9fb40fc0afda17c17f137c54de1f3a36857d3dcde7d975692e6e363d95d5ba88e336508277cc09cb8e01a00fdd41e0ef09e07fc4974effc0487ff0088ad6d3f4ad374a8da2d32d20b4476dccb044b1066c632420009c77abc3a0af94be3efed9df047f66bf11e9be16f8a375a9dbdfead64d7f6cb63a74d7a8d02c86224b440ed218743ea3d6803eab93ee37d0ff2afe24fc7bf1abe2be99e33d72ced7c5fe201147a9dfaa0fed9d4142aa5d4aaaa02dca8015400302bfa486ff82ad7ec86ca47f68f8879047fc806efff0089afe553c6faa5a6b7e2bd5b55b02cd6f777f793c4597692935c4922e4763b5864763c5007ebff00fc124be23f8efc63fb4dde59789bc41ab6a76abe12d5a416f7ba8ddddc3e625ce9e15f64f348a1943b00400704d7f4a75fc86ffc137be3f7c3afd9dbe3a5d78e3e25cf7906972787351d3d5acad24bc90cf733d9ba0f2e305b6ed85f2dd0703b8afdcdff0087adfec87ff411f10ffe086eff00c2803f42aefc35e1ebfb86babed32cee267c6e925b78e4738181966524e05790fc76f09f85e0f823f10668747b0474f0beb2cacb6b10208b39704109c115d3fc1af8c3e0af8f1f0fec3e267c3e96e66d0f5296e6181eeeddada62f6933dbca1a27f9971246c39eb8a83e3dff00c90df887ff0062b6b3ff00a472d007f0c770009063fb91ff00e802bf7abfe0877ff216f8c3ff005ebe19ff00d0b51afc16b9ff00583feb9c7ffa00afde9ff821dffc85be30ff00d7af867ff42d46803fa0ba28a2800a28a2800a28a2803fffd1fdfca28a2800a28a2800a28a2800a2bf27bfe0a5dfb60fc5ff00d95f51f87b6ff0bae74fb78fc4906b325f7db6c16f496b16b4116ccc91ede267cf5cf15f971ff0f7afdacffe823a17fe0893ff00926803faa8afcf4ff82a5ffc995f8cbfebff0040ff00d3ada57e317fc3debf6b3ffa08e85ff8224ffe49af26f8ddff000517fda0fe3dfc38d4be1878eaf3499b46d4e5b59664b5d292d65dd673a5c478944ef8c491ae7e5391c719a00f81dfefb7d4d3294924927bd7efdffc13a7f621fd9ebe3cfecf9278e7e24691a85e6aebe20d46c7cdb6d5af2ce3f22dc47b079704a89919396c64f73401f80983460d7f5dbff0eb8fd8f7fe85dd63ff000a1d4fff009228ff00875c7ec7bff42eeb1ff850ea7ffc91401fc88d15fb9dff000531fd8dfe03fecf3f08fc33e27f861a4df58ea1a8f885ac6e1eeb54bbbe5300b0ba9f012e257553e644a77000e0119c135f8716b1acb750c4fcabc8aa7e8481401060d7d8bfb03ffc9dbfc2dffb19ed3ff454f5fb2dfb1fff00c13e3f662f8a9fb357807e2178c743d4ee35ad734b1737b2c1ad5fdb46f2995c65628a648d0600e14015ea9f153f629fd9eff667f86de27fda07e15e8b7d6be31f87ba4def887439ef757bebdb78afeca17689a4b79a668e45ea0ab0e41238eb401fa9b5fcf9ff00c16dbfe43df0cbfec11ae7fe95e9d5f355d7fc15cff6b4b5b996d9b52d04989d9091a1260ed38ff9f9afb9bf652d3b4aff008297f8775bf177ed4f6a357bef03de45a6e8ada34b71a22c76da9431dcce245b59ff007859e34fbcc40da30064d007f37f83495fd777fc3ae7f63d183ff08eeb1d47fccc3a9fff002457f2cdf1bbc37a4f847e2b78b3c35a146d0d8699aeeaf656c8eed232c3697f710440bb659888e350589258f24e4d007dfdff000485ff0093b2d3bfec05aeff00e816d5fd54d7f0e9f007e3f78eff00672f1dc5f10be1ecb6b0eab0dadcda23dddb0bb8c47761049fbb2e992762e0eee39e0e6bee2ff87bd7ed67ff00411d0bff000449ff00c93401b7ff00058bff0093a05ffb16342ffd1ba957e48d7bd7ed09fb447c40fda4fc6a3c79f1166b59b531656d61bad2d45a47e4da195a31e5879390667c9ddce470315e0b40051457eb1ffc12fbf665f847fb46788fc69a77c53d3aeefe1d2349b0bab516b7f71625659ee6e2372cd6ef19605625c06240e48e49a00fc9dc1a4afebb4ffc12e3f63d033ff08eeb1ff850ea7ffc915fce0fedadf0d7c25f097f68ef1b780bc0f6d2dae8da36a82dad229a792e6458cd9da4d869652d239df2b9cb127040ce00a00ef3fe09c1ff002781f0d7fec36dff00a6ebfafec407415fc29fc1ef8b1e28f827f10747f891e0d7823d5b44b93756ad7108b8884862961cb4659370d933f1b873839e39fd04ff0087bd7ed658c7f68e85ff008224ff00e49a00fa3bfe0b69ff00238fc39ffb00eabffa5b675f863a4ffc7cc9ff005ed75ffa21ebe8efda47f6b3f8a9fb515fe8fa8fc4cb8b19e6d16da7b4b636564b64045712472b860249371dd1ae0e4606477af9c749ff008f993febdaebff00443d007f78be0eff00914f45ff00b07da7fe894ae92b9bf077fc8a7a2ffd83ed3ff44a57caff00b78fc6bf1dfc00fd9faefe22fc399ed6df598357d2ecd1ef2dc5d43e55dce239331965c9c1c8f985007d995fcab7fc15ebfe4ecb51ff00b01685ff00a05cd1ff000f7afdacff00e823a17fe0893ff926be1df8fdf1fbc77fb46f8ee5f885f10a5b59b559ad6dad1ded2d85a4663b40e23fdd877c11bdb27773c7031400bfb367fc974f01ff00d8d1e1ff00fd3ada57f7123a7e26bf82af06f8af54f04789f4bf15e8c516f749bdb5bfb732209104d673c7711165246e0248d4919191c646735fa5dff0f7afdacbfe823a17fe0893ff00926803faa8a2bf957ff87bd7ed67ff00411d0bff000449ff00c9347fc3debf6b3ffa08e85ff8224ffe49a00feaa28afe55ff00e1ef5fb59ffd04742ffc1127ff0024d7d35fb1effc147bf690f8e1fb457827e1b78c2ff479344d6efa7b7bd4b7d252da6289657570bb24133edf9e15cf1d3bd007f41d5fcdf7fc16c3fe4aef817fec569fff004b857f4803a0af97fe39fec79f033f68cf10e9de28f8a9a5df5fea1a5d93585ac96ba9ddd8aa40d2194a95b7910312c7249c9e07a0a00fe2970692bfaec7ff00825cfec7aaa4ff00c23bac700ffccc3a9fff002457f27fe39d32cf46f16eafa5d8294b7b5bfbc8225662c4243712468093c92154649e4f5a00e4e9e80ef5fa8afd0fff00826afc08f86ffb40fc78bbf057c4eb2b9bed2a3f0dea5a82c76b7935938b8b79ec9233e640c8f80b33e573839048e057eee7fc3ae3f63dff00a17758ff00c28753ff00e48a007ffc12d3fe4cafc1bff5ff00afff00e9d6eebeb2f8f7ff002437e21ffd8adacffe91cb5f851fb477ed59f137f609f8a1a87ecddfb3f3e9fa6f81b4082d6f2c2db51b36d56e966d595aeee4bdccf3891c34eeec371246ec7402bcd7c03ff052efda43e3478e3c3df08fc637ba3cde1ff1b6afa77873568adf4916b33e9fabdcc76772b1ce970cd1486295b6b804ab608a00fc85b907cc1ff5ce3ffd0057ef4ffc10f3fe42df187febd7c33ffa16a35f7da7fc12dbf63a44548fc37abaa280aaa3c41a98000e0003ed1c002be83f80ff00b2a7c1afd9b67d6ae7e1469b7961278812da3be377a8dd5fef5b33218b6fda647d9832be76e339e7a0a00fa368a28a0028a28a0028a28a00ffd2fdfca28a2800a28a2800a28a2803f0b3fe0b25f0dbc77f10755f850de0bf0feabadad8db78845c369ba75ddf884ccd61b049f66865d9bb636ddd8ce0e3a1afc48ff866cf8e9ff42278a3ff0009ed57ff00912bfb8ac5263ebf9d007f0edff0cd9f1d3fe844f147fe13daafff00225617897e097c56f0868f36bfe26f09ebda669d6e5165babdd1efed2043230440d2cf6f1c6a59880016049200c938afee9b1f5fcebf3d3fe0a9591fb15f8cc0cf37da07fe9dad2803f90fafeacffe0909ff00269d37fd8d7abffed1afe539fefb7d4d7f563ff0484ff934e9bfec6bd5ff00f68d007ea33ba468649085550496270001c924d70a3e2afc302323c5fa0ffe0cedbff8e5747e23ff00917f52ff00af3b8ffd16d5fc19dddd3c2d1c71a42079309e618c9c9452492572493401fd1bff00c161fc67e10f117c0af06db681ae69ba9cb1f8addde3b3bc8677553a5df2e488d988192067a64815fcde5910b7b6ecc7004a8493f514d96ea59976b88c0ff623443f9a819aaf401fd81fec13f10fc01a4fec81f0bf4ed57c4ba3d9ddc3a285960b8bf822950f9b270c8ce181f622ba9fdb1be237c3dd4bf655f8b361a7789f46bab99fc21abc71430ea16f24923b5bb00aaaae4b127a00326bf8dd4bc9a3508162207768a363f892a49a56bd9d94ae2219eeb0c6a47d085c8fc28024d5595f53bb6520833c8411c82371afe85ffe08d7e30f097873e1c7c4487c41ade9da64936b1a6b469797715bb385b2504a89194900f048ef5fcecd4f15c490821021cff7e357fcb7038fc2803fbb83f153e1863fe46fd07ff0676dff00c72bf90bf8e9f02fe2ff0089fe2ff8d35bd13c19e23bbb0bbf11eb93db5cc1a1ea33c13c13ea3732c52c52c56cf1c91c91bab2b2b10c0820d7c936b7524cef1c8909530cc7886307223620821720835fdd57c2b1ff0016c7c21d7fe403a677ff00a768e803f895f127c11f8ade11d264d77c4be13d774cb089911ee6f747bfb4855a46da80cb3dbc7182cc40505b2c4e064f15e555fd777fc151b8fd8f7c458cff00c863c3dffa73b7afe448f5a00f51f0cfc15f8a7e32d2975bf0c78535cd52c5dde35b8b1d22fef212f19c32896dede48f2a78237641e0806b765fd9c3e384313cd2f817c4ea91ab3b31f0fea80055192493680000773c0afe9a3fe0949cfec87a775ff91875effd2b6afd01f1871e13d648cffc83eefbff00d317a00fe0a9d1e3628e30460fe07907dc11d0f7afdc1ff8232f8a3c37e1bf16fc4397c43ab58e9892e87a5a46d79731db876179764853232e48041207622bf13b56ff008f98ff00ebdad7ff004425528a7921cec0a73d9d15c7e0181c5007f7727e2a7c31c1c78bb41ffc19db7ff1cafe5c3f6ebf84bf127e21fed47f103c51e0af0c6b7ade8d7fab24f67a8e9ba4df5f595d446c6cd37c3716d04b148bbe3652558e0a915f9d3a65dc936a56914b1c0c8f3c6ac0c116082c011f76bfb39fd87c67f644f8479ffa14f4dffd142803f91eff00866cf8e9ff0042278a3ff09ed57ff9128ff866cf8e9ff42278a3ff0009ed57ff00912bfb89c7d7f3a31f5fce803f83bf18fc38f1bf8025b787c65a1ea7a2bdd23490aea3617562d22210acc8b731445802402541009009c915cde93ff1f327fd7b5d7fe887afdceff82da7fc8e3f0e47fd40755ffd2db3afc31d27fe3e64ff00af6baffd10f401fde2f83bfe453d17fec1f69ffa252be17ff829ef877c41e29fd952ff0048f0ce977dac5f3ebda2482d74fb596f27291dc867611408ee42a82490a702bee8f077fc8a7a2ffd83ed3ff44a5749401fc3affc3367c74ffa113c51ff0084f6abff00c895e67e2bf06f89fc11aa1d1bc57a5dee937aa8921b7bfb59ece609202558c77091c80360e095c1c1c13835fdeae3ebf9d7f2b1ff00057aff0093b2d47fec05a17fe8175401f95745145001451450015f72ff00c139b54d3747fdaf7e1cdfead770595b43aa5c34935cc8b14680e997eb96672140c90393d4815f0d53e391a270e98c8ecc0303f504106803fbbc1f153e18607fc55fa0ff00e0cedbff008e52ff00c2d4f861ff00437e83ff00833b6ffe395fc267dbe7feec3ff7e22ffe268fb7cffdd87fefc45ffc4d007f76127c53f8625180f17683d0ff00cc4edbd3feba57f0f5f12258a6f1cebb2c2eb2236a9a832b210ca41ba988208e0820e47b5729f6f9ff00bb0ffdf88bff0089aa6492726803f5cffe08d7ff0027477bff00627eb1ff00a55a757f5115fcbbff00c11aff00e4e8ef7fec4fd63ff4ab4eafea22803f934ff82b27fc9e3f8a3fec1da1ff00e9257c87fb31ff00c9c47c30ff00b1d7c37ffa72b7afaf3fe0ac9ff278fe28ff00b07687ff00a495f21fecc7ff002711f0c3fec75f0dff00e9cade803fb87145028a0028a28a0028a28a0028a28a00ffd3fdfca28a2800a28a2800a28a2800a28a2800af9c3f6b0f81579fb48fc0fd6be11586af168536ad71a74e2fa7b76ba8e3fb0ddc374418924899b7f95b78718ce6be8fa2803f9f43ff00044ad7c9c9f89ba473ff00502bbffe58d7eacfec69fb376a3fb2cfc239be18ea5aedbf88647d62f3535bbb7b57b340b7623fddf96f2cc72a50f3bb90477193f59514014354b56bfd3aeac55821b882488311900ba95ce3db35f8087fe0899e22709e6fc4fd25d9111371d0aeb24228504e351033c7602bfa0aa2803f9f4ff8724ebdff00453748ff00c115dfff002c68ff008724ebdff453748ffc115dff00f2c6bfa0ba2803f9f4ff008724ebdff453748ffc115dff00f2c68ff8724ebdff00453748ff00c115dfff002c6bfa0ba2803f9f4ff8724ebdff00453748ff00c115dfff002c6be01fdb5bf629bdfd90ef7c336779e26b4f113788ad2fae95ad6c65b2108b296de22ac25b8b8dc5bed00820ae369e0e411fd84d7f3f1ff05b0826b8f10fc318ede3691ce8fae9da80b1ff008fbd3bb0a00fc11b7944321723394913fefb52b9fc335fd0ff0083ff00e0b29f0dfc39e12d13c3f37c3bd626934cd3ad2cde45d4ac943b5bc4b19600b640257383cd7f3d5fd93aaffcf9cfff007edbfc28fec9d57fe7ce7ffbf6dfe1401fd17ea1fb56f87bfe0a5fa55d7ecb1e10d12f3c0f7dab88b585d6b52961d4ada35d12e20ba68cc16d246ec64f957efae01272718af30ff8724ebdff00453748ff00c115dfff002c6be69ff82465adcdafed69a6adcc4f116d075e203a95cfc96deb5fd50d007cc1fb21fecfd7ff00b337c1bb7f859a96b506bf341a95fdf9bdb7b67b48c8bd97cdd822792561b338cef39afa2b5cb07d5347bed311c46d796d3401c8c8532a1404818ce335ab45007f3ecfff00044ef11cbb0cdf13f487748e38f71d0aeb2446a10138d440ce073803e94dff008724ebdff453748ffc115dff00f2c6bfa0ba2803f9f883fe089fe21b69a3b88fe26e8e1e26575ce8577d54e47fcc46bd5c7fc1417c1ffb1359d97ecade21f09ea5e26bdf8676969a04dacda5d5b59dbdf3c36d14be6a4333978c159572a4b60f426bf6bcf435fc77ff00c147ff00e4f03e257fd8717ff4dd61401fabdff0faef867ff44df5affc1a58ff00f1547fc3ebbe19ff00d137d6bff06963ff00c557f36d45007e8a7edf7fb65786ff006b7d6fc2daa787bc3b79a02e85a75e5948977730dc191ae678660ca61c801445820f3935f9ed65702da6672321a2963fa7988c99fc3766920b2bcba52f6d0492aa9c12885803f80a9ffb2755ff009f39ff00efdb7f85007f449a27fc167be1b695a35869aff0e75976b4b58202c353b10098e3552402d9e48ef5f547ecb1ff000519f097ed4bf137fe15ae83e0dd4b439469979a9fdb2eaf6dae232b66d02b4616125b71f3d4e4e07d7b7f267fd93aaffcf9cfff007edbfc2bf593fe08f96b736bfb5095b989e22de17d7480ea5723cdd3bd6803fa87afc99fdaff00fe09b1ad7ed47f192efe28daf8dec343b7b8d3ec2c858dce973ddba9b25906ff00323bb807cde61e369c60735facd45007f3ade29ff82316b9e1cf0ceafe217f893a4caba5d8dcde98d744ba56716f1b49b413a83004edc64838f435f865711793204ce7288e3e8ea1b1f866bfbbaf8a9ff24c3c5fff00601d4fff0049a4afe132ff00fd7aff00d7183ff452d007dcdfb14fec537bfb5e5ef89acecfc4d69e1d6f0eda58dd335d58cb7a2617b2dc4415445716fb4afd9c92496cee1c0c127efeff008724ebdff453748ffc115dff00f2c693fe0893ff0021ef89bff608d0ff00f4af51afe832803f9f4ff8724ebdff00453748ff00c115dfff002c68ff008724ebdff453748ffc115dff00f2c6bfa0ba2803f9f4ff008724ebdff453748ffc115dff00f2c6bf39bf6d2fd902f3f645f16e85e18bcf11daf885b59d2df5112dad9cb66b1049fc9d85659e72c4e7390c0638c57f6495fcdf7fc16c3fe4aef817fec579ff00f4b85007e2251451401fae9ff046bff93a3bdffb13f58ffd2ad3abfa88afe5dffe08d7ff0027477bff00627eb1ff00a55a757f511401fc9a7fc1593fe4f1fc51ff0060ed0fff00492be12f84be31b6f87df13bc25e39bcb77bb83c3dafe95abcb046ca8f2a585d4770c8acdf282c13009e013cf15f7a7fc1576d2eaebf6c8f148b685e52ba76864ec52d8cda7b57e6b3e97a922977b49955412498d8000773c5007f475ff0fadf867ff44df5affc1a58ff00f155f687ec7bfb6ff863f6bfbcf15da787bc337de1f3e158b4f9666bcba82e04c350338409e4938dbf6739cfa8afe39abf7c7fe0879ff216f8c3ff005ebe19ff00d0b51a00fe82e8a28a0028a28a0028a28a00ffd4fdfca28a2800a28a2800a28a2803f15bfe0adbf1f7e307c12d53e1847f0b7c57a9f86a3d62db5e6be5d3a558bcf6b56b1f28bee47cec123e31fdeafc71ff0086f8fdadbfe8a9789fff0002e3ff00e315fa59ff0005c4ff0090b7c1effaf5f137fe85a757e082a97385f427f00327f4a00fb0ff00e1be3f6b6ffa2a5e27ff00c0b8ff00f8c51ff0df1fb5b7fd152f13ff00e05c7ffc62b6742ff82777ed71e23d134ff10e93f0ef54b8b1d52d61bcb6992e34d0b243708248dc06be56c32b0232a0fa815abff0ed5fdb23fe89aeadff00813a5fff0027d00723ff000df1fb5b7fd152f13ffe05c7ff00c628ff0086f8fdadbfe8a9789fff0002e3ff00e315d77fc3b57f6c8ffa26bab7fe04e97ffc9f47fc3b57f6c8ff00a26bab7fe04e97ff00c9f401c8ff00c37c7ed6dff454bc4fff008171ff00f18a3fe1be3f6b6ffa2a5e27ff00c0b8ff00f8c575dff0ed5fdb23fe89aeadff00813a5fff0027d1ff000ed5fdb23fe89aeadff813a5ff00f27d00723ff0df1fb5b7fd152f13ff00e05c7ffc628ff86f8fdadbfe8a9789ff00f02e3ffe315d77fc3b57f6c8ff00a26bab7fe04e97ff00c9f49ff0ed5fdb23fe89aeadff00813a5fff0027d00725ff000df1fb5b7fd152f13ffe05c7ff00c62be98fd8f3f6c3fda5be20fed2df0e7c29e2bf88be20d4b48d4bc416d6d796773728f0cf0ba4ac51c2c4a482546466bf377e247c37f187c26f186a3e04f1de9d2e95ad6952ac377693346ef13bc693056685e48c931c88df2bb0c30e73903e8afd81ff00e4edfe16ff00d8cf69ff00a2a7a00fecdebcabe22fc0df83ff001767b1b9f8a1e0dd17c552e98924766fab5947766dd662a6411f980ed0e5549c75c0af55a2803e62ff00862cfd92bfe890f837ff0004f6dffc451ff0c59fb257fd121f06ff00e09edbff0088afa76be48d63f6effd91b40d5efb41d67e2668f69a869975359dddbcbe70786e2ddda39118797d55d483f4a00f9a7f6d6f857f0dbf668fd9ef5bf8a9fb3f786349f87be31b5bed22ca0d73c3d650d95fc56f7d7d0c3711ac8a9f7648d8ab02083dc702bf057fe1be3f6b6ffa2a5e27ff00c0b8ff00f8c57ecb7fc141bf6c0fd9abe2a7ecc5ae783be1ef8fb4bd735ab8d4f459e2b2b632195e3b6bf8659580280612352c79e82bf9a43401f62ffc37c7ed6dff00454bc4ff00f8171fff0018a3fe1be3f6b6ff00a2a5e27ffc0b8fff008c564fc2efd8a7f68ff8c9e128bc6ff0f3c157fad68d34f3db25ddbcd628865b76d922e27bb864cab020fc98f42457a27fc3b57f6c8ffa26bab7fe04e97ffc9f401c8ffc37c7ed6dff00454bc4ff00f8171fff0018a3fe1be3f6b6ff00a2a5e27ffc0b8fff008c575dff000ed5fdb23fe89aeadff813a5ff00f27d790fc62fd923e3d7c07d0ecbc45f147c277ba0586a172d696f35ccb6722c93244d31402dee67604468cdf30030a79cf0403b0ff86f8fdadbfe8a9789bff02e2ffe315f34f8e7c77e2bf891e26bef1878d353b9d5f57d4a5f3aeaf2edc3cd3481123dce40504ec455e00e14572914524f2a4110dcf230451ea58e00fcebec9f067ec09fb5478ffc29a4f8dbc27e02d4b51d1b5bb48ef6c6ee19f4f58e78261947512dec7200c39c322b7a81401f19515f66f8cff604fdaa3c01e14d5bc6de2cf016a5a768da25a497b7d7734fa7b4704108cbbb08af6490851ce1519bd01af8da58a482578251b5e3628c3d0a9c11f9d007f415ff0004a1f803f04be2bfc21f17ea9f12bc0da0f89ef2cbc416f6f6f71aad845752450b69b6b29446914955323b36071b989afd51ff00862cfd92bfe890f837ff0004f6dffc457c17ff000461ff009225e38ffb19ad7ff4d5675fb23401f317fc3167ec95ff004487c1bff827b6ff00e22bb4f017ece5f01fe176ba7c4ff0e7c03e1ff0d6ac6092d4dee97a7c56b398252a5e32f1a82558aa923a702bcb352fdbd3f642d1f52bbd2354f89da3db5ed8cf2dadcc1279caf14d03949118797d55948aed7e197ed5ff00b3c7c64f137fc21df0c3c6fa778875916d2de1b4b4f30b8b784aabb92c814052ea393ce78a00fa1e8a2be74f893fb5afece7f07fc512782be25f8e74df0feb714115cbd9ddf9a24114e098db2a8548600f43f5a00f4ff8a9ff0024c3c5ff00f601d4ff00f49a4afe132fff00d7affd7183ff00452d7f5edf10ff006f5fd90356f00f8974ad3be2868b35d5e68f7f6f044a65cbcb2c0ea8a3f77d4b10057f20d78eb24ca50e408a253f558d411f811401fbcdff000449ff0090f7c4dffb04687ffa57a8d7f4195fcf9ffc1127fe43df137fec11a1ff00e95ea35fd065007f2c1fb61fed87fb4b7c3efda5be237853c29f117c41a6e91a6f882e6dacecedae5121821448982206898800b1c0cd74bfb09fed71fb477c4cfda9be1ff84bc69f10b5ed5b46bfd4ae22bbb1bbb94782741a7de4aa1c2c484e1e35239c7147ed71fb09fed4ff00133f68ef885e34f097c3fd4aff0046d5b5eb9bbb1bb8ae34f093c0e912870b2de46e3250f054715d27ec41fb107ed3bf09bf69df01f8e3c71e03d474bd0b4bd46e27bdbd9ee2c1a38236b0bb8549586ee590e6495470a7ae4d007f4aa3a0afe6ff00fe0b61ff002577c0bff62bcfff00a5c2bfa401d057f37fff0005b0ff0092bbe05ffb15e7ff00d2e1401f88945145007a47c30f8b7f10fe0e7881fc51f0db5ebef0fea925acb66d7561208e5304cc8cf1e595c6d668d09e3f84735efcbfb7bfed6ccc17fe169789f938ff008fb8bff8c57897c1ef823f12be3bf89e4f087c2fd12e35dd562b39afdadadde08dc5bc0d1a3be6e268530ad2a023767e618079c7d36bff0004d6fdb21581ff00856bab7073ff001f3a5fff0027d007eddfec4bf097e17fed2bfb397877e2f7c7cf09e8de3ef1aeab75ab417baf6bd6105e5fdc47657f3db5bac92b2024450c688a30000a38af65f8cffb1ffecb5a2fc1ff001ceb1a4fc29f08da5ed8f87356b8b6b88749b749219a2b59591d182e559580208e845783fec87f1c3e137ec8ff0001741f817fb46789ac7c0de3bd16e353b9bed13517df71043a85f4f756cccd6fe6c444904a8e36c8d8ce0e0822bd9be227eda5fb2efc47f007897e1e781be21e93ac788fc51a45f68da3e9d6ed209af350d4217b7b6823de8abbe595d517730193c9039a00fe412e0059001c6510fe25413fad7ef57fc10eff00e42df187febd7c33ff00a16a35f01bff00c1367f6ca936b3fc33d555b622b0175a59195500e0fdbc6471e95faedff04a6fd9a3e34fecf9a97c4b9be2d785eefc3b1eb96fa125835d4b6b279ed68d7a660bf66b89f1b04a9f7b6e73c6707001fb23451450014514500145145007ffd5fdfca28a2800a28a2800a28a2803f9f4ff0082e27fc85be0f7fd7af89bff0042d3abf05adbfd61ff00ae727fe806bf7a7fe0b89ff216f83dff005ebe26ff00d0b4eafc16b6ff00587feb9c9ffa01a00fee6be0201ff0a37e1e7fd8aba37fe91c55eb5815e4df013fe486fc3cff00b15b46ff00d238abceff006c1f8e5aefece5f0135df8b7e1cd3ad355bfd26e34d863b5be67581c5f5e436cc58c7f30dab29231dc74a00fa77028c0afe6fcff00c16bfe2e8241f02f85b8ff00a6f7dfe147fc3ec3e2effd08be16ff00bff7dfe1401fd206051815fcdfff00c3ec3e2eff00d08be16ffbff007dfe149ff0fb0f8bbff422f85bfeff00df7f85007f483814d2060d7e59fec15fb7a78f3f6b8f1e788bc2de29f0ee8fa35ae8da3c7a8c52e9d25c3c9248f7021dade710028193c0f4e6bf534f43401fc7bffc14abfe4f23e257fd85adbff4d7a7d723fb03ff00c9dbfc2dff00b19ed3ff00454f5d7ffc14abfe4f23e257fd85adbff4d7a7d721fb03ff00c9dbfc2dff00b19ed3ff00454f401fd9bd145140087a7e22bf87dfda3eeae62f8e5e3c58a69117fe128f109c2b103fe42b77e86bfb823d3f115fc3b7ed27ff0025d3c79ff6347883ff004eb77401e2925d5d4abb259a4753d99891fa9a828a2803fad6ff00825201ff000c87a77fd8c3af7fe95b57e936057f27ff00b347fc14d3e217ecd9f0b6dfe17f87fc2ba16a76905f5edf0b8be96e9262f7b219597116570a4e077c75afab7c07ff000589f8b9e33f1a685e177f04f86618b55d52c2c659126bd2e91de5cc56ecca1b00b289323247f4a00fe84f02bf1bbfe0b3c07fc292f03ffd8cd75ffa6abcafd91afc6eff0082cf7fc912f03ffd8cd75ffa6abca00fe69747ff0090b597fd7cc5ff00a18afed0bf61f03fe1913e11ff00d8a7a6ff00e8a15fc59daced6b7315d2005a1916400f425483cd7eb6fc1fff0082b4fc4cf841f0bfc2ff000c349f077876f2cfc31a5dbe990dc5ccd7826952dd768770836863dc0e2803f77ff6e003fe1913e2e7fd8a7a97fe8a35fc5eeb1ff216bdff00af997ff4335fbc9f0eff00e0a0de3afdb63c4f6dfb2ef8cbc35a3687e1ff00890b73a06a3a8e9335c35fdb413dacf297805c0316ff00dce01756033f74d7bfc9ff000464f80f348d34be36f16b3c84b3129a56493c93ff001e14015bfe08c3ff00244bc71ff6335aff00e9aacebf640d7cbdfb2cfecabe10fd947c2dacf84fc1dac6a9acdb6b5a8a6a32cbaa0b71224890476e113ecd1429b3644bd5739cf35f511a00fe1a7e3bdd5cc5f17bc60914d222ff00c241adf0ac40ff009095d7a1afd0dff823ccd34dfb50b19a46908f0c6ba01624ff00cb5d3bd6bf3afe3d7fc95ff187fd8c1adffe9caeabf43ffe08e9ff002740ff00f62c6bbffa374da00fea3abf95bff82bb4d343fb59ea46191a32741d0812a48cfc973e95fd5257f2afff00057aff0093b2d47fec05a17fe8173401f96a6f6f581569e520f04173cfeb5568a2803f7c3fe0893ff21ef89bff00608d0fff004af51afe832bf8dbfd903f6d2f16fec8b79e22bcf0c685a5eb2de21b5b3b5946a2f3a0896ce59e5529e4e7258cec0e78c018afb8ff00e1f61f177fe845f0b7fdff00beff000a00fe903028c0f4afe703fe1f61f177fe845f0b7fdffbeff0a3fe1f61f177fe845f0b7fdffbeff0a00fe902bf9beff82d87fc95df02ff00d8af3ffe970a3fe1f61f177fe845f0b7fdff00beff000af817f6bcfdaf3c51fb5bf8a345f13f89f45d37469b46d35f4e8e3d39e6747479bcedcde772181e38e31401f1f51566d6159a4656e8b1c8ff00528a5b1f8e2bfa5ad2ff00e08d5f01afb4cb4bd7f1a78b15a782294809a5e01740c719b1cf7a00f83ffe08d9ff0027477bff00627eb1ff00a55a757f511815f007ecc9ff0004f2f863fb2dfc4597e24783fc49af6ab7b2e9775a51b7d4859080457524123b816d6d0b6f0605032c4633c57e805007f271ff00055f9a683f6c8f14986468f3a76879da4ae7fd13dabe46fd99eeae65fda1be18a4b348ea7c6be1bc866247fc84edfd4d7d6bff000564ff0093c7f147fd83b43ffd24af90ff00663ff9388f861ff63af86fff004e56f401fdc3003d2970074a051400514514005145140051451401ffd6fdfca28a2800a28a2800a28a2803f9f4ff0082e27fc85be0f7fd7af89bff0042d3abf05adbfd61ff00ae727fe806bf7a7fe0b89ff216f83dff005ebe26ff00d0b4eafc16b6ff00587feb9c9ffa01a00fee6fe027fc90df879ff62b68dffa47157c9bff00054bff00932bf197fd7fe81ffa75b4afacbe027fc90df879ff0062b68dff00a47157c9bff054bff932bf197fd7fe81ff00a75b4a00fe445fefb7d4d329eff7dbea69940051451401fb77ff00044fff0092bbe3affb15e0ff00d2e35fd201e86bf9bfff008227ff00c95df1d7fd8af07fe971afe900f43401fc7c7fc14abfe4f23e257fd85adbff004d7a7d721fb03ffc9dbfc2dffb19ed3ff454f5d7ff00c14abfe4f23e257fd85adbff004d7a7d721fb03ffc9dbfc2dffb19ed3ff454f401fd9bd145140087a7e22bf876fda4ff00e4ba78f3fec68f107fe9d6eebfb893d3f115fc3b7ed27ff25d3c79ff006347883ff4eb77401e25144d336d52a3dd9828fcce055afece9bfe7a41ff007fe3ff00e2abf4f7fe091d696b79fb5769f0ddc31cf19d0b5d25644575384b6c70c08afea5bfe11cd03fe81b67ff0080f1ff00f13401fc167f674dff003d20ff00bff1ff00f155eaff00032dcdb7c5df0734b243f3788744550b2a3127fb46d8f00127a027e95fdbeffc239a07fd036cff00f01e3ffe2681e1ed055832e9d680820822de3c823a1fbb401b22bf1bbfe0b3dff244bc0fff006335d7fe9aaf2bf646bf1bbfe0b3dff244bc0fff006335d7fe9aaf2803f994a28a2803eebff82707fc9e07c35ffb0e37fe9bafebfb101d057f1dff00f04e0ff93c0f86bff61c6ffd375fd7f6203a0a005a0d141a00fe18be3d7fc95ff187fd8c1adffe9caeabf43ffe08e9ff002740ff00f62c6bbffa374dafcf0f8f5ff257fc61ff006306b7ff00a72baafd0fff00823a7fc9d03ffd8b1aeffe8dd36803fa8eafe55ffe0af5ff002765a8ff00d80b42ff00d02e6bfaa8afe55ffe0af5ff002765a8ff00d80b42ff00d02e6803f2b554bb051d4d5dfecf9bfe7a41ff007fe3ff00e2abd87f66f4493e39781639155d5bc4fe1f04300410755b40410783915fdb97fc23ba01e4e9b67d4ffcbbc7ff00c4d007f04b35b3c001668db3fdc757c7d76938aaf5fd1dff00c168b4cd3ac3e1a7c396b2b582dcb6b3a9ee3144a84e2c5b192a066bf9d4d2403aa5983c8f3e3ffd08500469632ba860f0ae7b34a8a47d41208a24b296342e5e26c7659518fe001cd7f67dfb19687a2dc7ec9ff08e69ec2d6491fc1da396668236624db272495c935cafedfda268f6bfb1cfc539edac2d62917443b5d204561fbe8fa10b91401fc6fd156af862f6e07fd357ff00d08d55a00bda7ffad93feb84dffa2dabfbd5f0e7fc8034dffaf3b7ff00d16b5fc1569ffeb64ffae137fe8b6afef57c39ff00200d37febcedff00f45ad006c160a324e29be627f787e62bf317fe0ad9a8ea1a57ecaf05e699753d9cebe2ad2544b6d2bc326d613023746cad823debf97bff00859be3dffa0feabff831bbff00e3d401f7affc158c83fb63f8a31cff00c4bb43ff00d24af913f663ff009388f861ff0063af86ff00f4e56f5e37aaeafa96b778da86ab7335ddc3801a59e5799c851800bc8ccc703a64f15ec9fb31ff00c9c47c30ff00b1d7c37ffa72b7a00fee1c5140a2800a28a2800a28a2800a28a2803fffd7fdfca28a2800a28a2800a28a2803f9f4ff0082e27fc85be0f7fd7af89bff0042d3abf05adbfd61ff00ae727fe806bf7a7fe0b89ff216f83dff005ebe26ff00d0b4eafc16b6ff00587feb9c9ffa01a00fee6fe027fc90df879ff62b68dffa47157c9bff00054bff00932bf197fd7fe81ffa75b4afacbe027fc90df879ff0062b68dff00a47157c9bff054aff932cf197fd7fe81ff00a75b4a00fe445fefb7d4d32a5747dedf29ea7b537cb7fee9fca8019453fcb7fee9fca8f2dffba7f2a00fdb8ff8227ffc95df1d7fd8af07fe971afe900f435fce07fc114011f177c7408c7fc52d07fe971afe8fcf43401fc7c7fc14abfe4f23e257fd85adbff4d7a7d721fb03ff00c9dbfc2dff00b19ed3ff00454f5d7ffc14abfe4f23e257fd85adbff4d7a7d721fb03ff00c9dbfc2dff00b19ed3ff00454f401fd9bd7c9bfb48fed97f08bf659d4342d37e26c5ac3c9e21b7bab8b46d32cc5da84b378924f33f78854e665c7041f50719facabf9f3ff82db7fc87be197fd8235cff00d2bd3a803ec23ff057afd93b04987c5785058ffc4a3b2f27fe5b0ed5f9dde3aff82597ed1bf173c5dac7c4dd02ebc2b1e95e2ad46fb5cd3d6eb55b98ae05a6ab732dec2268d6c245491639c0755760181c3115f8d761febdbfeb8cff00fa29abfbb2f857ff0024c3c21ff601d33ff49a3a00fc85fd843fe09e7f1cff00669f8f167f11bc7973e1d9747834bd4acdd74dd427bab832de2c4130925a40bb47967277679e95fb6f451400552d4afe1d2f4fb9d4ae0318ad6192770a32db6252c7038e7038e6aed737e31ff914f5affb07ddff00e897a00fcd85ff0082bdfeca0c88ed6fe2c42e8afb5b4819018023a4c41e0f50483d8e2bf3c7fe0a33fb72fc13fda6be1a7867c2ff000dd35a5bdd2b5a9efae3fb46c45ac7e4c963716e36b798d96df2ae47a64d7e37eadff1f31ffd7b5aff00e884acd0a5bee827e94009453fcb7fee9fca98411c1a00fa83f63bf8afe15f82bfb41f837e23f8cc5d1d2343d48dd5d7d8e2f3e6f2cda5d43f226e5dc77ccbc67a64f6afe8487fc15ebf64ec7fa9f15ffe0a3ffb757f29a013c0a7f96ffdd3f95007f6dffb38fed3df0e3f6a2f0eeade28f86b16a715968d7eba75c7f6a5b0b591a668639c1450ee4aec917938e7a0c609fa28d7e377fc11878f827e38ff00b19ad7ff004d5675fb206803f863f8f5ff00257fc61ff6306b7ffa72baafa67fe09e7fb447c3ff00d9b7e36b78efe22aea0da61d1353b1ff008975b8b99bcebb7b468fe4dcbf2e207c9cf1c7ad7cd1f1e558fc5ff181009ff8a835bedff512baaf202ac39208fc2803fab0ff0087bd7ec9dff3c7c57ff828ff00edb5f1a7c7dfd97be217fc1463c663f695f81136956de0fd5acedf4ab78fc4575369da80b8d1e49ede72d0456d74a10bb1db970d81c819afc191d6bfaedff825c7fc99ef877fec31e21ffd39dc5007e597817fe0965fb46fc23f1768ff001375fbaf0ac9a5785751b1d73505b5d56e65b8369a55cc57b308636b08d5e468e021159d416232c057e888ff0082bd7ec9d80443e2bc300c3fe251d9b91ff2d8f6afd0ef8a9ff24c3c5fff00601d4fff0049a4afe132ff00fd7aff00d7183ff452d007ebff00fc148ff6d9f833fb4ff833c1da17c344d656e745d46f6eae7fb4ac85aa7973db185761f31b71dc791d857e42691ff215b3ff00aef1ff00e842b3c2b37dd04fd2b4b49471aad9fca7fd7c7dbfda1401fda67ec59ff2695f087fec4dd1ff00f4992b94ff0082837fc99a7c55ff00b021ff00d1d1d757fb167fc9a57c21ff00b13747ff00d264ae53fe0a0dff002669f157fec087ff004747401fc6b5ff00fc7f5c7fd757ff00d08d54abb7c8ff006db8f94ffad7edfed1aabe5bff0074fe54016f4fff005b27fd709bff0045b57f7abe1cff0090069bff005e76ff00fa2d6bf82cb04712c84a91fb89bb7fd336afef4fc39ff200d37febcedfff0045ad007e67ff00c15eff00e4d3a1ff00b1af48ff00dad5fca657f567ff00057aff00934e87fec6bd23ff006b57f29fe5bff74fe5400caf4ff82be2bd2bc0bf173c13e33d6fcd3a7e85e24d1f54bbf2137cbf67b2bc8a79762e46e6d88768cf278af33f2dff00ba7f2a3cb7fee9fca803fab0ff0087bd7ec9dda2f15ffe0a3ffb757d3bfb367ed8df08ff006a9b9f105a7c304d5964f0d476725eff0069d9fd97e5be3308b67cedbb981f3d31c57f165e5bff0074fe55fbdfff00043d046adf184118ff0045f0cffe85a8d007f415451450014514500145145007ffd0fdfca28a2800a28a2800a28a2803f9f4ff0082e27fc85be0f7fd7af89bff0042d3abf05adbfd61ff00ae727fe806bf7a7fe0b89ff216f83dff005ebe26ff00d0b4eafc15b72164249c7c920fcd4d007f739f013fe486fc3cff00b15b46ff00d238aaff00c5bf849e05f8e1e05bef86ff00122c64d4740d464b796e2de2b89ad5d9ed6649e222581d245db2229f95867183c578f7c0ef8e1f05ecbe0bf80acaf7c7be1982e2dfc33a4452c526af689247225a44acaca650432904107906bd4ffe17d7c0effa287e16ff00c1cd9fff001da00f933fe1d6bfb167fd09b7ff00f8506abffc9547fc3ad7f62cff00a136ff00ff000a0d57ff0092abeb3ff85f5f03bfe8a1f85bff0007367ffc768ff85f5f03bfe8a1f85bff0007367ffc76803e4cff00875afec59ff426dfff00e141aaff00f2551ff0eb5fd8b3fe84dbff00fc28355ffe4aafacff00e17d7c0eff00a287e16ffc1cd9ff00f1da3fe17d7c0eff00a287e16ffc1cd9ff00f1da00f3af81bfb1f7c04fd9cb5dd47c47f09342b8d26ff54b45b1ba926d4af2f83c0afe6050b73348ab86e72a01afa70f435e51ff000bebe077fd143f0b7fe0e6cfff008ed21f8f5f03b07fe2e1f85bff0007367ffc76803f950ff82957fc9e47c4affb0b5b7fe9af4fae43f607ff0093b7f85bff00633da7fe8a9eb67fe0a23aee89e23fdae3e226ade1fd42d753b1b8d52dde1b9b3992e2191469b6284a49196561b948c83d411dab1bf607ff0093b7f85bff00633da7fe8a9e803fb37afe7cff00e0b6dff21ef865ff00608d73ff004af4eafe832bf0abfe0b0df0d3e227c40d7be1c1f02f8575cf11a5b697ad453be8fa65d6a0b0bc97362c82436f1c9b0b2a315dd8ced38a00fe772391e26dc8704ab29fa302a7f435fa35a2ff00c153ff006bcd0b47b1d134ff001369e96ba7db4369029d16d18ac702045058f24800727ad7cac3f663fda209c7fc2b0f1ae4ff00d4b7a9ff00f23d78bea3a6dfe917b3e9baa5b4d67756d2490cd05c46d14b1c913147474601959594ab020104107906803fa0aff827c7edd5fb467ed0bfb42d8f813e24ebd697da14ba4eab74f6f06996f68e66b5584c67cc8c16c0f30f008f7cf6fde5afe4e7fe094be29f0cf84bf6a4d3f55f156af63a3592e89ad235c6a1731db421a44b7daa5e4655cb60e0679c1afe9bbfe17d7c0eff00a287e16ffc1cd9ff00f1da00f58ae6fc63ff00229eb5ff0060fbbffd12f5c5ff00c2faf81dff00450fc2dff839b3ff00e3b5cf78b3e3afc129bc2fabc30fc40f0bbbbd85d2aaaeb3664926170001e6f24d007f11fab7fc7cc7ff005ed6bffa212bf547fe097bfb327c1afda3bc47e34d3be2de8d3ead0e93a4d85d5a086feeac4a4b3dd5cc6e49b69622c0ac6a30d903191c935f957a9babdc2142180b7b65c839e5614047e07835fba3ff00044aff0091c7e237fd8074affd2dbca00fd0f3ff0004b5fd8af07fe28cbfff00c28355ff00e4aafe6fbf6d6f869e0df847fb4778dbc03e02b27b0d1347d505b59dbc93cb72c911b3b49b064999e46f9e573966279c670063fb4c3d0d7f289fb7e7c0cf8cfe2ffdabbe22eb3e19f00f8ab55d3ee7584960bcb0d0efeeada64363669ba39a281a371b9194e18e0822803e6efd8a7e1a7837e2e7ed1de09f00f8f6c9eff44d635436d796f1cf2db33c42ceee6c092164917e7890e5581e319c139fe9047fc12d7f62b23fe44cbfff00c28355ff00e4aafc3efd86be11fc51f863fb4df813c69f113c1fe21f0c787b49d524bbd4357d6b48bcd3b4eb2b74b1bc432dc5d5cc51c3126e915773b819615fd3a0f8f3f0380c1f885e16ff00c1cd9fff001da00c9f81bfb3bfc28fd9cf42d47c39f09b4b9b4ab0d56f05f5d473dedcdf33ceb1242183dcc92380234518040e3a57b7d793ff00c2faf81dff00450fc2dff839b3ff00e3b4c93e3f7c0b891a597e2278511114b3336b56602aa8c92499780075a00f98b5bff8266fec79e21d62fb5ed5bc237d35eea5753de5c38d77548c34d73234b23044ba0aa19dc9c280067815f0a7fc1403f614fd9a3e077ece97be3df86fe1abad3b5a8b58d26d1279756bfbb510dd5c08e51e5cf3c919254e01db91d4106bf77a19a2b8892781d648e450e8ea432b2b0c8208e0823a1afcfcff00829cf85fc4de2ffd95750d1bc25a3ea1ae6a0daee8b28b3d32d26bdb931c57219d845023c842a8c920702803f90c1d6bfaedff00825c7fc99ef877fec31e21ff00d39dc57f30dff0cc9fb43e7fe498f8d7ff0009bd4bff0091ebfa93ff00826bf86fc47e13fd93f40d17c55a4dfe89a8c7aaebb23d9ea56b2d95ca24da84ef1b3433aa48a1d1830ca8c839a00fae3e2a7fc930f17ffd80753ffd2692bf84cbff00f5ebff005c60ff00d14b5fdd9fc54ff9261e2fff00b00ea7ff00a4d257f0997ffebd7feb8c1ffa296803f58bfe0977fb2ffc17fda3b55f1cdafc5cd1a7d5a3d1b4ed2ae2cc437f75626392e6e2f2390936d2c45b2b0a0c3640c71824d7ec1aff00c12dbf62d460cbe0ebf041c823c41ab0208ffb7bafcd2ff8237f8f3c0fe0bd6fe23bf8c3c43a5686b71a568a909d4af61b412325d5f9609e6b2ee2a1d490338047ad7eef8f8f5f038f4f885e16ff00c1cd9fff001da00eb7c0be0af0efc37f0768de02f08dbb5a68be1fb2874ed3edda4799a2b6b75091a99242cef8500658927b9aa5f123e1d7853e2cf81f57f875e39b57bed075c83ecd7d6e9349034916e0d81244caebca8e54835d3e91abe95afe996dad687796fa8e9f7b124f6d776b2acd04f138cabc72212aeac390c090474a8f5bd7346f0de9771adf886fedb4cd3ad177dc5dde4c96f6f0ae40cbc9210aa3240c92393401f049ff00825b7ec584927c1b7e49e49ff848356ffe4ba3fe1d6bfb167fd09b7fff008506abff00c955f59ffc2faf81dff450bc2dff00839b3ffe3b47fc2faf81dff450fc2dff00839b3ffe3b401f24bffc12dbf62b08d9f065f9054823fe120d57904723fe3eba115f8bde26ff0082a2fed79e16d7f51f0f69fe27d3d6d34dbcb9b3b756d16d1d961b699e18c16206e3b5064e3935fd2a49f1e7e0714603e21785ba1ff98cd9ff00f1dafe253e234f05d78df5cb8b691258a4d4efd91d1832b2b5d4a4104704104107b83401fb41fb25fc71f1e7fc1437e245c7c0cfda8aea0f1278320d1eef5f8ecacedc69128d474f9ed63824fb459ba4a42adcc9f2ee009c120e057e8dff00c3ad7f62bffa136fff00f07faaff00f2557e417fc11aff00e4e8ef7fec4fd63ff4ab4eafea22803f3d3fe1d6bfb167fd09b7ff00f8506abffc9547fc3ad7f62cff00a136ff00ff000a0d57ff0092abf42eaadeded9e9b673ea1a84f1db5adb46f34f3cce238e28e31b99dd9880aaa012492001401f9fdff0eb5fd8b3fe84dbff00fc28355ffe4aaf7ff811fb297c11fd9ae7d6ae7e1068b3e912f8852d63d41a7d42eefbcd5b3329840fb54d2ecda667fbb8ce79e82bb31f1efe069191f10fc2b83c8235ab3ffe3b5d57863e20f813c6cd709e0df11e93af3598437034cbd86ecc224cec32089db6eeda719c67071d2803afa28a2800a28a2800a28a2803ffd1fdfca28a2800a28a2800a28a2803f9f4ff0082e27fc85be0f7fd7af89bff0042d3abf03abfaeefdba3f617bcfdb26f3c1b7769e328bc29ff0008a45a9c4cb2e98751fb4ff689b63918b9b7d9b3ecff00ed67776c73f01ffc38ef56ff00a2c36bff0084d37ff2c6803f0505c48a000138f58d0feb8a5fb4c9e91ffdfb4ff0afdeaff871deadff004586d7ff0009a6ff00e58d1ff0e3bd5bfe8b0daffe134dff00cb1a00fc15fb4c9e91ff00dfb4ff000a3ed327a47ff7ed3fc2bf7abfe1c77ab7fd161b5ffc269bff0096347fc38ef56ffa2c36bff84d37ff002c6803f057ed327a47ff007ed3fc28fb4c9e91ff00dfb4ff000afdeaff00871deadff4586d7ff09a6ffe58d1ff000e3bd5bfe8b0daff00e134dffcb1a00fc15fb4c9e91ffdfb4ff0a3ed327a47ff007ed3fc2bf7abfe1c77ab7fd161b5ff00c269bff96347fc38ef56ff00a2c36bff0084d37ff2c6803f03d98bb6e38fc0003f21c57d87fb03ff00c9dbfc2dff00b19ed3ff00454f5fa5bff0e3bd5bfe8b0daffe134dff00cb1af62f805ff0492d53e097c5ff0009fc5293e27db6af1f86b548b516b15d05ad5ae3ca575d825fb748133bfaec6e9401fb514628a2801a47d7a8afe1dff694ff0092e9e3cffb1a3c41ff00a75bbafee28f35f859f123fe08dbaafc41f1df883c66bf15ed6c975bd5751d496d8f879a6308bfbb9ae7cb2ff6f4ddb3cddbbb68ce3381d2803f9d84728772e3f1008fc8f1537da64f48ff00efda7f857ef57fc38ef56ffa2c36bff84d37ff002c68ff00871deadff4586d7ff09a6ffe58d007e0afda64f48ffefda7f8521b8908c613fefda7f857ef5ffc38ef56ff00a2c36bff0084d37ff2c68ff871deadff004586d7ff0009a6ff00e58d007e06f5e4d7ef0ffc112bfe471f88dff601d2bff4b6f2b47fe1c77ab7fd161b5ffc269bff009635f79fec3ffb085f7ec79ae789757bbf1a43e2a5f10585a592c7169874f307d9669a6dc49b99f7eef3718c0c63be7800fd17a4c52d1401f2b7edc031fb227c5cff00b14f52ff00d146bf8c2d525316a77714691aa24f22aa88930006200e95fdc77c75f86b27c64f83de2ff8570ea0ba4bf8ab49b9d2d6f5a13702dcdc2edf30c41d0bedebb772e7d457e2a5d7fc110b57bab99ae4fc60b453348d211ff08d39c6e39ffa08d007e077da64f48ffefda7f855ed3a4f3a7912548d97ecf727fd5a75585c83d3a82322bf773fe1c77ab7fd161b5ffc269bff0096353dbffc110b57b77671f182d4ee8e58ff00e45a71feb11933ff00211ed9cd007eeff83bfe453d17fec1d69ffa252ba4acbd16c0e95a4596965fcc3676d0c05c0c6ef2902671ce338cd6a500263ebf9d2d14500707f153fe49878bff00ec03a9ff00e93495fc265fff00af5ffae307fe8a5afef3bc5ba2b7897c2dac78712616e755d3eeac84c57788cdc44d1eedb919dbbb38c8cd7e0d49ff000440d62560cdf186d321113fe45a7e88a147fcc47d05007e06a48d1fddda73fde50dfcc1ad1d32532ea36b14891b23cd1ab03126082c323a57eeeffc38ef56ff00a2c36bff0084d37ff2c6a7b6ff0082216af6d7315cafc60b426275700f869f9da73ff411a00fd66fd8b3fe4d2be10ffd89ba3ffe9325727ff0506e7f633f8ab9ff00a021ff00d1d1d7bbfc11f87327c21f843e0ff85d2df8d51fc2ba2d9692d7ab11805c1b48c47e608cb3ecdd8ceddcd8f53591fb447c2697e3afc16f167c2487535d19fc4d63f6317ed01b9107ceafb8c41e32ff007718debf5a00fe1f2f2764bb9d11630ab23800469c004fb557fb4c9e91ff00dfb4ff000afdee97fe087fac4d2bcadf186d32ec58e3c34fd49cff00d046a3ff00871deadff4586d7ff09a6ffe58d007e0afda64f48ffefda7f8557afdf2ff00871deadff4586d7ff09a6ffe58d1ff000e3bd5bfe8b0daff00e134dffcb1a00f01ff008235ff00c9d1deff00d89fac7fe9569d5fd4457e587ec63ff04dfd43f64cf8af3fc4ab9f1fc1e278e6d16f349165168ed60c0ddcb6d2f986537738217ecf8dbb0677673c60fea7d0015e4df1ec03f033e2183c83e15d6baffd79cb5eb35c87c41f0c3f8dbc09e23f0647702cdb5ed26f74d17053cc109bb85e2de532bbb6eece32338c645007f07f34cd1b2aaac607971ffcb343d547a8afde6ff821f36fd5fe3092147fa2f867eea851f7f51ec00a56ff00821eeaee416f8c369c2aaffc8b4ffc200ffa08fb57df5fb0bfec2f79fb1b5e78caeeefc65178affe12b8b4c89562d30e9df66fece37272737371bf7fda3fd9c6def9e003f43a8a28a0028a28a0028a28a00fffd2fdfca28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2803ffd3fdfca28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2803ffd4fdfca28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2803ffd9", + expectedOutput: "http://en.m.wikipedia.org", + recipeConfig: [ + { + "op": "十六进制转字符", + "args": ["无"] + }, + { + "op": "解析二维码", + "args": [false] + } + ] + }, + { + name: "Parse QR Code : PNG", + input: "89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 00 00 00 7d 00 00 00 7d 08 00 00 00 00 aa eb 33 f9 00 00 01 06 49 44 41 54 78 da ed da cb 0e c3 20 0c 44 51 fe ff a7 db 5d a4 54 d8 99 29 01 24 73 59 75 91 f8 b0 70 79 38 6e 9f 9d a3 a1 a3 a3 a3 af d6 db f3 c8 9f b3 a2 a0 a3 5f ef c5 e9 79 8f 2f fc ca a3 a0 a3 77 22 f4 72 b9 17 eb 37 a0 13 05 1d 5d 7c cf 7a 0e 1d 7d 46 ce e7 2b 32 3a fa 2b fb 7b b8 c8 2e 39 5d a0 17 d4 d5 bb 8c ba fa ce b9 49 a1 97 d1 e5 62 4b 94 ee 4b aa 46 e8 b5 f4 5e de 86 1b b5 ba e7 87 41 d1 d1 c5 ac 56 27 28 ac d2 e8 e8 e9 dd c3 af e0 c8 45 1e f4 d3 75 f5 b3 47 5e b7 b6 8a d7 e8 e8 cf b5 67 6b f5 f5 6e d0 e8 a7 eb ea 5d 26 9f a5 90 f8 e8 c7 eb 23 75 1b 61 5a 6f 76 bc a0 d7 d2 d5 de 03 6b 82 61 64 74 74 eb 4b 9a d3 e2 12 9f 0d d0 d1 ff ea 3d b0 56 5f 74 f4 51 5d a8 4c e7 7f 0b 74 74 bb f7 40 d8 f8 27 76 3e a0 97 d1 47 8e 8c d6 77 3b 74 f4 4d 03 1d 1d 1d 7d cd f8 02 b8 db 6e a8 48 1e c7 f1 00 00 00 00 49 45 4e 44 ae 42 60 82", + expectedOutput: "Hello world!", + recipeConfig: [ + { + "op": "十六进制转字符", + "args": ["Space"] + }, + { + "op": "解析二维码", + "args": [false] + }, + ], + }, + { + name: "Parse QR Code : Transparent PNG", + input: "89504e470d0a1a0a0000000d49484452000000e8000000e80806000000e5a9a878000000017352474200aece1ce900000a49494441547801edddcb6e6db90d04d09ba0ffff97936b0499b20e4ccbb51fab8120035a22b5740a7b2024fdaf3f7ffefce7ef7f9efccfbf96874b3e69ffedfae5f8ebfb4de74bf3a5f3a7f5a9ff76ffd4bf5aff77b5bbe604088c02023af22812e80a0868d75f7702a380808e3c8a04ba0202daf5d79dc02820a0238f2281ae808076fd7527300afc3356ff574cef501f6c71f44fdaef60a9ffd6eff4feedf9d2f9b63f9eedf9b6fdd3faf1fcbea0894f9d405140408bf85a134802029a84d409140504b488af35812420a049489d405140408bf85a134802029a84d40914053e79074de38def3869f107f5f63b56ea7ffafc1f108d7f727afed3fb8f87fb81e2e9fb4b3ee3117c41471e45025d0101edfaeb4e601410d091479140574040bbfeba1318050474e45124d01510d0aebfee044601011d79140974057ee21db47b827df7f40e96deb1527d3fe1bc439a7f5efde74f9afff4fe69be57d77d415f7dfd0e7f750101bdfa0d99efd50202faeaeb77f8ab0b08e8d56fc87caf1610d0575fbfc35f5d4040af7e43e67bb58080befafa1dfeea02de41f33be0f60ed33b627a87dcf64ffb6fe7dbaedf9eefd1eb7d411f7dbd0e77770101bdfb0d9affd10202fae8eb75b8bb0b08e8dd6fd0fc8f1610d0475fafc3dd5d4040ef7e83e67fb480803efa7a1deeee023ff10e9aded9ae6e94def1d2fce9fca99efa6fd7a7f9533dcd97d65fbd9e7cabf3fb8256f93527300b08e8eca34aa02a20a0557ecd09cc02023afba812a80a0868955f7302b38080ce3eaa04aa02025ae5d79cc02cf0c93be8d3dfc166a1fcbf174d3e977e674b87ff5b4ff3a7f3a716697deabfdd3fadafd67d41abfc9a1398050474f65125501510d02abfe6046601019d7d5409540504b4caaf3981594040671f5502550101adf26b4e6016d8be31cdbbdfa39adee1d229b686dbfe69be544ff3a7f9b6ebb7f3a5f5b7aefb82defafa0cff7401017dfa0d3bdfad0504f4d6d767f8a70b08e8d36fd8f96e2d20a0b7be3ec33f5d40409f7ec3ce776b0101bdf5f519fee902e90debebfce91d2c197dd223ed31d5d37cdbfe69ff69b6afdad5fb5ffd7cc9eff4fc69ff345ffa7d8c755fd091479140574040bbfeba1318050474e45124d01510d0aebfee044601011d791409740504b4ebaf3b81514040471e45025d814ffe7f714f4f98de9952ffed3bd4b67f9a2fd553ff74beb43ef53fbd7feafff4faea7e7c419ffef370be5b0b08e8adafcff04f1710d0a7dfb0f3dd5a40406f7d7d867fba80803efd869defd602027aebeb33fcd30504f4e937ec7cb716486f603f71b8f40eb49d61bb7f5abf35d89e6fdb3fad4fe73f3dffe9fe57df7f9ccf1734fd7cd509140504b488af35812420a049489d405140408bf85a134802029a84d409140504b488af35812420a049489d4051e0eb8d6b7c87f95b3ffd0e76faf8e97ca97f3a7fda3fad4ffdb7f5f67ca9fff67cedf547efd717b47dbdfa1318040474c05122d01610d0f60de84f601010d0014789405b4040db37a03f81414040071c25026d01016ddf80fe040681af379cb7bf539d3effd177b2e16e3f2da5f39f9e3ff5fff41cdffdbb74be34dfd1f5bea0dfbd56eb08fc828080fe02b21604be2b20a0df95b38ec02f0808e82f206b41e0bb0202fa5d39eb08fc828080fe02b21604be2b20a0df95b38ec02f08fcc4bf1f74fb0eb43de6b6ff76fd76feedfaed3bdde9fe69ffe49fd6b7cf9fe64be71be7f7054dbcea048a02025ac4d79a401210d024a44ea02820a0457cad092401014d42ea048a02025ac4d79a401210d024a44ea02890de688aa37ddc7a7c47fabbcbddcfd83e5feafff1457df30fb7f797e64ffba7f5e958abfd7d4113af3a81a2808016f1b5269004043409a913280a0868115f6b0249404093903a81a2808016f1b5269004043409a913280aa4379a9f186dfb8e9466d89e613b5fea9ff6dfae3fed93f64fe74beb4fd76fedeb0b7afae7617f020b01015de0594ae0b480809e16b63f818580802ef02c25705a40404f0bdb9fc042404017789612382d20a0a785ed4f6021f0f546b47dc74aef4c8bf13e5a9ae6dfceb7ddfff4fa8f908a7f94fcdb3e69be2d5d3adfb8bf2fe8c8a348a02b20a05d7fdd098c02023af22812e80a0868d75f7702a380808e3c8a04ba0202daf5d79dc02820a0238f2281aec0d71b507aa749ef44edf549703bdf76ffb47e5b4ff793f6dffa6cd76fe74bebdb3e69bed1cf1734f1a913280a0868115f6b0249404093903a81a2808016f1b5269004043409a913280a0868115f6b0249404093903a81a2c0f68de86bf4f11da778b6ffb74e67dcce7f7affff9fe3bbffbd9d2fad4f7325dfedfea97faaa7f9d2fa544fe71bfbfb82265e75024501012de26b4d20090868125227501410d022bed604928080262175024501012de26b4d200908681252275014f87aa319df617e60b6f40e945aa4f9d2fea7d7a7f9b7f5edf9eedefff4fc69ffd3bf9ff17e7d41d3f5a813280a0868115f6b0249404093903a81a2808016f1b5269004043409a913280a0868115f6b0249404093903a81a2c0f806f3e15ca7df893e1ce3d89f25a3f6f9d37c09663bff76ffb4feeaf5e4bff2f505bdfaf59befd50202faeaeb77f8ab0b08e8d56fc87caf1610d0575fbfc35f5d4040af7e43e67bb58080befafa1dfeea02027af51b32dfab05d21bce1b70d23b5532681b6ee74fe74bf5edf9d3fcdbfdd3fca97f5a9fe65bedef0b9af8d509140504b488af35812420a049489d405140408bf85a134802029a84d409140504b488af35812420a049489d4051e09fbfbd57ef34c5d93f6d9ddea9523df5b9bbdff6fcc9e7743df99f3edfb6ffb8de17f4f4cfc7fe04160202bac0b394c06901013d2d6c7f020b01015de0594ae0b480809e16b63f818580802ef02c25705a40404f0bdb9fc042e0eb1d34fd73fa1d29f54ff5f11d292dfea0bedd7feb97faa7fdd3fa4490d69fee9fe64bfdd3fa6d3df55ff9f9826eafc77a02070504f420aead096c0504742b683d818302027a10d7d604b60202ba15b49ec04101013d886b6b025b0101dd0a5a4fe0a0c027efa0a97d7ae749eb533dbd33a5f5ed7af2d99eeff4fe57f73b3ddff67e56f3f982aef82c2670564040cffada9dc04a4040577c1613382b20a0677ded4e602520a02b3e8b099c1510d0b3be7627b01210d0159fc504ce0afcc43be8d909afbf7bf59decfa3c71c2e4b77de74debe380e10f8eeeef0b1af49509340504b4a9af37812020a0014899405340409bfa7a130802021a809409340504b4a9af37812020a00148994053c03b68fef7a3a677baedfd1d7d47fb3b5cda7f7bbeedfe69fdd6f7eaebc7f3fb825efdfaccf76a01017df5f53bfcd50504f4ea3764be570b08e8abafdfe1af2e20a057bf21f3bd5a40405f7dfd0e7f750101bdfa0d99efd5023ff10eba7d47bbfa058cef547f873f7dfeb4ffe9f9b6fdb7f79bfaa7fdb7eb4ffb8ef3fb828e3c8a04ba0202daf5d79dc02820a0238f2281ae808076fd7527300a08e8c8a348a02b20a05d7fdd098c02023af22812e80a7cbd11a5779eee84fbeea7dfc1f613ce3bb4e7dff69f4fb7fffda5f94effbe53ffd5f97d41139f3a81a2808016f1b5269004043409a913280a0868115f6b0249404093903a81a2808016f1b5269004043409a913280afc1753e7a9d79c5c60f40000000049454e44ae426082", + expectedOutput: "http://en.m.wikipedia.org", + recipeConfig: [ + { + "op": "十六进制转字符", + "args": ["无"] + }, + { + "op": "解析二维码", + "args": [true] + }, + ], + }, + { + name: "Parse QR Code : Angled code", + input: "89504e470d0a1a0a0000000d49484452000000d9000001010806000000f5a4008000000c45694343504943432050726f66696c65000048899557075853c9169e5b52496881084809bd89d2ab94105aa44a156c84249050624c082276655905d72e2260435744145d5d01592bf6b228f6fe5017959575b16043e54d0aaceb7eefbdef9d7c73ef7fcf9cf94fc9dcb93300e8d4f2a4d27c5417800249a12c31329435213d83457a0468f087003fe0c9e3cba5ec8484180065e8fe77797b035a42b9eaa2e4fa67ff7f153d8150ce07004980384b20e71740fc330078295f2a2b0480e80bf5d6330aa54a3c096203190c1062a912e7a871a91267a97195ca26399103f12e00c8341e4f960380762bd4b38af8399047fb16c4ae12815802800e19e220be882780380ae2510505d39418da0187acaf7872fec69935ccc9e3e50c63752e2a218789e5d27cdeccffb31cff5b0af215433eec60a389645189ca9c61dd6ee54d8b56621ac4bd92acb87888f5217e2f16a8ec2146a92245548ada1e35e5cb39b0668009b1ab8017160db129c41192fcb8188d3e2b5b1cc18518ce10b4585cc84dd68c5d2c94872769386b65d312e38770b68cc3d68c6de2c9547e95f6271579296c0dff2d91903bc4ffa644949ca68e19a3168953e320d6869829cf4b8a56db603625224edc908d4c91a88cdf06627fa1243254cd8f4dc99645246aec6505f2a17cb1c52231374e83ab0b45c9511a9e5d7c9e2a7e23885b851276ca108f503e2166281781302c5c9d3b76592849d1e48b75490b431335635f49f31334f63855981fa9d45b416c2a2f4ad28cc5830ae18454f3e371d2c28464759c78562e6f5c823a1ebc18c4000e08032ca0802d0b4c03b940dcd1dbd20b9fd43d11800764200708818b463334224dd52381d7245002fe804808e4c3e34255bd425004f59f87b5eaab0bc856f516a946e4812710178068900f9f15aa5192616fa9e037a811ffc33b1fc69a0f9bb2ef9f3a36d4c468348a215e96ce9025319c18468c2246101d71133c080fc063e035043677dc17f71b8af62f7bc2134227e111e13aa18b707baa78a1ec9b7c58201674410f119a9cb3bece19b783ac5e78281e08f92137cec44d800bee093db1f160e8db0b6a399ac895d97fcbfdb71cbeaabac68ee24a412923282114876f476a3b697b0db3286bfa7585d4b1660dd79533dcf3ad7fce579516c07bf4b796d8626c3f76063b8e9dc30e612d80851dc55ab18bd861251e9e45bfa966d190b744553c799047fc0f7f3c8d4f6525e5ae8dae3dae9fd47d85c262e5fa0838d3a43365e21c51218b0d577e218b2be18f1ec572777583abb6f23ba25ea65e3355df078479fe2fdda20b00049e1b1c1cfce52f5d743600fb4e0040fdcace7e9b7a2d3e5bcf57c88ad43a5c7921002ad0816f94313007d6c001e6e30ebc41000801e1601c8807c9201d4c815516c1f92c0333c06cb00094810ab002ac05d56013d80a7680dd601f680187c071701a5c0097c1757017ce9e6ef01cf481b7600041101242471888316281d822ce883be28b0421e1480c9288a42399480e224114c86c64115281ac42aa912d4803f2137210398e9c433a91dbc843a40779857c443194861aa066a81d3a06f545d968349a8c4e4673d0e968095a8a2e43abd03a7417da8c1e472fa0d7d12ef439da8f014c0b636296980be68b71b0782c03cbc664d85cac1cabc4eab026ac0dfecf57b12eac17fb80137106cec25de00c8ec253703e3e1d9f8b2fc5abf11d78337e12bf8a3fc4fbf02f043ac194e04cf0277009130839841984324225613be100e1147c9bba096f89442293684ff4816f633a3197388bb894b881b887788cd8497c4cec279148c624675220299ec4231592ca48eb49bb4847495748dda4f7642db205d99d1c41ce204bc80bc995e49de423e42be4a7e4018a2ec596e24f89a708283329cb29db286d944b946eca00558f6a4f0da4265373a90ba855d426ea29ea3dea6b2d2d2d2b2d3fadf15a62adf95a555a7bb5ce6a3dd4fa40d3a739d138b44934056d19ad9e768c769bf69a4ea7dbd143e819f442fa327a03fd04fd01fdbd36437bb436575ba03d4fbb46bb59fb8af60b1d8a8ead0e5b678a4e894ea5ce7e9d4b3abdba145d3b5d8e2e4f77ae6e8dee41dd9bbafd7a0c3d37bd78bd02bda57a3bf5cee93dd327e9dbe987eb0bf44bf5b7ea9fd07fccc018d60c0e83cf58c4d8c638c5e836201ad81b700d720d2a0c761b7418f419ea1b7a1aa61a161bd6181e36ec62624c3b269799cf5ccedcc7bcc1fc38c26c047b8470c492114d23ae8c786734d228c44868546eb4c7e8bad147639671b8719ef14ae316e3fb26b88993c9789319261b4d4e99f48e34181930923fb27ce4be91774c515327d344d359a65b4d2f9af69b999b459a49cdd69b9d30eb35679a8798e79aaf313f62de63c1b008b2105bacb1386af13bcb90c566e5b3aa5827597d96a69651960acb2d961d960356f65629560badf658ddb7a65afb5a675bafb16eb7eeb3b1b089b5996dd36873c79662eb6b2bb25d677bc6f69d9dbd5d9addf7762d76cfec8decb9f625f68df6f71ce80ec10ed31dea1cae39121d7d1df31c37385e76429dbc9c444e354e979c51676f67b1f306e7ce5184517ea324a3ea46dd74a1b9b05d8a5c1a5d1e8e668e8e19bd7074cbe817636cc6648c5939e6cc982fae5eaef9aedb5cefbae9bb8d735be8d6e6f6caddc99def5ee37ecd83ee11e131cfa3d5e3a5a7b3a7d073a3e72d2f8657acd7f75eed5e9fbd7dbc65de4dde3d3e363e993eb53e377d0d7c137c97fa9ef523f885facdf33be4f7c1dfdbbfd07f9fff9f012e0179013b039e8db51f2b1cbb6dece340ab405ee096c0ae20565066d0e6a0ae60cb605e705df0a310eb1041c8f690a76c47762e7b17fb45a86ba82cf440e83b8e3f670ee75818161619561ed611ae1f9e125e1dfe20c22a2227a231a22fd22b7256e4b128425474d4caa89b5c332e9fdbc0ed1be7336eceb893d1b4e8a4e8eae847314e31b298b65834765cecead87b71b67192b8967810cf8d5f1d7f3fc13e617ac22fe389e313c6d78c7f92e896383bf14c1223696ad2cea4b7c9a1c9cb93efa638a42852da53755227a536a4be4b0b4b5b95d63561cc8439132ea49ba48bd35b334819a919db33fa27864f5c3bb17b92d7a4b2493726db4f2e9e7c6e8ac994fc2987a7ea4ce54ddd9f49c84ccbdc99f98917cfabe3f56771b36ab3faf81cfe3afe734188608da04718285c257c9a1d98bd2afb594e60ceea9c1e51b0a852d42be688abc52f73a37237e5becb8bcfabcf1bcc4fcbdf53402ec82c3828d197e4494e4e339f563cad53ea2c2d93764df79fbe767a9f2c5ab65d8ec827cb5b0b0de086fda2c241f19de2615150514dd1fb19a933f617eb154b8a2fce749ab964e6d39288921f67e1b3f8b3da675bce5e30fbe11cf69c2d7391b95973dbe759cf2b9dd73d3f72fe8e05d405790b7e5de8ba70d5c2378bd216b5959a95ce2f7dfc5de4778d65da65b2b29bdf077cbf6931be58bcb86389c792f54bbe940bcacf57b85654567c5aca5f7afe07b71faa7e185c96bdac63b9f7f28d2b882b242b6eac0c5eb96395deaa92558f57c7ae6e5ec35a53bee6cddaa96bcf557a566e5a475da758d7551553d5bade66fd8af59faa45d5d76b426bf6d49ad62ea97db741b0e1cac6908d4d9bcc36556cfab859bcf9d696c82dcd757675955b895b8bb63ed996baedcc8fbe3f366c37d95eb1fd73bda4be6b47e28e930d3e0d0d3b4d772e6f441b158d3dbb26edbabc3b6c776b934bd3963dcc3d157bc15ec5dedf7fcafce9c6bee87dedfb7df737fd6cfb73ed01c681f266a47966735f8ba8a5ab35bdb5f3e0b883ed6d016d077e19fd4bfd21cb4335870d0f2f3f423d527a64f068c9d1fe63d263bdc7738e3f6e9fda7ef7c48413d74e8e3fd9712afad4d9d311a74f9c619f397a36f0eca173fee70e9ef73ddf72c1fb42f345af8b077ef5faf540877747f3259f4bad97fd2eb7758eed3c7225f8caf1ab61574f5fe35ebb703dee7ae78d941bb76e4ebad9754b70ebd9edfcdb2fef14dd19b83bff1ee15ef97dddfb950f4c1fd4fdcbf15f7bbabcbb0e3f0c7b78f151d2a3bb8ff98f9fff26ffed5377e913fa93caa7164f1b9eb93f3bd413d173f9f789bf773f973e1fe82dfb43ef8fda170e2f7efe33e4cf8b7d13faba5fca5e0ebe5afadaf875fd1bcf37edfd09fd0fde16bc1d7857fedef8fd8e0fbe1fce7c4cfbf87460c627d2a7aacf8e9fdbbe447fb93758303828e5c978aaad00061b9a0df70dafea01a0a703c0b80cf70f13d5e73c9520eab3a90a81ff84d56741957803d0046fcaed3ae718007b61b30b814712d8945bf5e410807a780c378dc8b33ddcd55c3478e221bc1f1c7c6d0600a90d80cfb2c1c1810d83839fe13e06bb0dc0b1e9eaf3a55288f06cb0394889ae1b09e6836fe4df597b805de7ee9c9f0000000970485973000016250000162501495224f00000020469545874584d4c3a636f6d2e61646f62652e786d7000000000003c783a786d706d65746120786d6c6e733a783d2261646f62653a6e733a6d6574612f2220783a786d70746b3d22584d5020436f726520352e342e30223e0a2020203c7264663a52444620786d6c6e733a7264663d22687474703a2f2f7777772e77332e6f72672f313939392f30322f32322d7264662d73796e7461782d6e7323223e0a2020202020203c7264663a4465736372697074696f6e207264663a61626f75743d22220a202020202020202020202020786d6c6e733a657869663d22687474703a2f2f6e732e61646f62652e636f6d2f657869662f312e302f220a202020202020202020202020786d6c6e733a746966663d22687474703a2f2f6e732e61646f62652e636f6d2f746966662f312e302f223e0a2020202020202020203c657869663a506978656c5944696d656e73696f6e3e3531343c2f657869663a506978656c5944696d656e73696f6e3e0a2020202020202020203c657869663a506978656c5844696d656e73696f6e3e3433343c2f657869663a506978656c5844696d656e73696f6e3e0a2020202020202020203c746966663a4f7269656e746174696f6e3e313c2f746966663a4f7269656e746174696f6e3e0a2020202020203c2f7264663a4465736372697074696f6e3e0a2020203c2f7264663a5244463e0a3c2f783a786d706d6574613e0adc0adfa8000040004944415478019cbdf9975dc775df7b7ac24080004180a448511249c99235d8966d65bdacf592b59265ffd779ebe5b767bf48721249912c5103e70100310f0da0877c3fdfefde75ea369a92e2eabee754edda73cd75eadcbbf5ebdffcea78d93a5e8e8f75d3dfb21c2fcbd696d2a4880f9060cef2dd70650d60c5c34544fe175fc375114ff3260aec9400ee966527937882734cde38cdcb3a8154a8a4c9b32d86152d602319c1a941642d5b5665816d5dc44b59e11bd88a019cfcd0c6872634bcf19acf69e9a6ed3ceecdb3ef336cc6fb6371680933ffd360338f39bfe39d9fda30fbae732443605597e7bcdb856f5a5cd44e1466d50c9753b2567d2d7b2a7bd30fdac8ed3201171b3bbd6af5c7636ddf5c365f166f4e737ecbedbce6b7a689510f97651bd3d09f44ecda56ba5d4ae32363f54f5ca1ab329ca76c3bd319182b647b3d98e1cb3569d04b9aef51c35849836654f88786dbf1f151f8926dc08aa75882e016ef4b039bf79c4e3cb80de73e841b38cbb75d6048467f409a1d1fdb632bbce77cf2fad370e3072d57f4970adb2e8df0eeece7703b63bab7be806659a4c96b1e8d27d0149240efc63dc9a34c9a682a3a32a8375350a22186b753d0456881e175c92e5cb4b09ead5ceb78c27f9dbddad4729ba0d3cfdfdbbe9339ed17e073bcf166d82a77aea7916d3cd5d7e3c383e5e0c9e365d7a3556a75f342827aa63802320cb27f6c19de60a4239f5c5cf57c0136b3129ba4e98bc43c424d66f02a2d9670c790964dc327173da24651b40a834303cc6140c36b430a621d36f31a7afa7dc6b50fa4103042a7d1d38e2e9833ebd2387defbc4eb7f63436bb56b7d6ba71bfecde7a9c963fe7b56ec18beeed5b6065ce6033db3c8015712382850ac58da5e12e287497f6cac71ef39d990beef2c55f8d37d313177eebfba7fc6091ae27c5e4cfb835ef3f0375e8b1892bcd514c8deae8480debe870397af66c79f6787f79f6e8e1f2ecdedd65d71e282f1859c66e0d8fc439669a4c7b05a77fb9e39168afc3c9b1a1d4e4603bd7685c4211bcb872ae14cc459ad47445614a5f22b325870f054ca8ab6f9b186d52f3063b7611db0ca71546ebd8b6741acaf0c1976b2569f87c274e9869ad651947e53bc92314ffb62bbc36f895f214f99f533f5b4ffcd17c28e594a22d4961dab991451934aeb56edbe05166cc1c00b5bf5bde067dd1ccb73f953fe3126ffe27e19dee7cea864de98c519f54328787cbe1c1b3e5e8e9538d58fbcbd3876a540f1f2c4f6edf591eddb8b91cdcbebdec3e7a442373ed1b37f3a2209a690b99a4b502a0b812142e701bab341ce05cdc577e0573ae4a95fcc80aa6c50816caa185215cc81f8c9ddd12227b20a281f23302029df8979ec16d0d5a560b5839756c2df086c01fb99d4e835af192d1e9c63aedde38eddbd60a5ccbe07e1ae1bf11863cf8b69c3fd5c05a3fc40d1ddb76ddbb14ad6529bad60dca79d51e7497bcdc93788c58750261aa8325b375b0b89054198bfb0cacbcbea51e44efe6d1797db78fcb270dc396233a78b487bf3e47079a023e7db21cee6ba452a37a72f7f6f2548d695f1feecbfd07cb9646b1ad274f9733c2df113335b254dca16319de70ee0eab8fecae51afdc44a2c46906acae8a9ba1335ef1b3b3cd231c576729cdff9837910f913e78ad528e983e318842439ab87d33eeb133f44d01acf15a7e446ce2813f170638276d1eb61569f0a1dce405deccabe51ad3ca102354212731ae4d1f8c786664e2a64d7123ab23a7c93e690bb88d77926e54023b1447805c9f386f52818c350cd50c5e956d1fac7566a2299e40120d17afd5075a735ee58567c38338fb6e902ad2f2811167ea77cc14506bab43462a8d4afbf7ee2dfb376f2ecfbef86239be7b6f39d474f0588d6d5b53c4b34792cb47817add76ecba3cdd8d29d3baa4624a8a915bc9e019948a55f93638a8c10fcab892d5386d2a3c5da0ca2c29c6273f0e2082a16b7a6d38e1b78e5026d5a598990eae70eb7b70da86be0345dec93474316f73946afcdcb96e86b6cbfc2acb766ea23915bf62233aae21f6c73f23af1434af3865836e93837861fa9f08ad57dfff04bab3db3e27ec5e49463782ed507c86b76db38385bada0859d1c3823c7d4e86933a468f889cf3da5f1b7a8ad90c5f7997d7748b9a891c69fa77a80673f0e489d754079afe3dbb777f797cebe6f2f8fa8de5e88ed6586a703bcf0e962d8d6a3b6a885e5d89f1f6b6360d6580b6e80453a444ecb602162e2095b97d63981c84e1c1afd8405819e16c70c020147fc7406f3879ddc21def4222e1b052a630d0298ee00e6df8b534e456af015eb3d13dac1b2f192d2e3cd7bc2eac1450d346eb86c12171e0c90bd75c078fa1c58ab3d2cd14a7c79b4fe70edae177f45b79cf781dff3fb90ffe220a5b7c1c4f22a7f323b37ca69bcb31682e01bbc4d95526662640c1d098b272d2e558e566c8691e0d7e49423b9b75d2741aea66275c3a1b3b6556d1615794922e1aa98ed4605853b151f1e4ee5d4dfdee2c4f6fdd5a9edcbcb11caa51ed686ab8abf5d7ae1ad5b6eedb6597b5c1980a8caa19c8c8296b955f1b1f8525b89d6b26e56860cd8518792e000c0bbe5d55307883afdb44078340cc6b6568a4b5c2a4008c5a1489a767870bb23a804d41afb2120b4471c9318e49bab23435a690e1cce1fc5597156f8e9d963f5c3210c3732415398d6eceffb2f84c67bf5ae7609f6c0827793cafd74a37f39de9d2b6ba802cd1badb22fc29a61bd691287468cd97bbffe02cbf0b4757fd811c02f884ae888b0db97398a575dd6cd8ea0af15ed98cb24462fb88fbb15a8077ff98fe69b38251eb68ffc9f2f4fedde5e1679f2d4f3eff7c3950a33abaff68d9d6d63b53c0ddc3238d505b79ac82ed8c56f0425e2d652c9acb5042564c83d5aebc62a2184766706d40807106706506946b0409360cb4e4f808064140baffa082038f07089b3d10f846883ee8652c2e2d4f1a0cc3820c6702a2c8ebca33ec755e70c90b9e4936e24d979cf5da85b442d658f8c5e11d5f73d7d81fcb036b96ddf26658739a61e035dfa669bcbe071edf358cfbcce7cbd233ed285ff96fcb0e0c0fe3c4fd66ef1e5ef92913c945f4c88f1ed01b0453d711488364bde00fc4e4ba2499db363929430a2f7680104121a53c80e57eacd1e79091eae9fe72f0f891362bee6bb4d2ba8ad18a4d0a8d52db9a121e6b6db568c38246c57c4fed4a5c75d9d1d6056a7888125ffe4bc7966be54a1f978991d035a1363ee022a5cd43ac95245dba0793247042df0bc54ae46279a0b42241a5c108d9817b417573cc595c700c72920f890d5512fa829a8be18e91573a83655e95a15bf36ab8cb75c800994f38a373e3c3e1641ad81cda46601db7935b58217f595ee3aef921681dfa5e6c9a9b64ad9e681c78ccf1e26458f3dfe4737aca9c27fdf1733f338daf442797ade5a9b49ceac6830e4e52def844170560433f700196db31a5a2c11b59e604a6f37d2dbbbb36b55ddcad0f02794e45c3d20e20cfaa9e3ea051a941ddd6f44f5340b6d58fb4c6dad19a6b4b5345b775d1b10b68855b19cb25e148e95c56c720c9744408dc09b95baf22055a0fa351bb9cd9569346e9a6171cac0e8e555e8c4d2258e49640c74217a5e0401eb0152f71e5984c1767b5c4d057260c148283ba6938c13179107cb509134e574473b0b0e20dc7081fd49b492b34f2be2c125f8457c7072eca28445f9958e9913ff2563c618f0a0a3e3a46afd5d2e6d3f7e6076ec3d67259750b07ae92c77fb3447cc7615669f350ad6c9f92e5204050565ec06716eddb3486cea47ca14c883b90a22065a69c19418c93033ef6b116626d45c37acaee9f1ad2935bda52d70ee0b33bb7976335b4ad478f972de5ef6aaab8a551894171e8241e8ee32ffd5903f8a246e5b9a32979f125f60140b5d09ba2d2403bec0e58792442c88655196a41e1576e44ba718482b6254cf802bb70615101b53b385605d5b0cd7bf14a4d2aa1d2c484d2cefc237ea0d888c2519ca475b08ee096134b8f766ee4b6ce9b5a745e712b7ea7e13c0f9bf9b71eab075af74046239031ad67db85ec8621a5f9ceb0193e6b020e9f91af68ebd27001064e1751f2a29be5150f51a38df0e123aebabb83133ce9c802af03f84e0a116a279c9d385176e19a7285e223530ba3e874077654d3363812bc850fae46af7d3d00bef18b5f2c4f3efc70d979fc78d9d1ba6b5bf02d3542ada4740f2ff44e508438707800240aaea21c6f033739954f4607e1cec9e661184e299e355d2c062299771741b66b8d4f450d776ebd7075a17486b351d51841f635e922b74da93082b46523b3c8cb50c831193f8062b48aaf921203670dadef738ccdcb5c4bef0df5ad50d3c02df14ddeab9493b1b921745ec348c3cd15686248befdd80485037ce86607449799df44926839c5388a938477a72dbf881a3edf11c89f6b44f97396670d1adebe313f744d39a536159fb2933274b04ee4b904ca21d1936bfbe1083a7d1075bce8791571362e048096ed723e5b5bbbde98d8daa2196d2fe71f3ef636fbd6b3a7cb4e2b0b4d8b870f4c140c4b34b0c2210ff9d6127f340edaf8bf0020965dd6cfca89093442040b969a2e628b922db19a790480d892433243906828178299590f4595d03fbc07eb4093766648206dc34d6d1d4acd123f942ea3a30d94044b6b6de02c7e82cef20d35b2e0d2bb758e05c9f0b5044e10a2a968abd455dfe4753af64a1f1bddf6ff319e628eae11c2d5a1e99d90b256d7bedce4157960098e5120f229346ea155c401dd1283b6f506b229133a959d89c9ed48712c2683bfd28887397fd03a8d22fc2baf3990e1b881d091023f4854f0d60b4ce25bda8dd8dadad13e441a971b18542241efeddddde5ec850bcbf98b9796fd9ddde5588d0c5af4e332362e4896ee2d0f1c8f58c8e9047404eb92e8c615c162cc5548beae51242720c32319c856665c950a60724e3943f03880c2222166856ec645e7782ba204b8293010c83530c4bab656653f10818a9953a592c8a0f634c30cd18382888c422d7ec983323a0b2cc476727172baf3c9ebf8e045c40242dff0c635cfc9094d6f9bbf84dfaa83b8c9cc62bfa19ff9e2107d363d610f580db261e0fa6a1d92e70a96ccd022c0f99b3698892e436770f01119451ffb4803c4d1755714aeded6870c705d89190d48e191e7b8113b9ef594f928d3eb2be46b6462eb7c57bb7bf623fc0483ab7731a51bb8c8b02f85b77dfedcb2f7d2e565fbcc99e560ff917c821712845d31f449bcf5b3cec0fc69fc4d3cd30c02f42b7e2bdbe821f2e6df02d5e4a9882411e2ff11075c035b60ba62642a48718718e54a01aa03a699165cd3cc2811e2fcce0c57634eac422b9c48328571cc9564cb2ef1d0761895d8b0e26079144e154c21cf4ee97850c10dedc466b55f799b72c210d8a02bfa81d70a4ef7c60534e3cdf1091dac918c2de52381e735ce402292427641a45456bbd6525a29bc1ca0fcaa23037bb0200eaa2e862b623d025d6d074721bea301cd1f352ea5d769217506795b1aadd460f4d9613ac85ebaf927df820c81b3e456edb0fc336797b32fbdb4ec9d3bbf1cead9978f5e28bfed6b3f5b77518f7a0ab1b9e11ee2b1ca40524ab6edc082be7219facd48c26b0c3d27834c41ad29420104e8b404d0d00c71bc009040e700ad707c697af026dac223177e56bc5b3069321ccc2431450758111f5511c4fa80c667040a4889268f27d6dc89512a4e238aaff24ea00f3630a0e0bb804877dc8da008c30d25c1d80c27e9e75cf328007cc3b300a7f06a9cbe0ffac26d3fba62ad265ae7c675b9768518860bb9edc48ce6070f0279be2963d060ee8a085f07e513f35591dc69508a731650b51f1650322a6df568a5b8d3c0f8e8cfbe1662fc0251e4b50473b73ec2d69471efc517fd7972f3bae41c926df950c567d6d0da398f7cd1c3d6b23adb54d11190d0da5263c497d2820ce73a923c684d105d359229e538177d246d804c52d9945ef149419a08cd0276cdc72dc1a7ff48ab6bb2e01b4d1758995db12951a2023029091b32851c9e1d2f60d02dcb7a1bb9882467759e33203a11da516b3e0e9c1d3ec7e35ce982ddfa741a3ffca930708538f384ce79f0301b2eab3ee43b08dc3cfade599dbf4166165c563dc379e5bdf2699b831f73ac0c428badf2caceca096f1282e7e6abd1d29894e61f3af8707a82cd0ae2e62b9eba3b381a59a0eb5f2132c175f9230774d8393fe42e0fed74ec5c7c61397ff5caf2f8c3dde5e0705d970d7f979ec53c1b1c1a1d90e750bc93884dc4bdd32824b22377a54163c891e178f38250c123d9fc70b39d11a5b0442c4f5a05653352b6dd8296c08c5f8a94f350b5661d500ed2d2d6b0be84ad38c612239bbfe9e0334c3449a647162c9aa2f6ad0d175af1b213ccb899c322f154b6769ce418cc3d952ff916e9cb69e9f82c389d3fee25c73a58aa042043bab67d0658ae2f61345fbf043c504ecd8f4fc059ed1f14ae5cd111bc3090d543a7b91c867d42ebb8ef2a7beffcc99801879bd989971ad68ed653dc91d53e592bc2aa4f3400a7612913f80aaaaa9872b5e3f01de9296febfcf9e5eccb57979d73e7fc5672d8ace568c6e8ab8f836e6d39323d7d7506d20420d3b8c8ef007da58ad8fa166cd867526d7cf86853d362045c4b69839b997986a385c3c01a9021f8d080781c5139ca575a89193a0c683ac446e0067da8c801b1f818315af411ada8221cf8344f25f04feb6b4794148b32cfc4b81a1716d00459f1a98084d3f0956a8d7581af10144519eedcd0642857e0356fce9a796dc485beda17b67fce159b4e0bb33d91c3142e98d0988efbb001187e9d3ef5ec0a113ee747836293427ce0393e06346f38cab710159e45206b8b0d0d9428455c1625cfe4e5c7ca06041be3efee2d3bdafcd8bd74510fa6b52e3bd6f67fe55b0fe2362af8660d9f82592eb64155ba377728d61089236ff004a375a7eef880f040ab2c41f50f8b080cdb912659fc61e5f69d16341c160af25241c10f89380a1774f3e362e5e014d5ac9533c3dd3c9c8ba410da018abb81156eb08da88b81e668273618fa1301c7cf017566d8896c1750f25b0626c499f618043071b674b47d6d9bee9535549133a2fbca0f7d9aeeb9f8503754abee915b620bdc38b9a377d4914c401580c5a6e095cad14109d2f8bc759aef50b8616db30bd81b1535bd9290494c8b1b7772573da49bfeb8126c8763d1098061e62998f50ac55a5e826b5db6adadfc33973465dcfd5c6f2df3e209feafbae80e2172b12bf24b4b009682fe099d8fcdf19fb5b02fa04a3eb03544aba461e9e764a870321832d1665a7612133789a59115d3bde902d2553cc286abb17defca068753833dd0f2e083eae2013a96c1cec98a3793ee1a9d160278f14463f8de0eeb0a034ad0108038083b54018919e07678e7ba908daf4c33090ff2d70a30f3acfc4637a395a6f9fef1fbac5fbce8adf4e788525da207764c748a762ae6625f20dc735ca97600cb76cccb43606db12b015ff3ae7bd30b587541fc142738cfc9d0b83c0daf0b68a3d369187c526f6243fba97858cf2e1f89e221f5f917bc95bfa3dd469e973948aee55b4640a5951356d1a60b5a22b8f1313824e30a7e7c5438e537fbc3584dc573b2d1326c5f9828df4ccc1e13c9932113ee2a79564384030721cae3dff214315f390cc74440e4c1e24440d9e11411c2c25745fcb4c4f9a517422cc3970d4e29a00d502a854056a148ca475d1f8c837c172c99e089001a82f31c130098f5113e305dacfb0975e2c9a0173bf3b5cb8ade2ca7cbf0c304db8c46a7d63f79d66203cdfa18b2e6356cbe139fd390a0c3b6a78092e5531658527cfa568eb1bf202adb1baf4bd0591b7e11836af891abb41c02bb9477ca20cfc8065bb48295823da97bd2d4ab1d6de19f7de9eab2abc676f8f8a150d8d10443d742dff0d79418fa0bd1fa800fad1522d175a25921374ccb0560172cd1750b9f7431a4d0a10b63c515ec56c1ad0ff984bac7912230a1e0e683a3aaa11855f112003e0e349d6136bfe8904b14051042080569624e950c72d10954e04efbca05992331225d898c31eb780277387cf5dee031f20a02cf8675bcef4d14f6b1bd6136165ba7d07e9bef64b7de9b70b8429fbb7d03b242cb6fba2fbbf32d4b8dcf7d3db2246e65bb7d2b119d36415f100f8b1858d02a14cac01078a5ac0ac16a53275c7ef0e60f1e260833a81b94ba4766f2c2a7e38173e561f4eee5cbd9cabf7d933656219ccc1e1ef99784408c64218a055539659850d00d7065f95eca965f845478f08a5d7aa09ec45a414857bd0f7d730cfd2a01c48d00a2826e422d05b8a752a5d1140e8aa8f61b8f565060d317f5cc239a0bcd25704280f05de9fa0ee19f085d51a874edd39324aea003b8a9a3f3ac0b8e8c7ceb30c5216d39838d22f821e6ae3ceda349117812e67b78a512261b2ef2a151c3911ee564a7028f533fd283ff98015f5506ad67b8b7de7db732272fa5233a38441d94367d5b194bda172005b2dad61c94a7ff869b9dd11d8b14a2412a3ef107d0391cebe8d5cec50b6a6497f4407b7739d4dbcc84e60d0fdb8d0df8a0d3c5c47827ed1322a0f8ab10eb869f56de2da7edac8d0f3bd306a0098c8a483014a0629c162c74ce281e2b2be83aa5a80870be2105360fcb54b660951baed5cb91e8027707905c5d2950b305c39f382115ab650706d1a63380c023a1f3926e7995b9719bf3da57dcfb333b3c66726d39a52f86e8bf79cd7c10161ef08c68fc74d29ec01a97dcd860547c7dcac75cc4d4d33f9e59d5b63ad48c601dda06eb6755cd35fa388dfaf89f446c0f0670854e94e9c12d6394dd7653273a8ecc8eb71ecd0adb1c8493d0bc62734f279dc7baec05d6655796adbd33cba2173691dfa17d3a81cadf2be7c666dd4980be61ed1be0cfe92b74db41a602d47576314c36d65c665eac85d90538288bc190dc9ab7138ac6f8a24e0fdb6aa60ca24ce705535a47492bfbe5464423708b4eb7141277602916a2a4319660a8018a0d59c90db8319b5fe7a502b48367e7cef139df7114585996326821dded3c5dd1cffe0331f65bd7f229f922e452b639aa38b4f9040296fe34c3891ec983371f1f57aa3555e347c4ac6072daa6f069ecd281a448b0c141b755c50926a05305dae4553428407ee386b5ed80377a60ff18a1c1b7307215a0132c7894114035323d273b77f5e565578dede0d17de1cd44a16bf5d1cb648027bc86c1d1fe98f24c136164275ff793bca01b8dcc0d0c24287c9d4500d19f401e49741f2a0333c9801440c0eae58605e029c00b242b6ec8742983b127f9a9746d8fa5281199666362d2d611ba21b062ce34656181e3285787d9b90d9bf5eb78df1be7cbeecdef64e722a356bba552ca0d6d80474764004f5e4b280bcd50962a73fe042bb0f0ea9129f77994b2edbac07fb6a775c69309c6549474e2e3216c61e416fc9491740738944f195036235434f88146a79617bd567dc20fb215a3b815c0b8360849b2796f2febb28b7a5e764bdf327508bc1ad3accad033325a5bb8473f5109bf759d75223efca7387f273504276bb2ce92c2d1395a2070301d79880f5e94064b786e50c91b57b179ae9259919233f25b6e336ee5a34d1b18bed16da210583021754741d2585d696d15851fda764cf4a73e6cc287fe7f66042de190c6115e4d1a59ce1d72c8eb029a2b6da9717a81228182f48d3b3c9346069f34a4c467f99ddf72a30d78cda7bd494e87aa0992131b2cdeba59777ccabfe45a1593ad3eb6cbada264282fdcd0788af3cc0add058387a26b2ee9410558992568e611f9b1a3e9a13be63c644f193ffa70d1c20ce65660b05a15b73f4d7f0216a221ba93eb5df8d601088ae54244011ff79a2c100383646b2d78647524596225e638c116eb62018d145603e48870abd223bc41435ef10dbf24ba22da69c940e22a0b964e073f784148c5480107b25616a7710e91f2783800383d74619e96db76a6e2cf1854c02a84a9f01a6306751cfcf513cc35dd927a07b05e0371ed086eec6e09eb1d1e1d828e9c86b49f9ef742ecc64d5418f28b28ce73d27e47efce9367c95eb90d41c257bcf4356f381ab4491139f11ff8e8dffcfa8ef6b35d9d26dfb32ebdfa72e6e59797edb367f5653a4fc876401efcf35675a9a3b569c34fe71f687cb0ca85d58c6f01c0265f8de9e28cda4e8b33ac91690d2f479a89b2c64865491187b34b25d35991a98121df8e556e639aad10c337c5e5a26a19e499db7409638fa2e491240c9d146fa7f4dd08139c74e7350fd2a716de29f0e677fadd0a9ec24bd85232fad6550e699927eff04627462aeee333ce3f251f3af208cdc3898d0b721a205c174478026d1ea6375ec9ab3c0ac1f21521db288507bd7da834a0d60538109767d40b4448c62918aa4737331cba18f994cb86aee21f9bd12b71b6f2cfbc7c65d9bd707139d0f77c1473eb28140967a38748e9e63868ab1f111b3b361507e61741d1d97f60366e7804c24806ad05025a8d13e7a4616270f2c07228d8502048ce0aacf0c4b3ca3d79289456402c8e574e17003082d38ee922d0fcf0da18a8a7c0cd06db31e80acf8470aac49f716bfc14d6f3045f067f1eb335e81cd2e18e9bba07f5e31b019aaf7597b3ba31418def868f9d0eeff671d3340f68fe7868ddcada2e1ceb25ca4ae37f978fd38a977fdd8309861db109422b8961a6efb5252826372e89e0465749b02ad08655782aeed0f99d86fd6995bff3cdac121178cc112bbdfab27be9f2b2e8d59745eb3274ce5bd2118b6ce49ab772fd542c8a3563e74307382646e18166b6ab6ef09a759d463224c1974b988c24107134613b342810b464170bb4d045193241541030ce9f9b4fe505c3d7ce7501376de557db1cd8ad93790f2811f1cdff6c8ee0566203f3ff2431e49d205ae16d0f1ee88a8854a5cbf1f8c585000f120ad946df1ca59ca10bbc0973a18dc2757ef28c54781d9fefab8e116bbe126ff840441615c4375d567b12eb74f06632e7541d010e5fc362a279d976f18fccf08a6f126f7ea30e947f1a2e4218db00f338911f58ec83664be72977348a9dbb7a7579fcd1fbda653c18acbe2c027b8a2b176952aab9ccf05711361af00eb66f7df2dd608d64e088c295daf766344c354a780901ee408acec9d244ee1b8c3198d010371037d04211fdec283bdb045038d3220a7bdcccaf64531b9c6e4f08abe53e471cc4c1e7df12992b7a3b378e859b05b831c4393a2be0c3a84a09a9f1a3aa462b4578fb771db5daa84dbfb49eb10b19e08567c70d90fcf83329f228c1f66bcaaf31a147e374885dd219692a4ffa599af18a674407c12030627761b852ceb2200986ae4404186cd06e4606772a4b98f1922701f8facc0a15aab18e7bc3c086af68a1d111ab3357b4957f4e5bf9fa06ab9667552cbbb5814e6123099f80875ec80be8f96b23574ecbdaf599c432bed8354aee08517e33a6d086c0c264bab07964aa33d6fb5892995f049ee4157b4a6012f879d8ed62c79052269504471432aa42408888c4ffcdd79589c52a89ac888bcc8ee74e7e2a45f0c0919614360f48a767540d6bd5a06fdda125de773ba0cc6a39a12ba01389736d6fe0df35284e12befa4b13b2769613bc50b70b07f5000412becd213ccb292410a160614ae56e88c9a32769b2361e221b28709c215a05f96ce55d08bab5af1a1299d11fd848b3957f49273fb4d3b875fb0ba3a3867deb542eed6be8aca62eed6b602be7a28d80890348ab5fa78cdac23757d88419774076a30835912908398a2b93bc8c4ec0c21c1afbc710e114d93a8005b05e854f620e853c1aa5f28c1f8592b00e2a6a9024c42caa606656648002e91cba805c9873c646bc15a3b227a3bff3afe9fa4e2ef14ed398fac12f0ab7bc993d303ea1c38cc467dcc0ccfd8411f6c8c40e1f08860e65add36d827da08b64109c67bd4c62d8ea3ee536fbc21f0889f8ea0bf910f2a9803d1b6405b75dce8b9d224211fe73311b60000db56f8a5ca0c08c7e223f3257ffc56fa5d39ea68c972f2d6774fae3f1679fe8547e6de58b470e5f8017dc2ebfe886978a47639cb0b375cb1dbbdb364c58e35e93a13ef4a331286e93e69ade828a33e23b9b26563eabdca22f1d5dd185627c848385000b31a7411798d94dd914bcf050d2344dab3b20c37401a5f38be3c934600ec4ce8566b6909bd7cac03ed1a59def573f465ac852063ef4c67d3c893830d2e47705402e614e271eb895dfc86ffec8c7c6b5d0a0481500873c5d409a8253a603887165570ac1fc8c5e85631e45dff248b6ed452dc81a2bf4e76ea595f5424f978b989ab2ef45e54e013dc92d258684b279d8ae74e7cd7e6c05c0db0828e27599de2fd356fe9646b52dfd880468b6cbbe20be52254fe909387ca04cc7b1a164411aa99be5d33470aef7c962df90558e4f05462338c12a2cc314f2849369438b6ce088d4060028562da671d6bb9c69fa6824f54364a714b10b4560196c7f94d1f0c0c07602e99361cd8357023cfc216986695cf0cac71946867e9dfea551c5286c9c793eaf472a1dce30ab886abf4eb48d6055408e2b4c040c395547ac1f19a982f11d8d2ab4215df5122fb3c3e0e8403ab4981ee0491f02358f665aa4020e1ab1715879595480513a2215c7545d11ad3b7f09409c279c2fd3a550477ea7b943cb32cee20ac011ab33dafcd83e7b6e39d28f4e58560c32223493a6e601640ead0b30fb214a5affc022b3698c2f1c58ab91a50a3b8566f478cab40e456190e203b739cd77915979e325636e136b2123032464108f22332bd71e2b68eb8d9f4247072545a66cb309abc22b26abac0dae23017d02df9ab45692d591143072d248b296eac6846ce92c9191b329bb39735ff54885711a420272f5072cf6cc7c569dc42536874ad7c8368bca63da8349f145f42611dea9d02b93d80676cb06175de660ed261e230ffdcb81c35f951643f36cdc6815bb2c6b64acb69a877d107dd0243606a7cb00d2c6457edbd62cb903833ef7f80df8f199bd654fcfcbce5ebea29f46ba23805ae1c035e1266f9c95fff19d1fe109b70a6db3929dc7bd9f9d19068e60bb59a60bd33e8e61b069a418d6d9c9372d4815ec16d35bb75c2aed460b73fe21b7712be12ab165749e9cd5960a29782936278ad0385228e228e4a2d7ad0bc8360cb65dc900249ee776704801617b6f52d80fcae1cea77935fc3419c98bcd511cdeb36249f631b0e6256805f418d111c1566ba90bd94ecb767049532e09d195f8e03df28092c0968a9bbed3c014b057b7b617d0c938bc8d43e650980421be053c44af0a067f4ac3c74104b39c0673475ee7711fb629afe18dbfa10e6b64b6f2af5d5b1e7dfcc1ba2e33f2cad33a940c7f6fbee2ad575c352c6931e15034d60182c92ee2bbf3683328c5cb4afa6277ad9e828919714f041e8827f57c589de1fc0d3d0519445d60412045168a9b771962c70a603b8440ee286cc3438f1ee0641de51497695b1df525439fb541ad304cb30c53ad17cbb746818147c8dd1a2bd515ac0b3072523106326409a85c7c1af4dcbd462b105bb7968d9e89c7573d4ac223fa36b7ca2f61d1563afa0ca1917541992814ec94031c9cd34e598592e5403e4e8b4c71d77f3815adb25b9f94ab70fde78c4dfef022b491ca8dc8f669f13e458f26735625b634653cab2923df627574a0af24b0b2dc6c1558014154d014497b29f986750108137bf2bdf97048688f9588acc9cc97fcf0f33d152206c108652ca029c11f842105e4201c46ad16062cf4e203bc7878640b45782b6e19be347b71a974a10aa9f46a3ed2a34723f4eee0829c1e0e668322d3bedea0683bbbf0a1ed38723b3fbce01dfeb34e8d93bc56169d565e56cb062b5f0e889aa964188daf80c11d0e91928865b9270bef99af3086be8e87d986dec009e6d33a4521a0a6771917cc7643207d23918482f29d06419ff6135903cf420a802c670ad7bc95808780b193dc7c870892cccf99f8c78a422d0cc5a774eb1a6a10e06949c62f1691d36cce6aca78e5cab277e1451db1d28ffef105abfa73cd2e7a44208a008f349ee8657d0cafba278486211f4ead81ebb8d361b69ef810066831c872362e36cc90995d746ae6ceae446e65a16505d275857bc7a1b35cddc1c25834c955f7a935c6b0f0a2036e437317177bca5c444fc8ce5f6f5498a770ba50d67be48666e5db65173cf84776f522437ee892e7ab0ba1b8610a818c226fccd88ab50d090ebe7118f88a541c3876b6ee417cfeda38f109f93028c6b37e02b7ffc1b14e864112a1e6a17852f05228c401035730240033bc68da9c95aca87473bd13dd33fdccd13376fff47746eb28caac790d3d56887d00bff6c36a67a4a11edced5bed326eebb59733fa1aeffd1bfa76611fb1221ffaba3b11b3062ff2c268c809f67a457ef0db3f125c7e5044dfbb584636c9ac30849d763eb85dc90458dd497c0da629c546866801851f1133f06da52c18b806ea9a88f1d74d0a2a7b3e460863f182e93afd8305f2f8644b3d3a98b53129801610e87038accc2f72a237bc82d7d7c02b6556e2e9dea3612900d3418b49f261a74763b21e81bbc08cab4bd1b4bcbeb7dcbe3f57562d5ebe691c4073bc79599ef4ea3cdfa15b119c17bd042d7f936db98dc71dc3c8b72f7463abaf9cc6dbd830c5e7a0f0b5004ff51b624ff4ab970f7480f7a67e5fec0bfd70df8b7a78fc97dff98eefad037768067f031008bc84291ef1f83cbb0dd842768e585d58ce5ed19451a7f28f1ff7ab2f2bad3533af0936f1b6b0e9d27291613d2da8e2135e7d83b098a288152a0168db9e2a82b5420400bb7618d611b708099b454dedd23850478a552bee0318678289d2460c36d7c092e7b514dff7c71ffaeabf75705af87d9f7dd5b00ca511dff9b9479e5d00d3d2961bb4c8400fe2e625b861d604747431d0cc4d03be796df28b74f88467464a1b92aca832e4052811a5f0e08d5e136c864333e7b5eee6655563d397f256065a4bc971b75a25cf74937c09eb81dea3117434b667fa21741ad5fe93fde5e18387cb2dfd22e6a79f7eba7cfac927cb8d1b379687faed669e617ef35bdf5ade78e38de5a20ef7dabf381ec9e233cb22af20c3be5629746db7f0342a2efa76e15ded32eee87eb8ff5899fa872744f92ff6e5cb82b5d808478d4d5f0b2d0156fa733b81a7959bdf8c2e4ead5c2320fc8f077182996f186dce2b599266110528d006a210e904570427c054a433946d9d45e6effa93c3e26001d0bbd909afe50f3b8a37375053d94230c71bade9bab01aeefb24a7e1034fba269b2bba9726d800c805131d5a46f3e01e3e6df0f3824ed2cce98ef7bd1b11e98eb7acd8dc2904270eee49fae771552ac5b33918075a1980d6747c04e08c54cf9eed2f8f7466f09e7e66f6d6ad2f345add7083ba7efdc6f2851ad9a3870f8d07ddae3efcfed803fda6f33d7dfbef57bef2159fefc481c33f1292b8c53c77699ddb160c747f87727a18bda35192efff58eedc62d1e53a91b289fe83ce72e49cba379cfb4919b0ee803b5d777547aed073accade2967a77e900829d7cec2ba1696fcca99a4206018650d3b333c4dc145bcb81df9e58208b1f2785024c871efa32c38582e04685df94a299ab49981b8114c20c8aa779c14a48ecf4e236ed9e6c565a585aa1d680e6e4874122d2718602590171d9b5d648a62c39785de5442c60d091d3187066edc4ff242066186b7dc4128b68dd7b8e077e8389c4ee281031c6c1fda55821d4a8e9d79faa7df667ea8c6734bbfdbfc9946aa8f3ff978b9ad0675f7ee3d8d600f96271acd2c4a74fc3cd219557e1a19537af8edeb813153c7b7df7e47ed622f7620541291897993aace99ed231e1c638f51756b7767d97d51df62a586b6ff99d67bbc2d2d14706350cacb40409cda419010665f5ae07c11ce2c1ffc66095a363ec467302117dd141c6d064a3429a692170cddc00f00608279988379174be779be2c400ab22ab5187894524f1625719485463704b40ca2f64cf128e614743b383ab5b3218ca34abb416f11baa04bfba0ef02187d38b064025c7d810ee8139d88175974c93264c062737007df7260d38943ab39e85af4d0ad304e4b5b3f11acfc8154056d27aa278cdef86695d734cdd739ad183c856d0ac1325269fab74fa37aa0467477b971f3a6a67f9f2e9f7df6e972470f7e596f3d3bc8cfd14247e8739d69a08186a7ae92f1f8f1fef28546bcc78f1e698677de34f61bd9fa8bb6abce46a80bfab70d8094b49d60fb37cf3405e507291eaaf11e6afa0a33f2c2b7eb009481616cfb0298f520af19371e990af1103cdbdf6e648848e504c50106468ac2c0189d1c946585587728c0ca8c3b1f88e264a731911e99ce33a12e28ea1ecc0b62338ef2f0852622ca20f007e5e0195de08f1ec24136f7426ed1ad4317c09c6eae2d109eb6490c6d0b0cc3ddd7d0b632ce747ecb6ad9e66b651c4b52489d3febe0b858c235f0266c392b3c398137af96d0e9e6dd703857b1460f7b29b9e0a23bb4a6434005600d67bd74a0dd3fd654f735a56374fae28b5bcbf5ebd797cfaf7fae91ebd6725f23d5c133dedb3af6d48f47258c56841449f440cea1f8c19bc6e6d75282a5c67bace9a2a68cf7ef2f2f69db1d7aaba44b553bd3811ef8a430308c71e0beca632bc4afbe70c48a1f0ad4884985593ddc3a423c43493f1f90a3858be5498aff80b5742888e7f7c948581f5df817ff88800062f253398ca734046d70188716064e83a8ffd5b1e252d30173245f995d8866094105c32b6ea8c84d22d878adc68a05097c8795c59aaeac00d6eb068d8c318b321e59094d2c003092fa700b4ed10579e31a9f016a1e1bd9f6133ae065fef00b772b62f9f10f54f04a102f470bcfc0ce23b1d2047fa235492a05856759904ee4ad0f30372a6dab3fd5f7c933aa3032b141e146f5398d4a6b2ac1f7f7f7352a68ea255bbcb6d2b42cd3bf340efff09ff4673ac90c6c5bbfe9bca3b796cfe8eb01cee9e130a63dd01a2c8d33fad2586fddbab9bcfebad665da0d24b41751d77a964f3adef7f655570913eb62333582edf16b9c3a01f2ecf6ade1577883df6e86d748980118e6306848816f5ce792f0bf52e02b08a19e9301d04738a963a45d04beeba26c0a45414430b62121312c5934c4e4e561afda399d9880fce59f4a009ff0b3824e934da442b48f1655f1c906dcc128244c96ca43f6061fe5b56c505300e810594e9b6798a7e2992838ca03b7e546ad35bdc1c71e8277f44c1efe8a2f91d8f8e812bd75477e14d73df438ce74a28566a6b3cea53f6ca2d3c4a2688c0782027178cc30f345895298e9df811a158de6716d56dcfce286a67e9f2d9f7fae8d8a2f6e7a5a089e06a1c8156fd6527b6a30ebf42f361f1e66a4627ac8daeaecb9b3cb45fde2ca15bd7672f59557966baf5c5b2ebc7041bc3f5bfee5c73ff5f63d0d92cd753644beb879cb53d133fae108ab8821e5a8b6039f9e0cb3afcab442915e5a8eec4a87b37a91735fafbe1cf50f529c64a2b48b05bf398f54c2cc1f27e8fff9508a9135d6641b584280103c9030a88d02b01157be0b4b95825eca65566ae1990d85940d47d42e1d0cb110c56679edd4d6c3884e080ba5743397ba5b47382313e4811389aebce4d0b88ba97503af15084700c5a39818920b34d0cf76256d46e214fb5612b401dfb70287efea05c5acb4b2c7bc3ca89153bc75233dcb06abd3cd0298fd819d15fc037cb16aca3b56a33a7445beafa9d9ddbb773c05bca14d87cf19a9d4d3b32bf85453c48c46e517e9c048655d9abfef5d4fd4e8f43b612f6817efc54b2f2e57aebcb45cd5b9c1abfab2d19735fdbb74e9a5e5c2455574359ec3a343ed2a6e2def6aad744b534f9fb2d033aefd274fbd514263bfa48d8a35c44a64bb1cd78c8d747cd2fa80b496d9d685179673afbeb23cf8fdd934b28db22986f88e676db8d022e3cb7669fb9c4adf4536eb33b3d48f00460173abd1c5c8ca20cbb272a9c2147915b42b8915d82c7865b766e221e3aa70a371984665239611932cc88b66186310f8a26c078fe42c1fa03ece8bf4c45b7a24c3ae430a8c147ca2c79a477aa53935de34f63682ada27d173e2024365f4fca2a6da1f6277e84223c61827c52cd2eba4f15a87c63bc62109ab552927eaa1d401efc7eac67543cabfafcb3cfbd71c1ae2023190dcb155e95688777b2f89e7cfd41db0f7a399a649f09eee99fa6758c4c34aa573452bdf6daabcbcb7a8feb457d27fd05460fe5ef6a2ad947da6ce5e196f22e2e975fd2dbcb1af17896065b4e7edcbd7377a1f1c38bbcd8843fd632c10fe633db3ae2b3a7a0015983815e7939a3cd8fbd8b979603d96b7b365111e150aca4f3ea3f32daa7611adcbeae7536e555d3c5249883331570cfa5a17e47bd113d527a2da67ef45ee9c1c23045dd8d284eb0c9b19c0a21e5a87b92e08ad1b670c73b86860d89d0e906af0eabd2404a5a5b2f4850bba2457e3b613867e21d414db7de9b65ee913f8e51594ecb808638fa2418db3274c97f154ce10acdd983089f8b419c33f889ab2b0d5c577f4606d7614f81663f01ea74fbacd37d473cf1bb5a5ffdf8c73f5e7ef2d39f6a0770fd0d2f7880b3b7a7aaa188f9608f689a07157e57f5e29ca67fe7f5f3448c34d7b499f0ea6baf2dafbef2eaf2927ee9925d41d65ce0c2a3f5813f818aed262a5917b5ad7e4d0de98cce17f208e0785b020f0f7c0a84d195064743ef109d529fc24b5e6373c565228615c01ba76b1aa83bdf62b5c3afbee8b37fe3f3e598dd4fc19b3274f00762e32bb7d24a111a23947048987d854edef80078a461fb99e6a76cc53245d084403b3b573da4e3a8763855a01dd64a444172ac922eabc00183ae94f0bd12867686e9275a4a7b0e544ac204266a8ace334234231a55e4ec7216ba477fa8f8accc5c89940ca4f3fa0eb7cdd0f81b6c263de26c74100f35263aa8112c67ad28489d254113f381125acf4e17d4156913969c359f9875f05d9c44c31a8a75121598291b9d2912c80b6e7941ac89ed68b3824d8a4b1a9568442f69f380e9dfd5abd7345a5d592e722ef0ac7eb2487874cad13fbc88d3811356ff53f23aa42bbf9cd5c87255231efc1fdc7f0096728ef410fbe172473b98d44b4641d37311bfb618dd7c6a03b842cb4d9cab3188544cb4ac6bd409b001c2dbd2c71a3dc7ab2d71baf1c5adeeb9d92f2e4bd1970ea9d5e209bc02f1d6037b77c9229f0fa3d8136dcf3e5643dbd24fcebcf8e24bd62c0fe588a2f049d1056d214131ae5db1ca2e5a1a40f18146d1912ea3506c84a61fb02a382140976cae96a69ba003375c2ae739f89051a3897581b66d1908805a9109a8689cd930f4e64303e9ca1addaca8e4d8e4c9bc3240f843cb21de22d1cd96ea5a76b579ad53e0c16bf943a346462b318cbe5bcb3955322a36d33ba6641d90e9998b460e1a0deb2a1a160deab5573452bdfaea7259df9971fe05fdd09e1a07235e8f56f0eed910fcfaac62f48b5f805b6f25ed0695173fd9c4687859230b8f045a4746581e13307dbd78b18f58e15d33c121edbed008dc7ee78ee9d8c3875037c5348430d26af4ddd3faec4875bea7c0e0b93e5a395f00ad61f267e7da9ec220de69ee6c0679ba88703ed0efb09053842d59769afcae110ccc3c6c9b09a95571702680e276ae10c0b7f1c2360cb482d920709ad33412a414cca858479a69661e936ed653bc7db73ad0ac69252aa02c4179956d1d928ca39d9fc6d27a1b5417c46034375f8b65e085441efa39af6572a7303a5d05a364f384c471771af0e2e1af9e53691a45d9c093e958a6f2e66611830e8a138a581a7cf5b7a78acd41dc175fbce8675b48c0674ccb18a9bef6e69bcbeb5ffdaa1ad52b5a6369a45263441ed3446fa2204801193d4a9146af7c94a72d7bb6e599023eae1d4bead48bdae480a73b6fe9c24362ce29b2dbf8fefb1fb8de3193627dc66383fb1add1831ed478428947451a736d8d6d22918cf5fe30f51ea7f5b9b2e7b7c559c1aefd3dbfc803bf8196d574a782b437ca16df9236e9a153bfce3830115ad1a19c47c94a9619e052c3d1343341f0a76cf051db3205e634aa48605d8718395d03f6ab67233a5d776e041e300577093b2214a9b6558393fb9c5a990e1b1ca58552206bbac7da08cb093f8c08d39e408d57a449999375c12f059290b5fe22e88ce8d6cf81417db3a689005befeac95e8c12b2ae332c53c92ffe9c97930cb46c09dbbb7b55679e86df06fe9202d2340ec11b155805b24a2c9a81024085635bb8317b5c3f7b246a8cf3fd7ab1f48d67496b3835f79ed2bcbfff5efffbd0fe932a504064f78f58786852dfed031573eb3a1671c02d601dc077ed675dbcfd578b6c6460b75eceffef66f97bfffd18f3205d4da0c1ee7355564b4e415977e5e86ae3c2fe3d1c19b6af46735b2dad698615b3015dd7dc79fa513a612940c0d118250ed637d8bd5ae1af69e763ab7773ed5260fef97b1aecb7b6641e69a7219fc4706b683be364c649f1676ad143942a017dbdda5b76280d3711837328d66528cd03c9c9cf9755c776637e4c3d7c62b4ec108e27f2f4495ea7493929d9088e9cb69c0db00f32a4582696d8ab6e37db794ba148a99adf9818a934795a144154cd92224e4da57a0da4280450dac46258394b6f5744e36acf92a175f54d266805bb470036677c15e91fbf7ef2dbffef56f965ffef257cb4d1d597aacca8b8cbff8e6b7966bda16672dc46866a6cd7ef0af081a434428df81cfe60423ca9e2af613ed3682e446a272c756462eee3c44663a957284451a2969d6f27eb6a6e3539cac677a775ddbffec58de54e3407f3a058e5ec1ff059e8b7df5ba9fc3d168e20b8dacd2e1b2be838369e1a347fb52148575c4ead163375276438d8f2d820f739cea744131bba2d88dbe03a038d81e502ebe30bec54a3d8370082b6ec8222f79b982c1c7c1489896fad1f7ce26ede92224da33f41c9a85eb1ebf4eb83c5223d3a2585393956391a2b9801eee250d460eba550c7f1a2769e15aaba19af2c859d3cda374867823247f35844c9c871c3b0d3d4c119c418c082381a08f7a016de21a39f83002a7f91594de82c04d7252508614cc6032eb63c4f0953cf0c921f47c7fdd99adb58b379b349d5205654a45e7c65637a3077cf97ba6e916e701fff0de1f5c3169506c42b039c58746012cfe46277cc2f3afbc00c9d9413ee030eaa5e144f7b31a3dd8c0a0b171548a3e5cad66b92bbe34684ec1b3e1d065135bf472a52a24e70bd9eef779458d529ff018408d8bd196753d2f60a21bb4e8847c3e0468eea9f15dd233341fa9126c67676fb9a435d9cb5a2731e279a4c47ef9859dd0873a5942034c2157196374b9dd8c75b13c3c577ee09e8037aba3108cf1675bdf2a9c6fb13aabaff0d60fb83b88b7fe0834afb0e79a308b9bfdd2f9abbc86f4c368a5d9a1e9e9427e3b387362e6c53e78cb8e4c07e3c620834a87564e8c646c1b2c0c9372691e10c880a2338f718961b66e031f049876450a81443914556a0f10782b2f22125937178007a19dd2f45d3866aa86e67c2b8a206185a1b38923ff34671b4117e8b7996e09974ac7629ed1885d34d61b9cf7a3423f50857de38daf2e7ff3d77fad69d355f3c5001a026b98b33a6bf7e4e9335554f45017a887b49c6aa771d228a35d3d5f5225f6b32f9d27a4c2b2a6e159d3dfffdddf49c6eb6865fe3438361b68d8b76fdfb14f8ea42fd3537483378d8c72c53fe87f5b0fa8df7befbde5830f3ef434f316a7403452f9b10f46c25d3ce88069548e170ca18c7c6cb4b06bf83aafb2d0a1c8919c4fbca0cd144656ea1f1d0f4e3b3a06ffde725b7ebaa675d92e3bdd159006ff6ec891deb9ebbdcba77dd4fde796d697672ebfe475d933d99bce30b6a213c13453a1771d6fd9e0347fe3a7784cdef08c6495a16e45ced14826e13c34a417a5276923c244c892d072ad849dab58199c4c464653184e2ca15d41a6110c16a9425775836057c50b0bcc0b7ecb0e565dd1a9018a406bbda1727ce5eebe4ac0990ffc4f8676922557c940831c633f47528db21841cf4605959629d31df5f21c21e2843a8d8b91e8e1c3c719695479e0fdad6f7ed36b1324a01295fcb27e9584350b3dba666e826fab011cb8a151460e3652dbde8f1e2cbffad52f97ffa6676037d5c09ed130c59b75cebd6ff18ed66b5a77678b9d0acb74d3238a2ab9470f2931460f357c1aa01b8b84d0406ee8d5fd9ffce427cb1ffef09e474bbcc12e221dc9491f9e4ca3270fb839ef785be71ee970d8487190fe6799bebe9c9195d76108f8844e823393dff8c65bcb9e763bf1abcb4034dc7194cbca3ea02334949ccdb821557ee072c652eb527e5e69ff537dbbf0c1babe32ad2eae776217fed052c682af228a6b6e889ee50375236b6770c7a13bea5dd87dc2091c0ee5b434888da7582a19268a6616484ecbefcabc59174915c6146d5878852f0aa2344639ee8bcd4485618cf5322f240e4ea52f4e17d019a9b8289c7200b819dc9bcd602b802cec2abd2dbc90741bfa399b0b98991272eeef5f7efa2fcbbbeffed68b78d6164ce558e7980efff1273e8f752afcc1c3fb1a31e46f37041590cae1b276fb1889a89c7e56a5691f9b135fd5ee1feb94944b74a301f2dc0bdc1ef9182568e83df29ddfcdeb23db5a835fbc7051479d5ef6563cd34a746754baa387c07402acfb32cd43479dfbab0e18996cdf47767ce1ca15c76e3a15aec0f5e16b00589f71840a1d794480ed7c3866c509914b7a44c0460f34f889a355744a1c527e410d11dfe2b3b83b76c300fc6852f22417fd0c5746df510e79eeb0c4efbc6c7ca0519d575fa007afc3a059419ed9753ef7c6a7eee0a30ea1ad2dfc06726798df510bdfe58b4c1e6fa9075423d357681d7b9d16e55050b118e9ab55134c9a90b9da8d06d953808480238adeb7400193e9540a8e54f113411b52e8e6d3f16c3a84af0b2002ca61c21a82ccb1c4a462b7239a579053586333c36beb88989b00004000494441543972a029bbcbc6d0010bc057c9f3668670a9b89f69adf2d1c71fbb00f24c4995d5cff7412ce5e400a6514cdb68844c01d16d4785c6aedb0f7ff837cb3bdf7c677989758b1a05eb2b1a5c1ac0ea8e331af958673142f1ba085a334265974e15556ba9bca34587bab5bca0918a75108770d3c8a87c475e6f314d638a3874d15a9075d115c93fb3f7913b60f83b50548ae0079795ece2cffe05013b5dc86ceb732ef18ea7cc991a5331b54120a7bca0e75617f558811d4d464e68dce8358ab35b99ad7f8dc4fab364f1c5e796899c3f12d200d0af5481c1193debbb724d3f48a11f0aac75d9a87f42c406748b30a2c427bb040a7ef3459b4d7dbcbbd80a828c22bc8a408f454fc7fb434c495e7821c448b3bfcca984d97f5c2c1f1455b2c647a23ea52b6441c039c1b15c23849f9d001682045f59914ea001580f65f62388352fb01e7d52e151ae89a18dacf0073f6aaeaa96762587ad740a9d4a47607328151cbe10972dce8d6dc8a04233dda31213401df7905817461bdcf154d3bbc8802f085bcb8baad8dff9ce774cc7c8c6e6148172a301f1413e3b6667a4d74b3ae5cee8f7f1c79fb821219b11800d13d6d7aef85204fd988ed260c9a7910b6a5e3446a6b7ec3a824370a3d4c84345673dc70608e6a065bcd5d70256f9c60ac1248f38f63152315a7ef580d1389b2bd43d1e8ed391d01981878e6cbeddd3faf38ed69a5f79fd75cbb6bbb141fc08e0753c90f54a5ed7f132cf99c055d1976dca47363dbb7d538f4c32fd5e79a171f8db6f1383b6abf9a70a5097e25bf0f9acd34551984808bb6a5c4c17b777b4bba3211467528ebde80eb1257341875c898a29aeb40140aba2cc489633c983a24374801f0aea5e598e5b4361da8850243b85bb6ec39b8b10e08c0ee0ea222623878860761c594ee19cc49147e5a577a707bd779751e69677cf6838ef7cf39b3e00ebb588689b1e7ea4e2a3638f36bc78484562b3c22700d46353493975c19ae7b22a39af7f3035fbdad7f23c484ca41d5ed05d95efacf03b8c86e5bc4cf1c94326a31023dc57b58942e5646383531aafbdfa9af8bfe21d3c9564b3f246c2256dac70348a750f8175139b339cc267578f46ee20e79cd7ef307352841107bf10cafb89a330be56882fd0bffd1c38fcd9e8610a48a792461cdf337de4c43ef632c5ed919aa9259b31ec787bf7d5ac901c9e9647aa0a10d9a9835645f1e8e34d3c121d34626e6be43cab4d95871fbcefaf2448ae0d1116a994a74994749dea1cc9495987ff6c6bcb4f97686a2e614c8fe8b9b77a3e9cc042f8f0e840bd0b051da121c110414c564a29ed1144c9d8023ea1ef49812dfd1c669bfd4e124629070a5335821d1763c21c217c84a81bfcaccf244ba091226e3c60a2c109e93822cf428d040ddbe74f97f7b593f6b39ffd7cf9e8a38fddfb5271dfd45a88d312d7aee9ebc5d4e342671586414a33ed16f405ed9881c79aeaae5e4ca4423312d0a0807b0d225e34361a9e0fc28a191aa15f7cabd10ade8caae2ebf251470888f5323b71ac5bf85a352a22cfaa2ee8e0ee3ffec33f782a48a3dbd3f3cfb993b42bd05b32d8f8409fdf68dd480366238372674dc67b5d54a2aeec5470d64c740c6cd9774382dfc9804f085d09b775f0d75336c9a501a127dbfd9c3ae9c00c818e81692c2317765327d808019f1d4fafe3e27193d9f723164e5dc19b2f77fc2556c4449d0e0c36db3ae8cc3946be2aeee8c9e3ca13f208c22e63429e86d532da078d33c82ab236325123d8052b6cafcbd4e3d293b3583fd43ae1b87782662ed61cb51588a335a16e2c6702078895dcb8acb2c8e910d3ac89b03b955c5231a8980f21704e834f4cdc25c3d4d4522aa745860e350f399ea4e90885b7af0aca08c00b853c2c453ab59b0d089ee9bcffde07eae56f2e5b1ad9d979c5273c2362ad40c3882de8680dcd8bf7b4e875c1a362fee0fbdff7c8f5b276cf38fbc79a89cacfd48f8a87dfb1a1dfdb6ad35c90ca43737a61a6ad0787f96a351a166bb8cfb5b9f2a976c718896864e07cef7bdf5bbeab0f5347fcefa2817f2292d730ceca660ac8ec655fb611686cf7efe77b3bd848a13381963b6ba62b57af2cdbbfd3264b4d9fa1e9ba43dcbec01d563c693a5ff8620d3e6424bb7fffbe3b1a771ed289cd14a6afac2b790ce1131592c93a9546863fc93347f8e373176e351c9240879da4ec02df5b21672bcb5f7ba0c6c50ee319c93db87f57b4d965b4ef43adabcaa778222f5c4bf448ad33049767e1bb9141bc3214a14632e6efccffc559239976c4b42e3bd6d4dcf6b46033411cdada62476db7717431b8542a14b20c696561daf4213075745a89922e63c12fb64656c24e00acde3e8ee00e6bbe9be2c0bb6d4fd4837aea71ef8e9f4ff11e156fe0d29bfeeddffd70f9f65f7cc70763699854ba0b6a0c340876e6904f8fceaeebdd3bf7dc1b5fd0b4e948158d06456744c567bdc1f329befe8c03af6c46fce0fb3f58bea92926af87a84d990ffcd83da3a26225e630da64c4c11e1a547853f9791ec586018de953352ceea9788fd509eafdaf5a6fd141d2d818d96864c8c1c5ad7f2a8b1a5949a513b82c3c1e06efebd91a6541c3471edf1ac59a9c9d3f1a398c78ffeb154d3dd9d97cf8f0916d8167f81ac54562fe22011e1d5246c0b764172325fabff9e69bcb9e1e1dc19ee937cf0519f97ff3eb5fdbaf12b01c686465db9f23563c8660c46b793123d6e0c596977ab15ed141da04a0064d532175cc216735305e7dd9faf4637d573e6bc1c2541940074ff38580cce255edd138335e84e4ba8e6405450d845399fca29eee54203640244599f4ba4415b7c0c4134599d0c7d0e0d81ceb05f72908993e63030a23318e4382dbee4b4a57082cdfc5150339b3521d892ba72a1d8de191b6c5efabb2ddbd735b8dea0b8f483c40bdadf443e51de8d908f6beaa42bda383a23d3a219f8e862dee0b5a93d8c9d402057ad5077a53e1961a120dcba71e541979a8cc770bf2dd172cd499dae0c7afe87b2ade79e71daf71fb1915fce8d531663c63127b7843c7d93ff4e6a1adbfac460deac6f51bfe42d047da05e3f91753c52e04a678348c1d8db6c8a42341af37dfe471402a24f2284b683ccd2dc7b3c14527f3b25eb6e43958c2b175a01130b5bbc0f329ca4599d95cc90e268d0caface595d21a652a3b89e3e339804f87444781bd8c60a9333c80d756be46caf392f9543630a3809ca92b0d2dbbaf59a3ae725729fdea4bf33b29d7f0d2077a0615be8b71570fa6b7346a6ea9eec4a85082dfc1b656dafe107de7f75d208595c68d6c55547908d587826224e38e51ac4ffcbcacd602166a6ef406d528c49754070b851f6ccb288447c7c2830705615ead5a1a2b29a635b00c8d70f3af7b1a3b73762acfa1a650f4b84c43ee3fc8b1a04f3ffdcce7de580bd12b3fd439381a05238f6d64d12b7b62e3a1f3530911c93aa44f875f5d3ef9386fee5ebe9cd3e2aca77ef7fbdf2f1f7ef4a11b159b003cff8177427ce8f58de0f7d56b53a9fc5cab8cc027f8f4408f48fcb56a8c1c376fe888d227d69ba920d3a947d29b1198510d1ab6b719add9a0c23704aaf2ec63687a3a4667f1407ce0879e9cb2fffa37beeee92a65042f7617fdcd50c26569804ff06b367dee6a6326af3d81df5346a66d4ca33b90873a943731f3264d1dd0c779158786b2fa421d1f1b42639d251e3c24e76d6abeba808e023eaccb78664b07c679c6733a01b319528f2428f245d38168d7af382c75d6f99ab110fa575fb6f4fa8e1413a0e9cb1a31410f87baad3cc5b5f2251e6ea8314246b2262af780802373f28387d29a0aa907f4738b3eee681693b2c5d2ce14bf1826a0e2f08b8864b4225d08286503504cc6416b7a780ad9e010099e29140d3f23957a7c55604e375cd7698a4f99fe6934a192b1ee620a454344aff4f4eb16b8a795169113143c576204a0422195170ad914f8c10fbee787bf172fe6d57a76d6d875fbc98f7fb2bcf7defb3ef7a72665199e62a3f1e400beaf82ed707462516f5364236b1da663bffad77f5d7efbdbdf2dd7358a50a9d9c840ef04f44617769be910d84e817d3a97b68d46019cc0b48e976f7fade916dbe434543a1c7648d9c9fbe1dffe707945afb0b026ec704ebb86ec30d2d8f0834759a9002d535f4fe97876a7b9073d3f5346364bde97fd8c44ae7018d60527daf83c775d93877ec2e1c326126b2c3a00762cb10138758f2923cfd0f87254d6b7941df8741c4c852f5346435eea083e1a5e539ef36de0d44094864cfd87f389bb8eca2f7bb2674f7a1c6806a16d46eb4236ba379e8061c05d017d3bc4de35ddf96964e11405a5a9ff6430d3450a966726de61544fda3260e8389c4ad1164643299656ce62adcc6a6c140a0f414d4ae78131e4b1ac2290e37589a647bc93f4400f48e9dd7036d33f7ac29b6a54c0d81123989e4ae78f1eac9b9f194b671d12c5c3e21c5bb0765de873a6900a46ef0f025bc95ffffad7f4f9bacfd751d8d0f25c8b518dca895c2a888314867fec03c281da67aaac0f3cdd81f7964622e7883fdf07fff39fff428decb7d60d3e3ea604bf1aad15313e370ad50d4b727635c5e26ce33955102a2215968e10ef3f56437df777bf5b7efdeebb594b8903a301f6df93af38e6e50d04588b171b38a4a9dc34321a2a5f0340a7c3949151c75be7d6458f262493ce08f974c0d88b6edc61983fd4169c8e4ebbd30478d0d0d94de579d8d7bef635aff7c8133985e78e842fda7945ef979d3b7b7e7970c00e676ca7f3a0ecdfd00e6fb6fea18cc4e890745fe746d03074f20c093a646293ea395f49705e8f3a9ee87b4f8e9ef2203c7c8d81597c2ac4ce4e59edce99fc10501ad9c8165331e203477a4d5736a5982e327a6c06344413087070e5a23909c16304c9c4834305ef02a1f04347c1d0811f494e46aa3ca3c2a9bcf3c46974d63c9942e9d850f5f8ed4816fcf482e911c5d36aa5f0c777354a183691c548827ed053f91f697dc1c9084e9073decf410ad3a0ec5474732566178c57332e7b47f2aea661ca320d9d12b86c66c013dec43ded52e5a537663a041c3da9d4573482f0750034001a183acd015c78a2330b7e76dfae5dbbea9e9e171dcf698af3c1071f2cfff37ffecc8d82f6090b6f5428821c4f31e1a10fd3562a2b7ae1331cc5231bde80667b9e512fb3169db66093478d92064cc320a0096f4d5fd2b413182311f2e01d5f52be7464aa43b295d75858d3bdc417ec68b4785515998d0d1e98f797ecd86e717155101f6ca2d1f30804d9a91b9ccd7ce48e89b2a791c53712451d34b1e215c8ebd0bacd77f2e09b7fe9aa06cdb70b6fcb36de961eec6c579de037d1cab7f9afa2e6bcc4ddc81841d2b0428213d9e1f29451958942a291b1d8c679e9cdc14da1c5a149e3a6b61783465094c6c436ae31bb40bc255d6fcfaaf0a98c4c07983ad1b0d84c60670fa7da69e691c2408faed4f0c424178647128d08faa312b10e62c4618a7349df8af4a27a49cec1bda7b770995a622f958aef60e79b7199aeb19b86fae4e1401a8c25204419f0a51270aa9d531154164e89d3bbef6be4ffad46118e53612db4d8d55f0a833ef0a262795aa4290a0d996fce3de933216a62400169f4d4cc822ffbfc915e78e42031cf9268ec8c94e8ca94934a681a74542749e7426363f380914c6f7f49977cfd1b744c6ff119f2193dd01fd8537a72690f0e27e069684c0fa91304377675102f5ebcb8683fd28f0dd8c0819632e1a1355ffd4663fa8abe60e7357d38fc4ba3e4040a78f0a20c2957976dccb42d3db2d291f9d517d53b8ac0cfd7a40f0d0fdfc55f22c44ecac6e5459a924be8c676b281e133996efb3d3b3823ddb5cbbaa7b23c54797547a11a607ee6262183afe0ab940d94b229f26bba2832346c6add51a81b194ecbc98f54f4b922b4616117a561630350ce20a922279162ad4183c5598c1c14de175acc7afaa75d3f16b68c54f4b607ec2ab94714a118d2d83d952a01eda48c2eadf3ae4eac9ff7b63beb291efc32dfa717e5a5c04b9ae261cfc73a4bd8a70e52c03923c70f22303da600e30f694dc7a08fe511b53adb7ebec3eb23dffdcbbff41a81e353f4ae9febfd2ab6f1e92468605b9a76e598526ffb3322b8e8dc01a0270d8f351bbec5bcf69ca2d53b11d108c2b129553cd62bbb6a603420fc92674b97bc3b4865d4d86cd93082270d8c005f74614ac85a8a0d0497a1f259a331bab0bb4719d1f8f02d534bf0d910e946b6ab064583611dc71d1ace34f2703d5fb043fcaa7ccea97abe0b440fc3d9b0b116dd6971ef2508fe5d3f32cd1d177621f3e840768a9aed75ea0cdbff34e074b2619aba78a2a352167ce78616ec15e634e52a393bda65dd93ce4ff428e3d8f52fd87d9d4b2771d14970cbe08e975b1e746e6438b3ad6f2614869f97f10c8146e6e9229b1fd3b0292268610b6f46a93ce44408ec6320ec11c0f48ce9deefd5cb7ff0e187de8e65947aa0c6f6443de681b64ea3b01a93863da6555b5beca0a15f2aa595b730ea9e7a4e5e23d75487dd291ef252c06cc773e89534a3178d8e290b5317f8b203481e05c8c36076ad7818cdda8a918c5ef2f048dfff20bb6d47d92289b283447a301e5cbffdf63b524d955815c8531e6573aa831d3c8f08350233225051a9b054c414069b199aa6490ff4a1f2e3a7c8c8153cfef033be8707bb845907d6036235324e607070d80fb3858f6c1a3c0d906784349096c9ac80d902a31e233032910a3e23078d822960b9d91b3bfc94110fee79e6071c9a4bea54bea787ec9c27c406a6bd744ef637f546be453e3e6cfd1599422a277a51ae7c78d0ce8c893712789c808e7424ccb650923a4403e3910c4b8a1dc94921b5154852406814b5bc96d13ed84489fd1ace976d465a1ee0cb7fc7aa17f64118da47a13340797644dd03b3305d22cfd8bd5528642b1567445d0916131c45453c3eaacd0ff5244c51c284ca16e673038375e7136fae14f475f5eefffd7ffc0fbde9fbbe17f03db7a430bc5692ccde5c88e399a6a151763c9946d0e33257670d4185e5c128bd1a151b38fa51c9bcf1a1866847c18082a493d01f158a0ac1da820680f9ec465289e9290f9ebd2e1ebd2e8b7c37709451601ac6e8c07753a21c793402dc818e5d5979e3181fe10f2a3795063c4f95c487691a15945effb3cff48c4a7cbceb293eed43f4254e1e1dc0179a2ed1313082d03970c2831189338a6c24d0f89956bea687b6c8fae52f7fb5fc4a1fe2f0e631063c7803fa157d4f624627be5c87067fd9079a29a7b25a159e9325795ee62350f8525621ffdbdffeb6f8b22348f9a953943d71b5b4960d6cc8e014456d031d51ece2b14b0e09e44b76345557c3666ac8490f7615f9fa02b6eb0f6b36c3948e453beb491e4a63071b2f04fb870882b85847e26b687f366eb97445956edbe2c7179feee8ade943d98d1194ad69c577e3e0bb58cf7522fc5779c480ada7f0ed1983958392d5c8f4247e57671641c6280a17c350304111d3a20c1127867094c037802900a6159ee608d6cfa8e04da01258696d9f228042e3cc1d15889e9ea9df15352aa6287cfc32a3b69c6930342a1a2ac1f2905b3cb167d5971130a7cf2f6b9a46a3e421a7a44b3ecfca5897691b5d769e3bcfe973b810304071f1b53395760ec20c76ca783472be45977508d3a4bc9e724515ff0d77046d2f3cd1925198ce82ddc2438d3a5bc734eef21b31c9a031219b87eb3c48a737a7f37ba807d3bccc49e524fce33ffe83e5724a855187e92f0fc67ff9cb7f358f1da9a97aea35220d079ff3209886c2687cc90fa5fb281630cd6234b2f01098519e692ab3014a8c06bbadaf0d88fd16ef066f7f5317e4670e99ebea4c9fb3d468c8333f1eb43322335ad1a0bfd08f4bdcd6e918a6d94c63e189103a5cd696ed337cc19a994e82e93523b15185ebba6332b40b8b2e2fa7292be0ba37bf21082ec85307b9c3ba547c9fe94b8b64106684376c9398e8cd72a4c37b5386bea67b151cf4ba225095961e8ac2a7005897f12e103434a8a12c69e46f30409f4d088d86c2a7e7264e01d3efe1487077d4a0999ab23dec13089afa5d53af72f5da7a909663493d52b951212226781ae828ba57c15221cac2e8ab1cca1f1e8c649c57e43100012b9ee9c1305faac933b8f68df3f093cdc16e743689650feb2d37bcbfaa9f63fd8fffe1ff566f9d93f0d8cde60ba31ca3577b8b2ac8260b534cec7ea046468fcda2db7e2e4c6cddd3a8eb759f747ff7dddfb8f17ca667837ecb5815f7ed77ded1c992b73d3a517604de18a073a23361c3c3be107f3662580753a674529847e7e3738ceac0e8b418e1c9e8af00e0991bf6f02a94772ef125a3a3e8f8c3a67ef64843a3e11ee80d6e1a0d23388d9a06c58f02ded0ba95919493334ff5b03ef65a657796d48f7632baa178fbe4c9919e3b7aed9e7519b86b5955799b28fcfa8a9e8dc77d942108c890099ccff5112bf9ecc9a71f697a5aafbe14b277a937089b7beea9f37323d648d628c94caa15b1db285c3532d6473898d309f4f8eecd4020e8de51947d8e174a199e42a742f1eb8a3e8caa2c2a1053c0d735b77febadb7b213a591807381544a0a1c4782572e7445a49176e8690882a8a47d0284d197e76c7cd5186b8d4c7d792cc241d4173d3d826f1e78b2f9a12d6b153ea3dba17470615300928c8d3428af3b318850b6195e5e801feb133f87120aa3013e01d53a9b344d131237788faa2f68aa7a4fe98cb4f8842919d3605e53e18b6dd8c16454fbd9cf7eb6fcd33ffdb3379062fba2d1ec96b6b7ef08ef0dfb0c7dd11f3df8fe44a69a04ca974e849184cacfbad565260264d22879cb8063635d1798ca71048ae7a57bda85c3041a657790e0513ff09f37b5981168dacd6317a67e6c34d199b1ae635d851fa0895fd425aa83c53f5d7794e5068ca0c6e32c29b3a80b175ff0d77ae3674a247e5514faa95cb07f0e6dcb0c339168521ae92476c4ffac66160f350b91c1e60fdb2ab126d964a354eb89eec8d6cdd7d1c89c3e71f10965194225a7521e1ce45b9558bb64fd140233c5d2721ad016987884e240d640ac57684034b2368fc27de7edb797bff9e15fbbb77683920fed181bd815544ed5e884e588d415b1296015a05fe1d7e602af493085e224c8c1b343f7f0dffdee5f6a24ad673de2c1daa5d3d183caa7b3883a9dc1f3322a0276db633615efe1be80e2f4c0326e081267084df3fbddf84418f60dd33407abcf4649783153a0217c5deba93c4fbaa2d1885761f44b282a6c2a3cfec928b8e351e825ed803102f11c8b86846f9986b1dd4ec74243206c1d65edc47494b709d00fdf629b7fc8efd61dc9600a180be0e535a21ada2d358ab687635f8c98ac9fd8ea670a070f463bde3d332f8d54376fdef0ee2af7dbf23d8d93e36ef0c135f047169d365e347f6b5ae5ab3878f806bfb05460c641a7853f587b737747a0a92d332d7874e314b5e9ed5adcdb3e2f198dd7728d221cea917175e3f7d0ce4a1eebb32dcd6a60e8cd97b8489c56c6cdafd85b17f8305b6a45d63559634d77f060826398c61defe7dd25767534c0380f1bc6f706dab28981a3565fb148a67172b29dc39fea76ad14d3001e003f53e3c50064d2dbf9f4b0e8bab7b441ca031f1dd848a14766dd9107d69fe7171f99db6b5dc57a85efb960cac434f3adb7def274d56b06f1a1e252808c243936c66f631dfa199a473216ed79a45546c516b9bcd231cb3e278a89fac4bf440a0f9d4d434e2a13330276087b1ac554ea1b6fbdbdfce8dffd3b3fabe26bb07b14c7ee54d21436158be9345f0540e5768591601a0295fac953358445fe2d5f62271d1b0d0f991d7884c28ba8cf9eade7186900940f53f4f7f8b21ca687251fdeec0e33b3a133e3b40a87963fd51703f1b88293241c18e61d31cacfe68b969196ef2c697dda169ce5cd0cca589d1e0fe4e938783b9a5de2d7b491734d23f0558dde3c7aa153e42135e5859ecdc7f68b3b777ca57f07a7ab701a27399d9fbbfd4bf9b8c844ac29a37f285075e340ebdf63f60808f492ca6e398a043eae95b602c185e95893ad7852948410cd4cc6e3201c0b2d9583ad766b1489216d7920f93ff4c9443128347f17806f5ef28150f146063d22bd6f8efb3cf0034e8f56ac4b6020230f84c3c8c74e17dbcb34aa1bd7f53b5a7a9ec1348646c543611a1feb842e50d626ec62ddd7e8c64154f4f008a2ae26a7c9397570d11b1e8842cbc7aa24f7ef6bad207e4c3113a4ab22e8efc66e7b0218dd88f44d2153d0ac33695afab3f150664ac5b3c0dfe931c6effff07bebce6e2615e8473ffa7b8f608c2496018568998621bc79efee69ab5d23df353504462e2a1d2320a73fd82ddcf5668435b582545c7a7ed685c88e6f3807cb97ebdcf5684443b49eb290f38b8c7cac216958e0d3b1ddd0e8f4fffdd33f7914e2351e660994093ea70c590777fd62064043c0379407eb337810b0c3a399ea141d20fee73926bba33c0ee017625852d011f30641463fec49c09dc8e39e907ab4a612737e91b53f1b67be9b0d97c6953f7dc44abe7ca2c3da877a306fbb8c504257e1e5b795237e7433103fd0c6741125ece4be5b262ed29f9cc5f328d6614c0f78ed250ecb346536c6a2c498ca6549cab45a186010d3343dd35245628ecda29866cd733876cc383b78f88a8e1ef17c0c1239f3fefd877aaef6813f376ee881b57a501a243d312320c1052add1183be716a0a134b1931c0b7de7ef6c6d485699a762cf57c899e183c7cc08840e5f3ee95d64ae935d3702909a1e015cbc5996d17d36a0295eea9461336153ce55125071f3ad6b3740a3ff9e94f7d5e11627a7aa6b41c2246f6c58b782c05e5ca68be9c4ad1482bde4f8403cd5bdf78cb8f2f38a5410363c795a9251b2909f0c908c1ba8ec6eb464667a03f7cce48c85a8db55f074650463eca298d0cd71c7be302dd79ce882dd487ac37b341e6ed6dca00a708812364945f2a5d960aacff98ead2a8d8a964adc8cf26b13ea6a3a143a05ce04d400e65967bfc62ef4b06233a01bfcc21560bcb8533e73c1f4fbd6fb828511def70c44aeb607ecbecd0cb1a97a0115de428f45c50198f3541f870fd92e9a2d90c16f4caf426191534aaa891611830374c3453c8356456498ab42ae8443e9586c2e3dc1b5bccf484140ecf7a986ab0dbc40edfee5e5e65809e2d6b16fa3fff5fffcb8d124e2d8b8a9d9304e924ec70324b3037a624341876d4a8f8bcd345a0203916c4763b8f13faf4015bd67775629dd18cd3ea527904ec70900ceb20003e78a60ac5712a64707099ef566403e01b6fbdb57ce7db7fe1ca030195d223399b307e0ea78aaacac03a97868d0f5eb946e79046e54e4d0d0bdee4f38229df2bcf691536427ef083ef7b3dc76846796038a3c61af20c8b46c463107e50041ff1de145341bec99786c43aa7cb934ace0f40f0aa098d0afbecc7aacc1cefca668bca017ee42bcf75a170910fce194d7b792cc0e38cd77504ed158d563c3467f4e23b4e58a35b77f91f8f5a9678f81d32411475a031a4b3cbccc7cb05f98cfc9e3eae8d0aa22aa0a2b76e40abe1a5715176cd7f5419ebb0a58db23dad55bd95ff402fec82c8ffc077e91b170ef087a74c96e4926dd6b5bbd802232e48e605ae8da3d760872f67189f3ecb14a17b6ec4d939ba87179cc401a128a63fe0702645e3622b9ab9372357f23897a6b78a359ad19b337d21e058a62d2c80596c53706ef0e2d70582284d48a65e04c54d2e9cf4749c558437a3005be504e4d273f24531ec3e3e7aa4675462d6159e9d3ee28ce2fe3a0398997564b70c9eff7caa93dbbcfaf29bdfe8fb15e9283442b37e6086c42fa4f8a1a9e8b18143b879de946750da9b70c5e7951d4e32dcd128422562b466778e87b3eccedd549c864b6f49a36126b0fd1dbdc9cdc825477b5a29bbf0b74702fb9e22d7e6873a367e4a960ae90d27e9820c1a2e1fe269a41ae1d5f839dfc914930605df74b46b45c77febba5912e44bd654941ba3120d89a91f3bc6d7b4be62ed4b1e233b75a9cb1c97a22f9daca20ee4019f031d03b6d32170fa848e8cef18e124cf5ffdd55f2d6fe819648f6ca6a59c261eabbc09380b203e6571c46a5be57456ebc1fdeb3a30ad511f84e6d30a9a440207dc1524952f0d4fd34522274320384ee6cb60be909206c58980fd637e88225f5b1626ae752e48f8343f78200a07b63020c098aff37c8a1d4694a3e745168d8b91209520238dd05d389ca9a3e79b17eee4115a0f7a55e4255443202d1bd19946c68896354fe828242a0595965184ed7176e4f245a3f74c4743c4122969fd89b7535b1ad3d62f348dfd54cfad08d8c8ae1a274898aab27b482f47a7c1c602273c68ec542e64339af25afdcd9b377cecccaff188df6dedfeb961798a97d181911b9eac89eec8a62baac49c25540f64bda4a6d5f5a6021540597454d8cd5410ff6208b6b2c6e58815d376f2088cace844a3a4f3d9df67e19fd1c53305c569c4e0bf709e470cfa4e0ee1f20b31fc842da3624f37c1e1f9273a500e08a6bc88765d1110ef8e30c31b4807fcdb77dff534fbe38f3e591eaa736656f29a46737edd930f5ff494baa0e50202ec884d39945bf31f6568bd22895d41374ef993b7a5d900d956bd3f927f9adf4c87abb1690ecd1f18f1b1269b91a8f0588de1907b4d4623532f04a0cf31364d988299ca47ba15b1781b4b05809ceddb1c2ea52079b7ea48eb112a0467ec1ef1b5d56a6ce141c9e498128b7636217a8d001cfd3a4407a5cab1c0e3d060d053731cc7e712d533bbd7969ddcd992bea8cf75a646f5c70e1d1b2c547064c3d76ea902897db6ce0d87af2960b380a92053476fae88d723c944671ee8d278e924d87040263d302f80b2e0a762d2d373aef39ffff9ff972e37cd1739d974ea062e7b6438959d9196d1ce5335f9111b197558333102715a068ff3c7260f159f1185a9b0b0ac23f6ddd1e849637379d86f9c63d48ea41a3e1d0c6f95d3d1f2109d87da79eea68aad29208f1ab09b236e79d8ae236d1ea9dcaa50d58d5967a3148b368e482702feb62f476126829b5394c1c35efcf8d1071f79b387ce8a0ffa6787b4bf5a0e190a27ea01a00ea9172da7e4d3d10f9dd4d18a9eadfc3d751ebb9a711deef376836842d6b7b08c8a8ebb1ea2bb3aa1ce1c8dac058fca5a0251d60255193d1717315facc3c81047c0ac2437db3270861a24809d5a8545e5a5400efd3d1b1486463355401c8753d504ac05d322d64e8c7e37b76eba01324573434466d9634d66a164d1852a037e8c922cf41975b0057c8f2c7222bdfc9e7439d0c684262f9eee711c8946492502cfa16c4bc206f9d40a0d84293095f9c9137d1fa1fc85efb087d30d1cd162a4bcaef389ecd23133f8fef7bfbfbcfdf63734e561b19fe74e7ca7871f04ebfb46a8d4dec491306ca531c52fd2fb585fe8233ff1c098af02672794d7766eded0035f75526fbff3cef2ddef7ed78d848d033f8b5385619afab1bedeceb307351c7664d9a9e5db8269e8e88c631881ae6aaaf4f5af7d43bee7d75618a5b4ae7afd356fefb3817156a31c6737d19d697c87e89aeecaecd43ba5acf038f1dcc177c5961bbbfe59b870069e68c161ed86ee8cf8747e70b07f35c25dd763048e9b7527e1cc13f520e8a68a2e558ec8756016808f83e8fb961ad919f9e08ca6becfeede52e7d587acc12a3a62156d1bcd258c94396d7c8010fe9bc369e052404ea462b0a660986634a3d0e55dd3c529151db7e2a9742b001e159635168dec8c46471ea812e0e7cd0f35061a31533911a621a8c7071f5a7aec8418eb82428d889bcc0716209593a922bd211b1b340abc43e5c836b29e9749dea1a746d528d5c0ee6b5a82bc9e4ab9508a672921b14c9d725ef1554d972e68514f65a071727fa45ef0bffc97ff47df13f289f8e9ed654d11bff6f537f5ed55ef882ecf7da8f4b892118e11676beb4337a8aebaeee8e4ff735a53bd7849affe6bbac9a60c53befffa5fff5fffca0ad3e167f225be6523e71d35b49eea6e6b4715fff15017417e8dc3ee39d6e607eff0e93b0ee5a3acb333c2bfa1f5d47ffacfff59d66df96be018c578258732c0abdea955047740eb027039505fa05aebc6a8cc2e0e2e9da7a842d73ff3501a7cbb19fefaa333a623649df8e1873aeea4d11a2e3c5bbdade92e9d21a761e8943aa4f2470efc57199137743241eb6305a3bb64f2e53a34b4471fbdaf1fa4a0de51dac1812c32cc203a3b3fbc5afe18c982d6c8ada8180ad37f4c17f4fc854278aa699dbf235f8ed56acdcac3304aaf0a6cf2546aaa9c54680a9d0a41c523e04cd642ac63387e43a19a9b989fd728063ebd26953eb2a4e770deea4c78911f71d8009abe0d583d3c85c1a98e178f9902062fa390a63beab95817f2b85e75de5328366698c6d2c8d08f29e30888745a27d895cfd714c08b8266278f06c393227e6ce2673ffb850fb5b2e867f38143ae1c6be20405a3aa7a1833e33511b6b819bdfba4040630cd7bfbedb796b7de62dbfeaad740c8e28de89ffffc171e9d281b1f7f53b9786dab1d49e8d4aad430f2e88486ef29a0fc80f28c2af884d10cbfa29b1bbcf01919783114ff753da0a27a34b5df712199c91743fb68d349ff9bb23b7fbbac28f245ff52149338d08202524a15a302ceb6daf674cff39cf39c9fcfbd7f6d9f73baafda628bd84e3880400156c920a808084551c3fd7ebeb172ef5d257aefcdaafdaeb57265464646464446460e4b6ce293be69e73119661a61da286f02632704b947577105ee6f94215c4c4e5b644ec8aa90623d9c3c75b28a121e0d5b1b815dbfc2f63c3ce1612b604bbef278d3b75370f697856e51ee37de968f6e646ef29ab4953069f6f82ebc9988fee193a69bcaedc7647b0406cc241eac65ae574f23a611ae092332b978de2c8958c428e8fc59cf07941a0222745e420a4374694cb4537710275ee33115eb61cc18e128036a7818dfe86dd686c26e1f997ad4f66d5dfc49ba433a572894172cc1c6d83c5356735c8a46d72be77505c3bc0d21b66e511cdcdfaedb3c1b48535f2b302656130dccfeadd4cdaa985bd3181c1a257660a8abb9a2b50adf172831b5faacc9774ae5e69b033b71aa8081ba113226aca98b96965e48fd4f9dbae7e8f39fff5cc771ed4d9241cf645c74f6ecd9e24d09a9b77a58d06bfe8c80716cf8e9e12c02e638697d421738d856224e398d1f726ef78471138ac684027a8c68a23258d3221acaa08a88ed5d639a69e2dd2669f3358b07ed33cab0b4cf9f8e69124f98281b968d71a3f9d52f7de98b1dc7daca636b0ee16befbd4c5640421be05b9802129630b8ef30628bf7bc25deeb8766090c530c514cd7862e17de7a63ea30e000693670b5b55f23aa2f53ff3efe15177e0bdeca862e0098922616b8a8998cd5dc8027f4efdcb6422d38f1ab7245627bc624bc7ae64b64d1885cc29637d1ac7a94ae76cf5609b460a66056e3322e6799a68a32efc767ab8cc46e6198177c662e7b1e7c7362d7665c8310c62b4c1163a16319df2c7cc7299045b4496f3217b312844178c0c3a14c9547c2734de6e02401435af860022b346cd0c4e89752683d8fc1458fa37719f773a63732a6c3384c4293ee48eba7f78537b868872911e6031118ebf9b4030f1c65427b3b70c8c25c0e1d8e164e1d5300c663a633169dc033116eac6a0a012efb774a183396708a376ea6388c5d5b5fc286068987a7e092c72be0f4c5557f9a46fa6454ffc24fbd6cdc44fb39ccd51ad4df677ef0b52a48caee739ffb5ccced4fd5ba185c072fc30dcfc5452b6cf787c50e9ed33687f1733f7598fca945405c8ad2ba367c7a7d8e5238ffbb38c6526f419a150607915b8c6bcadec8b1efc95606d7c53473bfbd4926ae633d90462664b4b1c676a0665eb7e0a9c43c17d256304486f813213d6f6157e4c7d4aa232544b2a2a15ec0ac3be4913b76cd7c45924d6eb06d227b35ceaa55610398d0cae75669bbcaab7d0404033a7b1163d7348ac92a9dfa308d08bd3cd245268b937d4f75ed47a376a538ea045cebd2fbad82caf68f164b98fa629c6cdd8900502684d95a4b34630918eff919a3e981403547f5c10fc1253babf3c1704e18b00827e5623da6f7049e86009b0280bfe3c6a7ed2fb7177bfc078f1ffdec173f4b1904282678ca1e8711a534bdfbd068364212eabb4e9c28f3a281809e96b459f5d0c5d31144a6a576f265d04ed607bfa1c2b4014accff895d0c599a6cede41ead0aff62f04a6fc542315430d9fe9bf4ccd644f653b6711871f2981fbcfb5426f71ffc74153e7a99b42eaca978716ee5c09fe2274e5db63bf19347c4c442abe969ddc41df21157fe4df1001b975d8a221bb80bda005d75dc9e72d9843d8077f3640bd17de1937cb9f37115130113301dad5fd4f07a88b8d2062959607b18d6f3e07ef8a6e398994fb929036f1a3e5a329ac2aa0fc43e9ff31ecdcd2da2d0ca16c596bf52eeeaf209caaa72f14f2918de7defd2385af442ae7a326e6fb8d38ad211b2717ee47c87944753b31b301a812468f03a1e81176a21ba51a846f13f654d55f31084a72124dae6f9d20beb75fef4d6b882c166a6e93d30bf5e0e1cc24699381b83eb9ef70f23516acc68c262f1ac3a633c66212550a10b02d3d8f16abef36e3f92c131b296faa8a73644b7524c7a080659c722e8e9089271d9444719c44964f5ca934f3e79f4fcf3cf770298596937fa3ffef33fd55ced17674a0b24999b5e738b2e94f3d06426cc295475b6eb599dccf739cfdf84fbabafda0d9d6d46198a54d037185586a18dfabe9cd3b43e79223bc0a35884906dccd75c4738d40b1e724d58f1c3135b642ec35b731d4887b9129f7fc7622a5e9ff9b87ec32c4206686ad5b25a4a9fa7ecc2dfb5c3c47559d5bec87527eb410004b10899ee3326114dccf9a147d0bb2976aa75983719f3b8f161dfefa0069e6545f60661764e80a46e78f7dcf9084316fca6c13f90de4e5076d73cc6a1c009a19102a2445a25caef1e41990e133f84e61058733ce6737685019e60bd1fc6366fc51941e0197e1584985e18a39eba9da60bdc483b37bb864087f5cd80e331f98cade0836e84853231a6c4507d11f8ccd0d9d66f8ecaaaf9a9a7d519bc927a3e0a8750e8c98d29ad72a0e0089bb1ac35975cf84ec2121613b5eeda2cffa20327bef40ab67959dc0ed2ab1f81674aea155750af97c2d48f6735cb4b313fcbeca9f31fafb7eac20a9477d21e719680b565822f45b8141565a99765a110121b37e1de3333b372c3d6a259749e3c104f5ee6a385ce680c5f8ac22bcbcbcc8b69234bbf08a22106fcdd6ba3a524a6a283d5a24b40ec02fe398c2fdfa494c5c92d30a92f87bfbb5b3ac389775f4ffb45e14a3b905d27c70ef0ba8177fe5de15d3c2c50ba36c6402a20c43326abd63d72445cbaf934f8e57c89628730c0101001465169697dde3545a2088e06350736639249626de49fd27bd074b744a8162cc2c5a4337027640b5640ed82b28d158ea5e18d6f78d7ac5cb8ed368b513fdef10b0785255dad5f72caa35e1d97253d41a868063041676ef14c9a709e90ba250fe6a1d569636306cc4ef3df7bdfbd47f767bda2e563c527e3990fa70736a654978ea79c601506e5885907e8b4370ac5d493096845bd01bdf92147cf7188bc9eb9bbc77ff08330b853929de59f3322834e7ba98de683e39ee670f06b53e4a5965934733f38e5901e66a53162ca034fc0b068ce5bca4c9476d58150ce0a1ae3d5715e60f4cb31ad31be33fb396f5ec9dc20017d2de342b4b25e12ee977a049e52465951e075a814757f4621103096049c5920a61b5e7bcdf86826a83bde0c6cb83ef2c8235dce557e6a33ad9a2a67ea3a77f3575d4a9b83772c15b990ac77a99c03766e081f9dfbcd99e2122278b90b8b2e0b1600044cfc1542b6cb919b16de6448b0cfc0ddccc328969b9d0601b08522ca96379784113169a7913d6ba4794b683520e6211076d5229c8f9933d3349231444f8d0aace5a0e0fcd00378079486e17eff40bc744c2dcc6c6577852a0245788c5db8c599bbf55cc1ae541c642c9c25ec9da35af826cd720a703a94f9da28d6ed5da8fbfc7b8f7defe8d9a79fade70b83d5fc0d9cbb4fde1d6199359255263175f564d3a834619c1f11108cfd46be30f3d69f6ea989a4775b2b30befad5af76113006b71f0e5d9ef8e9cfdacb1b9fe9458fe7376da53ee8da3f6eda264d937a1bffc28d47574fce2c3bf3eb33a95f164c87b9092b989658dd75d75ded9dc0ba2ef4e2d1b379f4cc99a48f296d88a0ad98d126ec3ff4c10ff7594ff546056014ce6fb29e93f2e1151cdc369a87e4f5566707381e18fe304e0cff444059112b503ca67b3e74e338c9ec10d7b6a74f9f3efab77ffbbf6b66ea4de17ac71d77a6cde750a5e3d746c1a185e6ddc0a113e69b161f5c7642b115a8e879efc6dde439166beb7aca277ce4f3ceab424ddf247b619db881a2ec0ad99505a9781224a5ea6ff815859a473be74734721a1dd111a58ee35ce56da32b690b6ea748ef440e42d259be73f3cd364ec6048cd03231682d7bbf0cf235fe98a33341eae0945bd32b313bac94b0994f4fe5645aee6a4cc499c2b4b44a8099a52161d3f1639109f9174e1b2d1c2d66d5b9033897392a0d5ceaa088d05300e1ecd601ee180d13319bf484cc421a5c6fc0ed6ffc3861beb8e96b9a4c418c5c633475a52c7ef18b5f76fca4a7a0ed31bb0383fef11fffe1e8c4271d473d6bf22cc9523fcb9aecfa86836035bd3624cc33fe51d7d02bcf7af0fb1f78e0e8ee7c6082ab9fa0518c4e1bb612e5cdd0bc4c9dbae95dba8e31f5620236a40cfbba46f9e8ad2ed624a3b898d82fe6bc0ec2e7401c38d9dfa6a7610108685fbc36654068e0ed4729a9025cfdb8eba565f23105f5e6cce65b0956dad7589449af4d9e78e2898ce1ced6d4d48359aa472973de505e63da1785963577f35759ef1756fc7c1526699a2c08c29d191a21bb364afcbd2817028cfa83bf9b3d44710df2e7a142b61a4bd9bb4cde3767f5415f785e441b22f98672e6ca8ab47792ad12e679fa2e49e42ed6dbdf49cbd1402068abcb6f4d6a95a5fdde78c3c6c99c8e950a42056cbd1e17eea953a7620ae6bcc234841e88e3423abd1e65d0a17de018f8d784e9c07094002d0a4b355bef357019232812eab57ef2724c1abd1833c7140053197e7a4fe55a6d7ec38de6a83053ce4189d26146713fd3fecbfc910fa31210828508cc205bfa7ff8c71f1647e5f334c25f3e6b13f534eaa40e890af3e96d3fdc670dd9ba86c1d051a09404b42264cafccc673e7374fffdf755796072eef15b43476361f5521f3f3d82f18ef11365304d19218bc9ca93f8e978f54cba9b9fd263d8dce9fcccc7bef7bd0a1833d84f2ba3a11e92d004f45686de6aeed14bf00c579646cde2bb3ed11387294cb462997847f1b1424c23516e567758c676eeed7cf236de6d43046d669ca71e1d57aa400bcc25ffca9b9e13dcabf32ee4793877c5cdf33604b7ce6c4e178e2bdf37dc7a586ff24f9e40b90ad67af63ede45054a33485c5170c9b5104aea24c4a09c086cde7eeda5deaf6d2d2040098591fb85ee2aa32f9b60eeac15a385b8b7798a2c8f9109f39927d323b862e61228d92c447d284c23b411d9f1296b119056b664c840bc75226e840e3eea19663596e2b5eb5ccc669ed937e543e667cf9847627e4c5a70de49431a9cf3d8dd94f3f80435a51c300206a73d95e74a38f446172fe5409b94adfe98bba671bebb659586b470bd94759bf280a70e4ce23252944bcdc6300c27ca8439d6fb1361c4db33df85f1eeb8e3e311804fd41c7cf699d3473ffce18f536e7afff4ace8cf41609e8c02803525a00d091f85e5acc7b65770391787934d9d4c58f365c1bac5322f3ffd994f1f9dcc3a4bc2b38ed9e6ac60d6c35b5d944111b42d9a93d08f79e951fcfa85ba93026dc24b3ee8f18ffff48ffda4939e1bfe33373738484f80f5dad89f09cb7a71fcc1c0baa665a119c5615180366f2f1de216a7e0989b96db3a0f061b1e838f2485979ba6ccb3b45d209ddef5c628973f3dff6ca634324cda6049df7c037a2c9eadaef26edec511b055e622d2212244068cce9545308c6f30a91dc088cd74a39df679609b1cb9c06534c92a2157c8e73d41b1f5e34399fb21b8343962321df5209d1f51c9aa14cc1a21dfb4f65021702238135250feeba7aa41734564da9d67ae671486f92daa35587640a65ec5094f9885a9d7b14299651a577e9ad3940273aa66d346407bc4ac14b939b8bf9e2f6fae006ff33b9c200450dd11417a7bd7ea9adf1c3718017d4a0df7f9273dad0c37789933132423d80f3ffc48058bb6860fafa98501cafb5936b6befd769c51fea5173765c19ce5a4a21cb5adde8839faf18fdd7ef4d4b1a79b4e6be9cd8db128944b9fa4009974a17d68ae5c9e60f88913280e5b5cc07a353dc8e29b75a52ca5bf318a49ef6b425edd9995f3bde9e10dbdb47f1c5ac6773ab9b13e4295cc95a60976b0bb4a2702a8de7ad4a79f79a6f56e9949b8685fd3337c2a7ee81b847737457ff767f1ecc21ba59b34f0d05c6db11f57fe0d190f76b7b47160db6d701b7a4f7a80b7576e97e363819a0c0a53f02ab448246e103657166ddb0aac951f59701b4c1063c2e487de82d1129a6087590427da2f0d6ffe4b0f457bbd6b509924cc504c6d699379a2d1c4035d031497242cfc30816bf1d453457b122cfba66835939b2f6610cefbc73cc3b83c7bd254d305acfc1a90701626260d93202e067dd34ee9085acfd048fd55a5bd70342a4fe51fb26ade7812f2948f71099317732e8aeb01ea8449cf30ded1a98fbfe09532f9e38ab9ea88c87eb2db3f7e477b100da927b1678bb90657422a039ceb1d0d2d99aa83fb98dd7f8cb0eacdf448e8eacc8a71460c1d99f280a09f718d7119daa89f36849c9a05a55cc7e40be553e6b1b45d162b67acf2f4e5086bdead7662e232ff4e9d3cd9435d79748d9f29bb1ffde847dd71ad97833707966fa7b126c6e9620c1a8cf48a5466d0839b76673e5386c6e4140b3c26c070be5863899885128e1ddcbdde526dc0d653afd3de5744e521705b6e2eab880c69aef3418a2882f7e215460da5ce9f29aa3c282e61dd975b9a3029dfaf3009a711c01aa6c68cb422b02aaa279b92401a68b929bca221aa385f8575b08f32acfd4fd3196caff92f04b5fd62c642391a3cda375e6f400b778d75682ce336ab056831e3219af2e57c0cf0c5b88d392608abd5da7a49f5d1304c48bd68baf222e7e21da153f68468f36c385ce31de5280fa3a3151ae84d3838c8d7083fa1cc06d1e0de6f8045d39759f29e1964ad2185f2c7d7e7a05125c9bb680fbb20529adb98a82ec3f0d91b86cb43df317da5e3f418e70166e414f059a2577e3bdb8102a6b880e163851fb8f9d5f68eaf6463e9995f9fade2199c37a51ae8a613385fcc0fce3ac68d91e059224d9be5b18112e1f163e6d6d39c58e908a8a317fee9fff8e7d6b90a214ae19dec17b45b9a62ad621aaeeafc1c210343b9e86c61b6674a8b62c40b70e3f0783113d77a7a4316a1744bb984181c96877165b0e9fba609413c69ff7dad9217a1c4ab5f42793e57b64c63fc49927e90224ad582e173bf7d29fc64fa2a30573ee973dffc0730e3f8c0b40a990272b32bd47ddfcf4d5300826033b09d951f0851cfdd018c55f020b9e04f6520dc90972a4c80b8e569facb9763e347b3d2c4e660b885119c60ab2937f67bc65421a6f92b6388d7b2a68c6dce2ce2a1239c3c717a402510a62554537062717ee0112ae666f1dfea66acc37ce50da48dad0dbc3333fec6428e225884b564aa265b16322f26434bce947e0925f8497b5d26557353a1e44a77604c4de3a4f31ed9d001ad90461439779c9d71a9ba62bc922d2ffd2354a944e1a0f5b5397f5bafc274eb969df444e0a9df99ec24fee3fffc9f55468e379875a11ca533ef5978497c3934e99aca3028a636dedc0d0152af55ef801dbc5382b663325292a60556a018580078053db58579315e580e2b9f55b200014c66a79e47fbf152ea75adcab16671e2a66df5b0f02248f2e92dd7d0c13385c1b4b727cfe4bc769bce60b05a3ca9be2bc8b7c2badfa5f3aa8de226b9727f0d9331f3add73e9b5ded7f1a577e9379b701dae71f9a1d57e07ab90a5bd755a8c62c5eaef9e945a682bec29915f921a60a12bcc1796016fdfc5168ef95a4b05dc53c8c27ea43b1b169b7d7d250cac5f4c6631a82e0804fbb3af7ef95f452560c68008374ab1410beaeecad3e70b40953d9c6592a5081daca2608f0c0da7ad01b33ae21540e7a99b996aca80ff37025f3a2d1cc845518ba0c73607e83708cb47a3aef7db75a43eb5dcfbf7bbc6618f7f2e9d3cf45cb8ea901a7d5200beec046a6304c18af9eca281a4c8c61e599b4b90b4dd49992b369d15a3f0ae6dad439fd5bd3f9b37a812a933c13f09a755b928583f7764c8381e60eea31b532ef47436f0db8a1619ac0369c116e42d63252fe7bf9d4916f757366114230ca6bf104727219cb9e39336378752694d62b7eeb9bdf6c5beb7ddf4acfe5db04944ddb2a65e9c5b583aba1467bf5d4a96c95127ce34d2ff6bbd75ecdb4c5dde1c9f466812f2827d912f6b4e9d3446eef373ca5c9ffc933e9db364cc628b2baf2dfcee65cd09abf8cdee755dea2eb584bf36af777012e6116827dbb894a80ceca0f3300717ef4609d0cf2b706916a52e666d547c4e01d8d00fb2d516e8dc53a9e08b30e15f232e5129ce79f7f2e638c59d06b2586710a172da12334ed01028f368493b02ad722304ecc2c717e14c1cccdcddc0f0f951dbf16bad689d11e95776b1b9f85b18b2e3af4ff5e3054adcbb1e298b0fccb78c6a4fa4d5922f5e1aca2274cfff99f599d11f3c5d2248cab775ea7620d71f6f0e0bb6854ad9cfa77de298a86f0ebcd696a2e763da5f93b4ae7a59c0d68ccd98f30641de38c61e3440aced00694d221a4a503592de572c97b6509ea0cff9613c74c9d1ea97c99ab29e64f4116d1e4174d06890000400049444154cb7b4bd1f4f868dbb2a3d49443b88d878db36eca7ce8a5f48670b839cad4e65667b6cc317d5180e9e938a4eceeae7778c3493123547aee9870f911640fa31406a77a2207b12ebde288a1e0ac98d14e0df20e41e63138c273c5b917f0d4619cd85d36ca360ae2ba8fde76f4ee1fb2c4ca50493e65a77e122e3860c9375ce9e920ac4403780a6ed11b521acc9c05a2824f83fbf10eed9051ea86f4609002e0b0e292af9549b4f2d8cf5cf51adae05c250d702d06e55ac798abd1a5d75851c80d83e11006c32c02691c381a372c9735d734f7aeedf4263a99584e9562eeb5a1da24d39382339a7970f46a1a405d525efef19611d0871e7ea86618f87a42cbb98c7d9cd7613cc4755ebc8313bc90017ce69878f7150af8c754568f887abdac18effa9ce6eb1838f340b6dbff3673357f88c2b1428479b5ea0cdef4787be1d15ef04ea22158ed9ea90bd3127dea6dfcb863c173406a049a60e8bd4b83e219081b9ec51da49c59e29fdedcbc166123f8de6b6b02c432b1de108dc58161f50ada2b774d971c5a19605298d2fa411dee68f25e7aee199a8479335ed6bedeab9ada49caa1c2f1c4d476a666e9218197603665ee13441f8645c75ddce17bf9b51f577ec6a1ef9c79bec31769bd92d40fcaab08f7ef2b644992c2472257a1931182d365fbb287f31d30a681bec5b18850330400a115532262297730dee1b0dd20a489e4e579a3a58b70f22fa1611acc51cf2a314cb918a03d5a608967fa31ddc06293dfeefbc4617ae7bd8b33012b8dc601b38d08558c1d6dbaab2fc51978cb1ce9fb9898505ee3b9dc168e2fb860528da967b3d2446dad4c306663ae5e73ddbe27916f95b3eae0aabce965e7f42a3d2c8d4f81fdef7ffbd73a73f462c69bc64ee839ca667addc222a4e89e00deaa9fe78975979087635194565238d0e781071f084366c955a6192c9b335e0b84ad0953ebc06c7eb0ab18bc1e7e80b3dddceafad69bd9719d78383856c2d8ea70ea43d1d76597b13199f6f87dc65d90299edab0146e546130eb032e29268db635ae45efdb6fbfa3c2fb4acc64738fe61b25667a5ade652ae2e29d99c34d1eb8830d4e174668c83f0bada1aab6ccf5bab1514cf080de35a1d1f5b7d9c8996f98e56889ddc958c9d832e4df32cbfb17856c35cebaee1a0e9894a671312ad3825bd5e0bcdada1985ba4d21e95a98c7decc7361e5596504afccbbb55709018db39696ac16ca7b79d64f7ada59f93c7f266bbbbe2ebd93f58a8e09b30487d7cf9ac6f6521d876c5d5ff20beda57a87f81bae9e5300e15e67edf36e71dfeb49686dde310ca5e9d591d97363b4f261a06d3111334aaf4fbb0a0bf7454fcac53c97896a9ec113274e74317015421a930279f2c9a7ca30a61f083d278e73edb543db2768c01773b92ed3505a6195252d927b46095734b44ccdb7d330ef6038ca2d505bc5d226790fe1edbdbab3c89959eb94ac45bb001f2744c664afc7dc35ed30c72c84e9a2a0d77ac8b3d64f36df28b954b834a5986abe863ecc4b4bac289c599ff8f13a65584e94ce4f7efce32e40ae900501b899efb36cecdd7befad73463ba97b5bb9fc39b451c1d230ef0424f393187dfcf3ec7ed1cf37cc8ea7873c1e3f820376427409e6bdeb402a10f77f51c89a6efba38015140a553d5617dca637e84468048de76f2d1e2e6249296b91de8a2eac89582093285a3542c3646442b48ec938c54e8912d1aed6d1196873575bb6c43c73ee2066be254c6d2e88b6ab4916488b50aeab5744acc39f72f454bb92726f8bc90b31d19e3d7dba269af114f7f6830f3e909509ff145c9d7d514c4b11c8b6acd67794d0cd3767b1b209d8f49c04d678c264beb1abd516d613f6f0cf30797b02a66bf067a615bf5008e3a82ff3f3f8f1df0e15536eb1a508f2db959b02ec59935fefa7b7832158d234b82692d98e1e1c0b2c873a8df28a59de7ac9988473daf2a4a548cdaff1ea724c98507eedd57140fd3ecbb19887e8b082323b399cde8c3b9e407bdbb60e6d66b57ff82730919275643c6bb994f624fca60798df7a2e4ad83408611d6756f6ea4508592a04cebebacef3a4100b0f8c07f5a2f2ed837642933d9edec175da7352a22f9ca42d7d9b287f44a6fce36993eb23f8e7ce66ec9b3612b40ab20dac46357f85ecea02bc5e71ae13a661dbf829681c0ddbb82cdb5e7ab04e842c399bfc706e6d22f237152bf2c6045bba416d084f80e6c0538dcddd1b410ec374cc1053e6f610d301a0263679db30b19e401a6979bac0f71f03b586ad7631281135b05e86b673141d1351cf80015c67ac95464aaff5e4534f1d3df6d8f7cbb0b4b872baf223efc018f7f6c0f6b70d0f87e0be045a2fca9970ee282bd1b7a456b69f8c80fddd37be51065136bc862aa941680eaf157ae270cc136732c2bb09830f2b42fd3111a1a5e56ffbd8ad9d57fad9133f8f0975a6b0569b81b718491929a54ea42bd72bc22221c812bc73e738586ce599a3e79c943cf350b3599407142de0df316df0caff968b9684f2f77110581860c5cbf0d3ac5524409410c17140ce898c03d5c17899d2e185648a52e69413dc8bb7f68b3280a35e1b7dac61f501929e6f1902719a71f9133279e0971bff8b5fabe82169bd5f74117f18e6ddca93f4cd92ba86571d7c1ac0cd3f40a70527ff94e57eb733fa10b0fb55e8aad808fe3031502a47a318f8139ed59b4d25201e2641ed72c4dcae7745748b579640487c5e96b964a06c6c634cc5443010afa68f5985d1872935668d9e809b0acdbcd846b094ddf1618842db3b93c2a4b9e54add3c9806e081b239d4d1670f3cf8601b75a185799984cac36c4c17389a46e09ae636bfe14613c4b04f7d2358ef651b3da6aaf64e1abb7d7ff5ab5fc594b604671a93f2b0d0d8b80abceb8fcf968c2594d3a8c350684f7130ad7c46e883d1f2d6d0e9d5687667cbcf87016fada0f1a4314dbb223e67e613327095532c17bd3444fea70fac40eaa579025905abdde5636e7dffb1c78e9ef8d9133157e763f005b4fda154cabcd98331d769ed31c35340f067cebd16e6b70a85308853366be3de7bef495dfe47ef294d4a96f9cf62b962bc1c7c177da61e534e9d3cd9406cd9d95d99c37c31eb39293567b57c24633edb8d2868a174cd75d5af91dbb377c2ba4ae3d7672cdcb009cdf67c2cb4be21cbd2b8f22fc6db5d0881a37abbbcc907467bb2ab0bdea01e5c9a539689cb23cdce03d4ae3b8c80e9fc981b7a3964907af7b71549fc605338036dba58111f8e8dfbf9cf7f3e1f687820664f5652d064196ff52cfa30175d0f69023cd719e71458fea8a012eba94a4f70eebdac028926ebf2a4cc591964bffa6a26acb3ecc62436c6a245bff4a52f1f9dc8716ebc63851038848c070c431b9369e4f3f999fbc2944e18eeee67550a4ee72248cf677bfe2f7ef18b9a51c621160afb7846f12a83077ad27214d1b2af6729d6adb77eac42a00d686b013c13b7e793f7dd3420e78195f05fffbbe9f96cfdc7b0f0a58ccae0c99f6ca5aff57f7a36c205ef46862e8b3e9e95874ec6717051af4b974eec041228ede92cfeb367ce365e7b2b4b0f1a70109d3606788017f7d5a2ae141ca5e4e42c6b0de104b6b93a5fd4f9487e848a15012425346d3bed3c05e5c5164aa7a495ce226f9ed5b7de7ab3dee3af7dfdabdd6bc621c2b132f4a1080ff00c1cf0377417d82d6ef84ba43482746e6137bc3c91d744f9fa1ae70d71a89d8fe91c8474dcf5e2ab083ca79c58491efe5258899a64ca94bff67c58a204af99158259f9cd01d21eac7824e5aacc86f02a6710f00428d407b85ea36bf2c290b4946a556092bf9bf994aec2b9ec7ab10daf29621e78b5ecc47d26e6553fa31a17b81ecb1e3513dc4cae324b1adca4baf117b30243aec6c60c56667006d0cc8e131fcfd64dedd5f45c838c7a641a23f357bf79f1ecd18f7efca308c59bc3f841565da72e535bde32aba32cdcb5621c2e68b8d614621cb81264630a13ef04c0b4c397bffce5a34fc6cc647e0edcc00c1aa3e5f54ba8c9c3bafff4912f8e8af55b8c83414bc370853c98d44e6b02871e2c02385bb8cd9260e629af02b2d51b8c66a6504bfced39f07641bbe5a73e94db7df7ddd7ba8ee0c36994b5f6afd9de98a9c5e09bbf93ac6ffc31d6b4bcca98d03446a732422b0afaeb5ffb5a77451bf3c2150c65b55df304325c87fffa2466f7bcf8bd91bb3fda6ff7909be4cfdf4b1403c7daed77c6957f26e34a13d31b2f07b4325638707cacc82b0b5f09177003e67159ce1804835c9be54517622ad1da1a28b2d2300521f47ade43eb5de21178874f505879f613b6ccde11b81227193b9eb2bc2a422d0e5395a8aa109806e8cf3df7dcd1771f7db4939bcd178672d5cb12664c042ed43099dee282150e8125b21eb0d8f9274f9eac93e3a3d9a2624b096dcced6d1cb4eaa52e607ef0e6d92cca913027781907a441921013953193d66e6638be1e67c1ab611482674780f92fce047383d6e5a99f86d7835cc8f8d1722827010b9e539d5da8d2d910d226988e4b9e1b7b05b0fc84454bf71c3a1d97a55730870569c27a73bca67a4d6633674743b37b3fa6bafa6943419e868332c431a1adce51e7b56d67399ae0a11e698ee6dfc0eff01c80292330098b0fd1fffbb7be7df4cb5ffeb21646346015e3a953a72a80044cbbced061f2c95bcc5ad6289be189c15b19eb79d1675dc5af2075e99cab2a5e13ba7ce4a18773b46016383ffdd4d1b9b4e1a50c05f650a7fcb87bd410187ff6af8bd856c02a704bd82c2843eb4f8f30e7005a5e451b1681c21c011b44c11ef8de2f584de6695526efbcae60a17c0281736ab1b11a8160fe751c110d69c9cebdf7de9b314ad6d9f91738ecf27a1963f275301e3c2b5470deca191c023bcc6f42d4c4256f5c8f0c0852f29963fb877ff887e0e37cc3b8e9d3ab753a2078a149e93255eae0dcb8c258c0042c73cf207c7af6e0948953bd22f3d3aa75fbc0d4f13ffee3d1ee8ac684be06ba9bb4ce3be623e501673dad49698c7afcf8ac7d4c92d27a590f53274a6b56c7df6aefda0b2f144f30bcdfd51f61534f34536fcbd74c53d8552e8d1f939907f7b65b6feb98cac734082f67c5f1d0472f629549e79dc03b08c525f0d1ce989690ead1ac5b546f615d8796636e15a9bc1b7c87b87bfe4bdd0298d50096786667566a759cfdea6baf6627f9dd9deb031f0c650cfcc01a0278d1386956d8973131f25e1d7638c9efb51ef3c45df16ae7dc9998a8affff427476f9efdf5d18578365b66eaee3faf45526f080cd5fb7c58c80eb8740916f0caa53169599a9346391f4db85f91df144983cc1a3ecfcd8e395b4f759dfb40937a88211947ca4cb8d6d1106dfc4a4ca7975e7ca90d6b0e443cdcefbfff81f62c1fc9225d837eb0396330b31f3308dc96bf95978c53160879772ee625f7af321b8adfac643079adf7f36f116e7a248926b97e564fa63ce78bb4e7494f7b43265db9df4d2adf91dfade901997db7fc4d3e031506561ffba14c1380094742b5d6167a1e1a6d5b564207f5c6fccc4bf8328d293bbdcd2c969d3c9c203d5035d307e7b319d3ee61411d044fd32699348f629cb1e6efea48c198e3c8b2ede89aae01fc54ccd43a59520782c2e3c829f2bb8c6d09f9e0ba683405284bef63186172d8b2a92e0f6392a64c738794b23aabd38ce5177683dfaeed120d2f736668793a8c6db5cbd48a12cac737b22d8812ea62ea940d36ab070ceda31cb56f1e84bd2a1c96a53e9e57dc7a3ecc723974bf782c70a3346fcc14c2c7320ebef999a78ffe1867d79fd2e35e082e61304236443fcc7c78bf0a11b70a829f7b95d6b0dd869202b94dadfc0073a5cddd8e105396cc5ba9a9c4d475044f1998c7eaeb67c27ccfc79140b8ec1d32969a31df9049d9566ebcf527878f66a57e7abbb067cbe69932b96b02d318a313c1c996e25af6dc286d88c80ddde3bbd35b587ea5b14be0269f41b63cfef9af6ebcab7017a737c4f0e2cdd97cf6b38fa4678d7919afa5c971eee5aea488425abd3ff8cc34837f6e6ace900a73600a4cc8f465bdb7b7ccfeb757f231ba679f7da60c632b0fb312a39dbaf79e2387eedc9a1e470062962e7db4a62e8feaeaede02810ea751aaeba3045ed6478f196dfb4d7ec7830a6abf1cf5d397ae02b7ffb95ce4da23be56a02fdf6f4c67a6793f41181c2552f82854eea207094587ffa62d658fa18879e88e79269fc56cea13c79f2e4d117bff8850a6f331cfc597c34b57220ee4d1d279aacefbc5cab83f7cef7a83963718e2a7461eda813e5fbd0430f754e12fe6db883320e6ff7e54deca2d7a11c1ca6c7df26aeb8f48f9dbce7e8c3b17e6ebafb547ab51f1fbd11813b1fcb6b1b93edb82fc9dd4f3804bc0a9fabf734269366e6ca206f4cb60ee36c5be68f9e22ffdbf01bd8d6b1714db4628761090b66fac10f1ecf8af5e7b7725ca2e55346cd3f70f3c328cc867af1cec7d44bcf519cd3e0e6d034845e96465b66b1f70b21ed83f9b8d3791a9964349f2d2c0d8b144958e1085c51355fd3a8162a338378e7782ef506cafd2fffe59fbb7a03a32f73153c650f7323c8f43e7a379e4393b5e2fa6be111747952478122206067cf9e2d43316f99968417e3c2fda31fdd945bd2738e70fdeb5d8def84c5305cefd766658ebc940a93d047f428b8fff5bffe674d4cab6e9891eaf3d1bfc9e2df2808bd410528ce7fd6824963c20617342e6d83b5723a46cdb8157c6343633b79ffed5fffb5e6315a514c5dc09b6a738ad8f1501cb5d101c788d356eb3466e7aa3059fb6d0154cacbf3190b9d397be6e8dd6f7e33e98e3a16d57bf23ece0aa0f90ace0d4eb12a7cf0405d014fadfb692b4fd21ea65b79d775bdab0f37cae4729c21c7efbdefe8b6e0f7c14f7eeae88df46a51ad5b8514e0b66334377f3d48ae8036581a802bff7c5ce67a1b8252e96d6506fe20bbc1ad413bf75b7d7785d1aa5648e81d3b3d4090d3b3ec423280b57eb6781010e3c10c9c9a0c5ecc8ff94cedf5ed315ab941a58d22e12214c1622e2e0f6374ef41711c161c0df647cd598234a46d252f65e1328d4fd09ca561d2f4739ffb5cb566e767823621b9946d1f5bcd03771404a1ed71041110c2e87835013d56c3153f75f52263bc3f65c363d77536224a27e531813a568db2510f4a457e160625631a8287152c8ad08ffbfffefbef3fbaefde7b6b6a1344f9ec78f8d9cf9ea8892d5d57f4e4da79c58c83fbad803cc3117c42d1e56597a7b7d0cbd81e7447be61663737cf2401e3a505dff8f0173fff457b972aade02e5094cccf8f5fb0f42a63d0c4a943feb72cb8ab1b256073ae5e5d7e4abebd71e97ca9cbb7fef0bb3f8c22481cbe3176340d62b78236ec27865beafc017be85da2964ede882bfd73bfd2ace7f5def53068a9a8e8a34b4cd38cd16e8a12bafe8eec41dc0d5ad56c17f60fab00af0e0bde278da0e9cd9842f957e70707c275647b63a83db8836c89542f42eebf964b14d3c29a430d5a1b3a1c8e98f9d3bcfe620042c4c3e79833eb149985420996441d2365bb3b66d7d3147853ac3f200d4c6bee086a7bc4280966e8120b63879773e2edd3bf7abafb9d98afdcc73c663433b4e0e77a297356c68b568598ff3281ad88f53e950ca3a4e74c19e38ace26c408a8dead00603428ed1a78618b46eae63cfedceda2792eebd28ee0df1d45839905b4233c3ca1bcaf4c79e5cb8a26f65a3d14cfd8acb0e18dbb5881b00ae3c5175fee3326a5343937d0f0ce78573b4716f8e80bb6f346eebc332b352c668ee7d5fc9c76f11e5fe8c9b51793da322a82675b4e5766a43ea647c61ad9be8f16f58cae172fce872699d414908d98b60cbd1873d08734ace384337aa956e993abdb6bb5471c22f067fd1873331bc1d2332fea1df276b2edc2a130892c4f6d6f970cac3869179cf269d2e19d0b41ec6294ceb11327324fd6cc1ba67d80e6845581ab0b2d961ba60848c0ea758b2c707c8cf36321b78707ea20e46603b011664a1c01b28691978b2665ca0921dbd17537641e2d668d5521c63ab7a6d18c7d301347028aafca6b608dfdc11cfe32e3914008d185364a08d127c448a35aeb665c66e0bc8ecc068b67ec6c5678fffbb7bfdd8da2e2fcd47bfd561cd8e058dd80d4b4f2f480b4ef300b973a217d2d9e300defc86a13b5350b379204fcae1ea55e85c3cb6950e5b44c377907b62908b8af150e60ac6f9da18d7794604044e08c59a6e70ba4d1fc017553bfff7c4b0594e363b5510f20cad88cc0111e6042818e012d0d1366bb905d1933361337d330701e65a45db5195e39778ec3634c62d688e3c65f7bcdf995d7b61d98b8afbe6aa7747645ff2ec726583cb0f5d6ea4e78083cfaa09dfa0a5dc4bcdd5340e8733942c6416381408fe993b884f57a7a3230ffdf833493f1afe543e7ce874a1d1e8edadb4a43b9dd7deeae2a7c3dbb4e48fa202617c2aa3053af4c959e6c9249bbba63b930a8eb04122f342a2f1664ab1808993183714100b7ac9b6ebcb966ce173248b68bd9b860ad922831036b60308de6c3793c6c9622d1a43df16ad02e7eab2ef0c0787fcc49be365ef2fe2da2eb8de0d195272118c16122cbbb7ead449ed55dafb20efa4417a6a50f36f8caa613b22c4f62961abbc209be15981206cc42db95bf2294854e43bf249a8cc5a18e9b985befbce3134ff99078ca156cb9e1747156637736040086573e4f1fe70005b5ca37be9aed2e3e86e8583c199c3fbf9dc71841b5f469052663a73ca44c523db2de65026c5116aef824e67134bbfd5d849fa365c167153cf9e493c589f774be9cf976cd60632ac38f42d0336df4575edb0ff8b6c5fe597d4a835c09207dfe762c1502ebe839ab7c501eccd586abbd0b73ab81cb61bc320fe3dc1fa6575730af4e33c70f880f261a789057fd7de24340855028f99324044bafe1045e15d38b6158f673b55e12c90fd91d1cf99a3d711a610b2b8ec9c34dac271acf9504ecef5c822707c74c9aaaa4c809f2af3bc261a5bc792b424bdbb77207e5cdf3e0c289a027639a305768d4404ffde653ae5cf32fc6e5aed1323ad8d725a9d4ad8d917a62e69ffce42747a76db24cc39ae4c5ccd62bb6b74acd57fa8e4b6a6ac27ca344c841f8fa25cab6aaf45386baeda885b1f2ac73e64060ce31536fbf5daf3165a02321c3d8bff9cd4bb22798b2f07de99c831185c084535f8caefe7aad19ef2469602be35ce611f5ba14084746eb0a527090b72109e9c215e0dca148e2618ad1f10373d21a4982543339ed773e9f107e29e3c6df9cb1d6126d0365ab033816542fc501a9a95fe8b495bff88b45c5d4a7608d9f3b21bdd58162b18976a640b2e0e02014bff686499c00fee2d5057bc5adbaafec57bf9f56596fe79a9e0cc03cf833656c170fa5d015051e664f758b90de44835acc6a6bc67b398ec011dea16a924b03bc42a602ae9eea73f1d0b0159e371d4fc4056f8ec918687033a792f9ac3001cd7af1621a5ba30ed8618c3c223ca6a389991f16ebb65192700832a5cddf621118ba775f5919278a319a86f5162ef5908531563d687ce3461a117cbb705760de9ec942d58b175ed8d1d33b0d6f93e4d063d5759f0443e805a4637a39ef449849f2991ed8e79da6a4a2aa1cc320c6937fc83c11a6c7ccaec60814956315302f3a6056bb0fe07cfaf4e92a0202ca6c75949a95278495521b864a0f9dbc1600f0089e38715760ccb869319cebc268d720c5fee029f465ed186bdb453d738304b4cc91eb6a9f194b1576c9243ea9105f2a8dd2b4782e5646da00cc6e794a1be989df493b72e0500c92cb6b1cccaaa00439841209d0c045c6a66c54f964eef6e5aee7433e3e14b0f57e776dc1f39431d946a02973abc4bcac4f227f0e01ef80ac9be4d3707a0e0cf96e5ce97a334c33c45984d9d56be0255f976815ce56f876af77348e300763107ba12b27a2b1730417a6b6e817435aceb50ae170b1620193385fd1e09ae7cfe1359c0cea7965182a68006f0cc0693b5fa824a4cc439e2b1da5c9653b023ef39987d288b7cc1eb0308a5ed496921ffce03f9b07a0d1ec331e5b8c11e82941298343e3951b02b5fcd4d76a0aa6de1d77ded179a95b6fcb81a1316fbefffdc75b8f9517acd51e2bceea92b733df44708a7b68274de4b5345cf3854cef859371a16567026f1dc788b1a9f78481b0af7298c7e69e8c69a4b92ebbbc579066a55b71ae87f1c307537ffbdd8ccb7cd3ac9f25a629872cbb9eb00e840ac1d06d6081ba125216d7749deb17bff8c5a393a74e763a81b79639f94ae60fb5fdefb21301be020716feb0b9949386223ac4bbc595b64dde3f8b562bc6f3cab3ae57a7112fae6937c1ad0b1fea25442aa15a1ab22b3544e6ff21f0be152fc85880567e8c90e14a0ccf3c1a87fae4dfa597559e5552e137aa384867f0ca1d4ce398cf39f7276bf6067142606995f10f82c28180d05adffbdef78e7efce39f54100900ed646e851220b80ad8304fb619f05f4cba1225e5be9701b21ec12a913733c8767622c632083758ffeffffdbf75f58606525fcc697e4caf6355ff223c9ca686296d2b10538895c64f7d8a57ae4ce32f7ce173d90df0a56ae435b9ff425cdea74f3f1786d91f455dc8c9238053fc53c8db11029f29a228c6635824d2db3a63de87383edac9df958fe2c1e40102ddd291a2440b78355d462e5d1e16fafaf28cb1662d83e43056e2ad539e3c3c8dcbe9d2bc81cb54418702df20fa9c94afb2589e75eeb7e792372f624910066d3674dad3cdfb890360b80668f7c683b7472939dfd1bca0784a4ecf6651c0b3cf9eaed5e3055163ee5a707dead4a90a192802fc57703bf027c6bbd5662b9d6761bd5bf12bce75cf6999ee5819bc80b8e28058052d40deaeb48771d2d78627643119514d03f24e3151d6e4b1fc0d2d40096ef6041cb41b953f73d88ab58018185606e8ecf94f6682cf5c4c192229cb22badc80b3339b170da131ca789a927b2314b1630ab66eb224be5f86615265ec62629893e3a74ffc242bea6d4eb47a3f33f681f5d96dfe8b0bb804ca1fc2461130c9982233e6824a90e97f5a3a0519908875f122a10d37b711d8a91fe6034b0f8ae9cc41d1fa1c3cd30bcde2e8e92d41313ecd9a48ded8ccc958e522acb6f3be265acc6e26a315161859bd055682ba6c8fbdaee906e51ba70266de92d2d3a6cf9d7eb64e0bbdac359a94921dccfffc4fffdcfd5b0b36f8a583bf296ef18e61059aa1b70da08ea06b5b45615e7f9dc37666bfdd4aaf2e037323562212d576e4a4e2adf579ab90a1ed2e9f096ecbd7ba1a258205014a96b385296c4c8ace0dabf2c15358e5f6e1aae7f5eeb08e2b8fb8152f9dfbd54e416dab045e688d86382ab2ca976165945ed801cc3b44aa1b3fde268cedc3808707eb4cdec9d7464d8506fe4638af102f91ca04fbfa68e013273e75f48d982e9899cd6d85c1cd190b69703d89b413b2ed9e672c1e47e3381fca2bb0829d9e23112563b7ab44901c5fd0837632b76343a45504fde2e7ebbfef6ee8a79e7aaacaa20b7bd3680e887923e7dd7fecb64c98ee34be73fcf311c32880a88b9d70a7065b93c18ea00d6dd56f85b2cac5d02ee3594a891382a38490a3176160fe705c981374b27299313de28d37a4978f023247e8f8020b996de2c458d2efc33436d7f96d118467c2c89c2aab67302d52f9cf354db80b1dcbe55b6f04573e655873a86d2d77fbe94f9f08ae3e6e1f81cd3f308deb4e5cccf7a6d356759defa0cdcde21765ebf14cc13805984080fdf1dbf39598c45bcff9e42f9fdc4c497c37f9d10b8c5a243c8679e1783de69f257778a2eff14e4cd1bf89e97dcb47fea6d329941525e3cb34f76635c6e1847457245d85ebd58f8bf75d5758f5f17c18bfdebb4ae35df793ad44892bf3972bb6bb954903af742b6e0142e835212d1d538e5b594f16efc796bc9cd67ba8563e20add08640d9c67fa210ca84e9a7b2997299a253b13d3768e432abf4614466826b1d0801e2bc11260501a4e59db188614c88dae2cefba81cf0e15f410c13181b193be0403d011c791d79c4d48d07abf320299f72b139f0fa1c6cb3e6f4864eeaa5a6658f1dedc4b61e8b08a1814139c78295237aeb4e4ba46cc2f5f1f46ebe8fcc2cfb588e6cb3d8d8a1344cc0ce0f4610b9d247f0d106030e594b9b3c531eb7a547246ccc60f8ad5ebe89932655295e70d35b3ff2d9cf66ffda979a0f4df540564dbc90ddd6cc44341667bca6ad9df181d9cd6d0a53471880ed28f5396cc9ea11bdbe7afc8ffff3ff6a5b98ae31dde02323bff8d9cfbb4994b733c00b07a906cf0dc7c45399d640826553a8a30af8022823ab4798b5be76fab5af7fad6337ab531c784461e351415bac8026c35f2b66ae57f3fc4ab3e2ffbf3cefcd459c2d68a1f7090ba8578b7ddc3779f242dc18c90a724c8319eb8a967ec1de724e4953ce14b7117357f454b82732d5cba59c611ed6751924b068c505bb6bf5a2dd694813957a2a82c4dc62ce101c660a4663fe2c4213a48edfc00fae84d259f5e672acec562e86c2e404815d8fa92a7469e03367ced4ce5fa681f4bbea22d016c4e5d5ae65e13db84f4f6050de3d6d29ebfad050e01c3879f264f1ae9995fac1ab4bce2886c098b35486360b87402ede05927bb4d1f3d1e804191250596dbada037ec52970f50ccc670b9887f611d6281e3d3e534bcf252daa31d1f528181bb34fbd62be0520a1b2accbd999c67404d532b093274f1dfdfddf3f98552477b42de04e299a52e0cd352e16c0d2db1853adfa693b3833fd79909f79fae95c67c29a43c7cfa2f0871f79a49f7cd2ee0df0058b729eca16cebcfceb7f17add675a506e6305cfd5e5b8cda49aa127a1179cbb9002ca22d600bae3cd35c438caea5a3c92e5bc378f8218a69c6bc488e957ba0a5de5b9878cf5bfdf950d250b374a638446b4aee3dc13017477bf5eb2d311169bf071e78e0e896bffdeaf454b6e76f5a584f7528949d43291cc54730c2d7cae096f7c54dbddf7266684cd306cef633896cdecb98c4cee63fc5abc7dc3b34914ab7f4cad10113b6723868d4523918c5bd60fc03f73703d742612657b7136dca82c258736a70d590ea7f215e4504d190fdeb7e11b4658a77ec5bc68e511c98d7ca0ae10a66489e81d157f5505228c6a3d67f8202be0d9126f7f5a0846c054eaef6c4317709a65e157c74b3da9e17d364f31a2f1326c2c8b174db7bb7b5e7461be51883538ea79f7b6e14b578f86d345326d89e098ba5568f3efa9d8e87093905ef3d33d43230534ada66d577eaa23605b46b8f1dddc427244bca98fb799e32578cf40373e217fcf57e770da02b8f1f58400360ddae0276990e6fa4dbd262ff31ebb20f343633ef9381f242a4d900c559b9ba9d8a89d822ddb572fbd2035eca08941515ef96a1f52a7a1413bd360edadec06c705ec457bef295a353f79caa29683804346debef6830e58e53847d0f3e1c0933f73766c7e8cb6133efe668338cf2cb5ffea23d5b57d3072673a99a75a3056ce5a912d8c60e535b53017384811effbd4e75ccca755554ae7928ae72025e02257e353e8563127ce2d567deb5474ed9686e09985e57eb8df908723469989e90e9d5afbfeef93062e63013e0b8e0376222ebeed6ab9a127016e43201f5a626e5ad153545a29ef2a31b21e385b5e44dd902c530f7f9ae41f841db0e5ec7da7eb6347164dd9895f12162be5d604ef2c33d14e757bf7aa68e2ca74a3bf2cf98fccd94f1d4530e268ab73969f994dc53b29a587dd08709ef5eef062fcaa5740a4e708608bc7327f92ef49db8b6e58a9642ca7d685e70b6b0f25dfdbcca684fb6887598680172dd85abb1ca8ba0dbd718d7f84583da2088f07e4d912483c89656ec0ec90dbe4b7efbaa8b2058b36bd7279034ba835d78c998046c6f4c4993128a4b31fd4c28f38ab567e90a9f20bdd561702458a385d6d89106b4da8189f2d2cb2fc6043cdb72967912448249ccc6e0b26875ad559fa5f3281af196ff60f6969d670d6b30cf54ed768f382aece036cf6770ff625661c8078cded037d0281073710bc745a6f6b4a1ce62163831c72d7e751a96b196f57d761318f49ff8e427a3cdc3bc0994090fe4dfa4473406ed21a0689263b6d166b58532e18e2e266ded96d686844b503673db7480b869df90213d8a5ecf7406a657c62e7d7a2c8b8e5914e7a358c0d0b2c6750b3eeb8185c08cf4d51969fffeefffae8e1c0e1da62e0febb3cf3ed33d69efbce2cba1d3aeda341528ccac0ac6406d6e6df766ac1bcaf7aebb4ca01ff814d41982887b55fd45afb0e8e2f9500e0ee357dabf740df8bd0b7f9f110926ac86f6d4fb416d7bbbd28c5618377ebc8c21066d45a3da5fa60122757b24951a380b761f874457c095064e18ff7b8f7d2f1fe2fe591bd000da8fa642383fc73eeb251673103c03dd0dc3b944006727aecfbbcec70199322ff958433f0e38ab013aa80f93a5c65b7e9731fd0c95a6398731bb2c283182d4703089cde368dec8c4f21dd9f2604cd8f160180e7db8ae7f97e98197b2da7d050cab77e66124ac8e461bda6ccc91fafa16d9a54b94d77c8554cfe14316bfc9c7d1994d6fbe319f967de4e1876bd661d6b64b70bc29781174632d822cc0bc262c664b682f932ba78e45bbcc61b494475047ce035e4c5e508a413bfad9186aa57c69bfb9c7eb49dc84cc58773986c0d2069639d95e235840d0d5f58141293999ebe4c99315d880af15e218041ee19eaf18bed2f60d417fda223826aa38055bd68123bc1fb8fffe9ae093387f014c9d5b7fd7fcf6fca8965eefe33caff7ee85f5feea7bcfd2eec336263b0430ef0f13ed93bb2bf060d73c928579f324a295d6507a158cd06d2f11b663d7ce89b8f227c35cf2778f8cb855e630f07ae7142c8375a605b2acd508c78e6d308bc310d9ba3426974962786a64c13de67d211f7e7826da9060fd3627df9a33792f732c790dfde2d3fae78ff2fd836f782e8c3f8d51801d6f8df74f4fc53b46901cb4f3899c8568f0ce394043335d9887cc4a30f514edd52284c65ec610e229085a9797eed2c593517ff90e75cc3ade328ccb818089f5e4e348f84d27ca312b8566f50c87cf0d71eabc9a55eb26ece1d46fbda51ec7339efa507a04caa74bd5542479aaa4d26668a4a7ee4032f470ee0927855e7e995b5a89a7d1ce0771757e24ee72cad53b7a466782002e987a3c5e5fe6eaec8503c54e80778f9e7efaa91c7874bad60767d2b4d9b5f916dc9d5538abfd3a7e4e3bd897e64ba7c78e3dd53a73fc944f722d1b029c32091c85685e8cf0b278aa7493cefbfea4dd42a705c427c04198a473ef79c5bb175aeedcfed9dff5aec393e0bd998bfb7480ad445703966ad83685eeb3344e1e8c44c868741ab2c7118489f434cddbcc2be730f00ecc56b9dd7333c4c3168d39aeea68c2301d4682979f81754db3a4b58e4dd9e23b891a0c0989d2c471f3fee8273f3afac1e38fa7812c2f1aa61fd7fd68441855f36b34665feab07eeac70c820b73ce18e1b630b139aadbe25a9f5dc23325c0052f7d8a2d1d50abf8069ef81bae9f3a11bef361620c8961089315269c13eaa017e7917bf5d5f9b8a13128a6c73c26ded778b0f9a35002a6f8524a7ecc4965a4f4d28632200076145b99af4722f04c31df067bfa574f07cf194719979ae805a7bd5deb33708c9b4c35bcf0fcf3a53fef5f972d8599bb6c29a77ea1b1bc04efd7bf7e21ca22ab4bb6b670a5186c09aa82089df08c6572942865a9073a77eec128ab59fe846e7a54531a4cd6ee2a00288da69d5b7704179130f49e7d8286191f8b45612823be21f056e8dd56bf891b38d386935e3ecfff7fc24a3fc6765971b2af170be8e1558a5d35568552eed2221df3d0dcf9c1a7bd597a3423d48570f19447fe0df1f56ec3a0797b9f2437d0c0618edaedd1eae95476819644785ad5798d8e78beebc4897ab8aa49115c3794404098386b558a1e46b9b41e416ad8b90383725acd2261798c7130a783644c1198d834e9cd83658d2513b0e5658ca35e9622e5efc054cfa08170f99f5b5ebafd413f14916555f0770aae39b8ef7ce73bf9dcec996a7367453225093d7ce18511c7b40be0d0b6b45462d248374ea1d9d4685e68e585f797bff4c59edccb24f3d3131380c7bfff78269a9f2dac698fecb14b6f688c4410d04d398441cf649e8bd7520f7c998e0a9df5189c434ed5d20bbe1a65d139ab98a7268ed51f9d840e23720577a66444e6972a99ff22184c777376e599bcd2869d748f23a4bb33f2027927a3ac6891ab76adf991f9cd379c899f0f029e3c79743c756df266c9dd64f634b46d41036392ec52f7fd8a035f183a1df276daa738edf349b709d964102120e661e269c47dbc2206ccfcf58c790819e69dad09b61b3019d78e5c30f7886dd450dcaebc3e6ccfdd2691f498c45915189d666632102a9e32e7e26b6c3d8b8338f510046f7aa7810b26fc2cc0f5f9242ee93fc64c14ab5efe414c9aa6ece39cbff1e0a73f9de3043e5b01c08ce6ad3a4db18d314b23d902a71a791332f1fd959ff6e5284579703cf1c91347ffedbffe57b98f3e7adbf428b6e49c3d7ba6f33ea60be0a36785dcf48ed3439676f316f685e986e0314129244c666ce7eda23b867de0d30f363b41852393cc8e052be3298e59819f6cc9e79e9389d9caa2300e3e7f3e63c308963196315fb7f0948e39572363cac71f7f2ce9428f94dd698896911e0412a06ebd792b95e73d0dfb5a353a76d60312543ded124cfcc5ec334dc069d432c09429e02993fea3f5539ee5690eaeb5589843cc78527ccd4c6d91fa344f014cf9f3d7bb81bba17df87277bfe442847a0887718dc89f9d90fdd9cbade012e1e0be80c02b16b9badf7e90c70cd54e6944db5dcc9731eb967926ff156187dcc41e564a1118d22a8daffceddf1e5dccf8c97887a9d69e24abf4697e0c8378ba54f540730474e31e5e84d5aa0f8c86799a6eabd7a41a41535fb862202b2b4ede9d15de318f5a47b0c1cc4faf312b54a668093043c711796feecb9e26f3780485c9ccbc53964da40e96a12894d5315bf2c2bcfbbf628ad1da357b9377da26f55bc4695d933abd98de840260bee96dd7f7d8cc11e96557bbbaaa9b4ffc86228dd7839b1a3186e62aa7bccc0d521882f28d857ff7fb6c19ca8650de4b5eddd762c2be1025a0c759cc253d9a57d96c65cddce408b37668ef957768b3cc7074ddf5ccc14f1a368031a589669b2c590c0b7fca52fb1fcf2ef90b6fcfae02746beb85561d83a68ca97756d344c91bebea518d25c59f8bf2d77b5f8cf200db8a98c37aa88bb0c8bdca9ed8fddfbf14bf4f31773b215b2f200be91506d9f574e5759f0a8be5974a6a30e6a2b193791b0db1ffa4d23068ea29f510a6393d0fb479a78208350df0f1f456c60f08612293e0812fcf686c33f803429a1957e57d607861598f31031c2d7f9266572f9153746ef6c140fe9d1c5c338e05cd3e4caa9c96a19ce495bd38a497bd78d109ca17ea46377e3261cd6181e1efbfef819ab6c533b0aa8cae09f903803e98c9f1cbe92df3adb5b8da35bef197b22648d8c277f8a3a09ef9739fff5c7b5c93b837dd389f4e22b872b6bcd2c17d685153762adcf74943f8396aac853c73e63735679709ce4bfa8b5ffcfce885177e5da6375e322624800465e1d75e2160b5fd623e0a0402c6cdde534c2be87599e20e8ca5044d3db054a490bf2efe08b5b2ec24109082f263f63a6cd666d2a6dff22066cb09dfad92d008ce7a33f832a59d42cc1b2bd143f1c43e929521e01ed64779fb7aa0d484555f4f87f7dbeb5e76f1adf7553d9997899fccbb16dab2cf8b1d2cc8174b7ff5209e93df98a14260501f4d61ccd1f312b3a8555079e52cf0725e1dd652a1a64e5a0da227924bc505a6426fe19b32f5201acb7bef8c634c523aef8f57eeb7d1bea74f9f0ea3f8c83b810990ad0a838d7a0c6c0d45037296f096315938564a9fe08b19686c6570134b435b7258d4159d41bbedfd0454cfe94cc44fe4b0194c35145083114e9568f99b89a307f3051b4c4fc806c9266ff973b7d1b074bcdce5527a7b2b5508ecbe1eee23085ba6d2bd79f6746cf989bb25735f5ffa62ceda3f7177cd315b4566ade39b478f7dffb17e48833344dbae00ef45bb8527122e6fe094979e3aed47502ccbb2d9d51a4c8b7599fae6f26ce979f43b8f7632bb8ead40e504d293719eec9645259e3050b826c57dce69f59ce84ac1e3939b62f6fae88676b36ccb385a5bfeebfffed7c0cc46ced004bf58286cbae5befbee2ddc55af7585ff0a687af8bce20fafeb7de98f2f931d841dc5760916d3e3b7e1b949991cabf1565a05340eb00d1fef0c8eeb084865eac64fafd0d68ff7a8c9c06d9e550038a0a5c91a2fd556dea685ab65fa3eef92761c00d39b69540c6fde860dcea43107e6d866fbcc3c5bc2e37d9581c295b0999770567ecb4ebc3a197798e0c5e8cabe9815f34c0c734c56118039a693ef376783604d92ec73e3a4d81ac4f8944962a2f69de4bb1c73a5da7d232c2c4641ede9c089c2f367799115508be6f095aaabc68758a232899bf93593d811766b13d72b75aae99ae3edd44b3c86b4bb1ccc5a1b61fed59684faa16874e61a13544f2823930f431390b72f39e33fc380a4119a3770873ed3b36977f1755264054e3d98392e1ca33b04c95a48caa06b302310c67494913a9bf79ba619fa1b97891b27cbf4fa7063beeb619f3bfd7cf0e488b10d2a6b546346eacd9565dc46386d7781cf534f3dd5096fed60cc7a4d7677e399dfa547e344b1bf6dd162b55f2bb9fd59ef0ee3dcafb487edd4341bcdd5a742b61268f409fbbb8d1f1bada0959649a9d1dba892e7e77dc72569088d21a848c7654918e5b10bf20de2a0acfbf57ae2fab4a56b3929c4b5e65fc667189a29a021f42206e94cb4dae0b1e9cd31612ca14507bf412140dafb1e94332cdcbf0a610e19bb992035f7648ec7a4ef2ce18ad0e65d976085496a3ab5c7985eb73dabb15634ab3aca2bfd1dc1c5ae6275183ac266fd6019e64f1e9b2e310ecd0cff2aacbca381a59f3a0c8c7195bfd15dbfc678d2aa84b426dfe5c7c41486deb51f790f73df932d1f6b3e2b28d6642478c522082e41aae3294e2763606b3657fb4fdb059b346a5abb3d815e01cc3be385e54831794ca88c190f57c037ef46037cc27b6c9ce5fb62e0335f95efe8058a0c0fc1c33b7999c89c52ac843ac122501401456131000157975132a3040821eff0eb31414ba35091195b85991ed158162eab7eebbaeab9e8e27a18b7ee5dff529edd2afc5d6250b6408c687b61446a58a2cf21d26a882dc1b04b0a836c0fd6c9bd9e4c23138c393330a90372cf28909edfc0c54c85b8556690e73c59df1a73e213e22fd3cc3d17f13203d7dc591920b8808f000beedc4f218b388b40533273346bdf32a9fbad6f7fabe6a10d826b39d272600c7d8c4352dfcb6b494ff05db54bb9762ecf3619c720ec8f54230813dc6c0d945b5f6df96098c7de389ec277526fb8a98350dce7a6f77a565e388ac5988a7039049599c569619e6dada67026a4f1a3b59d1f8e494a28e1ae75e1d0f6cc2dbaad80b9b9fa31a99516c565ab1ff3d711de0e49bd33d30f26bf79ff9cbb016e156e7a0cd7456765b40e5b637847c8f4564f5d6f11717a5ad64b8245d91403d391a04e3b1e55b89cf7f8d043f9a20adae7b7b76c8646ca719689009aefdd29e3574ffeaacae7baeb4659a1d36fce9ec9b4c6bd9b253082dc7ce599c1c5f39211f8afb0eae5baeed7bb5e93f68afd6457bcccc3022a1e4a031bb362af2073d018f34e414cc539ec1401393d986934eaf19c8d35907a69ed0f994739c2c4ad8656deccc1fcec673fcfae6527dcbe1613ecad12bf8d9e7268ada9a81e44defc5c52245c2b78ee8be8f6a275ba92a853bebacea2d677de7eab42a6aeab879ab2a6474ac278c352424c566335650e730df32a0f93684ce33ccc22d1012514b90bea70439c1e34b4f1c79cb2e5f5d48fb2c2406ad0f1473c6398ddf8e43fdffacf4e5c3b7a8ed031b798477a34d536deb193faedf4ac4c5826f1f410810ea090ebd0481ccb64ceafb428d830403ba2836025fd030f3ed031a7d5f94b98bc43bf0a6deed7b53cb3d563d284f6b9e97eb728082629478716839065796b02def86af871da0b6de619a4c17995d3fc22e1af62f9b10e3e91f1d94772e02dcb67f101ded4bb53a2d336034bf6c37058d6ba3fa4d3615af7dea5c58a63ed03991a09a1840e83e6b6cffe68a4bec61d0987638b5558620bb442c60318bb1ea134e61040e60d30807f25286be06a9a5922e3f3b096462dc6bb261accd1001ab760037352abde04795b546ea60ef3a6cd5b1492a3d72dfdf6b0f2a98b1eb884dda13e19e0177eafe62584d767bc00977ad3ccd924991eb14216af16f392e770d1694adcff6d7d5371e60e2163fef8885f2793ab381c8c33672332c94c3130cf984d3c66dffdee777b6c02a15a95a2e54d734c838f02c0c8e0724aad7589ab0d5d0f998842313739ce9fedbb026a103c095c97856d6db0dcef43c2a1917694f610e6bec6ee72bc41e8661e948b9d00d4224993aac78ccb7edf256be37419ba2e815ab017fe87b0e1f35e7ef88f97571e8e166db470540767e62b476f3def16ee7bb958f00febb1ee0fcbbce21ebfc412dc998b872f5988edb1739de2bc9dcab5b1762c3cf1fd0b6032790fd1b18963e3aa6834781d0271aee5d556418477af51a72156450a6f2b1831540671acc6a6f5986df2e48d3f0761b013116c0bbbf9570ac927637202acfc0d86f2bc4b86ade86ac1dee78fd8183d12f53f90b42941809789715f7191f9c9279feacf1406dc694ba6da72a260fcc3ba4a83662bac152126e19d35828600a3a915e57ffff7dfe821afe68c301e6bc1ea1a4a4dda9e7f1298fdb7814507f57331f9dc4d96e9e50cf80fc3fb318ec5c59c0acc3a0a6ee16e82d75172dce2deadbc2906197701231b57cdb0e1529508e1965e3a8accb2b43ba2347c7c9e90457506df5973ca33fc40f22fcfe6e4db17501e4a6962dc2b0b4e4c4df5b4def3b538c098ce84f83028cbe20463fabbee3ab1533a2bcdaa93e755ee8a6bb92abb85c3e795c6ab19e926617baf20b85e166129366aad78512b6eee87e9a6a8ed6f4c469e353f35ef1ac68c47fad04cfe10822b116c6cd22b0bc22b48c5243236f01d69bb88cb405754105a07f8e77e6060ac024d9e1168f187f0e151b342818169dca2f758d7511ac65c1bbe016705c403d9defe852f7cbeabed091b8f59773807bfa79f7ea68dcdb4a241e7f8eed1a68b5946b0c08ca3258cb8f35e46ab5a04cc63d8b7e801b5fc987b189a876d27ac79f1c1384a6c413973e66c53a2a1f42bb4ee7910676cd88ffec564e489036785d5ce431f748a991941268c7a33ccb8c64c9407a6b5c6d1e268f55974652eeab931b9b2b8ce99b0e0715a7ce6339fb9c299d1d5fd1923325f096344af69cd971164bb02a6d79d5a2d3c17dee8d30cb930cd7ff8c31f1efde4473f6a997608e8cdd493c9bbf8ae79b44d1c3ae6cd581c4bf807dc9e9fd6b3eb0a57f3e98a3fbce2bd113235c7402d759294311b7dd8547987417321942de4f0b57b8d9bdfacfc30205d7bcbc675ac28e16a22891b3486a15a06785b7a0d68e994f3349a37656caf644d1882ec1b59dc2057587dda03ec9c9a5c1b4204a1f0fa0c56ce7e7452318d9b3ad8eba4a116de98c140fa9e7beea999c32c106efac0850ac18ca730cbb8ce6d82c46c18d39c1ba1f263ba995e30ce7c29bb037c73ccde398cf227ce9c0d3f427ff11a4e9439a189ebfe86eb4383bc871321efb825755617aef755b78a1b7a8195ff29b95eb531cdf687a1c25539c896e449df980a0fc1669afe2a9b26c15f828941399ecac4ed49071fbdc973a74f1f7ddb370462e61338c20d8e76bcf7de7b2b5045280532917918b9dd798805f542231ec03f86769c418bfe4d70f067d121c8b59ece1b719a16fc46b8c611a372163a08f2a82ee132ddf3667a3f66fa28bf29ffa0883fbb5d65baaefb057725866f856c21beaecd5064923448e01fefc4e7461bcc8b5e10b5115bba2489403001540ec2177328e979a70a87002a38b006c62eefb4e8d03c254ccf3202ad5c70f4621a498134e58c0ba7ec44ae9b2baef2fa07ebae9f33868aab628d9b16436a64cb90cc35e9312d36b6cbd7bac817c3fcdf7df4bb1182975b27f89b9b232098692d0856865dc48e3be3ce66a65ca63d833b6d4aabf718b4382f1cc26a7ecd0ee39733f5c0bcc1a80d7860a3353aae76812ba6211c966ce939db56a9ba413b37b679b6f9e8dfbee1878a7bfab8eb7eb130ae32298481131aa1551a7ca1001fe513628b98d149bd2d0ad676e7d2c3e8a530ea82d33cf9634919463757c9db6c7a82a07956dfc39e8929cc64bcedb6783123647b419e05cf6864f1f794c17cded7a7e59527863f99d1e6d12c6476380f5e5a34c437480bbe1f6e300d62c99871aad5392bed157095a8c27f25a0a1b0d295f712373d595e1c2690a8e66373f8b37f3f4ff3b7407611f24c65544a4fc62c72a6a01db1c6659636994c14e0bbba6d15dd05f5d81ee1bce15d21e37d72c2942efffcf9ac0cdf76c7cabbf05fd7c338f0668508c69bcd7e3d4b304c4a73727d3b968ca6e6d62664cae90a0db4484f268e90818f3ec6987aa7377354dcad990fa250c47314981bba336334bd91d51b1adc4959e03df6d8f77aac9a81b65e8da626f0ca3096eadc5a8472873f5a6c6d1b4c6a7a5b88ab67000f7373e3f390f949ba1ab940b63f759ea0799e9557f778967e8183d957bdfe3cef146e3a010d30eedbeffcf6e8d846074a86194868d072f5027a520e19bd9355f0a63d280c533194841fbaac1e513ee9eda23e7e3c470f70de24508f705486ba12f2145dfe6982833f0b776998ced681b20a08d3e28b554f637c2b5acc474afb898c75d56ff872da78810657be056395e3fd8a5b6957dc2e4dbaca0ad902b07b01e06ad983dcd2edd2884fe19012e4d85ab83d997466e3113143d16a7c8e80e8cc1d8cc95146c9970000400049444154abd9f78c014e6336f3cbbb21aa45b6c7d3108e5fcbeaefaca4d8152e51c2aaf0d4a7318d2f2ec1c34c3f73c031cdfdfe7184cac25a8e0b3d012d691ca9c195098e71997572870c0428e130c16cceeedd77ef6c3d7d03002358af69ccf1f0239fad29c9816192f4e59c29783a8783125681d9682da61e79e89aba2b374ce1b9f508358ea5dbc0a428c39bf74a06f0bf7cf297154a26e64b2fbd52938a50eb2984a1017843df699cbec94b2b45d2b31a4f45513033a7cea96fca5637cf4b0064f0dc3d68b7ee0f49ed76ff805cbd2b9a52acca947799984f3ffd6c7b8bf46da99b8f0afa7ec0f4a2937eea4b69f03032d1d5a5b8079fb5eb9a8710cca1d5d4518d0e9fdd1b43ce2a908f1e3d7fecb9c07112d6f56d6307e31a87dab5dede2ef0c054b60da914c16180c382efeab9781d263ac0e1aae8f2e8ae275b80ae00aac157ae5191f324722b702d4d1a019bd7ad6890e5fa3ec6f99197bee9cb01c21dbc903d803e19b7d21485a1b6ffb2b76234d42d2192d5f476eeee70967bab3cfc310481a2a16fc907db11936019439930b54b9740d1b698412f923e28bf2122136a089998bc6342eaed5c31488aa8f0bdfec73fe454a567ebddeb3791b322840968bcf6852f7ee9e8f3993065662db3e0ed8fe4fc8d68f69ba228f4843b737783a7b7194f6286fdc10b03aa6317ce327342079ec4679f79e6e8f433293778120869fa0bae6835e41866181a89f43c66d2a21146b7a2a5db8862c2da0829ce625d3dfac99327cb7c3c7094aeef97d90541795ae59262734cc89caba2c7300fb884cc3b632f1f06b45a04dd2848719c24ed3d610529944ffa1b6eb22ed121b6d945fde69c0fa92eed2d3785308e9a615b79ae0ee0a9b30daaf7dd775fe05faa6235dda197a464958d97daf649ab1de42b2e9eaf023a382e5ce73a740de649bfdecbe6feeab89d901dc29d6a2783d23c6c61155e92330d133fe407dc83b8fc832806a6a931719ed9e7fdda4bb5f4e6575e70f35e288c42d4a4899b02b6789a3f3d598454378ff9578524f0aee3929808ec7aa61f130db3989b720214e6376629518387fc453b4ca4c782c6fca60eb49af7ea6155bc8679338d7fec58f2269e89f4a378b07ef293397f9f379090389bf0e4a97b2a00ad57c66fcab2c29e49a2677c434f9c505a19df44c88fe5dcff0f6d2696357804546ff36c04eacd68f1a450d54c6158ca653d28f7f7f438e0a097f75e4ccdf4b853f66c37b28b6168a8c730e5e003848f7ef73b1d27bd1607064f21985ffaf297ab9428a31568fb9eb41c5a58c572393d2c0560b915736e4c4f93edc22ce6bd3b5b55bef18dbf2bbdb44985288c6e6588fd79bb10dc675d623693661cacb76f9d92c05ce35b29af477247b8a543cf56b89073b7e3a112200ea80f7485bd034eb51da58b47d66a1d7997f7789448281698e5cc0dd60eb7ab6e5659a2078f7d82ddbb41a3efdf57c852dfbedc31fa1e46efbc4f82a9e3ee1dc1d0bcfe866582684daf4dc830c68c41d6716b2be3864df3ad387012bfbd52e716971b5ae80361448c504741d218ff69bccf3ef2f0d1830f3e50f367b4d59c7ceb7dc105485788eb15721f31dd4c5be69832f23e0d3aabece77c0d668b035f5e78e1f99a82834b0a4d7a8ae38d37b25f2e79c037317e6dcd3f3b72e7080082822232180732b9280370350878342a65f0c0030f1edd7bcf3db33c29f9647470272749997a238833ecafc5a01b7d24449ff9934b95c6bc5cbd3a0dae17707e0666d7bbdb66f2eb332f1c7deb9bdfea4e6cca07ade0a72731d663262fc6217084cc4e6eeb0ad14bdddf8d79ac07643e1bdf28d33bc2e0d42c0b83295d4a8a6388636ce52d654204cf0480125d93c2eda593362f2bc03eed649a031ed20f5e435dcf87b096b93a34ded2045763f38e819b7a4f42e9e0ddb0c1da92f4e2fd2a43c4befcc354132fa6b8852d94bc13b243203b6083db0eca0e30ce4858da52f508649febdbcf3373318435eee00ca049cd7f100c87644ef1a0402c4f0139607393e715a7ccb94f098179637a24e607ad645c9676a940132acc73e79d9fa8593790e7afb25ba7143026da34880211fc42c65270a3897b145abef365e272c63b2f47dbcfba43388f265cd88f901c8bf9a4fe10075f1d9dd561bc83b1e12ab8625a0ccfac5c95447be322f347e6deacfd53023898d699f4474716cf8e47b50d384d30f5423075cb3f65e8b5098245bab694182b31772dacc5a0d260424c6c9c43a80817a74b4da894a697f63109f8b3002aac1112a63a13bc1faf9816dfd5d7962293e58742d01dd4e9c1378a07b26a6bd3895957b45386b11445a03dd1efdab6192fe63ba1d9cb353bd1a33408acc5b7e0acfbbe0bf8c3725af0f6a7a42bdcadd5e01301bc901f5e29df863e681a20bbb2ae809177abccf78b6f1c990d1e3b21db57769f650179df7772974e4122ffae087954510d56132dc43b1781581ec6492b4f01f471a3f91617881bc8750dc434c251ddecb681582ae454aa158c7108ca98476297700ead26ddc273343037b6c36a1c90ea70192ef77e81328d4b8b73933bf454fd0917e66c0316d8c0d708a54f04c0ba42f7c610e6bef4567a3bee69f98c51adf7a32430543535da258fc1bd31cd6018e14f83ab33b38750820156698628219dcbd07efe1214c2faf0230f1d3dfcd0c31d8328cfd444856723e6c056ece598a4f3b51cbd0cb3099e7a7287dce8cd08ba745689607acbb74c106f2440e55d7a8a45dd099950bab8cec35c73bf68b87a0ecfd2a287dff198cddd8e8218895fe6fd9b7fcce7ac22f8b7e7501c79e559b01421f47963ab669fe82ae2bcec7bf168809e78c67ec03ab1523f6d6eb1b371a3f69667d503a875bfca7d3f1cb622b74b14df95117b022c60de6b42e333c83568e0dcac311b424f334fdaaedc4f02631a26832f4c5e8ebb5aa5cc9921dc1e18881b5572b7085e488121e90adeb1e399604c1dae6cef25e1d13379cbfbe679f0d9675e4491c1d481f9130e84c71e7b2c5fc63cdb05a25d651fab5c5a3f8ce99f3071f0c1fcae23a8a593ba26bd1e9a39a8e7b8efdefb7a251c2b18479ac3d1a358afb7735307962d3b18c82a7470d46b9c2e71de648c82e946c8d46d1fda364d9c583887d6a610b8a4397856bd31d5619b4e16f35ff3792642a66c30a4a5245ef8f5f381774d7bb57e54312b2fcc61594cabcd51be74083d67370037fb95fbdabc2fed22140befe649196574ca316dc131c22cb6d2e5f9e79eebf395353dd6722d843e75cf7e6eefb04e3baaac829498f2a5e1d97e2fbf2ae32a8c2c00485d4c941bfff99888ded2dce3df7efdab35df7510cdbf038c3c237460fa2dfa1edeafe4e2843d076c24582ff609dd05d892288594c0e235aceb3e68f412d5303d8435461964f331f2d8d3dde31546cd1014fe49bbcf3bc006b169c0d6714b90f8e040bbf0f29933abe9b755165ce320837093b2a3b9c13fd044a5f92804f13c75cc223fc13613f904b82d5ac08819d19017985f9d3802b87f9d0a6c3fd25dd17e8e4ae0dc60ee503053fec0c266e2098d35983d5eba65cd573e7b38681ada44f622ad41bb69068a05238227b4515722115bdd6af2860ee871f983a308bc5e61f51ef2fb318dcc4f117aab5ad4d58f89fce8a3df39faf7ef7c3b1f47dc4f0b287f681bb3a2b8e00d5302f38515bddf8ccb962b7c63f2f488a61766e918b37cd616fa281f4b82f0be9ecf5619b7ab8c7266b7c3dcc3cae6ca975f79a9c246291c86a17370d9e823bdfa195258c96f11c0cb29cb9993afe5cc4d93cf6865f7be312cbaf89dffc0f93881e6638735a3036fc1044f58cf5797ef79a5595771074206c0003c4c30382f493860d8a606621aa5c54b1c4402a58818e0f22e9a0b52015ac442d68e91d2cb0db2600ff2ae5b3d0af7f00f9c80c71496567d303f2b299817de5dc884b7cd916f672f150d398cb0c71bac055b69849580d8a2f17234b43cf0068bd9e4d92f18d554640221bac958db3e9cff67bc72dbc7e62b2433796d9e2d8215bc84056796f10c2e3d1c343d8d3d6396525db8805e42964b6d66ea47e11218f085e78ce33e3ce3b8a6dd37f4d02510421cf79894c7d058cba2dea534d05a7d9873e6f208acf28cad9e3bfd5cf3b5ee81011f9e37ceaa4517b4417b7028376d213d06f5201dcfab71dc5db91794839195c531f2eaab59fd1161e2897488ab437bba11b4e98d3709f0307b5a3a1086320841c7ab9b09774ad15aca8e8ff3aec8480da9ab82dee9a9a79eccf2ae7f6ff997b2d9b71f31519fd098c234a65e42c65ad05b5a68c019d5b60c5c90c157e715d6f361dc7ae72a8fd40742265ad49f23da3707c03db742925f9d25d9dbe9e5dac6484534b44a18b3d8feae41262c34b647e052cea2551bb144cc8b2dd2e01c8332859846844c005b831aa78933663bac3cd629aadb1f7bce3a1d905e5195f73811f4e9312d0236d0f791400e04e75ee8a53ecc431821776e851e8db00b50e548b91cad0d281aa9610bc80d7c088d31166fe8d9ec04e652b70cabeb20efbda7bdd82e5ff28fc76d76008b87275a0aab7eaedea5e43062ce2689030293bc77c72c02c09c18de894df69a9d3d73b66612c6371e61ea197b15864ac03bbf3a42328e9c802e5e4d3925a617add7e0628fdad9b367d24bdf5885f762e60df51ed66cbe9d7d794b09b6cd747f0969864c5dcc98156cbf15d4cbafdc92038050931b9f705af676656f36f0e48567bbd75c2806263b1a50ee8438dcd8725ad6aacfd652c6a34ea1a6043e75f1ee7612c503c80d3fcfc27aee43feacf8f5ec0ae745c1c6bf5f22710bd8ba775d69f724492105baff231f01c358c75339264c0fd64983de7003add062037faedbd3f630d0d6bb2d6918ccb8e6a68e25b883c15330d73bed4dcb2128534e1585e2da7562d87062e1369f81cd8703637ac0d1da48736a9649ddbe9de9b84c3502a52ed5b49b26df2085f1a7f7535669a5dca4816b6915dca6ae2334e6c0be9843464f9d3a556d69829c3384694868c19830b4671e1b67d1bac371aa3c14e95c4f1850913cb98e9ea3dc3871de7bef89f61add199db107c6b1bbdce05ef65d1ba6bc55e6c2bfcaa2450c0e2d602bb3750a826b4d2021b003bc736e8f3e7af41fdffb8fb4c57c526a2941b45bb4a819c892d96ab92ef35ecdf20fdfe51f67ce32cd2937ca6e792f27cdcaed0ae10d6a357d762d44d952687a7502eafdae5c3408415b77917d8e4d91cf6159a87dfe33efd669943783fbc6b08b56e2dd6b7f990fe35b177508dc0a59913d20b4cc87e1f0bdfb3f0bbba8ade9f30ce712295a1781112b98a681b35eaf9a1e820bd2eea61153c60e6821ad942ad3e555994f31b94b3b22d4a51c2caa27e3d163924a37586c39e1b40accbd3620ac777feaee9a3c4c416e6e2b1ab8bff5621a88f0a9079cfa0bdce207065a6c745bbd8b321483b9ce9d1bcf5cd72462b262e4f8b50fc7fbf750e15e178706daa00070530e9ca54e0830c2c76b085ff56bd85ea381ded6bc17f3c667618d8968fb7ff9977fc902e4974a73e34f42aa2d0821a794feb07550cc76ef597d09ef84a98f7b7583e30a04b182d68839fcc632b3b59113eda6970743ef11da214e82bfe284bacf839fb2e1a74e1c32ac86ddc138a91b45a3d7ff505cf804af79fb6798b95077f8b999fa32b7d1e7f99c8aa5a7d2bb7575cd56d7a654b1a266adedbb9d9c67e61a52ac30af871776bca494bc90bdb45bf5dbae8056c80e332c80575f57635c1d3f88257687f05418e218196c0dcaf4e099d3cbb07bc11b4d3f480fdc56f7aa22f21ec3f595c69a495df329377ff003b5d19977e081cdfce162a65dd47357d7402d9136bc144223de77dffd47274f9ecc44f1f108d5e6ea668e051e26d2282d3ef7a553cd1c80a76e6e94cddcb2d4889793f7cd3223632ebda53208c98419641f4fcfb890eb2afed066181196047952b7be612882a4c139770442ed9d9d025fffbbaf47597caa26ac1e1c84279f7c2a63e0cc4d666aa02b567cf43ce90b3db08d357a86bd5e36ff6668a5adc635ae8cd637d7d5f67b9ce69d7834dac1cc0d06be5e39fd05967f013e737c130f7643d2cfc47a4ea0ba91099d758596bff9be4084a21f1c8cc387b388c263f615efc016f67881dfa8ad5cef270ddeb3ba8479497899c6f5102f184956d62a3c30662ac1a7b438a2ccbd8e629250395b4192265cfd7c1837186488d0947fe10f008bd0921cde2fe03b0483f44aef8af8b43966d770d7f2b4a5c2b484bd3e973bff24cf61e183d6b45aeb9432e7bdb2a74cda294e8b9cd5b0b40ca2390997b7ce609ea019a3319f0e43d921e515e7c0a5d98d1f8e1ddbceddd8b4f7a58315e0ab4e6ddca41fc68c3b388e1642ad4ce519ccf34ad9d43847c4fdae757df081076baa1887958183902ab5da296faa7f48e7899168ab7a1b595d312117b771d95807598615e1bb2beefa3b6346edccb7d45d79ce37b45a05ee4b78e4d536d10a3376ce3dc170221711542761b5f5d0bc516dabc3f8753f6f53a785f062e0c0f66f579124dcd1734b630bcc3da74e1d7df56b5febf7c109945e4a1b2fe69eb667726fd45a703752295f9a858f74945e9d3cf1681b7b5a10ad67159602761faecacfdfbef0b7b4b2ebfbc59c9ff9a97cb79cb90e667be15c5739d21ed667e1275e0053dabf2a6487c056a64075bb0b05bc116c973ecf7a2966d2e508976f1c1bf7e8cd2ea5f2f69611c075764621eee0e649857259f01041504ce3f3ecdc7b30c7f4cc06cb98525602e8316c6390b7205dfd036f834150dc8b17aa699b66e2c5d5eb7468cea4f7ed5c5c883fa7654d4ff5db78ccccef9888d58371532fedcea961fe8beb790906d8578669e4c16e613435d643abb3ba6868630b8cb2c6392a65c7b571a841bda54bb3c0d8f79d1d679d435977349a52099bf1b15e9b39765bcc3066a6f4e68b7efef39fd7eceef8af4d31346a5b205ac2622c6f1a5f424bdcd712e416e2f9ab0207613d1f3224d35ccf359f459a9de42b8b74873f7c2570087508927bef09150561c8801e949ecda49662b1287e9fab39bc5a545b5b8303f5d2b8f80e6cc2246d7764a7e7638d08deeeab387757d767d5cbd53bd7f715b2c3040b484b8151c2ca7c78bf4bb761e1b99e1d5a976d9e59fc6bb2b78c0b751d78bafba452008dad9ecc5bfea9d2d6406c79651f94ff81cc937d2226c0a71f7c30eedc5b3a20e652c730ecf6698c8d5921ba0b8199ffc3cebdc973b479f06532d591e0ed7bd313779b0557b749cb68b65732a7a3f1fef4a6d3b2b2b5e57cce544f032b09035f93b9b6eb8cb1024f4f6a45014f1f06589a74a1a23a43b7033cafa8bf0782667e6dce805f9f0d420f820bb64d90f7de9323cdc20ce898ff5d846c5bc707c2c0968aad36654ede7df2eeae08b1aa418f6782dc3cd7134ffcb4a72c33a9665e74f09377e55f94d31694d568f769363485eb5421f7f9279fd0fbed4d2312e33fda31cd3a96cb8265bdd8beac2d65eb34b0bc6b6f9cbaeba966c58673f37fd7e90853124c756357ca0ddd5935f0a5d48d11e1e2ffc24d2970173a2d90ab8509cc451b39edfc90af29d46fab5333e4cfd5cfe2a75d27c5fb0ad9bcfacb7f7740951a64b73fbbc2bc1f94a7b09a2be9fe699e4bf130724cccb8cce07a181218b86b9a5d66a005c00a701888863746f9ead7bedad5e2e69eaecf2a899a50d57429bf84182c0a1b8803e2b0d58b7a006b34bd2b373733a1e724c67160e2f2a56c05e19dab4b3c84df23b7a072ecc4795160f0dbff14e70c444e081ab6df820e04c1bb36c4862766ddc54d0aa91ae7d12a1075b655e48f61a09a7c3137df8e276cbf02fea6d21563112c8c42b05bef00571e1add965399eebffffe4e1b543104f9eb5377dbfb6d11014f588cb2ae8d14bf6e7205bbcf48bdbd905efca2b77aa14fa2532389e0d24b689f9dcf3901d9c6ce7bee5d47126c703738762e4f2f351f433417e7901c0a8f79be84ca506421d17157ca6a9d0908fc8a53786e83bbeab5f0f44cc97209b1084c7958157222e6f8b5e131e928e356758311a80dcd9bba24fa8a20cf5ec8ae2292948748acfb2b206c007b497e69006d5a151a20d50e9d94362ebb661609ebde115b5895745f58fb3fa2924a3abf61447133a0cd5c597b8f89272c60e57f830aaf7bcc34049ac6b7edc69402138fa9371fdb73a291f58b39b3301a8c77c9f9efe0343f1811cec1125629c840444fab6c440e376164281004bdb615ebb6c17c34cfe3511b040feb0dd1f7a57121cda4b47586ccc6d22e65327d2e44f39bbad0cb72d1d3e8188346b7148ae934e49cb6719ab39e4a0f30214a86c72d78dbef6503abbace787a3c8cf05af597c77361e6fe62f0deb5f9417c8996f7da44981a4fbed47417e7bd31ad551ff0e2e55d74518e152267ce9ce96657665f8f09c818cb12342767ed4ce7c05487f5bbecb0d92d04c51d6ddd2f6c7678af84db95a0e9c9addfc4174c474eb2d53e7ff93a000ee14abb17b245b5830245c169013d7875e56d133665e34ba4004fce3eebc96854ae778d7521e316cc478b851fb770d0582b6abb86740155ea5468a611923ee310cc5dfc4294c4e41ebe7e03984653cec58be94133116e0500e78849598ce8b831676f302f9c42cc04e1ede392c012f0363756e1c0500b5badd5e7b9969839825bba12794b67653721b0b201335b0b282b6c8b68d2ad46997a7997e04f6928c128150266b589b1965ed138c651745cd4bebc72fad9d3ed3531a6b12178945bc7570143e809e81b6fc63ccb84eb850b3e3a38cb93ea4cea6a96391200f3f2b85ace754bc6ba7a453d5c77a4432ded08456504bd09ee17ce5b94f646ffad56bd68cae64b7aef559015e127885bef5900bf7aeaa9a36f7ef39b7d5f659634cb13cbbc9cb611397007c628157056e85dfe3073177d0fdf4bd7e7d401ade0e328398aca4a1f4aedea20fd920fd7f5bceea5df0bd9d5b9f3bc474fa50680640bd015f707ef5b680b5797fceb203513d2e9c9f43cb4a9496483f5e919c09f9f52d7807fee95721092b08d2b0ab1b68bb6aa3026bf95f8efbd87d1f25597ffa7b3737ff62e29affa3b93b930173061b80a8930049894fa432c49424c2a6aa5f4bf16a9524bac182f216a190c030c1733c31007c20c3017d767ad6775f7779f739868bfefd9ddfdf473efeedd97dd7b7fd5b8995ef8c335ea544cfdfeead51f6a7479c3d3d6dd44e08a0eec846a34228d24fdcfddf838bb281d68bcfe1176357c8ef8f091191afef7befb3ddd7539a6c43a4db4d2051df812120d95ed7482353f2aa83ea581e10bf21e1995a98e6ceafcdeeffd9e3e43f7dbf3bce883ee249ccdfbb7ffe6dff97c1e2354786447d1b2248c9b0dfad0c918b9f385a99c98c73edf5cd4193887f90ffee1dff7a6081d9877c158fffee55ffee5a37ff595af88ee75e907a7e86865950e84c4b41364ea1f230b37290c260f2d9f1760b7f4ef68facb8d8253f59ffaf4a7bc6955de897593d04d2e1b0ff05507e4a6a17f8e2d0b4d121881bcfe5296b555792d1d4749b1b17fbb1b0b3545c01d10245fb0a479fd473fd4eee48ffc819d6c9a0c2e380a95417d5d0365c0ef74b2567689ee6522e286326a7ec5e0e88fbb68a750de0091c3397dce1cdb774b9e299a9faae0d0337aa452167c8b4da52e3d7846d633793fb553985af0a62f3167d198fe7157a491c19b864803f00dc0bae278b48f1070f6c777e621a9a60c6caaf88d6b3db876ac46c22e1d0f4f1925fff44fff934ef57fd7535155b5eaea1d8f629cf2668461da710d6e840272c7a793e01b02b83432027e6624fbec675fb4eec0d58eac349b07ec3c32d5ea07554d245b3089bb3f1d890ecfa3130ee73225e654086b37361df015eb1c366bfef00fff501b497aa74b3bb68c14d0e23bd66c7c7dca3c23c07ab50db8914b068f53706676ff724a06b7aa0f786df9d9cf7ef6d197bef48f3d0a3ff71c275d723c8d3a3903f5c15a9417453954cd4653daca6e08dc3c32b5452b5e8065e7949d6c3eb6c468cecd05fc5c8d958a168cdcd4b75204b0d42c0c96d99ae1e8472fd47e7806ca21ea6bbf3011e8aa1fcaf8234d68fa4e273b0b8d39973211e5a8ab82f0ba610abaa7032ab3ae12e8ed560db5fcca23d312ce2ffa4f0b5a4fcbe29fcd909498d4a0701212eb1f39867f380307328f6701cc08e29d2535147685d859a2527eaebb11231b9474aab3d10a645ede4490432d4ffa622b23ce337a80cbf9468e3d71b6306f14e7a767794f8b4ec043d28c1cfaf516d9e6d7ea05a772f035f5c9da811d341e92739a04b80463a41bc8dffc4dcaf9d4008d989186cf03fc964e85b05bdae051d653d754e8bb3a5c4c603b9e4fd7f961b5d611ad64170a85a91debc4a79ed5e7b085c7a7d578c7ec3f7ced3f682dc789f4d7e5c7bcbec28f477cfe0b5f901d9a1ea917b70e783380d317effe4f31744352a49b426581e7b58c6c7aeeb9fcee17fab2a9e1c70be820ffe32ba6b77f4fcf9f18fda95b68cf3febad0b30f4cea1ec17bcc9813cdc4720cd86d713cfe98d734d6ba9a38febc40b1d9767957ff65fffcc533d084c032d8d53a783a88053f7f2a3445aaacc097f4b8677e8b841b20b6bdb6503ba9586f8cc976fe1fe4cb781d08831e91a7c4586c8d22d2078861d97d04c4588a1ff8927d3424632a6596fe916c10b9c3efe04af9119f638220c2bbffad431bed3c849dcf1bff9f2377dc29ae98c3b93f851b9b1239deb49c984297c192dcacfb174c174eee0feaea31a006b1ebe66c41637cf6e68bc38380f49b5f7245baa1b9a860fa323af8d3ce793171a1a6c04e6b18ee1a5401a32a382472ee9c21d9807d87ffaa7fff1d1d7bffe754fe198b671f7fec2175ff23b614c117b87af6f912783cc1f18d3f00f7de8577542e2d7f48dc87eb64e3e5063785a23d1afffc6a7dd61f9981067f868909c86ffca57beaa6dfbaf9bcf537ac44283e6aefd9aa6d3bffee9df78f4984684761c6878d31adfe2776e56e8c533507e0a978ff130f5fbe4a73e69df3125fcb67e9df32bffea5fdbae275469d0623f0789d9ece006927ac29ebba1372f765599ba7ee32fbee1d109bbb01f3ff173b79c03fd757de68077f9b87920e37f697dfabd57b4f12359aaa0dc9fa9e9711daf35d11eec4a5d7ca315d74c3383c4749229233f30cf3afe7c231b6da98787f44f1d8175acc93a3a003c098b0c6c0775306b7b8b4b79f16d01382243591cc69cd66b308139a4caab2f34fae903a28e8c3e1c8ec8c0967865710cfae48f8ea3d3fd1a15f1240d65c195e7d38ce0675a91a905e57418bebec49635a3c6c7d480325a7dcccf8eb873771a635bc40346f9874c86bee881dde84e47e5a02f5f5bc2e62cf8b50e5227e36b56bc6b05ddea3462c0c3eaefbef28abf21829e9e06aa31bea90ee9cd12e1af673492020ea1fec0df8cd03911f261950b41ff393c8fe778bef6f9cf7fe1d1efead73e795914db91cf949d9b0987acf10d7e830fd3c2d7b5ee625acd8d855d366878d8ff71dd707e438d99f534a33be73cfdda8f9e4f32ca7f503e7b7a8e4071c3c36ea6d2acff1a18d568b0c0b88975e3051904626ce006440367f381e92c3bc1e84d995d00bafe5817536f9ffa944ebcb8f3a56e5ed0630a74e207eee1856d10c43f74aa797bc3fe124715f34d1ab9de3796c7f5e9baf744437b65438c91f113fab4052f0dbbee5156c1fc1457efc62ef4c56bb23d2241704522dfe072b4319ea0e390e2505e1ea531b92eee6832a29de01d39904e4605ab49c05221f4f0200ddf3887b204b471a97058fc3235e0812a77b05ff8389430ac17eb8ff083920a607a4727607462cdf171bdc1cc3a833b31dbd794737774658dd9edf0a814d924a41b391b07773442465ec37127a321ab91a1ffbb6aa8bcb5fd863a19ef6879ca2a86dcd97980cc473891cba8f12bea1dd84d63e72e4c03798a6f7ee866446805a323e20d558cfe8c5234021aa4477395b28b4ba7e84600f4f8055b59c7d109d9fd04ce5a0a99fe64b562f464d4620b9bdd57e858af7100f9577fedef780d4827e18fd32db463fc049eacf6a8fe918fbcf0e8e5975ff6d4f23dddf128e3737e4c89e1dd351ff2e9d84ccb58023075f63135356e1a382f77b25ec537ac31a90d467d3a2cc7a6e0851ef027f02ad20bba71326be0a40e50d3ba981bbb4661cd00bc63fb910f7b94c627dfffde0fbc4b9b4fde65f47d4b3af188846fb0f4a6800cf899a7d2b619dd463ee504b2fa7db22402ba8b7c1247491a87f0cac18c80b9d9ad862714e35426e716a908a6172c8871a8b7f1d588549bc64656f1716282dda36439020d1e12b9dbf27ed9afe865c9b7e747dfc0e07b8fcf48166f49bfa0172bf942f027b58be50fca68a4e928c528618344033fa4590fd563ed851f38dec401479da18d1d1c170bce8d84cfbdf1ba0cb6bec5dd57d3126ca2e1f80c9dd6896c289846a4bc21cddd9e11855317b24cbc755cca0fb139aaa5d3175291f51cbb943cbba16172ba850692c0b7e4e79b85b29707bbad374ec4f3301cda8dcfa64abe3dcf1a8d87ae047cc2b4103bf89ee45bffed2dbf0fc65af70d6df9d3b9fee09ffce1a3cf7dee458f20d0d8479e826bfaeb980e063c5f9ec2ef9c47443ef753dcf5d6cf3432e8ede41fa8e1b259c39b14bc26d3e77b6c56b1a387edfd44439ee5650aec1b1f3705dd54e9649c78f9f9e77fee1b0776730382ef2735e567fdc7d7b4b899b08efe9046736ce60bcf1c46e6540c6b3ea6b774a63ffb2fffd5afb9b01b3c2d583729de969eadfc990d603bb21c1a273757bc907e71ff4f270d5199b4c2a06ec36b3c1c1d8d48a4fb7949f2345dfd9377992e72776434e359197718eed495030e21a35b64191068aef0b67ee1e986a1291f7718ee82be838bcfd31a195ed44edc3ffaeddfd667c93ee53b1b0dd1d3314f1d24c777445c419ae6ad9468d10355321a509832ee9cc8603d4163e7cef782d621bc6b060fe89e52da6b38551a9b1faa72d3731766cac88e97030214d089864003f0e708cc286f15f3b5acafffb9d64c924fc7e1c1280d92a9d13ff9833f70e3a541a11d3ea51331527352c575a632a4f8fca464d331d978aa7f19fd3ef399cfccc642def6fea83e00fab88ebffd0f3d9bfa933ff913e9fc5371e006c228fa9ebe85a167651a8d9f7a2fa346ebcc0e005376a5bd64e6c05a8da937fe8a8ff286f3cbdf7cd9fe60faf823751436867c0c4a7e823e4b8b3e86507d4827cbc26fb8541d163c7ee4909117ffe247ec659ac9c846fd7cf1b75e7af44549665afbc20b1f5107cba71c98417052c8b38a699f1cf3fba8de00601afd373f99578ac40f399c11a5e333357d523774661d04cace50fb55b2cabcbbb80b36fa1506331b299433ae90c6e680238ed006f8984630a6783488f7de7dcb1b1f1c99214082be216d23870f95567871368ccec5cf0871e7f2e9069188da53379cce94902fc7320a65aaa45148bb729ac139b0d9843dc84dccf42ea3091ffda16379eaa63b1b77da1eb3a2417ceec5171f7df9cb5f56c5f082a88278611b27e3d1e9d5575f338c22a6c6744eeecef88ace4121a7d03fa4e922e7e3780625160e3494975ffecb47af7cf7db7e5d85918c1b12232f9b47ac53f838293aa76e9822312a72ce8e67490abad001f92a17cf0999bad151b8fbfbed5fed2ad2617ffff77fdf5be48c6eecd651feed6f7f47bb82d4790e1264da9b1f7f60ba87bfafed207a4c6790781a30a3043baaaffe957ca180ddbc60cbc60be74199d1a4c1a69229676a6b1bdc18649fff8d2b458b8fb851d02ed854e2f51f463ffcc308d4c3c1d41bbb987c49981bca134fe8bb8ff237b678bc150378a077eb246bdb17346dfcfe82a323eb690e307ce6ef7d46bb99d994affdd0ca19d6cb0c6d692ee0045bf95656cb4d387060cd372ede35be9653d9347afee3c027748a800d0294f9999cc00363dd7f85954607bf731d06bfe8161d29df32b281c11ae879bd5b16c7cb58f51c1cfe536d8dd338dfd622fc71f52af3e22e88520a1ea97c8ba653b1d07edb0b6dde1ea6c3ae43a77a44c07481c6c7f40b63b081f5203f20f7823a71a69dea64b291dd303ed0f9c413dfca08291b7896053d77c34f7f5a8b74dd70508406051feef8ae345718532f7d4e417a78ada4343ea403d37019f158b3744d835d5e0f6aaac7eb2d9efa68c3201b2f1a7d345dfccfffe53febeb53dff2a606a7ed19ddf864faef6843845d416e48f52bd33b3a24df57fc2b750ee0a8d55dd29f680dc76611fa104ae7cc714157a6eb1f13ff6fbdcc06449e519a5fa796f297b65c6cbbfb140d4541e27cb3cb2b5100d290e924fc28219d9c43004c473ff2c247bd8efac637bea13a7bd53745365e90fd41ddecf02fa37ca7f84c603c9a2384a669c1b9f1e5918dd672aa5f3e2c449be5b3e4d0bea9293cebdd679ed1ae359d0a9d26343d4dcbd0f0d5cd865c1148b7e08491be3205f71a8a6787487e55903a6e24ccf53d0d50e500f136be8675e32f63a34ff440eed6affcec99e14e45d328389540434407fee8648c1cfcbdfb2e3f00c753ef3416f40e8e76af98fee92ec59d9d1d2f7ea28711a827b0d95982973bb0e8d09f0ec65a8bf388d052ee06377efa80a78c7c80f549cfe72b979b0afa9c9d431a79b3e2831fd217a37403e245cb36067c40e7c91418ad7dbbf24606534f6e0a34026c816747bbdcc4b6dfe814fffdbfff0f3fafa29180cf4e9a22af13cff51afea673b05dcd4f19bdaaedfcea8f3ca6c0f1697624e1d536e3ba72cda22b6d891b489eafa113a33332536604582f7aece66607cfe070139da35dba09f1409a1b023700a67d9cb37cfeb90ff926ca33310e01bcf1c65f7b4383352edf7b61c3e4c73ffeb1db48f99af75c469d258fb6c47a8d9f0b6616c3cd84df2ff8e847b583a99b236bbbb323995097c24e7fb46c8d6400eeeb4855ac71091b170e6d9d5d5e78945123af5f509e39360d92d18ab93d2319d38727d4981aa82c7bbf002a4ec05356ea21f02ed8691c2c94dd14e53d9cccce1953b52755c975288d85a9d977b4bdfbca2baff8213623c34f34ef7eebcdfce6d892251dd0f7b1c73a45c086d8ca6b303450f875670b1b590bf06880f7da68fc04a6ab8c823450d60f39916fe778c3819f58e246f17f7ea105378aaa88692b7ac41f8c7cba79a811f27c31236deedae8ce0379ecf9a6d73a79b00c1fdfb1c58e4d021a319d8bbb349d93f013ad3d7eacb38c6c0865bd96f51d3bae9e7aca766e220446633649904703640386b07ce55d57ab29a82bd1bee0c40675d4b3892e533153756e32e58f5f3352e95755d5b8f980119b133cb466d3854d193e05c83a96d1051bb8d9e1639eb9b15bcaa91ba6d3746af462e6c0f49132df08ad3172259f36eb7c7c2c74ebfbb9cffda6eb0f3ff1d6033383a79f7ec67ecb8c255490b65d9e6d3f75b6716e3a599d65b9ef7329a393f922a155c48a80e44d4fd104e6ce4ce560308d8e4ec6e257db71535910b653d1986b48e225032ca1528e13b8cb3185e0ae4501f37ca6446c49b36bf7b4ee40c1d7c96e758ebff88bbff0af40320d0417fdf45f7fec60714d23441e70b44aca095d72048a7391f06357b181ed781e2bb016e1ce9fa00f0969fdf0d73fcae97ed66c19a1f2022a7766604c293d728a3f9d021cb69b1fd3af6cd22069989c20c9d4e835a799d6f2a07b37561a1f1b22b7218d126b70511af78fdff8891a627e80904e46008fcd0a4e7870f3600d062fe47353e1813530eefa84b405cac9a4ce98aa5237d435a3019b099cb1e4743cfd9b76835d4fa9f1d2a1590bf1cb2b9fd21bde6c26f1e901ea13f9cc0878d19487d069d47994815c6e44e8cb71379ebbbda64d9fd915c9145d1fcfe1bc2a0fade18796b48315946916def06277d89b28dc4cf91b5f7a73ec8658dc261f1fc4b7d73e71d3c92af82428ec1a975185c4f8740e70517d2b9fe69906a3bb8ca66e38f8ddb77230352301d88bc2e2a824c2c5ae310c5c10b8fb5dbe2aac673454b6bfb7a1f7c3385ea5f69e2032365f18f50874301ceb16110c7114df0a1d2546156350c41f3788ffa3e909a325cfba980eabc43710eeba1c80fddf3fc83340bcc1230b46204633d60c4f68c70cb9bd63d2b0992ec18606fcbcd64974defe2a26233e811d4a3f3bd20da2eb1cfc9fbf6988cb97e82ae943eb860a13c1d816673acc9497f596066007f8b063ca9a8651d98f11e4133a3da3112343d765956b42aac481c61a8f3da11187b5135fff6294c53faa01eb4a07fabc3625bef4a57face7541c877ad623159d8a9b048e68fb22ee1a13990d24c9d381d84164adfbd64fdf721de057e4b12c61aa9a40edeeb689369b1b197259e3563eb24f99e1f3b7bb427bb3857f32fadb3006e70c273d0a5a5d706618a39c4ae663921eba85c050cff4e7bd47d9ad0acb9a2e67180027fec44a45f861e3319dcb7391de598d278e4c6f680cec32d1c8dc9954886cee9edcad18056c6b4542ac505b002f159cc0e18c72f0d70f36686b9a9d464691c7a6e3d28999fa71774616532ba17baa4787615b1c5ad60d04a620ac7f5e7cf1735a9f710ef1c3baabeb41b9a63834ecaffff99f3ffa8fda4e7f47d34c1b6f2a3555f9e1f1e1819e09db4fd88c6d3e32a4f8bc99007bcca3bd1e0ceb6e4fe761fd541e74008e693122333db401628d9df96ebc3e62fa29dd28241f19f6a1eb85ce909186693a6b5ed6b7d88bfcde4446909f19f223216c60106c872ee8e7338f82319ae84af1c849da005de0cb74f4a31fe18ce9c734b378d30f973974f009edc07efad33aaba972747cbf40bd83c59fa5d2d8c80f6ddb8581ba185f658d0b3f63d7f27d8427d283e9ddda6f8c8fc3a55c1513832a8c439896f0b09603a3efa88371a761edd077c0c0defe889198cd2b3084c663bf1b29cf3df82e1f73f1777828adff59a8e7278c681c6e64e2c35d9235111d8d06ce2b28345edf1624225246ae1541b6518233f6602777794626647974349e46024d4f19cd807123e11d263a0cd32c5e3804c69acdbe920eec467ef9cbbf6f1c3a1a0fede9a04ccfd8ee6644612d4760c43c2b151e344ae206caf133c78e7843800e0f8c07d33c14b7414266947f5d5fe5656dc6baa56d015aa6602fbca04fa9bdfc2db3f51454f5c4fb68affdf055dd3c7e73753210189dd19751169e7c5f239fe17e551d55321552074a48576e3ebc9d4027ff35d98fbd99224f0d485f42ccdab6d5ced30774a22f7ef18bfeb2338f45785ec829a05fd1b63d4b147e56f90cb5b371e4dc1db12a8bf25b0ed22b445c6f7c1fccde78660bbf8c4e81a67ce002be71272efd8223f442eb064c135503cfb3b27cde8bcd0fbe248473b3930665cd210ea7ddb17699ccc03aa3d36069d4fcb6f19b6fd318d3f0589779f3439d8013f3a03395611dc781523abbcf3d9a027e4a58fe6da305ea3289b706a03212cc91231a0c154d21e7fde8143d918f0e662be16cbff379031e6abff79e7ebb4cf894b17661f4e8740e18fee437cd3ea42f733195e40736902defdb5ff80c1cd60a14d0a17d4c4b6b8aecc4e953e29a86f9e3a9dabd64fdf8b57fff35efa2c2278d3abfb0c9e8c48d888d87d62323db0b2fe8a3aa426654f2da44a27ea291986753dc5808c5471f4e717c552f587e5347a9b82930b56ddd22cfd34068f40f3837a8d735d2fdba78514ebb82df19ca1fd8436d145a465dd672e074baca14f3caefe471c31b01928d6fc4c46927b94c4033974f7c935fbadfca5c6bb20aaed0f78b2b9418da3b860c42dd85531da4a1371670282399d62054561a4cd61c1861539308992df3c5b2f0012c6da43a2e0d9446c1c3547608238fad6d7600f5babd627702b988c6c2363baff34347473167464afdafedcb27166449146af6cb9442ffd4c97a028406c5c848e56017a709fc5bd4ea1c1c194ac8fa801d3a773cd1a732b75cdef65efe142f1a0fd35ad647dfd516b5f74e473ea342a675fcba685e7e64eb99b5893f55a0d11d9b1915d1e9f9e75ef72e220db01b13ba2378cacb94903ac0019955e846a11b179b16dcbcf8f6093725463b1e60bff8e28b968d5de89bbff88ee78c8c4eee54d231a39770843b5e4c5a190e41335233a5c796f2bbb62717cc65f9e7c83b293d08d0f6885d75bbf223cfdf959719e8e28f04112fc0dd36be781e72412f1cde049d5d0c7105b6a0b1b17ec9a50cef43c1a10ec89a3fefdb69f8c6f1fcb198679e9e456d9482cefa388643aa26ef98354fe5c216fdd97acd77eb69546f68c7cc5882f3ab1d4c85a844b6d5192d098c34dcf97820cb668425989f0ac5f8f44b6d1c9f797df7ae74d71ccd9db8fcbdce81b9f830aaf0909c3f5eb9608385518a69229d8e5d471af5133a108cec86ca228f0e6cb97fe8837ae8aaf5c5d34fff2febdd4fb97d4c47803ea63508c7a5e8d03cc361138829303792b30ee18badac7d388d6f9b2598b5129dfed557f52b969a3ae2bfdac90d8b1313fff25ffe0b77988f68ea886cb6b5f11f9dc8f53404c8e4c12f47b3befdadefe847f5b46b2c1815048aed941fa86b6c07c6b346a6af4cbbb999d46ec7baa45e84f840386d3c514ef8e95370ae7960d68f86e41635722998603b490771ec36603076846cf04da3f4cdc6c77dc2212dfc1a6fb63b75e2e01ad4b673ad7c1a2f0f74991ef13c83ef6e301af0611bee64dc65378f4c8b68b45a328b539c8d9d092be146cd3314eebe3672a47246d22f70eae97df942cbbb6dfec109dded691c2a0cf7f8f906177c57da38cf79f1e71feb39a67fac45986ee541b04620d9c8c89ad14d14e3080ed1f292266710bdc3a84f1db080a47e91c188839d34c25fe820ed9bda29e3ad6e1e07fcd11ffd9117f6ac93d828e1b57c8e42e14bfc96ca1537d1121891dc6ee028b6fcb81e0d99918907efe0a92b7a26c107673385e5f44dfccabaec131fd3b1349da86073890ecf549f802fb79ca9610961f3898d1bde1c785b1d47db435923db01599351cfec247293e3edf2cfe81375ddb4b29f2d212e9be44d74d663f1b115bd292b2cfed8ba9d74306cb9f1f1ffd8ed322e134c870309e3d04d1bf0793d75003ed3452a288eb3c083e254ac8c8fe2db64981856dc51cdf5e6d9987018c1f80e23bb8cefbe99e343ec6ef17e57471a98a491d0f8c5658c9c48a570e60f2766914fe361c1cb0157bf02af125e7fe9f12aeea0d3467c17e67508d63a34203a48aa43d7246e2a4bac5c81c484f83a6f36b305ce68f68e4e963ca987d67532ef606587910f92f29a46ee6e8caa1cc7e2392137853c5b5203d13f4e7c33d57aedb57ce8e7fb7ac6c3b1287ee2f5777fef77d5c93eee1b0afecd5f74a1c3d6e7d130d7dcb4e23efcc85b0b4c2d797192919dad733e61fde28b9ff521e569438b17a3153f7d45c0ae762cf2a73cd20446384eb9b3a5cea15adeae7ef2e9279de7fd33def1c206746063e5b96773e4890f9c12dade4ede2e980b62d0b13e6e9cb6103b4ffc5f96aece96d98625e6c04ff96e6582d32c4e9aa64f5ce435df783a19957fb7f024205d0790be37c044c19d62100ca9018ae968aa2a29a2c5be461376cad8c2f74369dd59dfd5cb7270008bbfdca159100f43c39b6e2c3c7514460e3a1a9db88d818eca193446842cec67f3430cc1e7ce4ec7a60cbd6341f926beda6de7a908f87bdac964eac7035d9e35d16708e07077a661d1917ff643d68991c0a8c12f8c700c8a91ae95c1d499531b5ffdea57f585a6ffe987e8be3168c7b4777a46ad066c8496d018db09e846391ff461538687d57c9a8e9feb653df53bbff3259fa4607ad7b713e8500dcb66f1af1c444d151b0d9ceade0246d68f7fe2638f5e7ae90b8f3ef9237d6c56dbeabc65ce07793804ed35a2a6a17e1636a3766bbbb2892bbffc9b4fdb08e68dfca1a91fcae3c4699ab285373618a6cbf56b5bc0859c16791a3f7e77f95c4efe809abfb3f151a34a0ce2ff4f301da4ab73c430f8fb6130d345ed64d18978a0ca33ad748c12c830a671636074603a506d56c200dc406361ca48e7e5854916f5f0e4d914533a1a1b77da1a4f85d3c878798f1104fc86550902dc9786876d94baac65d86d23e6dd36027ab253c7484647e26d63f760e1b353c76971ce1fbea74658fe58cefa14383b813c4c656dc74d80ad767600d7a302e1b64341cf1fb6f2d0155bd87cc88164bda7f5da6bde5667844487977eeb25adb53ee35318b20e754d6b7b9c4b032159dea4b7ef7779714c2b04a6943cf7faa7fff49f09e95d4f69f9e9e1ae15cb83b83edc5e87dbe68dec5327f267b933970bf8c5a368a50ff8c9131cd3109ff893be17177fab5ccc215e3a5656635008ab9391a982205d99df0783c6018f8d0306602518b508272ff870c7a461fb472894a7e1e47855d612980b3b6fdbdbf2f0a988f09b0264205e9d95b93e53c027d8aad794cc5ed385a928d323760019e9cc5cccc0f7f3140d3facdb465d152755bdafb62fb86d9bd3e9ea148c68f92647e8d9aee6bb219cca8726eb068d9aea0c3c5640a7ace3520ddc70d08f0d063631d848e186843aec46e65b899a92ea0645874aa7c277bca6cfdbcb7cfaee87deade3cbba742ed67ed0f677dcd8b46064a1d3fa9c679d1a17fb5afb0b3aedbfa66f7094c172a6c97e3155bccb4bd6cbd6db06593f9607f9e217d618be577cca4e9afb681fa22b5f9cebef302e4012e14bba3ac129f2964cdafd11aebe018fbfd5c98a0eb0a14a9f30caeec02f150507688c37f12d0f8d663ad8e9c3aa6a447c229b46e0dfd0b22d31e894b3d3d10e35fde521ab2b7cc5ac1dd841eb68952acf43cf1f6b0bfa4d352cd3cd948a51825186e91c0d929db0ada71963ad052ef9d8a63f1a38272a807393f8a91a396b2dbe1da12d82608994691e6b8f27b50665dd047f8f569abef169364657f42030727f4067f978bf8c07d26f6a43a80f9e79ede5356d75737a829b061b2dec10d2a978f8cb3638eb389fa5942f3932e51dcd69e8f0eeda9387e174484eb357b615382ed8555f34a6b8e9fae3c47379794c9b40071c76e217c5f8d33e82be659e384e638712c82faf3b3837001ab800a3c78a0d4a9d1a41e5465bb43b077deca330f0b22bfaa94f7d73c2c05b9d2c1a6d67805822101f4a53765fa8f3ed105b2bac8981f9785577ab047f5b2f4872574f1827d8b0eda7ad0377c82db5701a0c8b79460fbd1bec7f540dcfe2983272b01657cdaac55355463e16e9de7e57a7610a063ff85bddd9aca043b13eaa2c46639e4571529dc5fc8b9f7bd1cf907c2a02290852606383738d3cfce60896ac319cd3e24cff38bcdcf51605f9fa934ef06b24a3e3d29921a1637de73bdf76c7a163d2b95e7ffdafb581a2b59d4e9160231d8b902dfc6cff1bc00583f49fa9b97f7b4bdbe6e7d473e12911fba3e70927ddb2d66ff3f54b6370233272c937dce244ce348da2383ef100b8dd9c157f839d0cdca87982d365ec3a8d2c171a21f9da7296221bf8701992c2922d4ee3417274eabe3f09573655eaa4f87f489f02a3246aa2ac4c1f0701a731fb95047534022319c7ab68c84c9b5063fb739b7ff24fdae4bec08f3510a7d9e1ef6f4ea893d07959e7f0e7e999d63854046b1dd6649cbaa6d3b8418b137c69dbd679f4e0a6f0ecb3ea30da286111cf81575ec1e0a43aef5ec187359e3ba9b9a7a37a4aaacec8415f3a99475f99c3491746b23e1f72454be23a2c2c7e5e4309864f3803c82f45f247e7e06b5fbe29d99779b0cf4d063e6d1a38103b634f62bd16ee696a7f6986519f603af0a7fe1bbb70ca818147b8961b785c520eeea6398aef4d9e3ced8f915738f17d7a8e4ae699ee253cbc70d1b5f4e557194b998351ed5c659328ed15fe501e3e6ee1252ce32a7312de07a3fc0a2f8f1bdaf83a201b2217b893e9c33a340c95f08ce6677a2e9403a4401a681c690491a5b48a706665c1b238de31d4f317ceafbdf3162f84eabf7a0c53398ef0300a70f4cad5203a46113a259d8129181d8d291a8d8f3246460eeaf21903be70c57a8987b97ef0ab972ce954dea0b156e831d58ccefa47c3674aca5aebd557f396317ab31e62bac7ab398c84f8014a8fc65ac3fd9a46bfefe91b1fe8637c757ad65e20f9599a78706340769fadd56b1cb3f25946acc4390ae0323af221994fead02c27e37d4398860b4e75274d80b6b06bdc7262ca5a5e79c009278f339dd25c4f9af2212ebc741b461972e18fd708b9a1602d7fc125a1bf09e553be30b8035b04f1db559fe2972771f96dfd22bf65ee644642e309256a9eb8b093d1093f71ef4b97bb9d200fd1387c86513b68ac15d815632b9f4e468388abc229cf97aa832af4c611e15cfdd814a0a3f123813fa79311248f8d15a6676c103ca7c3c1bed3e9ca79463a52b7d2797ec3793f1a3e7f6cc1333a72a0988ec7ba8f07b8c8c3177996475dd6c2113938de61a4d3e878d2775e79c5272c40a513785dc5e6874626466191b8e1b3aee4b57946b5b77fc61a2f36b8330b07d9f1891a0965d64323b63a641b053e641accb7edf906e10bb2833518df3be130320f8273bac31c165dfd08db866b9d033ff15a5ed9a5233e61679ab22b8fc2aefc62af4bc58f29064ee03f1d2bfaa794eb6d90ab1c7af695cca298c2a50705aec65dbf26866670af36b49cb838859107dfc7aa00c27b0927afc22bd1096b9a985086571a1752beb823855c0cf1e70898beb1f5ac8ec05488edde3aa7f477e318708b2783d430e964748afd592f4dcff4bc88e91aeb97777f95d18111489d4c9d8617fafef88fffd8a3088d9167678c628c6e3c66884d7428d9aa4ae63b8a84da5e658da7228f246af07c5b846930cfc3e22fba0bafd7b38daf75e24f34baea0818a32b0d1e1cd674cfea277659c771548ccf056cfe48457674f14b84caf18c10bbb1851b02370abeb8cb2e22a33037113a1c5fd20287d1123e9d1ea3776d899e3b1fffc65e11d917c695aea9f90306c23da1bce3c7e017ad65cd37bec22d6b462dd24ec615e4f467a863fc0b0c1ec8e45fca8349293e3d651863f0617596958fe974818656e54b00002dc1494441543f7eb90bb760a3d5a7c499a388e0048275cd976163731a3cd285376e391a6dd1c29b02f8b326ea8f50d07879a9914ed6105edb28e0380c1e5b4e39868a3b3823156ba04ca34066d3e21dbf9ace83691a17a327816f7ffc5dbd8dcbb31d74829ed8f6e3c8d9ece8884565309d1442a65bcaa30b3c3997c86302d67e1c85e24c1edf17fcee775fd16f85e9cb4c3cbb437feec6529b0ec46911e2e79f8f1dc8e5d3644c4979b340fb1a597b89d241e58c7a9cdc6033852f637134891f33e75d2a46abe798ceaa3cc7adb25e832fb209341214b08dd44f000057087e7189cf3123680fd1175e66e545fe4c377f957fa5b76ce97faa8909b12657724d5137d8b7f1b175db82dcda53dee09b7e1359c2a96f71cd1e26378136730358b63ee12e208d617635eecc9f69585df8993b4a10aa8c33f015dc2510a1893636e800dc8199327a5da3221ed2bead861a3e77f5811f7c2a677282ecbb121d97bb36d33ff8b26d4e77622b9975191b0dfbcc60d4a163b1d942e80e22d3b986a82c2e83133c8d541a1d594b72ca9fd750f8c547b6d1f9ee212f83b2e6e2847f6e1ceac0acdfecb9543a23186bb26cfd338ae506c248e3b784d5c9d80dc457cf7c4067fd34c2f2e55ea67b1c877ae123fc56d9873df5c366be1ecc7943d7e5f8045ff9cfc64c43c22005e0ad57e2c20a37602e298dc749e39dc28a575e273de96b70f900cfd2d25374ea73a56ffea40596bcb4b25de151f9e557ac1ae069f72676f3ac75e3928abb8937bf82afda14ee2d7c1a337a8d8ae54c567ff71b7e3ffc8e60b30cdf8a847d2a07b979439ad1045a3a199f0c38479a2b4f57d0e8987438576df0736650273934357afb6d4dd50483270d9e87ce34eeeeaa859a4f54934ac74e8a6c3a6f3712788ec768eb1f43d748c5b3b51fe8a1efabea507fade34a4c4fdf7c8bd76a1891f3ca8a6f20ea5c7dde959680c3e9a4fa154e75b26eb8f0c3ea34103a198f0638018f0daca33ec98f5f28e660301f427d4653ca1c87ca4dead4756e69634fea0a9ba64a57e7a86f1b837386c2f133b4ce4fda784abb2eeb27e57738d3077470ee2b6d1b041bbe96e90cfeda37044084b32d0c2444a2640d26b556b8caa36cd3c3db226ee8a2ea965b7f3486f9e61151d09c72816a728e4271608d2ca1e3552561085185348e32bb1c9c06e308017b6de8200367fdc19d97f5041d8e1f07fcc53c48a5bd11aa13f8d56b4a4413d705873453383a99de15d33a843515eb210cf40e23273f3495634ac7baad7cdc01d411505255e3518f674e1ce0650dc7e90c9e473152f1d0978f5cf2e62f273cd8f1f3ec4f17a6a4d5d1bba678dba35724d5063a3cdabe277b7fa26d7ca68cc8a373d19c19593fa1ddcc7ffecffeb99f7fe5ed820fd857745a42a7acc863f4c5c6566ee5908f57129b5097ea08cd229ac2d2928d5f773c288ea8cfdb00c456d98fd8e183aa06df622ff9b70cb65ec3c954d4fb99571a1d09f03979610e3adc4a03a69042d3d6aef2a1ce09616b2617be8181535ad2183aaa2cfce629ad6ede5d2ca0711dddb870e2fb028cc7061757f91b207a620bc8fa03876d7c1a94a76bcaf34d07d6353cffc931580820ac91e4af015e611e1d58d3648791d7cfdf18120ce6d9925f7b51277363d6d4d24138347cca79484c47e4c02f0f7c33f5a353bdeee75a74d0f5f11a19c4c9954c77a5876ca963eb03a69dfcae32bea08c245f5e6207f1c37a378bef4ff0cd454f576d26f6e4fd384664a68a8b179d6902a91e07c2c41e611b73ed63e41118c9cbc30a18aa0b821a06175871cdd768b9c1410b09a8f82fd4bdf9011ffd26024bdcc401403a0be2e0bff898e77002785f18be8bff81630acae109dc31f22213b94953a8d4f0da3a8097006c8aad9f5a850aa253446cdf84027f8c6f8647e1e113deab93d14ea928881aaa50f337716d3880272d365a84a46d8e4276068747616f7e6834a3a1f25a0adbf8e7e60704558978f1bc618af35004a05ed9a7933d9fdf097bfc87fd5a94d67cdac1644dc6e84487fac563e7615a6d52682df583ef7fdfa315dbfd74484e8940872f10c108d24e6579962ba982af1d2fe1f939956888d19be929e7247990cddbcbbc6cc97b61fcaa0cdbe9d9f113330c8140c132f9d54a781ba2cb94b9fc848d7ea863dcf201aebf455f9a2977193cf9034620ab6850863ef647137806cfe8203794281cc4a40589db2897a8b127f2843c79f43a7572f9b08aceb77c5168482f05955bf30ec52f989659bd55b6e5d006a3c1d67b1353145a6031387602d70c65a1c2436123277fbdae8e348c9dc7c0412c7de0a92ce4061eac7431caa2bc47324601edbefdfce77a81532f72a2dcfeb0cea145ad342877118c8c51a97c762c1929f65785754712126b2a3e76f99d57beada9de1b3e70fbbff52b8a1ca6e57405af83305231aaa15d4687e8b84fa19057e9d8843ae033656344c5163a390fab3905c2bb556ca5f3f5297601f90605d359b6d27d83d168ca68e5aa396cab3d9859dfda64e118d7995d569c2b1f94a5acf562e54b3bf2285bf493ae941cd23e258e4c1140ee8d03d300e03ff2caadb1042a09dcb2a4536333b1452004bfba3ab6a3433b6a1b8d32fe85822974352eaef2a376d88219c08859e550987eec899cea52b5869642df4d456110f52ecb821ed2430e383a560593adc060ad2865308c90c6611c67816c198363e2e24f19f876036c9009922e8c0afd054e0abc8daf29631b6d940fad696c4052667493c716469bc7bd85cd8e1b3b983ca4c5234cf3f86594af7d2daf8c302d64678f45150d1e56d807bd0068681817cc41aa2b571dcaeb2f772c9e51e953d2fcd8831efcd291f834991ffaeac815a314eb3f3629e87cfc84947d2a866ca8c05457183bdd067afa99e206704feb0b2f0c07405b19f13b4453572d2ba1e50eb5a24c41274f9949f1bf02f9fc4f7a3449e701e108c3b774b60bfea00c5feb3924c67311ba03acbe930622b8db90cacc4f7c72546fa8950fa9af1813bc40c727f01b3ee05b18f4e228fc51cdb276d92ddc0ca01831a121532ba6c0387ae432ee5bc59b418c3c2b7b551eecaacd10444d048f736c18cae96f8461881521d21fb874329efb3002e038a68afe0ea3361104b4d13526a270468cb8d50129d182726fe3f3352a7500761311c8e6023b79ece891cef45825face8637123c748907fcf507ff9c22cfb40ff9f066a3e6034ff3830739c7c83736723a449d8add3f4e87a853211bfbb0d583231a2ab135a5887fd19c82fb6c5b76a2d7e0924ace20f3b4dec98a215212c2bf0deaa0133f971d75045578eb3a3e3097536900215cd858605ac15de7c8d79fed3783c0696f95315dc279e8135a3a22906059433ffce3a7299032c121bff1629d21869b636d520c0dbad6e720998f8d1b9218ba70e1017eebe44c9782f8e4cb4315979dc022b9602e657ae255b996c1cb2eba2871f2711ae348082f671879289d4d08b6b579f6e483bcfb25e01088ae32431eddcdf3700c15cb33231ee8f231183a1677a93ac436688ae68e851a439b6a9ece953d7d777ea676eeb41c8f62faa7d7f5f951024e8770269193193e6e25b939c132d3178d546e488c58134efd698484e5bfc1b9134d1d9dd6dec1194071e05c7b37eec83bf801312e482e1607df7daa5739aa7cd151cf786df81d3c30c9bba763dbb90b3a6e369d8b87f5f6fff05384a8e4504c015c1185ad3301b9b01d77181ad6cb094b7acc108224afe253da6e2be542dc1bc6595f671a1cf285452b6fe15384514b5a00eb0afc54208c282eb35666f3b7d86144599d4925368d583f98f62e9a3627f4ca4b7718a12c6f1c721b90724aba4d338ae44333bc090d9fe80b9677eca443f4cd2855dd4164fbfd397728fdcc29d33ffd18037ffed1038d8e74b8a7742a8373895e4fc1547fe5e11867236bb45ceeddca3ce8f3f211a979da076414285b7515a35290c2183a70e3026fdec978de2aa3cb3dc19a1f45453b6bc0691b654ee13272e85826273f30c7abbee29560f57a2832026b671beb6e0b701f6d86ffd99ee2bf2937dbc82319f4e663e48d4f8d7fa94bd3a5bda0c3d66390154566e2a631175c6f7c1408e03694f1052abcd250d2f47dc2cf72611e8c700b0a73ea23eb1a3e80f31e3b8c1acd78987b1be46a3ae75a74d2d880c590ead083a0fc9cedf31fe4cbb9cf6963037dc54de6e57894f89830eb375ea8643382cf9df1c9343a14a7ed3959c15bcdeb1ca33a1f232f66f46e9ddff71ee6a7c2f85232b65e532878bd0c1581bcf186c6c0cba534055ba28d2a2471f9dce00f9e61b6fb283d789cfac00d5e77dbc4adbce4a897812b019fd5d194aeaee13552c01f7b17292c863e4e0a2e55cee08aa788dae902d9570a634e398e2c50ac45cb0dd025dceeb3f16ccb6779ebd31cc777f7c12a8178ed2e3ec4b4ce3b1d5ea627cd29d4026c9f2ec3a066bbcc192a306ef3ef49ab01f3a6f45b8ff2d96e7eb8fd6e985d3f7bbc1cd390ab1f3cb19d351edfdb603ac7363a9d222366a6926c46d0a998f2f9277ad4b9f8fde0e7f4109b0f9f428f3edc00ca1319deac38ea0e2d92e51a9dea9fea5f3f19031e1488a969c993468842ed28b7c84e635f65a201bf7c4d785ee0393827f88a6f8987dc69a14bb7d0c2cba061153d6335e9b9e10e1fe0b5a5fcc0b2cdc30138e885e311bc511ce084941f5027ebb79443042ffbfc608ace09200cae01f7fbc6f425515cff5d7d06ca897ba6970b2e7cd2c9545aa64739ec56b68e3bf14e010bb1097b480e81c5b0c9dd887c8006e3008f667aa83b1b1dbcd0d8d75ebceb2792387248cd100134346047c51a3702d946f76b2a8af9dcf5873fcc0fc865f78fd18a737fcf3faf13f77ae8fb943e5bc614135d08b58d7857988b5c63a96ae1f1cff2a78cc672d1a7bc8a418cf6845589d44140b92214e38cb94b4e5e67fa2485c6a4b7c0c5bf74b96521a2d60c816447626c5f752fe81dbd4d1e287c6f545e4ac05f1ca7bcd6448f29abaed8bda428a96ce517c5f69141acf1a7c4fcb71f116f198816f252c764d56268ef89eaa7c6a09ce92bc9a9ca5976e7e3a6adf4fbe293106135fe4c2f1c6c10ceea58ca7ad88fd7f09c51e1c133a81c16cea8c12b2f6ff92de9d95a87661c1f23715860312c0edc0e489e337e2fbdf492dfa7e2b47ad65473429dd758e621385590d3f6a84cd34ba87dcd9ff1b6397650165db65f8abf704118bbb7aec1bae6afb476e70636b5ea00c092637f212a1d1e992e8b829bb629c1472de31d6a0e463a9b3363eed6b7f65743e5cb8038ff5d7b96010c7f4f7d06b698a69aa903351c305bef4e5e2eb649b0e2c5fed86d199615a2d83798ca801bfcea1f3c600f85b3acbebdc6276d64b2859fd42a2ba35381e2b40ce40ddbe9cd84541a9b699031ca572930cc43658c1e3e62a5f50e26b326e3e76fd8cee76c23b4fc0e34f8950b3d32c425a98b7390cbb4ef377ff3f38ff8e54446447687cac3ae1c1a47f08693e2d34ed2d5730439bad5c3d642ba68179d594e2307e112e0036ef1c3f7b6a25d0e1ffdd962f3696e33ac4ee5459cd0268d2de878578f8d9b1b54e990089bc55b05a5367b659a2755895ef38efcece2820523fd473e8803aabe754fb51d65e31f214777c860029f252d79242002be47a8eefeb0108497b06d877eea5b38271c1efc15d63886c0b0ba6cc382a3fd8653298055887419957963b38450a1b0d2196867d6e1c4a95ceb811384948788b84bffd8fad654913f3f3cf6e687ce08aa93bdf794a8fd6c29cf5cb28d6a0ed22fd272ddce03de69df934fd639db69accf84b0dc5d1b805d43edaa2f5a5eccc29b4ff9ad1fe1513ea53fe3961197df594ebafc1bb7528b5f1ea5cb888c5c410e3fddfa8c8283a3fd068721306d18580ec9818111eac1858cb2092e1b7b521ff7e3816e1d4d170616415e84b91f0802f9b0707bda597736dbff80ffea23e8d31647ce3df8a71f5d1f217ab05ecafbae5f3180a076eb68304e01c09b0f231a00509129715b16f8ba9ecac71e544d98bc677baa15afc7e8641a697856c6cb94744bd664accd682cddb88897cb691895af63606dd02947d7be235687a0fb7dd4b7acd022a17438e0bc432f7cec5566fb65f3af9f16ee24c03dff4e3cebf78082c56b7cf22d3ffc24f62b24d96be452b871ac7d1afb92bbf15741d0a034b11eef3b462e30bc60f2611c0e672742aa492c7bcb0f2ca5705a82d281426578640ce7d1959cdb8818a24b777e17bf819df92b5e6d407a836193dde5ab54897eeea13e05f94c83ebef2e864b9800dc01189599b8f0e4aff837791ab1feacaf78d8f3c424222e309c34b88c66798193531e39b8cbabfbfa092f8f708fdecb540fc2ea759bae7eb01cef6f9053c05b567d1ba3c768689ca6cb028ec0aafe828facad534a8c6b9bb73e4b9650d0e3e4e5f4e0c3c1e52742d8da769227afdbbc250f3678d84c16792ad3ffe4ef612eacf04d67a9176b3b3cf86726b0743a79b8059f36e342f142d6b9913432673d3d58a6eca39972d932547cf865fb59d63023416a541876f7db157fa6ac69e20692cb7665f694ba3467f9a9129d0a3bc3abf572a89c2dfc8580c4b3d4f9a851e2e4725d74a239d3f0a8a9964dc68a6ff8f28c74f368365be68c6854242f70721c8a914cef149bff0dcf51e4944b1a1c9c5ff8a089cfdc30001c3646357b3832062f2e0375db565e0fc5c5b51e95a7b8f0d22ddd2893cec8e26fe942dac6fa32f4c0843b8a511e3ea60a6b034313bca4533857d1579f8d334cd1a024035aba463bd3a26c6d0c571a6509b75ebb43a66ce92e06d681d8bcc40514649acfe605ffc51b06faa3943ae64c2a32ac23b40a1325735ccdbe0a0c7cf1759e0e9bb0e177eb0e450f534bb2755c909dd0d9c55b07ed225294393293ed706cbd4bb795db5cec2e59beca605863eb11e51fd7f941bfc0a9ce066f4eccfbd3dddcadd8fb1896f039653badb29647e12dbf29f0a801d383af3c53bfaa503ce2ea6a9a2928ec2c275d9c150b76e2165edc963546fe89236a9b80ba84e2b91eb695cb07c5736cfba04addc017f872b912f809d68d9383e66ea8ecea575e9b269c2c3b0a9a49933e2c200858a7447286e9e27a1b020638646edd9457fd3b3fc28d0f0371b07dc31d2450aaeb70ae24185b6875dbe566b62edbe6055a75b021b08b265b5e6067bef8f9c68772149690c222c764f4db154739b8a5692c2084369b24b43833ea40a58027145c06ba4174b2d9fc9875d9bb74329d616444e3c33279d5044ea187477921d750841abc71aaa70b84252b4cb777a6543274a7fdc1eff52edf9634ae9c60065a7ef525d0fbd2c51b2ab0925c9e83ab60f69d3c6721f17f69c6848376580011292478a99c5b57959d6a59a52adefc83b3cbd4f2cd74aa720b5aa97a61e489b4922bcfa8a3b4dfb9b37e61506a72d19bc4d6275071b402624ed920b30b9da0d8bb26cd878a6bdba5495cbc7186d8f637ddf8f409b0d6e5157ecdaf131fa7f12771055053879b0d2e4d63d7a64aac32c8a3fb913cd8a9b9cb21c8e21faf97e459d94c0d754a9ee9e2db3ea80ba32d7deae6802cb64e9c46d69629086219dc92ddc9c127b645f6b27330e15dd88d9c4b3938d772f2a75f6e79c975878e29dbfe0fafe826369566f7972c7070280e92e501506273a33c1555dae48d4de10aa74e0b784f22fac506a7c3fec626c8e85cf0b47f008c4f62eff6591e0180104ba64bd906518f6f494dc9c8ab1d64b9b9c6bee02c5b65a6cb75b11edba1ce5b28f4e8a6bffb7c50f83586169856a538f336dcc728aadce29103f70efeb24008561a1991e36bcb95893f52e68f9db28dcf4b8c9a26f80ce3accb9081c20d4d3f04bfe235ff508c1e573bc8e79f0a0fd9f7f1286de2f8a43048cd0b7eb51d79c328acb18f7280d6c6a5b5b33c866422fc519f94dba6ddb82dc30cba576890e5bc6057b9e8006ceb3b366da64e51ee472207bcb6026aba3b7ecd138f00eba04b4c213e02fa6ded5b0024f4604bb3c10116b6601a6ec306c30fb9951e9871aa0799919df2f826e0fa0bb97743f1af3198c0f859c8617ecba0047759de42ec848374d189af2b13fe54aae544688dc121569f8b0c6434e35d2da68ef06177f1e77afd9f2d78810cabf425478087d2c5bdc6e0a3f719c8a1e9b5ac4db2e5d0dca1bdf02a0f9b6c315b1eb0ea0b9ff82f1d2cbcb9de17ac9d68a1217dd15fbc228f98f2db808abb9cb2f2b3eb8dbc75298fcd67db249865a5ecea8b4a3d7538d32d870761d12f058391ecf61b50f319ba60d161e28974a353df5848175dee50c2368a18e9d010a8e3a46f7d5abd1b57d7e61b9bc971295e41335d1ce5886ee514cff1c9f46454878074975c907a8272216f57c411a193a96ce3f3bc4ccfca28e177a47fc6f12aa68c3e57d8ca87651c76ea71ea07cf3314bfb0f731d568e5dd1860d3e577e6cb1b2f54975670b7a8efc737e721a751e42f7e2dd77a8db23ece280c795b3728a25f69930f1c3cf22d2341c76d3e317ad686966c9af217ced4e65ddc69d04374dabdd22374899efcd61d5b239dc89a0a70bbee0adc57ec1a7da05a7c4989ae72290b3fc1565af8e61dc2ab3da1111584ef134a5b1fae3599e96ce4764e91288be1f7397e4bad11c5354ff4c21278638481d70b95cc1f9d8c4d10d665fa72ae4e7cf0111bbf9d2c925b5a981280368d985b1d4f838d7e5c6ef4840b428f70e6cba731683b5dcd22fba44b3a8de584574c75906b6c33f9a46f7589bce85835cb3bbc4a975c71b69cf0c75ff027547672a73d475a6ad4cef0dc7a5da789e5d378d36d9ab3acfe680d1a1f798cd4a3631ef8ce4d00b0477171310a6d5549707dd9729c6a99996dbbd1c16d9284da8e4a9cca05cac92fe62901ba2504f6d0b5b653ee278506986f9817a1318838843c71d32dafb3c02b8cd86969e5723b018c09cb2920e8ff1c12e6d73739f94187e337b75897f196b41d519e6211fbe3b8256b589ffa00aa4e535c623bec4ed9420a5dcbaf3c8386bf78be12bf11a3d7cec383bc8124ac0b3c4fbcc8c0963cab495970c30b26bd11998be8816db940c90752fe81222c3ca3c7690bb2af36363f2c5779711b5be2413fd21c9d320a870eb8adb142b12190a425cc665946093959c1bfc7120707fced816402c3cae136ae576eaaa01ef2f949e385067a3a9ecb053214dba01b5a7008e8f67ee1b47f6de19bd89738a24cea986bfe6442999d37c2bb64a8a2a56dec1b154e16beaadfff68059e2aea557f3eacc31aec1d7fbd2a3f4281d538e094eb7a2ad3894f079c3a51bc6c416e89955ef083c72a370c1c73706e54575a40670edec6082e340bd7e6c284bfb3e648170e5d7c9954af67270137f256a984d46edbe2f2969e99a175517906aff4a5aa4f0a272eec8ad3fce9efd251765ffad424f4e3631c76f8c39e11a88f5c5ccaa568261696f2e01a9ccada00385aa02f338a256df2cb459a880f9c261475e2dbb651a41da73cf690f64846318c471363d7a9c9f8ea86793a1228724f27a25c86e2a859fdc2214e206d13b8a8c7d9af4094807f0f0bc3d79f88d36876ca803efa91ba2f8cd43a7b1c16e311130796e743f98e16919551e6569af8d86d572bd10f5bc0c6d9209106107865030d2c3a9133fae8be4ca0204c9c3a2f27afcde5c4b84de7b59ee815dbeea6a1586587dccab22523acb0c6573d4f3e6e67e6971940ca3a5259681c1005ac436d6a6c39cbe52432164262e739a12404141b3778a98341b844d59f96db11cc03c6e099dd6d852c0eb66b148c4d29227dbb26139c061761a1a0a190473021653b06569a36d62826a3426c1b8b535e361959e0c05d317f7d28cd2608b87cb69b87d23e2aa5ed7d6c84a4b2907f0de0d079e18b872d8214a009d6c332634b3726621fba000f3d24e5b1e56e66c105ab32c14f79e5a0cbe6595ce284e8569cd05656e84e79e855bd930637badfc2b7fccad90768a1d932c22758e1d1b4b09c2c2e99ad8d522aa691152f75548cc6c34dd974b4c937329b5bdce688d180d8725c305c5420cb5d182dc3d034019b3658a3a3e0e607ad7100847aaacd4c6ad3e9c3d3079194ab6d171b0238ad0bf2773ad95908c299afb01376322c7cc18e56dd328c49ba2e51be4999eef7bed499f80514020fa4395ec539359ea38d2fcc23068f65e34abc0d8e65283ef195334f2e850720778e9faf3134d53734c12d5d62f8568fc629e1ba47b5abdc8d436aebb4f915b67c3a24f527f1b5ec96ebf8424070adddb821febb62275fb9d8bf436cc3470924f427e460919f9471a67cb041358650ece7c28fd8e5ca87924e414675609cd86aa001292f2eb16d9a32cba82162542dcdd238660d95b96f5a415c7ec08561fec6fce597d64db1d674b1801d63d091db9edd40a5ae0c290cec201e8ae2c607345652e34819ca93fd9b77cbb419c2ee623e1197df2d5b34c33ea3cd0898aa482e0851db122d0b79f0281fa7872ebc427736c0f8a13ce01ebd9d32b8657448a0f820b1ab76e97aca0607244d95bce1b108c6efeddc91566e8dabdf6dc3a95de1bd7665b7326e4ed0f6c7db499757794722fa543ff846cffaad38d65afced47c71ccea6ed847edf40873bbe50d1b8c43758511fa2272d5e467596cb6061cba020c259709536cfa183217659fe30f2ba0ebca1df4277ea8ebebbc8027e69f9c83c4948af1f66c749779d7d45c7a8686803ee492f0acaea49014b67390b6927dc90fd9c2ccfcaf8dad4da61d4f332ff08f9a0476cf5288f54746de8748d46587cc7e864a74307cdd00b561f44d7f28f11f04547f0535e26e90c918b0f5b0edfd04442f99de5e197f2dda9921f2a093c7d0d14f9d14199aa31f0e25a5f174291101d9b1329b40af6013c8717be5bfc97803a4a6543147ee431dac0303543f2ca962c59e5e19d4cf5a18e08956909e3ec514918e3cb21b6541b8074f92e0ac457e2d5dd43cb377b7080ebba0c47aa42c45b577cb0541ee1514572a68755cfea1f1e1062db6d7dc14b6385c2286ee4e35266076825cfb2330d3ff3f4250a57304a55b16588390a59652893676539f5d143c17c87912963f940125ea462587418a18027e0fc9461bc8008b6bdc0494693ed02231891b21e070a214c83bf6360e1037ef821e8d465d3549e891eb85c71ce3ce9add3556ef2f571d95ff3c0d370c027672f39763a40db728eb2c88e2e221203a8ec0ee3a7719d569bf3c90b0a482998d04e78da188c41941cfe11ccaa72c95bb88ba63d6cceee0fcd8a152c9c35fd2d6d6c882911129ec04d03fde8e0143ae9ef268c9dc0968fc4002f717440f4b704cb6033bf61b532c5b1036451655c49cab9f88d712e65290f152aa14b373f8831fd676fe9c33afa9d313f2fab20d41759b331a13b56dd09ac5ef0af26cb0427d259711ad936a2e2a463d63d9575cb2bba4311c7c368c336fc1606bca11556df00277ddfdfa649eaa4a92dc5f965716ca199e69f7536f0ae9ed563f3ab2f279ee8d405dc3b9c0008f70e7c334e0a3d40f25ffcb001b7c8b4bffa6f952cda05f128b573a841f317a2f441fd31c1a9e60b03afe993c74369fbcbcaa3351f37a532613d2de9c6517f0bced012429f9cf9e912ce292b5fe3a586478dd097874f7ce858150fa4ddc9c4876f30f20381e70983f04392a59909bc6d2020054ce2afeb36d79d2e63aa30366df1454152013cd2c9439b6954cab812829374709047fe967730ee5eab734beaa7c685178f181f25a481817b96477629896bc3b6bdf658cd5c4c50ad1f921f24d5ec56210d587a15e44669043b2275a2c26004069f25035afdb5bd545f3b72985a2fe134803f8ede7c545819f0ea0bb1877996515e5b6313dee9884bd6b2ecfd6b75d984fde8a8ff7a184d5a298556d664ac5bcbaee51b2e0ea20fad29738125cc914223209bd617952372a1041367f7bd325ee2ecf12aed306a5dc62fa9dc1f6cc5a5484e8e6a82eff21bbd4d6125efd00200970e5abd1befb2d0068ff4ad9cf864d343571869c2c9f34ca774d70df9adfb38af488a4b9bb8ba986ab0a2eb548161a52153cdcf7a1ac2a9c2d02f5809a0b53a53bea25bfc682c58c1b43957100ca67b0569c1c337124f5d0d598694e1c683f5fa2256c0539868fb115ad5cfe058fcd261935c4083bdcb6dd46272d3755d94ddc522880e636a90e7cbfc6882ca0b33eb03bfa2e2b34d6b14803281347f7794ada3ca4ff874b2f545614633c1f864f7cf3565e41371377ac07b7834ae3e659d3cfa3745bc3375f8a68fbda7b636e390757232b7615e5e27ccb863f896813e5bf7f283fefc63242fcfc6e51d5e270f6ce22f53e6f204b6e5aa1c5daccff601b8ab6e6e1da502950c6af9380bf8c4257dcb322a88de9d8532feb6a094dfd0b470788b67d76cf64b28369d686fc8291916b27afe153df9d38f2989895b32b25bb2d82de02d1e0a6c25b68e9b01b0bd852ff8a9c0464360a623f4511ccbbf863aba313c8c33c2c1dcc24365c795451062a98ad5acfce52a3e3cfa847ed0811739df7df76d9dc6d78f036aca5839e8d450589d6379f286db4781459ef80a3e6d87ee3efe1716cb5fb5b771e4a7c39846aa56c7f2280ef953f66d3e36deea73b59bfcfeab9cea92bc6acc5bf08a799d5fc6f70f792bbd59034e00977fa7c3a6a5c54d071100fd193238e49cc48818629c00836ffb41b25ec0c8d0da4cb950970ee89222d3e804f97446c1f3df3cec637172b052931e596562be941b5e287901464e8be1007e1f8f5ceb8ef2e82f0ad3ce890f9ce832e0625c637ed9705d1ce8489f74a36bf828e372f082bce6ca1ed2e971c81d6bac8b60d8c7b31c961f7d81939fa0655d960d11985d0312909e501d5d270b6c2de2870b6e1d56baf269dc72f2274ed32d6fbe748d5b8e6dd1692965ff15ef36be347017d25182b5796d9daef2c91b0f12d221f5f5c46d3b388adb32c65f5322d9233e00f357bb512e363a353283096fd7f1498852e53470838c3bb2c0107f734c214256a179ee6ce0835fbc55acc4127ff0004f2413464f882a5749ecaaafec4f35ccf26dbdc2c0769a574a497a245b480723606502714305355fdafbe062b1c2b2a1c0021ad3b12c1122a5a419a8e13fdf61641b5f9d2ce160bea49c0918072722c94758756ebe54d870b583b2136ebfd486122a067ec5a3b8f85be64134c9d05d4718b6ead1a7d3bfe8df1129a4d5b7f1fdbc6ba73d7bd1fd3e7bcb455c9dbccfd317362551ac7a9c427853a7fe1756c13b185276163903007f0e1e3763fe51667d293f8892a530ec5b14dbc63722371778ee1ee5b4675690a26fe50e9351c1726b5763d85cfd577c6ba27264fa5855111b475e5535ba2f36f4864b8458b1517cf118856f0c0ae3613877061b2d10e2946694f2f73efcf52afdae320fa51fb1bbc8c98febe687343af44105f2e870c24ffda2c2b6adfa9e3875e243b88517effde3dbca1857890d0d006e09d50578f41fa71461e253d7d81a5f568f0bba788daf55509c2d2bd827cea21f7f3a3f7eaeba95db7a5b0d954a1ca32a6bd10387cf661272f403095262d0843b2243035cc172c0d31fe5b603fc65637cc70dc26fd40bde7fd01b5f3007ebd9760843fd8d02d523884537d4996b5fa83f6ca275093b7c71e7ece2c9f49aee9ded0edcca5ea131681b3f79a1e1a82b2ff0f2c7898f7c51985f59e17304c0798173bd5b76c8a32c619ceb329c5178e341bb44d0dbf11738d9c2ebc07b5016a87cb63eab68f1c1ea86c304eac1f54bf97df49b26f4d50bf815ffaaeb43e5f7c12bc76aca6d3738e3d2d39ba43ddad8989418b618c587e633f4a997d881d1273fe4aeea3c7890349e2e74bdd2c40fca190e1289f0ce4f5ca5957155ab1a2617c6665e8ef08fcec622333adef8c285b797553eba9c76ec8d8f5b9a955bc40b9244e18d2fc52bdb06d1182b6a885d406602bcfc2b988c667e5696ed7cce33f209825f68f383b38ce7142a7c0f26e61e8631f42cab247c17c7562fe2fcc5c9854351dcb39636ac3cd3c9b1aebc761a9c0dcff3be9d6f99b1468fc575e9756bc7a91f69f401a37a6d1dca2971cb1774fc40be34749ca6179e11f0c0d18aaa9265cb9ee1511a78986cf40301af2f1d2eb6195e9e65728d2d3e48e8627ec241ba3510009867446a3704d4b02e26338300c104993f05bf02849202f89fec7231f6156774360134e04f19b050b454e52a7bdf91ac8eda64b729ca71ce15ef3e78a71455ac0a11db457843a147abfa5e190e7bfbed773d5dfc998e583d3b1fd6b9d524b95581ca56a7c6277e61d113dcd29f58851d5ebc5b2c88b41ffab318985ce3b2caa3bc3a16d6fc2ded3d0c0f84fb68287e88e7891f9bc72629892472278e1587e103a11ed9d3b7f27b8000708906a5f2aa73f56afee4646f8c3f475b4502b800485bd3d4c5c8b20cd283775351a23febed54afba558796594a332a74de6a30c612aa87337aa2323f3831d99bc886a2185a2c05374a1d81324defd2a420433062dbc12871de25e42e414494675dc64775325d64f4e20728589b455e2dc55155f0c2eb816cf0434727206047fe48f72fb0e2c711a72cd2f93b7538f177da626e74ad4c4a4efa60febf5feb9152dec71318726f8260b5a470f04a9fd294acf4d50d2a5e5c57a2dc26160df4e6211d9abefae18e7ef0164fb35d72cde510903c4d3d2331455184f6c4ff45aa12d26e36be149338757e8b3d048ae07386f2b48c6b2162a5f8bdd3c5b322422c56d5cab1c494fb2911709516f3b363593e86dad810bb22c5c84331f8fcab37514e5b8cfe24813eac8375f93e3e0785b3e35659a8605ed330aee996139f015184543229e972d8959dbc2d6bf3cd6b1fc1cf6e6778411f071125fdfe7175b0066303e933b4e19567cbc85396261628b005471185fbe8823dd7c12b2c3e29df711485a77f4a5af82aa31ee3d741b9956f6715191909a78d4d977e21b921092a229fa81f0478e003ffc316ab1cce24d3ba82c3332ed7ad99a8c4bca6ed85b54a0e9b47c68a4e5f295d7faff22622fefe4e761a18b5a39ce546e331427009297ed3ab42ad2717541ea5c19fb4e95004a5ad5070d239055096737a7c228e87d4eff877cbb226039e861d8b96cc1a3871756b0c38b4712a7068db71f37e57f29c55cc2b33565294b7716586de061c3ac52f270eb24f3dce346584c24af7503ed8c1a78ef0e94334c0e1d3f253ce4d9a7a98d07a5ff9298347f9d4ffd49a6b2ed5a71cd4546be422bb7f14b8515b6311805321f019a6a53d8ac233c44ba035168fc775a7e69ff9c34330421ef92074a400267b4aa5688a297472e8e17106db5f5e2aa8be27ce99a63cef93013d088d5421821ff2ade00684dd767a3097e0d829f4b95b1dc6b91aa6dcde0b2b5fd9fcc8ef953da9df727efad133cf3e23fd346dd4b63e01c7554665bbe072290ee09a17b3d033c2439f86b3f15b0625e93450ca835f3b552a86a5bbea72cd9bdbf8b565a5a5ace18415af65f7c65231cd7a976e5d0383cfc9776326657c256df9e83839fb0e78c1e5ed9631ae4c7d0ab25d7b23225e14c8e5ca4d855c755af60a6fa56f38450fd8248ce5c3d7700b535df9744bf10146bc3f6de1b460328a12aed8533fa067280287b63eb45eb405f3c09468d372701b80ddf939db169e71584491a8390285644123e4a439055a290a61349ae5b447f8b4885b1cacd8f878e2c9a7d4b99e7bf4b8b6f13ff0dc07f59a0bf05f79c4ef40331231f2d438d38f0e270cb86f9b92990682f0a60fab5478a55b3a8762e8421f230095878b6f2ea7fdf02ebfab9c12b59cfcfbe13c545e5e677ccabe4f4661a996a9a089caa738d85dd9e68baee3127738089a3f6c066c96e53b65d5ad60e395aeae86e58900121d03d81952c1814c99cfc1ea33724b8e782b636582321d65648527194a29a383214e30e8262c1f4c9e72608517aff00777172d6a188f0e3782c208c66549713265de122a203c4e65876be9c95a51b6ef957c5c0fa2f50dc6a79e79d65e8ec3383c9c759a0d17df1887a432223dd2309eec845bbd46becaaa3768e11b58d3c0c169fec43fd3e0359cf0d2b5ec1a535efce29e30f09b3fcbaf7cc8c3a73867fa8a5b1c11c03c2e9b1ee3aa108171a89321ae8e8bd790427f2d5bfce1e3fad872fc815267a79e16c39179c8830f6addf0a8cec42eb9955ffdd1899d69c2d20f122c72bca20de21ca430d091d8d60fae7d05f9114e3b015b5feb25da89ef74b213e9e0759bbcc7a91550c4c5e770d0e9d2918f8f56b041f2c963fa0cb5eb5d48369493f92688d9f688a80c1afac853554c3efc77be42965e02906e80ff993fd3e09cf9335d4796cfdf263ee9aff8e5d7185cd2fc5de98a038fe2ddc78fb287ca556092f09af4768bfd0fc229eb2a83bc75b3d3214ebd2d3889b3a20ffe2e7ae0629e2d130decadae628b2ad766ac2d9d837682c4f8ce19cb8f0fcba7e58e4b35bce001bc65d5e5f44361e19f2be5c04fbcff0b6812f2e14af2660e0000000049454e44ae426082", + expectedOutput: "https://globeon.mobi/jyri", + recipeConfig: [ + { + "op": "十六进制转字符", + "args": ["无"] + }, + { + "op": "解析二维码", + "args": [true] + } + ] + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/ParseSSHHostKey.mjs b/plugins/srktoolbox/tests/operations/tests/ParseSSHHostKey.mjs new file mode 100644 index 00000000..46a8d33d --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/ParseSSHHostKey.mjs @@ -0,0 +1,79 @@ +/** + * Parse SSH Host Key tests + * + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "SSH Host Key: RSA", + input: "AAAAB3NzaC1yc2EAAAADAQABAAABAQDiJZ/9W9Ix/Dk9b+K4E+RGCug1AtkGXaJ9vNIY0YHFHLpWsB8DAuh/cGEI9TLbL1gzR2wG+RJNQ2EAQVWe6ypkK63Jm4zw4re+vhEiszpnP889J0h5N9yzyTndesrl4d3cQtv861FcKDPxUJbRALdtl6gwOB7BCL8gsXJLLVLO4EesrbPXD454qpVt7CgJXEXByOFjcIm3XwkdOnXMPHHnMSD7EIN1SvQMD6PfIDrbDd6KQt5QXW/Rc/BsfX5cbUIV1QW5A/GbepXHHKmWRtLC2J/mH3hW2Zq/hITPEaJdG1CtIilQmJaZGXpfGIwFeb0Av9pSL926arZZ6vDi9ctF", + expectedOutput: `密钥类型: ssh-rsa +指数: 0x010001 +模数: 0x00e2259ffd5bd231fc393d6fe2b813e4460ae83502d9065da27dbcd218d181c51cba56b01f0302e87f706108f532db2f5833476c06f9124d43610041559eeb2a642badc99b8cf0e2b7bebe1122b33a673fcf3d27487937dcb3c939dd7acae5e1dddc42dbfceb515c2833f15096d100b76d97a830381ec108bf20b1724b2d52cee047acadb3d70f8e78aa956dec28095c45c1c8e1637089b75f091d3a75cc3c71e73120fb1083754af40c0fa3df203adb0dde8a42de505d6fd173f06c7d7e5c6d4215d505b903f19b7a95c71ca99646d2c2d89fe61f7856d99abf8484cf11a25d1b50ad222950989699197a5f188c0579bd00bfda522fddba6ab659eaf0e2f5cb45`, + recipeConfig: [ + { + op: "解析SSH主机密钥", + args: ["Base64"] + } + ] + }, + { + name: "SSH Host Key: DSA", + input: "AAAAB3NzaC1kc3MAAACBAMnoZCOzvaQqs//9mxK2USZvJBc7b1dFJiBcV80abN6maE+203pTRPIPCpPt0deQxv4YN3dSHoodEcArWxs1QRAIuRsQIvsUP7chovzGnxP84XWK5sbfrseD0vxZ7UR0NaAFPcSgeXcWC1SG9uvrAJQlyp4DBy+fKuqiYmwaz0bHAAAAFQCXNJ4yiE1V7LpCU2V1JKbqDvICMwAAAIB/5aR1iBOeyCVpj0dP3YZmoxd9R7FCC/0UuOf0lx4E6WHT6Z2QuPBhc2mpNDq2M0VF9oJfVWgcfG8r1rlXaCYODSacGcbnW5VKQ+LKkkALmg4h8jFCHReUC+Hmia/v8LyDwPO1wK6ETn7a3m80yM7gAU5ZNurVIVVP2lB65mjEsQAAAIA3ct9YRB6iUCvOD45sZM1C9oTC24Ttmaou0GcpWx3h0/iZ8mbil1cjaO9frRNZ/vSSVWEhEDNG8gwkjZWlvnJL3y1XUxbMll4WbmI/Q1kzKwopceaFwMbYTPKDg6L1RtCMUxSUyKsFk1c4SpEPlDS7DApZs5PgmWgMd/u6vwMXyg==", + expectedOutput: `密钥类型: ssh-dss +p: 0x00c9e86423b3bda42ab3fffd9b12b651266f24173b6f574526205c57cd1a6cdea6684fb6d37a5344f20f0a93edd1d790c6fe183777521e8a1d11c02b5b1b35411008b91b1022fb143fb721a2fcc69f13fce1758ae6c6dfaec783d2fc59ed447435a0053dc4a07977160b5486f6ebeb009425ca9e03072f9f2aeaa2626c1acf46c7 +q: 0x0097349e32884d55ecba4253657524a6ea0ef20233 +g: 0x7fe5a47588139ec825698f474fdd8666a3177d47b1420bfd14b8e7f4971e04e961d3e99d90b8f0617369a9343ab6334545f6825f55681c7c6f2bd6b95768260e0d269c19c6e75b954a43e2ca92400b9a0e21f231421d17940be1e689afeff0bc83c0f3b5c0ae844e7edade6f34c8cee0014e5936ead521554fda507ae668c4b1 +y: 0x3772df58441ea2502bce0f8e6c64cd42f684c2db84ed99aa2ed067295b1de1d3f899f266e297572368ef5fad1359fef492556121103346f20c248d95a5be724bdf2d575316cc965e166e623f4359332b0a2971e685c0c6d84cf28383a2f546d08c531494c8ab059357384a910f9434bb0c0a59b393e099680c77fbbabf0317ca`, + recipeConfig: [ + { + op: "解析SSH主机密钥", + args: ["Base64"] + } + ] + }, + { + name: "SSH Host Key: ECDSA", + input: "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBGxZWSAGJyJQoVBwFCpr420eRUZDE/kw2YWm5vDro8050DZ1ZzqIuYaNl0BGzMcRTeasGtJuI8G84ZQQSgca3C4=", + expectedOutput: `密钥类型: ecdsa-sha2-nistp256 +曲线: nistp256 +点位: 0x046c59592006272250a15070142a6be36d1e45464313f930d985a6e6f0eba3cd39d03675673a88b9868d974046ccc7114de6ac1ad26e23c1bce194104a071adc2e`, + recipeConfig: [ + { + op: "解析SSH主机密钥", + args: ["Base64"] + } + ] + }, + { + name: "SSH Host Key: Ed25519", + input: "AAAAC3NzaC1lZDI1NTE5AAAAIBOF6r99IkvqGu1kwZrHHIqjpTB5w79bpv67B/Aw3+WJ", + expectedOutput: `密钥类型: ssh-ed25519 +x: 0x1385eabf7d224bea1aed64c19ac71c8aa3a53079c3bf5ba6febb07f030dfe589`, + recipeConfig: [ + { + op: "解析SSH主机密钥", + args: ["Base64"] + } + ] + }, + { + name: "SSH Host Key: Extract key", + input: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDiJZ/9W9Ix/Dk9b+K4E+RGCug1AtkGXaJ9vNIY0YHFHLpWsB8DAuh/cGEI9TLbL1gzR2wG+RJNQ2EAQVWe6ypkK63Jm4zw4re+vhEiszpnP889J0h5N9yzyTndesrl4d3cQtv861FcKDPxUJbRALdtl6gwOB7BCL8gsXJLLVLO4EesrbPXD454qpVt7CgJXEXByOFjcIm3XwkdOnXMPHHnMSD7EIN1SvQMD6PfIDrbDd6KQt5QXW/Rc/BsfX5cbUIV1QW5A/GbepXHHKmWRtLC2J/mH3hW2Zq/hITPEaJdG1CtIilQmJaZGXpfGIwFeb0Av9pSL926arZZ6vDi9ctF test@test", + expectedOutput: `密钥类型: ssh-rsa +指数: 0x010001 +模数: 0x00e2259ffd5bd231fc393d6fe2b813e4460ae83502d9065da27dbcd218d181c51cba56b01f0302e87f706108f532db2f5833476c06f9124d43610041559eeb2a642badc99b8cf0e2b7bebe1122b33a673fcf3d27487937dcb3c939dd7acae5e1dddc42dbfceb515c2833f15096d100b76d97a830381ec108bf20b1724b2d52cee047acadb3d70f8e78aa956dec28095c45c1c8e1637089b75f091d3a75cc3c71e73120fb1083754af40c0fa3df203adb0dde8a42de505d6fd173f06c7d7e5c6d4215d505b903f19b7a95c71ca99646d2c2d89fe61f7856d99abf8484cf11a25d1b50ad222950989699197a5f188c0579bd00bfda522fddba6ab659eaf0e2f5cb45`, + recipeConfig: [ + { + op: "解析SSH主机密钥", + args: ["Base64"] + } + ] + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/ParseTCP.mjs b/plugins/srktoolbox/tests/operations/tests/ParseTCP.mjs new file mode 100644 index 00000000..ffdf0eec --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/ParseTCP.mjs @@ -0,0 +1,46 @@ +/** + * Parse TCP tests. + * + * @author n1474335 + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Parse TCP: No options", + input: "c2eb0050a138132e70dc9fb9501804025ea70000", + expectedMatch: /1026 \(扩大后: 1026\)/, + recipeConfig: [ + { + op: "解析TCP", + args: ["十六进制"], + } + ], + }, + { + name: "Parse TCP: Options", + input: "c2eb0050a1380c1f000000008002faf080950000020405b40103030801010402", + expectedMatch: /1460/, + recipeConfig: [ + { + op: "解析TCP", + args: ["十六进制"], + } + ], + }, + { + name: "Parse TCP: Timestamps", + input: "9e90e11574d57b2c00000000a002ffffe5740000020405b40402080aa4e8c8f50000000001030308", + expectedMatch: /2766719221/, + recipeConfig: [ + { + op: "解析TCP", + args: ["十六进制"], + } + ], + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/ParseTLSRecord.mjs b/plugins/srktoolbox/tests/operations/tests/ParseTLSRecord.mjs new file mode 100644 index 00000000..9a147a01 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/ParseTLSRecord.mjs @@ -0,0 +1,2045 @@ +/** + * Parse TLS record tests. + * + * @auther c65722 + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + */ + +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Parse TLS record: Truncated header", + input: "16030300", + expectedOutput: "[]", + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Change Cipher Spec", + input: "140303000101", + expectedOutput: '[{"type":"change_cipher_spec","version":"0x0303","length":1,"value":"0x01"}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Change Cipher Spec - Truncated before content", + input: "1403030001", + expectedOutput: '[{"type":"change_cipher_spec","version":"0x0303","length":1,"truncated":true}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Alert", + input: "150303001411770b5b5d11078535823266ec79671ed402bced", + expectedOutput: '[{"type":"alert","version":"0x0303","length":20,"value":"0x11770b5b5d11078535823266ec79671ed402bced"}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Alert - Truncated within content", + input: "150303001411770b5b5d1107853582", + expectedOutput: '[{"type":"alert","version":"0x0303","length":20,"truncated":true,"value":"0x11770b5b5d1107853582"}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Alert - Truncated before content", + input: "1503030014", + expectedOutput: '[{"type":"alert","version":"0x0303","length":20,"truncated":true}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Truncated within length", + input: "1603030032010000", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":50,"truncated":true,"handshakeType":"client_hello"}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Truncated before length", + input: "160303003201", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":50,"truncated":true,"handshakeType":"client_hello"}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Truncated before msg type", + input: "1603030032", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":50,"truncated":true}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Hello Request", + input: "160303000400000000", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":4,"handshakeType":"hello_request"}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Client Hello, No session ID, No extensions", + input: "16030300320100002e030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076000004123443210200010000", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":50,"handshakeType":"client_hello","clientVersion":"0x0303","random":"0x45cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076","cipherSuites":{"length":4,"values":["0x1234","0x4321"]},"compressionMethods":{"length":2,"values":["0x00","0x01"]},"extensions":{}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Client Hello, No session ID, No extensions - Truncated before extensions length", + input: "16030300320100002e030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd05107600000412344321020001", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":50,"truncated":true,"handshakeType":"client_hello","clientVersion":"0x0303","random":"0x45cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076","cipherSuites":{"length":4,"values":["0x1234","0x4321"]},"compressionMethods":{"length":2,"values":["0x00","0x01"]},"extensions":{}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Client Hello, No session ID, No extensions - Truncated within compression methods", + input: "16030300320100002e030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076000004123443210200", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":50,"truncated":true,"handshakeType":"client_hello","clientVersion":"0x0303","random":"0x45cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076","cipherSuites":{"length":4,"values":["0x1234","0x4321"]},"compressionMethods":{"length":2,"truncated":true,"values":["0x00"]},"extensions":{}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Client Hello, No session ID, No extensions - Truncated before compression methods", + input: "16030300320100002e030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd0510760000041234432102", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":50,"truncated":true,"handshakeType":"client_hello","clientVersion":"0x0303","random":"0x45cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076","cipherSuites":{"length":4,"values":["0x1234","0x4321"]},"compressionMethods":{"length":2,"truncated":true,"values":[]},"extensions":{}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Client Hello, No session ID, No extensions - Truncated before compression methods length", + input: "16030300320100002e030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd05107600000412344321", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":50,"truncated":true,"handshakeType":"client_hello","clientVersion":"0x0303","random":"0x45cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076","cipherSuites":{"length":4,"values":["0x1234","0x4321"]},"compressionMethods":{},"extensions":{}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Client Hello, No session ID, No extensions - Truncated within cipher suite value", + input: "16030300320100002e030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076000004123443", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":50,"truncated":true,"handshakeType":"client_hello","clientVersion":"0x0303","random":"0x45cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076","cipherSuites":{"length":4,"truncated":true,"values":["0x1234"]},"compressionMethods":{},"extensions":{}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Client Hello, No session ID, No extensions - Truncated within cipher suites", + input: "16030300320100002e030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd0510760000041234", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":50,"truncated":true,"handshakeType":"client_hello","clientVersion":"0x0303","random":"0x45cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076","cipherSuites":{"length":4,"truncated":true,"values":["0x1234"]},"compressionMethods":{},"extensions":{}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Client Hello, No session ID, No extensions - Truncated before cipher suites", + input: "16030300320100002e030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076000004", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":50,"truncated":true,"handshakeType":"client_hello","clientVersion":"0x0303","random":"0x45cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076","cipherSuites":{"length":4,"truncated":true,"values":[]},"compressionMethods":{},"extensions":{}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Client Hello, No session ID, No extensions - Truncated before cipher suites length", + input: "16030300320100002e030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd0510760000", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":50,"truncated":true,"handshakeType":"client_hello","clientVersion":"0x0303","random":"0x45cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076","cipherSuites":{},"compressionMethods":{},"extensions":{}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Client Hello, No session ID, No extensions - Truncated before session id length", + input: "16030300320100002e030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd05107600", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":50,"truncated":true,"handshakeType":"client_hello","clientVersion":"0x0303","random":"0x45cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076","cipherSuites":{},"compressionMethods":{},"extensions":{}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Client Hello, No session ID, No extensions - Truncated within random", + input: "16030300320100002e030345cd3a31beaebd2934dd4ec2a151d7a0", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":50,"truncated":true,"handshakeType":"client_hello","clientVersion":"0x0303","random":"","cipherSuites":{},"compressionMethods":{},"extensions":{}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Client Hello, No session ID, No extensions - Truncated before random", + input: "16030300320100002e0303", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":50,"truncated":true,"handshakeType":"client_hello","clientVersion":"0x0303","random":"","cipherSuites":{},"compressionMethods":{},"extensions":{}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Client Hello, No session ID, No extensions - Truncated within client version", + input: "16030300320100002e03", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":50,"truncated":true,"handshakeType":"client_hello","clientVersion":"","random":"","cipherSuites":{},"compressionMethods":{},"extensions":{}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Client Hello, No session ID, No extensions - Truncated before client version", + input: "16030300320100002e", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":50,"truncated":true,"handshakeType":"client_hello"}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Client Hello, Session ID, No extensions", + input: "16030300520100004e030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd05107620dc78c85fdcee405ebb7963543771005a3d1b7dbf88fb9f8df12e4f7ea525e1ae0004123443210200010000", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":82,"handshakeType":"client_hello","clientVersion":"0x0303","random":"0x45cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076","sessionID":"0xdc78c85fdcee405ebb7963543771005a3d1b7dbf88fb9f8df12e4f7ea525e1ae","cipherSuites":{"length":4,"values":["0x1234","0x4321"]},"compressionMethods":{"length":2,"values":["0x00","0x01"]},"extensions":{}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Client Hello, Session ID, No extensions - Truncated within session id", + input: "16030300520100004e030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd05107620dc78c85fdcee405ebb7963543771005a", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":82,"truncated":true,"handshakeType":"client_hello","clientVersion":"0x0303","random":"0x45cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076","cipherSuites":{},"compressionMethods":{},"extensions":{}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Client Hello, Session ID, No extensions - Truncated before session id", + input: "16030300520100004e030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd05107620", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":82,"truncated":true,"handshakeType":"client_hello","clientVersion":"0x0303","random":"0x45cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076","cipherSuites":{},"compressionMethods":{},"extensions":{}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Client Hello, Session ID, Extensions", + input: "160303006f0100006b030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd05107620dc78c85fdcee405ebb7963543771005a3d1b7dbf88fb9f8df12e4f7ea525e1ae000412344321020001001d00000010000e00000b6578616d706c652e636f6d00170000ff01000100", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":111,"handshakeType":"client_hello","clientVersion":"0x0303","random":"0x45cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076","sessionID":"0xdc78c85fdcee405ebb7963543771005a3d1b7dbf88fb9f8df12e4f7ea525e1ae","cipherSuites":{"length":4,"values":["0x1234","0x4321"]},"compressionMethods":{"length":2,"values":["0x00","0x01"]},"extensions":{"length":29,"values":[{"type":"0x0000","length":16,"value":"0x000e00000b6578616d706c652e636f6d"},{"type":"0x0017","length":0},{"type":"0xff01","length":1,"value":"0x00"}]}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Client Hello, Session ID, Extensions - Truncated within extension value", + input: "160303006f0100006b030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd05107620dc78c85fdcee405ebb7963543771005a3d1b7dbf88fb9f8df12e4f7ea525e1ae000412344321020001001d00000010000e00000b657861", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":111,"truncated":true,"handshakeType":"client_hello","clientVersion":"0x0303","random":"0x45cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076","sessionID":"0xdc78c85fdcee405ebb7963543771005a3d1b7dbf88fb9f8df12e4f7ea525e1ae","cipherSuites":{"length":4,"values":["0x1234","0x4321"]},"compressionMethods":{"length":2,"values":["0x00","0x01"]},"extensions":{"length":29,"truncated":true,"values":[{"type":"0x0000","length":16,"truncated":true,"value":"0x000e00000b657861"}]}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Client Hello, Session ID, Extensions - Truncated before extension value", + input: "160303006f0100006b030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd05107620dc78c85fdcee405ebb7963543771005a3d1b7dbf88fb9f8df12e4f7ea525e1ae000412344321020001001d00000010", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":111,"truncated":true,"handshakeType":"client_hello","clientVersion":"0x0303","random":"0x45cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076","sessionID":"0xdc78c85fdcee405ebb7963543771005a3d1b7dbf88fb9f8df12e4f7ea525e1ae","cipherSuites":{"length":4,"values":["0x1234","0x4321"]},"compressionMethods":{"length":2,"values":["0x00","0x01"]},"extensions":{"length":29,"truncated":true,"values":[{"type":"0x0000","length":16,"truncated":true}]}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Client Hello, Session ID, Extensions - Truncated within extension length", + input: "160303006f0100006b030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd05107620dc78c85fdcee405ebb7963543771005a3d1b7dbf88fb9f8df12e4f7ea525e1ae000412344321020001001d000000", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":111,"truncated":true,"handshakeType":"client_hello","clientVersion":"0x0303","random":"0x45cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076","sessionID":"0xdc78c85fdcee405ebb7963543771005a3d1b7dbf88fb9f8df12e4f7ea525e1ae","cipherSuites":{"length":4,"values":["0x1234","0x4321"]},"compressionMethods":{"length":2,"values":["0x00","0x01"]},"extensions":{"length":29,"truncated":true,"values":[]}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Client Hello, Session ID, Extensions - Truncated before extension length", + input: "160303006f0100006b030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd05107620dc78c85fdcee405ebb7963543771005a3d1b7dbf88fb9f8df12e4f7ea525e1ae000412344321020001001d0000", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":111,"truncated":true,"handshakeType":"client_hello","clientVersion":"0x0303","random":"0x45cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076","sessionID":"0xdc78c85fdcee405ebb7963543771005a3d1b7dbf88fb9f8df12e4f7ea525e1ae","cipherSuites":{"length":4,"values":["0x1234","0x4321"]},"compressionMethods":{"length":2,"values":["0x00","0x01"]},"extensions":{"length":29,"truncated":true,"values":[]}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Client Hello, Session ID, Extensions - Truncated within extension type", + input: "160303006f0100006b030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd05107620dc78c85fdcee405ebb7963543771005a3d1b7dbf88fb9f8df12e4f7ea525e1ae000412344321020001001d00", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":111,"truncated":true,"handshakeType":"client_hello","clientVersion":"0x0303","random":"0x45cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076","sessionID":"0xdc78c85fdcee405ebb7963543771005a3d1b7dbf88fb9f8df12e4f7ea525e1ae","cipherSuites":{"length":4,"values":["0x1234","0x4321"]},"compressionMethods":{"length":2,"values":["0x00","0x01"]},"extensions":{"length":29,"truncated":true,"values":[]}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Client Hello, Session ID, Extensions - Truncated before extension type", + input: "160303006f0100006b030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd05107620dc78c85fdcee405ebb7963543771005a3d1b7dbf88fb9f8df12e4f7ea525e1ae000412344321020001001d", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":111,"truncated":true,"handshakeType":"client_hello","clientVersion":"0x0303","random":"0x45cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076","sessionID":"0xdc78c85fdcee405ebb7963543771005a3d1b7dbf88fb9f8df12e4f7ea525e1ae","cipherSuites":{"length":4,"values":["0x1234","0x4321"]},"compressionMethods":{"length":2,"values":["0x00","0x01"]},"extensions":{"length":29,"truncated":true,"values":[]}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Server Hello, No session ID, No extensions", + input: "160303002c02000028030309684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132004321010000", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":44,"handshakeType":"server_hello","serverVersion":"0x0303","random":"0x09684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132","cipherSuite":"0x4321","compressionMethod":"0x01","extensions":{}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Server Hello, No session ID, No extensions - Truncated before extensions length", + input: "160303002c02000028030309684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b0113200432101", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":44,"truncated":true,"handshakeType":"server_hello","serverVersion":"0x0303","random":"0x09684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132","cipherSuite":"0x4321","compressionMethod":"0x01","extensions":{}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Server Hello, No session ID, No extensions - Truncated before compression method", + input: "160303002c02000028030309684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132004321", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":44,"truncated":true,"handshakeType":"server_hello","serverVersion":"0x0303","random":"0x09684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132","cipherSuite":"0x4321","compressionMethod":"","extensions":{}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Server Hello, No session ID, No extensions - Truncated within cipher suite", + input: "160303002c02000028030309684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b011320043", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":44,"truncated":true,"handshakeType":"server_hello","serverVersion":"0x0303","random":"0x09684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132","cipherSuite":"","compressionMethod":"","extensions":{}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Server Hello, No session ID, No extensions - Truncated before cipher suite", + input: "160303002c02000028030309684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b0113200", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":44,"truncated":true,"handshakeType":"server_hello","serverVersion":"0x0303","random":"0x09684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132","cipherSuite":"","compressionMethod":"","extensions":{}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Server Hello, No session ID, No extensions - Truncated before session id length", + input: "160303002c02000028030309684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":44,"truncated":true,"handshakeType":"server_hello","serverVersion":"0x0303","random":"0x09684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132","cipherSuite":"","compressionMethod":"","extensions":{}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Server Hello, No session ID, No extensions - Truncated within random", + input: "160303002c02000028030309684ab9c0f6e739e308cd42a18a73d9a", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":44,"truncated":true,"handshakeType":"server_hello","serverVersion":"0x0303","random":"","cipherSuite":"","compressionMethod":"","extensions":{}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Server Hello, No session ID, No extensions - Truncated before random", + input: "160303002c0200002803030", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":44,"truncated":true,"handshakeType":"server_hello","serverVersion":"0x0303","random":"","cipherSuite":"","compressionMethod":"","extensions":{}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Server Hello, No session ID, No extensions - Truncated within server version", + input: "160303002c0200002803", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":44,"truncated":true,"handshakeType":"server_hello","serverVersion":"","random":"","cipherSuite":"","compressionMethod":"","extensions":{}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Server Hello, No session ID, No extensions - Truncated before server version", + input: "160303002c02000028", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":44,"truncated":true,"handshakeType":"server_hello"}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Server Hello, Session ID, No extension", + input: "160303004c02000048030309684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b0113220a4fe3d1e9a7dc5ce3d9341b4d48a2df755a0fd83876d0330018306707c9b95984321010000", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":76,"handshakeType":"server_hello","serverVersion":"0x0303","random":"0x09684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132","sessionID":"0xa4fe3d1e9a7dc5ce3d9341b4d48a2df755a0fd83876d0330018306707c9b9598","cipherSuite":"0x4321","compressionMethod":"0x01","extensions":{}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Server Hello, Session ID, No extension - Truncated within session id", + input: "160303004c02000048030309684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b0113220a4fe3d1e9a7dc5ce3d9341b4d48a2df7", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":76,"truncated":true,"handshakeType":"server_hello","serverVersion":"0x0303","random":"0x09684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132","cipherSuite":"","compressionMethod":"","extensions":{}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Server Hello, Session ID, No extension - Truncated before session id", + input: "160303004c02000048030309684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b0113220", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":76,"truncated":true,"handshakeType":"server_hello","serverVersion":"0x0303","random":"0x09684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132","cipherSuite":"","compressionMethod":"","extensions":{}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Server Hello, Session ID, Extensions", + input: "160303005902000055030309684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b0113220a4fe3d1e9a7dc5ce3d9341b4d48a2df755a0fd83876d0330018306707c9b9598432101000d00000000ff0100010000170000", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":89,"handshakeType":"server_hello","serverVersion":"0x0303","random":"0x09684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132","sessionID":"0xa4fe3d1e9a7dc5ce3d9341b4d48a2df755a0fd83876d0330018306707c9b9598","cipherSuite":"0x4321","compressionMethod":"0x01","extensions":{"length":13,"values":[{"type":"0x0000","length":0},{"type":"0xff01","length":1,"value":"0x00"},{"type":"0x0017","length":0}]}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Server Hello, Session ID, Extensions - Truncated before extension value", + input: "160303005902000055030309684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b0113220a4fe3d1e9a7dc5ce3d9341b4d48a2df755a0fd83876d0330018306707c9b9598432101000d00000000ff010001", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":89,"truncated":true,"handshakeType":"server_hello","serverVersion":"0x0303","random":"0x09684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132","sessionID":"0xa4fe3d1e9a7dc5ce3d9341b4d48a2df755a0fd83876d0330018306707c9b9598","cipherSuite":"0x4321","compressionMethod":"0x01","extensions":{"length":13,"truncated":true,"values":[{"type":"0x0000","length":0},{"type":"0xff01","length":1,"truncated":true}]}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Server Hello, Session ID, Extensions - Truncated within extension length", + input: "160303005902000055030309684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b0113220a4fe3d1e9a7dc5ce3d9341b4d48a2df755a0fd83876d0330018306707c9b9598432101000d00000000ff0100", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":89,"truncated":true,"handshakeType":"server_hello","serverVersion":"0x0303","random":"0x09684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132","sessionID":"0xa4fe3d1e9a7dc5ce3d9341b4d48a2df755a0fd83876d0330018306707c9b9598","cipherSuite":"0x4321","compressionMethod":"0x01","extensions":{"length":13,"truncated":true,"values":[{"type":"0x0000","length":0}]}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Server Hello, Session ID, Extensions - Truncated before extension length", + input: "160303005902000055030309684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b0113220a4fe3d1e9a7dc5ce3d9341b4d48a2df755a0fd83876d0330018306707c9b9598432101000d00000000ff01", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":89,"truncated":true,"handshakeType":"server_hello","serverVersion":"0x0303","random":"0x09684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132","sessionID":"0xa4fe3d1e9a7dc5ce3d9341b4d48a2df755a0fd83876d0330018306707c9b9598","cipherSuite":"0x4321","compressionMethod":"0x01","extensions":{"length":13,"truncated":true,"values":[{"type":"0x0000","length":0}]}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Server Hello, Session ID, Extensions - Truncated within extension type", + input: "160303005902000055030309684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b0113220a4fe3d1e9a7dc5ce3d9341b4d48a2df755a0fd83876d0330018306707c9b9598432101000d00000000ff", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":89,"truncated":true,"handshakeType":"server_hello","serverVersion":"0x0303","random":"0x09684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132","sessionID":"0xa4fe3d1e9a7dc5ce3d9341b4d48a2df755a0fd83876d0330018306707c9b9598","cipherSuite":"0x4321","compressionMethod":"0x01","extensions":{"length":13,"truncated":true,"values":[{"type":"0x0000","length":0}]}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Server Hello, Session ID, Extensions - Truncated before extension type", + input: "160303005902000055030309684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b0113220a4fe3d1e9a7dc5ce3d9341b4d48a2df755a0fd83876d0330018306707c9b9598432101000d00000000", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":89,"truncated":true,"handshakeType":"server_hello","serverVersion":"0x0303","random":"0x09684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132","sessionID":"0xa4fe3d1e9a7dc5ce3d9341b4d48a2df755a0fd83876d0330018306707c9b9598","cipherSuite":"0x4321","compressionMethod":"0x01","extensions":{"length":13,"truncated":true,"values":[{"type":"0x0000","length":0}]}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - New Session Ticket", + input: "16030300ca040000c60000070800c0626f6889ce97edae08b0870505f9251e1d0713438ed014ac8f5e6969cf9e500aaba6080dfed5474ec85ff48d882d526cdae7f21d51b4beeb0be83fb822f18d22d2086b7519b29114364af034ac9a6915562ba686b81917bcb89fc4a750284470e7d67d8d33647e245e5e789f547d6a1be91ef0985bbfcf3b88760586b8f02570e0b7e8547fdad75530bc0261756ec994dfc725c8551c762f26e105e62290cd43773ea9e8a42ac8ac21467053240a29ef93c2e34c2f13ce8ff494d8c64f727248", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":202,"handshakeType":"new_session_ticket","ticketLifetimeHint":"1800s","ticket":"0x626f6889ce97edae08b0870505f9251e1d0713438ed014ac8f5e6969cf9e500aaba6080dfed5474ec85ff48d882d526cdae7f21d51b4beeb0be83fb822f18d22d2086b7519b29114364af034ac9a6915562ba686b81917bcb89fc4a750284470e7d67d8d33647e245e5e789f547d6a1be91ef0985bbfcf3b88760586b8f02570e0b7e8547fdad75530bc0261756ec994dfc725c8551c762f26e105e62290cd43773ea9e8a42ac8ac21467053240a29ef93c2e34c2f13ce8ff494d8c64f727248"}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - New Session Ticket - Truncated within ticket", + input: "16030300ca040000c60000070800c0626f6889ce97edae08b0870505f9251e1d0713438ed014ac8f5e6969cf9e500aaba6080dfed5474ec85ff48d882d526cdae7f21d51b4beeb0be83fb822f18d22d2086b7519b29114364af034ac9a6915562ba686b81917bcb89fc4a750284470", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":202,"truncated":true,"handshakeType":"new_session_ticket","ticketLifetimeHint":"1800s","ticket":""}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - New Session Ticket - Truncated before ticket", + input: "16030300ca040000c60000070800c0", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":202,"truncated":true,"handshakeType":"new_session_ticket","ticketLifetimeHint":"1800s","ticket":""}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - New Session Ticket - Truncated within ticket length", + input: "16030300ca040000c60000070800", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":202,"truncated":true,"handshakeType":"new_session_ticket","ticketLifetimeHint":"1800s","ticket":""}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - New Session Ticket - Truncated before ticket length", + input: "16030300ca040000c600000708", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":202,"truncated":true,"handshakeType":"new_session_ticket","ticketLifetimeHint":"1800s","ticket":""}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - New Session Ticket - Truncated within ticket lifetime hint", + input: "16030300ca040000c6000007", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":202,"truncated":true,"handshakeType":"new_session_ticket","ticketLifetimeHint":"","ticket":""}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - New Session Ticket - Truncated before ticket lifetime hint", + input: "16030300ca040000c6", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":202,"truncated":true,"handshakeType":"new_session_ticket"}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Certificate", + input: "1603030acf0b000acb000ac80002923082028e308201f7a003020102021468f6f88ecf1bf3d14e7503ef2e1b789cb77b86c3300d06092a864886f70d01010b05003058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d3020170d3234303932323039353335385a180f32313234303832393039353335385a3058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100c3df3e5745f05b3aa220ce4108838107653c3ae9584ace27d7088506ebdc3531afbe6265719278682eaa4fec7ae1f319395d356be79477bc62edbe7207d96f5717e9bd9083fdcc797c1b8e38bcf9fd08df6f101bc2a06101ddce6be2f5a0de80ebc8fdce2538867c1d6a84acef26b2068c5d27771abcee071bcf378899cb32730203010001a3533051301d0603551d0e041604144c9b134c1575c51ae9d03c4020da7541278ad928301f0603551d230418301680144c9b134c1575c51ae9d03c4020da7541278ad928300f0603551d130101ff040530030101ff300d06092a864886f70d01010b05000381810012a06cced33d721b1d7912ff0b190b74524ddfdeca103aba0f168f4f15f57212ba7d66328e48b021f32cfec84f65d79821bc1fe9f472f60c094e537160708a48a0898dbf613cece86892cf48fcd598757aa4379e18673626be2f048e35f585086ea7a3766ce50a14ca6f691b369c965e062f40619cde6262ed8019b522e76eaf00029430820290308201f9a00302010202142a3329f5e2e92940318cecd036ff135525b1d491300d06092a864886f70d01010b05003059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d3020170d3234303932323039353531375a180f32313234303832393039353531375a3059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100b27c861d957c49111b4f37f65bc142da564429c74a925e3de6d9add55ccfccf1316a5002b3ed2d35ec9822499e7256f9caaa2191010df354185c63a32c8d080ba49510953d7ec2210685030564be69a9f2262a9da22f3623b2a9b032f3a82b1c31ce11336c288fc3d5f63565aacc8c0f85ebaad6af2cd3505a7cf3945ca2ca690203010001a3533051301d0603551d0e0416041485478b7936ecd417647e9d8582d3f68fc670d839301f0603551d2304183016801485478b7936ecd417647e9d8582d3f68fc670d839300f0603551d130101ff040530030101ff300d06092a864886f70d01010b050003818100652656aef44c7a507a376de248cd1b36028fb1b0292593f88eb36b429f7de4c668aef7b0d862c9314e5d870f7c28353022657a7de07ec69505a54e48337ab6ba425bfd8865b720f1f2e86c92edaa261fd73e44856ac45c4d9378c86adb96b6f999f61e5f651cb885e06a3d909b5fa79458941bea36785ea585aeb5025032a18d000599308205953082037da00302010202141521d02e945395325d99051e616ad01c97627ee2300d06092a864886f70d01010b05003059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65332e636f6d3020170d3234303932323130303232325a180f32313234303832393130303232325a3059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65332e636f6d30820222300d06092a864886f70d01010105000382020f003082020a0282020100bd7c65b5c2c7027e4eb77722f84d7dc9b45f9fae45c59dd0035340b3d8fec5ea644ac4563c4260b2c078880bf81ffec0e4cd9193b708ded6431c0e7d9e8f45d595712b733262f8f62f1b4c3ae69f1f39bc68a39b1b5699adddfd7c51b83f59479fe5ffe0faef6376b1c5cea434aa9db85e792f989b5977c6fda87f7c00f79e67e417d826c1ab1fa304163414fc6321790f07cffede43170718536e5fe3128f6d101de82a7b1de37f89e61d822f09eef7304213d41998a49e5ab6b1a7eb1ab4ece21f005061828567047aaf640cff2f87c85eefc2d3a91ebf48aaa893e59451acbea894975df2587b203302fb39755f2e21e012d1fc89df86ec53723df497318d8b44eee9334a2699ad403a7df6719747bc37429d3c47ada354308380b09bb6d76e21dc1735a1479470c94c0282bbbdf5e2e6af60cf1f2e9b8dad20e45307729813eaaf584b31984e036d5452dfae47a4b8640bdf4c02ecf4ce4240d64d2ab895cbf512558712533cd3fc6838bfd24a2a588b9f1b1848bb0d6b1cd77345add6e9dc547a7b95b027bb18e96f30c4f9cd780c96984472b70ea39a7acdff9c649ac4a59e12a5a72d436036b31fa130f6a72c717b3df403113ee3b3d1605f76e57e96b83e501ed5fe9200e2ea9aefa797fa0c8b6c5d8f12e4bea7359be03d3ca35d3e22e20639fc7e03c990a494402268a08fb1589dc086995b0ba3c9ffe255b6b7cf0203010001a3533051301d0603551d0e041604143e8bac5b946c2eff6a8cb337081fa4fe6ce07312301f0603551d230418301680143e8bac5b946c2eff6a8cb337081fa4fe6ce07312300f0603551d130101ff040530030101ff300d06092a864886f70d01010b0500038202010081ad7acd39e5cc60682c962d367a84d32191e5b465ed531f617daf5fd33394a3ac9a42116d34211708ada0d9bd2cbf1d4a4175d67c87116c7495ed372c585ae6bdfe0bc713aa1afd0cc3f025c322dc45be0c3be982918dea938deaaa9e5bfd1fccb3eb8a111aec0498f64bdb16f6cb07bcecd85f6b9e445cf596d85596b4f0d7147d73cbc26000d374085e9c69f56262827fa3d5a037cf1d2cfe0f0eca779b101da08a8d732ecf584a193d93449697ee24ed6f41f9735ea3a3f206f8e6b5bf0b0ff3488a31d0feaccd701a144d35c265dc32d2e650f855debbfa5bd2d9dc2d80a1b8f81013f8049bd7be83a3ec5ae554c19fd4241a6686d4094ff073022d1f16afa5a0297e54a9b56fd469b44c6904d2b542f83ff0cf6af3b649f408f72f7cb49be5583ec4b1d912a677ae1fd81779506af9b688d8b753fdb0451925752fba8efcdaedf935f2a264caa1f4fe746ac6c339cca647b25f0bd2139205e67b6e90987da8b993b85037931443a6652426ab779db090cf08b28fed862a0ccdde1568bd930bf2d39ab7b850f97925e9bda13a6ee5166e48959711065c054bdf5ff04e4b8d5120caabca40c7707da3bb10f2ae7a00a6e56b012a6c00daaec5ddf0b63f61622aeeb81a71a5aa17508e5471e777bed8d09023c24280495adc38ffc3615dd20b139d32d7cc30b0690ab7f3e47a0131fa3d81929e64c6b9c6b363da410f6e5e", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":2767,"handshakeType":"certificate","certificateList":{"length":2760,"values":["0x3082028e308201f7a003020102021468f6f88ecf1bf3d14e7503ef2e1b789cb77b86c3300d06092a864886f70d01010b05003058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d3020170d3234303932323039353335385a180f32313234303832393039353335385a3058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100c3df3e5745f05b3aa220ce4108838107653c3ae9584ace27d7088506ebdc3531afbe6265719278682eaa4fec7ae1f319395d356be79477bc62edbe7207d96f5717e9bd9083fdcc797c1b8e38bcf9fd08df6f101bc2a06101ddce6be2f5a0de80ebc8fdce2538867c1d6a84acef26b2068c5d27771abcee071bcf378899cb32730203010001a3533051301d0603551d0e041604144c9b134c1575c51ae9d03c4020da7541278ad928301f0603551d230418301680144c9b134c1575c51ae9d03c4020da7541278ad928300f0603551d130101ff040530030101ff300d06092a864886f70d01010b05000381810012a06cced33d721b1d7912ff0b190b74524ddfdeca103aba0f168f4f15f57212ba7d66328e48b021f32cfec84f65d79821bc1fe9f472f60c094e537160708a48a0898dbf613cece86892cf48fcd598757aa4379e18673626be2f048e35f585086ea7a3766ce50a14ca6f691b369c965e062f40619cde6262ed8019b522e76eaf","0x30820290308201f9a00302010202142a3329f5e2e92940318cecd036ff135525b1d491300d06092a864886f70d01010b05003059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d3020170d3234303932323039353531375a180f32313234303832393039353531375a3059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100b27c861d957c49111b4f37f65bc142da564429c74a925e3de6d9add55ccfccf1316a5002b3ed2d35ec9822499e7256f9caaa2191010df354185c63a32c8d080ba49510953d7ec2210685030564be69a9f2262a9da22f3623b2a9b032f3a82b1c31ce11336c288fc3d5f63565aacc8c0f85ebaad6af2cd3505a7cf3945ca2ca690203010001a3533051301d0603551d0e0416041485478b7936ecd417647e9d8582d3f68fc670d839301f0603551d2304183016801485478b7936ecd417647e9d8582d3f68fc670d839300f0603551d130101ff040530030101ff300d06092a864886f70d01010b050003818100652656aef44c7a507a376de248cd1b36028fb1b0292593f88eb36b429f7de4c668aef7b0d862c9314e5d870f7c28353022657a7de07ec69505a54e48337ab6ba425bfd8865b720f1f2e86c92edaa261fd73e44856ac45c4d9378c86adb96b6f999f61e5f651cb885e06a3d909b5fa79458941bea36785ea585aeb5025032a18d","0x308205953082037da00302010202141521d02e945395325d99051e616ad01c97627ee2300d06092a864886f70d01010b05003059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65332e636f6d3020170d3234303932323130303232325a180f32313234303832393130303232325a3059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65332e636f6d30820222300d06092a864886f70d01010105000382020f003082020a0282020100bd7c65b5c2c7027e4eb77722f84d7dc9b45f9fae45c59dd0035340b3d8fec5ea644ac4563c4260b2c078880bf81ffec0e4cd9193b708ded6431c0e7d9e8f45d595712b733262f8f62f1b4c3ae69f1f39bc68a39b1b5699adddfd7c51b83f59479fe5ffe0faef6376b1c5cea434aa9db85e792f989b5977c6fda87f7c00f79e67e417d826c1ab1fa304163414fc6321790f07cffede43170718536e5fe3128f6d101de82a7b1de37f89e61d822f09eef7304213d41998a49e5ab6b1a7eb1ab4ece21f005061828567047aaf640cff2f87c85eefc2d3a91ebf48aaa893e59451acbea894975df2587b203302fb39755f2e21e012d1fc89df86ec53723df497318d8b44eee9334a2699ad403a7df6719747bc37429d3c47ada354308380b09bb6d76e21dc1735a1479470c94c0282bbbdf5e2e6af60cf1f2e9b8dad20e45307729813eaaf584b31984e036d5452dfae47a4b8640bdf4c02ecf4ce4240d64d2ab895cbf512558712533cd3fc6838bfd24a2a588b9f1b1848bb0d6b1cd77345add6e9dc547a7b95b027bb18e96f30c4f9cd780c96984472b70ea39a7acdff9c649ac4a59e12a5a72d436036b31fa130f6a72c717b3df403113ee3b3d1605f76e57e96b83e501ed5fe9200e2ea9aefa797fa0c8b6c5d8f12e4bea7359be03d3ca35d3e22e20639fc7e03c990a494402268a08fb1589dc086995b0ba3c9ffe255b6b7cf0203010001a3533051301d0603551d0e041604143e8bac5b946c2eff6a8cb337081fa4fe6ce07312301f0603551d230418301680143e8bac5b946c2eff6a8cb337081fa4fe6ce07312300f0603551d130101ff040530030101ff300d06092a864886f70d01010b0500038202010081ad7acd39e5cc60682c962d367a84d32191e5b465ed531f617daf5fd33394a3ac9a42116d34211708ada0d9bd2cbf1d4a4175d67c87116c7495ed372c585ae6bdfe0bc713aa1afd0cc3f025c322dc45be0c3be982918dea938deaaa9e5bfd1fccb3eb8a111aec0498f64bdb16f6cb07bcecd85f6b9e445cf596d85596b4f0d7147d73cbc26000d374085e9c69f56262827fa3d5a037cf1d2cfe0f0eca779b101da08a8d732ecf584a193d93449697ee24ed6f41f9735ea3a3f206f8e6b5bf0b0ff3488a31d0feaccd701a144d35c265dc32d2e650f855debbfa5bd2d9dc2d80a1b8f81013f8049bd7be83a3ec5ae554c19fd4241a6686d4094ff073022d1f16afa5a0297e54a9b56fd469b44c6904d2b542f83ff0cf6af3b649f408f72f7cb49be5583ec4b1d912a677ae1fd81779506af9b688d8b753fdb0451925752fba8efcdaedf935f2a264caa1f4fe746ac6c339cca647b25f0bd2139205e67b6e90987da8b993b85037931443a6652426ab779db090cf08b28fed862a0ccdde1568bd930bf2d39ab7b850f97925e9bda13a6ee5166e48959711065c054bdf5ff04e4b8d5120caabca40c7707da3bb10f2ae7a00a6e56b012a6c00daaec5ddf0b63f61622aeeb81a71a5aa17508e5471e777bed8d09023c24280495adc38ffc3615dd20b139d32d7cc30b0690ab7f3e47a0131fa3d81929e64c6b9c6b363da410f6e5e"]}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Certificate - Truncated within certificate", + input: "1603030acf0b000acb000ac80002923082028e308201f7a003020102021468f6f88ecf1bf3d14e7503ef2e1b789cb77b86c3300d06092a864886f70d01010b05003058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d3020170d3234303932323039353335385a180f32313234303832393039353335385a3058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100c3df3e5745f05b3aa220ce4108838107653c3ae9584ace27d7088506ebdc3531afbe6265719278682eaa4fec7ae1f319395d356be79477bc62edbe7207d96f5717e9bd9083fdcc797c1b8e38bcf9fd08df6f101bc2a06101ddce6be2f5a0de80ebc8fdce2538867c1d6a84acef26b2068c5d27771abcee071bcf378899cb32730203010001a3533051301d0603551d0e041604144c9b134c1575c51ae9d03c4020da7541278ad928301f0603551d230418301680144c9b134c1575c51ae9d03c4020da7541278ad928300f0603551d130101ff040530030101ff300d06092a864886f70d01010b05000381810012a06cced33d721b1d7912ff0b190b74524ddfdeca103aba0f168f4f15f57212ba7d66328e48b021f32cfec84f65d79821bc1fe9f472f60c094e537160708a48a0898dbf613cece86892cf48fcd598757aa4379e18673626be2f048e35f585086ea7a3766ce50a14ca6f691b369c965e062f40619cde6262ed8019b522e76eaf00029430820290308201f9a00302010202142a3329f5e2e92940318cecd036ff135525b1d491300d06092a864886f70d01010b05003059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d3020170d3234303932323039353531375a180f32313234303832393039353531375a3059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100b27c861d957c49111b4f37f65bc142da564429c74a925e3de6d9add55ccfccf1316a5002b3ed2d35ec9822499e7256f9caaa2191010df354185c63a32c8d080ba49510953d7ec2210685030564be69a9f2262a9da22f3623b2a9b032f3a82b1c31ce11336c288fc3d5f63565aacc8c0f85ebaad6af2cd3505a7cf3945ca2ca690203010001a3533051301d0603551d0e0416041485478b7936ecd417647e9d8582d3f68fc670d839301f0603551d2304183016801485478b7936ecd417647e9d8582d3f68fc670d839300f0603551d130101ff040530030101ff300d06092a864886f70d01010b050003818100652656aef44c7a507a376de248cd1b36028fb1b0292593f88eb36b429f7de4c668aef7b0d862c9314e5d870f7c28353022657a7de07ec69505a54e48337ab6ba425bfd8865b720f1f2e86c92edaa261fd73e44856ac45c4d9378c86adb96b6f999f61e5f651cb885e06a3d909b5fa79458941bea36785ea585aeb5025032a18d0005990x308205953082037da00302010202141521d02e945395325d99051e616ad01c97627ee2300d06092a864886f70d01010b05003059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65332e636f6d3020170d3234303932323130303232325a180f32313234303832393130303232325a3059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65332e636f6d30820222300d06092a864886f70d01010105000382020f003082020a0282020100bd7c65b5c2c7027e4eb77722f84d7dc9b45f9fae45c59dd0035340b3d8fec5ea644ac4563c4260b2c078880bf81ffec0e4cd9193b708ded6431c0e7d9e8f45d595712b733262f8f62f1b4c3ae69f1f39bc68a39b1b5699adddfd7c51b83f59479fe5ffe0faef6376b1c5cea434aa9db85e792f989b5977c6fda87f7c00f79e67e417d826c1ab1fa304163414fc6321790f07cffede43170718536e5fe3128f6d101de82a7b1de37f89e61d822f09eef7304213d41998a49e5ab6b1a7eb1ab4ece21f005061828567047aaf640cff2f87c85eefc2d3a91ebf48aaa893e59451acbea894975df2587b203302fb39755f2e21e012d1fc89df86ec53723df497318d8b44eee9334a2699ad403a7df6719747bc37429d3c47ada354308380b09bb6d76e21dc1735a1479470c94c0282bbbdf5e2e6af60cf1f2e9b8dad20e45307729813eaaf584b31984e036d5452dfae47a4b8640bdf4c02ecf4ce4240d64d2ab895cbf512558712533cd3fc6838bfd24a2a588b9f1b1848bb0d6b1cd77345add6e9dc547a7b95b027bb18e96f30c4f9cd780c96984472b70ea39a7acdff9c649ac4a5", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":2767,"truncated":true,"handshakeType":"certificate","certificateList":{"length":2760,"truncated":true,"values":["0x3082028e308201f7a003020102021468f6f88ecf1bf3d14e7503ef2e1b789cb77b86c3300d06092a864886f70d01010b05003058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d3020170d3234303932323039353335385a180f32313234303832393039353335385a3058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100c3df3e5745f05b3aa220ce4108838107653c3ae9584ace27d7088506ebdc3531afbe6265719278682eaa4fec7ae1f319395d356be79477bc62edbe7207d96f5717e9bd9083fdcc797c1b8e38bcf9fd08df6f101bc2a06101ddce6be2f5a0de80ebc8fdce2538867c1d6a84acef26b2068c5d27771abcee071bcf378899cb32730203010001a3533051301d0603551d0e041604144c9b134c1575c51ae9d03c4020da7541278ad928301f0603551d230418301680144c9b134c1575c51ae9d03c4020da7541278ad928300f0603551d130101ff040530030101ff300d06092a864886f70d01010b05000381810012a06cced33d721b1d7912ff0b190b74524ddfdeca103aba0f168f4f15f57212ba7d66328e48b021f32cfec84f65d79821bc1fe9f472f60c094e537160708a48a0898dbf613cece86892cf48fcd598757aa4379e18673626be2f048e35f585086ea7a3766ce50a14ca6f691b369c965e062f40619cde6262ed8019b522e76eaf","0x30820290308201f9a00302010202142a3329f5e2e92940318cecd036ff135525b1d491300d06092a864886f70d01010b05003059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d3020170d3234303932323039353531375a180f32313234303832393039353531375a3059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100b27c861d957c49111b4f37f65bc142da564429c74a925e3de6d9add55ccfccf1316a5002b3ed2d35ec9822499e7256f9caaa2191010df354185c63a32c8d080ba49510953d7ec2210685030564be69a9f2262a9da22f3623b2a9b032f3a82b1c31ce11336c288fc3d5f63565aacc8c0f85ebaad6af2cd3505a7cf3945ca2ca690203010001a3533051301d0603551d0e0416041485478b7936ecd417647e9d8582d3f68fc670d839301f0603551d2304183016801485478b7936ecd417647e9d8582d3f68fc670d839300f0603551d130101ff040530030101ff300d06092a864886f70d01010b050003818100652656aef44c7a507a376de248cd1b36028fb1b0292593f88eb36b429f7de4c668aef7b0d862c9314e5d870f7c28353022657a7de07ec69505a54e48337ab6ba425bfd8865b720f1f2e86c92edaa261fd73e44856ac45c4d9378c86adb96b6f999f61e5f651cb885e06a3d909b5fa79458941bea36785ea585aeb5025032a18d"]}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Certificate - Truncated before certificate", + input: "1603030acf0b000acb000ac80002923082028e308201f7a003020102021468f6f88ecf1bf3d14e7503ef2e1b789cb77b86c3300d06092a864886f70d01010b05003058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d3020170d3234303932323039353335385a180f32313234303832393039353335385a3058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100c3df3e5745f05b3aa220ce4108838107653c3ae9584ace27d7088506ebdc3531afbe6265719278682eaa4fec7ae1f319395d356be79477bc62edbe7207d96f5717e9bd9083fdcc797c1b8e38bcf9fd08df6f101bc2a06101ddce6be2f5a0de80ebc8fdce2538867c1d6a84acef26b2068c5d27771abcee071bcf378899cb32730203010001a3533051301d0603551d0e041604144c9b134c1575c51ae9d03c4020da7541278ad928301f0603551d230418301680144c9b134c1575c51ae9d03c4020da7541278ad928300f0603551d130101ff040530030101ff300d06092a864886f70d01010b05000381810012a06cced33d721b1d7912ff0b190b74524ddfdeca103aba0f168f4f15f57212ba7d66328e48b021f32cfec84f65d79821bc1fe9f472f60c094e537160708a48a0898dbf613cece86892cf48fcd598757aa4379e18673626be2f048e35f585086ea7a3766ce50a14ca6f691b369c965e062f40619cde6262ed8019b522e76eaf00029430820290308201f9a00302010202142a3329f5e2e92940318cecd036ff135525b1d491300d06092a864886f70d01010b05003059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d3020170d3234303932323039353531375a180f32313234303832393039353531375a3059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100b27c861d957c49111b4f37f65bc142da564429c74a925e3de6d9add55ccfccf1316a5002b3ed2d35ec9822499e7256f9caaa2191010df354185c63a32c8d080ba49510953d7ec2210685030564be69a9f2262a9da22f3623b2a9b032f3a82b1c31ce11336c288fc3d5f63565aacc8c0f85ebaad6af2cd3505a7cf3945ca2ca690203010001a3533051301d0603551d0e0416041485478b7936ecd417647e9d8582d3f68fc670d839301f0603551d2304183016801485478b7936ecd417647e9d8582d3f68fc670d839300f0603551d130101ff040530030101ff300d06092a864886f70d01010b050003818100652656aef44c7a507a376de248cd1b36028fb1b0292593f88eb36b429f7de4c668aef7b0d862c9314e5d870f7c28353022657a7de07ec69505a54e48337ab6ba425bfd8865b720f1f2e86c92edaa261fd73e44856ac45c4d9378c86adb96b6f999f61e5f651cb885e06a3d909b5fa79458941bea36785ea585aeb5025032a18d000599", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":2767,"truncated":true,"handshakeType":"certificate","certificateList":{"length":2760,"truncated":true,"values":["0x3082028e308201f7a003020102021468f6f88ecf1bf3d14e7503ef2e1b789cb77b86c3300d06092a864886f70d01010b05003058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d3020170d3234303932323039353335385a180f32313234303832393039353335385a3058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100c3df3e5745f05b3aa220ce4108838107653c3ae9584ace27d7088506ebdc3531afbe6265719278682eaa4fec7ae1f319395d356be79477bc62edbe7207d96f5717e9bd9083fdcc797c1b8e38bcf9fd08df6f101bc2a06101ddce6be2f5a0de80ebc8fdce2538867c1d6a84acef26b2068c5d27771abcee071bcf378899cb32730203010001a3533051301d0603551d0e041604144c9b134c1575c51ae9d03c4020da7541278ad928301f0603551d230418301680144c9b134c1575c51ae9d03c4020da7541278ad928300f0603551d130101ff040530030101ff300d06092a864886f70d01010b05000381810012a06cced33d721b1d7912ff0b190b74524ddfdeca103aba0f168f4f15f57212ba7d66328e48b021f32cfec84f65d79821bc1fe9f472f60c094e537160708a48a0898dbf613cece86892cf48fcd598757aa4379e18673626be2f048e35f585086ea7a3766ce50a14ca6f691b369c965e062f40619cde6262ed8019b522e76eaf","0x30820290308201f9a00302010202142a3329f5e2e92940318cecd036ff135525b1d491300d06092a864886f70d01010b05003059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d3020170d3234303932323039353531375a180f32313234303832393039353531375a3059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100b27c861d957c49111b4f37f65bc142da564429c74a925e3de6d9add55ccfccf1316a5002b3ed2d35ec9822499e7256f9caaa2191010df354185c63a32c8d080ba49510953d7ec2210685030564be69a9f2262a9da22f3623b2a9b032f3a82b1c31ce11336c288fc3d5f63565aacc8c0f85ebaad6af2cd3505a7cf3945ca2ca690203010001a3533051301d0603551d0e0416041485478b7936ecd417647e9d8582d3f68fc670d839301f0603551d2304183016801485478b7936ecd417647e9d8582d3f68fc670d839300f0603551d130101ff040530030101ff300d06092a864886f70d01010b050003818100652656aef44c7a507a376de248cd1b36028fb1b0292593f88eb36b429f7de4c668aef7b0d862c9314e5d870f7c28353022657a7de07ec69505a54e48337ab6ba425bfd8865b720f1f2e86c92edaa261fd73e44856ac45c4d9378c86adb96b6f999f61e5f651cb885e06a3d909b5fa79458941bea36785ea585aeb5025032a18d"]}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Certificate - Truncated within certificate length", + input: "1603030acf0b000acb000ac80002923082028e308201f7a003020102021468f6f88ecf1bf3d14e7503ef2e1b789cb77b86c3300d06092a864886f70d01010b05003058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d3020170d3234303932323039353335385a180f32313234303832393039353335385a3058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100c3df3e5745f05b3aa220ce4108838107653c3ae9584ace27d7088506ebdc3531afbe6265719278682eaa4fec7ae1f319395d356be79477bc62edbe7207d96f5717e9bd9083fdcc797c1b8e38bcf9fd08df6f101bc2a06101ddce6be2f5a0de80ebc8fdce2538867c1d6a84acef26b2068c5d27771abcee071bcf378899cb32730203010001a3533051301d0603551d0e041604144c9b134c1575c51ae9d03c4020da7541278ad928301f0603551d230418301680144c9b134c1575c51ae9d03c4020da7541278ad928300f0603551d130101ff040530030101ff300d06092a864886f70d01010b05000381810012a06cced33d721b1d7912ff0b190b74524ddfdeca103aba0f168f4f15f57212ba7d66328e48b021f32cfec84f65d79821bc1fe9f472f60c094e537160708a48a0898dbf613cece86892cf48fcd598757aa4379e18673626be2f048e35f585086ea7a3766ce50a14ca6f691b369c965e062f40619cde6262ed8019b522e76eaf00029430820290308201f9a00302010202142a3329f5e2e92940318cecd036ff135525b1d491300d06092a864886f70d01010b05003059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d3020170d3234303932323039353531375a180f32313234303832393039353531375a3059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100b27c861d957c49111b4f37f65bc142da564429c74a925e3de6d9add55ccfccf1316a5002b3ed2d35ec9822499e7256f9caaa2191010df354185c63a32c8d080ba49510953d7ec2210685030564be69a9f2262a9da22f3623b2a9b032f3a82b1c31ce11336c288fc3d5f63565aacc8c0f85ebaad6af2cd3505a7cf3945ca2ca690203010001a3533051301d0603551d0e0416041485478b7936ecd417647e9d8582d3f68fc670d839301f0603551d2304183016801485478b7936ecd417647e9d8582d3f68fc670d839300f0603551d130101ff040530030101ff300d06092a864886f70d01010b050003818100652656aef44c7a507a376de248cd1b36028fb1b0292593f88eb36b429f7de4c668aef7b0d862c9314e5d870f7c28353022657a7de07ec69505a54e48337ab6ba425bfd8865b720f1f2e86c92edaa261fd73e44856ac45c4d9378c86adb96b6f999f61e5f651cb885e06a3d909b5fa79458941bea36785ea585aeb5025032a18d0005", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":2767,"truncated":true,"handshakeType":"certificate","certificateList":{"length":2760,"truncated":true,"values":["0x3082028e308201f7a003020102021468f6f88ecf1bf3d14e7503ef2e1b789cb77b86c3300d06092a864886f70d01010b05003058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d3020170d3234303932323039353335385a180f32313234303832393039353335385a3058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100c3df3e5745f05b3aa220ce4108838107653c3ae9584ace27d7088506ebdc3531afbe6265719278682eaa4fec7ae1f319395d356be79477bc62edbe7207d96f5717e9bd9083fdcc797c1b8e38bcf9fd08df6f101bc2a06101ddce6be2f5a0de80ebc8fdce2538867c1d6a84acef26b2068c5d27771abcee071bcf378899cb32730203010001a3533051301d0603551d0e041604144c9b134c1575c51ae9d03c4020da7541278ad928301f0603551d230418301680144c9b134c1575c51ae9d03c4020da7541278ad928300f0603551d130101ff040530030101ff300d06092a864886f70d01010b05000381810012a06cced33d721b1d7912ff0b190b74524ddfdeca103aba0f168f4f15f57212ba7d66328e48b021f32cfec84f65d79821bc1fe9f472f60c094e537160708a48a0898dbf613cece86892cf48fcd598757aa4379e18673626be2f048e35f585086ea7a3766ce50a14ca6f691b369c965e062f40619cde6262ed8019b522e76eaf","0x30820290308201f9a00302010202142a3329f5e2e92940318cecd036ff135525b1d491300d06092a864886f70d01010b05003059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d3020170d3234303932323039353531375a180f32313234303832393039353531375a3059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100b27c861d957c49111b4f37f65bc142da564429c74a925e3de6d9add55ccfccf1316a5002b3ed2d35ec9822499e7256f9caaa2191010df354185c63a32c8d080ba49510953d7ec2210685030564be69a9f2262a9da22f3623b2a9b032f3a82b1c31ce11336c288fc3d5f63565aacc8c0f85ebaad6af2cd3505a7cf3945ca2ca690203010001a3533051301d0603551d0e0416041485478b7936ecd417647e9d8582d3f68fc670d839301f0603551d2304183016801485478b7936ecd417647e9d8582d3f68fc670d839300f0603551d130101ff040530030101ff300d06092a864886f70d01010b050003818100652656aef44c7a507a376de248cd1b36028fb1b0292593f88eb36b429f7de4c668aef7b0d862c9314e5d870f7c28353022657a7de07ec69505a54e48337ab6ba425bfd8865b720f1f2e86c92edaa261fd73e44856ac45c4d9378c86adb96b6f999f61e5f651cb885e06a3d909b5fa79458941bea36785ea585aeb5025032a18d"]}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Certificate - Truncated before certificate length", + input: "1603030acf0b000acb000ac80002923082028e308201f7a003020102021468f6f88ecf1bf3d14e7503ef2e1b789cb77b86c3300d06092a864886f70d01010b05003058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d3020170d3234303932323039353335385a180f32313234303832393039353335385a3058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100c3df3e5745f05b3aa220ce4108838107653c3ae9584ace27d7088506ebdc3531afbe6265719278682eaa4fec7ae1f319395d356be79477bc62edbe7207d96f5717e9bd9083fdcc797c1b8e38bcf9fd08df6f101bc2a06101ddce6be2f5a0de80ebc8fdce2538867c1d6a84acef26b2068c5d27771abcee071bcf378899cb32730203010001a3533051301d0603551d0e041604144c9b134c1575c51ae9d03c4020da7541278ad928301f0603551d230418301680144c9b134c1575c51ae9d03c4020da7541278ad928300f0603551d130101ff040530030101ff300d06092a864886f70d01010b05000381810012a06cced33d721b1d7912ff0b190b74524ddfdeca103aba0f168f4f15f57212ba7d66328e48b021f32cfec84f65d79821bc1fe9f472f60c094e537160708a48a0898dbf613cece86892cf48fcd598757aa4379e18673626be2f048e35f585086ea7a3766ce50a14ca6f691b369c965e062f40619cde6262ed8019b522e76eaf00029430820290308201f9a00302010202142a3329f5e2e92940318cecd036ff135525b1d491300d06092a864886f70d01010b05003059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d3020170d3234303932323039353531375a180f32313234303832393039353531375a3059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100b27c861d957c49111b4f37f65bc142da564429c74a925e3de6d9add55ccfccf1316a5002b3ed2d35ec9822499e7256f9caaa2191010df354185c63a32c8d080ba49510953d7ec2210685030564be69a9f2262a9da22f3623b2a9b032f3a82b1c31ce11336c288fc3d5f63565aacc8c0f85ebaad6af2cd3505a7cf3945ca2ca690203010001a3533051301d0603551d0e0416041485478b7936ecd417647e9d8582d3f68fc670d839301f0603551d2304183016801485478b7936ecd417647e9d8582d3f68fc670d839300f0603551d130101ff040530030101ff300d06092a864886f70d01010b050003818100652656aef44c7a507a376de248cd1b36028fb1b0292593f88eb36b429f7de4c668aef7b0d862c9314e5d870f7c28353022657a7de07ec69505a54e48337ab6ba425bfd8865b720f1f2e86c92edaa261fd73e44856ac45c4d9378c86adb96b6f999f61e5f651cb885e06a3d909b5fa79458941bea36785ea585aeb5025032a18d", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":2767,"truncated":true,"handshakeType":"certificate","certificateList":{"length":2760,"truncated":true,"values":["0x3082028e308201f7a003020102021468f6f88ecf1bf3d14e7503ef2e1b789cb77b86c3300d06092a864886f70d01010b05003058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d3020170d3234303932323039353335385a180f32313234303832393039353335385a3058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100c3df3e5745f05b3aa220ce4108838107653c3ae9584ace27d7088506ebdc3531afbe6265719278682eaa4fec7ae1f319395d356be79477bc62edbe7207d96f5717e9bd9083fdcc797c1b8e38bcf9fd08df6f101bc2a06101ddce6be2f5a0de80ebc8fdce2538867c1d6a84acef26b2068c5d27771abcee071bcf378899cb32730203010001a3533051301d0603551d0e041604144c9b134c1575c51ae9d03c4020da7541278ad928301f0603551d230418301680144c9b134c1575c51ae9d03c4020da7541278ad928300f0603551d130101ff040530030101ff300d06092a864886f70d01010b05000381810012a06cced33d721b1d7912ff0b190b74524ddfdeca103aba0f168f4f15f57212ba7d66328e48b021f32cfec84f65d79821bc1fe9f472f60c094e537160708a48a0898dbf613cece86892cf48fcd598757aa4379e18673626be2f048e35f585086ea7a3766ce50a14ca6f691b369c965e062f40619cde6262ed8019b522e76eaf","0x30820290308201f9a00302010202142a3329f5e2e92940318cecd036ff135525b1d491300d06092a864886f70d01010b05003059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d3020170d3234303932323039353531375a180f32313234303832393039353531375a3059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100b27c861d957c49111b4f37f65bc142da564429c74a925e3de6d9add55ccfccf1316a5002b3ed2d35ec9822499e7256f9caaa2191010df354185c63a32c8d080ba49510953d7ec2210685030564be69a9f2262a9da22f3623b2a9b032f3a82b1c31ce11336c288fc3d5f63565aacc8c0f85ebaad6af2cd3505a7cf3945ca2ca690203010001a3533051301d0603551d0e0416041485478b7936ecd417647e9d8582d3f68fc670d839301f0603551d2304183016801485478b7936ecd417647e9d8582d3f68fc670d839300f0603551d130101ff040530030101ff300d06092a864886f70d01010b050003818100652656aef44c7a507a376de248cd1b36028fb1b0292593f88eb36b429f7de4c668aef7b0d862c9314e5d870f7c28353022657a7de07ec69505a54e48337ab6ba425bfd8865b720f1f2e86c92edaa261fd73e44856ac45c4d9378c86adb96b6f999f61e5f651cb885e06a3d909b5fa79458941bea36785ea585aeb5025032a18d"]}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Certificate - Truncated within certificate list length", + input: "1603030acf0b000acb000a", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":2767,"truncated":true,"handshakeType":"certificate","certificateList":{}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Certificate - Truncated before certificate list length", + input: "1603030acf0b000acb", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":2767,"truncated":true,"handshakeType":"certificate"}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Server Key Exchange", + input: "16030300840c000080a90c12174921d7044303107b6e37523957439b436e57904e82702784bfc261a8f0a7e4143a77144357d29ee322f25e4fce393ac7570ee26c378298a6ad18fd8b87175e472c7c07b97699f72958e0af489df00d34e5e03dde2e09dfe06d448651ee45c07fadc05e0d1585589e3715a04b935e72bc28c34593712acef7883ed69a", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":132,"handshakeType":"server_key_exchange","handshakeValue":"0xa90c12174921d7044303107b6e37523957439b436e57904e82702784bfc261a8f0a7e4143a77144357d29ee322f25e4fce393ac7570ee26c378298a6ad18fd8b87175e472c7c07b97699f72958e0af489df00d34e5e03dde2e09dfe06d448651ee45c07fadc05e0d1585589e3715a04b935e72bc28c34593712acef7883ed69a"}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Server Key Exchange - Truncated within content", + input: "16030300840c000080a90c12174921d7044303107b6e37523957439b436e57904e82702784bfc261a8f0a7e4143a77144357d29ee322f25e4fce393ac7570ee26c378298a6ad18fd8b", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":132,"truncated":true,"handshakeType":"server_key_exchange","handshakeValue":"0xa90c12174921d7044303107b6e37523957439b436e57904e82702784bfc261a8f0a7e4143a77144357d29ee322f25e4fce393ac7570ee26c378298a6ad18fd8b"}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Server Key Exchange - Truncated before content", + input: "16030300840c000080", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":132,"truncated":true,"handshakeType":"server_key_exchange"}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Certificate Request, No certificate authorities", + input: "160303001f0d00001b040102030400120601060206030301030203030201020202030000", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":31,"handshakeType":"certificate_request","certificateTypes":{"length":4,"values":["0x01","0x02","0x03","0x04"]},"supportedSignatureAlgorithms":{"length":18,"values":["0x0601","0x0602","0x0603","0x0301","0x0302","0x0303","0x0201","0x0202","0x0203"]}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Certificate Request, Certificate authorities", + input: "16030300470d000043040102030400120601060206030301030203030201020202030028000c546bf13f358cf3ddc1eef77d001813b3cdd60a34fc74f2e4ef2344cfd2156924d8d2810e2c86", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":71,"handshakeType":"certificate_request","certificateTypes":{"length":4,"values":["0x01","0x02","0x03","0x04"]},"supportedSignatureAlgorithms":{"length":18,"values":["0x0601","0x0602","0x0603","0x0301","0x0302","0x0303","0x0201","0x0202","0x0203"]},"certificateAuthorities":{"length":40,"values":["0x546bf13f358cf3ddc1eef77d","0x13b3cdd60a34fc74f2e4ef2344cfd2156924d8d2810e2c86"]}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Certificate Request, Certificate authorities - Truncated within certificate authority", + input: "16030300470d000043040102030400120601060206030301030203030201020202030028000c546bf13f358cf3ddc1eef77d001813b3cdd60a34fc74f2e4ef23", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":71,"truncated":true,"handshakeType":"certificate_request","certificateTypes":{"length":4,"values":["0x01","0x02","0x03","0x04"]},"supportedSignatureAlgorithms":{"length":18,"values":["0x0601","0x0602","0x0603","0x0301","0x0302","0x0303","0x0201","0x0202","0x0203"]},"certificateAuthorities":{"length":40,"truncated":true,"values":["0x546bf13f358cf3ddc1eef77d"]}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Certificate Request, Certificate authorities - Truncated before certificate authority", + input: "16030300470d000043040102030400120601060206030301030203030201020202030028000c546bf13f358cf3ddc1eef77d0018", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":71,"truncated":true,"handshakeType":"certificate_request","certificateTypes":{"length":4,"values":["0x01","0x02","0x03","0x04"]},"supportedSignatureAlgorithms":{"length":18,"values":["0x0601","0x0602","0x0603","0x0301","0x0302","0x0303","0x0201","0x0202","0x0203"]},"certificateAuthorities":{"length":40,"truncated":true,"values":["0x546bf13f358cf3ddc1eef77d"]}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Certificate Request, Certificate authorities - Truncated within certificate authority length", + input: "16030300470d000043040102030400120601060206030301030203030201020202030028000c546bf13f358cf3ddc1eef77d00", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":71,"truncated":true,"handshakeType":"certificate_request","certificateTypes":{"length":4,"values":["0x01","0x02","0x03","0x04"]},"supportedSignatureAlgorithms":{"length":18,"values":["0x0601","0x0602","0x0603","0x0301","0x0302","0x0303","0x0201","0x0202","0x0203"]},"certificateAuthorities":{"length":40,"truncated":true,"values":["0x546bf13f358cf3ddc1eef77d"]}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Certificate Request, Certificate authorities - Truncated before certificate authority length", + input: "16030300470d000043040102030400120601060206030301030203030201020202030028000c546bf13f358cf3ddc1eef77d", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":71,"truncated":true,"handshakeType":"certificate_request","certificateTypes":{"length":4,"values":["0x01","0x02","0x03","0x04"]},"supportedSignatureAlgorithms":{"length":18,"values":["0x0601","0x0602","0x0603","0x0301","0x0302","0x0303","0x0201","0x0202","0x0203"]},"certificateAuthorities":{"length":40,"truncated":true,"values":["0x546bf13f358cf3ddc1eef77d"]}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Certificate Request, Certificate authorities - Truncated within certificate authorities length", + input: "16030300470d0000430401020304001206010602060303010302030302010202020300", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":71,"truncated":true,"handshakeType":"certificate_request","certificateTypes":{"length":4,"values":["0x01","0x02","0x03","0x04"]},"supportedSignatureAlgorithms":{"length":18,"values":["0x0601","0x0602","0x0603","0x0301","0x0302","0x0303","0x0201","0x0202","0x0203"]}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Certificate Request, Certificate authorities - Truncated before certificate authorities length", + input: "16030300470d00004304010203040012060106020603030103020303020102020203", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":71,"truncated":true,"handshakeType":"certificate_request","certificateTypes":{"length":4,"values":["0x01","0x02","0x03","0x04"]},"supportedSignatureAlgorithms":{"length":18,"values":["0x0601","0x0602","0x0603","0x0301","0x0302","0x0303","0x0201","0x0202","0x0203"]}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Certificate Request, Certificate authorities - Truncated within supported signature algorithm", + input: "16030300470d000043040102030400120601060206030301030203030201020202", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":71,"truncated":true,"handshakeType":"certificate_request","certificateTypes":{"length":4,"values":["0x01","0x02","0x03","0x04"]},"supportedSignatureAlgorithms":{"length":18,"truncated":true,"values":["0x0601","0x0602","0x0603","0x0301","0x0302","0x0303","0x0201","0x0202"]}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Certificate Request, Certificate authorities - Truncated before supported signature algorithm", + input: "16030300470d0000430401020304001206010602060303010302030302010202", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":71,"truncated":true,"handshakeType":"certificate_request","certificateTypes":{"length":4,"values":["0x01","0x02","0x03","0x04"]},"supportedSignatureAlgorithms":{"length":18,"truncated":true,"values":["0x0601","0x0602","0x0603","0x0301","0x0302","0x0303","0x0201","0x0202"]}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Certificate Request, Certificate authorities - Truncated within supported signature algorithms length", + input: "16030300470d000043040102030400", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":71,"truncated":true,"handshakeType":"certificate_request","certificateTypes":{"length":4,"values":["0x01","0x02","0x03","0x04"]},"supportedSignatureAlgorithms":{}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Certificate Request, Certificate authorities - Truncated before supported signature algorithms length", + input: "16030300470d0000430401020304", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":71,"truncated":true,"handshakeType":"certificate_request","certificateTypes":{"length":4,"values":["0x01","0x02","0x03","0x04"]},"supportedSignatureAlgorithms":{}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Certificate Request, Certificate authorities - Truncated within certificate types", + input: "16030300470d00004304010203", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":71,"truncated":true,"handshakeType":"certificate_request","certificateTypes":{"length":4,"truncated":true,"values":["0x01","0x02","0x03"]},"supportedSignatureAlgorithms":{}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Certificate Request, Certificate authorities - Truncated before certificate types", + input: "16030300470d00004304", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":71,"truncated":true,"handshakeType":"certificate_request","certificateTypes":{"length":4,"truncated":true,"values":[]},"supportedSignatureAlgorithms":{}}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Certificate Request, Certificate authorities - Truncated before certificate types length", + input: "16030300470d000043", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":71,"truncated":true,"handshakeType":"certificate_request"}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Server Hello Done", + input: "16030300040e000000", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":4,"handshakeType":"server_hello_done"}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Certificate Verify", + input: "16030301080f000104040101009310d3dda84b149a00258f0bb4501e710f7ed70a45cf4f0bab39dac1a456027f0f6167924f08a8221613bcf46c27e91458d05163200fd1bf3673351d74693c08c6640635d4e9f84e9568e39d3346e3ff2f3eacf9887d738935d8b07e42659dd3b212662bf028bcefe98b686a1a83fb2f24aead94cccd3f6b26c9d42ba43254d2a93d1b85ae2d0ee7c7170aac3397fa6de77183d30c99e6bb0e81f925793f64d8b490cb74d051896ebee9086c7606905b21bab6ebd9866a451958f7d839134aeb335b2ad5f9ce89a69321a099c081b5166332cf2bb231dd135b79cf94218e6ada94644eaa09ae6c0ec0164e3cca631c0f4b7b9a2d59fb40909ec88805e61b5917", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":264,"handshakeType":"certificate_verify","algorithmHash":"0x04","algorithmSignature":"0x01","signature":"0x9310d3dda84b149a00258f0bb4501e710f7ed70a45cf4f0bab39dac1a456027f0f6167924f08a8221613bcf46c27e91458d05163200fd1bf3673351d74693c08c6640635d4e9f84e9568e39d3346e3ff2f3eacf9887d738935d8b07e42659dd3b212662bf028bcefe98b686a1a83fb2f24aead94cccd3f6b26c9d42ba43254d2a93d1b85ae2d0ee7c7170aac3397fa6de77183d30c99e6bb0e81f925793f64d8b490cb74d051896ebee9086c7606905b21bab6ebd9866a451958f7d839134aeb335b2ad5f9ce89a69321a099c081b5166332cf2bb231dd135b79cf94218e6ada94644eaa09ae6c0ec0164e3cca631c0f4b7b9a2d59fb40909ec88805e61b5917"}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Certificate Verify - Truncated within signature", + input: "16030301080f000104040101009310d3dda84b149a00258f0bb4501e710f7ed70a45cf4f0bab39dac1a456027f0f6167924f08a8221613bcf46c27e91458d05163200fd1bf3673351d74693c08c6640635d4e9f84e9568e39d3346e3ff2f3eacf9887d738935d8b07e42659dd3b212662bf028bcefe98b686a1a83fb2f24aead94cccd3f6b26c9d42ba43254d2", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":264,"truncated":true,"handshakeType":"certificate_verify","algorithmHash":"0x04","algorithmSignature":"0x01","signature":""}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Certificate Verify - Truncated before signature", + input: "16030301080f00010404010100", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":264,"truncated":true,"handshakeType":"certificate_verify","algorithmHash":"0x04","algorithmSignature":"0x01","signature":""}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Certificate Verify - Truncated within signature length", + input: "16030301080f000104040101", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":264,"truncated":true,"handshakeType":"certificate_verify","algorithmHash":"0x04","algorithmSignature":"0x01","signature":""}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Certificate Verify - Truncated before signature length", + input: "16030301080f0001040401", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":264,"truncated":true,"handshakeType":"certificate_verify","algorithmHash":"0x04","algorithmSignature":"0x01","signature":""}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Certificate Verify - Truncated before algorithm.signature", + input: "16030301080f00010404", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":264,"truncated":true,"handshakeType":"certificate_verify","algorithmHash":"0x04","algorithmSignature":"","signature":""}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Certificate Verify - Truncated before algorithm.hash", + input: "16030301080f000104", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":264,"truncated":true,"handshakeType":"certificate_verify"}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Client Key Exchange", + input: "1603030084100000802b45af77539975e975c9389030193bb6d7841d870e058850a5aac5f8ded75d243ae8bec2bc8ba4e683eba22d5820b555c69f97001aa7d56cba1839588e7f1602ad0b4cb7319fc52694a67f1e381b4d8a581823410920717ee85ef352dea39097e6b131bdfeb3913f0f7eaa3b3882abe4615cc13e2a133558adff159771dfdc8d", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":132,"handshakeType":"client_key_exchange","handshakeValue":"0x2b45af77539975e975c9389030193bb6d7841d870e058850a5aac5f8ded75d243ae8bec2bc8ba4e683eba22d5820b555c69f97001aa7d56cba1839588e7f1602ad0b4cb7319fc52694a67f1e381b4d8a581823410920717ee85ef352dea39097e6b131bdfeb3913f0f7eaa3b3882abe4615cc13e2a133558adff159771dfdc8d"}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Client Key Exchange - Truncated within content", + input: "1603030084100000802b45af77539975e975c9389030193bb6d7841d870e058850a5aac5f8ded75d243ae8bec2bc8ba4e683eba22d5820b555c69f97001aa7d56cba1839588e7f1602", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":132,"truncated":true,"handshakeType":"client_key_exchange","handshakeValue":"0x2b45af77539975e975c9389030193bb6d7841d870e058850a5aac5f8ded75d243ae8bec2bc8ba4e683eba22d5820b555c69f97001aa7d56cba1839588e7f1602"}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Client Key Exchange - Truncated before content", + input: "160303008410000080", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":132,"truncated":true,"handshakeType":"client_key_exchange"}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Finished", + input: "1603030028ed83078db91b046358065ca3f7ea4494af3deb59bf72f522e15ef9071c52becb0069a093b23994c1", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":40,"handshakeType":"finished","handshakeValue":"0xed83078db91b046358065ca3f7ea4494af3deb59bf72f522e15ef9071c52becb0069a093b23994c1"}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Finished - Truncated within ciphertext", + input: "1603030028ed83078db91b046358065ca3f7ea4494af3deb59", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":40,"truncated":true,"handshakeType":"finished","handshakeValue":"0xed83078db91b046358065ca3f7ea4494af3deb59"}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Unknown", + input: "1603030024120000203c210cd33fd2a7379ae02700b208ae7357f98b46a1dea566c4061acfb6e188bc", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":36,"handshakeType":"18","handshakeValue":"0x3c210cd33fd2a7379ae02700b208ae7357f98b46a1dea566c4061acfb6e188bc"}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Unknown - Truncated within content", + input: "1603030024120000203c210cd33fd2a7379ae02700b208", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":36,"truncated":true,"handshakeType":"18","handshakeValue":"0x3c210cd33fd2a7379ae02700b208"}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Handshake - Unknown - Truncated before content", + input: "160303002412000020", + expectedOutput: '[{"type":"handshake","version":"0x0303","length":36,"truncated":true,"handshakeType":"18"}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Application Data", + input: "1703030064bbfd70f5d2ae0fe62262830040c264fa578bf2000ea50bb2c92d4837727f5db06b580e43896eaa1a0042b4fc3eb5aca6731705f5d957c481bade800cf1cd066dfd997851af09e820e84ee0b531b4eaccfd8b5f28b74d756a8aeadf78eefb2d26e46b5b69", + expectedOutput: '[{"type":"application_data","version":"0x0303","length":100,"value":"0xbbfd70f5d2ae0fe62262830040c264fa578bf2000ea50bb2c92d4837727f5db06b580e43896eaa1a0042b4fc3eb5aca6731705f5d957c481bade800cf1cd066dfd997851af09e820e84ee0b531b4eaccfd8b5f28b74d756a8aeadf78eefb2d26e46b5b69"}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Application Data - Truncated within content", + input: "1703030064bbfd70f5d2ae0fe62262830040c264fa578bf2000ea50bb2c92d4837727f5db06b580e43896eaa1a0042b4fc3eb5aca67317", + expectedOutput: '[{"type":"application_data","version":"0x0303","length":100,"truncated":true,"value":"0xbbfd70f5d2ae0fe62262830040c264fa578bf2000ea50bb2c92d4837727f5db06b580e43896eaa1a0042b4fc3eb5aca67317"}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Application Data - Truncated before content", + input: "1703030064", + expectedOutput: '[{"type":"application_data","version":"0x0303","length":100,"truncated":true}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Unknown", + input: "1c03030020c02beaae1dd2e9ec46c4d201d72105457af1f8e92d56ad95f339398e5774cb6f", + expectedOutput: '[{"type":"28","version":"0x0303","length":32,"value":"0xc02beaae1dd2e9ec46c4d201d72105457af1f8e92d56ad95f339398e5774cb6f"}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Unknown - Truncated within content", + input: "1c03030020c02beaae1dd2e9ec46c4d201d7210545", + expectedOutput: '[{"type":"28","version":"0x0303","length":32,"truncated":true,"value":"0xc02beaae1dd2e9ec46c4d201d7210545"}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + }, + { + name: "Parse TLS record: Unknown - Truncated before content", + input: "1c03030020", + expectedOutput: '[{"type":"28","version":"0x0303","length":32,"truncated":true}]', + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "解析TLS记录", + args: [] + }, + { + op: "JSON压缩", + args: [] + } + ] + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/ParseTLV.mjs b/plugins/srktoolbox/tests/operations/tests/ParseTLV.mjs new file mode 100644 index 00000000..fa1baea5 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/ParseTLV.mjs @@ -0,0 +1,58 @@ +/** + * Parse TLV tests. + * + * @author gchq77703 [] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Parse TLV: LengthValue", + input: "\x05\x48\x6f\x75\x73\x65\x04\x72\x6f\x6f\x6d\x04\x64\x6f\x6f\x72", + expectedOutput: JSON.stringify([{"length": 5, "value": [72, 111, 117, 115, 101]}, {"length": 4, "value": [114, 111, 111, 109]}, {"length": 4, "value": [100, 111, 111, 114]}], null, 4), + recipeConfig: [ + { + "op": "解析TLV", + "args": [0, 1, false] + } + ] + }, + { + name: "Parse TLV: LengthValue with BER", + input: "\x05\x48\x6f\x75\x73\x65\x04\x72\x6f\x6f\x6d\x04\x64\x6f\x6f\x72", + expectedOutput: JSON.stringify([{"length": 5, "value": [72, 111, 117, 115, 101]}, {"length": 4, "value": [114, 111, 111, 109]}, {"length": 4, "value": [100, 111, 111, 114]}], null, 4), + recipeConfig: [ + { + "op": "解析TLV", + "args": [0, 4, true] // length value is patently wrong, should be ignored by BER. + } + ] + }, + { + name: "Parse TLV: KeyLengthValue", + input: "\x04\x05\x48\x6f\x75\x73\x65\x05\x04\x72\x6f\x6f\x6d\x42\x04\x64\x6f\x6f\x72", + expectedOutput: JSON.stringify([{"key": [4], "length": 5, "value": [72, 111, 117, 115, 101]}, {"key": [5], "length": 4, "value": [114, 111, 111, 109]}, {"key": [66], "length": 4, "value": [100, 111, 111, 114]}], null, 4), + recipeConfig: [ + { + "op": "解析TLV", + "args": [1, 1, false] + } + ] + }, + { + name: "Parse TLV: KeyLengthValue with BER", + input: "\x04\x05\x48\x6f\x75\x73\x65\x05\x04\x72\x6f\x6f\x6d\x42\x04\x64\x6f\x6f\x72", + expectedOutput: JSON.stringify([{"key": [4], "length": 5, "value": [72, 111, 117, 115, 101]}, {"key": [5], "length": 4, "value": [114, 111, 111, 109]}, {"key": [66], "length": 4, "value": [100, 111, 111, 114]}], null, 4), + recipeConfig: [ + { + "op": "解析TLV", + "args": [1, 4, true] // length value is patently wrong, should be ignored by BER. + } + ] + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/ParseUDP.mjs b/plugins/srktoolbox/tests/operations/tests/ParseUDP.mjs new file mode 100644 index 00000000..65289a0f --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/ParseUDP.mjs @@ -0,0 +1,57 @@ +/** + * Parse UDP tests. + * + * @author h345983745 + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Parse UDP: No Data - JSON", + input: "04 89 00 35 00 2c 01 01", + expectedOutput: "{\"来源连接端口\":1161,\"目的连接端口\":53,\"长度\":44,\"校验和\":\"0x0101\"}", + recipeConfig: [ + { + op: "解析UDP", + args: ["十六进制"], + }, + { + op: "JSON压缩", + args: [], + }, + ], + }, { + name: "Parse UDP: With Data - JSON", + input: "04 89 00 35 00 2c 01 01 02 02", + expectedOutput: "{\"来源连接端口\":1161,\"目的连接端口\":53,\"长度\":44,\"校验和\":\"0x0101\",\"数据\":\"0x0202\"}", + recipeConfig: [ + { + op: "解析UDP", + args: ["十六进制"], + }, + { + op: "JSON压缩", + args: [], + }, + ], + }, + { + name: "Parse UDP: Not Enough Bytes", + input: "04 89 00", + expectedOutput: "UDP标头需要至少8字节。", + recipeConfig: [ + { + op: "解析UDP", + args: ["十六进制"], + }, + { + op: "JSON压缩", + args: [], + }, + ], + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/ParseX509CRL.mjs b/plugins/srktoolbox/tests/operations/tests/ParseX509CRL.mjs new file mode 100644 index 00000000..33de5e38 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/ParseX509CRL.mjs @@ -0,0 +1,331 @@ +/** + * Parse X.509 CRL tests. + * + * @author robinsandhu + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + */ + +import TestRegister from "../../lib/TestRegister.mjs"; + +const IN_CRL_PEM_RSA = `-----BEGIN X509 CRL----- +MIID7jCCAdYCAQEwDQYJKoZIhvcNAQELBQAwQjELMAkGA1UEBhMCVUsxDzANBgNV +BAgMBkxvbmRvbjELMAkGA1UECgwCQkIxFTATBgNVBAMMDFRlc3QgUm9vdCBDQRcN +MjQwODI1MTE0OTEwWhcNMjQwOTI0MTE0OTEwWjA1MDMCAhAAFw0yNDA4MjUwMzIz +MDhaMB4wCgYDVR0VBAMKAQYwEAYDVR0XBAkGByqGSM44AgOgggEnMIIBIzAJBgNV +HRIEAjAAMH0GA1UdIwR2MHSAFLjJrf2oUFTVhW40i0xgL7BJtodGoUakRDBCMQsw +CQYDVQQGEwJVSzEPMA0GA1UECAwGTG9uZG9uMQswCQYDVQQKDAJCQjEVMBMGA1UE +AwwMVGVzdCBSb290IENBghQ3XUv2vXwRfMxGGv/XLywm+B5LPTAtBgNVHS4EJjAk +MCKgIKAehhxodHRwOi8vZXhhbXBsZS5jb20vZGVsdGEtY3JsMFsGA1UdHwRUMFIw +IaAfoB2GG2h0dHA6Ly9leGFtcGxlLmNvbS9mdWxsLWNybDAhoB+gHYYbbGRhcDov +L2V4YW1wbGUuY29tL2Z1bGwtY3JsMAqgCKAGhwR/AAABMAsGA1UdFAQEAgIePDAN +BgkqhkiG9w0BAQsFAAOCAgEAAxsr+9nELUVWhFekwy6GsqH8xOf6EqGjRaEdX49W +mB40m2VajOkK8UHGoVyZzoDI2r/c8OPXUtbpK0fpvEl3SZU5j/C8JbZaZFFrEGeH +fSEqdVHFjohpawNcG41Qs+YT21TBqH1hD5yVI7gjVvfKICRfxDpl5oGClxBCVOSV +gVtLbe9q44uCBJ1kUkoc9Vz47Hv7JyckgqVXkORWHt2SFNALxlMEzOEQTpuC5Kcb +4i7hTCUF+kpkIvr02LJImq0Aaqzs6cC/DcdJiRPPyfaN8fQryFv76gg9i8zZcb6c +W42rvumiyw+7nnZfmq53webr5fCHaXhZk47ASOJD6GC5cX9rje1qGRgULXRhqcvK +n319s2iXj3FStDDorKGgsCV2zYmotX17ExB98CcCgBE52zMtRZilwhOGeh8mx3qT +l0W2B8uKKAq5BMmiziSBzQt700JPiruURZXbQ1fH1n7pKP6wGEh2e9TfQMlN20hE +I+CMt+1bG0Bpt5AfiwE8UykQ/WvpVxdJrgj0JM0yA37KfC8XD+cmavJ5/grorbj3 +t0zBdK7bl+Y45VU/5/mX5ZR3O3ea1RclPM3hKMREfPneOlpan6r3dVwFqEN/TeTu +46vuDeKaEr3yJkOFfy0lSYPhPhzhU5vDR5ibxqvwxZNznI2AdTnZLEf8LRqnTVo1 +qx0= +-----END X509 CRL-----`; + +const OUT_CRL_PEM_RSA = `Certificate Revocation List (CRL): + Version: 2 (0x1) + Signature Algorithm: SHA256withRSA + Issuer: + C = UK + ST = London + O = BB + CN = Test Root CA + Last Update: Sun, 25 Aug 2024 11:49:10 GMT + Next Update: Tue, 24 Sep 2024 11:49:10 GMT + CRL extensions: + 2.5.29.46: + Unsupported CRL extension. Try openssl CLI. + X509v3 Authority Key Identifier: + keyid:B8:C9:AD:FD:A8:50:54:D5:85:6E:34:8B:4C:60:2F:B0:49:B6:87:46 + DirName:/C=UK/ST=London/O=BB/CN=Test Root CA + serial:37:5D:4B:F6:BD:7C:11:7C:CC:46:1A:FF:D7:2F:2C:26:F8:1E:4B:3D + X509v3 CRL Distribution Points: + Full Name: + URI:http://example.com/full-crl + Full Name: + URI:ldap://example.com/full-crl + Full Name: + IP:127.0.0.1 + X509v3 CRL Number: + 1E3C + issuerAltName: + Unsupported CRL extension. Try openssl CLI. +Revoked Certificates: + Serial Number: 1000 + Revocation Date: Sun, 25 Aug 2024 03:23:08 GMT + CRL entry extensions: + X509v3 CRL Reason Code: + Certificate Hold + Hold Instruction Code: + Hold Instruction Reject +Signature Value: + 03:1b:2b:fb:d9:c4:2d:45:56:84:57:a4:c3:2e:86:b2:a1:fc: + c4:e7:fa:12:a1:a3:45:a1:1d:5f:8f:56:98:1e:34:9b:65:5a: + 8c:e9:0a:f1:41:c6:a1:5c:99:ce:80:c8:da:bf:dc:f0:e3:d7: + 52:d6:e9:2b:47:e9:bc:49:77:49:95:39:8f:f0:bc:25:b6:5a: + 64:51:6b:10:67:87:7d:21:2a:75:51:c5:8e:88:69:6b:03:5c: + 1b:8d:50:b3:e6:13:db:54:c1:a8:7d:61:0f:9c:95:23:b8:23: + 56:f7:ca:20:24:5f:c4:3a:65:e6:81:82:97:10:42:54:e4:95: + 81:5b:4b:6d:ef:6a:e3:8b:82:04:9d:64:52:4a:1c:f5:5c:f8: + ec:7b:fb:27:27:24:82:a5:57:90:e4:56:1e:dd:92:14:d0:0b: + c6:53:04:cc:e1:10:4e:9b:82:e4:a7:1b:e2:2e:e1:4c:25:05: + fa:4a:64:22:fa:f4:d8:b2:48:9a:ad:00:6a:ac:ec:e9:c0:bf: + 0d:c7:49:89:13:cf:c9:f6:8d:f1:f4:2b:c8:5b:fb:ea:08:3d: + 8b:cc:d9:71:be:9c:5b:8d:ab:be:e9:a2:cb:0f:bb:9e:76:5f: + 9a:ae:77:c1:e6:eb:e5:f0:87:69:78:59:93:8e:c0:48:e2:43: + e8:60:b9:71:7f:6b:8d:ed:6a:19:18:14:2d:74:61:a9:cb:ca: + 9f:7d:7d:b3:68:97:8f:71:52:b4:30:e8:ac:a1:a0:b0:25:76: + cd:89:a8:b5:7d:7b:13:10:7d:f0:27:02:80:11:39:db:33:2d: + 45:98:a5:c2:13:86:7a:1f:26:c7:7a:93:97:45:b6:07:cb:8a: + 28:0a:b9:04:c9:a2:ce:24:81:cd:0b:7b:d3:42:4f:8a:bb:94: + 45:95:db:43:57:c7:d6:7e:e9:28:fe:b0:18:48:76:7b:d4:df: + 40:c9:4d:db:48:44:23:e0:8c:b7:ed:5b:1b:40:69:b7:90:1f: + 8b:01:3c:53:29:10:fd:6b:e9:57:17:49:ae:08:f4:24:cd:32: + 03:7e:ca:7c:2f:17:0f:e7:26:6a:f2:79:fe:0a:e8:ad:b8:f7: + b7:4c:c1:74:ae:db:97:e6:38:e5:55:3f:e7:f9:97:e5:94:77: + 3b:77:9a:d5:17:25:3c:cd:e1:28:c4:44:7c:f9:de:3a:5a:5a: + 9f:aa:f7:75:5c:05:a8:43:7f:4d:e4:ee:e3:ab:ee:0d:e2:9a: + 12:bd:f2:26:43:85:7f:2d:25:49:83:e1:3e:1c:e1:53:9b:c3: + 47:98:9b:c6:ab:f0:c5:93:73:9c:8d:80:75:39:d9:2c:47:fc: + 2d:1a:a7:4d:5a:35:ab:1d`; + +const IN_CRL_PEM_RSA_CRL_REASON_AND_INVALIDITY_DATE = `-----BEGIN X509 CRL----- +MIID9jCCAd4CAQEwDQYJKoZIhvcNAQELBQAwQjELMAkGA1UEBhMCVUsxDzANBgNV +BAgMBkxvbmRvbjELMAkGA1UECgwCQkIxFTATBgNVBAMMDFRlc3QgUm9vdCBDQRcN +MjQwODI1MTIwODU2WhcNMjQwOTI0MTIwODU2WjA9MDsCAhAAFw0yNDA4MjUxMjA4 +NDhaMCYwCgYDVR0VBAMKAQEwGAYDVR0YBBEYDzIwMjQwODI1MDAwMDAwWqCCAScw +ggEjMAkGA1UdEgQCMAAwfQYDVR0jBHYwdIAUuMmt/ahQVNWFbjSLTGAvsEm2h0ah +RqREMEIxCzAJBgNVBAYTAlVLMQ8wDQYDVQQIDAZMb25kb24xCzAJBgNVBAoMAkJC +MRUwEwYDVQQDDAxUZXN0IFJvb3QgQ0GCFDddS/a9fBF8zEYa/9cvLCb4Hks9MC0G +A1UdLgQmMCQwIqAgoB6GHGh0dHA6Ly9leGFtcGxlLmNvbS9kZWx0YS1jcmwwWwYD +VR0fBFQwUjAhoB+gHYYbaHR0cDovL2V4YW1wbGUuY29tL2Z1bGwtY3JsMCGgH6Ad +hhtsZGFwOi8vZXhhbXBsZS5jb20vZnVsbC1jcmwwCqAIoAaHBH8AAAEwCwYDVR0U +BAQCAh49MA0GCSqGSIb3DQEBCwUAA4ICAQByLp7JWQmB1NhlLACH6zFOe31yCTVy +xJQtgujtSri1LNu6IwzBGsKBQIl3ucwMxPvoZzlujNLmshUT3nSogV0/5n1q0Gyj +5Yiz2iw8mmKJLmGZ9Oz3QoGxgFww0/0x/VwRHuS2hw+A7JB8tO/2nW3oTclvS55l +R+VtkDjUN58+Yl2SQksvb3qD6bHHJTCaP7Dskls0fdBIoYIDvZejrTYSSzTX/Kw4 +735P0GBMhj7zVF8azGz2PFpSISg4huJMyp7EDKZf2c2dnkuwmEUlPQEBLX25j/Il +81OxfVVFja+wUagaGtjEPGy5gsU8zFwkWhjaD5PGBbZvnT+EDsOtJPU7Ot/sBHfz +XqUtMrfmz/S/GsQ+QCpnBvarBy9QYuk9M0ePBGy33CUQpjPULxuJJVAHxNoetHCv +7udng2Pi4D8vDNfzbMwHt7HurMo0CsSju+cL4rnIfsz02RrD9WC84KxBLWkqC7Hi +IKGIpF740Yc4BliVE1HDaOKyI6FEft5asj3OgXwmBw7pVlxSNWACaA2vOFkdN/V5 +XZZjVJdRJxkgEfCvsJVenFp8ND6gmJmWum7tqM5ytmiXjPtejsPpVq4IclG+Yhnr +tFQ9TDEuCrNsRIGGGDodyXq1+kGXY0w8RqGEb7J4Og/M6r4LMAKPkO7e0nEibTqX +d2igvR2e5p+yKw== +-----END X509 CRL-----`; + +const OUT_CRL_PEM_RSA_CRL_REASON_AND_INVALIDITY_DATE = `Certificate Revocation List (CRL): + Version: 2 (0x1) + Signature Algorithm: SHA256withRSA + Issuer: + C = UK + ST = London + O = BB + CN = Test Root CA + Last Update: Sun, 25 Aug 2024 12:08:56 GMT + Next Update: Tue, 24 Sep 2024 12:08:56 GMT + CRL extensions: + 2.5.29.46: + Unsupported CRL extension. Try openssl CLI. + X509v3 Authority Key Identifier: + keyid:B8:C9:AD:FD:A8:50:54:D5:85:6E:34:8B:4C:60:2F:B0:49:B6:87:46 + DirName:/C=UK/ST=London/O=BB/CN=Test Root CA + serial:37:5D:4B:F6:BD:7C:11:7C:CC:46:1A:FF:D7:2F:2C:26:F8:1E:4B:3D + X509v3 CRL Distribution Points: + Full Name: + URI:http://example.com/full-crl + Full Name: + URI:ldap://example.com/full-crl + Full Name: + IP:127.0.0.1 + X509v3 CRL Number: + 1E3D + issuerAltName: + Unsupported CRL extension. Try openssl CLI. +Revoked Certificates: + Serial Number: 1000 + Revocation Date: Sun, 25 Aug 2024 12:08:48 GMT + CRL entry extensions: + X509v3 CRL Reason Code: + Key Compromise + Invalidity Date: + Sun, 25 Aug 2024 00:00:00 GMT +Signature Value: + 72:2e:9e:c9:59:09:81:d4:d8:65:2c:00:87:eb:31:4e:7b:7d: + 72:09:35:72:c4:94:2d:82:e8:ed:4a:b8:b5:2c:db:ba:23:0c: + c1:1a:c2:81:40:89:77:b9:cc:0c:c4:fb:e8:67:39:6e:8c:d2: + e6:b2:15:13:de:74:a8:81:5d:3f:e6:7d:6a:d0:6c:a3:e5:88: + b3:da:2c:3c:9a:62:89:2e:61:99:f4:ec:f7:42:81:b1:80:5c: + 30:d3:fd:31:fd:5c:11:1e:e4:b6:87:0f:80:ec:90:7c:b4:ef: + f6:9d:6d:e8:4d:c9:6f:4b:9e:65:47:e5:6d:90:38:d4:37:9f: + 3e:62:5d:92:42:4b:2f:6f:7a:83:e9:b1:c7:25:30:9a:3f:b0: + ec:92:5b:34:7d:d0:48:a1:82:03:bd:97:a3:ad:36:12:4b:34: + d7:fc:ac:38:ef:7e:4f:d0:60:4c:86:3e:f3:54:5f:1a:cc:6c: + f6:3c:5a:52:21:28:38:86:e2:4c:ca:9e:c4:0c:a6:5f:d9:cd: + 9d:9e:4b:b0:98:45:25:3d:01:01:2d:7d:b9:8f:f2:25:f3:53: + b1:7d:55:45:8d:af:b0:51:a8:1a:1a:d8:c4:3c:6c:b9:82:c5: + 3c:cc:5c:24:5a:18:da:0f:93:c6:05:b6:6f:9d:3f:84:0e:c3: + ad:24:f5:3b:3a:df:ec:04:77:f3:5e:a5:2d:32:b7:e6:cf:f4: + bf:1a:c4:3e:40:2a:67:06:f6:ab:07:2f:50:62:e9:3d:33:47: + 8f:04:6c:b7:dc:25:10:a6:33:d4:2f:1b:89:25:50:07:c4:da: + 1e:b4:70:af:ee:e7:67:83:63:e2:e0:3f:2f:0c:d7:f3:6c:cc: + 07:b7:b1:ee:ac:ca:34:0a:c4:a3:bb:e7:0b:e2:b9:c8:7e:cc: + f4:d9:1a:c3:f5:60:bc:e0:ac:41:2d:69:2a:0b:b1:e2:20:a1: + 88:a4:5e:f8:d1:87:38:06:58:95:13:51:c3:68:e2:b2:23:a1: + 44:7e:de:5a:b2:3d:ce:81:7c:26:07:0e:e9:56:5c:52:35:60: + 02:68:0d:af:38:59:1d:37:f5:79:5d:96:63:54:97:51:27:19: + 20:11:f0:af:b0:95:5e:9c:5a:7c:34:3e:a0:98:99:96:ba:6e: + ed:a8:ce:72:b6:68:97:8c:fb:5e:8e:c3:e9:56:ae:08:72:51: + be:62:19:eb:b4:54:3d:4c:31:2e:0a:b3:6c:44:81:86:18:3a: + 1d:c9:7a:b5:fa:41:97:63:4c:3c:46:a1:84:6f:b2:78:3a:0f: + cc:ea:be:0b:30:02:8f:90:ee:de:d2:71:22:6d:3a:97:77:68: + a0:bd:1d:9e:e6:9f:b2:2b`; + +const IN_CRL_PEM_RSA_CRL_EXTENSIONS = `-----BEGIN X509 CRL----- +MIIE0DCCArgCAQEwDQYJKoZIhvcNAQELBQAwQjELMAkGA1UEBhMCVUsxDzANBgNV +BAgMBkxvbmRvbjELMAkGA1UECgwCQkIxFTATBgNVBAMMDFRlc3QgUm9vdCBDQRcN +MjQwODI1MTIzNzEwWhcNMjQwOTI0MTIzNzEwWjA9MDsCAhAAFw0yNDA4MjUxMjA4 +NDhaMCYwCgYDVR0VBAMKAQEwGAYDVR0YBBEYDzIwMjQwODI1MDAwMDAwWqCCAgEw +ggH9MIHiBgNVHRIEgdowgdegFAYEKgMEBaAMFgpDdXN0b21OYW1lgQ5jYUBleGFt +cGxlLmNvbYYSaHR0cDovL2V4YW1wbGUuY29tgg5jYS5leGFtcGxlLmNvbYcEwKgB +AaSBhDCBgTELMAkGA1UEBhMCVVMxFTATBgNVBAgMDEV4YW1wbGVTdGF0ZTEUMBIG +A1UEBwwLRXhhbXBsZUNpdHkxEzARBgNVBAoMCkV4YW1wbGVPcmcxFDASBgNVBAsM +C0V4YW1wbGVVbml0MRowGAYDVQQDDBFFeGFtcGxlQ29tbW9uTmFtZTB9BgNVHSME +djB0gBS4ya39qFBU1YVuNItMYC+wSbaHRqFGpEQwQjELMAkGA1UEBhMCVUsxDzAN +BgNVBAgMBkxvbmRvbjELMAkGA1UECgwCQkIxFTATBgNVBAMMDFRlc3QgUm9vdCBD +QYIUN11L9r18EXzMRhr/1y8sJvgeSz0wLQYDVR0uBCYwJDAioCCgHoYcaHR0cDov +L2V4YW1wbGUuY29tL2RlbHRhLWNybDBbBgNVHR8EVDBSMCGgH6AdhhtodHRwOi8v +ZXhhbXBsZS5jb20vZnVsbC1jcmwwIaAfoB2GG2xkYXA6Ly9leGFtcGxlLmNvbS9m +dWxsLWNybDAKoAigBocEfwAAATALBgNVHRQEBAICHkIwDQYJKoZIhvcNAQELBQAD +ggIBAF/9L4aGmId2igw7+MfDxokevIJkJX/MkmHpXBl1b4hL85FGD7OPCmn47VzC +Wejlc/AQB7mWyUugvrVEq/FiCO8a8Fieyjw5uCYz0eiNnuvHVRGM2mOEkiA0I/rn +F5AFB1YfCFGXPyRkXNRbOBE91mhOzh1H9PX2qVnj5l3KsPE/7YuteacR0TkfkRJa +BXLic+5F/CCV/J/iYR7LncuLUlhBfsosG/ucHL70EytlfX6CBWY3kBbmj7nd497T +QG392+m9xp7MIsJAS+3qEzwJAfni6zUV0fWh/ucOl8BIjHEh97VqI3+8yzhdXfkF +2gkfpkqJQY0+5OO1VSRYTlQNld3QjN/VVJjatfHyaXfPCx4VEKW1kWYo+0zxO4SL +SB/+S/o99bCeNy1MXqEvy5HoDwFHePXGsAEPHWPdj7EWm7g9T/Fl1iSR6hpohvDD +K4LaGdVhzvCraLIh8H7XW3KztvZvDQejYQAgADW0UO0rFHJ1XXhKYSqXNGnfDt+3 +cRpt2XxSxt5HJtHlatiI25PuBMNWV2Zod4RHB/8UEvs1KC7dcwkAiCEY+E3o/zkC +rdZ/8XtNf5a4WSN/D7pPsfsO6SE+7lxkJ+UQcZLXAz8b5ArPTlWt2HdJIBEVs25K +FAkizyldhnAcNHFk7XN94eTLNeD6hUbFL9pNHiSmKu5A9YW0 +-----END X509 CRL-----`; + +const OUT_CRL_PEM_RSA_CRL_EXTENSIONS = `Certificate Revocation List (CRL): + Version: 2 (0x1) + Signature Algorithm: SHA256withRSA + Issuer: + C = UK + ST = London + O = BB + CN = Test Root CA + Last Update: Sun, 25 Aug 2024 12:37:10 GMT + Next Update: Tue, 24 Sep 2024 12:37:10 GMT + CRL extensions: + 2.5.29.46: + Unsupported CRL extension. Try openssl CLI. + X509v3 Authority Key Identifier: + keyid:B8:C9:AD:FD:A8:50:54:D5:85:6E:34:8B:4C:60:2F:B0:49:B6:87:46 + DirName:/C=UK/ST=London/O=BB/CN=Test Root CA + serial:37:5D:4B:F6:BD:7C:11:7C:CC:46:1A:FF:D7:2F:2C:26:F8:1E:4B:3D + X509v3 CRL Distribution Points: + Full Name: + URI:http://example.com/full-crl + Full Name: + URI:ldap://example.com/full-crl + Full Name: + IP:127.0.0.1 + X509v3 CRL Number: + 1E42 + X509v3 Issuer Alternative Name: + OtherName:1.2.3.4.5::CustomName + EMAIL:ca@example.com + URI:http://example.com + DNS:ca.example.com + IP:192.168.1.1 + DIR:/C=US/ST=ExampleState/L=ExampleCity/O=ExampleOrg/OU=ExampleUnit/CN=ExampleCommonName +Revoked Certificates: + Serial Number: 1000 + Revocation Date: Sun, 25 Aug 2024 12:08:48 GMT + CRL entry extensions: + X509v3 CRL Reason Code: + Key Compromise + Invalidity Date: + Sun, 25 Aug 2024 00:00:00 GMT +Signature Value: + 5f:fd:2f:86:86:98:87:76:8a:0c:3b:f8:c7:c3:c6:89:1e:bc: + 82:64:25:7f:cc:92:61:e9:5c:19:75:6f:88:4b:f3:91:46:0f: + b3:8f:0a:69:f8:ed:5c:c2:59:e8:e5:73:f0:10:07:b9:96:c9: + 4b:a0:be:b5:44:ab:f1:62:08:ef:1a:f0:58:9e:ca:3c:39:b8: + 26:33:d1:e8:8d:9e:eb:c7:55:11:8c:da:63:84:92:20:34:23: + fa:e7:17:90:05:07:56:1f:08:51:97:3f:24:64:5c:d4:5b:38: + 11:3d:d6:68:4e:ce:1d:47:f4:f5:f6:a9:59:e3:e6:5d:ca:b0: + f1:3f:ed:8b:ad:79:a7:11:d1:39:1f:91:12:5a:05:72:e2:73: + ee:45:fc:20:95:fc:9f:e2:61:1e:cb:9d:cb:8b:52:58:41:7e: + ca:2c:1b:fb:9c:1c:be:f4:13:2b:65:7d:7e:82:05:66:37:90: + 16:e6:8f:b9:dd:e3:de:d3:40:6d:fd:db:e9:bd:c6:9e:cc:22: + c2:40:4b:ed:ea:13:3c:09:01:f9:e2:eb:35:15:d1:f5:a1:fe: + e7:0e:97:c0:48:8c:71:21:f7:b5:6a:23:7f:bc:cb:38:5d:5d: + f9:05:da:09:1f:a6:4a:89:41:8d:3e:e4:e3:b5:55:24:58:4e: + 54:0d:95:dd:d0:8c:df:d5:54:98:da:b5:f1:f2:69:77:cf:0b: + 1e:15:10:a5:b5:91:66:28:fb:4c:f1:3b:84:8b:48:1f:fe:4b: + fa:3d:f5:b0:9e:37:2d:4c:5e:a1:2f:cb:91:e8:0f:01:47:78: + f5:c6:b0:01:0f:1d:63:dd:8f:b1:16:9b:b8:3d:4f:f1:65:d6: + 24:91:ea:1a:68:86:f0:c3:2b:82:da:19:d5:61:ce:f0:ab:68: + b2:21:f0:7e:d7:5b:72:b3:b6:f6:6f:0d:07:a3:61:00:20:00: + 35:b4:50:ed:2b:14:72:75:5d:78:4a:61:2a:97:34:69:df:0e: + df:b7:71:1a:6d:d9:7c:52:c6:de:47:26:d1:e5:6a:d8:88:db: + 93:ee:04:c3:56:57:66:68:77:84:47:07:ff:14:12:fb:35:28: + 2e:dd:73:09:00:88:21:18:f8:4d:e8:ff:39:02:ad:d6:7f:f1: + 7b:4d:7f:96:b8:59:23:7f:0f:ba:4f:b1:fb:0e:e9:21:3e:ee: + 5c:64:27:e5:10:71:92:d7:03:3f:1b:e4:0a:cf:4e:55:ad:d8: + 77:49:20:11:15:b3:6e:4a:14:09:22:cf:29:5d:86:70:1c:34: + 71:64:ed:73:7d:e1:e4:cb:35:e0:fa:85:46:c5:2f:da:4d:1e: + 24:a6:2a:ee:40:f5:85:b4`; + + +TestRegister.addTests([ + { + name: "Parse X.509 CRL: Example PEM encoded CRL with RSA signature", + input: IN_CRL_PEM_RSA, + expectedOutput: OUT_CRL_PEM_RSA, + recipeConfig: [ + { + "op": "Parse X.509 CRL", + "args": ["PEM"] + } + ] + }, + { + name: "Parse X.509 CRL: Example PEM encoded CRL with RSA signature, CRL Reason and Invalidity Date", + input: IN_CRL_PEM_RSA_CRL_REASON_AND_INVALIDITY_DATE, + expectedOutput: OUT_CRL_PEM_RSA_CRL_REASON_AND_INVALIDITY_DATE, + recipeConfig: [ + { + "op": "Parse X.509 CRL", + "args": ["PEM"] + } + ] + }, + { + name: "Parse X.509 CRL: Example PEM encoded CRL with RSA signature and CRL Extensions", + input: IN_CRL_PEM_RSA_CRL_EXTENSIONS, + expectedOutput: OUT_CRL_PEM_RSA_CRL_EXTENSIONS, + recipeConfig: [ + { + "op": "Parse X.509 CRL", + "args": ["PEM"] + } + ] + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/PowerSet.mjs b/plugins/srktoolbox/tests/operations/tests/PowerSet.mjs new file mode 100644 index 00000000..49f5631b --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/PowerSet.mjs @@ -0,0 +1,36 @@ +/** + * Power Set tests. + * + * @author d98762625 + * + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Power set: nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "幂集", + args: [","], + }, + ], + }, + { + name: "Power set", + input: "1 2 4", + expectedOutput: "\n4\n2\n1\n2 4\n1 4\n1 2\n1 2 4\n", + recipeConfig: [ + { + op: "幂集", + args: [" "], + }, + ], + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/Protobuf.mjs b/plugins/srktoolbox/tests/operations/tests/Protobuf.mjs new file mode 100644 index 00000000..690774b2 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/Protobuf.mjs @@ -0,0 +1,308 @@ +/** + * Protobuf tests. + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Protobuf Decode: no schema", + input: "0d1c0000001203596f751a024d65202b2a0a0a066162633132331200", + expectedOutput: JSON.stringify({ + "1": 28, + "2": "You", + "3": "Me", + "4": 43, + "5": { + "1": "abc123", + "2": {} + } + }, null, 4), + recipeConfig: [ + { + "op": "十六进制转字符", + "args": ["自动"] + }, + { + "op": "Protobuf解码", + "args": ["", false, false] + } + ] + }, + { + name: "Protobuf Decode: partial schema, no unknown fields", + input: "0d1c0000001203596f751a024d65202b2a0a0a066162633132331200", + expectedOutput: JSON.stringify({ + "Apple": [ + 28 + ], + "Carrot": [ + "Me" + ], + "Banana": "You" + }, null, 4), + recipeConfig: [ + { + "op": "十六进制转字符", + "args": ["自动"] + }, + { + "op": "Protobuf解码", + "args": [ + `message Test { + repeated fixed32 Apple = 1; + optional string Banana = 2; + repeated string Carrot = 3; + }`, + false, + false + ] + } + ] + }, + { + name: "Protobuf Decode: partial schema, show unknown fields", + input: "0d1c0000001203596f751a024d65202b2a0a0a066162633132331200", + expectedOutput: JSON.stringify({ + "Test": { + "Apple": [ + 28 + ], + "Carrot": [ + "Me" + ], + "Banana": "You" + }, + "Unknown Fields": { + "4": 43, + "5": { + "1": "abc123", + "2": {} + } + } + }, null, 4), + recipeConfig: [ + { + "op": "十六进制转字符", + "args": ["自动"] + }, + { + "op": "Protobuf解码", + "args": [ + `message Test { + repeated fixed32 Apple = 1; + optional string Banana = 2; + repeated string Carrot = 3; + }`, + true, + false + ] + } + ] + }, + { + name: "Protobuf Decode: full schema, no unknown fields", + input: "0d1c0000001203596f751a024d65202b2a0a0a06616263313233120031ff00000000000000", + expectedOutput: JSON.stringify({ + "Apple": [ + 28 + ], + "Carrot": [ + "Me" + ], + "Banana": "You", + "Date": 43, + "Elderberry": { + "Fig": "abc123", + "Grape": {} + }, + "Huckleberry": 255 + }, null, 4), + recipeConfig: [ + { + "op": "十六进制转字符", + "args": ["自动"] + }, + { + "op": "Protobuf解码", + "args": [ + `message Test { + repeated fixed32 Apple = 1; + optional string Banana = 2; + repeated string Carrot = 3; + optional int32 Date = 4; + optional subTest Elderberry = 5; + optional fixed64 Huckleberry = 6; + } + message subTest { + optional string Fig = 1; + optional subSubTest Grape = 2; + } + message subSubTest {}`, + false, + false + ] + } + ] + }, + { + name: "Protobuf Decode: partial schema, show unknown fields, show types", + input: "0d1c0000001203596f751a024d65202b2a0a0a06616263313233120031ba32a96cc10200003801", + expectedOutput: JSON.stringify({ + "Test": { + "Carrot (string)": [ + "Me" + ], + "Banana (string)": "You", + "Date (int32)": 43, + "Imbe (Options)": "Option1" + }, + "Unknown Fields": { + "field #1: 32-Bit (e.g. fixed32, float)": 28, + "field #5: L-delim (e.g. string, message)": { + "field #1: L-delim (e.g. string, message)": "abc123", + "field #2: L-delim (e.g. string, message)": {} + }, + "field #6: 64-Bit (e.g. fixed64, double)": 3029774971578 + } + }, null, 4), + recipeConfig: [ + { + "op": "十六进制转字符", + "args": ["自动"] + }, + { + "op": "Protobuf解码", + "args": [ + `message Test { + optional string Banana = 2; + repeated string Carrot = 3; + optional int32 Date = 4; + optional Options Imbe = 7; + } + message subTest { + optional string Fig = 1; + optional subSubTest Grape = 2; + } + message subSubTest {} + enum Options { + Option0 = 0; + Option1 = 1; + Option2 = 2; + }`, + true, + true + ] + } + ] + }, + { + name: "Protobuf Encode", + input: JSON.stringify({ + "Apple": [ + 28 + ], + "Banana": "You", + "Carrot": [ + "Me" + ], + "Date": 43, + "Elderberry": { + "Fig": "abc123", + "Grape": {} + }, + "Huckleberry": [3029774971578], + "Imbe": 1 + }, null, 4), + expectedOutput: "0d1c0000001203596f751a024d65202b2a0a0a06616263313233120031ba32a96cc10200003801", + recipeConfig: [ + { + "op": "Protobuf编码", + "args": [ + `message Test { + repeated fixed32 Apple = 1; + optional string Banana = 2; + repeated string Carrot = 3; + optional int32 Date = 4; + optional subTest Elderberry = 5; + repeated fixed64 Huckleberry = 6; + optional Options Imbe = 7; + } + message subTest { + optional string Fig = 1; + optional subSubTest Grape = 2; + } + message subSubTest {} + enum Options { + Option0 = 0; + Option1 = 1; + Option2 = 2; + }` + ] + }, + { + "op": "字符转十六进制", + "args": [ + "无", + 0 + ] + } + ] + }, + { + name: "Protobuf Encode: incomplete schema", + input: JSON.stringify({ + "Apple": [ + 28 + ], + "Banana": "You", + "Carrot": [ + "Me" + ], + "Date": 43, + "Elderberry": { + "Fig": "abc123", + "Grape": {} + }, + "Huckleberry": [3029774971578], + "Imbe": 1 + }, null, 4), + expectedOutput: "1203596f75202b2a0a0a06616263313233120031ba32a96cc1020000", + recipeConfig: [ + { + "op": "Protobuf编码", + "args": [ + `message Test { + optional string Banana = 2; + optional int32 Date = 4; + optional subTest Elderberry = 5; + repeated fixed64 Huckleberry = 6; + } + message subTest { + optional string Fig = 1; + optional subSubTest Grape = 2; + } + message subSubTest {} + enum Options { + Option0 = 0; + Option1 = 1; + Option2 = 2; + }` + ] + }, + { + "op": "字符转十六进制", + "args": [ + "无", + 0 + ] + } + ] + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/PubKeyFromCert.mjs b/plugins/srktoolbox/tests/operations/tests/PubKeyFromCert.mjs new file mode 100644 index 00000000..f77a84ec --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/PubKeyFromCert.mjs @@ -0,0 +1,217 @@ +/** + * Public Key from Certificate + * + * @author cplussharp + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +const RSA_CERT = `-----BEGIN CERTIFICATE----- +MIIBfTCCASegAwIBAgIUeisK5Nwss2DGg5PCs4uSxxXyyNkwDQYJKoZIhvcNAQEL +BQAwEzERMA8GA1UEAwwIUlNBIHRlc3QwHhcNMjExMTE5MTcyMDI2WhcNMzExMTE3 +MTcyMDI2WjATMREwDwYDVQQDDAhSU0EgdGVzdDBcMA0GCSqGSIb3DQEBAQUAA0sA +MEgCQQDyq9A6emHSLczn5Omu5muy+AReC53pTGCrW6Bi65OoobahT2RUSzXCYuvB +757fLLTKz+dLeo6sFkNhIzHZI+n7AgMBAAGjUzBRMB0GA1UdDgQWBBRO+jvkqq5p +pnQgwMMnRoun6e7eiTAfBgNVHSMEGDAWgBRO+jvkqq5ppnQgwMMnRoun6e7eiTAP +BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA0EAR/5HAZM5qBhU/ezDUIFx +gmUGoFbIb5kJD41YCnaSdrgWglh4He4melSs42G/oxBBjuCJ0bUpqWnLl+lJkv1z +IA== +-----END CERTIFICATE-----`; + +const RSA_PUBKEY = `-----BEGIN PUBLIC KEY----- +MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAPKr0Dp6YdItzOfk6a7ma7L4BF4LnelM +YKtboGLrk6ihtqFPZFRLNcJi68Hvnt8stMrP50t6jqwWQ2EjMdkj6fsCAwEAAQ== +-----END PUBLIC KEY-----`; + +const EC_P256_CERT = `-----BEGIN CERTIFICATE----- +MIIBfzCCASWgAwIBAgIUK4H8J3Hr7NpRLPrACj8Pje4JJJ0wCgYIKoZIzj0EAwIw +FTETMBEGA1UEAwwKUC0yNTYgdGVzdDAeFw0yMTExMTkxNzE5NDVaFw0zMTExMTcx +NzE5NDVaMBUxEzARBgNVBAMMClAtMjU2IHRlc3QwWTATBgcqhkjOPQIBBggqhkjO +PQMBBwNCAAQNRzwDQQM0qgJgg9YwfPXJTOoTmYmC6yBwATwfrzXR+QnxmZM2IIJr +qwuBHa8PVU2HZ2KKtaAo8fg9Uwpq/l7po1MwUTAdBgNVHQ4EFgQU/SxodXrpkybM +gcIgkxnRKd7HMzowHwYDVR0jBBgwFoAU/SxodXrpkybMgcIgkxnRKd7HMzowDwYD +VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNIADBFAiBU9PrOa/kXCpTTBInRf/sN +ac2iDHmbdpWzcXI+xLKNYAIhAIRR1LRSHVwOTLQ/iBXd+8LCkm5aTB27RW46LN80 +ylxt +-----END CERTIFICATE-----`; + +const EC_P256_PUBKEY = `-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDUc8A0EDNKoCYIPWMHz1yUzqE5mJ +gusgcAE8H6810fkJ8ZmTNiCCa6sLgR2vD1VNh2diirWgKPH4PVMKav5e6Q== +-----END PUBLIC KEY-----`; + +const DSA_CERT = `-----BEGIN CERTIFICATE----- +MIIEXzCCBA2gAwIBAgIUYYcPJB8UQLzUnqkGJvs3J4RI0OgwCwYJYIZIAWUDBAMC +MBMxETAPBgNVBAMMCERTQSBUZXN0MB4XDTIzMTAxNTAwMjEzNVoXDTMzMTAxMjAw +MjEzNVowEzERMA8GA1UEAwwIRFNBIFRlc3QwggNCMIICNQYHKoZIzjgEATCCAigC +ggEBALoLV+uz7vMYZCIuwXNkgZawvDgZAG1T7IiG030WgqesRNncuoUQOmAJCiuN +zkjVNSY08rabex/RIkWILvxP91SlzhA9t9+dp87p238ecxGa1sD2re+y35RP7IxN +T33633NtwGItZ3BqqAhoMmuwwwxau0E8zwYodTTlwTRp4QVPpMH1eJCUBeEzcWP5 +ZZ1lRNhR5M2TqzSU3ya5/4c3a9rI86h9VIVgw8yVvw3y6yclzjALm2ntD5riskdM +Z6mMkfYQwEbIGRTELX6A7LZ0lX1CislenF9ASb2E4g2nGcMQ0uSGzA4W9mf6wwmP +S6iwX5+Qu/i6jCm5i37fQ1H5HHUCHQDA+UnPHM6PZEgfFen8djZpl/cl05MpWk+d +nikFAoIBADXOTpBw0WA+UihxDG+6qqM05kxVMYmz6IRZ/06ffZSGVFN6Bx1i0s3v +kzM5V8GsKpkKkSk7V8fTQnAIIlMmt1Y7ff+ng7+TfYotMrvvEYlolYK06J2WWoUA +8iKp8+n58vdoky+xZmuGmcvCAojVDbEeU2wEqYE1PzrHCSOoOiKB2P4fOhyuF+qx +E8nkzURIg2RmSSkqWOkXiWyKyfpUaB+4cEisp4ThENEPmdntE1vLh2r7EOIxpE5D +0NAy2wFKqe3ljfgE6XsPZKgVAguRDVpzdmL6WDY7DM/BcS726vx+kX55QDkszvec +raNirnir2QrB/a0JQjF6Y62yGmG7GF8DggEFAAKCAQBpN+w0N0b5IIAspXnlJ9yu +B6ORk3j/5rZ+DUtTzW1YAJI6xjTcFQvN7FpVLkmLtXKUXF04R+sdGJ7VFwOb0rba +L5vQzrqNkBrbgSzuzeloiG+7OLA6VeQtNbQh6OurrZFi9gY+qA5ciT9kQXyrHudV +Xu956NDrooRxmv6JIVFvToaNiwe2vcgdkALw8HUbLFYof4SAE9jgU8EpxTp02e8H +zvVSVa6yj1nnGhpzLPlEqF8TZvs9pTg2kIk3/zvWojMJoPyTALfbTjbAeiFMMeKN +K/CKOOJj23AVAZxpMSR6cUbrIcRdKDnhCTVkkxXUecAIUs6Mk10kSfkuiGl9LjKj +o1MwUTAdBgNVHQ4EFgQUE+xZdvgiDIFWKQskMYnNaZ3iPHAwHwYDVR0jBBgwFoAU +E+xZdvgiDIFWKQskMYnNaZ3iPHAwDwYDVR0TAQH/BAUwAwEB/zALBglghkgBZQME +AwIDPwAwPAIcZbtf4+bjXEGQqNs6IglLrOgIjYF46q7qCNfXmQIcMKUtH3S6sDJE +3ds9eL+oC+HPFlfUNfUiU30aDA== +-----END CERTIFICATE-----`; + +const DSA_PUBKEY = `-----BEGIN PUBLIC KEY----- +MIIDQjCCAjUGByqGSM44BAEwggIoAoIBAQC6C1frs+7zGGQiLsFzZIGWsLw4GQBt +U+yIhtN9FoKnrETZ3LqFEDpgCQorjc5I1TUmNPK2m3sf0SJFiC78T/dUpc4QPbff +nafO6dt/HnMRmtbA9q3vst+UT+yMTU99+t9zbcBiLWdwaqgIaDJrsMMMWrtBPM8G +KHU05cE0aeEFT6TB9XiQlAXhM3Fj+WWdZUTYUeTNk6s0lN8muf+HN2vayPOofVSF +YMPMlb8N8usnJc4wC5tp7Q+a4rJHTGepjJH2EMBGyBkUxC1+gOy2dJV9QorJXpxf +QEm9hOINpxnDENLkhswOFvZn+sMJj0uosF+fkLv4uowpuYt+30NR+Rx1Ah0AwPlJ +zxzOj2RIHxXp/HY2aZf3JdOTKVpPnZ4pBQKCAQA1zk6QcNFgPlIocQxvuqqjNOZM +VTGJs+iEWf9On32UhlRTegcdYtLN75MzOVfBrCqZCpEpO1fH00JwCCJTJrdWO33/ +p4O/k32KLTK77xGJaJWCtOidllqFAPIiqfPp+fL3aJMvsWZrhpnLwgKI1Q2xHlNs +BKmBNT86xwkjqDoigdj+HzocrhfqsRPJ5M1ESINkZkkpKljpF4lsisn6VGgfuHBI +rKeE4RDRD5nZ7RNby4dq+xDiMaROQ9DQMtsBSqnt5Y34BOl7D2SoFQILkQ1ac3Zi ++lg2OwzPwXEu9ur8fpF+eUA5LM73nK2jYq54q9kKwf2tCUIxemOtshphuxhfA4IB +BQACggEAaTfsNDdG+SCALKV55SfcrgejkZN4/+a2fg1LU81tWACSOsY03BULzexa +VS5Ji7VylFxdOEfrHRie1RcDm9K22i+b0M66jZAa24Es7s3paIhvuziwOlXkLTW0 +Iejrq62RYvYGPqgOXIk/ZEF8qx7nVV7veejQ66KEcZr+iSFRb06GjYsHtr3IHZAC +8PB1GyxWKH+EgBPY4FPBKcU6dNnvB871UlWuso9Z5xoacyz5RKhfE2b7PaU4NpCJ +N/871qIzCaD8kwC32042wHohTDHijSvwijjiY9twFQGcaTEkenFG6yHEXSg54Qk1 +ZJMV1HnACFLOjJNdJEn5LohpfS4yow== +-----END PUBLIC KEY-----`; + +const ED25519_CERT = `-----BEGIN CERTIFICATE----- +MIIBQjCB9aADAgECAhRjPJhrdNco5LzpsIs0vSLLaZaZ0DAFBgMrZXAwFzEVMBMG +A1UEAwwMRWQyNTUxOSBUZXN0MB4XDTIzMTAxNTAwMjMwOFoXDTMzMTAxMjAwMjMw +OFowFzEVMBMGA1UEAwwMRWQyNTUxOSBUZXN0MCowBQYDK2VwAyEAELP6AflXwsuZ +5q4NDIO0LP2iCdKRvds4nwsUmRhOw3ijUzBRMB0GA1UdDgQWBBRfxS9q0IemWxkH +4mwAwzr9dQx2xzAfBgNVHSMEGDAWgBRfxS9q0IemWxkH4mwAwzr9dQx2xzAPBgNV +HRMBAf8EBTADAQH/MAUGAytlcANBAI/+03iVq4yJ+DaLVs61w41cVX2UxKvquSzv +lllkpkclM9LH5dLrw4ArdTjS9zAjzY/02WkphHhICHXt3KqZTwI= +-----END CERTIFICATE-----`; + +/* +const ED25519_PUBKEY = `-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEAELP6AflXwsuZ5q4NDIO0LP2iCdKRvds4nwsUmRhOw3g= +-----END PUBLIC KEY-----`; +*/ + +const ED448_CERT = `-----BEGIN CERTIFICATE----- +MIIBijCCAQqgAwIBAgIUZaCS7zEjOnQ7O4KUFym6fJF5vl8wBQYDK2VxMBUxEzAR +BgNVBAMMCkVkNDQ4IFRlc3QwHhcNMjMxMDE1MDAyMzI1WhcNMzMxMDEyMDAyMzI1 +WjAVMRMwEQYDVQQDDApFZDQ0OCBUZXN0MEMwBQYDK2VxAzoAVN8kG0TMVyGOu/Ov +BTe8H0Wi4HJrQAlSv4XLwJbkuoi4EeRlEHQwXsNYLZTtY2Jra6AWhbVYYaEAo1Mw +UTAdBgNVHQ4EFgQUJFrepAf9YXrmDMSAzrMeYQmosd0wHwYDVR0jBBgwFoAUJFre +pAf9YXrmDMSAzrMeYQmosd0wDwYDVR0TAQH/BAUwAwEB/zAFBgMrZXEDcwA+YiZj +puFr2aogfV1qg/ixk7qLi25BbKVNR6+7PEUjo7+4yBn9qnLbAHUGnHn7E96pSey9 +VkLqpoDNMRcM3Eb6h3AJpQM0oxGj8q9arjDXqJkXgaO2e0tVn8KKVfy7S8qO72Kd +rWzZowcOjnWKhXm7JgA= +-----END CERTIFICATE-----`; + +/* +const ED448_PUBKEY = `-----BEGIN PUBLIC KEY----- +MEMwBQYDK2VxAzoAVN8kG0TMVyGOu/OvBTe8H0Wi4HJrQAlSv4XLwJbkuoi4EeRl +EHQwXsNYLZTtY2Jra6AWhbVYYaEA +-----END PUBLIC KEY-----` +*/ + +TestRegister.addTests([ + { + name: "Public Key from Certificate: Missing footer", + input: RSA_CERT.substring(0, RSA_CERT.length / 2), + expectedOutput: "未找到PEM footer '-----END CERTIFICATE-----'", + recipeConfig: [ + { + op: "从证书提取公钥", + args: [], + } + ], + }, + + // test RSA certificate + { + name: "Public Key from Certificate: RSA", + input: RSA_CERT, + expectedOutput: (RSA_PUBKEY + "\n").replace(/\r/g, "").replace(/\n/g, "\r\n"), + recipeConfig: [ + { + op: "从证书提取公钥", + args: [], + } + ], + }, + + // test EC certificate + { + name: "Public Key from Certificate: EC", + input: EC_P256_CERT, + expectedOutput: (EC_P256_PUBKEY + "\n").replace(/\r/g, "").replace(/\n/g, "\r\n"), + recipeConfig: [ + { + op: "从证书提取公钥", + args: [], + } + ], + }, + + // test DSA certificate + { + name: "Public Key from Certificate: DSA", + input: DSA_CERT, + expectedOutput: (DSA_PUBKEY + "\n").replace(/\r/g, "").replace(/\n/g, "\r\n"), + recipeConfig: [ + { + op: "从证书提取公钥", + args: [], + } + ], + }, + + // test EdDSA certificates + { + name: "Public Key from Certificate: Ed25519", + input: ED25519_CERT, + expectedOutput: "不支持的公钥类型", + recipeConfig: [ + { + op: "从证书提取公钥", + args: [], + } + ], + }, + { + name: "Public Key from Certificate: Ed448", + input: ED448_CERT, + expectedOutput: "不支持的公钥类型", + recipeConfig: [ + { + op: "从证书提取公钥", + args: [], + } + ], + }, + + // test multi-input + { + name: "Public Key from Certificate: Multiple certificates", + input: RSA_CERT + "\n" + EC_P256_CERT, + expectedOutput: (RSA_PUBKEY + "\n" + EC_P256_PUBKEY + "\n").replace(/\r/g, "").replace(/\n/g, "\r\n"), + recipeConfig: [ + { + op: "从证书提取公钥", + args: [], + } + ], + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/PubKeyFromPrivKey.mjs b/plugins/srktoolbox/tests/operations/tests/PubKeyFromPrivKey.mjs new file mode 100644 index 00000000..6ecd6406 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/PubKeyFromPrivKey.mjs @@ -0,0 +1,256 @@ +/** + * Public Key from Private Key + * + * @author cplussharp + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +const RSA_PRIVKEY_PKCS1 = `-----BEGIN RSA PRIVATE KEY----- +MIIBOQIBAAJBAPKr0Dp6YdItzOfk6a7ma7L4BF4LnelMYKtboGLrk6ihtqFPZFRL +NcJi68Hvnt8stMrP50t6jqwWQ2EjMdkj6fsCAwEAAQJAOJUpM0lv36MAQR3WAwsF +F7DOy+LnigteCvaNWiNVxZ6jByB5Qb7sall/Qlu9sFI0ZwrlVcKS0kldee7JTYlL +WQIhAP3UKEfOtpTgT1tYmdhaqjxqMfxBom0Ri+rt9ajlzs6vAiEA9L85B8/Gnb7p +6Af7/wpmafL277OV4X4xBfzMR+TUzHUCIBq+VLQkInaTH6lXL3ZtLwyIf9W9MJjf +RWeuRLjT5bM/AiBF7Kw6kx5Hy1fAtydEApCoDIaIjWJw/kC7WTJ0B+jUUQIgV6dw +NSyj0feakeD890gmId+lvl/w/3oUXiczqvl/N9o= +-----END RSA PRIVATE KEY-----`; + +const RSA_PRIVKEY_PKCS8 = `-----BEGIN PRIVATE KEY----- +MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEA8qvQOnph0i3M5+Tp +ruZrsvgEXgud6Uxgq1ugYuuTqKG2oU9kVEs1wmLrwe+e3yy0ys/nS3qOrBZDYSMx +2SPp+wIDAQABAkA4lSkzSW/fowBBHdYDCwUXsM7L4ueKC14K9o1aI1XFnqMHIHlB +vuxqWX9CW72wUjRnCuVVwpLSSV157slNiUtZAiEA/dQoR862lOBPW1iZ2FqqPGox +/EGibRGL6u31qOXOzq8CIQD0vzkHz8advunoB/v/CmZp8vbvs5XhfjEF/MxH5NTM +dQIgGr5UtCQidpMfqVcvdm0vDIh/1b0wmN9FZ65EuNPlsz8CIEXsrDqTHkfLV8C3 +J0QCkKgMhoiNYnD+QLtZMnQH6NRRAiBXp3A1LKPR95qR4Pz3SCYh36W+X/D/ehRe +JzOq+X832g== +-----END PRIVATE KEY-----`; + +const RSA_PUBKEY = `-----BEGIN PUBLIC KEY----- +MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAPKr0Dp6YdItzOfk6a7ma7L4BF4LnelM +YKtboGLrk6ihtqFPZFRLNcJi68Hvnt8stMrP50t6jqwWQ2EjMdkj6fsCAwEAAQ== +-----END PUBLIC KEY-----`; + +const EC_P256_PRIVKEY_SEC1 = `-----BEGIN EC PRIVATE KEY----- +MHcCAQEEINtTjwUkgfAiSwqgcGAXWyE0ueIW6n2k395dmQZ3vGr4oAoGCCqGSM49 +AwEHoUQDQgAEDUc8A0EDNKoCYIPWMHz1yUzqE5mJgusgcAE8H6810fkJ8ZmTNiCC +a6sLgR2vD1VNh2diirWgKPH4PVMKav5e6Q== +-----END EC PRIVATE KEY-----`; + +const EC_P256_PRIVKEY_PKCS8 = `-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg21OPBSSB8CJLCqBw +YBdbITS54hbqfaTf3l2ZBne8avihRANCAAQNRzwDQQM0qgJgg9YwfPXJTOoTmYmC +6yBwATwfrzXR+QnxmZM2IIJrqwuBHa8PVU2HZ2KKtaAo8fg9Uwpq/l7p +-----END PRIVATE KEY-----`; + +const EC_P256_PUBKEY = `-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDUc8A0EDNKoCYIPWMHz1yUzqE5mJ +gusgcAE8H6810fkJ8ZmTNiCCa6sLgR2vD1VNh2diirWgKPH4PVMKav5e6Q== +-----END PUBLIC KEY-----`; + +const DSA_PRIVKEY_TRAD = `-----BEGIN DSA PRIVATE KEY----- +MIIDTQIBAAKCAQEAugtX67Pu8xhkIi7Bc2SBlrC8OBkAbVPsiIbTfRaCp6xE2dy6 +hRA6YAkKK43OSNU1JjTytpt7H9EiRYgu/E/3VKXOED23352nzunbfx5zEZrWwPat +77LflE/sjE1Pffrfc23AYi1ncGqoCGgya7DDDFq7QTzPBih1NOXBNGnhBU+kwfV4 +kJQF4TNxY/llnWVE2FHkzZOrNJTfJrn/hzdr2sjzqH1UhWDDzJW/DfLrJyXOMAub +ae0PmuKyR0xnqYyR9hDARsgZFMQtfoDstnSVfUKKyV6cX0BJvYTiDacZwxDS5IbM +Dhb2Z/rDCY9LqLBfn5C7+LqMKbmLft9DUfkcdQIdAMD5Sc8czo9kSB8V6fx2NmmX +9yXTkylaT52eKQUCggEANc5OkHDRYD5SKHEMb7qqozTmTFUxibPohFn/Tp99lIZU +U3oHHWLSze+TMzlXwawqmQqRKTtXx9NCcAgiUya3Vjt9/6eDv5N9ii0yu+8RiWiV +grTonZZahQDyIqnz6fny92iTL7Fma4aZy8ICiNUNsR5TbASpgTU/OscJI6g6IoHY +/h86HK4X6rETyeTNREiDZGZJKSpY6ReJbIrJ+lRoH7hwSKynhOEQ0Q+Z2e0TW8uH +avsQ4jGkTkPQ0DLbAUqp7eWN+ATpew9kqBUCC5ENWnN2YvpYNjsMz8FxLvbq/H6R +fnlAOSzO95yto2KueKvZCsH9rQlCMXpjrbIaYbsYXwKCAQBpN+w0N0b5IIAspXnl +J9yuB6ORk3j/5rZ+DUtTzW1YAJI6xjTcFQvN7FpVLkmLtXKUXF04R+sdGJ7VFwOb +0rbaL5vQzrqNkBrbgSzuzeloiG+7OLA6VeQtNbQh6OurrZFi9gY+qA5ciT9kQXyr +HudVXu956NDrooRxmv6JIVFvToaNiwe2vcgdkALw8HUbLFYof4SAE9jgU8EpxTp0 +2e8HzvVSVa6yj1nnGhpzLPlEqF8TZvs9pTg2kIk3/zvWojMJoPyTALfbTjbAeiFM +MeKNK/CKOOJj23AVAZxpMSR6cUbrIcRdKDnhCTVkkxXUecAIUs6Mk10kSfkuiGl9 +LjKjAhwpK4MOpkKEu+y308fZ+yZXypZW2m9Y/wOT0L4g +-----END DSA PRIVATE KEY-----`; + +const DSA_PRIVKEY_PKCS8 = `-----BEGIN PRIVATE KEY----- +MIICXAIBADCCAjUGByqGSM44BAEwggIoAoIBAQC6C1frs+7zGGQiLsFzZIGWsLw4 +GQBtU+yIhtN9FoKnrETZ3LqFEDpgCQorjc5I1TUmNPK2m3sf0SJFiC78T/dUpc4Q +PbffnafO6dt/HnMRmtbA9q3vst+UT+yMTU99+t9zbcBiLWdwaqgIaDJrsMMMWrtB +PM8GKHU05cE0aeEFT6TB9XiQlAXhM3Fj+WWdZUTYUeTNk6s0lN8muf+HN2vayPOo +fVSFYMPMlb8N8usnJc4wC5tp7Q+a4rJHTGepjJH2EMBGyBkUxC1+gOy2dJV9QorJ +XpxfQEm9hOINpxnDENLkhswOFvZn+sMJj0uosF+fkLv4uowpuYt+30NR+Rx1Ah0A +wPlJzxzOj2RIHxXp/HY2aZf3JdOTKVpPnZ4pBQKCAQA1zk6QcNFgPlIocQxvuqqj +NOZMVTGJs+iEWf9On32UhlRTegcdYtLN75MzOVfBrCqZCpEpO1fH00JwCCJTJrdW +O33/p4O/k32KLTK77xGJaJWCtOidllqFAPIiqfPp+fL3aJMvsWZrhpnLwgKI1Q2x +HlNsBKmBNT86xwkjqDoigdj+HzocrhfqsRPJ5M1ESINkZkkpKljpF4lsisn6VGgf +uHBIrKeE4RDRD5nZ7RNby4dq+xDiMaROQ9DQMtsBSqnt5Y34BOl7D2SoFQILkQ1a +c3Zi+lg2OwzPwXEu9ur8fpF+eUA5LM73nK2jYq54q9kKwf2tCUIxemOtshphuxhf +BB4CHCkrgw6mQoS77LfTx9n7JlfKllbab1j/A5PQviA= +-----END PRIVATE KEY-----`; + +const DSA_PUBKEY = `-----BEGIN PUBLIC KEY----- +MIIDQjCCAjUGByqGSM44BAEwggIoAoIBAQC6C1frs+7zGGQiLsFzZIGWsLw4GQBt +U+yIhtN9FoKnrETZ3LqFEDpgCQorjc5I1TUmNPK2m3sf0SJFiC78T/dUpc4QPbff +nafO6dt/HnMRmtbA9q3vst+UT+yMTU99+t9zbcBiLWdwaqgIaDJrsMMMWrtBPM8G +KHU05cE0aeEFT6TB9XiQlAXhM3Fj+WWdZUTYUeTNk6s0lN8muf+HN2vayPOofVSF +YMPMlb8N8usnJc4wC5tp7Q+a4rJHTGepjJH2EMBGyBkUxC1+gOy2dJV9QorJXpxf +QEm9hOINpxnDENLkhswOFvZn+sMJj0uosF+fkLv4uowpuYt+30NR+Rx1Ah0AwPlJ +zxzOj2RIHxXp/HY2aZf3JdOTKVpPnZ4pBQKCAQA1zk6QcNFgPlIocQxvuqqjNOZM +VTGJs+iEWf9On32UhlRTegcdYtLN75MzOVfBrCqZCpEpO1fH00JwCCJTJrdWO33/ +p4O/k32KLTK77xGJaJWCtOidllqFAPIiqfPp+fL3aJMvsWZrhpnLwgKI1Q2xHlNs +BKmBNT86xwkjqDoigdj+HzocrhfqsRPJ5M1ESINkZkkpKljpF4lsisn6VGgfuHBI +rKeE4RDRD5nZ7RNby4dq+xDiMaROQ9DQMtsBSqnt5Y34BOl7D2SoFQILkQ1ac3Zi ++lg2OwzPwXEu9ur8fpF+eUA5LM73nK2jYq54q9kKwf2tCUIxemOtshphuxhfA4IB +BQACggEAaTfsNDdG+SCALKV55SfcrgejkZN4/+a2fg1LU81tWACSOsY03BULzexa +VS5Ji7VylFxdOEfrHRie1RcDm9K22i+b0M66jZAa24Es7s3paIhvuziwOlXkLTW0 +Iejrq62RYvYGPqgOXIk/ZEF8qx7nVV7veejQ66KEcZr+iSFRb06GjYsHtr3IHZAC +8PB1GyxWKH+EgBPY4FPBKcU6dNnvB871UlWuso9Z5xoacyz5RKhfE2b7PaU4NpCJ +N/871qIzCaD8kwC32042wHohTDHijSvwijjiY9twFQGcaTEkenFG6yHEXSg54Qk1 +ZJMV1HnACFLOjJNdJEn5LohpfS4yow== +-----END PUBLIC KEY-----`; + +const ED25519_PRIVKEY = `-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIC18vtoHINC8Mo9dTIqOrBs3J28ZvHrwzRq57g2kpV98 +-----END PRIVATE KEY-----`; + +/* +const ED25519_PUBKEY = `-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEAELP6AflXwsuZ5q4NDIO0LP2iCdKRvds4nwsUmRhOw3g= +-----END PUBLIC KEY-----`; +*/ + +const ED448_PRIVKEY = `-----BEGIN PRIVATE KEY----- +MEcCAQAwBQYDK2VxBDsEOWdGJ06bDcWznJhBoQqPeTfsCe+AvBv1n7KfIGYzR4tv +1kcwHnbxlemnCMgqvbrRXaLuFUBysUZThA== +-----END PRIVATE KEY-----`; + +/* +const ED448_PUBKEY = `-----BEGIN PUBLIC KEY----- +MEMwBQYDK2VxAzoAVN8kG0TMVyGOu/OvBTe8H0Wi4HJrQAlSv4XLwJbkuoi4EeRl +EHQwXsNYLZTtY2Jra6AWhbVYYaEA +-----END PUBLIC KEY-----`; +*/ + +TestRegister.addTests([ + { + name: "Public Key from Private Key: Missing footer", + input: RSA_PRIVKEY_PKCS1.substring(0, RSA_PRIVKEY_PKCS1.length / 2), + expectedOutput: "未找到PEM footer '-----END RSA PRIVATE KEY-----'", + recipeConfig: [ + { + op: "从私钥提取公钥", + args: [], + } + ], + }, + + // test RSA + { + name: "Public Key from Private Key: RSA PKCS#1", + input: RSA_PRIVKEY_PKCS1, + expectedOutput: (RSA_PUBKEY + "\n").replace(/\r/g, "").replace(/\n/g, "\r\n"), + recipeConfig: [ + { + op: "从私钥提取公钥", + args: [], + } + ], + }, + { + name: "Public Key from Private Key: RSA PKCS#8", + input: RSA_PRIVKEY_PKCS8, + expectedOutput: (RSA_PUBKEY + "\n").replace(/\r/g, "").replace(/\n/g, "\r\n"), + recipeConfig: [ + { + op: "从私钥提取公钥", + args: [], + } + ], + }, + + // test EC certificate + { + name: "Public Key from Private Key: EC SEC1", + input: EC_P256_PRIVKEY_SEC1, + expectedOutput: (EC_P256_PUBKEY + "\n").replace(/\r/g, "").replace(/\n/g, "\r\n"), + recipeConfig: [ + { + op: "从私钥提取公钥", + args: [], + } + ], + }, + { + name: "Public Key from Private Key: EC PKCS#8", + input: EC_P256_PRIVKEY_PKCS8, + expectedOutput: (EC_P256_PUBKEY + "\n").replace(/\r/g, "").replace(/\n/g, "\r\n"), + recipeConfig: [ + { + op: "从私钥提取公钥", + args: [], + } + ], + }, + + // test DSA + { + name: "Public Key from Private Key: DSA Traditional", + input: DSA_PRIVKEY_TRAD, + expectedOutput: (DSA_PUBKEY + "\n").replace(/\r/g, "").replace(/\n/g, "\r\n"), + recipeConfig: [ + { + op: "从私钥提取公钥", + args: [], + } + ], + }, + { + name: "Public Key from Private Key: DSA PKCS#8", + input: DSA_PRIVKEY_PKCS8, + expectedOutput: "不支持PKCS#8格式DSA私钥", + recipeConfig: [ + { + op: "从私钥提取公钥", + args: [], + } + ], + }, + + // test EdDSA + { + name: "Public Key from Private Key: Ed25519", + input: ED25519_PRIVKEY, + expectedOutput: "不支持的密钥类型:Error: malformed PKCS8 private key(code:004)", + recipeConfig: [ + { + op: "从私钥提取公钥", + args: [], + } + ], + }, + { + name: "Public Key from Private Key: Ed448", + input: ED448_PRIVKEY, + expectedOutput: "不支持的密钥类型:Error: malformed PKCS8 private key(code:004)", + recipeConfig: [ + { + op: "从私钥提取公钥", + args: [], + } + ], + }, + + // test multi-input + { + name: "Public Key from Private Key: Multiple keys", + input: RSA_PRIVKEY_PKCS8 + "\n" + EC_P256_PRIVKEY_PKCS8, + expectedOutput: (RSA_PUBKEY + "\n" + EC_P256_PUBKEY + "\n").replace(/\r/g, "").replace(/\n/g, "\r\n"), + recipeConfig: [ + { + op: "从私钥提取公钥", + args: [], + } + ], + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/RAKE.mjs b/plugins/srktoolbox/tests/operations/tests/RAKE.mjs new file mode 100644 index 00000000..f221bc63 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/RAKE.mjs @@ -0,0 +1,24 @@ +/** + * RAKE, Rapid Automatic Keyword Extraction tests. + * + * @author sw5678 + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + "name": "RAKE: Basic Example", + "input": "test1 test2. test2", + "expectedOutput": "评分:, 关键词:\n3.5, test1 test2\n1.5, test2", + "recipeConfig": [ + { + "op": "RAKE", + "args": ["\\s", "\\.\\s|\\n", "i,me,my,myself,we,our"] + }, + ], + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/RSA.mjs b/plugins/srktoolbox/tests/operations/tests/RSA.mjs new file mode 100644 index 00000000..01e9dcf7 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/RSA.mjs @@ -0,0 +1,352 @@ +/** + * RSA tests. + * + * @author Matt C [me@mitt.dev] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; +import {ASCII_TEXT, UTF8_TEXT, ALL_BYTES} from "../../samples/Ciphers.mjs"; + +const PEM_PRIV_2048 = `-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAwfaUOpUEutKyU3wkCv6kYunz4MqxzSuTSckRz1IxwZtwIiqq ++ejkM6ioXPyGadfFNvG0JVOgr1q4KQglq0vXaYG57HZ8iinXnHgy1vr8i+fWYITB +RMrEDySaQh3sxVj8NudPDoTIxZwUcIUu/N53pUmI08ADxXPA+ZymPyZhZyxrj5Jq +2O2QuRu+R7K44NDweP/rETbGo5+QAPydm6UqBzTky/ohv6EGhjyqnaskTWwLWK6P +dKva8rEMb8nNJvhoTJDLYUfNjB7DFnWxgWuR/KVkXGAHX99J/wh6QTS+bsyJ2/Mw +Df6NWdh3iP7msLNl/GqL+HunhHjrthvvWlODDwIDAQABAoIBAApKwLvJC3q0UmUO +qcTxlRxwiJHNf5jA7qxUIH9NP7mju1P8ypy/KFi7Ys+oUKOOIPdU5Pe0E8sqN6pp +tcH8oL4G9awf72TPapLxZ9UzdTIhR6VQdgbl8XhSO2M1vkoMejmZlX7SOesOaKE9 +1+vwDA43tCx0PF7+UOeN0d549WMphvw3VkSInO/MYpobCGra4YdrhYOhFMyLEGgA +zCyVUOxi538tyyFtK2EEQdcMtvVA6SECjF4xD/qrme0LelIj/L1Uhiu+SOzYt4y+ +QLHL6zhJVfOejWxjeI7BhodkTV2D53n4svfizRgyYEb6iLPW3nlMYIlAksYaxxB9 +nR3sMHECgYEA9RU+8J5A8RnBcwnlc2X1xEW2PN7+A1MeWPQwFqRwIokgvGbCtwjG +PwwNUYJCTBhfGhsISeCBOSYrDGTHsNH+tqFW2zlq61BolYl56jb1KgWzMOX8dak4 +sgXIuBbvyuFNk08VMIzwcA76ka/Iuu/nN9ZOM2UYpdpGG+CTOoIFULECgYEAyppm +I+yAtrUn/BFmwmC8va4vqXlBFjvdkfX/71ywCpHIouLucMV7bILJu0nSCpmL1A7R +DT6qo0p5g+Dxl/+O2VyC5D89PBvcuT1+HtEZGLOoKZnojbSrwDApGbzQi57GoQR6 +/SRjsdAmoelY8PFz2s2ZLJ4NkrZXYvkT1Tu8/78CgYEA4MAvC/HUlEWORbTZmk3y +Z5+WU5QbVWkv91tXjiwWOVWPk7aY8ck2JDMlM45ExgvDiuknXLhpSMNbzu3MwraQ +42JpiHjLOChxAFEmYEct5O99OGZwcmZQ+9CaFVfTZzXeMizfvbpB9EGIP3n4lpXS +cD4zUKZxSAc3K/FyksERpsECgYEAhQPXeVBltQ68oKaAE6/VWqcIjbiY/dLyBkk+ +7dSpk1bhJefdadaN0NERRtARgXoLrn7Hy21QNILJwsaldwiGrbgqC1Zlipg0Ur3H +ls3rLyeMiTuNzbNHa5dy9H3dYT0t5Tr+0EHa3jvtkTGVfiLX0FhZb0yZVrA2MTmc +RsvAqxsCgYAgXy4qytgfzo5/bBt306NbtMEW3dWBWF77HAz4N1LynKZRUrAAK4rz +BVmXFUaNQOg0q8WJG+iFF79u2UnL8iZ5GoPMcpvifsZgef1OHnQnFrfyXSr0fXIm +xq8eZS0DpLvKGffCW03B9VDRHanE37Tng8lbgOtaufuVzFa1bCuLUA== +-----END RSA PRIVATE KEY-----`; + +const PEM_PUB_2048 = `-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwfaUOpUEutKyU3wkCv6k +Yunz4MqxzSuTSckRz1IxwZtwIiqq+ejkM6ioXPyGadfFNvG0JVOgr1q4KQglq0vX +aYG57HZ8iinXnHgy1vr8i+fWYITBRMrEDySaQh3sxVj8NudPDoTIxZwUcIUu/N53 +pUmI08ADxXPA+ZymPyZhZyxrj5Jq2O2QuRu+R7K44NDweP/rETbGo5+QAPydm6Uq +BzTky/ohv6EGhjyqnaskTWwLWK6PdKva8rEMb8nNJvhoTJDLYUfNjB7DFnWxgWuR +/KVkXGAHX99J/wh6QTS+bsyJ2/MwDf6NWdh3iP7msLNl/GqL+HunhHjrthvvWlOD +DwIDAQAB +-----END PUBLIC KEY-----`; + +TestRegister.addTests([ + { + name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-1, nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + "op": "RSA加密", + "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-1"] + }, + { + "op": "RSA解密", + "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-1"] + } + ] + }, + { + name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-1, ASCII", + input: ASCII_TEXT, + expectedOutput: ASCII_TEXT, + recipeConfig: [ + { + "op": "RSA加密", + "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-1"] + }, + { + "op": "RSA解密", + "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-1"] + } + ] + }, + { + name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-1, UTF-8", + input: UTF8_TEXT.substr(0, 100), + expectedOutput: UTF8_TEXT.substr(0, 100), + recipeConfig: [ + { + "op": "RSA加密", + "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-1"] + }, + { + "op": "RSA解密", + "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-1"] + } + ] + }, + { + name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-1, All bytes", + input: ALL_BYTES.substr(0, 100), + expectedOutput: ALL_BYTES.substr(0, 100), + recipeConfig: [ + { + "op": "RSA加密", + "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-1"] + }, + { + "op": "RSA解密", + "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-1"] + } + ] + }, + { + name: "RSA Encrypt/Decrypt: RSA-OAEP/MD5, nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + "op": "RSA加密", + "args": [PEM_PUB_2048, "RSA-OAEP", "MD5"] + }, + { + "op": "RSA解密", + "args": [PEM_PRIV_2048, "", "RSA-OAEP", "MD5"] + } + ] + }, + { + name: "RSA Encrypt/Decrypt: RSA-OAEP/MD5, ASCII", + input: ASCII_TEXT, + expectedOutput: ASCII_TEXT, + recipeConfig: [ + { + "op": "RSA加密", + "args": [PEM_PUB_2048, "RSA-OAEP", "MD5"] + }, + { + "op": "RSA解密", + "args": [PEM_PRIV_2048, "", "RSA-OAEP", "MD5"] + } + ] + }, + { + name: "RSA Encrypt/Decrypt: RSA-OAEP/MD5, UTF-8", + input: UTF8_TEXT.substr(0, 100), + expectedOutput: UTF8_TEXT.substr(0, 100), + recipeConfig: [ + { + "op": "RSA加密", + "args": [PEM_PUB_2048, "RSA-OAEP", "MD5"] + }, + { + "op": "RSA解密", + "args": [PEM_PRIV_2048, "", "RSA-OAEP", "MD5"] + } + ] + }, + { + name: "RSA Encrypt/Decrypt: RSA-OAEP/MD5, All bytes", + input: ALL_BYTES.substr(0, 100), + expectedOutput: ALL_BYTES.substr(0, 100), + recipeConfig: [ + { + "op": "RSA加密", + "args": [PEM_PUB_2048, "RSA-OAEP", "MD5"] + }, + { + "op": "RSA解密", + "args": [PEM_PRIV_2048, "", "RSA-OAEP", "MD5"] + } + ] + }, + { + name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-256, nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + "op": "RSA加密", + "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-256"] + }, + { + "op": "RSA解密", + "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-256"] + } + ] + }, + { + name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-256, ASCII", + input: ASCII_TEXT, + expectedOutput: ASCII_TEXT, + recipeConfig: [ + { + "op": "RSA加密", + "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-256"] + }, + { + "op": "RSA解密", + "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-256"] + } + ] + }, + { + name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-256, UTF-8", + input: UTF8_TEXT.substr(0, 100), + expectedOutput: UTF8_TEXT.substr(0, 100), + recipeConfig: [ + { + "op": "RSA加密", + "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-256"] + }, + { + "op": "RSA解密", + "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-256"] + } + ] + }, + { + name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-256, All bytes", + input: ALL_BYTES.substr(0, 100), + expectedOutput: ALL_BYTES.substr(0, 100), + recipeConfig: [ + { + "op": "RSA加密", + "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-256"] + }, + { + "op": "RSA解密", + "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-256"] + } + ] + }, + { + name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-384, nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + "op": "RSA加密", + "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-384"] + }, + { + "op": "RSA解密", + "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-384"] + } + ] + }, + { + name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-384, ASCII", + input: ASCII_TEXT, + expectedOutput: ASCII_TEXT, + recipeConfig: [ + { + "op": "RSA加密", + "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-384"] + }, + { + "op": "RSA解密", + "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-384"] + } + ] + }, + { + name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-384, UTF-8", + input: UTF8_TEXT.substr(0, 80), + expectedOutput: UTF8_TEXT.substr(0, 80), + recipeConfig: [ + { + "op": "RSA加密", + "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-384"] + }, + { + "op": "RSA解密", + "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-384"] + } + ] + }, + { + name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-384, All bytes", + input: ALL_BYTES.substr(0, 100), + expectedOutput: ALL_BYTES.substr(0, 100), + recipeConfig: [ + { + "op": "RSA加密", + "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-384"] + }, + { + "op": "RSA解密", + "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-384"] + } + ] + }, + { + name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-512, nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + "op": "RSA加密", + "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-512"] + }, + { + "op": "RSA解密", + "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-512"] + } + ] + }, + { + name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-512, ASCII", + input: ASCII_TEXT.substr(0, 100), + expectedOutput: ASCII_TEXT.substr(0, 100), + recipeConfig: [ + { + "op": "RSA加密", + "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-512"] + }, + { + "op": "RSA解密", + "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-512"] + } + ] + }, + { + name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-512, UTF-8", + input: UTF8_TEXT.substr(0, 60), + expectedOutput: UTF8_TEXT.substr(0, 60), + recipeConfig: [ + { + "op": "RSA加密", + "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-512"] + }, + { + "op": "RSA解密", + "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-512"] + } + ] + }, + { + name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-512, All bytes", + input: ALL_BYTES.substr(0, 100), + expectedOutput: ALL_BYTES.substr(0, 100), + recipeConfig: [ + { + "op": "RSA加密", + "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-512"] + }, + { + "op": "RSA解密", + "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-512"] + } + ] + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/Rabbit.mjs b/plugins/srktoolbox/tests/operations/tests/Rabbit.mjs new file mode 100644 index 00000000..2439bc5e --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/Rabbit.mjs @@ -0,0 +1,179 @@ +/** + * @author mikecat + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Rabbit: RFC Test vector, without IV 1", + input: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + expectedOutput: "b15754f036a5d6ecf56b45261c4af70288e8d815c59c0c397b696c4789c68aa7f416a1c3700cd451da68d1881673d696", + recipeConfig: [ + { + "op": "Rabbit", + "args": [ + {"option": "十六进制", "string": "00000000000000000000000000000000"}, + {"option": "十六进制", "string": ""}, + "大端序", "十六进制", "十六进制" + ] + } + ] + }, + { + name: "Rabbit: RFC Test vector, without IV 2", + input: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + expectedOutput: "3d2df3c83ef627a1e97fc38487e2519cf576cd61f4405b8896bf53aa8554fc19e5547473fbdb43508ae53b20204d4c5e", + recipeConfig: [ + { + "op": "Rabbit", + "args": [ + {"option": "十六进制", "string": "912813292e3d36fe3bfc62f1dc51c3ac"}, + {"option": "十六进制", "string": ""}, + "大端序", "十六进制", "十六进制" + ] + } + ] + }, + { + name: "Rabbit: RFC Test vector, without IV 3", + input: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + expectedOutput: "0cb10dcda041cdac32eb5cfd02d0609b95fc9fca0f17015a7b7092114cff3ead9649e5de8bfc7f3f924147ad3a947428", + recipeConfig: [ + { + "op": "Rabbit", + "args": [ + {"option": "十六进制", "string": "8395741587e0c733e9e9ab01c09b0043"}, + {"option": "十六进制", "string": ""}, + "大端序", "十六进制", "十六进制" + ] + } + ] + }, + { + name: "Rabbit: RFC Test vector, with IV 1", + input: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + expectedOutput: "c6a7275ef85495d87ccd5d376705b7ed5f29a6ac04f5efd47b8f293270dc4a8d2ade822b29de6c1ee52bdb8a47bf8f66", + recipeConfig: [ + { + "op": "Rabbit", + "args": [ + {"option": "十六进制", "string": "00000000000000000000000000000000"}, + {"option": "十六进制", "string": "0000000000000000"}, + "大端序", "十六进制", "十六进制" + ] + } + ] + }, + { + name: "Rabbit: RFC Test vector, with IV 2", + input: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + expectedOutput: "1fcd4eb9580012e2e0dccc9222017d6da75f4e10d12125017b2499ffed936f2eebc112c393e738392356bdd012029ba7", + recipeConfig: [ + { + "op": "Rabbit", + "args": [ + {"option": "十六进制", "string": "00000000000000000000000000000000"}, + {"option": "十六进制", "string": "c373f575c1267e59"}, + "大端序", "十六进制", "十六进制" + ] + } + ] + }, + { + name: "Rabbit: RFC Test vector, with IV 3", + input: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + expectedOutput: "445ad8c805858dbf70b6af23a151104d96c8f27947f42c5baeae67c6acc35b039fcbfc895fa71c17313df034f01551cb", + recipeConfig: [ + { + "op": "Rabbit", + "args": [ + {"option": "十六进制", "string": "00000000000000000000000000000000"}, + {"option": "十六进制", "string": "a6eb561ad2f41727"}, + "大端序", "十六进制", "十六进制" + ] + } + ] + }, + { + name: "Rabbit: generated stream should be XORed with the input", + input: "cedda96c054e3ddd93da7ed05e2a4b7bdb0c00fe214f03502e2708b2c2bfc77aa2311b0b9af8aa78d119f92b26db0a6b", + expectedOutput: "7f8afd9c33ebeb3166b13bf64260bc7953e4d8ebe4d30f69554e64f54b794ddd5627bac8eaf47e290b7128a330a8dcfd", + recipeConfig: [ + { + "op": "Rabbit", + "args": [ + {"option": "十六进制", "string": "00000000000000000000000000000000"}, + {"option": "十六进制", "string": ""}, + "大端序", "十六进制", "十六进制" + ] + } + ] + }, + { + name: "Rabbit: least significant bits should be used for the last block", + input: "0000000000000000", + expectedOutput: "f56b45261c4af702", + recipeConfig: [ + { + "op": "Rabbit", + "args": [ + {"option": "十六进制", "string": "00000000000000000000000000000000"}, + {"option": "十六进制", "string": ""}, + "大端序", "十六进制", "十六进制" + ] + } + ] + }, + { + name: "Rabbit: invalid key length", + input: "", + expectedOutput: "无效的key长度:8 字节(正确值:16)", + recipeConfig: [ + { + "op": "Rabbit", + "args": [ + {"option": "十六进制", "string": "0000000000000000"}, + {"option": "十六进制", "string": ""}, + "大端序", "十六进制", "十六进制" + ] + } + ] + }, + { + name: "Rabbit: invalid IV length", + input: "", + expectedOutput: "无效的IV长度:4 字节(正确值:0或8)", + recipeConfig: [ + { + "op": "Rabbit", + "args": [ + {"option": "十六进制", "string": "00000000000000000000000000000000"}, + {"option": "十六进制", "string": "00000000"}, + "大端序", "十六进制", "十六进制" + ] + } + ] + }, + { + // this testcase is taken from the first example on Crypto++ Wiki + // https://www.cryptopp.com/wiki/Rabbit + name: "Rabbit: little-endian mode (Crypto++ compatible)", + input: "Rabbit stream cipher test", + expectedOutput: "1ae2d4edcf9b6063b00fd6fda0b223aded157e77031cf0440b", + recipeConfig: [ + { + "op": "Rabbit", + "args": [ + {"option": "十六进制", "string": "23c2731e8b5469fd8dabb5bc592a0f3a"}, + {"option": "十六进制", "string": "712906405ef03201"}, + "小端序", "原始", "十六进制" + ] + } + ] + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/Regex.mjs b/plugins/srktoolbox/tests/operations/tests/Regex.mjs new file mode 100644 index 00000000..98bc3380 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/Regex.mjs @@ -0,0 +1,61 @@ +/** + * StrUtils tests. + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Regex: non-HTML op", + input: "/<>", + expectedOutput: "/<>", + recipeConfig: [ + { + "op": "正则表达式", + "args": ["自定义", "", true, true, false, false, false, false, "高亮匹配"] + }, + { + "op": "移除空白字符", + "args": [true, true, true, true, true, false] + } + ], + }, + { + name: "Regex: Dot matches all", + input: "Hello\nWorld", + expectedOutput: "Hello\nWorld", + recipeConfig: [ + { + "op": "正则表达式", + "args": ["自定义", ".+", true, true, true, false, false, false, "列出匹配"] + } + ], + }, + { + name: "Regex: Astral off", + input: "𝌆😆", + expectedOutput: "", + recipeConfig: [ + { + "op": "正则表达式", + "args": ["自定义", "\\pS", true, true, false, false, false, false, "列出匹配"] + } + ], + }, + { + name: "Regex: Astral on", + input: "𝌆😆", + expectedOutput: "𝌆\n😆", + recipeConfig: [ + { + "op": "正则表达式", + "args": ["自定义", "\\pS", true, true, false, false, true, false, "列出匹配"] + } + ], + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/Register.mjs b/plugins/srktoolbox/tests/operations/tests/Register.mjs new file mode 100644 index 00000000..e56f4e4f --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/Register.mjs @@ -0,0 +1,77 @@ +/** + * Register tests + * + * @author tlwr [toby@toby.codes] + * + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Register: RC4 key", + input: "http://malwarez.biz/beacon.php?key=0e932a5c&data=8db7d5ebe38663a54ecbb334e3db11", + expectedOutput: "zNu5y53uBoU2rm7qhq9ijjnVHSlJ9PJ/zpp+xL/to8qIBzkDwKzUNQ==", + recipeConfig: [ + { + op: "Register", + args: ["key=([\\da-f]*)", true, false] + }, + { + op: "RC4", + args: [ + { + "option": "十六进制", + "string": "$R0" + }, "十六进制", "Latin1" + ] + }, + { + op: "Base64编码", + args: ["A-Za-z0-9+/="] + } + ] + }, + { + name: "Register: AES key", + input: "51e201d463698ef5f717f71f5b4712af20be674b3bff53d38546396ee61daac4908e319ca3fcf7089bfb6b38ea99e781d26e577ba9dd6f311a39420b8978e93014b042d44726caedf5436eaf652429c0df94b521676c7c2ce812097c277273c7c72cd89aec8d9fb4a27586ccf6aa0aee224c34ba3bfdf7aeb1ddd477622b91e72c9e709ab60f8daf731ec0cc85ce0f746ff1554a5a3ec291ca40f9e629a872592d988fdd834534aba79c1ad1676769a7c010bf04739ecdb65d95302371d629d9e37e7b4a361da468f1ed5358922d2ea752dd11c366f3017b14aa011d2af03c44f95579098a15e3cf9b4486f8ffe9c239f34de7151f6ca6500fe4b850c3f1c02e801caf3a24464614e42801615b8ffaa07ac8251493ffda7de5ddf3368880c2b95b030f41f8f15066add071a66cf60e5f46f3a230d397b652963a21a53f", + expectedOutput: `"You know," said Arthur, "it's at times like this, when I'm trapped in a Vogon airlock with a man from Betelgeuse, and about to die of asphyxiation in deep space that I really wish I'd listened to what my mother told me when I was young." +"Why, what did she tell you?" +"I don't know, I didn't listen."`, + recipeConfig: [ + { + op: "Register", + args: ["(.{32})", true, false] + }, + { + op: "删除字节", + args: [0, 32, false] + }, + { + op: "AES解密", + args: [ + { + "option": "十六进制", + "string": "1748e7179bd56570d51fa4ba287cc3e5" + }, + { + "option": "十六进制", + "string": "$R0" + }, + "CTR", "十六进制", "原始", + { + "option": "十六进制", + "string": "" + }, + { + "option": "十六进制", + "string": "" + } + ] + } + ] + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/RisonEncodeDecode.mjs b/plugins/srktoolbox/tests/operations/tests/RisonEncodeDecode.mjs new file mode 100644 index 00000000..b3b3eea4 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/RisonEncodeDecode.mjs @@ -0,0 +1,68 @@ +/** + * @author sg5506844 [sg5506844@gmail.com] + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Rison Encode: Encoding example 1", + input: JSON.stringify({ any: "json", yes: true }), + expectedOutput: "(any:json,yes:!t)", + recipeConfig: [ + { + op: "Rison编码", + args: ["普通编码"] + } + ] + }, + { + name: "Rison Encode: Encoding example 2", + input: JSON.stringify({ supportsObjects: true, ints: 435 }), + expectedOutput: "ints:435,supportsObjects:!t", + recipeConfig: [ + { + op: "Rison编码", + args: ["编码为对象(O-Rison)"] + } + ] + }, + { + name: "Rison Encode: Encoding example 3", + input: JSON.stringify(["A", "B", { supportsObjects: true }]), + expectedOutput: "A,B,(supportsObjects:!t)", + recipeConfig: [ + { + op: "Rison编码", + args: ["编码为数组(A-Rison)"] + } + ] + }, + { + name: "Rison Encode: Object for an array", + input: JSON.stringify({ supportsObjects: true, ints: 435 }), + expectedOutput: "Rison编码 - rison.encode_array expects an array argument", + expectedError: "Rison编码 - rison.encode_array expects an array argument", + recipeConfig: [ + { + op: "Rison编码", + args: ["编码为数组(A-Rison)"] + } + ] + }, + { + name: "Rison Decode: Decoding example 1", + input: "(any:json,yes:!t)", + expectedOutput: JSON.stringify({ any: "json", yes: true }, null, 4), + recipeConfig: [ + { + op: "Rison解码", + args: ["普通解码"] + } + ] + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/Rotate.mjs b/plugins/srktoolbox/tests/operations/tests/Rotate.mjs new file mode 100644 index 00000000..1fadec90 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/Rotate.mjs @@ -0,0 +1,327 @@ +/** + * Rotate tests. + * + * @author Matt C [matt@artemisbot.uk] + * + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + + +TestRegister.addTests([ + { + name: "Rotate left: nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "十六进制转字符", + args: ["空格"] + }, + { + op: "循环左移", + args: [1, false], + }, + { + op: "字符转十六进制", + args: ["空格"] + } + ], + }, + { + name: "Rotate left: normal", + input: "61 62 63 31 32 33", + expectedOutput: "c2 c4 c6 62 64 66", + recipeConfig: [ + { + op: "十六进制转字符", + args: ["空格"] + }, + { + op: "循环左移", + args: [1, false], + }, + { + op: "字符转十六进制", + args: ["空格"] + } + ], + }, + { + name: "Rotate left: carry", + input: "61 62 63 31 32 33", + expectedOutput: "85 89 8c c4 c8 cd", + recipeConfig: [ + { + op: "十六进制转字符", + args: ["空格"] + }, + { + op: "循环左移", + args: [2, true], + }, + { + op: "字符转十六进制", + args: ["空格"] + } + ], + }, + { + name: "Rotate right: nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "十六进制转字符", + args: ["空格"] + }, + { + op: "循环右移", + args: [1, false], + }, + { + op: "字符转十六进制", + args: ["空格"] + } + ], + }, + { + name: "Rotate right: normal", + input: "61 62 63 31 32 33", + expectedOutput: "b0 31 b1 98 19 99", + recipeConfig: [ + { + op: "十六进制转字符", + args: ["空格"] + }, + { + op: "循环右移", + args: [1, false], + }, + { + op: "字符转十六进制", + args: ["空格"] + } + ], + }, + { + name: "Rotate right: carry", + input: "61 62 63 31 32 33", + expectedOutput: "d8 58 98 cc 4c 8c", + recipeConfig: [ + { + op: "十六进制转字符", + args: ["空格"] + }, + { + op: "循环右移", + args: [2, true], + }, + { + op: "字符转十六进制", + args: ["空格"] + } + ], + }, + { + name: "ROT13: nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "ROT13", + args: [true, true, true, 13] + }, + ], + }, + { + name: "ROT13: no shift amount", + input: "The Quick Brown Fox Jumped Over The Lazy Dog. 0123456789", + expectedOutput: "The Quick Brown Fox Jumped Over The Lazy Dog. 0123456789", + recipeConfig: [ + { + op: "ROT13", + args: [true, true, true, 0] + }, + ], + }, + { + name: "ROT13: normal", + input: "The Quick Brown Fox Jumped Over The Lazy Dog. 0123456789", + expectedOutput: "Gur Dhvpx Oebja Sbk Whzcrq Bire Gur Ynml Qbt. 3456789012", + recipeConfig: [ + { + op: "ROT13", + args: [true, true, true, 13] + }, + ], + }, + { + name: "ROT13: negative shift amount", + input: "The Quick Brown Fox Jumped Over The Lazy Dog. 0123456789", + expectedOutput: "Gur Dhvpx Oebja Sbk Whzcrq Bire Gur Ynml Qbt. 7890123456", + recipeConfig: [ + { + op: "ROT13", + args: [true, true, true, -13] + }, + ], + }, + { + name: "ROT13: full loop", + input: "The Quick Brown Fox Jumped Over The Lazy Dog. 0123456789", + expectedOutput: "The Quick Brown Fox Jumped Over The Lazy Dog. 6789012345", + recipeConfig: [ + { + op: "ROT13", + args: [true, true, true, 26] + }, + ], + }, + { + name: "ROT13: full loop (negative shift amount)", + input: "The Quick Brown Fox Jumped Over The Lazy Dog. 0123456789", + expectedOutput: "The Quick Brown Fox Jumped Over The Lazy Dog. 4567890123", + recipeConfig: [ + { + op: "ROT13", + args: [true, true, true, -26] + }, + ], + }, + { + name: "ROT13: lowercase only", + input: "The Quick Brown Fox Jumped Over The Lazy Dog. 0123456789", + expectedOutput: "Tur Qhvpx Bebja Fbk Jhzcrq Oire Tur Lnml Dbt. 0123456789", + recipeConfig: [ + { + op: "ROT13", + args: [true, false, false, 13] + }, + ], + }, + { + name: "ROT13: uppercase only", + input: "The Quick Brown Fox Jumped Over The Lazy Dog. 0123456789", + expectedOutput: "Ghe Duick Orown Sox Wumped Bver Ghe Yazy Qog. 0123456789", + recipeConfig: [ + { + op: "ROT13", + args: [false, true, false, 13] + }, + ], + }, + { + name: "ROT13: numbers only", + input: "The Quick Brown Fox Jumped Over The Lazy Dog. 0123456789", + expectedOutput: "The Quick Brown Fox Jumped Over The Lazy Dog. 5678901234", + recipeConfig: [ + { + op: "ROT13", + args: [false, false, true, 5] + }, + ], + }, + { + name: "ROT13: numbers only (negative shift amount)", + input: "The Quick Brown Fox Jumped Over The Lazy Dog. 0123456789", + expectedOutput: "The Quick Brown Fox Jumped Over The Lazy Dog. 5678901234", + recipeConfig: [ + { + op: "ROT13", + args: [false, false, true, 5] + }, + ], + }, + { + name: "ROT13: numbers only loop", + input: "The Quick Brown Fox Jumped Over The Lazy Dog. 0123456789", + expectedOutput: "The Quick Brown Fox Jumped Over The Lazy Dog. 0123456789", + recipeConfig: [ + { + op: "ROT13", + args: [false, false, true, 10] + }, + ], + }, + { + name: "ROT13: numbers only loop (negative shift amount)", + input: "The Quick Brown Fox Jumped Over The Lazy Dog. 0123456789", + expectedOutput: "The Quick Brown Fox Jumped Over The Lazy Dog. 0123456789", + recipeConfig: [ + { + op: "ROT13", + args: [false, false, true, -10] + }, + ], + }, + { + name: "ROT47: nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "ROT47", + args: [47] + }, + ], + }, + { + name: "ROT47: normal", + input: "The Quick Brown Fox Jumped Over The Lazy Dog.", + expectedOutput: "%96 \"F:4< qC@H? u@I yF>A65 ~G6C %96 {2KJ s@8]", + recipeConfig: [ + { + op: "ROT47", + args: [47] + }, + ], + }, + { + name: "ROT47: full loop", + input: "The Quick Brown Fox Jumped Over The Lazy Dog.", + expectedOutput: "The Quick Brown Fox Jumped Over The Lazy Dog.", + recipeConfig: [ + { + op: "ROT47", + args: [94] + }, + ], + }, + { + name: "ROT8000: nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "ROT8000", + args: [] + }, + ], + }, + { + name: "ROT8000: normal", + input: "The Quick Brown Fox Jumped Over The Lazy Dog.", + expectedOutput: "籝籱籮 籚籾籲籬籴 籋类籸粀籷 籏籸粁 籓籾籶籹籮籭 籘籿籮类 籝籱籮 籕籪粃粂 籍籸籰簷", + recipeConfig: [ + { + op: "ROT8000", + args: [] + }, + ], + }, + { + name: "ROT8000: backward", + input: "籝籱籮 籚籾籲籬籴 籋类籸粀籷 籏籸粁 籓籾籶籹籮籭 籘籿籮类 籝籱籮 籕籪粃粂 籍籸籰簷", + expectedOutput: "The Quick Brown Fox Jumped Over The Lazy Dog.", + recipeConfig: [ + { + op: "ROT8000", + args: [] + }, + ], + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/SIGABA.mjs b/plugins/srktoolbox/tests/operations/tests/SIGABA.mjs new file mode 100644 index 00000000..5f07ce20 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/SIGABA.mjs @@ -0,0 +1,91 @@ +/** + * SIGABA machine tests + * + * @author hettysymes + * @copyright hettysymes 2020 + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "SIGABA: encrypt test 1", + input: "HELLO WORLD TESTING THE SIGABA MACHINE", + expectedOutput: "ULBECJCZJBJFVUDWAVRGRBMPSQHOTTNVQEESKN", + recipeConfig: [ + { + "op": "SIGABA", + "args": [ + "BHKWECJDOVAYLFMITUGXRNSPZQ", true, "G", + "CDTAKGQOZXLVJYHSWMIBPRUNEF", false, "L", + "WAXHJZMBVDPOLTUYRCQFNSGKEI", false, "I", + "HUSCWIMJQXDALVGBFTOYZKRPNE", false, "T", + "RTLSMNKXFVWQUZGCHEJBYDAIPO", false, "B", + "GHAQBRJWDMNZTSKLOUXYPFIECV", false, "N", + "VFLGEMTCXZIQDYAKRPBONHWSUJ", true, "Q", + "ZQCAYHRJNXPFLKIOTBUSVWMGDE", false, "B", + "EZVSWPCTULGAOFDJNBIYMXKQHR", false, "J", + "ELKSGDXMVYJUZNCAROQBPWHITF", false, "R", + "3891625740", "3", + "6297135408", "1", + "2389715064", "8", + "9264351708", "6", + "9573086142", "6", + "Encrypt" + ] + } + ] + }, + { + name: "SIGABA: encrypt test 2", + input: "PCRPJZWSPNOHMWANBFBEIVZOXDQESPYDEFBNTHXLSICIRPKUATJVDUQFLZOKGHHHDUDIBRKUHVCGAGLBWVGFFXNDHKPFSPSCIIPCXUFRRHNYWIJFEJWQSGMSNJHWSLPKVXHUQUWIURHDIHIUTWGQFIYLTKEZAUESWYEKIWXUSSXWXBEHCXCUDQWKCISVPKXJVPOIJZWTUGKAORBMKBAQUZOPTSUSYZRROWQUYKNCLHVIHEGWCCONGVHEKCEXVYIPNILIXTXDELNGLJGMEQKKQJWZLPNXPOGIOSVAEAJYKWYJXXGKKPLVYAZGDCMNHMPLCYWDQSRBEMVVVZVFYJMRYGHJOTDOEQVRQOVXOGOVYGTXETFHAYELRYVDGWOFVGAOWPMHQYRZMNXVTAHWSKZLJDFVQPZGMHZWFNOBHSZHEDAEXIFCEEJYZDOEFOQWCXTKPJRUEITKHVCITCLKBUFNAFBYXELAYPBRGGGOCCAGLXXJXTSWCJHMHQPVUIBAGBDKAGEEEPKRGGICJQXSYHBNNAKGYODRAUWAEYHWCKHEQIBAONWQJYQCIFKDTOCTJMBJULWKMSNNMPXINHZQWUMJQLQKIPVZVRGYPCJJZMENWTFTUSPCSPRXHMZPCHCNQTUDCOUJHRKYQIUWWVEVVRYFDIYRQISNGPMQLNMCNMVBEWHNCUODHAGEVEUMKVZLEIKYAMPGVVSBYNRJMFCATDXTQCYXIBCXXKYEYHHYERQGQWZTWCEJBFQLRFFCIVVSZUKGLOTLNGLQNTIKTBBWVFMONUFKRLCJASEKUEEDDQDIVQMFRSJRNHYZJODFHSCJSDAIRUXOSDNFUFUFMNZYQIEGRUXKUPCHENUEZHRKYHDRJYSHLZNYRBWVXORMJMJRIRNSAJQRUMPCXUDFYRGKEAXQXJHPEWNIYIDURDGWIFEMSOFYYCFRZGMZXJNTLTJBBSZIULQSOMEVGCTCVXUHTIEHSPOPQYCJLPAJAPQPAQXE", + expectedOutput: "GMEXPPCMFGKUVGXZHVTCKXRSTJUYWNOKFVELWAHHSJBXGOEXCMLOVSIMCDMGEYMWWTFDUMCDUJEZITNPVVBGQDJEVHJXSKJAAUZWBELMSPUTXCUYPDTJCQXEBGWPWRSQLSNFMASCTJZDSFNKDDTAXLRGUPKCBNXMZPADJSFGGNYKRPYBNTYPTGVPACBEINILNACWFVKMJPGCEZFROEYYKTGYSQYMFSGVDOJJONNYEYSCCIXWLKUSJZDRVAQSNUWHMDJVDNNMPGOYRGQRSBGSPQKGCTFZQWSOXBWSQZDCRQJQAWZDPQEILGMMABIMCDPNSKAFCLPQGIRJCMGQREBEUHBYREXFABFMVZTZBDUMASVNUMHIYRSZLGNZFMVAIABLCUZLJLKKZPWEXDHYZFVSNRLCLNDRKLKSWRHQVQJRTHCNFZXDEXSLAXXOGMFVSGCJGAWOLGDMTLWSFNTCUVCCEACINRZAZZOGLEHHXLPHVKILBBJDPOOCILQKKGODSXOBDPZZDXHJLLBOBVFCHJVMUBUZZIKGCWGCYGXVEHHIJGPEQERWEZLILQNHPHALFKFMGADNELGBKILKIUETGDCBQUEOECWVFNOXTJKUYPWBNEKYSIKMVSAMBZGLIKDAOELRSTKFASEKABTUCPSFEGXXQGDFPSPVOLBHGLZSLLWCABSRKZDQQRKVCKXDGTIHPDNMPDZEXYFYKXZTPJPLYOFNLWAGKJEOHOYLMZELXIDWWNXPKEPUCKNNNHJLFYHPQNHMMCGMUPHSUSYYIVWTIMFKKKTFPGFTLTWWSQBRBMGBTZXPVULKNZIIKVTYLJFISGPTLZFTCLGNZOMVKZOIMUDGXRDDSVFRHRYWBEWHYLCUISYMRWAZZAQPJYXZQQKZLILOSHXUTQJFPTXQSREKSUDZTLGUDLUGOJMQHJRJHXCHQTKJULTWWQOXIRFRQEYBPJPEKXFIRMNATWNFBADOSIJVZYRYDBHDAEDJUVDHLDAU", + recipeConfig: [ + { "op": "SIGABA", + "args": [ + "YCHLQSUGBDIXNZKERPVJTAWFOM", true, "A", + "INPXBWETGUYSAOCHVLDMQKZJFR", false, "B", + "WNDRIOZPTAXHFJYQBMSVEKUCGL", false, "C", + "TZGHOBKRVUXLQDMPNFWCJYEIAS", false, "D", + "YWTAHRQJVLCEXUNGBIPZMSDFOK", true, "E", + "QSLRBTEKOGAICFWYVMHJNXZUDP", false, "F", + "CHJDQIGNBSAKVTUOXFWLEPRMZY", false, "G", + "CDFAJXTIMNBEQHSUGRYLWZKVPO", true, "H", + "XHFESZDNRBCGKQIJLTVMUOYAPW", false, "I", + "EZJQXMOGYTCSFRIUPVNADLHWBK", false, "J", + "7591482630", "0", + "3810592764", "1", + "4086153297", "2", + "3980526174", "3", + "6497135280", "4", + "Encrypt"] + } + ] + }, + { + name: "SIGABA: decrypt test", + input: "AKDHFWAYSLHJDKXEVMJJHGKFTQBZPJPJILOVHMBYOAGBZVLLTQUOIKXFPUFNILBDPCAELMAPSXTLMUEGSDTNUDWGZDADBFELWWHKVPRZNDATDPYEHIDMTGAGPDEZYXFSASVKSBMXVOJQXRMHDBWUNZDTIIIVKHJYPIEUHAJCNBXNLGVFADEWIKXDJZBUTGOQBCQZWYKRVEENWRWWRYDNOAPGMODTPTUJZCLUCRDILJABNTBTWUEIJSJRQBUVCOUJJDWFMNNUHXBDFYXLGUMXQEAWSVHBXQGEOOGPYRVOAJLAIYIOHHEXACDTAWWCBGQRNPERSIKHTXPXKBUNACZLFZTRBMBBDDGKNBIQMFHZROCZZBGNZSJKDRRWPEQHLCFADNPWPWSLPIFNKBWQPMARUERGWUUODXSCOJQECGHIZRFRNRSXWSFWKISHHTUFRVXLHCQWGBMRDHCYDSVNIDDRSTODCGJSSBLUYOBGEWFOVKOZBJTYCAKMZECUGLJGTSZJNBOLTMUZRRSIGGRQHLRPMGLINASSMZOBNACKUMSFNIZAUFCPFXXOOTJQWWLZOFLGZLHJCWZJCRJKVOUDLNMKQATGVTOFHACAEKFLRWRTTMVRXHYGOTYPNBMUSKDAKXFCICUOVSWXGPQOYUUWTWRPQMEQCSDJMMJKELIHGEDYKWOVHVPUAIBFGAODXODXVFIIZIGWRZSBTIGXVHFABMMOPGVMLGHQQXNOEJRDLOBGUOWSELBHERZFSBLUODMOGIBNVGVGQYDBTKLOPNKZZNGLTTGZYYXIBAHZJDCILZXKNSJDHXWTYQLFHTUINTYSBPIXOPLOQHSAHGQPYUWYNPKMRBBBYIICCBBJRKWVLBIDBBEKJCXHLPUBMIGBUFYDPOCSRUNZOKMKJHMYFJZWFNHQZOGGRTNNUVLMRLDSAJIECTYCJKBYVNAXGCMGNVFJEDSATZQDQTYRBPLZKHAXMOVJZEDKINXKBUVWXXHTYUFO", + expectedOutput: "KTSOYDGMLPMVXEAJIATXCNQFXHBNCBXIJOCQGCQBRQSBYFOOEVPVXACBMIUIRNVMJHREKRHBSXJFSMWCKTTCYXJOFSJCQECXXCHTEGPEYSMYDHCSMODUAVBNLILYUIBBIXJCXXNQPCERRSMJTPQLMOXSKTRPWOFUSWXOYRJLBIJGIOYTEAEJEGGYAGSXNHNQTETANPWEGATHSBFLHCVHVIJUAKDVGQCWUSIFFFVAJYPJAFUYDXSLGPGESOUAYXBQIIOXWTXNOXLNCGWSUKVIBMOUGNHORYLSNVNNJLKKFDUAEISOLBLCXYHMDGVBVVVIKDLTMTDVWWJBXWXROVTJBXXKXLEWTTISKIUMYSACVUGGNANMCGUMFNQUXDLTHJNYTFIQEPKQQQSSROYJOILJYQXICXACWGOHCSHENXJILOMIIFCIOUDXDCINIVKIRJCVHWXSFQXMNRBJJWTPXNJADEOPEJBLKHKXNTORIRVRLXUXXAMKMODBXNLQCVJXVOTBRHXBBVJHPFEQFCRXYRRXHXPTXXSUESUTHUGOWQYQPQFPXQPVGEIRPQNKXXMBHIPECRUWFEWJUTYIKSMJSRQIQAIAMXTGDXSJIABHIGKUPJBCHWMVYTMQNQYGDHCNMBSVTPXNFRELFXXQYIOLCDEXDXDVSINICOXRMNSPICPQMOBIDJCNBJKXFAVMUXOXHERJIBIXLMXXULDXKXXHAQDXEXIWXOEEUGKSUGCMRWJDPYCYKXTPCOXMURAJCPRXKFJAJALERWRHVMFHOGMFHXGSXQDPJCJNXRQFGHKRCYTEBJDHPCMYFEAPWSVVMMBVUJJMCAAYURHUPVQVJYDCSNMQEMNIFEXYXIIXBVRVILXAUCBDXRJHGPKPYXHPPPNVSBBCDRLVVIYPKAKYIXTJVYDGVPHXULWMADBEICNIFKWUOOHEFNANDKOXMCVBVORLQYNXLULOEGVGWNKNMOHYVRSYSOVYGAKCGAWKGAIAQNQR", + recipeConfig: [ + { "op": "SIGABA", + "args": [ + "YCHLQSUGBDIXNZKERPVJTAWFOM", true, "A", + "INPXBWETGUYSAOCHVLDMQKZJFR", false, "B", + "WNDRIOZPTAXHFJYQBMSVEKUCGL", false, "C", + "TZGHOBKRVUXLQDMPNFWCJYEIAS", false, "D", + "YWTAHRQJVLCEXUNGBIPZMSDFOK", true, "E", + "QSLRBTEKOGAICFWYVMHJNXZUDP", false, "F", + "CHJDQIGNBSAKVTUOXFWLEPRMZY", false, "G", + "CDFAJXTIMNBEQHSUGRYLWZKVPO", true, "H", + "XHFESZDNRBCGKQIJLTVMUOYAPW", false, "I", + "EZJQXMOGYTCSFRIUPVNADLHWBK", false, "J", + "7591482630", "0", + "3810592764", "1", + "4086153297", "2", + "3980526174", "3", + "6497135280", "4", + "Decrypt"] + } + ] + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/SM2.mjs b/plugins/srktoolbox/tests/operations/tests/SM2.mjs new file mode 100644 index 00000000..e74bcf2d --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/SM2.mjs @@ -0,0 +1,137 @@ +/** + * SM2 Tests + * + * @author flakjacket95 [dflack95@gmail.com] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + * + * Modified by Raka-loah@github + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +/* Plaintexts */ + +const SMALL_PLAIN = "I am a small plaintext"; +const LARGE_PLAIN = "I am a larger plaintext, that will require the encryption KDF to generate a much larger key to properly encrypt me"; + +/* Test Key Parameters */ +const PUBLIC_X = "f7d903cab7925066c31150a92b31e548e63f954f92d01eaa0271fb2a336baef8"; +const PUBLIC_Y = "fb0c45e410ef7a6cdae724e6a78dbff52562e97ede009e762b667d9b14adea6c"; +const PRIVATE_K = "e74a72505084c3269aa9b696d603e3e08c74c6740212c11a31e26cdfe08bdf6a"; + +const CURVE = "sm2p256v1"; + +/* Decryption Test Ciphertext*/ + +const CIPHERTEXT_1 = "9a31bc0adb4677cdc4141479e3949572a55c3e6fb52094721f741c2bd2e179aaa87be6263bc1be602e473be3d5de5dce97f8248948b3a7e15f9f67f64aef21575e0c05e6171870a10ff9ab778dbef24267ad90e1a9d47d68f757d57c4816612e9829f804025dea05a511cda39371c22a2828f976f72e"; +const CIPHERTEXT_2 = "d3647d68568a2e7a4f8e843286be7bf2b4d80256697d19a73df306ae1a7e6d0364d942e23d2340606e7a2502a838b132f9242587b2ea7e4c207e87242eea8cae68f5ff4da2a95a7f6d350608ae5b6777e1d925bf9c560087af84aba7befba713130106ddb4082d803811bca3864594722f3198d58257fe4ba37f4aa540adf4cb0568bddd2d8140ad3030deea0a87e3198655cc4d22bfc3d73b1c4afec2ff15d68c8d1298d97132cace922ee8a4e41ca288a7e748b77ca94aa81dc283439923ae7939e00898e16fe5111fbe1d928d152b216a"; +const CIPHERTEXT_3 = "5f340eeb4398fa8950ee3408d0e3fe34bf7728c9fdb060c94b916891b5c693610274160b52a7132a2bf16ad5cdb57d1e00da2f3ddbd55350729aa9c268b53e40c05ccce9912daa14406e8c132e389484e69757350be25351755dcc6c25c94b3c1a448b2cf8c2017582125eb6cf782055b199a875e966"; +const CIPHERTEXT_4 = "0649bac46c3f9fd7fb3b2be4bff27414d634651efd02ca67d8c802bbc5468e77d035c39b581d6b56227f5d87c0b4efbea5032c0761139295ae194b9f1fce698f2f4b51d89fa5554171a1aad2e61fe9de89831aec472ecc5ab178ebf4d2230c1fb94fca03e536b87b9eba6db71ba9939260a08ffd230ca86cb45cf754854222364231bdb8b873791d63ad57a4b3fa5b6375388dc879373f5f1be9051bc5072a8afbec5b7b034e4907aa5bb4b6b1f50e725d09cb6a02e07ce20263005f6c9157ce05d3ea739d231d4f09396fb72aa680884d78"; + + +TestRegister.addTests([ + { + name: "SM2 Decrypt: Small Input; Format One", + input: CIPHERTEXT_1, + expectedOutput: SMALL_PLAIN, + recipeConfig: [ + { + "op": "SM2解密", + "args": [PRIVATE_K, "C1C3C2", CURVE] + } + ] + }, + { + name: "SM2 Decrypt: Large Input; Format One", + input: CIPHERTEXT_2, + expectedOutput: LARGE_PLAIN, + recipeConfig: [ + { + "op": "SM2解密", + "args": [PRIVATE_K, "C1C3C2", CURVE] + } + ] + }, + { + name: "SM2 Decrypt: Small Input; Format Two", + input: CIPHERTEXT_3, + expectedOutput: SMALL_PLAIN, + recipeConfig: [ + { + "op": "SM2解密", + "args": [PRIVATE_K, "C1C2C3", CURVE] + } + ] + }, + { + name: "SM2 Decrypt: Large Input; Format Two", + input: CIPHERTEXT_4, + expectedOutput: LARGE_PLAIN, + recipeConfig: [ + { + "op": "SM2解密", + "args": [PRIVATE_K, "C1C2C3", CURVE] + } + ] + }, + { + name: "SM2 Encrypt And Decrypt: Small Input; Format One", + input: SMALL_PLAIN, + expectedOutput: SMALL_PLAIN, + recipeConfig: [ + { + "op": "SM2加密", + "args": [PUBLIC_X, PUBLIC_Y, "C1C3C2", CURVE], + }, + { + "op": "SM2解密", + "args": [PRIVATE_K, "C1C3C2", CURVE] + } + ] + }, + { + name: "SM2 Encrypt And Decrypt: Large Input; Format One", + input: LARGE_PLAIN, + expectedOutput: LARGE_PLAIN, + recipeConfig: [ + { + "op": "SM2加密", + "args": [PUBLIC_X, PUBLIC_Y, "C1C3C2", CURVE], + }, + { + "op": "SM2解密", + "args": [PRIVATE_K, "C1C3C2", CURVE] + } + ] + }, + { + name: "SM2 Encrypt And Decrypt: Small Input; Format Two", + input: SMALL_PLAIN, + expectedOutput: SMALL_PLAIN, + recipeConfig: [ + { + "op": "SM2加密", + "args": [PUBLIC_X, PUBLIC_Y, "C1C2C3", CURVE], + }, + { + "op": "SM2解密", + "args": [PRIVATE_K, "C1C2C2", CURVE] + } + ] + }, + { + name: "SM2 Encrypt And Decrypt: Large Input; Format Two", + input: LARGE_PLAIN, + expectedOutput: LARGE_PLAIN, + recipeConfig: [ + { + "op": "SM2加密", + "args": [PUBLIC_X, PUBLIC_Y, "C1C2C3", CURVE], + }, + { + "op": "SM2解密", + "args": [PRIVATE_K, "C1C2C3", CURVE] + } + ] + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/SM4.mjs b/plugins/srktoolbox/tests/operations/tests/SM4.mjs new file mode 100644 index 00000000..e91e8955 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/SM4.mjs @@ -0,0 +1,281 @@ +/** + * SM4 crypto tests. + * + * Test data used from IETF draft-ribose-cfrg-sm4-09, see: + * https://tools.ietf.org/id/draft-ribose-cfrg-sm4-09.html + * + * @author swesven + * @copyright 2021 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +/* Cleartexts */ +const TWO_BLOCK_PLAIN = "aa aa aa aa bb bb bb bb cc cc cc cc dd dd dd dd ee ee ee ee ff ff ff ff aa aa aa aa bb bb bb bb"; +const FOUR_BLOCK_PLAIN = "aa aa aa aa aa aa aa aa bb bb bb bb bb bb bb bb cc cc cc cc cc cc cc cc dd dd dd dd dd dd dd dd ee ee ee ee ee ee ee ee ff ff ff ff ff ff ff ff aa aa aa aa aa aa aa aa bb bb bb bb bb bb bb bb"; +/* Keys */ +const KEY_1 = "01 23 45 67 89 ab cd ef fe dc ba 98 76 54 32 10"; +const KEY_2 = "fe dc ba 98 76 54 32 10 01 23 45 67 89 ab cd ef"; +/* IV */ +const IV = "00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f"; +/* Ciphertexts */ +const ECB_1 = "5e c8 14 3d e5 09 cf f7 b5 17 9f 8f 47 4b 86 19 2f 1d 30 5a 7f b1 7d f9 85 f8 1c 84 82 19 23 04"; +const ECB_2 = "c5 87 68 97 e4 a5 9b bb a7 2a 10 c8 38 72 24 5b 12 dd 90 bc 2d 20 06 92 b5 29 a4 15 5a c9 e6 00"; +/* With PKCS#7 padding */ +const ECB_1P ="5e c8 14 3d e5 09 cf f7 b5 17 9f 8f 47 4b 86 19 2f 1d 30 5a 7f b1 7d f9 85 f8 1c 84 82 19 23 04 00 2a 8a 4e fa 86 3c ca d0 24 ac 03 00 bb 40 d2"; +const ECB_2P= "c5 87 68 97 e4 a5 9b bb a7 2a 10 c8 38 72 24 5b 12 dd 90 bc 2d 20 06 92 b5 29 a4 15 5a c9 e6 00 a2 51 49 20 93 f8 f6 42 89 b7 8d 6e 8a 28 b1 c6"; +const CBC_1 = "78 eb b1 1c c4 0b 0a 48 31 2a ae b2 04 02 44 cb 4c b7 01 69 51 90 92 26 97 9b 0d 15 dc 6a 8f 6d"; +const CBC_2 = "0d 3a 6d dc 2d 21 c6 98 85 72 15 58 7b 7b b5 9a 91 f2 c1 47 91 1a 41 44 66 5e 1f a1 d4 0b ae 38"; +const OFB_1 = "ac 32 36 cb 86 1d d3 16 e6 41 3b 4e 3c 75 24 b7 1d 01 ac a2 48 7c a5 82 cb f5 46 3e 66 98 53 9b"; +const OFB_2 = "5d cc cd 25 a8 4b a1 65 60 d7 f2 65 88 70 68 49 33 fa 16 bd 5c d9 c8 56 ca ca a1 e1 01 89 7a 97"; +const CFB_1 = "ac 32 36 cb 86 1d d3 16 e6 41 3b 4e 3c 75 24 b7 69 d4 c5 4e d4 33 b9 a0 34 60 09 be b3 7b 2b 3f"; +const CFB_2 = "5d cc cd 25 a8 4b a1 65 60 d7 f2 65 88 70 68 49 0d 9b 86 ff 20 c3 bf e1 15 ff a0 2c a6 19 2c c5"; +const CTR_1 = "ac 32 36 cb 97 0c c2 07 91 36 4c 39 5a 13 42 d1 a3 cb c1 87 8c 6f 30 cd 07 4c ce 38 5c dd 70 c7 f2 34 bc 0e 24 c1 19 80 fd 12 86 31 0c e3 7b 92 6e 02 fc d0 fa a0 ba f3 8b 29 33 85 1d 82 45 14"; +const CTR_2 = "5d cc cd 25 b9 5a b0 74 17 a0 85 12 ee 16 0e 2f 8f 66 15 21 cb ba b4 4c c8 71 38 44 5b c2 9e 5c 0a e0 29 72 05 d6 27 04 17 3b 21 23 9b 88 7f 6c 8c b5 b8 00 91 7a 24 88 28 4b de 9e 16 ea 29 06"; + +TestRegister.addTests([ + { + name: "SM4 Encrypt: ECB 1, No padding", + input: TWO_BLOCK_PLAIN, + expectedOutput: ECB_1, + recipeConfig: [ + { + "op": "SM4加密", + "args": [{string: KEY_1, option: "十六进制"}, {string: "", option: "十六进制"}, "ECB/NoPadding", "十六进制", "十六进制"] + } + ] + }, + { + name: "SM4 Encrypt: ECB 2, No padding", + input: TWO_BLOCK_PLAIN, + expectedOutput: ECB_2, + recipeConfig: [ + { + "op": "SM4加密", + "args": [{string: KEY_2, option: "十六进制"}, {string: "", option: "十六进制"}, "ECB/NoPadding", "十六进制", "十六进制"] + } + ] + }, + { + name: "SM4 Encrypt: ECB 1, With padding", + input: TWO_BLOCK_PLAIN, + expectedOutput: ECB_1P, + recipeConfig: [ + { + "op": "SM4加密", + "args": [{string: KEY_1, option: "十六进制"}, {string: "", option: "十六进制"}, "ECB", "十六进制", "十六进制"] + } + ] + }, + { + name: "SM4 Encrypt: ECB 2, With padding", + input: TWO_BLOCK_PLAIN, + expectedOutput: ECB_2P, + recipeConfig: [ + { + "op": "SM4加密", + "args": [{string: KEY_2, option: "十六进制"}, {string: "", option: "十六进制"}, "ECB", "十六进制", "十六进制"] + } + ] + }, + { + name: "SM4 Encrypt: CBC 1", + input: TWO_BLOCK_PLAIN, + expectedOutput: CBC_1, + recipeConfig: [ + { + "op": "SM4加密", + "args": [{string: KEY_1, option: "十六进制"}, {string: IV, option: "十六进制"}, "CBC/NoPadding", "十六进制", "十六进制"] + } + ] + }, + { + name: "SM4 Encrypt: CBC 2", + input: TWO_BLOCK_PLAIN, + expectedOutput: CBC_2, + recipeConfig: [ + { + "op": "SM4加密", + "args": [{string: KEY_2, option: "十六进制"}, {string: IV, option: "十六进制"}, "CBC/NoPadding", "十六进制", "十六进制"] + } + ] + }, + { + name: "SM4 Encrypt: OFB1", + input: TWO_BLOCK_PLAIN, + expectedOutput: OFB_1, + recipeConfig: [ + { + "op": "SM4加密", + "args": [{string: KEY_1, option: "十六进制"}, {string: IV, option: "十六进制"}, "OFB", "十六进制", "十六进制"] + } + ] + }, + { + name: "SM4 Encrypt: OFB2", + input: TWO_BLOCK_PLAIN, + expectedOutput: OFB_2, + recipeConfig: [ + { + "op": "SM4加密", + "args": [{string: KEY_2, option: "十六进制"}, {string: IV, option: "十六进制"}, "OFB", "十六进制", "十六进制"] + } + ] + }, + { + name: "SM4 Encrypt: CFB1", + input: TWO_BLOCK_PLAIN, + expectedOutput: CFB_1, + recipeConfig: [ + { + "op": "SM4加密", + "args": [{string: KEY_1, option: "十六进制"}, {string: IV, option: "十六进制"}, "CFB", "十六进制", "十六进制"] + } + ] + }, + { + name: "SM4 Encrypt: CFB2", + input: TWO_BLOCK_PLAIN, + expectedOutput: CFB_2, + recipeConfig: [ + { + "op": "SM4加密", + "args": [{string: KEY_2, option: "十六进制"}, {string: IV, option: "十六进制"}, "CFB", "十六进制", "十六进制"] + } + ] + }, + { + name: "SM4 Encrypt: CTR1", + input: FOUR_BLOCK_PLAIN, + expectedOutput: CTR_1, + recipeConfig: [ + { + "op": "SM4加密", + "args": [{string: KEY_1, option: "十六进制"}, {string: IV, option: "十六进制"}, "CTR", "十六进制", "十六进制"] + } + ] + }, + { + name: "SM4 Encrypt: CTR1", + input: FOUR_BLOCK_PLAIN, + expectedOutput: CTR_2, + recipeConfig: [ + { + "op": "SM4加密", + "args": [{string: KEY_2, option: "十六进制"}, {string: IV, option: "十六进制"}, "CTR", "十六进制", "十六进制"] + } + ] + }, + { + name: "SM4 Decrypt: ECB 1", + input: ECB_1, + expectedOutput: TWO_BLOCK_PLAIN, + recipeConfig: [ + { + "op": "SM4解密", + "args": [{string: KEY_1, option: "十六进制"}, {string: "", option: "十六进制"}, "ECB/NoPadding", "十六进制", "十六进制"] + } + ] + }, + { + name: "SM4 Decrypt: ECB 2", + input: ECB_2, + expectedOutput: TWO_BLOCK_PLAIN, + recipeConfig: [ + { + "op": "SM4解密", + "args": [{string: KEY_2, option: "十六进制"}, {string: "", option: "十六进制"}, "ECB/NoPadding", "十六进制", "十六进制"] + } + ] + }, + { + name: "SM4 Decrypt: CBC 1", + input: CBC_1, + expectedOutput: TWO_BLOCK_PLAIN, + recipeConfig: [ + { + "op": "SM4解密", + "args": [{string: KEY_1, option: "十六进制"}, {string: IV, option: "十六进制"}, "CBC/NoPadding", "十六进制", "十六进制"] + } + ] + }, + { + name: "SM4 Decrypt: CBC 2", + input: CBC_2, + expectedOutput: TWO_BLOCK_PLAIN, + recipeConfig: [ + { + "op": "SM4解密", + "args": [{string: KEY_2, option: "十六进制"}, {string: IV, option: "十六进制"}, "CBC/NoPadding", "十六进制", "十六进制"] + } + ] + }, + { + name: "SM4 Decrypt: OFB1", + input: TWO_BLOCK_PLAIN, + expectedOutput: OFB_1, + recipeConfig: [ + { + "op": "SM4解密", + "args": [{string: KEY_1, option: "十六进制"}, {string: IV, option: "十六进制"}, "OFB", "十六进制", "十六进制"] + } + ] + }, + { + name: "SM4 Decrypt: OFB2", + input: OFB_2, + expectedOutput: TWO_BLOCK_PLAIN, + recipeConfig: [ + { + "op": "SM4解密", + "args": [{string: KEY_2, option: "十六进制"}, {string: IV, option: "十六进制"}, "OFB", "十六进制", "十六进制"] + } + ] + }, + { + name: "SM4 Decrypt: CFB1", + input: CFB_1, + expectedOutput: TWO_BLOCK_PLAIN, + recipeConfig: [ + { + "op": "SM4解密", + "args": [{string: KEY_1, option: "十六进制"}, {string: IV, option: "十六进制"}, "CFB", "十六进制", "十六进制"] + } + ] + }, + { + name: "SM4 Decrypt: CFB2", + input: CFB_2, + expectedOutput: TWO_BLOCK_PLAIN, + recipeConfig: [ + { + "op": "SM4解密", + "args": [{string: KEY_2, option: "十六进制"}, {string: IV, option: "十六进制"}, "CFB", "十六进制", "十六进制"] + } + ] + }, + { + name: "SM4 Decrypt: CTR1", + input: CTR_1, + expectedOutput: FOUR_BLOCK_PLAIN, + recipeConfig: [ + { + "op": "SM4解密", + "args": [{string: KEY_1, option: "十六进制"}, {string: IV, option: "十六进制"}, "CTR", "十六进制", "十六进制"] + } + ] + }, + { + name: "SM4 Decrypt: CTR1", + input: CTR_2, + expectedOutput: FOUR_BLOCK_PLAIN, + recipeConfig: [ + { + "op": "SM4解密", + "args": [{string: KEY_2, option: "十六进制"}, {string: IV, option: "十六进制"}, "CTR", "十六进制", "十六进制"] + } + ] + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/Salsa20.mjs b/plugins/srktoolbox/tests/operations/tests/Salsa20.mjs new file mode 100644 index 00000000..9f621eb0 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/Salsa20.mjs @@ -0,0 +1,78 @@ +/** + * Salsa20 tests. + * + * @author joostrijneveld [joost@joostrijneveld.nl] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Salsa20: no key", + input: "", + expectedOutput: `无效的密钥长度: 0 字节。 + +Salsa20使用16或32字节(128或256位)的密钥。`, + recipeConfig: [ + { + "op": "Salsa20", + "args": [ + {"option": "十六进制", "string": ""}, + {"option": "十六进制", "string": ""}, + 0, "20", "十六进制", "十六进制", + ] + } + ], + }, + { + name: "Salsa20: no nonce", + input: "", + expectedOutput: `无效的nonce长度: 0 字节。 + +Salsa20使用8字节(64位)的nonce。`, + recipeConfig: [ + { + "op": "Salsa20", + "args": [ + {"option": "十六进制", "string": "00000000000000000000000000000000"}, + {"option": "十六进制", "string": ""}, + 0, "20", "十六进制", "十六进制", + ] + } + ], + }, + { + name: "Salsa20: ECRYPT Set 1 vector# 0", + input: "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", + expectedOutput: "e3 be 8f dd 8b ec a2 e3 ea 8e f9 47 5b 29 a6 e7 00 39 51 e1 09 7a 5c 38 d2 3b 7a 5f ad 9f 68 44 b2 2c 97 55 9e 27 23 c7 cb bd 3f e4 fc 8d 9a 07 44 65 2a 83 e7 2a 9c 46 18 76 af 4d 7e f1 a1 17 8d a2 b7 4e ef 1b 62 83 e7 e2 01 66 ab ca e5 38 e9 71 6e 46 69 e2 81 6b 6b 20 c5 c3 56 80 20 01 cc 14 03 a9 a1 17 d1 2a 26 69 f4 56 36 6d 6e bb 0f 12 46 f1 26 51 50 f7 93 cd b4 b2 53 e3 48 ae 20 3d 89 bc 02 5e 80 2a 7e 0e 00 62 1d 70 aa 36 b7 e0 7c b1 e7 d5 b3 8d 5e 22 2b 8b 0e 4b 84 07 01 42 b1 e2 95 04 76 7d 76 82 48 50 32 0b 53 68 12 9f dd 74 e8 61 b4 98 e3 be 8d 16 f2 d7 d1 69 57 be 81 f4 7b 17 d9 ae 7c 4f f1 54 29 a7 3e 10 ac f2 50 ed 3a 90 a9 3c 71 13 08 a7 4c 62 16 a9 ed 84 cd 12 6d a7 f2 8e 8a bf 8b b6 35 17 e1 ca 98 e7 12 f4 fb 2e 1a 6a ed 9f dc 73 29 1f aa 17 95 82 11 c4 ba 2e bd 58 38 c6 35 ed b8 1f 51 3a 91 a2 94 e1 94 f1 c0 39 ae ec 65 7d ce 40 aa 7e 7c 0a f5 7c ac ef a4 0c 9f 14 b7 1a 4b 34 56 a6 3e 16 2e c7 d8 d1 0b 8f fb 18 10 d7 10 01 b6 18 2f 9f 73 da 53 b8 54 05 c1 1f 7b 2d 89 0f a8 ae 0c 7f 2e 92 6d 8a 98 c7 ec 4e 91 b6 51 20 e9 88 34 96 31 a7 00 c6 fa ce c3 47 1c b0 41 36 56 e7 5e 30 94 56 58 40 84 d7 e1 2c 5b 43 a4 1c 43 ed 9a 04 8a bd 9b 88 0d a6 5f 6a 66 5a 20 fe 7b 77 cd 29 2f e6 2c ae 64 4b 7f 7d f6 9f 32 bd b3 31 90 3e 65 05 ce 44 fd c2 93 92 0c 6a 9e c7 05 7e 23 df 7d ad 29 8f 82 dd f4 ef b7 fd c7 bf c6 22 69 6a fc fd 0c dd cc 83 c7 e7 7f 11 a6 49 d7 9a cd c3 35 4e 96 35 ff 13 7e 92 99 33 a0 bd 6f 53 77 ef a1 05 a3 a4 26 6b 7c 0d 08 9d 08 f1 e8 55 cc 32 b1 5b 93 78 4a 36 e5 6a 76 cc 64 bc 84 77", + recipeConfig: [ + { + "op": "Salsa20", + "args": [ + {"option": "十六进制", "string": "80:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00"}, + {"option": "十六进制", "string": "00:00:00:00:00:00:00:00"}, + 0, "20", "十六进制", "十六进制", + ] + } + ], + }, + { + name: "Salsa20: ECRYPT Set 6 vector# 3", + input: "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", + expectedOutput: "71 da ee 51 42 d0 72 8b 41 b6 59 79 33 eb f4 67 e4 32 79 e3 09 78 67 70 78 94 16 02 62 9c bf 68 b7 3d 6b d2 c9 5f 11 8d 2b 3e 6e c9 55 da bb 6d c6 1c 41 43 bc 9a 9b 32 b9 9d be 68 66 16 6d c0 86 31 b7 d6 55 30 50 30 3d 72 52 c2 64 d3 a9 0d 26 c8 53 63 48 13 e0 9a d7 54 5a 6c e7 e8 4a 5d fc 75 ec 43 43 12 07 d5 31 99 70 b0 fa ad b0 e1 51 06 25 bb 54 37 2c 85 15 e2 8e 2a cc f0 a9 93 0a d1 5f 43 18 74 92 3d 2a 59 e2 0d 9f 2a 53 67 db a6 05 15 64 f1 50 28 7d eb b1 db 53 6f f9 b0 9a d9 81 f2 5e 50 10 d8 5d 76 ee 0c 30 5f 75 5b 25 e6 f0 93 41 e0 81 2f 95 c9 4f 42 ee ad 34 6e 81 f3 9c 58 c5 fa a2 c8 89 53 dc 0c ac 90 46 9d b2 06 3c b5 cd b2 2c 9e ae 22 af bf 05 06 fc a4 1d c7 10 b8 46 fb df e3 c4 68 83 dd 11 8f 3a 5e 8b 11 b6 af d9 e7 16 80 d8 66 65 57 30 1a 2d aa fb 94 96 c5 59 78 4d 35 a0 35 36 08 85 f9 b1 7b d7 19 19 77 de ea 93 2b 98 1e bd b2 90 57 ae 3c 92 cf ef f5 e6 c5 d0 cb 62 f2 09 ce 34 2d 4e 35 c6 96 46 cc d1 4e 53 35 0e 48 8b b3 10 a3 2f 8b 02 48 e7 0a cc 5b 47 3d f5 37 ce d3 f8 1a 01 4d 40 83 93 2b ed d6 2e d0 e4 47 b6 76 6c d2 60 4b 70 6e 9b 34 6c 44 68 be b4 6a 34 ec f1 61 0e bd 38 33 1d 52 bf 33 34 6a fe c1 5e ef b2 a7 69 9e 87 59 db 5a 1f 63 6a 48 a0 39 68 8e 39 de 34 d9 95 df 9f 27 ed 9e dc 8d d7 95 e3 9e 53 d9 d9 25 b2 78 01 05 65 ff 66 52 69 04 2f 05 09 6d 94 da 34 33 d9 57 ec 13 d2 fd 82 a0 06 62 83 d0 d1 ee b8 1b f0 ef 13 3b 7f d9 02 48 b8 ff b4 99 b2 41 4c d4 fa 00 30 93 ff 08 64 57 5a 43 74 9b f5 96 02 f2 6c 71 7f a9 6b 1d 05 76 97 db 08 eb c3 fa 66 4a 01 6a 67 dc ef 88 07 57 7c c3 a0 93 85 d3", + recipeConfig: [ + { + "op": "Salsa20", + "args": [ + {"option": "十六进制", "string": "0F:62:B5:08:5B:AE:01:54:A7:FA:4D:A0:F3:46:99:EC"}, + {"option": "十六进制", "string": "28:8F:F6:5D:C4:2B:92:F9"}, + 0, "20", "十六进制", "十六进制", + ] + } + ], + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/SeqUtils.mjs b/plugins/srktoolbox/tests/operations/tests/SeqUtils.mjs new file mode 100644 index 00000000..1d46ffb9 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/SeqUtils.mjs @@ -0,0 +1,46 @@ +/** + * SeqUtils tests. + * + * @author Chris van Marle + * @copyright Copyright 2017 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "SeqUtils - Numeric sort photos", + input: "Photo-1.jpg\nPhoto-4.jpg\nPhoto-2.jpg\nPhoto-3.jpg", + expectedOutput: "Photo-1.jpg\nPhoto-2.jpg\nPhoto-3.jpg\nPhoto-4.jpg", + recipeConfig: [ + { + "op": "排序", + "args": ["换行", false, "数字"] + } + ], + }, + { + name: "SeqUtils - Numeric sort CVE IDs", + input: "CVE-2017-1234,CVE-2017-9999,CVE-2017-10000,CVE-2017-10001,CVE-2017-12345,CVE-2016-1234,CVE-2016-4321,CVE-2016-10000,CVE-2016-9999,CVE-2016-10001", + expectedOutput: "CVE-2017-12345,CVE-2017-10001,CVE-2017-10000,CVE-2017-9999,CVE-2017-1234,CVE-2016-10001,CVE-2016-10000,CVE-2016-9999,CVE-2016-4321,CVE-2016-1234", + recipeConfig: [ + { + "op": "排序", + "args": ["逗号", true, "数字"] + } + ], + }, + { + name: "SeqUtils - Hexadecimal sort", + input: "06,08,0a,0d,0f,1,10,11,12,13,14,15,16,17,18,19,1a,1b,1c,1d,1e,1f,2,3,4,5,7,9,b,c,e", + expectedOutput: "1,2,3,4,5,06,7,08,9,0a,b,c,0d,e,0f,10,11,12,13,14,15,16,17,18,19,1a,1b,1c,1d,1e,1f", + recipeConfig: [ + { + "op": "排序", + "args": ["逗号", false, "数字(十六进制)"] + } + ], + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/SetDifference.mjs b/plugins/srktoolbox/tests/operations/tests/SetDifference.mjs new file mode 100644 index 00000000..f78584d0 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/SetDifference.mjs @@ -0,0 +1,58 @@ +/** + * Set Difference tests. + * + * @author d98762625 + * + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Set Difference", + input: "1 2 3 4 5\n\n3 4 5 6 7", + expectedOutput: "1 2", + recipeConfig: [ + { + op: "补集", + args: ["\n\n", " "], + }, + ], + }, + { + name: "Set Difference: wrong sample count", + input: "1 2 3 4 5_3_4 5 6 7", + expectedOutput: "集合数量错误,你可能需要调整集合分隔符或者添加一些数据。", + recipeConfig: [ + { + op: "补集", + args: [" ", "_"], + }, + ], + }, + { + name: "Set Difference: item delimiter", + input: "1;2;3;4;5\n\n3;4;5;6;7", + expectedOutput: "1;2", + recipeConfig: [ + { + op: "补集", + args: ["\n\n", ";"], + }, + ], + }, + { + name: "Set Difference: sample delimiter", + input: "1;2;3;4;5===3;4;5;6;7", + expectedOutput: "1;2", + recipeConfig: [ + { + op: "补集", + args: ["===", ";"], + }, + ], + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/SetIntersection.mjs b/plugins/srktoolbox/tests/operations/tests/SetIntersection.mjs new file mode 100644 index 00000000..d402f907 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/SetIntersection.mjs @@ -0,0 +1,58 @@ +/** + * Set Intersection tests. + * + * @author d98762625 + * + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Set Intersection", + input: "1 2 3 4 5\n\n3 4 5 6 7", + expectedOutput: "3 4 5", + recipeConfig: [ + { + op: "交集", + args: ["\n\n", " "], + }, + ], + }, + { + name: "Set Intersection: only one set", + input: "1 2 3 4 5 6 7 8", + expectedOutput: "集合数量错误,你可能需要调整集合分隔符或者添加一些数据。", + recipeConfig: [ + { + op: "交集", + args: ["\n\n", " "], + }, + ], + }, + { + name: "Set Intersection: item delimiter", + input: "1-2-3-4-5\n\n3-4-5-6-7", + expectedOutput: "3-4-5", + recipeConfig: [ + { + op: "交集", + args: ["\n\n", "-"], + }, + ], + }, + { + name: "Set Intersection: sample delimiter", + input: "1-2-3-4-5z3-4-5-6-7", + expectedOutput: "3-4-5", + recipeConfig: [ + { + op: "交集", + args: ["z", "-"], + }, + ], + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/SetUnion.mjs b/plugins/srktoolbox/tests/operations/tests/SetUnion.mjs new file mode 100644 index 00000000..600c74b7 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/SetUnion.mjs @@ -0,0 +1,69 @@ +/** + * Set Union tests. + * + * @author d98762625 + * + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Set Union: Nothing", + input: "\n\n", + expectedOutput: "", + recipeConfig: [ + { + op: "并集", + args: ["\n\n", " "], + }, + ], + }, + { + name: "并集", + input: "1 2 3 4 5\n\n3 4 5 6 7", + expectedOutput: "1 2 3 4 5 6 7", + recipeConfig: [ + { + op: "并集", + args: ["\n\n", " "], + }, + ], + }, + { + name: "Set Union: invalid sample number", + input: "1 2 3 4 5\n\n3 4 5 6 7\n\n1", + expectedOutput: "集合数量错误,你可能需要调整集合分隔符或者添加一些数据。", + recipeConfig: [ + { + op: "并集", + args: ["\n\n", " "], + }, + ], + }, + { + name: "Set Union: item delimiter", + input: "1,2,3,4,5\n\n3,4,5,6,7", + expectedOutput: "1,2,3,4,5,6,7", + recipeConfig: [ + { + op: "并集", + args: ["\n\n", ","], + }, + ], + }, + { + name: "Set Union: sample delimiter", + input: "1 2 3 4 5whatever3 4 5 6 7", + expectedOutput: "1 2 3 4 5 6 7", + recipeConfig: [ + { + op: "并集", + args: ["whatever", " "], + }, + ], + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/Shuffle.mjs b/plugins/srktoolbox/tests/operations/tests/Shuffle.mjs new file mode 100644 index 00000000..9d6b3273 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/Shuffle.mjs @@ -0,0 +1,56 @@ +/** + * @author mikecat + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + "name": "Shuffle empty", + "input": "", + "expectedOutput": "", + "recipeConfig": [ + { + "op": "乱序", + "args": ["逗号"] + } + ] + }, + { + "name": "Shuffle bytes", + "input": "12345678", + "expectedOutput": "31 32 33 34 35 36 37 38", + "recipeConfig": [ + { + "op": "乱序", + "args": ["无"] + }, + { + "op": "字符转十六进制", + "args": ["空格", 0] + }, + { + "op": "排序", + "args": ["空格", false, "字母顺序(不区分大小写)"] + } + ] + }, + { + "name": "Shuffle lines", + "input": "1\n2\n3\n4\n5\n6\n7\n8\n9\na\nb\nc\nd\ne\nf\n", + "expectedOutput": "\n1\n2\n3\n4\n5\n6\n7\n8\n9\na\nb\nc\nd\ne\nf", + "recipeConfig": [ + { + "op": "乱序", + "args": ["换行"] + }, + { + "op": "排序", + "args": ["换行", false, "字母顺序(不区分大小写)"] + } + ] + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/SplitColourChannels.mjs b/plugins/srktoolbox/tests/operations/tests/SplitColourChannels.mjs new file mode 100644 index 00000000..4452d970 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/SplitColourChannels.mjs @@ -0,0 +1,34 @@ +/** + * Colour channel split tests. + * + * @author Matt C matt@artemisbot.uk + * + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; +// Base 85 encoded +const testCard = "/9j/4AAQSkZJRgABAQEAYABgAAD/4QCqRXhpZgAATU0AKgAAAAgACQEaAAUAAAABAAAAegEbAAUAAAABAAAAggEoAAMAAAABAAIAAAExAAIAAAAQAAAAigMBAAUAAAABAAAAmgMDAAEAAAABAAAAAFEQAAEAAAABAQAAAFERAAQAAAABAAAOxFESAAQAAAABAAAOxAAAAAAAAXcKAAAD6AABdwoAAAPocGFpbnQubmV0IDQuMS40AAABhqAAALGP/9sAQwACAQECAQECAgICAgICAgMFAwMDAwMGBAQDBQcGBwcHBgcHCAkLCQgICggHBwoNCgoLDAwMDAcJDg8NDA4LDAwM/9sAQwECAgIDAwMGAwMGDAgHCAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwM/8AAEQgAtAFAAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A/djwd4X0s+EtL/4lmn/8ekX/AC7J/cHtWh/wielf9AzT/wDwHT/Cm+Df+RR0v/r0i/8AQBWlQBn/APCJ6V/0DNP/APAdP8KP+ET0r/oGaf8A+A6f4VoUUAZ//CJ6V/0DNP8A/AdP8K5j4w+FdLXwBeFdN09SGj5Fun98e1dvXMfGH/kn959Y/wD0MV8F4qSa4Lzdr/oFxH/pqZ3ZZ/vdL/FH80eCf8Izpv8A0D7L/vwv+FN/4RHSf+gXp/8A4DJ/hWhRX+LH1qv/ADv72frnLHsZ/wDwiOk/9AvT/wDwGT/Cj/hEdJ/6Ben/APgMn+FaFFH1qv8Azv72HJHsZ/8AwiOk/wDQL0//AMBk/wAK8F/az8N6fH4z00LY2YH2I8eSv99vavoqvA/2tv8AkddN/wCvI/8AobV/Xv0Fa9SfjHl0ZybXJiN3/wBOKh/MP0wnyeFuOlDR89Hb/r9A8b/4R7T/APnxtP8Avyv+FH/CPaf/AM+Np/35X/CrlFf74ezh2R/jN9arfzv72U/+Ee0//nxtP+/K/wCFH/CPaf8A8+Np/wB+V/wq5RR7OHZB9arfzv72U/8AhHrD/nxtP+/K/wCFeE/EXSbX/hN9S/0W3/17dIxX0FXgvxG/5HjUv+u5r+Pfpne5wtgnDT/aFtp/y7qH1fCWKq/WJ3k/h7vujnf7Itf+fW3/AO/Yo/si1/59bf8A79irFFf5u+3qfzP7z9A+sVf5n97K/wDZFr/z62//AH7FH9kWv/Prb/8AfsVYoo9vU/mf3h9Yq/zP72VDpFqx/wCPa35/6ZirP9nW/wDz7wf9+xSHhWPoKnr/AE2+gN+8yjN/aa/vKW+v2ZH+wn7MX95kOeuev76jvr9iZD/Z1v8A8+8H/fsUf2db/wDPvB/37FTUV/oD7Gn/ACr7j/UL2UOy+4h/s63/AOfeD/v2KP7Ot/8An3g/79ipqKPY0/5V9weyh2X3HBfHrTrdfCFqRbwg/bFHEY/uPXkn2OH/AJ4xf98CvYvj3/yJ9r/1+r/6A9eQ1/ll9LT3OP5qOi9lS/Jn6Vwzh6TwKvFbvoiP7HD/AM8Yv++BR9jh/wCeMX/fAqSiv5m55dz6D6vS/lX3Ij+xw/8APGL/AL4FBs4SP9TF/wB8CpKKOeXcPq9L+Vfcj5b/AGhbeP8A4W7qyiOMKvlYAUf88krifJT+4n/fIruP2hj/AMXe1b/tl/6JSuJr/pY8B8NSl4acPOUU39RwnRf8+KZ/gj4zSceP88S/6DMT/wCnpjfJT+4n/fIo8lP7if8AfIp1Ffq31Sh/IvuR+a+0l3G+Sn9xP++RR5Kf3E/75FOoo+qUP5F9yD2ku5/al4O/5FHTP+vWL/0AVpVm+Dv+RR0z/r1i/wDQBWlX+DZ+1BRRRQAVzHxh/wCSf3v1j/8AQxXT1zHxh/5J/e/WP/0MV8D4rf8AJFZx/wBguI/9MzO7K/8Ae6X+KP5o8Sooor/FE/XgooooAK8D/a2/5HXTf+vI/wDobV75Xgf7W3/I66b/ANeR/wDQ2r+wPoJf8nly3/BiP/TFQ/l/6Yv/ACazHf46H/p6B5PRRRX+/B/i+FFFFABXgvxH/wCR31L/AK7mveq8F+I//I8al/13Nfx19NH/AJJXBf8AYQv/AE3UPrOEf95n/h/VGLRRRX+bB+gBRRRQAxvuN9KnqBvuN9Knr/Tv6AX/ACKM3/6+Uv8A0iZ/sV+zB/5EGe/9fqP/AKRMKKKK/wBBT/UYKKKKAOJ+Pf8AyJ9r/wBfq/8AoD15DXr3x7/5E+1/6/V/9AevIa/yu+lv/wAnAqf9eqX5M/S+GP8AcV6sKKKK/mQ+hCiiigD5d/aI4+L2rf8AbL/0SlcTXbftEf8AJXtW/wC2X/olK4mv+mDwF/5Nnw7/ANgOE/8AUemf4F+NH/JwM8/7DMT/AOnphRRRX6wfmYUUUUAf2peDv+RR0z/r1i/9AFaVZvg7/kUdM/69Yv8A0AVpV/gmftgUUUUAFcx8Yf8Akn979Y//AEMV09cx8Yf+Sf3v1j/9DFfA+K3/ACRWcf8AYLiP/TMzuyv/AHul/ij+aPEqKKK/xRP14KKKKACvA/2tv+R103/ryP8A6G1e+V4H+1t/yOum/wDXkf8A0Nq/sD6CX/J5ct/wYj/0xUP5f+mL/wAmsx3+Oh/6egeT0UUV/vwf4vhRRRQAV4L8R/8Akd9S/wCu5r3qvBfiP/yPGpf9dzX8dfTR/wCSVwX/AGEL/wBN1D6zhH/eZ/4f1Ri0UUV/mwfoAUUUUAMb7jfSp6gb7jfSp6/07+gF/wAijN/+vlL/ANImf7Ffswf+RBnv/X6j/wCkTCiiiv8AQU/1GCiiigDifj3/AMifa/8AX6v/AKA9eQ1698e/+RPtf+v1f/QHryGv8rvpb/8AJwKn/Xql+TP0vhj/AHFerCiiiv5kPoQooooA+Xf2iP8Akr2rf9sv/RKVxNdt+0R/yV7Vv+2X/olK4mv+mDwF/wCTZ8O/9gOE/wDUemf4F+NH/JwM8/7DMT/6emFFFFfrB+ZhRRRQB/al4O/5FHTP+vWL/wBAFaVZvg7/AJFHTP8Ar1i/9AFaVf4Jn7YFFFFABXMfGH/kn979Y/8A0MV09cx8Yf8Akn979Y//AEMV8D4rf8kVnH/YLiP/AEzM7sr/AN7pf4o/mjxKiiiv8UT9eCiiigArwP8Aa2/5HXTf+vI/+htXvleB/tbf8jrpv/Xkf/Q2r+wPoJf8nly3/BiP/TFQ/l/6Yv8AyazHf46H/p6B5PRRRX+/B/i+FFFFABXgvxH/AOR31L/rua96rwX4j/8AI8al/wBdzX8dfTR/5JXBf9hC/wDTdQ+s4R/3mf8Ah/VGLRRRX+bB+gBRRRQAxvuN9KnqBvuN9Knr/Tv6AX/Iozf/AK+Uv/SJn+xX7MH/AJEGe/8AX6j/AOkTCiiiv9BT/UYKKKKAOJ+Pf/In2v8A1+r/AOgPXkNevfHv/kT7X/r9X/0B68hr/K76W/8AycCp/wBeqX5M/S+GP9xXqwooor+ZD6EKKKKAPl39oj/kr2rf9sv/AESlcTXbftEf8le1b/tl/wCiUria/wCmDwF/5Nnw7/2A4T/1Hpn+BfjR/wAnAzz/ALDMT/6emFFFFfrB+ZhRRRQB/al4O/5FHTP+vWL/ANAFaVZvg7/kUdM/69Yv/QBWlX+CZ+2BRRRQAVzHxh/5J/e/WP8A9DFdPXMfGH/kn979Y/8A0MV8D4rf8kVnH/YLiP8A0zM7sr/3ul/ij+aPEqKKK/xRP14KKKKACvA/2tv+R103/ryP/obV75Xgf7W3/I66b/15H/0Nq/sD6CX/ACeXLf8ABiP/AExUP5f+mL/yazHf46H/AKegeT0UUV/vwf4vhRRRQAV4L8R/+R31L/rua96rwX4j/wDI8al/13Nfx19NH/klcF/2EL/03UPrOEf95n/h/VGLRRRX+bB+gBRRRQAxvuN9KnqBvuN9Knr/AE7+gF/yKM3/AOvlL/0iZ/sV+zB/5EGe/wDX6j/6RMKKKK/0FP8AUYKKKKAOJ+Pf/In2v/X6v/oD15DXr3x7/wCRPtf+v1f/AEB68hr/ACu+lv8A8nAqf9eqX5M/S+GP9xXqwooor+ZD6EKKKKAPl39oj/kr2rf9sv8A0SlcTXbftEf8le1b/tl/6JSuJr/pg8Bf+TZ8O/8AYDhP/Uemf4F+NH/JwM8/7DMT/wCnphRRRX6wfmYUUUUAf2peDv8AkUdM/wCvWL/0AVpVm+Dv+RR0z/r1i/8AQBWlX+CZ+2BRRRQAVzHxh/5J/e/WP/0MV09cx8Yf+Sf3v1j/APQxXwPit/yRWcf9guI/9MzO7K/97pf4o/mjxKiiiv8AFE/XgooooAK8D/a2/wCR103/AK8j/wChtXvleB/tbf8AI66b/wBeR/8AQ2r+wPoJf8nly3/BiP8A0xUP5f8Api/8msx3+Oh/6egeT0UUV/vwf4vhRRRQAV4L8R/+R31L/rua96rwX4j/API8al/13Nfx19NH/klcF/2EL/03UPrOEf8AeZ/4f1Ri0UUV/mwfoAUUUUAMb7jfSp6gb7jfSp6/07+gF/yKM3/6+Uv/AEiZ/sV+zB/5EGe/9fqP/pEwooor/QU/1GCiiigDifj3/wAifa/9fq/+gPXkNevfHv8A5E+1/wCv1f8A0B68hr/K76W//JwKn/Xql+TP0vhj/cV6sKKKK/mQ+hCiiigD5d/aI/5K9q3/AGy/9EpXE1237RH/ACV7Vv8Atl/6JSuJr/pg8Bf+TZ8O/wDYDhP/AFHpn+BfjR/ycDPP+wzE/wDp6YUUUV+sH5mFFFFAH9qXg7/kUdM/69Yv/QBWlWb4O/5FHTP+vWL/ANAFaVf4Jn7YFFFFABXMfGH/AJJ/e/WP/wBDFdPXMfGH/kn979Y//QxXwPit/wAkVnH/AGC4j/0zM7sr/wB7pf4o/mjxKiiiv8UT9eCiiigArwP9rb/kddN/68j/AOhtXvleB/tbf8jrpv8A15H/ANDav7A+gl/yeXLf8GI/9MVD+X/pi/8AJrMd/jof+noHk9FFFf78H+L4UUUUAFeC/Eb/AJHfUv8Arsa96rwX4jf8jxqX/Xc1/HP00v8AklcF/wBhC/8ATdQ+s4R/3mf+H9UYtFFFf5sn6AFFFFADG+430qeoG+430qev9O/oBf8AIozf/r5S/wDSJn+xX7MH/kQZ7/1+o/8ApEwooor/AEFP9RgooooA4n49/wDIn2v/AF+r/wCgPXkNevfHv/kT7X/r9X/0B68hr/K76W//ACcCp/16pfkz9L4Y/wBxXqwooor+ZD6EKKKKAPl39oj/AJK9q3/bL/0SlcTXbftEf8le1b/tl/6JSuJr/pg8Bf8Ak2fDv/YDhP8A1Hpn+BfjR/ycDPP+wzE/+nphRRRX6wfmYUUUUAf2peDW/wCKS03/AK9Yv/QBWlX8U8fiXUjBH/xML77o/wCW7en1pf8AhJNR/wCf+9/7/t/jX+f1P6EtecFP+2Fqr/wH/wDLj7h8XxTt7J/+Bf8AAP7V6K/io/4STUf+f+9/7/t/jR/wkmo/8/8Ae/8Af9v8av8A4kir/wDQ4X/gh/8Ay4P9cI/8+n/4F/wD+1euX+MI/wCLf33+9H/6Gtfxof8ACSaj/wA/97/3/b/GvYf2Ddevrj9p3QUkvLt1aK6yGmYg/wCjye9fJcffQTr47hjMcEs6Ufa0K0L/AFdu3NTlG9vbK9r7dT6Pg7O1mef4HLlDl9tWpQ5r3tzzjG9rK9r3tdX7o/pior8ov7SuB/y2l/76NJ/aVx/z3m/77Nf5l/8AFKHF/wDRTR/8JH/80n98f8QNn/0Gr/wX/wDbn6vUV+UP9pXH/Peb/vs0f2lcf895v++zT/4pQ4v/AKKaP/hI/wD5pD/iBs/+g1f+C/8A7c/V6vAv2tjnxtpm0bv9C7f77V8Pf2lcf895v++zX2Z/wTZ/074Y6+0375hqmAZPmwPJT1rKt9Fmr9GmD8YK+ZLMo4L3Pq6pOg5/WP3F/aupW5eX2nNb2b5rW0vdfhH0kvoz1OIOA8TlUcxVNzlS972TlblqRlt7Rb2tuee5P91vyow392vtFbOHvDF/3wKT7HD/AM8Y/wDvkVw/8VOMN/0Tsv8AwqX/AMzn+aP/ABT9xP8A0PI/+E7/APlx8X4b+7Rhv7tfaH2OH/njH/3yKPscP/PGP/vkUf8AFTnDf9E7L/wqX/zOH/FP3E/9DyP/AITv/wCXHxfhv7teC/En5fHGqc/8t2r9SvscP/PGP/vkV+L/AO2q7RftbfEWNWKquvXWFB6fPXynGH0qqXjHg45HTyx4P6vJVeZ1lV5tHDlt7Onb4r3u9rW6n694O/sz8TxBmlbC/wCsMafLT5r/AFVyv70Va31hd+53e76fnRu+n5185ec/95vzo85/7zfnX5z/AKs/9PPw/wCCf0T/AMUi8X/0VMf/AAjf/wA1H0bu+n50bvp+dfOXnP8A3m/Ojzn/ALzfnR/qz/08/D/gh/xSLxf/AEVMf/CN/wDzUfRjcRnPHap9n1rw74TXDt8UvDY3N/yFLbv/ANNVr79+zx/3F/Kv9DfoTVP7JyvNIP3+apTfa1oy9T5LiTiRfQlqQyDFU/7bebJ1lOL+qey9j7nK4tYjn5ue97xta1nfT5z2fWjZ9a+jPs8f9xfyo+zx/wBxfyr+3/8AWb/p3+P/AAD5r/irphP+iXl/4WL/AOZT5z2fWjZ9a+jPs8f9xfyo+zx/3F/Kj/Wb/p3+P/AD/irphP8Aolpf+Fi/+ZT48+PX/IoWv/X4v/oD15Dj/e/Kut/4OFJnsP2O/C0kDNDJ/wAJhbjch2nH2O84yK/HL/hIb/8A5/Lr/v6a/g/6QfBcuIOLpZiq3s704K3LzbJ9br8j+w/Bf9oJh+KOGYZssjlSvOceX6ypfC1rf2Ed/Q/WDH+9+VGP978q/J//AISG/wD+fy6/7+n/ABo/4SG//wCfy6/7+n/Gvw//AIhHP/oKX/gH/wBsfrH/ABOlQ/6FL/8AB6/+VH6wY/3vyox/vflX5P8A/CQ3/wDz+XX/AH9P+NH/AAkN/wD8/l1/39P+NH/EI5/9BS/8A/8Atg/4nSof9Cl/+D1/8qPr/wDaJ+X4v6uOcgxf+ikriM1/TL/wbKaHZ6r/AMERPglPdWltcTPFq+55IwzN/wATq/6kivvH/hF9M/6B1j/4Dp/hX+nHAP0tocNcMZdw68rdX6pQo0ef2/Lz+ypxhzcvspcvNy3td2va73P8/eNcD/b/ABDjs9T9n9arVavLbm5faTlPlvpe17Xsr72R/FTmjNf2rf8ACL6Z/wBA6x/8B0/wo/4RfTP+gdY/+A6f4V9Z/wATvU/+hM//AAo/+4nzP+p7/wCfv/kv/BP4qc0Zr+1b/hF9M/6B1j/4Dp/hR/wi+mf9A6x/8B0/wo/4nep/9CZ/+FH/ANxD/U9/8/f/ACX/AIJ/IFZfsB/He4s4nX4K/FplZAQw8IahhhjjH7qnN+wF8d06/BT4tf8AhIah/wDGq/rx8Nr/AMUppv8A16xf+gCquoKEc/X1r52n9NTN4QUP7Mp6K38SX+Rt/qjTbv7R/cfyLt+wN8dE6/Bf4sf+EjqH/wAaqOT9hD44xfe+DPxWX6+Er/8A+NV/WZqafe9hXPaod272rT/idjN/+hZT/wDBkv8A5EP9Uaf/AD8f3H8qB/Yd+NS9fg/8UFx6+Fb4f+0q9U/Yl/ZF+K/hf9pDRLzU/hj8Q9NtYY7kPNdeHLyGNcwSAZZowOSQK/o11diDXKa4xfdmuXMPpmZpi8LUws8sppTi43VSWl1a/wAJ7XDOVrJ83wubQlzPD1adRReifJJSs3ra9rX6H5vN8IPFife8LeIh9dNm/wDiaif4W+JovveHdbX62Mo/pX3trY5Ncjrig7q/L/8AiP2M/wCgOP8A4G/8j+uv+Jn8f/0AQ/8AA5f/ACJ8Yv8AD7Xoj82h6sv1tJP8KjfwVrEf3tJ1Bfrbv/hX1B4gjUb+K4vXkxu4o/4j9jP+gOP/AIG/8hf8TP4//oAh/wCBy/8AkTwuTw/fRfesbpfrEwr6z/4J8+NdF8DfDfXIda1jTNHmm1LzEjvrqO3Z18pBkByCRkYz7V4br45/M1xGvtkH2zX4748cQVPE7gzE8HYqCw8K7ptzi+Zr2dSNRaOy15bb6XufPcUeP2LzrL5YCpg4wUmndTb2d9rI/SN/jx4HjPzeMvCi/XVrf/4uoX/aI+H8X3vHXg9frrNsP/Z6/K7WzjcK4nXhk/8A1q/z/wD+JJ8s/wChpU/8Fx/+SPy//XCf/Ptff/wD9g5P2mfhrF974heB1+uvWv8A8cqu37VPwvT73xI8BL9fEFp/8cr8UtfGdwri9c43Uf8AEk+Wf9DSp/4Lj/8AJB/rhP8A59r7/wDgH7uSftbfCmI/N8Tvh6v18RWf/wAcr8hf2wviFoHiD9qTx5qFjrmk31jea3cSwXFveRywzIXJDKynBB9Qa+bNe+b8q5DXPmLN3+lfbcD/AEW8Dw3iamJp4+dTnjy2cIq2qd9JeR+heHPjfieEsdUxtHCxqucOSzk1bVO90n2PdG8Y6On3tW01cet1GP61G3j7QU663pIx63kf/wAVXy/rYyxrldYXBPXn0r9M/wCIS0f+gl/+Ar/M/Yf+Jz8y6ZXT/wDBsv8A5A+xpPiX4bi+94h0MfW/i/8AiqYfil4XX/mZNB/8GEP/AMVXwrqy/M1c5qQ+8aP+IS0P+gl/+Ar/ADF/xOfmf/Qrp/8Ag2X/AMgfpN8MPjL4P0/4leH5rjxV4bt4IdStnkkk1OBVjUSrkklsADua+5E/a6+E8jqq/E74eszEAAeI7PJJ6f8ALSv51tQfaePpVXQ33eJNP3f8/Mf/AKEK/ZvC3EPgrD16FD977aUW3LS3Kmul+5/DP0ussh485jl2Y5i3g3g4VIJQ99SU5RlduXLa3L07n9K//C2PCv8A0Mugf+DCL/4qmj4r+GG/5mTQf/BhD/8AFV+fuKsaIM30fua8OX0tcwSv/Z0P/A5f/In30v2MvDCTf+s2I/8ACen/APLD9C7PxdpOof8AHvqum3HtHco38jWxY6Xc6mP9Ht5rjpzGhb+VfNnwM4lT8K+2Pgb8qxe+M1+X8TfT0zfK2+TKacvWrJf+2n4zxd+y1yDJr+zz+tO3ejBf+3n51/8ABeT9nnx98Vf2S/Den+F/A/i7xHqEPiuC5e30zR7i8ljiFpdqXKxoSFBZRk8ZYetfkTdfsQfGiy/13wh+J8P+/wCFr5f/AGlX9fmqDHh9Bz0yOfavBfi6oKTV+H436cGacQ5g8TVyqnC6Ssqknt6xRnkvh7R8MOHllGErPEqMpS5pJQfvO9rLm2P5a7j9k74p2hxL8NfH0Z/2/D12v846h/4Zg+JW7H/CvfHH/giuv/iK/ff4nOS8lclpH+ur7jKvH7F4txUsHFc395/5H4TnX0kcbga8qMcFCVuvO/8AI/DuL9lD4pTfc+G3j5vp4fuz/wC06mX9j74tSD5fhd8RWHt4bvP/AI3X76+Geq13nh7lVr+quAcIuIYqVR+zv21/yPicZ9L3MqF7ZdTf/cSX/wAie8f8G63jnRPgf/wRt+DnhzxprGl+EfEGnx6t9q0zWruOwvLbdq96674pSrrlGVhkDKsD0Ir7D1D9sP4R6Tu+1fFL4c223r5viWzTH5yV+UPxX+S3k5PHTJ6V8b/tAjYJccda/pTh36PuDzOSjLGSj/24n+p9Rwn9JzMM4mozwMIX7Tk/0P6Ebn9vz4E2ZxN8avhLF6b/ABfp65/OWn2v7eXwNv2Ag+M3womJ7J4tsG/lLX8lHxOQfbG9q6D4RIDPF8o7dq/Vqv0OMthhvbrM537ezj/8kf01wlxBPOKsaVSCjzdtT+su0/a7+E9+oMHxO+Hs+7p5fiOzbP5SVa/4ae+Gv/RQfBP/AIPbX/4uv54fguQqxsPQCvYui1+Z5l9HXBYar7OONm/+3F/mf2Zwx9HvB5pg1iqmMnFvooL/ADP3f8OjHhTTP+vSL/0AVU1U/MfrX5xaL/wdh/sa2mg2VvJ4x8WLJBBHG/8AxS151CgH+CmT/wDB1j+xtfy7U8aeKtzkBf8Ailbzqf8AgNfymfy2foFqX8Vc1qpwxr4iu/8Ag5q/ZLn3bfFnivn18LXv/wARWaf+DkH9lXWbxYbfxV4paSQ/KD4YvFzwe+ygD7K1jrXJ6yfv18u6h/wXy/ZtuR8vibxB/wCE/df/ABFZLf8ABcH9nrXbrybXxDrskkmSAdDuV9+pWgD6L1v+lchrnQ/jXiup/wDBXT4JXWdmtaz+OkT/APxNY8f/AAU5+EviedobTVdWaRQWO7S5lGPxFAHqHiA/e/WuN19R831rkdW/bo+Hd0Ds1K+bP/TjJ/hWOv7U3hDxW0gsby6k8vBbdaOuM5x29qANTX1/niuF1zk/nWlq3xd0W6+5PM3/AGxNZK38PiaJ5LNtyxttO4bTnrQBx+udT+NcVroyTXpWq+Db65b93Gh+rgVmf8M/+JfElq81na27R7inzXCKc/ifegDxXxAcbvrXE+ITgn619Cap+yF43ut22xs2yf8An7T/ABqjc/8ABOP4peILZbi10vS2hk5UtqcIz2/vUAfLWt85rkNa6GvrrU/+CWnxguVPl6PpOffV4P8A4qqMn/BFv4+a1aJND4f0No5RuU/23bdP++qAPiPXDjdXLax1r7r1T/ghd+0PdE7fDehn/uO2v/xdM1L/AINz/wBqO5PyeF/Df4+JLT/4ugD88dV+81c3qfRq/Ri+/wCDbH9qy6fEfhbwuWY4GfEtoOf++6Lz/g1b/bGnU7fBvhX/AMKiz/8AiqAPzJvowVaqWgjPiex/6+Y//QhX6HfGb/g2V/a0+C/wo8UeM9e8J+GbfQfCOk3etalKniS0keK2toXmlYIrEsQiMQByTgV+dekajFZ63aTyE+XFOjuQMkAMCaAP1aq1on/H/F9RXibft5fC/wD6GKb/AMFtz/8AEVY039vr4W2d2jt4gnKqe2m3P/xFfyXU4ZzdxdsLU/8AAJf5H+y1TxR4McHbN8L/AOD6X/yZ95fAv/WR/hX2v8DvuQ1+S3wu/wCCs3wR8JtH9s8Raj8uM7dIuW/9kr6Y+GX/AAcB/sx+FvL+2eKdeXb/AHNAum/9kr8G468O+KcU39Xy6tL0pzf6H8v+JnGWQYly+rY6jP8Aw1IS/KTP1D1U/wDEhT+9t4rwf4ufcm+teAX3/Byn+yfcaYsS+L/Em5Rj/kW7v/4ivKfiH/wcBfs06+ri18TeIJN2eugXK/8AstfmXD/hPxpSq3qZViFr1pTX6H8HeKUljMPKOF99/wB3X8rnYfE370n1rk9H/wBdXhfjX/gsP8CdeZvs/iLVPm/vaPcj/wBkrB0//grJ8EbaTLeItRP00m5/+Ir+huHeCOIKU6ftcDVWvWnL/I/zf4o8P+Jq+MnOjl9aSfVU5/5H2R4Z+9+Vd94f+6tfD+if8FjvgLYn954k1Qf9we5/+IrrNK/4Lb/s82YG7xVqo/7gd3/8RX+hXg7VhgYRWMfs/wDF7v52PyrNPCzjGbfJleIfpSn/APIn0h8WObaSvjj9oIf8fHtmt/x5/wAFnf2f9ftWW38UaozMO+i3S/8AslfN/wAXf+CiXwr8Web9i1u9k35xu06dc/mtf3XwPxtw9Qmvb46jH1qQX5s/QPDnw74pwtSLxOXVof4qU1+aPLPid/x+N9a6H4RHNxH+FeT+OP2hvCeuXZa31CZlJ720g/8AZa2Ph3+094L8Pzxm61KaNV6kWkrfyWv6LxHidwc8CoLNsNft7elf/wBLP7w8OcDiMLiacsTBwSt8Sa/M+9/gyq+TH+Fev54r4v8Ahr/wUa+Enh5UF14huo9uM40y5b+SV6IP+CrfwPz/AMjRef8Agou//jdfgudcccOzxDlDH0WvKrB/+3H+o/AfGnD2HyyNOvjqMZaaOrBPbzkfkXVnSBnVbX/rsn8xVarOj/8AIVtf+uyfzr/O0/z4PVNxrV8Et/xVFp/vH/0E1k1reCP+RotP94/+gmgD0YjNbHgQY8SQ/Rv/AEE1j1seBf8AkY4fo3/oJoA9Arovhmf+J7J/1wb/ANCWudrovhr/AMh5/wDri381oA7quw+E4z/aHsI//Zq4+uw+E/TUf92P/wBmoA7Cu2+F4/4lVz/12/8AZRXE123ww/5BNx/12/8AZRQB01d18NDnQZP+u5/ktcLXd/DYY0OT/rsf5LQB0OK9M8Ff8irZ/wC6f5mvM69M8EHPhSz/AN0/+hGgDUr07wuNvh2x/wCuK/yrzGvTvDP/ACLtj/1xWgC9Xq+wV5RXrFAElmg+2Rf74/nXrm6vI7P/AI/If99f5165QB4j/wAFMzn/AIJu/tBf9k18R/8Aprua/h1r+4r/AIKZ/wDKN39oL/smviP/ANNdzX8OtABRRRQAUUUUAFFFFABQDiiigAooooAKCc0UUAGeaKKKADOaKKKACrWjf8hW1/67J/OiigD0/ca1/Az/APFU2n+8f/QTRRQB6RW18P13+JoQf7rfyoooA9CKAGui+GUYbXpP+uJ/9CWiigD0D7Gu3q1dV8Koh5l+OnCf+zUUUAdgsAI6tXffCbT0l0W5Zi3+vI6/7IoooA6aXT0U9W/Ou9+FumxyaBLuLf689/YUUUAdL/ZMX+1+dejeDNNjHhizGW+4T1/22oooA1P7NT+8/wCn+FepeHdIjHh6x+aT/Ur3H+FFFAFs6dGD95vzr1uPSo5B95x9DRRQBNFo0aTRkNJnevceo9q9KoooA8S/4KXDd/wTi/aBH/VNvEf/AKa7mv4dKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//9k="; +const testCardSplit = "iVBORw0KGgoAAAANSUhEUgAAAUAAAAC0CAYAAADl5PURAAAhfklEQVR4AezBP49maZon5Ot+zhsRVdXTO03zRyxidxnhIKQRHgjhgMMXwMblS+EigYGBhYMQKwwsLLDQaqRFsAxCC730dE1lRrzn+dHSvejM2YjMyqzMrIiseK6r/lvyZxiIx+LLmtpFu2qbFgQDQeEev0Lh3vvd+7Ki3WubdtHutL/AX+A/8XX5Fj/gou1elkIQZ/+R9p9qb7Wrs+F57SgUgt/gH+K3KIddG9qubZ5HtF8j+C2mr8/lz/Bn3i2+rKlt2q5t3u0NvtHuvV/5sqLda5t2cfY9/oBfofANJsrzilbOot3hHr9BsCMO5WV40K7an2q/1m61q7PheU2Uw5/gV/iVs10b2q5tnke0X2HHN7jiAcGG8m7lZbgEQTkE5eUKguDi08Sn2bRNK2dB8IC3+B6Fe0xcvQylxdm32PE7BNPLUlqcTe2iXXw9fos/9XUIJiYm4hCH6Wxo0/O6BPG0eH7RyqFQiJdvICgUCoVCeTlKi7Nd271M8bSpTWellRbPqzwWX5eJielQWrxsF/+MQrwsQSEoh0J5GTZPmwh2TATBWy/TcDa0W+2qlRbPa3ra0Ib3K88rWjkEQTlMbWjx/IKBDYWBiUIQ7za1eF4X/4wgXo7yZcWnKR+mUL5uQRCUVl6moBBn5eUJymFiojC0OIufR1Aei0OcBRPx8l2CoBCU16V8mmhX7cZZoVAoL1c8rbSplVaI53dBYWrRhrNdm9rwMgwEQWnxWHke5WnlMFAorbRCvGyXQmmF0grx/KZWHgvKy1aIxwrxcpSnxWOFQiGeX/yyFArD1yEIogXl63CJs2jxMpTHSivE85patGjlUNgwtMLARLwM09N2bUe8PNO7BaVt2qaVl2vDcDacDS/H0MpZvHzDsizLJ4iv18VXLj5N+TSbs/JYsGPXgl0bntfUytM2PGBDsDsbntf0bnEoL9OuDZS2Y6JQ2tSGNr0MA+XrNSzLsrxSF1+58jKUpwXxbtPLEE/btR3x2PSyDJRWDruXafPYhuFsOBteluHrNCzLL1Qsy/tdvHLl00TbnZWzQmmFDRPTy1CeNrSBYDobntd0NlEI4jCclRbPK1o5TI9NbWhTG5ZPMSzLsrxSl0K0QiGeXzxWPr/4PC6eVihn8XLF+xXiML088ePi6zYsn8OwLMvyCcrXa1iWZXmlhmVZlldqWJZleaWGZVmWV2pYlmV5pYZlWZZXaliWjxDL8ssxLMuyvFLDsizLKzUsy7K8UsOyLMsrNSzLsrxSw7Isyys1LMuyvFLDsnyEsiy/HMOyLMsrNSzLsrxSw7Isyys1LMuyvFLDsizLKzUsy7K8UsOyLMsrNSzLR4hl+eUYlmVZXqlhWZbllRqWZVleqWFZluWVGpZlWV6pYVmW5ZUalmVZXqlhWT5CWZZfjmFZluWVGpZlWV6pYVmW5ZUalmVZXqlhWZbllRqWZVleqWFZluWVGpblI8Sy/HIMy7Isr9SwLMvySg3Lsiyv1LAsy/JKDcuyLK/UsCzL8koNy7Isr9SwLB+hLMsvx7Asy/JKDcuyLK/UsCzL8koNy7Isr9SwLMvySg3Lsiyv1LAsy/JKDcvyEWJZfjmGZVmWV2pYlmV5pYZlWZZXaliWZXmlhmVZlldqWJZleaWGZVmWV2pYlo9QluWXY1iWZXmlhmVZlldqWJZleaWGZVmWV2pYlmV5pYZlWZZXaliWZXmlhmX5CLEsvxzDsizLKzUsy7K8UsOyLMsrNSzLsrxSw7Isyys1LMuyvFLDsizLKzUsyweKZfllGZZlWV6pYVmW5ZUalmVZXqlhWZbllRqWZVleqWFZluWVGpZlWV6pYVk+UFmWX5ZhWZbllRqWZVleqWFZluWVGpZlWV6pYVmW5ZUalmVZXqlhWZbllboMDK208vPZtF2LtjsrDI/F8yqtnE3tAYWJqRUKG6bnVVppcbZ5v+F5TY8VJiZ2bddutfg8pk+zaUFpw2FqQ5va8HIUrtgxtOnrMPxRfF3K16O0QqF8XaLF1yOI5ecShyC+Hpd73KNQKMTP50bbtHvt1tk9gs3Z8LzutRtn99odCre4RbTdyxAtzsrZdHaD4Op5DRSi3Wkbgl2LdtUuPo/Np9m1zWFiavfaN9qDdutlKBRuseONs3J2q1216XldLF9cfL3Kyxe/XOWstPLylK/P5QY3CAqFeLfh83rQbrV42kRQmFphel63nrZppW0YDtPLsGnT2abF08rLMJ39oN0huPW0B23zaYZPc9U2Z7tWvg7BxEW7atFKe+tlucSy/PLE+0WLTxNfVpzFyxQE8XW5DAyU53Gj7dqds7faN1owMBGU5/VGu9NKu2gThd/jr7w8u6ft2qZ9h+AH7d7LdIeJ77Vo5WxqNz5N+TR3WlDaLW603dmts93zu/fYtyj8tRYv08U/FZTlc5soRCsUplbeL55H+ToU4t3K00orL9twVs6G5xWUs0KhvHz1d8gdpsNEtDgbPq8H7U773tl3CP4KhQ2/wl94GaKVNrXh7H/Hv4p/gIk7TNx7XndaaW+1aH8X/yX+XdziFsFFe+N5lbO/1v5z/Hv4j7W/pU3tjbb5NNOnucem7fgP8Z/hzzGwa5u2a9HK8/or3OG/wRUXDPweE3+i3WvfaVctntflH+EO02EinjZ8Xg/anfbG2S2CB4dfIwjuvV98Wbs2tF3btB0Df63dYOA7BD94v/iy7rTShhZtQ2HHAwrlsHtehaC0HcGOHRftok3tom0+zfTpCgMTQ/vHzjZt1+JluMcdfoMb3Gm/RuFPtHvtG21q8bwuE28Qz+OiPWibtmv32h2Ce2wICt94XtHKuwW32r/mMDE8r6kNZ1Mb+Hv4N5ztCIbnNTztBjf4P7R/pJUWbfg08WkumNi1v9R+h8IPXrYLvsEFNxgI/hYKU5ueVp7XxVektIkdA5vnVX6aIJ5fnEWLQ3msUF628vINxNM2j5UWL8PAwMTUymFow9nwMlz80UC06ed11UqLp+0OwY0PE19WadGilTYxcdUmJoYWrTyPzVlpm8NEULhiOAzP61671Xbtgg2lRbvR7rXpeb11Vg5XP27zvO7xBvcYuNV2BOXswdnmeV38URDPK95vIlp8uPLzKGelFQZKC4Jo8X7xZQ1Pm9rQSiuHeBnilyPaQPy4eF6FG9zi4mxgatHiLJ7XxR9t2B3ix5UWn8dFu2rD2XQoh90hHitf1tB2rbRdu9W+0aa2aVOLVs7Kl3XVLs6Gsys2TBQu2vS8bj3tih3RSnvweZVPE60QlPYG5bE4m55XYWLDhmibNrSp3XpZLv4oWny4+LziLFpQKC2IQzmUFi9PeVohDvHzig8zUZjYLF/CwNDi61AoFAbK1+XijzZMBOVluEUhmHjQBm61oLxfeVpp8WlKu2rfaJuzt9rAhiB+mvLh4v2Gp01tYEdwxZ2zoDytUH5em1YoXBDsWhyGQ7Q4G54WLSgUSotDUJgeG4i24YobLYjD0Ka2OQTR4v0KcRja9LRyKC2IFgxc8Y22Y3N262lBYdfibHO2aw8IvtV2rVAorbzfxR9NTD+PchZtIB4LylmwY0M8NhziLFo8rfx0QZyVdqeVVlo8Vp4WLQ5xVj6PcrgguOLO2XAWhyAoj5UWrXy6iaEVBiYKhcLuMFFa/DTDIQ7x46bDFd+iPK2cxWOFeL94v/K00uKscMUFEwMbJoYfFy0olFZaadGiRSuttCAYWnm/iz+6ojCw+zClxceJpz1opb11tmHHd/gWOzaU9ytn5cu41Up70Da8xffajl3bMHy48vm90TbvdoM7XBzuceuxclber3weE4Ud93jAjl271XZnpcXTpveLHxdPKwzs2g8OG3YMrbRyNh3iUNpwFi1anMVZeb+gcMU3eIMbFKazoLSJ4Ea7aNHKWWk32o2zTYsW7eoszi7+KBgoHy6+jNLibNeCiQ0Tw/vF+xXi3cpZnJU2tdKmVhgY2g2uKEwMz6sQj0UrFAoXh+Aetz5OtPL5FR5QuMONs3gsnk8wsGPDxNB2rbTSSitPK620OIsWbTiLD1PaDSYumLh1mChPK5THyoeJVs4KQbThLM4u3mFgeqwQlA8T73eHYGq793uDDRvix5UfV94tHiuPRSvtVrviDjcOF4f4eHEY3m86lMc2xCFaHHZcUdi0O8THK5/XjoGBHYWBb3DjcO/sO0y89fMoBEMLBh60XXvrsDlcEY+VFi3OLtrU4ixaPG0gKGfRgu+wYWjRLoizIM6CqRUKQbRCedrVoVDO4mw4u/ijwsTUCvG0aNHKIT7e1OJQiDYwHXZEK8ShPBaH8liclbPyftGmFpTDBcHV03aUx0qLVp42nZWzeFpp8bRyGChsDlMrz+uKO0S74AH3uPVuVww/n2hBtOmxW23DjqHF0wpBIZ4WRIuz8rRo0aJFi7bhLUoLytMK5bHC0EorRCvvVs6CaEE5i7PLLX6lBYXSJkqLFq0QDC1aHILhEC0Ope3avXbRfoOJ32PDFb/F/4xv8b1DPLZ5LA6lRYuzDXGIsxvtQbtFEG3HxO/xd/DXKOwo3DjEIVp5WrSLQ7RoQTnEYxPBLYJ7BFftn8Nf4n/Aju/wgCsGvnEWFKIV4jC1aJsWLYjDcIjHNgR/0Ep7i9/hP8DFYccVwbf4d7TpacNZtF170HYU7rUH7S0K99quXVF4iyt2XPGvaL9C4a32gGA6m54W7cH7Te83vd8b/J/4+yjcoVDYcEFwo32jbZi40S7aBYUbFAYKdwjutKFtPs3lv8C/iFtMBNEK0aLF2XQI4hCHYGIiCIKJ4EF7g2BD8FsEf8AVP6Dw5z6/YCIIgqlNBMFEtCC4x8QFExPBRPCA/wn/C4J7BBcEQTARBMGOIJiYCKb2gCCYCCaCYNeCiSAIggcEN5h4g+ABwT+Pv8K/jx13mHir3SCYCCaCiSAIgmAiCIIdQRBMBEEQj8Vhwz0eMPEG3+G/xn+P/877vdWmpw2ttKntKNxioHCLwq224QY32HCHDbcILghu8IP2b+PfxL+FHb9H8AMe8D2ueIOJ/xUTP+CK77HjAcFVmwh2FKb2DQrBQDBQKGwoFAYmyuEBA/8VJv6AHd/iewRvEdxr97jirRYU7jHxgE37BgPf4YJf4xZ/ggt+hRv8KW7wt3CDP8UNfqUNbTi7/Dn+nrMgCCaCOARB4aoFE9GCiUIQTATBRLBp95h4i4lbbUPwBsE/wcT/iImJYGIimAiCYGIimAgmJoKJHUEQ7JjaxEQQTExEu2JHMLFjQ3CDqU18j+AWE28QBEEwMTGxIwgmdkwEQRBMBBMTExPBRDARBBPTobBjYuIBE/8AO/4CO75HsCO4RzAxMRFMbSK4IlowEQQTE0EQBNEmJqIFQfAdCr/HxG9QWhzuEOyYCP5l/H2f5h432HHBjg0PuMHEwBvcYccFE8NhYuABb/H/YMc/xBW/wwP+L9zj/8UD/jdc8Ttc8Y9xxT/BjgctWmEgKOw+j0IwMLFhd9iw4wYPGJgoBIWgENwg+DVu8Ftc8Ldxgw23+Ndxh38BF/xdfIO/jQt+o5XHglEo7xeHIFp8uHi/aPFYtHi/0sq7lUOhUCgUSiuUViiHQjmUHxc/TaFQWqFQKJ9HvF+hMFAoH66cBUEQX0Y8Fo+VLy9aeb+JYCII4mnlEIcgiBY/n2jRopVWfpqBgQ0bBjYMbB4rH+eyYUO0wtSCYCJaEASFQhAUogWFQlAIgqAQvEGwaRcEt4jDBcG/hIm32oZgIpgIJoIgmJgIJiaiTewIgomJiWBiIpgIJiaC0oKrtiN4wEQhCIIrJgamFq1QKGeFQiEolFYICoVCaaWVQ6HwRivs2BDcYWJi4g8IdgQ3CAaCiYmJYCIIJm4QBMFEtImJIAiCIAgmogVBUNoFE1ftiuBWu3d2wY3D1KKVVtpEYcfApt1qF6200kobKESLdsUFF21i4g2uuMPAd7jiOzzgLW7wHa74g7MH7IizYHcYKBQGrhiYGJgYCApXFIKB6awQh4EdQ9twxYaJaHE2MXGP4KoFExsG3mo7ymFgQ3najmD4p8rTCoXSCoVyKIdCOQTxYaIF0eIQxKH8NIVyVg7l3cpZnEUrh2hxFh8niEN8WYUgDuUQPy5aoVAoFMqhtEJ5rFA+TBAtiOcTrZxFK2eFQjkrrbTSosWXFR+mnJVWfpogCIIgCOKsUA7xbtEu97jXgg1TizYxtSCINrWJINrE1IKJaEEwcUFwg4kdA/daIfgewQ2CP0XwfyOYCCaCiSAIJoKJYCIIJiYmgmDHRDAxEUwEExPBxA0mNkSbeMCOHaUFV+zYtDjEobSgUFqhMBFPKwSFOATR/kS7YscDgrcIrgjeILjRgiAIgmhxVohDtEJQiA9XiHZFEO0HBNOhHL5DULj1WHlaOQsKb3CDe9ziijvs2FDaRbvRLtoVhaFF+xYb7nHBBdEKb/GgRQsKhQsKV4dCMBAUpjYRTG1qU5sOhWi32DFwxYYdAxNTe6vdI3jQvsEVA/e4YEehEARX7YqBiTgEwcSOB+1e27SpTW34G8pZOZSnlaeVz6N8PvFh4vMI4seVd4vHosWXU1oQP11ppQVBMJ2VQ3la+XFxKGdBED+P8n5xFsSnKT+fIN4tPk75ePG0aOXdLje4cShMZxPRgoloE8FEHCZKCybiMDExELzFxMDEVbug8GtM/DWCv0TwLYJCUAgKQRAUCkFQmFpphaBQKAQDExMDE4XCWwQTE7sW7HiLiUIwEGzajmBHMBEEEzuCiYlgYmrBxMREMBFMTAQTE8HERHCvTUxMBMHEBUEh2DDxgOCKYGJqE0EQBMHUgiCYCCaCIAiCiSCIFkwEE8EdgsLEQOEHbXP2A37nMLxfOQyHG2y4wYbdWVDYseEBN7jHLb7RJgpv8YAdE29wxRtcsWHHjfZXuOJ7POAeV1yxIw7RdmfRdm1qU5tatGjBgza1XZtaIbjgigvucYMHvHF2xdQmgmDDhjvc4Fvc4Rvc4g63uMENBjZsKAxtc3bxI4IgWhDEh4vPo7xbIZ4WP64QjxWiFYLSyoeJDxOPlVYoBIXS4udRWqFQfppCaYXpp4sWxMeLL698nPhliJ8mzgqFQqFQKJRPd4lWmAiixSEIgmCiEARBtCBatCAIgmhXBEOLdqNdMTER3CDYEEwtCIJoQRBEmwiCaMFEEARBEExMBMGOieAGQRyCqd1g0yZ2TK0wMLFhYEcQhzibGAiGQ2FqEwOF6RAEhUJholAYmJgoTEQLHjARBBuCQiEoTARBEG06BAPRgiAIogVBtGiFicJ0CK4ICsGDs4HvMD1taEFhahPBQDkMbWgDhR0bokW7aBMDA8HAhmCgtA1BEBQKv8IVf8CmBRPxWGFgojC9WyEoDAxcMRAMTEwMTI9NbWpX7UErBDd4wAVXbeAGF1y1N9hxr10xsGHDPSauKGzarm3Ohj8q7xdn0eJ5xfMqj5Xl/xc/j0L5ZSiUFkt8vPLhLuX5xPOLVgiCOBRKKwSFQrxf+TIK8dPEWXzdCtEK0UqL9xver7ThUChtx0A8rbSLdtGGVg6FCwqFoFAIgh07rrjiiiuCoFAYCOIs2LWgMLCjEBSCaMGOXdu16TC1aIXggitu8BZ3uMcN3iLag/agxdmGDRdccItbvEXwgMLExMDw2O5sWJ4UlENp5ax8mFh+DoXy48rycyhnpZWfJs4KhfLTDH9DWf6m+DiF8uPKL1sQP69yVn4etyhs2tDK2VW7arunFQqFQqE8NjEdCoULLrjBDS64YGDThkOwa9Hiw11QuGgXbXN21XbsuEdwo92isGmF0uIQBDt2fIfv8C2+wx3ucMHFYWBgYGBgYFi+mPJ84iwei0Ocla9f+ToFhfJ1Kj+faEF8vItFEEQr7xZn5acrX0a0eFp8mPL1iJ9m935BYWo7CoVbvMUNrrjBA+5wr03cYMcFE9PhLW604AFXTAQDAxftgolbFK64YseOHTuu2DE9rVCYGJgOhWCgUNrAxMDEwL121e61B+1B27UHbdeu2hXBrkW7ajsKO3bs2HGv3WtvMfHgaeVseOXKWTnETxM/rvw0pRXKWSwfozy/QjnEoSwfKwiC+HGXQhCUw8AVA0G0ICgtnhaHIJgY2LFhIg6FOAQbrigEQSEYmJgOhSDOSistiFYoFOJQiBZnEwMThSAICsFEYWJgaoUrLrg6xFmhsKMQFIKBB5QWh3JWCAqFoBCUVlpQDnEWBMH0WDxWCAqFaKXFWbSB3fsVJgpBIZjawAMGJiY2RNt8mE3bUCjtBgO32kW7wQVB4Ua7wXC4YDhszq64YseOB+zYccUNChcEAxuCeNpEOUxn0aazgYmBiYFCMDBxwRU3eMANHrBhx8B0uOABGyYKcdiwYcPmLCgUNmy4wQ02bB6LNrURFAYKpQWlFUorlBYEE0EcymFgw4aBYOKKQqG0QqFQuGJqAwOlTcShtEKhEAQTQRAUhncrFAYKA4XSCvFuhdI2DAyUw45CoVAoZxPRCoXhMBHEWaEQhyDOJiaCiSCYmBgOhYGBwvBYOZuYmJiYCIIgCIIgWrCjUCiUQ6G0gQuuiHbRpkNhQ2nl48XZ1KKVtmmllVbOylmhHAYGCqUFQVAoFMqHi48XLdpEaeVsaldtanFWKO9WKBQGylmhHIIgiB932RFtahNBIVoQBIWJHcNZtGjBRDBR/r/24GbHjus6A+jap6r5Y8eGBWSSx8xbZZxZHifTBEYSyrIp8tb5gmAjOLi+l81uUqJIqdYiKGw4tGBiIpgIdkwtiFaIJQiCIAgGgtImJoKJYCIIgiAIgosWTASFiYkgCIIgCIJoQbRCIQiCIK7tKESbiBaUNhBtaNGCgaAwUJYgKExMlBYE0xIEQWmF0goDQSEoTC2YWjC0aMHERBAEQRAEU5u44BXe4y1+j6kVohWC+DSFskxtYsPEwMRAUAgKEwNB4YIHHAje4cAFF1xwQRBsCF5ohcLAhg0ThUJ82MRAYWLgwMBEISgE0WKZ2uHawIENFzzgHTYc2HDgPYL3WlzbseMBOzbs2LDhwMTEgULhAS8sUztQmNpeWqFQ2sTABcHERGkT0QqxBEGwaQcKhYFCYSDa0GKZWhAEhQ2FIChMbaK095gYKJRbhaAQFApBYWgHgolCfNzQgmhBIRgIJgqFWIJgIpiWgQPB1IKJiaAwEUwcmAiCYYklmG7FUpgIokULYgmCYCIIphZMRDswMXzYwIENByYK/4D3lkIQbaIwEC2ulVZaUJgolPvKtdJK27TSNhQKhYFgYEOhUCjXCoWBgfI8hUKhUCgMBIUgPl20uFbahgs2HJYNwYYNhbJEGyjLxMSBw32x7IWLFpR2oDBQOFAYWmlTGwiiDQSFDYWgMBFMTC2IpRBtQ7QLJoKLFgQTQRAEG8oSlFaWQlCIVlppE4VowUAwUSgMTEthYmiHFteCIJgIBgqFTQumViiUa2UpDAQDhUObWhAtlguiBUEsAxMDEwOxXFwrlGVDEESL5UC0IAiC4AUOvMZfUSj8iAccKNcKA0E8XVAISnvQhmsHNhzY8RYv8RYvccEDSpsoTExMTEtZpuUdDhQKr/GA7zAxtYlCobRCtImBiaEVBgrBwIGBicKPCF7iPR7wDg94jx1v8RI/4gV2bPgR0TZMFKZWKG1iYiK4YOAFXmBgs7xH4aJNbdM2bf9XfIegUNrEhgMDB4INQbBjIG4FQRAEwYYLXmilRQuCaNEmgkPbMS1BtCDagYmJaA/YMFEIgmAimNpEULjgNQ4MBAeCIJgIgiAIgpfYUdrEoQUbgiDagYGBl5gILtrARLRgIggmCjuiFQ7tQCxBEAQX7NgRTEtwIJiIFgTBay0IosUSLQiiFXZLLEEw8Q4vtcI7/B7/gjdaUJgIgjf43rVope0IDu2wDLzFjr9iww8YeIMdP2LD99jwF2x4h4FC4Z32PYL/QfCfuODPuOA/cOANDvw7gr/hwPeIr88P2g/ue6dFO7R3GPgbNvwF7/AKO/4bD3iFHRMPCHa81aa2abu2/zMKQSEoBKUNTG1govAamxaUW6UFExsKf8QLPLgVS2FgolB4gT9iQ7RopcUSrfASf8BLBJsWLVq0oFAI/hGFgcJmCWIJNkwMbNixoRA8+LBCELzEdxgoTAwEQSyxvMJL/B6bNnBgeFzwgN/hlTZRKASbFi2W4J98XLSBIBh4wB8QS2lBMLSgtAs2/BveYCIYiBa8wZ/cV9pAEBSmawOFw32lDe1wrVyLazsmSjtcKy1uFYYW16LF05QPKwzEEgRxq7AjuGi7dmjR/kv7s2u7dtEetPeWYZnarj1o+9DiVrSJaEG0gd9pQbk1sWkTpb3GK0yPCzZMlDYwMCxBuRYMLQgGNmw4cGjlWqFwoBAEO4ZWOLSylFYIJiamNjEQDC1uBYVgIpjaBbsWrVCWwg8IXmnRDmyIW4VoP2LDgxYUChNTi1auvdfivoEDhYGJic1SbhVKCwpBYdcmgri24cDh4+LzRIvHxX1TC8rTlRa3CvF5ohWC6Vp8WBC3CoX4fOVx0fZpiRb3FaYW7JZy30AQlGXTyocFA7EUgs21cqsQrVB4jw2HVj4sKJRWliAot+LaQLAhGFohPm4gKK2woRCUJZZgR2FohYlNK/eVNvCAWKIV4lpcmx43ERQOrRAU4nHlVlAYiGVqh4+LFkvcmp5mui8eNz0u7osWt8rzxX3R4r6yxHJx7eJ5Lq69dy2IWxftog0/s7gvPq6cgvh1K6cvKYgvK75Ou88UX0b5ZZSvR1C+LeV5yvPE6VPFzy++bsMXFC2+rPj2xS+r3IrPV1qhLIVyOv28hmcqvz1B/LTKtyt+WuWnE6fT0w3fqCCeLn46hfLbUCin06/T8AXEEt+2cvo/8XWJ0+n5hi8kPl359Sinvxen0y9jeIJoQWnl6QqlleeLz1NaadPTBYXSYonnC4J4ukJp8WnKMjxPUJZC/DSC0oLSpucpt+J0etxwOp1Ov1HD6VcvTqfTPcPpq1ZOp9PPZTj9ZgRxOp3+33D6ZsXTxel0+nvD6VctTqfThwynr145nU4/h+ELCGIpFMrTlFaY2BDPFxQKQWmFQqFQKK0QrRBM9xXKtdIKpRUKQaFQ7itMlLYhnm4gGAgKQTxNeVyh3CqUpVAoFAqlFSYKQXm+WKINp9Pjdt+AWMpSKC2ulSWulWvxYYWg3Be3YgmiBdFKixb3TQRBEM8TRAuCINrweab74loQS7RNK5RWTqcvY/eZ4nHlVrT4uKDcKpSlfFhZSisUgnJfUJbSSitEK0u5b2BoA2WJxxUGBqKVjwsGBgYGgqBweFwhCKYlKGweN1yLFm2iEC0oHDh8mnI6Pc3uZxaUJVo8X7RCobR4mtLKsnlcUIhWWiEYlnJfUNiwawNBLHFfsGHDjmiF+LgdO4KhBYUHjxt4jVc4LMHAYYmltM21cm2gEAxEu+CVpwnKrXI6Pe5/AXt+L94J8N2HAAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAAUAAAAC0CAYAAADl5PURAAAjzUlEQVR4AezBwY5maZ4f5Of/ni8iq7qY7vFoLDRIlj1CXAASO18C98FlcCVsESztC0Cy8cJbhFjakr0ALEaA7J7uroz4zvtDPX+1DscRGZVZWVkRWd/7POV/En+NgXgqfh5TG1q0QhAEGx7wiML0sgc/j02bWrQ77Vf41/hzhx3ldQ1ncfYN/gabFq20eH3B0C7aP9P+O23TLlppu9dVKEwE3+Lf4D9BIdrQopUWryPa9wgKE4VgolBatNLiTbj4a/y1D4ufx9SGFm1oE8HA93iv7V5Wfh6bNrVoU/sGf4vfoLSJ8rrKWZwVHnBBEATlbYhW2kX7jfZn2qZdtNJ2r2ugsGvf4Dt8p0UbWrTS4nVE27BrVxSCiUJp0UqLN+EiCMohKG9LOQQTwcXnKZ9n10obzjYE97jgDoXCxOZ1RYtWWmn3+B4XRAtKi7chzi7a1HbtQYtWXl8hiPYHDGdDi7chWmFix9VhauV50cqrugjiefH64qlCId6+gaBQKBQKhfK2lFbajh3ledPri6emNrU4i1beliCYKIdo8TZEK0xMTBQGylfh4j9WiLclWjBQKAzEy8qXVVpp8WGFQqG06XVFi1ZaaUOb2tDibRjOJgqbNrSplTa18rpKCwobNtyhMLWhxdsRbWBiYKBQCOKsvCkX/7Eg3o7S4qwQPyy+rGjR4nmFwkBpA/H1CII4lNcVTwVBIVqcxdtQCKIFExOFaNHi5xWUp+IQh2gTQZyVN+UiCApB+ToUCvGy8rL4acXZru3YsaNQmCivaziLVg7RCkG0YHhdO8ohDnHYtKFFi7ejfLryZZXnlRYUCoVCoVCIs6FNrbyqi0JphdIK8fqm5wVBedsK8VShUF5XeV5p8VRp5W0IytevUCgUBnZv20QQxFfnIs6ixdtQDtEKhUJ5XaWVNrTpbKA8Vd6GeN7AhoFgICgtXtdAIdpEaUG0aFOLt6G0IJgIgoloU5vOhtcVFOIwtTiUFq20eFXDsizL54hDfFUubl18GaWVs4HC0Havq7Q4K60wUQiCOMTrGiiHQmlxiLNo5XUNbaIwMFAolDY8r7yuIJ4qrZyVFq28qmFZluVGXbwV5XWUzzO1aPGyaEEwvK7S4qy0gYHSSitvw45yiDYRlFZaeVumFgQTExOFaNHibQqC0oL4sHgThmX5mpUPi2V50cVrKy1eR3ye0korL4sWBOV1RYsWrbSBIIin4nUNLQ6FQiGeF29boRxK25xNb0OhUM5KixYt2vCqhmVZlht1UYhWKMTPJ1q04SxaOSuUz1d+GlOLVloQh4FCEJS3pbTyYUFQKK8rKMQhDtGGFq20eBsKhUI5RIsWb0uhEASlBXEY2tRKi1c1LMuyfI7y1RqWZVlu1LAsy3KjhmVZlp9SfDWGZVmWLyHevGFZluVGDcuyLD9W+aoNy/IpYlkO8WHlzRuWZVlu1LAsy3KjhmVZlhs1LMuy3KhhWZblRg3Lsiw3aliWZblRw7J8irIsvxjDsizLjRqWZVlu1LAsy3KjhmVZlhs1LMuy3KhhWZblRg3Lsiw3aliWTxHL8osxLMuy3KhhWZblRg3Lsiw3aliWZblRw7Isy40almVZbtSwLMtyo4Zl+RRlWX4xhmVZlhs1LMuy3KhhWZblRg3Lsiw3aliWZblRw7Isy40almVZbtSwLJ8iluUXY1iWZblRw7Isy40almVZbtSwLMtyo4ZlWZYbNSzLstyoYVmW5UYNy/IpyrL8YgzLsiw3aliWZblRw7Isy40almVZbtSwLMtyo4ZlWZYbNSzLstyoYVk+RSzLL8awLMtyo4ZlWZYbNSzLstyoYVmW5UYNy7IsN2pYlmW5UcOyLMuNGpblU5Rl+cUYlmVZbtSwLMtyo4ZlWZYbNSzLstyoYVmW5UYNy7IsN2pYlmW5UcOyfIpYll+MYVmW5UYNy7IsN2pYlmW5UcOyLMuNGpZlWW7UsCzLcqOGZVmWG3Wx3Lb4eNHKsvwiDMuyLDdqWJZluVHDsizLjRqWZVlu1LAsy3KjhmVZlhs1LMuy3KhhWT5WabEsvwjDsizLjRqWZVlu1LAsy3KjhmVZlhs1LMuy3KhhWZblRg3Lsiw36mJiolAYiBZEK1/WcFbOJgau2LVCvK6pRYt20TYUCoVHFC4YPl/8NHbt4mwgCKIVLgiuXldQzgqFwtCGdtWilc9TPs+uDQRBEASbdtU2bWrD6wp2DATRBgrRdq20oU2v6uKPgvL1KF+P0gqFclZeFq8rWpwF8TYFsfxc4hBflYs73KEcys8n2tQ2bWrRNm3DxHvE67vXdmdDu6KwY8edFi1e16aVs6kFwcUheEBw53VdtdJ2LQjK2UV7r20+T3yeTZtaobBp5WXT2xAE9wh2BNGiRYtWXtXF8uXF16u8fUH5ZSqttNLK21AoBKWVr8bFxEShUIgPi1ZafJ6hlbNd25wFwUC8vgdt16K9cyhM7M6C8rL4sh600uKsMFAICoUdwfS6LlpQ2BAEwe5577T4PPF5hhaUQ2lDG9pF27V4fQMTwYOzcvaN9uBNuIjlSypPBUF8/crbFC2eFy0+T3xZ0aJFixZvQxBn5c27+JOpDQTlZfHT2LWh7c6G9ojCBQOlxevatKFdnQWFC+6wawOFeFn5su61XRva1DZseERwh+AO0+sLgg0D7zFx0S7a1HZtaLvX9ahdUNix43vtXpvag7ZrF6/rgolHbWgPKFy0TXvwplz8SVCWn9pEIVqhsGnxsviySiuttHKIp6LF6xpaeV5ppZVWWnnbhhattGjD6yqUs0KhvHkX/zXeYdeCiU2LFi3a0OLzbNpVu9eiXRHcozDxHQYe8a1D+flt2tQu2lUbGPgL/Ff4bzAxMXHvdV21oQ3tov0N/in+e9xjR7Brw+sqZ3faf45/jP9Be9BKu9Omz1M+T2HDFTv+Ff5b/B8Y+F6LVtrwNlzxDv8brggGLpiYWmm7NrwJF/873mHXgonNWbRoQ4vPs2lX7V6L9ojgGy34Dd4hflh8WZs2tYt21YKBbzBxh4FCMLwsvqyrs6FdtA2FHY/YUbhqm9dVCEobCHbsuGhTK+2iTZ+nfJ4NhYGJgd/hb3093uHPcYeh3aEwtdJ2bXgTLv4omFoQBMHQ4mxq5fN8r5X2B23Ton2P4IIdQeHf+7BCfFlxVtpVuyD4NR7wB+0OOwrleYX4sjbtUbtoV+0Rf8DfYNMKG4LpdU2ttCD4h3jE39M27aqVFp+nfJ4rJu4x8Gt851BaaVMrLV7fhnvcoxCUNrWL9qi98yZc/EkhzsrrC0orBBM7hpcF5edVWmmFaAOFiYlo0cqhEMSXFS1anBXKWaFQmF5Xeaq08vYNxPMKpZWz0uL1DUxMbCiH4Wxow5tw8UeFOCsE0eKstOnz3GnRokUbKOxaobChsHvZ9GWVs6mVtqEwMHDBxAXB1MpZafFl7Vq0qZW2YyIIBgq7Nr2ui7ZrmxZMxNmuvdPidQUXbFoQLYg2nMXbUFpQuGoDwebsnTflIgiiBRNBHOJ58XmiRYuzQjARDMShPBWH8mWVlxUGCkEQRIvnRYsva2pTK62cFQrlEG9DfL0mCkEQDF+Pwh3uMTAdBqKVN+niT0oLCkGwaXFWWvk8F+1RG9p0tmmFgSsGyiFaOZQvK1ppQyvtPQoPeMCGDTuCclbOype1aZsWZxsG7rAhKDxow+t61IZW2sCG0q7acBafJz7PnbYjmNg9NZ1FG17XxBXvEVy0IJjaRdu1aMOruvijaNHiEC2+jGhxFm2iUFohWjAcSou3o1AoZ8HEBXGI1xEtnjdRCDbLT2Ui2sBA+XjxugqFwkBp01fhIthwxY6BQlCIs2ilxeeJFmc7Cht23GPgATs27JieikN5Xmnx09i0R23TJgbeY8MjLigMxKcrHy9eVtrUytlAcK9dsSPYEJTnFSbKIQ7lqfg0E4WpBRNBMBGUdo+JRwxEK62cTc8rbaIQFApxVlpQ2sTQNgQPmHjEnXbVopUWLc7K84LSgtLi8xV2DAQ7hrOJOAytUJ6anjecTWeFwtTiRReFiempieGHBYWgvKycDW0gKGdB4YJHLXjEBfHUcIizaPG88uMF0aLt2jtMlFY+rDwvWhzirPw0SitcEFwxMPCIR9w7i0MwUZ4qBNHKj1O44BE7vsEFhYGJQqEQh+nzDS2IQ7RyKBQmgolNe8B3KIfC0HYfp7TSpkP8sHIWP+yKCyYGNlwxEC1aOYtWzoYWLVq00spZEMRHuSgEAwNBMBAMlKdKi5fFy6ZDIdqdsx2PGAgGLnh0Vs7K2fDxCvGyXYt2p23aO7zH0IIrBjZctTgrbTorZ8OHBfGyTQuCoZX2PR7xgB1Du8e3+K2XDZSnopVDeSpetiG4R+EeD3jEjk0LgkLhHhNTK62c7Vq0clba5lAoFK4YiLZhw0Thiis2PGh32oOn4mXT54mPU1pQ2LHhAXe481ScBROFQhAfFpQ2tCsKpW3a0KazcnKxY6BQ2DExtCCYzoY2nZWPE4dCOSvtgit2BMHEholyCOIsXrYhnheUszjbUZjOymFg4A53uGJgIg5xiFZeFof4OHEoRCuHaBcUBu5QeETwB5RWnhfEIVq04RBPlaeCQrDhEQOFRxTe4Q6lBdEKA0F8nnKIFgSFcgiCYEdhaN9gYmDHox+ntHhZPK8QP6y0DRMXTNw7TJTnFQoTcRjO4iza1DZnhSDa8KKLgUIcCgPTYWhBUNrwsnheaVcEU9u0oT0g2PANCncoPGI4K2flLM6ml01PlcM9gqFdEYeJbxBM/AGFgQfc+3RxGF42HcqHlXZxtmNHsOMBhe+wo7R43nAIyln5sKA8VVohuMeOB1ww8B6/R2mbFkw8YOJOK88rZ4VyCAqFQjwvCKZWGLjiDleHe21gOiuttGjR4uOUFmfxcaY2ccEDNm1HYXhqIg4DE3EoLVqhtOmsHEorHxYnF4XCFbs2EOwY2tSiTa0c4qmpledNLQ6FoQUT0a4IdgzEoRBncShPxVk5K2flbHc2UQ4XXHHVNgQThR3lqdKiledNZ+UsnldaPK+0DQOFC3ZccdWms0Jpwe6sfJp42Y53iHbBIx5wj2Ag2sDEFcMPixaHOFy0oU0tiEMQBNGmVhiYuMcVA9NThUJpE0EhPl5QDuUsPt57FAo7Ni0oh8JwFgyH0grRymEgKB8WLShncXJxh++wIxgobWJoU4tDsGnR4hAMh2hxKGf32tR+h0Jh4Ff4+/gO32JziKc2T8WhtGhxtiEOcRZtaI8INq0w8Vf4S1xReERhOsQhWnletItDtGhBOcRTFwQ7gguCoT3iG/wVdu0R9xjYnRUK0QpxKC3apkUL4jAc4qmJYGib9g0m/i0u2LV7XDHxLf6tVp4XZ0Mb2qZtKFy0i7ahsGl32obCBVd8iyv+PS74DQpXbSAoZ3/mbNMGSosWFEorvNdKKy3a7qycXfAb/JmPU86i7dqOwkThisKO4OpQeMDA1N4juKKwa9Hi5OJ/xN/HhqlFK4epRYsWhyAOcQgmJoIgmAimdkGwI/gDgg1XbCj8K+y4IAiCIIh2RRBMTARBsCMIgokgCKY2EQQT0d4jKEy8x8RAMBB8i/8V7xA8INgRBMFEEAQ7gmBiIpjaI4JgIpgIgl0LJoIgCArBFRMDQSH4f/Bb/J/YsWNi0x4QTEQLJoIgCIJCEMSX87/gf8Z/iQuuCL7BVftz/COtPC9aaaUNFH6NgXe4x3e4x7fY8Bvc4R7v8B3ucY8LfoU7h1/jn+D/xo7fIZh4xI4rdkxcMHHFFTt2TARTC4JCobSBQmFgYKBQGChtoFAobcfAP8fE/4sdA7/De7zH3+IBv8cDfof3+B7BAx7wBzziqg0M7YKBe1xwwcAdNtxh4g6FO2xatDi5+C/wDxGHoBBMBLtDEBR2LZiIFkwUgmAiCCaCXRuYGJh41K6IFlww8Q8wMRFMTAQTQRBMTAQTwcREMLEjCIIdU5uYCIKJiSC44oLgETsmgkdMBBP/DsEjJoIgCIKJiYkdQTCxYyIIgmAimJiYmAgmgokgmJjYtR07gomBiQ07/jPsuEMwEBSCiYmJYGoTwUS0YCIIJiaCIAiiTUxEC4LgexTuMPE9Cjv+A/4lgomgMLW/wr/QyvOmVigUBu5xwV/iDt/hHn+Bd/h7uMdf4g5/jnf4C9zjO9zhW9zjV9jwK2z4T7FjwxUDj7jgATsecYcrJq4YuGo7SgsKhYGBwkChMLSBiYEdQyutUBi4IniPR/wKD/gt7vF/YcPExO/xgN/iAf8OO36HB/wWV3yP4Io77Lhodyjc4xHvMHHBjm8wcfFUOYShUF4WhyBafLx4WbR4Klq8rLTyYeVQKBQKhdIKpRXKoVAO5YfFj1MolFYoFMpPI15WKAwUyscrZ0EQxJcRh2jxVPnyopXnBRMTExNBEM8rhzgEQbT48oIdOyaiBROFQjlEi+eVw8DAhg0DGwY2T5VPchEE0QpBEATxvEIQBBPRgolCMBEEwUQwEExEC64IgolC8HtMBFMLJoKJYCIIgmAimJiIFgRBEARBMDERTAQTE6VNBBNxuGCiELxHMLQdQTARBEEwEQRBEEwtCIKJIAiCIAiCIAiCixbseI/CAyYumPgGQRA8IAiCiYmJaEEwMREEwUQQTEwEQRAEQTARLQiCzdnQCsGu3WtXDFxxxdBKKy3OCoWBgYGBiYkgDkFQKK0cBjZccMEjJu5Q2BBM7JjYMTARbccVhWBgYGhDC0obKBQeUSgMbSAYmAgKpRVK27QgKJQWBMHExBVXPOIRG4JCobRCMDC0gaAcNmzawNQm4lBaaYUw/El5XqFQWqFQDuVQKIcgPk60IFocgjiUH6dQzsqhfFg5i7No5RAtzuLTBHGIL6sQxKEc4odFKxQKhUI5lFYoTxXKxwmiBUEQP71C+bBo5RBEKxQKhUKhnJVWWmnR4suK520YKAwUyqG0QnleOSuHIAiCIAjirFAO8WHxdy4KhUJQDkFQCMoh2nAoxFPB1ApBYWJHsGOiEGza1DYMLfhbBO+00korZ4WBqRUGgkKhHAqlFUoLCoVCtCsmHhFsmNixoxAEwcR0iEMcSgsKpRUKE/G8QlCIQxDtvcPusCEoFAaCq1YoxFkhCIJoQRBMxKEQH68Qh2Ai2BCHHYWpPTobnldaaUMrFAY23OEOd7jDwEChMBFcMfCoPaDwa1y0oRWueEThig07JoJCOQt27Ah2h4nSgkI5FApxViittKEVHrFjYsfE7rBhwwUTd5i4w8SOK3bsmJgOA0MbmBiYKAwMBAMDhYENm7PS4u8M/3/lrBzK88rzyk+j/HTi48RPI4gfVj4snooWX05pQfx4pZUWBMF0Vg7leeWHxaG8rvKyaKUF8XnKzyeI5xXi05RPF8+LVj7oYseOoQXTU1MLJuIQTMRhYmrBRBwmJoLggomJYGjR3mPiTvs1gu8RBEEQBEEQBEEQBFMLJoIgCII4RAuCYMNAtDttonDBRBBcEWza1EoLSisU4lAoBKUVCqUVBiZKK5RWKO1OmyhMRJt4RPCI4IqpFQaCQiEoBEEQBOUQBIWgEARBMDARBNGCQjC1HcHExBWFTbs6e4dv8XtnpUWbKE9N7NgxcMVAEBQKhcIFF9zhDu9wjwcUvkVhYKIwMBBsWjC1iXcoh6EF5TBQKAwUChsKhYGJgWBgYiAorbTCpm0ICsNZEEzs2HHFFRdtwwUbgg2lDUxtxxU7Bh4RRJsY2DGwoxBtaru/c/FDgiBaEEQrPyx+GuXDCvG8+GGFeKoQrRCUVj5OED8sniqtUAgKpcXPo7RC+fEKpRXix4sWxKcJ4ssrT5UPi69DaYXSogXB9OnirFAoFAqFQvlsF4WBwtRKCwpDC4LCRCFaOQSFgWBgakG0oQVBHKa2YWrBRDARFAoThdIGplbawK4VSiuttEKhEGeF6bAjKBQK0QZ2rTAxMBGHoDCw+2HR4qwQLVohzoI4TATRgomgtCAYWrSJIAiiBUEQBEEcgiAIgiAIgiAIogXRopW2YWAgWjCcXXHFcFba1AYKpQ0UChsmJkoLgiBatEIQTOwYDhOFaEMLJiYuKEwED7hiw8QFhUJQWrQgDsHAxEQhmCgEE/HUQDARZwMDA8Nhw4YLdi244oodV+xasDtccNEGBgauCDYMBANTK2ebvzP8UXlZnEWL1xWvqzxVlj+Jn0eh/DIUSotfpvLx4tOVj3ZRXk+8vmiFIIhDobRCUCjEy8qXUYgfJ/9fe3C2LFl2ZQV0zH08spFQYYXxwmfyVzzzxkeByWSlKmVcP3tiaIEdvO7NaLKJbORjeFS/bUGNoEaM+rBtxKhH9WEHDiwsBHEpio2NEyc2Nopie23jMBYWFhaCjWAhCDa2sRGPFoIguKNGsFCXIDiwUCxj4W4EcdnYuOOOO+54wQve4z0ObNxQHCiWcUdRFEWxsfE1vkLwDsENJ4Iay6gHy9PbirjEiEfxaerpSwji4+LpS4hHMeKHqUdBED/Izf8vqKf/pz5PEB8X1O9XUV9WUJegfrj6NO9R3BGcOLGxXYJgYeHAgSBe29hYKIqiKIoiHhVFsRGXuATBgYUiWFjYCIJ4LQg2NjY2NjaKIgiChQMHbrjhBS+4446NjW3E24Lgb9h4QbGMEws14k3L088nfjn1qF6rSz2K3774bSqC+G2KL6dGUZ/t5omiqBHfrx7FDxc/jxr1tvo08dtRP8zhw4ogxoHgwIF3eIcbbrjhhhtuOHDgwIGFhSA4cOBAEcRYKIqNjY2NYuOG5RIEQRGXIIjL9mgbG8FGjIViGQvvsHA3Dmwc2DhwIAiChSAIgoWFm7GNEwc2lrEQBDccuOGGAzcUN2wjxunB8o8uHsWlfpj6uPhhYgTxqJ4+R/zygrjUJZ4+V1EU9VE3QVHEZeGOhaJGUcSot9WlKDYWThzYqEtQl+LAHUFRBMXCxnYJinoUI0ZRIwiCugQ16tHGwkZQFEVQbAQbC9sI7rjh7lKPguBEUATFwgti1CUeBUUQFEERI0YRl3pUFMX2Wr0WFEFQI0Y9qrFw+rBgIyiCYhsLL1jY2DhQYxtBXWrUOIwTwcaJelRsnDhx4sCJO14QvCB4MRaKhRMLdTlQLNxQbJw4EZxYHi2XoEaNGMFCsFwWgmIZy1i4Y+PEHRtFsVEUcSmKYmNjY+PEiRPFHTWCjdOo14qiKLaxjBjxdzdFEJeiiBGjCGIUxUZRxAhqLARBUGzccbgUQVzu2DiwPNqoS1AEMTaKGkURLGxvixFsLBQxgvp+cTkQFEWNE3EJgrpsFEEQLJeNoh7F2C5FPdooio2i2CgWthEsY2NhexTUZRsbxUZRoyiKokaxEY9qxCgWDvwbbsYNd2yX4EBQBPVaUY9qFHHZKGoECwduCIIgCBYWgiCIEcRlYSGIURTFQhDExxVFEW8LgqAuNWpsBEEQY2Nj444TG0WNYCGI7xfEWKhHQVyKoqiPujlRYxsbRVCjKIpg48TyqEaNYqPYCIrgwGkUGxvFRnHDNooaQV2KoiiKYqGIsbFRbBQbRVEURVHcjWKjCDY2iqIoiqIoahQ1gqAoiqIe3RDU2KhRxFiosYwaxUIRLMSlKIKNjRhFsV2KoogRxAgWiqAItlFso1hGjWJjoyiKoiiKbWzc8Q1e8Df8EdsIagRFvS0IinoUBEEQrxVFUSzEJUZRBMFCsVAUxcbGiRPFRrERFEVRbCyjiEsQBMEdQVAERRAsxFgoFhaC77CNIFhYWAhiFBsnTpxYWAiCGqdxNxZObBQLCyeCeC1GsYyFIP7uJkYQxNhYuKPY2IixUSOoS1EUh3EiCBaCYKHGMuqyjaIoggNBUQTb2Ijxgo2FIF4LiqAIgiJYxoliI6iPW0ZRowiKhWIjCOpSFBvFdlk4UWyj2Ngogo1i48RGUSyXuhTba3UJNooaNYq6FEWxURTbKDZqnNhYvt/CiQMnNoL/gBeXoKixESxsI0aMGjWCIAiK7bV4FATBwoEbbrjhwEIQLBTFgSII4lEQLCO+XxAjRhAEQRAsBEFdtrGNoEYQj4KiKOoSBAc2DmwsFAeKA4cRlxoLcdnYOHEiXivi726Cu1HEOBEsBCeCZcTYxkJRY6EIDgRFsFFsbKOoS1DjQI07Noq7URQbRVEUB+JSxIhLUAQ1YsTYCGoUC8VGECxsl2BjGadRj4qi2CgWguAwim0EQTyKS7BQLASnsY2iRl3uqFEUdVnYWNhYqMvdoyAuB4qiRl1O1CiKoii+wolv8a8Igu/wDifiUbBQvPO2uhQxllEUJ+64Y+EFwQuCFxQvWDhx4sTC33BgIzhwIjixsHHHC4r3KF7wFU5svKC4o9hYRhAjKIKbESwEC0WwEJxYKJYRLBTv8YL3eI/3eMGJO+6444477rjjBS+4446NE9vYOBFsBAvBge9wosYNNyx8hdNYRo27v7v57/hnFEGMjQMnFk4UB4rihoV6rSiKoigO3PGVEaNGUdSosVGcxg3bpahR1DixsVHjHQ5sBEVRbBTb2CiCO77FiYXiRFEUG0VRFEXxNW6IsXEaxYGiqHFiYeFrbBR3Y2GjRrFRFBvBDTWC0zhRl6IoijtuuKHYLsWJYqNGURTfGkVRoy41iqJGcHOpS1FsvMfXRvAef8R/w1+MItgoir/g7lGNGkFdlnHDwl9xw59x4H9h4U+44T/hwJ9w4E848Acs/AHB18YNRVAUd7zDHQsnghP/ETVOLGwsFAeKGDW2sRAjRlDjQHAiqEtcvsOJf8Udf8WJP+M9/ow7/ow7/or3+Bec+Bec+A4nXhC8x0JxYOM9ihve4x2+ww3BO9xxwzaWUQ9u/iuCIiiCIsbCNhY2gm9xGEW8FqPYOBD8E77CO6/VJVjYCIKv8E84UKNGjLrUCL7Gn/A1isOoUaNGEQTFf0awEBwuRV2KAxsLB244EBTvfL+gKL7GP2Mh2FgoirrU5Rt8jT/iMBZOLB9WvMMf8I2xEQTFYdSoS/FffFyNhaJYeIc/oS4ximIZRYw7DvwP/AUbxUKN4i/YKOJRfdhC8D89WkYR1KhHMWrEiLH9MmLE2H6cAxvLOD1aKOpRjODEwkZxxzYW3uPEd1jGxsL2QTfLqNdqbNQoaiz8wSjitY3D2IjxLb7B9mHFgY0YCwvLpYhHxTKKYuHAgROnEY+C4ERQFDcsIziNuMQIio2NbWwsFMuo14qg2Ci2ccfNqBHEJfgrim+MGicO1GtBje9w4J1RBMHGNmrEoxej3rZwIljY2Dhc4rUgRhEUwc3YKOrRgROnzxcfFqNGfZr6PEH99GrUT2MbQb1W1GtBsF3ibXWJEZ/kZrvUqLcF2yhuLvG2haKIy2HE9ysW6hIUh0fxWlAjCF5w4DTi+xVBjLgURbxWjxaKA8Uygvq4hSJGcCAo4lKX4oZgGcHGYcTbYiy8Q11qBPWoHm0ftlEEpxEUQX1YvFYEC3XZxum1+jQ16m2nS32/elt9mvrh4lJfxol6rd62PTq9bXt0utRHLT+3elt9XDwV9fsWT19SUV9W/Srd/Fj1ZcQvI349ivhtic8Tn6eefqj6+dWv2vIl1agvq3776pcVr9WPFyOISxBPTz+r5XPFP56iflrx21U/rfjp1NPTJ1t+q4r6dPXTCeIfQxBPT79Ly5dQl/pti6f/o35d6unpsy1fSv1w8fsRT/9ePT39IpZPUaOIEZ8uiBGfr36cGDG2T1cEMepSn68o6tMFMeqHicvyeYq4BPXTKGIUMbbPE6/V09MHLU9PT0//oJan3796enp6w/L06xZPT08/k+XpH0dRT09P/9fy9NtVn66enp7+neXp962enp6+x/L06xdPT08/g+VLKOoSBPFpYgQbB+rzFUFQxAiCIAhiBDWCYntbEI9iBDGCoAiCeFuwEeNAfbqFYqEIivo08WFBvBbEJQiCIIgRbARFfL661Fienj7o5regLnEJYtSjuNSjeFTfLyjibfVaXYoaRY0YNeptG0VR1OcpahRFUWP5cba31aOiLjUOI4gRT09fxM2PVR8Wr9WojyvitSAu8f3iEiMIinhbEZcYMYIacYm3LSxjIS71YcHCQo34uGJhYWGhKILThwVFsV2K4PBhy6MaNTaCGkVw4vTDxNPTJ7n5uRVxqVGfr0YQxKhPEyMuhw8rghoxgmK5xNuK4MDNWCjqUm8rDhy4oUZQH3fDDcUyiuCdD1v4Ft/gdCkWTpe6xDg8ikcLQbFQ445vfJoiXounpw/63wTdhS07SlnhAAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAAUAAAAC0CAYAAADl5PURAAAhNElEQVR4AezBy4pl25oQ4O8fc66IyMxzrcJDFSVogSCICII9e76B+Cp2fBMbYtOWL2DDTkF17HhpCWohlqIcL3U5e+/MWGvO8WvVj2eeWSsi8rYzV8RZ4/uCf5n8LhrSufRlhbIqB2W1STQkZhzx17wsfxv/Cf9BOWHF0WUt9g5KU/4r/if+gb2DsrisGWmzKH9f+cf2vlO6cnBZicABiX+K/4x/4mX5OToSiSMCizIrR2X2HMz8Ln7X49KXFcqqTEoq4dwJP1TeeFr4smZlUWZlUe6V38I3+JmyoOPosk72DkpTvsERP0BiQuJGWV3WpKRyr/xY+aG9SenKweUFZuWHeIODclAWe7OyuIxUTsodFiRSCTRltjd7DmYSibBJhOcnEVixIPE/PC/NXkPiO3yDewQSHa9c1g887KSs6PgOifQ8hb1UbpWm3CnheXiHwIzEhGazKKuHpctI5UdY8EMkQkkllaZ0pSnpkmYS6WHp8lIJm0AgPX8NiUAgEEogPE+hrFjRPW9prytdaUpXJiVdVleakkh0hE3aSyVdRtp0pWNSQknP2exMIF1et0lMSAQSK9Lz0+2dkEjlgFBWNM9TKoHwsoRNszfZC5d1pzRlxmzTlW4vlO5yEr9A4h1W3CGURLMX9sIlzc4k0vMRHhZIL0cgEDbN8CUkEoH0/KVNoqMjPC6VdFlpL5VE2kxKV8JzMJNIBBLheepIpWNBd3k3ymKvK4FAIOwFustalVQmJZUDZhyUFYkDEqvLSgSaksqkdGVSTkpTwmU1D0uklyORCAQmJe2FEp6DmUAogVAC6fK6EvYS6fkLpMeFywp7oYSSzgUCgdXlpV8fgVAaupchkUgvyUzaSyV9HelpoSS6kggE0vPQPWxVFpywIJSO5rJmJZVQVqUjsSKRyr3nZbEJdCSavYPnbUVX0uMmpbusGR236Jjthb2wFy6pGYZh+CzppZq9GM1eR0dzWUcfZsKE2aYhPA9hr9nrXo5U0vPXleZhTeke1lxWItHRkUogkUpTTvaaS2qGYRiu1OzZ6MqkNKV7WFO6521CYsZsL1zeaq8pXWlomJBIJJqSLisRNoFAoKErk7Iq4XlqaGgIpIetSndZN8qMjrAJ5w5KeA6aYXjRwuPSMDxldnHd05qSSkPzcqzKEUccEUh0HFxWsxdKs5dIpNK9DM1eKM3zFAg0BFLp9rrnYcWKEzpSCSSaEh6WLqkZhmG4UjOBVAKB9PU0ZVJWpSmhrOiY0bAivTwzAoE0fF/CJrGgY1JWZVK60jwvK1YsCITnbVUO6AilKal0pSnpOWiGYRiuVDMMw3ClmmEYhivVDMMwfK/SS9EMwzBcqWYYhuFKNcMwDFeqGYZhuFLNMAzDlWqGYRiuVDMMw3ClmmEYhivVDMMwXKlmGIbhSjXDMAxXqhmGYbhSzTAMw5VqhmEYrlQzDMNwpZphGIYr1QzDMFypZhiG4Uo1wzAMV6oZhmG4Us0wDMOVaoZhGK5UMwzDcKWaYRiGK9UMwzBcqWYYhuFKNcMwDFeqGYZhuFLNMAzDlWqGYRiuVDMMw3ClmmEYhivVDMMwXKlmGIbhSjXDMAxXqhmGYbhSzTAMw5VqhmEYrlQzDMNwpZphGIYr1QzDMFypZhiG4Uo1wzAMV6oZhmG4Us0wDMOVaoZhGK5UMwzDcKWaYRiGK9UMwzBcqWYYhuFKNcMwDFeqGYZhuFLNMAzDlWqGYRiuVDMMw3ClmmEYhivVDMMwXKlmGIbhSjXDMAxXqhmGYbhSzTAMw5VqhmEYrlQzDMNwpZphGIYr1QzDMFypZhiG4Uo1wzAMV6oZhmG4Us0wDMOVaoZhGK5UMwzDcKWaYRiGK9UMwzBcqWYYhuFKNcMwDFdqNly5UNJeVzq6kobh10kzDMNwpZphGIYr1QzDMFypZhiG4Uo1wzAMV6oZhmG4Us0wDMOVaobhg4Vh+HXSDMMwXKlmGIbhSjXDMAxXqhmGYbhSzTAMw5VqhmEYrlQzDMNwpWbuMCERNuHrWJXJ0wIH5RbpebhRjsqtclK6csQRTblXbnye8HlWZVJSOSgL7nFA4uj5CZtA4DW6c2+VV56nhqYk0l5TuufhFRYEGsJeKKGkEp6D2fCVBMK58LR0WamklyORhq8lvVQz91ixIhBIm6Z0X0az15WuHJRv0fAKJ9wicXRZR3v39m4RaGg2ByVd1r1yq0z2bnCDxV5DIj0PqaSyIG0m5cbz1BE2B+WkhNKUULrLequcsGJB4qDMyqy8U26V7pJmw1eQXq7w/KVff6GEvXBZaRNKeClmv3RAeNxkL5XweVI5KbMSykn5kU3Dvefl1t5JuVcawiaQ6J6WntZ8njsPW2xCSc9PKOlhqYTSPE8rAh0dYa8r3fM0KT/wtK40pbmkmTRcQiKRhi8hlVRCSSUM36dEemlm3uIt7pRZWe01e4sy+zyhzErYOymBwBEnBNLlvVLeKk3pSkPgBrdYlFnpnha+rLD3TrlV3uItQpmQCHSEy+r2GjpSuVdulWZvdVmT0hFoaJiVo71J6Uq6rFscbd4pBwSaclTe2EuXNPulRBi+bx2BVAJh0zwvoYRNOpdKet5CCQ8LL0uz111WIOwFAuG5m/ltZbYXSirhyzgpTenKQXmtHBF4hVf47zjhZBO+vqNyo3yrvFEOaPhX+Dv4bXQ0dLz2PByVV8qfKn8Tv4cf4Qb/y164rElJm4Y/wt/FnbLaSyVc1jeY8Er5W/jnSDT8ROnKrLxVust6jZ/iLRZMaHiHjlReKf9H6crBJc38N9zijXJAx2JvVkK5V2afZ1Ga0pUb5YDEd8otfow/RqJ7WvqyjspJ+dZeQ8N36Dig4YDEraelr2NSXiknZVJWnJxLl7V62IrVZvI83SBsGr7FWy9Dxy1+4MO88pzM/CZu8FNlVjoSq3Jj76jMPk+zl0rYe4dE4A5/XekeF0hfVlNWpSuT0pQ/wG/gfysrVqTHBdLXEUra+7HyrectlBmJQCCVRTnYS5c1o9tMnnarHD0PJ6xYMGNBYkHgTjkqN8qiNJc0+6VA2gvPSyB9uET4OkIJJZxrCHR0pKelryc9LLw8oYTnryF9uFBCSZfV0NBtAmGv2WtKc0kzK7oSNoH0fuHzdOWt8trePQJ3yqr8MQI/dlnvlFmZ7S3omDHjhI70tFDSlzUpTTkpoRyUW0x4i4ZJWVxWV1JZlY4V3cNOSnNZgbBpaB53r6TSXFbHd1iRmJXZ3qLM9rpLmkkk0l4ikR6WSvo8qaSSStikTcfk+QglPCzQEEgk0vulr6N7WNoLBMImDZ+rI2wSzYdLl3fADcLj0l56DmYCgbDXkJg9bFaa78et0uzNSioLDviJc+lc+LJu7Z2USZmUFSu60pBIe+EyViWUVJryzl5X0mXNyqJ0ZUW3OdhbldllrUibRMeMQFdW5UY5KunyTggllVBSuVNWZVLSJc3+XCrpXHpY+n6lh3UEQgmPC89X2EskAmmTLisNX1NHYlYaAunlCIQSSnoJZhITGlabRPjy3iqvlJNyUBo6Tmjo3i9twpe1KpNyUP5U+ZFywCubVNLHCx8ufZo3yltlVhYlMWH1uEAibNImnEsfJxF4rXynvMM7TMqizOi4U9Je97RmL5xLm/C0hqaccMI7dHsH5d5eQyI9LRDoPk4oDYGGRFdWvMZ3eKN0NGVVEmnTbQJNCSWV5nGJUFIJH2Mm0NGd6768Zi/sJQITTj5MeFx6Wvh0iVDS3q0PFx6WSvryQgklsdpbEfbSJpX0sPT5AjM6ViUQaDaBQNpLe2kvfJxUUgkPSwRCOeGAsAmEEh6WPlz6dKGkvcCCGYlAQ0dDKl0Je6kkAqGEh6WHhZL2wlNmOhasaDYdiQWB2d6qzJ6WnnawN3tYYMZJOeIGq73wtLCX9tImkJ422YTNj5XEPb7DO8zK4nHp65uRWJXvlO+U7lw4F/YC4XFpE86lpy1Y7CW6vckmbJq95tMsSkcgEDihodlLZUJisjlgxgmhzEoos9KVrqS92V5XunJQutKVVFJZPC5xwi1WTGjK7OOkvbBJhL0VgVC6MindU2ZWNATCuUQ6l0q3Fz5M2oTHzViVRCoTOsImkfbS0xrSwxJhLz2t20s0NBxwwOL5CKQSNqnMSsOEFauSaEp4WCJt0l7YpHPhXCKQHneLg68nkEoiEQibVBIdgVlpSsOKkxJKKKGEh4W9tJf2QgklkD7chI4ZHZPPEx4Xzk32AolUmqfM3GFGQ7Np6Jg9bFLSXtpLTwt0zEoqi01iwoRZmXwdaS8Q9hKhNGVVfoE7hHKPwB2O6D5PeFp6WiqLvVRSmRA44AYLVnSfJ32/Ag0zAqvSlUAg0DF5WjoXNisC3blEQ9g0m6YETsoJgcCEVVmQSCWV1dO6Ekook3L0tAlp05SurLhDoikdgUA6l2g2iVQCgY5UAqGkvbAJJbB4WvNnZgKBFStulMTqcV1pNulcV8LD0rlAs+k2i720CefSJpxLe2EvPG1VJg+bsWBRJiQ6wudLX9akBGZlweJ5mnHCETdINJuGjhXN+6VzadOUpnQlkTaJRCKR6JiUho4bLJiwIpVUAoHmw6VziXAulFRSSSWVVCbcI5SO5mGhhL1A2GtIJWwCifCwRCKRCA9Lf2bmn+ENfojELQILuk1TQrlH4rWSStokmk0qabMqr5TFXkPHHSYs+Ble4xWONunc5FzahJJK2puQNmnvZO81Et8o79DxDX6Ov4fADxE42aRNKuFhqcw2qaSSCJt0bkaiIXFEIpXfwM/xl7AicMIJDc25QCqBdC6VSUklkTbNJp1rSMzKjfJT3OIPMOMb5YAFE17hP9oLe2kv7S3KCYF75aTcI3BUVmVBoGPBhAX/Fv/audXe6sOkkvZWD0tlUg7KnXKrTMpv4Me4U5q98OnCw8LjJh9j5h8aflUikUh0pSOR6EjlWyQSHXfoOCBxj8R3+Hf4F8oBicXLsGBFR8fJ8/af8Xv4fcz4BRK3WHDAT/A79sJeKqF0m8AtGhI3yo0y4YADJtxiwg0SM2YcsCqv8Tfwh1jxDokDTlixINDxDh2BBR0rEmnTkLhB4Eb5GQIzGt6g4RUCrxG4Q8MtArPyBg3/Bh3fYsUrfIsF91hwROKIBfc4IJUjOk4IpaGhY1ZuEJhxhwNmHLDioBwQSirhV838e/wV3CgTEisSHYmwSSwIHJVERyqJjkAi0ZFIdCROSqBjRcekJBKJxAEdf4SOWyQ6OhIdiUSioyPRkejoSHSsSCQSK7rS0ZFIdHQkDljwCyQCKw5InNBxQMc/QiLQMSORSCQ6OjpWJBIdKzoSiUSiI9HR0dGR6Eh0JBIdHU1ZseKIjhVdWfFDrHiHREPiHomOjo5EVzoSHakkOhKJjo5EIpFIpaMjlUQiEQgkOo4IBL7B7yPxc6TNAb+N/6I0D0sl7c2YcY8DTrjBPW5xjwkrDjghkJix4ICTcosJRyw2gcQrnHCHIwIn3GLBhAUHLEisaMoNAj9AwxsE/ioCv4mGGzT8Fhp+Bw1/GYGfIfAjNLx2rqPZHHGDBTNOaDjiFToCR9xgwQGpBBbMeIcDVtzgLW6RmJAIH6oRCE9Lm0Qq6cOlp6WSzqWSnhZKeFzYBAKBQCCUQCiBsAmETXi/9GkCgVACgUD4fqSnBQINgfDhwl4ikUhfRtqkks6F56Ojo3u/sEmbRCKV9HWk0u2lEvZSSQ8Lm1AmpWFC87DwMWa/tCgrupJIdHRlRWJFYEIilVRSCaSSSKSSeIPECYmGVBJHdNwjkej4CTp+gURHoiPRkUgkOjoSHR2pdKxIJDo6OhIdHYmOREfHO6xIJO7QMSHREfhTJH6CxJ8iEEglkfYSibSXzqWP962SWHFA4AYdCzr+CIkTErdINKS9RCCRNolEKolAIJBIJBKJRCCQSiKR+E4JdExKIvFH9m6Q+Cl+ij/0aRKJxAmJRVmVVTkpqSzKqjSbGxwRWBFIvFW+sfdOOSkdabMqRwS60hCYEZjRcEDDAQ0NzSYQaGhKR8OKhlQSgYMyKzfKa6VjxoTApIRNKKGkcofwfqmkEkjNL4WHBQKhBAJhEzaBsEmkD5NKIpW0SaRN+DSBsBc24XFhL+2lEjappL30cRJpk76sQCJtwia9XyqBQCAQCJtQAuFcIHyYRCqJ9GWFzxMIBAKBsBcuKz2uIdCU8HHCXnhcIpE+T/ozM29wZ5PoSqKhoysrEpOy2gTSwxJpE0ibZhNYlQmJGYkFiQmJWyQ6Eh2JjkQi0ZHoSHQkEh0dHYnEio5ER0eiI9HRcYOOGR0LEr9Axy0Sb5DoSPwMK/5E6WhIdDSbRFcCiVA6uk3aBDoCXUl0BBpeK4kVDakkAolE4g4diY5UAoGGbhMIdJtEINARSB8ukMobJI5oaEhMyrf2jsof4zW6T7Mila50exNW3OGIGyw4IHGLP8GKVZmwYrY3YcENTko6N2FxriNsGsImEGhoaGg2XVnR0G3C3orAghvc4xYLZhzRcI83mJTm/RoCs3KPW6yYsWDGEQcsStoLf6Z5UtiEh4WHhe9H+P6kD5O+H4n0fuFx6Vwq6csJJZE+XSihJBKJbi9swsPC+6X3S19H+Djp84WvKz0skL689LBUwmNmv9SVjo6mJDoWJdGRStgLJT0ukMq3SEzoaOhIZULDDTq+Q+ItEg2JjkRHoiORSHR0JBIdXelYkUh0dHQkEh0diY6OjrdI3KHjF8otVnR0JBIdiZ9jxa29dC6RSJv0tEB6v1RSuUdiRUdDIpSm3CORSHR0JDoSiUQi0dHRkUgl0ZFIJBKJjkQilURHYlYWJCZ0HBCY7S3KCd+h2Wv2FgTS3owZ73DAghvcoymJxAkd75RFeafM6AgEuk1XFpvEhNVmxoIbLFixelwgsCCwIm260pVE2nSPawg0BCZlUg4I5zoSCwIdgQNWzOhINGVGQyizMqOheUrzXolEIpFIJNKHSd+P8LjwuPR+4WFhE0oo4cMk0vulc4FAIJRAIHw9gUAgEAgfLxAINJ8nlUT6eOn5SS9D2ISSSirdx0t7gUAgEAjfl5kjOgIdgaakEpiQSAQ6AulcKoFEIJVEIhG4QSKVRNqs6OhINCRmJDoCaS+Q9gId6Vx6WCKRSKUjkbhDIpF4hURHoCsNHYmOO3R0JAINq/dLJe0FUkklkPYSqQQ6EokJXWnoNokFHQ2ppE0i0JFIJFLpaEgkGlJJJBKJVBKJVFIJLEjc2DQckQgkutJsbtHtdefSuQWhJMImlFBSaeg2B5yUQKLbNEw4IZDoSigzFizKW6RzicQ9Gk4IJZDo6EoqBwRu0DChYUJTOhpCSSWUriw4YEXgiBkdTZkRmOyF0nyY5kM0fy48Le2lki4rXVY4F4b/L30dgfDrIWzSr6fw4dKXNBMuJ11eKoFEIm0CoQQSgUB6WvgyAunTpL30sgVSCaQSSnpaU7pPtyj3yklZlbTX0G06ur1EYlVSWZTFwxpWzFjRlBmBN2j4AQIHNEwINDSbjsAJDfdouEfDHZrSlEmZlRvloEzKLRILDujOhb0FgRUHHJUVNzjigAUHhNKV5lc1wyMSYRNK2AsfJg1fQyC8XxheorQXCIRP0eyE4VeljxMI7xd+vSXS1xX2wpfXMeEOB7xGw61yUCZlsmloSnculUkJ5Ua5QWCx15VEoqOjo9skEiecsGLFihUd3WbChBvc4ICDp3VlUU7KCSveITAj0JRmk/YaGmblFSbcIHCLhglh09D8Rc3wBYXLSXvpXNqkvfDyhZcrvFzhpZgN/08ikUp4XNoLny58Gamkh6UPE16OdBmrsiodq7Iqq3JSunL04VLpyorEjMWmYbVJZUHghIYjAgc0TAhMaGhoNt3DVqyYsOAWK2abphyUg3KnNJuOxaZjxgmzTSiJsFkxYUVD2mt+VXP1wl7YpE+T3i98mlACYS8NHyMMv25SSaT3aQQS3V5DIpBIJZEIhMelTSLREehoCHthLzGhI5REINEQSJtQ0l4gEAglkQgEwl4glbTXEUiEkkiE0hHoCKQSWDEhbdJeINARSAQSDR2hpE3YCyQCgUQogUAglEAgkPYSiUR3Lp0LJRA2gXAulYb0tEAilFA6Eg0dTekIpBIIhE+3KiflqHQllFACgRs03OIGB0zKhAlpb1FWpaE5FwgEAg0NgcCMWUklbAJpE2hoaJgwYcKMCZMSSldSWZQViaO9CQ03uMEN7jDjFgdM9u7RcVJSaWhoaGho/qJGItAQCCURSiCUQCiJREcibcKmYcKEhkTHgkAglEAgEFjQlYaGUDrSJpRAIJBIdCQSiUDzuECgIdAQCCWQHhcIZUJDQ9isCAQCgbDXkUog0Gw6EmkvEEibRNrr6Eh0JBIdHc0m0NAQaM6FvY6Ojo6ORCKRSCQSqSRWBAKBsAmE0jBjQSqz0m0CE0IJJBLp+xNKKKGE0tAQCAQCgUDYNITHBQLhwyQS6XGBQNhLJBKJroQSSle6sippE2g+XChhLxA2iUQivc/MilS60pEIpJJIJAIdK5q9VFJJdCQ6AonAhFVJdHQkOhIzupJIJZA2iUQikUg0JELp6Eh0JDoSiUQikUgsSqIjEejoSCQSiUQikUglkUogkEgkEmlvRiCVjlQSoTSk0pRUEg2JQEPYJBKBjo5QEoluk0gkQgmEEmhIBBKBriS6kmhKKomOjkQikUgkEl3pWHCHE97hDboSSCWQSB+mKR2BVAITVgQSMxYccMSEBaGEkugIrFidOyGRSI+7t7cqK9K5CQ2hNAQa/m97cJQbR3qdAfTcv6pJyZMxMkBevEzvKs95y268AAexBcQWya7/C4SLoNDpFkVqNJrRqM4ZWrBhYKIwtScMPKHwFqUt2km701ZtsQuCBzzhhCf8gA2rS0HhPe6w4YSBwqqdUFhRdps2UZg+WCmtUChtYuCMYGKitIlohdgFQbBoGwqFgUJhINrQYje1IAgKCwpBUJjaRGlPmBgolGuFoBAUCkFhaBuCiUJ82tCCaEEhGAgmCoXYBcFEMO0GNgRTCyYmgsJEMLFhIgiGXeyC6VrsChNBtGhB7IIgmAiCqQUT0TZMDB83sGHBhonCv+DJrhBEmygMTM+bdrELppcpbWDDig0rzm4b2FBaIa4V4tMKhUKhUCgUCoXCwEAhWjARTEyUl4kWu0JhwcSCicJwrVyKNlyaWLBhuC3+z0rhrAWlbSgMFDYUhlba1AaCaANBYUEhKEwEE1MLYleItiDaGRPBWQuCiSAIggVlF5RWdoWgEK200iYK0YKBYKJQGJh2hYmhbVpcCoJgIhgoFBYtmFqhUC6VXWEgGChs2tSCaLE7I1oQxG5gYmBiIHZnlwpltyAIosVuQ7QgCILgDhve4h8oFB5wwoZyqTAQ3GlxqbQFsZsobcWCOwR32HCHM+5QWLHhHhP3CE74BxZMFAY23GPDCU/aEwqPuMMTzti0J6w4Y8HEtCvcoXBC4Ul7xMAjBh5QmCg8YWBiYKLwgOCEJyx4xB2ecMJ7vMUD7vCAgQf8KyYKhQ1nLHYLzlixYMWq/RN3mDjZLS5N7axNH6z8B35CUChtYsGGgQ3BgiBYMRDXgiAIgmDBGXdaadGCIFq0iWDTVky7IFoQbcPERLQTFkwUgiCYCKY2ERTOeIsNA8GGIAgmgiAIguAeK0qb2LRgQRBE2zAwcI+J4KwNTEQLJoJgorAiWmHTNsQuCILgjBUrgmkXbAgmogVB8FYLgmixixYE0QqrXeyCYOIR91rhET/g3/FOCwoTQfAOjz7fg/bg2/F37W/af6HwEwp/R+G/tXco/BWFv2o/ovBWu8OGO5zxFhtWPOIHnPEGZ6x41DacsOENNqwoTAw8YsF7PKKw4m844YwVTzjhESs27Uk7a/HByp9RCApBIShtYGoDE4W3WLSgXCstmFhQ+CPucHItdoWBiULhDn/EgmjRSotdtMI9fsQ9gkWLFi1aUCgE/4bCQGGxC2IXLJgYWLBiQSE4+bhCENzjJwwUJgaCIHaxe4N7/IBFG9gwPC844Q94o00UCsGiRYtd8CefFm0gCAZO+BGxKy0IhhaUdsaC/8Q7TAQD0YJ3KMTHDUQrTJcWFM5uK6206dIb7UGLVlphunavPbi0aAOFR7eV9hftL9q9Fu1Ri1baop1RKMQuLpU2UFgRLYhLZ7vYlTbsCpsWl0obLpUPVoYW16JNRAuiDfxBC8q1iUWbKO0t3mB6XrBgorSBgWEXlEvB0IJgYMGCDZtWLhUKGwpBsGJohU0ru9IKwcTE1CYGgqHFtaAQTARTO2PVohXKrvA/CN5o0TYsiGuFaA9YcNKCQmFiatHKpSctbhvYUBiYmFjsyrVCaUEhKKzaRBCXFmzYfFpcKi1eJl4mbptuK8THlY8rHxdtel7sgnheIVoQLYhWWlAobC6Va/F68cHKtIsWtxWmFqx25baBICi7RSsfFwzErhAsLpVrhWiFwhMWbFr5uKBQWtkFQbkWlwaCBcHQCvFpA0FphQWFoOxiF6woDK0wsWjlttIGTohdtEJcikvT8yaCwqYVgkI8r1wLCgOxm9rm5WIX1zYvE7e9d1s8773bNm3zvHcuRXvwvGiblyktCDZt83mm14m2uWX4xcVt8WnlEMTvWzl8TUF8XfFbtPrZ4usov47y2xGUb0t5nfI6cfhc8cuL37Lhq4oWX1d8++LXVa7Fz1daoewK5XD4JQ2vVr4/QXxZ5dsVX1b5cuJweKnhmxXEy8WXUyjfh0I5HH6Phq8idvFtK4cP4rclDofXGr6a+Hzl96Mc/r84HH4Nw4tEC0orL1corbxe/DyllTa9XFAoLXbxekEQL1coLT5P2Q2vE5RdIb6MoLSgtOl1yrU4HJ4zHA6Hw3dqOHwH4nA4XBsOv3HlcDj8MobDdySIw+HQhsM3LF4uDofDpeHwOxeHw+G24fANKIfD4csbvoogdoVCeZnSChML4vWCQiEorVAoFAqlFaIVgum2QrlUWqG0QiEoFMpthYnSFsTLDQQDQSGIlynPK5RrhbIrFAqFQmmFiUJQXi920YbD4Tmrb0Lsyq5QWlwqu7hULsXHFYJyW1yLXRAtiFZatLhtIgiCeJ0gWhAE0YafZ7otLgWxi7ZohdLK4fA1rH62eF65Fi0+LSjXCmVXPq7sSisUgnJbUHallVaIVnbltoGhDZRdPK8wMBCtfFowMDAwEASFzfMKQTDtgsLiecOlaNEmCtGCwobN5ymHw0usfnFB2UWL14tWKJQWL1Na2S2eFxSilVYIhl25LSgsWLWBIHZxW7BgwYpohfi0FSuCoQWFk+cNvMUbbHbBwGYXu9IWl8qlgUIwEO2MN14mKNfK4fCc/wWQn0TduldZ/gAAAABJRU5ErkJggg=="; + +TestRegister.addTests([ + { + name: "Split Colour Channels: Default (JPEG)", + input: testCard, + expectedOutput: testCardSplit, + recipeConfig: [ + { + "op": "From Base64", + "args": ["A-Za-z0-9+/=", true] + }, + { + "op": "Split Colour Channels", + "args": [] + }, + { + "op": "To Base64", + "args": ["A-Za-z0-9+/="] + } + ] + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/StrUtils.mjs b/plugins/srktoolbox/tests/operations/tests/StrUtils.mjs new file mode 100644 index 00000000..f13470e9 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/StrUtils.mjs @@ -0,0 +1,321 @@ +/** + * StrUtils tests. + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Diff, basic usage", + input: "testing23\n\ntesting123", + expectedOutput: "testing123", + recipeConfig: [ + { + "op": "Diff", + "args": ["\\n\\n", "字符", true, true, false, false] + } + ], + }, + { + name: "Diff added with subtraction, basic usage", + input: "testing23\n\ntesting123", + expectedOutput: "1", + recipeConfig: [ + { + "op": "Diff", + "args": ["\\n\\n", "字符", true, true, true, false] + } + ], + }, + { + name: "Diff removed with subtraction, basic usage", + input: "testing123\n\ntesting3", + expectedOutput: "12", + recipeConfig: [ + { + "op": "Diff", + "args": ["\\n\\n", "字符", true, true, true, false] + } + ], + }, + { + name: "Head 0", + input: [1, 2, 3, 4, 5, 6].join("\n"), + expectedOutput: [].join("\n"), + recipeConfig: [ + { + "op": "Head", + "args": ["换行", 0] + } + ], + }, + { + name: "Head 1", + input: [1, 2, 3, 4, 5, 6].join("\n"), + expectedOutput: [1].join("\n"), + recipeConfig: [ + { + "op": "Head", + "args": ["换行", 1] + } + ], + }, + { + name: "Head 2", + input: [1, 2, 3, 4, 5, 6].join("\n"), + expectedOutput: [1, 2].join("\n"), + recipeConfig: [ + { + "op": "Head", + "args": ["换行", 2] + } + ], + }, + { + name: "Head 6", + input: [1, 2, 3, 4, 5, 6].join("\n"), + expectedOutput: [1, 2, 3, 4, 5, 6].join("\n"), + recipeConfig: [ + { + "op": "Head", + "args": ["换行", 6] + } + ], + }, + { + name: "Head big", + input: [1, 2, 3, 4, 5, 6].join("\n"), + expectedOutput: [1, 2, 3, 4, 5, 6].join("\n"), + recipeConfig: [ + { + "op": "Head", + "args": ["换行", 100] + } + ], + }, + { + name: "Head all but 1", + input: [1, 2, 3, 4, 5, 6].join("\n"), + expectedOutput: [1, 2, 3, 4, 5].join("\n"), + recipeConfig: [ + { + "op": "Head", + "args": ["换行", -1] + } + ], + }, + { + name: "Head all but 2", + input: [1, 2, 3, 4, 5, 6].join("\n"), + expectedOutput: [1, 2, 3, 4].join("\n"), + recipeConfig: [ + { + "op": "Head", + "args": ["换行", -2] + } + ], + }, + { + name: "Head all but 6", + input: [1, 2, 3, 4, 5, 6].join("\n"), + expectedOutput: [].join("\n"), + recipeConfig: [ + { + "op": "Head", + "args": ["换行", -6] + } + ], + }, + { + name: "Head all but big", + input: [1, 2, 3, 4, 5, 6].join("\n"), + expectedOutput: [].join("\n"), + recipeConfig: [ + { + "op": "Head", + "args": ["换行", -100] + } + ], + }, + { + name: "Tail 0", + input: [1, 2, 3, 4, 5, 6].join("\n"), + expectedOutput: [].join("\n"), + recipeConfig: [ + { + "op": "Tail", + "args": ["换行", 0] + } + ], + }, + { + name: "Tail 1", + input: [1, 2, 3, 4, 5, 6].join("\n"), + expectedOutput: [6].join("\n"), + recipeConfig: [ + { + "op": "Tail", + "args": ["换行", 1] + } + ], + }, + { + name: "Tail 2", + input: [1, 2, 3, 4, 5, 6].join("\n"), + expectedOutput: [5, 6].join("\n"), + recipeConfig: [ + { + "op": "Tail", + "args": ["换行", 2] + } + ], + }, + { + name: "Tail 6", + input: [1, 2, 3, 4, 5, 6].join("\n"), + expectedOutput: [1, 2, 3, 4, 5, 6].join("\n"), + recipeConfig: [ + { + "op": "Tail", + "args": ["换行", 6] + } + ], + }, + { + name: "Tail big", + input: [1, 2, 3, 4, 5, 6].join("\n"), + expectedOutput: [1, 2, 3, 4, 5, 6].join("\n"), + recipeConfig: [ + { + "op": "Tail", + "args": ["换行", 100] + } + ], + }, + { + name: "Tail all but 1", + input: [1, 2, 3, 4, 5, 6].join("\n"), + expectedOutput: [2, 3, 4, 5, 6].join("\n"), + recipeConfig: [ + { + "op": "Tail", + "args": ["换行", -1] + } + ], + }, + { + name: "Tail all but 2", + input: [1, 2, 3, 4, 5, 6].join("\n"), + expectedOutput: [3, 4, 5, 6].join("\n"), + recipeConfig: [ + { + "op": "Tail", + "args": ["换行", -2] + } + ], + }, + { + name: "Tail all but 6", + input: [1, 2, 3, 4, 5, 6].join("\n"), + expectedOutput: [].join("\n"), + recipeConfig: [ + { + "op": "Tail", + "args": ["换行", -6] + } + ], + }, + { + name: "Tail all but big", + input: [1, 2, 3, 4, 5, 6].join("\n"), + expectedOutput: [].join("\n"), + recipeConfig: [ + { + "op": "Tail", + "args": ["换行", -100] + } + ], + }, + { + name: "Escape String: single quotes", + input: "Escape 'these' quotes.", + expectedOutput: "Escape \\'these\\' quotes.", + recipeConfig: [ + { + "op": "转义字符串", + "args": ["特殊字符", "单引号", false, true, false] + } + ], + }, + { + name: "Escape String: double quotes", + input: "Hello \"World\"!", + expectedOutput: "Hello \\\"World\\\"!", + recipeConfig: [ + { + "op": "转义字符串", + "args": ["特殊字符", "双引号", false, true, false] + } + ], + }, + { + name: "Escape String: special characters", + input: "Fizz & buzz\n\ttabbed newline\rcarriage returned line\nbackspace character: \"\" form feed character: \" \"", + expectedOutput: "Fizz & buzz\\n\\ttabbed newline\\rcarriage returned line\\nbackspace character: \\\"\\b\\\" form feed character: \\\"\\f\\\"", + recipeConfig: [ + { + "op": "转义字符串", + "args": ["特殊字符", "双引号", false, true, false] + } + ], + }, + { + name: "Unescape String: quotes", + input: "Hello \\\"World\\\"! Escape \\'these\\' quotes.", + expectedOutput: "Hello \"World\"! Escape 'these' quotes.", + recipeConfig: [ + { + "op": "字符串转义恢复", + "args": [] + } + ], + }, + { + name: "Unescape String: special characters", + input: "Fizz \x26 buzz\\n\\ttabbed newline\\rcarriage returned line\\nbackspace character: \\\"\\b\\\" form feed character: \\\"\\f\\\"", + expectedOutput: "Fizz & buzz\n\ttabbed newline\rcarriage returned line\nbackspace character: \"\" form feed character: \" \"", + recipeConfig: [ + { + "op": "字符串转义恢复", + "args": [] + } + ], + }, + { + name: "Escape String: complex", + input: "null\0backspace\btab\tnewline\nverticaltab\vformfeed\fcarriagereturn\rdoublequote\"singlequote'hex\xa9unicode\u2665codepoint\u{1D306}", + expectedOutput: "null\\0backspace\\btab\\tnewline\\nverticaltab\\x0bformfeed\\fcarriagereturn\\rdoublequote\"singlequote\\'hex\\xa9unicode\\u2665codepoint\\u{1d306}", + recipeConfig: [ + { + "op": "转义字符串", + "args": ["特殊字符", "单引号", false, true, false] + } + ], + }, + { + name: "Unescape String: complex", + input: "null\\0backspace\\btab\\tnewline\\nverticaltab\\vformfeed\\fcarriagereturn\\rdoublequote\\\"singlequote\\'hex\\xa9unicode\\u2665codepoint\\u{1D306}", + expectedOutput: "null\0backspace\btab\tnewline\nverticaltab\vformfeed\fcarriagereturn\rdoublequote\"singlequote'hex\xa9unicode\u2665codepoint\u{1D306}", + recipeConfig: [ + { + "op": "字符串转义恢复", + "args": [] + } + ], + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/StripIPv4Header.mjs b/plugins/srktoolbox/tests/operations/tests/StripIPv4Header.mjs new file mode 100644 index 00000000..8ba3fc57 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/StripIPv4Header.mjs @@ -0,0 +1,128 @@ +/** + * Strip IPv4 header tests. + * + * @author c65722 [] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + * + * Modified by Raka-loah@github + */ + +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Strip IPv4 header: No options, No payload", + input: "450000140005400080060000c0a80001c0a80002", + expectedOutput: "", + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "移除IPv4标头", + args: [], + }, + { + op: "字符转十六进制", + args: ["无", 0] + } + ] + }, + { + name: "Strip IPv4 header: No options, Payload", + input: "450000140005400080060000c0a80001c0a80002ffffffffffffffff", + expectedOutput: "ffffffffffffffff", + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "移除IPv4标头", + args: [], + }, + { + op: "字符转十六进制", + args: ["无", 0] + } + ] + }, + { + name: "Strip IPv4 header: Options, No payload", + input: "460000140005400080060000c0a80001c0a8000207000000", + expectedOutput: "", + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "移除IPv4标头", + args: [], + }, + { + op: "字符转十六进制", + args: ["无", 0] + } + ] + }, + { + name: "Strip IPv4 header: Options, Payload", + input: "460000140005400080060000c0a80001c0a8000207000000ffffffffffffffff", + expectedOutput: "ffffffffffffffff", + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "移除IPv4标头", + args: [], + }, + { + op: "字符转十六进制", + args: ["无", 0] + } + ] + }, + { + name: "Strip IPv4 header: Input length lesss than minimum header length", + input: "450000140005400080060000c0a80001c0a800", + expectedOutput: "输入长度小于IPv4标头最小长度", + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "移除IPv4标头", + args: [], + }, + { + op: "字符转十六进制", + args: ["无", 0] + } + ] + }, + { + name: "Strip IPv4 header: Input length less than IHL", + input: "460000140005400080060000c0a80001c0a80000", + expectedOutput: "输入长度小于IHL", + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "移除IPv4标头", + args: [], + }, + { + op: "字符转十六进制", + args: ["无", 0] + } + ] + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/StripTCPHeader.mjs b/plugins/srktoolbox/tests/operations/tests/StripTCPHeader.mjs new file mode 100644 index 00000000..7bd393f6 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/StripTCPHeader.mjs @@ -0,0 +1,128 @@ +/** + * Strip TCP header tests. + * + * @author c65722 [] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + * + * Modified by Raka-loah@github + */ + +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Strip TCP header: No options, No payload", + input: "7f900050000fa4b2000cb2a45010bff100000000", + expectedOutput: "", + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "移除TCP标头", + args: [], + }, + { + op: "字符转十六进制", + args: ["无", 0] + } + ] + }, + { + name: "Strip TCP header: No options, Payload", + input: "7f900050000fa4b2000cb2a45010bff100000000ffffffffffffffff", + expectedOutput: "ffffffffffffffff", + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "移除TCP标头", + args: [], + }, + { + op: "字符转十六进制", + args: ["无", 0] + } + ] + }, + { + name: "Strip TCP header: Options, No payload", + input: "7f900050000fa4b2000cb2a47010bff100000000020405b404020000", + expectedOutput: "", + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "移除TCP标头", + args: [], + }, + { + op: "字符转十六进制", + args: ["无", 0] + } + ] + }, + { + name: "Strip TCP header: Options, Payload", + input: "7f900050000fa4b2000cb2a47010bff100000000020405b404020000ffffffffffffffff", + expectedOutput: "ffffffffffffffff", + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "移除TCP标头", + args: [], + }, + { + op: "字符转十六进制", + args: ["无", 0] + } + ] + }, + { + name: "Strip TCP header: Input length less than minimum header length", + input: "7f900050000fa4b2000cb2a45010bff1000000", + expectedOutput: "TCP标头长度至少20字节", + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "移除TCP标头", + args: [], + }, + { + op: "字符转十六进制", + args: ["无", 0] + } + ] + }, + { + name: "Strip TCP header: Input length less than data offset", + input: "7f900050000fa4b2000cb2a47010bff100000000", + expectedOutput: "输入长度小于数据偏移量", + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "移除TCP标头", + args: [], + }, + { + op: "字符转十六进制", + args: ["无", 0] + } + ] + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/StripUDPHeader.mjs b/plugins/srktoolbox/tests/operations/tests/StripUDPHeader.mjs new file mode 100644 index 00000000..49ca15eb --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/StripUDPHeader.mjs @@ -0,0 +1,71 @@ +/** + * Strip UDP header tests. + * + * @author c65722 [] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + * + * Modified by Raka-loah@github + */ + +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Strip UDP header: No payload", + input: "8111003500000000", + expectedOutput: "", + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "移除UDP标头", + args: [], + }, + { + op: "字符转十六进制", + args: ["无", 0] + } + ] + }, + { + name: "Strip UDP header: Payload", + input: "8111003500080000ffffffffffffffff", + expectedOutput: "ffffffffffffffff", + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "移除UDP标头", + args: [], + }, + { + op: "字符转十六进制", + args: ["无", 0] + } + ] + }, + { + name: "Strip UDP header: Input length less than header length", + input: "81110035000000", + expectedOutput: "UDP标头长度至少8字节", + recipeConfig: [ + { + op: "十六进制转字符", + args: ["无"] + }, + { + op: "移除UDP标头", + args: [], + }, + { + op: "字符转十六进制", + args: ["无", 0] + } + ] + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/Subsection.mjs b/plugins/srktoolbox/tests/operations/tests/Subsection.mjs new file mode 100644 index 00000000..86a6a313 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/Subsection.mjs @@ -0,0 +1,104 @@ +/** + * Subsection Tests. + * + * @author n1073645 [n1073645@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Subsection: nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + "op": "Subsection", + "args": ["", true, true, false], + }, + ], + }, + { + name: "Subsection, Full Merge: nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + "op": "Subsection", + "args": ["", true, true, false], + }, + { + "op": "Merge", + "args": [true], + }, + ], + }, + { + name: "Subsection, Partial Merge: nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + "op": "Subsection", + "args": ["", true, true, false], + }, + { + "op": "Merge", + "args": [false], + }, + ], + }, + { + name: "Subsection, Full Merge: Base64 with Hex", + input: "SGVsbG38675629ybGQ=", + expectedOutput: "Hello World", + recipeConfig: [ + { + "op": "Subsection", + "args": ["386756", true, true, false], + }, + { + "op": "十六进制转字符", + "args": ["自动"], + }, + { + "op": "Merge", + "args": [true], + }, + { + "op": "Base64解码", + "args": ["A-Za-z0-9+/=", true, false], + }, + ], + }, + { + name: "Subsection, Partial Merge: Base64 with Hex surrounded by binary data.", + input: "000000000SGVsbG38675629ybGQ=0000000000", + expectedOutput: "000000000Hello World0000000000", + recipeConfig: [ + { + "op": "Subsection", + "args": ["SGVsbG38675629ybGQ=", true, true, false], + }, + { + "op": "Subsection", + "args": ["386756", true, true, false], + }, + { + "op": "十六进制转字符", + "args": ["自动"], + }, + { + "op": "Merge", + "args": [false], + }, + { + "op": "Base64解码", + "args": ["A-Za-z0-9+/=", true, false], + }, + ], + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/SwapCase.mjs b/plugins/srktoolbox/tests/operations/tests/SwapCase.mjs new file mode 100644 index 00000000..54551f06 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/SwapCase.mjs @@ -0,0 +1,35 @@ +/** + * @author mikecat + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + "name": "Swap Case: basic example", + "input": "Hello, World!", + "expectedOutput": "hELLO, wORLD!", + "recipeConfig": [ + { + "op": "大小写互换", + "args": [ + ], + }, + ], + }, + { + "name": "Swap Case: empty input", + "input": "", + "expectedOutput": "", + "recipeConfig": [ + { + "op": "大小写互换", + "args": [ + ], + }, + ], + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/SymmetricDifference.mjs b/plugins/srktoolbox/tests/operations/tests/SymmetricDifference.mjs new file mode 100644 index 00000000..7404dac3 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/SymmetricDifference.mjs @@ -0,0 +1,58 @@ +/** + * Symmetric difference tests. + * + * @author d98762625 + * + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Symmetric Difference", + input: "1 2 3 4 5\n\n3 4 5 6 7", + expectedOutput: "1 2 6 7", + recipeConfig: [ + { + op: "对称差", + args: ["\n\n", " "], + }, + ], + }, + { + name: "Symmetric Difference: wrong sample count", + input: "1 2\n\n3 4 5\n\n3 4 5 6 7", + expectedOutput: "集合数量错误,你可能需要调整集合分隔符或者添加一些数据。", + recipeConfig: [ + { + op: "对称差", + args: ["\n\n", " "], + }, + ], + }, + { + name: "Symmetric Difference: item delimiter", + input: "a_b_c_d_e\n\nc_d_e_f_g", + expectedOutput: "a_b_f_g", + recipeConfig: [ + { + op: "对称差", + args: ["\n\n", "_"], + }, + ], + }, + { + name: "Symmetric Difference: sample delimiter", + input: "a_b_c_d_eAAAAAc_d_e_f_g", + expectedOutput: "a_b_f_g", + recipeConfig: [ + { + op: "对称差", + args: ["AAAAA", "_"], + }, + ], + }, +]); diff --git a/plugins/srktoolbox/tests/operations/tests/TakeNthBytes.mjs b/plugins/srktoolbox/tests/operations/tests/TakeNthBytes.mjs new file mode 100644 index 00000000..70263ee4 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/TakeNthBytes.mjs @@ -0,0 +1,125 @@ +/** + * @author Oshawk [oshawk@protonmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + * + * Modified by Raka-loah@github + */ + +import TestRegister from "../../lib/TestRegister.mjs"; + +/** + * Take nth bytes tests + */ +TestRegister.addTests([ + { + name: "Take nth bytes: Nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "提取每N个字节", + args: [4, 0, false], + }, + ], + }, + { + name: "Take nth bytes: Nothing (apply to each line)", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "提取每N个字节", + args: [4, 0, true], + }, + ], + }, + { + name: "Take nth bytes: Basic single line", + input: "0123456789", + expectedOutput: "048", + recipeConfig: [ + { + op: "提取每N个字节", + args: [4, 0, false], + }, + ], + }, + { + name: "Take nth bytes: Basic single line (apply to each line)", + input: "0123456789", + expectedOutput: "048", + recipeConfig: [ + { + op: "提取每N个字节", + args: [4, 0, true], + }, + ], + }, + { + name: "Take nth bytes: Complex single line", + input: "0123456789", + expectedOutput: "59", + recipeConfig: [ + { + op: "提取每N个字节", + args: [4, 5, false], + }, + ], + }, + { + name: "Take nth bytes: Complex single line (apply to each line)", + input: "0123456789", + expectedOutput: "59", + recipeConfig: [ + { + op: "提取每N个字节", + args: [4, 5, true], + }, + ], + }, + { + name: "Take nth bytes: Basic multi line", + input: "01234\n56789", + expectedOutput: "047", + recipeConfig: [ + { + op: "提取每N个字节", + args: [4, 0, false], + }, + ], + }, + { + name: "Take nth bytes: Basic multi line (apply to each line)", + input: "01234\n56789", + expectedOutput: "04\n59", + recipeConfig: [ + { + op: "提取每N个字节", + args: [4, 0, true], + }, + ], + }, + { + name: "Take nth bytes: Complex multi line", + input: "01234\n56789", + expectedOutput: "\n8", + recipeConfig: [ + { + op: "提取每N个字节", + args: [4, 5, false], + }, + ], + }, + { + name: "Take nth bytes: Complex multi line (apply to each line)", + input: "012345\n6789ab", + expectedOutput: "5\nb", + recipeConfig: [ + { + op: "提取每N个字节", + args: [4, 5, true], + }, + ], + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/Template.mjs b/plugins/srktoolbox/tests/operations/tests/Template.mjs new file mode 100644 index 00000000..0c2a205a --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/Template.mjs @@ -0,0 +1,55 @@ +/** + * @author kendallgoto [k@kgo.to] + * @copyright Crown Copyright 2025 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ + +import TestRegister from "../../lib/TestRegister.mjs"; +TestRegister.addTests([ + { + "name": "Template: Simple Print", + "input": "{}", + "expectedOutput": "Hello, world!", + "recipeConfig": [ + { + "op": "模板渲染", + "args": ["Hello, world!"] + } + ] + }, + { + "name": "Template: Print Basic Variables", + "input": "{\"one\": 1, \"two\": 2}", + "expectedOutput": "1 2", + "recipeConfig": [ + { + "op": "模板渲染", + "args": ["{{ one }} {{ two }}"] + } + ] + }, + { + "name": "Template: Partials", + "input": "{\"users\":[{\"name\":\"Someone\",\"age\":25},{\"name\":\"Someone Else\",\"age\":32}]}", + "expectedOutput": "Name: Someone\nAge: 25\n\nName: Someone Else\nAge: 32\n\n", + "recipeConfig": [ + { + "op": "模板渲染", + "args": ["{{#*inline \"user\"}}\nName: {{ name }}\nAge: {{ age }}\n{{/inline}}\n{{#each users}}\n{{> user}}\n\n{{/each}}"] + } + ] + }, + { + "name": "Template: Disallow XSS", + "input": "{\"test\": \"\"}", + "expectedOutput": "<script></script>", + "recipeConfig": [ + { + "op": "模板渲染", + "args": ["{{ test }}"] + } + ] + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/TextEncodingBruteForce.mjs b/plugins/srktoolbox/tests/operations/tests/TextEncodingBruteForce.mjs new file mode 100644 index 00000000..33865684 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/TextEncodingBruteForce.mjs @@ -0,0 +1,37 @@ +/** + * Text Encoding Brute Force tests. + * + * @author Cynser + * + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Text Encoding Brute Force - Encode", + input: "Булкі праз ляніва сабаку.", + expectedMatch: /Windows-1251 Cyrillic \(1251\).{1,10}Булкі праз ляніва сабаку\./, + recipeConfig: [ + { + op: "文本编码暴力破解", + args: ["编码"], + }, + ], + }, + { + name: "Text Encoding Brute Force - Decode", + input: "Áóëê³ ïðàç ëÿí³âà ñàáàêó.", + expectedMatch: /Windows-1251 Cyrillic \(1251\).{1,10}Булкі праз ляніва сабаку\./, + recipeConfig: [ + { + op: "文本编码暴力破解", + args: ["解码"], + }, + ], + } +]); + diff --git a/plugins/srktoolbox/tests/operations/tests/ToFromInsensitiveRegex.mjs b/plugins/srktoolbox/tests/operations/tests/ToFromInsensitiveRegex.mjs new file mode 100644 index 00000000..c2767439 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/ToFromInsensitiveRegex.mjs @@ -0,0 +1,190 @@ +/** + * To/From Case Insensitive Regex tests. + * + * @author masq [github.cyberchef@masq.cc] + * + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "To Case Insensitive Regex: nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "转换为大小写不敏感正则", + args: [], + }, + ], + }, + { + name: "From Case Insensitive Regex: nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "从大小写不敏感正则恢复", + args: [], + }, + ], + }, + { + name: "To Case Insensitive Regex: simple test", + input: "S0meth!ng", + expectedOutput: "[sS]0[mM][eE][tT][hH]![nN][gG]", + recipeConfig: [ + { + op: "转换为大小写不敏感正则", + args: [], + }, + ], + }, + { + name: "From Case Insensitive Regex: simple test", + input: "[sS]0[mM][eE][tT][hH]![nN][Gg] [wr][On][g]?", + expectedOutput: "s0meth!nG [wr][On][g]?", + recipeConfig: [ + { + op: "从大小写不敏感正则恢复", + args: [], + }, + ], + }, + { + name: "To Case Insensitive Regex: [A-Z] -> [A-Za-z]", + input: "[A-Z]", + expectedOutput: "[A-Za-z]", + recipeConfig: [ + { + op: "转换为大小写不敏感正则", + args: [], + }, + ], + }, + { + name: "To Case Insensitive Regex: [a-z] -> [A-Za-z]", + input: "[a-z]", + expectedOutput: "[A-Za-z]", + recipeConfig: [ + { + op: "转换为大小写不敏感正则", + args: [], + }, + ], + }, + { + name: "To Case Insensitive Regex: [H-d] -> [A-DH-dh-z]", + input: "[H-d]", + expectedOutput: "[A-DH-dh-z]", + recipeConfig: [ + { + op: "转换为大小写不敏感正则", + args: [], + }, + ], + }, + { + name: "To Case Insensitive Regex: [!-D] -> [!-Da-d]", + input: "[!-D]", + expectedOutput: "[!-Da-d]", + recipeConfig: [ + { + op: "转换为大小写不敏感正则", + args: [], + }, + ], + }, + { + name: "To Case Insensitive Regex: [%-^] -> [%-^a-z]", + input: "[%-^]", + expectedOutput: "[%-^a-z]", + recipeConfig: [ + { + op: "转换为大小写不敏感正则", + args: [], + }, + ], + }, + { + name: "To Case Insensitive Regex: [K-`] -> [K-`k-z]", + input: "[K-`]", + expectedOutput: "[K-`k-z]", + recipeConfig: [ + { + op: "转换为大小写不敏感正则", + args: [], + }, + ], + }, + { + name: "To Case Insensitive Regex: [[-}] -> [[-}A-Z]", + input: "[[-}]", + expectedOutput: "[[-}A-Z]", + recipeConfig: [ + { + op: "转换为大小写不敏感正则", + args: [], + }, + ], + }, + { + name: "To Case Insensitive Regex: [b-}] -> [b-}B-Z]", + input: "[b-}]", + expectedOutput: "[b-}B-Z]", + recipeConfig: [ + { + op: "转换为大小写不敏感正则", + args: [], + }, + ], + }, + { + name: "To Case Insensitive Regex: [<-j] -> [<-z]", + input: "[<-j]", + expectedOutput: "[<-z]", + recipeConfig: [ + { + op: "转换为大小写不敏感正则", + args: [], + }, + ], + }, + { + name: "To Case Insensitive Regex: [^-j] -> [A-J^-j]", + input: "[^-j]", + expectedOutput: "[A-J^-j]", + recipeConfig: [ + { + op: "转换为大小写不敏感正则", + args: [], + }, + ], + }, + { + name: "To Case Insensitive Regex: not simple test", + input: "Mozilla[A-Z0-9]+[A-Z]Mozilla[0-9whatA-Z][H-d][!-H][a-~](.)+", + expectedOutput: "[mM][oO][zZ][iI][lL][lL][aA][A-Za-z0-9]+[A-Za-z][mM][oO][zZ][iI][lL][lL][aA][0-9[wW][hH][aA][tT]A-Za-z][A-DH-dh-z][!-Ha-h][a-~A-Z](.)+", + recipeConfig: [ + { + op: "转换为大小写不敏感正则", + args: [], + }, + ], + }, + { + name: "To Case Insensitive Regex: erroneous test", + input: "Mozilla[A-Z", + expectedOutput: "无效的正则表达式(请注意此版本的Node不支持正则的后行断言)。", + recipeConfig: [ + { + op: "转换为大小写不敏感正则", + args: [], + }, + ], + } +]); diff --git a/plugins/srktoolbox/tests/operations/tests/TranslateDateTimeFormat.mjs b/plugins/srktoolbox/tests/operations/tests/TranslateDateTimeFormat.mjs new file mode 100644 index 00000000..da3a0b43 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/TranslateDateTimeFormat.mjs @@ -0,0 +1,59 @@ +/** + * Translate DateTime Format tests. + * + * @author Cynser + * + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * Modified by Raka-loah@github for zh-CN i18n + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Translate DateTime Format", + input: "01/04/1999 22:33:01", + expectedOutput: "Thursday 1st April 1999 22:33:01 +00:00 UTC", + recipeConfig: [ + { + op: "转换DateTime格式", + args: ["标准日期时间格式", "DD/MM/YYYY HH:mm:ss", "UTC", "dddd Do MMMM YYYY HH:mm:ss Z z", "UTC"], + }, + ], + }, + { + name: "Translate DateTime Format: invalid input", + input: "1234567890", + expectedMatch: /无效格式。/, + recipeConfig: [ + { + op: "转换DateTime格式", + args: ["标准日期时间格式", "DD/MM/YYYY HH:mm:ss", "UTC", "dddd Do MMMM YYYY HH:mm:ss Z z", "UTC"], + }, + ], + }, + { + name: "Translate DateTime Format: timezone conversion", + input: "01/04/1999 22:33:01", + expectedOutput: "Thursday 1st April 1999 17:33:01 -05:00 EST", + recipeConfig: [ + { + op: "转换DateTime格式", + args: ["标准日期时间格式", "DD/MM/YYYY HH:mm:ss", "UTC", "dddd Do MMMM YYYY HH:mm:ss Z z", "US/Eastern"], + }, + ], + }, + { + name: "Translate DateTime Format: automatic input format", + input: "1999-04-01 22:33:01", + expectedOutput: "Thursday 1st April 1999 22:33:01 +00:00 UTC", + recipeConfig: [ + { + op: "转换DateTime格式", + args: ["自动", "", "UTC", "dddd Do MMMM YYYY HH:mm:ss Z z", "UTC"], + }, + ], + } +]); + diff --git a/plugins/srktoolbox/tests/operations/tests/Typex.mjs b/plugins/srktoolbox/tests/operations/tests/Typex.mjs new file mode 100644 index 00000000..f7f3d757 --- /dev/null +++ b/plugins/srktoolbox/tests/operations/tests/Typex.mjs @@ -0,0 +1,105 @@ +/** + * Typex machine tests. + * @author s2224834 + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + // Unlike Enigma we're not verifying against a real machine here, so this is just a test + // to catch inadvertent breakage. + name: "Typex: basic", + input: "hello world, this is a test message.", + expectedOutput: "VIXQQ VHLPN UCVLA QDZNZ EAYAT HWC", + recipeConfig: [ + { + "op": "Typex", + "args": [ + "MCYLPQUVRXGSAOWNBJEZDTFKHI