diff --git a/doc/api/http.md b/doc/api/http.md index 7f097b0bc3a7f4..7b6bd77f9a1ab2 100644 --- a/doc/api/http.md +++ b/doc/api/http.md @@ -2440,6 +2440,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)` + + +* `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)` + + +* `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 diff --git a/lib/_http_outgoing.js b/lib/_http_outgoing.js index a22621ea4319dc..59b7a8995bd489 100644 --- a/lib/_http_outgoing.js +++ b/lib/_http_outgoing.js @@ -917,6 +917,9 @@ function(err, event) { this.destroy(err); }; +OutgoingMessage.validateHeaderName = validateHeaderName; +OutgoingMessage.validateHeaderValue = validateHeaderValue; + module.exports = { OutgoingMessage }; diff --git a/lib/http.js b/lib/http.js index 55c655763d2647..2b01e1edf5ea19 100644 --- a/lib/http.js +++ b/lib/http.js @@ -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, @@ -63,6 +64,8 @@ module.exports = { Server, ServerResponse, createServer, + validateHeaderName, + validateHeaderValue, get, request }; diff --git a/test/parallel/test-http-header-validators.js b/test/parallel/test-http-header-validators.js new file mode 100644 index 00000000000000..fb23bd4885c766 --- /dev/null +++ b/test/parallel/test-http-header-validators.js @@ -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`); +}