diff --git a/.eslintrc.json b/.eslintrc.json index b892ae60..629f7b10 100755 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -35,7 +35,6 @@ }], // disable rules from base configurations - "no-console": "off", "no-control-regex": "off", // stylistic conventions @@ -90,6 +89,7 @@ "$": false, "jQuery": false, "moment": false, + "log": false, "COMPILE_TIME": false, "COMPILE_MSG": false, diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 9e029351..8e7dee82 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -5,21 +5,10 @@ ### Summary - - + ### Example -### Possible solutions - - - -### Environment - - -* CyberChef compile time: -* User-Agent: -* [Link to reproduce]() diff --git a/.travis.yml b/.travis.yml index 521006ec..e02684ec 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,4 +39,10 @@ deploy: on: tags: true branch: master - +notifications: + webhooks: + urls: + - https://webhooks.gitter.im/e/83c143a6822e218d5b34 + on_success: change + on_failure: always + on_start: never diff --git a/README.md b/README.md index bd11ebac..d174fd21 100755 --- a/README.md +++ b/README.md @@ -2,9 +2,11 @@ [![Build Status](https://travis-ci.org/gchq/CyberChef.svg?branch=master)](https://travis-ci.org/gchq/CyberChef) [![dependencies Status](https://david-dm.org/gchq/CyberChef/status.svg)](https://david-dm.org/gchq/CyberChef) -[![npm](http://img.shields.io/npm/v/cyberchef.svg)](https://www.npmjs.com/package/cyberchef) +[![npm](https://img.shields.io/npm/v/cyberchef.svg)](https://www.npmjs.com/package/cyberchef) ![](https://reposs.herokuapp.com/?path=gchq/CyberChef&color=blue) [![](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/gchq/CyberChef/blob/master/LICENSE) +[![Gitter](https://badges.gitter.im/gchq/CyberChef.svg)](https://gitter.im/gchq/CyberChef?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) + #### *The Cyber Swiss Army Knife* @@ -25,7 +27,7 @@ Cryptographic operations in CyberChef should not be relied upon to provide secur There are four main areas in CyberChef: - 1. The **input** box in the top right, where you can paste, type or drag the data you want to operate on. + 1. The **input** box in the top right, where you can paste, type or drag the text or file you want to operate on. 2. The **output** box in the bottom right, where the outcome of your processing will be displayed. 3. The **operations** list on the far left, where you can find all the operations that CyberChef is capable of in categorised lists, or by searching. 4. The **recipe** area in the middle, where you can drag the operations that you want to use and specify arguments and options. @@ -40,32 +42,32 @@ You can use as many operations as you like in simple or complex ways. Some examp - [Display multiple timestamps as full dates][7] - [Carry out different operations on data of different types][8] - [Use parts of the input as arguments to operations][9] + - [Perform AES decryption, extracting the IV from the beginning of the cipher stream][10] ## Features - Drag and drop - Operations can be dragged in and out of the recipe list, or reorganised. - - Files can be dragged over the input box to load them directly. + - Files can be dragged over the input box to load them directly into the browser. - Auto Bake - - Whenever you modify the input or the recipe, CyberChef will automatically “bake” for you and produce the output immediately. + - Whenever you modify the input or the recipe, CyberChef will automatically "bake" for you and produce the output immediately. - This can be turned off and operated manually if it is affecting performance (if the input is very large, for instance). - - If any bake takes longer than 200 milliseconds, auto bake will be switched off automatically to prevent further performance issues. - Breakpoints - You can set breakpoints on any operation in your recipe to pause execution before running it. - You can also step through the recipe one operation at a time to see what the data looks like at each stage. - Save and load recipes - - If you come up with an awesome recipe that you know you’ll want to use again, just click save and add it to your local storage. It'll be waiting for you next time you visit CyberChef. - - You can also copy a URL which includes your recipe and input which can be shared with others. + - If you come up with an awesome recipe that you know you’ll want to use again, just click "Save recipe" and add it to your local storage. It'll be waiting for you next time you visit CyberChef. + - You can also copy the URL, which includes your recipe and input, to easily share it with others. - Search - If you know the name of the operation you want or a word associated with it, start typing it into the search field and any matching operations will immediately be shown. - Highlighting - - When you highlight text in the input or output, the offset and length values will be displayed and, if possible, the corresponding data will be highlighted in the output or input respectively (example: [highlight the word 'question' in the input to see where it appears in the output][10]). + - When you highlight text in the input or output, the offset and length values will be displayed and, if possible, the corresponding data will be highlighted in the output or input respectively (example: [highlight the word 'question' in the input to see where it appears in the output][11]). - Save to file and load from file - - You can save the output to a file at any time or load a file by dragging and dropping it into the input field (note that files larger than about 500kb may cause your browser to hang or even crash due to the way that browsers handle large amounts of textual data). + - You can save the output to a file at any time or load a file by dragging and dropping it into the input field. Files up to around 500MB are supported (depending on your browser), however some operations may take a very long time to run over this much data. - CyberChef is entirely client-side - - It should be noted that none of your input or recipe configuration is ever sent to the CyberChef web server - all processing is carried out within your browser, on your own computer. - - Due to this feature, CyberChef can be compiled into a single HTML file. You can download this file and drop it into a virtual machine, share it with other people, or use it independently on your desktop. + - It should be noted that none of your recipe configuration or input (either text or files) is ever sent to the CyberChef web server - all processing is carried out within your browser, on your own computer. + - Due to this feature, CyberChef can be compiled into a single HTML file. You can download this file and drop it into a virtual machine, share it with other people, or use it independently on your local machine. ## Browser support @@ -98,6 +100,7 @@ CyberChef is released under the [Apache 2.0 Licence](https://www.apache.org/lice [5]: https://gchq.github.io/CyberChef/#recipe=From_Hexdump()Gunzip()&input=MDAwMDAwMDAgIDFmIDhiIDA4IDAwIDEyIGJjIGYzIDU3IDAwIGZmIDBkIGM3IGMxIDA5IDAwIDIwICB8Li4uLi6881cu/y7HwS4uIHwKMDAwMDAwMTAgIDA4IDA1IGQwIDU1IGZlIDA0IDJkIGQzIDA0IDFmIGNhIDhjIDQ0IDIxIDViIGZmICB8Li7QVf4uLdMuLsouRCFb/3wKMDAwMDAwMjAgIDYwIGM3IGQ3IDAzIDE2IGJlIDQwIDFmIDc4IDRhIDNmIDA5IDg5IDBiIDlhIDdkICB8YMfXLi6%2BQC54Sj8uLi4ufXwKMDAwMDAwMzAgIDRlIGM4IDRlIDZkIDA1IDFlIDAxIDhiIDRjIDI0IDAwIDAwIDAwICAgICAgICAgICB8TshObS4uLi5MJC4uLnw [6]: https://gchq.github.io/CyberChef/#recipe=RC4(%7B'option':'UTF8','string':'secret'%7D,'Hex','Hex')Disassemble_x86('64','Full%20x86%20architecture',16,0,true,true)&input=MjFkZGQyNTQwMTYwZWU2NWZlMDc3NzEwM2YyYTM5ZmJlNWJjYjZhYTBhYWJkNDE0ZjkwYzZjYWY1MzEyNzU0YWY3NzRiNzZiM2JiY2QxOTNjYjNkZGZkYmM1YTI2NTMzYTY4NmI1OWI4ZmVkNGQzODBkNDc0NDIwMWFlYzIwNDA1MDcxMzhlMmZlMmIzOTUwNDQ2ZGIzMWQyYmM2MjliZTRkM2YyZWIwMDQzYzI5M2Q3YTVkMjk2MmMwMGZlNmRhMzAwNzJkOGM1YTZiNGZlN2Q4NTlhMDQwZWVhZjI5OTczMzYzMDJmNWEwZWMxOQ [7]: https://gchq.github.io/CyberChef/#recipe=Fork('%5C%5Cn','%5C%5Cn',false)From_UNIX_Timestamp('Seconds%20(s)')&input=OTc4MzQ2ODAwCjEwMTI2NTEyMDAKMTA0NjY5NjQwMAoxMDgxMDg3MjAwCjExMTUzMDUyMDAKMTE0OTYwOTYwMA - [8]: https://gchq.github.io/CyberChef/#recipe=Fork('%5C%5Cn','%5C%5Cn',false)Conditional_Jump('1',2,10)To_Hex('Space')Return()To_Base64('A-Za-z0-9%2B/%3D')&input=U29tZSBkYXRhIHdpdGggYSAxIGluIGl0ClNvbWUgZGF0YSB3aXRoIGEgMiBpbiBpdA + [8]: https://gchq.github.io/CyberChef/#recipe=Fork('%5C%5Cn','%5C%5Cn',false)Conditional_Jump('1',false,'base64',10)To_Hex('Space')Return()Label('base64')To_Base64('A-Za-z0-9%2B/%3D')&input=U29tZSBkYXRhIHdpdGggYSAxIGluIGl0ClNvbWUgZGF0YSB3aXRoIGEgMiBpbiBpdA [9]: https://gchq.github.io/CyberChef/#recipe=Register('key%3D(%5B%5C%5Cda-f%5D*)',true,false)Find_/_Replace(%7B'option':'Regex','string':'.*data%3D(.*)'%7D,'$1',true,false,true)RC4(%7B'option':'Hex','string':'$R0'%7D,'Hex','Latin1')&input=aHR0cDovL21hbHdhcmV6LmJpei9iZWFjb24ucGhwP2tleT0wZTkzMmE1YyZkYXRhPThkYjdkNWViZTM4NjYzYTU0ZWNiYjMzNGUzZGIxMQ - [10]: https://gchq.github.io/CyberChef/#recipe=XOR(%7B'option':'Hex','string':'3a'%7D,'',false)To_Hexdump(16,false,false)&input=VGhlIGFuc3dlciB0byB0aGUgdWx0aW1hdGUgcXVlc3Rpb24gb2YgbGlmZSwgdGhlIFVuaXZlcnNlLCBhbmQgZXZlcnl0aGluZyBpcyA0Mi4 + [10]: https://gchq.github.io/CyberChef/#recipe=Register('(.%7B32%7D)',true,false)Drop_bytes(0,32,false)AES_Decrypt(%7B'option':'Hex','string':'1748e7179bd56570d51fa4ba287cc3e5'%7D,%7B'option':'Hex','string':'$R0'%7D,'CTR','Hex','Raw',%7B'option':'Hex','string':''%7D)&input=NTFlMjAxZDQ2MzY5OGVmNWY3MTdmNzFmNWI0NzEyYWYyMGJlNjc0YjNiZmY1M2QzODU0NjM5NmVlNjFkYWFjNDkwOGUzMTljYTNmY2Y3MDg5YmZiNmIzOGVhOTllNzgxZDI2ZTU3N2JhOWRkNmYzMTFhMzk0MjBiODk3OGU5MzAxNGIwNDJkNDQ3MjZjYWVkZjU0MzZlYWY2NTI0MjljMGRmOTRiNTIxNjc2YzdjMmNlODEyMDk3YzI3NzI3M2M3YzcyY2Q4OWFlYzhkOWZiNGEyNzU4NmNjZjZhYTBhZWUyMjRjMzRiYTNiZmRmN2FlYjFkZGQ0Nzc2MjJiOTFlNzJjOWU3MDlhYjYwZjhkYWY3MzFlYzBjYzg1Y2UwZjc0NmZmMTU1NGE1YTNlYzI5MWNhNDBmOWU2MjlhODcyNTkyZDk4OGZkZDgzNDUzNGFiYTc5YzFhZDE2NzY3NjlhN2MwMTBiZjA0NzM5ZWNkYjY1ZDk1MzAyMzcxZDYyOWQ5ZTM3ZTdiNGEzNjFkYTQ2OGYxZWQ1MzU4OTIyZDJlYTc1MmRkMTFjMzY2ZjMwMTdiMTRhYTAxMWQyYWYwM2M0NGY5NTU3OTA5OGExNWUzY2Y5YjQ0ODZmOGZmZTljMjM5ZjM0ZGU3MTUxZjZjYTY1MDBmZTRiODUwYzNmMWMwMmU4MDFjYWYzYTI0NDY0NjE0ZTQyODAxNjE1YjhmZmFhMDdhYzgyNTE0OTNmZmRhN2RlNWRkZjMzNjg4ODBjMmI5NWIwMzBmNDFmOGYxNTA2NmFkZDA3MWE2NmNmNjBlNWY0NmYzYTIzMGQzOTdiNjUyOTYzYTIxYTUzZg + [11]: https://gchq.github.io/CyberChef/#recipe=XOR(%7B'option':'Hex','string':'3a'%7D,'',false)To_Hexdump(16,false,false)&input=VGhlIGFuc3dlciB0byB0aGUgdWx0aW1hdGUgcXVlc3Rpb24gb2YgbGlmZSwgdGhlIFVuaXZlcnNlLCBhbmQgZXZlcnl0aGluZyBpcyA0Mi4 diff --git a/package-lock.json b/package-lock.json index 721a80f8..876908f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "cyberchef", - "version": "6.4.0", + "version": "7.4.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -242,7 +242,7 @@ "dev": true, "requires": { "define-properties": "1.1.2", - "es-abstract": "1.9.0" + "es-abstract": "1.10.0" } }, "array-union": { @@ -286,9 +286,9 @@ "dev": true }, "asn1.js": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.9.1.tgz", - "integrity": "sha1-SLokC0WpKA6UdImQull9IWYX/UA=", + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.9.2.tgz", + "integrity": "sha512-b/OsSjvWEo8Pi8H0zsDd2P6Uqo2TK2pH8gNLSJtNLM2Db0v2QaAZ0pBQJXVjAn4gBuugeVDr7s63ZogpUIwWDg==", "dev": true, "requires": { "bn.js": "4.11.8", @@ -1069,7 +1069,6 @@ "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", - "dev": true, "requires": { "babel-runtime": "6.26.0", "core-js": "2.5.1", @@ -1077,9 +1076,9 @@ } }, "babel-preset-env": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/babel-preset-env/-/babel-preset-env-1.6.0.tgz", - "integrity": "sha512-OVgtQRuOZKckrILgMA5rvctvFZPv72Gua9Rt006AiPoB0DJKGN07UmaQA+qRrYgK71MVct8fFhT0EyNWYorVew==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/babel-preset-env/-/babel-preset-env-1.6.1.tgz", + "integrity": "sha512-W6VIyA6Ch9ePMI7VptNn2wBM6dbG0eSz25HEiL40nQXCsXGTGZSTZu1Iap+cj3Q0S5a7T9+529l/5Bkvd+afNA==", "dev": true, "requires": { "babel-plugin-check-es2015-constants": "6.22.0", @@ -1109,7 +1108,7 @@ "babel-plugin-transform-es2015-unicode-regex": "6.24.1", "babel-plugin-transform-exponentiation-operator": "6.24.1", "babel-plugin-transform-regenerator": "6.26.0", - "browserslist": "2.4.0", + "browserslist": "2.5.1", "invariant": "2.2.2", "semver": "5.4.1" } @@ -1212,7 +1211,6 @@ "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, "requires": { "core-js": "2.5.1", "regenerator-runtime": "0.11.0" @@ -1221,8 +1219,7 @@ "regenerator-runtime": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", - "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", - "dev": true + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==" } } }, @@ -1308,10 +1305,15 @@ "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==", "dev": true }, + "bignumber.js": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-5.0.0.tgz", + "integrity": "sha512-KWTu6ZMVk9sxlDJQh2YH1UOnfDP8O8TpxUxgQG/vKASoSnEjK9aVuOueFaPcQEYQ5fyNXNTOYwYw3099RYebWg==" + }, "binary-extensions": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.10.0.tgz", - "integrity": "sha1-muuabF6IY4qtFx4Wf1kAq+JINdA=", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz", + "integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=", "dev": true }, "bluebird": { @@ -1359,7 +1361,7 @@ "deep-equal": "1.0.1", "dns-equal": "1.0.0", "dns-txt": "2.0.2", - "multicast-dns": "6.1.1", + "multicast-dns": "6.2.1", "multicast-dns-service-types": "1.1.0" } }, @@ -1424,9 +1426,9 @@ "dev": true }, "browserify-aes": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.0.8.tgz", - "integrity": "sha512-WYCMOT/PtGTlpOKFht0YJFYcPy6pLCR98CtWfzK13zoynLlBMvAdEMSRGmgnJCw2M2j/5qxBkinZQFobieM8dQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.1.1.tgz", + "integrity": "sha512-UGnTYAnB2a3YuYKIRy1/4FB2HdM866E0qC46JXvVTYKlBlZlnvfpSfY6OKfXZAkv70eJ2a1SqzpAo5CRhZGDFg==", "dev": true, "requires": { "buffer-xor": "1.0.3", @@ -1443,7 +1445,7 @@ "integrity": "sha1-mYgkSHS/XtTijalWZtzWasj8Njo=", "dev": true, "requires": { - "browserify-aes": "1.0.8", + "browserify-aes": "1.1.1", "browserify-des": "1.0.0", "evp_bytestokey": "1.0.3" } @@ -1485,21 +1487,21 @@ } }, "browserify-zlib": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz", - "integrity": "sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0=", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", "dev": true, "requires": { - "pako": "0.2.9" + "pako": "1.0.6" } }, "browserslist": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-2.4.0.tgz", - "integrity": "sha512-aM2Gt4x9bVlCUteADBS6JP0F+2tMWKM1jQzUulVROtdFWFIcIVvY76AJbr7GDqy0eDhn+PcnpzzivGxY4qiaKQ==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-2.5.1.tgz", + "integrity": "sha512-jAvM2ku7YDJ+leAq3bFH1DE0Ylw+F+EQDq4GkqZfgPEqpWYw9ofQH85uKSB9r3Tv7XDbfqVtE+sdvKJW7IlPJA==", "dev": true, "requires": { - "caniuse-lite": "1.0.30000741", + "caniuse-lite": "1.0.30000751", "electron-to-chromium": "1.3.24" } }, @@ -1621,9 +1623,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30000741", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000741.tgz", - "integrity": "sha1-vFJrwgRua8OHN8/XfTAm7wS49GQ=", + "version": "1.0.30000751", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000751.tgz", + "integrity": "sha1-KYrTQYLKQ1l1e0qTr8aBt7kX41g=", "dev": true }, "caseless": { @@ -1860,9 +1862,9 @@ "dev": true }, "compressible": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.11.tgz", - "integrity": "sha1-FnGKdd4oPtjmBAQWJaIGRYZ5fYo=", + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.12.tgz", + "integrity": "sha1-xZpcmdt2dn6YdlAOJx72OzSTvWY=", "dev": true, "requires": { "mime-db": "1.30.0" @@ -1876,7 +1878,7 @@ "requires": { "accepts": "1.3.4", "bytes": "3.0.0", - "compressible": "2.0.11", + "compressible": "2.0.12", "debug": "2.6.9", "on-headers": "1.0.1", "safe-buffer": "5.1.1", @@ -1901,9 +1903,9 @@ } }, "connect-history-api-fallback": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.3.0.tgz", - "integrity": "sha1-5R0X+PDvDbkKZP20feMFFVbp8Wk=", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz", + "integrity": "sha1-sGhzk0vF40T+9hGhlqb6rgruAVo=", "dev": true }, "console-browserify": { @@ -1960,8 +1962,7 @@ "core-js": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.1.tgz", - "integrity": "sha1-rmh03GaTd4m4B1T/VCjfZoGcpQs=", - "dev": true + "integrity": "sha1-rmh03GaTd4m4B1T/VCjfZoGcpQs=" }, "core-util-is": { "version": "1.0.2", @@ -2065,9 +2066,9 @@ "integrity": "sha1-TCc3K8s85mnSKNV7NZG8YRjFKdU=" }, "crypto-browserify": { - "version": "3.11.1", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.11.1.tgz", - "integrity": "sha512-Na7ZlwCOqoaW5RwUK1WpXws2kv8mNhWdTlzob0UXulk6G9BDbyiJaGTYBIX61Ozn9l1EPPJpICZb4DaOpT9NlQ==", + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", "dev": true, "requires": { "browserify-cipher": "1.0.0", @@ -2079,7 +2080,8 @@ "inherits": "2.0.3", "pbkdf2": "3.0.14", "public-encrypt": "4.0.0", - "randombytes": "2.0.5" + "randombytes": "2.0.5", + "randomfill": "1.0.3" } }, "crypto-js": { @@ -2243,7 +2245,7 @@ "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", "dev": true, "requires": { - "es5-ext": "0.10.30" + "es5-ext": "0.10.37" } }, "dashdash": { @@ -2405,9 +2407,9 @@ "dev": true }, "diff": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz", - "integrity": "sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==" + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.4.0.tgz", + "integrity": "sha512-QpVuMTEoJMF7cKzi6bvWhRulU1fZqZnvyVQgNhPaxxuTYwyjn/j1v9falseQ/uXWwPnO56RBfwtg4h/EQXmucA==" }, "diffie-hellman": { "version": "5.0.2", @@ -2642,9 +2644,9 @@ } }, "es-abstract": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.9.0.tgz", - "integrity": "sha512-kk3IJoKo7A3pWJc0OV8yZ/VEX2oSUytfekrJiqoxBlKJMFAJVJVpGdHClCCTdv+Fn2zHfpDHHIelMFhZVfef3Q==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.10.0.tgz", + "integrity": "sha512-/uh/DhdqIOSkAWifU+8nG78vlQxdLckUdI/sPgy0VhuXi2qJ7T8czBmqIYtLQVpCIFYafChnsRsB5pyb1JdmCQ==", "dev": true, "requires": { "es-to-primitive": "1.1.1", @@ -2666,23 +2668,23 @@ } }, "es5-ext": { - "version": "0.10.30", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.30.tgz", - "integrity": "sha1-cUGhaDZpfbq/qq7uQUlc4p9SyTk=", + "version": "0.10.37", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.37.tgz", + "integrity": "sha1-DudB0Ui4AGm6J9AgOTdWryV978M=", "dev": true, "requires": { - "es6-iterator": "2.0.1", + "es6-iterator": "2.0.3", "es6-symbol": "3.1.1" } }, "es6-iterator": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.1.tgz", - "integrity": "sha1-jjGcnwRTv1ddN0lAplWSDlnKVRI=", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", "dev": true, "requires": { "d": "1.0.0", - "es5-ext": "0.10.30", + "es5-ext": "0.10.37", "es6-symbol": "3.1.1" } }, @@ -2693,18 +2695,37 @@ "dev": true, "requires": { "d": "1.0.0", - "es5-ext": "0.10.30", - "es6-iterator": "2.0.1", + "es5-ext": "0.10.37", + "es6-iterator": "2.0.3", "es6-set": "0.1.5", "es6-symbol": "3.1.1", "event-emitter": "0.3.5" } }, + "es6-object-assign": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", + "integrity": "sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw=" + }, + "es6-polyfills": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es6-polyfills/-/es6-polyfills-2.0.0.tgz", + "integrity": "sha1-fzWP04jYyIjQDPyaHuqJ+XFoOTE=", + "requires": { + "es6-object-assign": "1.1.0", + "es6-promise-polyfill": "1.2.0" + } + }, "es6-promise": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.0.5.tgz", "integrity": "sha1-eILzCt3lskDM+n99eMVIMwlRrkI=" }, + "es6-promise-polyfill": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es6-promise-polyfill/-/es6-promise-polyfill-1.2.0.tgz", + "integrity": "sha1-84kl8jyz4+jObNqP93T867sJDN4=" + }, "es6-promisify": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", @@ -2720,8 +2741,8 @@ "dev": true, "requires": { "d": "1.0.0", - "es5-ext": "0.10.30", - "es6-iterator": "2.0.1", + "es5-ext": "0.10.37", + "es6-iterator": "2.0.3", "es6-symbol": "3.1.1", "event-emitter": "0.3.5" } @@ -2733,7 +2754,7 @@ "dev": true, "requires": { "d": "1.0.0", - "es5-ext": "0.10.30" + "es5-ext": "0.10.37" } }, "es6-weak-map": { @@ -2743,8 +2764,8 @@ "dev": true, "requires": { "d": "1.0.0", - "es5-ext": "0.10.30", - "es6-iterator": "2.0.1", + "es5-ext": "0.10.37", + "es6-iterator": "2.0.3", "es6-symbol": "3.1.1" } }, @@ -3105,7 +3126,7 @@ "dev": true, "requires": { "d": "1.0.0", - "es5-ext": "0.10.30" + "es5-ext": "0.10.37" } }, "eventemitter2": { @@ -3200,9 +3221,9 @@ } }, "express": { - "version": "4.16.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.16.1.tgz", - "integrity": "sha512-STB7LZ4N0L+81FJHGla2oboUHTk4PaN1RsOkoRh9OSeEKylvF5hwKYVX1xCLFaCT7MD0BNG/gX2WFMLqY6EMBw==", + "version": "4.16.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.16.2.tgz", + "integrity": "sha1-41xt/i1kt9ygpc1PIXgb4ymeB2w=", "dev": true, "requires": { "accepts": "1.3.4", @@ -3272,9 +3293,9 @@ } }, "extract-text-webpack-plugin": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/extract-text-webpack-plugin/-/extract-text-webpack-plugin-3.0.1.tgz", - "integrity": "sha512-zv0/Cg2mU8uMzeQQ3oyfJvZU4Iv/GbQYUIr/HU+8pZetT/0W3xj6XAbxoG4gsp8SbnYcFd4BOsCAZPl9NvplPw==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extract-text-webpack-plugin/-/extract-text-webpack-plugin-3.0.2.tgz", + "integrity": "sha512-bt/LZ4m5Rqt/Crl2HiKuAl/oqg0psx1tsTLkvWbJen1CtD+fftkZhMaQ9HOtY2gWsl2Wq+sABmMVi9z3DhKWQQ==", "dev": true, "requires": { "async": "2.5.0", @@ -3382,15 +3403,20 @@ } }, "file-loader": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-1.1.4.tgz", - "integrity": "sha512-E4mCBrAVk8pB6VmuCW/rbHvtQDy2sknh0G4c2c449Q5qC7fCkL1P6sZGxQXWPaAxXBdU8WsTzPEB973Ei8vkxg==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-1.1.6.tgz", + "integrity": "sha512-873ztuL+/hfvXbLDJ262PGO6XjERnybJu2gW1/5j8HUfxSiFJI9Hj/DhZ50ZGRUxBvuNiazb/cM2rh9pqrxP6Q==", "dev": true, "requires": { "loader-utils": "1.1.0", "schema-utils": "0.3.0" } }, + "file-saver": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-1.3.3.tgz", + "integrity": "sha1-zdTETTqiZOrC9o7BZbx5HDSvEjI=" + }, "file-sync-cmp": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/file-sync-cmp/-/file-sync-cmp-0.1.1.tgz", @@ -5177,9 +5203,9 @@ } }, "http-parser-js": { - "version": "0.4.8", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.8.tgz", - "integrity": "sha512-jmHp99g6/fLx0pRNJqzsQgjsclCHAY7NhIeA3/U+bsGNvgbvUCQFQY9m5AYpqpAxY/2VcikfbKpjQozSTiz0jA==", + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.9.tgz", + "integrity": "sha1-6hoE+2St/wJC6ZdPKX3Uw8rSceE=", "dev": true }, "http-proxy": { @@ -5233,9 +5259,9 @@ } }, "https-browserify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-0.0.1.tgz", - "integrity": "sha1-P5E2XKvmC3ftDruiS0VOPgnZWoI=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", "dev": true }, "iced-error": { @@ -5344,6 +5370,16 @@ "dev": true, "optional": true }, + "import-local": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-0.1.1.tgz", + "integrity": "sha1-sReVcqrNwRxqkQCftDDbyrX2aKg=", + "dev": true, + "requires": { + "pkg-dir": "2.0.0", + "resolve-cwd": "2.0.0" + } + }, "imports-loader": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/imports-loader/-/imports-loader-0.7.1.tgz", @@ -5404,13 +5440,13 @@ "dev": true }, "ink-docstrap": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/ink-docstrap/-/ink-docstrap-1.3.0.tgz", - "integrity": "sha1-6QBeW7kCXMmpvo5ErYf4rViIyB0=", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/ink-docstrap/-/ink-docstrap-1.3.2.tgz", + "integrity": "sha512-STx5orGQU1gfrkoI/fMU7lX6CSP7LBGO10gXNgOZhwKhUqbtNjCkYSewJtNnLmWP1tAGN6oyEpG1HFPw5vpa5Q==", "dev": true, "requires": { - "moment": "2.18.1", - "sanitize-html": "1.14.1" + "moment": "2.20.1", + "sanitize-html": "1.15.0" } }, "inquirer": { @@ -5497,9 +5533,9 @@ } }, "interpret": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.0.4.tgz", - "integrity": "sha1-ggzdWIuGj/sZGoCVBtbJyPISsbA=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", + "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=", "dev": true }, "invariant": { @@ -5547,13 +5583,13 @@ "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", "dev": true, "requires": { - "binary-extensions": "1.10.0" + "binary-extensions": "1.11.0" } }, "is-buffer": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz", - "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true }, "is-builtin-module": { @@ -5844,9 +5880,9 @@ "integrity": "sha1-9yxcdhgXa/91zIEqHO2949jraDk=" }, "js-sha3": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.6.1.tgz", - "integrity": "sha1-W4n3enR3Z5h39YxKB1JAk0sflcA=" + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.7.0.tgz", + "integrity": "sha512-Wpks3yBDm0UcL5qlVhwW9Jr9n9i4FfeWBFOOXP5puDS/SiudJGhw7DPyBqn3487qD4F0lsC0q3zxink37f7zeA==" }, "js-tokens": { "version": "3.0.2", @@ -5959,7 +5995,7 @@ "cssstyle": "0.2.37", "escodegen": "1.9.0", "html-encoding-sniffer": "1.0.1", - "nwmatcher": "1.4.2", + "nwmatcher": "1.4.3", "parse5": "1.5.1", "request": "2.83.0", "sax": "1.2.4", @@ -6038,13 +6074,13 @@ "dev": true }, "jsonpath": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/jsonpath/-/jsonpath-0.2.12.tgz", - "integrity": "sha1-W/nZEftGFsHjNwvs658NskrjTNI=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/jsonpath/-/jsonpath-1.0.0.tgz", + "integrity": "sha1-Rc2dTE0NaCXZC9fkD4PxGCsT3Qc=", "requires": { "esprima": "1.2.2", "jison": "0.4.13", - "static-eval": "0.2.3", + "static-eval": "2.0.0", "underscore": "1.7.0" }, "dependencies": { @@ -6135,13 +6171,19 @@ } } }, + "killable": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.0.tgz", + "integrity": "sha1-2ouEvUfeU5WHj5XWTQLyRJ/gXms=", + "dev": true + }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.5" + "is-buffer": "1.1.6" } }, "klaw": { @@ -6169,9 +6211,9 @@ } }, "less": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/less/-/less-2.7.2.tgz", - "integrity": "sha1-No1sxz4fsDmBGDKAkYdDxdz5s98=", + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/less/-/less-2.7.3.tgz", + "integrity": "sha512-KPdIJKWcEAb02TuJtaLrhue0krtRLoRoo7x6BNJIBelO00t/CCdJQUnHW5V34OnHMWzIktSalJxRO+FvytQlCQ==", "dev": true, "requires": { "errno": "0.1.4", @@ -6180,8 +6222,170 @@ "mime": "1.4.1", "mkdirp": "0.5.1", "promise": "7.3.1", - "request": "2.83.0", + "request": "2.81.0", "source-map": "0.5.7" + }, + "dependencies": { + "ajv": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "dev": true, + "optional": true, + "requires": { + "co": "4.6.0", + "json-stable-stringify": "1.0.1" + } + }, + "assert-plus": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", + "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=", + "dev": true, + "optional": true + }, + "aws-sign2": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", + "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=", + "dev": true, + "optional": true + }, + "boom": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", + "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", + "dev": true, + "requires": { + "hoek": "2.16.3" + } + }, + "cryptiles": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", + "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", + "dev": true, + "optional": true, + "requires": { + "boom": "2.10.1" + } + }, + "form-data": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", + "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", + "dev": true, + "optional": true, + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.5", + "mime-types": "2.1.17" + } + }, + "har-schema": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", + "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=", + "dev": true, + "optional": true + }, + "har-validator": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", + "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=", + "dev": true, + "optional": true, + "requires": { + "ajv": "4.11.8", + "har-schema": "1.0.5" + } + }, + "hawk": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", + "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", + "dev": true, + "optional": true, + "requires": { + "boom": "2.10.1", + "cryptiles": "2.0.5", + "hoek": "2.16.3", + "sntp": "1.0.9" + } + }, + "hoek": { + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=", + "dev": true + }, + "http-signature": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", + "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", + "dev": true, + "optional": true, + "requires": { + "assert-plus": "0.2.0", + "jsprim": "1.4.1", + "sshpk": "1.13.1" + } + }, + "performance-now": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", + "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=", + "dev": true, + "optional": true + }, + "qs": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", + "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=", + "dev": true, + "optional": true + }, + "request": { + "version": "2.81.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", + "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=", + "dev": true, + "optional": true, + "requires": { + "aws-sign2": "0.6.0", + "aws4": "1.6.0", + "caseless": "0.12.0", + "combined-stream": "1.0.5", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.1.4", + "har-validator": "4.2.1", + "hawk": "3.1.3", + "http-signature": "1.1.1", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.17", + "oauth-sign": "0.8.2", + "performance-now": "0.2.0", + "qs": "6.4.0", + "safe-buffer": "5.1.1", + "stringstream": "0.0.5", + "tough-cookie": "2.3.3", + "tunnel-agent": "0.6.0", + "uuid": "3.1.0" + } + }, + "sntp": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", + "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", + "dev": true, + "optional": true, + "requires": { + "hoek": "2.16.3" + } + } } }, "less-loader": { @@ -6268,6 +6472,12 @@ "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", "dev": true }, + "lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha1-ZHYsSGGAglGKw99Mz11YhtriA0c=", + "dev": true + }, "lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -6287,10 +6497,18 @@ "dev": true }, "loglevel": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.5.0.tgz", - "integrity": "sha512-OQ2jhWI5G2qsvO0UFNyCQWgKl/tFiwuPIXxELzACeUO2FqstN/R7mmL09+nhv6xOWVPPojQO1A90sCEoJSgBcQ==", - "dev": true + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.0.tgz", + "integrity": "sha1-rgyqVhERSYxboTcj1vtjHSQAOTQ=" + }, + "loglevel-message-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/loglevel-message-prefix/-/loglevel-message-prefix-3.0.0.tgz", + "integrity": "sha1-ER/bltlPlh2PyLiqv7ZrBqw+dq0=", + "requires": { + "es6-polyfills": "2.0.0", + "loglevel": "1.6.0" + } }, "longest": { "version": "1.0.1", @@ -6560,16 +6778,16 @@ } }, "moment": { - "version": "2.18.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.18.1.tgz", - "integrity": "sha1-w2GT3Tzhwu7SrbfIAtu8d6gbHA8=" + "version": "2.20.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.20.1.tgz", + "integrity": "sha512-Yh9y73JRljxW5QxN08Fner68eFLxM5ynNOAw2LbIB1YAGeQzZT8QFSUvkAz609Zf+IHhhaUxqZK8dG3W/+HEvg==" }, "moment-timezone": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.13.tgz", - "integrity": "sha1-mc5cfYJyYusPH3AgRBd/YHRde5A=", + "version": "0.5.14", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.14.tgz", + "integrity": "sha1-TrOP+VOLgBCLpGekWPPtQmjM/LE=", "requires": { - "moment": "2.18.1" + "moment": "2.20.1" } }, "more-entropy": { @@ -6587,9 +6805,9 @@ "dev": true }, "multicast-dns": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.1.1.tgz", - "integrity": "sha1-bn3oalcIcqsXBYrepxYLvsqBTd4=", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.1.tgz", + "integrity": "sha512-uV3/ckdsffHx9IrGQrx613mturMdMqQ06WTq+C09NsStJ9iNG6RcUWgPKs1Rfjy+idZT6tfQoXEusGNnEZhT3w==", "dev": true, "requires": { "dns-packet": "1.2.2", @@ -6646,27 +6864,26 @@ } }, "node-forge": { - "version": "0.6.33", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.6.33.tgz", - "integrity": "sha1-RjgRh59XPUUVWtap9D3ClujoXrw=", - "dev": true + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.1.tgz", + "integrity": "sha1-naYR6giYL0uUIGs760zJZl8gwwA=" }, "node-libs-browser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.0.0.tgz", - "integrity": "sha1-o6WeyXAkmFtG6Vg3lkb5bEthZkY=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.1.0.tgz", + "integrity": "sha512-5AzFzdoIMb89hBGMZglEegffzgRg+ZFoUmisQ8HI4j1KDdpx13J0taNp2y9xPbur6W61gepGDDotGBVQ7mfUCg==", "dev": true, "requires": { "assert": "1.4.1", - "browserify-zlib": "0.1.4", + "browserify-zlib": "0.2.0", "buffer": "4.9.1", "console-browserify": "1.1.0", "constants-browserify": "1.0.0", - "crypto-browserify": "3.11.1", + "crypto-browserify": "3.12.0", "domain-browser": "1.1.7", "events": "1.1.1", - "https-browserify": "0.0.1", - "os-browserify": "0.2.1", + "https-browserify": "1.0.0", + "os-browserify": "0.3.0", "path-browserify": "0.0.0", "process": "0.11.10", "punycode": "1.4.1", @@ -6674,20 +6891,12 @@ "readable-stream": "2.3.3", "stream-browserify": "2.0.1", "stream-http": "2.7.2", - "string_decoder": "0.10.31", + "string_decoder": "1.0.3", "timers-browserify": "2.0.4", "tty-browserify": "0.0.0", "url": "0.11.0", "util": "0.10.3", "vm-browserify": "0.0.4" - }, - "dependencies": { - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - } } }, "node-md6": { @@ -6790,10 +6999,9 @@ "dev": true }, "nwmatcher": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/nwmatcher/-/nwmatcher-1.4.2.tgz", - "integrity": "sha512-QMkCGQFYp5p+zwU3INntLmz1HMfSx9dMVJMYKmE1yuSf/22Wjo6VPFa405mCLUuQn9lbQvH2DZN9lt10ZNvtAg==", - "dev": true + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/nwmatcher/-/nwmatcher-1.4.3.tgz", + "integrity": "sha512-IKdSTiDWCarf2JTS5e9e2+5tPZGdkRJ79XjYV0pzK8Q9BpsFyBq1RGKxzs7Q8UBushGw7m6TzVKz6fcY99iSWw==" }, "oauth-sign": { "version": "0.8.2", @@ -6906,9 +7114,9 @@ } }, "os-browserify": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.2.1.tgz", - "integrity": "sha1-Y/xMzuXS13Y9Jrv4YBB45sLgBE8=", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", "dev": true }, "os-homedir": { @@ -6983,9 +7191,9 @@ } }, "pako": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", - "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz", + "integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==", "dev": true }, "param-case": { @@ -7003,8 +7211,8 @@ "integrity": "sha1-N8T5t+06tlx0gXtfJICTf7+XxxI=", "dev": true, "requires": { - "asn1.js": "4.9.1", - "browserify-aes": "1.0.8", + "asn1.js": "4.9.2", + "browserify-aes": "1.1.1", "create-hash": "1.1.3", "evp_bytestokey": "1.0.3", "pbkdf2": "3.0.14" @@ -7608,13 +7816,13 @@ } }, "postcss-loader": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-2.0.6.tgz", - "integrity": "sha512-HIq7yy1hh9KI472Y38iSRV4WupZUNy6zObkxQM/ZuInoaE2+PyX4NcO6jjP5HG5mXL7j5kcNEl0fAG4Kva7O9w==", + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-2.0.9.tgz", + "integrity": "sha512-sgoXPtmgVT3aBAhU47Kig8oPF+mbXl8Unjvtz1Qj1q2D2EvSVJW2mKJNzxv5y/LvA9xWwuvdysvhc7Zn80UWWw==", "dev": true, "requires": { "loader-utils": "1.1.0", - "postcss": "6.0.12", + "postcss": "6.0.14", "postcss-load-config": "1.2.0", "schema-utils": "0.3.0" }, @@ -7629,14 +7837,14 @@ } }, "chalk": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.1.0.tgz", - "integrity": "sha512-LUHGS/dge4ujbXMJrnihYMcL4AoOweGnw9Tp3kQuqy1Kx5c1qKjqvMJZ6nVJPMWJtKCTN72ZogH3oeSO9g9rXQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", + "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", "dev": true, "requires": { "ansi-styles": "3.2.0", "escape-string-regexp": "1.0.5", - "supports-color": "4.4.0" + "supports-color": "4.5.0" } }, "has-flag": { @@ -7646,20 +7854,26 @@ "dev": true }, "postcss": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.12.tgz", - "integrity": "sha512-K6SLofXEK43FBSyZ6/ExQV7ji24OEw4tEY6x1CAf7+tcoMWJoO24Rf3rVFVpk+5IQL1e1Cy3sTKfg7hXuLzafg==", + "version": "6.0.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.14.tgz", + "integrity": "sha512-NJ1z0f+1offCgadPhz+DvGm5Mkci+mmV5BqD13S992o0Xk9eElxUfPPF+t2ksH5R/17gz4xVK8KWocUQ5o3Rog==", "dev": true, "requires": { - "chalk": "2.1.0", - "source-map": "0.5.7", - "supports-color": "4.4.0" + "chalk": "2.3.0", + "source-map": "0.6.1", + "supports-color": "4.5.0" } }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, "supports-color": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", - "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", + "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", "dev": true, "requires": { "has-flag": "2.0.0" @@ -8298,7 +8512,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.5" + "is-buffer": "1.1.6" } } } @@ -8309,7 +8523,7 @@ "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", "dev": true, "requires": { - "is-buffer": "1.1.5" + "is-buffer": "1.1.6" } } } @@ -8323,6 +8537,16 @@ "safe-buffer": "5.1.1" } }, + "randomfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.3.tgz", + "integrity": "sha512-YL6GrhrWoic0Eq8rXVbMptH7dAxCs0J+mh5Y0euNekPPYaxEmdVGim6GdoxoRzKW2yJoU8tueifS7mYxvcFDEQ==", + "dev": true, + "requires": { + "randombytes": "2.0.5", + "safe-buffer": "5.1.1" + } + }, "range-parser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", @@ -8494,8 +8718,7 @@ "regenerator-runtime": { "version": "0.10.5", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", - "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=", - "dev": true + "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=" }, "regenerator-transform": { "version": "0.10.1", @@ -8517,12 +8740,6 @@ "is-equal-shallow": "0.1.3" } }, - "regexp-quote": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/regexp-quote/-/regexp-quote-0.0.0.tgz", - "integrity": "sha1-Hg9GUMhi3L/tVP1CsUjpuxch/PI=", - "dev": true - }, "regexpu-core": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz", @@ -8707,6 +8924,23 @@ "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", "dev": true }, + "resolve-cwd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", + "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "dev": true, + "requires": { + "resolve-from": "3.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + } + } + }, "resolve-from": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", @@ -8779,13 +9013,14 @@ "dev": true }, "sanitize-html": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-1.14.1.tgz", - "integrity": "sha1-cw/6Ikm98YMz7/5FsoYXPJxa0Lg=", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-1.15.0.tgz", + "integrity": "sha512-1jWLToWx8ZV53Z1Jg+2fHl8dNFsxvQt2Cmrk4o/z1+MUdB5EXSU0QVuzlGGhfp7cQrYbEEfMO/TUWHfkBUqujQ==", "dev": true, "requires": { "htmlparser2": "3.9.2", - "regexp-quote": "0.0.0", + "lodash.escaperegexp": "4.1.2", + "srcset": "1.0.0", "xtend": "4.0.1" }, "dependencies": { @@ -8842,6 +9077,14 @@ "dev": true, "requires": { "node-forge": "0.6.33" + }, + "dependencies": { + "node-forge": { + "version": "0.6.33", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.6.33.tgz", + "integrity": "sha1-RjgRh59XPUUVWtap9D3ClujoXrw=", + "dev": true + } } }, "semver": { @@ -9017,7 +9260,7 @@ "faye-websocket": "0.11.1", "inherits": "2.0.3", "json3": "3.3.2", - "url-parse": "1.1.9" + "url-parse": "1.2.0" }, "dependencies": { "faye-websocket": { @@ -9041,9 +9284,9 @@ } }, "sortablejs": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.6.1.tgz", - "integrity": "sha1-0SDRA/u59gx9sngUoThAcubG4IM=" + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.7.0.tgz", + "integrity": "sha1-gKKyNwq9Vo4c7IwnETHvMKkE+ig=" }, "source-list-map": { "version": "2.0.0", @@ -9135,6 +9378,16 @@ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, + "srcset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/srcset/-/srcset-1.0.0.tgz", + "integrity": "sha1-pWad4StC87HV6D7QPHEEb8SPQe8=", + "dev": true, + "requires": { + "array-uniq": "1.0.3", + "number-is-nan": "1.0.1" + } + }, "sshpk": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", @@ -9161,33 +9414,11 @@ } }, "static-eval": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-0.2.3.tgz", - "integrity": "sha1-Aj8XrJ/uQm6niMEuo5IG3Bdfiyo=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.0.0.tgz", + "integrity": "sha512-6flshd3F1Gwm+Ksxq463LtFd1liC77N/PX1FVVc3OzL3hAmo2fwHFbuArkcfi7s9rTNsLEhcRmXGFZhlgy40uw==", "requires": { - "escodegen": "0.0.28" - }, - "dependencies": { - "escodegen": { - "version": "0.0.28", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-0.0.28.tgz", - "integrity": "sha1-Dk/xcV8yh3XWyrUaxEpAbNer/9M=", - "requires": { - "esprima": "1.0.4", - "estraverse": "1.3.2", - "source-map": "0.5.7" - } - }, - "esprima": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz", - "integrity": "sha1-n1V+CPw7TSbs6d00+Pv0drYlha0=" - }, - "estraverse": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.3.2.tgz", - "integrity": "sha1-N8K4k+8T1yPydth41g2FNRUqbEI=" - } + "escodegen": "1.9.0" } }, "statuses": { @@ -9319,9 +9550,9 @@ "dev": true }, "style-loader": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.19.0.tgz", - "integrity": "sha512-9mx9sC9nX1dgP96MZOODpGC6l1RzQBITI2D5WJhu+wnbrSYVKLGuy14XJSLVQih/0GFrPpjelt+s//VcZQ2Evw==", + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.19.1.tgz", + "integrity": "sha512-IRE+ijgojrygQi3rsqT0U4dd+UcPCqcVvauZpCnQrGAlEe+FUIyrK93bUDScamesjP08JlQNsFJU+KmPedP5Og==", "dev": true, "requires": { "loader-utils": "1.1.0", @@ -9762,9 +9993,9 @@ } }, "url-parse": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.1.9.tgz", - "integrity": "sha1-xn8dd11R8KGJEd17P/rSe7nlvRk=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.2.0.tgz", + "integrity": "sha512-DT1XbYAfmQP65M/mE6OALxmXzZ/z1+e5zk2TcSKe/KiYbNGZxgtttzC0mR/sjopbpOXcbniq7eIKmocJnUWlEw==", "dev": true, "requires": { "querystringify": "1.0.0", @@ -9779,6 +10010,11 @@ } } }, + "utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz", + "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==" + }, "util": { "version": "0.10.3", "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", @@ -9821,9 +10057,9 @@ "dev": true }, "val-loader": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/val-loader/-/val-loader-1.0.2.tgz", - "integrity": "sha1-eQkZgJOzfLoKlr9PbSnelw0JnT0=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/val-loader/-/val-loader-1.1.0.tgz", + "integrity": "sha512-8m62XF42FcfrBBl02rtDY9hQhDcDczrEcr60/aSMxlzJiXAcbAimRPvsDoDa5QcGAusOgOmVTpFtK5EbfZdDwA==", "dev": true, "requires": { "loader-utils": "1.1.0" @@ -9956,28 +10192,28 @@ "dev": true }, "webpack": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-3.6.0.tgz", - "integrity": "sha512-OsHT3D0W0KmPPh60tC7asNnOmST6bKTiR90UyEdT9QYoaJ4OYN4Gg7WK1k3VxHK07ZoiYWPsKvlS/gAjwL/vRA==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-3.10.0.tgz", + "integrity": "sha512-fxxKXoicjdXNUMY7LIdY89tkJJJ0m1Oo8PQutZ5rLgWbV5QVKI15Cn7+/IHnRTd3vfKfiwBx6SBqlorAuNA8LA==", "dev": true, "requires": { - "acorn": "5.1.2", + "acorn": "5.2.1", "acorn-dynamic-import": "2.0.2", "ajv": "5.2.3", "ajv-keywords": "2.1.0", "async": "2.5.0", "enhanced-resolve": "3.4.1", "escope": "3.6.0", - "interpret": "1.0.4", + "interpret": "1.1.0", "json-loader": "0.5.7", "json5": "0.5.1", "loader-runner": "2.3.0", "loader-utils": "1.1.0", "memory-fs": "0.4.1", "mkdirp": "0.5.1", - "node-libs-browser": "2.0.0", + "node-libs-browser": "2.1.0", "source-map": "0.5.7", - "supports-color": "4.4.0", + "supports-color": "4.5.0", "tapable": "0.2.8", "uglifyjs-webpack-plugin": "0.4.6", "watchpack": "1.4.0", @@ -9986,9 +10222,9 @@ }, "dependencies": { "acorn": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.1.2.tgz", - "integrity": "sha512-o96FZLJBPY1lvTuJylGA9Bk3t/GKPPJG8H0ydQQl01crzwJgspa4AEIq/pVTXigmK0PHVQhiAtn8WMBLL9D2WA==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.2.1.tgz", + "integrity": "sha512-jG0u7c4Ly+3QkkW18V+NRDN+4bWHdln30NL1ZL2AvFZZmQe/BfopYCtghCKKVBUSetZ4QKcyA0pY6/4Gw8Pv8w==", "dev": true }, "escope": { @@ -10010,9 +10246,9 @@ "dev": true }, "supports-color": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", - "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", + "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", "dev": true, "requires": { "has-flag": "2.0.0" @@ -10021,22 +10257,30 @@ } }, "webpack-dev-middleware": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-1.12.0.tgz", - "integrity": "sha1-007++y7dp+HTtdvgcolRMhllFwk=", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-1.12.2.tgz", + "integrity": "sha512-FCrqPy1yy/sN6U/SaEZcHKRXGlqU0DUaEBL45jkUYoB8foVb6wCnbIJ1HKIx+qUFTW+3JpVcCJCxZ8VATL4e+A==", "dev": true, "requires": { "memory-fs": "0.4.1", - "mime": "1.4.1", + "mime": "1.6.0", "path-is-absolute": "1.0.1", "range-parser": "1.2.0", "time-stamp": "2.0.0" + }, + "dependencies": { + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true + } } }, "webpack-dev-server": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-2.9.1.tgz", - "integrity": "sha512-qFKs4Wg6JI6FkAQ6WFqeDCCxXEBLsDHkqJB3f9tmlqx8C68Y9vQWwcaMT4Q9H8WF32Q6QUNmgK4qQkdHfXvj/g==", + "version": "2.9.7", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-2.9.7.tgz", + "integrity": "sha512-Pu7uoQFgQj5RE5wmlfkpYSzihMKxulwEuO2xCsaMnAnyRSApwoVi3B8WCm9XbigyWTHaIMzYGkB90Vr6leAeTQ==", "dev": true, "requires": { "ansi-html": "0.0.7", @@ -10044,14 +10288,17 @@ "bonjour": "3.5.0", "chokidar": "1.7.0", "compression": "1.7.1", - "connect-history-api-fallback": "1.3.0", + "connect-history-api-fallback": "1.5.0", + "debug": "3.1.0", "del": "3.0.0", - "express": "4.16.1", + "express": "4.16.2", "html-entities": "1.2.1", "http-proxy-middleware": "0.17.4", + "import-local": "0.1.1", "internal-ip": "1.2.0", "ip": "1.1.5", - "loglevel": "1.5.0", + "killable": "1.0.0", + "loglevel": "1.6.0", "opn": "5.1.0", "portfinder": "1.0.13", "selfsigned": "1.10.1", @@ -10060,8 +10307,8 @@ "sockjs-client": "1.1.4", "spdy": "3.4.7", "strip-ansi": "3.0.1", - "supports-color": "4.4.0", - "webpack-dev-middleware": "1.12.0", + "supports-color": "4.5.0", + "webpack-dev-middleware": "1.12.2", "yargs": "6.6.0" }, "dependencies": { @@ -10082,6 +10329,15 @@ "wrap-ansi": "2.1.0" } }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, "del": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz", @@ -10159,9 +10415,9 @@ } }, "supports-color": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", - "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", + "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", "dev": true, "requires": { "has-flag": "2.0.0" @@ -10227,14 +10483,14 @@ "integrity": "sha1-DK+dLXVdk67gSdS90NP+LMoqJOs=", "dev": true, "requires": { - "http-parser-js": "0.4.8", - "websocket-extensions": "0.1.2" + "http-parser-js": "0.4.9", + "websocket-extensions": "0.1.3" } }, "websocket-extensions": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.2.tgz", - "integrity": "sha1-Dhh4HeYpoYMIzhSBZQ9n/6JpOl0=", + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", + "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==", "dev": true }, "whatwg-encoding": { @@ -10305,9 +10561,9 @@ "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" }, "worker-loader": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/worker-loader/-/worker-loader-1.0.0.tgz", - "integrity": "sha512-dUwgs4Rdi1qG3VciM1+EPgAoO8m9USpCXxE3xmpWrnHJSMKGkzpCUNeYLjBRgYcSkf2A5xnXpR450Wqtu+pq0w==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/worker-loader/-/worker-loader-1.1.0.tgz", + "integrity": "sha512-W91q8Wi1JxbzFQZuLJlFK4x8UuWjKgeOX9IMMyng007K0UkP6I8lOejckoCWY61QmnJq2x9qZ/Viru+uF8g6nA==", "dev": true, "requires": { "loader-utils": "1.1.0", @@ -10385,9 +10641,9 @@ "integrity": "sha1-1QH5ezvbQDr4757MIFcxh6rawOk=" }, "xpath": { - "version": "0.0.24", - "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.24.tgz", - "integrity": "sha1-Gt4WLhzFI8jTn8fQavwW6iFvKfs=" + "version": "0.0.27", + "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.27.tgz", + "integrity": "sha512-fg03WRxtkCV6ohClePNAECYsmpKKTv5L8y/X3Dn1hQrec3POx2jHZ/0P2qQ6HvsrU1BmeqXcof3NGGueG6LxwQ==" }, "xtend": { "version": "4.0.1", diff --git a/package.json b/package.json index 6f1c3089..facc5e98 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cyberchef", - "version": "6.4.0", + "version": "7.4.0", "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.", "author": "n1474335 ", "homepage": "https://gchq.github.io/CyberChef", @@ -32,15 +32,15 @@ "devDependencies": { "babel-core": "^6.26.0", "babel-loader": "^7.1.2", + "babel-preset-env": "^1.6.0", "babel-plugin-transform-runtime": "^6.23.0", "babel-polyfill": "^6.26.0", - "babel-preset-env": "^1.6.0", "babel-preset-es2015": "^6.24.1", "babel-preset-stage-0": "^6.24.1", "css-loader": "^0.28.7", "exports-loader": "^0.6.4", - "extract-text-webpack-plugin": "^3.0.1", - "file-loader": "^1.1.4", + "extract-text-webpack-plugin": "^3.0.2", + "file-loader": "^1.1.6", "grunt": ">=1.0.1", "grunt-accessibility": "~5.0.0", "grunt-chmod": "~1.1.1", @@ -54,23 +54,25 @@ "grunt-webpack": "^3.0.2", "html-webpack-plugin": "^2.30.1", "imports-loader": "^0.7.1", - "ink-docstrap": "^1.3.0", + "ink-docstrap": "^1.3.2", "jsdoc-babel": "^0.3.0", - "less": "^2.7.2", + "less": "^2.7.3", "less-loader": "^4.0.5", "postcss-css-variables": "^0.8.0", "postcss-import": "^11.0.0", - "postcss-loader": "^2.0.6", - "style-loader": "^0.19.0", + "postcss-loader": "^2.0.9", + "style-loader": "^0.19.1", "url-loader": "^0.6.2", - "val-loader": "^1.0.2", + "val-loader": "^1.1.0", "web-resource-inliner": "^4.2.0", - "webpack": "^3.6.0", - "webpack-dev-server": "^2.9.1", + "webpack": "^3.10.0", + "webpack-dev-server": "^2.9.7", "webpack-node-externals": "^1.6.0", - "worker-loader": "^1.0.0" + "worker-loader": "^1.1.0" }, "dependencies": { + "babel-polyfill": "^6.26.0", + "bignumber.js": "^5.0.0", "bootstrap": "^3.3.7", "bootstrap-colorpicker": "^2.5.2", "bootstrap-switch": "^3.3.4", @@ -82,25 +84,31 @@ "esmangle": "^1.0.1", "esprima": "^4.0.0", "exif-parser": "^0.1.12", + "file-saver": "^1.3.3", "google-code-prettify": "^1.0.5", "jquery": "^3.2.1", "js-crc": "^0.2.0", - "js-sha3": "^0.6.1", + "js-sha3": "^0.7.0", "jsbn": "^1.1.0", - "jsonpath": "^0.2.12", + "jsonpath": "^1.0.0", "jsrsasign": "8.0.4", "kbpgp": "^2.0.76", "lodash": "^4.17.4", - "moment": "^2.18.1", - "moment-timezone": "^0.5.13", + "loglevel": "^1.6.0", + "loglevel-message-prefix": "^3.0.0", + "moment": "^2.20.1", + "moment-timezone": "^0.5.14", + "node-forge": "^0.7.1", "node-md6": "^0.1.0", + "nwmatcher": "^1.4.3", "otp": "^0.1.3", "sladex-blowfish": "^0.8.1", - "sortablejs": "^1.6.1", + "sortablejs": "^1.7.0", "split.js": "^1.3.5", + "utf8": "^3.0.0", "vkbeautify": "^0.99.3", "xmldom": "^0.1.27", - "xpath": "0.0.24", + "xpath": "0.0.27", "zlibjs": "^0.3.1" }, "scripts": { diff --git a/src/core/Chef.js b/src/core/Chef.js index 7c9817df..aba3f4f7 100755 --- a/src/core/Chef.js +++ b/src/core/Chef.js @@ -19,7 +19,7 @@ const Chef = function() { /** * Runs the recipe over the input. * - * @param {string} inputText - The input data as a string + * @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 {boolean} options.attempHighlight - Whether or not to attempt highlighting @@ -33,11 +33,13 @@ const Chef = function() { * @returns {number} response.duration - The number of ms it took to execute the recipe * @returns {number} response.error - The error object thrown by a failed operation (false if no error) */ -Chef.prototype.bake = async function(inputText, recipeConfig, options, progress, step) { - let startTime = new Date().getTime(), +Chef.prototype.bake = async function(input, recipeConfig, options, progress, step) { + log.debug("Chef baking"); + const startTime = new Date().getTime(), recipe = new Recipe(recipeConfig), containsFc = recipe.containsFlowControl(), - error = false; + notUTF8 = options && options.hasOwnProperty("treatAsUtf8") && !options.treatAsUtf8; + let error = false; if (containsFc && ENVIRONMENT_IS_WORKER()) self.setOption("attemptHighlight", false); @@ -62,22 +64,30 @@ Chef.prototype.bake = async function(inputText, recipeConfig, options, progress, // If starting from scratch, load data if (progress === 0) { - this.dish.set(inputText, Dish.STRING); + 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) { - // Return the error in the result so that everything else gets correctly updated - // rather than throwing it here and losing state info. - error = err; + log.error(err); + error = { + displayStr: err.displayStr, + }; progress = err.progress; } + // Depending on the size of the output, we may send it back as a string or an ArrayBuffer. + // This can prevent unnecessary casting as an ArrayBuffer can be easily downloaded as a file. + // The threshold is specified in KiB. + const threshold = (options.ioDisplayThreshold || 1024) * 1024; + const returnType = this.dish.size() > threshold ? Dish.ARRAY_BUFFER : Dish.STRING; + return { result: this.dish.type === Dish.HTML ? - this.dish.get(Dish.HTML) : - this.dish.get(Dish.STRING), + this.dish.get(Dish.HTML, notUTF8) : + this.dish.get(returnType, notUTF8), type: Dish.enumLookup(this.dish.type), progress: progress, duration: new Date().getTime() - startTime, @@ -104,6 +114,8 @@ Chef.prototype.bake = async function(inputText, recipeConfig, options, progress, * @returns {number} The time it took to run the silent bake in milliseconds. */ Chef.prototype.silentBake = function(recipeConfig) { + log.debug("Running silent bake"); + let startTime = new Date().getTime(), recipe = new Recipe(recipeConfig), dish = new Dish("", Dish.STRING); diff --git a/src/core/ChefWorker.js b/src/core/ChefWorker.js index 4bb7f9e5..9e9f8a30 100644 --- a/src/core/ChefWorker.js +++ b/src/core/ChefWorker.js @@ -11,6 +11,15 @@ import Chef from "./Chef.js"; import OperationConfig from "./config/MetaConfig.js"; import OpModules from "./config/modules/Default.js"; +// Add ">" to the start of all log messages in the Chef Worker +import loglevelMessagePrefix from "loglevel-message-prefix"; + +loglevelMessagePrefix(log, { + prefixes: [], + staticPrefixes: [">"], + prefixFormat: "%p" +}); + // Set up Chef instance self.chef = new Chef(); @@ -42,6 +51,8 @@ self.postMessage({ self.addEventListener("message", function(e) { // Handle message const r = e.data; + log.debug("ChefWorker receiving command '" + r.action + "'"); + switch (r.action) { case "bake": bake(r.data); @@ -61,6 +72,9 @@ self.addEventListener("message", function(e) { r.data.pos ); break; + case "setLogLevel": + log.setLevel(r.data, false); + break; default: break; } @@ -86,7 +100,7 @@ async function bake(data) { ); self.postMessage({ - action: "bakeSuccess", + action: "bakeComplete", data: response }); } catch (err) { @@ -121,7 +135,7 @@ function loadRequiredModules(recipeConfig) { let module = self.OperationConfig[op.op].module; if (!OpModules.hasOwnProperty(module)) { - console.log("Loading module " + module); + log.info("Loading module " + module); self.sendStatusMessage("Loading module " + module); self.importScripts(self.docURL + "/" + module + ".js"); } diff --git a/src/core/Dish.js b/src/core/Dish.js index 914188c1..7a136060 100755 --- a/src/core/Dish.js +++ b/src/core/Dish.js @@ -1,19 +1,21 @@ import Utils from "./Utils.js"; +import BigNumber from "bignumber.js"; /** * The data being operated on by each operation. * * @author n1474335 [n1474335@gmail.com] + * @author Matt C [matt@artemisbot.uk] * @copyright Crown Copyright 2016 * @license Apache-2.0 * * @class - * @param {byteArray|string|number} value - The value of the input data. + * @param {byteArray|string|number|ArrayBuffer|BigNumber} value - The value of the input data. * @param {number} type - The data type of value, see Dish enums. */ const Dish = function(value, type) { - this.value = value || typeof value == "string" ? value : null; - this.type = type || Dish.BYTE_ARRAY; + this.value = value || typeof value === "string" ? value : null; + this.type = type || Dish.BYTE_ARRAY; }; @@ -41,6 +43,18 @@ Dish.NUMBER = 2; * @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; /** @@ -51,19 +65,22 @@ Dish.HTML = 3; * @returns {number} The data type enum value. */ Dish.typeEnum = function(typeStr) { - switch (typeStr) { - case "byteArray": - case "Byte array": + switch (typeStr.toLowerCase()) { + case "bytearray": + case "byte array": return Dish.BYTE_ARRAY; case "string": - case "String": return Dish.STRING; case "number": - case "Number": return Dish.NUMBER; case "html": - case "HTML": return Dish.HTML; + case "arraybuffer": + case "array buffer": + return Dish.ARRAY_BUFFER; + case "bignumber": + case "big number": + return Dish.BIG_NUMBER; default: throw "Invalid data type string. No matching enum."; } @@ -74,8 +91,8 @@ Dish.typeEnum = function(typeStr) { * Returns the data type string for the given type enum. * * @static - * @param {string} typeEnum - The enum value of the data type. - * @returns {number} The data type as a string. + * @param {number} typeEnum - The enum value of the data type. + * @returns {string} The data type as a string. */ Dish.enumLookup = function(typeEnum) { switch (typeEnum) { @@ -87,6 +104,10 @@ Dish.enumLookup = function(typeEnum) { return "number"; case Dish.HTML: return "html"; + case Dish.ARRAY_BUFFER: + return "ArrayBuffer"; + case Dish.BIG_NUMBER: + return "BigNumber"; default: throw "Invalid data type enum. No matching type."; } @@ -96,12 +117,13 @@ Dish.enumLookup = function(typeEnum) { /** * Sets the data value and type and then validates them. * - * @param {byteArray|string|number} value - The value of the input data. + * @param {byteArray|string|number|ArrayBuffer|BigNumber} value - The value of the input data. * @param {number} type - The data type of value, see Dish enums. */ Dish.prototype.set = function(value, type) { + log.debug("Dish type: " + Dish.enumLookup(type)); this.value = value; - this.type = type; + this.type = type; if (!this.valid()) { const sample = Utils.truncate(JSON.stringify(this.value), 13); @@ -114,11 +136,12 @@ Dish.prototype.set = function(value, type) { * Returns the value of the data in the type format specified. * * @param {number} type - The data type of value, see Dish enums. - * @returns {byteArray|string|number} The value of the output data. + * @param {boolean} [notUTF8] - Do not treat strings as UTF8. + * @returns {byteArray|string|number|ArrayBuffer|BigNumber} The value of the output data. */ -Dish.prototype.get = function(type) { +Dish.prototype.get = function(type, notUTF8) { if (this.type !== type) { - this.translate(type); + this.translate(type, notUTF8); } return this.value; }; @@ -128,37 +151,59 @@ Dish.prototype.get = function(type) { * Translates the data to the given type format. * * @param {number} toType - The data type of value, see Dish enums. + * @param {boolean} [notUTF8] - Do not treat strings as UTF8. */ -Dish.prototype.translate = function(toType) { +Dish.prototype.translate = function(toType, notUTF8) { + log.debug(`Translating Dish from ${Dish.enumLookup(this.type)} to ${Dish.enumLookup(toType)}`); + const byteArrayToStr = notUTF8 ? Utils.byteArrayToChars : Utils.byteArrayToUtf8; + // Convert data to intermediate byteArray type switch (this.type) { case Dish.STRING: this.value = this.value ? Utils.strToByteArray(this.value) : []; - this.type = Dish.BYTE_ARRAY; break; case Dish.NUMBER: this.value = typeof this.value == "number" ? Utils.strToByteArray(this.value.toString()) : []; - this.type = Dish.BYTE_ARRAY; break; case Dish.HTML: this.value = this.value ? Utils.strToByteArray(Utils.unescapeHtml(Utils.stripHtmlTags(this.value, true))) : []; - this.type = Dish.BYTE_ARRAY; + break; + case Dish.ARRAY_BUFFER: + // Array.from() would be nicer here, but it's slightly slower + this.value = Array.prototype.slice.call(new Uint8Array(this.value)); + break; + case Dish.BIG_NUMBER: + this.value = this.value instanceof BigNumber ? Utils.strToByteArray(this.value.toString()) : []; break; default: break; } + this.type = Dish.BYTE_ARRAY; + // Convert from byteArray to toType switch (toType) { case Dish.STRING: case Dish.HTML: - this.value = this.value ? Utils.byteArrayToUtf8(this.value) : ""; + this.value = this.value ? byteArrayToStr(this.value) : ""; this.type = Dish.STRING; break; case Dish.NUMBER: - this.value = this.value ? parseFloat(Utils.byteArrayToUtf8(this.value)) : 0; + this.value = this.value ? parseFloat(byteArrayToStr(this.value)) : 0; this.type = Dish.NUMBER; break; + case Dish.ARRAY_BUFFER: + this.value = new Uint8Array(this.value).buffer; + this.type = Dish.ARRAY_BUFFER; + break; + case Dish.BIG_NUMBER: + try { + this.value = new BigNumber(byteArrayToStr(this.value)); + } catch (err) { + this.value = new BigNumber(NaN); + } + this.type = Dish.BIG_NUMBER; + break; default: break; } @@ -180,7 +225,7 @@ Dish.prototype.valid = function() { // 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" || + if (typeof this.value[i] !== "number" || this.value[i] < 0 || this.value[i] > 255) { return false; @@ -189,18 +234,41 @@ Dish.prototype.valid = function() { return true; case Dish.STRING: case Dish.HTML: - if (typeof this.value == "string") { - return true; - } - return false; + return typeof this.value === "string"; case Dish.NUMBER: - if (typeof this.value == "number") { - return true; - } - return false; + return typeof this.value === "number"; + case Dish.ARRAY_BUFFER: + return this.value instanceof ArrayBuffer; + case Dish.BIG_NUMBER: + return this.value instanceof BigNumber; 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} +*/ +Dish.prototype.size = function() { + 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; + default: + return -1; + } +}; + + export default Dish; diff --git a/src/core/FlowControl.js b/src/core/FlowControl.js index 4a94ffdf..f847ab25 100755 --- a/src/core/FlowControl.js +++ b/src/core/FlowControl.js @@ -56,6 +56,7 @@ const FlowControl = { // Run recipe over each tranche for (i = 0; i < inputs.length; i++) { + log.debug(`Entering tranche ${i + 1} of ${inputs.length}`); const dish = new Dish(inputs[i], inputType); try { progress = await recipe.execute(dish, 0); @@ -169,20 +170,19 @@ const FlowControl = { * @returns {Object} The updated state of the recipe. */ runJump: function(state) { - let ings = state.opList[state.progress].getIngValues(), - jumpNum = ings[0], - maxJumps = ings[1]; + const ings = state.opList[state.progress].getIngValues(), + label = ings[0], + maxJumps = ings[1], + jmpIndex = FlowControl._getLabelIndex(label, state); - if (jumpNum < 0) { - jumpNum--; - } - - if (state.numJumps >= maxJumps) { + if (state.numJumps >= maxJumps || jmpIndex === -1) { + log.debug("Maximum jumps reached or label cannot be found"); return state; } - state.progress += jumpNum; + state.progress = jmpIndex; state.numJumps++; + log.debug(`Jumping to label '${label}' at position ${jmpIndex} (jumps = ${state.numJumps})`); return state; }, @@ -198,23 +198,26 @@ const FlowControl = { * @returns {Object} The updated state of the recipe. */ runCondJump: function(state) { - let ings = state.opList[state.progress].getIngValues(), + const ings = state.opList[state.progress].getIngValues(), dish = state.dish, regexStr = ings[0], - jumpNum = ings[1], - maxJumps = ings[2]; + invert = ings[1], + label = ings[2], + maxJumps = ings[3], + jmpIndex = FlowControl._getLabelIndex(label, state); - if (jumpNum < 0) { - jumpNum--; - } - - if (state.numJumps >= maxJumps) { + if (state.numJumps >= maxJumps || jmpIndex === -1) { + log.debug("Maximum jumps reached or label cannot be found"); return state; } - if (regexStr !== "" && dish.get(Dish.STRING).search(regexStr) > -1) { - state.progress += jumpNum; - state.numJumps++; + if (regexStr !== "") { + let strMatch = dish.get(Dish.STRING).search(regexStr) > -1; + if (!invert && strMatch || invert && !strMatch) { + state.progress = jmpIndex; + state.numJumps++; + log.debug(`Jumping to label '${label}' at position ${jmpIndex} (jumps = ${state.numJumps})`); + } } return state; @@ -249,6 +252,27 @@ const FlowControl = { return state; }, + + /** + * Returns the index of a label. + * + * @private + * @param {Object} state + * @param {string} name + * @returns {number} + */ + _getLabelIndex: function(name, state) { + for (let o = 0; o < state.opList.length; o++) { + let operation = state.opList[o]; + if (operation.name === "Label"){ + let ings = operation.getIngValues(); + if (name === ings[0]) { + return o; + } + } + } + return -1; + }, }; export default FlowControl; diff --git a/src/core/Recipe.js b/src/core/Recipe.js index fdd06943..877c0ac1 100755 --- a/src/core/Recipe.js +++ b/src/core/Recipe.js @@ -146,18 +146,23 @@ Recipe.prototype.lastOpIndex = function(startIndex) { Recipe.prototype.execute = async function(dish, startFrom) { startFrom = startFrom || 0; let op, input, output, numJumps = 0, numRegisters = 0; + 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.getIngValues())}`); if (op.isDisabled()) { + log.debug("Operation is disabled, skipping"); continue; } if (op.isBreakpoint()) { + log.debug("Pausing at breakpoint"); return i; } try { input = dish.get(op.inputType); + log.debug("Executing operation"); if (op.isFlowControl()) { // Package up the current state @@ -193,6 +198,7 @@ Recipe.prototype.execute = async function(dish, startFrom) { } } + log.debug("Recipe complete"); return this.opList.length; }; diff --git a/src/core/Utils.js b/src/core/Utils.js index f2a992e5..a72a319c 100755 --- a/src/core/Utils.js +++ b/src/core/Utils.js @@ -1,4 +1,4 @@ -import CryptoJS from "crypto-js"; +import utf8 from "utf8"; /** @@ -64,58 +64,6 @@ const Utils = { }, - /** - * Adds leading zeros to strings - * - * @param {string} str - String to add leading characters to. - * @param {number} max - Maximum width of the string. - * @param {char} [chr='0'] - The character to pad with. - * @returns {string} - * - * @example - * // returns "0a" - * Utils.padLeft("a", 2); - * - * // returns "000a" - * Utils.padLeft("a", 4); - * - * // returns "xxxa" - * Utils.padLeft("a", 4, "x"); - * - * // returns "bcabchello" - * Utils.padLeft("hello", 10, "abc"); - */ - padLeft: function(str, max, chr) { - chr = chr || "0"; - let startIndex = chr.length - (max - str.length); - startIndex = startIndex < 0 ? 0 : startIndex; - return str.length < max ? - Utils.padLeft(chr.slice(startIndex, chr.length) + str, max, chr) : str; - }, - - - /** - * Adds trailing spaces to strings. - * - * @param {string} str - String to add trailing characters to. - * @param {number} max - Maximum width of the string. - * @param {char} [chr='0'] - The character to pad with. - * @returns {string} - * - * @example - * // returns "a " - * Utils.padRight("a", 4); - * - * // returns "axxx" - * Utils.padRight("a", 4, "x"); - */ - padRight: function(str, max, chr) { - chr = chr || " "; - return str.length < max ? - Utils.padRight(str + chr.slice(0, max-str.length), max, chr) : str; - }, - - /** * Adds trailing bytes to a byteArray. * @@ -152,14 +100,6 @@ const Utils = { }, - /** - * @alias Utils.padLeft - */ - pad: function(str, max, chr) { - return Utils.padLeft(str, max, chr); - }, - - /** * Truncates a long string to max length and adds suffix. * @@ -201,7 +141,7 @@ const Utils = { hex: function(c, length) { c = typeof c == "string" ? Utils.ord(c) : c; length = length || 2; - return Utils.pad(c.toString(16), length); + return c.toString(16).padStart(length, "0"); }, @@ -222,7 +162,7 @@ const Utils = { bin: function(c, length) { c = typeof c == "string" ? Utils.ord(c) : c; length = length || 8; - return Utils.pad(c.toString(2), length); + return c.toString(2).padStart(length, "0"); }, @@ -261,7 +201,7 @@ const Utils = { * Utils.parseEscapedChars("\\n"); */ parseEscapedChars: function(str) { - return str.replace(/(\\)?\\([nrtbf]|x[\da-f]{2})/g, function(m, a, b) { + return str.replace(/(\\)?\\([nrtbf]|x[\da-fA-F]{2})/g, function(m, a, b) { if (a === "\\") return "\\"+b; switch (b[0]) { case "n": @@ -340,6 +280,72 @@ const Utils = { }, + /** + * Coverts data of varying types to a byteArray. + * Accepts hex, Base64, UTF8 and Latin1 strings. + * + * @param {string} str + * @param {string} type - One of "Hex", "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"); + */ + convertToByteArray: function(str, type) { + switch (type.toLowerCase()) { + case "hex": + return Utils.fromHex(str); + case "base64": + return Utils.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 "Hex", "Base64", "UTF8" or "Latin1" + * @returns {string} + * + * @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"); + */ + convertToByteString: function(str, type) { + switch (type.toLowerCase()) { + case "hex": + return Utils.byteArrayToChars(Utils.fromHex(str)); + case "base64": + return Utils.byteArrayToChars(Utils.fromBase64(str, null, "byteArray")); + case "utf8": + return utf8.encode(str); + case "latin1": + default: + return str; + } + }, + + /** * Converts a string to a byte array. * Treats the string as UTF-8 if any values are over 255. @@ -381,17 +387,17 @@ const Utils = { * Utils.strToUtf8ByteArray("你好"); */ strToUtf8ByteArray: function(str) { - let wordArray = CryptoJS.enc.Utf8.parse(str), - byteArray = Utils.wordArrayToByteArray(wordArray); + const utf8Str = utf8.encode(str); - if (str.length !== wordArray.sigBytes) { + if (str.length !== utf8Str.length) { if (ENVIRONMENT_IS_WORKER()) { self.setOption("attemptHighlight", false); } else if (ENVIRONMENT_IS_WEB()) { window.app.options.attemptHighlight = false; } } - return byteArray; + + return Utils.strToByteArray(utf8Str); }, @@ -443,26 +449,21 @@ const Utils = { * Utils.byteArrayToUtf8([228,189,160,229,165,189]); */ byteArrayToUtf8: function(byteArray) { + const str = Utils.byteArrayToChars(byteArray); try { - // Try to output data as UTF-8 string - const words = []; - for (let i = 0; i < byteArray.length; i++) { - words[i >>> 2] |= byteArray[i] << (24 - (i % 4) * 8); - } - let wordArray = new CryptoJS.lib.WordArray.init(words, byteArray.length), - str = CryptoJS.enc.Utf8.stringify(wordArray); + const utf8Str = utf8.decode(str); - if (str.length !== wordArray.sigBytes) { + if (str.length !== utf8Str.length) { if (ENVIRONMENT_IS_WORKER()) { self.setOption("attemptHighlight", false); } else if (ENVIRONMENT_IS_WEB()) { window.app.options.attemptHighlight = false; } } - return str; + return utf8Str; } catch (err) { // If it fails, treat it as ANSI - return Utils.byteArrayToChars(byteArray); + return str; } }, @@ -470,7 +471,7 @@ const Utils = { /** * Converts a charcode array to a string. * - * @param {byteArray} byteArray + * @param {byteArray|Uint8Array} byteArray * @returns {string} * * @example @@ -491,33 +492,25 @@ const Utils = { /** - * Converts a CryptoJS.lib.WordArray to a byteArray. + * Converts an ArrayBuffer to a string. * - * @param {CryptoJS.lib.WordArray} wordArray - * @returns {byteArray} + * @param {ArrayBuffer} arrayBuffer + * @returns {string} * * @example - * // returns [84, 101, 115, 116] - * Utils.wordArrayToByteArray(CryptoJS.enc.Hex.parse("54657374")); + * // returns "hello" + * Utils.arrayBufferToStr(Uint8Array.from([104,101,108,108,111]).buffer); */ - wordArrayToByteArray: function(wordArray) { - if (wordArray.sigBytes <= 0) return []; - - let words = wordArray.words, - byteArray = []; - - for (let i = 0; i < wordArray.sigBytes; i++) { - byteArray.push((words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff); - } - - return byteArray; + arrayBufferToStr: function(arrayBuffer) { + const byteArray = Array.prototype.slice.call(new Uint8Array(arrayBuffer)); + return Utils.byteArrayToUtf8(byteArray); }, /** * Base64's the input byte array using the given alphabet, returning a string. * - * @param {byteArray|string} data + * @param {byteArray|Uint8Array|string} data * @param {string} [alphabet] * @returns {string} * @@ -636,7 +629,7 @@ const Utils = { /** * Convert a byte array into a hex string. * - * @param {byteArray} data + * @param {Uint8Array|byteArray} data * @param {string} [delim=" "] * @param {number} [padding=2] * @returns {string} @@ -656,7 +649,7 @@ const Utils = { let output = ""; for (let i = 0; i < data.length; i++) { - output += Utils.pad(data[i].toString(16), padding) + delim; + output += data[i].toString(16).padStart(padding, "0") + delim; } // Add \x or 0x to beginning @@ -859,7 +852,7 @@ const Utils = { * * fragment = *( pchar / "/" / "?" ) * query = *( pchar / "/" / "?" ) - * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" * pct-encoded = "%" HEXDIG HEXDIG * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" @@ -1248,21 +1241,6 @@ const Utils = { "None": /\s+/g // Included here to remove whitespace when there shouldn't be any }, - - /** - * A mapping of string formats to their classes in the CryptoJS library. - * @constant - */ - format: { - "Hex": CryptoJS.enc.Hex, - "Base64": CryptoJS.enc.Base64, - "UTF8": CryptoJS.enc.Utf8, - "UTF16": CryptoJS.enc.Utf16, - "UTF16LE": CryptoJS.enc.Utf16LE, - "UTF16BE": CryptoJS.enc.Utf16BE, - "Latin1": CryptoJS.enc.Latin1, - }, - }; export default Utils; @@ -1376,29 +1354,43 @@ String.prototype.count = function(chr) { }; -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Library overrides /////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * Override for the CryptoJS Hex encoding parser to remove whitespace before attempting to parse - * the hex string. - * - * @param {string} hexStr - * @returns {CryptoJS.lib.WordArray} +/* + * Polyfills */ -CryptoJS.enc.Hex.parse = function (hexStr) { - // Remove whitespace - hexStr = hexStr.replace(/\s/g, ""); - // Shortcut - const hexStrLength = hexStr.length; +// https://github.com/uxitten/polyfill/blob/master/string.polyfill.js +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart +if (!String.prototype.padStart) { + String.prototype.padStart = function padStart(targetLength, padString) { + targetLength = targetLength>>0; //floor if number or convert non-number to 0; + padString = String((typeof padString !== "undefined" ? padString : " ")); + if (this.length > targetLength) { + return String(this); + } else { + targetLength = targetLength-this.length; + if (targetLength > padString.length) { + padString += padString.repeat(targetLength/padString.length); //append to original to ensure we are longer than needed + } + return padString.slice(0, targetLength) + String(this); + } + }; +} - // Convert - const words = []; - for (let i = 0; i < hexStrLength; i += 2) { - words[i >>> 3] |= parseInt(hexStr.substr(i, 2), 16) << (24 - (i % 8) * 4); - } - return new CryptoJS.lib.WordArray.init(words, hexStrLength / 2); -}; +// https://github.com/uxitten/polyfill/blob/master/string.polyfill.js +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padEnd +if (!String.prototype.padEnd) { + String.prototype.padEnd = function padEnd(targetLength, padString) { + targetLength = targetLength>>0; //floor if number or convert non-number to 0; + padString = String((typeof padString !== "undefined" ? padString : " ")); + if (this.length > targetLength) { + return String(this); + } else { + targetLength = targetLength-this.length; + if (targetLength > padString.length) { + padString += padString.repeat(targetLength/padString.length); //append to original to ensure we are longer than needed + } + return String(this) + padString.slice(0, targetLength); + } + }; +} diff --git a/src/core/config/Categories.js b/src/core/config/Categories.js index f04b5fd9..2038ad50 100755 --- a/src/core/config/Categories.js +++ b/src/core/config/Categories.js @@ -79,8 +79,8 @@ const Categories = [ "DES Decrypt", "Triple DES Encrypt", "Triple DES Decrypt", - "Rabbit Encrypt", - "Rabbit Decrypt", + "RC2 Encrypt", + "RC2 Decrypt", "RC4", "RC4 Drop", "ROT13", @@ -99,6 +99,7 @@ const Categories = [ "Substitute", "Derive PBKDF2 key", "Derive EVP key", + "Pseudo-Random Number Generator", ] }, { @@ -113,7 +114,7 @@ const Categories = [ ] }, { - name: "Logical operations", + name: "Arithmetic / Logic", ops: [ "XOR", "XOR Brute Force", @@ -122,6 +123,13 @@ const Categories = [ "AND", "ADD", "SUB", + "Sum", + "Subtract", + "Multiply", + "Divide", + "Mean", + "Median", + "Standard Deviation", "Bit shift left", "Bit shift right", "Rotate left", @@ -191,6 +199,7 @@ const Categories = [ "Parse colour code", "Escape string", "Unescape string", + "Pseudo-Random Number Generator", ] }, { @@ -288,6 +297,7 @@ const Categories = [ "XPath expression", "JPath expression", "CSS selector", + "PHP Deserialize", "Microsoft Script Decoder", "Strip HTML tags", "Diff", @@ -301,9 +311,11 @@ const Categories = [ ops: [ "Entropy", "Frequency distribution", + "Chi Square", "Detect File Type", "Scan for Embedded Files", "Disassemble x86", + "Pseudo-Random Number Generator", "Generate UUID", "Generate TOTP", "Generate HOTP", @@ -319,6 +331,7 @@ const Categories = [ "Fork", "Merge", "Register", + "Label", "Jump", "Conditional Jump", "Return", diff --git a/src/core/config/OperationConfig.js b/src/core/config/OperationConfig.js old mode 100755 new mode 100644 index 5d3f7020..779f91cf --- a/src/core/config/OperationConfig.js +++ b/src/core/config/OperationConfig.js @@ -1,3 +1,4 @@ +import Arithmetic from "../operations/Arithmetic.js"; import Base from "../operations/Base.js"; import Base58 from "../operations/Base58.js"; import Base64 from "../operations/Base64.js"; @@ -26,6 +27,7 @@ import JS from "../operations/JS.js"; import MAC from "../operations/MAC.js"; import MorseCode from "../operations/MorseCode.js"; import NetBIOS from "../operations/NetBIOS.js"; +import PHP from "../operations/PHP.js"; import PGP from "../operations/PGP.js"; import PublicKey from "../operations/PublicKey.js"; import Punycode from "../operations/Punycode.js"; @@ -137,15 +139,15 @@ const OperationConfig = { }, "Jump": { module: "Default", - description: "Jump forwards or backwards over the specified number of operations.", + description: "Jump forwards or backwards to the specified Label", inputType: "string", outputType: "string", flowControl: true, args: [ { - name: "Number of operations to jump over", - type: "number", - value: 0 + name: "Label name", + type: "string", + value: "" }, { name: "Maximum jumps (if jumping backwards)", @@ -156,7 +158,7 @@ const OperationConfig = { }, "Conditional Jump": { module: "Default", - description: "Conditionally jump forwards or backwards over the specified number of operations based on whether the data matches the specified regular expression.", + description: "Conditionally jump forwards or backwards to the specified Label based on whether the data matches the specified regular expression.", inputType: "string", outputType: "string", flowControl: true, @@ -167,9 +169,14 @@ const OperationConfig = { value: "" }, { - name: "Number of operations to jump over if match found", - type: "number", - value: 0 + name: "Invert match", + type: "boolean", + value: false + }, + { + name: "Label name", + type: "shortString", + value: "" }, { name: "Maximum jumps (if jumping backwards)", @@ -178,6 +185,20 @@ const OperationConfig = { } ] }, + "Label": { + module: "Default", + description: "Provides a location for conditional and fixed jumps to redirect execution to.", + inputType: "string", + outputType: "string", + flowControl: true, + args: [ + { + name: "Name", + type: "shortString", + value: "" + } + ] + }, "Return": { module: "Default", description: "End execution of operations at this point in the recipe.", @@ -225,7 +246,7 @@ const OperationConfig = { description: "Base64 is a notation for encoding arbitrary byte data using a restricted set of symbols that can be conveniently used by humans and processed by computers.

