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'
+ '')
}
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)
}
}
}