--- /dev/null
+/**
+* @file Node entry and main driver
+* @author Jordan Lavatai, Ken Grimes
+* @version 0.0.3
+* @license AGPL-3.0
+* @copyright Strapp.io 2017
+* @summary HTTP(S) Router that uses the first directory in the requested URL
+* as the route name
+*/
+'use strict'
+const opts = require('./opts.js')
+
+const dlog = (msg) => console.log(msg)
+
+class Server {
+ constructor(...props) {
+ Object.assign(this, new.target.defaults, ...props)
+ this.init()
+ }
+ init() {
+ if (this.cacheDir)
+ this.loadCache(this.cacheDir, '')
+ require('fs').readFile('./strapp.js', (err, data) => {
+ if (err)
+ throw new Error (err)
+ this.cache['strapp.js'] = ['application/javascript',data]
+ })
+ this.startHttpd()
+ }
+ loadCache(dirPath, prefix) {
+ require('fs').readdir(this.cacheDir, (err, files) => {
+ if (err)
+ console.log(err)
+ else
+ files.forEach((entName) => {
+ let filePath = `${dirPath}${require('path').sep}${entName}`
+ require('fs').stat(filePath, (err, stat) => {
+ if (err)
+ console.log(err)
+ else if (stat && stat.isDirectory())
+ this.loadCache(filePath, `${entName}/`)
+ else
+ require('fs').readFile(filePath, (err, data) => {
+ if (err)
+ console.log(err)
+ else
+ this.cache[`${prefix}${entName}`] =
+ [require('mime').lookup(filePath), data]
+ })
+ })
+ })
+ })
+ }
+ startHttpd() {
+ if (this.tls)
+ this.httpd = require('https')
+ .createServer(this.tls, (rq, rs) => this.httpRequest(rq, rs))
+ else
+ this.httpd = require('http')
+ .createServer((rq, rs) => this.httpRequest(rq, rs))
+ this.httpd.listen(this, () => this.port = this.httpd.address().port)
+ }
+ httpRequest(request, response) {
+ const htArgv = request.url.slice(1).split('?')
+ dlog(`request for ${request.url} received`)
+ if (request.method === 'GET' && htArgv[0] in this.cache) {
+ dlog(`found in cache`)
+ response.writeHead(200, { 'Content-Type': this.cache[htArgv[0]][0] })
+ response.write(this.cache[htArgv[0]][1])
+ response.end()
+ return
+ }
+ let pubKey = ''
+ const authHeader = request.headers['Authorization']
+ if (authHeader) {
+ const authInfo = authHeader.split(' ')
+ dlog(`authInfo: ${authInfo}`)
+ if (authInfo[0] === 'STRAPP') {
+ let answer = ''
+ switch (authInfo.length) {
+ case 3: answer = authInfo[2]
+ case 2: pubKey = authInfo[1]
+ break
+ default:
+ response.writeHead(400)
+ response.end()
+ return
+ }
+ this.authenticate(pubKey, answer).then((question, authenticated) => {
+ response.setHeader('WWW-Authenticate',
+ `STRAPP ${this.question(pubKey)} ${this.tls.key}`)
+ if (authenticated) {
+ this.processRequestData(request, response, htArgv, pubKey)
+ }
+ else {
+ response.writeHead(401)
+ response.end()
+ }
+ }).catch((err) => console.log(err))
+ }
+ }
+ else
+ this.processRequestData(request, response, htArgv, pubKey)
+ }
+ processRequestData(request, response, args, pubKey) {
+ if (request.method === 'PUT' || request.method === 'POST') {
+ let data = ''
+ request.on('data', (chunk) => {
+ data += chunk
+ if (data.length > 5e5)
+ request.connection.destroy()
+ })
+ request.on('end', this.sendData(request, response, args, pubKey, data))
+ }
+ else
+ this.sendData(request, response, args, pubKey)
+ }
+ authenticate(pubKey, answer) {
+ return new Promise((resolve, reject) => {
+ require('crypto').randomBytes(256, (err, buf) => {
+ if (err)
+ reject(err)
+ else {
+ let authenticated = false
+ if (pubKey in this.answers) {
+ const answerBuf = Buffer.from(answer)
+ require('crypto').privateDecrypt({ key: this.tls.cert }, answerBuf)
+ authenticated = this.answers[pubKey].equals(answerBuf)
+ }
+ this.answers[pubKey] = buf
+ let question = Buffer.from(buf)
+ require('crypto').publicEncrypt({ key: pubKey }, question)
+ resolve(question.toString(), authenticated)
+ }
+ })
+ })
+ }
+ sendData(request, response, args, pubKey, data) {
+ if (this.app) {
+ let msgID = this.msgID++
+ if (this.msgID >= this.pendingResponses.length)
+ this.msgID = 0
+ this.pendingResponses[msgID] = response
+ if (!data)
+ data = ''
+ this.app.send(`${args[0]} ${request.method} ${pubKey} ${msgID} ${data}`)
+ }
+ else {
+ response.writeHead(200, { 'Content-Type': 'text/html' })
+ response.write(Server.bootstrapp)
+ response.end()
+ }
+ }
+}
+Server.defaults = {
+ port: 0,
+ tls: { key: '', cert: '' },
+ host: undefined,
+ cache: {},
+ cacheDir: './www',
+ pendingResponses: new Array(20),
+ msgID: 0,
+ answers: {},
+ remoteAdminKey: undefined,
+ controller: undefined,
+ app: undefined,
+ httpd: undefined,
+ wsd: undefined
+}
+Server.bootstrapp = `
+<!DOCTYPE html>
+<html>
+ <title>bootstrapp</title>
+ <head>
+ <script src='/strapp.js'></script>
+ </head>
+ <body>
+ </body>
+</html>
+`
+
+//TODO: module.exports = Server
+
+const server = new Server({
+ port: 2443,
+ tls: { key: require('fs').readFileSync('../certs/key.pem'),
+ cert: require('fs').readFileSync('../certs/cert.pem')
+ }
+})
+
+
+// //TTL implementation
+// setInterval(30000, () => {
+// applications.forEach((app) => {
+// app.sockets.forEach((socket, idx) => {
+// if (!socket.connected) {
+// console.log(`Socket ${socket} timed out`)
+// socket.terminate()
+// app.sockets.splice(idx, 1)
+// }
+// else {
+// socket.connected = false
+// socket.ping('', false, true)
+// }
+// })
+// })
+// })
+
+// const startApplication = (conf) => {
+// let httpd
+// if (conf.tls)
+// httpd = require('https')
+// .createServer(conf.tls, listen)
+// .listen(conf, conf.callback)
+// else
+// httpd = require('http')
+// .createServer(listen)
+// .listen(conf, conf.callback)
+
+// let app = {
+// httpd: httpd,
+// sockets: [],
+// openSocket: function(pubKey) {
+// const wsd = require('ws').Server()({
+// noServer: true,
+// verifyClient: (info) => pubKey === parseAuth(info.req).pubKey
+// && authenticate(info.req)
+// })
+// let timeout = setTimeout(30000, wsd.close())
+// wsd.on('connection', (socket) => {
+// clearTimeout(timeout)
+// socket.on('pong', socket.connected = true)
+// socket.emit('pong')
+// this.sockets.push(socket)
+// })
+// }
+// }
+// return app
+// }
+
+// startApplication(opts.port, opts.tls)
+
+// if (conf.tls)
+// httpd = require('https')
+// .createServer(conf.tls)
+// .listen(conf, resolve)
+// else
+// httpd = require('http')
+// .createServer()
+// .listen(resolve)
+
+
+// const listener = function(port, conf) Promise((resolve, reject) => {
+// resolve(Object.create(null, {
+// httpd: { value: null },
+// port: { value: 0 },
+// transceiver: { value: null, writable: true, configurable: true }
+// }))
+// })
+
+// const a = {
+// httpd: null,
+// port: 0,
+// transceiver: null
+// }
+
+
+// /** @func
+// * @summary Authenticate a request
+// * @arg {http.ClientRequest} request
+// * @return {bool} true if the request is authentic
+// */
+// const authenticate = (request) => {
+// return true
+// }
+
+// const startPortForward = function(port, httpOpts, transceiver) {
+// const httpd, port, channel
+// }
+
+// /** @func
+// * Assumes Authorization header of form STRAPP {pubKey} {answer}
+// * @summary parse the components of a STRAPP authorization header
+// * @arg {http.ClientRequest} request
+// * @return {string} the empty string '' on error
+// */
+// const parseAuth = (request) => {
+// if (request.headers['Authorization']) {
+// request.auth = request.headers['Authorization'].split(' ')
+// if (request.auth[0] === 'STRAPP')
+// switch(request.auth.length) {
+// case 3: request.answer = request.auth[2]
+// case 2: request.pubKey = request.auth[1]
+// default:
+// }
+// }
+// return request
+// }
+
+// /** @func
+// * @summary Creates a promise that resolves a websocket to an application host
+// * @arg {string} pubKey - the public key authorized to connect.
+// * @arg {Object} [conf] - configuration for the websocket.
+// * @arg {Object} [conf.tls] - TLS configuration for HTTPS layer security
+// * @arg {string} [conf.tls.cert] - domain certificate for HTTPS
+// * @arg {string} [conf.tls.key] - domain public key for HTTPS
+// * @arg {string} [conf.host] - host to attach the port listener to (all IPs)
+// * @arg {number} [conf.backlog] - how many connections to buffer (511)
+// * @arg {boolean} [conf.exclusive] - don't share the port (false)
+// */
+// const pWebSocket = function(pubKey, conf) Promise((resolve, reject) => {
+// let timeout
+// conf = conf || {}
+// if (!pubKey)
+// reject(new Error('no pubKey'))
+// require('get-port')().then((port) => {
+// conf.port = port
+// new Promise((resolve, reject) => {
+// if (conf.tls)
+// httpd = require('https')
+// .createServer(conf.tls)
+// .listen(conf, resolve)
+// else
+// httpd = require('http')
+// .createServer()
+// .listen(resolve)
+// }).then(() => {
+// const wsd = new Promise((resolve, reject) => {
+// (require('ws').Server())({
+// server: httpd,
+// verifyClient: (info) => pubKey === parseAuth(info.req).pubKey
+// && authenticate(info.req)
+// }).on('connection', resolve)
+// timeout = setTimeout(30000, reject('timeout'))
+// }).then((socket) => {
+// clearTimeout(timeout)
+// const webSocket = Object.create(null, {
+// socket: { value: socket },
+// httpd: { value: httpd },
+// port: { value: port },
+// transmit: { value: socket.send },
+// receive: { value: this.receive }
+// })
+// socket.on('message', webSocket.receive)
+// resolve(webSocket)
+// }).catch(reject)
+// }).catch(reject)
+// }).catch(reject)
+// })
+
+// Object.keys(opts['bindings']).forEach((key) => {
+// router.createBind(key, opts['bindings'][key])
+// })
+
+// router.startHttpServer({
+// port: opts['port'],
+// skelFile: './skel.html',
+// clientJS: opts['client-js'],
+// hostJS: opts['host-js'],
+// httpdRoot: opts['file-dir'],
+// tls: {
+// certFile: opts['ca-cert'],
+// keyFile: opts['ca-key']
+// }
+// })