diff --git a/src/core/Chef.mjs b/src/core/Chef.mjs
index a7302377..94d85608 100755
--- a/src/core/Chef.mjs
+++ b/src/core/Chef.mjs
@@ -96,7 +96,7 @@ class Chef {
const returnType = this.dish.size > threshold ? Dish.ARRAY_BUFFER : Dish.STRING;
// Create a raw version of the dish, unpresented
- const rawDish = new Dish(this.dish);
+ const rawDish = this.dish.clone();
// Present the raw result
await recipe.present(this.dish);
diff --git a/src/core/Dish.mjs b/src/core/Dish.mjs
index eee404fc..d3a7e665 100755
--- a/src/core/Dish.mjs
+++ b/src/core/Dish.mjs
@@ -300,6 +300,69 @@ class Dish {
}
}
+
+ /**
+ * Returns a deep clone of the current Dish.
+ *
+ * @returns {Dish}
+ */
+ clone() {
+ const newDish = new Dish();
+
+ switch (this.type) {
+ case Dish.STRING:
+ case Dish.HTML:
+ case Dish.NUMBER:
+ case Dish.BIG_NUMBER:
+ // These data types are immutable so it is acceptable to copy them by reference
+ newDish.set(
+ this.value,
+ this.type
+ );
+ break;
+ case Dish.BYTE_ARRAY:
+ case Dish.JSON:
+ // These data types are mutable so they need to be copied by value
+ newDish.set(
+ JSON.parse(JSON.stringify(this.value)),
+ this.type
+ );
+ break;
+ case Dish.ARRAY_BUFFER:
+ // Slicing an ArrayBuffer returns a new ArrayBuffer with a copy its contents
+ newDish.set(
+ this.value.slice(0),
+ this.type
+ );
+ break;
+ case Dish.FILE:
+ // A new file can be created by copying over all the values from the original
+ newDish.set(
+ new File([this.value], this.value.name, {
+ "type": this.value.type,
+ "lastModified": this.value.lastModified
+ }),
+ this.type
+ );
+ break;
+ case Dish.LIST_FILE:
+ newDish.set(
+ this.value.map(f =>
+ new File([f], f.name, {
+ "type": f.type,
+ "lastModified": f.lastModified
+ })
+ ),
+ this.type
+ );
+ break;
+ default:
+ throw new Error("Cannot clone Dish, unknown type");
+ }
+
+ return newDish;
+ }
+
}
diff --git a/src/core/lib/Magic.mjs b/src/core/lib/Magic.mjs
index 61e113b3..b4d5a7b0 100644
--- a/src/core/lib/Magic.mjs
+++ b/src/core/lib/Magic.mjs
@@ -287,6 +287,8 @@ class Magic {
useful: useful
});
+ const prevOp = recipeConfig[recipeConfig.length - 1];
+
// Execute each of the matching operations, then recursively call the speculativeExecution()
// method on the resulting data, recording the properties of each option.
await Promise.all(matchingOps.map(async op => {
@@ -294,8 +296,14 @@ class Magic {
op: op.op,
args: op.args
},
- output = await this._runRecipe([opConfig]),
- magic = new Magic(output, this.opPatterns),
+ output = await this._runRecipe([opConfig]);
+
+ // If the recipe is repeating and returning the same data, do not continue
+ if (prevOp && op.op === prevOp.op && _buffersEqual(output, this.inputBuffer)) {
+ return;
+ }
+
+ const magic = new Magic(output, this.opPatterns),
speculativeResults = await magic.speculativeExecution(
depth-1, extLang, intensive, [...recipeConfig, opConfig], op.useful);
@@ -315,13 +323,16 @@ class Magic {
}));
}
- // Prune branches that do not match anything
+ // Prune branches that result in unhelpful outputs
results = results.filter(r =>
- r.languageScores[0].probability > 0 ||
- r.fileType ||
- r.isUTF8 ||
- r.matchingOps.length ||
- r.useful);
+ (r.useful || r.data.length > 0) && // The operation resulted in ""
+ ( // One of the following must be true
+ r.languageScores[0].probability > 0 || // Some kind of language was found
+ r.fileType || // A file was found
+ r.isUTF8 || // UTF-8 was found
+ r.matchingOps.length // A matching op was found
+ )
+ );
// Return a sorted list of possible recipes along with their properties
return results.sort((a, b) => {
@@ -374,7 +385,7 @@ class Magic {
const recipe = new Recipe(recipeConfig);
try {
- await recipe.execute(dish, 0);
+ await recipe.execute(dish);
return dish.get(Dish.ARRAY_BUFFER);
} catch (err) {
// If there are errors, return an empty buffer
@@ -395,7 +406,10 @@ class Magic {
let i = len;
const counts = new Array(256).fill(0);
- if (!len) return counts;
+ if (!len) {
+ this.freqDist = counts;
+ return this.freqDist;
+ }
while (i--) {
counts[this.inputBuffer[i]]++;
diff --git a/src/core/operations/FromHexdump.mjs b/src/core/operations/FromHexdump.mjs
index 85d74c16..e44b9120 100644
--- a/src/core/operations/FromHexdump.mjs
+++ b/src/core/operations/FromHexdump.mjs
@@ -26,7 +26,7 @@ class FromHexdump extends Operation {
this.args = [];
this.patterns = [
{
- match: "^(?:(?:[\\dA-F]{4,16}h?:?)?[ \\t]*((?:[\\dA-F]{2} ){1,8}(?:[ \\t]|[\\dA-F]{2}-)(?:[\\dA-F]{2} ){1,8}|(?:[\\dA-F]{4} )*[\\dA-F]{4}|(?:[\\dA-F]{2} )*[\\dA-F]{2})[^\\n]*\\n?)+$",
+ match: "^(?:(?:[\\dA-F]{4,16}h?:?)?[ \\t]*((?:[\\dA-F]{2} ){1,8}(?:[ \\t]|[\\dA-F]{2}-)(?:[\\dA-F]{2} ){1,8}|(?:[\\dA-F]{4} )*[\\dA-F]{4}|(?:[\\dA-F]{2} )*[\\dA-F]{2})[^\\n]*\\n?){2,}$",
flags: "i",
args: []
},
diff --git a/src/core/operations/RenderImage.mjs b/src/core/operations/RenderImage.mjs
index 8a8fb4a9..7edd2072 100644
--- a/src/core/operations/RenderImage.mjs
+++ b/src/core/operations/RenderImage.mjs
@@ -26,7 +26,8 @@ class RenderImage extends Operation {
this.module = "Image";
this.description = "Displays the input as an image. Supports the following formats: