summaryrefslogtreecommitdiffstats
path: root/lib/request-parser.js
diff options
context:
space:
mode:
authorJannis R <mail@jannisr.de>2020-05-02 15:32:36 +0200
committerJannis R <mail@jannisr.de>2020-05-02 16:35:50 +0200
commit1b5af6f4bdfca939e780ae70bd6e1819425824f7 (patch)
tree4e3fb0c4957680348c473ff45e3478780d1ba55c /lib/request-parser.js
parent635b65aa8af3b35a4ea2c8a5fea805853942cbf4 (diff)
move code, server example :memo:
Diffstat (limited to 'lib/request-parser.js')
-rw-r--r--lib/request-parser.js75
1 files changed, 75 insertions, 0 deletions
diff --git a/lib/request-parser.js b/lib/request-parser.js
new file mode 100644
index 0000000..04c5cd6
--- /dev/null
+++ b/lib/request-parser.js
@@ -0,0 +1,75 @@
+'use strict'
+
+const {Transform} = require('stream')
+const {MESSAGES} = require('./statuses')
+
+// https://gemini.circumlunar.space/docs/spec-spec.txt, 1.2
+// > Gemini requests are a single CRLF-terminated line with the
+// > following structure: <URL><CR><LF>
+// > <URL> is a UTF-8 encoded absolute URL, of maximum length
+// > 1024 bytes. [...]
+
+const CRLF = '\r\n'
+const MAX_HEADER_SIZE = 1024 + CRLF.length
+
+const createRequestParser = () => {
+ let headerParsed = false
+ let peek = Buffer.alloc(0)
+
+ const invalid = () => {
+ peek = null
+ out.destroy(new Error('invalid Gemini request'))
+ }
+
+ const onData = (data) => {
+ if (headerParsed) {
+ out.push(data)
+ return;
+ }
+
+ peek = Buffer.concat([peek, data], peek.length + data.length)
+ if (
+ data.indexOf(CRLF) < 0 &&
+ peek.length < MAX_HEADER_SIZE
+ ) return; // keep peeking
+
+ const iCRLF = peek.indexOf(CRLF)
+ if (iCRLF < 0) return invalid()
+
+ const url = peek.slice(0, iCRLF).toString('utf8')
+ headerParsed = true
+ out.emit('header', {url})
+
+ const iBody = iCRLF + 2
+ if (peek.length > (iBody + 1)) {
+ // `data` contains the beginning of the body
+ out.push(peek.slice(iBody))
+ }
+ peek = null // allow garbage collection
+ }
+
+ const out = new Transform({
+ write: (chunk, _, cb) => {
+ onData(chunk)
+ cb()
+ },
+ writev: (chunks, cb) => {
+ for (let i = 0; i < chunks.length; i++) {
+ onData(chunks[i].chunk)
+ }
+ cb()
+ },
+ })
+ return out
+}
+
+// const p = createRequestParser()
+// p.on('error', console.error)
+// p.on('header', h => console.log('header', h))
+// p.on('data', d => console.log('data', d.toString('utf8')))
+// const b = str => Buffer.from(str, 'utf8')
+// p.write(b('gemini://examp'))
+// p.write(b('le.org/foo?bar#baz\r\nhel'))
+// p.end(b('lo server!'))
+
+module.exports = createRequestParser