Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add QR code generation and parsing #448

Merged
merged 10 commits into from
Dec 25, 2018
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,15 @@
"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",
"jsbn": "^1.1.0",
"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",
Expand All @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions src/core/config/Categories.json
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,8 @@
"Generate UUID",
"Generate TOTP",
"Generate HOTP",
"Generate QR Code",
"Parse QR Code",
"Haversine distance",
"Render Image",
"Remove EXIF",
Expand Down
104 changes: 104 additions & 0 deletions src/core/operations/GenerateQRCode.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/**
* @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";

/**
* Generate QR Code operation
*/
class GenerateQRCode extends Operation {

/**
* GenerateQRCode constructor
*/
constructor() {
super();

this.name = "Generate QR Code";
this.module = "Image";
this.description = "Generates a QR code from text.";
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"]
},
{
"name": "Size of QR module",
"type": "number",
"value": 5
},
{
"name": "Margin",
"type": "number",
"value": 2
}
];
}

/**
* @param {string} input
* @param {Object[]} args
* @returns {File}
*/
run(input, args) {
// Create new QR image from the input data, and convert it to a buffer
const [format, size, margin] = args;
const qrImage = qr.imageSync(input, { type: format, size: size, margin: margin });
if (qrImage == null) {
throw new OperationError("Error generating QR code.");
}
if (format === "SVG") {
return [...Buffer.from(qrImage)];
} else if (format === "PNG") {
// Return the QR image buffer as a byte array
return [...qrImage];
} else {
throw new OperationError("Error generating QR code.");
}
}

/**
* 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 === "SVG") {
let outputData = "";
for (let i = 0; i < data.length; i++){
outputData += String.fromCharCode(parseInt(data[i]));
}
return outputData;
} else {
let dataURI = "data:";
const type = Magic.magicFileType(data);
if (type && type.mime.indexOf("image") === 0){
dataURI += type.mime + ";";
} else {
throw new OperationError("Invalid file type");
}
dataURI += "base64," + toBase64(data);

return "<img src='" + dataURI + "'>";
}
}

}

export default GenerateQRCode;
105 changes: 105 additions & 0 deletions src/core/operations/ParseQRCode.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/**
* @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 QR code from the image.<br><br><u>Normalise Image</u><br>Attempt to normalise the image before parsing it, to try and 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": true
}
];
}

/**
* @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 normalisedImage = null;
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
normalisedImage = 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."));
});
});
} else {
normalisedImage = input;
}
if (normalisedImage instanceof OperationError){
return normalisedImage;
}
return new Promise((resolve, reject) => {
jimp.read(Buffer.from(normalisedImage))
.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 normalised image file."));
}
})
.catch(err => {
reject(new OperationError("Error reading the normalised image file."));
});
});
} else {
throw new OperationError("Invalid file type.");
}

}

}

export default ParseQRCode;
1 change: 1 addition & 0 deletions test/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,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";
Expand Down
71 changes: 71 additions & 0 deletions test/tests/operations/ParseQRCode.mjs

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ module.exports = {
raw: true,
entryOnly: true
}),
new webpack.DefinePlugin({
"process.browser": "true"
}),
vendorCSS,
projectCSS
],
Expand Down