mirror of
https://github.com/n8n-io/n8n
synced 2026-04-21 15:47:20 +00:00
fix(core): Handle invalid percent sequences and equals signs in HTTP response headers (#27691)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
550409923a
commit
ca71d89d88
2 changed files with 56 additions and 5 deletions
|
|
@ -50,6 +50,17 @@ describe('parseContentType', () => {
|
|||
},
|
||||
description: 'should parse content type with multiple parameters',
|
||||
},
|
||||
{
|
||||
input: 'multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxk=',
|
||||
expected: {
|
||||
type: 'multipart/form-data',
|
||||
parameters: {
|
||||
charset: 'utf-8',
|
||||
boundary: '----WebKitFormBoundary7MA4YWxk=',
|
||||
},
|
||||
},
|
||||
description: 'should preserve trailing = in boundary parameter',
|
||||
},
|
||||
{
|
||||
input: 'text/plain; charset="utf-8"; filename="test.txt"',
|
||||
expected: {
|
||||
|
|
@ -141,6 +152,21 @@ describe('parseContentDisposition', () => {
|
|||
expected: { type: 'attachment', filename: '😀.txt' },
|
||||
description: 'should handle encoded filenames',
|
||||
},
|
||||
{
|
||||
input: 'attachment; filename="my_scan_144dpi_75%.pdf"',
|
||||
expected: { type: 'attachment', filename: 'my_scan_144dpi_75%.pdf' },
|
||||
description: 'should handle filenames with bare percent sign',
|
||||
},
|
||||
{
|
||||
input: 'attachment; filename="report=final.pdf"',
|
||||
expected: { type: 'attachment', filename: 'report=final.pdf' },
|
||||
description: 'should handle filenames with equals sign',
|
||||
},
|
||||
{
|
||||
input: 'attachment; filename="report 50% done.pdf"',
|
||||
expected: { type: 'attachment', filename: 'report 50% done.pdf' },
|
||||
description: 'should handle filenames with bare percent sign and space',
|
||||
},
|
||||
{
|
||||
input: 'attachment; size=123; filename="test.txt"; creation-date="Thu, 1 Jan 2020"',
|
||||
expected: { type: 'attachment', filename: 'test.txt' },
|
||||
|
|
|
|||
|
|
@ -3,12 +3,37 @@ import type { IncomingMessage } from 'http';
|
|||
function parseHeaderParameters(parameters: string[]): Record<string, string> {
|
||||
return parameters.reduce(
|
||||
(acc, param) => {
|
||||
const [key, value] = param.split('=');
|
||||
let decodedValue = decodeURIComponent(value).trim();
|
||||
if (decodedValue.startsWith('"') && decodedValue.endsWith('"')) {
|
||||
decodedValue = decodedValue.slice(1, -1);
|
||||
const eqIdx = param.indexOf('=');
|
||||
if (eqIdx === -1) return acc;
|
||||
const key = param.slice(0, eqIdx);
|
||||
let processedValue = param.slice(eqIdx + 1).trim();
|
||||
|
||||
if (processedValue.startsWith('"') && processedValue.endsWith('"')) {
|
||||
// Quoted string: strip quotes first, then try to percent-decode.
|
||||
// Some non-standard servers percent-encode inside quoted strings
|
||||
// (e.g. filename="my%20file.pdf"). Per RFC 6266, quoted filename
|
||||
// values are plain strings but we decode as a best-effort fallback.
|
||||
// A bare % that isn't a valid percent-encoded sequence is kept as-is.
|
||||
processedValue = processedValue.slice(1, -1);
|
||||
try {
|
||||
processedValue = decodeURIComponent(processedValue);
|
||||
} catch {
|
||||
// Keep raw value — contains an invalid percent sequence (e.g. 75%.pdf)
|
||||
}
|
||||
} else {
|
||||
// Unquoted value: may be entirely percent-encoded, including the quotes
|
||||
// themselves (e.g. filename=%22test%20file.txt%22 → "test file.txt")
|
||||
try {
|
||||
processedValue = decodeURIComponent(processedValue);
|
||||
if (processedValue.startsWith('"') && processedValue.endsWith('"')) {
|
||||
processedValue = processedValue.slice(1, -1);
|
||||
}
|
||||
} catch {
|
||||
// Keep raw value
|
||||
}
|
||||
}
|
||||
acc[key.toLowerCase().trim()] = decodedValue;
|
||||
|
||||
acc[key.toLowerCase().trim()] = processedValue;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, string>,
|
||||
|
|
|
|||
Loading…
Reference in a new issue