This operation encodes data in an ASCII Base64 string.

e.g. hello becomes aGVsbG8=", highlight: "func", highlightReverse: "func", - inputType: "byteArray", + inputType: "ArrayBuffer", outputType: "string", args: [ { @@ -500,6 +521,97 @@ const OperationConfig = { } ] }, + "Sum": { + module: "Default", + description: "Adds together a list of numbers. If an item in the string is not a number it is excluded from the list.

e.g. 0x0a 8 .5 becomes 18.5", + inputType: "string", + outputType: "BigNumber", + args: [ + { + name: "Delimiter", + type: "option", + value: Arithmetic.DELIM_OPTIONS + } + ] + }, + "Subtract": { + module: "Default", + description: "Subtracts a list of numbers. If an item in the string is not a number it is excluded from the list.

e.g. 0x0a 8 .5 becomes 1.5", + inputType: "string", + outputType: "BigNumber", + args: [ + { + name: "Delimiter", + type: "option", + value: Arithmetic.DELIM_OPTIONS + } + ] + }, + "Multiply": { + module: "Default", + description: "Multiplies a list of numbers. If an item in the string is not a number it is excluded from the list.

e.g. 0x0a 8 .5 becomes 40", + inputType: "string", + outputType: "BigNumber", + args: [ + { + name: "Delimiter", + type: "option", + value: Arithmetic.DELIM_OPTIONS + } + ] + }, + "Divide": { + module: "Default", + description: "Divides a list of numbers. If an item in the string is not a number it is excluded from the list.

