2017-03-23 17:52:20 +00:00
import Recipe from "./Recipe.js" ;
import Dish from "./Dish.js" ;
2018-01-14 16:07:39 +00:00
import Magic from "./lib/Magic.js" ;
2018-01-22 23:34:24 +00:00
import Utils from "./Utils.js" ;
2017-03-21 22:41:44 +00:00
2016-11-28 10:42:58 +00:00
/ * *
* Flow Control operations .
*
* @ author n1474335 [ n1474335 @ gmail . com ]
* @ copyright Crown Copyright 2016
* @ license Apache - 2.0
*
* @ namespace
* /
2017-03-23 17:52:20 +00:00
const FlowControl = {
2016-11-28 10:42:58 +00:00
/ * *
* Fork operation .
*
* @ param { Object } state - The current state of the recipe .
* @ param { number } state . progress - The current position in the recipe .
* @ param { Dish } state . dish - The Dish being operated on .
2017-01-31 18:24:56 +00:00
* @ param { Operation [ ] } state . opList - The list of operations in the recipe .
2016-11-28 10:42:58 +00:00
* @ returns { Object } The updated state of the recipe .
* /
2017-04-21 17:48:42 -04:00
runFork : async function ( state ) {
2018-04-02 17:10:51 +01:00
const opList = state . opList ,
2017-01-31 18:24:56 +00:00
inputType = opList [ state . progress ] . inputType ,
outputType = opList [ state . progress ] . outputType ,
2018-04-06 18:11:13 +00:00
input = await state . dish . get ( inputType ) ,
2018-03-26 23:14:23 +01:00
ings = opList [ state . progress ] . ingValues ,
2017-01-31 18:24:56 +00:00
splitDelim = ings [ 0 ] ,
mergeDelim = ings [ 1 ] ,
ignoreErrors = ings [ 2 ] ,
2018-04-02 17:10:51 +01:00
subOpList = [ ] ;
let inputs = [ ] ,
2017-04-13 18:31:26 +01:00
i ;
2017-02-09 15:09:33 +00:00
2016-11-28 10:42:58 +00:00
if ( input )
2017-01-31 18:24:56 +00:00
inputs = input . split ( splitDelim ) ;
2017-02-09 15:09:33 +00:00
2017-01-31 18:24:56 +00:00
// Create subOpList for each tranche to operate on
2016-11-28 10:42:58 +00:00
// (all remaining operations unless we encounter a Merge)
2017-04-13 18:31:26 +01:00
for ( i = state . progress + 1 ; i < opList . length ; i ++ ) {
2018-03-26 23:14:23 +01:00
if ( opList [ i ] . name === "Merge" && ! opList [ i ] . disabled ) {
2016-11-28 10:42:58 +00:00
break ;
} else {
2017-01-31 18:24:56 +00:00
subOpList . push ( opList [ i ] ) ;
2016-11-28 10:42:58 +00:00
}
}
2017-02-09 15:09:33 +00:00
2018-04-02 17:10:51 +01:00
const recipe = new Recipe ( ) ;
let output = "" ,
2017-01-16 15:58:38 +00:00
progress = 0 ;
2017-02-09 15:09:33 +00:00
2018-01-24 16:54:37 +00:00
state . forkOffset += state . progress + 1 ;
2017-01-31 18:24:56 +00:00
recipe . addOperations ( subOpList ) ;
2017-02-09 15:09:33 +00:00
2018-01-25 14:03:13 +00:00
// Take a deep(ish) copy of the ingredient values
2018-03-26 23:14:23 +01:00
const ingValues = subOpList . map ( op => JSON . parse ( JSON . stringify ( op . ingValues ) ) ) ;
2017-02-09 15:09:33 +00:00
2016-11-28 10:42:58 +00:00
// Run recipe over each tranche
for ( i = 0 ; i < inputs . length ; i ++ ) {
2017-12-29 17:32:23 +00:00
log . debug ( ` Entering tranche ${ i + 1 } of ${ inputs . length } ` ) ;
2018-01-24 16:54:37 +00:00
// Baseline ing values for each tranche so that registers are reset
subOpList . forEach ( ( op , i ) => {
2018-03-26 23:14:23 +01:00
op . ingValues = JSON . parse ( JSON . stringify ( ingValues [ i ] ) ) ;
2018-01-24 16:54:37 +00:00
} ) ;
2018-04-21 12:25:48 +01:00
const dish = new Dish ( ) ;
dish . set ( inputs [ i ] , inputType ) ;
2017-01-16 15:58:38 +00:00
try {
2018-01-24 16:54:37 +00:00
progress = await recipe . execute ( dish , 0 , state ) ;
2017-02-09 15:09:33 +00:00
} catch ( err ) {
2017-01-31 18:24:56 +00:00
if ( ! ignoreErrors ) {
2017-01-16 15:58:38 +00:00
throw err ;
}
progress = err . progress + 1 ;
}
2018-04-06 18:11:13 +00:00
output += await dish . get ( outputType ) + mergeDelim ;
2016-11-28 10:42:58 +00:00
}
2017-02-09 15:09:33 +00:00
2017-01-31 18:24:56 +00:00
state . dish . set ( output , outputType ) ;
2016-11-28 10:42:58 +00:00
state . progress += progress ;
return state ;
} ,
2017-02-09 15:09:33 +00:00
2016-11-28 10:42:58 +00:00
/ * *
* Merge operation .
*
* @ param { Object } state - The current state of the recipe .
* @ param { number } state . progress - The current position in the recipe .
* @ param { Dish } state . dish - The Dish being operated on .
2017-01-31 18:24:56 +00:00
* @ param { Operation [ ] } state . opList - The list of operations in the recipe .
2016-11-28 10:42:58 +00:00
* @ returns { Object } The updated state of the recipe .
* /
2017-01-31 18:24:56 +00:00
runMerge : function ( state ) {
2016-11-28 10:42:58 +00:00
// No need to actually do anything here. The fork operation will
// merge when it sees this operation.
return state ;
} ,
2017-02-09 15:09:33 +00:00
2017-09-28 16:27:39 +00:00
/ * *
* Register operation .
*
* @ param { Object } state - The current state of the recipe .
* @ param { number } state . progress - The current position in the recipe .
* @ param { Dish } state . dish - The Dish being operated on .
* @ param { Operation [ ] } state . opList - The list of operations in the recipe .
* @ returns { Object } The updated state of the recipe .
* /
2018-04-06 18:11:13 +00:00
runRegister : async function ( state ) {
2018-03-26 23:14:23 +01:00
const ings = state . opList [ state . progress ] . ingValues ,
2017-09-28 16:27:39 +00:00
extractorStr = ings [ 0 ] ,
i = ings [ 1 ] ,
m = ings [ 2 ] ;
let modifiers = "" ;
if ( i ) modifiers += "i" ;
if ( m ) modifiers += "m" ;
const extractor = new RegExp ( extractorStr , modifiers ) ,
2018-04-06 18:11:13 +00:00
input = await state . dish . get ( Dish . STRING ) ,
2017-09-28 16:27:39 +00:00
registers = input . match ( extractor ) ;
2017-09-28 17:35:52 +00:00
if ( ! registers ) return state ;
if ( ENVIRONMENT _IS _WORKER ( ) ) {
2018-01-24 16:54:37 +00:00
self . setRegisters ( state . forkOffset + state . progress , state . numRegisters , registers . slice ( 1 ) ) ;
2017-09-28 17:35:52 +00:00
}
2017-09-28 16:27:39 +00:00
/ * *
* Replaces references to registers ( e . g . $R0 ) with the contents of those registers .
*
* @ param { string } str
* @ returns { string }
* /
function replaceRegister ( str ) {
// Replace references to registers ($Rn) with contents of registers
2017-09-28 19:24:28 +00:00
return str . replace ( /(\\*)\$R(\d{1,2})/g , ( match , slashes , regNum ) => {
2017-09-28 16:27:39 +00:00
const index = parseInt ( regNum , 10 ) + 1 ;
2017-09-28 19:24:28 +00:00
if ( index <= state . numRegisters || index >= state . numRegisters + registers . length )
return match ;
if ( slashes . length % 2 !== 0 ) return match . slice ( 1 ) ; // Remove escape
return slashes + registers [ index - state . numRegisters ] ;
} ) ;
2017-09-28 16:27:39 +00:00
}
// Step through all subsequent ops and replace registers in args with extracted content
for ( let i = state . progress + 1 ; i < state . opList . length ; i ++ ) {
2018-03-26 23:14:23 +01:00
if ( state . opList [ i ] . disabled ) continue ;
2017-09-28 18:39:35 +00:00
2018-03-26 23:14:23 +01:00
let args = state . opList [ i ] . ingValues ;
2017-09-28 16:27:39 +00:00
args = args . map ( arg => {
if ( typeof arg !== "string" && typeof arg !== "object" ) return arg ;
2017-09-28 18:39:35 +00:00
if ( typeof arg === "object" && arg . hasOwnProperty ( "string" ) ) {
2017-09-28 16:27:39 +00:00
arg . string = replaceRegister ( arg . string ) ;
return arg ;
}
return replaceRegister ( arg ) ;
} ) ;
state . opList [ i ] . setIngValues ( args ) ;
}
2017-09-28 18:39:35 +00:00
state . numRegisters += registers . length - 1 ;
2017-09-28 16:27:39 +00:00
return state ;
} ,
2016-11-28 10:42:58 +00:00
/ * *
* Jump operation .
*
* @ param { Object } state - The current state of the recipe .
* @ param { number } state . progress - The current position in the recipe .
* @ param { Dish } state . dish - The Dish being operated on .
2017-01-31 18:24:56 +00:00
* @ param { Operation [ ] } state . opList - The list of operations in the recipe .
* @ param { number } state . numJumps - The number of jumps taken so far .
2016-11-28 10:42:58 +00:00
* @ returns { Object } The updated state of the recipe .
* /
2017-01-31 18:24:56 +00:00
runJump : function ( state ) {
2018-05-20 16:49:42 +01:00
const ings = state . opList [ state . progress ] . ingValues ,
2017-12-29 17:32:23 +00:00
label = ings [ 0 ] ,
maxJumps = ings [ 1 ] ,
jmpIndex = FlowControl . _getLabelIndex ( label , state ) ;
2017-02-09 15:09:33 +00:00
2017-11-24 10:31:26 -08:00
if ( state . numJumps >= maxJumps || jmpIndex === - 1 ) {
2017-12-29 17:32:23 +00:00
log . debug ( "Maximum jumps reached or label cannot be found" ) ;
2017-01-16 16:00:44 +00:00
return state ;
2016-11-28 10:42:58 +00:00
}
2017-02-09 15:09:33 +00:00
2017-11-24 10:12:08 -08:00
state . progress = jmpIndex ;
2017-01-31 18:24:56 +00:00
state . numJumps ++ ;
2017-12-29 17:32:23 +00:00
log . debug ( ` Jumping to label ' ${ label } ' at position ${ jmpIndex } (jumps = ${ state . numJumps } ) ` ) ;
2016-11-28 10:42:58 +00:00
return state ;
} ,
2017-02-09 15:09:33 +00:00
2016-11-28 10:42:58 +00:00
/ * *
* Conditional Jump operation .
*
* @ param { Object } state - The current state of the recipe .
* @ param { number } state . progress - The current position in the recipe .
* @ param { Dish } state . dish - The Dish being operated on .
2017-01-31 18:24:56 +00:00
* @ param { Operation [ ] } state . opList - The list of operations in the recipe .
* @ param { number } state . numJumps - The number of jumps taken so far .
2016-11-28 10:42:58 +00:00
* @ returns { Object } The updated state of the recipe .
* /
2018-04-06 18:11:13 +00:00
runCondJump : async function ( state ) {
2018-05-20 16:49:42 +01:00
const ings = state . opList [ state . progress ] . ingValues ,
2017-01-31 18:24:56 +00:00
dish = state . dish ,
regexStr = ings [ 0 ] ,
2017-11-24 05:48:40 -08:00
invert = ings [ 1 ] ,
2017-12-29 17:32:23 +00:00
label = ings [ 2 ] ,
maxJumps = ings [ 3 ] ,
jmpIndex = FlowControl . _getLabelIndex ( label , state ) ;
2017-02-09 15:09:33 +00:00
2017-11-24 10:31:26 -08:00
if ( state . numJumps >= maxJumps || jmpIndex === - 1 ) {
2017-12-29 17:32:23 +00:00
log . debug ( "Maximum jumps reached or label cannot be found" ) ;
2017-01-16 16:00:44 +00:00
return state ;
2016-11-28 10:42:58 +00:00
}
2017-02-09 15:09:33 +00:00
2017-11-24 05:48:40 -08:00
if ( regexStr !== "" ) {
2018-05-21 10:58:35 +01:00
const str = await dish . get ( Dish . STRING )
const strMatch = str . search ( regexStr ) > - 1 ;
2017-11-24 05:48:40 -08:00
if ( ! invert && strMatch || invert && ! strMatch ) {
2017-11-24 10:12:08 -08:00
state . progress = jmpIndex ;
2017-11-24 05:48:40 -08:00
state . numJumps ++ ;
2017-12-29 17:32:23 +00:00
log . debug ( ` Jumping to label ' ${ label } ' at position ${ jmpIndex } (jumps = ${ state . numJumps } ) ` ) ;
2017-11-24 10:12:08 -08:00
}
2016-11-28 10:42:58 +00:00
}
2017-02-09 15:09:33 +00:00
2016-11-28 10:42:58 +00:00
return state ;
} ,
2017-02-09 15:09:33 +00:00
2017-11-24 10:12:08 -08:00
2016-11-28 10:42:58 +00:00
/ * *
* Return operation .
*
* @ param { Object } state - The current state of the recipe .
* @ param { number } state . progress - The current position in the recipe .
* @ param { Dish } state . dish - The Dish being operated on .
2017-01-31 18:24:56 +00:00
* @ param { Operation [ ] } state . opList - The list of operations in the recipe .
2016-11-28 10:42:58 +00:00
* @ returns { Object } The updated state of the recipe .
* /
2017-01-31 18:24:56 +00:00
runReturn : function ( state ) {
state . progress = state . opList . length ;
2016-11-28 10:42:58 +00:00
return state ;
} ,
2017-02-09 15:09:33 +00:00
2017-04-27 13:05:29 +00:00
/ * *
* Comment operation .
2017-04-27 13:12:45 +00:00
*
2017-04-27 13:05:29 +00:00
* @ param { Object } state - The current state of the recipe .
* @ param { number } state . progress - The current position in the recipe .
* @ param { Dish } state . dish - The Dish being operated on .
* @ param { Operation [ ] } state . opList - The list of operations in the recipe .
* @ returns { Object } The updated state of the recipe .
* /
runComment : function ( state ) {
return state ;
} ,
2017-12-19 13:18:25 +00:00
2018-01-14 16:07:39 +00:00
/ * *
* Magic operation .
*
* @ param { Object } state - The current state of the recipe .
* @ param { number } state . progress - The current position in the recipe .
* @ param { Dish } state . dish - The Dish being operated on .
* @ param { Operation [ ] } state . opList - The list of operations in the recipe .
* @ returns { Object } The updated state of the recipe .
* /
2018-01-22 22:06:26 +00:00
runMagic : async function ( state ) {
2018-05-20 16:49:42 +01:00
const ings = state . opList [ state . progress ] . ingValues ,
2018-01-22 22:06:26 +00:00
depth = ings [ 0 ] ,
2018-02-14 16:08:59 +00:00
intensive = ings [ 1 ] ,
extLang = ings [ 2 ] ,
2018-01-22 22:06:26 +00:00
dish = state . dish ,
2018-01-22 23:34:24 +00:00
currentRecipeConfig = state . opList . map ( op => op . getConfig ( ) ) ,
magic = new Magic ( dish . get ( Dish . ARRAY _BUFFER ) ) ,
2018-02-14 16:08:59 +00:00
options = await magic . speculativeExecution ( depth , extLang , intensive ) ;
2018-01-22 23:34:24 +00:00
let output = ` <table
class = 'table table-hover table-condensed table-bordered'
style = 'table-layout: fixed;' >
< tr >
< th > Recipe ( click to load ) < / t h >
2018-02-10 15:31:50 +00:00
< th > Result snippet < / t h >
2018-02-10 15:10:53 +00:00
< th > Properties < / t h >
2018-01-22 23:34:24 +00:00
< / t r > ` ;
2018-02-19 17:25:28 +00:00
/ * *
* Returns a CSS colour value based on an integer input .
*
* @ param { number } val
* @ returns { string }
* /
function chooseColour ( val ) {
if ( val < 3 ) return "green" ;
if ( val < 5 ) return "goldenrod" ;
return "red" ;
}
2018-01-22 23:34:24 +00:00
options . forEach ( option => {
// Construct recipe URL
// Replace this Magic op with the generated recipe
const recipeConfig = currentRecipeConfig . slice ( 0 , state . progress )
. concat ( option . recipe )
. concat ( currentRecipeConfig . slice ( state . progress + 1 ) ) ,
recipeURL = "recipe=" + Utils . encodeURIFragment ( Utils . generatePrettyRecipe ( recipeConfig ) ) ;
2018-02-10 15:31:50 +00:00
let language = "" ,
fileType = "" ,
matchingOps = "" ,
2018-02-15 13:39:55 +00:00
useful = "" ,
2018-02-19 17:25:28 +00:00
entropy = ` <span data-toggle="tooltip" data-container="body" title="Shannon Entropy is measured from 0 to 8. High entropy suggests encrypted or compressed data. Normal text is usually around 3.5 to 5.">Entropy: <span style="color: ${ chooseColour ( option . entropy ) } "> ${ option . entropy . toFixed ( 2 ) } </span></span> ` ,
validUTF8 = option . isUTF8 ? "<span data-toggle='tooltip' data-container='body' title='The data could be a valid UTF8 string based on its encoding.'>Valid UTF8</span>\n" : "" ;
2018-02-10 15:10:53 +00:00
2018-02-15 13:39:55 +00:00
if ( option . languageScores [ 0 ] . probability > 0 ) {
let likelyLangs = option . languageScores . filter ( l => l . probability > 0 ) ;
2018-02-10 17:53:59 +00:00
if ( likelyLangs . length < 1 ) likelyLangs = [ option . languageScores [ 0 ] ] ;
2018-02-19 17:25:28 +00:00
language = "<span data-toggle='tooltip' data-container='body' title='Based on a statistical comparison of the frequency of bytes in various languages. Ordered by likelihood.'>" +
"Possible languages:\n " + likelyLangs . map ( lang => {
return Magic . codeToLanguage ( lang . lang ) ;
} ) . join ( "\n " ) + "</span>\n" ;
2018-02-10 15:10:53 +00:00
}
2018-01-22 23:34:24 +00:00
if ( option . fileType ) {
2018-02-19 17:25:28 +00:00
fileType = ` <span data-toggle="tooltip" data-container="body" title="Based on the presence of magic bytes.">File type: ${ option . fileType . mime } ( ${ option . fileType . ext } )</span> \n ` ;
2018-02-10 15:31:50 +00:00
}
if ( option . matchingOps . length ) {
matchingOps = ` Matching ops: ${ [ ... new Set ( option . matchingOps . map ( op => op . op ) ) ] . join ( ", " ) } \n ` ;
2018-01-22 23:34:24 +00:00
}
2018-01-14 16:07:39 +00:00
2018-02-15 13:39:55 +00:00
if ( option . useful ) {
2018-02-19 17:25:28 +00:00
useful = "<span data-toggle='tooltip' data-container='body' title='This could be an operation that displays data in a useful way, such as rendering an image.'>Useful op detected</span>\n" ;
2018-02-15 13:39:55 +00:00
}
2018-01-22 23:34:24 +00:00
output += ` <tr>
< td > < a href = "#${recipeURL}" > $ { Utils . generatePrettyRecipe ( option . recipe , true ) } < / a > < / t d >
2018-01-23 01:15:13 +00:00
< td > $ { Utils . escapeHtml ( Utils . printable ( Utils . truncate ( option . data , 99 ) ) ) } < / t d >
2018-02-19 17:25:28 +00:00
< td > $ { language } $ { fileType } $ { matchingOps } $ { useful } $ { validUTF8 } $ { entropy } < / t d >
2018-01-22 23:34:24 +00:00
< / t r > ` ;
} ) ;
2018-01-14 16:07:39 +00:00
2018-02-19 17:25:28 +00:00
output += "</table><script type='application/javascript'>$('[data-toggle=\"tooltip\"]').tooltip()</script>" ;
2018-02-14 16:08:59 +00:00
if ( ! options . length ) {
output = "Nothing of interest could be detected about the input data.\nHave you tried modifying the operation arguments?" ;
}
2018-01-22 23:34:24 +00:00
dish . set ( output , Dish . HTML ) ;
2018-01-14 16:07:39 +00:00
return state ;
} ,
2017-12-19 13:18:25 +00:00
/ * *
* Returns the index of a label .
*
2017-12-29 17:32:23 +00:00
* @ private
2017-12-19 13:18:25 +00:00
* @ param { Object } state
* @ param { string } name
* @ returns { number }
* /
_getLabelIndex : function ( name , state ) {
for ( let o = 0 ; o < state . opList . length ; o ++ ) {
2018-04-02 17:10:51 +01:00
const operation = state . opList [ o ] ;
2017-12-19 13:18:25 +00:00
if ( operation . name === "Label" ) {
2018-04-02 17:10:51 +01:00
const ings = operation . ingValues ;
2017-12-19 13:18:25 +00:00
if ( name === ings [ 0 ] ) {
return o ;
}
}
}
return - 1 ;
} ,
2016-11-28 10:42:58 +00:00
} ;
2017-03-23 17:52:20 +00:00
export default FlowControl ;