CyberChef/src/core/operations/Code.js

545 lines
14 KiB
JavaScript
Raw Normal View History

2017-05-02 17:21:04 +02:00
import {camelCase, kebabCase, snakeCase} from "lodash";
import Utils from "../Utils.js";
import vkbeautify from "vkbeautify";
import {DOMParser as dom} from "xmldom";
import xpath from "xpath";
import jpath from "jsonpath";
import prettyPrintOne from "imports-loader?window=>global!exports-loader?prettyPrintOne!google-code-prettify/bin/prettify.min.js";
2016-11-28 11:42:58 +01:00
/**
* Code operations.
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*
* @namespace
*/
const Code = {
2016-12-14 17:39:17 +01:00
2016-11-28 11:42:58 +01:00
/**
* @constant
* @default
*/
LANGUAGES: ["default-code", "default-markup", "bash", "bsh", "c", "cc", "coffee", "cpp", "cs", "csh", "cv", "cxx", "cyc", "htm", "html", "in.tag", "java", "javascript", "js", "json", "m", "mxml", "perl", "pl", "pm", "py", "python", "rb", "rc", "rs", "ruby", "rust", "sh", "uq.val", "xhtml", "xml", "xsl"],
/**
* @constant
* @default
*/
LINE_NUMS: false,
2016-12-14 17:39:17 +01:00
2016-11-28 11:42:58 +01:00
/**
* Syntax highlighter operation.
*
* @param {string} input
* @param {Object[]} args
* @returns {html}
*/
runSyntaxHighlight: function(input, args) {
2017-04-13 19:08:50 +02:00
let language = args[0],
lineNums = args[1];
return "<code class='prettyprint'>" + prettyPrintOne(Utils.escapeHtml(input), language, lineNums) + "</code>";
2016-11-28 11:42:58 +01:00
},
2016-12-14 17:39:17 +01:00
2016-11-28 11:42:58 +01:00
/**
* @constant
* @default
*/
BEAUTIFY_INDENT: "\\t",
2016-12-14 17:39:17 +01:00
2016-11-28 11:42:58 +01:00
/**
* XML Beautify operation.
*
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
runXmlBeautify: function(input, args) {
2017-04-13 19:08:50 +02:00
const indentStr = args[0];
return vkbeautify.xml(input, indentStr);
2016-11-28 11:42:58 +01:00
},
2016-12-14 17:39:17 +01:00
2016-11-28 11:42:58 +01:00
/**
* JSON Beautify operation.
*
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
runJsonBeautify: function(input, args) {
2017-04-13 19:08:50 +02:00
const indentStr = args[0];
if (!input) return "";
return vkbeautify.json(input, indentStr);
2016-11-28 11:42:58 +01:00
},
2016-12-14 17:39:17 +01:00
2016-11-28 11:42:58 +01:00
/**
* CSS Beautify operation.
*
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
runCssBeautify: function(input, args) {
2017-04-13 19:08:50 +02:00
const indentStr = args[0];
return vkbeautify.css(input, indentStr);
2016-11-28 11:42:58 +01:00
},
2016-12-14 17:39:17 +01:00
2016-11-28 11:42:58 +01:00
/**
* SQL Beautify operation.
*
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
runSqlBeautify: function(input, args) {
2017-04-13 19:08:50 +02:00
const indentStr = args[0];
return vkbeautify.sql(input, indentStr);
2016-11-28 11:42:58 +01:00
},
2016-12-14 17:39:17 +01:00
2016-11-28 11:42:58 +01:00
/**
* @constant
* @default
*/
PRESERVE_COMMENTS: false,
2016-12-14 17:39:17 +01:00
2016-11-28 11:42:58 +01:00
/**
* XML Minify operation.
*
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
runXmlMinify: function(input, args) {
2017-04-13 19:08:50 +02:00
const preserveComments = args[0];
return vkbeautify.xmlmin(input, preserveComments);
2016-11-28 11:42:58 +01:00
},
2016-12-14 17:39:17 +01:00
2016-11-28 11:42:58 +01:00
/**
* JSON Minify operation.
*
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
runJsonMinify: function(input, args) {
if (!input) return "";
return vkbeautify.jsonmin(input);
2016-11-28 11:42:58 +01:00
},
2016-12-14 17:39:17 +01:00
2016-11-28 11:42:58 +01:00
/**
* CSS Minify operation.
*
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
runCssMinify: function(input, args) {
2017-04-13 19:08:50 +02:00
const preserveComments = args[0];
return vkbeautify.cssmin(input, preserveComments);
2016-11-28 11:42:58 +01:00
},
2016-12-14 17:39:17 +01:00
2016-11-28 11:42:58 +01:00
/**
* SQL Minify operation.
*
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
runSqlMinify: function(input, args) {
return vkbeautify.sqlmin(input);
2016-11-28 11:42:58 +01:00
},
2016-12-14 17:39:17 +01:00
2016-11-28 11:42:58 +01:00
/**
* Generic Code Beautify operation.
*
* Yeeeaaah...
*
* I'm not proud of this code, but seriously, try writing a generic lexer and parser that
* correctly generates an AST for multiple different languages. I have tried, and I can tell
* you it's pretty much impossible.
2016-12-14 17:39:17 +01:00
*
2016-11-28 11:42:58 +01:00
* This basically works. That'll have to be good enough. It's not meant to produce working code,
* just slightly more readable code.
2016-12-14 17:39:17 +01:00
*
2016-11-28 11:42:58 +01:00
* Things that don't work:
* - For loop formatting
* - Do-While loop formatting
* - Switch/Case indentation
* - Bit shift operators
*
* @author n1474335 [n1474335@gmail.com]
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
runGenericBeautify: function(input, args) {
2017-04-13 19:08:50 +02:00
let code = input,
2016-11-28 11:42:58 +01:00
t = 0,
preservedTokens = [],
2016-11-28 11:42:58 +01:00
m;
2016-12-14 17:39:17 +01:00
2016-11-28 11:42:58 +01:00
// Remove strings
2017-04-13 19:08:50 +02:00
const sstrings = /'([^'\\]|\\.)*'/g;
2016-12-14 17:39:17 +01:00
while ((m = sstrings.exec(code))) {
code = preserveToken(code, m, t++);
2016-11-28 11:42:58 +01:00
sstrings.lastIndex = m.index;
}
2016-12-14 17:39:17 +01:00
2017-04-13 19:08:50 +02:00
const dstrings = /"([^"\\]|\\.)*"/g;
2016-12-14 17:39:17 +01:00
while ((m = dstrings.exec(code))) {
code = preserveToken(code, m, t++);
2016-11-28 11:42:58 +01:00
dstrings.lastIndex = m.index;
}
2016-12-14 17:39:17 +01:00
2016-11-28 11:42:58 +01:00
// Remove comments
2017-04-13 19:08:50 +02:00
const scomments = /\/\/[^\n\r]*/g;
2016-12-14 17:39:17 +01:00
while ((m = scomments.exec(code))) {
code = preserveToken(code, m, t++);
2016-11-28 11:42:58 +01:00
scomments.lastIndex = m.index;
}
2016-12-14 17:39:17 +01:00
2017-04-13 19:08:50 +02:00
const mcomments = /\/\*[\s\S]*?\*\//gm;
2016-12-14 17:39:17 +01:00
while ((m = mcomments.exec(code))) {
code = preserveToken(code, m, t++);
2016-11-28 11:42:58 +01:00
mcomments.lastIndex = m.index;
}
2016-12-14 17:39:17 +01:00
2017-04-13 19:08:50 +02:00
const hcomments = /(^|\n)#[^\n\r#]+/g;
2016-12-14 17:39:17 +01:00
while ((m = hcomments.exec(code))) {
code = preserveToken(code, m, t++);
2016-11-28 11:42:58 +01:00
hcomments.lastIndex = m.index;
}
2016-12-14 17:39:17 +01:00
2016-11-28 11:42:58 +01:00
// Remove regexes
2017-04-13 19:08:50 +02:00
const regexes = /\/.*?[^\\]\/[gim]{0,3}/gi;
2016-12-14 17:39:17 +01:00
while ((m = regexes.exec(code))) {
code = preserveToken(code, m, t++);
2016-11-28 11:42:58 +01:00
regexes.lastIndex = m.index;
}
2016-12-14 17:39:17 +01:00
code = code
2017-07-24 15:49:16 +02:00
// Create newlines after ;
.replace(/;/g, ";\n")
// Create newlines after { and around }
.replace(/{/g, "{\n")
.replace(/}/g, "\n}\n")
// Remove carriage returns
.replace(/\r/g, "")
// Remove all indentation
.replace(/^\s+/g, "")
.replace(/\n\s+/g, "\n")
// Remove trailing spaces
.replace(/\s*$/g, "")
.replace(/\n{/g, "{");
2016-12-14 17:39:17 +01:00
2016-11-28 11:42:58 +01:00
// Indent
2017-04-13 19:08:50 +02:00
let i = 0,
2017-04-13 19:31:26 +02:00
level = 0,
indent;
2016-11-28 11:42:58 +01:00
while (i < code.length) {
2017-02-09 16:09:33 +01:00
switch (code[i]) {
2016-11-28 11:42:58 +01:00
case "{":
level++;
break;
case "\n":
if (i+1 >= code.length) break;
2016-12-14 17:39:17 +01:00
if (code[i+1] === "}") level--;
2017-04-13 19:31:26 +02:00
indent = (level >= 0) ? Array(level*4+1).join(" ") : "";
2016-12-14 17:39:17 +01:00
2016-11-28 11:42:58 +01:00
code = code.substring(0, i+1) + indent + code.substring(i+1);
if (level > 0) i += level*4;
break;
}
i++;
}
code = code
2017-07-24 15:49:16 +02:00
// Add strategic spaces
.replace(/\s*([!<>=+-/*]?)=\s*/g, " $1= ")
.replace(/\s*<([=]?)\s*/g, " <$1 ")
.replace(/\s*>([=]?)\s*/g, " >$1 ")
.replace(/([^+])\+([^+=])/g, "$1 + $2")
.replace(/([^-])-([^-=])/g, "$1 - $2")
.replace(/([^*])\*([^*=])/g, "$1 * $2")
.replace(/([^/])\/([^/=])/g, "$1 / $2")
.replace(/\s*,\s*/g, ", ")
.replace(/\s*{/g, " {")
.replace(/}\n/g, "}\n\n")
// Hacky horribleness
.replace(/(if|for|while|with|elif|elseif)\s*\(([^\n]*)\)\s*\n([^{])/gim, "$1 ($2)\n $3")
.replace(/(if|for|while|with|elif|elseif)\s*\(([^\n]*)\)([^{])/gim, "$1 ($2) $3")
.replace(/else\s*\n([^{])/gim, "else\n $1")
.replace(/else\s+([^{])/gim, "else $1")
// Remove strategic spaces
.replace(/\s+;/g, ";")
.replace(/\{\s+\}/g, "{}")
.replace(/\[\s+\]/g, "[]")
.replace(/}\s*(else|catch|except|finally|elif|elseif|else if)/gi, "} $1");
2016-12-14 17:39:17 +01:00
2016-11-28 11:42:58 +01:00
// Replace preserved tokens
2017-04-13 19:08:50 +02:00
const ptokens = /###preservedToken(\d+)###/g;
2016-12-14 17:39:17 +01:00
while ((m = ptokens.exec(code))) {
2017-04-13 19:08:50 +02:00
const ti = parseInt(m[1], 10);
code = code.substring(0, m.index) + preservedTokens[ti] + code.substring(m.index + m[0].length);
2016-11-28 11:42:58 +01:00
ptokens.lastIndex = m.index;
}
return code;
2016-12-14 17:39:17 +01:00
/**
* Replaces a matched token with a placeholder value.
*/
function preserveToken(str, match, t) {
preservedTokens[t] = match[0];
2016-11-28 11:42:58 +01:00
return str.substring(0, match.index) +
"###preservedToken" + t + "###" +
2016-11-28 11:42:58 +01:00
str.substring(match.index + match[0].length);
}
},
/**
* @constant
* @default
*/
XPATH_INITIAL: "",
/**
* @constant
* @default
*/
XPATH_DELIMITER: "\\n",
/**
* XPath expression operation.
*
* @author Mikescher (https://github.com/Mikescher | https://mikescher.com)
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
2017-07-24 16:55:48 +02:00
runXpath: function(input, args) {
2017-04-13 19:08:50 +02:00
let query = args[0],
delimiter = args[1];
2017-04-13 19:08:50 +02:00
let doc;
try {
doc = new dom().parseFromString(input);
} catch (err) {
return "Invalid input XML.";
}
2017-04-13 19:08:50 +02:00
let nodes;
try {
nodes = xpath.select(query, doc);
} catch (err) {
return "Invalid XPath. Details:\n" + err.message;
}
2017-04-13 19:08:50 +02:00
const nodeToString = function(node) {
return node.toString();
};
return nodes.map(nodeToString).join(delimiter);
},
2017-07-25 17:27:59 +02:00
/**
* @constant
* @default
*/
JPATH_INITIAL: "",
/**
* @constant
* @default
*/
JPATH_DELIMITER: "\\n",
/**
* JPath expression operation.
2017-07-25 17:27:59 +02:00
*
* @author Matt C (matt@artemisbot.uk)
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
runJpath: function(input, args) {
let query = args[0],
2017-08-03 15:50:16 +02:00
delimiter = args[1],
results,
obj;
2017-07-25 17:27:59 +02:00
try {
obj = JSON.parse(input);
} catch (err) {
return "Invalid input JSON: " + err.message;
2017-07-25 17:27:59 +02:00
}
2017-08-03 15:50:16 +02:00
try {
results = jpath.query(obj, query);
} catch (err) {
return "Invalid JPath expression: " + err.message;
2017-08-03 15:50:16 +02:00
}
2017-07-25 17:27:59 +02:00
return results.map(result => JSON.stringify(result)).join(delimiter);
},
/**
* @constant
* @default
*/
CSS_SELECTOR_INITIAL: "",
/**
* @constant
* @default
*/
CSS_QUERY_DELIMITER: "\\n",
/**
* CSS selector operation.
*
* @author Mikescher (https://github.com/Mikescher | https://mikescher.com)
* @author n1474335 [n1474335@gmail.com]
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
runCSSQuery: function(input, args) {
2017-04-13 19:08:50 +02:00
let query = args[0],
delimiter = args[1],
parser = new DOMParser(),
html,
result;
if (!query.length || !input.length) {
return "";
}
try {
html = parser.parseFromString(input, "text/html");
} catch (err) {
return "Invalid input HTML.";
}
try {
result = html.querySelectorAll(query);
} catch (err) {
return "Invalid CSS Selector. Details:\n" + err.message;
}
2017-04-13 19:08:50 +02:00
const nodeToString = function(node) {
switch (node.nodeType) {
case Node.ELEMENT_NODE: return node.outerHTML;
case Node.ATTRIBUTE_NODE: return node.value;
case Node.COMMENT_NODE: return node.data;
case Node.TEXT_NODE: return node.wholeText;
case Node.DOCUMENT_NODE: return node.outerHTML;
default: throw new Error("Unknown Node Type: " + node.nodeType);
}
};
return Array.apply(null, Array(result.length))
.map(function(_, i) {
return result[i];
})
.map(nodeToString)
.join(delimiter);
},
2017-05-02 17:21:04 +02:00
/**
* This tries to rename variable names in a code snippet according to a function.
*
* @param {string} input
* @param {function} replacer - this function will be fed the token which should be renamed.
* @returns {string}
*/
_replaceVariableNames(input, replacer) {
2017-05-03 00:06:28 +02:00
const tokenRegex = /\\"|"(?:\\"|[^"])*"|(\b[a-z0-9\-_]+\b)/ig;
2017-05-02 17:21:04 +02:00
return input.replace(tokenRegex, (...args) => {
let match = args[0],
quotes = args[1];
if (!quotes) {
return match;
} else {
return replacer(match);
}
});
},
/**
* Converts to snake_case.
*
* @param {string} input
* @param {Object[]} args
* @returns {string}
*
*/
runToSnakeCase(input, args) {
2017-05-03 00:06:28 +02:00
const smart = args[0];
2017-05-02 17:21:04 +02:00
if (smart) {
return Code._replaceVariableNames(input, snakeCase);
} else {
return snakeCase(input);
}
},
/**
* Converts to camelCase.
*
* @param {string} input
* @param {Object[]} args
* @returns {string}
*
*/
runToCamelCase(input, args) {
2017-05-03 00:06:28 +02:00
const smart = args[0];
2017-05-02 17:21:04 +02:00
if (smart) {
return Code._replaceVariableNames(input, camelCase);
} else {
return camelCase(input);
}
},
/**
* Converts to kebab-case.
*
* @param {string} input
* @param {Object[]} args
* @returns {string}
*
*/
runToKebabCase(input, args) {
2017-05-03 00:06:28 +02:00
const smart = args[0];
2017-05-02 17:21:04 +02:00
if (smart) {
return Code._replaceVariableNames(input, kebabCase);
} else {
return kebabCase(input);
}
},
2016-11-28 11:42:58 +01:00
};
export default Code;