var proc = require('child_process') var fs = require('fs') var http = require('http') var URL = require('url') var crypto = require('crypto') var os = require('os') var qs = require('querystring') function escapeHTML (html) { return String(html) .replace(/&/g, '&') .replace(/"/g, '"') .replace(//g, '>') .replace(/\n/g, ' ') } function noPublish(content, cb) { cb(new Error('missing publish function')) } function noPrivatePublish(content, recps, cb) { cb(new Error('missing private publish function')) } function getForm(stream, cb) { var bufs = [] stream.on('data', function (buf) { bufs.push(buf) }) stream.on('end', function () { var data try { data = qs.parse(Buffer.concat(bufs).toString('utf8')) } catch(e) { return cb(e) } cb(null, data) }) } function serve(req, res, ctxs) { var q = req.uri.query var opts = ctxs[q.id] if (!opts) { res.writeHead(404) return res.end('Not Found') } var content = opts.content var browser = opts.browser var recps = opts.recps var publish = opts.publish || noPublish var getUrl = opts.getUrl var privatePublish = opts.privatePublish || noPrivatePublish var redirectBase = opts.redirectBase var ctxs = opts.ctxs var url = opts.url var cb = opts.cb if (req.method === 'POST') return getForm(req, function (err, data) { if (err) return res.end(err.stack || err) if (!data.token || data.token !== opts.token) { res.writeHead(403) return res.end('Invalid token') } function publish1(content, recps, cb) { if (recps) privatePublish(content, recps, cb) else publish(content, cb) } if (data.publish) { return publish1(content, recps, function (err, msg) { if (err) { res.writeHead(500, {'Content-Type': 'text/html'}) res.end('' + 'publish error' + '

' + escapeHTML(err.name || err) + '

' + '
' + escapeHTML(err.stack || '') + '
' + '') delete ctxs[q.id] return !getUrl && cb(err) } var json = JSON.stringify(msg, 0, 2) if (redirectBase) { var url = redirectBase + encodeURIComponent(msg.key) res.writeHead(302, {'Location': url}) res.end() } else { res.writeHead(200, {'Content-Type': 'text/html'}) res.end('' + 'published' + '
' + escapeHTML(json) + '
' + '') } delete ctxs[q.id] return !getUrl && cb(null, msg) }) } if (data.cancel) { res.writeHead(200, {'Content-Type': 'text/html'}) res.end('' + 'canceled' + '

Cancelled

' + '') delete ctxs[q.id] return !getUrl && cb(new Error('User cancelled')) } }) if (!opts.token) opts.token = crypto.randomBytes(32).toString('base64') var contentJson = JSON.stringify(content, null, 2) var recpsJson = JSON.stringify(recps, null, 2) res.writeHead(200, {'Content-Type': 'text/html'}) return res.end('' + 'publish' + '
' + (recps ? '
' + recpsJson + '
' : '') + '
' + escapeHTML(contentJson) + '
' + '' + '' + ' ' + '' + '
') } function confirm(opts, cb) { var browser = opts.browser var recps = opts.recps var getUrl = opts.getUrl var ctxs = opts.ctxs var url = opts.url if (typeof cb !== 'function') throw new TypeError('bad callback') if (!opts.ctxs) throw new TypeError('missing ctxs') if (!opts.url) throw new TypeError('missing url') if (browser && getUrl) { return cb(new TypeError('browser option and getUrl option conflict')) } if (!browser && !getUrl) { return cb(new TypeError('missing browser option')) } if (recps && !Array.isArray(recps)) { return cb(new TypeError('recps should be array')) } var id = crypto.randomBytes(16).toString('base64') ctxs[id] = opts var reqUrl = opts.url + '?id=' + encodeURIComponent(id) if (browser) { proc.spawn(browser, [reqUrl], {stdio: 'inherit'}) opts.cb = cb } else cb(null, reqUrl) } module.exports = function (opts) { var publish = opts.publish var privatePublish = opts.privatePublish var config = opts.config || {} var browser = config.browser || (os.platform() === 'darwin' ? 'open' : 'xdg-open') var name = opts.name || 'confirm' var port = config.port || 0 var ctxs = {} var url if (opts.ws && opts.ws.use) { var wsPort = opts.wsConfig && opts.wsConfig.port || 8989 var prefix = '/publishguard' url = 'http://localhost' + ':' + wsPort + prefix opts.ws.use(function (req, res, next) { req.uri = URL.parse(req.url, true) if (req.uri.pathname !== prefix) return next() serve(req, res, ctxs) }) } else { var server = http.createServer(function (req, res) { req.uri = URL.parse(req.url, true) if (req.uri.pathname !== '/') { res.writeHead(404) return res.end('Not Found') } serve(req, res, ctxs) }).listen(port, '127.0.0.1', function () { if (port === 0) port = this.address().port url = 'http://127.0.0.1:' + port + '/' console.log('[publishguard] listening on ' + url) }) } return { publish: function (content, cb) { confirm({ publish: publish, content: content, browser: browser, ctxs: ctxs, url: url, }, cb) }, privatePublish: function (content, recps, cb) { confirm({ privatePublish: privatePublish, recps: recps, content: content, browser: browser, ctxs: ctxs, url: url, }, cb) }, publishGetUrl: function (opts, cb) { confirm({ publish: publish, content: opts.content, redirectBase: opts.redirectBase, getUrl: true, ctxs: ctxs, url: url, }, cb) }, privatePublishGetUrl: function (opts, cb) { confirm({ privatePublish: privatePublish, recps: opts.recps, content: opts.content, redirectBase: opts.redirectBase, getUrl: true, ctxs: ctxs, url: url, }, cb) } } }