Skip to content

Commit 382a5a6

Browse files
authored
refactor(stringifyFormData): respond with 400 with invalid multipart data
1 parent 582d1ed commit 382a5a6

5 files changed

Lines changed: 88 additions & 28 deletions

File tree

src/errors.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,23 @@ export enum ERRORS {
44
ERR_CONTEXT_MATCHER_INVALID_ARRAY = '[HPM] Invalid pathFilter. Plain paths (e.g. "/api") can not be mixed with globs (e.g. "/api/**"). Expecting something like: ["/api", "/ajax"] or ["/api/**", "!**.html"].',
55
ERR_PATH_REWRITER_CONFIG = '[HPM] Invalid pathRewrite config. Expecting object with pathRewrite config or a rewrite function',
66
}
7+
8+
export class HttpProxyMiddlewareError extends Error {
9+
code: string;
10+
11+
constructor(message: string, code: string) {
12+
super(message);
13+
14+
// add custom `code` property
15+
// so this can be used in src/plugins/default/error-response-plugin.ts to determine the status code to return
16+
this.code = code;
17+
18+
// set the correct name for the error class
19+
this.name = this.constructor.name;
20+
21+
// maintain proper stack trace (V8 environments)
22+
if (Error.captureStackTrace) {
23+
Error.captureStackTrace(this, this.constructor);
24+
}
25+
}
26+
}

src/handlers/fix-request-body-utils/stringify-form-data.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
import { HttpProxyMiddlewareError } from '../../errors.js';
2+
13
const CR_OR_LF = /[\r\n]/;
4+
const ERROR_CODE_PREFIX = 'HPM_ERR_INVALID_MULTIPART';
25

