diff --git a/.babelrc b/.babelrc
new file mode 100644
index 0000000..12e02c0
--- /dev/null
+++ b/.babelrc
@@ -0,0 +1,12 @@
+{
+ "presets": [
+ ["env", {
+ "targets": {
+ "chrome": 40,
+ "firefox": 35,
+ "edge": 14
+ },
+ "modules": false
+ }]
+ ]
+}
diff --git a/.gitignore b/.gitignore
index 5019a4e..570ea49 100755
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,6 @@
node_modules
npm-debug.log
-build/dev
-build/test
+build
docs/*
!docs/*.conf.json
!docs/*.ico
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..2c6a3fc
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,32 @@
+language: node_js
+node_js:
+ - node
+install: npm install
+before_script:
+ - npm install -g grunt
+script:
+ - grunt lint
+ - grunt test
+ - grunt docs
+ - grunt node
+ - grunt prod
+before_deploy:
+ - grunt copy:ghPages
+deploy:
+ - provider: pages
+ skip_cleanup: true
+ github_token: $GITHUB_TOKEN
+ local_dir: build/prod/
+ target_branch: gh-pages
+ on:
+ branch: master
+ - provider: releases
+ skip_cleaup: true
+ api_key:
+ secure: $GITHUB_API_KEY
+ file:
+ - build/prod/cyberchef.htm
+ - build/node/CyberChef.js
+ on:
+ repo: gchq/CyberChef
+ tags: true
diff --git a/Gruntfile.js b/Gruntfile.js
index af83ad9..82b4ca3 100755
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -1,38 +1,40 @@
-/* eslint-env node */
+var webpack = require("webpack"),
+ ExtractTextPlugin = require("extract-text-webpack-plugin"),
+ HtmlWebpackPlugin = require("html-webpack-plugin"),
+ Inliner = require("web-resource-inliner");
-module.exports = function(grunt) {
+module.exports = function (grunt) {
grunt.file.defaultEncoding = "utf8";
grunt.file.preserveBOM = false;
// Tasks
grunt.registerTask("dev",
"A persistent task which creates a development build whenever source files are modified.",
- ["clean:dev", "concat:css", "concat:js", "copy:htmlDev", "copy:staticDev", "chmod:build", "watch"]);
+ ["clean:dev", "webpack:webDev"]);
+
+ grunt.registerTask("node",
+ "Compiles CyberChef into a single NodeJS module.",
+ ["clean:node", "webpack:node", "chmod:build"]);
grunt.registerTask("test",
"A task which runs all the tests in test/tests.",
- ["clean:test", "concat:jsTest", "copy:htmlTest", "chmod:build", "execute:test"]);
-
- grunt.registerTask("prod",
- "Creates a production-ready build. Use the --msg flag to add a compile message.",
- ["eslint", "exec:stats", "clean", "jsdoc", "concat", "copy:htmlDev", "copy:htmlProd", "copy:htmlInline",
- "copy:staticDev", "copy:staticProd", "cssmin", "uglify:prod", "inline", "htmlmin", "chmod", "test"]);
+ ["clean:test", "webpack:tests", "execute:test"]);
grunt.registerTask("docs",
"Compiles documentation in the /docs directory.",
["clean:docs", "jsdoc", "chmod:docs"]);
- grunt.registerTask("stats",
- "Provides statistics about the code base such as how many lines there are as well as details of file sizes before and after compression.",
- ["concat:js", "uglify:prod", "exec:stats", "exec:repoSize", "exec:displayStats"]);
-
- grunt.registerTask("release",
- "Prepares and deploys a production version of CyberChef to the gh-pages branch.",
- ["copy:ghPages", "exec:deployGhPages"]);
+ grunt.registerTask("prod",
+ "Creates a production-ready build. Use the --msg flag to add a compile message.",
+ ["eslint", "clean:prod", "webpack:webProd", "inline", "chmod"]);
grunt.registerTask("default",
- "Lints the code base and shows stats",
- ["eslint", "exec:stats", "exec:displayStats"]);
+ "Lints the code base",
+ ["eslint", "exec:repoSize"]);
+
+ grunt.registerTask("inline",
+ "Compiles a production build of CyberChef into a single, portable web page.",
+ runInliner);
grunt.registerTask("doc", "docs");
grunt.registerTask("tests", "test");
@@ -41,179 +43,79 @@ module.exports = function(grunt) {
// Load tasks provided by each plugin
grunt.loadNpmTasks("grunt-eslint");
+ grunt.loadNpmTasks("grunt-webpack");
grunt.loadNpmTasks("grunt-jsdoc");
grunt.loadNpmTasks("grunt-contrib-clean");
- grunt.loadNpmTasks("grunt-contrib-concat");
grunt.loadNpmTasks("grunt-contrib-copy");
- grunt.loadNpmTasks("grunt-contrib-uglify");
- grunt.loadNpmTasks("grunt-contrib-cssmin");
- grunt.loadNpmTasks("grunt-contrib-htmlmin");
- grunt.loadNpmTasks("grunt-inline-alt");
grunt.loadNpmTasks("grunt-chmod");
grunt.loadNpmTasks("grunt-exec");
grunt.loadNpmTasks("grunt-execute");
- grunt.loadNpmTasks("grunt-contrib-watch");
- // JS includes
- var jsIncludes = [
- // Third party framework libraries
- "src/js/lib/jquery-2.1.1.js",
- "src/js/lib/bootstrap-3.3.6.js",
- "src/js/lib/split.js",
- "src/js/lib/bootstrap-switch.js",
- "src/js/lib/yahoo.js",
- "src/js/lib/snowfall.jquery.js",
-
- // Third party operation libraries
- "src/js/lib/cryptojs/core.js",
- "src/js/lib/cryptojs/x64-core.js",
- "src/js/lib/cryptojs/enc-base64.js",
- "src/js/lib/cryptojs/enc-utf16.js",
- "src/js/lib/cryptojs/md5.js",
- "src/js/lib/cryptojs/evpkdf.js",
- "src/js/lib/cryptojs/cipher-core.js",
- "src/js/lib/cryptojs/mode-cfb.js",
- "src/js/lib/cryptojs/mode-ctr-gladman.js",
- "src/js/lib/cryptojs/mode-ctr.js",
- "src/js/lib/cryptojs/mode-ecb.js",
- "src/js/lib/cryptojs/mode-ofb.js",
- "src/js/lib/cryptojs/format-hex.js",
- "src/js/lib/cryptojs/lib-typedarrays.js",
- "src/js/lib/cryptojs/pad-ansix923.js",
- "src/js/lib/cryptojs/pad-iso10126.js",
- "src/js/lib/cryptojs/pad-iso97971.js",
- "src/js/lib/cryptojs/pad-nopadding.js",
- "src/js/lib/cryptojs/pad-zeropadding.js",
- "src/js/lib/cryptojs/aes.js",
- "src/js/lib/cryptojs/hmac.js",
- "src/js/lib/cryptojs/rabbit-legacy.js",
- "src/js/lib/cryptojs/rabbit.js",
- "src/js/lib/cryptojs/ripemd160.js",
- "src/js/lib/cryptojs/sha1.js",
- "src/js/lib/cryptojs/sha256.js",
- "src/js/lib/cryptojs/sha224.js",
- "src/js/lib/cryptojs/sha512.js",
- "src/js/lib/cryptojs/sha384.js",
- "src/js/lib/cryptojs/sha3.js",
- "src/js/lib/cryptojs/tripledes.js",
- "src/js/lib/cryptojs/rc4.js",
- "src/js/lib/cryptojs/pbkdf2.js",
- "src/js/lib/cryptoapi/crypto-api.js",
- "src/js/lib/cryptoapi/hasher.md2.js",
- "src/js/lib/cryptoapi/hasher.md4.js",
- "src/js/lib/cryptoapi/hasher.sha0.js",
- "src/js/lib/jsbn/jsbn.js",
- "src/js/lib/jsbn/jsbn2.js",
- "src/js/lib/jsbn/base64.js",
- "src/js/lib/jsbn/ec.js",
- "src/js/lib/jsbn/prng4.js",
- "src/js/lib/jsbn/rng.js",
- "src/js/lib/jsbn/rsa.js",
- "src/js/lib/jsbn/sec.js",
- "src/js/lib/jsrasign/asn1-1.0.js",
- "src/js/lib/jsrasign/asn1hex-1.1.js",
- "src/js/lib/jsrasign/asn1x509-1.0.js",
- "src/js/lib/jsrasign/base64x-1.1.js",
- "src/js/lib/jsrasign/crypto-1.1.js",
- "src/js/lib/jsrasign/dsa-modified-1.0.js",
- "src/js/lib/jsrasign/ecdsa-modified-1.0.js",
- "src/js/lib/jsrasign/ecparam-1.0.js",
- "src/js/lib/jsrasign/keyutil-1.0.js",
- "src/js/lib/jsrasign/x509-1.1.js",
- "src/js/lib/blowfish.dojo.js",
- "src/js/lib/rawdeflate.js",
- "src/js/lib/rawinflate.js",
- "src/js/lib/zip.js",
- "src/js/lib/unzip.js",
- "src/js/lib/zlib_and_gzip.js",
- "src/js/lib/bzip2.js",
- "src/js/lib/punycode.js",
- "src/js/lib/uas_parser.js",
- "src/js/lib/esprima.js",
- "src/js/lib/escodegen.browser.js",
- "src/js/lib/esmangle.min.js",
- "src/js/lib/diff.js",
- "src/js/lib/moment.js",
- "src/js/lib/moment-timezone.js",
- "src/js/lib/prettify.js",
- "src/js/lib/vkbeautify.js",
- "src/js/lib/Sortable.js",
- "src/js/lib/bootstrap-colorpicker.js",
- "src/js/lib/es6-promise.auto.js",
- "src/js/lib/xpath.js",
-
- // Custom libraries
- "src/js/lib/canvascomponents.js",
-
- // Utility functions
- "src/js/core/Utils.js",
-
- // Operation objects
- "src/js/operations/*.js",
-
- // Core framework objects
- "src/js/core/*.js",
- "src/js/config/Categories.js",
- "src/js/config/OperationConfig.js",
-
- // HTML view objects
- "src/js/views/html/*.js",
- "!src/js/views/html/main.js",
-
- ];
-
- var jsAppFiles = jsIncludes.concat([
- // Start the main app!
- "src/js/views/html/main.js",
- ]);
-
- var jsTestFiles = jsIncludes.concat([
- "test/TestRegister.js",
- "test/tests/**/*.js",
- "test/TestRunner.js",
- ]);
-
- var banner = '/**\n\
- * CyberChef - The Cyber Swiss Army Knife\n\
- *\n\
- * @copyright Crown Copyright 2016\n\
- * @license Apache-2.0\n\
- *\n\
- * Copyright 2016 Crown Copyright\n\
- *\n\
- * Licensed under the Apache License, Version 2.0 (the "License");\n\
- * you may not use this file except in compliance with the License.\n\
- * You may obtain a copy of the License at\n\
- *\n\
- * http://www.apache.org/licenses/LICENSE-2.0\n\
- *\n\
- * Unless required by applicable law or agreed to in writing, software\n\
- * distributed under the License is distributed on an "AS IS" BASIS,\n\
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n\
- * See the License for the specific language governing permissions and\n\
- * limitations under the License.\n\
- */\n';
-
- var templateOptions = {
- data: {
- compileTime: grunt.template.today("dd/mm/yyyy HH:MM:ss") + " UTC",
- compileMsg: grunt.option("compile-msg") || grunt.option("msg") || "",
- codebaseStats: grunt.file.read("src/static/stats.txt").split("\n").join("
")
- }
- };
-
// Project configuration
+ var compileTime = grunt.template.today("dd/mm/yyyy HH:MM:ss") + " UTC",
+ banner = "/**\n" +
+ "* CyberChef - The Cyber Swiss Army Knife\n" +
+ "*\n" +
+ "* @copyright Crown Copyright 2016\n" +
+ "* @license Apache-2.0\n" +
+ "*\n" +
+ "* Copyright 2016 Crown Copyright\n" +
+ "*\n" +
+ '* Licensed under the Apache License, Version 2.0 (the "License");\n' +
+ "* you may not use this file except in compliance with the License.\n" +
+ "* You may obtain a copy of the License at\n" +
+ "*\n" +
+ "* http://www.apache.org/licenses/LICENSE-2.0\n" +
+ "*\n" +
+ "* Unless required by applicable law or agreed to in writing, software\n" +
+ '* distributed under the License is distributed on an "AS IS" BASIS,\n' +
+ "* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" +
+ "* See the License for the specific language governing permissions and\n" +
+ "* limitations under the License.\n" +
+ "*/\n";
+
+ /**
+ * Compiles a production build of CyberChef into a single, portable web page.
+ */
+ function runInliner() {
+ var inlinerError = false;
+ Inliner.html({
+ relativeTo: "build/prod/",
+ fileContent: grunt.file.read("build/prod/cyberchef.htm"),
+ images: true,
+ svgs: true,
+ scripts: true,
+ links: true,
+ strict: true
+ }, function(error, result) {
+ if (error) {
+ console.log(error);
+ inlinerError = true;
+ return false;
+ }
+ grunt.file.write("build/prod/cyberchef.htm", result);
+ });
+
+ return !inlinerError;
+ }
+
grunt.initConfig({
+ clean: {
+ dev: ["build/dev/*"],
+ prod: ["build/prod/*"],
+ test: ["build/test/*"],
+ node: ["build/node/*"],
+ docs: ["docs/*", "!docs/*.conf.json", "!docs/*.ico"],
+ },
eslint: {
options: {
- configFile: "src/js/.eslintrc.json"
+ configFile: "src/.eslintrc.json"
},
- gruntfile: ["Gruntfile.js"],
- core: ["src/js/core/**/*.js"],
- config: ["src/js/config/**/*.js"],
- views: ["src/js/views/**/*.js"],
- operations: ["src/js/operations/**/*.js"],
+ configs: ["Gruntfile.js"],
+ core: ["src/core/**/*.js", "!src/core/lib/**/*"],
+ web: ["src/web/**/*.js"],
+ node: ["src/node/**/*.js"],
tests: ["test/**/*.js"],
},
jsdoc: {
@@ -226,209 +128,183 @@ module.exports = function(grunt) {
},
all: {
src: [
- "src/js/**/*.js",
- "!src/js/lib/**/*",
+ "src/**/*.js",
+ "!src/core/lib/**/*",
],
}
},
- clean: {
- dev: ["build/dev/*"],
- prod: ["build/prod/*"],
- test: ["build/test/*"],
- docs: ["docs/*", "!docs/*.conf.json", "!docs/*.ico"],
- },
- concat: {
+ webpack: {
options: {
- process: templateOptions
- },
- css: {
- options: {
- banner: banner.replace(/\/\*\*/g, "/*!"),
- process: function(content, srcpath) {
- // Change special comments from /** to /*! to comply with cssmin
- content = content.replace(/^\/\*\* /g, "/*! ");
- return grunt.template.process(content);
+ plugins: [
+ new webpack.ProvidePlugin({
+ $: "jquery",
+ jQuery: "jquery",
+ moment: "moment-timezone"
+ }),
+ new webpack.BannerPlugin({
+ banner: banner,
+ raw: true,
+ entryOnly: true
+ }),
+ new webpack.DefinePlugin({
+ COMPILE_TIME: JSON.stringify(compileTime),
+ COMPILE_MSG: JSON.stringify(grunt.option("compile-msg") || grunt.option("msg") || "")
+ }),
+ new ExtractTextPlugin("styles.css"),
+ ],
+ resolve: {
+ alias: {
+ jquery: "jquery/src/jquery"
}
},
- src: [
- "src/css/lib/**/*.css",
- "src/css/structure/**/*.css",
- "src/css/themes/classic.css"
+ module: {
+ rules: [
+ {
+ test: /\.js$/,
+ exclude: /node_modules/,
+ loader: "babel-loader?compact=false"
+ },
+ {
+ test: /\.css$/,
+ use: ExtractTextPlugin.extract({
+ use: "css-loader?minimize"
+ })
+ },
+ {
+ test: /\.less$/,
+ use: ExtractTextPlugin.extract({
+ use: [
+ { loader: "css-loader?minimize" },
+ { loader: "less-loader" }
+ ]
+ })
+ },
+ {
+ test: /\.(ico|eot|ttf|woff|woff2)$/,
+ loader: "url-loader",
+ options: {
+ limit: 10000
+ }
+ },
+ { // First party images are saved as files to be cached
+ test: /\.(png|jpg|gif|svg)$/,
+ exclude: /node_modules/,
+ loader: "file-loader",
+ options: {
+ name: "images/[name].[ext]"
+ }
+ },
+ { // Third party images are inlined
+ test: /\.(png|jpg|gif|svg)$/,
+ exclude: /web\/static/,
+ loader: "url-loader",
+ options: {
+ limit: 10000
+ }
+ },
+ ]
+ },
+ stats: {
+ children: false
+ }
+ },
+ webDev: {
+ target: "web",
+ entry: "./src/web/index.js",
+ output: {
+ filename: "scripts.js",
+ path: __dirname + "/build/dev"
+ },
+ plugins: [
+ new HtmlWebpackPlugin({
+ filename: "index.html",
+ template: "./src/web/html/index.html",
+ compileTime: compileTime
+ })
],
- dest: "build/dev/styles.css"
+ watch: true
},
- js: {
- options: {
- banner: '"use strict";\n'
+ webProd: {
+ target: "web",
+ entry: "./src/web/index.js",
+ output: {
+ filename: "scripts.js",
+ path: __dirname + "/build/prod"
},
- src: jsAppFiles,
- dest: "build/dev/scripts.js"
+ plugins: [
+ new webpack.optimize.UglifyJsPlugin({
+ compress: {
+ "screw_ie8": true,
+ "dead_code": true,
+ "unused": true,
+ "warnings": false
+ },
+ comments: false,
+ }),
+ new HtmlWebpackPlugin({ // Main version
+ filename: "index.html",
+ template: "./src/web/html/index.html",
+ compileTime: compileTime,
+ minify: {
+ removeComments: true,
+ collapseWhitespace: true,
+ minifyJS: true,
+ minifyCSS: true
+ }
+ }),
+ new HtmlWebpackPlugin({ // Inline version
+ filename: "cyberchef.htm",
+ template: "./src/web/html/index.html",
+ compileTime: compileTime,
+ inline: true,
+ minify: {
+ removeComments: true,
+ collapseWhitespace: true,
+ minifyJS: true,
+ minifyCSS: true
+ }
+ }),
+ ]
},
- jsTest: {
- options: {
- banner: '"use strict";\n'
- },
- src: jsTestFiles,
- dest: "build/test/tests.js"
+ tests: {
+ target: "node",
+ entry: "./test/index.js",
+ output: {
+ filename: "index.js",
+ path: __dirname + "/build/test"
+ }
+ },
+ node: {
+ target: "node",
+ entry: "./src/node/index.js",
+ output: {
+ filename: "CyberChef.js",
+ path: __dirname + "/build/node",
+ library: "CyberChef",
+ libraryTarget: "commonjs2"
+ }
}
},
copy: {
- htmlDev: {
- options: {
- process: function(content, srcpath) {
- return grunt.template.process(content, templateOptions);
- }
- },
- src: "src/html/index.html",
- dest: "build/dev/index.html"
- },
- htmlTest: {
- src: "test/test.html",
- dest: "build/test/index.html"
- },
- htmlProd: {
- options: {
- process: function(content, srcpath) {
- return grunt.template.process(content, templateOptions);
- }
- },
- src: "src/html/index.html",
- dest: "build/prod/index.html"
- },
- htmlInline: {
- options: {
- process: function(content, srcpath) {
- // TODO: Do all this in Jade
- content = content.replace(
- 'Download CyberChef',
- 'Compile time: ' + grunt.template.today("dd/mm/yyyy HH:MM:ss") + " UTC");
- return grunt.template.process(content, templateOptions);
- }
- },
- src: "src/html/index.html",
- dest: "build/prod/cyberchef.htm"
- },
- staticDev: {
- files: [
- {
- expand: true,
- cwd: "src/static/",
- src: [
- "**/*",
- "**/.*",
- "!stats.txt",
- "!ga.html"
- ],
- dest: "build/dev/"
- }
- ]
- },
- staticProd: {
- files: [
- {
- expand: true,
- cwd: "src/static/",
- src: [
- "**/*",
- "**/.*",
- "!stats.txt",
- "!ga.html"
- ],
- dest: "build/prod/"
- }
- ]
- },
ghPages: {
options: {
- process: function(content, srcpath) {
+ process: function (content, srcpath) {
// Add Google Analytics code to index.html
content = content.replace("