From 585e4baf2eb6c24066cc5e1f6e7900569f93e237 Mon Sep 17 00:00:00 2001 From: shivanraptor Date: Fri, 23 Oct 2020 12:23:57 +0800 Subject: [PATCH 1/7] Added Hashcat Command Validation When creating new task, the hashcat command is parsed and validated by Optparse.js and Optparse Hashtopolis plugin. Task name is marked as required as well. --- .gitignore | 1 + src/static/optparse.hashtopolis.js | 342 ++++++++++++++++++++++++++ src/static/optparse.js | 311 +++++++++++++++++++++++ src/templates/tasks/new.template.html | 42 +++- 4 files changed, 692 insertions(+), 4 deletions(-) create mode 100644 src/static/optparse.hashtopolis.js create mode 100755 src/static/optparse.js diff --git a/.gitignore b/.gitignore index 1c7e13809..54c7b007b 100755 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ src/files/* *.phpproj *.sln *.phpproj.user +.DS_Store diff --git a/src/static/optparse.hashtopolis.js b/src/static/optparse.hashtopolis.js new file mode 100644 index 000000000..89b35c304 --- /dev/null +++ b/src/static/optparse.hashtopolis.js @@ -0,0 +1,342 @@ +/** + * Hashcat Command Validation (based on Hashcat v6.1.1) + * + * This plugin provides validation of Hashcat command + * require jQuery 3.x and optparse.js Library: https://github.com/shivanraptor/optparse-js + * + * @package optparse_hashtopolis + * @copyright 2020 Raptor Kwok + * @license MIT License + * @website https://github.com/shivanraptor/optparse-js + * @version 0.1 + */ + +// TODO: Change hardcoded values to ENUM + +let switches = [ + ['-m', '--hash-type NUMBER', "Hash-type"], + ['-a', '--attack-mode NUMBER', "Attack-mode"], + ['-V', '--version', "Print version"], + ['-h', '--help', "Print help"], + ['--quiet', "Suppress output"], + ['--hex-charset', "Assume charset is given in hex"], + ['--hex-salt', "Assume salt is given in hex"], + ['--hex-wordlist', "Assume words in wordlist are given in hex"], + ['--force', "Ignore warnings"], + ['--status', "Enable automatic update of the status screen"], + ['--status-json', "Enable JSON format for status output"], + ['--status-timer NUMBER', "Sets seconds between status screen updates to X"], + ['--stdin-timeout-abort NUMBER', "Abort if there is no input from stdin for X seconds"], + ['--machine-readable', "Display the status view in a machine-readable format"], + ['--keep-guessing', "Keep guessing the hash after it has been cracked"], + ['--self-test-disable', "Disable self-test functionality on startup"], + ['--loopback', "Add new plains to induct directory"], + ['--markov-hcstat2 FILE', "Specify hcstat2 file to use"], + ['--markov-disable', "Disables markov-chains, emulates classic brute-force"], + ['--markov-classic', "Enables classic markov-chains, no per-position"], + ['-t', '--markov-threshold NUMBER', "Threshold X when to stop accepting new markov-chains"], + ['--runtime NUMBER', "Abort session after X seconds of runtime"], + ['--session MESSAGE', "Define specific session name"], + ['--session', "Restore session from --session"], + ['--restore-disable', "Do not write restore file"], + ['--restore-file-path FILE', "Specific path to restore file"], + ['-o', '--outfile FILE', "Define outfile for recovered hash"], + ['--outfile-format CSV', "Outfile format to use, separated with commas"], + ['--outfile-autohex-disable', "Disable the use of $HEX[] in output plains"], + ['--outfile-check-timer NUMBER', "Sets seconds between outfile checks to X"], + ['--wordlist-autohex-disable', "Disable the conversion of $HEX[] from the wordlist"], + ['-p', '--separator SINGLE_CHAR', "Separator char for hashlists and outfile"], + ['--stdout', "Do not crack a hash, instead print candidates only"], + ['--show', "Compare hashlist with potfile; show cracked hashes"], + ['--left', "Compare hashlist with potfile; show uncracked hashes"], + ['--username', "Enable ignoring of usernames in hashfile"], + ['--remove', "Enable removal of hashes once they are cracked"], + ['--remove-timer NUMBER', "Update input hash file each X seconds"], + ['--potfile-disable', "Do not write potfile"], + ['--potfile-path FILE', "Specific path to potfile"], + ['--encoding-from ENCODING', "Force internal wordlist encoding from X"], + ['--encoding-to ENCODING', "Force internal wordlist encoding to X"], + ['--debug-mode NUMBER', "Defines the debug mode (hybrid only by using rules)"], + ['--debug-file FILE', "Output file for debugging rules"], + ['--induction-dir FILE', "Specify the induction directory to use for loopback"], // TODO: Change to DIRECTORY filter + ['--outfile-check-dir FILE', "Specify the outfile directory to monitor for plains"], // TODO: Change to DIRECTORY filter + ['--logfile-disable', "Disable the logfile"], + ['--hccapx-message-pair NUMBER', "Load only message pairs from hccapx matching X"], + ['--nonce-error-corrections NUMBER', "The BF size range to replace AP's nonce last bytes"], + ['--keyboard-layout-mapping FILE', "Keyboard layout mapping table for special hash-modes"], + ['--truecrypt-keyfiles FILE', "TrueCrypt Keyfiles to use, separated with commas"], + ['--veracrypt-keyfiles FILE', "VeraCrypt Keyfiles to use, separated with commas"], + ['--veracrypt-pim-start NUMBER', "VeraCrypt personal iterations multiplier start"], + ['--veracrypt-pim-stop NUMBER', "VeraCrypt personal iterations multiplier stop"], + ['-b', '--benchmark', "Run benchmark of selected hash-modes"], + ['--benchmark-all', "Run benchmark of all hash-modes (requires -b)"], // TODO: Check existence of -b flag + ['--speed-only', "Return expected speed of the attack, then quit"], + ['--progress-only', "Return ideal progress step size and time to process"], + ['-c', '--segement-size NUMBER', "Sets size in MB to cache from the wordfile to X"], + ['--bitmap-min NUMBER', "Sets minimum bits allowed for bitmaps to X"], + ['--bitmap-max NUMBER', "Sets maximum bits allowed for bitmaps to X"], // TODO: Compare with --bitmap-min + ['--cpu-affinity CSV', "Locks to CPU devices, separated with commas"], + ['--hook-threads NUMBER', "Sets number of threads for a hook (per compute unit)"], + ['--example-hashes', "Show an example hash for each hash-mode"], + ['--backend-ignore-cuda', "Do not try to open CUDA interface on startup"], + ['--backend-ignore-opencl', "Do not try to open OpenCL interface on startup"], + ['-I', '--backend-info', "Show info about detected backend API devices"], + ['-d', '--backend-devices CSV', "Backend devices to use, separated with commas"], + ['-D', '--opencl-device-types CSV', "OpenCL device-types to use, separated with commas"], + ['-O', '--optimized-kernel-enable', "Enable optimized kernels (limits password length)"], + ['-w', '--workload-profile NUMBER', "Enable a specific workload profile, see pool below"], // TODO: Check Workload Profile range + ['-n', '--kernel-accel NUMBER', "Manual workload tuning, set outerloop step size to X"], + ['-u', '--kernel-loops NUMBER', "Manual workload tuning, set innerloop step size to X"], + ['-T', '--kernel-threads NUMBER', "Manual workload tuning, set thread count to X"], + ['--backend-vector-width NUMBER', "Manually override backend vector-width to X"], + ['--spin-damp PERCENT', "Use CPU for device synchronization, in percent"], + ['--hwmon-disable', "Disable temperature and fanspeed reads and triggers"], + ['--hwmon-temp-abort NUMBER', "Abort if temperature reaches X degrees Celsius"], + ['--scrypt-tmto NUMBER', "Manually override TMTO value for scrypt to X"], + ['-s', '--skip NUMBER', "Skip X words from the start"], + ['-l', '--limit', "Limit X words from the start + skipped words"], + ['--keyspace', "Show keyspace base:mod values and quit"], + ['-j', '--rule-left MESSAGE', "Single rule applied to each word from left wordlist"], // TODO: Check Rule format + ['-k', '--rule-right MESSAGE', "Single rule applied to each word from right wordlist"], // TODO: Check Rule format + ['-r', '--rules-file FILE', "Multiple rules applied to each word from wordlists"], + ['-g', '--generate-rules NUMBER', "Generate X random rules"], + ['--generate-rules-func-min NUMBER', "Force min X functions per rule"], + ['--generate-rules-func-max NUMBER', "Force max X functions per rule"], + ['--generate-rules-seed NUMBER', "Force RNG seed set to X"], + ['-1', '--custom-charset1 MESSAGE', "User-defined charset ?1"], // TODO: Check Charset + ['-2', '--custom-charset2 MESSAGE', "User-defined charset ?2"], // TODO: Check Charset + ['-3', '--custom-charset3 MESSAGE', "User-defined charset ?3"], // TODO: Check Charset + ['-4', '--custom-charset4 MESSAGE', "User-defined charset ?4"], // TODO: Check Charset + ['-i', '--increment', "Enable mask increment mode"], + ['--increment-min NUMBER', "Start mask incrementing at X"], + ['--increment-max NUMBER', "Stop mask incrementing at X"], // TODO: Compare MIN and MAX values + ['-S', '--slow-candidates', "Enable slower (but advanced) candidate generators"], + ['--brain-server', "Enable brain server"], + ['--brain-server-timer NUMBER', "Update the brain server dump each X seconds (min:60)"], + ['-z', '--brain-client', "Enable brain client, activates -S"], + ['--brain-client-features NUMBER', "Define brain client features, see below"], + ['--brain-host MESSAGE', "Brain server host (IP or domain)"], // TODO: Check IP or Domain format + ['--brain-port NUMBER', "Brain server port"], // TODO: Check Port Value (1 - 65536) + ['--brain-password MESSAGE', "Brain server authentication password"], + ['--brain-session HEX', "Overrides automatically calculated brain session"], + ['--brain-session-whitelist CSV_HEX', "Allow given sessions only, separated with commas"], +]; +let defaultOptions = { + debug: false, + ruleFiles: [], + attackType: -1, + hashMode: -1, + posArgs: [], // Positional Arguments + customCharset1: '', + customCharset2: '', + customCharset3: '', + customCharset4: '' +}; +var options = defaultOptions; + +var parser = new optparse.OptionParser(switches); + +// ======================================================================================= +// Custom Filters +// ======================================================================================= +parser.filter('single_char', function(value) { + if(value.length != 1) { + throw "Single character filter mismatch"; + } + return value; +}); +parser.filter('encoding', function(value) { + // TODO: Complete Encoding List: http://www.iana.org/assignments/character-sets + var encodings = ['us-ascii', 'utf-8', 'utf-16', 'utf-32le', 'iso-8859-15']; // Incomplete, to be extended + if(encodings.indexOf(value) == -1) { + throw "Invalid encoding standards"; + } + return value; +}); +parser.filter('csv', function(value) { + var tokens = value.split(','); + for(var i = 0; i < tokens.length; i++) { + if(parseInt(tokens[i]) == NaN) { + throw "Invalid comma-separated value: " + tokens[i]; + } + } + return value; +}); +parser.filter('csv_hex', function(value) { + var tokens = value.split(','); + for(var i = 0; i < tokens.length; i++) { + if(parseInt(tokens[i], 16) == NaN) { + throw "Invalid comma-separated hex value: " + tokens[i]; + } + } + return value; +}); +parser.filter('percent', function(value) { + if(parseInt(value) == NaN) { + throw "Invalid percent value: " + value; + } else if(parseInt(value) < 0 || parseInt(value) > 100) { + throw "Percent out of range: " + value; + } + return value; +}); + +// ======================================================================================= +// Option Parser (to be completed) +// ======================================================================================= +parser.on('hash-type', function(name, value) { + console.log('Hash Type: ' + value); + options.hashMode = parseInt(value); // TODO: Check Hash Mode +}); +parser.on('attack-mode', function(name, value) { + console.log('Attack Mode: ' + value); + if(parseInt(value) >= 0 && parseInt(value) <= 3) { + options.attackType = parseInt(value); + } else { + throw "Invalid Attack Type"; + } +}); +parser.on('rules-file', function(name, value) { + options.ruleFiles.push(value); +}); +parser.on('status-timer', function(name, value) { + console.log('Status Timer: ' + value); +}); +parser.on('separator', function(name, value) { + console.log('Separator: ' + value); +}); +parser.on('encoding-from', function(name, value) { + console.log('Encoding From: ' + value); +}); +parser.on('cpu-affinity', function(name, value) { + console.log('CPU Affinity: ' + value); +}); +parser.on('brain-session', function(name, value) { + console.log('Brain Session: ' + value); +}); +parser.on('brain-session-whitelist', function(name, value) { + console.log('Brain Session-whitelist: ' + value); +}); +parser.on('spin-damp', function(name, value) { + console.log('Spin Damp Percent: ' + value); +}); +parser.on('custom-charset1', function(name, value) { + console.log('Custom Charset 1: ' + value); + options.customCharset1 = value; +}); +parser.on('custom-charset2', function(name, value) { + console.log('Custom Charset 2: ' + value); + options.customCharset2 = value; +}); +parser.on('custom-charset3', function(name, value) { + console.log('Custom Charset 3: ' + value); + options.customCharset3 = value; +}); +parser.on('custom-charset4', function(name, value) { + console.log('Custom Charset 4: ' + value); + options.customCharset4 = value; +}); +parser.on('print', function(value) { + console.log('PRINT: ' + value); +}); +parser.on('debug', function() { + options.debug = true; +}); +parser.on(0, function(opt) { + console.log('The first non-switch option is: ' + opt); + options.posArgs[0] = opt; +}); +parser.on(1, function(opt) { + console.log('The second non-switch option is: ' + opt); + options.posArgs[1] = opt; +}); +parser.on(2, function(opt) { + console.log('The third non-switch option is: ' + opt); + options.posArgs[2] = opt; +}); +parser.on(3, function(opt) { + console.log('The fourth non-switch option is: ' + opt); + options.posArgs[3] = opt; +}); +parser.on(4, function(opt) { + console.log('The fifth non-switch option is: ' + opt); + options.posArgs[4] = opt; +}); +parser.on('*', function(opt, value) { + console.log('wild handler for ' + opt + ', value=' + value); +}); + +// ======================================================================================= +// Functions +// ======================================================================================= +function startParse(cmd, isHashtopolis = true) { + // resetting the options + options = defaultOptions; + options.ruleFiles = []; + options.posArgs = []; + + var result = false; + if(isHashtopolis) { + args = cmd.replace('hashcat', '').trim().split(/ |=/g); + parser.parse(args); + result = validateHashtopolisCommand(options); + } else { + parser.parse(args); + result = validateHashcatCommand(options); + } + return result; +} + +function validateHashtopolisCommand(opt) { + // Pre-case Check + if(opt.posArgs[0] != '#HL#') { + // console.log(opt.posArgs); + return {"result": false, "reason": "Hashlist is missing"}; + } + return validateHashcatCommand(opt); +} + +function validateHashcatCommand(opt) { + if(opt.attackType == 0) { // 0: Word List Attack + // Required Dictionary + if(opt.posArgs.length == 2) { + console.log('Dictionary: ' + opt.posArgs[1]); + if(opt.ruleFiles.length == 0) { + return {"result": true, "reason": "Word List Attack"}; + } else { + return {"result": true, "reason": "Word List Attack with " + opt.ruleFiles.length + " rule(s)."}; + } + } else { + return {"result": false, "reason": "Missing wordlist"}; + } + } else if(opt.attackType == 3) { // 3: Bruteforce Attack + if(opt.posArgs.length > 1) { + if(opt.customCharset1 != '') { + if(opt.posArgs[1].indexOf('?1') !== -1) { + return {"result": true, "reason": "Bruteforce Attack with Character Set 1"}; + } + } + if(opt.customCharset2 != '') { + if(opt.posArgs[1].indexOf('?2') !== -1) { + return {"result": true, "reason": "Bruteforce Attack with Character Set 2"}; + } + } + if(opt.customCharset3 != '') { + if(opt.posArgs[1].indexOf('?3') !== -1) { + return {"result": true, "reason": "Bruteforce Attack with Character Set 3"}; + } + } + if(opt.customCharset4 != '') { + if(opt.posArgs[1].indexOf('?4') !== -1) { + return {"result": true, "reason": "Bruteforce Attack with Character Set 4"}; + } + } + + // No custom character set + return {"result": true, "reason": "Bruteforce Attack with pattern: " + opt.posArgs[1]}; + } else { + return {"result": false, "reason": "Bruteforce Attack but missing pattern"}; + } + } else { + return {"result": false, "reason": "Missing / Unsupported Attack Type (Supported Types: 0, 3)"}; + } +} \ No newline at end of file diff --git a/src/static/optparse.js b/src/static/optparse.js new file mode 100755 index 000000000..32ef1c606 --- /dev/null +++ b/src/static/optparse.js @@ -0,0 +1,311 @@ +// Optparse.js 1.0.3 - Option Parser for Javascript +// +// Copyright (c) 2009 Johan Dahlberg +// +// See README.md for license. +// +var optparse = {}; +try{ optparse = exports } catch(e) {}; // Try to export the lib for node.js +(function(self) { +var VERSION = '1.0.3'; +var LONG_SWITCH_RE = /^--\w/; +var SHORT_SWITCH_RE = /^-\w/; +var NUMBER_RE = /^(0x[A-Fa-f0-9]+)|([0-9]+\.[0-9]+)|(\d+)$/; +var DATE_RE = /^\d{4}-(0[0-9]|1[0,1,2])-([0,1,2][0-9]|3[0,1])$/; +var EMAIL_RE = /^([0-9a-zA-Z]+([_.-]?[0-9a-zA-Z]+)*@[0-9a-zA-Z]+[0-9,a-z,A-Z,.,-]*(.){1}[a-zA-Z]{2,4})+$/; +var EXT_RULE_RE = /(\-\-[\w_-]+)\s+([\w\[\]_-]+)|(\-\-[\w_-]+)/; +var ARG_OPTIONAL_RE = /\[(.+)\]/; + +// The default switch argument filter to use, when argument name doesnt match +// any other names. +var DEFAULT_FILTER = '_DEFAULT'; +var PREDEFINED_FILTERS = {}; + +// The default switch argument filter. Parses the argument as text. +function filter_text(value) { + return value; +} + +// Switch argument filter that expects an integer, HEX or a decimal value. An +// exception is throwed if the criteria is not matched. +// Valid input formats are: 0xFFFFFFF, 12345 and 1234.1234 +function filter_number(value) { + var m = NUMBER_RE.exec(value); + if(m == null) throw OptError('Expected a number representative'); + if(m[1]) { + // The number is in HEX format. Convert into a number, then return it + return parseInt(m[1], 16); + } else { + // The number is in regular- or decimal form. Just run in through + // the float caster. + return parseFloat(m[2] || m[3]); + } +}; + +// Switch argument filter that expects a Date expression. The date string MUST be +// formated as: "yyyy-mm-dd" An exception is throwed if the criteria is not +// matched. An DATE object is returned on success. +function filter_date(value) { + var m = DATE_RE.exec(value); + if(m == null) throw OptError('Expected a date representation in the "yyyy-mm-dd" format.'); + return new Date(parseInt(m[0]), parseInt(m[1]) - 1, parseInt(m[2])); +}; + +// Switch argument filter that expects an email address. An exception is throwed +// if the criteria doesn`t match. +function filter_email(value) { + var m = EMAIL_RE.exec(value); + if(m == null) throw OptError('Excpeted an email address.'); + return m[1]; +} + +// Register all predefined filters. This dict is used by each OptionParser +// instance, when parsing arguments. Custom filters can be added to the parser +// instance by calling the "add_filter" -method. +PREDEFINED_FILTERS[DEFAULT_FILTER] = filter_text; +PREDEFINED_FILTERS['TEXT'] = filter_text; +PREDEFINED_FILTERS['NUMBER'] = filter_number; +PREDEFINED_FILTERS['DATE'] = filter_date; +PREDEFINED_FILTERS['EMAIL'] = filter_email; + +// Buildes rules from a switches collection. The switches collection is defined +// when constructing a new OptionParser object. +function build_rules(filters, arr) { + var rules = []; + for(var i=0; i> value means that the switch does +// not take anargument. +function build_rule(filters, short, expr, desc) { + var optional, filter; + var m = expr.match(EXT_RULE_RE); + if(m == null) throw OptError('The switch is not well-formed.'); + var long = m[1] || m[3]; + if(m[2] != undefined) { + // A switch argument is expected. Check if the argument is optional, + // then find a filter that suites. + var optional = ARG_OPTIONAL_RE.test(m[2]); + var optional_match = m[2].match(ARG_OPTIONAL_RE); + var filter_name = optional ? optional_match[1] : m[2]; + filter = filters[filter_name]; + if(filter === undefined) filter = filters[DEFAULT_FILTER]; + } + return { + name: long.substr(2), + short: short, + long: long, + decl: expr, + desc: desc, + optional_arg: optional, + filter: filter + } +} + +// Loop's trough all elements of an array and check if there is valid +// options expression within. An valid option is a token that starts +// double dashes. E.G. --my_option +function contains_expr(arr) { + if(!arr || !arr.length) return false; + var l = arr.length; + while(l-- > 0) if(LONG_SWITCH_RE.test(arr[l])) return true; + return false; +} + +// Extends destination object with members of source object +function extend(dest, src) { + var result = dest; + for(var n in src) { + result[n] = src[n]; + } + return result; +} + +// Appends spaces to match specified number of chars +function spaces(arg1, arg2) { + var l, builder = []; + if(arg1.constructor === Number) { + l = arg1; + } else { + if(arg1.length == arg2) return arg1; + l = arg2 - arg1.length; + builder.push(arg1); + } + while(l-- > 0) builder.push(' '); + return builder.join(''); +} + +// Create a new Parser object that can be used to parse command line arguments. +// +// +function Parser(rules) { + return new OptionParser(rules); +} + +// Creates an error object with specified error message. +function OptError(msg) { + return new function() { + this.msg = msg; + this.toString = function() { + return this.msg; + } + } +} + +function OptionParser(rules) { + this.banner = 'Usage: [Options]'; + this.options_title = 'Available options:' + this._rules = rules; + this._halt = false; + this.filters = extend({}, PREDEFINED_FILTERS); + this.on_args = {}; + this.on_switches = {}; + this.on_halt = function() {}; + this.default_handler = function() {}; +} + +OptionParser.prototype = { + + // Adds args and switchs handler. + on: function(value, fn) { + if(value.constructor === Function ) { + this.default_handler = value; + } else if(value.constructor === Number) { + this.on_args[value] = fn; + } else { + this.on_switches[value] = fn; + } + }, + + // Adds a custom filter to the parser. It's possible to override the + // default filter by passing the value "_DEFAULT" to the ´´name´´ + // argument. The name of the filter is automatically transformed into + // upper case. + filter: function(name, fn) { + this.filters[name.toUpperCase()] = fn; + }, + + // Parses specified args. Returns remaining arguments. + parse: function(args) { + var result = [], callback; + var rules = build_rules(this.filters, this._rules); + var tokens = args.concat([]); + var token; + while(this._halt == false && (token = tokens.shift())) { + if(LONG_SWITCH_RE.test(token) || SHORT_SWITCH_RE.test(token)) { + var arg = undefined; + // The token is a long or a short switch. Get the corresponding + // rule, filter and handle it. Pass the switch to the default + // handler if no rule matched. + for(var i = 0; i < rules.length; i++) { + var rule = rules[i]; + if(rule.long == token || rule.short == token) { + if(rule.filter !== undefined) { + arg = tokens.shift(); + if(arg && (!LONG_SWITCH_RE.test(arg) && !SHORT_SWITCH_RE.test(arg))) { + try { + arg = rule.filter(arg, tokens); + } catch(e) { + throw OptError(token + ': ' + e.toString()); + } + } else if(rule.optional_arg) { + if(arg) { + tokens.unshift(arg); + } + } else { + throw OptError('Expected switch argument.'); + } + } + callback = this.on_switches[rule.name]; + if (!callback) callback = this.on_switches['*']; + if(callback) callback.apply(this, [rule.name, arg]); + break; + } + } + if(i == rules.length) this.default_handler.apply(this, [token]); + } else { + // Did not match long or short switch. Parse the token as a + // normal argument. + callback = this.on_args[result.length]; + result.push(token); + if(callback) callback.apply(this, [token]); + } + } + return this._halt ? this.on_halt.apply(this, [tokens]) : result; + }, + + // Returns an Array with all defined option rules + options: function() { + return build_rules(this.filters, this._rules); + }, + + // Add an on_halt callback if argument ´´fn´´ is specified. on_switch handlers can + // call instance.halt to abort the argument parsing. This can be useful when + // displaying help or version information. + halt: function(fn) { + this._halt = fn === undefined + if(fn) this.on_halt = fn; + }, + + // Returns a string representation of this OptionParser instance. + toString: function() { + var builder = [this.banner, '', this.options_title], + shorts = false, longest = 0, rule; + var rules = build_rules(this.filters, this._rules); + for(var i = 0; i < rules.length; i++) { + rule = rules[i]; + // Quick-analyze the options. + if(rule.short) shorts = true; + if(rule.decl.length > longest) longest = rule.decl.length; + } + for(var i = 0; i < rules.length; i++) { + var text = spaces(6); + rule = rules[i]; + if(shorts) { + if(rule.short) text = spaces(2) + rule.short + ', '; + } + text += spaces(rule.decl, longest) + spaces(3); + text += rule.desc; + builder.push(text); + } + return builder.join('\n'); + } +} + +self.VERSION = VERSION; +self.OptionParser = OptionParser; + +})(optparse); diff --git a/src/templates/tasks/new.template.html b/src/templates/tasks/new.template.html index 5c62616e5..adbcece01 100755 --- a/src/templates/tasks/new.template.html +++ b/src/templates/tasks/new.template.html @@ -2,7 +2,7 @@ {%TEMPLATE->struct/menu%}

