Browse Source

Merge branch 'j433866-qrcodes'

n1474335 6 years ago
parent
commit
dfe31980b7

+ 6 - 0
CHANGELOG.md

@@ -2,6 +2,9 @@
 All major and minor version changes will be documented in this file. Details of patch-level version changes can be found in [commit messages](https://github.com/gchq/CyberChef/commits/master).
 
 
+### [8.17.0] - 2018-12-25
+- 'Generate QR Code' and 'Parse QR Code' operations added [@j433866] | [#448]
+
 ### [8.16.0] - 2018-12-19
 - 'Play Media' operation added [@anthony-arnold] | [#446]
 
@@ -79,6 +82,7 @@ All major and minor version changes will be documented in this file. Details of
 
 
 
+[8.17.0]: https://github.com/gchq/CyberChef/releases/tag/v8.17.0
 [8.16.0]: https://github.com/gchq/CyberChef/releases/tag/v8.16.0
 [8.15.0]: https://github.com/gchq/CyberChef/releases/tag/v8.15.0
 [8.14.0]: https://github.com/gchq/CyberChef/releases/tag/v8.14.0
@@ -103,6 +107,7 @@ All major and minor version changes will be documented in this file. Details of
 
 [@n1474335]: https://github.com/n1474335
 [@d98762625]: https://github.com/d98762625
+[@j433866]: https://github.com/j433866
 [@GCHQ77703]: https://github.com/GCHQ77703
 [@artemisbot]: https://github.com/artemisbot
 [@picapi]: https://github.com/picapi
@@ -144,3 +149,4 @@ All major and minor version changes will be documented in this file. Details of
 [#441]: https://github.com/gchq/CyberChef/pull/441
 [#443]: https://github.com/gchq/CyberChef/pull/443
 [#446]: https://github.com/gchq/CyberChef/pull/446
+[#448]: https://github.com/gchq/CyberChef/pull/448

File diff suppressed because it is too large
+ 473 - 56
package-lock.json


+ 3 - 0
package.json

@@ -92,6 +92,7 @@
     "exif-parser": "^0.1.12",
     "file-saver": "^2.0.0-rc.4",
     "highlight.js": "^9.13.1",
+    "jimp": "^0.6.0",
     "jquery": "^3.3.1",
     "js-crc": "^0.2.0",
     "js-sha3": "^0.8.0",
@@ -99,6 +100,7 @@
     "jsesc": "^2.5.1",
     "jsonpath": "^1.0.0",
     "jsonwebtoken": "^8.3.0",
+    "jsqr": "^1.1.1",
     "jsrsasign": "8.0.12",
     "kbpgp": "^2.0.82",
     "lodash": "^4.17.11",
@@ -113,6 +115,7 @@
     "nwmatcher": "^1.4.4",
     "otp": "^0.1.3",
     "popper.js": "^1.14.4",
+    "qr-image": "^3.2.0",
     "scryptsy": "^2.0.0",
     "snackbarjs": "^1.1.0",
     "sortablejs": "^1.7.0",

+ 2 - 0
src/core/config/Categories.json

@@ -363,6 +363,8 @@
             "Generate UUID",
             "Generate TOTP",
             "Generate HOTP",
+            "Generate QR Code",
+            "Parse QR Code",
             "Haversine distance",
             "Numberwang",
             "XKCD Random Number"

+ 119 - 0
src/core/operations/GenerateQRCode.mjs

@@ -0,0 +1,119 @@
+/**
+ * @author j433866 [j433866@gmail.com]
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import OperationError from "../errors/OperationError";
+import qr from "qr-image";
+import { toBase64 } from "../lib/Base64";
+import Magic from "../lib/Magic";
+import Utils from "../Utils";
+
+/**
+ * Generate QR Code operation
+ */
+class GenerateQRCode extends Operation {
+
+    /**
+     * GenerateQRCode constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "Generate QR Code";
+        this.module = "Image";
+        this.description = "Generates a Quick Response (QR) code from the input text.<br><br>A QR code is a type of matrix barcode (or two-dimensional barcode) first designed in 1994 for the automotive industry in Japan. A barcode is a machine-readable optical label that contains information about the item to which it is attached.";
+        this.infoURL = "https://wikipedia.org/wiki/QR_code";
+        this.inputType = "string";
+        this.outputType = "byteArray";
+        this.presentType = "html";
+        this.args = [
+            {
+                "name": "Image Format",
+                "type": "option",
+                "value": ["PNG", "SVG", "EPS", "PDF"]
+            },
+            {
+                "name": "Module size (px)",
+                "type": "number",
+                "value": 5
+            },
+            {
+                "name": "Margin (num modules)",
+                "type": "number",
+                "value": 2
+            },
+            {
+                "name": "Error correction",
+                "type": "option",
+                "value": ["Low", "Medium", "Quartile", "High"],
+                "defaultIndex": 1
+            }
+        ];
+    }
+
+    /**
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {byteArray}
+     */
+    run(input, args) {
+        const [format, size, margin, errorCorrection] = args;
+
+        // Create new QR image from the input data, and convert it to a buffer
+        const qrImage = qr.imageSync(input, {
+            type: format,
+            size: size,
+            margin: margin,
+            "ec_level": errorCorrection.charAt(0).toUpperCase()
+        });
+
+        if (qrImage == null) {
+            throw new OperationError("Error generating QR code.");
+        }
+
+        switch (format) {
+            case "SVG":
+            case "EPS":
+            case "PDF":
+                return [...Buffer.from(qrImage)];
+            case "PNG":
+                // Return the QR image buffer as a byte array
+                return [...qrImage];
+            default:
+                throw new OperationError("Unsupported QR code format.");
+        }
+    }
+
+    /**
+     * Displays the QR image using HTML for web apps
+     *
+     * @param {byteArray} data
+     * @returns {html}
+     */
+    present(data, args) {
+        if (!data.length) return "";
+
+        const [format] = args;
+
+        if (format === "PNG") {
+            let dataURI = "data:";
+            const type = Magic.magicFileType(data);
+            if (type && type.mime.indexOf("image") === 0){
+                dataURI += type.mime + ";";
+            } else {
+                throw new OperationError("Invalid PNG file generated by QR image");
+            }
+            dataURI += "base64," + toBase64(data);
+
+            return `<img src="${dataURI}">`;
+        }
+
+        return Utils.byteArrayToChars(data);
+    }
+
+}
+
+export default GenerateQRCode;

+ 107 - 0
src/core/operations/ParseQRCode.mjs

@@ -0,0 +1,107 @@
+/**
+ * @author j433866 [j433866@gmail.com]
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import OperationError from "../errors/OperationError";
+import Magic from "../lib/Magic";
+import jsqr from "jsqr";
+import jimp from "jimp";
+
+/**
+ * Parse QR Code operation
+ */
+class ParseQRCode extends Operation {
+
+    /**
+     * ParseQRCode constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "Parse QR Code";
+        this.module = "Image";
+        this.description = "Reads an image file and attempts to detect and read a Quick Response (QR) code from the image.<br><br><u>Normalise Image</u><br>Attempts to normalise the image before parsing it to improve detection of a QR code.";
+        this.infoURL = "https://wikipedia.org/wiki/QR_code";
+        this.inputType = "byteArray";
+        this.outputType = "string";
+        this.args = [
+            {
+                "name": "Normalise image",
+                "type": "boolean",
+                "value": false
+            }
+        ];
+    }
+
+    /**
+     * @param {byteArray} input
+     * @param {Object[]} args
+     * @returns {string}
+     */
+    async run(input, args) {
+        const type = Magic.magicFileType(input);
+        const [normalise] = args;
+
+        // Make sure that the input is an image
+        if (type && type.mime.indexOf("image") === 0) {
+            let image = input;
+
+            if (normalise) {
+                // Process the image to be easier to read by jsqr
+                // Disables the alpha channel
+                // Sets the image default background to white
+                // Normalises the image colours
+                // Makes the image greyscale
+                // Converts image to a JPEG
+                image = await new Promise((resolve, reject) => {
+                    jimp.read(Buffer.from(input))
+                        .then(image => {
+                            image
+                                .rgba(false)
+                                .background(0xFFFFFFFF)
+                                .normalize()
+                                .greyscale()
+                                .getBuffer(jimp.MIME_JPEG, (error, result) => {
+                                    resolve(result);
+                                });
+                        })
+                        .catch(err => {
+                            reject(new OperationError("Error reading the image file."));
+                        });
+                });
+            }
+
+            if (image instanceof OperationError) {
+                throw image;
+            }
+
+            return new Promise((resolve, reject) => {
+                jimp.read(Buffer.from(image))
+                    .then(image => {
+                        if (image.bitmap != null) {
+                            const qrData = jsqr(image.bitmap.data, image.getWidth(), image.getHeight());
+                            if (qrData != null) {
+                                resolve(qrData.data);
+                            } else {
+                                reject(new OperationError("Couldn't read a QR code from the image."));
+                            }
+                        } else {
+                            reject(new OperationError("Error reading the image file."));
+                        }
+                    })
+                    .catch(err => {
+                        reject(new OperationError("Error reading the image file."));
+                    });
+            });
+        } else {
+            throw new OperationError("Invalid file type.");
+        }
+
+    }
+
+}
+
+export default ParseQRCode;

+ 1 - 0
test/index.mjs

@@ -64,6 +64,7 @@ import "./tests/operations/OTP";
 import "./tests/operations/PGP";
 import "./tests/operations/PHP";
 import "./tests/operations/ParseIPRange";
+import "./tests/operations/ParseQRCode";
 import "./tests/operations/PowerSet";
 import "./tests/operations/Regex";
 import "./tests/operations/Register";

File diff suppressed because it is too large
+ 12 - 0
test/tests/operations/ParseQRCode.mjs


+ 3 - 0
webpack.config.js

@@ -46,6 +46,9 @@ module.exports = {
             raw: true,
             entryOnly: true
         }),
+        new webpack.DefinePlugin({
+            "process.browser": "true"
+        }),
         vendorCSS,
         projectCSS
     ],

Some files were not shown because too many files changed in this diff