From b6763f748325bf9c4129c5230c5e8101f93a2388 Mon Sep 17 00:00:00 2001 From: "Max P." Date: Sun, 11 May 2025 10:59:49 +0200 Subject: [PATCH] feat(handler): sanitize sensitive fields in form data - Removes `username` and `apiKey` from form data to prevent errors from the LanguageTool server when these fields are present - Updates test cases to reflect the new handling of form data bodies --- src/__tests__/ltProxyHandler.test.ts | 10 +++++++-- src/ltProxyHandler.ts | 33 ++++++++++++++++++++++++---- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/__tests__/ltProxyHandler.test.ts b/src/__tests__/ltProxyHandler.test.ts index 27cd254..1eb2061 100644 --- a/src/__tests__/ltProxyHandler.test.ts +++ b/src/__tests__/ltProxyHandler.test.ts @@ -16,12 +16,16 @@ Deno.test('ltProxyHandler: proxies request and returns response', async () => { const originalFetch = globalThis.fetch; globalThis.fetch = async () => expectedResponse; + // Form body wie bei echtem Request + const formData = new URLSearchParams({ text: 'Hallo Welt' }); + const bodyBytes = new TextEncoder().encode(formData.toString()); + const req = new Request('http://localhost/v2/check?language=de-DE', { method: 'POST', - body: new TextEncoder().encode('text=Hallo+Welt'), headers: { 'content-type': 'application/x-www-form-urlencoded', }, + body: bodyBytes, }); const ctx: IContext = { @@ -30,7 +34,9 @@ Deno.test('ltProxyHandler: proxies request and returns response', async () => { query: { language: 'de-DE', }, - state: {}, + state: { + body: bodyBytes, + }, }; const response = await ltProxyHandler(ctx); diff --git a/src/ltProxyHandler.ts b/src/ltProxyHandler.ts index 18fd1f7..8d593e8 100644 --- a/src/ltProxyHandler.ts +++ b/src/ltProxyHandler.ts @@ -4,6 +4,7 @@ import { Env } from './env.ts'; /** * Forwards the incoming request to the actual LanguageTool server. * Dynamically passes through path and query string. + * Removes `username` and `apiKey` from the FormData body if present. */ export const handler: Handler = async (ctx) => { const originalUrl = new URL(ctx.req.url); @@ -12,16 +13,40 @@ export const handler: Handler = async (ctx) => { `http://${Env.ltServerHost}:${Env.ltServerPort}`, ); + const contentType = ctx.req.headers.get('content-type') ?? ''; + let body: BodyInit | null = null; + + if ( + contentType.includes('application/x-www-form-urlencoded') && + ctx.state.body + ) { + const text = new TextDecoder().decode(ctx.state.body as Uint8Array); + const params = new URLSearchParams(text); + + // Remove `apiKey` and `username` from the params + // LanguageTool will react with a error if they are present + params.delete('apiKey'); + params.delete('username'); + + body = params.toString(); + } else { + console.debug('Unsupported content type:', contentType); + body = ctx.state.body as BodyInit | null; + } + + const headers = new Headers(ctx.req.headers); + headers.delete('content-length'); + const forwarded = await fetch(proxyUrl.toString(), { method: ctx.req.method, - headers: ctx.req.headers, - body: ctx.req.body, + headers, + body, }); - const headers = new Headers(forwarded.headers); + const respHeaders = new Headers(forwarded.headers); return new Response(forwarded.body, { status: forwarded.status, - headers, + headers: respHeaders, }); };