From 83a8786ca5d8dd598745f000d6bf3c86e76f59c6 Mon Sep 17 00:00:00 2001 From: Burak Keskin Date: Wed, 2 Sep 2026 15:56:57 +0300 Subject: [PATCH] feat: add HTTP_ALL_METHOD_COUNT for iterating method names Embedders had to hardcode the method range, so QUERY never showed up in name caches. Unknown ids now return NULL instead of aborting. Fixes #872 --- package.json | 2 +- src/llhttp/c-headers.ts | 5 +++++ src/native/api.c | 2 +- src/native/api.h | 4 +++- test/headers-test.ts | 32 ++++++++++++++++++++++++++++++++ 5 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 test/headers-test.ts diff --git a/package.json b/package.json index f02f1026..843305b2 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "wasm": "node --import tsx bin/build_wasm.ts", "clean": "node --import tsx scripts/clean.ts", "prepare": "npm run clean && npm run build-ts", - "test": "node --import tsx ./test/md-test.ts", + "test": "node --import tsx ./test/md-test.ts && node --import tsx ./test/headers-test.ts", "lint": "eslint", "lint-fix": "eslint --fix", "postversion": "RELEASE=`node -e \"process.stdout.write(require('./package').version)\"` make -B postversion", diff --git a/src/llhttp/c-headers.ts b/src/llhttp/c-headers.ts index a8716a28..2e5c1745 100644 --- a/src/llhttp/c-headers.ts +++ b/src/llhttp/c-headers.ts @@ -41,6 +41,7 @@ export class CHeaders { res += this.buildMap('RTSP_METHOD', constants.METHODS_RTSP); res += '\n'; res += this.buildMap('HTTP_ALL_METHOD', constants.METHODS); + res += `#define HTTP_ALL_METHOD_COUNT ${this.maxValue(constants.METHODS) + 1}\n`; res += '\n'; res += this.buildMap('HTTP_STATUS', constants.STATUSES); @@ -86,4 +87,8 @@ export class CHeaders { return res; } + + private maxValue(map: IntDict): number { + return Math.max(...Object.values(map)); + } } diff --git a/src/native/api.c b/src/native/api.c index ae5e862d..cdc85d17 100644 --- a/src/native/api.c +++ b/src/native/api.c @@ -219,7 +219,7 @@ const char* llhttp_method_name(llhttp_method_t method) { #define HTTP_METHOD_GEN(NUM, NAME, STRING) case HTTP_##NAME: return #STRING; switch (method) { HTTP_ALL_METHOD_MAP(HTTP_METHOD_GEN) - default: abort(); + default: return NULL; } #undef HTTP_METHOD_GEN } diff --git a/src/native/api.h b/src/native/api.h index 0a58d4e0..5144283d 100644 --- a/src/native/api.h +++ b/src/native/api.h @@ -212,7 +212,9 @@ const char* llhttp_get_error_pos(const llhttp_t* parser); LLHTTP_EXPORT const char* llhttp_errno_name(llhttp_errno_t err); -/* Returns textual name of HTTP method */ +/* Returns textual name of HTTP method, or NULL if `method` is unknown. + * Method ids run from 0 up to HTTP_ALL_METHOD_COUNT-1. + */ LLHTTP_EXPORT const char* llhttp_method_name(llhttp_method_t method); diff --git a/test/headers-test.ts b/test/headers-test.ts new file mode 100644 index 00000000..83a11974 --- /dev/null +++ b/test/headers-test.ts @@ -0,0 +1,32 @@ +import assert from 'node:assert'; +import { describe, test } from 'node:test'; + +import { CHeaders, constants } from '../src/llhttp'; + +describe('CHeaders method count', () => { + test('HTTP_ALL_METHOD_COUNT is one past the highest method id', () => { + const headers = new CHeaders().build(); + const maxId = Math.max(...Object.values(constants.METHODS)); + const match = headers.match(/#define HTTP_ALL_METHOD_COUNT (\d+)/); + assert.ok(match); + assert.strictEqual(Number(match[1]), maxId + 1); + }); + + test('every METHODS id is in [0, HTTP_ALL_METHOD_COUNT)', () => { + const headers = new CHeaders().build(); + const match = headers.match(/#define HTTP_ALL_METHOD_COUNT (\d+)/); + assert.ok(match); + const count = Number(match[1]); + for (const id of Object.values(constants.METHODS)) { + assert.ok(id >= 0 && id < count); + } + }); + + test('HTTP_ALL_METHOD_MAP includes QUERY so a hardcoded 46 would miss it', () => { + const headers = new CHeaders().build(); + assert.match(headers, /XX\(46, QUERY, QUERY\)/); + const match = headers.match(/#define HTTP_ALL_METHOD_COUNT (\d+)/); + assert.ok(match); + assert.ok(Number(match[1]) > 46); + }); +});