├── justify ├── README.md └── justify.js ├── gzip └── gzip.js ├── threadify └── threadify.js ├── zlib └── zlib.js ├── run ├── run.js └── monitor.js ├── wasm └── wasm.js ├── cache └── cache.js ├── webrtc └── webrtc.js ├── stringify └── stringify.js ├── tar └── tar.js ├── dns ├── dns.js └── protocol.js ├── binary └── binary.js ├── packet └── packet.js ├── tls └── tls.js ├── http ├── parsers.js └── http.js ├── unix └── unix.js ├── pg ├── common.js ├── parser.js └── pg.js ├── tcp └── tcp.js ├── html └── html.js ├── socket └── socket.js ├── fetch └── fetch.js ├── sqlite └── sqlite.js └── md5 └── md5.js /justify/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /gzip/gzip.js: -------------------------------------------------------------------------------- 1 | const { zlib } = just.library('zlib') 2 | const { createInflate, writeInflate, endInflate, Z_NO_FLUSH, Z_STREAM_END } = zlib 3 | 4 | function gunzip (src) { 5 | const state = [0, 0] 6 | const dest = createInflate(src, maxArchiveSize, 31) 7 | const r = writeInflate(dest, src, 0, src.byteLength, state, Z_NO_FLUSH) 8 | const [read, write] = state 9 | if (read === src.byteLength && r === Z_STREAM_END) { 10 | const tar = new ArrayBuffer(write) 11 | tar.copyFrom(dest, 0, write, 0) 12 | endInflate(dest, true) 13 | return tar 14 | } 15 | } 16 | 17 | const maxArchiveSize = just.env().MAX_ARCHIVE || (64 * 1024 * 1024) 18 | 19 | module.exports = { gunzip } 20 | -------------------------------------------------------------------------------- /threadify/threadify.js: -------------------------------------------------------------------------------- 1 | const threading = just.library('thread') 2 | const { readFile } = require('fs') 3 | const { fileName } = require('path') 4 | 5 | function spawn (main) { 6 | if (just.sys.tid() !== just.sys.pid()) { 7 | main().catch(err => just.error(err.stack)) 8 | return 9 | } 10 | const cpus = parseInt(just.env().CPUS || just.sys.cpus, 10) 11 | if (cpus === 1) { 12 | // if only one thread specified, then just run main in the current thread 13 | main().catch(err => just.error(err.stack)) 14 | return [just.sys.pid()] 15 | } 16 | let source = just.builtin(`${fileName(just.args[0])}.js`) 17 | if (!source) { 18 | source = readFile(just.args[1]) 19 | } 20 | const threads = [] 21 | for (let i = 0; i < cpus; i++) { 22 | threads.push(threading.thread.spawn(source, just.builtin('just.js'), just.args)) 23 | } 24 | return threads 25 | } 26 | 27 | module.exports = { spawn } 28 | -------------------------------------------------------------------------------- /zlib/zlib.js: -------------------------------------------------------------------------------- 1 | const { zlib } = just.library('zlib') 2 | 3 | const { Z_DEFAULT_COMPRESSION, Z_FINISH } = zlib 4 | 5 | function gunzip (buf, targetSize = 8 * 1024 * 1024) { 6 | const inflate = zlib.createInflate(buf, targetSize, 31) 7 | const status = [0, 0] 8 | const len = buf.byteLength 9 | const rc = zlib.writeInflate(inflate, buf, 0, len, status, Z_FINISH) 10 | zlib.endInflate(inflate) 11 | const [read, written] = status 12 | if (rc === 1) return inflate.slice(0, written) 13 | return new ArrayBuffer(0) 14 | } 15 | 16 | function gzip (buf, compression = Z_DEFAULT_COMPRESSION) { 17 | const deflate = zlib.createDeflate(buf, buf.byteLength, compression, 31) 18 | const bytes = zlib.writeDeflate(deflate, buf.byteLength, Z_FINISH) 19 | zlib.endDeflate(deflate) 20 | if (bytes < 0) return new ArrayBuffer(0) 21 | return deflate.slice(0, bytes) 22 | } 23 | 24 | zlib.gunzip = gunzip 25 | zlib.gzip = gzip 26 | 27 | module.exports = zlib 28 | -------------------------------------------------------------------------------- /run/run.js: -------------------------------------------------------------------------------- 1 | const { launch, watch } = require('process') 2 | const { Monitor } = require('monitor.js') 3 | const { sys } = just.library('sys') 4 | 5 | const { pageSize } = sys 6 | 7 | const SIGTERM = 15 8 | 9 | function run (cmd = '', args = [], path = sys.cwd(), stdio) { 10 | const buf = new ArrayBuffer(16384) 11 | const app = launch(cmd, args.map(arg => arg.toString()), path, buf, stdio) 12 | app.out = [] 13 | app.err = [] 14 | app.stats = [] 15 | const monitor = new Monitor(app.stats) 16 | monitor.open(app.pid) 17 | app.onStdout = (buf, bytes) => app.out.push(buf.readString(bytes)) 18 | app.onStderr = (buf, bytes) => app.err.push(buf.readString(bytes)) 19 | const last = [0, 0] 20 | app.onClose = () => { 21 | app.stats = app.stats.map(s => { 22 | const utime = s.utime - last[0] 23 | const stime = s.stime - last[1] 24 | last[0] = s.utime 25 | last[1] = s.stime 26 | const total = utime + stime 27 | const [upc, spc] = [(utime / total) * 100, (stime / total) * 100] 28 | return [upc, spc, Math.floor((s.rssPages * pageSize))] 29 | }) 30 | monitor.close() 31 | } 32 | app.kill = (sig = SIGTERM) => sys.kill(app.pid, sig) 33 | app.waitfor = () => watch(app) 34 | return app 35 | } 36 | 37 | module.exports = { run, Monitor } 38 | -------------------------------------------------------------------------------- /wasm/wasm.js: -------------------------------------------------------------------------------- 1 | const { readFileBytes, writeFile } = require('fs') 2 | const WabtModule = require('./libwabt.js') 3 | 4 | const binarySettings = { log: false, write_debug_names: false } 5 | const features = { 6 | exceptions: false, 7 | mutable_globals: false, 8 | sat_float_to_int: false, 9 | sign_extension: false, 10 | simd: true, 11 | threads: false, 12 | multi_value: false, 13 | tail_call: false, 14 | bulk_memory: false, 15 | reference_types: false 16 | } 17 | 18 | function compile (fileName, feat = features, settings = binarySettings) { 19 | return new Promise((resolve, reject) => { 20 | WabtModule().then(wabt => { 21 | var module = wabt.parseWat(fileName, readFileBytes(fileName), feat) 22 | module.resolveNames() 23 | module.validate(feat) 24 | const binaryOutput = module.toBinary(settings) 25 | resolve({ wasm: binaryOutput.buffer.buffer, source: binaryOutput.log }) 26 | }) 27 | }) 28 | } 29 | 30 | function createMemory (opts = { initial: 1 }) { 31 | return new WebAssembly.Memory(opts) 32 | } 33 | 34 | function createInstance (mod, context) { 35 | return new WebAssembly.Instance(mod, context) 36 | } 37 | 38 | function evaluate (wasm, context = {}, memory = createMemory()) { 39 | const mod = new WebAssembly.Module(wasm) 40 | const js = Object.assign(context, { memory }) 41 | return createInstance(mod, { js }).exports 42 | } 43 | 44 | function save (fileName, wasm, flags) { 45 | return writeFile(fileName, wasm, flags) 46 | } 47 | 48 | function load (fileName) { 49 | return readFileBytes(fileName) 50 | } 51 | 52 | module.exports = { 53 | compile, 54 | save, 55 | load, 56 | evaluate, 57 | createMemory, 58 | binarySettings, 59 | features 60 | } 61 | -------------------------------------------------------------------------------- /cache/cache.js: -------------------------------------------------------------------------------- 1 | class SimpleCache { 2 | constructor (refresh = () => {}, expiration = 60000) { 3 | this.maxItemSize = 65536 4 | this.maxItems = 1 * 1024 * 1024 5 | this.now = Date.now() 6 | this.clock = 0 7 | this.resolution = 1000 // milliseconds 8 | this.map = new Map() 9 | this.defaultExpiration = expiration 10 | this.refresh = refresh 11 | this.hit = 0 12 | this.miss = 0 13 | } 14 | 15 | get (key, expires = this.defaultExpiration) { 16 | const entry = this.map.get(key) 17 | if (!entry) { 18 | // todo: stop thundering herd 19 | const value = this.refresh(key) 20 | if (!value) return null 21 | this.map.set(key, { value, ts: this.now }) 22 | this.miss++ 23 | return value 24 | } 25 | if (this.now - entry.ts < expires) { 26 | this.hit++ 27 | return entry.value 28 | } 29 | // we don't need this for sync 30 | // stop anyone else re-fetching 31 | entry.ts = this.now + 5000 32 | // todo: we need to stop parallel requests 33 | const value = this.refresh(key) 34 | this.map.set(key, { value, ts: this.now }) 35 | this.miss++ 36 | return value 37 | } 38 | 39 | async getAsync (key, expires = this.defaultExpiration) { 40 | const entry = this.map.get(key) 41 | if (!entry) { 42 | // todo: stop thundering herd 43 | const value = await this.refresh(key) 44 | if (!value) return null 45 | this.map.set(key, { value, ts: this.now }) 46 | this.miss++ 47 | return value 48 | } 49 | if (this.now - entry.ts < expires) { 50 | this.hit++ 51 | return entry.value 52 | } 53 | // stop anyone else re-fetching 54 | entry.ts = this.now + 5000 55 | // todo: we need to stop parallel requests 56 | const value = await this.refresh(key) 57 | this.map.set(key, { value, ts: this.now }) 58 | this.miss++ 59 | return value 60 | } 61 | 62 | delete (key) { 63 | this.map.delete(key) 64 | } 65 | 66 | tick () { 67 | this.now = Date.now() 68 | } 69 | 70 | start () { 71 | if (this.clock) return 72 | const cache = this 73 | this.clock = just.setInterval(() => cache.tick(), this.resolution) 74 | return this 75 | } 76 | 77 | stop () { 78 | if (this.clock) just.clearInterval(this.clock) 79 | return this 80 | } 81 | } 82 | 83 | module.exports = { SimpleCache } 84 | -------------------------------------------------------------------------------- /webrtc/webrtc.js: -------------------------------------------------------------------------------- 1 | const { webrtc } = just.library('webrtc') 2 | 3 | /* 4 | STUN Protocol 5 | https://datatracker.ietf.org/doc/html/rfc8489 6 | 7 | Client Initiated SIP 8 | https://datatracker.ietf.org/doc/html/rfc5626 9 | 10 | ICE 11 | https://datatracker.ietf.org/doc/html/rfc8445 12 | 13 | DTLS for STUN 14 | https://datatracker.ietf.org/doc/html/rfc7350 15 | */ 16 | 17 | class PeerConnection { 18 | constructor (stun) { 19 | this.stun = stun 20 | this.candidates = [] 21 | this.sdp = '' 22 | this.label = '' 23 | this.peer = webrtc.createConnection(stun, 24 | () => { 25 | // onDescription 26 | this.sdp = webrtc.getDescription(this.peer) 27 | this.onDescription(this.sdp[0], this.sdp[1]) 28 | }, 29 | () => { 30 | // onCandidate 31 | const candidates = webrtc.getCandidates(this.peer) 32 | for (const candidate of candidates) { 33 | const [str, mid] = candidate 34 | if (!this.candidates.some(c => c === str)) { 35 | this.onCandidate(str) 36 | this.candidates.push(str) 37 | } 38 | } 39 | }, 40 | () => { 41 | // onDataChannelOpen 42 | this.label = webrtc.getLabel(this.peer) 43 | this.onOpen(this.label) 44 | }, 45 | () => { 46 | // onMessage 47 | const message = webrtc.getMessage(this.peer) 48 | if (message) this.onMessage(message) 49 | }, 50 | () => { 51 | // onClose 52 | this.onClose() 53 | }, 54 | ) 55 | this.onDescription = () => {} 56 | this.onCandidate = () => {} 57 | this.onOpen = () => {} 58 | this.onMessage = () => {} 59 | this.onClose = () => {} 60 | } 61 | 62 | get state () { 63 | return webrtc.getState(this.peer) 64 | } 65 | 66 | get gatheringState () { 67 | return webrtc.getGatheringState(this.peer) 68 | } 69 | 70 | createOffer (mid) { 71 | return webrtc.createOffer(this.peer, mid) 72 | } 73 | 74 | setRemoteDescription (sdp, type) { 75 | return webrtc.setRemoteDescription(this.peer, sdp, type) 76 | } 77 | 78 | addCandidate (candidate, mid) { 79 | return webrtc.addCandidate(this.peer, candidate, mid) 80 | } 81 | 82 | send (message) { 83 | return webrtc.send(this.peer, message) 84 | } 85 | } 86 | 87 | function createConnection (stun) { 88 | return new PeerConnection(stun) 89 | } 90 | 91 | module.exports = { createConnection, webrtc } 92 | -------------------------------------------------------------------------------- /run/monitor.js: -------------------------------------------------------------------------------- 1 | const { fs } = just.library('fs') 2 | const { net } = just.library('net') 3 | 4 | const { SystemError, clearTimeout, setInterval } = just 5 | 6 | class Monitor { 7 | constructor (stats = [], opts = { bufferSize: 16384, interval: 100 }) { 8 | this.stats = stats 9 | this.bufferSize = opts.bufferSize || 16384 10 | this.buffer = new ArrayBuffer(this.bufferSize) 11 | this.interval = opts.interval || 100 12 | this.timer = 0 13 | this.pid = 0 14 | this.fd = 0 15 | this.path = '' 16 | } 17 | 18 | read () { 19 | const { stats, pid, buffer, fd, bufferSize } = this 20 | net.seek(fd, 0, net.SEEK_SET) 21 | // todo: stat the file and create a buffer of correct size 22 | let bytes = net.read(fd, buffer, 0, bufferSize) 23 | const parts = [] 24 | while (bytes > 0) { 25 | parts.push(buffer.readString(bytes)) 26 | bytes = net.read(fd, buffer, 0, bufferSize) 27 | } 28 | const fields = parts.join('').split(' ') 29 | const comm = fields[1] 30 | const state = fields[2] 31 | const [ 32 | ppid, 33 | pgrp, 34 | session, 35 | ttyNr, 36 | tpgid, 37 | flags, 38 | minflt, 39 | cminflt, 40 | majflt, 41 | cmajflt, 42 | utime, 43 | stime, 44 | cutime, 45 | cstime, 46 | priority, 47 | nice, 48 | numThreads, 49 | itrealvalue, 50 | starttime, 51 | vsize, 52 | rssPages, 53 | rsslim, 54 | startcode, 55 | endcode, 56 | startstack, 57 | kstkesp, 58 | kstkeip, 59 | signal, 60 | blocked, 61 | sigignore, 62 | sigcatch, 63 | wchan, 64 | nswap, 65 | cnswap, 66 | exitSignal, 67 | processor, 68 | rtPriority, 69 | policy, 70 | delayacctBlkioTicks, 71 | guestTime, 72 | cguestTime, 73 | startData, 74 | endData, 75 | startBrk, 76 | argStart, 77 | argEnd, 78 | envStart, 79 | envEnd, 80 | exitCode 81 | ] = fields.slice(3).map(v => Number(v)) 82 | stats.push({ 83 | pid, 84 | comm, 85 | state, 86 | ppid, 87 | pgrp, 88 | session, 89 | ttyNr, 90 | tpgid, 91 | flags, 92 | minflt, 93 | cminflt, 94 | majflt, 95 | cmajflt, 96 | utime, 97 | stime, 98 | cutime, 99 | cstime, 100 | priority, 101 | nice, 102 | numThreads, 103 | itrealvalue, 104 | starttime, 105 | vsize, 106 | rssPages, 107 | rsslim, 108 | startcode, 109 | endcode, 110 | startstack, 111 | kstkesp, 112 | kstkeip, 113 | signal, 114 | blocked, 115 | sigignore, 116 | sigcatch, 117 | wchan, 118 | nswap, 119 | cnswap, 120 | exitSignal, 121 | processor, 122 | rtPriority, 123 | policy, 124 | delayacctBlkioTicks, 125 | guestTime, 126 | cguestTime, 127 | startData, 128 | endData, 129 | startBrk, 130 | argStart, 131 | argEnd, 132 | envStart, 133 | envEnd, 134 | exitCode 135 | }) 136 | } 137 | 138 | open (pid) { 139 | this.close() 140 | this.path = `/proc/${pid}/stat` 141 | this.fd = fs.open(this.path) 142 | if (this.fd <= 0) throw new SystemError(`could not open ${this.path}`) 143 | this.timer = setInterval(() => this.read(), this.interval) 144 | } 145 | 146 | close () { 147 | if (this.fd) { 148 | net.close(this.fd) 149 | this.fd = 0 150 | clearTimeout(this.timer) 151 | } 152 | this.pid = 0 153 | } 154 | } 155 | 156 | module.exports = { Monitor } 157 | -------------------------------------------------------------------------------- /stringify/stringify.js: -------------------------------------------------------------------------------- 1 | // mostly lifted from here: https://github.com/lucagez/slow-json-stringify 2 | 3 | var _prepare = function(e) { 4 | var r = JSON.stringify(e, function(e, r) { 5 | return r.isSJS ? r.type + "__sjs" : r 6 | }); 7 | return { 8 | preparedString: r, 9 | preparedSchema: JSON.parse(r) 10 | } 11 | }, 12 | _find = function(path) { 13 | for (var length = path.length, str = "obj", i = 0; i < length; i++) str = str.replace(/^/, "("), str += " || {})." + path[i]; 14 | return just.vm.runScript("((obj) => " + str + ")") 15 | }, 16 | _makeArraySerializer = function(e) { 17 | return e instanceof Function ? function(r) { 18 | for (var n = "", t = r.length, a = 0; a < t - 1; a++) n += e(r[a]) + ","; 19 | return "[" + (n += e(r[t - 1])) + "]" 20 | } : function(e) { 21 | return JSON.stringify(e) 22 | } 23 | }, 24 | TYPES = ["number", "string", "boolean", "array", "null"], 25 | attr = function(e, r) { 26 | if (!TYPES.includes(e)) throw new Error('Expected one of: "number", "string", "boolean", "null". received "' + e + '" instead'); 27 | var n = r || function(e) { 28 | return e 29 | }; 30 | return { 31 | isSJS: !0, 32 | type: e, 33 | serializer: "array" === e ? _makeArraySerializer(r) : n 34 | } 35 | }, 36 | defaultRegex = new RegExp('\\n|\\r|\\t|\\"|\\\\', "gm"), 37 | escape = function(e) { 38 | return void 0 === e && (e = defaultRegex), 39 | function(r) { 40 | return r.replace(e, function(e) { 41 | return "\\" + e 42 | }) 43 | } 44 | }, 45 | _makeQueue = function(e, r) { 46 | var n = []; 47 | return function e(t, a) { 48 | if (void 0 === a && (a = []), !/__sjs/.test(t)) return Object.keys(t).map(function(r) { 49 | return e(t[r], a.concat([r])) 50 | }); 51 | var i = Array.from(a), 52 | u = _find(i), 53 | s = u(r); 54 | n.push({ 55 | serializer: s.serializer, 56 | find: u, 57 | name: a[a.length - 1] 58 | }) 59 | }(e), n 60 | }, 61 | _makeChunks = function(e, r) { 62 | return e.replace(/"\w+__sjs"/gm, function(e) { 63 | return /string/.test(e) ? '"__par__"' : "__par__" 64 | }).split("__par__").map(function(e, n, t) { 65 | var a = '("' + (r[n] || {}).name + '":("?))$', 66 | i = "(,?)" + a, 67 | u = /^("}|})/.test(t[n + 1] || ""), 68 | s = new RegExp(u ? i : a), 69 | f = /^(\"\,|\,|\")/; 70 | return { 71 | flag: !1, 72 | pure: e, 73 | prevUndef: e.replace(f, ""), 74 | isUndef: e.replace(s, ""), 75 | bothUndef: e.replace(f, "").replace(s, "") 76 | } 77 | }) 78 | }, 79 | _select = function(e) { 80 | return function(r, n) { 81 | var t = e[n]; 82 | return void 0 !== r ? t.flag ? t.prevUndef + r : t.pure + r : (e[n + 1].flag = !0, t.flag ? t.bothUndef : t.isUndef) 83 | } 84 | }, 85 | sjs = function(e) { 86 | var r = _prepare(e), 87 | n = r.preparedString, 88 | t = _makeQueue(r.preparedSchema, e), 89 | a = _makeChunks(n, t), 90 | i = _select(a), 91 | u = t.length; 92 | return function(e) { 93 | for (var r = "", n = 0; n !== u;) { 94 | var s = t[n], 95 | f = s.serializer, 96 | p = (0, s.find)(e); 97 | r += i(f(p), n), n += 1 98 | } 99 | var o = a[a.length - 1]; 100 | return r + (o.flag ? o.prevUndef : o.pure) 101 | } 102 | }; 103 | 104 | const JSONify = (o, sp = ' ') => JSON.stringify(o, (k, v) => { 105 | if (v.constructor && v.constructor.name === 'BigInt') return v.toString() 106 | if (v.constructor && v.constructor.name === 'ArrayBuffer') return `[ArrayBuffer](${v.byteLength})` 107 | if (v.constructor && v.constructor.name === 'DataView') return `[DataView](${v.buffer.byteLength})` 108 | if (v.constructor && v.constructor.name === 'Uint8Array') return `[Uint8Array](${v.buffer.byteLength})` 109 | return v 110 | }, sp) 111 | 112 | exports.JSONify = JSONify 113 | exports.sjs = sjs, exports.attr = attr, exports.escape = escape; -------------------------------------------------------------------------------- /tar/tar.js: -------------------------------------------------------------------------------- 1 | const { fs } = just.library('fs') 2 | const { sys } = just.library('sys') 3 | const { net } = just.library('net') 4 | const { mkdir, open, O_TRUNC, O_CREAT, O_WRONLY, EEXIST } = fs 5 | const { errno, strerror } = sys 6 | const { write, close } = net 7 | 8 | function isLastBlock (dv, off) { 9 | for (let n = off + 511; n >= off; --n) { 10 | if (dv.getUint8(n) !== 0) { 11 | return false 12 | } 13 | } 14 | return true 15 | } 16 | 17 | function verifyChecksum (dv, off) { 18 | return true 19 | } 20 | 21 | function getOctal (buf, off, len) { 22 | const { u8 } = buf 23 | let i = 0 24 | while ((u8[off] < ZERO || u8[off] > SEVEN) && len > 0) { 25 | len-- 26 | off++ 27 | } 28 | while (u8[off] >= ZERO && u8[off] <= SEVEN && len > 0) { 29 | i *= 8 30 | i += (u8[off] - ZERO) 31 | len-- 32 | off++ 33 | } 34 | return i 35 | } 36 | 37 | function createFile (src, off, mode) { 38 | const { u8 } = src 39 | let len = 0 40 | let i = off 41 | while (u8[i++] !== 0) len++ 42 | if (u8[off + len - 1] === SLASH) len-- 43 | const fileName = src.readString(len, off) 44 | let fd = open(`${justDir}/${fileName}`, O_TRUNC | O_CREAT | O_WRONLY, mode) 45 | if (fd < 0) { 46 | const lastSlash = fileName.lastIndexOf('/') 47 | if (lastSlash > -1) { 48 | //createDirectory(fileName.slice(0, lastSlash)) 49 | fd = open(`${justDir}/${fileName}`, O_TRUNC | O_CREAT | O_WRONLY, mode) 50 | } 51 | } 52 | return fd 53 | } 54 | 55 | function createDirectory (src, off, mode) { 56 | const { u8 } = src 57 | let len = 0 58 | let i = off 59 | while (u8[i++] !== 0) len++ 60 | if (u8[off + len - 1] === SLASH) len-- 61 | const dirName = src.readString(len, off) 62 | let r = mkdir(`${justDir}/${dirName}`, mode) 63 | if (r !== 0) { 64 | if (errno() !== EEXIST) { 65 | const lastSlash = dirName.lastIndexOf('/') 66 | if (lastSlash > -1) { 67 | r = mkdir(`${justDir}/${dirName.slice(0, lastSlash)}`, mode) 68 | } 69 | } 70 | } 71 | return r 72 | } 73 | 74 | function writeBytes (fd, src, off, len) { 75 | const chunks = Math.ceil(len / 4096) 76 | const end = off + len 77 | let bytes = 0 78 | for (let i = 0; i < chunks; ++i, off += 4096) { 79 | const towrite = Math.min(end - off, 4096) 80 | bytes = write(fd, src, towrite, off) 81 | if (bytes <= 0) break 82 | } 83 | if (bytes < 0) { 84 | just.error(`Error Writing to File: ${errno()} (${strerror(errno())})`) 85 | } 86 | if (bytes === 0) { 87 | just.error(`Zero Bytes Written: ${errno()}`) 88 | } 89 | const r = close(fd) 90 | if (r < 0) { 91 | just.error(`Error Closing File: ${errno()}`) 92 | return r 93 | } 94 | return bytes 95 | } 96 | 97 | function untar2 (src, size, stat) { 98 | const dv = new DataView(src) 99 | const u8 = new Uint8Array(src) 100 | src.view = dv 101 | src.u8 = u8 102 | for (let off = 0; off < size; off += 512) { 103 | const end = off + 512 104 | if (end > size) { 105 | just.error('Short read') 106 | return -1 107 | } 108 | if (isLastBlock(dv, off)) { 109 | return 0 110 | } 111 | if (!verifyChecksum(dv, off)) { 112 | just.error('Checksum failed') 113 | return -1 114 | } 115 | let fileSize = getOctal(src, off + 124, 12) 116 | let fd = 0 117 | const fileType = dv.getUint8(off + 156) 118 | if (fileType === 53) { 119 | const mode = getOctal(src, off + 100, 8) 120 | createDirectory(src, off, mode) 121 | fileSize = 0 122 | } else if (fileType === 48) { 123 | const mode = getOctal(src, off + 100, 8) 124 | fd = createFile(src, off, mode) 125 | } else { 126 | //just.error(`unknown file type ${fileType}`) 127 | } 128 | if (fd < 0) { 129 | if (errno() !== EEXIST) { 130 | just.error(`bad fd: ${fd}`) 131 | just.error(strerror(errno())) 132 | return -1 133 | } 134 | } 135 | if (fileSize > 0) { 136 | writeBytes(fd, src, off + 512, fileSize) 137 | off += (fileSize + (512 - (fileSize % 512))) 138 | } 139 | } 140 | return -1 141 | } 142 | 143 | function untar (src, size = src.byteLength) { 144 | const stat = {} 145 | const code = untar2(src, size, stat) 146 | const { pathName } = stat 147 | return { code, pathName } 148 | } 149 | 150 | const ASCII0 = 48 151 | const SLASH = '/'.charCodeAt(0) 152 | const ZERO = '0'.charCodeAt(0) 153 | const SEVEN = '7'.charCodeAt(0) 154 | const justDir = `${just.env().JUST_HOME || just.sys.cwd()}` 155 | 156 | module.exports = { untar } 157 | -------------------------------------------------------------------------------- /dns/dns.js: -------------------------------------------------------------------------------- 1 | const { create, parse } = require('protocol.js') 2 | const { udp } = just.library('udp') 3 | const { net } = just 4 | const { loop } = just.factory 5 | const { readFile, isFile } = require('fs') 6 | 7 | const dnsServer = just.env().DNS_SERVER || '127.0.0.11' 8 | 9 | function parseLine (line) { 10 | const parts = line.split(/\s+/) 11 | const [address, ...hosts] = parts 12 | return { address, hosts } 13 | } 14 | 15 | const rxipv4 = /\d+\.\d+\.\d+\.\d+/ 16 | const rxComment = /(\s+)?#.+/ 17 | const rxName = /nameserver\s+(.+)/ 18 | 19 | function readHosts () { 20 | const ipv4 = {} 21 | const ipv6 = {} 22 | const fileName = '/etc/hosts' 23 | if (!isFile(fileName)) { 24 | just.error(`${fileName} not found`) 25 | return { ipv4, ipv6 } 26 | } 27 | const hosts = readFile(fileName) 28 | const lines = hosts.split('\n').filter(line => line.trim()) 29 | for (const line of lines) { 30 | if (line.match(rxComment)) continue 31 | const { address, hosts } = parseLine(line) 32 | if (address.match(rxipv4)) { 33 | for (const host of hosts) { 34 | ipv4[host] = address 35 | } 36 | } else { 37 | for (const host of hosts) { 38 | ipv6[host] = address 39 | } 40 | } 41 | } 42 | return { ipv4, ipv6 } 43 | } 44 | 45 | function lookupHosts (hostname) { 46 | const { ipv4 } = readHosts() 47 | return ipv4[hostname] 48 | } 49 | 50 | function readResolv () { 51 | const fileName = '/etc/resolv.conf' 52 | const results = [] 53 | if (!isFile(fileName)) { 54 | just.error(`${fileName} not found`) 55 | return results 56 | } 57 | const resolv = readFile(fileName) 58 | const lines = resolv.split('\n').filter(line => line.trim()) 59 | for (const line of lines) { 60 | const match = line.match(rxName) 61 | if (match && match.length > 1) { 62 | const [, ip] = match 63 | if (ip.match(rxipv4)) { 64 | results.push(ip) 65 | } 66 | } 67 | } 68 | return results 69 | } 70 | 71 | function lookup (query = 'www.google.com', onRecord = () => {}, address = dnsServer, port = 53, buf = new ArrayBuffer(65536)) { 72 | const ip = lookupHosts(query) 73 | const byteLength = buf.byteLength 74 | if (ip) { 75 | onRecord(null, ip) 76 | return 77 | } 78 | const ips = readResolv() 79 | if (ips.length) { 80 | address = ips[0] 81 | } 82 | const fd = net.socket(net.AF_INET, net.SOCK_DGRAM | net.SOCK_NONBLOCK, 0) 83 | net.bind(fd, address, port) 84 | loop.add(fd, (fd, event) => { 85 | just.clearTimeout(timer) 86 | const answer = [] 87 | const len = udp.recvmsg(fd, buf, answer, byteLength) 88 | if (len <= 0) { 89 | onRecord(new Error('Bad Message Length')) 90 | return 91 | } 92 | const message = parse(buf, len) 93 | if (!message.answer.length) { 94 | onRecord(new Error(`Address Not Found for ${query}`)) 95 | return 96 | } 97 | if (message.answer.length === 0 && message.answer[0].ctype === 1) { 98 | const { ip } = message.answer[0] 99 | const result = `${ip[0]}.${ip[1]}.${ip[2]}.${ip[3]}` 100 | loop.remove(fd) 101 | net.close(fd) 102 | onRecord(null, result) 103 | return 104 | } 105 | const dict = {} 106 | message.answer.forEach(answer => { 107 | const { ip, cname, qtype } = answer 108 | const name = answer.name.join('.') 109 | if (qtype === 5) { 110 | dict[name] = { cname: cname.join('.') } 111 | } else if (qtype === 1) { 112 | dict[name] = { ip: `${ip[0]}.${ip[1]}.${ip[2]}.${ip[3]}` } 113 | } 114 | }) 115 | let ip 116 | let q = query 117 | while (!ip) { 118 | const res = dict[q] 119 | if (res.ip) { 120 | ip = res.ip 121 | break 122 | } 123 | q = res.cname 124 | } 125 | loop.remove(fd) 126 | net.close(fd) 127 | onRecord(null, ip) 128 | }) 129 | const len = create(query, buf, 1) 130 | const rc = udp.sendmsg(fd, buf, address, port, len) 131 | if (rc === -1) { 132 | const errno = just.sys.errno() 133 | onRecord(new Error(`Error sending ${query} to ${address}: ${just.sys.strerror(errno)} (${errno})`)) 134 | loop.remove(fd) 135 | net.close(fd) 136 | return 137 | } 138 | const timer = just.setTimeout(() => { 139 | onRecord(new Error(`Request timed out for ${query} at ${address}`)) 140 | loop.remove(fd) 141 | net.close(fd) 142 | }, 1000) 143 | } 144 | 145 | const dnsMap = new Map() 146 | 147 | function getIPAddress (hostname, map = dnsMap) { 148 | return new Promise((resolve, reject) => { 149 | if (map.has(hostname)) { 150 | resolve(map.get(hostname)) 151 | return 152 | } 153 | lookup(hostname, (err, ip) => { 154 | if (err) { 155 | reject(err) 156 | return 157 | } 158 | map.set(hostname, ip) 159 | resolve(ip) 160 | }) 161 | }) 162 | } 163 | 164 | module.exports = { lookup, getIPAddress } 165 | -------------------------------------------------------------------------------- /binary/binary.js: -------------------------------------------------------------------------------- 1 | // Foreground Colors 2 | const AD = '\u001b[0m' // ANSI Default 3 | const A0 = '\u001b[30m' // ANSI Black 4 | const AR = '\u001b[31m' // ANSI Red 5 | const AG = '\u001b[32m' // ANSI Green 6 | const AY = '\u001b[33m' // ANSI Yellow 7 | const AB = '\u001b[34m' // ANSI Blue 8 | const AM = '\u001b[35m' // ANSI Magenta 9 | const AC = '\u001b[36m' // ANSI Cyan 10 | const AW = '\u001b[37m' // ANSI White 11 | 12 | // Background Colors 13 | const BD = '\u001b[0m' // ANSI Default 14 | const B0 = '\u001b[40m' // ANSI Black 15 | const BR = '\u001b[41m' // ANSI Red 16 | const BG = '\u001b[42m' // ANSI Green 17 | const BY = '\u001b[43m' // ANSI Yellow 18 | const BB = '\u001b[44m' // ANSI Blue 19 | const BM = '\u001b[45m' // ANSI Magenta 20 | const BC = '\u001b[46m' // ANSI Cyan 21 | const BW = '\u001b[47m' // ANSI White 22 | 23 | function dump (bytes, len = bytes.length, off = 0, width = 16, pos = 0, decimal = false) { 24 | const result = [] 25 | const chars = [] 26 | const base = decimal ? 10 : 16 27 | for (let i = 0; i < len; i++) { 28 | if (i % width === 0) { 29 | if (i === 0) { 30 | result.push('') 31 | } else { 32 | result.push(` ${chars.join('')}\n`) 33 | chars.length = 0 34 | } 35 | } 36 | const boff = i + off 37 | if (i % 8 === 0) { 38 | result.push(`${AG}${(boff).toString(base).padStart(5, ' ')}${AD}`) 39 | } 40 | result.push(` ${bytes[boff].toString(16).padStart(2, '0')}`) 41 | if (bytes[boff] >= 32 && bytes[boff] <= 126) { 42 | chars.push(`${AC}${String.fromCharCode(bytes[boff])}${AD}`) 43 | } else { 44 | chars.push('.') 45 | } 46 | } 47 | const remaining = width - (len % width) 48 | if (remaining === width) { 49 | result.push(` ${chars.join('')}\n`) 50 | } else if (remaining < 8) { 51 | result.push(`${' '.repeat(remaining)} ${chars.join('')}\n`) 52 | } else { 53 | result.push(`${' '.repeat(remaining)} ${chars.join('')}\n`) 54 | } 55 | return result.join('') 56 | } 57 | 58 | function b2ipv4 (v) { 59 | return `${v >> 24 & 0xff}.${v >> 16 & 0xff}.${v >> 8 & 0xff}.${v & 0xff}` 60 | } 61 | 62 | function ipv42b (v) { 63 | const [b0, b1, b2, b3] = v.split('.').map(o => parseInt(o, 10)) 64 | return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3 65 | } 66 | 67 | function toMAC (u8) { 68 | return Array.prototype.map.call(u8, v => v.toString(16).padStart(2, '0')).join(':') 69 | } 70 | 71 | function htons16 (n) { 72 | const u16 = n & 0xffff 73 | return (u16 & 0xff) << 8 + (u16 >> 8) 74 | } 75 | 76 | function getFlags (flags) { 77 | return Object.keys(flags).filter(v => flags[v]) 78 | } 79 | 80 | function pad (n, p = 10) { 81 | return n.toString().padStart(p, ' ') 82 | } 83 | 84 | function tcpDump (packet) { 85 | const { frame, header, message, bytes, offset } = packet // eth frame, ip header, tcp message 86 | const size = bytes - offset 87 | const { seq, ack, flags } = message // get tcp fields 88 | const [source, dest] = [b2ipv4(header.source), b2ipv4(header.dest)] // convert source and dest ip to human-readable 89 | return ` 90 | ${AM}Eth ${AD}: ${AM}${toMAC(frame.source)}${AD} -> ${AM}${toMAC(frame.dest)}${AD} 91 | ${AG}${frame.protocol.padEnd(4, ' ')} ${AD}: ${AG}${source}${AD} -> ${AG}${dest}${AD} 92 | ${AY}TCP ${AD}: ${AY}${message.source}${AD} -> ${AY}${message.dest}${AD} seq ${AY}${pad(seq)}${AD} ack ${AY}${pad(ack)}${AD} (${AC}${getFlags(flags).join(' ')}${AD}) ${size} 93 | `.trim() 94 | } 95 | 96 | function udpDump (packet) { 97 | const { frame, header, message, bytes, offset } = packet // eth frame, ip header, udp message 98 | const size = bytes - offset 99 | const [source, dest] = [b2ipv4(header.source), b2ipv4(header.dest)] // convert source and dest ip to human-readable 100 | return ` 101 | ${AM}Eth ${AD}: ${AM}${toMAC(frame.source)}${AD} -> ${AM}${toMAC(frame.dest)}${AD} 102 | ${AG}${frame.protocol.padEnd(4, ' ')} ${AD}: ${AG}${source}${AD} -> ${AG}${dest}${AD} 103 | ${AY}UDP ${AD}: ${AY}${message.source}${AD} -> ${AY}${message.dest}${AD} ${size} 104 | `.trim() 105 | } 106 | 107 | const ANSI = { AD, AY, AM, AC, AG, AR, AB } 108 | ANSI.colors = { fore: { AD, A0, AR, AG, AY, AB, AM, AC, AW }, back: { BD, B0, BR, BG, BY, BB, BM, BC, BW } } 109 | const HOME = '\u001b[0;0H' // set cursor to 0,0 110 | const CLS = '\u001b[2J' // clear screen 111 | const EL = '\u001b[K' // erase line 112 | const SAVECUR = '\u001b[s' // save cursor 113 | const RESTCUR = '\u001b[u' // restore cursor 114 | const HIDE = '\u001b[?25l' // hide cursor 115 | const SHOW = '\u001b[?25h' // show cursor 116 | ANSI.control = { 117 | home: () => HOME, 118 | move: (x = 0, y = 0) => `\u001b[${x};${y}H`, 119 | column: x => `\u001b[${x}G`, 120 | cls: () => CLS, 121 | eraseLine: () => EL, 122 | cursor: { 123 | hide: () => HIDE, 124 | show: () => SHOW, 125 | save: () => SAVECUR, 126 | restore: () => RESTCUR 127 | } 128 | } 129 | 130 | module.exports = { dump, ANSI, getFlags, htons16, toMAC, ipv42b, b2ipv4, tcpDump, udpDump } 131 | -------------------------------------------------------------------------------- /packet/packet.js: -------------------------------------------------------------------------------- 1 | const protocols = { 2 | IP: 0, 3 | ICMP: 1, 4 | IGMP: 2, 5 | TCP: 6, 6 | UDP: 17, 7 | RDP: 27, 8 | IPv6: 41, 9 | 'IPv6-ICMP': 58, 10 | ARP: 59, 11 | SCTP: 132 12 | } 13 | 14 | function Parser () { 15 | const buf = new ArrayBuffer(65536) 16 | const dv = new DataView(buf) 17 | const u8 = new Uint8Array(buf) 18 | 19 | let offset = 0 20 | 21 | function parseUDPMessage (message) { 22 | const type = 'udp' 23 | const source = dv.getUint16(offset) 24 | const dest = dv.getUint16(offset + 2) 25 | const length = dv.getUint16(offset + 4) 26 | const checksum = dv.getUint16(offset + 6) 27 | offset += 8 28 | return { type, source, dest, length, checksum } 29 | } 30 | 31 | function parseTCPMessage (message) { 32 | const type = 'tcp' 33 | const source = dv.getUint16(offset) 34 | const dest = dv.getUint16(offset + 2) 35 | const seq = dv.getUint32(offset + 4) 36 | const ack = dv.getUint32(offset + 8) 37 | const field = dv.getUint16(offset + 12) 38 | const doff = field >> 12 39 | const flags = field & 0b111111111 40 | const [NS, CWR, ECE, URG, ACK, PSH, RST, SYN, FIN] = [ 41 | (flags >> 8) & 0b1, 42 | (flags >> 7) & 0b1, 43 | (flags >> 6) & 0b1, 44 | (flags >> 5) & 0b1, 45 | (flags >> 4) & 0b1, 46 | (flags >> 3) & 0b1, 47 | (flags >> 2) & 0b1, 48 | (flags >> 1) & 0b1, 49 | flags & 0b1 50 | ] 51 | const window = dv.getUint16(offset + 14) 52 | const checksum = dv.getUint16(offset + 16) 53 | const urgent = dv.getUint16(offset + 18) 54 | const extra = (doff * 4) - 20 55 | const options = {} 56 | offset += 20 57 | if (extra > 0) { 58 | let i = extra 59 | while (i) { 60 | const kind = u8[offset] 61 | if (kind === 0) { 62 | offset++ 63 | i-- 64 | break 65 | } 66 | if (kind === 1) { 67 | offset++ 68 | i-- 69 | continue 70 | } 71 | if (kind === 2) { 72 | options.maxss = dv.getUint16(offset + 2) 73 | offset += 4 74 | i -= 4 75 | continue 76 | } 77 | if (kind === 3) { 78 | options.windowScale = u8[offset + 2] 79 | offset += 3 80 | i -= 3 81 | continue 82 | } 83 | if (kind === 4) { 84 | options.selAck = true 85 | offset += 2 86 | i -= 2 87 | continue 88 | } 89 | if (kind === 5) { 90 | const len = u8[offset + 1] 91 | options.sackSize = len - 2 92 | options.sack = new Uint8Array(buf, offset + 2, options.sackSize) 93 | offset += len 94 | i -= len 95 | continue 96 | } 97 | if (kind === 8) { 98 | options.timestamp = { tsval: dv.getUint32(offset + 2), tsecr: dv.getUint32(offset + 6), bytes: new Uint8Array(buf, offset + 2, 8) } 99 | offset += 10 100 | i -= 10 101 | continue 102 | } 103 | throw new Error(`Unknown Option Kind ${kind}`) 104 | } 105 | } 106 | return { type, options, source, dest, seq, ack, doff, flags: { NS, CWR, ECE, URG, ACK, PSH, RST, SYN, FIN }, window, checksum, urgent, extra } 107 | } 108 | 109 | function parse (bytes, ethernet = false) { 110 | offset = 0 111 | let frame 112 | if (ethernet) { 113 | const dest = new Uint8Array(buf, offset, 6) 114 | const source = new Uint8Array(buf, offset + 6, 6) 115 | const type = dv.getUint16(offset + 12) 116 | frame = { source, dest, type } 117 | if (type <= 1500) { 118 | frame.size = type 119 | } 120 | if (type >= 1536) { 121 | if (type === 0x0800) { 122 | // IPv4 Datagram 123 | frame.protocol = 'IPv4' 124 | } else if (type === 0x0806) { 125 | frame.protocol = 'ARP' 126 | } 127 | } 128 | offset += 14 129 | } 130 | let field = u8[offset] 131 | const version = field >> 4 132 | const ihl = field & 0b1111 133 | field = u8[offset + 1] 134 | const dscp = field >> 2 135 | const ecn = field & 0b11 136 | const len = dv.getUint16(offset + 2) 137 | const id = dv.getUint16(offset + 4) 138 | field = dv.getUint16(offset + 6) 139 | const flags = field >> 13 140 | const DF = flags >> 1 & 0b1 141 | const MF = flags >> 2 & 0b1 142 | const foff = field & 0b1111111111111 143 | const ttl = u8[offset + 8] 144 | const protocol = u8[offset + 9] 145 | const checksum = dv.getUint16(offset + 10) 146 | const source = dv.getUint32(offset + 12) 147 | const dest = dv.getUint32(offset + 16) 148 | const header = { version, ihl, dscp, ecn, id, flags, DF, MF, foff, ttl, protocol, checksum, source, dest } 149 | header.length = { header: ihl * 4, total: len } 150 | offset += header.length.header 151 | let message 152 | if (protocol === protocols.TCP) { 153 | message = parseTCPMessage() 154 | } else if (protocol === protocols.UDP) { 155 | message = parseUDPMessage() 156 | } 157 | const remaining = bytes - offset 158 | return { frame, header, message, offset, bytes, remaining } 159 | } 160 | 161 | return { u8, dv, buf, parse } 162 | } 163 | 164 | module.exports = { Parser, protocols } 165 | -------------------------------------------------------------------------------- /dns/protocol.js: -------------------------------------------------------------------------------- 1 | const opcode = { 2 | QUERY: 0, 3 | IQUERY: 1, 4 | STATUS: 2 5 | } 6 | 7 | const qtype = { 8 | A: 1, 9 | NS: 2, 10 | MD: 3, 11 | MF: 4, 12 | CNAME: 5, 13 | SOA: 6, 14 | MB: 7, 15 | MG: 8, 16 | MR: 9, 17 | NULL: 10, 18 | WKS: 11, 19 | PTR: 12, 20 | HINFO: 13, 21 | MINFO: 14, 22 | MX: 15, 23 | TXT: 16, 24 | // Additional 25 | AXFR: 252, 26 | MAILB: 253, 27 | MAILA: 254, 28 | ANY: 255 29 | } 30 | 31 | const qclass = { 32 | IN: 1, 33 | CS: 2, 34 | CH: 3, 35 | HS: 4, 36 | ANY: 255 37 | } 38 | 39 | const rcode = { 40 | NOERROR: 0, 41 | FORMAT: 1, 42 | SERVER: 2, 43 | NAME: 3, 44 | NOTIMPL: 4, 45 | REFUSED: 5 46 | } 47 | 48 | const types = { opcode, qtype, qclass, rcode } 49 | 50 | function readName (offset, buf, view) { 51 | let name = [] 52 | let qnameSize = view.getUint8(offset++) 53 | while (qnameSize) { 54 | if ((qnameSize & 192) === 192) { 55 | let off = (qnameSize - 192) << 8 56 | off += view.getUint8(offset++) 57 | name = name.concat(readName(off, buf, view)) 58 | qnameSize = 0 59 | } else { 60 | name.push(buf.readString(qnameSize, offset)) 61 | offset += qnameSize 62 | qnameSize = view.getUint8(offset++) 63 | } 64 | } 65 | return name 66 | } 67 | 68 | const parse = (buf, len) => { 69 | const bytes = new Uint8Array(buf) 70 | const view = new DataView(buf) 71 | const id = view.getUint16(0) 72 | const flags = view.getUint16(2) 73 | const QR = (flags >> 15) & 0b1 74 | const opCode = (flags >> 11) & 0b1111 75 | const AA = (flags >> 10) & 0b1 76 | const TC = (flags >> 9) & 0b1 77 | const RD = (flags >> 8) & 0b1 78 | const RA = (flags >> 7) & 0b1 79 | const Z = (flags >> 4) & 0b111 80 | const RCODE = flags & 0b1111 81 | const qcount = view.getUint16(4) 82 | const ancount = view.getUint16(6) 83 | const nscount = view.getUint16(8) 84 | const arcount = view.getUint16(10) 85 | const question = [] 86 | const answer = [] 87 | const authority = [] 88 | const additional = [] 89 | const start = 12 90 | let off = start 91 | let i = off 92 | let counter = qcount 93 | while (counter--) { 94 | let size = 0 95 | const sections = [] 96 | while (bytes[i++]) size++ 97 | if (size > 0) { 98 | while (off - start < size) { 99 | const qnameSize = view.getUint8(off++) 100 | sections.push(buf.readString(qnameSize, off)) 101 | off += qnameSize 102 | } 103 | } 104 | off++ 105 | const qtype = view.getUint16(off) 106 | off += 2 107 | const qclass = view.getUint16(off) 108 | off += 2 109 | question.push({ qtype, qclass, name: sections }) 110 | } 111 | counter = ancount 112 | while (counter--) { 113 | const next = view.getUint16(off) 114 | let name 115 | if ((0b1100000000000000 & next) === 0b1100000000000000) { 116 | name = readName(next & 0b11111111111111, buf, view) 117 | off += 2 118 | } else { 119 | name = readName(off, buf, view) 120 | off += name.length + (name.reduce((a, v) => a + v.length, 0)) + 1 121 | } 122 | const qtype = view.getUint16(off) 123 | off += 2 124 | const qclass = view.getUint16(off) 125 | off += 2 126 | const ttl = view.getUint32(off) 127 | off += 4 128 | const rdLength = view.getUint16(off) 129 | off += 2 130 | if (qtype === 5) { 131 | const cname = readName(off, buf, view) 132 | answer.push({ name, cname, qtype, qclass, ttl }) 133 | } else if (qtype === 1) { 134 | answer.push({ name, qtype, qclass, ttl, ip: bytes.slice(off, off + rdLength) }) 135 | } 136 | off += rdLength 137 | } 138 | return { bytes: bytes.slice(0, len), qcount, nscount, ancount, arcount, id, flags, QR, opCode, AA, TC, RD, RA, Z, RCODE, question, answer, authority, additional } 139 | } 140 | 141 | const create = (domain, buf, id, qtype = 1, qclass = 1) => { 142 | const view = new DataView(buf) 143 | const bytes = new Uint8Array(buf) 144 | view.setUint16(0, id) 145 | view.setUint16(2, 0b0000000101000000) 146 | view.setUint16(4, 1) 147 | view.setUint16(6, 0) 148 | view.setUint16(8, 0) 149 | view.setUint16(10, 0) 150 | let off = 12 151 | const parts = domain.split('.') 152 | for (const part of parts) { 153 | view.setUint8(off++, part.length) 154 | buf.writeString(part, off) 155 | off += part.length 156 | } 157 | bytes[off++] = 0 158 | view.setUint16(off, qtype) 159 | off += 2 160 | view.setUint16(off, qclass) 161 | off += 2 162 | return off 163 | } 164 | 165 | const qtypes = {} 166 | Object.keys(types.qtype).forEach(k => { 167 | qtypes[types.qtype[k]] = k 168 | }) 169 | const qclasses = {} 170 | Object.keys(types.qclass).forEach(k => { 171 | qclasses[types.qclass[k]] = k 172 | }) 173 | const opcodes = {} 174 | Object.keys(types.opcode).forEach(k => { 175 | opcodes[types.opcode[k]] = k 176 | }) 177 | const rcodes = {} 178 | Object.keys(types.rcode).forEach(k => { 179 | rcodes[types.rcode[k]] = k 180 | }) 181 | 182 | function getFlags (message) { 183 | const flags = [] 184 | if (message.QR) flags.push('qr') 185 | if (message.AA) flags.push('aa') 186 | if (message.TC) flags.push('tc') 187 | if (message.RD) flags.push('rd') 188 | if (message.RA) flags.push('ra') 189 | if (message.Z) flags.push('z') 190 | return flags.join(' ') 191 | } 192 | 193 | module.exports = { getFlags, create, parse, types, qtypes, qclasses, opcodes, rcodes } 194 | -------------------------------------------------------------------------------- /tls/tls.js: -------------------------------------------------------------------------------- 1 | const { net } = just.library('net') 2 | const { tls } = just.library('tls', 'openssl.so') 3 | const { epoll } = just.library('epoll') 4 | const socket = require('@socket') 5 | 6 | const { AF_INET, SOCK_STREAM, SOCK_NONBLOCK } = net 7 | const { EPOLLIN, EPOLLOUT, EPOLLET } = epoll 8 | const { createSocket, Socket } = socket 9 | const { loop } = just.factory 10 | 11 | Socket.prototype.serverName = function () { 12 | if (!this.tls) return 13 | return tls.getServerName(this.buffer) 14 | } 15 | 16 | Socket.prototype.certificate = function () { 17 | if (!this.tls) return 18 | return tls.getCertificate(this.buffer) 19 | } 20 | 21 | // required 22 | Socket.prototype.setSecure = function () { 23 | this.tls = true 24 | 25 | this.recv = function (off = 0, len = this.bufLen - off) { 26 | const bytes = tls.read(this.buffer, len, off) 27 | if (bytes > 0) this.stats.recv += bytes 28 | return bytes 29 | } 30 | /* 31 | this.recv = function () { 32 | const bytes = tls.read(this.buffer) 33 | if (bytes > 0) this.stats.recv += bytes 34 | return bytes 35 | } 36 | */ 37 | this.send = function (buf, len = buf.byteLength, off = 0) { 38 | const bytes = tls.write(this.buffer, this.buffer.copyFrom(buf, off, len)) 39 | if (bytes > 0) this.stats.send += bytes 40 | return bytes 41 | } 42 | this.sendString = function (str) { 43 | const bytes = tls.write(this.buffer, this.buffer.writeString(str)) 44 | if (bytes > 0) this.stats.send += bytes 45 | return bytes 46 | } 47 | return this 48 | } 49 | 50 | // https://stackoverflow.com/questions/67657974/c-c-when-and-why-do-we-need-to-call-ssl-do-handshake-in-a-tls-client-server 51 | 52 | Socket.prototype.negotiate = function (context = this.context, isClient = this.isClient) { 53 | if (!this.tls) return Promise.resolve() 54 | this.context = context 55 | this.isClient = isClient 56 | const socket = this 57 | loop.update(socket.fd, EPOLLOUT | EPOLLIN | EPOLLET) 58 | //tls.setSNICallback(this.context) 59 | return new Promise ((resolve, reject) => { 60 | if (!this.accepted) { 61 | let r = 0 62 | if (socket.isClient) { 63 | if (context.serverName) { 64 | r = tls.connectSocket(this.fd, this.context, this.buffer, context.serverName) 65 | } else { 66 | r = tls.connectSocket(this.fd, this.context, this.buffer) 67 | } 68 | } else { 69 | r = tls.acceptSocket(this.fd, this.context, this.buffer) 70 | //const serverName = tls.getServerName(this.buffer) 71 | //just.print(serverName) 72 | //if (serverName === 'api.billywhizz.io') { 73 | // this.context = createServerContext('cert2.pem', 'key2.pem') 74 | // tls.setContext(this.buffer, this.context) 75 | //} 76 | } 77 | if (r === 1) { 78 | this.accepted = true 79 | socket.negotiate().then(resolve).catch(reject) 80 | return 81 | } 82 | const err = tls.error(socket.buffer, r) 83 | if (err === tls.SSL_ERROR_WANT_WRITE) { 84 | this.accepted = true 85 | socket.onWritable = () => { 86 | socket.onWritable = null 87 | socket.negotiate().then(resolve).catch(reject) 88 | } 89 | return 90 | } 91 | if (err === tls.SSL_ERROR_WANT_READ) { 92 | this.accepted = true 93 | socket.onReadable = () => { 94 | socket.onReadable = null 95 | socket.negotiate().then(resolve).catch(reject) 96 | } 97 | return 98 | } 99 | socket.error = new Error(`TLS Error A ${err}`) 100 | resolve() 101 | return 102 | } 103 | if (!this.handshake) { 104 | const r = tls.handshake(socket.buffer) 105 | if (r === 1) { 106 | this.handshake = true 107 | loop.update(socket.fd, socket.events) 108 | resolve() 109 | return 110 | } 111 | const err = tls.error(socket.buffer, r) 112 | if (err === tls.SSL_ERROR_WANT_WRITE) { 113 | socket.onWritable = () => { 114 | socket.onWritable = null 115 | socket.negotiate().then(resolve).catch(reject) 116 | } 117 | return 118 | } 119 | if (err === tls.SSL_ERROR_WANT_READ) { 120 | socket.onReadable = () => { 121 | socket.onReadable = null 122 | socket.negotiate().then(resolve).catch(reject) 123 | } 124 | return 125 | } 126 | socket.error = new Error(`TLS Error B ${err}`) 127 | resolve() 128 | return 129 | } 130 | }) 131 | } 132 | 133 | // required 134 | Socket.prototype.shutdown = function () { 135 | return tls.shutdown(this.buffer) 136 | } 137 | 138 | // required 139 | Socket.prototype.free = function () { 140 | return tls.free(this.buffer) 141 | } 142 | 143 | function createSecureSocket (type = AF_INET, flags = SOCK_STREAM | SOCK_NONBLOCK) { 144 | return createSocket(type, flags).setSecure() 145 | } 146 | 147 | function createServerContext (cert = 'cert.pem', key = 'key.pem') { 148 | return tls.serverContext(new ArrayBuffer(0), cert, key) 149 | } 150 | 151 | function createClientContext (serverName = '', ciphers = '') { 152 | const context = tls.clientContext(new ArrayBuffer(0)) 153 | if (ciphers) { 154 | const r = tls.setCiphers(context, ciphers) 155 | just.print(`setCiphers ${r}`) 156 | } 157 | context.serverName = serverName 158 | return context 159 | } 160 | 161 | module.exports = { createSecureSocket, createServerContext, createClientContext } 162 | -------------------------------------------------------------------------------- /http/parsers.js: -------------------------------------------------------------------------------- 1 | const { http } = just.library('http') 2 | const { 3 | getResponses, 4 | parseRequestsHandle, 5 | parseResponsesHandle, 6 | createHandle, 7 | getStatusCode, 8 | getHeaders, 9 | getRequests, 10 | getUrl 11 | } = http 12 | 13 | const free = [] 14 | 15 | // todo: use picohttpparser for this 16 | function chunkedParser (buf) { 17 | let inHeader = true 18 | let offset = 0 19 | let chunkLen = 0 20 | let ending = false 21 | const digits = [] 22 | const u8 = new Uint8Array(buf) 23 | function parse (bytes) { 24 | offset = buf.offset 25 | while (bytes) { 26 | if (inHeader) { 27 | const c = u8[offset] 28 | offset++ 29 | bytes-- 30 | if (c === 13) { 31 | continue 32 | } else if (c === 10) { 33 | if (ending) { 34 | buf.offset = offset 35 | parser.onEnd() 36 | ending = false 37 | continue 38 | } 39 | if (digits.length) { 40 | chunkLen = parseInt(`0x${digits.join('')}`) 41 | if (chunkLen > 0) { 42 | inHeader = false 43 | } else if (chunkLen === 0) { 44 | ending = true 45 | } 46 | digits.length = 0 47 | } 48 | continue 49 | } else if ((c > 47 && c < 58)) { 50 | digits.push(String.fromCharCode(c)) 51 | continue 52 | } else if ((c > 96 && c < 103)) { 53 | digits.push(String.fromCharCode(c)) 54 | continue 55 | } else if ((c > 64 && c < 71)) { 56 | digits.push(String.fromCharCode(c)) 57 | continue 58 | } else { 59 | just.print('BAD_CHAR') 60 | } 61 | just.print('OOB:') 62 | just.print(`c ${c}`) 63 | just.print(`bytes ${bytes}`) 64 | just.print(`offset ${offset}`) 65 | just.print(`chunkLen ${chunkLen}`) 66 | throw new Error('OOB') 67 | } else { 68 | if (bytes >= chunkLen) { 69 | buf.offset = offset 70 | parser.onData(chunkLen) 71 | inHeader = true 72 | offset += chunkLen 73 | bytes -= chunkLen 74 | chunkLen = 0 75 | } else { 76 | buf.offset = offset 77 | parser.onData(bytes) 78 | chunkLen -= bytes 79 | bytes = 0 80 | } 81 | } 82 | buf.offset = offset 83 | } 84 | } 85 | function reset () { 86 | 87 | } 88 | const parser = { parse, reset } 89 | return parser 90 | } 91 | 92 | function requestParser (buffer) { 93 | if (free.length) { 94 | const parser = free.shift() 95 | parser.buffer.offset = 0 96 | return parser 97 | } 98 | const info = new ArrayBuffer(4) 99 | const dv = new DataView(info) 100 | const parser = createHandle(buffer, info) 101 | function parse (bytes, off = 0) { 102 | const { offset } = buffer 103 | parseRequestsHandle(parser, offset + bytes, off) 104 | const r = dv.getUint32(0, true) 105 | const count = r & 0xff 106 | const remaining = r >> 16 107 | if (count > 0) { 108 | parser.onRequests(count) 109 | } 110 | if (remaining > 0) { 111 | const start = offset + bytes - remaining 112 | const len = remaining 113 | if (start > offset) { 114 | buffer.copyFrom(buffer, 0, len, start) 115 | } 116 | buffer.offset = len 117 | return 118 | } 119 | buffer.offset = 0 120 | } 121 | buffer.offset = 0 122 | parser.parse = parse 123 | // TODO: fix this - expects an array of count 4 element arrays as second argument 124 | parser.get = count => { 125 | const requests = [[]] 126 | getRequests(count, requests) 127 | return requests.map(req => { 128 | const [path, version, methodLen, headers] = req 129 | return { path, version, methodLen, headers } 130 | }) 131 | } 132 | parser.url = index => getUrl(index) 133 | parser.headers = index => { 134 | const headers = {} 135 | getHeaders(index, headers) 136 | return headers 137 | } 138 | parser.free = () => free.push(parser) 139 | return parser 140 | } 141 | 142 | function responseParser (buffer) { 143 | if (free.length) { 144 | const parser = free.shift() 145 | parser.buffer.offset = 0 146 | return parser 147 | } 148 | const info = new ArrayBuffer(4) 149 | const dv = new DataView(info) 150 | const parser = createHandle(buffer, info) 151 | function parse (bytes, off = 0) { 152 | parseResponsesHandle(parser, bytes, off) 153 | const r = dv.getUint32(0, true) 154 | const count = r & 0xff 155 | const remaining = r >> 16 156 | just.print(`count ${count} remaining ${remaining}`) 157 | if (count > 0) { 158 | parser.onResponses(count, remaining) 159 | } 160 | buffer.offset = 0 161 | } 162 | buffer.offset = 0 163 | parser.parse = parse 164 | // TODO: fix this - expects an array of count 4 element arrays as second argument 165 | parser.get = count => { 166 | const responses = [[]] 167 | getResponses(count, responses) 168 | return responses.map(res => { 169 | const [version, statusCode, statusMessage, headers] = res 170 | return { version, statusCode, statusMessage, headers } 171 | }) 172 | } 173 | parser.status = index => getStatusCode(index) 174 | parser.headers = index => { 175 | const headers = {} 176 | getHeaders(index, headers) 177 | return headers 178 | } 179 | parser.free = () => free.push(parser) 180 | return parser 181 | } 182 | 183 | const [HTTP_REQUEST, HTTP_RESPONSE, HTTP_CHUNKED] = [0, 1, 2] 184 | const create = { [HTTP_CHUNKED]: chunkedParser, [HTTP_REQUEST]: requestParser, [HTTP_RESPONSE]: responseParser } 185 | 186 | function createParser (buffer, type = HTTP_REQUEST) { 187 | return create[type](buffer) 188 | } 189 | 190 | module.exports = { createParser, HTTP_RESPONSE, HTTP_REQUEST, HTTP_CHUNKED } 191 | -------------------------------------------------------------------------------- /unix/unix.js: -------------------------------------------------------------------------------- 1 | const { sys, net } = just 2 | const { EPOLLIN, EPOLLERR, EPOLLHUP, EPOLLOUT } = just.loop 3 | const { O_NONBLOCK, SOMAXCONN, SOCK_STREAM, SOL_SOCKET, AF_UNIX, SOCK_NONBLOCK, SO_ERROR } = net 4 | const { loop } = just.factory 5 | const { unlink } = just.fs 6 | 7 | const readableMask = EPOLLIN | EPOLLERR | EPOLLHUP 8 | const readableWritableMask = EPOLLIN | EPOLLERR | EPOLLHUP | EPOLLOUT 9 | 10 | function createServer (path) { 11 | const server = { path } 12 | const sockets = {} 13 | 14 | function closeSocket (sock) { 15 | if (sock.closing) return 16 | const { fd } = sock 17 | sock.closing = true 18 | sock.onClose && sock.onClose(sock) 19 | delete sockets[fd] 20 | loop.remove(fd) 21 | net.close(fd) 22 | } 23 | 24 | function onConnect (fd, event) { 25 | if (event & EPOLLERR || event & EPOLLHUP) { 26 | return closeSocket({ fd }) 27 | } 28 | const clientfd = net.accept(fd) 29 | const socket = sockets[clientfd] = { fd: clientfd } 30 | loop.add(clientfd, (fd, event) => { 31 | if (event & EPOLLERR || event & EPOLLHUP) { 32 | closeSocket(socket) 33 | return 34 | } 35 | const { offset } = buffer 36 | // TODO: it would be better if we raised a readable even and the client 37 | // did the reading and handled any errors. otherwise the error becomes 38 | // disassociated from the read and has to be sent in an anonymous 39 | // onClose/onError callback 40 | const bytes = net.recv(fd, buffer, offset, byteLength - offset) 41 | if (bytes > 0) { 42 | socket.onData(bytes) 43 | return 44 | } 45 | if (bytes < 0) { 46 | const errno = sys.errno() 47 | if (errno === net.EAGAIN) return 48 | just.error(`recv error: ${sys.strerror(errno)} (${errno})`) 49 | } 50 | closeSocket(socket) 51 | }) 52 | let flags = sys.fcntl(clientfd, sys.F_GETFL, 0) 53 | flags |= O_NONBLOCK 54 | sys.fcntl(clientfd, sys.F_SETFL, flags) 55 | loop.update(clientfd, readableMask) 56 | socket.write = (buf, len = byteLength, off = 0) => { 57 | const written = net.send(clientfd, buf, len, off) 58 | if (written > 0) { 59 | return written 60 | } 61 | if (written < 0) { 62 | const errno = sys.errno() 63 | if (errno === net.EAGAIN) return written 64 | just.error(`write error (${clientfd}): ${sys.strerror(errno)} (${errno})`) 65 | } 66 | if (written === 0) { 67 | just.error(`zero write ${clientfd}`) 68 | } 69 | return written 70 | } 71 | socket.writeString = str => net.sendString(clientfd, str) 72 | socket.close = () => closeSocket(socket) 73 | const buffer = server.onConnect(socket) 74 | const byteLength = buffer.byteLength 75 | buffer.offset = 0 76 | } 77 | 78 | function listen (maxconn = SOMAXCONN) { 79 | const r = net.listen(sockfd, maxconn) 80 | if (r === 0) loop.add(sockfd, onConnect) 81 | return r 82 | } 83 | server.listen = listen 84 | server.close = () => net.close(sockfd) 85 | server.bind = () => net.bind(sockfd, path) 86 | const sockfd = net.socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0) 87 | unlink(path) 88 | net.bind(sockfd, path) 89 | return server 90 | } 91 | 92 | function createClient (path) { 93 | const sock = { path, connected: false } 94 | let fd 95 | let byteLength = 0 96 | 97 | function closeSocket () { 98 | if (sock.closing) return 99 | sock.closing = true 100 | sock.onClose && sock.onClose(sock) 101 | loop.remove(fd) 102 | net.close(fd) 103 | } 104 | 105 | function handleRead (fd, event) { 106 | const { offset } = buffer 107 | const bytes = net.recv(fd, buffer, offset, byteLength - offset) 108 | if (bytes > 0) { 109 | sock.onData(bytes) 110 | return 111 | } 112 | if (bytes < 0) { 113 | const errno = sys.errno() 114 | if (errno === net.EAGAIN) return 115 | just.print(`recv error: ${sys.strerror(errno)} (${errno})`) 116 | } 117 | closeSocket(sock) 118 | } 119 | 120 | function handleError (fd, event) { 121 | const errno = net.getsockopt(fd, SOL_SOCKET, SO_ERROR) 122 | if (!sock.connected) { 123 | sock.onConnect(new Error(`${errno} : ${just.sys.strerror(errno)}`)) 124 | } 125 | } 126 | 127 | function handleWrite (fd, event) { 128 | if (!sock.connected) { 129 | let flags = sys.fcntl(fd, sys.F_GETFL, 0) 130 | flags |= O_NONBLOCK 131 | sys.fcntl(fd, sys.F_SETFL, flags) 132 | loop.update(fd, readableMask) 133 | buffer = sock.onConnect(null, sock) 134 | byteLength = buffer.byteLength 135 | buffer.offset = 0 136 | sock.connected = true 137 | } 138 | } 139 | 140 | function onSocketEvent (fd, event) { 141 | if (event & EPOLLERR || event & EPOLLHUP) { 142 | handleError(fd, event) 143 | closeSocket() 144 | return 145 | } 146 | if (event & EPOLLIN) { 147 | handleRead(fd, event) 148 | } 149 | if (event & EPOLLOUT) { 150 | handleWrite(fd, event) 151 | } 152 | } 153 | 154 | sock.write = (buf, len = buf.byteLength, off = 0) => { 155 | const written = net.send(fd, buf, len, off) 156 | if (written > 0) { 157 | return written 158 | } 159 | if (written < 0) { 160 | const errno = sys.errno() 161 | if (errno === net.EAGAIN) return written 162 | just.error(`write error (${fd}): ${sys.strerror(errno)} (${errno})`) 163 | } 164 | if (written === 0) { 165 | just.error(`zero write ${fd}`) 166 | } 167 | return written 168 | } 169 | sock.writeString = str => net.sendString(fd, str) 170 | 171 | sock.close = () => closeSocket(sock) 172 | 173 | function connect () { 174 | fd = net.socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0) 175 | loop.add(fd, onSocketEvent, readableWritableMask) 176 | net.connect(fd, path) 177 | sock.fd = fd 178 | return sock 179 | } 180 | 181 | let buffer 182 | sock.connect = connect 183 | return sock 184 | } 185 | 186 | module.exports = { createServer, createClient } 187 | -------------------------------------------------------------------------------- /pg/common.js: -------------------------------------------------------------------------------- 1 | const constants = { 2 | AuthenticationMD5Password: 5, 3 | formats: { 4 | Text: 0, 5 | Binary: 1 6 | }, 7 | fieldTypes: { 8 | BOOLOID: 16, 9 | BYTEAOID: 17, 10 | CHAROID: 18, 11 | NAMEOID: 19, 12 | INT8OID: 20, 13 | INT2OID: 21, 14 | INT2VECTOROID: 22, 15 | INT4OID: 23, 16 | REGPROCOID: 24, 17 | TEXTOID: 25, 18 | OIDOID: 26, 19 | TIDOID: 27, 20 | XIDOID: 28, 21 | CIDOID: 29, 22 | OIDVECTOROID: 30, 23 | JSONOID: 114, 24 | XMLOID: 142, 25 | PGNODETREEOID: 194, 26 | PGNDISTINCTOID: 3361, 27 | PGDEPENDENCIESOID: 3402, 28 | PGMCVLISTOID: 5017, 29 | PGDDLCOMMANDOID: 32, 30 | POINTOID: 600, 31 | LSEGOID: 601, 32 | PATHOID: 602, 33 | BOXOID: 603, 34 | POLYGONOID: 604, 35 | LINEOID: 628, 36 | FLOAT4OID: 700, 37 | FLOAT8OID: 701, 38 | UNKNOWNOID: 705, 39 | CIRCLEOID: 718, 40 | CASHOID: 790, 41 | MACADDROID: 829, 42 | INETOID: 869, 43 | CIDROID: 650, 44 | MACADDR8OID: 774, 45 | ACLITEMOID: 1033, 46 | BPCHAROID: 1042, 47 | VARCHAROID: 1043, 48 | DATEOID: 1082, 49 | TIMEOID: 1083, 50 | TIMESTAMPOID: 1114, 51 | TIMESTAMPTZOID: 1184, 52 | INTERVALOID: 1186, 53 | TIMETZOID: 1266, 54 | BITOID: 1560, 55 | VARBITOID: 1562, 56 | NUMERICOID: 1700, 57 | REFCURSOROID: 1790, 58 | REGPROCEDUREOID: 2202, 59 | REGOPEROID: 2203, 60 | REGOPERATOROID: 2204, 61 | REGCLASSOID: 2205, 62 | REGTYPEOID: 2206, 63 | REGROLEOID: 4096, 64 | REGNAMESPACEOID: 4089, 65 | UUIDOID: 2950, 66 | LSNOID: 3220, 67 | TSVECTOROID: 3614, 68 | GTSVECTOROID: 3642, 69 | TSQUERYOID: 3615, 70 | REGCONFIGOID: 3734, 71 | REGDICTIONARYOID: 3769, 72 | JSONBOID: 3802, 73 | JSONPATHOID: 4072, 74 | TXID_SNAPSHOTOID: 2970, 75 | INT4RANGEOID: 3904, 76 | NUMRANGEOID: 3906, 77 | TSRANGEOID: 3908, 78 | TSTZRANGEOID: 3910, 79 | DATERANGEOID: 3912, 80 | INT8RANGEOID: 3926, 81 | RECORDOID: 2249, 82 | RECORDARRAYOID: 2287, 83 | CSTRINGOID: 2275, 84 | ANYOID: 2276, 85 | ANYARRAYOID: 2277, 86 | VOIDOID: 2278, 87 | TRIGGEROID: 2279, 88 | EVTTRIGGEROID: 3838, 89 | LANGUAGE_HANDLEROID: 2280, 90 | INTERNALOID: 2281, 91 | OPAQUEOID: 2282, 92 | ANYELEMENTOID: 2283, 93 | ANYNONARRAYOID: 2776, 94 | ANYENUMOID: 3500, 95 | FDW_HANDLEROID: 3115, 96 | INDEX_AM_HANDLEROID: 325, 97 | TSM_HANDLEROID: 3310, 98 | TABLE_AM_HANDLEROID: 269, 99 | ANYRANGEOID: 3831, 100 | BOOLARRAYOID: 1000, 101 | BYTEAARRAYOID: 1001, 102 | CHARARRAYOID: 1002, 103 | NAMEARRAYOID: 1003, 104 | INT8ARRAYOID: 1016, 105 | INT2ARRAYOID: 1005, 106 | INT2VECTORARRAYOID: 1006, 107 | INT4ARRAYOID: 1007, 108 | REGPROCARRAYOID: 1008, 109 | TEXTARRAYOID: 1009, 110 | OIDARRAYOID: 1028, 111 | TIDARRAYOID: 1010, 112 | XIDARRAYOID: 1011, 113 | CIDARRAYOID: 1012, 114 | OIDVECTORARRAYOID: 1013, 115 | JSONARRAYOID: 199, 116 | XMLARRAYOID: 143, 117 | POINTARRAYOID: 1017, 118 | LSEGARRAYOID: 1018, 119 | PATHARRAYOID: 1019, 120 | BOXARRAYOID: 1020, 121 | POLYGONARRAYOID: 1027, 122 | LINEARRAYOID: 629, 123 | FLOAT4ARRAYOID: 1021, 124 | FLOAT8ARRAYOID: 1022, 125 | CIRCLEARRAYOID: 719, 126 | MONEYARRAYOID: 791, 127 | MACADDRARRAYOID: 1040, 128 | INETARRAYOID: 1041, 129 | CIDRARRAYOID: 651, 130 | MACADDR8ARRAYOID: 775, 131 | ACLITEMARRAYOID: 1034, 132 | BPCHARARRAYOID: 1014, 133 | VARCHARARRAYOID: 1015, 134 | DATEARRAYOID: 1182, 135 | TIMEARRAYOID: 1183, 136 | TIMESTAMPARRAYOID: 1115, 137 | TIMESTAMPTZARRAYOID: 1185, 138 | INTERVALARRAYOID: 1187, 139 | TIMETZARRAYOID: 1270, 140 | BITARRAYOID: 1561, 141 | VARBITARRAYOID: 1563, 142 | NUMERICARRAYOID: 1231, 143 | REFCURSORARRAYOID: 2201, 144 | REGPROCEDUREARRAYOID: 2207, 145 | REGOPERARRAYOID: 2208, 146 | REGOPERATORARRAYOID: 2209, 147 | REGCLASSARRAYOID: 2210, 148 | REGTYPEARRAYOID: 2211, 149 | REGROLEARRAYOID: 4097, 150 | REGNAMESPACEARRAYOID: 4090, 151 | UUIDARRAYOID: 2951, 152 | PG_LSNARRAYOID: 3221, 153 | TSVECTORARRAYOID: 3643, 154 | GTSVECTORARRAYOID: 3644, 155 | TSQUERYARRAYOID: 3645, 156 | REGCONFIGARRAYOID: 3735, 157 | REGDICTIONARYARRAYOID: 3770, 158 | JSONBARRAYOID: 3807, 159 | JSONPATHARRAYOID: 4073, 160 | TXID_SNAPSHOTARRAYOID: 2949, 161 | INT4RANGEARRAYOID: 3905, 162 | NUMRANGEARRAYOID: 3907, 163 | TSRANGEARRAYOID: 3909, 164 | TSTZRANGEARRAYOID: 3911, 165 | DATERANGEARRAYOID: 3913, 166 | INT8RANGEARRAYOID: 3927, 167 | CSTRINGARRAYOID: 1263 168 | }, 169 | messageTypes: { 170 | AuthenticationOk: 82, 171 | ErrorResponse: 69, 172 | RowDescription: 84, 173 | CommandComplete: 67, 174 | ParseComplete: 49, 175 | CloseComplete: 51, 176 | BindComplete: 50, 177 | ReadyForQuery: 90, 178 | BackendKeyData: 75, 179 | ParameterStatus: 83, 180 | ParameterDescription: 116, 181 | DataRow: 68, 182 | NoData: 110, 183 | NoticeResponse: 78, 184 | NotificationResponse: 65 185 | }, 186 | messageFields: { 187 | 83: 'severity', 188 | 67: 'sqlstate', 189 | 77: 'message', 190 | 68: 'detail', 191 | 72: 'hint', 192 | 80: 'position', 193 | 113: 'internalquery', 194 | 87: 'where', 195 | 115: 'schema', 196 | 116: 'table', 197 | 99: 'column', 198 | 100: 'datatype', 199 | 110: 'constraint', 200 | 70: 'filename', 201 | 76: 'line', 202 | 82: 'routine' 203 | }, 204 | PG_VERSION: 0x00030000 205 | } 206 | 207 | constants.messageNames = {} 208 | Object.keys(constants.messageTypes).forEach(k => { 209 | constants.messageNames[constants.messageTypes[k]] = k 210 | }) 211 | 212 | constants.BinaryInt = { 213 | format: constants.formats.Binary, 214 | oid: constants.fieldTypes.INT4OID 215 | } 216 | 217 | constants.VarChar = { 218 | format: constants.formats.Text, 219 | oid: constants.fieldTypes.VARCHAROID 220 | } 221 | 222 | function readCString (u8, off) { 223 | const start = off 224 | const len = u8.length 225 | while (u8[off] !== 0 && off < len) off++ 226 | return u8.buffer.readString(off - start, start) 227 | } 228 | 229 | module.exports = { readCString, constants } 230 | -------------------------------------------------------------------------------- /tcp/tcp.js: -------------------------------------------------------------------------------- 1 | 2 | const { sys, net } = just 3 | const { EPOLLIN, EPOLLERR, EPOLLHUP, EPOLLOUT } = just.loop 4 | const { IPPROTO_TCP, O_NONBLOCK, TCP_NODELAY, SO_KEEPALIVE, SOMAXCONN, AF_INET, SOCK_STREAM, SOL_SOCKET, SO_REUSEADDR, SO_REUSEPORT, SOCK_NONBLOCK, SO_ERROR } = net 5 | 6 | const { loop } = just.factory 7 | 8 | const readableMask = EPOLLIN | EPOLLERR | EPOLLHUP 9 | const readableWritableMask = EPOLLIN | EPOLLERR | EPOLLHUP | EPOLLOUT 10 | 11 | function createServer (host = '127.0.0.1', port = 3000) { 12 | const server = { host, port } 13 | const sockets = {} 14 | 15 | function closeSocket (sock) { 16 | if (sock.closing) return 17 | const { fd } = sock 18 | sock.closing = true 19 | sock.onClose && sock.onClose(sock) 20 | delete sockets[fd] 21 | loop.remove(fd) 22 | net.close(fd) 23 | } 24 | 25 | function onConnect (fd, event) { 26 | if (event & EPOLLERR || event & EPOLLHUP) { 27 | return closeSocket({ fd }) 28 | } 29 | const clientfd = net.accept(fd) 30 | const socket = sockets[clientfd] = { fd: clientfd } 31 | net.setsockopt(clientfd, IPPROTO_TCP, TCP_NODELAY, 0) 32 | net.setsockopt(clientfd, SOL_SOCKET, SO_KEEPALIVE, 0) 33 | loop.add(clientfd, (fd, event) => { 34 | if (event & EPOLLERR || event & EPOLLHUP) { 35 | closeSocket(socket) 36 | return 37 | } 38 | const { offset } = buffer 39 | const bytes = net.recv(fd, buffer, offset, byteLength - offset) 40 | if (bytes > 0) { 41 | socket.onData(bytes) 42 | return 43 | } 44 | if (bytes < 0) { 45 | const errno = sys.errno() 46 | if (errno === net.EAGAIN) return 47 | just.error(`recv error: ${sys.strerror(errno)} (${errno})`) 48 | } 49 | closeSocket(socket) 50 | }) 51 | let flags = sys.fcntl(clientfd, sys.F_GETFL, 0) 52 | flags |= O_NONBLOCK 53 | sys.fcntl(clientfd, sys.F_SETFL, flags) 54 | loop.update(clientfd, readableMask) 55 | socket.write = (buf, len = byteLength, off = 0) => { 56 | const written = net.send(clientfd, buf, len, off) 57 | if (written > 0) { 58 | return written 59 | } 60 | if (written < 0) { 61 | const errno = sys.errno() 62 | if (errno === net.EAGAIN) return written 63 | just.error(`write error (${clientfd}): ${sys.strerror(errno)} (${errno})`) 64 | } 65 | if (written === 0) { 66 | just.error(`zero write ${clientfd}`) 67 | } 68 | return written 69 | } 70 | socket.writeString = str => net.sendString(clientfd, str) 71 | socket.close = () => closeSocket(socket) 72 | const buffer = server.onConnect(socket) 73 | const byteLength = buffer.byteLength 74 | buffer.offset = 0 75 | } 76 | 77 | function listen (maxconn = SOMAXCONN) { 78 | const r = net.listen(sockfd, maxconn) 79 | if (r === 0) loop.add(sockfd, onConnect) 80 | return r 81 | } 82 | server.listen = listen 83 | server.close = () => net.close(sockfd) 84 | server.bind = () => net.bind(sockfd, host, port) 85 | 86 | const sockfd = net.socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0) 87 | net.setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, 1) 88 | net.setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, 1) 89 | net.bind(sockfd, host, port) 90 | 91 | return server 92 | } 93 | 94 | function createClient (address = '127.0.0.1', port = 3000) { 95 | const sock = { address, port, connected: false } 96 | let fd 97 | let byteLength = 0 98 | 99 | function closeSocket () { 100 | if (sock.closing) return 101 | sock.closing = true 102 | sock.onClose && sock.onClose(sock) 103 | loop.remove(fd) 104 | net.close(fd) 105 | } 106 | 107 | function handleRead (fd, event) { 108 | const { offset } = buffer 109 | const bytes = net.recv(fd, buffer, offset, byteLength - offset) 110 | if (bytes > 0) { 111 | sock.onData(bytes) 112 | return 113 | } 114 | if (bytes < 0) { 115 | const errno = sys.errno() 116 | if (errno === net.EAGAIN) return 117 | just.print(`recv error: ${sys.strerror(errno)} (${errno})`) 118 | } 119 | closeSocket(sock) 120 | } 121 | 122 | function handleError (fd, event) { 123 | const errno = net.getsockopt(fd, SOL_SOCKET, SO_ERROR) 124 | if (!sock.connected) { 125 | sock.onConnect(new Error(`${errno} : ${just.sys.strerror(errno)}`)) 126 | } 127 | } 128 | 129 | function handleWrite (fd, event) { 130 | if (!sock.connected) { 131 | net.setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, 0) 132 | net.setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, 0) 133 | let flags = sys.fcntl(fd, sys.F_GETFL, 0) 134 | flags |= O_NONBLOCK 135 | sys.fcntl(fd, sys.F_SETFL, flags) 136 | loop.update(fd, readableMask) 137 | buffer = sock.onConnect(null, sock) 138 | byteLength = buffer.byteLength 139 | buffer.offset = 0 140 | sock.connected = true 141 | } 142 | } 143 | 144 | function onSocketEvent (fd, event) { 145 | if (event & EPOLLERR || event & EPOLLHUP) { 146 | handleError(fd, event) 147 | closeSocket() 148 | return 149 | } 150 | if (event & EPOLLIN) { 151 | handleRead(fd, event) 152 | } 153 | if (event & EPOLLOUT) { 154 | handleWrite(fd, event) 155 | } 156 | } 157 | 158 | sock.write = (buf, len = buf.byteLength, off = 0) => { 159 | const written = net.send(fd, buf, len, off) 160 | if (written > 0) { 161 | return written 162 | } 163 | if (written < 0) { 164 | const errno = sys.errno() 165 | if (errno === net.EAGAIN) return written 166 | just.error(`write error (${fd}): ${sys.strerror(errno)} (${errno})`) 167 | } 168 | if (written === 0) { 169 | just.error(`zero write ${fd}`) 170 | } 171 | return written 172 | } 173 | sock.writeString = str => net.sendString(fd, str) 174 | sock.setNoDelay = (on = 1) => { 175 | net.setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, on) 176 | } 177 | sock.setKeepAlive = (on = 1) => { 178 | net.setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, on) 179 | } 180 | sock.close = () => closeSocket(sock) 181 | 182 | function connect () { 183 | fd = net.socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0) 184 | loop.add(fd, onSocketEvent, readableWritableMask) 185 | net.connect(fd, address, port) 186 | sock.fd = fd 187 | return sock 188 | } 189 | 190 | let buffer 191 | sock.connect = connect 192 | return sock 193 | } 194 | 195 | module.exports = { createServer, createClient } 196 | -------------------------------------------------------------------------------- /html/html.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const { readFileBytes } = fs 3 | const path = require('path') 4 | const { baseName } = path 5 | 6 | const rx = [ 7 | [/`/g, '`'], // replace backticks 8 | [/\$\{([^}]+)\}/g, '${$1}'], // replace literal variables - ${x} 9 | [/\n?\s+?([<{])/g, '$1'] 10 | ] 11 | 12 | function sanitize (str, removeWhiteSpace = false) { 13 | if (removeWhiteSpace) { 14 | return str.trim() 15 | .replace(rx[2][0], rx[2][1]) 16 | .replace(rx[0][0], rx[0][1]) 17 | .replace(rx[1][0], rx[1][1]) 18 | } 19 | return str 20 | .replace(rx[0][0], rx[0][1]) 21 | .replace(rx[1][0], rx[1][1]) 22 | } 23 | 24 | class Tokenizer { 25 | constructor () { 26 | this.tokens = [] 27 | } 28 | 29 | tokenize (buf) { 30 | let inDirective = false 31 | let inName = false 32 | let name = [] 33 | let last = '' 34 | let directive 35 | let start = 0 36 | let end = 0 37 | const u8 = new Uint8Array(buf) 38 | for (const b of u8) { 39 | const c = String.fromCharCode(b) 40 | if (inDirective) { 41 | if (c === '}' && last === '}') { 42 | if (name.length) { 43 | directive[directive.name ? 'value' : 'name'] = name.join('') 44 | name = [] 45 | } 46 | this.tokens.push({ type: 'directive', value: directive }) 47 | inDirective = false 48 | start = end + 1 49 | } else if (c !== '}') { 50 | if (inName) { 51 | if (c === ' ') { 52 | directive.name = name.join('') 53 | name = [] 54 | inName = false 55 | } else { 56 | name.push(c) 57 | } 58 | } else { 59 | name.push(c) 60 | } 61 | } 62 | } else { 63 | if (c === '{' && last === '{') { 64 | if (end - start > 2) { 65 | this.tokens.push({ type: 'string', value: buf.readString(end - start - 1, start) }) 66 | } 67 | inDirective = true 68 | directive = {} 69 | inName = true 70 | } 71 | } 72 | last = c 73 | end++ 74 | } 75 | if (end - start > 0) { 76 | this.tokens.push({ type: 'string', value: buf.readString(end - start, start) }) 77 | } 78 | } 79 | } 80 | 81 | class Parser { 82 | constructor (root = '', rawStrings = true) { 83 | this.source = [] 84 | this.args = [] 85 | this.command = '' 86 | this.depth = 0 87 | this.this = 'this' 88 | this.root = root 89 | this.rawStrings = rawStrings 90 | this.plugins = {} 91 | } 92 | 93 | start () { 94 | this.source = [] 95 | this.args = [] 96 | this.command = '' 97 | this.depth = 0 98 | this.this = 'this' 99 | this.source.push("let html = ''") 100 | } 101 | 102 | finish () { 103 | this.source.push('return html') 104 | } 105 | 106 | parse (token) { 107 | const { source } = this 108 | const { type } = token 109 | if (type === 'string') { 110 | if (this.rawStrings) { 111 | source.push(`html += String.raw\`${sanitize(token.value)}\``) 112 | } else { 113 | source.push(`html += "${sanitize(token.value, true)}"`) 114 | } 115 | return 116 | } 117 | const { name, value } = token.value 118 | if (name[0] === '#') { 119 | this.command = name.slice(1) 120 | if (this.command === 'template') { 121 | const fileName = `${this.root}${value}` 122 | const template = readFileBytes(fileName) 123 | const tokenizer = new Tokenizer() 124 | tokenizer.tokenize(template) 125 | for (const token of tokenizer.tokens) { 126 | this.parse(token) 127 | } 128 | return 129 | } 130 | if (this.command === 'code') { 131 | source.push(`html += ${value}`) 132 | return 133 | } 134 | if (this.command === 'arg') { 135 | this.args.push(value) 136 | return 137 | } 138 | if (this.command === 'each') { 139 | this.depth++ 140 | if (value === 'this') { 141 | source.push(`for (const v${this.depth} of ${value}) {`) 142 | } else { 143 | source.push(`for (const v${this.depth} of ${this.this}.${value}) {`) 144 | } 145 | this.this = `v${this.depth}` 146 | return 147 | } 148 | if (this.plugins[this.command]) { 149 | this.plugins[this.command].call(this, token.value) 150 | return 151 | } 152 | if (this.command === 'eachField') { 153 | this.depth++ 154 | if (value === 'this') { 155 | source.push(`for (const v${this.depth} in ${value}) {`) 156 | source.push(`const name = v${this.depth}`) 157 | source.push(`const value = ${value}[v${this.depth}]`) 158 | } else { 159 | source.push(`for (const v${this.depth} in ${this.this}.${value}) {`) 160 | source.push(`const name = v${this.depth}`) 161 | source.push(`const value = ${this.this}.${value}[v${this.depth}]`) 162 | } 163 | this.this = '' 164 | } 165 | return 166 | } 167 | if (name[0] === '/') { 168 | const command = name.slice(1) 169 | if (command === 'each') { 170 | source.push('}') 171 | this.depth-- 172 | } 173 | if (command === 'eachField') { 174 | source.push('}') 175 | this.depth-- 176 | } 177 | this.command = '' 178 | this.this = 'this' 179 | return 180 | } 181 | if (this.this) { 182 | if (name === 'this') { 183 | source.push(`html += ${this.this}`) 184 | } else { 185 | const variable = name.split('.')[0] 186 | if (this.args.some(arg => arg === variable)) { 187 | source.push(`html += ${name}`) 188 | } else { 189 | source.push(`html += ${this.this}.${name}`) 190 | } 191 | } 192 | } else { 193 | source.push(`html += ${name}`) 194 | } 195 | } 196 | 197 | all (tokens) { 198 | this.start() 199 | for (const token of tokens) { 200 | this.parse(token) 201 | } 202 | this.finish() 203 | } 204 | } 205 | 206 | let index = 0 207 | 208 | function compile (template, name = 'template', root = '', opts = {}) { 209 | const { plugins = {}, rawStrings } = opts 210 | const tokenizer = new Tokenizer() 211 | tokenizer.tokenize(template) 212 | const parser = new Parser(root, rawStrings) 213 | parser.plugins = plugins 214 | parser.all(tokenizer.tokens) 215 | const call = just.vm.compile(parser.source.join('\n'), `${name}${index++}.js`, parser.args, []) 216 | return { call, tokenizer, parser, template } 217 | } 218 | 219 | function load (fileName, opts = { rawStrings: true, compile: true, plugins: {} }) { 220 | const template = readFileBytes(fileName) 221 | if (template === -1) return 222 | if (opts.compile) return compile(template, fileName, baseName(fileName), opts).call 223 | return template 224 | } 225 | 226 | module.exports = { compile, load, Tokenizer, Parser, sanitize } 227 | -------------------------------------------------------------------------------- /pg/parser.js: -------------------------------------------------------------------------------- 1 | const { readCString, constants } = require('common.js') 2 | const { messageFields } = constants 3 | 4 | class Parser { 5 | constructor (buffer) { 6 | this.pos = 0 7 | this.header = true 8 | this.buf = buffer 9 | buffer.offset = 0 10 | const dv = new DataView(buffer) 11 | const u8 = new Uint8Array(buffer) 12 | this.dv = dv 13 | this.u8 = u8 14 | const fields = [{}] 15 | this.fields = fields 16 | this.parameters = {} 17 | this.type = 0 18 | this.len = 0 19 | const errors = [] 20 | const notice = {} 21 | this.notice = notice 22 | this.errors = errors 23 | const state = { start: 0, end: 0, rows: 0, running: false } 24 | this.state = state 25 | this.nextRow = 0 26 | this.parseNext = 0 27 | this.byteLength = buffer.byteLength 28 | const { messageTypes } = constants 29 | const parser = this 30 | this.handlers = { 31 | [messageTypes.AuthenticationOk]: (len, off) => { 32 | // R = AuthenticationOk 33 | const method = dv.getInt32(off) 34 | off += 4 35 | if (method === constants.AuthenticationMD5Password) { 36 | parser.salt = buffer.slice(off, off + 4) 37 | off += 4 38 | parser.onAuthenticationOk() 39 | } 40 | return off 41 | }, 42 | [messageTypes.NotificationResponse]: (len, off) => { 43 | // A = NotificationResponse 44 | const notification = {} 45 | notification.pid = dv.getUint32(off) 46 | off += 4 47 | notification.channel = readCString(u8, off) 48 | off += notification.channel.length + 1 49 | notification.message = readCString(u8, off) 50 | off += notification.message.length + 1 51 | parser.notification = notification 52 | parser.onNotificationResponse() 53 | parser.notification = {} 54 | return off 55 | }, 56 | [messageTypes.NoticeResponse]: (len, off) => { 57 | // N = NoticeResponse 58 | const notice = {} 59 | let fieldType = u8[off++] 60 | while (fieldType !== 0) { 61 | const val = readCString(u8, off) 62 | notice[messageFields[fieldType]] = val 63 | off += (val.length + 1) 64 | fieldType = u8[off++] 65 | } 66 | parser.notice = notice 67 | parser.onNoticeResponse() 68 | parser.notice = {} 69 | return off 70 | }, 71 | [messageTypes.ErrorResponse]: (len, off) => { 72 | // E = ErrorResponse 73 | errors.length = 0 74 | let fieldType = u8[off++] 75 | while (fieldType !== 0) { 76 | const val = readCString(u8, off) 77 | errors.push({ type: fieldType, val }) 78 | off += (val.length + 1) 79 | fieldType = u8[off++] 80 | } 81 | parser.onErrorResponse() 82 | return off 83 | }, 84 | [messageTypes.RowDescription]: (len, off) => { 85 | // T = RowDescription 86 | const fieldCount = dv.getInt16(off) 87 | off += 2 88 | fields.length = 0 89 | for (let i = 0; i < fieldCount; i++) { 90 | const name = readCString(u8, off) 91 | off += name.length + 1 92 | const tid = dv.getInt32(off) 93 | off += 4 94 | const attrib = dv.getInt16(off) 95 | off += 2 96 | const oid = dv.getInt32(off) 97 | off += 4 98 | const size = dv.getInt16(off) 99 | off += 2 100 | const mod = dv.getInt32(off) 101 | off += 4 102 | const format = dv.getInt16(off) 103 | off += 2 104 | fields.push({ name, tid, attrib, oid, size, mod, format }) 105 | } 106 | parser.onRowDescription() 107 | return off 108 | }, 109 | [messageTypes.CommandComplete]: (len, off) => { 110 | // C = CommandComplete 111 | state.end = off - 5 112 | state.rows = parser.nextRow 113 | state.running = false 114 | off += len - 4 115 | parser.nextRow = 0 116 | parser.onCommandComplete() 117 | return off 118 | }, 119 | [messageTypes.CloseComplete]: (len, off) => { 120 | // 3 = CloseComplete 121 | parser.onCloseComplete() 122 | return off + len - 4 123 | }, 124 | [messageTypes.ParseComplete]: (len, off) => { 125 | // 1 = ParseComplete 126 | off += len - 4 127 | parser.onParseComplete() 128 | return off 129 | }, 130 | [messageTypes.BindComplete]: (len, off) => { 131 | // 2 = BindComplete 132 | off += len - 4 133 | parser.onBindComplete() 134 | state.rows = 0 135 | state.start = off 136 | state.running = true 137 | return off 138 | }, 139 | [messageTypes.ReadyForQuery]: (len, off) => { 140 | // Z = ReadyForQuery 141 | parser.status = u8[off] 142 | parser.onReadyForQuery() 143 | off += len - 4 144 | return off 145 | }, 146 | [messageTypes.BackendKeyData]: (len, off) => { 147 | // K = BackendKeyData 148 | parser.pid = dv.getUint32(off) 149 | off += 4 150 | parser.key = dv.getUint32(off) 151 | off += 4 152 | parser.onBackendKeyData() 153 | return off 154 | }, 155 | [messageTypes.ParameterStatus]: (len, off) => { 156 | // S = ParameterStatus 157 | const key = readCString(u8, off) 158 | off += (key.length + 1) 159 | const val = readCString(u8, off) 160 | off += val.length + 1 161 | parser.parameters[key] = val 162 | return off 163 | }, 164 | [messageTypes.ParameterDescription]: (len, off) => { 165 | // t = ParameterDescription 166 | const nparams = dv.getInt16(off) 167 | parser.params = [] 168 | off += 2 169 | for (let i = 0; i < nparams; i++) { 170 | parser.params.push(dv.getUint32(off)) 171 | off += 4 172 | } 173 | return off 174 | }, 175 | [messageTypes.DataRow]: (len, off) => { 176 | // D = DataRow 177 | if (this.nextRow === 0) this.state.start = off - 5 178 | parser.nextRow++ 179 | return off + len - 4 180 | }, 181 | 0: (len, off) => { 182 | off += len - 4 183 | return off 184 | } 185 | } 186 | } 187 | 188 | resetBuffer (off, remaining) { 189 | const { buf, state } = this 190 | const queryLen = off - state.start + remaining 191 | buf.copyFrom(buf, 0, queryLen, state.start) 192 | buf.offset = queryLen 193 | this.parseNext = off - state.start 194 | state.start = 0 195 | } 196 | 197 | checkAvailable (off, remaining, want) { 198 | const { buf, byteLength } = this 199 | if (remaining < want) { 200 | if (byteLength - off < 1024) { 201 | this.resetBuffer(off, remaining) 202 | return true 203 | } 204 | buf.offset = off + remaining 205 | this.parseNext = off 206 | return true 207 | } 208 | return false 209 | } 210 | 211 | parse (bytesRead) { 212 | const { buf, parseNext, dv, handlers } = this 213 | let type 214 | let len 215 | let off = parseNext 216 | const end = buf.offset + bytesRead 217 | while (off < end) { 218 | const remaining = end - off 219 | let want = 5 220 | if (this.checkAvailable(off, remaining, want)) return 221 | type = this.type = dv.getUint8(off) 222 | len = this.len = dv.getUint32(off + 1) 223 | want = len + 1 224 | if (this.checkAvailable(off, remaining, want)) return 225 | off += 5 226 | off = (handlers[type] || handlers[0])(len, off) 227 | } 228 | this.parseNext = buf.offset = 0 229 | } 230 | 231 | readStatus () { 232 | return readCString(this.u8, this.state.start + 5) 233 | } 234 | 235 | free () { 236 | const { state, fields, errors } = this 237 | fields.length = 0 238 | errors.length = 0 239 | this.parameters = {} 240 | this.nextRow = 0 241 | this.parseNext = 0 242 | state.start = state.end = state.rows = 0 243 | state.running = false 244 | } 245 | 246 | onAuthenticationOk () {} 247 | onNotificationResponse () {} 248 | onNoticeResponse () {} 249 | onErrorResponse () {} 250 | onRowDescription () {} 251 | onCommandComplete () {} 252 | onCloseComplete () {} 253 | onParseComplete () {} 254 | onBindComplete () {} 255 | onReadyForQuery () {} 256 | onBackendKeyData () {} 257 | } 258 | 259 | function createParser (buf) { 260 | return new Parser(buf) 261 | } 262 | 263 | module.exports = { createParser } 264 | -------------------------------------------------------------------------------- /socket/socket.js: -------------------------------------------------------------------------------- 1 | const { net } = just.library('net') 2 | const { epoll } = just.library('epoll') 3 | const { sys } = just.library('sys') 4 | 5 | const { 6 | AF_INET, 7 | AF_UNIX, 8 | SOCK_STREAM, 9 | SOCK_NONBLOCK, 10 | SOL_SOCKET, 11 | IPPROTO_TCP, 12 | TCP_NODELAY, 13 | SO_KEEPALIVE, 14 | SO_REUSEADDR, 15 | SO_REUSEPORT, 16 | EAGAIN, 17 | O_NONBLOCK 18 | } = net 19 | 20 | const { 21 | EPOLLERR, 22 | EPOLLHUP, 23 | EPOLLIN, 24 | EPOLLOUT, 25 | EPOLLET 26 | } = epoll 27 | 28 | const { errno, strerror, fcntl, F_GETFL, F_SETFL } = sys 29 | 30 | const { loop } = just.factory 31 | const { SystemError } = just 32 | 33 | class SocketStats { 34 | constructor () { 35 | this.send = 0 36 | this.recv = 0 37 | } 38 | } 39 | 40 | class Socket { 41 | constructor (fd) { 42 | this.fd = fd 43 | this.connected = false 44 | this.closed = false 45 | this.buffer = new ArrayBuffer(65536) 46 | this.bufLen = this.buffer.byteLength 47 | this.error = null 48 | this.stats = new SocketStats() 49 | this.events = EPOLLIN | EPOLLET 50 | this.flags = 0 51 | this.maxConn = 4096 52 | this.readable = false 53 | this.writable = false 54 | // TLS 55 | this.handshake = false 56 | this.accepted = false 57 | this.context = null 58 | this.isClient = false 59 | this.tls = false 60 | } 61 | 62 | //get serverName () {} 63 | get certificate () {} 64 | 65 | set edgeTriggered (on = true) { 66 | if (on && (this.events & EPOLLET === 0)) { 67 | this.events = EPOLLIN | EPOLLET 68 | return loop.update(this.fd, this.events) 69 | } else if (!on && ((this.events & EPOLLET === EPOLLET))) { 70 | this.events = EPOLLIN 71 | return loop.update(this.fd, this.events) 72 | } 73 | } 74 | 75 | set nonBlocking (on = true) { 76 | let flags = fcntl(this.fd, F_GETFL, 0) 77 | if (flags < 0) return flags 78 | if (on) { 79 | flags = flags | O_NONBLOCK 80 | } else { 81 | flags = flags & ~O_NONBLOCK 82 | } 83 | this.flags = flags 84 | fcntl(this.fd, F_SETFL, this.flags) 85 | } 86 | 87 | setSecure () {} 88 | 89 | recv (off = 0, len = this.bufLen - off) { 90 | const bytes = net.recv(this.fd, this.buffer, off, len) 91 | if (bytes > 0) this.stats.recv += bytes 92 | return bytes 93 | } 94 | 95 | send (buf, len = buf.byteLength, off = 0) { 96 | const bytes = net.send(this.fd, buf, len, off) 97 | if (bytes > 0) this.stats.send += bytes 98 | return bytes 99 | } 100 | 101 | sendString (str) { 102 | const bytes = net.sendString(this.fd, str) 103 | if (bytes > 0) this.stats.send += bytes 104 | return bytes 105 | } 106 | 107 | close () { 108 | if (this.closed) return 109 | if (this.tls) this.shutdown() 110 | this.closed = true 111 | if (this.onReadable) this.onReadable() 112 | loop.remove(this.fd) 113 | this.onClose() 114 | if (this.tls) { 115 | this.free() 116 | this.context = null 117 | } 118 | return net.close(this.fd) 119 | } 120 | 121 | listen () { 122 | const socket = this 123 | const r = loop.add(this.fd, (fd, event) => { 124 | if (event & EPOLLERR || event & EPOLLHUP) { 125 | // TODO 126 | just.print('accept error') 127 | return 128 | } 129 | if (socket.onConnection) socket.onConnection() 130 | }, EPOLLIN | EPOLLET) 131 | if (r < 0) return r 132 | return net.listen(socket.fd, socket.maxConn) 133 | } 134 | 135 | pause () { 136 | this.paused = true 137 | return loop.update(this.fd, EPOLLOUT) 138 | } 139 | 140 | resume () { 141 | this.paused = false 142 | return loop.update(this.fd, this.events) 143 | } 144 | 145 | get blocked () { 146 | this.error = null 147 | const err = errno() 148 | // TODO 149 | if (err === EAGAIN) return true 150 | this.error = new SystemError('socket.error') 151 | return false 152 | } 153 | 154 | negotiate (context = this.context, isClient = this.isClient) {} 155 | 156 | pull (off = 0, len = this.bufLen - off) { 157 | const socket = this 158 | const bytes = socket.recv(off, len) 159 | if (bytes >= 0) return Promise.resolve(bytes) 160 | const err = errno() 161 | if (err === EAGAIN) { 162 | socket.readable = false 163 | return new Promise((resolve, reject) => { 164 | // todo: this will clobber any previous ones 165 | socket.onReadable = () => { 166 | socket.onReadable = null 167 | socket.readable = true 168 | socket.pull(off, len).then(resolve).catch(reject) 169 | } 170 | }) 171 | } 172 | socket.error = new SystemError(`socket.recv (${socket.fd})`) 173 | return Promise.resolve(0) 174 | } 175 | 176 | push (buf, len = buf.byteLength, off = 0) { 177 | const socket = this 178 | const bytes = socket.send(buf, len, off) 179 | if (bytes === len) return Promise.resolve(bytes) 180 | if (bytes > 0 || errno() === EAGAIN) { 181 | socket.writable = false 182 | return new Promise((resolve, reject) => { 183 | // todo: this will clobber any previous ones 184 | socket.onWritable = () => { 185 | socket.onWritable = null 186 | socket.writable = true 187 | if (bytes > 0) { 188 | socket.push(buf, len - bytes, bytes).then(resolve).catch(reject) 189 | return 190 | } 191 | socket.push(buf, len).then(resolve).catch(reject) 192 | } 193 | }) 194 | } 195 | socket.error = new SystemError(`socket.send (${socket.fd})`) 196 | return Promise.resolve(0) 197 | } 198 | 199 | pushString (str, len = String.byteLength(str)) { 200 | const socket = this 201 | const bytes = socket.sendString(str) 202 | if (bytes === len) return bytes 203 | if (bytes > 0 || errno() === EAGAIN) { 204 | socket.writable = false 205 | return new Promise((resolve, reject) => { 206 | socket.onWritable = () => { 207 | socket.onWritable = null 208 | socket.writable = true 209 | socket.pushString(str, len).then(resolve).catch(reject) 210 | } 211 | }) 212 | } 213 | socket.error = new SystemError(`socket.send (${socket.fd})`) 214 | return Promise.resolve(0) 215 | } 216 | 217 | accept () { 218 | const socket = this 219 | return new Promise((resolve, reject) => { 220 | const fd = net.accept(this.fd) 221 | if (fd > 0) { 222 | const sock = new socket.constructor(fd) 223 | if (socket.tls) sock.setSecure() 224 | sock.nonBlocking = true 225 | loop.add(fd, (fd, event) => sock.onSocketEvent(event), socket.events) 226 | resolve(sock) 227 | return 228 | } 229 | socket.onConnection = () => { 230 | socket.onConnection = null 231 | socket.accept().then(resolve).catch(reject) 232 | } 233 | }) 234 | } 235 | 236 | onSocketEvent (event) { 237 | const socket = this 238 | if (event & EPOLLERR || event & EPOLLHUP) { 239 | socket.error = new SystemError(`socket.error (${socket.fd})`) 240 | socket.close() 241 | return 242 | } 243 | if (event & EPOLLIN && socket.onReadable) socket.onReadable() 244 | if (event & EPOLLOUT && socket.onWritable) socket.onWritable() 245 | } 246 | 247 | onConnectEvent (event, resolve, reject) { 248 | const socket = this 249 | if (event & EPOLLERR || event & EPOLLHUP) { 250 | reject(new SystemError('connect')) 251 | socket.close() 252 | return 253 | } 254 | if (!socket.connected) { 255 | socket.connected = true 256 | loop.remove(socket.fd) 257 | loop.add(socket.fd, (fd, event) => socket.onSocketEvent(event), socket.events) 258 | resolve(socket) 259 | } 260 | } 261 | 262 | onConnection () {} 263 | onClose () {} 264 | } 265 | 266 | class UnixSocket extends Socket { 267 | constructor (fd) { 268 | super(fd) 269 | } 270 | 271 | connect (path = 'unix.sock') { 272 | const socket = this 273 | return new Promise((resolve, reject) => { 274 | const r = net.connect(this.fd, path) 275 | if (r < 0 && errno() !== 115) { 276 | reject(new SystemError('connect')) 277 | return 278 | } 279 | loop.add(socket.fd, (fd, event) => { 280 | socket.onConnectEvent(event, resolve, reject) 281 | }, EPOLLIN | EPOLLOUT | EPOLLET) 282 | }) 283 | } 284 | 285 | listen (path = 'unix.sock') { 286 | const r = net.bind(this.fd, path) 287 | if (r < 0) return r 288 | return super.listen() 289 | } 290 | } 291 | 292 | class TCPSocket extends Socket { 293 | constructor (fd) { 294 | super(fd) 295 | this.reusePort = true 296 | this.reuseAddress = true 297 | } 298 | 299 | set noDelay (on = true) { 300 | return net.setsockopt(this.fd, IPPROTO_TCP, TCP_NODELAY, on ? 1 : 0) 301 | } 302 | 303 | set keepAlive (on = true) { 304 | return net.setsockopt(this.fd, SOL_SOCKET, SO_KEEPALIVE, on ? 1 : 0) 305 | } 306 | 307 | set reusePort (on = true) { 308 | return net.setsockopt(this.fd, SOL_SOCKET, SO_REUSEPORT, on ? 1 : 0) 309 | } 310 | 311 | set reuseAddress (on = true) { 312 | return net.setsockopt(this.fd, SOL_SOCKET, SO_REUSEADDR, on ? 1 : 0) 313 | } 314 | 315 | connect (address = '127.0.0.1', port = 3000) { 316 | const socket = this 317 | return new Promise((resolve, reject) => { 318 | const r = net.connect(this.fd, address, port) 319 | if (r < 0 && errno() !== 115) { 320 | reject(new SystemError('connect')) 321 | return 322 | } 323 | loop.add(socket.fd, (fd, event) => { 324 | socket.onConnectEvent(event, resolve, reject) 325 | }, EPOLLIN | EPOLLOUT | EPOLLET) 326 | }) 327 | } 328 | 329 | listen (address = '127.0.0.1', port = 3000) { 330 | const r = net.bind(this.fd, address, port) 331 | if (r < 0) return r 332 | return super.listen() 333 | } 334 | } 335 | 336 | const socketTypes = { 337 | tcp: AF_INET, 338 | unix: AF_UNIX 339 | } 340 | 341 | function createSocket (type = AF_INET, flags = SOCK_STREAM | SOCK_NONBLOCK) { 342 | const fd = net.socket(type, flags, 0) 343 | // todo: handle error 344 | if (type === AF_INET) return new TCPSocket(fd) 345 | return new UnixSocket(fd) 346 | } 347 | 348 | module.exports = { createSocket, Socket, TCPSocket, UnixSocket, socketTypes } 349 | -------------------------------------------------------------------------------- /pg/pg.js: -------------------------------------------------------------------------------- 1 | const { html } = just.library('html') 2 | const { constants } = require('common.js') 3 | const { createParser } = require('parser.js') 4 | const md5 = require('@md5') 5 | 6 | const { messageFields } = constants 7 | const { INT4OID, VARCHAROID } = constants.fieldTypes 8 | 9 | class NameIndex { 10 | constructor () { 11 | this.index = 0 12 | this.vals = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-+' 13 | } 14 | 15 | getId (name) { 16 | const { vals, index } = this 17 | if (index >= vals.length) return name 18 | const id = vals.charCodeAt(index) 19 | this.index++ 20 | return id 21 | } 22 | } 23 | 24 | const nameIndexer = new NameIndex() 25 | 26 | class Protocol { 27 | constructor (buffer, config) { 28 | this.buffer = buffer 29 | this.view = new DataView(buffer) 30 | this.bytes = new Uint8Array(buffer) 31 | this.len = buffer.byteLength 32 | this.params = config.params 33 | this.config = config 34 | this.id = nameIndexer.getId(config.name) 35 | } 36 | 37 | startup (off = 0) { 38 | const { view, buffer, config } = this 39 | const { user, database, version, parameters = [] } = config 40 | view.setInt32(0, 0) 41 | off += 4 42 | view.setInt32(4, version) // protocol version 43 | off += 4 44 | off += buffer.writeString('user', off) 45 | view.setUint8(off++, 0) 46 | off += buffer.writeString(user, off) 47 | view.setUint8(off++, 0) 48 | off += buffer.writeString('database', off) 49 | view.setUint8(off++, 0) 50 | off += buffer.writeString(database, off) 51 | view.setUint8(off++, 0) 52 | for (let i = 0; i < parameters.length; i++) { 53 | const { name, value } = parameters[i] 54 | off += buffer.writeString(name, off) 55 | view.setUint8(off++, 0) 56 | off += buffer.writeString(value, off) 57 | view.setUint8(off++, 0) 58 | } 59 | view.setUint8(off++, 0) 60 | view.setInt32(0, off) 61 | return off 62 | } 63 | 64 | md5auth (off = 0) { 65 | const { view, buffer, config } = this 66 | const { user, pass, salt } = config 67 | const token = `${pass}${user}` 68 | let hash = md5(token) 69 | const plain = new ArrayBuffer(36) 70 | plain.writeString(`md5${hash}`, 0) 71 | const plain2 = new ArrayBuffer(36) 72 | plain2.copyFrom(plain, 0, 32, 3) 73 | plain2.copyFrom(salt, 32, 4) 74 | hash = `md5${md5(plain2)}` 75 | const len = hash.length + 5 76 | view.setUint8(off++, 112) 77 | view.setUint32(off, len) 78 | off += 4 79 | off += buffer.writeString(hash, off) 80 | view.setUint8(off++, 0) 81 | return off 82 | } 83 | 84 | parse (off = 0) { 85 | const { view, buffer, config, id, bytes } = this 86 | const { formats, sql } = config 87 | const flen = formats.length 88 | const len = 9 + String.byteLength(sql) + 1 + (flen * 4) 89 | view.setUint8(off++, 80) // 'P' 90 | view.setUint32(off, len - 1) 91 | off += 4 92 | bytes[off++] = id 93 | view.setUint8(off++, 0) 94 | off += buffer.writeString(sql, off) 95 | view.setUint8(off++, 0) 96 | view.setUint16(off, flen) 97 | off += 2 98 | for (let i = 0; i < flen; i++) { 99 | view.setUint32(off, formats[i].oid) 100 | off += 4 101 | } 102 | return off 103 | } 104 | 105 | describe (off = 0) { 106 | const { view, id, bytes } = this 107 | const len = 8 108 | view.setUint8(off++, 68) // 'D' 109 | view.setUint32(off, len - 1) 110 | off += 4 111 | view.setUint8(off++, 83) // 'S' 112 | bytes[off++] = id 113 | view.setUint8(off++, 0) 114 | return off 115 | } 116 | 117 | bind (off = 0) { 118 | const { view, buffer, config, params, bytes, id } = this 119 | const { formats, portal, fields } = config 120 | const start = off 121 | const flen = formats.length || 0 122 | const plen = params.length || 0 123 | const filen = fields.length 124 | view.setUint8(off++, 66) // 'B' 125 | off += 4 // length - will be filled in later 126 | if (portal.length) { 127 | off += buffer.writeString(portal, off) 128 | } 129 | view.setUint8(off++, 0) 130 | bytes[off++] = id 131 | view.setUint8(off++, 0) 132 | view.setUint16(off, flen) 133 | off += 2 134 | for (let i = 0; i < flen; i++) { 135 | view.setUint16(off, formats[i].format) 136 | off += 2 137 | } 138 | view.setUint16(off, plen) 139 | off += 2 140 | for (let i = 0; i < plen; i++) { 141 | const param = params[i] 142 | if ((formats[i] || formats[0]).format === 1) { 143 | view.setUint32(off, 4) 144 | off += 4 145 | view.setUint32(off, param) 146 | off += 4 147 | } else { 148 | view.setUint32(off, param.length) 149 | off += 4 150 | off += buffer.writeString(param, off) 151 | } 152 | } 153 | if (filen > 0) { 154 | const format = fields[0].format.format 155 | let same = true 156 | for (let i = 1; i < filen; i++) { 157 | if (fields[i].format.format !== format) { 158 | same = false 159 | break 160 | } 161 | } 162 | if (same) { 163 | view.setUint16(off, 1) 164 | off += 2 165 | view.setUint16(off, fields[0].format.format) 166 | off += 2 167 | } else { 168 | view.setUint16(off, filen) 169 | off += 2 170 | for (let i = 0; i < filen; i++) { 171 | view.setUint16(off, fields[i].format.format) 172 | off += 2 173 | } 174 | } 175 | } else { 176 | view.setUint16(off, 0) 177 | off += 2 178 | } 179 | view.setUint32(start + 1, (off - start) - 1) 180 | return off 181 | } 182 | 183 | exec (off = 0) { 184 | const { view, buffer, config } = this 185 | const { portal, maxRows } = config 186 | const len = 6 + portal.length + 4 187 | view.setUint8(off++, 69) // 'E' 188 | view.setUint32(off, len - 1) 189 | off += 4 190 | if (portal.length) off += buffer.writeString(portal, off) 191 | view.setUint8(off++, 0) 192 | view.setUint32(off, maxRows) 193 | off += 4 194 | return off 195 | } 196 | 197 | flush (off = 0) { 198 | const { view } = this 199 | view.setUint8(off++, 72) // 'H' 200 | view.setUint32(off, 4) 201 | off += 4 202 | return off 203 | } 204 | 205 | sync (off = 0) { 206 | const { view } = this 207 | view.setUint8(off++, 83) // 'S' 208 | view.setUint32(off, 4) 209 | off += 4 210 | return off 211 | } 212 | } 213 | 214 | class PGError extends Error { 215 | constructor (errors) { 216 | const err = {} 217 | errors.forEach(e => { 218 | const name = messageFields[e.type] 219 | if (!name) return 220 | err[name] = e.val 221 | }) 222 | super(err.message) 223 | Object.assign(this, err) 224 | this.name = 'PGError' 225 | } 226 | } 227 | 228 | function createError (errors) { 229 | return new PGError(errors) 230 | } 231 | 232 | class Query { 233 | constructor (query, sock) { 234 | this.query = query 235 | this.buffer = sock.buffer 236 | this.parser = sock.parser 237 | this.htmlEscape = html.escape 238 | this.protocol = new Protocol(this.buffer, query) 239 | } 240 | 241 | generate () { 242 | const { query } = this 243 | const { fields } = query 244 | const source = [] 245 | source.push(' const { parser, htmlEscape } = this') 246 | source.push(' const { state, dv, buf, u8 } = parser') 247 | source.push(' const { start, rows } = state') 248 | source.push(' let off = start + 7') 249 | source.push(' let len = 0') 250 | source.push(' if (rows === 1) {') 251 | for (const field of fields) { 252 | const { name, format } = field 253 | if (format.oid === INT4OID) { 254 | if (format.format === constants.formats.Binary) { 255 | source.push(` const ${name} = dv.getInt32(off + 4)`) 256 | source.push(' off += 8') 257 | } else { 258 | source.push(' len = dv.getUint32(off)') 259 | source.push(' off += 4') 260 | source.push(` const ${name} = parseInt(buf.readString(len, off), 10)`) 261 | source.push(' off += len') 262 | } 263 | } else if (format.oid === VARCHAROID) { 264 | source.push(' len = dv.getUint32(off)') 265 | source.push(' off += 4') 266 | if (format.format === constants.formats.Binary) { 267 | source.push(` const ${name} = buf.slice(off, off + len)`) 268 | } else { 269 | source.push(` const ${name} = buf.readString(len, off)`) 270 | } 271 | source.push(' off += len') 272 | } 273 | } 274 | source.push(` return { ${fields.map(f => f.name).join(', ')} }`) 275 | source.push(' }') 276 | source.push(' const result = []') 277 | source.push(' off = start + 7') 278 | source.push(' for (let i = 0; i < rows; i++) {') 279 | for (const field of fields) { 280 | const { name, format } = field 281 | if (format.oid === INT4OID) { 282 | if (format.format === constants.formats.Binary) { 283 | source.push(` const ${name} = dv.getInt32(off + 4)`) 284 | source.push(' off += 8') 285 | } else { 286 | source.push(' len = dv.getInt32(off)') 287 | source.push(' off += 4') 288 | source.push(` const ${name} = parseInt(buf.readString(len, off), 10)`) 289 | source.push(' off += len') 290 | } 291 | } else if (format.oid === VARCHAROID) { 292 | source.push(' len = dv.getInt32(off)') 293 | source.push(' off += 4') 294 | if (format.format === constants.formats.Binary) { 295 | source.push(` const ${name} = buf.slice(len, off)`) 296 | } else { 297 | if (field.htmlEscape) { 298 | source.push(` const ${name} = htmlEscape(buf, len, off)`) 299 | } else { 300 | source.push(` const ${name} = buf.readString(len, off)`) 301 | } 302 | } 303 | source.push(' off += len') 304 | } 305 | } 306 | source.push(' if (u8[off] === 84) {') 307 | source.push(' len = dv.getUint32(off + 1)') 308 | source.push(' off += len') 309 | source.push(' }') 310 | source.push(` result.push({ ${fields.map(f => f.name).join(', ')} })`) 311 | source.push(' off += 7') 312 | source.push(' }') 313 | source.push(' return result') 314 | const read = source.join('\n').trim() 315 | if (read.length) this.read = just.vm.compile(read, `${query.name}r.js`, [], []) 316 | return this 317 | } 318 | } 319 | 320 | class RingBuffer { 321 | constructor () { 322 | this.rb = new Array(65536) 323 | this.head = new Uint16Array(1) 324 | this.tail = new Uint16Array(1) 325 | this.length = 0 326 | } 327 | 328 | at (index) { 329 | return this.rb[this.head[0] + index] 330 | } 331 | 332 | push (fn) { 333 | if (this.length === 65536) this.shift() 334 | this.rb[this.tail[0]++] = fn 335 | this.length++ 336 | } 337 | 338 | shift () { 339 | this.length-- 340 | return this.rb[this.head[0]++] 341 | } 342 | } 343 | 344 | class PGCommand { 345 | constructor (query, resolve, reject, args = [], type = 'exec') { 346 | this.query = query 347 | this.type = type 348 | this.resolve = resolve 349 | this.reject = reject 350 | this.args = args 351 | } 352 | } 353 | 354 | const commandCache = new RingBuffer() 355 | 356 | function getCommand (query, resolve, reject, args) { 357 | if (commandCache.length) { 358 | const cmd = commandCache.shift() 359 | cmd.query = query 360 | cmd.resolve = resolve 361 | cmd.reject = reject 362 | cmd.args = args 363 | return cmd 364 | } 365 | return new PGCommand(query, resolve, reject, args) 366 | } 367 | 368 | class PGSocket { 369 | constructor (sock, db) { 370 | this.sock = sock 371 | this.db = db 372 | this.off = 0 373 | sock.edgeTriggered = false 374 | const parser = this.parser = createParser(sock.buffer) 375 | const pending = this.pending = new RingBuffer() 376 | this.buffer = new ArrayBuffer(64 * 1024) 377 | this.protocol = new Protocol(this.buffer, db) 378 | this.len = this.buffer.byteLength 379 | this.flushing = false 380 | parser.onErrorResponse = () => { 381 | if (pending.length) { 382 | const cmd = pending.shift() 383 | cmd.reject(createError(parser.errors)) 384 | commandCache.push(cmd) 385 | } 386 | } 387 | parser.onBackendKeyData = () => { 388 | const cmd = pending.shift() 389 | cmd.resolve() 390 | commandCache.push(cmd) 391 | } 392 | parser.onAuthenticationOk = () => { 393 | const cmd = pending.shift() 394 | cmd.resolve() 395 | commandCache.push(cmd) 396 | } 397 | parser.onParseComplete = () => { 398 | const cmd = pending.shift() 399 | const { query, resolve } = cmd 400 | resolve(this.wrap(query.generate())) 401 | commandCache.push(cmd) 402 | } 403 | parser.onCommandComplete = () => { 404 | const cmd = pending.shift() 405 | const { query, resolve } = cmd 406 | resolve(query.read()) 407 | commandCache.push(cmd) 408 | } 409 | } 410 | 411 | compile (config) { 412 | const { pending } = this 413 | const query = new Query(config, this) 414 | const { protocol } = query 415 | return new Promise((resolve, reject) => { 416 | this.off = protocol.parse(this.off) 417 | this.off = protocol.describe(this.off) 418 | this.off = protocol.flush(this.off) 419 | pending.push(getCommand(query, resolve, reject, [])) 420 | this.flush() 421 | }) 422 | } 423 | 424 | flush () { 425 | if (this.flushing || this.off === 0) return 0 426 | const { sock, off } = this 427 | this.flushing = true 428 | const written = sock.send(this.buffer, this.off, 0) 429 | if (written === off) { 430 | this.off = 0 431 | this.flushing = false 432 | return written 433 | } 434 | if (written === 0) { 435 | this.flushing = false 436 | sock.close() 437 | return -1 438 | } 439 | if (written < off || (written < 0 && sock.blocked)) { 440 | if (written > 0) { 441 | this.buffer.copyFrom(this.buffer, 0, off - written, written) 442 | this.off = off - written 443 | } 444 | sock.onWritable = () => { 445 | sock.resume() 446 | this.flushing = false 447 | this.flush() 448 | } 449 | sock.pause() 450 | return 0 451 | } 452 | this.flushing = false 453 | sock.close() 454 | return -2 455 | } 456 | 457 | wrap (query) { 458 | const { pending } = this 459 | const { protocol } = query 460 | return (...args) => new Promise((resolve, reject) => { 461 | if (!pending.length) { 462 | protocol.params = args 463 | const cmd = getCommand(query, resolve, reject, args) 464 | pending.push(cmd) 465 | this.off = protocol.sync(protocol.exec(protocol.bind(this.off))) 466 | this.flush() 467 | return 468 | } 469 | protocol.params = args 470 | const cmd = getCommand(query, resolve, reject, args) 471 | this.off = protocol.sync(protocol.exec(protocol.bind(this.off))) 472 | pending.push(cmd) 473 | }) 474 | } 475 | 476 | login () { 477 | const { db, parser, pending, protocol } = this 478 | return new Promise((resolve, reject) => { 479 | db.salt = parser.salt 480 | this.off = protocol.md5auth(this.off) 481 | pending.push(getCommand({}, resolve, reject)) 482 | this.flush() 483 | }) 484 | } 485 | 486 | start () { 487 | const { sock, parser, pending, protocol } = this 488 | sock.onReadable = () => { 489 | this.flush(true) 490 | const bytes = sock.recv(sock.buffer.offset) 491 | if (bytes > 0) return parser.parse(bytes) 492 | if (bytes === 0 || !sock.blocked) sock.close() 493 | } 494 | return new Promise((resolve, reject) => { 495 | this.off = protocol.startup(this.off) 496 | pending.push(getCommand({}, resolve, reject)) 497 | sock.resume() 498 | this.flush() 499 | }) 500 | } 501 | 502 | stop () { 503 | const { sock } = this 504 | sock.pause() 505 | sock.onReadable = () => {} 506 | } 507 | 508 | close () { 509 | return this.sock.close() 510 | } 511 | } 512 | 513 | async function createSocket (sock, db) { 514 | const pgsocket = new PGSocket(sock, db) 515 | await pgsocket.start() 516 | await pgsocket.login() 517 | return pgsocket 518 | } 519 | 520 | function createRingBuffer () { 521 | return new RingBuffer() 522 | } 523 | 524 | module.exports = { 525 | constants, 526 | Protocol, 527 | PGError, 528 | PGCommand, 529 | RingBuffer, 530 | Query, 531 | createParser, 532 | createSocket, 533 | createRingBuffer 534 | } 535 | -------------------------------------------------------------------------------- /fetch/fetch.js: -------------------------------------------------------------------------------- 1 | const { net } = just.library('net') 2 | const { sys } = just.library('sys') 3 | const { tls } = just.library('tls', 'openssl.so') 4 | const { epoll } = just.library('epoll') 5 | const { http } = just.library('http') 6 | 7 | const dns = require('@dns') 8 | 9 | const { SystemError } = just 10 | 11 | const { 12 | getResponses, 13 | parseResponsesHandle, 14 | createHandle 15 | } = http 16 | 17 | const { 18 | AF_INET, 19 | SOCK_STREAM, 20 | SOCK_NONBLOCK, 21 | SOL_SOCKET, 22 | IPPROTO_TCP, 23 | TCP_NODELAY, 24 | SO_KEEPALIVE, 25 | EAGAIN, 26 | SO_ERROR 27 | } = net 28 | 29 | const { 30 | EPOLLERR, 31 | EPOLLHUP, 32 | EPOLLIN, 33 | EPOLLOUT, 34 | EPOLLET 35 | } = epoll 36 | 37 | const { loop } = just.factory 38 | 39 | const socketMap = new Map() 40 | const { getIPAddress } = dns 41 | 42 | function parseUrl (url) { 43 | const protocolEnd = url.indexOf(':') 44 | const protocol = url.slice(0, protocolEnd) 45 | const hostnameEnd = url.indexOf('/', protocolEnd + 3) 46 | const host = url.slice(protocolEnd + 3, hostnameEnd) 47 | const path = url.slice(hostnameEnd) 48 | const [hostname, port] = host.split(':') 49 | if (port) return { protocol, host, hostname, path, port } 50 | return { protocol, host, hostname, path, port: protocol === 'https' ? 443 : 80 } 51 | } 52 | 53 | function acquireSocket (ip, port) { 54 | const key = `${ip}:${port}` 55 | if (socketMap.has(key)) { 56 | const sockets = socketMap.get(key) 57 | if (sockets.length) { 58 | const sock = sockets.shift() 59 | sock.free = false 60 | return sock 61 | } 62 | } 63 | const fd = net.socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0) 64 | return new Socket(fd, ip, port) 65 | } 66 | 67 | function releaseSocket (sock) { 68 | if (sock.free) return 69 | const key = `${sock.ip}:${sock.port}` 70 | sock.free = true 71 | if (socketMap.has(key)) { 72 | socketMap.get(key).push(sock) 73 | return 74 | } 75 | socketMap.set(`${sock.ip}:${sock.port}`, [sock]) 76 | } 77 | 78 | function getHeaders (headers) { 79 | if (!headers) return '' 80 | return Object.keys(headers).map(key => `${key}: ${headers[key]}\r\n`).join('') 81 | } 82 | 83 | function writeRequest (sock, req) { 84 | const headers = `${req.method} ${req.path} HTTP/1.1\r\nUser-Agent: ${req.userAgent}\r\nAccept: ${req.accept}\r\nHost: ${req.hostname}\r\n${getHeaders(req.headers)}\r\n` 85 | const bytes = sock.write(`${headers}${req.body || ''}`) 86 | if (bytes < headers.length) { 87 | just.print('short write') 88 | } 89 | return bytes 90 | } 91 | 92 | function getResponse () { 93 | const responses = [[]] 94 | getResponses(1, responses) 95 | return responses.map(res => { 96 | const [version, statusCode, statusMessage, headers] = res 97 | res.chunked = false 98 | return { version, statusCode, statusMessage, headers } 99 | })[0] 100 | } 101 | 102 | class Request { 103 | constructor (url, options = { method: 'GET' }) { 104 | this.url = url 105 | const { protocol, hostname, path, port } = parseUrl(url) 106 | this.protocol = protocol 107 | this.hostname = hostname 108 | this.port = port 109 | this.path = path 110 | this.method = options.method || 'GET' 111 | this.fileName = options.fileName 112 | this.headers = options.headers || {} 113 | this.userAgent = 'curl/7.58.0' 114 | this.accept = '*/*' 115 | this.body = options.body 116 | if (this.body) { 117 | this.headers['Content-Length'] = String.byteLength(this.body) 118 | } 119 | } 120 | } 121 | 122 | class Socket { 123 | constructor (fd, ip, port, options = { buffer: new ArrayBuffer(16384), keepAlive: true, noDelay: true }) { 124 | this.fd = fd 125 | this.ip = ip 126 | this.port = port 127 | this.closed = false 128 | this.tls = null 129 | this.paused = false 130 | this.secure = false 131 | this.connected = false 132 | this.keepAlive = options.keepAlive || false 133 | this.noDelay = options.noDelay || false 134 | this.buffer = options.buffer || new ArrayBuffer(16384) 135 | this.free = false 136 | this.buffer.offset = 0 137 | } 138 | 139 | #onEvent (event) { 140 | if (event & EPOLLIN) this.onReadable() 141 | if (event & EPOLLOUT) this.onWritable() 142 | } 143 | 144 | onReadable () {} 145 | onWritable () {} 146 | 147 | pull () { 148 | const socket = this 149 | if (socket.paused) socket.resume() 150 | return new Promise(resolve => { 151 | socket.onReadable = () => { 152 | const bytes = socket.read() 153 | if (bytes >= 0) { 154 | resolve(bytes) 155 | return 156 | } 157 | if (this.closed) { 158 | resolve(-1) 159 | return 160 | } 161 | } 162 | }) 163 | } 164 | 165 | read () { 166 | // todo: non-secure 167 | const { offset, byteLength } = this.buffer 168 | const expected = byteLength - offset 169 | const bytes = tls.read(this.buffer, expected, offset) 170 | if (bytes > 0) return bytes 171 | if (bytes === 0) { 172 | const err = tls.error(this.buffer, bytes) 173 | if (err === tls.SSL_ERROR_ZERO_RETURN) { 174 | just.print('tls read error: ssl has been shut down') 175 | } else { 176 | just.print('tls read error: connection has been aborted') 177 | } 178 | this.close() 179 | return bytes 180 | } 181 | const err = tls.error(this.buffer, bytes) 182 | if (err === tls.SSL_ERROR_WANT_READ) { 183 | const errno = sys.errno() 184 | if (errno !== EAGAIN) { 185 | just.print(`tls read error: ${sys.errno()}: ${sys.strerror(sys.errno())}`) 186 | this.close() 187 | } 188 | } else { 189 | just.print(`tls error ${err}: ${sys.errno()}: ${sys.strerror(sys.errno())}`) 190 | this.close() 191 | } 192 | return bytes 193 | } 194 | 195 | write (str) { 196 | if (this.secure) { 197 | return tls.write(this.buffer, this.buffer.writeString(str)) 198 | } 199 | return net.writeString(this.fd, str) 200 | } 201 | 202 | pause () { 203 | loop.update(this.fd, EPOLLOUT) 204 | this.paused = true 205 | } 206 | 207 | resume () { 208 | loop.update(this.fd, EPOLLIN) 209 | this.paused = false 210 | } 211 | 212 | setNoDelay (on = true) { 213 | net.setsockopt(this.fd, IPPROTO_TCP, TCP_NODELAY, on ? 1 : 0) 214 | } 215 | 216 | setKeepalive (on = true) { 217 | net.setsockopt(this.fd, SOL_SOCKET, SO_KEEPALIVE, on ? 1 : 0) 218 | } 219 | 220 | handshake () { 221 | if (this.secure) return Promise.resolve() 222 | const socket = this 223 | this.tls = tls.clientContext(new ArrayBuffer(0)) 224 | return new Promise((resolve, reject) => { 225 | loop.remove(this.fd) 226 | loop.add(this.fd, (fd, event) => { 227 | if (event & EPOLLERR || event & EPOLLHUP) { 228 | this.close() 229 | return 230 | } 231 | let r = 0 232 | if (!socket.connected) { 233 | r = tls.connectSocket(socket.fd, socket.tls, socket.buffer) 234 | socket.connected = true 235 | } else if (!socket.secure) { 236 | r = tls.handshake(socket.buffer) 237 | } 238 | if (r === 1) { 239 | socket.secure = true 240 | loop.handles[socket.fd] = (fd, event) => socket.#onEvent(event) 241 | socket.resume() 242 | resolve() 243 | return 244 | } 245 | const err = tls.error(socket.buffer, r) 246 | if (err === tls.SSL_ERROR_WANT_WRITE) { 247 | loop.update(socket.fd, EPOLLOUT) 248 | } else if (err === tls.SSL_ERROR_WANT_READ) { 249 | loop.update(socket.fd, EPOLLIN) 250 | } else { 251 | reject(new Error('socket handshake error')) 252 | } 253 | }, EPOLLOUT) 254 | }) 255 | } 256 | 257 | connect () { 258 | if (this.connected && !this.closed) return Promise.resolve() 259 | const socket = this 260 | const { ip, port } = socket 261 | const ok = net.connect(socket.fd, ip, port) 262 | if (ok < 0) { 263 | if (sys.errno() !== 115) throw new just.SystemError('net.connect') 264 | } 265 | if (this.keepAlive) this.setKeepalive(true) 266 | if (this.noDelay) this.setNoDelay(true) 267 | let resolved = false 268 | return new Promise((resolve, reject) => { 269 | loop.add(socket.fd, (fd, event) => { 270 | if (event & EPOLLERR || event & EPOLLHUP) { 271 | const err = net.getsockopt(fd, SOL_SOCKET, SO_ERROR) 272 | socket.close() 273 | return 274 | } 275 | if (event & EPOLLOUT) { 276 | if (!resolved) resolve() 277 | resolved = true 278 | loop.update(fd, EPOLLIN) 279 | return 280 | } 281 | reject(new Error('unexpected epoll state')) 282 | }, EPOLLOUT) 283 | }) 284 | } 285 | 286 | release () { 287 | releaseSocket(this) 288 | } 289 | 290 | close () { 291 | if (this.closed) return 292 | loop.remove(this.fd) 293 | if (this.tls) { 294 | if (this.secure) tls.shutdown(this.buffer) 295 | tls.free(this.buffer) 296 | } 297 | net.close(this.fd) 298 | this.closed = true 299 | socketMap.delete(`${this.ip}:${this.port}`) 300 | } 301 | } 302 | 303 | class ChunkParser { 304 | constructor (buf) { 305 | this.size = 0 306 | this.consumed = 0 307 | this.buffer = buf 308 | this.bytes = new Uint8Array(buf) 309 | this.digits = [] 310 | this.header = true 311 | this.final = false 312 | } 313 | 314 | reset () { 315 | this.size = 0 316 | this.consumed = 0 317 | this.digits.length = 0 318 | this.final = false 319 | this.header = true 320 | } 321 | 322 | parse (len, start) { 323 | const { bytes, digits } = this 324 | let off = start 325 | const chunks = [] 326 | while (len) { 327 | if (this.header) { 328 | const c = bytes[off] 329 | off++ 330 | len-- 331 | if (c === 13) { 332 | continue 333 | } else if (c === 10) { 334 | if (this.final) { 335 | this.reset() 336 | return 337 | } 338 | if (digits.length) { 339 | this.size = parseInt(digits.join(''), 16) 340 | if (this.size > 0) { 341 | this.header = false 342 | } else if (this.size === 0) { 343 | this.final = true 344 | } 345 | digits.length = 0 346 | } 347 | continue 348 | } else if ((c > 47 && c < 58)) { 349 | digits.push(String.fromCharCode(c)) 350 | continue 351 | } else if ((c > 96 && c < 103)) { 352 | digits.push(String.fromCharCode(c)) 353 | continue 354 | } else if ((c > 64 && c < 71)) { 355 | digits.push(String.fromCharCode(c)) 356 | continue 357 | } else { 358 | just.print('BAD_CHAR') 359 | } 360 | just.print('OOB:') 361 | just.print(`c ${c}`) 362 | just.print(`len ${len}`) 363 | just.print(`off ${off}`) 364 | just.print(`size ${this.size}`) 365 | just.print(`consumed ${this.consumed}`) 366 | throw new Error('OOB') 367 | } else { 368 | const remaining = this.size - this.consumed 369 | if (remaining > len) { 370 | chunks.push(this.buffer.slice(off, off + len)) 371 | this.consumed += len 372 | off += len 373 | len = 0 374 | } else { 375 | chunks.push(this.buffer.slice(off, off + remaining)) 376 | len -= remaining 377 | off += remaining 378 | this.consumed += remaining 379 | this.reset() 380 | } 381 | } 382 | } 383 | return chunks 384 | } 385 | } 386 | 387 | async function fetch (url, options) { 388 | const req = new Request(url, options) 389 | const ip = await getIPAddress(req.hostname) 390 | const sock = acquireSocket(ip, req.port) 391 | await sock.connect() 392 | 393 | if (req.protocol === 'https') { 394 | await sock.handshake() 395 | } 396 | 397 | writeRequest(sock, req) 398 | 399 | const info = new ArrayBuffer(4) 400 | const dv = new DataView(info) 401 | const handle = createHandle(sock.buffer, info) 402 | let body = [] 403 | let remaining = 0 404 | 405 | let bytes = await sock.pull() 406 | while (bytes) { 407 | parseResponsesHandle(handle, sock.buffer.offset + bytes, 0) 408 | const r = dv.getUint32(0, true) 409 | const count = r & 0xff 410 | remaining = r >> 16 411 | if (count > 0) break 412 | sock.buffer.offset = remaining 413 | bytes = await sock.pull() 414 | } 415 | 416 | const res = getResponse() 417 | res.bytes = 0 418 | const empty = { byteLength: 0 } 419 | res.json = async () => { 420 | const text = await res.text() 421 | return JSON.parse(text) 422 | } 423 | res.pull = async () => { 424 | if (!body.length && res.contentLength && (res.bytes === res.contentLength)) return 425 | if (body.length) { 426 | return body.shift() 427 | } 428 | let bytes = await sock.pull() 429 | if (bytes < 0) throw new SystemError('pull') 430 | if (bytes === 0) return 431 | if (res.chunked) { 432 | const chunks = parser.parse(bytes, 0) 433 | if (!chunks) return 434 | if (chunks.length) { 435 | res.bytes += chunks.reduce((size, chunk) => size + chunk.byteLength, 0) 436 | body = body.concat(chunks) 437 | } 438 | const chunk = body.shift() 439 | if (chunk) return chunk 440 | return empty 441 | } else { 442 | res.bytes += bytes 443 | } 444 | return sock.buffer.slice(0, bytes) 445 | } 446 | res.chunkedText = async () => { 447 | let bytes = await sock.pull() 448 | while (bytes) { 449 | const chunks = parser.parse(bytes, 0) 450 | if (!chunks) break 451 | if (chunks.length) { 452 | res.bytes += chunks.reduce((size, chunk) => size + chunk.byteLength, 0) 453 | body = body.concat(chunks) 454 | } 455 | bytes = await sock.pull() 456 | } 457 | releaseSocket(sock) 458 | return body.map(buf => buf.readString(buf.byteLength, 0)).join('') 459 | } 460 | res.chunkedRaw = async () => { 461 | let bytes = await sock.pull() 462 | while (bytes) { 463 | const chunks = parser.parse(bytes, 0) 464 | if (!chunks) break 465 | if (chunks.length) { 466 | res.bytes += chunks.reduce((size, chunk) => size + chunk.byteLength, 0) 467 | body = body.concat(chunks) 468 | } 469 | bytes = await sock.pull() 470 | } 471 | releaseSocket(sock) 472 | return body 473 | } 474 | res.text = async () => { 475 | if (res.chunked) return res.chunkedText() 476 | if (res.contentLength && (res.bytes === res.contentLength)) { 477 | releaseSocket(sock) 478 | return body.map(buf => buf.readString(buf.byteLength, 0)).join('') 479 | } 480 | let bytes = await sock.pull() 481 | while (bytes) { 482 | body.push(sock.buffer.slice(0, bytes)) 483 | res.bytes += bytes 484 | if (res.contentLength && (res.bytes === res.contentLength)) { 485 | releaseSocket(sock) 486 | return body.map(buf => buf.readString(buf.byteLength, 0)).join('') 487 | } 488 | bytes = await sock.pull() 489 | } 490 | } 491 | res.raw = async () => { 492 | if (res.chunked) return res.chunkedRaw() 493 | if (res.contentLength && (res.bytes === res.contentLength)) { 494 | releaseSocket(sock) 495 | return body 496 | } 497 | let bytes = await sock.pull() 498 | while (bytes) { 499 | body.push(sock.buffer.slice(0, bytes)) 500 | res.bytes += bytes 501 | if (res.contentLength && (res.bytes === res.contentLength)) { 502 | releaseSocket(sock) 503 | return body 504 | } 505 | bytes = await sock.pull() 506 | } 507 | } 508 | res.request = req 509 | res.socket = sock 510 | let parser 511 | 512 | sock.buffer.offset = 0 513 | if (res.headers['Content-Length']) { 514 | res.contentLength = parseInt(res.headers['Content-Length'], 10) 515 | } else { 516 | res.contentLength = 0 517 | if (res.headers['Transfer-Encoding'] === 'chunked') { 518 | res.chunked = true 519 | parser = new ChunkParser(sock.buffer) 520 | } 521 | } 522 | if (remaining > 0) { 523 | if (res.chunked) { 524 | const chunks = parser.parse(remaining, sock.buffer.offset + bytes - remaining) 525 | if (chunks && chunks.length) { 526 | res.bytes += chunks.reduce((size, chunk) => size + chunk.byteLength, 0) 527 | body = body.concat(chunks) 528 | } 529 | //just.print(`bytes ${remaining} total ${res.bytes} chunkSize ${parser.size} consumed ${parser.consumed}`) 530 | } else { 531 | body.push(sock.buffer.slice(sock.buffer.offset + bytes - remaining, sock.buffer.offset + bytes)) 532 | res.bytes += remaining 533 | } 534 | } 535 | if (!res.chunked && (res.bytes === res.contentLength)) { 536 | releaseSocket(sock) 537 | } 538 | 539 | return res 540 | } 541 | 542 | module.exports = { fetch } 543 | -------------------------------------------------------------------------------- /sqlite/sqlite.js: -------------------------------------------------------------------------------- 1 | const { sqlite } = just.library('sqlite') 2 | 3 | const checkpoint = { 4 | SQLITE_CHECKPOINT_PASSIVE: 0, 5 | SQLITE_CHECKPOINT_FULL: 1, 6 | SQLITE_CHECKPOINT_RESTART: 2, 7 | SQLITE_CHECKPOINT_TRUNCATE: 3 8 | } 9 | 10 | const v2 = { 11 | SQLITE_OPEN_READONLY : 0x00000001, /* Ok for sqlite3_open_v2() */ 12 | SQLITE_OPEN_READWRITE : 0x00000002, /* Ok for sqlite3_open_v2() */ 13 | SQLITE_OPEN_CREATE : 0x00000004, /* Ok for sqlite3_open_v2() */ 14 | SQLITE_OPEN_DELETEONCLOSE : 0x00000008, /* VFS only */ 15 | SQLITE_OPEN_EXCLUSIVE : 0x00000010, /* VFS only */ 16 | SQLITE_OPEN_AUTOPROXY : 0x00000020, /* VFS only */ 17 | SQLITE_OPEN_URI : 0x00000040, /* Ok for sqlite3_open_v2() */ 18 | SQLITE_OPEN_MEMORY : 0x00000080, /* Ok for sqlite3_open_v2() */ 19 | SQLITE_OPEN_MAIN_DB : 0x00000100, /* VFS only */ 20 | SQLITE_OPEN_TEMP_DB : 0x00000200, /* VFS only */ 21 | SQLITE_OPEN_TRANSIENT_DB : 0x00000400, /* VFS only */ 22 | SQLITE_OPEN_MAIN_JOURNAL : 0x00000800, /* VFS only */ 23 | SQLITE_OPEN_TEMP_JOURNAL : 0x00001000, /* VFS only */ 24 | SQLITE_OPEN_SUBJOURNAL : 0x00002000, /* VFS only */ 25 | SQLITE_OPEN_SUPER_JOURNAL : 0x00004000, /* VFS only */ 26 | SQLITE_OPEN_NOMUTEX : 0x00008000, /* Ok for sqlite3_open_v2() */ 27 | SQLITE_OPEN_FULLMUTEX : 0x00010000, /* Ok for sqlite3_open_v2() */ 28 | SQLITE_OPEN_SHAREDCACHE : 0x00020000, /* Ok for sqlite3_open_v2() */ 29 | SQLITE_OPEN_PRIVATECACHE : 0x00040000, /* Ok for sqlite3_open_v2() */ 30 | SQLITE_OPEN_WAL : 0x00080000, /* VFS only */ 31 | SQLITE_OPEN_NOFOLLOW : 0x01000000, /* Ok for sqlite3_open_v2() */ 32 | SQLITE_OPEN_EXRESCODE : 0x02000000 /* Extended result codes */ 33 | } 34 | 35 | const constants = { 36 | SQLITE_OK : 0, // Successful result 37 | SQLITE_ERROR : 1, // Generic error 38 | SQLITE_INTERNAL : 2, // Internal logic error in SQLite 39 | SQLITE_PERM : 3, // Access permission denied 40 | SQLITE_ABORT : 4, // Callback routine requested an abort 41 | SQLITE_BUSY : 5, // The database file is locked 42 | SQLITE_LOCKED : 6, // A table in the database is locked 43 | SQLITE_NOMEM : 7, // A malloc() failed 44 | SQLITE_READONLY : 8, // Attempt to write a readonly database 45 | SQLITE_INTERRUPT : 9, // Operation terminated by sqlite3_interrupt() 46 | SQLITE_IOERR : 10, // Some kind of disk I/O error occurred 47 | SQLITE_CORRUPT : 11, // The database disk image is malformed 48 | SQLITE_NOTFOUND : 12, // Unknown opcode in sqlite3_file_control() 49 | SQLITE_FULL : 13, // Insertion failed because database is full 50 | SQLITE_CANTOPEN : 14, // Unable to open the database file 51 | SQLITE_PROTOCOL : 15, // Database lock protocol error 52 | SQLITE_EMPTY : 16, // Internal use only 53 | SQLITE_SCHEMA : 17, // The database schema changed 54 | SQLITE_TOOBIG : 18, // String or BLOB exceeds size limit 55 | SQLITE_CONSTRAINT : 19, // Abort due to constraint violation 56 | SQLITE_MISMATCH : 20, // Data type mismatch 57 | SQLITE_MISUSE : 21, // Library used incorrectly 58 | SQLITE_NOLFS : 22, // Uses OS features not supported on host 59 | SQLITE_AUTH : 23, // Authorization denied 60 | SQLITE_FORMAT : 24, // Not used 61 | SQLITE_RANGE : 25, // 2nd parameter to sqlite3_bind out of range 62 | SQLITE_NOTADB : 26, // File opened that is not a database file 63 | SQLITE_NOTICE : 27, // Notifications from sqlite3_log() 64 | SQLITE_WARNING : 28, // Warnings from sqlite3_log() 65 | SQLITE_ROW : 100, // sqlite3_step() has another row ready 66 | SQLITE_DONE : 101 // sqlite3_step() has finished executing 67 | } 68 | 69 | constants.SQLITE_DESERIALIZE_FREEONCLOSE = 1 /* Call sqlite3_free() on close */ 70 | constants.SQLITE_DESERIALIZE_RESIZEABLE = 2 /* Resize using sqlite3_realloc64() */ 71 | constants.SQLITE_DESERIALIZE_READONLY = 4 /* Database is read-only */ 72 | 73 | constants.v2 = v2 74 | constants.checkpoint = checkpoint 75 | 76 | constants.SQLITE_SERIALIZE_NOCOPY = 0x001 77 | 78 | const fieldTypes = { 79 | SQLITE_INTEGER : 1, 80 | SQLITE_FLOAT : 2, 81 | SQLITE_TEXT : 3, 82 | SQLITE_BLOB : 4, 83 | SQLITE_NULL : 5, 84 | SQLITE_INT64 : 6 85 | } 86 | constants.fieldTypes = fieldTypes 87 | 88 | function getType (type, i) { 89 | if (type === fieldTypes.SQLITE_INTEGER) { 90 | return `sqlite.columnInt(stmt, ${i})` 91 | } else if (type === fieldTypes.SQLITE_INT64) { 92 | return `sqlite.columnInt64(stmt, ${i})` 93 | } else if (type === fieldTypes.SQLITE_FLOAT) { 94 | return `sqlite.columnDouble(stmt, ${i})` 95 | } else if (type === fieldTypes.SQLITE_NULL) { 96 | return `sqlite.columnText(stmt, ${i})` 97 | } else if (type === fieldTypes.SQLITE_TEXT) { 98 | return `sqlite.columnText(stmt, ${i})` 99 | } else if (type === fieldTypes.SQLITE_BLOB) { 100 | return `sqlite.columnBlob(stmt, ${i})` 101 | } else { 102 | return `null` 103 | } 104 | } 105 | 106 | function getDefault (type) { 107 | if (type === fieldTypes.SQLITE_INTEGER) { 108 | return '0' 109 | } else if (type === fieldTypes.SQLITE_INT64) { 110 | return '0n' 111 | } else if (type === fieldTypes.SQLITE_FLOAT) { 112 | return '0.0' 113 | } else if (type === fieldTypes.SQLITE_NULL) { 114 | return '\'\'' 115 | } else if (type === fieldTypes.SQLITE_TEXT) { 116 | return '\'\'' 117 | } else if (type === fieldTypes.SQLITE_BLOB) { 118 | return 'new ArrayBuffer(0)' 119 | } else { 120 | return `null` 121 | } 122 | } 123 | 124 | class Row { 125 | constructor () { 126 | 127 | } 128 | } 129 | 130 | class Query { 131 | constructor (db, sql, maxRows = 1000, name = sql) { 132 | this.db = db 133 | this.sql = sql 134 | this.stmt = null 135 | this.params = [] 136 | this.types = [] 137 | this.names = [] 138 | this.count = 0 139 | this.Row = Row 140 | this.rows = [] 141 | this.maxRows = maxRows 142 | this.name = name 143 | } 144 | 145 | prepare (fields = [], params = []) { 146 | if (fields.length) { 147 | this.types.length = 0 148 | this.names.length = 0 149 | } 150 | for (const field of fields) { 151 | const { name, type } = field 152 | this.types.push(type) 153 | this.names.push(name) 154 | } 155 | this.params = params 156 | const { db } = this 157 | this.stmt = sqlite.prepare(db.db, this.sql) 158 | if (!this.stmt) throw new Error(db.errorCode()) 159 | return this 160 | } 161 | 162 | close () { 163 | sqlite.finalize(this.stmt) 164 | } 165 | 166 | compile (maxRows = this.maxRows) { 167 | const { types, names, params } = this 168 | this.sqlite = sqlite 169 | this.constants = constants 170 | const source = [] 171 | const fParams = [] 172 | source.push(`const { db, stmt, rows, sqlite, constants } = this`) 173 | source.push(` const { SQLITE_OK, SQLITE_ROW, SQLITE_DONE } = constants`) 174 | source.push(` const { bindText, bindInt, bindDouble, bindInt64, bindBlob, step, reset } = sqlite\n`) 175 | let i = 0 176 | for (const param of params) { 177 | if (param.type) { 178 | fParams.push(param.name) 179 | const { name, type } = param 180 | if (type === fieldTypes.SQLITE_TEXT) { 181 | source.push(` if (bindText(stmt, ${i + 1}, ${name}) !== SQLITE_OK) throw new Error(db.errorMessage())`) 182 | } else if (type === fieldTypes.SQLITE_INTEGER) { 183 | source.push(` if (bindInt(stmt, ${i + 1}, Number(${name})) !== SQLITE_OK) throw new Error(db.errorMessage())`) 184 | } else if (type === fieldTypes.SQLITE_FLOAT) { 185 | source.push(` if (bindDouble(stmt, ${i + 1}, Number(${name})) !== SQLITE_OK) throw new Error(db.errorMessage())`) 186 | } else if (type === fieldTypes.SQLITE_INT64) { 187 | source.push(` if (bindInt64(stmt, ${i + 1}, BigInt(${name})) !== SQLITE_OK) throw new Error(db.errorMessage())`) 188 | } else if (type === fieldTypes.SQLITE_BLOB) { 189 | source.push(` if (bindBlob(stmt, ${i + 1}, ${name}) !== SQLITE_OK) throw new Error(db.errorMessage())`) 190 | } 191 | i++ 192 | continue 193 | } 194 | if (param === fieldTypes.SQLITE_TEXT) { 195 | source.push(` if (bindText(stmt, ${i + 1}, arguments[${i}]) !== SQLITE_OK) throw new Error(db.errorMessage())`) 196 | } else if (param === fieldTypes.SQLITE_INTEGER) { 197 | source.push(` if (bindInt(stmt, ${i + 1}, Number(arguments[${i}])) !== SQLITE_OK) throw new Error(db.errorMessage())`) 198 | } else if (param === fieldTypes.SQLITE_FLOAT) { 199 | source.push(` if (bindDouble(stmt, ${i + 1}, Number(arguments[${i}])) !== SQLITE_OK) throw new Error(db.errorMessage())`) 200 | } else if (param === fieldTypes.SQLITE_INT64) { 201 | source.push(` if (bindInt64(stmt, ${i + 1}, BigInt(arguments[${i}])) !== SQLITE_OK) throw new Error(db.errorMessage())`) 202 | } else if (param === fieldTypes.SQLITE_BLOB) { 203 | source.push(` if (bindBlob(stmt, ${i + 1}, arguments[${i}]) !== SQLITE_OK) throw new Error(db.errorMessage())`) 204 | } 205 | i++ 206 | } 207 | // TODO: this will break if we exceed maxRows 208 | source.push(` 209 | let count = 0 210 | let ok = step(stmt) 211 | while (ok === SQLITE_ROW) {`) 212 | source.push(` if (count === ${maxRows}) break`) 213 | source.push(' const row = rows[count]') 214 | for (let i = 0; i < types.length; i++) { 215 | source.push(` row.${names[i]} = ${getType(types[i], i)}`) 216 | } 217 | source.push(` ok = step(stmt) 218 | count++ 219 | } 220 | if (ok !== SQLITE_OK && ok !== SQLITE_DONE) { 221 | throw new Error(db.errorMessage()) 222 | } 223 | this.count = count 224 | reset(stmt) 225 | return rows`) 226 | const text = ` ${source.join('\n').trim()}` 227 | this.exec = just.vm.compile(text, this.name, fParams, []) 228 | source.length = 0 229 | // TODO: use contructor of row so we can do new Row(...) 230 | const args = names.map((n, i) => `${n} = ${getDefault(types[i])}`).join(', ') 231 | source.push(` 232 | class Row { 233 | constructor (${args}) {`) 234 | i = 0 235 | for (const name of names) { 236 | source.push(` this.${name} = ${name}`) 237 | } 238 | source.push(` } 239 | } 240 | return Row 241 | `) 242 | const Row = (just.vm.compile(source.join('\n'), `${this.name}.Row`, [], []))() 243 | this.Row = Row 244 | this.rows = new Array(maxRows).fill(0).map(v => new Row()) 245 | return this 246 | } 247 | 248 | exec (...values) { 249 | const { params, stmt, types, names, rows, maxRows } = this 250 | const { db } = this 251 | 252 | let i = 0 253 | // TODO: have a sqlite.binParams, where i can pass in all params as an array and bind them with one c++ call 254 | // e.g. sqlite.bindParams(stmt, params) 255 | for (const param of params) { 256 | const p = param.type ? param.type : param 257 | if (p === fieldTypes.SQLITE_TEXT) { 258 | if (sqlite.bindText(stmt, i + 1, values[i]) !== constants.SQLITE_OK) throw new Error(db.errorCode()) 259 | } else if (p === fieldTypes.SQLITE_INTEGER) { 260 | if (sqlite.bindInt(stmt, i + 1, Number(values[i])) !== constants.SQLITE_OK) throw new Error(db.errorCode()) 261 | } else if (p === fieldTypes.SQLITE_FLOAT) { 262 | if (sqlite.bindDouble(stmt, i + 1, Number(values[i])) !== constants.SQLITE_OK) throw new Error(db.errorCode()) 263 | } else if (p === fieldTypes.SQLITE_INT64) { 264 | if (sqlite.bindInt64(stmt, i + 1, BigInt(values[i])) !== constants.SQLITE_OK) throw new Error(db.errorCode()) 265 | } else if (p === fieldTypes.SQLITE_BLOB) { 266 | if (sqlite.bindBlob(stmt, i + 1, values[i]) !== constants.SQLITE_OK) throw new Error(db.errorCode()) 267 | } 268 | i++ 269 | } 270 | rows.length = 0 271 | let count = 0 272 | let ok = sqlite.step(stmt) 273 | if (!types.length) { 274 | const columns = sqlite.columnCount(stmt) 275 | for (let i = 0; i < columns; i++) { 276 | const type = sqlite.columnType(stmt, i) 277 | types.push(type) 278 | const name = sqlite.columnName(stmt, i) 279 | names.push(name) 280 | } 281 | } 282 | while (ok === constants.SQLITE_ROW) { 283 | if (count === maxRows) break 284 | const row = {} 285 | // TODO: also here, have a sqlite.readRow() so we can just use one C++ call per row 286 | // also, a readRows() call would be nice too to read all rows into an array 287 | // e.g. sqlite.readRow(stmt, types, names) 288 | // sqlite.readRows(stmt, types, names) 289 | // even better again, bind these types to handle in C++ with a struct containing the types/names and we only have to pass in the statement 290 | for (let i = 0; i < types.length; i++) { 291 | if (types[i] === fieldTypes.SQLITE_INTEGER) { 292 | row[names[i]] = sqlite.columnInt(stmt, i) 293 | } else if (types[i] === fieldTypes.SQLITE_INT64) { 294 | row[names[i]] = sqlite.columnInt64(stmt, i) 295 | } else if (types[i] === fieldTypes.SQLITE_FLOAT) { 296 | row[names[i]] = sqlite.columnDouble(stmt, i) 297 | } else if (types[i] === fieldTypes.SQLITE_NULL) { 298 | row[names[i]] = sqlite.columnText(stmt, i) 299 | } else if (types[i] === fieldTypes.SQLITE_TEXT) { 300 | row[names[i]] = sqlite.columnText(stmt, i) 301 | } else if (types[i] === fieldTypes.SQLITE_BLOB) { 302 | row[names[i]] = sqlite.columnBlob(stmt, i) 303 | } else { 304 | row[names[i]] = null 305 | } 306 | } 307 | count++ 308 | rows.push(row) 309 | ok = sqlite.step(stmt) 310 | } 311 | if (ok !== constants.SQLITE_OK && ok !== constants.SQLITE_DONE) { 312 | throw new Error(db.errorCode()) 313 | } 314 | this.count = count 315 | sqlite.reset(stmt) 316 | return rows 317 | } 318 | } 319 | 320 | class Database { 321 | constructor (name = ':memory:') { 322 | this.name = name 323 | this.db = null 324 | } 325 | 326 | releaseMemory () { 327 | if (!this.db) return 328 | return sqlite.releaseDBMemory(this.db) 329 | } 330 | 331 | errorCode () { 332 | if (!this.db) return 333 | return sqlite.errCode(this.db) 334 | } 335 | 336 | errorMessage () { 337 | if (!this.db) return 338 | return sqlite.errMessage(this.db) 339 | } 340 | 341 | open (flags = defaultFlags, vfs) { 342 | let db 343 | if (flags) { 344 | if (vfs) { 345 | db = sqlite.open(this.name || ':memory:', flags, vfs) 346 | } else { 347 | db = sqlite.open(this.name || ':memory:', flags) 348 | } 349 | } else { 350 | db = sqlite.open(this.name || ':memory:') 351 | } 352 | if (!db) throw new Error('Failed to open database') 353 | this.db = db 354 | return this 355 | } 356 | 357 | query (sql, name = sql, maxRows = 1000) { 358 | return new Query(this, sql, maxRows, name) 359 | } 360 | 361 | checkpoint (mode = checkpoint.SQLITE_CHECKPOINT_FULL) { 362 | return sqlite.checkpoint(this.db, mode) 363 | } 364 | 365 | changes () { 366 | return sqlite.changes(this.db) 367 | } 368 | 369 | onWal (callback) { 370 | if (!callback) return sqlite.walHook(this.db) 371 | return sqlite.walHook(this.db, callback) 372 | } 373 | 374 | deserialize (buf, flags, name, size = buf.byteLength) { 375 | return sqlite.deserialize(this.db, buf, flags, name, size) 376 | } 377 | 378 | serialize (name, flags = 0) { 379 | return sqlite.serialize(this.db, name, flags) 380 | } 381 | 382 | exec (sql, fields = []) { 383 | const query = new Query(this, sql).prepare(fields) 384 | const rows = query.exec() 385 | query.close() 386 | return rows 387 | } 388 | 389 | schema () { 390 | const tables = this.exec('SELECT * FROM sqlite_schema', [ 391 | { name: 'type', type: fieldTypes.SQLITE_TEXT }, 392 | { name: 'name', type: fieldTypes.SQLITE_TEXT }, 393 | { name: 'tableName', type: fieldTypes.SQLITE_TEXT }, 394 | { name: 'rootPage', type: fieldTypes.SQLITE_INT64 }, 395 | { name: 'sql', type: fieldTypes.SQLITE_TEXT }, 396 | ]) 397 | for (const table of tables) { 398 | table.cols = this.exec(`PRAGMA table_info(${table.name})`, [ 399 | { name: 'cid', type: fieldTypes.SQLITE_INTEGER }, 400 | { name: 'name', type: fieldTypes.SQLITE_TEXT }, 401 | { name: 'type', type: fieldTypes.SQLITE_TEXT }, 402 | { name: 'notnull', type: fieldTypes.SQLITE_INTEGER }, 403 | { name: 'default', type: fieldTypes.SQLITE_INTEGER }, 404 | { name: 'pk', type: fieldTypes.SQLITE_INTEGER } 405 | ]) 406 | } 407 | return tables 408 | } 409 | 410 | close () { 411 | sqlite.close(this.db) 412 | } 413 | } 414 | 415 | function initialize () { 416 | return sqlite.initialize() 417 | } 418 | 419 | function shutdown () { 420 | return sqlite.shutdown() 421 | } 422 | 423 | function registerVFS (vfs) { 424 | return sqlite.registerVFS(vfs) 425 | } 426 | 427 | function unregisterVFS (vfs) { 428 | return sqlite.unregisterVFS(vfs) 429 | } 430 | 431 | function findVFS (vfs) { 432 | return sqlite.findVFS(vfs) 433 | } 434 | 435 | function memoryUsed () { 436 | return sqlite.memoryUsed() 437 | } 438 | 439 | function memoryHighwater () { 440 | return sqlite.memoryHighwater() 441 | } 442 | 443 | function releaseMemory () { 444 | return sqlite.releaseMemory() 445 | } 446 | 447 | const SQLTypes = { 448 | int: name => ({ name, type: fieldTypes.SQLITE_INTEGER }), 449 | int64: name => ({ name, type: fieldTypes.SQLITE_INT64 }), 450 | text: name => ({ name, type: fieldTypes.SQLITE_TEXT }), 451 | blob: name => ({ name, type: fieldTypes.SQLITE_BLOB }), 452 | float: name => ({ name, type: fieldTypes.SQLITE_NULL }), 453 | null: name => ({ name, type: fieldTypes.SQLITE_FLOAT }) 454 | } 455 | 456 | const defaultFlags = v2.SQLITE_OPEN_READWRITE | v2.SQLITE_OPEN_SHAREDCACHE | 457 | v2.SQLITE_OPEN_NOMUTEX | v2.SQLITE_OPEN_CREATE 458 | 459 | module.exports = { 460 | constants, 461 | Database, 462 | sqlite, 463 | initialize, 464 | shutdown, 465 | registerVFS, 466 | unregisterVFS, 467 | findVFS, 468 | memoryUsed, 469 | memoryHighwater, 470 | releaseMemory, 471 | SQLTypes, 472 | Query, 473 | defaultFlags 474 | } 475 | -------------------------------------------------------------------------------- /justify/justify.js: -------------------------------------------------------------------------------- 1 | const { epoll } = just.library('epoll') 2 | const { http } = just.library('http') 3 | const { sys } = just.library('sys') 4 | const { net } = just.library('net') 5 | const { sendfile } = just.library('fs') 6 | const { tls } = just.library('tls', 'openssl.so') 7 | 8 | const { parseRequestsHandle, createHandle, getUrl, getMethod, getHeaders } = http 9 | const { EPOLLIN, EPOLLERR, EPOLLHUP, EPOLLOUT } = epoll 10 | const { close, recv, accept, setsockopt, socket, bind, listen, sendString, send } = net 11 | const { fcntl } = sys 12 | const { loop } = just.factory 13 | const { F_GETFL, F_SETFL } = just.sys 14 | const { IPPROTO_TCP, O_NONBLOCK, TCP_NODELAY, SO_KEEPALIVE, SOMAXCONN, AF_INET, SOCK_STREAM, SOL_SOCKET, SO_REUSEADDR, SO_REUSEPORT, SOCK_NONBLOCK } = just.net 15 | const { setInterval } = just 16 | const { SSL_OP_ALL, SSL_OP_NO_RENEGOTIATION, SSL_OP_NO_SSLv3, SSL_OP_NO_TLSv1, SSL_OP_NO_TLSv1_1, SSL_OP_NO_DTLSv1, SSL_OP_NO_DTLSv1_2 } = (tls || {}) 17 | 18 | let time = (new Date()).toUTCString() 19 | 20 | function createResponses (serverName) { 21 | // todo: expose this so it can be configured 22 | time = (new Date()).toUTCString() 23 | Object.keys(contentTypes).forEach(contentType => { 24 | Object.keys(statusMessages).forEach(status => { 25 | responses[contentType][status] = `HTTP/1.1 ${status} ${statusMessages[status]}\r\nServer: ${serverName}\r\nContent-Type: ${contentTypes[contentType]}\r\nDate: ${time}\r\nContent-Length: ` 26 | }) 27 | }) 28 | } 29 | 30 | function checkError (fd, event) { 31 | if (event & EPOLLERR || event & EPOLLHUP) { 32 | loop.remove(fd) 33 | net.close(fd) 34 | return true 35 | } 36 | return false 37 | } 38 | 39 | function serverOptions (fd, opts = { server: { reuseAddress: true, reusePort: true } }) { 40 | const { reuseAddress, reusePort } = opts.server 41 | setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, reuseAddress ? 1 : 0) 42 | setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, reusePort ? 1 : 0) 43 | } 44 | 45 | function clientOptions (fd, opts = { client: {} }) { 46 | const { tcpNoDelay, soKeepAlive } = opts.client 47 | setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, tcpNoDelay ? 1 : 0) 48 | setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, soKeepAlive ? 1 : 0) 49 | } 50 | 51 | function joinHeaders (headers) { 52 | return headers.map(h => h.join(': ')).join('\r\n') 53 | } 54 | 55 | // TODO: This should really be a Pipeline class with one Response for each one queued in the pipeline 56 | class Response { 57 | constructor (fd, onFinish) { 58 | this.fd = fd 59 | this.pipeline = false 60 | this.queue = '' 61 | this.status = 200 62 | this.headers = [] 63 | this.onFinish = onFinish 64 | this.contentType = 'text' 65 | this.chunked = false 66 | this.contentLength = 0 67 | this.close = false 68 | } 69 | 70 | sendFile (fd) { 71 | return sendString(this.fd, str) 72 | } 73 | 74 | send (buf, len, off) { 75 | return send(this.fd, buf, len, off) 76 | } 77 | 78 | sendString (str) { 79 | return sendString(this.fd, str) 80 | } 81 | 82 | write (str) { 83 | return this.sendString(str) 84 | } 85 | 86 | writeHeaders () { 87 | const { status, socket, contentType, chunked, contentLength, close } = this 88 | const { server } = socket 89 | let str = `HTTP/1.1 ${status} ${statusMessages[status]}\r\nServer: ${server.name}\r\nContent-Type: ${contentTypes[contentType]}\r\nDate: ${time}\r\n` 90 | if (close) { 91 | str += 'Connection: close\r\n' 92 | } 93 | if (chunked) { 94 | str += 'Transfer-Encoding: chunked\r\n\r\n' 95 | } else { 96 | str += `Content-Length: ${contentLength}\r\n\r\n` 97 | } 98 | return this.sendString(str) 99 | } 100 | 101 | setHeader (...args) { 102 | // todo: sanitize 103 | this.headers.push(args) 104 | } 105 | 106 | end () { 107 | if (this.headers.length) this.headers = [] 108 | if (this.onFinish) this.onFinish(this.socket.request, this) 109 | this.status = 200 110 | } 111 | 112 | json (str) { 113 | // TODO: we need a buffer for each request and then join them all together or write them out sequentially at the end 114 | if (this.pipeline) { 115 | this.queue += `${json[this.status]}${String.byteLength(str)}${END}${str}` 116 | return 117 | } 118 | this.sendString(`${json[this.status]}${String.byteLength(str)}${END}${str}`) 119 | this.end() 120 | } 121 | 122 | // todo: allow calling multiple times and then calling end 123 | text (str, contentType = text) { 124 | if (this.pipeline) { 125 | this.queue += `${contentType[this.status]}${str.length}${END}${str}` 126 | return 127 | } 128 | if (this.headers.length) { 129 | this.sendString(`${contentType[this.status]}${str.length}${CRLF}${joinHeaders(this.headers)}${END}${str}`) 130 | return 131 | } 132 | this.sendString(`${contentType[this.status]}${str.length}${END}${str}`) 133 | this.end() 134 | } 135 | 136 | html (str) { 137 | if (this.pipeline) { 138 | this.queue += `${html[this.status]}${str.length}${END}${str}` 139 | return 140 | } 141 | this.sendString(`${html[this.status]}${String.byteLength(str)}${END}${str}`) 142 | this.end() 143 | } 144 | 145 | utf8 (str, contentType = utf8) { 146 | if (this.pipeline) { 147 | this.queue += `${contentType[this.status]}${String.byteLength(str)}${END}${str}` 148 | return 149 | } 150 | if (this.headers.length) { 151 | this.sendString(`${contentType[this.status]}${String.byteLength(str)}${CRLF}${joinHeaders(this.headers)}${END}${str}`) 152 | return 153 | } 154 | this.sendString(`${contentType[this.status]}${String.byteLength(str)}${END}${str}`) 155 | this.end() 156 | } 157 | 158 | raw (buf, contentType = octet) { 159 | if (this.headers.length) { 160 | this.sendString(`${contentType[this.status]}${buf.byteLength}${CRLF}${joinHeaders(this.headers)}${END}`) 161 | this.send(buf, buf.byteLength, 0) 162 | return 163 | } 164 | this.sendString(`${contentType[this.status]}${buf.byteLength}${END}`) 165 | this.send(buf, buf.byteLength, 0) 166 | this.end() 167 | } 168 | 169 | finish () { 170 | if (this.pipeline && this.queue.length) { 171 | this.sendString(this.queue) 172 | // todo: check return codes - backpressure 173 | //const written = sendString(this.fd, this.queue) 174 | //just.print(`length ${this.queue.length} written ${written}`) 175 | this.queue = '' 176 | this.pipeline = false 177 | } 178 | } 179 | } 180 | 181 | class Request { 182 | constructor (fd, index) { 183 | this.fd = fd 184 | this.index = index 185 | this.method = getMethod(index) 186 | this.url = getUrl(index) 187 | this.params = [] 188 | this.hasHeaders = false 189 | this._headers = {} 190 | this.version = 0 191 | this.qs = '' 192 | this.path = '' 193 | this.query = null 194 | this.contentLength = 0 195 | this.bytes = 0 196 | this.onBody = (buf, len) => {} 197 | this.onEnd = () => {} 198 | } 199 | 200 | get headers () { 201 | if (this.hasHeaders) return this._headers 202 | this.version = getHeaders(this.index, this._headers) 203 | this.hasHeaders = true 204 | return this._headers 205 | } 206 | 207 | json () { 208 | const req = this 209 | let str = '' 210 | req.onBody = (buf, len, off) => { 211 | str += buf.readString(len, off) 212 | } 213 | return new Promise(resolve => { 214 | req.onEnd = () => resolve(JSON.parse(str)) 215 | }) 216 | } 217 | 218 | text () { 219 | const req = this 220 | let str = '' 221 | req.onBody = (buf, len, off) => { 222 | str += buf.readString(len, off) 223 | } 224 | return new Promise(resolve => { 225 | req.onEnd = () => resolve(str) 226 | }) 227 | } 228 | 229 | // todo: we need a drop replacement for this that is RFC compliant and safe 230 | parseUrl (qs = false) { 231 | if (!qs && this.path) return this 232 | if (qs && this.query) return this 233 | const { url } = this 234 | const i = url.indexOf(PATHSEP) 235 | if (i > -1) { 236 | this.path = url.slice(0, i) 237 | this.qs = url.slice(i + 1) 238 | } else { 239 | this.path = url 240 | this.qs = '' 241 | } 242 | if (qs) { 243 | // parse the querystring 244 | if (!this.qs) return this 245 | this.query = this.qs.split('&') 246 | .map(p => p.split('=')) 247 | .reduce((o, p) => { 248 | o[p[0]] = p[1] 249 | return o 250 | }, {}) 251 | } 252 | return this 253 | } 254 | } 255 | 256 | class Socket { 257 | constructor (fd, handler, onResponseComplete) { 258 | this.fd = fd 259 | this.handler = handler 260 | this.buf = new ArrayBuffer(bufferSize) 261 | this.len = this.buf.byteLength 262 | this.off = 0 263 | this.tls = null 264 | this.response = new Response(fd, onResponseComplete) 265 | this.request = null 266 | this.response.socket = this 267 | this.inBody = false 268 | const info = new ArrayBuffer(4) 269 | this.dv = new DataView(info) 270 | this.parser = createHandle(this.buf, info) 271 | this.server = null 272 | } 273 | 274 | close () { 275 | const { fd, buf } = this 276 | loop.remove(fd) 277 | if (this.tls) { 278 | // todo - wait for clean shutdown before removing from loop and closing? 279 | tls.shutdown(buf) 280 | tls.free(buf) 281 | } 282 | close(fd) 283 | } 284 | 285 | onEvent (fd, event) { 286 | if (checkError(fd, event)) { 287 | this.close() 288 | return true 289 | } 290 | if (this.tls) { 291 | const { handshake, secured, serverContext } = this.tls 292 | if (!handshake) { 293 | let r = 0 294 | if (!secured) { 295 | r = tls.acceptSocket(fd, serverContext, this.buf) 296 | this.tls.secured = true 297 | } else { 298 | r = tls.handshake(this.buf) 299 | } 300 | if (r === 1) { 301 | this.tls.handshake = true 302 | //just.print('handshake complete') 303 | this.response.sendString = str => { 304 | return tls.write(this.buf, this.buf.writeString(str)) 305 | } 306 | this.response.send = (buf, len, off) => { 307 | return tls.write(this.buf, this.buf.copyFrom(buf, off, len)) 308 | } 309 | return 310 | } 311 | //just.print(`handshake fail ${r}`) 312 | const err = tls.error(this.buf, r) 313 | //just.print(`handshake fail ${err}`) 314 | if (err === tls.SSL_ERROR_WANT_WRITE) { 315 | //just.print(`set EPOLLOUT`) 316 | loop.update(fd, EPOLLOUT) 317 | } else if (err === tls.SSL_ERROR_WANT_READ) { 318 | //just.print(`set EPOLLIN`) 319 | loop.update(fd, EPOLLIN) 320 | } else { 321 | //just.print(`socket handshake error ${err}: ${tls.error(this.buf, err)}`) 322 | net.shutdown(fd) 323 | } 324 | return 325 | } 326 | } 327 | if (event & EPOLLOUT) { 328 | //just.print(`EPOLLOUT ${fd}`) 329 | loop.update(fd, EPOLLIN) 330 | return 331 | } 332 | const { buf } = this 333 | let bytes = 0 334 | if (this.tls) { 335 | bytes = tls.read(buf) 336 | //just.print(`bytes ${fd} ${bytes}`) 337 | if (bytes < 0) { 338 | const err = tls.error(buf, bytes) 339 | if (err === tls.SSL_ERROR_WANT_READ) { 340 | const errno = sys.errno() 341 | if (errno !== EAGAIN) { 342 | just.print(`tls read error: ${sys.errno()}: ${sys.strerror(sys.errno())}`) 343 | } 344 | } else { 345 | just.print(`tls read error: negative bytes: ${tls.error(buf, err)}`) 346 | } 347 | return 348 | } 349 | if (bytes === 0) { 350 | const err = tls.error(buf, bytes) 351 | if (err === tls.SSL_ERROR_ZERO_RETURN) { 352 | just.print(`tls read error: ssl has been shut down: ${tls.error(buf, err)}`) 353 | } else { 354 | just.print(`tls read error: connection has been aborted: ${tls.error(buf, err)}`) 355 | } 356 | this.close() 357 | return 358 | } 359 | } else { 360 | bytes = recv(fd, this.buf, this.off, this.len) 361 | if (bytes <= 0) { 362 | this.close() 363 | return true 364 | } 365 | } 366 | if (this.inBody) { 367 | const { request } = this 368 | if (request.bytes <= bytes) { 369 | request.onBody(this.buf, request.bytes, this.off) 370 | request.inBody = false 371 | request.onEnd() 372 | this.off += request.bytes 373 | bytes -= request.bytes 374 | request.bytes = 0 375 | } else { 376 | request.onBody(this.buf, bytes, this.off) 377 | this.off = 0 378 | request.bytes -= bytes 379 | return false 380 | } 381 | } 382 | const { dv } = this 383 | // TODO: shouldn't we close here? 384 | if (bytes === 0) return 385 | // TODO: we need to loop and keep parsing until all bytes are consumed 386 | parseRequestsHandle(this.parser, this.off + bytes, 0) 387 | const r = dv.getUint32(0, true) 388 | const count = r & 0xffff 389 | const remaining = r >> 16 390 | if (count < 0) { 391 | just.error(`parse failed ${count}`) 392 | this.close() 393 | return true 394 | } 395 | // count will always be 1 if we have a body 396 | if (count === 1) { 397 | const request = this.request = new Request(fd, 0) 398 | this.handler(this.response, request, this) 399 | if (remaining > 0) { 400 | if (remaining === bytes) { 401 | const from = this.off + bytes - remaining 402 | if (from > 0) { 403 | just.print(`copyFrom ${remaining} bytes from ${from} to 0`) 404 | this.buf.copyFrom(this.buf, 0, remaining, from) 405 | } 406 | this.off = remaining 407 | return false 408 | } 409 | request.contentLength = parseInt(request.headers[CONTENT_LENGTH] || 0) 410 | request.onBody(this.buf, remaining, this.off + bytes - remaining) 411 | request.bytes = request.contentLength - remaining 412 | if (request.bytes === 0) { 413 | this.off = 0 414 | request.onEnd() 415 | this.inBody = false 416 | } else { 417 | this.inBody = true 418 | } 419 | } else { 420 | this.off = 0 421 | if (request.method === GET) return false 422 | request.contentLength = parseInt(request.headers[CONTENT_LENGTH] || 0) 423 | request.bytes = request.contentLength 424 | if (request.bytes === 0) { 425 | request.onEnd() 426 | this.inBody = false 427 | } else { 428 | this.inBody = true 429 | } 430 | } 431 | return false 432 | } 433 | this.response.pipeline = true 434 | for (let i = 0; i < count; i++) { 435 | const request = new Request(fd, i) 436 | // todo - get return code from handler to decide whether to end now or not 437 | this.handler(this.response, request, this) 438 | } 439 | if (remaining > 0) { 440 | const from = this.off + bytes - remaining 441 | if (from > 0) { 442 | just.print(`copyFrom ${remaining} bytes from ${from} to 0`) 443 | this.buf.copyFrom(this.buf, 0, remaining, from) 444 | } 445 | this.off = remaining 446 | } else { 447 | this.off = 0 448 | } 449 | this.response.finish() 450 | return false 451 | } 452 | } 453 | 454 | class Server { 455 | constructor (opts = { client: {}, server: {} }) { 456 | this.fd = -1 457 | this.staticHandlers = {} 458 | this.regexHandlers = {} 459 | this.hooks = { pre: [], post: [], connect: [], disconnect: [] } 460 | this.defaultHandler = this.notFound 461 | this.name = opts.name || 'just' 462 | const server = this 463 | this.timer = setInterval(() => createResponses(server.name), 200) 464 | this.opts = opts 465 | this.onError = undefined 466 | this.sockets = {} 467 | this.error = 0 468 | this.address = '127.0.0.1' 469 | this.port = 3000 470 | this.stackTraces = false 471 | this.tls = opts.tls || null 472 | } 473 | 474 | connect (handler) { 475 | this.hooks.connect.push(handler) 476 | return this 477 | } 478 | 479 | disconnect (handler) { 480 | this.hooks.disconnect.push(handler) 481 | return this 482 | } 483 | 484 | notFound (res, req) { 485 | res.status = 404 486 | res.text(`Not Found ${req.url}`) 487 | } 488 | 489 | // todo: server.badRequest, server.forbidden, etc. 490 | 491 | serverError (res, req, err) { 492 | res.status = 500 493 | if (this.stackTraces) { 494 | res.text(` 495 | error: ${err.toString()} 496 | stack: 497 | ${err.stack} 498 | `) 499 | return 500 | } 501 | res.text(err.toString()) 502 | } 503 | 504 | match (url, method) { 505 | for (const handler of this.regexHandlers[method]) { 506 | const match = url.match(handler.path) 507 | if (match) { 508 | return [handler.handler, match.slice(1)] 509 | } 510 | } 511 | return [null, null] 512 | } 513 | 514 | addPath (path, handler, method, opts) { 515 | if (opts) handler.opts = opts 516 | if (!this.staticHandlers[method]) this.staticHandlers[method] = {} 517 | if (!this.regexHandlers[method]) this.regexHandlers[method] = [] 518 | if (handler.constructor.name === 'AsyncFunction') { 519 | if (handler.opts) { 520 | handler.opts.async = true 521 | } else { 522 | handler.opts = { async: true } 523 | } 524 | } 525 | if (typeof path === 'string') { 526 | this.staticHandlers[method][path] = handler 527 | return 528 | } 529 | if (typeof path === 'object') { 530 | if (path.constructor.name === 'RegExp') { 531 | this.regexHandlers[method].push({ handler, path }) 532 | } else if (path.constructor.name === 'Array') { 533 | for (const p of path) { 534 | this.staticHandlers[method][p] = handler 535 | } 536 | } 537 | } 538 | return this 539 | } 540 | 541 | removePath (method, path) { 542 | // this only works for staticHandlers as we cannot compared to regex 543 | delete this.staticHandlers[method][path] 544 | } 545 | 546 | get (path, handler, opts) { 547 | if (opts) handler.opts = opts 548 | this.addPath(path, handler, methods.get) 549 | return this 550 | } 551 | 552 | put (path, handler, opts) { 553 | if (opts) handler.opts = opts 554 | this.addPath(path, handler, methods.put) 555 | return this 556 | } 557 | 558 | post (path, handler, opts) { 559 | if (opts) handler.opts = opts 560 | this.addPath(path, handler, methods.post) 561 | return this 562 | } 563 | 564 | delete (path, handler, opts) { 565 | if (opts) handler.opts = opts 566 | this.addPath(path, handler, methods.delete) 567 | return this 568 | } 569 | 570 | options (path, handler, opts) { 571 | if (opts) handler.opts = opts 572 | this.addPath(path, handler, methods.options) 573 | return this 574 | } 575 | 576 | default (handler, opts) { 577 | if (opts) handler.opts = opts 578 | this.defaultHandler = handler 579 | return this 580 | } 581 | 582 | close () { 583 | loop.remove(this.fd) 584 | net.close(this.fd) 585 | just.clearInterval(this.timer) 586 | } 587 | 588 | handleRequest (response, request) { 589 | const server = this 590 | if (this.hooks.pre.length) { 591 | for (const handler of this.hooks.pre) handler(response, request) 592 | } 593 | if (response.complete) return 594 | const methodHandler = this.staticHandlers[request.method] 595 | if (!methodHandler) { 596 | this.defaultHandler(response, request) 597 | return 598 | } 599 | let handler = methodHandler[request.url] 600 | if (handler) { 601 | if (handler.opts) { 602 | if (handler.opts.async) { 603 | handler(response, request).catch(err => server.serverError(response, request, err)) 604 | return 605 | } 606 | if (handler.opts.err) { 607 | try { 608 | handler(response, request) 609 | } catch (err) { 610 | this.serverError(response, request, err) 611 | } 612 | return 613 | } 614 | } 615 | handler(response, request) 616 | return 617 | } 618 | request.parseUrl() 619 | handler = methodHandler[request.path] 620 | if (handler) { 621 | if (handler.opts) { 622 | if (handler.opts.async) { 623 | handler(response, request).catch(err => server.serverError(response, request, err)) 624 | return 625 | } 626 | if (handler.opts.err) { 627 | try { 628 | handler(response, request) 629 | } catch (err) { 630 | this.serverError(response, request, err) 631 | } 632 | return 633 | } 634 | } 635 | handler(response, request) 636 | return 637 | } 638 | const result = this.match(request.path, request.method) 639 | if (result[0]) { 640 | request.params = result[1] 641 | result[0](response, request) 642 | return 643 | } 644 | handler = this.defaultHandler 645 | if (handler.opts) { 646 | if (handler.opts.err) { 647 | try { 648 | handler(response, request) 649 | } catch (err) { 650 | this.serverError(response, request, err) 651 | } 652 | return 653 | } 654 | } 655 | handler(response, request) 656 | } 657 | 658 | use (handler, post = false) { 659 | if (post) { 660 | this.hooks.post.push(handler) 661 | return this 662 | } 663 | this.hooks.pre.push(handler) 664 | return this 665 | } 666 | 667 | listen (port = 3000, address = '127.0.0.1', maxConn = SOMAXCONN) { 668 | const server = this 669 | const fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0) 670 | if (fd < 1) return fd 671 | const { hooks, opts, sockets } = server 672 | server.fd = fd 673 | server.address = address 674 | server.port = port 675 | serverOptions(fd, opts) 676 | let r = bind(fd, address, port) 677 | if (r < 0) return r 678 | r = listen(fd, maxConn) 679 | if (r < 0) return r 680 | const requestHandler = (response, request) => server.handleRequest(response, request) 681 | function onResponseComplete (response, request) { 682 | for (const handler of hooks.post) handler(response, request) 683 | } 684 | loop.add(fd, (fd, event) => { 685 | // todo: surface any socket errors 686 | if (checkError(fd, event)) return 687 | const newfd = accept(fd) 688 | clientOptions(newfd, opts) 689 | let socket 690 | if (hooks.post.length) { 691 | socket = new Socket(newfd, requestHandler, onResponseComplete) 692 | } else { 693 | socket = new Socket(newfd, requestHandler, () => {}) 694 | } 695 | socket.server = this 696 | if (server.tls) socket.tls = { serverContext: server.tls.context } 697 | loop.add(newfd, (fd, event) => { 698 | if (socket.onEvent(fd, event)) { 699 | if (hooks.disconnect.length) { 700 | for (const handler of hooks.disconnect) handler(socket) 701 | } 702 | delete sockets[fd] 703 | } 704 | }) 705 | const flags = fcntl(newfd, F_GETFL, 0) | O_NONBLOCK 706 | fcntl(newfd, F_SETFL, flags) 707 | loop.update(newfd, EPOLLIN | EPOLLERR | EPOLLHUP) 708 | sockets[newfd] = socket 709 | if (hooks.connect.length) { 710 | for (const handler of hooks.connect) handler(socket) 711 | } 712 | }) 713 | return server 714 | } 715 | } 716 | const methods = { 717 | get: 'GET'.charCodeAt(0), 718 | put: 'PUT'.charCodeAt(0), 719 | post: 'POST'.charCodeAt(0), 720 | delete: 'DELETE'.charCodeAt(0), 721 | options: 'OPTIONS'.charCodeAt(0) 722 | } 723 | const contentTypes = { 724 | text: 'text/plain', 725 | css: 'text/css', 726 | utf8: 'text/plain; charset=utf-8', 727 | json: 'application/json; charset=utf-8', 728 | html: 'text/html; charset=utf-8', 729 | octet: 'application/octet-stream' 730 | } 731 | const statusMessages = { 732 | 200: 'OK', 733 | 201: 'Created', 734 | 204: 'OK', 735 | 101: 'Switching Protocols', 736 | 400: 'Bad Request', 737 | 401: 'Unauthorized', 738 | 403: 'Forbidden', 739 | 404: 'Not Found', 740 | 500: 'Server Error' 741 | } 742 | const CONTENT_LENGTH = 'Content-Length' 743 | const GET = 'GET' 744 | const bufferSize = 64 * 1024 745 | const responses = { js: {}, text: {}, utf8: {}, json: {}, html: {}, css: {}, octet: {} } 746 | responses.ico = {} 747 | responses.png = {} 748 | responses.xml = {} 749 | contentTypes.ico = 'application/favicon' 750 | contentTypes.png = 'application/png' 751 | contentTypes.xml = 'application/xml; charset=utf-8' 752 | contentTypes.js = 'application/javascript; charset=utf-8' 753 | const END = '\r\n\r\n' 754 | const CRLF = '\r\n' 755 | const PATHSEP = '?' 756 | const { text, utf8, json, html, octet } = responses 757 | const defaultOptions = { 758 | name: 'just', 759 | server: { 760 | reuseAddress: true, 761 | reusePort: true 762 | }, 763 | client: { 764 | tcpNoDelay: false, 765 | soKeepAlive: false 766 | } 767 | } 768 | 769 | module.exports = { 770 | createServer: (opts = defaultOptions, handler) => { 771 | if (opts.tls) { 772 | const options = BigInt(opts.tls.options || (SSL_OP_ALL | SSL_OP_NO_RENEGOTIATION | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1 | SSL_OP_NO_DTLSv1 | SSL_OP_NO_DTLSv1_2)) 773 | const { cert = 'cert.pem', key = 'key.pem' } = opts.tls 774 | opts.tls.context = tls.serverContext(new ArrayBuffer(0), cert, key, options) 775 | } 776 | const o = JSON.parse(JSON.stringify(defaultOptions)) 777 | const server = new Server(Object.assign(o, opts)) 778 | if (handler) server.default(handler) 779 | return server 780 | }, 781 | defaultOptions, 782 | responses, 783 | contentTypes, 784 | Socket, 785 | Server, 786 | Request, 787 | Response 788 | } 789 | -------------------------------------------------------------------------------- /md5/md5.js: -------------------------------------------------------------------------------- 1 | /** 2 | * [js-md5]{@link https://github.com/emn178/js-md5} 3 | * 4 | * @namespace md5 5 | * @version 0.7.3 6 | * @author Chen, Yi-Cyuan [emn178@gmail.com] 7 | * @copyright Chen, Yi-Cyuan 2014-2017 8 | * @license MIT 9 | */ 10 | (function () { 11 | 'use strict'; 12 | 13 | var ERROR = 'input is invalid type'; 14 | var WINDOW = typeof window === 'object'; 15 | var root = WINDOW ? window : {}; 16 | if (root.JS_MD5_NO_WINDOW) { 17 | WINDOW = false; 18 | } 19 | var WEB_WORKER = !WINDOW && typeof self === 'object'; 20 | var NODE_JS = !root.JS_MD5_NO_NODE_JS && typeof process === 'object' && process.versions && process.versions.node; 21 | if (NODE_JS) { 22 | root = global; 23 | } else if (WEB_WORKER) { 24 | root = self; 25 | } 26 | var COMMON_JS = !root.JS_MD5_NO_COMMON_JS && typeof module === 'object' && module.exports; 27 | var AMD = typeof define === 'function' && define.amd; 28 | var ARRAY_BUFFER = !root.JS_MD5_NO_ARRAY_BUFFER && typeof ArrayBuffer !== 'undefined'; 29 | var HEX_CHARS = '0123456789abcdef'.split(''); 30 | var EXTRA = [128, 32768, 8388608, -2147483648]; 31 | var SHIFT = [0, 8, 16, 24]; 32 | var OUTPUT_TYPES = ['hex', 'array', 'digest', 'buffer', 'arrayBuffer', 'base64']; 33 | var BASE64_ENCODE_CHAR = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split(''); 34 | 35 | var blocks = [], buffer8; 36 | if (ARRAY_BUFFER) { 37 | var buffer = new ArrayBuffer(68); 38 | buffer8 = new Uint8Array(buffer); 39 | blocks = new Uint32Array(buffer); 40 | } 41 | 42 | if (root.JS_MD5_NO_NODE_JS || !Array.isArray) { 43 | Array.isArray = function (obj) { 44 | return Object.prototype.toString.call(obj) === '[object Array]'; 45 | }; 46 | } 47 | 48 | if (ARRAY_BUFFER && (root.JS_MD5_NO_ARRAY_BUFFER_IS_VIEW || !ArrayBuffer.isView)) { 49 | ArrayBuffer.isView = function (obj) { 50 | return typeof obj === 'object' && obj.buffer && obj.buffer.constructor === ArrayBuffer; 51 | }; 52 | } 53 | 54 | /** 55 | * @method hex 56 | * @memberof md5 57 | * @description Output hash as hex string 58 | * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash 59 | * @returns {String} Hex string 60 | * @example 61 | * md5.hex('The quick brown fox jumps over the lazy dog'); 62 | * // equal to 63 | * md5('The quick brown fox jumps over the lazy dog'); 64 | */ 65 | /** 66 | * @method digest 67 | * @memberof md5 68 | * @description Output hash as bytes array 69 | * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash 70 | * @returns {Array} Bytes array 71 | * @example 72 | * md5.digest('The quick brown fox jumps over the lazy dog'); 73 | */ 74 | /** 75 | * @method array 76 | * @memberof md5 77 | * @description Output hash as bytes array 78 | * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash 79 | * @returns {Array} Bytes array 80 | * @example 81 | * md5.array('The quick brown fox jumps over the lazy dog'); 82 | */ 83 | /** 84 | * @method arrayBuffer 85 | * @memberof md5 86 | * @description Output hash as ArrayBuffer 87 | * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash 88 | * @returns {ArrayBuffer} ArrayBuffer 89 | * @example 90 | * md5.arrayBuffer('The quick brown fox jumps over the lazy dog'); 91 | */ 92 | /** 93 | * @method buffer 94 | * @deprecated This maybe confuse with Buffer in node.js. Please use arrayBuffer instead. 95 | * @memberof md5 96 | * @description Output hash as ArrayBuffer 97 | * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash 98 | * @returns {ArrayBuffer} ArrayBuffer 99 | * @example 100 | * md5.buffer('The quick brown fox jumps over the lazy dog'); 101 | */ 102 | /** 103 | * @method base64 104 | * @memberof md5 105 | * @description Output hash as base64 string 106 | * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash 107 | * @returns {String} base64 string 108 | * @example 109 | * md5.base64('The quick brown fox jumps over the lazy dog'); 110 | */ 111 | var createOutputMethod = function (outputType) { 112 | return function (message) { 113 | return new Md5(true).update(message)[outputType](); 114 | }; 115 | }; 116 | 117 | /** 118 | * @method create 119 | * @memberof md5 120 | * @description Create Md5 object 121 | * @returns {Md5} Md5 object. 122 | * @example 123 | * var hash = md5.create(); 124 | */ 125 | /** 126 | * @method update 127 | * @memberof md5 128 | * @description Create and update Md5 object 129 | * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash 130 | * @returns {Md5} Md5 object. 131 | * @example 132 | * var hash = md5.update('The quick brown fox jumps over the lazy dog'); 133 | * // equal to 134 | * var hash = md5.create(); 135 | * hash.update('The quick brown fox jumps over the lazy dog'); 136 | */ 137 | var createMethod = function () { 138 | var method = createOutputMethod('hex'); 139 | if (NODE_JS) { 140 | method = nodeWrap(method); 141 | } 142 | method.create = function () { 143 | return new Md5(); 144 | }; 145 | method.update = function (message) { 146 | return method.create().update(message); 147 | }; 148 | for (var i = 0; i < OUTPUT_TYPES.length; ++i) { 149 | var type = OUTPUT_TYPES[i]; 150 | method[type] = createOutputMethod(type); 151 | } 152 | return method; 153 | }; 154 | 155 | var nodeWrap = function (method) { 156 | var crypto = eval("require('crypto')"); 157 | var Buffer = eval("require('buffer').Buffer"); 158 | var nodeMethod = function (message) { 159 | if (typeof message === 'string') { 160 | return crypto.createHash('md5').update(message, 'utf8').digest('hex'); 161 | } else { 162 | if (message === null || message === undefined) { 163 | throw ERROR; 164 | } else if (message.constructor === ArrayBuffer) { 165 | message = new Uint8Array(message); 166 | } 167 | } 168 | if (Array.isArray(message) || ArrayBuffer.isView(message) || 169 | message.constructor === Buffer) { 170 | return crypto.createHash('md5').update(new Buffer(message)).digest('hex'); 171 | } else { 172 | return method(message); 173 | } 174 | }; 175 | return nodeMethod; 176 | }; 177 | 178 | /** 179 | * Md5 class 180 | * @class Md5 181 | * @description This is internal class. 182 | * @see {@link md5.create} 183 | */ 184 | function Md5(sharedMemory) { 185 | if (sharedMemory) { 186 | blocks[0] = blocks[16] = blocks[1] = blocks[2] = blocks[3] = 187 | blocks[4] = blocks[5] = blocks[6] = blocks[7] = 188 | blocks[8] = blocks[9] = blocks[10] = blocks[11] = 189 | blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0; 190 | this.blocks = blocks; 191 | this.buffer8 = buffer8; 192 | } else { 193 | if (ARRAY_BUFFER) { 194 | var buffer = new ArrayBuffer(68); 195 | this.buffer8 = new Uint8Array(buffer); 196 | this.blocks = new Uint32Array(buffer); 197 | } else { 198 | this.blocks = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; 199 | } 200 | } 201 | this.h0 = this.h1 = this.h2 = this.h3 = this.start = this.bytes = this.hBytes = 0; 202 | this.finalized = this.hashed = false; 203 | this.first = true; 204 | } 205 | 206 | /** 207 | * @method update 208 | * @memberof Md5 209 | * @instance 210 | * @description Update hash 211 | * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash 212 | * @returns {Md5} Md5 object. 213 | * @see {@link md5.update} 214 | */ 215 | Md5.prototype.update = function (message) { 216 | if (this.finalized) { 217 | return; 218 | } 219 | 220 | var notString, type = typeof message; 221 | if (type !== 'string') { 222 | if (type === 'object') { 223 | if (message === null) { 224 | throw ERROR; 225 | } else if (ARRAY_BUFFER && message.constructor === ArrayBuffer) { 226 | message = new Uint8Array(message); 227 | } else if (!Array.isArray(message)) { 228 | if (!ARRAY_BUFFER || !ArrayBuffer.isView(message)) { 229 | throw ERROR; 230 | } 231 | } 232 | } else { 233 | throw ERROR; 234 | } 235 | notString = true; 236 | } 237 | var code, index = 0, i, length = message.length, blocks = this.blocks; 238 | var buffer8 = this.buffer8; 239 | 240 | while (index < length) { 241 | if (this.hashed) { 242 | this.hashed = false; 243 | blocks[0] = blocks[16]; 244 | blocks[16] = blocks[1] = blocks[2] = blocks[3] = 245 | blocks[4] = blocks[5] = blocks[6] = blocks[7] = 246 | blocks[8] = blocks[9] = blocks[10] = blocks[11] = 247 | blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0; 248 | } 249 | 250 | if (notString) { 251 | if (ARRAY_BUFFER) { 252 | for (i = this.start; index < length && i < 64; ++index) { 253 | buffer8[i++] = message[index]; 254 | } 255 | } else { 256 | for (i = this.start; index < length && i < 64; ++index) { 257 | blocks[i >> 2] |= message[index] << SHIFT[i++ & 3]; 258 | } 259 | } 260 | } else { 261 | if (ARRAY_BUFFER) { 262 | for (i = this.start; index < length && i < 64; ++index) { 263 | code = message.charCodeAt(index); 264 | if (code < 0x80) { 265 | buffer8[i++] = code; 266 | } else if (code < 0x800) { 267 | buffer8[i++] = 0xc0 | (code >> 6); 268 | buffer8[i++] = 0x80 | (code & 0x3f); 269 | } else if (code < 0xd800 || code >= 0xe000) { 270 | buffer8[i++] = 0xe0 | (code >> 12); 271 | buffer8[i++] = 0x80 | ((code >> 6) & 0x3f); 272 | buffer8[i++] = 0x80 | (code & 0x3f); 273 | } else { 274 | code = 0x10000 + (((code & 0x3ff) << 10) | (message.charCodeAt(++index) & 0x3ff)); 275 | buffer8[i++] = 0xf0 | (code >> 18); 276 | buffer8[i++] = 0x80 | ((code >> 12) & 0x3f); 277 | buffer8[i++] = 0x80 | ((code >> 6) & 0x3f); 278 | buffer8[i++] = 0x80 | (code & 0x3f); 279 | } 280 | } 281 | } else { 282 | for (i = this.start; index < length && i < 64; ++index) { 283 | code = message.charCodeAt(index); 284 | if (code < 0x80) { 285 | blocks[i >> 2] |= code << SHIFT[i++ & 3]; 286 | } else if (code < 0x800) { 287 | blocks[i >> 2] |= (0xc0 | (code >> 6)) << SHIFT[i++ & 3]; 288 | blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3]; 289 | } else if (code < 0xd800 || code >= 0xe000) { 290 | blocks[i >> 2] |= (0xe0 | (code >> 12)) << SHIFT[i++ & 3]; 291 | blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3]; 292 | blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3]; 293 | } else { 294 | code = 0x10000 + (((code & 0x3ff) << 10) | (message.charCodeAt(++index) & 0x3ff)); 295 | blocks[i >> 2] |= (0xf0 | (code >> 18)) << SHIFT[i++ & 3]; 296 | blocks[i >> 2] |= (0x80 | ((code >> 12) & 0x3f)) << SHIFT[i++ & 3]; 297 | blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3]; 298 | blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3]; 299 | } 300 | } 301 | } 302 | } 303 | this.lastByteIndex = i; 304 | this.bytes += i - this.start; 305 | if (i >= 64) { 306 | this.start = i - 64; 307 | this.hash(); 308 | this.hashed = true; 309 | } else { 310 | this.start = i; 311 | } 312 | } 313 | if (this.bytes > 4294967295) { 314 | this.hBytes += this.bytes / 4294967296 << 0; 315 | this.bytes = this.bytes % 4294967296; 316 | } 317 | return this; 318 | }; 319 | 320 | Md5.prototype.finalize = function () { 321 | if (this.finalized) { 322 | return; 323 | } 324 | this.finalized = true; 325 | var blocks = this.blocks, i = this.lastByteIndex; 326 | blocks[i >> 2] |= EXTRA[i & 3]; 327 | if (i >= 56) { 328 | if (!this.hashed) { 329 | this.hash(); 330 | } 331 | blocks[0] = blocks[16]; 332 | blocks[16] = blocks[1] = blocks[2] = blocks[3] = 333 | blocks[4] = blocks[5] = blocks[6] = blocks[7] = 334 | blocks[8] = blocks[9] = blocks[10] = blocks[11] = 335 | blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0; 336 | } 337 | blocks[14] = this.bytes << 3; 338 | blocks[15] = this.hBytes << 3 | this.bytes >>> 29; 339 | this.hash(); 340 | }; 341 | 342 | Md5.prototype.hash = function () { 343 | var a, b, c, d, bc, da, blocks = this.blocks; 344 | 345 | if (this.first) { 346 | a = blocks[0] - 680876937; 347 | a = (a << 7 | a >>> 25) - 271733879 << 0; 348 | d = (-1732584194 ^ a & 2004318071) + blocks[1] - 117830708; 349 | d = (d << 12 | d >>> 20) + a << 0; 350 | c = (-271733879 ^ (d & (a ^ -271733879))) + blocks[2] - 1126478375; 351 | c = (c << 17 | c >>> 15) + d << 0; 352 | b = (a ^ (c & (d ^ a))) + blocks[3] - 1316259209; 353 | b = (b << 22 | b >>> 10) + c << 0; 354 | } else { 355 | a = this.h0; 356 | b = this.h1; 357 | c = this.h2; 358 | d = this.h3; 359 | a += (d ^ (b & (c ^ d))) + blocks[0] - 680876936; 360 | a = (a << 7 | a >>> 25) + b << 0; 361 | d += (c ^ (a & (b ^ c))) + blocks[1] - 389564586; 362 | d = (d << 12 | d >>> 20) + a << 0; 363 | c += (b ^ (d & (a ^ b))) + blocks[2] + 606105819; 364 | c = (c << 17 | c >>> 15) + d << 0; 365 | b += (a ^ (c & (d ^ a))) + blocks[3] - 1044525330; 366 | b = (b << 22 | b >>> 10) + c << 0; 367 | } 368 | 369 | a += (d ^ (b & (c ^ d))) + blocks[4] - 176418897; 370 | a = (a << 7 | a >>> 25) + b << 0; 371 | d += (c ^ (a & (b ^ c))) + blocks[5] + 1200080426; 372 | d = (d << 12 | d >>> 20) + a << 0; 373 | c += (b ^ (d & (a ^ b))) + blocks[6] - 1473231341; 374 | c = (c << 17 | c >>> 15) + d << 0; 375 | b += (a ^ (c & (d ^ a))) + blocks[7] - 45705983; 376 | b = (b << 22 | b >>> 10) + c << 0; 377 | a += (d ^ (b & (c ^ d))) + blocks[8] + 1770035416; 378 | a = (a << 7 | a >>> 25) + b << 0; 379 | d += (c ^ (a & (b ^ c))) + blocks[9] - 1958414417; 380 | d = (d << 12 | d >>> 20) + a << 0; 381 | c += (b ^ (d & (a ^ b))) + blocks[10] - 42063; 382 | c = (c << 17 | c >>> 15) + d << 0; 383 | b += (a ^ (c & (d ^ a))) + blocks[11] - 1990404162; 384 | b = (b << 22 | b >>> 10) + c << 0; 385 | a += (d ^ (b & (c ^ d))) + blocks[12] + 1804603682; 386 | a = (a << 7 | a >>> 25) + b << 0; 387 | d += (c ^ (a & (b ^ c))) + blocks[13] - 40341101; 388 | d = (d << 12 | d >>> 20) + a << 0; 389 | c += (b ^ (d & (a ^ b))) + blocks[14] - 1502002290; 390 | c = (c << 17 | c >>> 15) + d << 0; 391 | b += (a ^ (c & (d ^ a))) + blocks[15] + 1236535329; 392 | b = (b << 22 | b >>> 10) + c << 0; 393 | a += (c ^ (d & (b ^ c))) + blocks[1] - 165796510; 394 | a = (a << 5 | a >>> 27) + b << 0; 395 | d += (b ^ (c & (a ^ b))) + blocks[6] - 1069501632; 396 | d = (d << 9 | d >>> 23) + a << 0; 397 | c += (a ^ (b & (d ^ a))) + blocks[11] + 643717713; 398 | c = (c << 14 | c >>> 18) + d << 0; 399 | b += (d ^ (a & (c ^ d))) + blocks[0] - 373897302; 400 | b = (b << 20 | b >>> 12) + c << 0; 401 | a += (c ^ (d & (b ^ c))) + blocks[5] - 701558691; 402 | a = (a << 5 | a >>> 27) + b << 0; 403 | d += (b ^ (c & (a ^ b))) + blocks[10] + 38016083; 404 | d = (d << 9 | d >>> 23) + a << 0; 405 | c += (a ^ (b & (d ^ a))) + blocks[15] - 660478335; 406 | c = (c << 14 | c >>> 18) + d << 0; 407 | b += (d ^ (a & (c ^ d))) + blocks[4] - 405537848; 408 | b = (b << 20 | b >>> 12) + c << 0; 409 | a += (c ^ (d & (b ^ c))) + blocks[9] + 568446438; 410 | a = (a << 5 | a >>> 27) + b << 0; 411 | d += (b ^ (c & (a ^ b))) + blocks[14] - 1019803690; 412 | d = (d << 9 | d >>> 23) + a << 0; 413 | c += (a ^ (b & (d ^ a))) + blocks[3] - 187363961; 414 | c = (c << 14 | c >>> 18) + d << 0; 415 | b += (d ^ (a & (c ^ d))) + blocks[8] + 1163531501; 416 | b = (b << 20 | b >>> 12) + c << 0; 417 | a += (c ^ (d & (b ^ c))) + blocks[13] - 1444681467; 418 | a = (a << 5 | a >>> 27) + b << 0; 419 | d += (b ^ (c & (a ^ b))) + blocks[2] - 51403784; 420 | d = (d << 9 | d >>> 23) + a << 0; 421 | c += (a ^ (b & (d ^ a))) + blocks[7] + 1735328473; 422 | c = (c << 14 | c >>> 18) + d << 0; 423 | b += (d ^ (a & (c ^ d))) + blocks[12] - 1926607734; 424 | b = (b << 20 | b >>> 12) + c << 0; 425 | bc = b ^ c; 426 | a += (bc ^ d) + blocks[5] - 378558; 427 | a = (a << 4 | a >>> 28) + b << 0; 428 | d += (bc ^ a) + blocks[8] - 2022574463; 429 | d = (d << 11 | d >>> 21) + a << 0; 430 | da = d ^ a; 431 | c += (da ^ b) + blocks[11] + 1839030562; 432 | c = (c << 16 | c >>> 16) + d << 0; 433 | b += (da ^ c) + blocks[14] - 35309556; 434 | b = (b << 23 | b >>> 9) + c << 0; 435 | bc = b ^ c; 436 | a += (bc ^ d) + blocks[1] - 1530992060; 437 | a = (a << 4 | a >>> 28) + b << 0; 438 | d += (bc ^ a) + blocks[4] + 1272893353; 439 | d = (d << 11 | d >>> 21) + a << 0; 440 | da = d ^ a; 441 | c += (da ^ b) + blocks[7] - 155497632; 442 | c = (c << 16 | c >>> 16) + d << 0; 443 | b += (da ^ c) + blocks[10] - 1094730640; 444 | b = (b << 23 | b >>> 9) + c << 0; 445 | bc = b ^ c; 446 | a += (bc ^ d) + blocks[13] + 681279174; 447 | a = (a << 4 | a >>> 28) + b << 0; 448 | d += (bc ^ a) + blocks[0] - 358537222; 449 | d = (d << 11 | d >>> 21) + a << 0; 450 | da = d ^ a; 451 | c += (da ^ b) + blocks[3] - 722521979; 452 | c = (c << 16 | c >>> 16) + d << 0; 453 | b += (da ^ c) + blocks[6] + 76029189; 454 | b = (b << 23 | b >>> 9) + c << 0; 455 | bc = b ^ c; 456 | a += (bc ^ d) + blocks[9] - 640364487; 457 | a = (a << 4 | a >>> 28) + b << 0; 458 | d += (bc ^ a) + blocks[12] - 421815835; 459 | d = (d << 11 | d >>> 21) + a << 0; 460 | da = d ^ a; 461 | c += (da ^ b) + blocks[15] + 530742520; 462 | c = (c << 16 | c >>> 16) + d << 0; 463 | b += (da ^ c) + blocks[2] - 995338651; 464 | b = (b << 23 | b >>> 9) + c << 0; 465 | a += (c ^ (b | ~d)) + blocks[0] - 198630844; 466 | a = (a << 6 | a >>> 26) + b << 0; 467 | d += (b ^ (a | ~c)) + blocks[7] + 1126891415; 468 | d = (d << 10 | d >>> 22) + a << 0; 469 | c += (a ^ (d | ~b)) + blocks[14] - 1416354905; 470 | c = (c << 15 | c >>> 17) + d << 0; 471 | b += (d ^ (c | ~a)) + blocks[5] - 57434055; 472 | b = (b << 21 | b >>> 11) + c << 0; 473 | a += (c ^ (b | ~d)) + blocks[12] + 1700485571; 474 | a = (a << 6 | a >>> 26) + b << 0; 475 | d += (b ^ (a | ~c)) + blocks[3] - 1894986606; 476 | d = (d << 10 | d >>> 22) + a << 0; 477 | c += (a ^ (d | ~b)) + blocks[10] - 1051523; 478 | c = (c << 15 | c >>> 17) + d << 0; 479 | b += (d ^ (c | ~a)) + blocks[1] - 2054922799; 480 | b = (b << 21 | b >>> 11) + c << 0; 481 | a += (c ^ (b | ~d)) + blocks[8] + 1873313359; 482 | a = (a << 6 | a >>> 26) + b << 0; 483 | d += (b ^ (a | ~c)) + blocks[15] - 30611744; 484 | d = (d << 10 | d >>> 22) + a << 0; 485 | c += (a ^ (d | ~b)) + blocks[6] - 1560198380; 486 | c = (c << 15 | c >>> 17) + d << 0; 487 | b += (d ^ (c | ~a)) + blocks[13] + 1309151649; 488 | b = (b << 21 | b >>> 11) + c << 0; 489 | a += (c ^ (b | ~d)) + blocks[4] - 145523070; 490 | a = (a << 6 | a >>> 26) + b << 0; 491 | d += (b ^ (a | ~c)) + blocks[11] - 1120210379; 492 | d = (d << 10 | d >>> 22) + a << 0; 493 | c += (a ^ (d | ~b)) + blocks[2] + 718787259; 494 | c = (c << 15 | c >>> 17) + d << 0; 495 | b += (d ^ (c | ~a)) + blocks[9] - 343485551; 496 | b = (b << 21 | b >>> 11) + c << 0; 497 | 498 | if (this.first) { 499 | this.h0 = a + 1732584193 << 0; 500 | this.h1 = b - 271733879 << 0; 501 | this.h2 = c - 1732584194 << 0; 502 | this.h3 = d + 271733878 << 0; 503 | this.first = false; 504 | } else { 505 | this.h0 = this.h0 + a << 0; 506 | this.h1 = this.h1 + b << 0; 507 | this.h2 = this.h2 + c << 0; 508 | this.h3 = this.h3 + d << 0; 509 | } 510 | }; 511 | 512 | /** 513 | * @method hex 514 | * @memberof Md5 515 | * @instance 516 | * @description Output hash as hex string 517 | * @returns {String} Hex string 518 | * @see {@link md5.hex} 519 | * @example 520 | * hash.hex(); 521 | */ 522 | Md5.prototype.hex = function () { 523 | this.finalize(); 524 | 525 | var h0 = this.h0, h1 = this.h1, h2 = this.h2, h3 = this.h3; 526 | 527 | return HEX_CHARS[(h0 >> 4) & 0x0F] + HEX_CHARS[h0 & 0x0F] + 528 | HEX_CHARS[(h0 >> 12) & 0x0F] + HEX_CHARS[(h0 >> 8) & 0x0F] + 529 | HEX_CHARS[(h0 >> 20) & 0x0F] + HEX_CHARS[(h0 >> 16) & 0x0F] + 530 | HEX_CHARS[(h0 >> 28) & 0x0F] + HEX_CHARS[(h0 >> 24) & 0x0F] + 531 | HEX_CHARS[(h1 >> 4) & 0x0F] + HEX_CHARS[h1 & 0x0F] + 532 | HEX_CHARS[(h1 >> 12) & 0x0F] + HEX_CHARS[(h1 >> 8) & 0x0F] + 533 | HEX_CHARS[(h1 >> 20) & 0x0F] + HEX_CHARS[(h1 >> 16) & 0x0F] + 534 | HEX_CHARS[(h1 >> 28) & 0x0F] + HEX_CHARS[(h1 >> 24) & 0x0F] + 535 | HEX_CHARS[(h2 >> 4) & 0x0F] + HEX_CHARS[h2 & 0x0F] + 536 | HEX_CHARS[(h2 >> 12) & 0x0F] + HEX_CHARS[(h2 >> 8) & 0x0F] + 537 | HEX_CHARS[(h2 >> 20) & 0x0F] + HEX_CHARS[(h2 >> 16) & 0x0F] + 538 | HEX_CHARS[(h2 >> 28) & 0x0F] + HEX_CHARS[(h2 >> 24) & 0x0F] + 539 | HEX_CHARS[(h3 >> 4) & 0x0F] + HEX_CHARS[h3 & 0x0F] + 540 | HEX_CHARS[(h3 >> 12) & 0x0F] + HEX_CHARS[(h3 >> 8) & 0x0F] + 541 | HEX_CHARS[(h3 >> 20) & 0x0F] + HEX_CHARS[(h3 >> 16) & 0x0F] + 542 | HEX_CHARS[(h3 >> 28) & 0x0F] + HEX_CHARS[(h3 >> 24) & 0x0F]; 543 | }; 544 | 545 | /** 546 | * @method toString 547 | * @memberof Md5 548 | * @instance 549 | * @description Output hash as hex string 550 | * @returns {String} Hex string 551 | * @see {@link md5.hex} 552 | * @example 553 | * hash.toString(); 554 | */ 555 | Md5.prototype.toString = Md5.prototype.hex; 556 | 557 | /** 558 | * @method digest 559 | * @memberof Md5 560 | * @instance 561 | * @description Output hash as bytes array 562 | * @returns {Array} Bytes array 563 | * @see {@link md5.digest} 564 | * @example 565 | * hash.digest(); 566 | */ 567 | Md5.prototype.digest = function () { 568 | this.finalize(); 569 | 570 | var h0 = this.h0, h1 = this.h1, h2 = this.h2, h3 = this.h3; 571 | return [ 572 | h0 & 0xFF, (h0 >> 8) & 0xFF, (h0 >> 16) & 0xFF, (h0 >> 24) & 0xFF, 573 | h1 & 0xFF, (h1 >> 8) & 0xFF, (h1 >> 16) & 0xFF, (h1 >> 24) & 0xFF, 574 | h2 & 0xFF, (h2 >> 8) & 0xFF, (h2 >> 16) & 0xFF, (h2 >> 24) & 0xFF, 575 | h3 & 0xFF, (h3 >> 8) & 0xFF, (h3 >> 16) & 0xFF, (h3 >> 24) & 0xFF 576 | ]; 577 | }; 578 | 579 | /** 580 | * @method array 581 | * @memberof Md5 582 | * @instance 583 | * @description Output hash as bytes array 584 | * @returns {Array} Bytes array 585 | * @see {@link md5.array} 586 | * @example 587 | * hash.array(); 588 | */ 589 | Md5.prototype.array = Md5.prototype.digest; 590 | 591 | /** 592 | * @method arrayBuffer 593 | * @memberof Md5 594 | * @instance 595 | * @description Output hash as ArrayBuffer 596 | * @returns {ArrayBuffer} ArrayBuffer 597 | * @see {@link md5.arrayBuffer} 598 | * @example 599 | * hash.arrayBuffer(); 600 | */ 601 | Md5.prototype.arrayBuffer = function () { 602 | this.finalize(); 603 | 604 | var buffer = new ArrayBuffer(16); 605 | var blocks = new Uint32Array(buffer); 606 | blocks[0] = this.h0; 607 | blocks[1] = this.h1; 608 | blocks[2] = this.h2; 609 | blocks[3] = this.h3; 610 | return buffer; 611 | }; 612 | 613 | /** 614 | * @method buffer 615 | * @deprecated This maybe confuse with Buffer in node.js. Please use arrayBuffer instead. 616 | * @memberof Md5 617 | * @instance 618 | * @description Output hash as ArrayBuffer 619 | * @returns {ArrayBuffer} ArrayBuffer 620 | * @see {@link md5.buffer} 621 | * @example 622 | * hash.buffer(); 623 | */ 624 | Md5.prototype.buffer = Md5.prototype.arrayBuffer; 625 | 626 | /** 627 | * @method base64 628 | * @memberof Md5 629 | * @instance 630 | * @description Output hash as base64 string 631 | * @returns {String} base64 string 632 | * @see {@link md5.base64} 633 | * @example 634 | * hash.base64(); 635 | */ 636 | Md5.prototype.base64 = function () { 637 | var v1, v2, v3, base64Str = '', bytes = this.array(); 638 | for (var i = 0; i < 15;) { 639 | v1 = bytes[i++]; 640 | v2 = bytes[i++]; 641 | v3 = bytes[i++]; 642 | base64Str += BASE64_ENCODE_CHAR[v1 >>> 2] + 643 | BASE64_ENCODE_CHAR[(v1 << 4 | v2 >>> 4) & 63] + 644 | BASE64_ENCODE_CHAR[(v2 << 2 | v3 >>> 6) & 63] + 645 | BASE64_ENCODE_CHAR[v3 & 63]; 646 | } 647 | v1 = bytes[i]; 648 | base64Str += BASE64_ENCODE_CHAR[v1 >>> 2] + 649 | BASE64_ENCODE_CHAR[(v1 << 4) & 63] + 650 | '=='; 651 | return base64Str; 652 | }; 653 | 654 | var exports = createMethod(); 655 | 656 | if (COMMON_JS) { 657 | module.exports = exports; 658 | } else { 659 | /** 660 | * @method md5 661 | * @description Md5 hash function, export to global in browsers. 662 | * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash 663 | * @returns {String} md5 hashes 664 | * @example 665 | * md5(''); // d41d8cd98f00b204e9800998ecf8427e 666 | * md5('The quick brown fox jumps over the lazy dog'); // 9e107d9d372bb6826bd81d3542a419d6 667 | * md5('The quick brown fox jumps over the lazy dog.'); // e4d909c290d0fb1ca068ffaddf22cbd0 668 | * 669 | * // It also supports UTF-8 encoding 670 | * md5('中文'); // a7bac2239fcdcb3a067903d8077c4a07 671 | * 672 | * // It also supports byte `Array`, `Uint8Array`, `ArrayBuffer` 673 | * md5([]); // d41d8cd98f00b204e9800998ecf8427e 674 | * md5(new Uint8Array([])); // d41d8cd98f00b204e9800998ecf8427e 675 | */ 676 | root.md5 = exports; 677 | if (AMD) { 678 | define(function () { 679 | return exports; 680 | }); 681 | } 682 | } 683 | })(); -------------------------------------------------------------------------------- /http/http.js: -------------------------------------------------------------------------------- 1 | const { http } = just.library('http') 2 | const { encode } = just.library('encode') 3 | const { sha1 } = just.library('sha1') 4 | const { createSocket, Socket } = require('@socket') 5 | const websocket = require('websocket') 6 | const parsers = require('parsers.js') 7 | 8 | const { hash } = sha1 9 | const { createMessage, createBinaryMessage, unmask } = websocket 10 | const { 11 | parseRequestsHandle, 12 | parseResponsesHandle, 13 | createHandle, 14 | getStatusCode, 15 | getStatusMessage, 16 | getMethod, 17 | getUrl, 18 | getHeaders 19 | } = http 20 | const messageTypes = { 21 | Request: 0, 22 | Response: 1 23 | } 24 | 25 | class HTTPParser { 26 | constructor (buffer, type = messageTypes.Request) { 27 | this.buffer = buffer 28 | this.type = type 29 | this.info = new ArrayBuffer(4) 30 | this.dv = new DataView(this.info) 31 | this.handle = createHandle(this.buffer, this.info) 32 | this.remaining = 0 33 | this.count = 0 34 | this.index = 0 35 | this.parseHandle = this.type === messageTypes.Request ? parseRequestsHandle : parseResponsesHandle 36 | } 37 | 38 | get request () { 39 | return new IncomingRequest(this.index) 40 | } 41 | 42 | get response () { 43 | return new IncomingResponse(this.index) 44 | } 45 | 46 | parse (bytes, off = 0) { 47 | const { handle, dv } = this 48 | this.parseHandle(handle, bytes, off) 49 | const flags = dv.getUint32(0, true) 50 | this.count = flags & 0xffff 51 | this.remaining = flags >> 16 52 | this.index = 0 53 | if (this.count > 0) return true 54 | return false 55 | } 56 | 57 | reset () { 58 | this.remaining = this.count = this.index = 0 59 | } 60 | } 61 | 62 | class URL { 63 | static parse (url) { 64 | const protocolEnd = url.indexOf(':') 65 | const protocol = url.slice(0, protocolEnd) 66 | const hostnameEnd = url.indexOf('/', protocolEnd + 3) 67 | const host = url.slice(protocolEnd + 3, hostnameEnd) 68 | const path = url.slice(hostnameEnd) 69 | const [hostname, port] = host.split(':') 70 | if (port) return { protocol, host, hostname, path, port } 71 | return { protocol, host, hostname, path, port: protocol === 'https' ? 443 : 80 } 72 | } 73 | } 74 | 75 | class OutgoingRequest { 76 | constructor () { 77 | 78 | } 79 | } 80 | 81 | function joinHeaders (headers) { 82 | return headers.map(h => h.join(': ')).join('\r\n') 83 | } 84 | 85 | class OutgoingResponse { 86 | constructor (sock) { 87 | this.sock = sock 88 | this.pipeline = false 89 | this.queue = '' 90 | this.status = 200 91 | this.headers = [] 92 | this.contentType = 'text' 93 | this.chunked = false 94 | this.contentLength = 0 95 | this.close = false 96 | this.index = 0 97 | this.request = new IncomingRequest(this.index) 98 | } 99 | 100 | raw (buf) { 101 | return this.sock.send(buf) 102 | } 103 | 104 | header (size, contentType = responses.octet) { 105 | const { sock, status, headers } = this 106 | if (headers.length) { 107 | if (size === 0) { 108 | sock.sendString(`${contentType[status].slice(0, -16)}${joinHeaders(headers)}${END}`) 109 | } else { 110 | sock.sendString(`${contentType[status]}${size}${CRLF}${joinHeaders(headers)}${END}`) 111 | } 112 | } else { 113 | if (size === 0) { 114 | sock.sendString(`${contentType[status].slice(0, -16)}${END}`) 115 | } else { 116 | sock.sendString(`${contentType[status]}${size}${END}`) 117 | } 118 | } 119 | } 120 | 121 | buffer (buf, contentType = responses.octet) { 122 | const { sock, status, headers } = this 123 | if (headers.length) { 124 | sock.sendString(`${contentType[status]}${buf.byteLength}${CRLF}${joinHeaders(headers)}${END}`) 125 | } else { 126 | sock.sendString(`${contentType[status]}${buf.byteLength}${END}`) 127 | } 128 | sock.send(buf) 129 | } 130 | 131 | json (json, contentType = responses.json) { 132 | const str = JSON.stringify(json) 133 | const { sock, pipeline, status, headers } = this 134 | if (pipeline) { 135 | this.queue += `${contentType[status]}${String.byteLength(str)}${END}${str}` 136 | return 137 | } 138 | if (headers.length) { 139 | return sock.sendString(`${contentType[status]}${String.byteLength(str)}${CRLF}${joinHeaders(headers)}${END}${str}`) 140 | } 141 | return sock.sendString(`${contentType[status]}${String.byteLength(str)}${END}${str}`) 142 | } 143 | 144 | utf8 (str, contentType = responses.utf8) { 145 | if (this.pipeline) { 146 | this.queue += `${contentType[this.status]}${String.byteLength(str)}${END}${str}` 147 | return 148 | } 149 | if (this.headers.length) { 150 | return this.sock.sendString(`${contentType[this.status]}${String.byteLength(str)}${CRLF}${joinHeaders(this.headers)}${END}${str}`) 151 | } 152 | return this.sock.sendString(`${contentType[this.status]}${String.byteLength(str)}${END}${str}`) 153 | } 154 | 155 | html (str, contentType = responses.html) { 156 | if (this.pipeline) { 157 | this.queue += `${contentType[this.status]}${String.byteLength(str)}${END}${str}` 158 | return 159 | } 160 | if (this.headers.length) { 161 | return this.sock.sendString(`${contentType[this.status]}${String.byteLength(str)}${CRLF}${joinHeaders(this.headers)}${END}${str}`) 162 | } 163 | return this.sock.sendString(`${contentType[this.status]}${String.byteLength(str)}${END}${str}`) 164 | } 165 | 166 | redirect (location, status = 302) { 167 | this.headers.push(['Location', location]) 168 | this.status = status 169 | this.text('') 170 | } 171 | 172 | notFound () { 173 | this.status = 404 174 | this.text('Not Found') 175 | } 176 | 177 | forbidden () { 178 | this.status = 403 179 | this.text('Forbidden') 180 | } 181 | 182 | badRequest () { 183 | this.status = 400 184 | this.text('Bad Request') 185 | } 186 | 187 | text (str, contentType = responses.text) { 188 | if (this.pipeline) { 189 | this.queue += `${contentType[this.status]}${str.length}${END}${str}` 190 | return 191 | } 192 | if (this.headers.length) { 193 | return this.sock.sendString(`${contentType[this.status]}${str.length}${CRLF}${joinHeaders(this.headers)}${END}${str}`) 194 | } 195 | return this.sock.sendString(`${contentType[this.status]}${str.length}${END}${str}`) 196 | } 197 | 198 | finish () { 199 | if (this.pipeline && this.queue.length) { 200 | this.sock.sendString(this.queue) 201 | this.queue = '' 202 | this.pipeline = false 203 | this.headers.length = 0 204 | } 205 | } 206 | } 207 | 208 | class IncomingResponse { 209 | constructor (index) { 210 | this.index = index 211 | this.hasHeaders = false 212 | this._headers = {} 213 | this.version = 0 214 | } 215 | 216 | get statusCode () { 217 | return getStatusCode(this.index) 218 | } 219 | 220 | get statusMessage () { 221 | return getStatusMessage(this.index) 222 | } 223 | 224 | get headers () { 225 | if (this.hasHeaders) return this._headers 226 | this.version = getHeaders(this.index, this._headers) 227 | this.hasHeaders = true 228 | return this._headers 229 | } 230 | } 231 | 232 | class IncomingRequest { 233 | constructor (index) { 234 | this.index = index 235 | this.params = [] 236 | this.hasHeaders = false 237 | this._headers = {} 238 | this.version = 0 239 | this.qs = '' 240 | this.path = '' 241 | this.query = null 242 | this.contentLength = 0 243 | this.bytes = 0 244 | this.url = '' 245 | this.chunked = false 246 | this.method = methods.get 247 | this.onBody = (buf, len) => {} 248 | this.onEnd = () => {} 249 | } 250 | 251 | get headers () { 252 | if (this.hasHeaders) return this._headers 253 | this.version = getHeaders(this.index, this._headers) 254 | this.hasHeaders = true 255 | return this._headers 256 | } 257 | 258 | json () { 259 | const req = this 260 | let str = '' 261 | req.onBody = (buf, len, off) => { 262 | str += buf.readString(len, off) 263 | } 264 | return new Promise(resolve => { 265 | req.onEnd = () => resolve(JSON.parse(str)) 266 | }) 267 | } 268 | 269 | text () { 270 | const req = this 271 | let str = '' 272 | req.onBody = (buf, len, off) => { 273 | str += buf.readString(len, off) 274 | } 275 | return new Promise(resolve => { 276 | req.onEnd = () => resolve(str) 277 | }) 278 | } 279 | 280 | parseUrl (qs = false) { 281 | if (!qs && this.path) return this 282 | if (qs && this.query) return this 283 | const { url } = this 284 | const i = url.indexOf(PATHSEP) 285 | if (i > -1) { 286 | this.path = url.slice(0, i) 287 | this.qs = url.slice(i + 1) 288 | } else { 289 | this.path = url 290 | this.qs = '' 291 | } 292 | if (qs) { 293 | // parse the querystring 294 | if (!this.qs) return this 295 | this.query = this.qs.split('&') 296 | .map(p => p.split('=')) 297 | .reduce((o, p) => { 298 | o[p[0]] = p[1] 299 | return o 300 | }, {}) 301 | } 302 | return this 303 | } 304 | } 305 | 306 | class ChunkParser { 307 | constructor (buf) { 308 | this.size = 0 309 | this.consumed = 0 310 | this.buffer = buf 311 | this.bytes = new Uint8Array(buf) 312 | this.digits = [] 313 | this.header = true 314 | this.final = false 315 | } 316 | 317 | reset () { 318 | this.size = 0 319 | this.consumed = 0 320 | this.digits.length = 0 321 | this.final = false 322 | this.header = true 323 | } 324 | 325 | parse (len, start) { 326 | const { bytes, digits } = this 327 | let off = start 328 | const chunks = [] 329 | while (len) { 330 | if (this.header) { 331 | const c = bytes[off] 332 | off++ 333 | len-- 334 | if (c === 13) { 335 | continue 336 | } else if (c === 10) { 337 | if (this.final) { 338 | this.reset() 339 | return 340 | } 341 | if (digits.length) { 342 | this.size = parseInt(digits.join(''), 16) 343 | if (this.size > 0) { 344 | this.header = false 345 | } else if (this.size === 0) { 346 | this.final = true 347 | } 348 | digits.length = 0 349 | } 350 | continue 351 | } else if ((c > 47 && c < 58)) { 352 | digits.push(String.fromCharCode(c)) 353 | continue 354 | } else if ((c > 96 && c < 103)) { 355 | digits.push(String.fromCharCode(c)) 356 | continue 357 | } else if ((c > 64 && c < 71)) { 358 | digits.push(String.fromCharCode(c)) 359 | continue 360 | } else { 361 | just.print('BAD_CHAR') 362 | } 363 | just.print('OOB:') 364 | just.print(`c ${c}`) 365 | just.print(`len ${len}`) 366 | just.print(`off ${off}`) 367 | just.print(`size ${this.size}`) 368 | just.print(`consumed ${this.consumed}`) 369 | throw new Error('OOB') 370 | } else { 371 | const remaining = this.size - this.consumed 372 | if (remaining > len) { 373 | chunks.push(this.buffer.slice(off, off + len)) 374 | this.consumed += len 375 | off += len 376 | len = 0 377 | } else { 378 | chunks.push(this.buffer.slice(off, off + remaining)) 379 | len -= remaining 380 | off += remaining 381 | this.consumed += remaining 382 | this.reset() 383 | } 384 | } 385 | } 386 | return chunks 387 | } 388 | } 389 | 390 | function createResponses (serverName) { 391 | // todo: expose this so it can be configured 392 | time = (new Date()).toUTCString() 393 | Object.keys(contentTypes).forEach(contentType => { 394 | Object.keys(statusMessages).forEach(status => { 395 | responses[contentType][status] = `HTTP/1.1 ${status} ${statusMessages[status]}\r\nServer: ${serverName}\r\nContent-Type: ${contentTypes[contentType]}\r\nDate: ${time}\r\nContent-Length: ` 396 | }) 397 | }) 398 | } 399 | 400 | const random = v => Math.ceil(Math.random() * v) 401 | const randomSleep = () => new Promise(resolve => just.setTimeout(resolve, random(300) + 20)) 402 | 403 | async function createServerSocket (sock, server) { 404 | const { staticHandlers, defaultHandler, hooks } = server 405 | const { buffer } = sock 406 | function match (url, method) { 407 | for (const handler of server.regexHandlers[method]) { 408 | const match = url.match(handler.path) 409 | if (match) { 410 | return [handler.handler, match.slice(1)] 411 | } 412 | } 413 | return [null, null] 414 | } 415 | if (server.tls) { 416 | await sock.negotiate(server.context) 417 | if(sock.error) { 418 | just.print(sock.error.stack) 419 | sock.close() 420 | return 421 | } 422 | } 423 | sock.edgeTriggered = false 424 | const parser = new HTTPParser(buffer) 425 | if (hooks.connect.length) { 426 | for (const handler of hooks.connect) handler(sock) 427 | } 428 | if (hooks.disconnect.length) { 429 | sock.onClose = () => { 430 | for (const handler of hooks.disconnect) handler(sock) 431 | } 432 | } 433 | sock.pause() 434 | await randomSleep() 435 | let offset = 0 436 | sock.onReadable = () => { 437 | const bytes = sock.recv(offset) 438 | if (bytes === 0) { 439 | sock.close() 440 | return 441 | } 442 | if (bytes > 0) { 443 | if (parser.parse(offset + bytes)) { 444 | const { count } = parser 445 | const res = new OutgoingResponse(sock) 446 | res.pipeline = count > 1 447 | for (let i = 0; i < count; i++) { 448 | res.index = i 449 | const method = getMethod(i) 450 | res.request.method = method 451 | const methodHandler = staticHandlers[method] 452 | if (!methodHandler) { 453 | const url = getUrl(i) 454 | const { request } = res 455 | request.url = url 456 | defaultHandler(res, res.request) 457 | continue 458 | } 459 | const url = getUrl(i) 460 | const { request } = res 461 | request.url = url 462 | let handler = methodHandler[url] 463 | if (handler) { 464 | const p = handler(res, request) 465 | if (handler.opts && handler.opts.async) { 466 | p.catch(err => server.error(res, err)) 467 | } 468 | continue 469 | } 470 | request.parseUrl(true) 471 | handler = methodHandler[request.path] 472 | if (handler) { 473 | const p = handler(res, request) 474 | if (handler.opts && handler.opts.async) { 475 | p.catch(err => server.error(res, err)) 476 | } 477 | continue 478 | } 479 | const [h, params] = match(request.path, request.method) 480 | if (h) { 481 | request.params = params 482 | h(res, request) 483 | continue 484 | } 485 | defaultHandler(res, request) 486 | } 487 | const { remaining } = parser 488 | if (remaining > 0) { 489 | parser.buffer.copyFrom(parser.buffer, 0, remaining, offset + bytes - remaining) 490 | offset = remaining 491 | } else { 492 | //for (const handler of hooks.post) handler(res) 493 | offset = 0 494 | } 495 | // TODO - figure out how to handle this correctly for sync/async - write tests 496 | res.finish() 497 | } else { 498 | if (!sock.upgraded) { 499 | const { remaining } = parser 500 | if (remaining > 0) { 501 | offset += remaining 502 | } else { 503 | offset = 0 504 | } 505 | } 506 | } 507 | } else { 508 | if (!sock.blocked) sock.close() 509 | } 510 | } 511 | sock.resume() 512 | } 513 | 514 | // todo: change this to be like pg where we pass the socket in 515 | class Server { 516 | constructor (sock = createSocket()) { 517 | this.sock = sock 518 | this.staticHandlers = {} 519 | this.regexHandlers = {} 520 | this.defaultHandler = this.notFound 521 | this.hooks = { connect: [], disconnect: [], post: [], pre: [] } 522 | this.accepting = false 523 | this.stackTraces = false 524 | this.tls = false 525 | this.context = null 526 | this.name = 'j' 527 | } 528 | 529 | async start () { 530 | let sock = await this.sock.accept() 531 | while (sock && this.accepting) { 532 | createServerSocket(sock, this) 533 | // todo handle rejection 534 | sock = await this.sock.accept() 535 | } 536 | } 537 | 538 | stop () { 539 | this.accepting = false 540 | } 541 | 542 | close () { 543 | this.stop() 544 | return this.sock.close() 545 | } 546 | 547 | connect (handler) { 548 | this.hooks.connect.push(handler) 549 | return this 550 | } 551 | 552 | disconnect (handler) { 553 | this.hooks.disconnect.push(handler) 554 | return this 555 | } 556 | 557 | match (url, method) { 558 | for (const handler of this.regexHandlers[method]) { 559 | const match = url.match(handler.path) 560 | if (match) { 561 | return [handler.handler, match.slice(1)] 562 | } 563 | } 564 | return [null, null] 565 | } 566 | 567 | use (handler, post = false) { 568 | if (post) { 569 | this.hooks.post.push(handler) 570 | return this 571 | } 572 | this.hooks.pre.push(handler) 573 | return this 574 | } 575 | 576 | notFound (res) { 577 | res.status = 404 578 | res.text('Not Found') 579 | } 580 | 581 | error (res, err) { 582 | res.status = 500 583 | if (this.stackTraces) { 584 | res.text(` 585 | error: ${err.toString()} 586 | stack: 587 | ${err.stack} 588 | `) 589 | return 590 | } 591 | res.text(err.toString()) 592 | } 593 | 594 | addPath (path, handler, method, opts) { 595 | if (opts) handler.opts = opts 596 | if (!this.staticHandlers[method]) this.staticHandlers[method] = {} 597 | if (!this.regexHandlers[method]) this.regexHandlers[method] = [] 598 | if (handler.constructor.name === 'AsyncFunction') { 599 | if (handler.opts) { 600 | handler.opts.async = true 601 | } else { 602 | handler.opts = { async: true } 603 | } 604 | } 605 | if (typeof path === 'string') { 606 | this.staticHandlers[method][path] = handler 607 | return 608 | } 609 | if (typeof path === 'object') { 610 | if (path.constructor.name === 'RegExp') { 611 | this.regexHandlers[method].push({ handler, path }) 612 | } else if (path.constructor.name === 'Array') { 613 | for (const p of path) { 614 | this.staticHandlers[method][p] = handler 615 | } 616 | } 617 | } 618 | return this 619 | } 620 | 621 | removePath (method, path) { 622 | // this only works for staticHandlers as we cannot compared to regex 623 | delete this.staticHandlers[method][path] 624 | } 625 | 626 | get (path, handler, opts) { 627 | if (opts) handler.opts = opts 628 | this.addPath(path, handler, methods.get) 629 | return this 630 | } 631 | 632 | put (path, handler, opts) { 633 | if (opts) handler.opts = opts 634 | this.addPath(path, handler, methods.put) 635 | return this 636 | } 637 | 638 | head (path, handler, opts) { 639 | if (opts) handler.opts = opts 640 | this.addPath(path, handler, methods.head) 641 | return this 642 | } 643 | 644 | post (path, handler, opts) { 645 | if (opts) handler.opts = opts 646 | this.addPath(path, handler, methods.post) 647 | return this 648 | } 649 | 650 | delete (path, handler, opts) { 651 | if (opts) handler.opts = opts 652 | this.addPath(path, handler, methods.delete) 653 | return this 654 | } 655 | 656 | options (path, handler, opts) { 657 | if (opts) handler.opts = opts 658 | this.addPath(path, handler, methods.options) 659 | return this 660 | } 661 | 662 | default (handler, opts) { 663 | if (opts) handler.opts = opts 664 | this.defaultHandler = handler 665 | return this 666 | } 667 | 668 | listen (address = '127.0.0.1', port = 3000, maxConn = this.sock.maxConn) { 669 | this.sock.maxConn = maxConn 670 | const ok = this.sock.listen(address, port) 671 | if (ok !== 0) throw new just.SystemError('listen') 672 | this.accepting = true 673 | this.start() 674 | return this 675 | } 676 | 677 | update () { 678 | createResponses(this.name) 679 | return this 680 | } 681 | } 682 | 683 | function getResponses (count = 1) { 684 | if (count === 0) return [] 685 | const responses = [[]] 686 | http.getResponses(count, responses) 687 | return responses.map(res => { 688 | const [version, statusCode, statusMessage, headers] = res 689 | res.chunked = false 690 | return { version, statusCode, statusMessage, headers } 691 | }) 692 | } 693 | 694 | function getRequests (count = 1) { 695 | if (count === 0) return [] 696 | const requests = [[]] 697 | http.getRequests(count, requests) 698 | return requests.map(req => { 699 | const [path, version, methodLen, headers] = req 700 | return { path, version, methodLen, headers } 701 | }) 702 | } 703 | 704 | function createServer (opts = {}, sock = createSocket()) { 705 | const server = new Server(sock) 706 | if (opts.tls) server.tls = true 707 | return server.update() 708 | } 709 | 710 | function joinHeaders2 (headers) { 711 | return `${Object.keys(headers).map(k => [k, headers[k]].join(': ')).join('\r\n')}\r\n` 712 | } 713 | 714 | function createRequest (path, hostname, headers) { 715 | if (headers) { 716 | return ArrayBuffer.fromString(`GET ${path} HTTP/1.1\r\nHost: ${hostname}\r\nUser-Agent: curl/7.58.0\r\nAccept: */*\r\n${joinHeaders2(headers)}\r\n`) 717 | } 718 | return ArrayBuffer.fromString(`GET ${path} HTTP/1.1\r\nConnection: close\r\nHost: ${hostname}\r\nUser-Agent: curl/7.58.0\r\nAccept: */*\r\n\r\n`) 719 | } 720 | 721 | async function fetchJSON (sock, path, hostname, headers, onHeaders, onBody, onComplete) { 722 | await sock.push(createRequest(path, hostname, headers)) 723 | const info = new ArrayBuffer(4) 724 | const dv = new DataView(info) 725 | const handle = createHandle(sock.buffer, info) 726 | let inBody = false 727 | let res 728 | //let rawHeaders 729 | let parser 730 | let expectedLength = 0 731 | let bytes = await sock.pull() 732 | const body = [] 733 | while (bytes) { 734 | if (!inBody) { 735 | parseResponsesHandle(handle, bytes, 0) 736 | const r = dv.getUint32(0, true) 737 | const count = r & 0xff 738 | const remaining = r >> 16 739 | if (count > 0) { 740 | //rawHeaders = sock.buffer.readString(bytes - remaining, 0) 741 | //just.print(rawHeaders) 742 | res = getResponses()[0] 743 | res.sock = sock 744 | if (onHeaders) onHeaders(res.headers) 745 | res.chunked = (res.headers['Transfer-Encoding'] && res.headers['Transfer-Encoding'].toLowerCase() === 'chunked') 746 | res.bytes = 0 747 | if (res.chunked) { 748 | parser = new ChunkParser(sock.buffer) 749 | const chunks = parser.parse(remaining, bytes - remaining) 750 | if (chunks && chunks.length) { 751 | res.bytes += chunks.reduce((size, chunk) => size + chunk.byteLength, 0) 752 | for (const chunk of chunks) { 753 | if (onBody) { 754 | onBody(chunk) 755 | } else { 756 | body.push(chunk.readString()) 757 | } 758 | } 759 | } 760 | inBody = true 761 | } else { 762 | expectedLength = parseInt(res.headers['Content-Length'] || res.headers['content-length'], 10) 763 | //just.print(`expected ${expectedLength} remaining ${remaining}`) 764 | if (remaining > 0) { 765 | res.bytes += remaining 766 | if (onBody) { 767 | onBody(sock.buffer.slice(remaining, bytes)) 768 | } else { 769 | body.push(sock.buffer.readString(remaining, bytes - remaining)) 770 | } 771 | } 772 | //just.print(res.bytes) 773 | if (expectedLength === res.bytes) break 774 | } 775 | } else { 776 | just.print(`count ${count} remaining ${remaining}`) 777 | } 778 | } else { 779 | if (res.chunked) { 780 | const chunks = parser.parse(bytes, 0) 781 | if (chunks && chunks.length) { 782 | res.bytes += chunks.reduce((size, chunk) => size + chunk.byteLength, 0) 783 | for (const chunk of chunks) { 784 | if (onBody) { 785 | onBody(chunk) 786 | } else { 787 | body.push(chunk.readString()) 788 | } 789 | } 790 | } 791 | } else { 792 | if (onBody) { 793 | onBody(sock.buffer.slice(0, bytes)) 794 | } else { 795 | body.push(sock.buffer.readString(bytes)) 796 | } 797 | res.bytes += bytes 798 | if (expectedLength === res.bytes) break 799 | } 800 | } 801 | bytes = await sock.pull() 802 | } 803 | if (onComplete) { 804 | onComplete() 805 | } else { 806 | res.body = body.join('') 807 | } 808 | //sock.close() 809 | return res 810 | } 811 | 812 | function shasum (str) { 813 | const source = ArrayBuffer.fromString(str) 814 | const dest = new ArrayBuffer(20) 815 | return source.readString(encode.base64Encode(dest, source, hash(source, dest))) 816 | } 817 | 818 | Socket.prototype.message = function (str) { 819 | return this.send(createMessage(str)) 820 | } 821 | 822 | Socket.prototype.upgrade = function (res, onMessage = () => {}, onClose = () => {}) { 823 | const { sock, request } = res 824 | const { buffer } = sock 825 | const key = request.headers['Sec-WebSocket-Key'] 826 | const hash = shasum(`${key}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`) 827 | res.headers.push(['Upgrade', 'WebSocket']) 828 | res.headers.push(['Connection', 'Upgrade']) 829 | res.headers.push(['Sec-WebSocket-Accept', hash]) 830 | res.status = 101 831 | res.text('') 832 | const parser = new websocket.Parser() 833 | const chunks = [] 834 | parser.onHeader = header => { 835 | chunks.length = 0 836 | } 837 | parser.onChunk = (off, len, header) => { 838 | let size = len 839 | let pos = 0 840 | const bytes = new Uint8Array(buffer, off, len) 841 | while (size--) { 842 | bytes[pos] = bytes[pos] ^ header.maskkey[pos % 4] 843 | pos++ 844 | } 845 | chunks.push(buffer.readString(len, off)) 846 | } 847 | parser.onMessage = header => { 848 | if (header.OpCode === 8) { 849 | onClose() 850 | chunks.length = 0 851 | return 852 | } 853 | const str = chunks.join('') 854 | if (!str) return 855 | chunks.length = 0 856 | onMessage(str) 857 | } 858 | const u8 = new Uint8Array(buffer) 859 | sock.upgraded = true 860 | sock.onBytes = bytes => { 861 | parser.execute(u8, 0, bytes) 862 | } 863 | } 864 | 865 | const statusMessages = { 866 | 200: 'OK', 867 | 201: 'Created', 868 | 204: 'OK', 869 | 206: 'Partial Content', 870 | 101: 'Switching Protocols', 871 | 302: 'Redirect', 872 | 400: 'Bad Request', 873 | 401: 'Unauthorized', 874 | 403: 'Forbidden', 875 | 404: 'Not Found', 876 | 429: 'Server Busy', 877 | 500: 'Server Error' 878 | } 879 | 880 | const methods = { 881 | get: 'GET'.charCodeAt(0), 882 | put: 'PUT'.charCodeAt(0), 883 | head: 'HEAD'.charCodeAt(0), 884 | post: 'POST'.charCodeAt(0), // TODO: PUT and POST are same 885 | delete: 'DELETE'.charCodeAt(0), 886 | options: 'OPTIONS'.charCodeAt(0) 887 | } 888 | 889 | const contentTypes = { 890 | text: 'text/plain', 891 | css: 'text/css', 892 | utf8: 'text/plain; charset=utf-8', 893 | json: 'application/json; charset=utf-8', 894 | html: 'text/html; charset=utf-8', 895 | octet: 'application/octet-stream', 896 | ico: 'application/favicon', 897 | png: 'application/png', 898 | xml: 'application/xml; charset=utf-8', 899 | js: 'application/javascript; charset=utf-8', 900 | wasm: 'application/wasm' 901 | } 902 | 903 | const responses = {} 904 | Object.keys(contentTypes).forEach(k => responses[k] = {}) 905 | 906 | const END = '\r\n\r\n' 907 | const CRLF = '\r\n' 908 | const PATHSEP = '?' 909 | 910 | let time = (new Date()).toUTCString() 911 | 912 | module.exports = { 913 | URL, 914 | Server, 915 | ChunkParser, 916 | IncomingRequest, 917 | IncomingResponse, 918 | OutgoingRequest, 919 | OutgoingResponse, 920 | HTTPParser, 921 | messageTypes, 922 | methods, 923 | contentTypes, 924 | statusMessages, 925 | responses, 926 | createServer, 927 | getResponses, 928 | getRequests, 929 | fetchJSON, 930 | parsers 931 | } 932 | --------------------------------------------------------------------------------