diff --git a/components/component-args.js b/components/component-args.js new file mode 100644 index 0000000..2fd2e55 --- /dev/null +++ b/components/component-args.js @@ -0,0 +1,82 @@ +var parseARGS = (kvKeys) => { + return exports.parse($ARGS, kvKeys); +}; + +var parse = (args, kvKeys) => { + var newArgs = []; + var keyValueMap = {}; + var keyFlagMap = {}; + + keyValueMap.vals = () => { + var ret = []; + for (var i = 0; i < arguments.length; i++) { + var v = keyValueMap[arguments[i]]; + if (v) { v.forEach((x) => { ret.push(x) } ); } + } + return ret; + }; + keyValueMap.val = () => { + for (var i = 0; i < arguments.length; i++) { + var v = keyValueMap[arguments[i]] && keyValueMap[arguments[i]][0]; + if (v) { return v; } + } + return null; + }; + keyFlagMap.flg = () => { + for (var i = 0; i < arguments.length; i++) { + var v = keyFlagMap[arguments[i]]; + if (v) { return true; } + } + return false; + }; + + var kvKeyMap = {}; (kvKeys || []).forEach((k) => { kvKeyMap[k] = 1; }); + for (var i = 0; i < args.length; i++) { + var arg = args[i]; + var key = null; + if (arg.startsWith('--')) { + key = arg.substring(2); + } else if (arg.startsWith('-')) { + key = arg.substring(1); + if (key.length > 1) { + for (var x = 0; x < key.length; x++) { + var k = key.substring(x, 1); + kvKeyMap[k] = 1; + keyFlagMap[k] = 1; + } + continue; + } + } + if ((key != null) && ((i + 1) < args.length) || kvKeyMap[key]) { + if (kvKeyMap[key]) { + keyFlagMap[key] = 1; + } else { + keyValueMap[key] = keyValueMap[key] || []; + keyValueMap[key].push(args[++i]); + } + } else { + newArgs.push(arg); + } + } + return { + args: newArgs, + flags: keyFlagMap, + values: keyValueMap + }; +}; + +var parseDefARGs = (kvKeys) => { + var args = parseARGS(kvKeys); + var newArgs = args.args; + newArgs.flg = args.flags.flg; + newArgs.val = args.values.val; + newArgs.vals = args.values.vals; + return newArgs; +}; + + +if (typeof exports == 'object') { + exports.parse = parse; + exports.parseARGS = parseARGS; + exports.parseDefARGs = parseDefARGs; +} \ No newline at end of file diff --git a/components/component-bytes.js b/components/component-bytes.js new file mode 100644 index 0000000..527bdb3 --- /dev/null +++ b/components/component-bytes.js @@ -0,0 +1,17 @@ + +var UNITS = ['bytes', 'KB', 'MB', 'GB', 'TB']; +var UNITS_BYTES = [0, Math.pow(1024, 1), Math.pow(1024, 2), Math.pow(1024, 3), Math.pow(1024, 4)]; + +exports.showBytes = (bytes) => { + for (var i = 0; i < (UNITS.length - 1); i++) { + if ((bytes >= UNITS_BYTES[i]) && bytes < UNITS_BYTES[i + 1]) { + if (i == 0) { + return bytes + ((bytes <= 1) ? ' byte' : ' bytes'); + } else { + return (parseInt(bytes * 100 / UNITS_BYTES[i]) / 100) + ' ' + UNITS[i]; + } + } + } + + return (parseInt(bytes * 100 / UNITS_BYTES[i]) / 100) + ' ' + UNITS[i]; // i == UNITS.length - 1 +}; diff --git a/components/component-colorprint-ex.js b/components/component-colorprint-ex.js new file mode 100644 index 0000000..475e893 --- /dev/null +++ b/components/component-colorprint-ex.js @@ -0,0 +1,41 @@ + +var HEADER = '\033[95m'; +var OKBLUE = '\033[94m'; +var OKGREEN = '\033[92m'; +var WARNING = '\033[93m'; +var FAIL = '\033[91m'; +var BOLD = '\033[1m'; +var UNDERLINE = '\033[4m'; +var ENDC = '\033[0m'; + +var colorMapping = { + "header": HEADER, + "okblue": OKBLUE, + "okgreen": OKGREEN, + "warning": WARNING, + "fail": FAIL, + "bold": BOLD, + "underline": UNDERLINE, + "none": null // nothing special +}; + +var colorPrint = exports; + +for (var k in colorMapping) { + colorPrint[k] = {}; + colorPrint[k].render = ((k) => { + return (x) => { + return colorMapping[k] ? (colorMapping[k] + x + ENDC) : x; + }; + })(k); + colorPrint[k].print = ((k) => { + return (x) => { + print(colorPrint[k].render(x)); + }; + })(k); + colorPrint[k].println = ((k) => { + return (x) => { + println(colorPrint[k].render(x)); + }; + })(k); +} diff --git a/components/component-colorprint.js b/components/component-colorprint.js new file mode 100644 index 0000000..b5b8b9e --- /dev/null +++ b/components/component-colorprint.js @@ -0,0 +1,3 @@ +(() => { + colorPrint = require('component-colorprint-ex.js'); +})(); \ No newline at end of file diff --git a/components/component-console.js b/components/component-console.js new file mode 100644 index 0000000..5218e49 --- /dev/null +++ b/components/component-console.js @@ -0,0 +1,61 @@ +var System = java.lang.System; + +var readLineEx = (prompt) => { + var ret = null; + while ((ret == null) || $STR(ret).trim() == '') { + ret = readLine(prompt); + } + return ret; +}; + +var readLine = (prompt) => { + print(prompt); + return System.console().readLine(); +}; + +var readYNEx = (prompt) => { + var ret = null; + while (ret == null) { + ret = readYN(prompt); + } + return ret; +}; + +var readYN = (prompt) => { + print(prompt); + var read = readLine(); + if ($STR(read).toLowerCase() == 'y') { + return true; + } + if ($STR(read).toLowerCase() == 'n') { + return false; + } + return null; +}; + +var readListToMark = (prompt, mark) => { + if (!(mark)) { throw 'Mark cannot be null or empty.'; } + print(prompt); + var list = []; + while (true) { + var ln = System.console().readLine(); + if ($STR(ln) == $STR(mark)) { + return list; + } + list.push(ln); + } +}; + +var readListToCustomMark = (prompt) => { + var mark = readLineEx(prompt); + return readListToMark('', mark); +}; + +if (typeof exports == 'object') { + exports.readYN = readYN; + exports.readYNEx = readYNEx; + exports.readLine = readLine; + exports.readLineEx = readLineEx; + exports.readListToMark = readListToMark; + exports.readListToCustomMark = readListToCustomMark; +} diff --git a/components/component-coordtransform.js b/components/component-coordtransform.js new file mode 100644 index 0000000..914ce1e --- /dev/null +++ b/components/component-coordtransform.js @@ -0,0 +1,137 @@ +// https://github.com/wandergis/coordtransform/blob/master/index.js +(() => { + /** + * Created by Wandergis on 2015/7/8. + * 提供了百度坐标(BD09)、国测局坐标(火星坐标,GCJ02)、和WGS84坐标系之间的转换 + */ + //定义一些常量 + var x_PI = 3.14159265358979324 * 3000.0 / 180.0; + var PI = 3.1415926535897932384626; + var a = 6378245.0; + var ee = 0.00669342162296594323; + /** + * 百度坐标系 (BD-09) 与 火星坐标系 (GCJ-02)的转换 + * 即 百度 转 谷歌、高德 + * @param bd_lon + * @param bd_lat + * @returns {*[]} + */ + var bd09togcj02 = function bd09togcj02(bd_lon, bd_lat) { + var bd_lon = +bd_lon; + var bd_lat = +bd_lat; + var x = bd_lon - 0.0065; + var y = bd_lat - 0.006; + var z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * x_PI); + var theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * x_PI); + var gg_lng = z * Math.cos(theta); + var gg_lat = z * Math.sin(theta); + return [gg_lng, gg_lat] + }; + + /** + * 火星坐标系 (GCJ-02) 与百度坐标系 (BD-09) 的转换 + * 即谷歌、高德 转 百度 + * @param lng + * @param lat + * @returns {*[]} + */ + var gcj02tobd09 = function gcj02tobd09(lng, lat) { + var lat = +lat; + var lng = +lng; + var z = Math.sqrt(lng * lng + lat * lat) + 0.00002 * Math.sin(lat * x_PI); + var theta = Math.atan2(lat, lng) + 0.000003 * Math.cos(lng * x_PI); + var bd_lng = z * Math.cos(theta) + 0.0065; + var bd_lat = z * Math.sin(theta) + 0.006; + return [bd_lng, bd_lat] + }; + + /** + * WGS84转GCj02 + * @param lng + * @param lat + * @returns {*[]} + */ + var wgs84togcj02 = function wgs84togcj02(lng, lat) { + var lat = +lat; + var lng = +lng; + if (out_of_china(lng, lat)) { + return [lng, lat] + } else { + var dlat = transformlat(lng - 105.0, lat - 35.0); + var dlng = transformlng(lng - 105.0, lat - 35.0); + var radlat = lat / 180.0 * PI; + var magic = Math.sin(radlat); + magic = 1 - ee * magic * magic; + var sqrtmagic = Math.sqrt(magic); + dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI); + dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * PI); + var mglat = lat + dlat; + var mglng = lng + dlng; + return [mglng, mglat] + } + }; + + /** + * GCJ02 转换为 WGS84 + * @param lng + * @param lat + * @returns {*[]} + */ + var gcj02towgs84 = function gcj02towgs84(lng, lat) { + var lat = +lat; + var lng = +lng; + if (out_of_china(lng, lat)) { + return [lng, lat] + } else { + var dlat = transformlat(lng - 105.0, lat - 35.0); + var dlng = transformlng(lng - 105.0, lat - 35.0); + var radlat = lat / 180.0 * PI; + var magic = Math.sin(radlat); + magic = 1 - ee * magic * magic; + var sqrtmagic = Math.sqrt(magic); + dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI); + dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * PI); + var mglat = lat + dlat; + var mglng = lng + dlng; + return [lng * 2 - mglng, lat * 2 - mglat] + } + }; + + var transformlat = function transformlat(lng, lat) { + var lat = +lat; + var lng = +lng; + var ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng)); + ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0; + ret += (20.0 * Math.sin(lat * PI) + 40.0 * Math.sin(lat / 3.0 * PI)) * 2.0 / 3.0; + ret += (160.0 * Math.sin(lat / 12.0 * PI) + 320 * Math.sin(lat * PI / 30.0)) * 2.0 / 3.0; + return ret + }; + + var transformlng = function transformlng(lng, lat) { + var lat = +lat; + var lng = +lng; + var ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng)); + ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0; + ret += (20.0 * Math.sin(lng * PI) + 40.0 * Math.sin(lng / 3.0 * PI)) * 2.0 / 3.0; + ret += (150.0 * Math.sin(lng / 12.0 * PI) + 300.0 * Math.sin(lng / 30.0 * PI)) * 2.0 / 3.0; + return ret + }; + + /** + * 判断是否在国内,不在国内则不做偏移 + * @param lng + * @param lat + * @returns {boolean} + */ + var out_of_china = function out_of_china(lng, lat) { + var lat = +lat; + var lng = +lng; + // 纬度3.86~53.55,经度73.66~135.05 + return !(lng > 73.66 && lng < 135.05 && lat > 3.86 && lat < 53.55); + }; + + exports.bd09togcj02 = bd09togcj02; + exports.gcj02tobd09 = gcj02tobd09; + exports.wgs84togcj02 = wgs84togcj02; + exports.gcj02towgs84 = gcj02towgs84; +})(); \ No newline at end of file diff --git a/components/component-cos.js b/components/component-cos.js new file mode 100644 index 0000000..cc3eb2d --- /dev/null +++ b/components/component-cos.js @@ -0,0 +1,68 @@ + +var counter = require('component-counter.js'); + +var requireJARs = () => { + $ONCE('component-cos-requireJARS').run(() => { + requireJAR('cos_api-5.2.2.jar'); + requireJAR('httpcore-4.4.6.jar'); + requireJAR('commons-codec-1.10.jar'); + requireJAR('commons-logging-1.2.jar'); + requireJAR('httpclient-4.5.3.jar'); + requireJAR('httpmime-4.5.2.jar'); + requireJAR('slf4j-api-1.7.21.jar'); + requireJAR('log4j-1.2.17.jar'); + requireJAR('slf4j-log4j12-1.7.21.jar'); + requireJAR('joda-time-2.9.6.jar'); + requireJAR('jackson-core-2.8.5.jar'); + requireJAR('jackson-annotations-2.8.0.jar'); + requireJAR('jackson-databind-2.8.5.jar'); + }); +}; + +var createCounterIS = (file) => { + var CounterInputStream = Packages.me.hatter.tools.commons.io.CounterInputStream; + var uploadFile = (file instanceof java.io.File) ? file : new java.io.File(file); + var uploadIS = new java.io.FileInputStream(uploadFile); + var counterIS = new CounterInputStream(uploadIS, counter.getCounterBar(uploadFile.length())); + return counterIS; +}; + +var putObject = (client, bucket, key, file) => { + var f = $$.file(file); + var objectMetadata = new Packages.com.qcloud.cos.model.ObjectMetadata(); + objectMetadata.setContentLength(f.length()); + var result = client.putObject(bucket, key, createCounterIS(f), objectMetadata); + return result; +}; + +// client -> https://cloud.tencent.com/document/product/436/10199 +// region -> https://cloud.tencent.com/document/product/436/6224 +var newClient = (region, appId, accessKey, secretKey) => { + requireJARs(); + + var COSClient = Packages.com.qcloud.cos.COSClient; + var Region = Packages.com.qcloud.cos.region.Region; + var ClientConfig = Packages.com.qcloud.cos.ClientConfig; + var BasicCOSCredentials = Packages.com.qcloud.cos.auth.BasicCOSCredentials; + var cred = new BasicCOSCredentials(appId, accessKey, secretKey); + var clientConfig = new ClientConfig(new Region(region)); + var client = new COSClient(cred, clientConfig); + return { + runWith: (runFunc) => { + try { + runFunc(client); + } catch (e) { + println('COS Error: ' + e); + } finally { + client.shutdown(); + } + } + }; +}; + +if (typeof exports == 'object') { + exports.requireJARs = requireJARs; + exports.createCounterIS = createCounterIS; + exports.putObject = putObject; + exports.newClient = newClient; +} diff --git a/components/component-counter.js b/components/component-counter.js new file mode 100644 index 0000000..7d1b12c --- /dev/null +++ b/components/component-counter.js @@ -0,0 +1,77 @@ +var bytesjs = require('component-bytes.js'); + +exports.getCounter = (fileLength, dotStep) => { + var total = 0; + var step = dotStep || (1024 * 64); + var percentPrintFlag = {}; + var percentN = (fileLength > (60 * 1024 * 1024)) ? 100 : 10; + return (len) => { + var t = total; + total += len; + var n = parseInt(total / step) - parseInt(t / step); + if (n > 0) { n.times(() => { print('.') }); } + if (fileLength) { + var updatePercentN = parseInt((total * percentN) / fileLength); + if (updatePercentN && (!(percentPrintFlag[updatePercentN]))) { + percentPrintFlag[updatePercentN] = 1; + print('[' + parseInt((total * 100) / fileLength) + '%]'); + } + } + }; +}; + +exports.getCounter2 = (dotStep) => { + var total = 0; + var step = dotStep || (1024 * 64); + var percentPrintFlag = {}; + return (len, fileLength) => { + var t = total; + total += len; + var n = parseInt(total / step) - parseInt(t / step); + if (n > 0) { n.times(() => { print('.') }); } + if (fileLength > 0) { + var updatePercent10 = parseInt((total * 10) / fileLength); + if (updatePercent10 && (!(percentPrintFlag[updatePercent10]))) { + percentPrintFlag[updatePercent10] = 1; + print('[' + parseInt((total * 100) / fileLength) + '%]'); + } + } + }; +}; + +exports.getCounter2Bar = (dotStep) => { + Packages.me.hatter.tools.jssp.main.StandaloneMainInteractive.initJLine(); // require jline + var total = 0; + var start = $$.date().millis(); + var step = dotStep || (1024 * 64); + var percentPrintFlag = {}; + return (len, fileLength) => { + var t = total; total += len; + var diff = $$.date().millis() - start; + var download = bytesjs.showBytes(total); + var speed = 'unknown'; + if (diff > 0) { + speed = bytesjs.showBytes(parseInt(total * 1000 / diff)) + '/s'; + } + if (fileLength > 0) { + var width = Packages.jline.TerminalFactory.get().getWidth(); + var per = ' ' + $$.num(total).mul(100).divr(fileLength, 2, null) + '% [' + download + ' - ' + speed + ']'; + var w = $$.num(total).divr((fileLength), 10, null).mul(width - 2 - per.length).floor().aint(); + reprint(repeat('>', w) + per); + } else { + reprint('Downloaded: ' + download + ', speed: ' + speed); + } + }; +}; + +exports.getCounterBar = (fileLength) => { + Packages.me.hatter.tools.jssp.main.StandaloneMainInteractive.initJLine(); // require jline + var total = 0; + return (len) => { + var width = Packages.jline.TerminalFactory.get().getWidth(); + total += len; + var per = ' ' + $$.num(total).mul(100).divr(fileLength, 2, null) + '%'; + var w = $$.num(total).divr((fileLength), 10, null).mul(width - 1 - per.length).floor().aint(); + print('\r' + repeat('>', w) + per + '\033[K'); + }; +}; diff --git a/components/component-dataaccessentity.js b/components/component-dataaccessentity.js new file mode 100644 index 0000000..ef90023 --- /dev/null +++ b/components/component-dataaccessentity.js @@ -0,0 +1,26 @@ +requireJAR('dataaccess-1.0.jar'); + +var InMemoryCompiler = Packages.me.hatter.tools.commons.classloader.compile.InMemoryCompiler; +var EntityJavaSourceCodeGenerateTool = Packages.me.hatter.tools.dataaccess.entity.EntityJavaSourceCodeGenerateTool; + +var systemInMemoryCompiler = InMemoryCompiler.systemInstance().updateClassPath(); +var allCompiledEntityClassMap = {}; + +var generateClass = (json) => { + var sourceCode = EntityJavaSourceCodeGenerateTool.instance().config(json).generate(); + var clazz = systemInMemoryCompiler.compile(sourceCode); + return clazz; +}; + +var getClass = (json) => { + var jsonSha256Hex = $$.digests().sha256().digest(json.getBytes('UTF-8')).asHex(); + if (allCompiledEntityClassMap[jsonSha256Hex]) { + return allCompiledEntityClassMap[jsonSha256Hex]; + } + var clazz = generateClass(json); + allCompiledEntityClassMap[jsonSha256Hex] = clazz; + return clazz; +}; + +exports.getClass = getClass; +exports.generateClass = generateClass; diff --git a/components/component-diffutil-ex.js b/components/component-diffutil-ex.js new file mode 100644 index 0000000..078a645 --- /dev/null +++ b/components/component-diffutil-ex.js @@ -0,0 +1,38 @@ +requireJAR('diffutils-1.3.0.jar'); + +var DiffUtils = Packages.difflib.DiffUtils; +var DeleteDelta = Packages.difflib.DeleteDelta; +var ChangeDelta = Packages.difflib.ChangeDelta; +var InsertDelta = Packages.difflib.InsertDelta; + +exports.diff2List = (left, right) => { + var patch = DiffUtils.diff(left, right); + var result = []; + $EACH(patch.getDeltas(), (delta) => { + if (delta instanceof DeleteDelta) { + result.push("@@deleted @" + delta.getOriginal().getPosition() + + " #" + delta.getOriginal().getLines().size()); + $EACH(delta.getOriginal().getLines(), (line) => { + result.push("-- " + line); + }) + } + if (delta instanceof ChangeDelta) { + result.push("@@modified @" + delta.getOriginal().getPosition() + + " #" + delta.getOriginal().getLines().size() + '-' + delta.getRevised().getLines().size()); + $EACH(delta.getOriginal().getLines(), (line) => { + result.push("-- " + line); + }); + $EACH(delta.getRevised().getLines(), (line) => { + result.push("++ " + line); + }); + } + if (delta instanceof InsertDelta) { + result.push("@@inserted @" + delta.getOriginal().getPosition() + + " #" + delta.getRevised().getLines().size()); + $EACH(delta.getRevised().getLines(), (line) => { + result.push("++ " + line); + }); + } + }); + return result; +}; diff --git a/components/component-diffutil.js b/components/component-diffutil.js new file mode 100644 index 0000000..57bf913 --- /dev/null +++ b/components/component-diffutil.js @@ -0,0 +1,9 @@ +requireJAR('diffutils-1.3.0.jar'); + +(() => { + var diff = require('component-diffutil-ex.js'); + + diff2List = (left, right) => { + return diff.diff2List(left, right); + }; +})(); diff --git a/components/component-dingtalkrobot-ex.js b/components/component-dingtalkrobot-ex.js new file mode 100644 index 0000000..b7a76b7 --- /dev/null +++ b/components/component-dingtalkrobot-ex.js @@ -0,0 +1,24 @@ + +exports.sendText = (token, message) => { + var dingTalkRobotURL = 'https://oapi.dingtalk.com/robot/send?access_token=' + token; + var postResult = $$.httpRequest() + .url(dingTalkRobotURL) + .addHeader('Content-Type', 'application/json') + .post(JSON.stringify({ + "msgtype": "text", + "text": { + "content": $STR(message) + } + })); + return postResult; +}; + +exports.sendTextDefault = (message) => { + var tokenFile = $$.file($$.prop('user.home'), '.component-dingtalkrobot.js.token'); + if (!tokenFile.exists()) { + println('DingTalk token file not exists: ' + tokenFile); + return; + } + var token = $$.rFile(tokenFile).string().trim(); + sendDingTalkRobotTextMessage(token, message); +}; diff --git a/components/component-dingtalkrobot.js b/components/component-dingtalkrobot.js new file mode 100644 index 0000000..1819f2b --- /dev/null +++ b/components/component-dingtalkrobot.js @@ -0,0 +1,11 @@ +(() => { + var dingtalkrobot = require('component-dingtalkrobot-ex.js'); + + sendDingTalkRobotTextMessage = (token, message) => { + return dingtalkrobot.sendText(token, message); + }; + + sendDingTalkRobotTextMessageDefault = (message) => { + return dingtalkrobot.sendTextDefault(message); + }; +})(); \ No newline at end of file diff --git a/components/component-filechooser-ex.js b/components/component-filechooser-ex.js new file mode 100644 index 0000000..57c0718 --- /dev/null +++ b/components/component-filechooser-ex.js @@ -0,0 +1,26 @@ + +var Proxy = java.lang.reflect.Proxy; +var Toolkit = java.awt.Toolkit; +var FileFilter = javax.swing.filechooser.FileFilter; +var JFileChooser = javax.swing.JFileChooser; + +exports.showDialog = (filter, filterName) => { + var fileFilter = new FileFilter({ + "getDescription": () => { + return filterName || 'Default Filter'; + }, + "accept": (f) => { + return filter(f); + } + }); + var chooser = new JFileChooser(); + if (filter != null) { + chooser.setFileFilter(fileFilter); + } + var ret = chooser.showOpenDialog(null); + + if (ret == JFileChooser.APPROVE_OPTION) { + return chooser.getSelectedFile(); + } + return null; +}; diff --git a/components/component-filechooser.js b/components/component-filechooser.js new file mode 100644 index 0000000..13f9619 --- /dev/null +++ b/components/component-filechooser.js @@ -0,0 +1,7 @@ +(() => { + var filechooser = require('component-filechooser-ex.js'); + + showFileOpenDialog = (filter, filterName) => { + return filechooser.showDialog(filter, filterName); + }; +})(); diff --git a/components/component-filesystem.js b/components/component-filesystem.js new file mode 100644 index 0000000..8c1cac1 --- /dev/null +++ b/components/component-filesystem.js @@ -0,0 +1,14 @@ +var Files = java.nio.file.Files; +var PosixFilePermission = java.nio.file.attribute.PosixFilePermission; + +var chmodAddExec = (file) => { + file = $$.file(file); + var perms = $$.set(); + perms.addAll(Files.getPosixFilePermissions(file.toPath())); + perms.add(PosixFilePermission.OWNER_EXECUTE); + Files.setPosixFilePermissions(file.toPath(), perms); +}; + +if (typeof exports == 'object') { + exports.chmodAddExec = chmodAddExec; +} diff --git a/components/component-geohash.js b/components/component-geohash.js new file mode 100644 index 0000000..69dd573 --- /dev/null +++ b/components/component-geohash.js @@ -0,0 +1,243 @@ +// https://github.com/chrisveness/latlon-geohash/blob/master/latlon-geohash.js +(() => { + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + /* Geohash encoding/decoding and associated functions (c) Chris Veness 2014-2016 / MIT Licence */ + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + 'use strict'; + + + /** + * Geohash encode, decode, bounds, neighbours. + * + * @namespace + */ + var Geohash = {}; + + /* (Geohash-specific) Base32 map */ + Geohash.base32 = '0123456789bcdefghjkmnpqrstuvwxyz'; + + /** + * Encodes latitude/longitude to geohash, either to specified precision or to automatically + * evaluated precision. + * + * @param {number} lat - Latitude in degrees. + * @param {number} lon - Longitude in degrees. + * @param {number} [precision] - Number of characters in resulting geohash. + * @returns {string} Geohash of supplied latitude/longitude. + * @throws Invalid geohash. + * + * @example + * var geohash = Geohash.encode(52.205, 0.119, 7); // geohash: 'u120fxw' + */ + exports.encode = Geohash.encode = function (lat, lon, precision) { + // infer precision? + if (typeof precision == 'undefined') { + // refine geohash until it matches precision of supplied lat/lon + for (var p = 1; p <= 12; p++) { + var hash = Geohash.encode(lat, lon, p); + var posn = Geohash.decode(hash); + if (posn.lat == lat && posn.lon == lon) return hash; + } + precision = 12; // set to maximum + } + + lat = Number(lat); + lon = Number(lon); + precision = Number(precision); + + if (isNaN(lat) || isNaN(lon) || isNaN(precision)) throw new Error('Invalid geohash'); + + var idx = 0; // index into base32 map + var bit = 0; // each char holds 5 bits + var evenBit = true; + var geohash = ''; + + var latMin = -90, latMax = 90; + var lonMin = -180, lonMax = 180; + + while (geohash.length < precision) { + if (evenBit) { + // bisect E-W longitude + var lonMid = (lonMin + lonMax) / 2; + if (lon >= lonMid) { + idx = idx * 2 + 1; + lonMin = lonMid; + } else { + idx = idx * 2; + lonMax = lonMid; + } + } else { + // bisect N-S latitude + var latMid = (latMin + latMax) / 2; + if (lat >= latMid) { + idx = idx * 2 + 1; + latMin = latMid; + } else { + idx = idx * 2; + latMax = latMid; + } + } + evenBit = !evenBit; + + if (++bit == 5) { + // 5 bits gives us a character: append it and start over + geohash += Geohash.base32.charAt(idx); + bit = 0; + idx = 0; + } + } + + return geohash; + }; + + + /** + * Decode geohash to latitude/longitude (location is approximate centre of geohash cell, + * to reasonable precision). + * + * @param {string} geohash - Geohash string to be converted to latitude/longitude. + * @returns {{lat:number, lon:number}} (Center of) geohashed location. + * @throws Invalid geohash. + * + * @example + * var latlon = Geohash.decode('u120fxw'); // latlon: { lat: 52.205, lon: 0.1188 } + */ + exports.decode = Geohash.decode = function (geohash) { + + var bounds = Geohash.bounds(geohash); // <-- the hard work + // now just determine the centre of the cell... + + var latMin = bounds.sw.lat, lonMin = bounds.sw.lon; + var latMax = bounds.ne.lat, lonMax = bounds.ne.lon; + + // cell centre + var lat = (latMin + latMax) / 2; + var lon = (lonMin + lonMax) / 2; + + // round to close to centre without excessive precision: ⌊2-log10(Δ°)⌋ decimal places + lat = lat.toFixed(Math.floor(2 - Math.log(latMax - latMin) / Math.LN10)); + lon = lon.toFixed(Math.floor(2 - Math.log(lonMax - lonMin) / Math.LN10)); + + return { lat: Number(lat), lon: Number(lon) }; + }; + + + /** + * Returns SW/NE latitude/longitude bounds of specified geohash. + * + * @param {string} geohash - Cell that bounds are required of. + * @returns {{sw: {lat: number, lon: number}, ne: {lat: number, lon: number}}} + * @throws Invalid geohash. + */ + exports.bounds = Geohash.bounds = function (geohash) { + if (geohash.length === 0) throw new Error('Invalid geohash'); + + geohash = geohash.toLowerCase(); + + var evenBit = true; + var latMin = -90, latMax = 90; + var lonMin = -180, lonMax = 180; + + for (var i = 0; i < geohash.length; i++) { + var chr = geohash.charAt(i); + var idx = Geohash.base32.indexOf(chr); + if (idx == -1) throw new Error('Invalid geohash'); + + for (var n = 4; n >= 0; n--) { + var bitN = idx >> n & 1; + if (evenBit) { + // longitude + var lonMid = (lonMin + lonMax) / 2; + if (bitN == 1) { + lonMin = lonMid; + } else { + lonMax = lonMid; + } + } else { + // latitude + var latMid = (latMin + latMax) / 2; + if (bitN == 1) { + latMin = latMid; + } else { + latMax = latMid; + } + } + evenBit = !evenBit; + } + } + + var bounds = { + sw: { lat: latMin, lon: lonMin }, + ne: { lat: latMax, lon: lonMax }, + }; + + return bounds; + }; + + + /** + * Determines adjacent cell in given direction. + * + * @param geohash - Cell to which adjacent cell is required. + * @param direction - Direction from geohash (N/S/E/W). + * @returns {string} Geocode of adjacent cell. + * @throws Invalid geohash. + */ + exports.adjacent = Geohash.adjacent = function (geohash, direction) { + // based on github.com/davetroy/geohash-js + + geohash = geohash.toLowerCase(); + direction = direction.toLowerCase(); + + if (geohash.length === 0) throw new Error('Invalid geohash'); + if ('nsew'.indexOf(direction) == -1) throw new Error('Invalid direction'); + + var neighbour = { + n: ['p0r21436x8zb9dcf5h7kjnmqesgutwvy', 'bc01fg45238967deuvhjyznpkmstqrwx'], + s: ['14365h7k9dcfesgujnmqp0r2twvyx8zb', '238967debc01fg45kmstqrwxuvhjyznp'], + e: ['bc01fg45238967deuvhjyznpkmstqrwx', 'p0r21436x8zb9dcf5h7kjnmqesgutwvy'], + w: ['238967debc01fg45kmstqrwxuvhjyznp', '14365h7k9dcfesgujnmqp0r2twvyx8zb'], + }; + var border = { + n: ['prxz', 'bcfguvyz'], + s: ['028b', '0145hjnp'], + e: ['bcfguvyz', 'prxz'], + w: ['0145hjnp', '028b'], + }; + + var lastCh = geohash.slice(-1); // last character of hash + var parent = geohash.slice(0, -1); // hash without last character + + var type = geohash.length % 2; + + // check for edge-cases which don't share common prefix + if (border[direction][type].indexOf(lastCh) != -1 && parent !== '') { + parent = Geohash.adjacent(parent, direction); + } + + // append letter for direction to parent + return parent + Geohash.base32.charAt(neighbour[direction][type].indexOf(lastCh)); + }; + + + /** + * Returns all 8 adjacent cells to specified geohash. + * + * @param {string} geohash - Geohash neighbours are required of. + * @returns {{n,ne,e,se,s,sw,w,nw: string}} + * @throws Invalid geohash. + */ + exports.neighbours = Geohash.neighbours = function (geohash) { + return { + 'n': Geohash.adjacent(geohash, 'n'), + 'ne': Geohash.adjacent(Geohash.adjacent(geohash, 'n'), 'e'), + 'e': Geohash.adjacent(geohash, 'e'), + 'se': Geohash.adjacent(Geohash.adjacent(geohash, 's'), 'e'), + 's': Geohash.adjacent(geohash, 's'), + 'sw': Geohash.adjacent(Geohash.adjacent(geohash, 's'), 'w'), + 'w': Geohash.adjacent(geohash, 'w'), + 'nw': Geohash.adjacent(Geohash.adjacent(geohash, 'n'), 'w'), + }; + }; +})(); \ No newline at end of file diff --git a/components/component-git.js b/components/component-git.js new file mode 100644 index 0000000..7cdec7b --- /dev/null +++ b/components/component-git.js @@ -0,0 +1,9 @@ + +exports.isGitUptodate = () => { + var r = $STR($$.shell().commands('git', 'fetch', '--dry-run').mergeError().start()[0]); + if (r.trim().length > 0) { + return false; + } + return true; +}; + diff --git a/components/component-gpg-ex.js b/components/component-gpg-ex.js new file mode 100644 index 0000000..1b3889b --- /dev/null +++ b/components/component-gpg-ex.js @@ -0,0 +1,32 @@ + +exports.encrypt = (keyId, srcFile, destFile) => { + $$.shell().commands('sh', '-c', 'gpg -r ' + keyId + ' -e -o "' + destFile + '" ' + srcFile).start(); +}; + +exports.encryptArmor = (keyId, srcFile, destFile) => { + $$.shell().commands('sh', '-c', 'gpg -r ' + keyId + ' -e -a --no-comment --comment "https://hatter.in/key" -o "' + destFile + '" ' + srcFile).start(); +}; + +exports.decryptArmor = (gpgArmor) => { + var gpgArmorB64 = __.bytes.from(gpgArmor).asBase64(); + var result = $$.shell().commands('sh', '-c', "echo '" + gpgArmorB64 + "' | base64 -D | gpg").start(); + return __decrypt(result); +}; + +exports.decrypt = (gpgFile) => { + var result = $$.shell().commands('sh', '-c', 'cat ' + gpgFile + ' | gpg').start(); + __decrypt(result); +}; + +var __decrypt = (result) => { + var out = result[0].string(); + var err = result[1].string(); + if (err.contains('public key decryption failed')) { + println('+ Decrypt file FAILED: ' + gpgFile); + if (!Packages.me.hatter.tools.jssp.main.StandaloneMain.JSSP_MAIN_MUTE) { + println("ERROR detail:\n" + err); + } + throw 'decrypt file failed: ' + gpgFile; + } + return out; +} diff --git a/components/component-gpg.js b/components/component-gpg.js new file mode 100644 index 0000000..f25056b --- /dev/null +++ b/components/component-gpg.js @@ -0,0 +1,15 @@ +(() => { + var gpg = require('component-gpg-ex.js'); + + gpgEncrypt = (keyId, srcFile, destFile) => { + return gpg.encrypt(keyId, srcFile, destFile); + }; + + gpgEncryptArmor = (keyId, srcFile, destFile) => { + return gpg.encryptArmor(keyId, srcFile, destFile); + }; + + gpgDecrypt = (gpgFile) => { + return gpg.decrypt(gpgFile); + }; +})(); diff --git a/components/component-hashjs.js b/components/component-hashjs.js new file mode 100644 index 0000000..ce7cbff --- /dev/null +++ b/components/component-hashjs.js @@ -0,0 +1,48 @@ +var COMMENT_HASH_TAG = '//HASH:'; + +var CollectionUtil = Packages.me.hatter.tools.commons.collection.CollectionUtil; + +var __containsCommentHashTag = (list) => { + for (var i = (list.size() - 1); i >= 0; i--) { + var l = list.get(i); + if ($STR(l.trim()) != '') { + return l.startsWith(COMMENT_HASH_TAG); + } + } + return false; +}; + +var __getDigestJSON = (list) => { + var digestJSON = null; + if (__containsCommentHashTag(list)) { + var last = CollectionUtil.last(list); + while ((list.size() > 0) && ($STR(last.trim()) == '')) { CollectionUtil.removeLast(list); last = CollectionUtil.last(list); } + if (last.startsWith(COMMENT_HASH_TAG)) { + CollectionUtil.removeLast(list); + digestJSON = JSON.parse(last.substring(COMMENT_HASH_TAG.length)); + } + } + return digestJSON; +}; + +var __calcSHA256Hex = (list) => { + return $STR($$.digests().sha256().digest($$.bytes($ARR(list).join('\n'))).asHex()); +}; + +var __checkSHA256 = (list) => { + var digestJSON = __getDigestJSON(list); + return digestJSON && (digestJSON.SHA256 == __calcSHA256Hex(list)); +}; + +var checkBytesSHA256 = (bytes) => { + return __checkSHA256(bytes.list()); +}; + +if (typeof exports == 'object') { + exports.__containsCommentHashTag = __containsCommentHashTag; + exports.__getDigestJSON = __getDigestJSON; + exports.__calcSHA256Hex = __calcSHA256Hex; + exports.__checkSHA256 = __checkSHA256; + exports.COMMENT_HASH_TAG = COMMENT_HASH_TAG; + exports.checkBytesSHA256 = checkBytesSHA256; +} diff --git a/components/component-helloworld.js b/components/component-helloworld.js new file mode 100644 index 0000000..6a3c410 --- /dev/null +++ b/components/component-helloworld.js @@ -0,0 +1,6 @@ +(() => { + helloworld = () => { + return 'Hello World!!!'; + }; +})(); + diff --git a/components/component-helloworld2.js b/components/component-helloworld2.js new file mode 100644 index 0000000..d4751aa --- /dev/null +++ b/components/component-helloworld2.js @@ -0,0 +1,5 @@ + +exports.sayhello = () => { + return 'Say Hello World!'; +}; + diff --git a/components/component-httpserver-ex.js b/components/component-httpserver-ex.js new file mode 100644 index 0000000..d5551f7 --- /dev/null +++ b/components/component-httpserver-ex.js @@ -0,0 +1,415 @@ + +var bytes = require('component-bytes.js'); + +var Runtime = java.lang.Runtime; +var InetSocketAddress = java.net.InetSocketAddress; +var Executors = java.util.concurrent.Executors; +var Bytes = Packages.me.hatter.tools.commons.bytes.Bytes; +var IOUtil = Packages.me.hatter.tools.commons.io.IOUtil; +var HttpServer = Packages.com.sun.net.httpserver.HttpServer; + +// { +// "status": 200, +// "headers": [[key, value],[key2, value2]], +// "responseJSON": ..., +// "responseBytes": ..., +// "responseText": ..., +// } +var writeResponse = (httpExchange, response) => { + response.status = (response.status == null) ? 200 : response.status; + var resBs; + var resStream = null; + if (response.responseJSON != null) { + resBs = Bytes.from(JSON.stringify(response.responseJSON)).getBytes(); + } else if (response.responseText != null) { + resBs = Bytes.from(response.responseText).getBytes(); + } else if (response.responseStream != null) { + resBs = null; + resStream = response.responseStream; + } else { + resBs = response.responseBytes; + } + var responseHeaders = httpExchange.getResponseHeaders(); + if ((response.headers != null) && (response.headers.length > 0)) { + for (var i = 0; i < response.headers.length; i++) { + var keyValue = response.headers[i]; + responseHeaders.set(keyValue[0], keyValue[1]); + } + } else { + responseHeaders.set('Content-Type', 'text/plain;charset=UTF-8'); + } + httpExchange.sendResponseHeaders(response.status, (resBs != null) ? resBs.length : 0); + var resBody = httpExchange.getResponseBody(); + if (resBs != null) { + resBody.write(resBs); + } else { + IOUtil.copy(response.responseStream, resBody); + IOUtil.closeQuietly(response.responseStream); + } + resBody.close(); +}; + +exports.parseQuery = exports.parseQueryParameterMap = (httpExchange) => { + var queryParameterMap = {}; + var rawQuery = $STR(httpExchange.getRequestURI().getRawQuery()); + if (rawQuery != null) { + rawQuery.split('&').forEach((kv) => { + var indexOfE = kv.indexOf('='); + var key = ''; + var value = ''; + if (indexOfE >= 0) { + key = kv.substring(0, indexOfE); + value = kv.substring(indexOfE + 1); + } else { + key = kv; + } + queryParameterMap[key] = queryParameterMap[key] || []; + queryParameterMap[key].push(value); + }); + } + return queryParameterMap; +}; + +var handleDir = (httpExchange, dir, opts) => { + var sb = []; + if (opts.handleDir) { + if (!httpExchange.getRequestURI().getPath().endsWith('/')) { + return { + status: 302, + "headers": [ + ["Location", httpExchange.getRequestURI().getPath() + '/'], + ["Content-Type", 'text/html;charset=UTF-8'], + ], + bytes: new java.lang.String('\n' + + '\n' + + 'Redirecting ...\n' + + '\n' + + '\n' + + '\n' + + 'Redirect to new location: ' + httpExchange.getRequestURI().getPath() + '/' + '\n' + + '\n' + + '').getBytes('UTF-8') + }; + } + + var files = dir.getAbsoluteFile().listFiles(); + sb.push('

Directory Listing

\n'); + sb.push('
\n'); + $EACH(files, (f) => { + var isDir = f.isDirectory(); + if (isDir) { + sb.push('[' + f.getName() + ']' + + '   ' + new Date(f.lastModified()) + '' + + '
\n'); + } else { + sb.push('' + f.getName() + '' + + '   ' + + bytes.showBytes(f.length()) + + '   ' + new Date(f.lastModified()) + '' + + '' + + '
\n'); + } + }); + } else { + sb.push('Directory listing is forbidden.'); + } + return { html: sb.join('') }; +}; + +// https://www.freeformatter.com/mime-types-list.html +var mimeTypeList = [ + [['txt', 'text'], 'text/plain;charset=UTF-8'], + [['jpg', 'jpeg'], 'image/jpeg'], + [['png'], 'image/png'], + [['swf'], 'application/x-shockwave-flash'], + [['js'], 'text/javascript;charset=UTF-8'], + [['htm', 'html'], 'text/html;charset=UTF-8'], + [['css'], 'text/css;charset=UTF-8'], + [['geojson'], 'application/vnd.geo+json'], + [['svg'], 'image/svg+xml'], + [['7z'], 'application/x-7z-compressed'], + [['ace'], 'application/x-ace-compressed'], + [['acc'], 'application/vnd.americandynamics.acc'], + [['pdf'], 'application/pdf'], + [['aac'], 'audio/x-aac'], + [['apk'], 'application/vnd.android.package-archive'], + [['dmg'], 'application/x-apple-diskimage'], + [['mpkg'], 'application/vnd.apple.installer+xml'], + [['s'], 'text/x-asm'], + [['avi'], 'video/x-msvideo'], + [['bin'], 'application/octet-stream'], + [['bmp'], 'image/bmp'], + [['torrent'], 'application/x-bittorrent'], + [['sh'], 'application/x-sh'], + [['bz'], 'application/x-bzip'], + [['bz2'], 'application/x-bzip2'], + [['csh'], 'application/x-csh'], + [['c'], 'text/x-c'], + [['csv'], ' text/csv'], + [['deb'], 'application/x-debian-package'], + [['dtd'], 'application/xml-dtd'], + [['dwg'], 'image/vnd.dwg'], + [['es'], 'application/ecmascript'], + [['epub'], 'application/epub+zip'], + [['eml'], 'message/rfc822'], + [['f4v'], 'video/x-f4v'], + [['flv'], 'video/x-flv'], + [['gif'], 'image/gif'], + [['h261'], 'video/h261'], + [['h263'], 'video/h263'], + [['h264'], 'video/h264'], + [['ico'], 'image/x-icon'], + [['cer'], 'application/pkix-cert'], + [['pki'], 'application/pkixcmp'], + [['crl'], 'application/pkix-crl'], + [['jad'], 'text/vnd.sun.j2me.app-descriptor'], + [['jar'], 'application/java-archive'], + [['class'], 'application/java-vm'], + [['jnlp'], 'application/x-java-jnlp-file'], + [['ser'], 'application/java-serialized-object'], + [['java'], 'text/x-java-source,java'], + [['json'], 'application/json'], + [['m3u', 'm3u8'], 'audio/x-mpegurl'], + [['mathml'], 'application/mathml+xml'], + [['exe'], 'application/x-msdownload'], + [['cab'], 'application/vnd.ms-cab-compressed'], + [['eot'], 'application/vnd.ms-fontobject'], + [['xls'], 'application/vnd.ms-excel'], + [['ppt'], 'application/vnd.ms-powerpoint'], + [['doc'], 'application/msword'], + [['vsd'], 'application/vnd.visio'], + [['chm'], 'application/vnd.ms-htmlhelp'], + [['pptx'], 'application/vnd.openxmlformats-officedocument.presentationml.presentation'], + [['ppsx'], 'application/vnd.openxmlformats-officedocument.presentationml.slideshow'], + [['xlsx'], 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'], + [['docx'], 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'], + [['dotx'], 'application/vnd.openxmlformats-officedocument.wordprocessingml.template'], + [['vsdx'], 'application/vnd.visio2013'], + [['mpeg'], 'video/mpeg'], + [['mp4a'], 'audio/mp4'], + [['mp4'], 'video/mp4'], + [['psd'], 'image/vnd.adobe.photoshop'], + [['p10'], 'application/pkcs10'], + [['p12'], 'application/x-pkcs12'], + [['p7m'], 'application/pkcs7-mime'], + [['p7s'], 'application/pkcs7-signature'], + [['p7r'], 'application/x-pkcs7-certreqresp'], + [['p7b'], 'application/x-pkcs7-certificates'], + [['p8'], 'application/pkcs8'], + [['pgp'], 'application/pgp-encrypted'], + [['rar'], 'application/x-rar-compressed'], + [['rm'], 'application/vnd.rn-realmedia'], + [['rtf'], 'application/rtf'], + [['movie'], 'video/x-sgi-movie'], + [['tiff'], 'image/tiff'], + [['tar'], 'application/x-tar'], + [['tex'], 'application/x-tex'], + [['ttf'], 'application/x-font-ttf'], + [['vcs'], 'text/x-vcalendar'], + [['vcf'], 'text/x-vcard'], + [['wav'], 'audio/x-wav'], + [['woff'], 'application/x-font-woff'], + [['webp'], 'image/webp'], + [['der'], 'application/x-x509-ca-cert'], + [['xml'], 'application/xml'], + [['zip'], 'application/zip'] +]; + +var mimeTypeMap = {}; +mimeTypeList.forEach((m) => { + m[0].forEach((t) => { + mimeTypeMap[t] = m[1]; + }); +}); + +var getMimeTypeByExt = (ext) => { + return mimeTypeMap[ext] || 'application/octet-stream'; +}; + +var fileCacheSize = 0; +var fileCacheCount = 0; +var fileCacheMap = {}; +var handleFile = (httpExchange, file, opts) => { + var fileAbsPath = file.getAbsolutePath(); + if (opts.enableFileCache) { + println('[INFO] ' + getDateYmd() + ' ' + 'File cache hit: ' + fileAbsPath); + var cachedFileContent = fileCacheMap[fileAbsPath]; + if (cachedFileContent) { return cachedFileContent; } // cache hit + } + if (file.exists()) { + var path = $STR(httpExchange.getRequestURI().getPath()).toLowerCase(); + var lastIndexOfDot = path.lastIndexOf('.'); + var ext = ''; + if (lastIndexOfDot >= 0) { + ext = path.substring(lastIndexOfDot + 1); + } + var contentType = null; + if (opts.getContentType) { + contentType = opts.getContentType(httpExchange, file, ext); + } + contentType = contentType || getMimeTypeByExt(ext); + + if (opts.enableFileCache && (file.length() < (opts.cacheFileLength || (1024 * 1024 * 5)))) { // default cache file length 5MB + if (fileCacheCount < (opts.cacheCount || 1000)) { // default cache size 1000 + var fileContent = { + headers: [ + ['Content-Type', contentType], + ['Content-Length', file.length()] + ], + bytes: $$.rFile(file).rStream().bytesAndClose() + }; + fileCacheCount++; + fileCacheSize += file.length(); + println('[INFO] ' + getDateYmd() + ' ' + 'File cached: ' + fileAbsPath + ', size: ' + bytes.showBytes(file.length()) + ', cached count: ' + fileCacheCount + ', cached size: ' + bytes.showBytes(fileCacheSize)); + fileCacheMap[fileAbsPath] = fileContent; + return fileContent; + } + } + return { + headers: [ + ['Content-Type', contentType], + ['Content-Length', file.length()] + ], + stream: $$.rFile(file).rStream().stream() + }; + } else { + return null; + } +}; + +var getDateYmd = () => { + return new java.text.SimpleDateFormat('yyyy-MM-dd HH:mm:ss').format(new java.util.Date()); +}; + +/** + * @param {*} httpExchange + * @param {*} options + * basePath - base path + * getContentType (httpExchange, file, ext) - get content type + * handleDir - enable dir listing + */ +exports.handleFile = (httpExchange, options) => { + var opts = options || {}; + var path = $STR(httpExchange.getRequestURI().getPath()); + if (path == '/') { + return handleDir(httpExchange, opts.basePath ? $$.file(opts.basePath) : $$.file(''), opts); + } + var f = path.substring(1); + var file = opts.basePath ? $$.file(opts.basePath, f) : $$.file(f); + if (file.exists()) { + if (file.isDirectory()) { + return handleDir(httpExchange, file, opts); + } else { + return handleFile(httpExchange, file, opts); + } + } + return null; +}; + +/** + * + * @param {*} port port number + * @param {*} handler process handle + * return { + * text: 'text' + * } + * return { + * html: 'html' + * } + * ... + * @param {*} options + * processors - processor count + * printErrors - print exception detail + */ +exports.serveHTTP = (port, handler, options) => { + var opts = options || {}; + var addr = new InetSocketAddress(port); + var httpServer = HttpServer.create(addr, 0); + if (opts.processors) { + httpServer.setExecutor(Executors.newFixedThreadPool(opts.processors)); + } else { + httpServer.setExecutor(Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2)); + } + httpServer.createContext("/", (httpExchange) => { + try { + println('[INFO] ' + getDateYmd() + ' ' + httpExchange.getRequestURI()); + var response = handler(httpExchange); + if (response != null) { + if (response.text != null) { + responseResult = { + "status": response.status || 200, + "headers": [ + ["Content-Type", "text/plain;charset=UTF-8"], + ], + "responseText": response.text + }; + } else if (response.html != null) { + responseResult = { + "status": response.status || 200, + "headers": [ + ["Content-Type", "text/html;charset=UTF-8"], + ], + "responseText": response.html + }; + } else if (response.css != null) { + responseResult = { + "status": response.status || 200, + "headers": [ + ["Content-Type", "text/css;charset=UTF-8"], + ], + "responseText": response.css + }; + } else if (response.js != null) { + responseResult = { + "status": response.status || 200, + "headers": [ + ["Content-Type", "text/javascript;charset=UTF-8"], + ], + "responseText": response.js + }; + } else if (response.json != null) { + responseResult = { + "status": response.status || 200, + "headers": [ + ["Content-Type", "application/json;charset=UTF-8"], + ], + "responseJSON": response.json + }; + } else if (response.bytes != null) { + responseResult = { + "status": response.status || 200, + "headers": response.headers, + "responseBytes": response.bytes + }; + } else if (response.stream != null) { + responseResult = { + "status": response.status || 200, + "headers": response.headers, + "responseStream": response.stream + }; + } else { + responseResult = { + "status": response.status || 200, + "headers": [ + ["Content-Type", "text/plain;charset=UTF-8"], + ], + "responseText": $STR(response) + }; + } + writeResponse(httpExchange, responseResult); + } + } catch (e) { + var errMsg = '[ERROR] Request: ' + httpExchange.getRequestURI() + ', exception: ' + e; + println('[ERROR] ' + getDateYmd() + ' ' + errMsg); + if (e.printStackTrace) { e.printStackTrace(); } + + writeResponse(httpExchange, { + "status": 500, + "responseText": opts.printErrors ? errMsg : '500 - Server Error' + }); + } + }); + println('[INFO] ' + getDateYmd() + ' ' + "Start listen at: " + port + ' ...'); + httpServer.start(); +}; + diff --git a/components/component-httpserver.js b/components/component-httpserver.js new file mode 100644 index 0000000..4668f7b --- /dev/null +++ b/components/component-httpserver.js @@ -0,0 +1,12 @@ +(() => { + var httpserver = require('component-httpserver-ex.js'); + + parseQueryParameterMap = (httpExchange) => { + return httpserver.parseQueryParameterMap(httpExchange); + }; + + serveHTTP = (port, handler) => { + return httpserver.serveHTTP(port, handler); + }; +})(); + diff --git a/components/component-javahome.js b/components/component-javahome.js new file mode 100644 index 0000000..51e0852 --- /dev/null +++ b/components/component-javahome.js @@ -0,0 +1,67 @@ + +var OSUtil = Packages.me.hatter.tools.commons.os.OSUtil; + +var getJavaHomeFromJdks = (version) => { + var jdksPath = $$.file('~/.jssp/jdks'); + if (!(jdksPath.exists() && jdksPath.isDirectory())) { + return null; + } + var jdkVersions = $ARRAY(jdksPath.listFiles()).filter((p) => { + return p.isDirectory(); + }).map((p) => { + var jdkVersion = p.getName(); + if (jdkVersion.startsWith('jdk-')) { + jdkVersion = jdkVersion.substring('jdk-'.length); + } else if (jdkVersion.startsWith('jdk')) { + jdkVersion = jdkVersion.substring('jdk'.length); + } + return [jdkVersion, p.getAbsolutePath()]; + }).sort((a, b) => { + return b[0] - a[0]; + }); + for (var i = 0; i < jdkVersions.length; i++) { + if (jdkVersions[i][0].startsWith(version)) { + return jdkVersions[i][1]; + } + } + return null; +}; + +var getJavaHomeFromBuildConfig = (version) => { + var buildConfigRFile = __.rfile.from('~/.jssp/config/buildconfig.json'); + if (buildConfigRFile.notExists()) { + return null; + } + var buildConfigJSON = JSON.parse(buildConfigRFile.string()); + if (buildConfigJSON['java'] && buildConfigJSON['java']['versions']) { + var versions = buildConfigJSON['java']['versions']; + for (var i = 0; i < versions.length; i++) { + if (versions[i].startsWith(version)) { + return buildConfigJSON['java'][versions[i]]['home']; + } + } + } + return null; +} + +var getJavaHome = (version) => { + if ((version == null) || ($STR(version) == '')) { return null; } + if (OSUtil.isMacOS()) { + var jversions = $$.shell().commands('/usr/libexec/java_home', '-version', version).start(); + if (jversions[1] && $STR(jversions[1].toString()).contains('Unable to find any JVMs matching version')) { + return null; + } + return jversions[0].toString().trim(); + } else { + var javaHomeFromJdks = getJavaHomeFromJdks(version); + if (javaHomeFromJdks != null) { + return javaHomeFromJdks; + } + var javaHomeFromBuildConfig = getJavaHomeFromBuildConfig(version); + return javaHomeFromBuildConfig; + } +}; + +if (typeof exports == 'object') { + exports.getJavaHome = getJavaHome; +} diff --git a/components/component-jose.js b/components/component-jose.js new file mode 100644 index 0000000..b610503 --- /dev/null +++ b/components/component-jose.js @@ -0,0 +1,51 @@ + +$ONCE('component-jose-requireJARS').run(() => { + requireJAR('jose4j-0.5.5.jar'); + requireJAR('slf4j-api-1.7.21.jar'); +}); + +var JsonWebKey = Packages.org.jose4j.jwk.JsonWebKey; +var JsonWebEncryption = Packages.org.jose4j.jwe.JsonWebEncryption; +var AlgorithmConstraints = Packages.org.jose4j.jwa.AlgorithmConstraints; +var ConstraintType = Packages.org.jose4j.jwa.AlgorithmConstraints.ConstraintType; +var KeyManagementAlgorithmIdentifiers = Packages.org.jose4j.jwe.KeyManagementAlgorithmIdentifiers; +var ContentEncryptionAlgorithmIdentifiers = Packages.org.jose4j.jwe.ContentEncryptionAlgorithmIdentifiers; + +var jwkLoad = (jwkJSONString) => { + return JsonWebKey.Factory.newJwk(jwkJSONString); +} + +var jweEnc = (jwk, txt) => { + var jwe = new JsonWebEncryption(); + jwe.setPlaintext(txt); + jwe.setAlgorithmHeaderValue(KeyManagementAlgorithmIdentifiers.DIRECT); + jwe.setEncryptionMethodHeaderParameter(ContentEncryptionAlgorithmIdentifiers.AES_128_GCM); + jwe.setKey(jwk.getKey()); + return jwe.getCompactSerialization(); +}; + +var jweDec = (jwk, compactSerialization) => { + var jwe = new JsonWebEncryption(); + var algConstraints = new AlgorithmConstraints(ConstraintType.WHITELIST, KeyManagementAlgorithmIdentifiers.DIRECT); + jwe.setAlgorithmConstraints(algConstraints); + var encConstraints = new AlgorithmConstraints(ConstraintType.WHITELIST, ContentEncryptionAlgorithmIdentifiers.AES_128_GCM); + jwe.setContentEncryptionAlgorithmConstraints(encConstraints); + jwe.setCompactSerialization(compactSerialization); + jwe.setKey(jwk.getKey()); + return receiverJwe.getPlaintextString(); +}; + +var jwk = { + "load": jwkLoad +}; +var jwe = { + "enc": jweEnc, + "dec": jweDec +}; + + +if (typeof exports == 'object') { + exports.jwk = jwk; + exports.jwe = jwe; +} + diff --git a/components/component-json-ex.js b/components/component-json-ex.js new file mode 100644 index 0000000..049f1db --- /dev/null +++ b/components/component-json-ex.js @@ -0,0 +1,13 @@ + +var FastJSON = Packages.com.alibaba.fastjson.JSON; +var FastJSONSerializerFeature = Packages.com.alibaba.fastjson.serializer.SerializerFeature; + +exports.prettyJSON = (obj) => { + return FastJSON.toJSONString(FastJSON.parse(JSON.stringify(obj)), FastJSONSerializerFeature.PrettyFormat); +}; + +exports.prettyJavaJSON = (obj) => { + return FastJSON.toJSONString(obj, FastJSONSerializerFeature.PrettyFormat); +}; + + diff --git a/components/component-json.js b/components/component-json.js new file mode 100644 index 0000000..edf355a --- /dev/null +++ b/components/component-json.js @@ -0,0 +1,7 @@ +(() => { + var json = require('component-json-ex.js'); + + prettyJSON = (obj) => { + return json.prettyJSON(obj); + }; +})(); diff --git a/components/component-mysqldataaccess-ex.js b/components/component-mysqldataaccess-ex.js new file mode 100644 index 0000000..1ba9bc2 --- /dev/null +++ b/components/component-mysqldataaccess-ex.js @@ -0,0 +1,8 @@ +requireJAR('mysql-connector-java-5.1.44.jar'); +requireJAR('dataaccess-1.0.jar'); + +var DataAccessObjectUtil = Packages.me.hatter.tools.dataaccess.util.DataAccessObjectUtil; + +exports.createDAO = (host, sid, userName, password) => { + return DataAccessObjectUtil.createMySQLObject(host, sid, userName, password); +}; diff --git a/components/component-os.js b/components/component-os.js new file mode 100644 index 0000000..9d529d7 --- /dev/null +++ b/components/component-os.js @@ -0,0 +1,16 @@ + +// https://stackoverflow.com/questions/246007/how-to-determine-whether-a-given-linux-is-32-bit-or-64-bit +// https://stackoverflow.com/questions/106387/is-it-possible-to-detect-32-bit-vs-64-bit-in-a-bash-script +var getOS = () => { + var osName = $STR($$.shell().commands('uname').start()[0]).trim(); + var arch = $STR($$.shell().commands('uname', '-m').start()[0]).trim(); // OR getconf LONG_BIT + + return { + 'name': osName, + 'arch': arch + }; +}; + +if (typeof exports == 'object') { + exports.getOS = getOS; +} diff --git a/components/component-oss.js b/components/component-oss.js new file mode 100644 index 0000000..4a10a2d --- /dev/null +++ b/components/component-oss.js @@ -0,0 +1,78 @@ + +var URLEncoder = java.net.URLEncoder; +var Bytes = Packages.me.hatter.tools.commons.bytes.Bytes; +var HMacs = Packages.me.hatter.tools.commons.security.hmac.HMacs; + +var counter = require('component-counter.js'); + +var requireJARs = () => { + $ONCE('component-oss-requireJARS').run(() => { + requireJAR('aliyun-sdk-oss-2.4.1-SNAPSHOT.jar'); + requireJAR('commons-beanutils-1.8.0.jar'); + requireJAR('commons-codec-1.9.jar'); + requireJAR('commons-collections-3.2.1.jar'); + requireJAR('commons-lang-2.5.jar'); + requireJAR('commons-logging-1.2.jar'); + requireJAR('ezmorph-1.0.6.jar'); + requireJAR('hamcrest-core-1.1.jar'); + requireJAR('httpclient-4.4.1.jar'); + requireJAR('httpcore-4.4.1.jar'); + requireJAR('jdom-1.1.jar'); + requireJAR('json-lib-2.4-jdk15.jar'); + }); +}; + +var generateSignedURL = (endpoint, accessKeyId, accessKeySecret, bucketName, key, expiresInSeconds, isHTTP) => { + var sb = []; + sb.push(isHTTP ? 'http://' : 'https://'); // default HTTPS + sb.push(bucketName); + sb.push('.' + endpoint); + sb.push('/' + key); + var expires = parseInt($$.date().millis() / 1000) + expiresInSeconds; + sb.push('?Expires=' + expires); + sb.push('&OSSAccessKeyId=' + URLEncoder.encode(accessKeyId, 'UTF-8')); + sb.push('&Signature=' + URLEncoder.encode(sign(accessKeySecret, expires, bucketName, key), 'UTF-8')); + return sb.join(''); +}; + +var sign = (accessKeySecret, expires, bucketName, key) => { + return HMacs.sha1(Bytes.from(accessKeySecret).getBytes()) + .sign(Bytes.from('GET\n\n\n' + expires + '\n/' + bucketName + '/' + key).getBytes()) + .asBase64(); +}; + +var createCounterIS = (file) => { + var CounterInputStream = Packages.me.hatter.tools.commons.io.CounterInputStream; + var uploadFile = (file instanceof java.io.File) ? file : new java.io.File(file); + var uploadIS = new java.io.FileInputStream(uploadFile); + var counterIS = new CounterInputStream(uploadIS, counter.getCounter(uploadFile.length())); + return counterIS; +}; + +var newClient = (endpoint, accessKey, secretKey) => { + requireJARs(); + + var OSSClient = Packages.com.aliyun.oss.OSSClient; + var client = new OSSClient(endpoint, accessKey, secretKey); + return { + runWith: (runFunc) => { + try { + runFunc(client); + } catch (e) { + println('OSS Error: ' + e); + } finally { + client.shutdown(); + } + } + }; +}; + + +if (typeof exports == 'object') { + exports.requireJARs = requireJARs; + exports.sign = sign; + exports.generateSignedURL = generateSignedURL; + exports.createCounterIS = createCounterIS; + exports.newClient = newClient; +} + diff --git a/components/component-package.js b/components/component-package.js new file mode 100644 index 0000000..af88a81 --- /dev/null +++ b/components/component-package.js @@ -0,0 +1,55 @@ +var counter = require('component-counter.js'); + +var getLocalPackage = (packageInfo, skipCertCheck) => { + var basePath = $$.file(packageInfo.basePath); + var bin = $$.file(basePath, packageInfo.subPath, packageInfo.bin); + if (!(bin.exists())) { + var packageFn = packageInfo.package.substring(packageInfo.package.lastIndexOf('/') + 1); + xprintln('[INFO] Package bin not exists, downloading...'); + var packageFile = $$.file(basePath, packageFn); + packageFile.getAbsoluteFile().getParentFile().mkdirs(); + var fos = new java.io.FileOutputStream(packageFile); + $$.httpRequest() + .url(packageInfo.package) + .addHeader('User-Agent', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36') + .skipCertCheck(!! skipCertCheck) + .get(fos, counter.getCounter2()); + fos.close(); + println(); + + var sha256 = $$.rFile(packageFile).digest($$.digests().sha256()).asHex(); + if ($STR(sha256) == packageInfo.sha256) { + xprintln('[SUCCESS] Check SHA256 success: ' + packageFn); + } else { + xprintln('[ERROR] Check SHA256 failed, actual is: ' + sha256 + ',expected: ' + packageInfo.sha256 + ': ' + packageFn); + f.renameTo($$.file(basePath, packageFn + '.invalid.' + $$.date().millis())); + return null; + } + + var installBuilderScripts = []; + installBuilderScripts.push('cd ' + packageInfo.basePath); + if (/.*\.tar\.gz$/.test(packageInfo.package) || /.*\.tgz$/.test(packageInfo.package)) { + installBuilderScripts.push('tar xzvf ' + packageFn); + } else if (/.*\.zip$/.test(packageInfo.package)) { + installBuilderScripts.push('unzip ' + packageFn); + } else { + xprintln('[ERROR] Extract package failed: ' + packageFn); + return; + } + installBuilderScripts.push(''); + + var builderIntall = $$.file('.builder_install.sh'); + $$.rFile(builderIntall).write(installBuilderScripts.join('\n')); + $$.shell().inheritIO().commands('sh', builderIntall.getName()).run(); + builderIntall.delete(); + } + return { + 'packageInfo': packageInfo, + 'packageHome': $STR($$.file(packageInfo.basePath, packageInfo.subPath).getAbsolutePath()), + 'binFile': bin + }; +}; + +if (typeof exports == 'object') { + exports.getLocalPackage = getLocalPackage; +} diff --git a/components/component-pki.js b/components/component-pki.js new file mode 100644 index 0000000..847ec45 --- /dev/null +++ b/components/component-pki.js @@ -0,0 +1,149 @@ +var gpg = require('component-gpg-ex.js'); + +var File = java.io.File; +var ECSignatureTool = Packages.me.hatter.tools.commons.security.sign.SignatureTool; + +var DEFAULT_SIGN_FILE_EXT = '.sig.txt'; +var DEFAULT_PGP_KEY_ID = '6FAFC0E0170985AA71545483C794B1646A886CD6'; +var DEFAULT_EC_PRIVATE_KEY_PGP_ENC = '-----BEGIN PGP MESSAGE-----\n' + + 'Comment: GPGTools - https://gpgtools.org\n' + + '\n' + + 'hQIMA8N6kJ6vG/sAAQ//ef8seBe4daxW1tQg12G6tE2VhA1Y0W/Y3GKCtAxM8PwC\n' + + 'j/VjanTqozVwVQp7Qb3hHoBkEJBAEjrNtIhUE7dJBSbrGkK5i8mkQpkc8MFgtx1A\n' + + 'LVkhdNkTWenk07l6d56v0+iVjlWJsl9wlpP9eY3GkqkWydCXPEn0rICrB3mBf2BA\n' + + 'SRPqucnzSpIjH+KXhUcyVFjU86ZHyzoRP5RwG2MVVSWhwt7Oyd9A6p1hAKHKIjwH\n' + + 'K1W4PhxwrjnelcVWC2uwSLWXqgdXQ0EZXMzH5fv3R2EdZopyaC7AF6/jtauEs5RO\n' + + 'X4l64jjfajqswSZKW0fGKqJgPVzb3p2fBGBeBFx2sYGU/Xk7QTQlfKOvADzLdEIE\n' + + 'hw/i1Huo/xSg4CYiyjlVw82x6cA10UP2d7KRA21NPqWQUKv9p6aWpjkXoM1UE+5D\n' + + 'TdyHSnT0Fu+VaBsGZ+3/x+oRlM6PHjk22n8CUEsZecYzGnyXyAEJuCaYOVtfSMTW\n' + + 'gdy13gJInaxMawHHyRVFqJClZsXc4LEsSsY4fdTus6mEkD+S5S2gkoFffB6uLWYL\n' + + 'pq59IIJ+tp2Y3wxCqUXhu0PYer5No7lU/A53+dtcj7xQYLoOUlB/DkHWfmqAsJi7\n' + + 'Zt+SXoy0ksCtXciBZRhuJESbISALjKqOfQIV/8J9GrN/zQl/dzEmrfgqE00hY1bS\n' + + 'kQFX26y+2iY7k2xoLg+fNB6LNKmtM4U6115CL+rcX3p02P1f4E6O4gGEg9l/3fe2\n' + + 'rz7+2a3WK/MNE1Ak7ImFf6ufU3PHUCQqyEqk7wGeNmRRG3SPJUyBQbpa6I+4xTZ8\n' + + 'R6RqgeigqUMDFyyIwYULEwP+dqnHfRBeckh3MKGfrHQUmgrGDOYawxb1G+h2iaVC\n' + + 'VtA=\n' + + '=QCYC\n' + + '-----END PGP MESSAGE-----'; +var DEFAULT_EC_PUBLIC_KEY = 'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAESZWG1x7tlv02YF+UZzuZdLquR6Oi3H7QRmaRWzDoupG66dWeKruRUQVj2ozliDKaRQ+5b91u/z9BApIaFs6quw=='; + +var generateECsecp256r1KeyPair = () => { + var ecSignTool = ECSignatureTool.secp256r1(); + ecSignTool.generateKeyPair(); + return { + 'privKey': ecSignTool.encodePriKey(), + 'pubKey': ecSignTool.encodePubKey() + }; +}; + +var __defaultEcPrivateKeyBytes = null; +var getDefaultEcPrivateKeyBytes = () => { + if (__defaultEcPrivateKeyBytes != null) { return __defaultEcPrivateKeyBytes; } + + var __defaultEcPrivateKeyLocalFile = $$.file('~/.jssp/config/.defaultecprivatekey.json'); + if (__defaultEcPrivateKeyLocalFile.exists()) { + return __.bytes.fromBase64(JSON.parse($$.rFile(__defaultEcPrivateKeyLocalFile).string()).base64Key); + } + + var tempPrivKeyFile = File.createTempFile('component-pki', '.temp_key'); + $$.rFile(tempPrivKeyFile).write(DEFAULT_EC_PRIVATE_KEY_PGP_ENC); + tempPrivKeyFile.deleteOnExit(); + var privKey = gpg.decrypt(tempPrivKeyFile.getAbsolutePath()); + __defaultEcPrivateKeyBytes = __.bytes.fromBase64(privKey.trim()); + + __defaultEcPrivateKeyLocalFile.getParentFile().mkdirs(); + $$.rFile(__defaultEcPrivateKeyLocalFile).write(JSON.stringify({ + 'date': $$.date().fmt('yyyy-MM-dd HH:mm:ss Z').format($$.date().today()), + 'base64Key': __defaultEcPrivateKeyBytes.asBase64() + })); + + return __defaultEcPrivateKeyBytes; +}; + +var getDefaultEcPublicKeyBytes = () => { + return __.bytes.fromBase64(DEFAULT_EC_PUBLIC_KEY); +}; + +var getDefaultEcSignSignature = () => { + return ECSignatureTool.secp256r1().decodePriKey(getDefaultEcPrivateKeyBytes()).signSHA256ECDSA(); +}; + +var getDefaultEcVerifySignature = () => { + return ECSignatureTool.secp256r1().decodePubKey(getDefaultEcPublicKeyBytes()).verifySHA256ECDSA(); +}; + +var defaultSignFile = (file) => { + file = $$.file(file); + if (file.getName().endsWith(DEFAULT_SIGN_FILE_EXT) || !(file.exists()) || !(file.isFile())) { + return false; + } + var signFile = $$.file(file.getAbsoluteFile().getParentFile(), file.getName() + DEFAULT_SIGN_FILE_EXT); + var sigHex = $$.rFile(file).sign(getDefaultEcSignSignature()).asHex(); + $$.rFile(signFile).write(JSON.stringify({ + 'length': $$.str(file.length()), + 'lastModified': $$.str(file.lastModified()), + 'signature': sigHex + })); + return true; +}; + +var defaultSignFileV2 = (file) => { + file = $$.file(file); + if (file.getName().endsWith(DEFAULT_SIGN_FILE_EXT) || !(file.exists()) || !(file.isFile())) { + return false; + } + var signFile = $$.file(file.getAbsoluteFile().getParentFile(), file.getName() + DEFAULT_SIGN_FILE_EXT); + var sha256 = $$.rFile(file).digest(__.digests.sha256()).asHex(); + var sigHex = __.bytes.fromHex(sha256).sign(getDefaultEcSignSignature()).asHex(); + $$.rFile(signFile).write(JSON.stringify({ + 'version': 2, + 'length': $$.str(file.length()), + 'lastModified': $$.str(file.lastModified()), + 'SHA256': sha256, + 'signature': sigHex + })); + return true; +}; + +var defaultQuickVerifyFile = (file) => { + file = $$.file(file); + if (file.getName().endsWith(DEFAULT_SIGN_FILE_EXT) || !(file.exists()) || !(file.isFile())) { + return null; + } + var signFile = $$.file(file.getAbsoluteFile().getParentFile(), file.getName() + DEFAULT_SIGN_FILE_EXT); + if (!(signFile.exists())) { + return null; + } + var signatureJSON = JSON.parse($$.rFile(signFile).string().trim()); + return $$.strEquals(file.length(), signatureJSON.length) && $$.strEquals(file.lastModified(), signatureJSON.lastModified); +}; + +var defaultVerifyFile = (file) => { + file = $$.file(file); + if (file.getName().endsWith(DEFAULT_SIGN_FILE_EXT) || !(file.exists()) || !(file.isFile())) { + return null; + } + var signFile = $$.file(file.getAbsoluteFile().getParentFile(), file.getName() + DEFAULT_SIGN_FILE_EXT); + if (!(signFile.exists())) { + return null; + } + var signatureJSON = JSON.parse($$.rFile(signFile).string().trim()); + if (signatureJSON.version) { + var sha256 = $$.rFile(file).digest(__.digests.sha256()); + return $$.strEquals(sha256.asHex(), signatureJSON.SHA256) && sha256.verify(getDefaultEcVerifySignature(), __.bytes.fromHex(signatureJSON.signature)); + } else { + return $$.rFile(file).verify(getDefaultEcVerifySignature(), __.bytes.fromHex(signatureJSON.signature)); + } +}; + +if (typeof exports == 'object') { + exports.generateECsecp256r1KeyPair = generateECsecp256r1KeyPair; + exports.getDefaultEcPrivateKeyBytes = getDefaultEcPrivateKeyBytes; + exports.getDefaultEcPublicKeyBytes = getDefaultEcPublicKeyBytes; + exports.getDefaultEcSignSignature = getDefaultEcSignSignature; + exports.getDefaultEcVerifySignature = getDefaultEcVerifySignature; + exports.defaultSignFile = defaultSignFile; + exports.defaultVerifyFile = defaultVerifyFile; + exports.defaultSignFileV2 = defaultSignFileV2; + exports.defaultQuickVerifyFile = defaultQuickVerifyFile; +} diff --git a/components/component-shannonentropy-ex.js b/components/component-shannonentropy-ex.js new file mode 100644 index 0000000..e72d4c9 --- /dev/null +++ b/components/component-shannonentropy-ex.js @@ -0,0 +1,33 @@ +// http://blog.dkbza.org/2007/05/scanning-data-for-entropy-anomalies.html +// https://github.com/dxa4481/truffleHog/blob/master/truffleHog.py + +var Math_log2 = Math.log2 || (x) => { + return Math.log(x) * Math.LOG2E; +}; + +exports.SHANNON_ENTROPY_BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; +exports.SHANNON_ENTROPY_HEX = "1234567890abcdefABCDEF"; + +exports.shannonEntropy = (data, charRange) => { + if (!data) { + return 0; + } + var lenOfData = data.length; + var charCodeCountMap = {}; + for (var i = 0; i < data.length; i++) { + var charCode = data.charCodeAt(i); + charCodeCountMap[charCode] = charCodeCountMap[charCode] || 0; + charCodeCountMap[charCode] += 1; + } + var entropy = 0; + for (var i = 0; i < charRange.length; i++) { + var charCode = charRange.charCodeAt(i); + var charCodeCount = charCodeCountMap[charCode]; + if (charCodeCount != null) { + var pCharCode = charCodeCount / lenOfData; + entropy += - pCharCode * Math_log2(pCharCode); + } + } + return entropy; +}; + diff --git a/components/component-shannonentropy.js b/components/component-shannonentropy.js new file mode 100644 index 0000000..0c240b9 --- /dev/null +++ b/components/component-shannonentropy.js @@ -0,0 +1,10 @@ +(() => { + var shannonentropy = require('component-shannonentropy-ex.js'); + + SHANNON_ENTROPY_BASE64 = shannonentropy.SHANNON_ENTROPY_BASE64; + SHANNON_ENTROPY_HEX = shannonentropy.SHANNON_ENTROPY_HEX; + + shannonEntropy = (data, charRange) => { + return shannonentropy.shannonEntropy(data, charRange); + }; +})(); diff --git a/components/component-sqlitedataaccess-ex.js b/components/component-sqlitedataaccess-ex.js new file mode 100644 index 0000000..2093537 --- /dev/null +++ b/components/component-sqlitedataaccess-ex.js @@ -0,0 +1,8 @@ +requireJAR('jdbc.sqlite-3.6.7.jar'); +requireJAR('dataaccess-1.0.jar'); + +var DataAccessObjectUtil = Packages.me.hatter.tools.dataaccess.util.DataAccessObjectUtil; + +exports.createDAO = (dbFile) => { + return DataAccessObjectUtil.createSqliteObject(dbFile); +}; diff --git a/components/component-sqlitedataaccess.js b/components/component-sqlitedataaccess.js new file mode 100644 index 0000000..9765262 --- /dev/null +++ b/components/component-sqlitedataaccess.js @@ -0,0 +1,7 @@ +(() => { + var sqlitedao = require('component-sqlitedataaccess-ex.js'); + + createSqliteDataAccessObject = (dbFile) => { + return sqlitedao.createDAO(dbFile); + }; +})(); diff --git a/components/component-swing.js b/components/component-swing.js new file mode 100644 index 0000000..9750e2f --- /dev/null +++ b/components/component-swing.js @@ -0,0 +1,94 @@ +(() => { + var JFrame = javax.swing.JFrame; + var JLabel = javax.swing.JLabel; + var JPanel = javax.swing.JPanel; + var JButton = javax.swing.JButton; + var JTextArea = javax.swing.JTextArea; + var JScrollPane = javax.swing.JScrollPane; + var Frame = java.awt.Frame; + var Toolkit = java.awt.Toolkit; + var BorderLayout = java.awt.BorderLayout; + var WindowListener = java.awt.event.WindowListener; + var Proxy = java.lang.reflect.Proxy; + var CountDownLatch = java.util.concurrent.CountDownLatch; + + try { + //javax.swing.UIManager.setLookAndFeel(javax.swing.UIManager.getCrossPlatformLookAndFeelClassName()); + } catch (e) { /* IGNORE */ } + + function FutureResult() { + this.result = null; + this.countDown = new CountDownLatch(1); + } + + FutureResult.prototype.set = (result) => { + this.result = result; + this.countDown.countDown(); + }; + + FutureResult.prototype.get = () => { + this.countDown.await(); + return this.result; + }; + + showWindow = (title, text, message, editable) => { // GLOBAL + var result = new FutureResult(); + var f = new JFrame(title || "Hatter's Frame"); + f.addWindowListener(Proxy.newProxyInstance( + Proxy.class.getClassLoader(), + [WindowListener.class], + (proxy, method, args) => { + if (method && method.name == "windowClosing") { + f.setVisible(false); + f.dispose(); + result.set(null); + } + } + )); + + var text = new JTextArea(text || "", 30, 80); + text.setWrapStyleWord(true); + text.setLineWrap(true); + text.setEditable(!!editable); + + var textScrollPane = new JScrollPane(text); + + var label = new JLabel(message || "This is default message!"); + + var btnOK = null; + if (editable) { + btnOK = new JButton("OK!"); + btnOK.addActionListener(() => { + var t = text.getText(); + f.setVisible(false); + f.dispose(); + result.set(t); + }); + } + var btnCancel = new JButton("Cancel"); + btnCancel.addActionListener(() => { + f.setVisible(false); + f.dispose(); + result.set(null); + }); + + var pane = new JPanel(); + if (btnOK != null) { + pane.add(btnOK); + } + pane.add(btnCancel); + + f.getContentPane().add(label, BorderLayout.NORTH); + f.getContentPane().add(pane, BorderLayout.SOUTH); + f.getContentPane().add(textScrollPane, BorderLayout.CENTER); + + f.pack(); + + var dim = Toolkit.getDefaultToolkit().getScreenSize(); + f.setLocation((dim.width / 2) - (f.getSize().width / 2), (dim.height / 2) - (f.getSize().height / 2)); + + f.setVisible(true); + + return result; + }; +})(); diff --git a/components/component-time.js b/components/component-time.js new file mode 100644 index 0000000..95e71e9 --- /dev/null +++ b/components/component-time.js @@ -0,0 +1,66 @@ +var getMillis = (date) => { + if (date == null) { return 0; } + if (date instanceof java.util.Date) { + return date.getTime(); + } + return date; +}; + +var formatDuration = (date) => { + var millis = getMillis(date); + var diffMillis = $$.date().millis() - millis; + if (diffMillis < 3000) { + return 'just now'; + } + var secs = $$.num(diffMillis).divr(1000, 0, null).along(); + if (secs < 60) { + return secs + 's'; + } + var mins = $$.num(secs).divr(60, 0, null).along(); + if (mins < 60) { + return mins + 'm'; + } + var hours = $$.num(mins).divr(60, 0, null).along(); + if (hours < 24) { + return hours + 'h'; + } + var days = $$.num(hours).divr(24, 0, null).along(); + if (days < 31) { + return days + 'd'; + } + return $$.date().fmt('yyyy-MM-dd').format(new java.util.Date(millis)); +}; + + +var formatDueInDays = (date) => { + var millis = getMillis(date); + var diffMillis = millis - $$.date().millis(); + var isMinus = diffMillis < 0; + if (isMinus) { + diffMillis = -diffMillis; + } + var prefix = isMinus ? '-' : ''; + var secs = $$.num(diffMillis).divr(1000, 0, null).along(); + if (secs < 60) { + return 'in ' + prefix + secs + 's'; + } + var mins = $$.num(secs).divr(60, 0, null).along(); + if (mins < 60) { + return 'in ' + prefix + secs + 'm'; + } + var hours = $$.num(mins).divr(60, 0, null).along(); + if (hours < 24) { + return 'in ' + prefix + hours + 'h'; + } + var days = $$.num(hours).divr(24, 0, null).along(); + if (days < 31) { + return 'in ' + prefix + days + 'd'; + } + return $$.date().fmt('yyyy-MM-dd').format(new java.util.Date(millis)); +}; + +if (typeof exports == 'object') { + exports.getMillis = getMillis; + exports.formatDuration = formatDuration; + exports.formatDueInDays = formatDueInDays; +} \ No newline at end of file