New task

{%TEMPLATE->struct/messages%} -
+
@@ -19,7 +19,7 @@

New task

Name: - + @@ -36,8 +36,9 @@

New task

Command line: - Use [[config.getVal('hashlistAlias')]] for hash list and assume all files in current directory.
- If you have Linux agents, please mind the filename case sensitivity! +
Invalid command
+

Use [[config.getVal('hashlistAlias')]] for hash list and assume all files in current directory.
+ If you have Linux agents, please mind the filename case sensitivity!

@@ -306,6 +307,8 @@

New task

+ + {%TEMPLATE->struct/foot%} From 8c411bba49a105841ccfcdaf0fbf6f2bcf221b92 Mon Sep 17 00:00:00 2001 From: shivanraptor Date: Fri, 23 Oct 2020 13:33:52 +0800 Subject: [PATCH 2/7] Updated Optparse library linkage to CDN version --- src/static/optparse.hashtopolis.js | 342 -------------------------- src/static/optparse.js | 311 ----------------------- src/templates/tasks/new.template.html | 6 +- 3 files changed, 3 insertions(+), 656 deletions(-) delete mode 100644 src/static/optparse.hashtopolis.js delete mode 100755 src/static/optparse.js diff --git a/src/static/optparse.hashtopolis.js b/src/static/optparse.hashtopolis.js deleted file mode 100644 index 89b35c304..000000000 --- a/src/static/optparse.hashtopolis.js +++ /dev/null @@ -1,342 +0,0 @@ -/** - * Hashcat Command Validation (based on Hashcat v6.1.1) - * - * This plugin provides validation of Hashcat command - * require jQuery 3.x and optparse.js Library: https://github.com/shivanraptor/optparse-js - * - * @package optparse_hashtopolis - * @copyright 2020 Raptor Kwok - * @license MIT License - * @website https://github.com/shivanraptor/optparse-js - * @version 0.1 - */ - -// TODO: Change hardcoded values to ENUM - -let switches = [ - ['-m', '--hash-type NUMBER', "Hash-type"], - ['-a', '--attack-mode NUMBER', "Attack-mode"], - ['-V', '--version', "Print version"], - ['-h', '--help', "Print help"], - ['--quiet', "Suppress output"], - ['--hex-charset', "Assume charset is given in hex"], - ['--hex-salt', "Assume salt is given in hex"], - ['--hex-wordlist', "Assume words in wordlist are given in hex"], - ['--force', "Ignore warnings"], - ['--status', "Enable automatic update of the status screen"], - ['--status-json', "Enable JSON format for status output"], - ['--status-timer NUMBER', "Sets seconds between status screen updates to X"], - ['--stdin-timeout-abort NUMBER', "Abort if there is no input from stdin for X seconds"], - ['--machine-readable', "Display the status view in a machine-readable format"], - ['--keep-guessing', "Keep guessing the hash after it has been cracked"], - ['--self-test-disable', "Disable self-test functionality on startup"], - ['--loopback', "Add new plains to induct directory"], - ['--markov-hcstat2 FILE', "Specify hcstat2 file to use"], - ['--markov-disable', "Disables markov-chains, emulates classic brute-force"], - ['--markov-classic', "Enables classic markov-chains, no per-position"], - ['-t', '--markov-threshold NUMBER', "Threshold X when to stop accepting new markov-chains"], - ['--runtime NUMBER', "Abort session after X seconds of runtime"], - ['--session MESSAGE', "Define specific session name"], - ['--session', "Restore session from --session"], - ['--restore-disable', "Do not write restore file"], - ['--restore-file-path FILE', "Specific path to restore file"], - ['-o', '--outfile FILE', "Define outfile for recovered hash"], - ['--outfile-format CSV', "Outfile format to use, separated with commas"], - ['--outfile-autohex-disable', "Disable the use of $HEX[] in output plains"], - ['--outfile-check-timer NUMBER', "Sets seconds between outfile checks to X"], - ['--wordlist-autohex-disable', "Disable the conversion of $HEX[] from the wordlist"], - ['-p', '--separator SINGLE_CHAR', "Separator char for hashlists and outfile"], - ['--stdout', "Do not crack a hash, instead print candidates only"], - ['--show', "Compare hashlist with potfile; show cracked hashes"], - ['--left', "Compare hashlist with potfile; show uncracked hashes"], - ['--username', "Enable ignoring of usernames in hashfile"], - ['--remove', "Enable removal of hashes once they are cracked"], - ['--remove-timer NUMBER', "Update input hash file each X seconds"], - ['--potfile-disable', "Do not write potfile"], - ['--potfile-path FILE', "Specific path to potfile"], - ['--encoding-from ENCODING', "Force internal wordlist encoding from X"], - ['--encoding-to ENCODING', "Force internal wordlist encoding to X"], - ['--debug-mode NUMBER', "Defines the debug mode (hybrid only by using rules)"], - ['--debug-file FILE', "Output file for debugging rules"], - ['--induction-dir FILE', "Specify the induction directory to use for loopback"], // TODO: Change to DIRECTORY filter - ['--outfile-check-dir FILE', "Specify the outfile directory to monitor for plains"], // TODO: Change to DIRECTORY filter - ['--logfile-disable', "Disable the logfile"], - ['--hccapx-message-pair NUMBER', "Load only message pairs from hccapx matching X"], - ['--nonce-error-corrections NUMBER', "The BF size range to replace AP's nonce last bytes"], - ['--keyboard-layout-mapping FILE', "Keyboard layout mapping table for special hash-modes"], - ['--truecrypt-keyfiles FILE', "TrueCrypt Keyfiles to use, separated with commas"], - ['--veracrypt-keyfiles FILE', "VeraCrypt Keyfiles to use, separated with commas"], - ['--veracrypt-pim-start NUMBER', "VeraCrypt personal iterations multiplier start"], - ['--veracrypt-pim-stop NUMBER', "VeraCrypt personal iterations multiplier stop"], - ['-b', '--benchmark', "Run benchmark of selected hash-modes"], - ['--benchmark-all', "Run benchmark of all hash-modes (requires -b)"], // TODO: Check existence of -b flag - ['--speed-only', "Return expected speed of the attack, then quit"], - ['--progress-only', "Return ideal progress step size and time to process"], - ['-c', '--segement-size NUMBER', "Sets size in MB to cache from the wordfile to X"], - ['--bitmap-min NUMBER', "Sets minimum bits allowed for bitmaps to X"], - ['--bitmap-max NUMBER', "Sets maximum bits allowed for bitmaps to X"], // TODO: Compare with --bitmap-min - ['--cpu-affinity CSV', "Locks to CPU devices, separated with commas"], - ['--hook-threads NUMBER', "Sets number of threads for a hook (per compute unit)"], - ['--example-hashes', "Show an example hash for each hash-mode"], - ['--backend-ignore-cuda', "Do not try to open CUDA interface on startup"], - ['--backend-ignore-opencl', "Do not try to open OpenCL interface on startup"], - ['-I', '--backend-info', "Show info about detected backend API devices"], - ['-d', '--backend-devices CSV', "Backend devices to use, separated with commas"], - ['-D', '--opencl-device-types CSV', "OpenCL device-types to use, separated with commas"], - ['-O', '--optimized-kernel-enable', "Enable optimized kernels (limits password length)"], - ['-w', '--workload-profile NUMBER', "Enable a specific workload profile, see pool below"], // TODO: Check Workload Profile range - ['-n', '--kernel-accel NUMBER', "Manual workload tuning, set outerloop step size to X"], - ['-u', '--kernel-loops NUMBER', "Manual workload tuning, set innerloop step size to X"], - ['-T', '--kernel-threads NUMBER', "Manual workload tuning, set thread count to X"], - ['--backend-vector-width NUMBER', "Manually override backend vector-width to X"], - ['--spin-damp PERCENT', "Use CPU for device synchronization, in percent"], - ['--hwmon-disable', "Disable temperature and fanspeed reads and triggers"], - ['--hwmon-temp-abort NUMBER', "Abort if temperature reaches X degrees Celsius"], - ['--scrypt-tmto NUMBER', "Manually override TMTO value for scrypt to X"], - ['-s', '--skip NUMBER', "Skip X words from the start"], - ['-l', '--limit', "Limit X words from the start + skipped words"], - ['--keyspace', "Show keyspace base:mod values and quit"], - ['-j', '--rule-left MESSAGE', "Single rule applied to each word from left wordlist"], // TODO: Check Rule format - ['-k', '--rule-right MESSAGE', "Single rule applied to each word from right wordlist"], // TODO: Check Rule format - ['-r', '--rules-file FILE', "Multiple rules applied to each word from wordlists"], - ['-g', '--generate-rules NUMBER', "Generate X random rules"], - ['--generate-rules-func-min NUMBER', "Force min X functions per rule"], - ['--generate-rules-func-max NUMBER', "Force max X functions per rule"], - ['--generate-rules-seed NUMBER', "Force RNG seed set to X"], - ['-1', '--custom-charset1 MESSAGE', "User-defined charset ?1"], // TODO: Check Charset - ['-2', '--custom-charset2 MESSAGE', "User-defined charset ?2"], // TODO: Check Charset - ['-3', '--custom-charset3 MESSAGE', "User-defined charset ?3"], // TODO: Check Charset - ['-4', '--custom-charset4 MESSAGE', "User-defined charset ?4"], // TODO: Check Charset - ['-i', '--increment', "Enable mask increment mode"], - ['--increment-min NUMBER', "Start mask incrementing at X"], - ['--increment-max NUMBER', "Stop mask incrementing at X"], // TODO: Compare MIN and MAX values - ['-S', '--slow-candidates', "Enable slower (but advanced) candidate generators"], - ['--brain-server', "Enable brain server"], - ['--brain-server-timer NUMBER', "Update the brain server dump each X seconds (min:60)"], - ['-z', '--brain-client', "Enable brain client, activates -S"], - ['--brain-client-features NUMBER', "Define brain client features, see below"], - ['--brain-host MESSAGE', "Brain server host (IP or domain)"], // TODO: Check IP or Domain format - ['--brain-port NUMBER', "Brain server port"], // TODO: Check Port Value (1 - 65536) - ['--brain-password MESSAGE', "Brain server authentication password"], - ['--brain-session HEX', "Overrides automatically calculated brain session"], - ['--brain-session-whitelist CSV_HEX', "Allow given sessions only, separated with commas"], -]; -let defaultOptions = { - debug: false, - ruleFiles: [], - attackType: -1, - hashMode: -1, - posArgs: [], // Positional Arguments - customCharset1: '', - customCharset2: '', - customCharset3: '', - customCharset4: '' -}; -var options = defaultOptions; - -var parser = new optparse.OptionParser(switches); - -// ======================================================================================= -// Custom Filters -// ======================================================================================= -parser.filter('single_char', function(value) { - if(value.length != 1) { - throw "Single character filter mismatch"; - } - return value; -}); -parser.filter('encoding', function(value) { - // TODO: Complete Encoding List: http://www.iana.org/assignments/character-sets - var encodings = ['us-ascii', 'utf-8', 'utf-16', 'utf-32le', 'iso-8859-15']; // Incomplete, to be extended - if(encodings.indexOf(value) == -1) { - throw "Invalid encoding standards"; - } - return value; -}); -parser.filter('csv', function(value) { - var tokens = value.split(','); - for(var i = 0; i < tokens.length; i++) { - if(parseInt(tokens[i]) == NaN) { - throw "Invalid comma-separated value: " + tokens[i]; - } - } - return value; -}); -parser.filter('csv_hex', function(value) { - var tokens = value.split(','); - for(var i = 0; i < tokens.length; i++) { - if(parseInt(tokens[i], 16) == NaN) { - throw "Invalid comma-separated hex value: " + tokens[i]; - } - } - return value; -}); -parser.filter('percent', function(value) { - if(parseInt(value) == NaN) { - throw "Invalid percent value: " + value; - } else if(parseInt(value) < 0 || parseInt(value) > 100) { - throw "Percent out of range: " + value; - } - return value; -}); - -// ======================================================================================= -// Option Parser (to be completed) -// ======================================================================================= -parser.on('hash-type', function(name, value) { - console.log('Hash Type: ' + value); - options.hashMode = parseInt(value); // TODO: Check Hash Mode -}); -parser.on('attack-mode', function(name, value) { - console.log('Attack Mode: ' + value); - if(parseInt(value) >= 0 && parseInt(value) <= 3) { - options.attackType = parseInt(value); - } else { - throw "Invalid Attack Type"; - } -}); -parser.on('rules-file', function(name, value) { - options.ruleFiles.push(value); -}); -parser.on('status-timer', function(name, value) { - console.log('Status Timer: ' + value); -}); -parser.on('separator', function(name, value) { - console.log('Separator: ' + value); -}); -parser.on('encoding-from', function(name, value) { - console.log('Encoding From: ' + value); -}); -parser.on('cpu-affinity', function(name, value) { - console.log('CPU Affinity: ' + value); -}); -parser.on('brain-session', function(name, value) { - console.log('Brain Session: ' + value); -}); -parser.on('brain-session-whitelist', function(name, value) { - console.log('Brain Session-whitelist: ' + value); -}); -parser.on('spin-damp', function(name, value) { - console.log('Spin Damp Percent: ' + value); -}); -parser.on('custom-charset1', function(name, value) { - console.log('Custom Charset 1: ' + value); - options.customCharset1 = value; -}); -parser.on('custom-charset2', function(name, value) { - console.log('Custom Charset 2: ' + value); - options.customCharset2 = value; -}); -parser.on('custom-charset3', function(name, value) { - console.log('Custom Charset 3: ' + value); - options.customCharset3 = value; -}); -parser.on('custom-charset4', function(name, value) { - console.log('Custom Charset 4: ' + value); - options.customCharset4 = value; -}); -parser.on('print', function(value) { - console.log('PRINT: ' + value); -}); -parser.on('debug', function() { - options.debug = true; -}); -parser.on(0, function(opt) { - console.log('The first non-switch option is: ' + opt); - options.posArgs[0] = opt; -}); -parser.on(1, function(opt) { - console.log('The second non-switch option is: ' + opt); - options.posArgs[1] = opt; -}); -parser.on(2, function(opt) { - console.log('The third non-switch option is: ' + opt); - options.posArgs[2] = opt; -}); -parser.on(3, function(opt) { - console.log('The fourth non-switch option is: ' + opt); - options.posArgs[3] = opt; -}); -parser.on(4, function(opt) { - console.log('The fifth non-switch option is: ' + opt); - options.posArgs[4] = opt; -}); -parser.on('*', function(opt, value) { - console.log('wild handler for ' + opt + ', value=' + value); -}); - -// ======================================================================================= -// Functions -// ======================================================================================= -function startParse(cmd, isHashtopolis = true) { - // resetting the options - options = defaultOptions; - options.ruleFiles = []; - options.posArgs = []; - - var result = false; - if(isHashtopolis) { - args = cmd.replace('hashcat', '').trim().split(/ |=/g); - parser.parse(args); - result = validateHashtopolisCommand(options); - } else { - parser.parse(args); - result = validateHashcatCommand(options); - } - return result; -} - -function validateHashtopolisCommand(opt) { - // Pre-case Check - if(opt.posArgs[0] != '#HL#') { - // console.log(opt.posArgs); - return {"result": false, "reason": "Hashlist is missing"}; - } - return validateHashcatCommand(opt); -} - -function validateHashcatCommand(opt) { - if(opt.attackType == 0) { // 0: Word List Attack - // Required Dictionary - if(opt.posArgs.length == 2) { - console.log('Dictionary: ' + opt.posArgs[1]); - if(opt.ruleFiles.length == 0) { - return {"result": true, "reason": "Word List Attack"}; - } else { - return {"result": true, "reason": "Word List Attack with " + opt.ruleFiles.length + " rule(s)."}; - } - } else { - return {"result": false, "reason": "Missing wordlist"}; - } - } else if(opt.attackType == 3) { // 3: Bruteforce Attack - if(opt.posArgs.length > 1) { - if(opt.customCharset1 != '') { - if(opt.posArgs[1].indexOf('?1') !== -1) { - return {"result": true, "reason": "Bruteforce Attack with Character Set 1"}; - } - } - if(opt.customCharset2 != '') { - if(opt.posArgs[1].indexOf('?2') !== -1) { - return {"result": true, "reason": "Bruteforce Attack with Character Set 2"}; - } - } - if(opt.customCharset3 != '') { - if(opt.posArgs[1].indexOf('?3') !== -1) { - return {"result": true, "reason": "Bruteforce Attack with Character Set 3"}; - } - } - if(opt.customCharset4 != '') { - if(opt.posArgs[1].indexOf('?4') !== -1) { - return {"result": true, "reason": "Bruteforce Attack with Character Set 4"}; - } - } - - // No custom character set - return {"result": true, "reason": "Bruteforce Attack with pattern: " + opt.posArgs[1]}; - } else { - return {"result": false, "reason": "Bruteforce Attack but missing pattern"}; - } - } else { - return {"result": false, "reason": "Missing / Unsupported Attack Type (Supported Types: 0, 3)"}; - } -} \ No newline at end of file diff --git a/src/static/optparse.js b/src/static/optparse.js deleted file mode 100755 index 32ef1c606..000000000 --- a/src/static/optparse.js +++ /dev/null @@ -1,311 +0,0 @@ -// Optparse.js 1.0.3 - Option Parser for Javascript -// -// Copyright (c) 2009 Johan Dahlberg -// -// See README.md for license. -// -var optparse = {}; -try{ optparse = exports } catch(e) {}; // Try to export the lib for node.js -(function(self) { -var VERSION = '1.0.3'; -var LONG_SWITCH_RE = /^--\w/; -var SHORT_SWITCH_RE = /^-\w/; -var NUMBER_RE = /^(0x[A-Fa-f0-9]+)|([0-9]+\.[0-9]+)|(\d+)$/; -var DATE_RE = /^\d{4}-(0[0-9]|1[0,1,2])-([0,1,2][0-9]|3[0,1])$/; -var EMAIL_RE = /^([0-9a-zA-Z]+([_.-]?[0-9a-zA-Z]+)*@[0-9a-zA-Z]+[0-9,a-z,A-Z,.,-]*(.){1}[a-zA-Z]{2,4})+$/; -var EXT_RULE_RE = /(\-\-[\w_-]+)\s+([\w\[\]_-]+)|(\-\-[\w_-]+)/; -var ARG_OPTIONAL_RE = /\[(.+)\]/; - -// The default switch argument filter to use, when argument name doesnt match -// any other names. -var DEFAULT_FILTER = '_DEFAULT'; -var PREDEFINED_FILTERS = {}; - -// The default switch argument filter. Parses the argument as text. -function filter_text(value) { - return value; -} - -// Switch argument filter that expects an integer, HEX or a decimal value. An -// exception is throwed if the criteria is not matched. -// Valid input formats are: 0xFFFFFFF, 12345 and 1234.1234 -function filter_number(value) { - var m = NUMBER_RE.exec(value); - if(m == null) throw OptError('Expected a number representative'); - if(m[1]) { - // The number is in HEX format. Convert into a number, then return it - return parseInt(m[1], 16); - } else { - // The number is in regular- or decimal form. Just run in through - // the float caster. - return parseFloat(m[2] || m[3]); - } -}; - -// Switch argument filter that expects a Date expression. The date string MUST be -// formated as: "yyyy-mm-dd" An exception is throwed if the criteria is not -// matched. An DATE object is returned on success. -function filter_date(value) { - var m = DATE_RE.exec(value); - if(m == null) throw OptError('Expected a date representation in the "yyyy-mm-dd" format.'); - return new Date(parseInt(m[0]), parseInt(m[1]) - 1, parseInt(m[2])); -}; - -// Switch argument filter that expects an email address. An exception is throwed -// if the criteria doesn`t match. -function filter_email(value) { - var m = EMAIL_RE.exec(value); - if(m == null) throw OptError('Excpeted an email address.'); - return m[1]; -} - -// Register all predefined filters. This dict is used by each OptionParser -// instance, when parsing arguments. Custom filters can be added to the parser -// instance by calling the "add_filter" -method. -PREDEFINED_FILTERS[DEFAULT_FILTER] = filter_text; -PREDEFINED_FILTERS['TEXT'] = filter_text; -PREDEFINED_FILTERS['NUMBER'] = filter_number; -PREDEFINED_FILTERS['DATE'] = filter_date; -PREDEFINED_FILTERS['EMAIL'] = filter_email; - -// Buildes rules from a switches collection. The switches collection is defined -// when constructing a new OptionParser object. -function build_rules(filters, arr) { - var rules = []; - for(var i=0; i> value means that the switch does -// not take anargument. -function build_rule(filters, short, expr, desc) { - var optional, filter; - var m = expr.match(EXT_RULE_RE); - if(m == null) throw OptError('The switch is not well-formed.'); - var long = m[1] || m[3]; - if(m[2] != undefined) { - // A switch argument is expected. Check if the argument is optional, - // then find a filter that suites. - var optional = ARG_OPTIONAL_RE.test(m[2]); - var optional_match = m[2].match(ARG_OPTIONAL_RE); - var filter_name = optional ? optional_match[1] : m[2]; - filter = filters[filter_name]; - if(filter === undefined) filter = filters[DEFAULT_FILTER]; - } - return { - name: long.substr(2), - short: short, - long: long, - decl: expr, - desc: desc, - optional_arg: optional, - filter: filter - } -} - -// Loop's trough all elements of an array and check if there is valid -// options expression within. An valid option is a token that starts -// double dashes. E.G. --my_option -function contains_expr(arr) { - if(!arr || !arr.length) return false; - var l = arr.length; - while(l-- > 0) if(LONG_SWITCH_RE.test(arr[l])) return true; - return false; -} - -// Extends destination object with members of source object -function extend(dest, src) { - var result = dest; - for(var n in src) { - result[n] = src[n]; - } - return result; -} - -// Appends spaces to match specified number of chars -function spaces(arg1, arg2) { - var l, builder = []; - if(arg1.constructor === Number) { - l = arg1; - } else { - if(arg1.length == arg2) return arg1; - l = arg2 - arg1.length; - builder.push(arg1); - } - while(l-- > 0) builder.push(' '); - return builder.join(''); -} - -// Create a new Parser object that can be used to parse command line arguments. -// -// -function Parser(rules) { - return new OptionParser(rules); -} - -// Creates an error object with specified error message. -function OptError(msg) { - return new function() { - this.msg = msg; - this.toString = function() { - return this.msg; - } - } -} - -function OptionParser(rules) { - this.banner = 'Usage: [Options]'; - this.options_title = 'Available options:' - this._rules = rules; - this._halt = false; - this.filters = extend({}, PREDEFINED_FILTERS); - this.on_args = {}; - this.on_switches = {}; - this.on_halt = function() {}; - this.default_handler = function() {}; -} - -OptionParser.prototype = { - - // Adds args and switchs handler. - on: function(value, fn) { - if(value.constructor === Function ) { - this.default_handler = value; - } else if(value.constructor === Number) { - this.on_args[value] = fn; - } else { - this.on_switches[value] = fn; - } - }, - - // Adds a custom filter to the parser. It's possible to override the - // default filter by passing the value "_DEFAULT" to the ´´name´´ - // argument. The name of the filter is automatically transformed into - // upper case. - filter: function(name, fn) { - this.filters[name.toUpperCase()] = fn; - }, - - // Parses specified args. Returns remaining arguments. - parse: function(args) { - var result = [], callback; - var rules = build_rules(this.filters, this._rules); - var tokens = args.concat([]); - var token; - while(this._halt == false && (token = tokens.shift())) { - if(LONG_SWITCH_RE.test(token) || SHORT_SWITCH_RE.test(token)) { - var arg = undefined; - // The token is a long or a short switch. Get the corresponding - // rule, filter and handle it. Pass the switch to the default - // handler if no rule matched. - for(var i = 0; i < rules.length; i++) { - var rule = rules[i]; - if(rule.long == token || rule.short == token) { - if(rule.filter !== undefined) { - arg = tokens.shift(); - if(arg && (!LONG_SWITCH_RE.test(arg) && !SHORT_SWITCH_RE.test(arg))) { - try { - arg = rule.filter(arg, tokens); - } catch(e) { - throw OptError(token + ': ' + e.toString()); - } - } else if(rule.optional_arg) { - if(arg) { - tokens.unshift(arg); - } - } else { - throw OptError('Expected switch argument.'); - } - } - callback = this.on_switches[rule.name]; - if (!callback) callback = this.on_switches['*']; - if(callback) callback.apply(this, [rule.name, arg]); - break; - } - } - if(i == rules.length) this.default_handler.apply(this, [token]); - } else { - // Did not match long or short switch. Parse the token as a - // normal argument. - callback = this.on_args[result.length]; - result.push(token); - if(callback) callback.apply(this, [token]); - } - } - return this._halt ? this.on_halt.apply(this, [tokens]) : result; - }, - - // Returns an Array with all defined option rules - options: function() { - return build_rules(this.filters, this._rules); - }, - - // Add an on_halt callback if argument ´´fn´´ is specified. on_switch handlers can - // call instance.halt to abort the argument parsing. This can be useful when - // displaying help or version information. - halt: function(fn) { - this._halt = fn === undefined - if(fn) this.on_halt = fn; - }, - - // Returns a string representation of this OptionParser instance. - toString: function() { - var builder = [this.banner, '', this.options_title], - shorts = false, longest = 0, rule; - var rules = build_rules(this.filters, this._rules); - for(var i = 0; i < rules.length; i++) { - rule = rules[i]; - // Quick-analyze the options. - if(rule.short) shorts = true; - if(rule.decl.length > longest) longest = rule.decl.length; - } - for(var i = 0; i < rules.length; i++) { - var text = spaces(6); - rule = rules[i]; - if(shorts) { - if(rule.short) text = spaces(2) + rule.short + ', '; - } - text += spaces(rule.decl, longest) + spaces(3); - text += rule.desc; - builder.push(text); - } - return builder.join('\n'); - } -} - -self.VERSION = VERSION; -self.OptionParser = OptionParser; - -})(optparse); diff --git a/src/templates/tasks/new.template.html b/src/templates/tasks/new.template.html index adbcece01..d72675d2e 100755 --- a/src/templates/tasks/new.template.html +++ b/src/templates/tasks/new.template.html @@ -307,8 +307,6 @@

New task

- - + - + + - + + - + +