e.g. 0x0a 8 .5 becomes 2.5", + inputType: "string", + outputType: "BigNumber", + args: [ + { + name: "Delimiter", + type: "option", + value: Arithmetic.DELIM_OPTIONS + } + ] + }, + "Mean": { + module: "Default", + description: "Computes the mean (average) of a number list. If an item in the string is not a number it is excluded from the list.

e.g. 0x0a 8 .5 .5 becomes 4.75", + inputType: "string", + outputType: "BigNumber", + args: [ + { + name: "Delimiter", + type: "option", + value: Arithmetic.DELIM_OPTIONS + } + ] + }, + "Median": { + module: "Default", + description: "Computes the median of a number list. If an item in the string is not a number it is excluded from the list.

e.g. 0x0a 8 1 .5 becomes 4.5", + inputType: "string", + outputType: "BigNumber", + args: [ + { + name: "Delimiter", + type: "option", + value: Arithmetic.DELIM_OPTIONS + } + ] + }, + "Standard Deviation": { + module: "Default", + description: "Computes the standard deviation of a number list. If an item in the string is not a number it is excluded from the list.

e.g. 0x0a 8 .5 becomes 4.089281382128433", + inputType: "string", + outputType: "BigNumber", + args: [ + { + name: "Delimiter", + type: "option", + value: Arithmetic.DELIM_OPTIONS + } + ] + }, "From Hex": { module: "Default", description: "Converts a hexadecimal byte string back into its raw value.

