git ssb

3+

cel / ssb-publishguard



Tree: 5a3cd636ffd4211ee7958de59fc2011a7d6f58eb

Files: 5a3cd636ffd4211ee7958de59fc2011a7d6f58eb / confirm.js

7030 bytesRaw
1var proc = require('child_process')
2var fs = require('fs')
3var http = require('http')
4var URL = require('url')
5var crypto = require('crypto')
6var os = require('os')
7var qs = require('querystring')
8
9function escapeHTML (html) {
10 return String(html)
11 .replace(/&/g, '&')
12 .replace(/"/g, '"')
13 .replace(/</g, '&lt;')
14 .replace(/>/g, '&gt;')
15 .replace(/\n/g, '&#x0a;')
16}
17
18function noPublish(content, cb) {
19 cb(new Error('missing publish function'))
20}
21
22function noPrivatePublish(content, recps, cb) {
23 cb(new Error('missing private publish function'))
24}
25
26function getForm(stream, cb) {
27 var bufs = []
28 stream.on('data', function (buf) {
29 bufs.push(buf)
30 })
31 stream.on('end', function () {
32 var data
33 try {
34 data = qs.parse(Buffer.concat(bufs).toString('utf8'))
35 } catch(e) {
36 return cb(e)
37 }
38 cb(null, data)
39 })
40}
41
42function serve(req, res, ctxs) {
43 var q = req.uri.query
44 var opts = ctxs[q.id]
45 if (!opts) {
46 res.writeHead(404)
47 return res.end('Not Found')
48 }
49
50 var content = opts.content
51 var browser = opts.browser
52 var recps = opts.recps
53 var publish = opts.publish || noPublish
54 var getUrl = opts.getUrl
55 var privatePublish = opts.privatePublish || noPrivatePublish
56 var redirectBase = opts.redirectBase
57 var ctxs = opts.ctxs
58 var url = opts.url
59 var cb = opts.cb
60
61 if (req.method === 'POST') return getForm(req, function (err, data) {
62 if (err) return res.end(err.stack || err)
63
64 if (!data.token || data.token !== opts.token) {
65 res.writeHead(403)
66 return res.end('Invalid token')
67 }
68
69 function publish1(content, recps, cb) {
70 if (recps) privatePublish(content, recps, cb)
71 else publish(content, cb)
72 }
73
74 if (data.publish) {
75 return publish1(content, recps, function (err, msg) {
76 if (err) {
77 res.writeHead(500, {'Content-Type': 'text/html'})
78 res.end('<!doctype html><html><head><meta charset=utf-8>'
79 + '<title>publish error</title></head><body>'
80 + '<h1>' + escapeHTML(err.name || err) + '</h1>'
81 + '<pre style="white-space: pre-wrap">' + escapeHTML(err.stack || '') + '</pre>'
82 + '</body></html>')
83 delete ctxs[q.id]
84 return !getUrl && cb(err)
85 }
86 var json = JSON.stringify(msg, 0, 2)
87 if (redirectBase) {
88 var url = redirectBase + encodeURIComponent(msg.key)
89 res.writeHead(302, {'Location': url})
90 res.end()
91 } else {
92 res.writeHead(200, {'Content-Type': 'text/html'})
93 res.end('<!doctype html><html><head><meta charset=utf-8>'
94 + '<title>published</title></head><body>'
95 + '<pre style="white-space: pre-wrap">' + escapeHTML(json) + '</pre>'
96 + '</body></html>')
97 }
98 delete ctxs[q.id]
99 return !getUrl && cb(null, msg)
100 })
101 }
102 if (data.cancel) {
103 res.writeHead(200, {'Content-Type': 'text/html'})
104 res.end('<!doctype html><html><head><meta charset=utf-8>'
105 + '<title>canceled</title></head><body>'
106 + '<p>Cancelled</p>'
107 + '</body></html>')
108 delete ctxs[q.id]
109 return !getUrl && cb(new Error('User cancelled'))
110 }
111 })
112
113 if (!opts.token) opts.token = crypto.randomBytes(32).toString('base64')
114 var contentJson = JSON.stringify(content, null, 2)
115 var recpsJson = JSON.stringify(recps, null, 2)
116 res.writeHead(200, {'Content-Type': 'text/html'})
117 return res.end('<!doctype html><html><head><meta charset=utf-8>'
118 + '<title>publish</title></head><body>'
119 + '<form action="" method="post">'
120 + (recps ? '<pre style="white-space: pre-wrap">' + recpsJson + '</pre>' : '')
121 + '<pre style="white-space: pre-wrap">' + escapeHTML(contentJson) + '</pre>'
122 + '<input type="hidden" name="id" value="' + escapeHTML(q.id) + '">'
123 + '<input type="hidden" name="token" value="' + escapeHTML(opts.token) + '">'
124 + '<input type="submit" name="publish" value="Publish"> '
125 + '<input type="submit" name="cancel" value="Cancel">'
126 + '</form></body></html>')
127}
128
129function confirm(opts, cb) {
130 var browser = opts.browser
131 var recps = opts.recps
132 var getUrl = opts.getUrl
133 var ctxs = opts.ctxs
134 var url = opts.url
135
136 if (typeof cb !== 'function') throw new TypeError('bad callback')
137 if (!opts.ctxs) throw new TypeError('missing ctxs')
138 if (!opts.url) throw new TypeError('missing url')
139
140 if (browser && getUrl) {
141 return cb(new TypeError('browser option and getUrl option conflict'))
142 }
143 if (!browser && !getUrl) {
144 return cb(new TypeError('missing browser option'))
145 }
146 if (recps && !Array.isArray(recps)) {
147 return cb(new TypeError('recps should be array'))
148 }
149
150 var id = crypto.randomBytes(16).toString('base64')
151 ctxs[id] = opts
152 var reqUrl = opts.url + '?id=' + encodeURIComponent(id)
153 if (browser) {
154 proc.spawn(browser, [reqUrl], {stdio: 'inherit'})
155 opts.cb = cb
156 } else cb(null, reqUrl)
157}
158
159module.exports = function (opts) {
160 var publish = opts.publish
161 var privatePublish = opts.privatePublish
162 var config = opts.config || {}
163 var browser = config.browser ||
164 (os.platform() === 'darwin' ? 'open' : 'xdg-open')
165 var name = opts.name || 'confirm'
166 var port = config.port || 0
167
168 var ctxs = {}
169 var url
170 if (opts.ws && opts.ws.use) {
171 var wsPort = opts.wsConfig && opts.wsConfig.port || 8989
172 var prefix = '/publishguard'
173 url = 'http://localhost' + ':' + wsPort + prefix
174 opts.ws.use(function (req, res, next) {
175 req.uri = URL.parse(req.url, true)
176 if (req.uri.pathname !== prefix) return next()
177 serve(req, res, ctxs)
178 })
179 } else {
180 var server = http.createServer(function (req, res) {
181 req.uri = URL.parse(req.url, true)
182 if (req.uri.pathname !== '/') {
183 res.writeHead(404)
184 return res.end('Not Found')
185 }
186 serve(req, res, ctxs)
187 }).listen(port, '127.0.0.1', function () {
188 if (port === 0) port = this.address().port
189 url = 'http://127.0.0.1:' + port + '/'
190 console.log('[publishguard] listening on ' + url)
191 })
192 }
193
194 return {
195 publish: function (content, cb) {
196 confirm({
197 publish: publish,
198 content: content,
199 browser: browser,
200 ctxs: ctxs,
201 url: url,
202 }, cb)
203 },
204 privatePublish: function (content, recps, cb) {
205 confirm({
206 privatePublish: privatePublish,
207 recps: recps,
208 content: content,
209 browser: browser,
210 ctxs: ctxs,
211 url: url,
212 }, cb)
213 },
214 publishGetUrl: function (opts, cb) {
215 confirm({
216 publish: publish,
217 content: opts.content,
218 redirectBase: opts.redirectBase,
219 getUrl: true,
220 ctxs: ctxs,
221 url: url,
222 }, cb)
223 },
224 privatePublishGetUrl: function (opts, cb) {
225 confirm({
226 privatePublish: privatePublish,
227 recps: opts.recps,
228 content: opts.content,
229 redirectBase: opts.redirectBase,
230 getUrl: true,
231 ctxs: ctxs,
232 url: url,
233 }, cb)
234 }
235 }
236}
237

Built with git-ssb-web