diff --git a/src/core/operations/ExtractEXIF.mjs b/src/core/operations/ExtractEXIF.mjs
new file mode 100644
index 00000000..ae293763
--- /dev/null
+++ b/src/core/operations/ExtractEXIF.mjs
@@ -0,0 +1,56 @@
+/**
+ * @author tlwr [toby@toby.codes]
+ * @copyright Crown Copyright 2017
+ * @license Apache-2.0
+ */
+
+import ExifParser from "exif-parser";
+import Operation from "../Operation";
+import OperationError from "../errors/OperationError";
+
+/**
+ * Extract EXIF operation
+ */
+class ExtractEXIF extends Operation {
+
+ /**
+ * ExtractEXIF constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "Extract EXIF";
+ this.module = "Image";
+ this.description = "Extracts EXIF data from an image.\n
\nEXIF data is metadata embedded in images (JPEG, JPG, TIFF) and audio files.\n
\nEXIF data from photos usually contains information about the image file itself as well as the device used to create it.";
+ this.inputType = "ArrayBuffer";
+ this.outputType = "string";
+ this.args = [];
+ }
+
+ /**
+ * @param {ArrayBuffer} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ try {
+ const parser = ExifParser.create(input);
+ const result = parser.parse();
+
+ const lines = [];
+ for (const tagName in result.tags) {
+ const value = result.tags[tagName];
+ lines.push(`${tagName}: ${value}`);
+ }
+
+ const numTags = lines.length;
+ lines.unshift(`Found ${numTags} tags.\n`);
+ return lines.join("\n");
+ } catch (err) {
+ throw new OperationError(`Could not extract EXIF data from image: ${err}`);
+ }
+ }
+
+}
+
+export default ExtractEXIF;
diff --git a/src/core/operations/RemoveEXIF.mjs b/src/core/operations/RemoveEXIF.mjs
new file mode 100644
index 00000000..0d5c9d49
--- /dev/null
+++ b/src/core/operations/RemoveEXIF.mjs
@@ -0,0 +1,50 @@
+/**
+ * @author tlwr [toby@toby.codes]
+ * @copyright Crown Copyright 2017
+ * @license Apache-2.0
+ */
+
+import { removeEXIF } from "../vendor/remove-exif";
+import Operation from "../Operation";
+import OperationError from "../errors/OperationError";
+
+/**
+ * Remove EXIF operation
+ */
+class RemoveEXIF extends Operation {
+
+ /**
+ * RemoveEXIF constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "Remove EXIF";
+ this.module = "Image";
+ this.description = "Removes EXIF data from a JPEG image.\n
\nEXIF data embedded in photos usually contains information about the image file itself as well as the device used to create it.";
+ this.inputType = "byteArray";
+ this.outputType = "byteArray";
+ this.args = [];
+ }
+
+ /**
+ * @param {byteArray} input
+ * @param {Object[]} args
+ * @returns {byteArray}
+ */
+ run(input, args) {
+ // Do nothing if input is empty
+ if (input.length === 0) return input;
+
+ try {
+ return removeEXIF(input);
+ } catch (err) {
+ // Simply return input if no EXIF data is found
+ if (err === "Exif not found.") return input;
+ throw new OperationError(`Could not remove EXIF data from image: ${err}`);
+ }
+ }
+
+}
+
+export default RemoveEXIF;
diff --git a/src/core/operations/RenderImage.mjs b/src/core/operations/RenderImage.mjs
new file mode 100644
index 00000000..b99fbe9b
--- /dev/null
+++ b/src/core/operations/RenderImage.mjs
@@ -0,0 +1,91 @@
+/**
+ * @author tlwr [toby@toby.codes]
+ * @copyright Crown Copyright 2017
+ * @license Apache-2.0
+ */
+
+import { fromBase64, toBase64 } from "../lib/Base64";
+import { fromHex } from "../lib/Hex";
+import Operation from "../Operation";
+import Utils from "../Utils";
+import Magic from "../lib/Magic";
+
+/**
+ * Render Image operation
+ */
+class RenderImage extends Operation {
+
+ /**
+ * RenderImage constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "Render Image";
+ this.module = "Image";
+ this.description = "Displays the input as an image. Supports the following formats:
";
+ this.inputType = "string";
+ this.outputType = "html";
+ this.args = [
+ {
+ "name": "Input format",
+ "type": "option",
+ "value": ["Raw", "Base64", "Hex"]
+ }
+ ];
+ this.patterns = [
+ {
+ "match": "^(?:\\xff\\xd8\\xff|\\x89\\x50\\x4e\\x47|\\x47\\x49\\x46|.{8}\\x57\\x45\\x42\\x50|\\x42\\x4d)",
+ "flags": "",
+ "args": [
+ "Raw"
+ ],
+ "useful": true
+ }
+ ];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {html}
+ */
+ run(input, args) {
+ const inputFormat = args[0];
+ let dataURI = "data:";
+
+ if (!input.length) return "";
+
+ // Convert input to raw bytes
+ switch (inputFormat) {
+ case "Hex":
+ input = fromHex(input);
+ break;
+ case "Base64":
+ // Don't trust the Base64 entered by the user.
+ // Unwrap it first, then re-encode later.
+ input = fromBase64(input, undefined, "byteArray");
+ break;
+ case "Raw":
+ default:
+ input = Utils.strToByteArray(input);
+ break;
+ }
+
+ // Determine file type
+ const type = Magic.magicFileType(input);
+ if (type && type.mime.indexOf("image") === 0) {
+ dataURI += type.mime + ";";
+ } else {
+ throw "Invalid file type";
+ }
+
+ // Add image data to URI
+ dataURI += "base64," + toBase64(input);
+
+ return "";
+ }
+
+}
+
+export default RenderImage;
diff --git a/src/core/operations/legacy/Code.js b/src/core/operations/legacy/Code.js
deleted file mode 100755
index ad777d01..00000000
--- a/src/core/operations/legacy/Code.js
+++ /dev/null
@@ -1,540 +0,0 @@
-import {camelCase, kebabCase, snakeCase} from "lodash";
-import vkbeautify from "vkbeautify";
-import {DOMParser} from "xmldom";
-import xpath from "xpath";
-import jpath from "jsonpath";
-import nwmatcher from "nwmatcher";
-import hljs from "highlight.js";
-
-
-/**
- * Code operations.
- *
- * @author n1474335 [n1474335@gmail.com]
- * @copyright Crown Copyright 2016
- * @license Apache-2.0
- *
- * @namespace
- */
-const Code = {
-
- /**
- * @constant
- * @default
- */
- LANGUAGES: ["auto detect"].concat(hljs.listLanguages()),
-
- /**
- * Syntax highlighter operation.
- *
- * @param {string} input
- * @param {Object[]} args
- * @returns {html}
- */
- runSyntaxHighlight: function(input, args) {
- const language = args[0];
-
- if (language === "auto detect") {
- return hljs.highlightAuto(input).value;
- }
-
- return hljs.highlight(language, input, true).value;
- },
-
-
- /**
- * @constant
- * @default
- */
- BEAUTIFY_INDENT: "\\t",
-
- /**
- * XML Beautify operation.
- *
- * @param {string} input
- * @param {Object[]} args
- * @returns {string}
- */
- runXmlBeautify: function(input, args) {
- const indentStr = args[0];
- return vkbeautify.xml(input, indentStr);
- },
-
-
- /**
- * JSON Beautify operation.
- *
- * @param {string} input
- * @param {Object[]} args
- * @returns {string}
- */
- runJsonBeautify: function(input, args) {
- const indentStr = args[0];
- if (!input) return "";
- return vkbeautify.json(input, indentStr);
- },
-
-
- /**
- * CSS Beautify operation.
- *
- * @param {string} input
- * @param {Object[]} args
- * @returns {string}
- */
- runCssBeautify: function(input, args) {
- const indentStr = args[0];
- return vkbeautify.css(input, indentStr);
- },
-
-
- /**
- * SQL Beautify operation.
- *
- * @param {string} input
- * @param {Object[]} args
- * @returns {string}
- */
- runSqlBeautify: function(input, args) {
- const indentStr = args[0];
- return vkbeautify.sql(input, indentStr);
- },
-
-
- /**
- * @constant
- * @default
- */
- PRESERVE_COMMENTS: false,
-
- /**
- * XML Minify operation.
- *
- * @param {string} input
- * @param {Object[]} args
- * @returns {string}
- */
- runXmlMinify: function(input, args) {
- const preserveComments = args[0];
- return vkbeautify.xmlmin(input, preserveComments);
- },
-
-
- /**
- * JSON Minify operation.
- *
- * @param {string} input
- * @param {Object[]} args
- * @returns {string}
- */
- runJsonMinify: function(input, args) {
- if (!input) return "";
- return vkbeautify.jsonmin(input);
- },
-
-
- /**
- * CSS Minify operation.
- *
- * @param {string} input
- * @param {Object[]} args
- * @returns {string}
- */
- runCssMinify: function(input, args) {
- const preserveComments = args[0];
- return vkbeautify.cssmin(input, preserveComments);
- },
-
-
- /**
- * SQL Minify operation.
- *
- * @param {string} input
- * @param {Object[]} args
- * @returns {string}
- */
- runSqlMinify: function(input, args) {
- return vkbeautify.sqlmin(input);
- },
-
-
- /**
- * Generic Code Beautify operation.
- *
- * Yeeeaaah...
- *
- * I'm not proud of this code, but seriously, try writing a generic lexer and parser that
- * correctly generates an AST for multiple different languages. I have tried, and I can tell
- * you it's pretty much impossible.
- *
- * This basically works. That'll have to be good enough. It's not meant to produce working code,
- * just slightly more readable code.
- *
- * Things that don't work:
- * - For loop formatting
- * - Do-While loop formatting
- * - Switch/Case indentation
- * - Bit shift operators
- *
- * @author n1474335 [n1474335@gmail.com]
- * @param {string} input
- * @param {Object[]} args
- * @returns {string}
- */
- runGenericBeautify: function(input, args) {
- let code = input,
- t = 0,
- preservedTokens = [],
- m;
-
- // Remove strings
- const sstrings = /'([^'\\]|\\.)*'/g;
- while ((m = sstrings.exec(code))) {
- code = preserveToken(code, m, t++);
- sstrings.lastIndex = m.index;
- }
-
- const dstrings = /"([^"\\]|\\.)*"/g;
- while ((m = dstrings.exec(code))) {
- code = preserveToken(code, m, t++);
- dstrings.lastIndex = m.index;
- }
-
- // Remove comments
- const scomments = /\/\/[^\n\r]*/g;
- while ((m = scomments.exec(code))) {
- code = preserveToken(code, m, t++);
- scomments.lastIndex = m.index;
- }
-
- const mcomments = /\/\*[\s\S]*?\*\//gm;
- while ((m = mcomments.exec(code))) {
- code = preserveToken(code, m, t++);
- mcomments.lastIndex = m.index;
- }
-
- const hcomments = /(^|\n)#[^\n\r#]+/g;
- while ((m = hcomments.exec(code))) {
- code = preserveToken(code, m, t++);
- hcomments.lastIndex = m.index;
- }
-
- // Remove regexes
- const regexes = /\/.*?[^\\]\/[gim]{0,3}/gi;
- while ((m = regexes.exec(code))) {
- code = preserveToken(code, m, t++);
- regexes.lastIndex = m.index;
- }
-
- code = code
- // Create newlines after ;
- .replace(/;/g, ";\n")
- // Create newlines after { and around }
- .replace(/{/g, "{\n")
- .replace(/}/g, "\n}\n")
- // Remove carriage returns
- .replace(/\r/g, "")
- // Remove all indentation
- .replace(/^\s+/g, "")
- .replace(/\n\s+/g, "\n")
- // Remove trailing spaces
- .replace(/\s*$/g, "")
- .replace(/\n{/g, "{");
-
- // Indent
- let i = 0,
- level = 0,
- indent;
- while (i < code.length) {
- switch (code[i]) {
- case "{":
- level++;
- break;
- case "\n":
- if (i+1 >= code.length) break;
-
- if (code[i+1] === "}") level--;
- indent = (level >= 0) ? Array(level*4+1).join(" ") : "";
-
- code = code.substring(0, i+1) + indent + code.substring(i+1);
- if (level > 0) i += level*4;
- break;
- }
- i++;
- }
-
- code = code
- // Add strategic spaces
- .replace(/\s*([!<>=+-/*]?)=\s*/g, " $1= ")
- .replace(/\s*<([=]?)\s*/g, " <$1 ")
- .replace(/\s*>([=]?)\s*/g, " >$1 ")
- .replace(/([^+])\+([^+=])/g, "$1 + $2")
- .replace(/([^-])-([^-=])/g, "$1 - $2")
- .replace(/([^*])\*([^*=])/g, "$1 * $2")
- .replace(/([^/])\/([^/=])/g, "$1 / $2")
- .replace(/\s*,\s*/g, ", ")
- .replace(/\s*{/g, " {")
- .replace(/}\n/g, "}\n\n")
- // Hacky horribleness
- .replace(/(if|for|while|with|elif|elseif)\s*\(([^\n]*)\)\s*\n([^{])/gim, "$1 ($2)\n $3")
- .replace(/(if|for|while|with|elif|elseif)\s*\(([^\n]*)\)([^{])/gim, "$1 ($2) $3")
- .replace(/else\s*\n([^{])/gim, "else\n $1")
- .replace(/else\s+([^{])/gim, "else $1")
- // Remove strategic spaces
- .replace(/\s+;/g, ";")
- .replace(/\{\s+\}/g, "{}")
- .replace(/\[\s+\]/g, "[]")
- .replace(/}\s*(else|catch|except|finally|elif|elseif|else if)/gi, "} $1");
-
- // Replace preserved tokens
- const ptokens = /###preservedToken(\d+)###/g;
- while ((m = ptokens.exec(code))) {
- const ti = parseInt(m[1], 10);
- code = code.substring(0, m.index) + preservedTokens[ti] + code.substring(m.index + m[0].length);
- ptokens.lastIndex = m.index;
- }
-
- return code;
-
- /**
- * Replaces a matched token with a placeholder value.
- */
- function preserveToken(str, match, t) {
- preservedTokens[t] = match[0];
- return str.substring(0, match.index) +
- "###preservedToken" + t + "###" +
- str.substring(match.index + match[0].length);
- }
- },
-
-
- /**
- * @constant
- * @default
- */
- XPATH_INITIAL: "",
-
- /**
- * @constant
- * @default
- */
- XPATH_DELIMITER: "\\n",
-
- /**
- * XPath expression operation.
- *
- * @author Mikescher (https://github.com/Mikescher | https://mikescher.com)
- * @param {string} input
- * @param {Object[]} args
- * @returns {string}
- */
- runXpath: function(input, args) {
- let query = args[0],
- delimiter = args[1];
-
- let doc;
- try {
- doc = new DOMParser().parseFromString(input, "application/xml");
- } catch (err) {
- return "Invalid input XML.";
- }
-
- let nodes;
- try {
- nodes = xpath.select(query, doc);
- } catch (err) {
- return "Invalid XPath. Details:\n" + err.message;
- }
-
- const nodeToString = function(node) {
- return node.toString();
- };
-
- return nodes.map(nodeToString).join(delimiter);
- },
-
-
- /**
- * @constant
- * @default
- */
- JPATH_INITIAL: "",
-
- /**
- * @constant
- * @default
- */
- JPATH_DELIMITER: "\\n",
-
- /**
- * JPath expression operation.
- *
- * @author Matt C (matt@artemisbot.uk)
- * @param {string} input
- * @param {Object[]} args
- * @returns {string}
- */
- runJpath: function(input, args) {
- let query = args[0],
- delimiter = args[1],
- results,
- obj;
-
- try {
- obj = JSON.parse(input);
- } catch (err) {
- return "Invalid input JSON: " + err.message;
- }
-
- try {
- results = jpath.query(obj, query);
- } catch (err) {
- return "Invalid JPath expression: " + err.message;
- }
-
- return results.map(result => JSON.stringify(result)).join(delimiter);
- },
-
-
- /**
- * @constant
- * @default
- */
- CSS_SELECTOR_INITIAL: "",
-
- /**
- * @constant
- * @default
- */
- CSS_QUERY_DELIMITER: "\\n",
-
- /**
- * CSS selector operation.
- *
- * @author Mikescher (https://github.com/Mikescher | https://mikescher.com)
- * @author n1474335 [n1474335@gmail.com]
- * @param {string} input
- * @param {Object[]} args
- * @returns {string}
- */
- runCSSQuery: function(input, args) {
- let query = args[0],
- delimiter = args[1],
- parser = new DOMParser(),
- dom,
- result;
-
- if (!query.length || !input.length) {
- return "";
- }
-
- try {
- dom = parser.parseFromString(input);
- } catch (err) {
- return "Invalid input HTML.";
- }
-
- try {
- 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.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 result
- .map(nodeToString)
- .join(delimiter);
- },
-
- /**
- * This tries to rename variable names in a code snippet according to a function.
- *
- * @param {string} input
- * @param {function} replacer - this function will be fed the token which should be renamed.
- * @returns {string}
- */
- _replaceVariableNames(input, replacer) {
- const tokenRegex = /\\"|"(?:\\"|[^"])*"|(\b[a-z0-9\-_]+\b)/ig;
-
- return input.replace(tokenRegex, (...args) => {
- let match = args[0],
- quotes = args[1];
-
- if (!quotes) {
- return match;
- } else {
- return replacer(match);
- }
- });
- },
-
-
- /**
- * To Snake Case operation.
- *
- * @param {string} input
- * @param {Object[]} args
- * @returns {string}
- */
- runToSnakeCase(input, args) {
- const smart = args[0];
-
- if (smart) {
- return Code._replaceVariableNames(input, snakeCase);
- } else {
- return snakeCase(input);
- }
- },
-
-
- /**
- * To Camel Case operation.
- *
- * @param {string} input
- * @param {Object[]} args
- * @returns {string}
- */
- runToCamelCase(input, args) {
- const smart = args[0];
-
- if (smart) {
- return Code._replaceVariableNames(input, camelCase);
- } else {
- return camelCase(input);
- }
- },
-
-
- /**
- * To Kebab Case operation.
- *
- * @param {string} input
- * @param {Object[]} args
- * @returns {string}
- */
- runToKebabCase(input, args) {
- const smart = args[0];
-
- if (smart) {
- return Code._replaceVariableNames(input, kebabCase);
- } else {
- return kebabCase(input);
- }
- },
-
-};
-
-export default Code;
diff --git a/src/core/operations/legacy/Image.js b/src/core/operations/legacy/Image.js
deleted file mode 100644
index 23581382..00000000
--- a/src/core/operations/legacy/Image.js
+++ /dev/null
@@ -1,125 +0,0 @@
-import * as ExifParser from "exif-parser";
-import removeEXIF from "../vendor/remove-exif.js";
-import Utils from "../Utils.js";
-import FileType from "./FileType.js";
-import {fromBase64, toBase64} from "../lib/Base64";
-import {fromHex} from "../lib/Hex";
-
-
-/**
- * Image operations.
- *
- * @author tlwr [toby@toby.codes]
- * @copyright Crown Copyright 2017
- * @license Apache-2.0
- *
- * @namespace
- */
-const Image = {
-
- /**
- * Extract EXIF operation.
- *
- * Extracts EXIF data from a byteArray, representing a JPG or a TIFF image.
- *
- * @param {ArrayBuffer} input
- * @param {Object[]} args
- * @returns {string}
- */
- runExtractEXIF(input, args) {
- try {
- const parser = ExifParser.create(input);
- const result = parser.parse();
-
- let lines = [];
- for (let tagName in result.tags) {
- let value = result.tags[tagName];
- lines.push(`${tagName}: ${value}`);
- }
-
- const numTags = lines.length;
- lines.unshift(`Found ${numTags} tags.\n`);
- return lines.join("\n");
- } catch (err) {
- throw "Could not extract EXIF data from image: " + err;
- }
- },
-
-
- /**
- * Remove EXIF operation.
- *
- * Removes EXIF data from a byteArray, representing a JPG.
- *
- * @author David Moodie [davidmoodie12@gmail.com]
- * @param {byteArray} input
- * @param {Object[]} args
- * @returns {byteArray}
- */
- runRemoveEXIF(input, args) {
- // Do nothing if input is empty
- if (input.length === 0) return input;
-
- try {
- return removeEXIF(input);
- } catch (err) {
- // Simply return input if no EXIF data is found
- if (err === "Exif not found.") return input;
- throw "Could not remove EXIF data from image: " + err;
- }
- },
-
-
- /**
- * @constant
- * @default
- */
- INPUT_FORMAT: ["Raw", "Base64", "Hex"],
-
- /**
- * Render Image operation.
- *
- * @author n1474335 [n1474335@gmail.com]
- * @param {string} input
- * @param {Object[]} args
- * @returns {html}
- */
- runRenderImage(input, args) {
- const inputFormat = args[0];
- let dataURI = "data:";
-
- if (!input.length) return "";
-
- // Convert input to raw bytes
- switch (inputFormat) {
- case "Hex":
- input = fromHex(input);
- break;
- case "Base64":
- // Don't trust the Base64 entered by the user.
- // Unwrap it first, then re-encode later.
- input = fromBase64(input, null, "byteArray");
- break;
- case "Raw":
- default:
- input = Utils.strToByteArray(input);
- break;
- }
-
- // Determine file type
- const type = FileType.magicType(input);
- if (type && type.mime.indexOf("image") === 0) {
- dataURI += type.mime + ";";
- } else {
- throw "Invalid file type";
- }
-
- // Add image data to URI
- dataURI += "base64," + toBase64(input);
-
- return "";
- },
-
-};
-
-export default Image;
diff --git a/src/core/vendor/remove-exif.js b/src/core/vendor/remove-exif.mjs
similarity index 98%
rename from src/core/vendor/remove-exif.js
rename to src/core/vendor/remove-exif.mjs
index 5dbb5bb0..6c5c9e53 100644
--- a/src/core/vendor/remove-exif.js
+++ b/src/core/vendor/remove-exif.mjs
@@ -18,10 +18,10 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
-import Utils from "../Utils.js";
+import Utils from "../Utils.mjs";
// Param jpeg should be a binaryArray
-function removeEXIF(jpeg) {
+export function removeEXIF(jpeg) {
// Convert binaryArray to char string
jpeg = Utils.byteArrayToChars(jpeg);
if (jpeg.slice(0, 2) != "\xff\xd8") {
@@ -149,5 +149,3 @@ function unpack(mark, str) {
return unpacked;
}
-
-export default removeEXIF;
diff --git a/test/index.mjs b/test/index.mjs
index cd7a2278..11811e7e 100644
--- a/test/index.mjs
+++ b/test/index.mjs
@@ -45,7 +45,7 @@ import "./tests/operations/Register";
import "./tests/operations/Comment";
import "./tests/operations/Hash";
import "./tests/operations/Hexdump";
-// import "./tests/operations/Image";
+import "./tests/operations/Image";
import "./tests/operations/MorseCode";
import "./tests/operations/MS";
import "./tests/operations/PHP";
diff --git a/test/tests/operations/Image.mjs b/test/tests/operations/Image.mjs
index 4cb405e3..a24cfa20 100644
--- a/test/tests/operations/Image.mjs
+++ b/test/tests/operations/Image.mjs
@@ -57,7 +57,7 @@ TestRegister.addTests([
{
name: "Extract EXIF: hello world text (error)",
input: "hello world",
- expectedError: true,
+ expectedOutput: "Could not extract EXIF data from image: Error: Invalid JPEG section offset",
recipeConfig: [
{
op: "Extract EXIF",
@@ -129,7 +129,7 @@ TestRegister.addTests([
{
name: "Remove EXIF: hello world text (error)",
input: "hello world",
- expectedError: true,
+ expectedOutput: "Could not remove EXIF data from image: Given data is not jpeg.",
recipeConfig: [
{
op: "Remove EXIF",