36
/**
47
* stringify FormData data
@@ -30,8 +33,9 @@ function getMultipartBoundary(contentType: string): string {
3033
const boundary = (boundaryMatch?.[1] ?? boundaryMatch?.[2] ?? contentType).trim();
3134

3235
if (!boundary || CR_OR_LF.test(boundary)) {
33-
throw new Error(
34-
'[HPM] unsafe multipart boundary detected. Request rejected per RFC 9112 obsolete line folding guidance.',
36+
throw new HttpProxyMiddlewareError(
37+
'[HPM] invalid multipart boundary detected.',
38+
`${ERROR_CODE_PREFIX}_BOUNDARY`,
3539
);
3640
}
3741

@@ -42,14 +46,16 @@ function validateMultipartField(fieldName: string, fieldValue: string, boundary:
4246
const boundaryDelimiter = `--${boundary}`;
4347

4448
if (CR_OR_LF.test(fieldName)) {
45-
throw new Error(
46-
`[HPM] unsafe multipart field name "${fieldName}" detected. Request rejected per RFC 9112 obsolete line folding guidance.`,
49+
throw new HttpProxyMiddlewareError(
50+
`[HPM] invalid multipart field name "${fieldName}" detected.`,
51+
`${ERROR_CODE_PREFIX}_FIELD_NAME`,
4752
);
4853
}
4954

5055
if (CR_OR_LF.test(fieldValue) || fieldValue.includes(boundaryDelimiter)) {
51-
throw new Error(
52-
`[HPM] unsafe multipart field value for "${fieldName}" detected. Request rejected per RFC 9112 obsolete line folding guidance.`,
56+
throw new HttpProxyMiddlewareError(
57+
`[HPM] invalid multipart field value for "${fieldName}" detected.`,
58+
`${ERROR_CODE_PREFIX}_FIELD_VALUE`,
5359
);
5460
}
5561
}

src/status-code.ts

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,24 @@ export function getStatusCode(errorCode: string): number {
33

44
if (/HPE_INVALID/.test(errorCode)) {
55
statusCode = 502;
6-
} else {
7-
switch (errorCode) {
8-
case 'ECONNRESET':
9-
case 'ENOTFOUND':
10-
case 'ECONNREFUSED':
11-
case 'ETIMEDOUT':
12-
statusCode = 504;
13-
break;
14-
default:
15-
statusCode = 500;
16-
break;
17-
}
6+
return statusCode;
7+
}
8+
9+
if (/HPM_ERR_INVALID_MULTIPART_/.test(errorCode)) {
10+
statusCode = 400;
11+
return statusCode;
12+
}
13+
14+
switch (errorCode) {
15+
case 'ECONNRESET':
16+
case 'ENOTFOUND':
17+
case 'ECONNREFUSED':
18+
case 'ETIMEDOUT':
19+
statusCode = 504;
20+
break;
21+
default:
22+
statusCode = 500;
23+
break;
1824
}
1925

2026
return statusCode;

test/e2e/http-proxy-middleware.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ describe('E2E http-proxy-middleware', () => {
167167
.post('/api')
168168
.set('Content-Type', 'application/x-www-form-urlencoded')
169169
.send(`user=${encodeURIComponent(injectedValue)}`)
170-
.expect(500);
170+
.expect(400);
171171

172172
expect(response.text).toContain('Error occurred while trying to proxy');
173173
expect(targetSpy).toHaveBeenCalledTimes(0);

test/unit/fix-request-body-utils/stringify-form-data.spec.ts

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { describe, expect, it } from 'vitest';
22

3+
import { HttpProxyMiddlewareError } from '../../../src/errors.js';
34
import { stringifyFormData } from '../../../src/handlers/fix-request-body-utils/stringify-form-data.js';
45

56
describe('stringifyFormData', () => {
@@ -77,31 +78,43 @@ describe('stringifyFormData', () => {
7778
user: 'alice',
7879
}),
7980
).toThrow(
80-
'[HPM] unsafe multipart boundary detected. Request rejected per RFC 9112 obsolete line folding guidance.',
81+
new HttpProxyMiddlewareError(
82+
'[HPM] invalid multipart boundary detected.',
83+
'HPM_ERR_INVALID_MULTIPART_BOUNDARY',
84+
),
8185
);
8286
});
8387

8488
it('should reject unsafe multipart boundary containing CR only', () => {
8589
expect(() =>
8690
stringifyFormData('multipart/form-data; boundary="BB\rX"', { user: 'alice' }),
8791
).toThrow(
88-
'[HPM] unsafe multipart boundary detected. Request rejected per RFC 9112 obsolete line folding guidance.',
92+
new HttpProxyMiddlewareError(
93+
'[HPM] invalid multipart boundary detected.',
94+
'HPM_ERR_INVALID_MULTIPART_BOUNDARY',
95+
),
8996
);
9097
});
9198

9299
it('should reject unsafe multipart boundary containing LF only', () => {
93100
expect(() =>
94101
stringifyFormData('multipart/form-data; boundary="BB\nX"', { user: 'alice' }),
95102
).toThrow(
96-
'[HPM] unsafe multipart boundary detected. Request rejected per RFC 9112 obsolete line folding guidance.',
103+
new HttpProxyMiddlewareError(
104+
'[HPM] invalid multipart boundary detected.',
105+
'HPM_ERR_INVALID_MULTIPART_BOUNDARY',
106+
),
97107
);
98108
});
99109

100110
it('should reject unsafe multipart boundary when empty after trimming', () => {
101111
expect(() =>
102112
stringifyFormData('multipart/form-data; boundary= ', { user: 'alice' }),
103113
).toThrow(
104-
'[HPM] unsafe multipart boundary detected. Request rejected per RFC 9112 obsolete line folding guidance.',
114+
new HttpProxyMiddlewareError(
115+
'[HPM] invalid multipart boundary detected.',
116+
'HPM_ERR_INVALID_MULTIPART_BOUNDARY',
117+
),
105118
);
106119
});
107120

@@ -111,7 +124,10 @@ describe('stringifyFormData', () => {
111124
'bad\nname': 'alice',
112125
}),
113126
).toThrow(
114-
'[HPM] unsafe multipart field name "bad\nname" detected. Request rejected per RFC 9112 obsolete line folding guidance.',
127+
new HttpProxyMiddlewareError(
128+
'[HPM] invalid multipart field name "bad\nname" detected.',
129+
'HPM_ERR_INVALID_MULTIPART_FIELD_NAME',
130+
),
115131
);
116132
});
117133

@@ -121,7 +137,10 @@ describe('stringifyFormData', () => {
121137
'bad\rname': 'alice',
122138
}),
123139
).toThrow(
124-
'[HPM] unsafe multipart field name "bad\rname" detected. Request rejected per RFC 9112 obsolete line folding guidance.',
140+
new HttpProxyMiddlewareError(
141+
'[HPM] invalid multipart field name "bad\rname" detected.',
142+
'HPM_ERR_INVALID_MULTIPART_FIELD_NAME',
143+
),
125144
);
126145
});
127146

@@ -131,7 +150,10 @@ describe('stringifyFormData', () => {
131150
user: 'alice\r\nadmin',
132151
}),
133152
).toThrow(
134-
'[HPM] unsafe multipart field value for "user" detected. Request rejected per RFC 9112 obsolete line folding guidance.',
153+
new HttpProxyMiddlewareError(
154+
'[HPM] invalid multipart field value for "user" detected.',
155+
'HPM_ERR_INVALID_MULTIPART_FIELD_VALUE',
156+
),
135157
);
136158
});
137159

@@ -141,7 +163,10 @@ describe('stringifyFormData', () => {
141163
user: 'alice\radmin',
142164
}),
143165
).toThrow(
144-
'[HPM] unsafe multipart field value for "user" detected. Request rejected per RFC 9112 obsolete line folding guidance.',
166+
new HttpProxyMiddlewareError(
167+
'[HPM] invalid multipart field value for "user" detected.',
168+
'HPM_ERR_INVALID_MULTIPART_FIELD_VALUE',
169+
),
145170
);
146171
});
147172

@@ -151,7 +176,10 @@ describe('stringifyFormData', () => {
151176
user: 'prefix --BB suffix',
152177
}),
153178
).toThrow(
154-
'[HPM] unsafe multipart field value for "user" detected. Request rejected per RFC 9112 obsolete line folding guidance.',
179+
new HttpProxyMiddlewareError(
180+
'[HPM] invalid multipart field value for "user" detected.',
181+
'HPM_ERR_INVALID_MULTIPART_FIELD_VALUE',
182+
),
155183
);
156184
});
157185
});

0 commit comments

Comments
 (0)