e.g. ce 93 ce b5 ce b9 ce ac 20 cf 83 ce bf cf 85 0a becomes the UTF-8 encoded string Γειά σου", @@ -520,7 +632,7 @@ const OperationConfig = { description: "Converts the input string to hexadecimal bytes separated by the specified delimiter.

e.g. The UTF-8 encoded string Γειά σου becomes ce 93 ce b5 ce b9 ce ac 20 cf 83 ce bf cf 85 0a", highlight: "func", highlightReverse: "func", - inputType: "byteArray", + inputType: "ArrayBuffer", outputType: "string", args: [ { @@ -671,7 +783,7 @@ const OperationConfig = { description: "Creates a hexdump of the input data, displaying both the hexadecimal values of each byte and an ASCII representation alongside.", highlight: "func", highlightReverse: "func", - inputType: "byteArray", + inputType: "ArrayBuffer", outputType: "string", args: [ { @@ -695,7 +807,7 @@ const OperationConfig = { module: "Default", description: "Converts a number to decimal from a given numerical base.", inputType: "string", - outputType: "number", + outputType: "BigNumber", args: [ { name: "Radix", @@ -707,7 +819,7 @@ const OperationConfig = { "To Base": { module: "Default", description: "Converts a decimal number to a given numerical base.", - inputType: "number", + inputType: "BigNumber", outputType: "string", args: [ { @@ -761,14 +873,14 @@ const OperationConfig = { ] }, "URL Decode": { - module: "Default", + module: "URL", description: "Converts URI/URL percent-encoded characters back to their raw values.

e.g. %3d becomes =", inputType: "string", outputType: "string", args: [] }, "URL Encode": { - module: "Default", + module: "URL", description: "Encodes problematic characters into percent-encoding, a format supported by URIs/URLs.

e.g. = becomes %3d", inputType: "string", outputType: "string", @@ -781,7 +893,7 @@ const OperationConfig = { ] }, "Parse URI": { - module: "Default", + module: "URL", description: "Pretty prints complicated Uniform Resource Identifier (URI) strings for ease of reading. Particularly useful for Uniform Resource Locators (URLs) with a lot of arguments.", inputType: "string", outputType: "string", @@ -991,287 +1103,7 @@ const OperationConfig = { }, "AES Decrypt": { module: "Ciphers", - description: "To successfully decrypt AES, you need either:The IV should be the first 16 bytes of encrypted material.", - inputType: "string", - outputType: "string", - args: [ - { - name: "Passphrase/Key", - type: "toggleString", - value: "", - toggleValues: Cipher.IO_FORMAT2 - }, - { - name: "IV", - type: "toggleString", - value: "", - toggleValues: Cipher.IO_FORMAT1 - }, - { - name: "Salt", - type: "toggleString", - value: "", - toggleValues: Cipher.IO_FORMAT1 - }, - { - name: "Mode", - type: "option", - value: Cipher.MODES - }, - { - name: "Padding", - type: "option", - value: Cipher.PADDING - }, - { - name: "Input format", - type: "option", - value: Cipher.IO_FORMAT1 - }, - { - name: "Output format", - type: "option", - value: Cipher.IO_FORMAT2 - }, - ] - }, - "AES Encrypt": { - module: "Ciphers", - description: "Input: Either enter a passphrase (which will be used to derive a key using the OpenSSL KDF) or both the key and IV.

Advanced Encryption Standard (AES) is a U.S. Federal Information Processing Standard (FIPS). It was selected after a 5-year process where 15 competing designs were evaluated.

AES-128, AES-192, and AES-256 are supported. The variant will be chosen based on the size of the key passed in. If a passphrase is used, a 256-bit key will be generated.", - inputType: "string", - outputType: "string", - args: [ - { - name: "Passphrase/Key", - type: "toggleString", - value: "", - toggleValues: Cipher.IO_FORMAT2 - }, - { - name: "IV", - type: "toggleString", - value: "", - toggleValues: Cipher.IO_FORMAT1 - }, - { - name: "Salt", - type: "toggleString", - value: "", - toggleValues: Cipher.IO_FORMAT1 - }, - { - name: "Mode", - type: "option", - value: Cipher.MODES - }, - { - name: "Padding", - type: "option", - value: Cipher.PADDING - }, - { - name: "Output result", - type: "option", - value: Cipher.RESULT_TYPE - }, - { - name: "Output format", - type: "option", - value: Cipher.IO_FORMAT1 - }, - ] - }, - "DES Decrypt": { - module: "Ciphers", - description: "To successfully decrypt DES, you need either:The IV should be the first 8 bytes of encrypted material.", - inputType: "string", - outputType: "string", - args: [ - { - name: "Passphrase/Key", - type: "toggleString", - value: "", - toggleValues: Cipher.IO_FORMAT2 - }, - { - name: "IV", - type: "toggleString", - value: "", - toggleValues: Cipher.IO_FORMAT1 - - }, - { - name: "Salt", - type: "toggleString", - value: "", - toggleValues: Cipher.IO_FORMAT1 - }, - { - name: "Mode", - type: "option", - value: Cipher.MODES - }, - { - name: "Padding", - type: "option", - value: Cipher.PADDING - }, - { - name: "Input format", - type: "option", - value: Cipher.IO_FORMAT1 - }, - { - name: "Output format", - type: "option", - value: Cipher.IO_FORMAT2 - }, - ] - }, - "DES Encrypt": { - module: "Ciphers", - description: "Input: Either enter a passphrase (which will be used to derive a key using the OpenSSL KDF) or both the key and IV.

DES is a previously dominant algorithm for encryption, and was published as an official U.S. Federal Information Processing Standard (FIPS). It is now considered to be insecure due to its small key size.", - inputType: "string", - outputType: "string", - args: [ - { - name: "Passphrase/Key", - type: "toggleString", - value: "", - toggleValues: Cipher.IO_FORMAT2 - }, - { - name: "IV", - type: "toggleString", - value: "", - toggleValues: Cipher.IO_FORMAT1 - - }, - { - name: "Salt", - type: "toggleString", - value: "", - toggleValues: Cipher.IO_FORMAT1 - }, - { - name: "Mode", - type: "option", - value: Cipher.MODES - }, - { - name: "Padding", - type: "option", - value: Cipher.PADDING - }, - { - name: "Output result", - type: "option", - value: Cipher.RESULT_TYPE - }, - { - name: "Output format", - type: "option", - value: Cipher.IO_FORMAT1 - }, - ] - }, - "Triple DES Decrypt": { - module: "Ciphers", - description: "To successfully decrypt Triple DES, you need either:The IV should be the first 8 bytes of encrypted material.", - inputType: "string", - outputType: "string", - args: [ - { - name: "Passphrase/Key", - type: "toggleString", - value: "", - toggleValues: Cipher.IO_FORMAT2 - }, - { - name: "IV", - type: "toggleString", - value: "", - toggleValues: Cipher.IO_FORMAT1 - - }, - { - name: "Salt", - type: "toggleString", - value: "", - toggleValues: Cipher.IO_FORMAT1 - }, - { - name: "Mode", - type: "option", - value: Cipher.MODES - }, - { - name: "Padding", - type: "option", - value: Cipher.PADDING - }, - { - name: "Input format", - type: "option", - value: Cipher.IO_FORMAT1 - }, - { - name: "Output format", - type: "option", - value: Cipher.IO_FORMAT2 - }, - ] - }, - "Triple DES Encrypt": { - module: "Ciphers", - description: "Input: Either enter a passphrase (which will be used to derive a key using the OpenSSL KDF) or both the key and IV.

Triple DES applies DES three times to each block to increase key size.", - inputType: "string", - outputType: "string", - args: [ - { - name: "Passphrase/Key", - type: "toggleString", - value: "", - toggleValues: Cipher.IO_FORMAT2 - }, - { - name: "IV", - type: "toggleString", - value: "", - toggleValues: Cipher.IO_FORMAT1 - - }, - { - name: "Salt", - type: "toggleString", - value: "", - toggleValues: Cipher.IO_FORMAT1 - }, - { - name: "Mode", - type: "option", - value: Cipher.MODES - }, - { - name: "Padding", - type: "option", - value: Cipher.PADDING - }, - { - name: "Output result", - type: "option", - value: Cipher.RESULT_TYPE - }, - { - name: "Output format", - type: "option", - value: Cipher.IO_FORMAT1 - }, - ] - }, - "Blowfish Decrypt": { - module: "Ciphers", - description: "Blowfish is a symmetric-key block cipher designed in 1993 by Bruce Schneier and included in a large number of cipher suites and encryption products. AES now receives more attention.", + description: "Advanced Encryption Standard (AES) is a U.S. Federal Information Processing Standard (FIPS). It was selected after a 5-year process where 15 competing designs were evaluated.

Key: The following algorithms will be used based on the size of the key:

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

Padding: In CBC and ECB mode, PKCS#7 padding will be used.

GCM Tag: This field is ignored unless 'GCM' mode is used.", inputType: "string", outputType: "string", args: [ @@ -1279,7 +1111,229 @@ const OperationConfig = { name: "Key", type: "toggleString", value: "", - toggleValues: Cipher.IO_FORMAT2 + toggleValues: Cipher.IO_FORMAT1 + }, + { + name: "IV", + type: "toggleString", + value: "", + toggleValues: Cipher.IO_FORMAT1 + }, + { + name: "Mode", + type: "option", + value: Cipher.AES_MODES + }, + { + name: "Input", + type: "option", + value: Cipher.IO_FORMAT4 + }, + { + name: "Output", + type: "option", + value: Cipher.IO_FORMAT3 + }, + { + name: "GCM Tag", + type: "toggleString", + value: "", + toggleValues: Cipher.IO_FORMAT1 + }, + ] + }, + "AES Encrypt": { + module: "Ciphers", + description: "Advanced Encryption Standard (AES) is a U.S. Federal Information Processing Standard (FIPS). It was selected after a 5-year process where 15 competing designs were evaluated.

Key: The following algorithms will be used based on the size of the key:You can generate a password-based key using one of the KDF operations.

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

Padding: In CBC and ECB mode, PKCS#7 padding will be used.", + inputType: "string", + outputType: "string", + args: [ + { + name: "Key", + type: "toggleString", + value: "", + toggleValues: Cipher.IO_FORMAT1 + }, + { + name: "IV", + type: "toggleString", + value: "", + toggleValues: Cipher.IO_FORMAT1 + }, + { + name: "Mode", + type: "option", + value: Cipher.AES_MODES + }, + { + name: "Input", + type: "option", + value: Cipher.IO_FORMAT3 + }, + { + name: "Output", + type: "option", + value: Cipher.IO_FORMAT4 + }, + ] + }, + "DES Decrypt": { + module: "Ciphers", + description: "DES is a previously dominant algorithm for encryption, and was published as an official U.S. Federal Information Processing Standard (FIPS). It is now considered to be insecure due to its small key size.

Key: DES uses a key length of 8 bytes (64 bits).
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.", + inputType: "string", + outputType: "string", + args: [ + { + name: "Key", + type: "toggleString", + value: "", + toggleValues: Cipher.IO_FORMAT1 + }, + { + name: "IV", + type: "toggleString", + value: "", + toggleValues: Cipher.IO_FORMAT1 + }, + { + name: "Mode", + type: "option", + value: Cipher.DES_MODES + }, + { + name: "Input", + type: "option", + value: Cipher.IO_FORMAT4 + }, + { + name: "Output", + type: "option", + value: Cipher.IO_FORMAT3 + }, + ] + }, + "DES Encrypt": { + module: "Ciphers", + description: "DES is a previously dominant algorithm for encryption, and was published as an official U.S. Federal Information Processing Standard (FIPS). It is now considered to be insecure due to its small key size.

