Skip to content

Commit

Permalink
feat: Added support for RTSP and ICE response and the on_protocol_com…
Browse files Browse the repository at this point in the history
…plete callback.
  • Loading branch information
ShogunPanda committed Aug 26, 2024
1 parent 6055e85 commit cca8909
Show file tree
Hide file tree
Showing 28 changed files with 661 additions and 13 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ The following callbacks can return `0` (proceed normally), `-1` (error) or `HPE_
* `on_message_complete`: Invoked when a request/response has been completedly parsed.
* `on_url_complete`: Invoked after the URL has been parsed.
* `on_method_complete`: Invoked after the HTTP method has been parsed.
* `on_protocol_complete`: Invoked after the HTTP version has been parsed.
* `on_version_complete`: Invoked after the HTTP version has been parsed.
* `on_status_complete`: Invoked after the status code has been parsed.
* `on_header_field_complete`: Invoked after a header name has been parsed.
Expand All @@ -130,6 +131,7 @@ The following callbacks can return `0` (proceed normally), `-1` (error) or `HPE_
* `on_method`: Invoked when another character of the method is received.
When parser is created with `HTTP_BOTH` and the input is a response, this also invoked for the sequence `HTTP/`
of the first message.
* `on_protocol`: Invoked when another character of the protocol is received.
* `on_version`: Invoked when another character of the version is received.
* `on_header_field`: Invoked when another character of a header name is received.
* `on_header_value`: Invoked when another character of a header value is received.
Expand Down
1 change: 1 addition & 0 deletions src/llhttp/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export const ERROR: IntDict = {
CB_CHUNK_EXTENSION_NAME_COMPLETE: 34,
CB_CHUNK_EXTENSION_VALUE_COMPLETE: 35,
CB_RESET: 31,
CB_PROTOCOL_COMPLETE: 38,
};

