Skip to content

Commit

Permalink
http: fix validation of "Link" header
Browse files Browse the repository at this point in the history
Updated regex for "Link" header validation to better match the
specification in RFC 8288 section 3. Does not check for valid URI
format but handles the rest of the header more permissively than
before. Alternative to another outstanding PR that disables validation
entirely.

Fixes: #46453
Refs: https://www.rfc-editor.org/rfc/rfc8288.html#section-3
Refs: #46464
PR-URL: #46466
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Paolo Insogna <paolo@cowtech.it>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
  • Loading branch information
SRHerzog authored and danielleadams committed Apr 5, 2023
1 parent eef3051 commit 9582c8e
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 28 deletions.
10 changes: 9 additions & 1 deletion lib/internal/validators.js
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,15 @@ function validateUnion(value, name, union) {
}
}

const linkValueRegExp = /^(?:<[^>]*>;)\s*(?:rel=(")?[^;"]*\1;?)\s*(?:(?:as|anchor|title|crossorigin|disabled|fetchpriority|rel|referrerpolicy)=(")?[^;"]*\2)?$/;
/*
The rules for the Link header field are described here:
https://www.rfc-editor.org/rfc/rfc8288.html#section-3
This regex validates any string surrounded by angle brackets
(not necessarily a valid URI reference) followed by zero or more
link-params separated by semicolons.
*/
const linkValueRegExp = /^(?:<[^>]*>)(?:\s*;\s*[^;"\s]+(?:=(")?[^;"\s]*\1)?)*$/;

/**
* @param {any} value
Expand Down
66 changes: 41 additions & 25 deletions test/parallel/test-http-early-hints-invalid-argument.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,44 @@ const debug = require('node:util').debuglog('test');

const testResBody = 'response content\n';

const server = http.createServer(common.mustCall((req, res) => {
debug('Server sending early hints...');
res.writeEarlyHints('bad argument type');

debug('Server sending full response...');
res.end(testResBody);
}));

server.listen(0, common.mustCall(() => {
const req = http.request({
port: server.address().port, path: '/'
});

req.end();
debug('Client sending request...');

req.on('information', common.mustNotCall());

process.on('uncaughtException', (err) => {
debug(`Caught an exception: ${JSON.stringify(err)}`);
if (err.name === 'AssertionError') throw err;
assert.strictEqual(err.code, 'ERR_INVALID_ARG_TYPE');
process.exit(0);
});
}));
{
const server = http.createServer(common.mustCall((req, res) => {
debug('Server sending early hints...');
assert.throws(() => {
res.writeEarlyHints('bad argument type');
}, (err) => err.code === 'ERR_INVALID_ARG_TYPE');

assert.throws(() => {
res.writeEarlyHints({
link: '</>; '
});
}, (err) => err.code === 'ERR_INVALID_ARG_VALUE');

assert.throws(() => {
res.writeEarlyHints({
link: 'rel=preload; </scripts.js>'
});
}, (err) => err.code === 'ERR_INVALID_ARG_VALUE');

assert.throws(() => {
res.writeEarlyHints({
link: 'invalid string'
});
}, (err) => err.code === 'ERR_INVALID_ARG_VALUE');

debug('Server sending full response...');
res.end(testResBody);
server.close();
}));

server.listen(0, common.mustCall(() => {
const req = http.request({
port: server.address().port, path: '/'
});

req.end();
debug('Client sending request...');

req.on('information', common.mustNotCall());
}));
}
6 changes: 4 additions & 2 deletions test/parallel/test-http-early-hints.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ const testResBody = 'response content\n';
res.writeEarlyHints({
link: [
'</styles.css>; rel=preload; as=style',
'</scripts.js>; rel=preload; as=script',
'</scripts.js>; crossorigin; rel=preload; as=script',
'</scripts.js>; rel=preload; as=script; crossorigin',
]
});

Expand All @@ -75,7 +76,8 @@ const testResBody = 'response content\n';
req.on('information', common.mustCall((res) => {
assert.strictEqual(
res.headers.link,
'</styles.css>; rel=preload; as=style, </scripts.js>; rel=preload; as=script'
'</styles.css>; rel=preload; as=style, </scripts.js>; crossorigin; ' +
'rel=preload; as=script, </scripts.js>; rel=preload; as=script; crossorigin'
);
}));

Expand Down

0 comments on commit 9582c8e

Please sign in to comment.