Key: DES uses a key length of 8 bytes (64 bits).
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.", + inputType: "string", + outputType: "string", + args: [ + { + name: "Key", + type: "toggleString", + value: "", + toggleValues: Cipher.IO_FORMAT1 + }, + { + name: "IV", + type: "toggleString", + value: "", + toggleValues: Cipher.IO_FORMAT1 + }, + { + name: "Mode", + type: "option", + value: Cipher.DES_MODES + }, + { + name: "Input", + type: "option", + value: Cipher.IO_FORMAT3 + }, + { + name: "Output", + type: "option", + value: Cipher.IO_FORMAT4 + }, + ] + }, + "Triple DES Decrypt": { + module: "Ciphers", + 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).
DES uses a key length of 8 bytes (64 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.", + inputType: "string", + outputType: "string", + args: [ + { + name: "Key", + type: "toggleString", + value: "", + toggleValues: Cipher.IO_FORMAT1 + }, + { + name: "IV", + type: "toggleString", + value: "", + toggleValues: Cipher.IO_FORMAT1 + }, + { + name: "Mode", + type: "option", + value: Cipher.DES_MODES + }, + { + name: "Input", + type: "option", + value: Cipher.IO_FORMAT4 + }, + { + name: "Output", + type: "option", + value: Cipher.IO_FORMAT3 + }, + ] + }, + "Triple DES Encrypt": { + module: "Ciphers", + 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).
DES uses a key length of 8 bytes (64 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.", + inputType: "string", + outputType: "string", + args: [ + { + name: "Key", + type: "toggleString", + value: "", + toggleValues: Cipher.IO_FORMAT1 + }, + { + name: "IV", + type: "toggleString", + value: "", + toggleValues: Cipher.IO_FORMAT1 + }, + { + name: "Mode", + type: "option", + value: Cipher.DES_MODES + }, + { + name: "Input", + type: "option", + value: Cipher.IO_FORMAT3 + }, + { + name: "Output", + type: "option", + value: Cipher.IO_FORMAT4 + }, + ] + }, + "Blowfish Decrypt": { + module: "Ciphers", + description: "Blowfish is a symmetric-key block cipher designed in 1993 by Bruce Schneier and included in a large number of cipher suites and encryption products. AES now receives more attention.

IV: The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.", + inputType: "string", + outputType: "string", + args: [ + { + name: "Key", + type: "toggleString", + value: "", + toggleValues: Cipher.IO_FORMAT1 + }, + { + name: "IV", + type: "toggleString", + value: "", + toggleValues: Cipher.IO_FORMAT1 }, { name: "Mode", @@ -1287,7 +1341,12 @@ const OperationConfig = { value: Cipher.BLOWFISH_MODES }, { - name: "Input format", + name: "Input", + type: "option", + value: Cipher.BLOWFISH_OUTPUT_TYPES + }, + { + name: "Output", type: "option", value: Cipher.IO_FORMAT3 }, @@ -1295,7 +1354,7 @@ const OperationConfig = { }, "Blowfish Encrypt": { module: "Ciphers", - description: "Blowfish is a symmetric-key block cipher designed in 1993 by Bruce Schneier and included in a large number of cipher suites and encryption products. AES now receives more attention.", + description: "Blowfish is a symmetric-key block cipher designed in 1993 by Bruce Schneier and included in a large number of cipher suites and encryption products. AES now receives more attention.

IV: The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.", inputType: "string", outputType: "string", args: [ @@ -1303,7 +1362,13 @@ const OperationConfig = { name: "Key", type: "toggleString", value: "", - toggleValues: Cipher.IO_FORMAT2 + toggleValues: Cipher.IO_FORMAT1 + }, + { + name: "IV", + type: "toggleString", + value: "", + toggleValues: Cipher.IO_FORMAT1 }, { name: "Mode", @@ -1311,109 +1376,20 @@ const OperationConfig = { value: Cipher.BLOWFISH_MODES }, { - name: "Output format", + name: "Input", type: "option", value: Cipher.IO_FORMAT3 }, - ] - }, - "Rabbit Decrypt": { - module: "Ciphers", - description: "To successfully decrypt Rabbit, you need either:The IV should be the first 8 bytes of encrypted material.", - inputType: "string", - outputType: "string", - args: [ { - name: "Passphrase/Key", - type: "toggleString", - value: "", - toggleValues: Cipher.IO_FORMAT2 - }, - { - name: "IV", - type: "toggleString", - value: "", - toggleValues: Cipher.IO_FORMAT1 - - }, - { - name: "Salt", - type: "toggleString", - value: "", - toggleValues: Cipher.IO_FORMAT1 - }, - { - name: "Mode", + name: "Output", type: "option", - value: Cipher.MODES - }, - { - name: "Padding", - type: "option", - value: Cipher.PADDING - }, - { - name: "Input format", - type: "option", - value: Cipher.IO_FORMAT1 - }, - { - name: "Output format", - type: "option", - value: Cipher.IO_FORMAT2 - }, - ] - }, - "Rabbit Encrypt": { - module: "Ciphers", - description: "Input: Either enter a passphrase (which will be used to derive a key using the OpenSSL KDF) or both the key and IV.

Rabbit is a high-performance stream cipher and a finalist in the eSTREAM Portfolio. It is one of the four designs selected after a 3 1/2 year process where 22 designs were evaluated.", - inputType: "string", - outputType: "string", - args: [ - { - name: "Passphrase/Key", - type: "toggleString", - value: "", - toggleValues: Cipher.IO_FORMAT2 - }, - { - name: "IV", - type: "toggleString", - value: "", - toggleValues: Cipher.IO_FORMAT1 - - }, - { - name: "Salt", - type: "toggleString", - value: "", - toggleValues: Cipher.IO_FORMAT1 - }, - { - name: "Mode", - type: "option", - value: Cipher.MODES - }, - { - name: "Padding", - type: "option", - value: Cipher.PADDING - }, - { - name: "Output result", - type: "option", - value: Cipher.RESULT_TYPE - }, - { - name: "Output format", - type: "option", - value: Cipher.IO_FORMAT1 + value: Cipher.BLOWFISH_OUTPUT_TYPES }, ] }, "RC4": { module: "Ciphers", - description: "RC4 is a widely-used stream cipher. It is used in popular protocols such as SSL and WEP. Although remarkable for its simplicity and speed, the algorithm's history doesn't inspire confidence in its security.", + description: "RC4 (also known as ARC4) is a widely-used stream cipher designed by Ron Rivest. It is used in popular protocols such as SSL and WEP. Although remarkable for its simplicity and speed, the algorithm's history doesn't inspire confidence in its security.", highlight: true, highlightReverse: true, inputType: "string", @@ -1423,17 +1399,17 @@ const OperationConfig = { name: "Passphrase", type: "toggleString", value: "", - toggleValues: Cipher.IO_FORMAT2 + toggleValues: Cipher.RC4_KEY_FORMAT }, { name: "Input format", type: "option", - value: Cipher.IO_FORMAT4 + value: Cipher.CJS_IO_FORMAT }, { name: "Output format", type: "option", - value: Cipher.IO_FORMAT4 + value: Cipher.CJS_IO_FORMAT }, ] }, @@ -1449,17 +1425,17 @@ const OperationConfig = { name: "Passphrase", type: "toggleString", value: "", - toggleValues: Cipher.IO_FORMAT2 + toggleValues: Cipher.RC4_KEY_FORMAT }, { name: "Input format", type: "option", - value: Cipher.IO_FORMAT4 + value: Cipher.CJS_IO_FORMAT }, { name: "Output format", type: "option", - value: Cipher.IO_FORMAT4 + value: Cipher.CJS_IO_FORMAT }, { name: "Number of bytes to drop", @@ -1468,50 +1444,96 @@ const OperationConfig = { }, ] }, - "Derive PBKDF2 key": { + "RC2 Decrypt": { module: "Ciphers", - description: "PBKDF2 is a password-based key derivation function. In many applications of cryptography, user security is ultimately dependent on a password, and because a password usually can't be used directly as a cryptographic key, some processing is required.

A salt provides a large set of keys for any given password, and an iteration count increases the cost of producing keys from a password, thereby also increasing the difficulty of attack.

Enter your passphrase as the input and then set the relevant options to generate a key.", + description: "RC2 (also known as ARC2) is a symmetric-key block cipher designed by Ron Rivest in 1987. 'RC' stands for 'Rivest Cipher'.

Key: RC2 uses a variable size key.

IV: To run the cipher in CBC mode, the Initialization Vector should be 8 bytes long. If the IV is left blank, the cipher will run in ECB mode.

Padding: In both CBC and ECB mode, PKCS#7 padding will be used.", inputType: "string", outputType: "string", args: [ { - name: "Key size", - type: "number", - value: Cipher.KDF_KEY_SIZE + name: "Key", + type: "toggleString", + value: "", + toggleValues: Cipher.IO_FORMAT1 }, { - name: "Iterations", - type: "number", - value: Cipher.KDF_ITERATIONS + name: "IV", + type: "toggleString", + value: "", + toggleValues: Cipher.IO_FORMAT1 }, { - name: "Hashing function", + name: "Input", type: "option", - value: Cipher.HASHERS + value: Cipher.IO_FORMAT4 }, { - name: "Salt (hex)", - type: "string", - value: "" - }, - { - name: "Input format", - type: "option", - value: Cipher.IO_FORMAT2 - }, - { - name: "Output format", + name: "Output", type: "option", value: Cipher.IO_FORMAT3 }, ] }, - "Derive EVP key": { + "RC2 Encrypt": { module: "Ciphers", - description: "EVP is a password-based key derivation function used extensively in OpenSSL. In many applications of cryptography, user security is ultimately dependent on a password, and because a password usually can't be used directly as a cryptographic key, some processing is required.

A salt provides a large set of keys for any given password, and an iteration count increases the cost of producing keys from a password, thereby also increasing the difficulty of attack.

Enter your passphrase as the input and then set the relevant options to generate a key.", + description: "RC2 (also known as ARC2) is a symmetric-key block cipher designed by Ron Rivest in 1987. 'RC' stands for 'Rivest Cipher'.

Key: RC2 uses a variable size key.

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

IV: To run the cipher in CBC mode, the Initialization Vector should be 8 bytes long. If the IV is left blank, the cipher will run in ECB mode.

Padding: In both CBC and ECB mode, PKCS#7 padding will be used.", inputType: "string", outputType: "string", args: [ + { + name: "Key", + type: "toggleString", + value: "", + toggleValues: Cipher.IO_FORMAT1 + }, + { + name: "IV", + type: "toggleString", + value: "", + toggleValues: Cipher.IO_FORMAT1 + }, + { + name: "Input", + type: "option", + value: Cipher.IO_FORMAT3 + }, + { + name: "Output", + type: "option", + value: Cipher.IO_FORMAT4 + }, + ] + }, + "Pseudo-Random Number Generator": { + module: "Ciphers", + description: "A cryptographically-secure pseudo-random number generator (PRNG).

This operation uses the browser's built-in crypto.getRandomValues() method if available. If this cannot be found, it falls back to a Fortuna-based PRNG algorithm.", + inputType: "string", + outputType: "string", + args: [ + { + name: "Number of bytes", + type: "number", + value: Cipher.PRNG_BYTES + }, + { + name: "Output as", + type: "option", + value: Cipher.PRNG_OUTPUT + } + ] + }, + "Derive PBKDF2 key": { + module: "Ciphers", + description: "PBKDF2 is a password-based key derivation function. It is part of RSA Laboratories' Public-Key Cryptography Standards (PKCS) series, specifically PKCS #5 v2.0, also published as Internet Engineering Task Force's RFC 2898.

In many applications of cryptography, user security is ultimately dependent on a password, and because a password usually can't be used directly as a cryptographic key, some processing is required.

A salt provides a large set of keys for any given password, and an iteration count increases the cost of producing keys from a password, thereby also increasing the difficulty of attack.

If you leave the salt argument empty, a random salt will be generated.", + inputType: "string", + outputType: "string", + args: [ + { + name: "Passphrase", + type: "toggleString", + value: "", + toggleValues: Cipher.IO_FORMAT2 + }, { name: "Key size", type: "number", @@ -1528,19 +1550,45 @@ const OperationConfig = { value: Cipher.HASHERS }, { - name: "Salt (hex)", - type: "string", - value: "" + name: "Salt", + type: "toggleString", + value: "", + toggleValues: Cipher.IO_FORMAT1 + }, + ] + }, + "Derive EVP key": { + module: "Ciphers", + description: "EVP is a password-based key derivation function (PBKDF) used extensively in OpenSSL. In many applications of cryptography, user security is ultimately dependent on a password, and because a password usually can't be used directly as a cryptographic key, some processing is required.

A salt provides a large set of keys for any given password, and an iteration count increases the cost of producing keys from a password, thereby also increasing the difficulty of attack.

If you leave the salt argument empty, a random salt will be generated.", + inputType: "string", + outputType: "string", + args: [ + { + name: "Passphrase", + type: "toggleString", + value: "", + toggleValues: Cipher.IO_FORMAT2 }, { - name: "Input format", - type: "option", - value: Cipher.IO_FORMAT2 + name: "Key size", + type: "number", + value: Cipher.KDF_KEY_SIZE }, { - name: "Output format", + name: "Iterations", + type: "number", + value: Cipher.KDF_ITERATIONS + }, + { + name: "Hashing function", type: "option", - value: Cipher.IO_FORMAT3 + value: Cipher.HASHERS + }, + { + name: "Salt", + type: "toggleString", + value: "", + toggleValues: Cipher.IO_FORMAT1 }, ] }, @@ -1866,9 +1914,9 @@ const OperationConfig = { }, "Drop bytes": { module: "Default", - description: "Cuts the specified number of bytes out of the data.", - inputType: "byteArray", - outputType: "byteArray", + description: "Cuts a slice of the specified number of bytes out of the data.", + inputType: "ArrayBuffer", + outputType: "ArrayBuffer", args: [ { name: "Start", @@ -1890,8 +1938,8 @@ const OperationConfig = { "Take bytes": { module: "Default", description: "Takes a slice of the specified number of bytes from the data.", - inputType: "byteArray", - outputType: "byteArray", + inputType: "ArrayBuffer", + outputType: "ArrayBuffer", args: [ { name: "Start", @@ -2468,8 +2516,8 @@ const OperationConfig = { "Convert distance": { module: "Default", description: "Converts a unit of distance to another format.", - inputType: "number", - outputType: "number", + inputType: "BigNumber", + outputType: "BigNumber", args: [ { name: "Input units", @@ -2486,8 +2534,8 @@ const OperationConfig = { "Convert area": { module: "Default", description: "Converts a unit of area to another format.", - inputType: "number", - outputType: "number", + inputType: "BigNumber", + outputType: "BigNumber", args: [ { name: "Input units", @@ -2504,8 +2552,8 @@ const OperationConfig = { "Convert mass": { module: "Default", description: "Converts a unit of mass to another format.", - inputType: "number", - outputType: "number", + inputType: "BigNumber", + outputType: "BigNumber", args: [ { name: "Input units", @@ -2522,8 +2570,8 @@ const OperationConfig = { "Convert speed": { module: "Default", description: "Converts a unit of speed to another format.", - inputType: "number", - outputType: "number", + inputType: "BigNumber", + outputType: "BigNumber", args: [ { name: "Input units", @@ -2540,8 +2588,8 @@ const OperationConfig = { "Convert data units": { module: "Default", description: "Converts a unit of data to another format.", - inputType: "number", - outputType: "number", + inputType: "BigNumber", + outputType: "BigNumber", args: [ { name: "Input units", @@ -3176,7 +3224,7 @@ const OperationConfig = { "Frequency distribution": { module: "Default", description: "Displays the distribution of bytes in the data as a graph.", - inputType: "byteArray", + inputType: "ArrayBuffer", outputType: "html", args: [ { @@ -3186,6 +3234,13 @@ const OperationConfig = { } ] }, + "Chi Square": { + module: "Default", + description: "Calculates the Chi Square distribution of values.", + inputType: "ArrayBuffer", + outputType: "number", + args: [] + }, "Numberwang": { module: "Default", description: "Based on the popular gameshow by Mitchell and Webb.", @@ -3261,14 +3316,14 @@ const OperationConfig = { "Detect File Type": { module: "Default", description: "Attempts to guess the MIME (Multipurpose Internet Mail Extensions) type of the data based on 'magic bytes'.

Currently supports the following file types: 7z, amr, avi, bmp, bz2, class, cr2, crx, dex, dmg, doc, elf, eot, epub, exe, flac, flv, gif, gz, ico, iso, jpg, jxr, m4a, m4v, mid, mkv, mov, mp3, mp4, mpg, ogg, otf, pdf, png, ppt, ps, psd, rar, rtf, sqlite, swf, tar, tar.z, tif, ttf, utf8, vmdk, wav, webm, webp, wmv, woff, woff2, xls, xz, zip.", - inputType: "byteArray", + inputType: "ArrayBuffer", outputType: "string", args: [] }, "Scan for Embedded Files": { module: "Default", description: "Scans the data for potential embedded files by looking for magic bytes at all offsets. This operation is prone to false positives.

WARNING: Files over about 100KB in size will take a VERY long time to process.", - inputType: "byteArray", + inputType: "ArrayBuffer", outputType: "string", args: [ { @@ -3424,14 +3479,14 @@ const OperationConfig = { }, "Escape string": { module: "Default", - description: "Escapes special characters in a string so that they do not cause conflicts. For example, Don't stop me now becomes Don\\'t stop me now.", + description: "Escapes special characters in a string so that they do not cause conflicts. For example, Don't stop me now becomes Don\\'t stop me now.

Supports the following escape sequences:", inputType: "string", outputType: "string", args: [] }, "Unescape string": { module: "Default", - description: "Unescapes characters in a string that have been escaped. For example, Don\\'t stop me now becomes Don't stop me now.", + description: "Unescapes characters in a string that have been escaped. For example, Don\\'t stop me now becomes Don't stop me now.

Supports the following escape sequences:", inputType: "string", outputType: "string", args: [] @@ -3622,7 +3677,7 @@ const OperationConfig = { "

", "EXIF data from photos usually contains information about the image file itself as well as the device used to create it.", ].join("\n"), - inputType: "byteArray", + inputType: "ArrayBuffer", outputType: "string", args: [], }, @@ -3696,7 +3751,7 @@ const OperationConfig = { module: "Default", description: "Binary-Coded Decimal (BCD) is a class of binary encodings of decimal numbers where each decimal digit is represented by a fixed number of bits, usually four or eight. Special bit patterns are sometimes used for a sign.", inputType: "string", - outputType: "number", + outputType: "BigNumber", args: [ { name: "Scheme", @@ -3724,7 +3779,7 @@ const OperationConfig = { "To BCD": { module: "Default", description: "Binary-Coded Decimal (BCD) is a class of binary encodings of decimal numbers where each decimal digit is represented by a fixed number of bits, usually four or eight. Special bit patterns are sometimes used for a sign", - inputType: "number", + inputType: "BigNumber", outputType: "string", args: [ { @@ -3821,7 +3876,7 @@ const OperationConfig = { "Generate HOTP": { module: "Default", description: "The HMAC-based One-Time Password algorithm (HOTP) is an algorithm that computes a one-time password from a shared secret key and an incrementing counter. It has been adopted as Internet Engineering Task Force standard RFC 4226, is the cornerstone of Initiative For Open Authentication (OATH), and is used in a number of two-factor authentication systems.

Enter the secret as the input or leave it blank for a random secret to be generated.", - inputType: "string", + inputType: "byteArray", outputType: "string", args: [ { @@ -3846,6 +3901,19 @@ const OperationConfig = { } ] }, + "PHP Deserialize": { + module: "Default", + description: "Deserializes PHP serialized data, outputting keyed arrays as JSON.

This function does not support object tags.

Example:
a:2:{s:1:"a";i:10;i:0;a:1:{s:2:"ab";b:1;}}
becomes
{"a": 10,0: {"ab": true}}

Output valid JSON: JSON doesn't support integers as keys, whereas PHP serialization does. Enabling this will cast these integers to strings. This will also escape backslashes.", + inputType: "string", + outputType: "string", + args: [ + { + name: "Output valid JSON", + type: "boolean", + value: PHP.OUTPUT_VALID_JSON + } + ] + }, "Generate PGP Key Pair": { module: "PGP", manualBake: true, @@ -3883,12 +3951,20 @@ const OperationConfig = { "PGP Encrypt": { module: "PGP", manualBake: true, - description: "", + description: [ + "Input: the message you want to encrypt.", + "

", + "Arguments: the ASCII-armoured PGP public key of the recipient.", + "

", + "Pretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.", + "

", + "This function relies on kbpgp.js for the implementation of PGP.", + ].join("\n"), inputType: "string", outputType: "string", args: [ { - name: "Public key", + name: "Public key of recipient", type: "text", value: "" }, @@ -3897,15 +3973,98 @@ const OperationConfig = { "PGP Decrypt": { module: "PGP", manualBake: true, - description: "", + description: [ + "Input: the ASCII-armoured PGP message you want to decrypt.", + "

", + "Arguments: the ASCII-armoured PGP private key of the recipient, ", + "(and the private key password if necessary).", + "

", + "Pretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.", + "

", + "This function relies on kbpgp.js for the implementation of PGP.", + ].join("\n"), inputType: "string", outputType: "string", args: [ { - name: "Private key", + name: "Private key of recipient", type: "text", value: "" }, + { + name: "Private key passphrase", + type: "string", + value: "" + }, + ] + }, + "PGP Sign": { + module: "PGP", + manualBake: true, + description: [ + "Input: the cleartext you want to sign.", + "

", + "Arguments: the ASCII-armoured private key of the signer (plus the private key password if necessary)", + "and the ASCII-armoured PGP public key of the recipient.", + "

", + "This operation uses PGP to produce an encrypted digital signature.", + "

", + "Pretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.", + "

", + "This function relies on kbpgp.js for the implementation of PGP.", + ].join("\n"), + inputType: "string", + outputType: "string", + args: [ + { + name: "Private key of signer", + type: "text", + value: "" + }, + { + name: "Private key passphrase", + type: "string", + value: "" + }, + { + name: "Public key of recipient", + type: "text", + value: "" + }, + ] + }, + "PGP Verify": { + module: "PGP", + description: [ + "Input: the ASCII-armoured encrypted PGP message you want to verify.", + "

", + "Arguments: the ASCII-armoured PGP public key of the signer, ", + "the ASCII-armoured private key of the recipient (and the private key password if necessary).", + "

", + "This operation uses PGP to decrypt and verify an encrypted digital signature.", + "

", + "Pretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.", + "

", + "This function relies on kbpgp.js for the implementation of PGP.", + ].join("\n"), + inputType: "string", + outputType: "string", + args: [ + { + name: "Public key of signer", + type: "text", + value: "", + }, + { + name: "Private key of recipient", + type: "text", + value: "", + }, + { + name: "Private key password", + type: "string", + value: "", + }, ] }, }; diff --git a/src/core/config/modules/CharEnc.js b/src/core/config/modules/CharEnc.js index 4c27ebed..6a9244d8 100644 --- a/src/core/config/modules/CharEnc.js +++ b/src/core/config/modules/CharEnc.js @@ -6,7 +6,6 @@ import CharEnc from "../../operations/CharEnc.js"; * * Libraries: * - cptable - * - CryptoJS * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2017 diff --git a/src/core/config/modules/Ciphers.js b/src/core/config/modules/Ciphers.js index de7e1f20..3f8ae51a 100644 --- a/src/core/config/modules/Ciphers.js +++ b/src/core/config/modules/Ciphers.js @@ -7,6 +7,7 @@ import Cipher from "../../operations/Cipher.js"; * Libraries: * - CryptoJS * - Blowfish + * - Forge * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2017 @@ -23,12 +24,12 @@ OpModules.Ciphers = { "DES Decrypt": Cipher.runDesDec, "Triple DES Encrypt": Cipher.runTripleDesEnc, "Triple DES Decrypt": Cipher.runTripleDesDec, - "Rabbit Encrypt": Cipher.runRabbitEnc, - "Rabbit Decrypt": Cipher.runRabbitDec, "Derive PBKDF2 key": Cipher.runPbkdf2, "Derive EVP key": Cipher.runEvpkdf, "RC4": Cipher.runRc4, "RC4 Drop": Cipher.runRc4drop, + "RC2 Encrypt": Cipher.runRc2Enc, + "RC2 Decrypt": Cipher.runRc2Dec, "Vigenère Encode": Cipher.runVigenereEnc, "Vigenère Decode": Cipher.runVigenereDec, "Bifid Cipher Encode": Cipher.runBifidEnc, @@ -37,6 +38,7 @@ OpModules.Ciphers = { "Affine Cipher Decode": Cipher.runAffineDec, "Atbash Cipher": Cipher.runAtbash, "Substitute": Cipher.runSubstitute, + "Pseudo-Random Number Generator": Cipher.runPRNG, }; export default OpModules; diff --git a/src/core/config/modules/Default.js b/src/core/config/modules/Default.js index 6e51367b..b36e00aa 100644 --- a/src/core/config/modules/Default.js +++ b/src/core/config/modules/Default.js @@ -1,4 +1,5 @@ import FlowControl from "../../FlowControl.js"; +import Arithmetic from "../../operations/Arithmetic.js"; import Base from "../../operations/Base.js"; import Base58 from "../../operations/Base58.js"; import Base64 from "../../operations/Base64.js"; @@ -20,16 +21,15 @@ import NetBIOS from "../../operations/NetBIOS.js"; import Numberwang from "../../operations/Numberwang.js"; import OS from "../../operations/OS.js"; import OTP from "../../operations/OTP.js"; +import PHP from "../../operations/PHP.js"; import QuotedPrintable from "../../operations/QuotedPrintable.js"; import Rotate from "../../operations/Rotate.js"; import SeqUtils from "../../operations/SeqUtils.js"; import StrUtils from "../../operations/StrUtils.js"; import Tidy from "../../operations/Tidy.js"; import Unicode from "../../operations/Unicode.js"; -import URL_ from "../../operations/URL.js"; import UUID from "../../operations/UUID.js"; - /** * Default module. * @@ -38,8 +38,8 @@ import UUID from "../../operations/UUID.js"; * * Libraries: * - Utils.js - * - CryptoJS * - otp + * - crypto * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2017 @@ -77,9 +77,6 @@ OpModules.Default = { "From HTML Entity": HTML.runFromEntity, "Strip HTML tags": HTML.runStripTags, "Parse colour code": HTML.runParseColourCode, - "URL Encode": URL_.runTo, - "URL Decode": URL_.runFrom, - "Parse URI": URL_.runParse, "Unescape Unicode Characters": Unicode.runUnescape, "To Quoted Printable": QuotedPrintable.runTo, "From Quoted Printable": QuotedPrintable.runFrom, @@ -146,6 +143,7 @@ OpModules.Default = { "Microsoft Script Decoder": MS.runDecodeScript, "Entropy": Entropy.runEntropy, "Frequency distribution": Entropy.runFreqDistrib, + "Chi Square": Entropy.runChiSq, "Detect File Type": FileType.runDetect, "Scan for Embedded Files": FileType.runScanForEmbeddedFiles, "Generate UUID": UUID.runGenerateV4, @@ -155,10 +153,19 @@ OpModules.Default = { "Fork": FlowControl.runFork, "Merge": FlowControl.runMerge, "Register": FlowControl.runRegister, + "Label": FlowControl.runComment, "Jump": FlowControl.runJump, "Conditional Jump": FlowControl.runCondJump, "Return": FlowControl.runReturn, "Comment": FlowControl.runComment, + "PHP Deserialize": PHP.runDeserialize, + "Sum": Arithmetic.runSum, + "Subtract": Arithmetic.runSub, + "Multiply": Arithmetic.runMulti, + "Divide": Arithmetic.runDiv, + "Mean": Arithmetic.runMean, + "Median": Arithmetic.runMedian, + "Standard Deviation": Arithmetic.runStdDev, /* diff --git a/src/core/config/modules/OpModules.js b/src/core/config/modules/OpModules.js index 2d79753e..3f3963c3 100644 --- a/src/core/config/modules/OpModules.js +++ b/src/core/config/modules/OpModules.js @@ -19,6 +19,7 @@ import ImageModule from "./Image.js"; import JSBNModule from "./JSBN.js"; import PublicKeyModule from "./PublicKey.js"; import ShellcodeModule from "./Shellcode.js"; +import URLModule from "./URL.js"; Object.assign( OpModules, @@ -33,7 +34,8 @@ Object.assign( ImageModule, JSBNModule, PublicKeyModule, - ShellcodeModule + ShellcodeModule, + URLModule ); export default OpModules; diff --git a/src/core/config/modules/PGP.js b/src/core/config/modules/PGP.js index 1e74b73a..702141d3 100644 --- a/src/core/config/modules/PGP.js +++ b/src/core/config/modules/PGP.js @@ -8,6 +8,7 @@ import PGP from "../../operations/PGP.js"; * - kbpgp * * @author tlwr [toby@toby.codes] + * @author Matt C [matt@artemisbot.uk] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ @@ -17,6 +18,8 @@ OpModules.PGP = { "Generate PGP Key Pair": PGP.runGenerateKeyPair, "PGP Encrypt": PGP.runEncrypt, "PGP Decrypt": PGP.runDecrypt, + "PGP Sign": PGP.runSign, + "PGP Verify": PGP.runVerify, }; export default OpModules; diff --git a/src/core/config/modules/URL.js b/src/core/config/modules/URL.js new file mode 100644 index 00000000..54bceb7c --- /dev/null +++ b/src/core/config/modules/URL.js @@ -0,0 +1,23 @@ +import URL_ from "../../operations/URL.js"; + + +/** + * URL module. + * + * Libraries: + * - Utils.js + * - url + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ +let OpModules = typeof self === "undefined" ? {} : self.OpModules || {}; + +OpModules.URL = { + "URL Encode": URL_.runTo, + "URL Decode": URL_.runFrom, + "Parse URI": URL_.runParse, +}; + +export default OpModules; diff --git a/src/core/operations/Arithmetic.js b/src/core/operations/Arithmetic.js new file mode 100644 index 00000000..070cea80 --- /dev/null +++ b/src/core/operations/Arithmetic.js @@ -0,0 +1,253 @@ +import Utils from "../Utils.js"; +import BigNumber from "bignumber.js"; + + +/** + * Math operations on numbers. + * + * @author bwhitn [brian.m.whitney@outlook.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * @namespace + */ +const Arithmetic = { + + /** + * @constant + * @default + */ + DELIM_OPTIONS: ["Line feed", "Space", "Comma", "Semi-colon", "Colon", "CRLF"], + + + /** + * Splits a string based on a delimiter and calculates the sum of numbers. + * + * @param {string} input + * @param {Object[]} args + * @returns {BigNumber} + */ + runSum: function(input, args) { + const val = Arithmetic._sum(Arithmetic._createNumArray(input, args[0])); + return val instanceof BigNumber ? val : new BigNumber(NaN); + }, + + + /** + * Splits a string based on a delimiter and subtracts all the numbers. + * + * @param {string} input + * @param {Object[]} args + * @returns {BigNumber} + */ + runSub: function(input, args) { + let val = Arithmetic._sub(Arithmetic._createNumArray(input, args[0])); + return val instanceof BigNumber ? val : new BigNumber(NaN); + }, + + + /** + * Splits a string based on a delimiter and multiplies the numbers. + * + * @param {string} input + * @param {Object[]} args + * @returns {BigNumber} + */ + runMulti: function(input, args) { + let val = Arithmetic._multi(Arithmetic._createNumArray(input, args[0])); + return val instanceof BigNumber ? val : new BigNumber(NaN); + }, + + + /** + * Splits a string based on a delimiter and divides the numbers. + * + * @param {string} input + * @param {Object[]} args + * @returns {BigNumber} + */ + runDiv: function(input, args) { + let val = Arithmetic._div(Arithmetic._createNumArray(input, args[0])); + return val instanceof BigNumber ? val : new BigNumber(NaN); + }, + + + /** + * Splits a string based on a delimiter and computes the mean (average). + * + * @param {string} input + * @param {Object[]} args + * @returns {BigNumber} + */ + runMean: function(input, args) { + let val = Arithmetic._mean(Arithmetic._createNumArray(input, args[0])); + return val instanceof BigNumber ? val : new BigNumber(NaN); + }, + + + /** + * Splits a string based on a delimiter and finds the median. + * + * @param {string} input + * @param {Object[]} args + * @returns {BigNumber} + */ + runMedian: function(input, args) { + let val = Arithmetic._median(Arithmetic._createNumArray(input, args[0])); + return val instanceof BigNumber ? val : new BigNumber(NaN); + }, + + + /** + * splits a string based on a delimiter and computes the standard deviation. + * + * @param {string} input + * @param {Object[]} args + * @returns {BigNumber} + */ + runStdDev: function(input, args) { + let val = Arithmetic._stdDev(Arithmetic._createNumArray(input, args[0])); + return val instanceof BigNumber ? val : new BigNumber(NaN); + }, + + + /** + * Converts a string array to a number array. + * + * @private + * @param {string[]} input + * @param {string} delim + * @returns {BigNumber[]} + */ + _createNumArray: function(input, delim) { + delim = Utils.charRep[delim || "Space"]; + let splitNumbers = input.split(delim), + numbers = [], + num; + + for (let i = 0; i < splitNumbers.length; i++) { + try { + num = BigNumber(splitNumbers[i].trim()); + if (!num.isNaN()) { + numbers.push(num); + } + } catch (err) { + // This line is not a valid number + } + } + return numbers; + }, + + + /** + * Adds an array of numbers and returns the value. + * + * @private + * @param {BigNumber[]} data + * @returns {BigNumber} + */ + _sum: function(data) { + if (data.length > 0) { + return data.reduce((acc, curr) => acc.plus(curr)); + } + }, + + + /** + * Subtracts an array of numbers and returns the value. + * + * @private + * @param {BigNumber[]} data + * @returns {BigNumber} + */ + _sub: function(data) { + if (data.length > 0) { + return data.reduce((acc, curr) => acc.minus(curr)); + } + }, + + + /** + * Multiplies an array of numbers and returns the value. + * + * @private + * @param {BigNumber[]} data + * @returns {BigNumber} + */ + _multi: function(data) { + if (data.length > 0) { + return data.reduce((acc, curr) => acc.times(curr)); + } + }, + + + /** + * Divides an array of numbers and returns the value. + * + * @private + * @param {BigNumber[]} data + * @returns {BigNumber} + */ + _div: function(data) { + if (data.length > 0) { + return data.reduce((acc, curr) => acc.div(curr)); + } + }, + + + /** + * Computes mean of a number array and returns the value. + * + * @private + * @param {BigNumber[]} data + * @returns {BigNumber} + */ + _mean: function(data) { + if (data.length > 0) { + return Arithmetic._sum(data).div(data.length); + } + }, + + + /** + * Computes median of a number array and returns the value. + * + * @private + * @param {BigNumber[]} data + * @returns {BigNumber} + */ + _median: function (data) { + if ((data.length % 2) === 0 && data.length > 0) { + let first, second; + data.sort(function(a, b){ + return a.minus(b); + }); + first = data[Math.floor(data.length / 2)]; + second = data[Math.floor(data.length / 2) - 1]; + return Arithmetic._mean([first, second]); + } else { + return data[Math.floor(data.length / 2)]; + } + }, + + + /** + * Computes standard deviation of a number array and returns the value. + * + * @private + * @param {BigNumber[]} data + * @returns {BigNumber} + */ + _stdDev: function (data) { + if (data.length > 0) { + let avg = Arithmetic._mean(data); + let devSum = new BigNumber(0); + for (let i = 0; i < data.length; i++) { + devSum = devSum.plus(data[i].minus(avg).pow(2)); + } + return devSum.div(data.length).sqrt(); + } + }, +}; + +export default Arithmetic; diff --git a/src/core/operations/BCD.js b/src/core/operations/BCD.js index 63c0cda3..7d29f4e5 100755 --- a/src/core/operations/BCD.js +++ b/src/core/operations/BCD.js @@ -1,4 +1,5 @@ import Utils from "../Utils.js"; +import BigNumber from "bignumber.js"; /** @@ -61,14 +62,14 @@ const BCD = { /** * To BCD operation. * - * @param {number} input + * @param {BigNumber} input * @param {Object[]} args * @returns {string} */ runToBCD: function(input, args) { - if (isNaN(input)) + if (input.isNaN()) return "Invalid input"; - if (Math.floor(input) !== input) + if (!input.floor().equals(input)) return "Fractional values are not supported by BCD"; const encoding = BCD.ENCODING_LOOKUP[args[0]], @@ -77,7 +78,7 @@ const BCD = { outputFormat = args[3]; // Split input number up into separate digits - const digits = input.toString().split(""); + const digits = input.toFixed().split(""); if (digits[0] === "-" || digits[0] === "+") { digits.shift(); @@ -134,11 +135,11 @@ const BCD = { switch (outputFormat) { case "Nibbles": return nibbles.map(n => { - return Utils.padLeft(n.toString(2), 4); + return n.toString(2).padStart(4, "0"); }).join(" "); case "Bytes": return bytes.map(b => { - return Utils.padLeft(b.toString(2), 8); + return b.toString(2).padStart(8, "0"); }).join(" "); case "Raw": default: @@ -152,7 +153,7 @@ const BCD = { * * @param {string} input * @param {Object[]} args - * @returns {number} + * @returns {BigNumber} */ runFromBCD: function(input, args) { const encoding = BCD.ENCODING_LOOKUP[args[0]], @@ -206,7 +207,7 @@ const BCD = { output += val.toString(); }); - return parseInt(output, 10); + return new BigNumber(output); }, }; diff --git a/src/core/operations/Base.js b/src/core/operations/Base.js index 8a79bf3b..06bbeb7c 100755 --- a/src/core/operations/Base.js +++ b/src/core/operations/Base.js @@ -1,3 +1,5 @@ +import BigNumber from "bignumber.js"; + /** * Numerical base operations. * @@ -18,7 +20,7 @@ const Base = { /** * To Base operation. * - * @param {number} input + * @param {BigNumber} input * @param {Object[]} args * @returns {string} */ @@ -39,7 +41,7 @@ const Base = { * * @param {string} input * @param {Object[]} args - * @returns {number} + * @returns {BigNumber} */ runFrom: function(input, args) { const radix = args[0] || Base.DEFAULT_RADIX; @@ -48,14 +50,14 @@ const Base = { } let number = input.replace(/\s/g, "").split("."), - result = parseInt(number[0], radix) || 0; + result = new BigNumber(number[0], radix) || 0; if (number.length === 1) return result; // Fractional part for (let i = 0; i < number[1].length; i++) { - const digit = parseInt(number[1][i], radix); - result += digit / Math.pow(radix, i+1); + const digit = new BigNumber(number[1][i], radix); + result += digit.div(Math.pow(radix, i+1)); } return result; diff --git a/src/core/operations/Base64.js b/src/core/operations/Base64.js index c6d9ce6c..31c7e2a1 100755 --- a/src/core/operations/Base64.js +++ b/src/core/operations/Base64.js @@ -40,13 +40,13 @@ const Base64 = { /** * To Base64 operation. * - * @param {byteArray} input + * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ runTo: function(input, args) { const alphabet = args[0] || Base64.ALPHABET; - return Utils.toBase64(input, alphabet); + return Utils.toBase64(new Uint8Array(input), alphabet); }, diff --git a/src/core/operations/BitwiseOp.js b/src/core/operations/BitwiseOp.js index 7512aa32..f2551cba 100755 --- a/src/core/operations/BitwiseOp.js +++ b/src/core/operations/BitwiseOp.js @@ -67,7 +67,7 @@ const BitwiseOp = { * @constant * @default */ - KEY_FORMAT: ["Hex", "Base64", "UTF8", "UTF16", "UTF16LE", "UTF16BE", "Latin1"], + KEY_FORMAT: ["Hex", "Base64", "UTF8", "Latin1"], /** * XOR operation. @@ -77,12 +77,10 @@ const BitwiseOp = { * @returns {byteArray} */ runXor: function (input, args) { - let key = Utils.format[args[0].option].parse(args[0].string || ""), + const key = Utils.convertToByteArray(args[0].string || "", args[0].option), scheme = args[1], nullPreserving = args[2]; - key = Utils.wordArrayToByteArray(key); - return BitwiseOp._bitOp(input, key, BitwiseOp._xor, nullPreserving, scheme); }, @@ -200,8 +198,7 @@ const BitwiseOp = { * @returns {byteArray} */ runAnd: function (input, args) { - let key = Utils.format[args[0].option].parse(args[0].string || ""); - key = Utils.wordArrayToByteArray(key); + const key = Utils.convertToByteArray(args[0].string || "", args[0].option); return BitwiseOp._bitOp(input, key, BitwiseOp._and); }, @@ -215,8 +212,7 @@ const BitwiseOp = { * @returns {byteArray} */ runOr: function (input, args) { - let key = Utils.format[args[0].option].parse(args[0].string || ""); - key = Utils.wordArrayToByteArray(key); + const key = Utils.convertToByteArray(args[0].string || "", args[0].option); return BitwiseOp._bitOp(input, key, BitwiseOp._or); }, @@ -230,8 +226,7 @@ const BitwiseOp = { * @returns {byteArray} */ runAdd: function (input, args) { - let key = Utils.format[args[0].option].parse(args[0].string || ""); - key = Utils.wordArrayToByteArray(key); + const key = Utils.convertToByteArray(args[0].string || "", args[0].option); return BitwiseOp._bitOp(input, key, BitwiseOp._add); }, @@ -245,8 +240,7 @@ const BitwiseOp = { * @returns {byteArray} */ runSub: function (input, args) { - let key = Utils.format[args[0].option].parse(args[0].string || ""); - key = Utils.wordArrayToByteArray(key); + const key = Utils.convertToByteArray(args[0].string || "", args[0].option); return BitwiseOp._bitOp(input, key, BitwiseOp._sub); }, diff --git a/src/core/operations/ByteRepr.js b/src/core/operations/ByteRepr.js index 5ba7f082..986926ca 100755 --- a/src/core/operations/ByteRepr.js +++ b/src/core/operations/ByteRepr.js @@ -31,13 +31,13 @@ const ByteRepr = { /** * To Hex operation. * - * @param {byteArray} input + * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ runToHex: function(input, args) { const delim = Utils.charRep[args[0] || "Space"]; - return Utils.toHex(input, delim, 2); + return Utils.toHex(new Uint8Array(input), delim, 2); }, @@ -186,7 +186,7 @@ const ByteRepr = { // 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; + pos[0].end += 2; } return pos; }, @@ -266,7 +266,7 @@ const ByteRepr = { padding = 8; for (let i = 0; i < input.length; i++) { - output += Utils.pad(input[i].toString(2), padding) + delim; + output += input[i].toString(2).padStart(padding, "0") + delim; } if (delim.length) { diff --git a/src/core/operations/Cipher.js b/src/core/operations/Cipher.js index 465c3e9e..c253cfbe 100755 --- a/src/core/operations/Cipher.js +++ b/src/core/operations/Cipher.js @@ -1,6 +1,8 @@ import Utils from "../Utils.js"; import CryptoJS from "crypto-js"; +import forge from "imports-loader?jQuery=>null!node-forge/dist/forge.min.js"; import {blowfish as Blowfish} from "sladex-blowfish"; +import BigNumber from "bignumber.js"; /** @@ -18,132 +20,27 @@ const Cipher = { * @constant * @default */ - IO_FORMAT1: ["Hex", "Base64", "UTF8", "UTF16", "UTF16LE", "UTF16BE", "Latin1"], + IO_FORMAT1: ["Hex", "UTF8", "Latin1", "Base64"], + /** + * @constant + * @default + */ + IO_FORMAT2: ["UTF8", "Latin1", "Hex", "Base64"], + /** + * @constant + * @default + */ + IO_FORMAT3: ["Raw", "Hex"], + /** + * @constant + * @default + */ + IO_FORMAT4: ["Hex", "Raw"], /** * @constant * @default */ - IO_FORMAT2: ["UTF8", "UTF16", "UTF16LE", "UTF16BE", "Latin1", "Hex", "Base64"], - /** - * @constant - * @default - */ - IO_FORMAT3: ["Hex", "Base64", "UTF16", "UTF16LE", "UTF16BE", "Latin1"], - /** - * @constant - * @default - */ - IO_FORMAT4: ["Latin1", "UTF8", "UTF16", "UTF16LE", "UTF16BE", "Hex", "Base64"], - /** - * @constant - * @default - */ - MODES: ["CBC", "CFB", "CTR", "OFB", "ECB"], - /** - * @constant - * @default - */ - PADDING: ["Pkcs7", "Iso97971", "AnsiX923", "Iso10126", "ZeroPadding", "NoPadding"], - /** - * @constant - * @default - */ - RESULT_TYPE: ["Show all", "Ciphertext", "Key", "IV", "Salt"], - - - /** - * Runs encryption operations using the CryptoJS framework. - * - * @private - * @param {function} algo - The CryptoJS algorithm to use - * @param {byteArray} input - * @param {function} args - * @returns {string} - */ - _enc: function (algo, input, args) { - let key = Utils.format[args[0].option].parse(args[0].string || ""), - iv = Utils.format[args[1].option].parse(args[1].string || ""), - salt = Utils.format[args[2].option].parse(args[2].string || ""), - mode = CryptoJS.mode[args[3]], - padding = CryptoJS.pad[args[4]], - resultOption = args[5].toLowerCase(), - outputFormat = args[6]; - - if (iv.sigBytes === 0) { - // Use passphrase rather than key. Need to convert it to a string. - key = key.toString(CryptoJS.enc.Latin1); - } - - const encrypted = algo.encrypt(input, key, { - salt: salt.sigBytes > 0 ? salt : false, - iv: iv.sigBytes > 0 ? iv : null, - mode: mode, - padding: padding - }); - - let result = ""; - if (resultOption === "show all") { - result += "Key: " + encrypted.key.toString(Utils.format[outputFormat]); - result += "\nIV: " + encrypted.iv.toString(Utils.format[outputFormat]); - if (encrypted.salt) result += "\nSalt: " + encrypted.salt.toString(Utils.format[outputFormat]); - result += "\n\nCiphertext: " + encrypted.ciphertext.toString(Utils.format[outputFormat]); - } else { - result = encrypted[resultOption].toString(Utils.format[outputFormat]); - } - - return result; - }, - - - /** - * Runs decryption operations using the CryptoJS framework. - * - * @private - * @param {function} algo - The CryptoJS algorithm to use - * @param {byteArray} input - * @param {function} args - * @returns {string} - */ - _dec: function (algo, input, args) { - let key = Utils.format[args[0].option].parse(args[0].string || ""), - iv = Utils.format[args[1].option].parse(args[1].string || ""), - salt = Utils.format[args[2].option].parse(args[2].string || ""), - mode = CryptoJS.mode[args[3]], - padding = CryptoJS.pad[args[4]], - inputFormat = args[5], - outputFormat = args[6]; - - // The ZeroPadding option causes a crash when the input length is 0 - if (!input.length) { - return "No input"; - } - - const ciphertext = Utils.format[inputFormat].parse(input); - - if (iv.sigBytes === 0) { - // Use passphrase rather than key. Need to convert it to a string. - key = key.toString(CryptoJS.enc.Latin1); - } - - const decrypted = algo.decrypt({ - ciphertext: ciphertext, - salt: salt.sigBytes > 0 ? salt : false - }, key, { - iv: iv.sigBytes > 0 ? iv : null, - mode: mode, - padding: padding - }); - - let result; - try { - result = decrypted.toString(Utils.format[outputFormat]); - } catch (err) { - result = "Decrypt error: " + err.message; - } - - return result; - }, - + AES_MODES: ["CBC", "CFB", "OFB", "CTR", "GCM", "ECB"], /** * AES Encrypt operation. @@ -153,7 +50,41 @@ const Cipher = { * @returns {string} */ runAesEnc: function (input, args) { - return Cipher._enc(CryptoJS.AES, input, args); + const key = Utils.convertToByteArray(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 ([16, 24, 32].indexOf(key.length) < 0) { + return `Invalid key length: ${key.length} bytes + +The following algorithms will be used based on the size of the key: + 16 bytes = AES-128 + 24 bytes = AES-192 + 32 bytes = AES-256`; + } + + input = Utils.convertToByteString(input, inputType); + + const cipher = forge.cipher.createCipher("AES-" + mode, key); + cipher.start({iv: iv}); + cipher.update(forge.util.createBuffer(input)); + cipher.finish(); + + if (outputType === "Hex") { + if (mode === "GCM") { + return cipher.output.toHex() + "\n\n" + + "Tag: " + cipher.mode.tag.toHex(); + } + return cipher.output.toHex(); + } else { + if (mode === "GCM") { + return cipher.output.getBytes() + "\n\n" + + "Tag: " + cipher.mode.tag.getBytes(); + } + return cipher.output.getBytes(); + } }, @@ -165,10 +96,46 @@ const Cipher = { * @returns {string} */ runAesDec: function (input, args) { - return Cipher._dec(CryptoJS.AES, input, args); + const key = Utils.convertToByteArray(args[0].string, args[0].option), + iv = Utils.convertToByteArray(args[1].string, args[1].option), + mode = args[2], + inputType = args[3], + outputType = args[4], + gcmTag = Utils.convertToByteString(args[5].string, args[5].option); + + if ([16, 24, 32].indexOf(key.length) < 0) { + return `Invalid key length: ${key.length} bytes + +The following algorithms will be used based on the size of the key: + 16 bytes = AES-128 + 24 bytes = AES-192 + 32 bytes = AES-256`; + } + + input = Utils.convertToByteString(input, inputType); + + const decipher = forge.cipher.createDecipher("AES-" + mode, key); + decipher.start({ + iv: iv, + tag: gcmTag + }); + decipher.update(forge.util.createBuffer(input)); + const result = decipher.finish(); + + if (result) { + return outputType === "Hex" ? decipher.output.toHex() : decipher.output.getBytes(); + } else { + return "Unable to decrypt input with these parameters."; + } }, + /** + * @constant + * @default + */ + DES_MODES: ["CBC", "CFB", "OFB", "CTR", "ECB"], + /** * DES Encrypt operation. * @@ -177,7 +144,27 @@ const Cipher = { * @returns {string} */ runDesEnc: function (input, args) { - return Cipher._enc(CryptoJS.DES, 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 !== 8) { + return `Invalid key length: ${key.length} bytes + +DES uses a key length of 8 bytes (64 bits). +Triple DES uses a key length of 24 bytes (192 bits).`; + } + + input = Utils.convertToByteString(input, inputType); + + const cipher = forge.cipher.createCipher("DES-" + mode, key); + cipher.start({iv: iv}); + cipher.update(forge.util.createBuffer(input)); + cipher.finish(); + + return outputType === "Hex" ? cipher.output.toHex() : cipher.output.getBytes(); }, @@ -189,7 +176,31 @@ const Cipher = { * @returns {string} */ runDesDec: function (input, args) { - return Cipher._dec(CryptoJS.DES, 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 !== 8) { + return `Invalid key length: ${key.length} bytes + +DES uses a key length of 8 bytes (64 bits). +Triple DES uses a key length of 24 bytes (192 bits).`; + } + + input = Utils.convertToByteString(input, inputType); + + const decipher = forge.cipher.createDecipher("DES-" + mode, key); + decipher.start({iv: iv}); + decipher.update(forge.util.createBuffer(input)); + const result = decipher.finish(); + + if (result) { + return outputType === "Hex" ? decipher.output.toHex() : decipher.output.getBytes(); + } else { + return "Unable to decrypt input with these parameters."; + } }, @@ -201,7 +212,27 @@ const Cipher = { * @returns {string} */ runTripleDesEnc: function (input, args) { - return Cipher._enc(CryptoJS.TripleDES, 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) { + return `Invalid key length: ${key.length} bytes + +Triple DES uses a key length of 24 bytes (192 bits). +DES uses a key length of 8 bytes (64 bits).`; + } + + input = Utils.convertToByteString(input, inputType); + + const cipher = forge.cipher.createCipher("3DES-" + mode, key); + cipher.start({iv: iv}); + cipher.update(forge.util.createBuffer(input)); + cipher.finish(); + + return outputType === "Hex" ? cipher.output.toHex() : cipher.output.getBytes(); }, @@ -213,31 +244,79 @@ const Cipher = { * @returns {string} */ runTripleDesDec: function (input, args) { - return Cipher._dec(CryptoJS.TripleDES, 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) { + return `Invalid key length: ${key.length} bytes + +Triple DES uses a key length of 24 bytes (192 bits). +DES uses a key length of 8 bytes (64 bits).`; + } + + input = Utils.convertToByteString(input, inputType); + + const decipher = forge.cipher.createDecipher("3DES-" + mode, key); + decipher.start({iv: iv}); + decipher.update(forge.util.createBuffer(input)); + const result = decipher.finish(); + + if (result) { + return outputType === "Hex" ? decipher.output.toHex() : decipher.output.getBytes(); + } else { + return "Unable to decrypt input with these parameters."; + } }, /** - * Rabbit Encrypt operation. + * RC2 Encrypt operation. * * @param {string} input * @param {Object[]} args * @returns {string} */ - runRabbitEnc: function (input, args) { - return Cipher._enc(CryptoJS.Rabbit, input, args); + runRc2Enc: function (input, args) { + const key = Utils.convertToByteString(args[0].string, args[0].option), + iv = Utils.convertToByteString(args[1].string, args[1].option), + inputType = args[2], + outputType = args[3], + cipher = forge.rc2.createEncryptionCipher(key); + + input = Utils.convertToByteString(input, inputType); + + cipher.start(iv || null); + cipher.update(forge.util.createBuffer(input)); + cipher.finish(); + + return outputType === "Hex" ? cipher.output.toHex() : cipher.output.getBytes(); }, /** - * Rabbit Decrypt operation. + * RC2 Decrypt operation. * * @param {string} input * @param {Object[]} args * @returns {string} */ - runRabbitDec: function (input, args) { - return Cipher._dec(CryptoJS.Rabbit, input, args); + runRc2Dec: function (input, args) { + const key = Utils.convertToByteString(args[0].string, args[0].option), + iv = Utils.convertToByteString(args[1].string, args[1].option), + inputType = args[2], + outputType = args[3], + decipher = forge.rc2.createDecryptionCipher(key); + + input = Utils.convertToByteString(input, inputType); + + decipher.start(iv || null); + decipher.update(forge.util.createBuffer(input)); + decipher.finish(); + + return outputType === "Hex" ? decipher.output.toHex() : decipher.output.getBytes(); }, @@ -245,12 +324,29 @@ const Cipher = { * @constant * @default */ - BLOWFISH_MODES: ["ECB", "CBC", "PCBC", "CFB", "OFB", "CTR"], + BLOWFISH_MODES: ["CBC", "PCBC", "CFB", "OFB", "CTR", "ECB"], /** * @constant * @default */ - BLOWFISH_OUTPUT_TYPES: ["Base64", "Hex", "String", "Raw"], + BLOWFISH_OUTPUT_TYPES: ["Hex", "Base64", "Raw"], + + /** + * Lookup table for Blowfish output types. + * + * @private + */ + _BLOWFISH_OUTPUT_TYPE_LOOKUP: { + Base64: 0, Hex: 1, String: 2, Raw: 3 + }, + /** + * Lookup table for Blowfish modes. + * + * @private + */ + _BLOWFISH_MODE_LOOKUP: { + ECB: 0, CBC: 1, PCBC: 2, CFB: 3, OFB: 4, CTR: 5 + }, /** * Blowfish Encrypt operation. @@ -260,19 +356,24 @@ const Cipher = { * @returns {string} */ runBlowfishEnc: function (input, args) { - let key = Utils.format[args[0].option].parse(args[0].string).toString(Utils.format.Latin1), - mode = args[1], - outputFormat = args[2]; + 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 === 0) return "Enter a key"; - let encHex = Blowfish.encrypt(input, key, { - outputType: 1, - cipherMode: Cipher.BLOWFISH_MODES.indexOf(mode) - }), - enc = CryptoJS.enc.Hex.parse(encHex); + input = Utils.convertToByteString(input, inputType); - return enc.toString(Utils.format[outputFormat]); + Blowfish.setIV(Utils.toBase64(iv), 0); + + const enc = Blowfish.encrypt(input, key, { + outputType: Cipher._BLOWFISH_OUTPUT_TYPE_LOOKUP[outputType], + cipherMode: Cipher._BLOWFISH_MODE_LOOKUP[mode] + }); + + return outputType === "Raw" ? Utils.byteArrayToChars(enc) : enc ; }, @@ -284,18 +385,24 @@ const Cipher = { * @returns {string} */ runBlowfishDec: function (input, args) { - let key = Utils.format[args[0].option].parse(args[0].string).toString(Utils.format.Latin1), - mode = args[1], - inputFormat = args[2]; + 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 === 0) return "Enter a key"; - input = Utils.format[inputFormat].parse(input); + input = inputType === "Raw" ? Utils.strToByteArray(input) : input; - return Blowfish.decrypt(input.toString(CryptoJS.enc.Base64), key, { - outputType: 0, // This actually means inputType. The library is weird. - cipherMode: Cipher.BLOWFISH_MODES.indexOf(mode) + Blowfish.setIV(Utils.toBase64(iv), 0); + + const result = Blowfish.decrypt(input, key, { + outputType: Cipher._BLOWFISH_OUTPUT_TYPE_LOOKUP[inputType], // This actually means inputType. The library is weird. + cipherMode: Cipher._BLOWFISH_MODE_LOOKUP[mode] }); + + return outputType === "Hex" ? Utils.toHexFast(Utils.strToByteArray(result)) : result; }, @@ -303,7 +410,7 @@ const Cipher = { * @constant * @default */ - KDF_KEY_SIZE: 256, + KDF_KEY_SIZE: 128, /** * @constant * @default @@ -313,7 +420,7 @@ const Cipher = { * @constant * @default */ - HASHERS: ["MD5", "SHA1", "SHA224", "SHA256", "SHA384", "SHA512", "SHA3", "RIPEMD160"], + HASHERS: ["SHA1", "SHA256", "SHA384", "SHA512", "MD5"], /** * Derive PBKDF2 key operation. @@ -323,20 +430,15 @@ const Cipher = { * @returns {string} */ runPbkdf2: function (input, args) { - let keySize = args[0] / 32, - iterations = args[1], - hasher = args[2], - salt = CryptoJS.enc.Hex.parse(args[3] || ""), - inputFormat = args[4], - outputFormat = args[5], - passphrase = Utils.format[inputFormat].parse(input), - key = CryptoJS.PBKDF2(passphrase, salt, { - keySize: keySize, - hasher: CryptoJS.algo[hasher], - iterations: iterations, - }); + const passphrase = Utils.convertToByteString(args[0].string, args[0].option), + keySize = args[1], + iterations = args[2], + hasher = args[3], + salt = Utils.convertToByteString(args[4].string, args[4].option) || + forge.random.getBytesSync(keySize), + derivedKey = forge.pkcs5.pbkdf2(passphrase, salt, iterations, keySize / 8, hasher.toLowerCase()); - return key.toString(Utils.format[outputFormat]); + return forge.util.bytesToHex(derivedKey); }, @@ -348,23 +450,33 @@ const Cipher = { * @returns {string} */ runEvpkdf: function (input, args) { - let keySize = args[0] / 32, - iterations = args[1], - hasher = args[2], - salt = CryptoJS.enc.Hex.parse(args[3] || ""), - inputFormat = args[4], - outputFormat = args[5], - passphrase = Utils.format[inputFormat].parse(input), + const passphrase = Utils.convertToByteString(args[0].string, args[0].option), + keySize = args[1] / 32, + iterations = args[2], + hasher = args[3], + salt = Utils.convertToByteString(args[4].string, args[4].option), key = CryptoJS.EvpKDF(passphrase, salt, { keySize: keySize, hasher: CryptoJS.algo[hasher], iterations: iterations, }); - return key.toString(Utils.format[outputFormat]); + return key.toString(CryptoJS.enc.Hex); }, + /** + * @constant + * @default + */ + RC4_KEY_FORMAT: ["UTF8", "UTF16", "UTF16LE", "UTF16BE", "Latin1", "Hex", "Base64"], + /** + * @constant + * @default + */ + CJS_IO_FORMAT: ["Latin1", "UTF8", "UTF16", "UTF16LE", "UTF16BE", "Hex", "Base64"], + + /** * RC4 operation. * @@ -373,11 +485,11 @@ const Cipher = { * @returns {string} */ runRc4: function (input, args) { - let message = Utils.format[args[1]].parse(input), - passphrase = Utils.format[args[0].option].parse(args[0].string), + let message = Cipher._format[args[1]].parse(input), + passphrase = Cipher._format[args[0].option].parse(args[0].string), encrypted = CryptoJS.RC4.encrypt(message, passphrase); - return encrypted.ciphertext.toString(Utils.format[args[2]]); + return encrypted.ciphertext.toString(Cipher._format[args[2]]); }, @@ -395,12 +507,59 @@ const Cipher = { * @returns {string} */ runRc4drop: function (input, args) { - let message = Utils.format[args[1]].parse(input), - passphrase = Utils.format[args[0].option].parse(args[0].string), + let message = Cipher._format[args[1]].parse(input), + passphrase = Cipher._format[args[0].option].parse(args[0].string), drop = args[3], encrypted = CryptoJS.RC4Drop.encrypt(message, passphrase, { drop: drop }); - return encrypted.ciphertext.toString(Utils.format[args[2]]); + return encrypted.ciphertext.toString(Cipher._format[args[2]]); + }, + + + /** + * @constant + * @default + */ + PRNG_BYTES: 32, + PRNG_OUTPUT: ["Hex", "Integer", "Byte array", "Raw"], + + /** + * Pseudo-Random Number Generator operation. + * + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + runPRNG: function(input, args) { + const numBytes = args[0], + outputAs = args[1]; + + let bytes; + + if (ENVIRONMENT_IS_WORKER() && self.crypto) { + bytes = self.crypto.getRandomValues(new Uint8Array(numBytes)); + bytes = Utils.arrayBufferToStr(bytes.buffer); + } else { + bytes = forge.random.getBytesSync(numBytes); + } + + let value = new BigNumber(0), + i; + + switch (outputAs) { + case "Hex": + return forge.util.bytesToHex(bytes); + case "Integer": + for (i = bytes.length - 1; i >= 0; i--) { + value = value.mul(256).plus(bytes.charCodeAt(i)); + } + return value.toFixed(); + case "Byte array": + return JSON.stringify(Utils.strToCharcode(bytes)); + case "Raw": + default: + return bytes; + } }, @@ -783,6 +942,23 @@ const Cipher = { return output; }, + + /** + * A mapping of string formats to their classes in the CryptoJS library. + * + * @private + * @constant + */ + _format: { + "Hex": CryptoJS.enc.Hex, + "Base64": CryptoJS.enc.Base64, + "UTF8": CryptoJS.enc.Utf8, + "UTF16": CryptoJS.enc.Utf16, + "UTF16LE": CryptoJS.enc.Utf16LE, + "UTF16BE": CryptoJS.enc.Utf16BE, + "Latin1": CryptoJS.enc.Latin1, + }, + }; export default Cipher; @@ -827,3 +1003,27 @@ CryptoJS.kdf.OpenSSL.execute = function (password, keySize, ivSize, salt) { // Return params return CryptoJS.lib.CipherParams.create({ key: key, iv: iv, salt: salt }); }; + + +/** + * Override for the CryptoJS Hex encoding parser to remove whitespace before attempting to parse + * the hex string. + * + * @param {string} hexStr + * @returns {CryptoJS.lib.WordArray} + */ +CryptoJS.enc.Hex.parse = function (hexStr) { + // Remove whitespace + hexStr = hexStr.replace(/\s/g, ""); + + // Shortcut + const hexStrLength = hexStr.length; + + // Convert + const words = []; + for (let i = 0; i < hexStrLength; i += 2) { + words[i >>> 3] |= parseInt(hexStr.substr(i, 2), 16) << (24 - (i % 8) * 4); + } + + return new CryptoJS.lib.WordArray.init(words, hexStrLength / 2); +}; diff --git a/src/core/operations/Code.js b/src/core/operations/Code.js index fb4a7e9d..d6904e09 100755 --- a/src/core/operations/Code.js +++ b/src/core/operations/Code.js @@ -2,9 +2,10 @@ import {camelCase, kebabCase, snakeCase} from "lodash"; import Utils from "../Utils.js"; import vkbeautify from "vkbeautify"; -import {DOMParser as dom} from "xmldom"; +import {DOMParser} from "xmldom"; import xpath from "xpath"; import jpath from "jsonpath"; +import nwmatcher from "nwmatcher"; import prettyPrintOne from "imports-loader?window=>global!exports-loader?prettyPrintOne!google-code-prettify/bin/prettify.min.js"; @@ -336,7 +337,7 @@ const Code = { let doc; try { - doc = new dom().parseFromString(input); + doc = new DOMParser().parseFromString(input, "application/xml"); } catch (err) { return "Invalid input XML."; } @@ -423,7 +424,7 @@ const Code = { let query = args[0], delimiter = args[1], parser = new DOMParser(), - html, + dom, result; if (!query.length || !input.length) { @@ -431,32 +432,32 @@ const Code = { } try { - html = parser.parseFromString(input, "text/html"); + dom = parser.parseFromString(input); } catch (err) { return "Invalid input HTML."; } try { - result = html.querySelectorAll(query); + const matcher = nwmatcher({document: dom}); + result = matcher.select(query, dom); } catch (err) { return "Invalid CSS Selector. Details:\n" + err.message; } const nodeToString = function(node) { + return node.toString(); + /* xmldom does not return the outerHTML value. switch (node.nodeType) { - case Node.ELEMENT_NODE: return node.outerHTML; - case Node.ATTRIBUTE_NODE: return node.value; - case Node.COMMENT_NODE: return node.data; - case Node.TEXT_NODE: return node.wholeText; - case Node.DOCUMENT_NODE: return node.outerHTML; + case node.ELEMENT_NODE: return node.outerHTML; + case node.ATTRIBUTE_NODE: return node.value; + case node.TEXT_NODE: return node.wholeText; + case node.COMMENT_NODE: return node.data; + case node.DOCUMENT_NODE: return node.outerHTML; default: throw new Error("Unknown Node Type: " + node.nodeType); - } + }*/ }; - return Array.apply(null, Array(result.length)) - .map(function(_, i) { - return result[i]; - }) + return result .map(nodeToString) .join(delimiter); }, diff --git a/src/core/operations/Compress.js b/src/core/operations/Compress.js index 639b89c6..57b01027 100755 --- a/src/core/operations/Compress.js +++ b/src/core/operations/Compress.js @@ -418,9 +418,9 @@ const Compress = { } }; - const fileSize = Utils.padLeft(input.length.toString(8), 11, "0"); + const fileSize = input.length.toString(8).padStart(11, "0"); const currentUnixTimestamp = Math.floor(Date.now() / 1000); - const lastModTime = Utils.padLeft(currentUnixTimestamp.toString(8), 11, "0"); + const lastModTime = currentUnixTimestamp.toString(8).padStart(11, "0"); const file = { fileName: Utils.padBytesRight(args[0], 100), @@ -452,7 +452,7 @@ const Compress = { } }); } - checksum = Utils.padBytesRight(Utils.padLeft(checksum.toString(8), 7, "0"), 8); + checksum = Utils.padBytesRight(checksum.toString(8).padStart(7, "0"), 8); file.checksum = checksum; const tarball = new Tarball(); diff --git a/src/core/operations/Convert.js b/src/core/operations/Convert.js index 2c95253d..d47166ce 100755 --- a/src/core/operations/Convert.js +++ b/src/core/operations/Convert.js @@ -60,17 +60,16 @@ const Convert = { /** * Convert distance operation. * - * @param {number} input + * @param {BigNumber} input * @param {Object[]} args - * @returns {number} + * @returns {BigNumber} */ runDistance: function (input, args) { let inputUnits = args[0], outputUnits = args[1]; - input = input * Convert.DISTANCE_FACTOR[inputUnits]; - return input / Convert.DISTANCE_FACTOR[outputUnits]; - // TODO Remove rounding errors (e.g. 1.000000000001) + input = input.mul(Convert.DISTANCE_FACTOR[inputUnits]); + return input.div(Convert.DISTANCE_FACTOR[outputUnits]); }, @@ -141,16 +140,16 @@ const Convert = { /** * Convert data units operation. * - * @param {number} input + * @param {BigNumber} input * @param {Object[]} args - * @returns {number} + * @returns {BigNumber} */ runDataSize: function (input, args) { let inputUnits = args[0], outputUnits = args[1]; - input = input * Convert.DATA_FACTOR[inputUnits]; - return input / Convert.DATA_FACTOR[outputUnits]; + input = input.mul(Convert.DATA_FACTOR[inputUnits]); + return input.div(Convert.DATA_FACTOR[outputUnits]); }, @@ -221,16 +220,16 @@ const Convert = { /** * Convert area operation. * - * @param {number} input + * @param {BigNumber} input * @param {Object[]} args - * @returns {number} + * @returns {BigNumber} */ runArea: function (input, args) { let inputUnits = args[0], outputUnits = args[1]; - input = input * Convert.AREA_FACTOR[inputUnits]; - return input / Convert.AREA_FACTOR[outputUnits]; + input = input.mul(Convert.AREA_FACTOR[inputUnits]); + return input.div(Convert.AREA_FACTOR[outputUnits]); }, @@ -332,16 +331,16 @@ const Convert = { /** * Convert mass operation. * - * @param {number} input + * @param {BigNumber} input * @param {Object[]} args - * @returns {number} + * @returns {BigNumber} */ runMass: function (input, args) { let inputUnits = args[0], outputUnits = args[1]; - input = input * Convert.MASS_FACTOR[inputUnits]; - return input / Convert.MASS_FACTOR[outputUnits]; + input = input.mul(Convert.MASS_FACTOR[inputUnits]); + return input.div(Convert.MASS_FACTOR[outputUnits]); }, @@ -397,16 +396,16 @@ const Convert = { /** * Convert speed operation. * - * @param {number} input + * @param {BigNumber} input * @param {Object[]} args - * @returns {number} + * @returns {BigNumber} */ runSpeed: function (input, args) { let inputUnits = args[0], outputUnits = args[1]; - input = input * Convert.SPEED_FACTOR[inputUnits]; - return input / Convert.SPEED_FACTOR[outputUnits]; + input = input.mul(Convert.SPEED_FACTOR[inputUnits]); + return input.div(Convert.SPEED_FACTOR[outputUnits]); }, }; diff --git a/src/core/operations/Entropy.js b/src/core/operations/Entropy.js index 3451914d..baf9edb3 100755 --- a/src/core/operations/Entropy.js +++ b/src/core/operations/Entropy.js @@ -81,22 +81,23 @@ const Entropy = { /** * Frequency distribution operation. * - * @param {byteArray} input + * @param {ArrayBuffer} input * @param {Object[]} args * @returns {html} */ runFreqDistrib: function (input, args) { - if (!input.length) return "No data"; + const data = new Uint8Array(input); + if (!data.length) return "No data"; let distrib = new Array(256).fill(0), percentages = new Array(256), - len = input.length, + len = data.length, showZeroes = args[0], i; // Count bytes for (i = 0; i < len; i++) { - distrib[input[i]]++; + distrib[data[i]]++; } // Calculate percentages @@ -126,7 +127,7 @@ const Entropy = { for (i = 0; i < 256; i++) { if (distrib[i] || showZeroes) { output += " " + Utils.hex(i, 2) + " (" + - Utils.padRight(percentages[i].toFixed(2).replace(".00", "") + "%)", 8) + + (percentages[i].toFixed(2).replace(".00", "") + "%)").padEnd(8, " ") + Array(Math.ceil(percentages[i])+1).join("|") + "\n"; } } @@ -135,6 +136,32 @@ const Entropy = { }, + /** + * Chi Square operation. + * + * @param {ArrayBuffer} data + * @param {Object[]} args + * @returns {number} + */ + runChiSq: function(input, args) { + const data = new Uint8Array(input); + let distArray = new Array(256).fill(0), + total = 0; + + for (let i = 0; i < data.length; i++) { + distArray[data[i]]++; + } + + for (let i = 0; i < distArray.length; i++) { + if (distArray[i] > 0) { + total += Math.pow(distArray[i] - data.length / 256, 2) / (data.length / 256); + } + } + + return total; + }, + + /** * Calculates the Shannon entropy for a given chunk of data. * diff --git a/src/core/operations/FileType.js b/src/core/operations/FileType.js index ad3e5ba7..715f8205 100755 --- a/src/core/operations/FileType.js +++ b/src/core/operations/FileType.js @@ -15,12 +15,13 @@ const FileType = { /** * Detect File Type operation. * - * @param {byteArray} input + * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ runDetect: function(input, args) { - const type = FileType.magicType(input); + const data = new Uint8Array(input), + type = FileType.magicType(data); if (!type) { return "Unknown file type. Have you tried checking the entropy of this data to determine whether it might be encrypted or compressed?"; @@ -46,20 +47,21 @@ const FileType = { /** * Scan for Embedded Files operation. * - * @param {byteArray} input + * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ runScanForEmbeddedFiles: function(input, args) { let output = "Scanning data for 'magic bytes' which may indicate embedded files. The following results may be false positives and should not be treat as reliable. Any suffiently long file is likely to contain these magic bytes coincidentally.\n", type, - ignoreCommon = args[0], - commonExts = ["ico", "ttf", ""], numFound = 0, numCommonFound = 0; + const ignoreCommon = args[0], + commonExts = ["ico", "ttf", ""], + data = new Uint8Array(input); - for (let i = 0; i < input.length; i++) { - type = FileType.magicType(input.slice(i)); + for (let i = 0; i < data.length; i++) { + type = FileType.magicType(data.slice(i)); if (type) { if (ignoreCommon && commonExts.indexOf(type.ext) > -1) { numCommonFound++; @@ -96,7 +98,7 @@ const FileType = { * Given a buffer, detects magic byte sequences at specific positions and returns the * extension and mime type. * - * @param {byteArray} buf + * @param {Uint8Array} buf * @returns {Object} type * @returns {string} type.ext - File extension * @returns {string} type.mime - Mime type diff --git a/src/core/operations/HTML.js b/src/core/operations/HTML.js index 30eda63e..b1f7e065 100755 --- a/src/core/operations/HTML.js +++ b/src/core/operations/HTML.js @@ -215,9 +215,9 @@ const HTML = { k = k.toFixed(2); let hex = "#" + - Utils.padLeft(Math.round(r).toString(16), 2) + - Utils.padLeft(Math.round(g).toString(16), 2) + - Utils.padLeft(Math.round(b).toString(16), 2), + 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 + "%)", diff --git a/src/core/operations/Hash.js b/src/core/operations/Hash.js index 6d1d3a16..b8bd4797 100755 --- a/src/core/operations/Hash.js +++ b/src/core/operations/Hash.js @@ -16,6 +16,22 @@ import Checksum from "./Checksum.js"; */ const Hash = { + /** + * Generic hash function. + * + * @param {string} name + * @param {string} input + * @returns {string} + */ + runHash: function(name, input) { + const hasher = CryptoApi.hasher(name); + hasher.state.message = input; + hasher.state.length += input.length; + hasher.process(); + return hasher.finalize().stringify("hex"); + }, + + /** * MD2 operation. * @@ -24,7 +40,7 @@ const Hash = { * @returns {string} */ runMD2: function (input, args) { - return CryptoApi.hash("md2", input, {}).stringify("hex"); + return Hash.runHash("md2", input); }, @@ -36,7 +52,7 @@ const Hash = { * @returns {string} */ runMD4: function (input, args) { - return CryptoApi.hash("md4", input, {}).stringify("hex"); + return Hash.runHash("md4", input); }, @@ -48,7 +64,7 @@ const Hash = { * @returns {string} */ runMD5: function (input, args) { - return CryptoApi.hash("md5", input, {}).stringify("hex"); + return Hash.runHash("md5", input); }, @@ -92,7 +108,7 @@ const Hash = { * @returns {string} */ runSHA0: function (input, args) { - return CryptoApi.hash("sha0", input, {}).stringify("hex"); + return Hash.runHash("sha0", input); }, @@ -104,7 +120,7 @@ const Hash = { * @returns {string} */ runSHA1: function (input, args) { - return CryptoApi.hash("sha1", input, {}).stringify("hex"); + return Hash.runHash("sha1", input); }, @@ -123,7 +139,7 @@ const Hash = { */ runSHA2: function (input, args) { const size = args[0]; - return CryptoApi.hash("sha" + size, input, {}).stringify("hex"); + return Hash.runHash("sha" + size, input); }, @@ -259,7 +275,7 @@ const Hash = { */ runRIPEMD: function (input, args) { const size = args[0]; - return CryptoApi.hash("ripemd" + size, input, {}).stringify("hex"); + return Hash.runHash("ripemd" + size, input); }, @@ -271,7 +287,7 @@ const Hash = { * @returns {string} */ runHAS: function (input, args) { - return CryptoApi.hash("has160", input, {}).stringify("hex"); + return Hash.runHash("has160", input); }, @@ -290,7 +306,7 @@ const Hash = { */ runWhirlpool: function (input, args) { const variant = args[0].toLowerCase(); - return CryptoApi.hash(variant, input, {}).stringify("hex"); + return Hash.runHash(variant, input); }, @@ -315,7 +331,7 @@ const Hash = { runSnefru: function (input, args) { const rounds = args[0], size = args[1]; - return CryptoApi.hash(`snefru-${rounds}-${size}`, input, {}).stringify("hex"); + return Hash.runHash(`snefru-${rounds}-${size}`, input); }, diff --git a/src/core/operations/Hexdump.js b/src/core/operations/Hexdump.js index a9ed7a10..fc907d9e 100755 --- a/src/core/operations/Hexdump.js +++ b/src/core/operations/Hexdump.js @@ -31,18 +31,19 @@ const Hexdump = { /** * To Hexdump operation. * - * @param {byteArray} input + * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ runTo: function(input, args) { + const data = new Uint8Array(input); const length = args[0] || Hexdump.WIDTH; const upperCase = args[1]; const includeFinalLength = args[2]; let output = "", padding = 2; - for (let i = 0; i < input.length; i += length) { - const buff = input.slice(i, i+length); + for (let i = 0; i < data.length; i += length) { + const buff = data.slice(i, i+length); let hexa = ""; for (let j = 0; j < buff.length; j++) { hexa += Utils.hex(buff[j], padding) + " "; @@ -56,10 +57,10 @@ const Hexdump = { } output += lineNo + " " + - Utils.padRight(hexa, (length*(padding+1))) + - " |" + Utils.padRight(Utils.printable(Utils.byteArrayToChars(buff)), buff.length) + "|\n"; + hexa.padEnd(length*(padding+1), " ") + + " |" + Utils.printable(Utils.byteArrayToChars(buff)).padEnd(buff.length, " ") + "|\n"; - if (includeFinalLength && i+buff.length === input.length) { + if (includeFinalLength && i+buff.length === data.length) { output += Utils.hex(i+buff.length, 8) + "\n"; } } diff --git a/src/core/operations/Image.js b/src/core/operations/Image.js index 2dd72df5..afab21c1 100644 --- a/src/core/operations/Image.js +++ b/src/core/operations/Image.js @@ -20,14 +20,13 @@ const Image = { * * Extracts EXIF data from a byteArray, representing a JPG or a TIFF image. * - * @param {byteArray} input + * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ runExtractEXIF(input, args) { try { - const bytes = Uint8Array.from(input); - const parser = ExifParser.create(bytes.buffer); + const parser = ExifParser.create(input); const result = parser.parse(); let lines = []; @@ -53,7 +52,7 @@ const Image = { * @author David Moodie [davidmoodie12@gmail.com] * @param {byteArray} input * @param {Object[]} args - * @returns {string} + * @returns {byteArray} */ runRemoveEXIF(input, args) { // Do nothing if input is empty diff --git a/src/core/operations/MS.js b/src/core/operations/MS.js index d0f6149a..a74dbe17 100644 --- a/src/core/operations/MS.js +++ b/src/core/operations/MS.js @@ -1,5 +1,5 @@ /** - * Microsoft operations. + * Microsoft operations. * * @author bmwhitn [brian.m.whitney@outlook.com] * @copyright Crown Copyright 2017 diff --git a/src/core/operations/NetBIOS.js b/src/core/operations/NetBIOS.js index 0927775a..60866b65 100644 --- a/src/core/operations/NetBIOS.js +++ b/src/core/operations/NetBIOS.js @@ -26,9 +26,14 @@ const NetBIOS = { let output = [], offset = args[0]; - for (let i = 0; i < input.length; i++) { - output.push((input[i] >> 4) + offset); - output.push((input[i] & 0xf) + offset); + if (input.length <= 16) { + let len = input.length; + input.length = 16; + input.fill(32, len, 16); + for (let i = 0; i < input.length; i++) { + output.push((input[i] >> 4) + offset); + output.push((input[i] & 0xf) + offset); + } } return output; @@ -46,9 +51,15 @@ const NetBIOS = { let output = [], offset = args[0]; - for (let i = 0; i < input.length; i += 2) { - output.push(((input[i] - offset) << 4) | - ((input[i + 1] - offset) & 0xf)); + if (input.length <= 32 && (input.length % 2) === 0) { + for (let i = 0; i < input.length; i += 2) { + output.push((((input[i] & 0xff) - offset) << 4) | + (((input[i + 1] & 0xff) - offset) & 0xf)); + } + for (let i = output.length - 1; i > 0; i--) { + if (output[i] === 32) output.splice(i, i); + else break; + } } return output; diff --git a/src/core/operations/Numberwang.js b/src/core/operations/Numberwang.js index 9d0fce68..e323f74c 100755 --- a/src/core/operations/Numberwang.js +++ b/src/core/operations/Numberwang.js @@ -14,16 +14,72 @@ const Numberwang = { * @returns {string} */ run: function(input, args) { - if (!input) return "Let's play Wangernumb!"; - const match = input.match(/\d+/); - if (match) { - return match[0] + "! That's Numberwang!"; + let output; + if (!input) { + output = "Let's play Wangernumb!"; } else { - // That's a bad miss! - return "Sorry, that's not Numberwang. Let's rotate the board!"; + 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() * Numberwang._didYouKnow.length); + return output + "\n\nDid you know: " + Numberwang._didYouKnow[rand]; }, + + /** + * Taken from http://numberwang.wikia.com/wiki/Numberwang_Wikia + * + * @private + * @constant + */ + _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/src/core/operations/OTP.js b/src/core/operations/OTP.js index ff67d1c8..f7862310 100755 --- a/src/core/operations/OTP.js +++ b/src/core/operations/OTP.js @@ -1,6 +1,7 @@ import otp from "otp"; import Base64 from "./Base64.js"; + /** * One-Time Password operations. * diff --git a/src/core/operations/PGP.js b/src/core/operations/PGP.js index 5eb842f7..7c691ad8 100755 --- a/src/core/operations/PGP.js +++ b/src/core/operations/PGP.js @@ -11,6 +11,7 @@ const KEY_TYPES = ["RSA", "ECC"]; * PGP operations. * * @author tlwr [toby@toby.codes] + * @author Matt C [matt@artemisbot.uk] * @copyright Crown Copyright 2016 * @license Apache-2.0 * @@ -21,10 +22,12 @@ const PGP = { /** * Validate PGP Key Size + * + * @private * @param {string} keySize * @returns {Integer} */ - validateKeySize(keySize, keyType) { + _validateKeySize(keySize, keyType) { if (KEY_SIZES.indexOf(keySize) < 0) { throw `Invalid key size ${keySize}, must be in ${JSON.stringify(KEY_SIZES)}`; } @@ -46,10 +49,12 @@ const PGP = { /** * Get size of subkey + * + * @private * @param {Integer} keySize * @returns {Integer} */ - getSubkeySize(keySize) { + _getSubkeySize(keySize) { return { 1024: 1024, 2048: 1024, @@ -64,18 +69,65 @@ const PGP = { /** * Validate PGP Key Type + * + * @private * @param {string} keyType * @returns {string} */ - validateKeyType(keyType) { + _validateKeyType(keyType) { if (KEY_TYPES.indexOf(keyType) >= 0) return keyType.toLowerCase(); throw `Invalid key type ${keyType}, must be in ${JSON.stringify(KEY_TYPES)}`; }, + /** + * Import private key and unlock if necessary + * + * @private + * @param {string} privateKey + * @param {string} [passphrase] + * @returns {Object} + */ + async _importPrivateKey (privateKey, passphrase) { + try { + const key = await promisify(kbpgp.KeyManager.import_from_armored_pgp)({ + armored: privateKey, + }); + if (key.is_pgp_locked() && passphrase) { + if (passphrase) { + await promisify(key.unlock_pgp, key)({ + passphrase + }); + } else if (!passphrase) { + throw "Did not provide passphrase with locked private key."; + } + } + return key; + } catch (err) { + throw `Could not import private key: ${err}`; + } + }, + + /** + * Import public key + * + * @private + * @param {string} publicKey + * @returns {Object} + */ + async _importPublicKey (publicKey) { + try { + const key = await promisify(kbpgp.KeyManager.import_from_armored_pgp)({ + armored: publicKey, + }); + return key; + } catch (err) { + throw `Could not import public key: ${err}`; + } + }, + /** * Generate PGP Key Pair operation. * - * @author tlwr [toby@toby.codes] * @param {string} input * @param {Object[]} args * @returns {string} @@ -87,8 +139,8 @@ const PGP = { name = args[3], email = args[4]; - keyType = PGP.validateKeyType(keyType); - keySize = PGP.validateKeySize(keySize, keyType); + keyType = PGP._validateKeyType(keyType); + keySize = PGP._validateKeySize(keySize, keyType); let userIdentifier = ""; if (name) userIdentifier += name; @@ -109,11 +161,11 @@ const PGP = { expire_in: 0 }, subkeys: [{ - nbits: PGP.getSubkeySize(keySize), + nbits: PGP._getSubkeySize(keySize), flags: kbpgp.const.openpgp.sign_data, expire_in: 86400 * 365 * 8 }, { - nbits: PGP.getSubkeySize(keySize), + nbits: PGP._getSubkeySize(keySize), flags: kbpgp.const.openpgp.encrypt_comm | kbpgp.const.openpgp.encrypt_storage, expire_in: 86400 * 365 * 2 }], @@ -134,6 +186,13 @@ const PGP = { }); }, + /** + * PGP Encrypt operation. + * + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ async runEncrypt(input, args) { let plaintextMessage = input, plainPubKey = args[0]; @@ -145,7 +204,6 @@ const PGP = { armored: plainPubKey, }); } catch (err) { - console.error(err); throw `Could not import public key: ${err}`; } @@ -155,30 +213,27 @@ const PGP = { encrypt_for: key, }); } catch (err) { - console.error(err); - throw `Could encrypt message to provided public key: ${err}`; + throw `Couldn't encrypt message with provided public key: ${err}`; } - console.log(encryptedMessage); - - return encryptedMessage; + return encryptedMessage.toString(); }, + /** + * PGP Decrypt operation. + * + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ async runDecrypt(input, args) { let encryptedMessage = input, - plainPrivateKey = args[0], + privateKey = args[0], + passphrase = args[1], keyring = new kbpgp.keyring.KeyRing(); - let key, plaintextMessage; - - try { - key = await promisify(kbpgp.KeyManager.import_from_armored_pgp)({ - armored: plainPrivateKey, - }); - } catch (err) { - throw `Could not import private key: ${err}`; - } - + let plaintextMessage; + const key = await PGP._importPrivateKey(privateKey, passphrase); keyring.add_key_manager(key); try { @@ -187,10 +242,97 @@ const PGP = { keyfetch: keyring, }); } catch (err) { - throw `Could decrypt message to provided private key: ${err}`; + throw `Couldn't decrypt message with provided private key: ${err}`; } - return plaintextMessage; + return plaintextMessage.toString(); + }, + + /** + * PGP Sign Message operation. + * + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async runSign(input, args) { + let message = input, + privateKey = args[0], + passphrase = args[1], + publicKey = args[2]; + + let signedMessage; + const privKey = await PGP._importPrivateKey(privateKey, passphrase); + const pubKey = await PGP._importPublicKey(publicKey); + + try { + signedMessage = await promisify(kbpgp.box)({ + msg: message, + encrypt_for: pubKey, + sign_with: privKey + }); + } catch (err) { + throw `Couldn't sign message: ${err}`; + } + + return signedMessage; + }, + + /** + * PGP Verify Message operation. + * + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async runVerify(input, args) { + let signedMessage = input, + publicKey = args[0], + privateKey = args[1], + passphrase = args[2], + keyring = new kbpgp.keyring.KeyRing(); + + let unboxedLiterals; + const privKey = await PGP._importPrivateKey(privateKey, passphrase); + const pubKey = await PGP._importPublicKey(publicKey); + keyring.add_key_manager(privKey); + keyring.add_key_manager(pubKey); + + try { + unboxedLiterals = await promisify(kbpgp.unbox)({ + armored: signedMessage, + keyfetch: keyring + }); + 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 = "Signed by "; + 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 fingerprint: ${km.get_pgp_fingerprint().toString("hex")}`, + `Signed on ${new Date(ds.sig.hashed_subpackets[0].time * 1000).toUTCString()}`, + "----------------------------------\n" + ].join("\n"); + text += unboxedLiterals.toString(); + return text.trim(); + } + } + } catch (err) { + throw `Couldn't verify message: ${err}`; + } }, }; diff --git a/src/core/operations/PHP.js b/src/core/operations/PHP.js new file mode 100644 index 00000000..e4bb0b5b --- /dev/null +++ b/src/core/operations/PHP.js @@ -0,0 +1,160 @@ +/** + * PHP operations. + * + * @author Jarmo van Lenthe [github.com/jarmovanlenthe] + * @copyright Jarmo van Lenthe + * @license Apache-2.0 + * + * @namespace + */ +const PHP = { + + /** + * @constant + * @default + */ + OUTPUT_VALID_JSON: true, + + /** + * PHP Deserialize operation. + * + * This Javascript implementation is based on the Python implementation by + * Armin Ronacher (2016), who released it under the 3-Clause BSD license. + * See: https://github.com/mitsuhiko/phpserialize/ + * + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + runDeserialize: function (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++) { + let char = inputPart.shift(); + if (char === undefined) { + throw "End of input reached before end of script"; + } + result += char; + } + return result; + } + + /** + * Read characters from the input until `until` is found. + * @param until + * @returns {string} + */ + function readUntil(until) { + let result = ""; + for (;;) { + let 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) { + let result = read(expect.length); + if (result !== expect) { + throw "Unexpected input found"; + } + return result; + } + + /** + * Helper function to handle deserialized arrays. + * @returns {Array} + */ + function handleArray() { + let items = parseInt(readUntil(":"), 10) * 2; + expect("{"); + let result = []; + let isKey = true; + let lastItem = null; + for (let idx = 0; idx < items; idx++) { + let item = handleInput(); + if (isKey) { + lastItem = item; + isKey = false; + } else { + let 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; + } + + + let kind = read(1).toLowerCase(); + + switch (kind) { + case "n": + expect(";"); + return ""; + + case "i": + case "d": + case "b": { + expect(":"); + let data = readUntil(";"); + if (kind === "b") { + return (parseInt(data, 10) !== 0); + } + return data; + } + + case "a": + expect(":"); + return "{" + handleArray() + "}"; + + case "s": { + expect(":"); + let length = readUntil(":"); + expect("\""); + let value = read(length); + expect("\";"); + if (args[0]) { + return "\"" + value.replace(/"/g, "\\\"") + "\""; + } else { + return "\"" + value + "\""; + } + } + + default: + throw "Unknown type: " + kind; + } + } + + let inputPart = input.split(""); + return handleInput(); + } + +}; + +export default PHP; diff --git a/src/core/operations/PublicKey.js b/src/core/operations/PublicKey.js index 295a5bf4..66b177a5 100755 --- a/src/core/operations/PublicKey.js +++ b/src/core/operations/PublicKey.js @@ -121,8 +121,7 @@ const PublicKey = { // Format Public Key fields for (let i = 0; i < pkFields.length; i++) { pkStr += " " + pkFields[i].key + ":" + - Utils.padLeft( - pkFields[i].value + "\n", + (pkFields[i].value + "\n").padStart( 18 - (pkFields[i].key.length + 3) + pkFields[i].value.length + 1, " " ); @@ -286,9 +285,9 @@ ${extensions}`; key = fields[i].split("=")[0]; value = fields[i].split("=")[1]; - str = Utils.padRight(key, maxKeyLen) + " = " + value + "\n"; + str = key.padEnd(maxKeyLen, " ") + " = " + value + "\n"; - output += Utils.padLeft(str, indent + str.length, " "); + output += str.padStart(indent + str.length, " "); } return output.slice(0, -1); @@ -314,7 +313,7 @@ ${extensions}`; if (i === 0) { output += str; } else { - output += Utils.padLeft(str, indent + str.length, " "); + output += str.padStart(indent + str.length, " "); } } diff --git a/src/core/operations/SeqUtils.js b/src/core/operations/SeqUtils.js index 70207e00..fa900cf9 100755 --- a/src/core/operations/SeqUtils.js +++ b/src/core/operations/SeqUtils.js @@ -158,7 +158,7 @@ const SeqUtils = { width = lines.length.toString().length; for (let n = 0; n < lines.length; n++) { - output += Utils.pad((n+1).toString(), width, " ") + " " + lines[n] + "\n"; + output += (n+1).toString().padStart(width, " ") + " " + lines[n] + "\n"; } return output.slice(0, output.length-1); }, @@ -249,7 +249,7 @@ const SeqUtils = { } } - return 0; + return a.localeCompare(b); }, }; diff --git a/src/core/operations/Tidy.js b/src/core/operations/Tidy.js index 881508cf..175d888e 100755 --- a/src/core/operations/Tidy.js +++ b/src/core/operations/Tidy.js @@ -1,6 +1,3 @@ -import Utils from "../Utils.js"; - - /** * Tidy operations. * @@ -104,32 +101,39 @@ const Tidy = { /** * Drop bytes operation. * - * @param {byteArray} input + * @param {ArrayBuffer} input * @param {Object[]} args - * @returns {byteArray} + * @returns {ArrayBuffer} */ runDropBytes: function(input, args) { - let start = args[0], + const start = args[0], length = args[1], applyToEachLine = args[2]; if (start < 0 || length < 0) throw "Error: Invalid value"; - if (!applyToEachLine) - return input.slice(0, start).concat(input.slice(start+length, input.length)); + if (!applyToEachLine) { + const left = input.slice(0, start), + right = input.slice(start + length, input.byteLength); + let result = new Uint8Array(left.byteLength + right.byteLength); + result.set(new Uint8Array(left), 0); + result.set(new Uint8Array(right), left.byteLength); + return result.buffer; + } // Split input into lines + const data = new Uint8Array(input); let lines = [], line = [], i; - for (i = 0; i < input.length; i++) { - if (input[i] === 0x0a) { + for (i = 0; i < data.length; i++) { + if (data[i] === 0x0a) { lines.push(line); line = []; } else { - line.push(input[i]); + line.push(data[i]); } } lines.push(line); @@ -139,7 +143,7 @@ const Tidy = { output = output.concat(lines[i].slice(0, start).concat(lines[i].slice(start+length, lines[i].length))); output.push(0x0a); } - return output.slice(0, output.length-1); + return new Uint8Array(output.slice(0, output.length-1)).buffer; }, @@ -157,12 +161,12 @@ const Tidy = { /** * Take bytes operation. * - * @param {byteArray} input + * @param {ArrayBuffer} input * @param {Object[]} args - * @returns {byteArray} + * @returns {ArrayBuffer} */ runTakeBytes: function(input, args) { - let start = args[0], + const start = args[0], length = args[1], applyToEachLine = args[2]; @@ -173,16 +177,17 @@ const Tidy = { return input.slice(start, start+length); // Split input into lines + const data = new Uint8Array(input); let lines = [], - line = []; - let i; + line = [], + i; - for (i = 0; i < input.length; i++) { - if (input[i] === 0x0a) { + for (i = 0; i < data.length; i++) { + if (data[i] === 0x0a) { lines.push(line); line = []; } else { - line.push(input[i]); + line.push(data[i]); } } lines.push(line); @@ -192,7 +197,7 @@ const Tidy = { output = output.concat(lines[i].slice(start, start+length)); output.push(0x0a); } - return output.slice(0, output.length-1); + return new Uint8Array(output.slice(0, output.length-1)).buffer; }, @@ -229,11 +234,11 @@ const Tidy = { if (position === "Start") { for (i = 0; i < lines.length; i++) { - output += Utils.padLeft(lines[i], lines[i].length+len, chr) + "\n"; + output += lines[i].padStart(lines[i].length+len, chr) + "\n"; } } else if (position === "End") { for (i = 0; i < lines.length; i++) { - output += Utils.padRight(lines[i], lines[i].length+len, chr) + "\n"; + output += lines[i].padEnd(lines[i].length+len, chr) + "\n"; } } diff --git a/src/core/operations/URL.js b/src/core/operations/URL.js index 9e00ebf8..2f30c952 100755 --- a/src/core/operations/URL.js +++ b/src/core/operations/URL.js @@ -1,5 +1,5 @@ /* globals unescape */ -import Utils from "../Utils.js"; +import url from "url"; /** @@ -58,56 +58,36 @@ const URL_ = { * @returns {string} */ runParse: function(input, args) { - if (!document) { - throw "This operation only works in a browser."; - } + const uri = url.parse(input, true); - const a = document.createElement("a"); + let output = ""; - // Overwrite base href which will be the current CyberChef URL to reduce confusion. - a.href = "http://example.com/"; - a.href = input; + if (uri.protocol) output += "Protocol:\t" + uri.protocol + "\n"; + if (uri.auth) output += "Auth:\t\t" + uri.auth + "\n"; + if (uri.hostname) output += "Hostname:\t" + uri.hostname + "\n"; + if (uri.port) output += "Port:\t\t" + uri.port + "\n"; + if (uri.pathname) output += "Path name:\t" + uri.pathname + "\n"; + if (uri.query) { + let keys = Object.keys(uri.query), + padding = 0; - if (a.protocol) { - let output = ""; - if (a.hostname !== window.location.hostname) { - output = "Protocol:\t" + a.protocol + "\n"; - if (a.hostname) output += "Hostname:\t" + a.hostname + "\n"; - if (a.port) output += "Port:\t\t" + a.port + "\n"; - } + keys.forEach(k => { + padding = (k.length > padding) ? k.length : padding; + }); - if (a.pathname && a.pathname !== window.location.pathname) { - let pathname = a.pathname; - if (pathname.indexOf(window.location.pathname) === 0) - pathname = pathname.replace(window.location.pathname, ""); - if (pathname) - output += "Path name:\t" + pathname + "\n"; - } - - if (a.hash && a.hash !== window.location.hash) { - output += "Hash:\t\t" + a.hash + "\n"; - } - - if (a.search && a.search !== window.location.search) { - output += "Arguments:\n"; - const args_ = (a.search.slice(1, a.search.length)).split("&"); - let splitArgs = [], padding = 0, i; - for (i = 0; i < args_.length; i++) { - splitArgs.push(args_[i].split("=")); - padding = (splitArgs[i][0].length > padding) ? splitArgs[i][0].length : padding; - } - for (i = 0; i < splitArgs.length; i++) { - output += "\t" + Utils.padRight(splitArgs[i][0], padding); - if (splitArgs[i].length > 1 && splitArgs[i][1].length) - output += " = " + splitArgs[i][1] + "\n"; - else output += "\n"; + output += "Arguments:\n"; + for (let key in uri.query) { + output += "\t" + key.padEnd(padding, " "); + if (uri.query[key].length) { + output += " = " + uri.query[key] + "\n"; + } else { + output += "\n"; } } - - return output; } + if (uri.hash) output += "Hash:\t\t" + uri.hash + "\n"; - return "Invalid URI"; + return output; }, diff --git a/src/core/operations/UUID.js b/src/core/operations/UUID.js index 761f245a..7b485579 100755 --- a/src/core/operations/UUID.js +++ b/src/core/operations/UUID.js @@ -1,3 +1,6 @@ +import crypto from "crypto"; + + /** * UUID operations. * @@ -17,25 +20,17 @@ const UUID = { * @returns {string} */ runGenerateV4: function(input, args) { - if (window && typeof(window.crypto) !== "undefined" && typeof(window.crypto.getRandomValues) !== "undefined") { - let buf = new Uint32Array(4), - i = 0; - window.crypto.getRandomValues(buf); - return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) { - let r = (buf[i >> 3] >> ((i % 8) * 4)) & 0xf, - v = c === "x" ? r : (r & 0x3 | 0x8); - i++; - return v.toString(16); - }); - } else { - return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) { - let r = Math.random() * 16 | 0, - v = c === "x" ? r : (r & 0x3 | 0x8); - return v.toString(16); - }); - } - }, - + const buf = new Uint32Array(4).map(() => { + return crypto.randomBytes(4).readUInt32BE(0, true); + }); + let i = 0; + return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) { + let r = (buf[i >> 3] >> ((i % 8) * 4)) & 0xf, + v = c === "x" ? r : (r & 0x3 | 0x8); + i++; + return v.toString(16); + }); + } }; export default UUID; diff --git a/src/web/App.js b/src/web/App.js index ff57a7ed..f60420ae 100755 --- a/src/web/App.js +++ b/src/web/App.js @@ -49,9 +49,11 @@ App.prototype.setup = function() { this.manager.setup(); this.resetLayout(); this.setCompileMessage(); - this.loadURIParams(); + log.debug("App loaded"); this.appLoaded = true; + + this.loadURIParams(); this.loaded(); }; @@ -88,9 +90,10 @@ App.prototype.loaded = function() { * An error handler for displaying the error to the user. * * @param {Error} err + * @param {boolean} [logToConsole=false] */ -App.prototype.handleError = function(err) { - console.error(err); +App.prototype.handleError = function(err, logToConsole) { + if (logToConsole) log.error(err); const msg = err.displayStr || err.toString(); this.alert(msg, "danger", this.options.errorTimeout, !this.options.showErrors); }; @@ -128,6 +131,7 @@ App.prototype.autoBake = function() { if (this.autoBakePause) return false; if (this.autoBake_ && !this.baking) { + log.debug("Auto-baking"); this.bake(); } else { this.manager.controls.showStaleIndicator(); @@ -528,7 +532,7 @@ App.prototype.setCompileMessage = function() { /** * Determines whether the browser supports Local Storage and if it is accessible. - * + * * @returns {boolean} */ App.prototype.isLocalStorageAvailable = function() { @@ -568,7 +572,7 @@ App.prototype.isLocalStorageAvailable = function() { App.prototype.alert = function(str, style, timeout, silent) { const time = new Date(); - console.log("[" + time.toLocaleString() + "] " + str); + log.info("[" + time.toLocaleString() + "] " + str); if (silent) return; style = style || "danger"; diff --git a/src/web/BindingsWaiter.js b/src/web/BindingsWaiter.js new file mode 100644 index 00000000..802590b8 --- /dev/null +++ b/src/web/BindingsWaiter.js @@ -0,0 +1,217 @@ +/** + * Waiter to handle keybindings to CyberChef functions (i.e. Bake, Step, Save, Load etc.) + * + * @author Matt C [matt@artemisbot.uk] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * @constructor + * @param {App} app - The main view object for CyberChef. + * @param {Manager} manager - The CyberChef event manager. + */ +const BindingsWaiter = function (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 + */ +BindingsWaiter.prototype.parseInput = function(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(); + document.getElementById("input-text").focus(); + break; + case "KeyO": // Focus output + e.preventDefault(); + document.getElementById("output-text").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.app.bake(); + break; + case "Quote": // Step through + e.preventDefault(); + this.app.bake(true); + 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; + 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; + } + } +}; + + +/** + * Updates keybinding list when metaKey option is toggled + * + */ +BindingsWaiter.prototype.updateKeybList = function() { + let modWinLin = "Alt"; + let modMac = "Opt"; + if (this.app.options.useMetaKey) { + modWinLin = "Win"; + modMac = "Cmd"; + } + document.getElementById("keybList").innerHTML = ` + + Command + Shortcut (Win/Linux) + Shortcut (Mac) + + + Place cursor in search field + Ctrl+${modWinLin}+f + Ctrl+${modMac}+f + + Place cursor in input box + Ctrl+${modWinLin}+i + Ctrl+${modMac}+i + + + Place cursor in output box + Ctrl+${modWinLin}+o + Ctrl+${modMac}+o + + + Place cursor in first argument field of the next operation in the recipe + Ctrl+${modWinLin}+. + Ctrl+${modMac}+. + + + Place cursor in first argument field of the nth operation in the recipe + Ctrl+${modWinLin}+[1-9] + Ctrl+${modMac}+[1-9] + + + Disable current operation + Ctrl+${modWinLin}+d + Ctrl+${modMac}+d + + + Set/clear breakpoint + Ctrl+${modWinLin}+b + Ctrl+${modMac}+b + + + Bake + Ctrl+${modWinLin}+Space + Ctrl+${modMac}+Space + + + Step + Ctrl+${modWinLin}+' + Ctrl+${modMac}+' + + + Clear recipe + Ctrl+${modWinLin}+c + Ctrl+${modMac}+c + + + Save to file + Ctrl+${modWinLin}+s + Ctrl+${modMac}+s + + + Load recipe + Ctrl+${modWinLin}+l + Ctrl+${modMac}+l + + + Move output to input + Ctrl+${modWinLin}+m + Ctrl+${modMac}+m + + `; +}; + +export default BindingsWaiter; diff --git a/src/web/HighlighterWaiter.js b/src/web/HighlighterWaiter.js index c650e6ba..6e4ca599 100755 --- a/src/web/HighlighterWaiter.js +++ b/src/web/HighlighterWaiter.js @@ -1,6 +1,3 @@ -import Utils from "../core/Utils.js"; - - /** * Waiter to handle events related to highlighting in CyberChef. * @@ -312,9 +309,9 @@ HighlighterWaiter.prototype.outputHtmlMousemove = function(e) { HighlighterWaiter.prototype.selectionInfo = function(start, end) { const len = end.toString().length; const width = len < 2 ? 2 : len; - const startStr = Utils.pad(start.toString(), width, " ").replace(/ /g, " "); - const endStr = Utils.pad(end.toString(), width, " ").replace(/ /g, " "); - const lenStr = Utils.pad((end-start).toString(), width, " ").replace(/ /g, " "); + const startStr = start.toString().padStart(width, " ").replace(/ /g, " "); + const endStr = end.toString().padStart(width, " ").replace(/ /g, " "); + const lenStr = (end-start).toString().padStart(width, " ").replace(/ /g, " "); return "start: " + startStr + "
end: " + endStr + "
length: " + lenStr; }; @@ -402,7 +399,7 @@ HighlighterWaiter.prototype.highlight = function(textarea, highlighter, pos) { // Check if there is a carriage return in the output dish as this will not // be displayed by the HTML textarea and will mess up highlighting offsets. - if (!this.app.dishStr || this.app.dishStr.indexOf("\r") >= 0) return false; + if (this.manager.output.containsCR()) return false; const startPlaceholder = "[startHighlight]"; const startPlaceholderRegex = /\[startHighlight\]/g; diff --git a/src/web/InputWaiter.js b/src/web/InputWaiter.js index aba57334..2fb6d7df 100755 --- a/src/web/InputWaiter.js +++ b/src/web/InputWaiter.js @@ -1,4 +1,4 @@ -import Utils from "../core/Utils.js"; +import LoaderWorker from "worker-loader?inline&fallback=false!./LoaderWorker.js"; /** @@ -33,6 +33,9 @@ const InputWaiter = function(app, manager) { 144, //Num 145, //Scroll ]; + + this.loaderWorker = null; + this.fileBuffer = null; }; @@ -42,20 +45,52 @@ const InputWaiter = function(app, manager) { * @returns {string} */ InputWaiter.prototype.get = function() { - return document.getElementById("input-text").value; + return this.fileBuffer || document.getElementById("input-text").value; }; /** - * Sets the input in the input textarea. + * Sets the input in the input area. * - * @param {string} input + * @param {string|File} input * * @fires Manager#statechange */ InputWaiter.prototype.set = function(input) { - document.getElementById("input-text").value = input; - window.dispatchEvent(this.manager.statechange); + const inputText = document.getElementById("input-text"); + if (input instanceof File) { + this.setFile(input); + inputText.value = ""; + this.setInputInfo(input.size, null); + } else { + inputText.value = input; + window.dispatchEvent(this.manager.statechange); + const lines = input.length < (this.app.options.ioDisplayThreshold * 1024) ? + input.count("\n") + 1 : null; + this.setInputInfo(input.length, lines); + } +}; + + +/** + * Shows file details. + * + * @param {File} file + */ +InputWaiter.prototype.setFile = function(file) { + // Display file overlay in input area with details + const fileOverlay = document.getElementById("input-file"), + fileName = document.getElementById("input-file-name"), + fileSize = document.getElementById("input-file-size"), + fileType = document.getElementById("input-file-type"), + fileLoaded = document.getElementById("input-file-loaded"); + + this.fileBuffer = new ArrayBuffer(); + fileOverlay.style.display = "block"; + fileName.textContent = file.name; + fileSize.textContent = file.size.toLocaleString() + " bytes"; + fileType.textContent = file.type || "unknown"; + fileLoaded.textContent = "0%"; }; @@ -69,22 +104,29 @@ InputWaiter.prototype.setInputInfo = function(length, lines) { let width = length.toString().length; width = width < 2 ? 2 : width; - const lengthStr = Utils.pad(length.toString(), width, " ").replace(/ /g, " "); - const linesStr = Utils.pad(lines.toString(), width, " ").replace(/ /g, " "); + const lengthStr = length.toString().padStart(width, " ").replace(/ /g, " "); + let msg = "length: " + lengthStr; - document.getElementById("input-info").innerHTML = "length: " + lengthStr + "
lines: " + linesStr; + if (typeof lines === "number") { + const linesStr = lines.toString().padStart(width, " ").replace(/ /g, " "); + msg += "
lines: " + linesStr; + } + + document.getElementById("input-info").innerHTML = msg; }; /** - * Handler for input scroll events. - * Scrolls the highlighter pane to match the input textarea position and updates history state. + * Handler for input change events. * * @param {event} e * * @fires Manager#statechange */ InputWaiter.prototype.inputChange = function(e) { + // Ignore this function if the input is a File + if (this.fileBuffer) return; + // Remove highlighting from input and output panes as the offsets might be different now this.manager.highlighter.removeHighlights(); @@ -93,18 +135,47 @@ InputWaiter.prototype.inputChange = function(e) { // Update the input metadata info const inputText = this.get(); - const lines = inputText.count("\n") + 1; + const lines = inputText.length < (this.app.options.ioDisplayThreshold * 1024) ? + inputText.count("\n") + 1 : null; this.setInputInfo(inputText.length, lines); - - if (this.badKeys.indexOf(e.keyCode) < 0) { + if (e && this.badKeys.indexOf(e.keyCode) < 0) { // Fire the statechange event as the input has been modified window.dispatchEvent(this.manager.statechange); } }; +/** + * Handler for input paste events. + * Checks that the size of the input is below the display limit, otherwise treats it as a file/blob. + * + * @param {event} e + */ +InputWaiter.prototype.inputPaste = function(e) { + const pastedData = e.clipboardData.getData("Text"); + + if (pastedData.length < (this.app.options.ioDisplayThreshold * 1024)) { + this.inputChange(e); + } else { + e.preventDefault(); + e.stopPropagation(); + + const file = new File([pastedData], "PastedData", { + type: "text/plain", + lastModified: Date.now() + }); + + this.loaderWorker = new LoaderWorker(); + this.loaderWorker.addEventListener("message", this.handleLoaderMessage.bind(this)); + this.loaderWorker.postMessage({"file": file}); + this.set(file); + return false; + } +}; + + /** * Handler for input dragover events. * Gives the user a visual cue to show that items can be dropped here. @@ -118,7 +189,7 @@ InputWaiter.prototype.inputDragover = function(e) { e.stopPropagation(); e.preventDefault(); - e.target.classList.add("dropping-file"); + e.target.closest("#input-text,#input-file").classList.add("dropping-file"); }; @@ -131,7 +202,8 @@ InputWaiter.prototype.inputDragover = function(e) { InputWaiter.prototype.inputDragleave = function(e) { e.stopPropagation(); e.preventDefault(); - e.target.classList.remove("dropping-file"); + document.getElementById("input-text").classList.remove("dropping-file"); + document.getElementById("input-file").classList.remove("dropping-file"); }; @@ -149,55 +221,62 @@ InputWaiter.prototype.inputDrop = function(e) { e.stopPropagation(); e.preventDefault(); - const el = e.target; const file = e.dataTransfer.files[0]; const text = e.dataTransfer.getData("Text"); - const reader = new FileReader(); - let inputCharcode = ""; - let offset = 0; - const CHUNK_SIZE = 20480; // 20KB - const setInput = function() { - const recipeConfig = this.app.getRecipeConfig(); - if (!recipeConfig[0] || recipeConfig[0].op !== "From Hex") { - recipeConfig.unshift({op: "From Hex", args: ["Space"]}); - this.app.setRecipeConfig(recipeConfig); - } + document.getElementById("input-text").classList.remove("dropping-file"); + document.getElementById("input-file").classList.remove("dropping-file"); - this.set(inputCharcode); - - el.classList.remove("loadingFile"); - }.bind(this); - - const seek = function() { - if (offset >= file.size) { - setInput(); - return; - } - el.value = "Processing... " + Math.round(offset / file.size * 100) + "%"; - const slice = file.slice(offset, offset + CHUNK_SIZE); - reader.readAsArrayBuffer(slice); - }; - - reader.onload = function(e) { - const data = new Uint8Array(reader.result); - inputCharcode += Utils.toHexFast(data); - offset += CHUNK_SIZE; - seek(); - }; - - - el.classList.remove("dropping-file"); + if (text) { + this.closeFile(); + this.set(text); + return; + } if (file) { - el.classList.add("loadingFile"); - seek(); - } else if (text) { - this.set(text); + this.closeFile(); + this.loaderWorker = new LoaderWorker(); + this.loaderWorker.addEventListener("message", this.handleLoaderMessage.bind(this)); + this.loaderWorker.postMessage({"file": file}); + this.set(file); } }; +/** + * Handler for messages sent back by the LoaderWorker. + * + * @param {MessageEvent} e + */ +InputWaiter.prototype.handleLoaderMessage = function(e) { + const r = e.data; + if (r.hasOwnProperty("progress")) { + const fileLoaded = document.getElementById("input-file-loaded"); + fileLoaded.textContent = r.progress + "%"; + } + + if (r.hasOwnProperty("error")) { + this.app.alert(r.error, "danger", 10000); + } + + if (r.hasOwnProperty("fileBuffer")) { + log.debug("Input file loaded"); + this.fileBuffer = r.fileBuffer; + window.dispatchEvent(this.manager.statechange); + } +}; + + +/** + * Handler for file close events. + */ +InputWaiter.prototype.closeFile = function() { + if (this.loaderWorker) this.loaderWorker.terminate(); + this.fileBuffer = null; + document.getElementById("input-file").style.display = "none"; +}; + + /** * Handler for clear IO events. * Resets the input, output and info areas. @@ -205,6 +284,8 @@ InputWaiter.prototype.inputDrop = function(e) { * @fires Manager#statechange */ InputWaiter.prototype.clearIoClick = function() { + this.closeFile(); + this.manager.output.closeFile(); this.manager.highlighter.removeHighlights(); document.getElementById("input-text").value = ""; document.getElementById("output-text").value = ""; diff --git a/src/web/LoaderWorker.js b/src/web/LoaderWorker.js new file mode 100644 index 00000000..bcb68829 --- /dev/null +++ b/src/web/LoaderWorker.js @@ -0,0 +1,54 @@ +/** + * 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 + */ + + +/** + * Respond to message from parent thread. + */ +self.addEventListener("message", function(e) { + const r = e.data; + if (r.hasOwnProperty("file")) { + self.loadFile(r.file); + } +}); + + +/** + * Loads a file object into an ArrayBuffer, then transfers it back to the parent thread. + * + * @param {File} file + */ +self.loadFile = function(file) { + const reader = new FileReader(); + let data = new Uint8Array(file.size); + let offset = 0; + const CHUNK_SIZE = 10485760; // 10MiB + + const seek = function() { + if (offset >= file.size) { + self.postMessage({"progress": 100}); + self.postMessage({"fileBuffer": data.buffer}, [data.buffer]); + return; + } + self.postMessage({"progress": Math.round(offset / file.size * 100)}); + 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": file.error.message}); + }; + + seek(); +}; diff --git a/src/web/Manager.js b/src/web/Manager.js index 1d32758c..878077c1 100755 --- a/src/web/Manager.js +++ b/src/web/Manager.js @@ -8,6 +8,7 @@ import OutputWaiter from "./OutputWaiter.js"; import OptionsWaiter from "./OptionsWaiter.js"; import HighlighterWaiter from "./HighlighterWaiter.js"; import SeasonalWaiter from "./SeasonalWaiter.js"; +import BindingsWaiter from "./BindingsWaiter.js"; /** @@ -57,9 +58,10 @@ const Manager = function(app) { this.ops = new OperationsWaiter(this.app, this); this.input = new InputWaiter(this.app, this); this.output = new OutputWaiter(this.app, this); - this.options = new OptionsWaiter(this.app); + 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); // Object to store dynamic handlers to fire on elements that may not exist yet this.dynamicHandlers = {}; @@ -75,6 +77,7 @@ Manager.prototype.setup = function() { this.worker.registerChefWorker(); this.recipe.initialiseOperationDragNDrop(); this.controls.autoBakeChange(); + this.bindings.updateKeybList(); this.seasonal.load(); }; @@ -116,7 +119,7 @@ Manager.prototype.initialiseEventListeners = function() { this.addDynamicListener(".op-list .op-icon", "mouseover", this.ops.opIconMouseover, this.ops); this.addDynamicListener(".op-list .op-icon", "mouseleave", this.ops.opIconMouseleave, this.ops); this.addDynamicListener(".op-list", "oplistcreate", this.ops.opListCreate, this.ops); - this.addDynamicListener("li.operation", "operationadd", this.recipe.opAdd.bind(this.recipe)); + this.addDynamicListener("li.operation", "operationadd", this.recipe.opAdd, this.recipe); // Recipe this.addDynamicListener(".arg:not(select)", "input", this.recipe.ingChange, this.recipe); @@ -129,19 +132,22 @@ Manager.prototype.initialiseEventListeners = function() { this.addDynamicListener("#rec-list", "operationremove", this.recipe.opRemove.bind(this.recipe)); // Input - this.addMultiEventListener("#input-text", "keyup paste", this.input.inputChange, this.input); + this.addMultiEventListener("#input-text", "keyup", this.input.inputChange, this.input); + this.addMultiEventListener("#input-text", "paste", this.input.inputPaste, this.input); document.getElementById("reset-layout").addEventListener("click", this.app.resetLayout.bind(this.app)); document.getElementById("clr-io").addEventListener("click", this.input.clearIoClick.bind(this.input)); - document.getElementById("input-text").addEventListener("dragover", this.input.inputDragover.bind(this.input)); - document.getElementById("input-text").addEventListener("dragleave", this.input.inputDragleave.bind(this.input)); - document.getElementById("input-text").addEventListener("drop", this.input.inputDrop.bind(this.input)); + this.addListeners("#input-text,#input-file", "dragover", this.input.inputDragover, this.input); + this.addListeners("#input-text,#input-file", "dragleave", this.input.inputDragleave, this.input); + this.addListeners("#input-text,#input-file", "drop", this.input.inputDrop, this.input); document.getElementById("input-text").addEventListener("scroll", this.highlighter.inputScroll.bind(this.highlighter)); document.getElementById("input-text").addEventListener("mouseup", this.highlighter.inputMouseup.bind(this.highlighter)); document.getElementById("input-text").addEventListener("mousemove", this.highlighter.inputMousemove.bind(this.highlighter)); this.addMultiEventListener("#input-text", "mousedown dblclick select", this.highlighter.inputMousedown, this.highlighter); + document.querySelector("#input-file .close").addEventListener("click", this.input.clearIoClick.bind(this.input)); // Output document.getElementById("save-to-file").addEventListener("click", this.output.saveClick.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("undo-switch").addEventListener("click", this.output.undoSwitchClick.bind(this.output)); document.getElementById("maximise-output").addEventListener("click", this.output.maximiseOutputClick.bind(this.output)); @@ -153,18 +159,24 @@ Manager.prototype.initialiseEventListeners = function() { this.addMultiEventListener("#output-text", "mousedown dblclick select", this.highlighter.outputMousedown, this.highlighter); this.addMultiEventListener("#output-html", "mousedown dblclick select", this.highlighter.outputHtmlMousedown, this.highlighter); this.addDynamicListener(".file-switch", "click", this.output.fileSwitch, this.output); + this.addDynamicListener("#output-file-download", "click", this.output.downloadFile, this.output); + this.addDynamicListener("#output-file-slice", "click", this.output.displayFileSlice, this.output); + document.getElementById("show-file-overlay").addEventListener("click", this.output.showFileOverlayClick.bind(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)); $(document).on("switchChange.bootstrapSwitch", ".option-item input:checkbox", this.options.switchChange.bind(this.options)); $(document).on("switchChange.bootstrapSwitch", ".option-item input:checkbox", this.options.setWordWrap.bind(this.options)); + $(document).on("switchChange.bootstrapSwitch", ".option-item input:checkbox#useMetaKey", this.bindings.updateKeybList.bind(this.bindings)); 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)); document.getElementById("alert-close").addEventListener("click", this.app.alertCloseClick.bind(this.app)); }; diff --git a/src/web/OptionsWaiter.js b/src/web/OptionsWaiter.js index b3eb364c..a3832dc4 100755 --- a/src/web/OptionsWaiter.js +++ b/src/web/OptionsWaiter.js @@ -8,8 +8,9 @@ * @constructor * @param {App} app - The main view object for CyberChef. */ -const OptionsWaiter = function(app) { +const OptionsWaiter = function(app, manager) { this.app = app; + this.manager = manager; }; @@ -86,6 +87,7 @@ OptionsWaiter.prototype.switchChange = function(e, state) { const el = e.target; const option = el.getAttribute("option"); + log.debug(`Setting ${option} to ${state}`); this.app.options[option] = state; if (this.app.isLocalStorageAvailable()) @@ -102,8 +104,10 @@ OptionsWaiter.prototype.switchChange = function(e, state) { OptionsWaiter.prototype.numberChange = function(e) { const el = e.target; const option = el.getAttribute("option"); + const val = parseInt(el.value, 10); - this.app.options[option] = parseInt(el.value, 10); + log.debug(`Setting ${option} to ${val}`); + this.app.options[option] = val; if (this.app.isLocalStorageAvailable()) localStorage.setItem("options", JSON.stringify(this.app.options)); @@ -120,6 +124,7 @@ OptionsWaiter.prototype.selectChange = function(e) { const el = e.target; const option = el.getAttribute("option"); + log.debug(`Setting ${option} to ${el.value}`); this.app.options[option] = el.value; if (this.app.isLocalStorageAvailable()) @@ -149,6 +154,8 @@ OptionsWaiter.prototype.setWordWrap = function() { /** * Changes the theme by setting the class of the element. + * + * @param {Event} e */ OptionsWaiter.prototype.themeChange = function (e) { const themeClass = e.target.value; @@ -156,4 +163,16 @@ OptionsWaiter.prototype.themeChange = function (e) { document.querySelector(":root").className = themeClass; }; + +/** + * Changes the console logging level. + * + * @param {Event} e + */ +OptionsWaiter.prototype.logLevelChange = function (e) { + const level = e.target.value; + log.setLevel(level, false); + this.manager.worker.setLogLevel(); +}; + export default OptionsWaiter; diff --git a/src/web/OutputWaiter.js b/src/web/OutputWaiter.js index 0b16c0f2..e78b34b2 100755 --- a/src/web/OutputWaiter.js +++ b/src/web/OutputWaiter.js @@ -1,4 +1,5 @@ import Utils from "../core/Utils.js"; +import FileSaver from "file-saver"; /** @@ -15,6 +16,9 @@ import Utils from "../core/Utils.js"; const OutputWaiter = function(app, manager) { this.app = app; this.manager = manager; + + this.dishBuffer = null; + this.dishStr = null; }; @@ -31,47 +35,151 @@ OutputWaiter.prototype.get = function() { /** * Sets the output in the output textarea. * - * @param {string} dataStr - The output string/HTML + * @param {string|ArrayBuffer} data - The output string/HTML/ArrayBuffer * @param {string} type - The data type of the output * @param {number} duration - The length of time (ms) it took to generate the output + * @param {boolean} [preserveBuffer=false] - Whether to preserve the dishBuffer */ -OutputWaiter.prototype.set = function(dataStr, type, duration) { +OutputWaiter.prototype.set = function(data, type, duration, preserveBuffer) { + log.debug("Output type: " + type); const outputText = document.getElementById("output-text"); const outputHtml = document.getElementById("output-html"); + const outputFile = document.getElementById("output-file"); const outputHighlighter = document.getElementById("output-highlighter"); const inputHighlighter = document.getElementById("input-highlighter"); + let scriptElements, lines, length; - if (type === "html") { - outputText.style.display = "none"; - outputHtml.style.display = "block"; - outputHighlighter.display = "none"; - inputHighlighter.display = "none"; + if (!preserveBuffer) { + this.closeFile(); + document.getElementById("show-file-overlay").style.display = "none"; + } - outputText.value = ""; - outputHtml.innerHTML = dataStr; + switch (type) { + case "html": + outputText.style.display = "none"; + outputHtml.style.display = "block"; + outputFile.style.display = "none"; + outputHighlighter.display = "none"; + inputHighlighter.display = "none"; - // Execute script sections - const scriptElements = outputHtml.querySelectorAll("script"); - for (let i = 0; i < scriptElements.length; i++) { - try { - eval(scriptElements[i].innerHTML); // eslint-disable-line no-eval - } catch (err) { - console.error(err); + outputText.value = ""; + outputHtml.innerHTML = data; + this.dishStr = Utils.stripHtmlTags(data, true); + length = data.length; + lines = this.dishStr.count("\n") + 1; + + // Execute script sections + scriptElements = 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); + } } - } - } else { - outputText.style.display = "block"; - outputHtml.style.display = "none"; - outputHighlighter.display = "block"; - inputHighlighter.display = "block"; + break; + case "ArrayBuffer": + outputText.style.display = "block"; + outputHtml.style.display = "none"; + outputHighlighter.display = "none"; + inputHighlighter.display = "none"; - outputText.value = Utils.printable(dataStr, true); - outputHtml.innerHTML = ""; + outputText.value = ""; + outputHtml.innerHTML = ""; + this.dishStr = ""; + length = data.byteLength; + + this.setFile(data); + break; + case "string": + default: + outputText.style.display = "block"; + outputHtml.style.display = "none"; + outputFile.style.display = "none"; + outputHighlighter.display = "block"; + inputHighlighter.display = "block"; + + outputText.value = Utils.printable(data, true); + outputHtml.innerHTML = ""; + + lines = data.count("\n") + 1; + length = data.length; + this.dishStr = data; + break; } this.manager.highlighter.removeHighlights(); - const lines = dataStr.count("\n") + 1; - this.setOutputInfo(dataStr.length, lines, duration); + this.setOutputInfo(length, lines, duration); +}; + + +/** + * Shows file details. + * + * @param {ArrayBuffer} buf + */ +OutputWaiter.prototype.setFile = function(buf) { + this.dishBuffer = buf; + const file = new File([buf], "output.dat"); + + // Display file overlay in output area with details + const fileOverlay = document.getElementById("output-file"), + fileSize = document.getElementById("output-file-size"); + + fileOverlay.style.display = "block"; + fileSize.textContent = file.size.toLocaleString() + " bytes"; +}; + + +/** + * Removes the output file and nulls its memory. + */ +OutputWaiter.prototype.closeFile = function() { + this.dishBuffer = null; + document.getElementById("output-file").style.display = "none"; +}; + + +/** + * Handler for file download events. + */ +OutputWaiter.prototype.downloadFile = function() { + this.filename = window.prompt("Please enter a filename:", this.filename || "download.dat"); + const file = new File([this.dishBuffer], this.filename); + + if (this.filename) FileSaver.saveAs(file, this.filename, false); +}; + + +/** + * Handler for file slice display events. + */ +OutputWaiter.prototype.displayFileSlice = function() { + const startTime = new Date().getTime(), + showFileOverlay = document.getElementById("show-file-overlay"), + sliceFromEl = document.getElementById("output-file-slice-from"), + sliceToEl = document.getElementById("output-file-slice-to"), + sliceFrom = parseInt(sliceFromEl.value, 10), + sliceTo = parseInt(sliceToEl.value, 10), + str = Utils.arrayBufferToStr(this.dishBuffer.slice(sliceFrom, sliceTo)); + + showFileOverlay.style.display = "block"; + this.set(str, "string", new Date().getTime() - startTime, true); +}; + + +/** + * Handler for show file overlay events. + * + * @param {Event} e + */ +OutputWaiter.prototype.showFileOverlayClick = function(e) { + const outputFile = document.getElementById("output-file"), + showFileOverlay = e.target; + + outputFile.style.display = "block"; + showFileOverlay.style.display = "none"; + this.setOutputInfo(this.dishBuffer.byteLength, null, 0); }; @@ -86,13 +194,17 @@ OutputWaiter.prototype.setOutputInfo = function(length, lines, duration) { let width = length.toString().length; width = width < 4 ? 4 : width; - const lengthStr = Utils.pad(length.toString(), width, " ").replace(/ /g, " "); - const linesStr = Utils.pad(lines.toString(), width, " ").replace(/ /g, " "); - const timeStr = Utils.pad(duration.toString() + "ms", width, " ").replace(/ /g, " "); + const lengthStr = length.toString().padStart(width, " ").replace(/ /g, " "); + const timeStr = (duration.toString() + "ms").padStart(width, " ").replace(/ /g, " "); - document.getElementById("output-info").innerHTML = "time: " + timeStr + - "
length: " + lengthStr + - "
lines: " + linesStr; + let msg = "time: " + timeStr + "
length: " + lengthStr; + + if (typeof lines === "number") { + const linesStr = lines.toString().padStart(width, " ").replace(/ /g, " "); + msg += "
lines: " + linesStr; + } + + document.getElementById("output-info").innerHTML = msg; document.getElementById("input-selection-info").innerHTML = ""; document.getElementById("output-selection-info").innerHTML = ""; }; @@ -105,17 +217,20 @@ OutputWaiter.prototype.setOutputInfo = function(length, lines, duration) { OutputWaiter.prototype.adjustWidth = function() { const output = document.getElementById("output"); const saveToFile = document.getElementById("save-to-file"); + const copyOutput = document.getElementById("copy-output"); const switchIO = document.getElementById("switch"); const undoSwitch = document.getElementById("undo-switch"); const maximiseOutput = document.getElementById("maximise-output"); if (output.clientWidth < 680) { saveToFile.childNodes[1].nodeValue = ""; + copyOutput.childNodes[1].nodeValue = ""; switchIO.childNodes[1].nodeValue = ""; undoSwitch.childNodes[1].nodeValue = ""; maximiseOutput.childNodes[1].nodeValue = ""; } else { saveToFile.childNodes[1].nodeValue = " Save to file"; + copyOutput.childNodes[1].nodeValue = " Copy output"; switchIO.childNodes[1].nodeValue = " Move output to input"; undoSwitch.childNodes[1].nodeValue = " Undo"; maximiseOutput.childNodes[1].nodeValue = @@ -126,24 +241,51 @@ OutputWaiter.prototype.adjustWidth = function() { /** * Handler for save click events. - * Saves the current output to a file, downloaded as a URL octet stream. + * Saves the current output to a file. */ OutputWaiter.prototype.saveClick = function() { - const data = Utils.toBase64(this.app.dishStr); - const filename = window.prompt("Please enter a filename:", "download.dat"); - - if (filename) { - const el = document.createElement("a"); - el.setAttribute("href", "data:application/octet-stream;base64;charset=utf-8," + data); - el.setAttribute("download", filename); - - // Firefox requires that the element be added to the DOM before it can be clicked - el.style.display = "none"; - document.body.appendChild(el); - - el.click(); - el.remove(); + if (!this.dishBuffer) { + this.dishBuffer = new Uint8Array(Utils.strToCharcode(this.dishStr)).buffer; } + this.downloadFile(); +}; + + +/** + * Handler for copy click events. + * Copies the output to the clipboard. + */ +OutputWaiter.prototype.copyClick = function() { + // Create invisible textarea to populate with the raw dishStr (not the printable version that + // contains dots instead of the actual bytes) + const textarea = document.createElement("textarea"); + textarea.style.position = "fixed"; + textarea.style.top = 0; + textarea.style.left = 0; + textarea.style.width = 0; + textarea.style.height = 0; + textarea.style.border = "none"; + + textarea.value = this.dishStr; + document.body.appendChild(textarea); + + // Select and copy the contents of this textarea + let success = false; + try { + textarea.select(); + success = this.dishStr && document.execCommand("copy"); + } catch (err) { + success = false; + } + + if (success) { + this.app.alert("Copied raw output successfully.", "success", 2000); + } else { + this.app.alert("Sorry, the output could not be copied.", "danger", 2000); + } + + // Clean up + document.body.removeChild(textarea); }; @@ -154,7 +296,17 @@ OutputWaiter.prototype.saveClick = function() { OutputWaiter.prototype.switchClick = function() { this.switchOrigData = this.manager.input.get(); document.getElementById("undo-switch").disabled = false; - this.app.setInput(this.app.dishStr); + if (this.dishBuffer) { + this.manager.input.setFile(new File([this.dishBuffer], "output.dat")); + this.manager.input.handleLoaderMessage({ + data: { + progress: 100, + fileBuffer: this.dishBuffer + } + }); + } else { + this.app.setInput(this.dishStr); + } }; @@ -169,7 +321,7 @@ OutputWaiter.prototype.undoSwitchClick = function() { /** * Handler for file switch click events. - * Moves a files data for items created via Utils.displayFilesAsHTML to the input. + * Moves a file's data for items created via Utils.displayFilesAsHTML to the input. */ OutputWaiter.prototype.fileSwitch = function(e) { e.preventDefault(); @@ -241,4 +393,14 @@ OutputWaiter.prototype.setStatusMsg = function(msg) { el.textContent = msg; }; + +/** + * Returns true if the output contains carriage returns + * + * @returns {boolean} + */ +OutputWaiter.prototype.containsCR = function() { + return this.dishStr.indexOf("\r") >= 0; +}; + export default OutputWaiter; diff --git a/src/web/RecipeWaiter.js b/src/web/RecipeWaiter.js index 3400fa32..be7ace65 100755 --- a/src/web/RecipeWaiter.js +++ b/src/web/RecipeWaiter.js @@ -253,7 +253,7 @@ RecipeWaiter.prototype.breakpointClick = function(e) { */ RecipeWaiter.prototype.operationDblclick = function(e) { e.target.remove(); - window.dispatchEvent(this.manager.statechange); + this.opRemove(e); }; @@ -266,7 +266,7 @@ RecipeWaiter.prototype.operationDblclick = function(e) { */ RecipeWaiter.prototype.operationChildDblclick = function(e) { e.target.parentNode.remove(); - window.dispatchEvent(this.manager.statechange); + this.opRemove(e); }; @@ -421,6 +421,7 @@ RecipeWaiter.prototype.dropdownToggleClick = function(e) { * @param {event} e */ RecipeWaiter.prototype.opAdd = function(e) { + log.debug(`'${e.target.querySelector(".arg-title").textContent}' added to recipe`); window.dispatchEvent(this.manager.statechange); }; @@ -433,6 +434,7 @@ RecipeWaiter.prototype.opAdd = function(e) { * @param {event} e */ RecipeWaiter.prototype.opRemove = function(e) { + log.debug("Operation removed from recipe"); window.dispatchEvent(this.manager.statechange); }; diff --git a/src/web/WorkerWaiter.js b/src/web/WorkerWaiter.js index 6fc69e40..1473e101 100644 --- a/src/web/WorkerWaiter.js +++ b/src/web/WorkerWaiter.js @@ -1,4 +1,3 @@ -import Utils from "../core/Utils.js"; import ChefWorker from "worker-loader?inline&fallback=false!../core/ChefWorker.js"; /** @@ -22,8 +21,10 @@ const WorkerWaiter = function(app, manager) { * Sets up the ChefWorker and associated listeners. */ WorkerWaiter.prototype.registerChefWorker = function() { + log.debug("Registering new ChefWorker"); this.chefWorker = new ChefWorker(); this.chefWorker.addEventListener("message", this.handleChefMessage.bind(this)); + this.setLogLevel(); let docURL = document.location.href.split(/[#?]/)[0]; const index = docURL.lastIndexOf("/"); @@ -41,8 +42,10 @@ WorkerWaiter.prototype.registerChefWorker = function() { */ WorkerWaiter.prototype.handleChefMessage = function(e) { const r = e.data; + log.debug("Receiving '" + r.action + "' from ChefWorker"); + switch (r.action) { - case "bakeSuccess": + case "bakeComplete": this.bakingComplete(r.data); break; case "bakeError": @@ -53,12 +56,14 @@ WorkerWaiter.prototype.handleChefMessage = function(e) { break; case "workerLoaded": this.app.workerLoaded = true; + log.debug("ChefWorker loaded"); this.app.loaded(); break; case "statusMessage": this.manager.output.setStatusMsg(r.data); 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": @@ -68,7 +73,7 @@ WorkerWaiter.prototype.handleChefMessage = function(e) { this.manager.highlighter.displayHighlights(r.data.pos, r.data.direction); break; default: - console.error("Unrecognised message from ChefWorker", e); + log.error("Unrecognised message from ChefWorker", e); break; } }; @@ -111,10 +116,10 @@ WorkerWaiter.prototype.bakingComplete = function(response) { this.app.handleError(response.error); } - this.app.dishStr = response.type === "html" ? Utils.stripHtmlTags(response.result, true) : response.result; this.app.progress = response.progress; this.manager.recipe.updateBreakpointIndicator(response.progress); this.manager.output.set(response.result, response.type, response.duration); + log.debug("--- Bake complete ---"); }; @@ -147,7 +152,7 @@ WorkerWaiter.prototype.bake = function(input, recipeConfig, options, progress, s * 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 {Objectp[]} [recipeConfig] + * @param {Object[]} [recipeConfig] */ WorkerWaiter.prototype.silentBake = function(recipeConfig) { this.chefWorker.postMessage({ @@ -180,4 +185,19 @@ WorkerWaiter.prototype.highlight = function(recipeConfig, direction, pos) { }; +/** + * Sets the console log level in the worker. + * + * @param {string} level + */ +WorkerWaiter.prototype.setLogLevel = function(level) { + if (!this.chefWorker) return; + + this.chefWorker.postMessage({ + action: "setLogLevel", + data: log.getLevel() + }); +}; + + export default WorkerWaiter; diff --git a/src/web/html/index.html b/src/web/html/index.html index 42864f1e..de439577 100755 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -1,17 +1,17 @@