regexoid/test/http.mjs

130 lines
3.7 KiB
JavaScript

export function httpParser(regexoid) {
return regexoid(({
seq, any, rep, markBreak, chars,
mark, capture, captureMulti,
output, captureOutput,
cond, map, limit,
}) => {
let field = (out='fields') => seq(
capture('key', rep([1], chars.none([' :\r\n']))),
':',
rep([],' '),
capture('value', rep([1], chars.none(['\r\n']))),
'\r\n',
map(({ get, set, add }) => {
let k = get('key').toLowerCase();
let v = get('value').trim();
if (!v) {
return false;
}
set('value', null);
set('key', null);
add(out,[k,v]);
})
);
let packFields = (out='fields') => (
map(({ get, set }) => {
let o = Object.create(null);
for (let [k,v] of [...(get(out)??[])].reverse()) {
if (!(k in o)) {
o[k] = [v];
} else {
o[k].push(v);
}
}
set(out, o);
})
)
let hexdigit = (b=true) => chars[b?'any':'none'](['0','9'],['A','Z'],['a','z']);
let chunkExt = () => rep([,1], ';', rep([], chars.none(['\r\n'])));
let chunkSize = (quick) => (
limit(256,
capture('chunksize', rep([1,], hexdigit())),
...(quick ? [
hexdigit(false)
] : [
chunkExt(),
'\r\n'
])
)
);
return any(
seq(mark('type', 'h2'), 'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n', output('type'), mark('type', null)),
seq(mark('type', 'h1'),
limit(32768,
capture('method', rep([1,12], chars.any(['A','Z']))),
' ',
capture('path',rep([1,], chars.none([' \r\n']))),
' ',
'HTTP/1.',any('1','0'),
'\r\n',
rep([], field()),
'\r\n'
),
packFields(),
map(({ get, set }) => {
let o = get('fields');
let enc = null;
let CE = o['content-length'];
let TE = o['transfer-encoding'];
if ((CE!=null)+(TE!=null) != 1)
return false;
if (CE != null) {
if (CE.length > 1) return false;
enc = parseInt(CE[0])
if (enc != enc)
return false;
} else if (TE != null) {
if (TE.length > 1) return false;
if (TE[0] != 'chunked') return false;
enc = 'chunked';
}
set('bodyFraming', enc)
}),
output('type'),
output('method'),output('path'),
output('fields'),
mark('type', null),
mark('method', null),mark('path', null),
mark('fields', null),
cond(
[map(({ get }) => get('bodyFraming') == 'chunked'),
rep.strict([],
cond(
[seq(chunkSize(true), map(({ get }) => get('chunksize') != '0')),
chunkSize(),
map(({ get, set }) => void(set('chunksize', parseInt(get('chunksize'), 16)))),
map(({ get }, c) => (c = get('chunksize'), c > 0 && c < 2**52)),
captureOutput('body', rep('chunksize', chars.none())),
'\r\n',
mark('chunksize', null),
],
['', markBreak()]
)
),
limit(256,
'0', chunkExt(), '\r\n'
),
limit(8192, rep([], field('trailers'))),
'\r\n',
packFields('trailers'),
output('trailers'),
mark('trailers', null),
mark('bodyFraming', null),
],
[map(({ get }) => typeof get('bodyFraming') == 'number'),
captureOutput('body', rep('bodyFraming', chars.none())),
mark('bodyFraming', null),
]
)
)
)
})
}