2018-05-15 17:36:45 +00:00
/ * *
* @ author n1474335 [ n1474335 @ gmail . com ]
* @ copyright Crown Copyright 2016
* @ license Apache - 2.0
* /
2020-12-11 16:24:39 +00:00
import Utils , { debounce } from "../core/Utils.mjs" ;
import { fromBase64 } from "../core/lib/Base64.mjs" ;
import Manager from "./Manager.mjs" ;
import HTMLCategory from "./HTMLCategory.mjs" ;
import HTMLOperation from "./HTMLOperation.mjs" ;
2018-05-15 17:36:45 +00:00
import Split from "split.js" ;
2018-12-25 23:58:00 +00:00
import moment from "moment-timezone" ;
2022-10-21 18:29:52 +01:00
import cptable from "codepage" ;
2018-05-15 17:36:45 +00:00
/ * *
* HTML view for CyberChef responsible for building the web page and dealing with all user
* interactions .
* /
class App {
/ * *
* App constructor .
*
* @ param { CatConf [ ] } categories - The list of categories and operations to be populated .
* @ param { Object . < string , OpConf > } operations - The list of operation configuration objects .
* @ param { String [ ] } defaultFavourites - A list of default favourite operations .
* @ param { Object } options - Default setting for app options .
* /
constructor ( categories , operations , defaultFavourites , defaultOptions ) {
this . categories = categories ;
this . operations = operations ;
this . dfavourites = defaultFavourites ;
this . doptions = defaultOptions ;
this . options = Object . assign ( { } , defaultOptions ) ;
this . manager = new Manager ( this ) ;
this . baking = false ;
this . autoBake _ = false ;
this . progress = 0 ;
this . ingId = 0 ;
2022-10-21 18:29:52 +01:00
this . appLoaded = false ;
this . workerLoaded = false ;
this . waitersLoaded = false ;
2024-03-26 16:34:36 +00:00
this . snackbars = [ ] ;
2018-05-15 17:36:45 +00:00
}
/ * *
* This function sets up the stage and creates listeners for all events .
*
* @ fires Manager # appstart
* /
setup ( ) {
document . dispatchEvent ( this . manager . appstart ) ;
2019-01-15 19:03:17 +00:00
2018-05-15 17:36:45 +00:00
this . initialiseSplitter ( ) ;
this . loadLocalStorage ( ) ;
this . populateOperationsList ( ) ;
this . manager . setup ( ) ;
2019-01-15 19:03:17 +00:00
this . manager . output . saveBombe ( ) ;
2022-05-30 22:53:17 +01:00
this . adjustComponentSizes ( ) ;
2018-05-15 17:36:45 +00:00
this . setCompileMessage ( ) ;
2022-10-21 18:29:52 +01:00
this . uriParams = this . getURIParams ( ) ;
2018-05-15 17:36:45 +00:00
log . debug ( "App loaded" ) ;
this . appLoaded = true ;
this . loaded ( ) ;
}
/ * *
* Fires once all setup activities have completed .
*
* @ fires Manager # apploaded
* /
loaded ( ) {
// Check that both the app and the worker have loaded successfully, and that
// we haven't already loaded before attempting to remove the loading screen.
2022-10-21 18:29:52 +01:00
if ( ! this . workerLoaded || ! this . appLoaded || ! this . waitersLoaded ||
2018-05-15 17:36:45 +00:00
! document . getElementById ( "loader-wrapper" ) ) return ;
2022-10-21 18:29:52 +01:00
// Load state from URI
this . loadURIParams ( this . uriParams ) ;
2018-05-15 17:36:45 +00:00
// Trigger CSS animations to remove preloader
document . body . classList . add ( "loaded" ) ;
// Wait for animations to complete then remove the preloader and loaded style
// so that the animations for existing elements don't play again.
setTimeout ( function ( ) {
document . getElementById ( "loader-wrapper" ) . remove ( ) ;
document . body . classList . remove ( "loaded" ) ;
2019-05-02 11:29:54 +01:00
// Bake initial input
2019-06-03 11:16:54 +01:00
this . manager . input . bakeAll ( ) ;
2019-05-02 11:29:54 +01:00
} . bind ( this ) , 1000 ) ;
2018-05-15 17:36:45 +00:00
// Clear the loading message interval
clearInterval ( window . loadingMsgsInt ) ;
// Remove the loading error handler
window . removeEventListener ( "error" , window . loadingErrorHandler ) ;
document . dispatchEvent ( this . manager . apploaded ) ;
2019-04-04 10:15:13 +01:00
this . manager . input . calcMaxTabs ( ) ;
this . manager . output . calcMaxTabs ( ) ;
2018-05-15 17:36:45 +00:00
}
/ * *
* An error handler for displaying the error to the user .
*
* @ param { Error } err
* @ param { boolean } [ logToConsole = false ]
* /
handleError ( err , logToConsole ) {
if ( logToConsole ) log . error ( err ) ;
const msg = err . displayStr || err . toString ( ) ;
2019-06-14 14:31:38 +01:00
this . alert ( Utils . escapeHtml ( msg ) , this . options . errorTimeout , ! this . options . showErrors ) ;
2018-05-15 17:36:45 +00:00
}
/ * *
* Asks the ChefWorker to bake the current input using the current recipe .
*
* @ param { boolean } [ step ] - Set to true if we should only execute one operation instead of the
* whole recipe .
* /
2019-05-16 09:38:34 +01:00
bake ( step = false ) {
2019-05-02 11:29:54 +01:00
if ( this . baking ) return ;
2018-05-15 17:36:45 +00:00
// Reset attemptHighlight flag
this . options . attemptHighlight = true ;
2019-01-16 12:29:34 +00:00
// Remove all current indicators
this . manager . recipe . updateBreakpointIndicator ( false ) ;
2018-05-15 17:36:45 +00:00
this . manager . worker . bake (
this . getRecipeConfig ( ) , // The configuration of the recipe
this . options , // Options set by the user
this . progress , // The current position in the recipe
step // Whether or not to take one step or execute the whole recipe
) ;
}
/ * *
* Runs Auto Bake if it is set .
* /
autoBake ( ) {
2024-04-07 00:23:17 +02:00
if ( this . baking ) {
this . manager . worker . cancelBakeForAutoBake ( ) ;
this . baking = false ;
}
if ( this . autoBake _ ) {
2018-05-15 17:36:45 +00:00
log . debug ( "Auto-baking" ) ;
2023-02-03 14:55:15 +00:00
this . manager . worker . bakeInputs ( {
nums : [ this . manager . tabs . getActiveTab ( "input" ) ] ,
step : false
2019-04-25 16:32:48 +01:00
} ) ;
2018-05-15 17:36:45 +00:00
} else {
this . manager . controls . showStaleIndicator ( ) ;
}
}
2019-07-02 12:23:46 +01:00
/ * *
* Executes the next step of the recipe .
* /
step ( ) {
if ( this . baking ) return ;
// Reset status using cancelBake
this . manager . worker . cancelBake ( true , false ) ;
2022-10-21 13:57:46 +01:00
const activeTab = this . manager . tabs . getActiveTab ( "input" ) ;
2019-07-02 12:23:46 +01:00
if ( activeTab === - 1 ) return ;
let progress = 0 ;
if ( this . manager . output . outputs [ activeTab ] . progress !== false ) {
log . error ( this . manager . output . outputs [ activeTab ] ) ;
progress = this . manager . output . outputs [ activeTab ] . progress ;
}
this . manager . input . inputWorker . postMessage ( {
action : "step" ,
data : {
activeTab : activeTab ,
progress : progress + 1
}
} ) ;
}
2018-05-15 17:36:45 +00:00
/ * *
* Runs a silent bake , forcing the browser to load and cache all the relevant JavaScript code needed
* to do a real bake .
*
* The output will not be modified ( hence "silent" bake ) . This will only actually execute the recipe
* if auto - bake is enabled , otherwise it will just wake up the ChefWorker with an empty recipe .
* /
silentBake ( ) {
let recipeConfig = [ ] ;
if ( this . autoBake _ ) {
// If auto-bake is not enabled we don't want to actually run the recipe as it may be disabled
// for a good reason.
recipeConfig = this . getRecipeConfig ( ) ;
}
this . manager . worker . silentBake ( recipeConfig ) ;
}
/ * *
* Sets the user ' s input data .
*
* @ param { string } input - The string to set the input to
* /
2019-05-29 14:07:46 +01:00
setInput ( input ) {
2019-06-03 11:16:54 +01:00
// Get the currently active tab.
// If there isn't one, assume there are no inputs so use inputNum of 1
2022-10-21 13:57:46 +01:00
let inputNum = this . manager . tabs . getActiveTab ( "input" ) ;
2019-05-02 15:44:31 +01:00
if ( inputNum === - 1 ) inputNum = 1 ;
this . manager . input . updateInputValue ( inputNum , input ) ;
2019-05-29 13:25:12 +01:00
this . manager . input . inputWorker . postMessage ( {
action : "setInput" ,
data : {
inputNum : inputNum ,
2024-04-24 17:13:44 +01:00
silent : true
2019-05-29 13:25:12 +01:00
}
} ) ;
2018-05-15 17:36:45 +00:00
}
/ * *
* Populates the operations accordion list with the categories and operations specified in the
* view constructor .
*
* @ fires Manager # oplistcreate
* /
populateOperationsList ( ) {
// Move edit button away before we overwrite it
document . body . appendChild ( document . getElementById ( "edit-favourites" ) ) ;
let html = "" ;
let i ;
for ( i = 0 ; i < this . categories . length ; i ++ ) {
const catConf = this . categories [ i ] ,
selected = i === 0 ,
cat = new HTMLCategory ( catConf . name , selected ) ;
for ( let j = 0 ; j < catConf . ops . length ; j ++ ) {
const opName = catConf . ops [ j ] ;
2019-07-05 12:22:52 +01:00
if ( ! ( opName in this . operations ) ) {
2018-05-15 17:36:45 +00:00
log . warn ( ` ${ opName } could not be found. ` ) ;
continue ;
}
const op = new HTMLOperation ( opName , this . operations [ opName ] , this , this . manager ) ;
cat . addOperation ( op ) ;
}
html += cat . toHtml ( ) ;
}
document . getElementById ( "categories" ) . innerHTML = html ;
const opLists = document . querySelectorAll ( "#categories .op-list" ) ;
for ( i = 0 ; i < opLists . length ; i ++ ) {
opLists [ i ] . dispatchEvent ( this . manager . oplistcreate ) ;
}
// Add edit button to first category (Favourites)
2023-03-17 17:46:13 +00:00
const favCat = document . querySelector ( "#categories a" ) ;
favCat . appendChild ( document . getElementById ( "edit-favourites" ) ) ;
favCat . setAttribute ( "data-help-title" , "Favourite operations" ) ;
favCat . setAttribute ( "data-help" , ` <p>This category displays your favourite operations.</p>
< ul >
< li > < b > To add : < / b > d r a g a n o p e r a t i o n o v e r t h e F a v o u r i t e s c a t e g o r y < / l i >
< li > < b > To reorder : < / b > C l i c k o n t h e ' E d i t f a v o u r i t e s ' b u t t o n a n d d r a g o p e r a t i o n s u p a n d d o w n i n t h e l i s t p r o v i d e d < / l i >
< li > < b > To remove : < / b > C l i c k o n t h e ' E d i t f a v o u r i t e s ' b u t t o n a n d h i t t h e d e l e t e b u t t o n n e x t t o t h e o p e r a t i o n y o u w a n t t o r e m o v e < / l i >
< / u l > ` ) ;
2018-05-15 17:36:45 +00:00
}
/ * *
* Sets up the adjustable splitter to allow the user to resize areas of the page .
2019-01-08 18:29:07 +00:00
*
2019-03-27 09:05:10 +00:00
* @ param { boolean } [ minimise = false ] - Set this flag if attempting to minimise frames to 0 width
2018-05-15 17:36:45 +00:00
* /
2019-01-08 18:29:07 +00:00
initialiseSplitter ( minimise = false ) {
if ( this . columnSplitter ) this . columnSplitter . destroy ( ) ;
if ( this . ioSplitter ) this . ioSplitter . destroy ( ) ;
2018-05-15 17:36:45 +00:00
this . columnSplitter = Split ( [ "#operations" , "#recipe" , "#IO" ] , {
sizes : [ 20 , 30 , 50 ] ,
2019-06-28 17:09:00 +01:00
minSize : minimise ? [ 0 , 0 , 0 ] : [ 240 , 310 , 450 ] ,
2018-06-19 00:55:08 +01:00
gutterSize : 4 ,
2019-06-28 17:09:00 +01:00
expandToMin : true ,
2019-08-29 14:08:56 +01:00
onDrag : debounce ( function ( ) {
2022-05-30 22:53:17 +01:00
this . adjustComponentSizes ( ) ;
2019-06-03 14:59:41 +01:00
} , 50 , "dragSplitter" , this , [ ] )
2018-05-15 17:36:45 +00:00
} ) ;
this . ioSplitter = Split ( [ "#input" , "#output" ] , {
direction : "vertical" ,
2019-01-08 18:29:07 +00:00
gutterSize : 4 ,
minSize : minimise ? [ 0 , 0 ] : [ 100 , 100 ]
2018-05-15 17:36:45 +00:00
} ) ;
2022-05-30 22:53:17 +01:00
this . adjustComponentSizes ( ) ;
2018-05-15 17:36:45 +00:00
}
/ * *
* Loads the information previously saved to the HTML5 local storage object so that user options
* and favourites can be restored .
* /
loadLocalStorage ( ) {
// Load options
let lOptions ;
if ( this . isLocalStorageAvailable ( ) && localStorage . options !== undefined ) {
lOptions = JSON . parse ( localStorage . options ) ;
}
this . manager . options . load ( lOptions ) ;
// Load favourites
this . loadFavourites ( ) ;
}
/ * *
* Loads the user ' s favourite operations from the HTML5 local storage object and populates the
* Favourites category with them .
* If the user currently has no saved favourites , the defaults from the view constructor are used .
* /
loadFavourites ( ) {
let favourites ;
if ( this . isLocalStorageAvailable ( ) ) {
2022-10-28 13:24:03 +01:00
favourites = localStorage ? . favourites ? . length > 2 ?
2018-05-15 17:36:45 +00:00
JSON . parse ( localStorage . favourites ) :
this . dfavourites ;
favourites = this . validFavourites ( favourites ) ;
this . saveFavourites ( favourites ) ;
} else {
favourites = this . dfavourites ;
}
const favCat = this . categories . filter ( function ( c ) {
return c . name === "Favourites" ;
} ) [ 0 ] ;
if ( favCat ) {
favCat . ops = favourites ;
} else {
this . categories . unshift ( {
name : "Favourites" ,
ops : favourites
} ) ;
}
}
/ * *
* Filters the list of favourite operations that the user had stored and removes any that are no
* longer available . The user is notified if this is the case .
* @ param { string [ ] } favourites - A list of the user ' s favourite operations
* @ returns { string [ ] } A list of the valid favourites
* /
validFavourites ( favourites ) {
const validFavs = [ ] ;
for ( let i = 0 ; i < favourites . length ; i ++ ) {
2019-07-05 12:22:52 +01:00
if ( favourites [ i ] in this . operations ) {
2018-05-15 17:36:45 +00:00
validFavs . push ( favourites [ i ] ) ;
} else {
2018-06-20 00:18:59 +01:00
this . alert ( ` The operation " ${ Utils . escapeHtml ( favourites [ i ] ) } " is no longer available. ` +
"It has been removed from your favourites." ) ;
2018-05-15 17:36:45 +00:00
}
}
return validFavs ;
}
/ * *
* Saves a list of favourite operations to the HTML5 local storage object .
*
* @ param { string [ ] } favourites - A list of the user ' s favourite operations
* /
saveFavourites ( favourites ) {
if ( ! this . isLocalStorageAvailable ( ) ) {
this . alert (
"Your security settings do not allow access to local storage so your favourites cannot be saved." ,
5000
) ;
return false ;
}
localStorage . setItem ( "favourites" , JSON . stringify ( this . validFavourites ( favourites ) ) ) ;
}
/ * *
* Resets favourite operations back to the default as specified in the view constructor and
* refreshes the operation list .
* /
resetFavourites ( ) {
this . saveFavourites ( this . dfavourites ) ;
this . loadFavourites ( ) ;
this . populateOperationsList ( ) ;
this . manager . recipe . initialiseOperationDragNDrop ( ) ;
}
/ * *
* Adds an operation to the user ' s favourites .
*
* @ param { string } name - The name of the operation
* /
addFavourite ( name ) {
const favourites = JSON . parse ( localStorage . favourites ) ;
if ( favourites . indexOf ( name ) >= 0 ) {
2018-06-20 00:18:59 +01:00
this . alert ( ` ' ${ name } ' is already in your favourites ` , 3000 ) ;
2018-05-15 17:36:45 +00:00
return ;
}
favourites . push ( name ) ;
this . saveFavourites ( favourites ) ;
this . loadFavourites ( ) ;
this . populateOperationsList ( ) ;
this . manager . recipe . initialiseOperationDragNDrop ( ) ;
}
/ * *
2019-06-03 14:23:00 +01:00
* Gets the URI params from the window and parses them to extract the actual values .
2019-05-02 13:54:15 +01:00
*
2019-06-03 14:23:00 +01:00
* @ returns { object }
2018-05-15 17:36:45 +00:00
* /
2019-05-02 13:54:15 +01:00
getURIParams ( ) {
2018-05-15 17:36:45 +00:00
// Load query string or hash from URI (depending on which is populated)
// We prefer getting the hash by splitting the href rather than referencing
// location.hash as some browsers (Firefox) automatically URL decode it,
// which cause issues.
const params = window . location . search ||
window . location . href . split ( "#" ) [ 1 ] ||
window . location . hash ;
2019-05-02 13:54:15 +01:00
const parsedParams = Utils . parseURIParams ( params ) ;
return parsedParams ;
}
/ * *
2019-06-03 11:16:54 +01:00
* Searches the URI parameters for recipe and input parameters .
* If recipe is present , replaces the current recipe with the recipe provided in the URI .
* If input is present , decodes and sets the input to the one provided in the URI .
2022-09-16 19:24:57 +01:00
* If character encodings are present , sets them appropriately .
2019-10-24 16:32:14 -07:00
* If theme is present , uses the theme .
2019-06-03 11:16:54 +01:00
*
2022-10-21 18:29:52 +01:00
* @ param { Object } params
2019-06-03 11:16:54 +01:00
* @ fires Manager # statechange
2019-05-02 13:54:15 +01:00
* /
2022-10-21 18:29:52 +01:00
loadURIParams ( params = this . getURIParams ( ) ) {
this . uriParams = params ;
2018-05-15 17:36:45 +00:00
// Read in recipe from URI params
if ( this . uriParams . recipe ) {
try {
const recipeConfig = Utils . parseRecipeConfig ( this . uriParams . recipe ) ;
this . setRecipeConfig ( recipeConfig ) ;
} catch ( err ) { }
} else if ( this . uriParams . op ) {
// If there's no recipe, look for single operations
this . manager . recipe . clearRecipe ( ) ;
// Search for nearest match and add it
const matchedOps = this . manager . ops . filterOperations ( this . uriParams . op , false ) ;
if ( matchedOps . length ) {
this . manager . recipe . addOperation ( matchedOps [ 0 ] . name ) ;
}
// Populate search with the string
const search = document . getElementById ( "search" ) ;
search . value = this . uriParams . op ;
search . dispatchEvent ( new Event ( "search" ) ) ;
}
2022-09-16 19:24:57 +01:00
// Input Character Encoding
2022-10-21 18:29:52 +01:00
// Must be set before the input is loaded
2022-09-16 19:24:57 +01:00
if ( this . uriParams . ienc ) {
2024-04-05 18:48:45 +02:00
this . manager . input . chrEncChange ( parseInt ( this . uriParams . ienc , 10 ) , true , true ) ;
2022-09-16 19:24:57 +01:00
}
// Output Character Encoding
if ( this . uriParams . oenc ) {
2024-03-26 13:33:00 +00:00
this . manager . output . chrEncChange ( parseInt ( this . uriParams . oenc , 10 ) , true ) ;
2022-09-16 19:24:57 +01:00
}
2022-10-28 12:44:06 +01:00
// Input EOL sequence
if ( this . uriParams . ieol ) {
2024-03-26 13:33:00 +00:00
this . manager . input . eolChange ( this . uriParams . ieol , true ) ;
2022-10-28 12:44:06 +01:00
}
// Output EOL sequence
if ( this . uriParams . oeol ) {
2024-03-26 13:33:00 +00:00
this . manager . output . eolChange ( this . uriParams . oeol , true ) ;
2022-10-28 12:44:06 +01:00
}
2022-10-21 18:29:52 +01:00
// Read in input data from URI params
if ( this . uriParams . input ) {
try {
let inputVal ;
const inputChrEnc = this . manager . input . getChrEnc ( ) ;
2022-11-04 14:38:30 +00:00
const inputData = fromBase64 ( this . uriParams . input , null , "byteArray" ) ;
2022-10-21 18:29:52 +01:00
if ( inputChrEnc > 0 ) {
2022-11-04 14:38:30 +00:00
inputVal = cptable . utils . decode ( inputChrEnc , inputData ) ;
2022-10-21 18:29:52 +01:00
} else {
2022-11-04 14:38:30 +00:00
inputVal = Utils . byteArrayToChars ( inputData ) ;
2022-10-21 18:29:52 +01:00
}
this . setInput ( inputVal ) ;
} catch ( err ) { }
}
2019-10-27 15:17:06 +00:00
// Read in theme from URI params
2019-10-24 16:32:14 -07:00
if ( this . uriParams . theme ) {
2019-10-27 15:17:06 +00:00
this . manager . options . changeTheme ( Utils . escapeHtml ( this . uriParams . theme ) ) ;
2019-10-24 16:32:14 -07:00
}
2024-04-24 17:13:44 +01:00
window . dispatchEvent ( this . manager . statechange ) ;
2018-05-15 17:36:45 +00:00
}
/ * *
* Returns the next ingredient ID and increments it for next time .
*
* @ returns { number }
* /
nextIngId ( ) {
return this . ingId ++ ;
}
/ * *
* Gets the current recipe configuration .
*
* @ returns { Object [ ] }
* /
getRecipeConfig ( ) {
return this . manager . recipe . getConfig ( ) ;
}
/ * *
* Given a recipe configuration , sets the recipe to that configuration .
*
* @ fires Manager # statechange
* @ param { Object [ ] } recipeConfig - The recipe configuration
* /
setRecipeConfig ( recipeConfig ) {
document . getElementById ( "rec-list" ) . innerHTML = null ;
for ( let i = 0 ; i < recipeConfig . length ; i ++ ) {
const item = this . manager . recipe . addOperation ( recipeConfig [ i ] . op ) ;
// Populate arguments
2019-02-28 15:27:35 +00:00
log . debug ( ` Populating arguments for ${ recipeConfig [ i ] . op } ` ) ;
2018-05-15 17:36:45 +00:00
const args = item . querySelectorAll ( ".arg" ) ;
for ( let j = 0 ; j < args . length ; j ++ ) {
if ( recipeConfig [ i ] . args [ j ] === undefined ) continue ;
if ( args [ j ] . getAttribute ( "type" ) === "checkbox" ) {
// checkbox
args [ j ] . checked = recipeConfig [ i ] . args [ j ] ;
} else if ( args [ j ] . classList . contains ( "toggle-string" ) ) {
// toggleString
args [ j ] . value = recipeConfig [ i ] . args [ j ] . string ;
2018-06-10 12:03:55 +01:00
args [ j ] . parentNode . parentNode . querySelector ( "button" ) . innerHTML =
Utils . escapeHtml ( recipeConfig [ i ] . args [ j ] . option ) ;
2018-05-15 17:36:45 +00:00
} else {
// all others
args [ j ] . value = recipeConfig [ i ] . args [ j ] ;
}
}
// Set disabled and breakpoint
if ( recipeConfig [ i ] . disabled ) {
item . querySelector ( ".disable-icon" ) . click ( ) ;
}
if ( recipeConfig [ i ] . breakpoint ) {
item . querySelector ( ".breakpoint" ) . click ( ) ;
}
2019-02-28 15:27:35 +00:00
this . manager . recipe . triggerArgEvents ( item ) ;
2018-05-15 17:36:45 +00:00
this . progress = 0 ;
}
}
/ * *
* Resets the splitter positions to default .
* /
resetLayout ( ) {
this . columnSplitter . setSizes ( [ 20 , 30 , 50 ] ) ;
this . ioSplitter . setSizes ( [ 50 , 50 ] ) ;
2022-05-30 22:53:17 +01:00
this . adjustComponentSizes ( ) ;
}
/ * *
* Adjust components to fit their containers .
* /
adjustComponentSizes ( ) {
2018-07-27 15:18:08 +00:00
this . manager . recipe . adjustWidth ( ) ;
2019-05-29 13:25:12 +01:00
this . manager . input . calcMaxTabs ( ) ;
this . manager . output . calcMaxTabs ( ) ;
2022-07-01 12:01:48 +01:00
this . manager . controls . calcControlsHeight ( ) ;
2018-05-15 17:36:45 +00:00
}
/ * *
* Sets the compile message .
* /
setCompileMessage ( ) {
// Display time since last build and compile message
const now = new Date ( ) ,
2018-12-25 23:58:00 +00:00
msSinceCompile = now . getTime ( ) - window . compileTime ,
timeSinceCompile = moment . duration ( msSinceCompile , "milliseconds" ) . humanize ( ) ;
2018-05-15 17:36:45 +00:00
// Calculate previous version to compare to
const prev = PKG _VERSION . split ( "." ) . map ( n => {
return parseInt ( n , 10 ) ;
} ) ;
if ( prev [ 2 ] > 0 ) prev [ 2 ] -- ;
else if ( prev [ 1 ] > 0 ) prev [ 1 ] -- ;
else prev [ 0 ] -- ;
2019-11-14 08:55:27 +00:00
// const compareURL = `https://github.com/gchq/CyberChef/compare/v${prev.join(".")}...v${PKG_VERSION}`;
2018-05-15 17:36:45 +00:00
2018-08-06 07:35:30 +01:00
let compileInfo = ` <a href='https://github.com/gchq/CyberChef/blob/master/CHANGELOG.md'>Last build: ${ timeSinceCompile . substr ( 0 , 1 ) . toUpperCase ( ) + timeSinceCompile . substr ( 1 ) } ago</a> ` ;
2018-05-15 17:36:45 +00:00
if ( window . compileMessage !== "" ) {
compileInfo += " - " + window . compileMessage ;
}
2018-08-20 00:04:49 +01:00
const notice = document . getElementById ( "notice" ) ;
notice . innerHTML = compileInfo ;
notice . setAttribute ( "title" , Utils . stripHtmlTags ( window . compileMessage ) ) ;
2023-03-17 17:46:13 +00:00
notice . setAttribute ( "data-help-title" , "Last build" ) ;
notice . setAttribute ( "data-help" , "This live version of CyberChef is updated whenever new commits are added to the master branch of the CyberChef repository. It represents the latest, most up-to-date build of CyberChef." ) ;
2018-05-15 17:36:45 +00:00
}
/ * *
* Determines whether the browser supports Local Storage and if it is accessible .
*
* @ returns { boolean }
* /
isLocalStorageAvailable ( ) {
try {
if ( ! localStorage ) return false ;
return true ;
} catch ( err ) {
// Access to LocalStorage is denied
return false ;
}
}
/ * *
* Pops up a message to the user and writes it to the console log .
*
* @ param { string } str - The message to display ( HTML supported )
2019-08-28 16:14:13 +01:00
* @ param { number } [ timeout = 0 ] - The number of milliseconds before the alert closes automatically
2018-05-15 17:36:45 +00:00
* 0 for never ( until the user closes it )
* @ param { boolean } [ silent = false ] - Don ' t show the message in the popup , only print it to the
* console
*
* @ example
2018-06-20 00:18:59 +01:00
* // Pops up a box with the message "Error: Something has gone wrong!" that will need to be
* // dismissed by the user.
* this . alert ( "Error: Something has gone wrong!" , 0 ) ;
2018-05-15 17:36:45 +00:00
*
2018-06-20 00:18:59 +01:00
* // Pops up a box with the message "Happy Christmas!" that will disappear after 5 seconds.
* this . alert ( "Happy Christmas!" , 5000 ) ;
2018-05-15 17:36:45 +00:00
* /
2019-08-28 16:14:13 +01:00
alert ( str , timeout = 0 , silent = false ) {
2018-05-15 17:36:45 +00:00
const time = new Date ( ) ;
log . info ( "[" + time . toLocaleString ( ) + "] " + str ) ;
if ( silent ) return ;
2024-03-26 16:34:36 +00:00
this . snackbars . push ( $ . snackbar ( {
2018-06-20 00:18:59 +01:00
content : str ,
timeout : timeout ,
htmlAllowed : true ,
onClose : ( ) => {
2024-03-26 16:34:36 +00:00
this . snackbars . shift ( ) . remove ( ) ;
2018-06-20 00:18:59 +01:00
}
2024-03-26 16:34:36 +00:00
} ) ) ;
2018-05-15 17:36:45 +00:00
}
/ * *
* Pops up a box asking the user a question and sending the answer to a specified callback function .
*
* @ param { string } title - The title of the box
* @ param { string } body - The question ( HTML supported )
2019-08-22 11:26:04 +01:00
* @ param { string } accept - The text of the accept button
* @ param { string } reject - The text of the reject button
2018-05-15 17:36:45 +00:00
* @ param { function } callback - A function accepting one boolean argument which handles the
* response e . g . function ( answer ) { ... }
* @ param { Object } [ scope = this ] - The object to bind to the callback function
*
* @ example
* // Pops up a box asking if the user would like a cookie. Prints the answer to the console.
2019-08-22 11:26:04 +01:00
* this . confirm ( "Question" , "Would you like a cookie?" , "Yes" , "No" , function ( answer ) { console . log ( answer ) ; } ) ;
2018-05-15 17:36:45 +00:00
* /
2019-08-22 11:26:04 +01:00
confirm ( title , body , accept , reject , callback , scope ) {
2018-05-15 17:36:45 +00:00
scope = scope || this ;
document . getElementById ( "confirm-title" ) . innerHTML = title ;
document . getElementById ( "confirm-body" ) . innerHTML = body ;
2019-08-22 11:26:04 +01:00
document . getElementById ( "confirm-yes" ) . innerText = accept ;
document . getElementById ( "confirm-no" ) . innerText = reject ;
2018-05-15 17:36:45 +00:00
document . getElementById ( "confirm-modal" ) . style . display = "block" ;
this . confirmClosed = false ;
$ ( "#confirm-modal" ) . modal ( )
. one ( "show.bs.modal" , function ( e ) {
this . confirmClosed = false ;
} . bind ( this ) )
. one ( "click" , "#confirm-yes" , function ( ) {
this . confirmClosed = true ;
callback . bind ( scope ) ( true ) ;
$ ( "#confirm-modal" ) . modal ( "hide" ) ;
} . bind ( this ) )
2019-08-22 11:26:04 +01:00
. one ( "click" , "#confirm-no" , function ( ) {
this . confirmClosed = true ;
callback . bind ( scope ) ( false ) ;
} . bind ( this ) )
2018-05-15 17:36:45 +00:00
. one ( "hide.bs.modal" , function ( e ) {
2019-08-22 11:26:04 +01:00
if ( ! this . confirmClosed ) {
callback . bind ( scope ) ( undefined ) ;
}
2018-05-15 17:36:45 +00:00
this . confirmClosed = true ;
} . bind ( this ) ) ;
}
/ * *
* Handler for CyerChef statechange events .
* Fires whenever the input or recipe changes in any way .
*
* @ listens Manager # statechange
* @ param { event } e
* /
stateChange ( e ) {
2022-07-11 13:43:19 +01:00
debounce ( function ( ) {
this . progress = 0 ;
this . autoBake ( ) ;
2022-09-16 19:24:57 +01:00
this . updateURL ( true , null , true ) ;
2022-07-11 13:43:19 +01:00
} , 20 , "stateChange" , this , [ ] ) ( ) ;
2019-05-02 11:29:54 +01:00
}
2019-08-29 14:08:56 +01:00
2019-05-02 11:29:54 +01:00
/ * *
2022-09-16 19:24:57 +01:00
* Update the page title and URL to contain the new recipe
2019-05-02 11:29:54 +01:00
*
* @ param { boolean } includeInput
2022-09-16 19:24:57 +01:00
* @ param { string } [ input = null ]
2019-05-02 13:54:15 +01:00
* @ param { boolean } [ changeUrl = true ]
2019-05-02 11:29:54 +01:00
* /
2022-09-16 19:24:57 +01:00
updateURL ( includeInput , input = null , changeUrl = true ) {
2018-05-15 17:36:45 +00:00
// Set title
const recipeConfig = this . getRecipeConfig ( ) ;
let title = "CyberChef" ;
if ( recipeConfig . length === 1 ) {
title = ` ${ recipeConfig [ 0 ] . op } - ${ title } ` ;
} else if ( recipeConfig . length > 1 ) {
// See how long the full recipe is
const ops = recipeConfig . map ( op => op . op ) . join ( ", " ) ;
if ( ops . length < 45 ) {
title = ` ${ ops } - ${ title } ` ;
} else {
// If it's too long, just use the first one and say how many more there are
title = ` ${ recipeConfig [ 0 ] . op } , ${ recipeConfig . length - 1 } more - ${ title } ` ;
}
}
document . title = title ;
// Update the current history state (not creating a new one)
2019-05-02 13:54:15 +01:00
if ( this . options . updateUrl && changeUrl ) {
2019-05-02 11:29:54 +01:00
this . lastStateUrl = this . manager . controls . generateStateUrl ( true , includeInput , input , recipeConfig ) ;
window . history . replaceState ( { } , title , this . lastStateUrl ) ;
2018-05-15 17:36:45 +00:00
}
}
/ * *
* Handler for the history popstate event .
* Reloads parameters from the URL .
*
* @ param { event } e
* /
popState ( e ) {
this . loadURIParams ( ) ;
}
}
export default App ;