fix(backend): apply the large-body limit to every spelling Express routes

req.path was compared to the canonical '/api/voice/extract' only, but
Express routes case-insensitively and ignores a trailing slash by default.
'/api/voice/extract/' therefore reached the controller with the 100 kb
parser, and 413'd every recording past ~20 seconds — a failure that reads
as a broken microphone rather than a routing detail.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-21 04:02:49 +08:00
parent 1be735a7ab
commit efff258910
2 changed files with 25 additions and 1 deletions

View File

@@ -74,6 +74,20 @@ describe('createJsonBodyParser', () => {
expect((res.body as ProbeBody).keys).toBe(1); expect((res.body as ProbeBody).keys).toBe(1);
}); });
it('widens the limit for the spellings Express itself accepts', async () => {
// Express routes case-insensitively and ignores a trailing slash by default, so these
// all reach the voice controller. Any of them taking the 100 kb parser would 413 a
// real recording and read as a broken microphone.
for (const path of [
'/api/voice/extract/',
'/API/Voice/Extract',
'/api/Voice/extract/',
]) {
const res = await request(buildApp()).post(path).send(bodyOfKb(300));
expect(res.status).toBe(200);
}
});
it('does not widen the limit for a path that merely looks similar', async () => { it('does not widen the limit for a path that merely looks similar', async () => {
for (const path of [ for (const path of [
'/api/voice/extract/extra', '/api/voice/extract/extra',

View File

@@ -24,12 +24,22 @@ export const VOICE_BODY_LIMIT = '10mb';
* the endpoint rejected every real recording with a 500. One explicit branch has no such * the endpoint rejected every real recording with a 500. One explicit branch has no such
* coupling, and is covered by body-parsers.spec.ts. * coupling, and is covered by body-parsers.spec.ts.
*/ */
/**
* Express routes case-insensitively and ignores a trailing slash unless configured
* otherwise, so `/API/Voice/Extract/` reaches the same controller. Matching only the
* canonical spelling would hand those requests the 100 kb parser and 413 every real
* recording — a failure that looks like a broken microphone, not a routing detail.
*/
function isVoiceExtractPath(path: string): boolean {
return path.toLowerCase().replace(/\/+$/, '') === VOICE_EXTRACT_PATH;
}
export function createJsonBodyParser(): RequestHandler { export function createJsonBodyParser(): RequestHandler {
const voiceParser = json({ limit: VOICE_BODY_LIMIT }); const voiceParser = json({ limit: VOICE_BODY_LIMIT });
const defaultParser = json(); const defaultParser = json();
return (req: Request, res: Response, next: NextFunction) => return (req: Request, res: Response, next: NextFunction) =>
req.path === VOICE_EXTRACT_PATH isVoiceExtractPath(req.path)
? voiceParser(req, res, next) ? voiceParser(req, res, next)
: defaultParser(req, res, next); : defaultParser(req, res, next);
} }