130 lines
3.7 KiB
JavaScript
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),
|
|
]
|
|
)
|
|
)
|
|
)
|
|
})
|
|
}
|