Skip to content

Commit

Permalink
http: expose http.validate-header-name/value
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
osher authored and codebytere committed May 11, 2020
1 parent b061655 commit f33e866
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 0 deletions.
70 changes: 70 additions & 0 deletions doc/api/http.md
Original file line number Diff line number Diff line change
Expand Up @@ -2465,6 +2465,76 @@ events will be emitted in the following order:
Setting the `timeout` option or using the `setTimeout()` function will
not abort the request or do anything besides add a `'timeout'` event.

## `http.validateHeaderName(name)`
<!-- YAML
added: REPLACEME
-->

* `name` {string}

Performs the low-level validations on the provided `name` that are done when
`res.setHeader(name, value)` is called.

Passing illegal value as `name` will result in a [`TypeError`][] being thrown,
identified by `code: 'ERR_INVALID_HTTP_TOKEN'`.

It is not necessary to use this method before passing headers to an HTTP request
or response. The HTTP module will automatically validate such headers.
Examples:

Example:
```js
const { validateHeaderName } = require('http');

try {
validateHeaderName('');
} catch (err) {
err instanceof TypeError; // --> true
err.code; // --> 'ERR_INVALID_HTTP_TOKEN'
err.message; // --> 'Header name must be a valid HTTP token [""]'
}
```

## `http.validateHeaderValue(name, value)`
<!-- YAML
added: REPLACEME
-->

* `name` {string}
* `value` {any}

Performs the low-level validations on the provided `value` that are done when
`res.setHeader(name, value)` is called.

Passing illegal value as `value` will result in a [`TypeError`][] being thrown.
* Undefined value error is identified by `code: 'ERR_HTTP_INVALID_HEADER_VALUE'`.
* Invalid value character error is identified by `code: 'ERR_INVALID_CHAR'`.

It is not necessary to use this method before passing headers to an HTTP request
or response. The HTTP module will automatically validate such headers.

Examples:

```js
const { validateHeaderValue } = require('http');

try {
validateHeaderValue('x-my-header', undefined);
} catch (err) {
err instanceof TypeError; // --> true
err.code === 'ERR_HTTP_INVALID_HEADER_VALUE'; // --> true
err.message; // --> 'Invalid value "undefined" for header "x-my-header"'
}

try {
validateHeaderValue('x-my-header', 'oʊmɪɡə');
} catch (err) {
err instanceof TypeError; // --> true
err.code === 'ERR_INVALID_CHAR'; // --> true
err.message; // --> 'Invalid character in header content ["x-my-header"]'
}
```

[`--insecure-http-parser`]: cli.html#cli_insecure_http_parser
[`--max-http-header-size`]: cli.html#cli_max_http_header_size_size
[`'checkContinue'`]: #http_event_checkcontinue
Expand Down
3 changes: 3 additions & 0 deletions lib/_http_outgoing.js
Original file line number Diff line number Diff line change
Expand Up @@ -909,6 +909,9 @@ function(err, event) {
this.destroy(err);
};

OutgoingMessage.validateHeaderName = validateHeaderName;
OutgoingMessage.validateHeaderValue = validateHeaderValue;

module.exports = {
OutgoingMessage
};
3 changes: 3 additions & 0 deletions lib/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const { ClientRequest } = require('_http_client');
const { methods } = require('_http_common');
const { IncomingMessage } = require('_http_incoming');
const { OutgoingMessage } = require('_http_outgoing');
const { validateHeaderName, validateHeaderValue } = OutgoingMessage;
const {
_connectionListener,
STATUS_CODES,
Expand Down Expand Up @@ -63,6 +64,8 @@ module.exports = {
Server,
ServerResponse,
createServer,
validateHeaderName,
validateHeaderValue,
get,
request
};
Expand Down
62 changes: 62 additions & 0 deletions test/parallel/test-http-header-validators.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
'use strict';
require('../common');
const assert = require('assert');
const { validateHeaderName, validateHeaderValue } = require('http');

// Expected static methods
isFunc(validateHeaderName, 'validateHeaderName');
isFunc(validateHeaderValue, 'validateHeaderValue');

// Expected to be useful as static methods
console.log('validateHeaderName');
// - when used with valid header names - should not throw
[
'user-agent',
'USER-AGENT',
'User-Agent',
'x-forwarded-for'
].forEach((name) => {
console.log('does not throw for "%s"', name);
validateHeaderName(name);
});

// - when used with invalid header names:
[
'איקס-פורוורד-פור',
'x-forwarded-fםr',
].forEach((name) => {
console.log('throws for: "%s"', name.slice(0, 50));
assert.throws(
() => validateHeaderName(name),
{ code: 'ERR_INVALID_HTTP_TOKEN' }
);
});

console.log('validateHeaderValue');
// - when used with valid header values - should not throw
[
['x-valid', 1],
['x-valid', '1'],
['x-valid', 'string'],
].forEach(([name, value]) => {
console.log('does not throw for "%s"', name);
validateHeaderValue(name, value);
});

// - when used with invalid header values:
[
// [header, value, expectedCode]
['x-undefined', undefined, 'ERR_HTTP_INVALID_HEADER_VALUE'],
['x-bad-char', 'לא תקין', 'ERR_INVALID_CHAR'],
].forEach(([name, value, code]) => {
console.log('throws %s for: "%s: %s"', code, name, value);
assert.throws(
() => validateHeaderValue(name, value),
{ code }
);
});

// Misc.
function isFunc(v, ttl) {
assert.ok(v.constructor === Function, `${ttl} is expected to be a function`);
}

0 comments on commit f33e866

Please sign in to comment.