export const TYPE: IntDict = {
Expand Down
49 changes: 42 additions & 7 deletions src/llhttp/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ const NODES: ReadonlyArray<string> = [

'req_or_res_method',

'res_after_start',
'res_after_protocol',
'res_http_major',
'res_http_dot',
'res_http_minor',
Expand All @@ -41,6 +43,8 @@ const NODES: ReadonlyArray<string> = [
'req_first_space_before_url',
'req_spaces_before_url',
'req_http_start',
'req_after_http_start',
'req_after_protocol',
'req_http_version',
'req_http_major',
'req_http_dot',
Expand Down Expand Up @@ -109,6 +113,7 @@ const NODES: ReadonlyArray<string> = [
];

interface ISpanMap {
readonly protocol: source.Span;
readonly status: source.Span;
readonly method: source.Span;
readonly version: source.Span;
Expand All @@ -121,6 +126,7 @@ interface ISpanMap {

interface ICallbackMap {
readonly onMessageBegin: source.code.Code;
readonly onProtocolComplete: source.code.Code;
readonly onUrlComplete: source.code.Code;
readonly onMethodComplete: source.code.Code;
readonly onVersionComplete: source.code.Code;
Expand Down Expand Up @@ -178,6 +184,7 @@ export class HTTP {
chunkExtensionValue: p.span(p.code.span('llhttp__on_chunk_extension_value')),
headerField: p.span(p.code.span('llhttp__on_header_field')),
headerValue: p.span(p.code.span('llhttp__on_header_value')),
protocol: p.span(p.code.span('llhttp__on_protocol')),
method: p.span(p.code.span('llhttp__on_method')),
status: p.span(p.code.span('llhttp__on_status')),
version: p.span(p.code.span('llhttp__on_version')),
Expand All @@ -186,6 +193,7 @@ export class HTTP {
/* tslint:disable:object-literal-sort-keys */
this.callback = {
// User callbacks
onProtocolComplete: p.code.match('llhttp__on_protocol_complete'),
onUrlComplete: p.code.match('llhttp__on_url_complete'),
onStatusComplete: p.code.match('llhttp__on_status_complete'),
onMethodComplete: p.code.match('llhttp__on_method_complete'),
Expand Down Expand Up @@ -315,8 +323,23 @@ export class HTTP {
};

// Response
const endResponseProtocol = () => {
return this.span.protocol.end(
this.invokePausable('on_protocol_complete', ERROR.CB_PROTOCOL_COMPLETE, n('res_after_protocol'),
));
}

n('start_res')
.match('HTTP/', span.version.start(n('res_http_major')))
.otherwise(this.span.protocol.start(n('res_after_start')));

n('res_after_start')
.match('HTTP', endResponseProtocol())
.match('RTSP', endResponseProtocol())
.match('ICE', endResponseProtocol())
.otherwise(this.span.protocol.end(p.error(ERROR.INVALID_CONSTANT, 'Expected HTTP/')));

n('res_after_protocol')
.match('/', span.version.start(n('res_http_major')))
.otherwise(p.error(ERROR.INVALID_CONSTANT, 'Expected HTTP/'));

n('res_http_major')
Expand Down Expand Up @@ -438,25 +461,34 @@ export class HTTP {
);

const checkMethod = (methods: number[], error: string): Node => {
const success = n('req_http_version');
const success = n('req_after_protocol');
const failure = p.error(ERROR.INVALID_CONSTANT, error);

const map: { [key: number]: Node } = {};
for (const method of methods) {
map[method] = success;
}

return this.load('method', map, failure);
return this.span.protocol.end(
this.invokePausable('on_protocol_complete', ERROR.CB_PROTOCOL_COMPLETE, this.load('method', map, failure)),
);
};

n('req_http_start')
.match('HTTP/', checkMethod(METHODS_HTTP,
.match(' ', n('req_http_start'))
.otherwise(this.span.protocol.start(n('req_after_http_start')));

n('req_after_http_start')
.match('HTTP', checkMethod(METHODS_HTTP,
'Invalid method for HTTP/x.x request'))
.match('RTSP/', checkMethod(METHODS_RTSP,
.match('RTSP', checkMethod(METHODS_RTSP,
'Invalid method for RTSP/x.x request'))
.match('ICE/', checkMethod(METHODS_ICE,
.match('ICE', checkMethod(METHODS_ICE,
'Expected SOURCE method for ICE/x.x request'))
.match(' ', n('req_http_start'))
.otherwise(this.span.protocol.end(p.error(ERROR.INVALID_CONSTANT, 'Expected HTTP/')));

n('req_after_protocol')
.match('/', n('req_http_version'))
.otherwise(p.error(ERROR.INVALID_CONSTANT, 'Expected HTTP/'));

n('req_http_version').otherwise(span.version.start(n('req_http_major')));
Expand Down Expand Up @@ -1250,6 +1282,9 @@ export class HTTP {
case 'on_message_begin':
cb = this.callback.onMessageBegin;
break;
case 'on_protocol_complete':
cb = this.callback.onProtocolComplete;
break;
case 'on_url_complete':
cb = this.callback.onUrlComplete;
break;
Expand Down
14 changes: 14 additions & 0 deletions src/native/api.c
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,20 @@ int llhttp__on_message_begin(llhttp_t* s, const char* p, const char* endp) {
}


int llhttp__on_protocol(llhttp_t* s, const char* p, const char* endp) {
int err;
SPAN_CALLBACK_MAYBE(s, on_protocol, p, endp - p);
return err;
}


int llhttp__on_protocol_complete(llhttp_t* s, const char* p, const char* endp) {
int err;
CALLBACK_MAYBE(s, on_protocol_complete);
return err;
}


int llhttp__on_url(llhttp_t* s, const char* p, const char* endp) {
int err;
SPAN_CALLBACK_MAYBE(s, on_url, p, endp - p);
Expand Down
1 change: 1 addition & 0 deletions src/native/api.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ struct llhttp_settings_s {

/* Possible return values 0, -1, `HPE_PAUSED` */
llhttp_cb on_message_complete;
llhttp_cb on_protocol_complete;
llhttp_cb on_url_complete;
llhttp_cb on_status_complete;
llhttp_cb on_method_complete;
Expand Down
22 changes: 22 additions & 0 deletions test/fixtures/extra.c
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,28 @@ int llhttp__on_message_complete(llparse_t* s, const char* p, const char* endp) {
}


int llhttp__on_protocol(llparse_t* s, const char* p, const char* endp) {
if (llparse__in_bench)
return 0;

return llparse__print_span("protocol", p, endp);
}


int llhttp__on_protocol_complete(llparse_t* s, const char* p, const char* endp) {
if (llparse__in_bench)
return 0;

llparse__print(p, endp, "protocol complete");

#ifdef LLHTTP__TEST_PAUSE_ON_PROTOCOL_COMPLETE
return LLPARSE__ERROR_PAUSE;
#else
return 0;
#endif
}


int llhttp__on_status(llparse_t* s, const char* p, const char* endp) {
if (llparse__in_bench)
return 0;
Expand Down
8 changes: 5 additions & 3 deletions test/md-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,10 @@ function run(name: string): void {
const raw = fs.readFileSync(path.join(__dirname, name + '.md')).toString();
const groups = md.parse(raw);

function runSingleTest(ty: TestType, meta: Metadata,
function runSingleTest(location: string, ty: TestType, meta: Metadata,
input: string,
expected: ReadonlyArray<string | RegExp>): void {
test(`should pass for type="${ty}"`, { timeout: 60000 }, async () => {
test(`should pass for type="${ty}" (location=${location})`, { timeout: 60000 }, async () => {
const binary = await buildMode(ty, meta);
await binary.check(input, expected, {
noScan: meta.noScan === true,
Expand All @@ -103,6 +103,8 @@ function run(name: string): void {

function runTest(test: Test) {
describe(test.name + ` at ${name}.md:${test.line + 1}`, () => {
const location = `${name}.md:${test.line + 1}`

let types: TestType[] = [];

const isURL = test.values.has('url');
Expand Down Expand Up @@ -202,7 +204,7 @@ function run(name: string): void {
continue;
}

runSingleTest(ty, meta, input, fullExpected);
runSingleTest(location, ty, meta, input, fullExpected);
}
});
}
Expand Down
Loading

0 comments on commit cca8909

Please sign in to comment.