Skip to content

Commit f33e866

Browse files
oshercodebytere
authored andcommitted
http: expose http.validate-header-name/value
The use-case is for any framework that provides user mw a response replacement, that collects the desired response state, and applies them only on conclusion. As such a framework, I'd want to validate the header names and values as soon as the user-code provides them. This - to eliminate errors on response-send time, and provide developer stack trace that contains the line that submits the offending values. PR-URL: #33119 Reviewed-By: Anna Henningsen <[email protected]>
1 parent b061655 commit f33e866

File tree

4 files changed

+138
-0
lines changed

4 files changed

+138
-0
lines changed

doc/api/http.md

+70
Original file line numberDiff line numberDiff line change
@@ -2465,6 +2465,76 @@ events will be emitted in the following order:
24652465
Setting the `timeout` option or using the `setTimeout()` function will
24662466
not abort the request or do anything besides add a `'timeout'` event.
24672467

2468+
## `http.validateHeaderName(name)`
2469+
<!-- YAML
2470+
added: REPLACEME
2471+
-->
2472+
2473+
* `name` {string}
2474+
2475+
Performs the low-level validations on the provided `name` that are done when
2476+
`res.setHeader(name, value)` is called.
2477+
2478+
Passing illegal value as `name` will result in a [`TypeError`][] being thrown,
2479+
identified by `code: 'ERR_INVALID_HTTP_TOKEN'`.
2480+
2481+
It is not necessary to use this method before passing headers to an HTTP request
2482+
or response. The HTTP module will automatically validate such headers.
2483+
Examples:
2484+
2485+
Example:
2486+
```js
2487+
const { validateHeaderName } = require('http');
2488+
2489+
try {
2490+
validateHeaderName('');
2491+
} catch (err) {
2492+
err instanceof TypeError; // --> true
2493+
err.code; // --> 'ERR_INVALID_HTTP_TOKEN'
2494+
err.message; // --> 'Header name must be a valid HTTP token [""]'
2495+
}
2496+
```
2497+
2498+
## `http.validateHeaderValue(name, value)`
2499+
<!-- YAML
2500+
added: REPLACEME
2501+
-->
2502+
2503+
* `name` {string}
2504+
* `value` {any}
2505+
2506+
Performs the low-level validations on the provided `value` that are done when
2507+
`res.setHeader(name, value)` is called.
2508+
2509+
Passing illegal value as `value` will result in a [`TypeError`][] being thrown.
2510+
* Undefined value error is identified by `code: 'ERR_HTTP_INVALID_HEADER_VALUE'`.
2511+
* Invalid value character error is identified by `code: 'ERR_INVALID_CHAR'`.
2512+
2513+
It is not necessary to use this method before passing headers to an HTTP request
2514+
or response. The HTTP module will automatically validate such headers.
2515+
2516+
Examples:
2517+
2518+
```js
2519+
const { validateHeaderValue } = require('http');
2520+
2521+
try {
2522+
validateHeaderValue('x-my-header', undefined);
2523+
} catch (err) {
2524+
err instanceof TypeError; // --> true
2525+
err.code === 'ERR_HTTP_INVALID_HEADER_VALUE'; // --> true
2526+
err.message; // --> 'Invalid value "undefined" for header "x-my-header"'
2527+
}
2528+
2529+
try {
2530+
validateHeaderValue('x-my-header', 'oʊmɪɡə');
2531+
} catch (err) {
2532+
err instanceof TypeError; // --> true
2533+
err.code === 'ERR_INVALID_CHAR'; // --> true
2534+
err.message; // --> 'Invalid character in header content ["x-my-header"]'
2535+
}
2536+
```
2537+
24682538
[`--insecure-http-parser`]: cli.html#cli_insecure_http_parser
24692539
[`--max-http-header-size`]: cli.html#cli_max_http_header_size_size
24702540
[`'checkContinue'`]: #http_event_checkcontinue

lib/_http_outgoing.js

+3
Original file line numberDiff line numberDiff line change
@@ -909,6 +909,9 @@ function(err, event) {
909909
this.destroy(err);
910910
};
911911

912+
OutgoingMessage.validateHeaderName = validateHeaderName;
913+
OutgoingMessage.validateHeaderValue = validateHeaderValue;
914+
912915
module.exports = {
913916
OutgoingMessage
914917
};

lib/http.js

+3
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ const { ClientRequest } = require('_http_client');
3030
const { methods } = require('_http_common');
3131
const { IncomingMessage } = require('_http_incoming');
3232
const { OutgoingMessage } = require('_http_outgoing');
33+
const { validateHeaderName, validateHeaderValue } = OutgoingMessage;
3334
const {
3435
_connectionListener,
3536
STATUS_CODES,
@@ -63,6 +64,8 @@ module.exports = {
6364
Server,
6465
ServerResponse,
6566
createServer,
67+
validateHeaderName,
68+
validateHeaderValue,
6669
get,
6770
request
6871
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
'use strict';
2+
require('../common');
3+
const assert = require('assert');
4+
const { validateHeaderName, validateHeaderValue } = require('http');
5+
6+
// Expected static methods
7+
isFunc(validateHeaderName, 'validateHeaderName');
8+
isFunc(validateHeaderValue, 'validateHeaderValue');
9+
10+
// Expected to be useful as static methods
11+
console.log('validateHeaderName');
12+
// - when used with valid header names - should not throw
13+
[
14+
'user-agent',
15+
'USER-AGENT',
16+
'User-Agent',
17+
'x-forwarded-for'
18+
].forEach((name) => {
19+
console.log('does not throw for "%s"', name);
20+
validateHeaderName(name);
21+
});
22+
23+
// - when used with invalid header names:
24+
[
25+
'איקס-פורוורד-פור',
26+
'x-forwarded-fםr',
27+
].forEach((name) => {
28+
console.log('throws for: "%s"', name.slice(0, 50));
29+
assert.throws(
30+
() => validateHeaderName(name),
31+
{ code: 'ERR_INVALID_HTTP_TOKEN' }
32+
);
33+
});
34+
35+
console.log('validateHeaderValue');
36+
// - when used with valid header values - should not throw
37+
[
38+
['x-valid', 1],
39+
['x-valid', '1'],
40+
['x-valid', 'string'],
41+
].forEach(([name, value]) => {
42+
console.log('does not throw for "%s"', name);
43+
validateHeaderValue(name, value);
44+
});
45+
46+
// - when used with invalid header values:
47+
[
48+
// [header, value, expectedCode]
49+
['x-undefined', undefined, 'ERR_HTTP_INVALID_HEADER_VALUE'],
50+
['x-bad-char', 'לא תקין', 'ERR_INVALID_CHAR'],
51+
].forEach(([name, value, code]) => {
52+
console.log('throws %s for: "%s: %s"', code, name, value);
53+
assert.throws(
54+
() => validateHeaderValue(name, value),
55+
{ code }
56+
);
57+
});
58+
59+
// Misc.
60+
function isFunc(v, ttl) {
61+
assert.ok(v.constructor === Function, `${ttl} is expected to be a function`);
62+
}

0 commit comments

Comments
 (0)