From a3b77f59e5c086ddd79d0ed5ada14ad6559815f8 Mon Sep 17 00:00:00 2001 From: JSap0914 Date: Wed, 17 Jun 2026 20:35:00 +0900 Subject: [PATCH] fix(res.links): skip empty-array rel values to prevent trailing comma in Link header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When res.links() is called with an empty array for a rel value, the inner .map().join(', ') produces an empty string ''. The outer .join(', ') then includes that empty entry, resulting in either a trailing comma: res.links({ next: 'http://example.com/2', prev: [] }) // Before: Link: ; rel="next", // After: Link: ; rel="next" or a dangling separator when a second call appends nothing: res.links({ next: 'http://example.com/2' }) res.links({ prev: [] }) // Before: Link: ; rel="next", // After: Link: ; rel="next" Fix: collect new link entries first, filter out empty strings with .filter(Boolean), then only prepend the ', ' separator when both the existing Link value and the new entries are non-empty. Fixes: RFC 8288 compliance — Link header values must be a comma-separated list of link values; an empty string or trailing comma is invalid. --- History.md | 1 + lib/response.js | 8 +++++--- test/res.links.js | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/History.md b/History.md index 9e6ee903f40..6013f28d7af 100644 --- a/History.md +++ b/History.md @@ -3,6 +3,7 @@ ## 🐞 Bug fixes - Fixed HTTP header conflict between Content-Length and Transfer-Encoding in res.send - by [@YuryShkoda](https://github.com/YuryShkoda) in [#4893](https://github.com/expressjs/express/pull/4893) +- Fixed `res.links()` producing a trailing comma in the `Link` header when a rel value is an empty array - by [@JSap0914](https://github.com/JSap0914) Fixed the behavior of `res.send()` to prevent conflicts between `Content-Length` and `Transfer-Encoding` HTTP headers in responses. The `Content-Length` header in `res.send()` is now only added when a `Transfer-Encoding` header is not present, complying with the HTTP specification that states both headers should not coexist in the same response diff --git a/lib/response.js b/lib/response.js index b4755a5c060..6280d2ffacc 100644 --- a/lib/response.js +++ b/lib/response.js @@ -97,8 +97,7 @@ res.status = function status(code) { res.links = function(links) { var link = this.get('Link') || ''; - if (link) link += ', '; - return this.set('Link', link + Object.keys(links).map(function(rel) { + var newLinks = Object.keys(links).map(function(rel) { // Allow multiple links if links[rel] is an array if (Array.isArray(links[rel])) { return links[rel].map(function (singleLink) { @@ -107,7 +106,10 @@ res.links = function(links) { } else { return `<${links[rel]}>; rel="${rel}"`; } - }).join(', ')); + }).filter(Boolean).join(', '); + + if (link && newLinks) link += ', '; + return this.set('Link', link + newLinks); }; /** diff --git a/test/res.links.js b/test/res.links.js index 40665fd558a..cd4bdb02385 100644 --- a/test/res.links.js +++ b/test/res.links.js @@ -61,5 +61,38 @@ describe('res', function(){ .expect('Link', '; rel="next", ; rel="last", ; rel="last"') .expect(200, done); }) + + it('should skip empty-array rel values without producing trailing commas', function (done) { + var app = express(); + + app.use(function (req, res) { + res.links({ + next: 'http://api.example.com/users?page=2', + prev: [] + }); + + res.end(); + }); + + request(app) + .get('/') + .expect('Link', '; rel="next"') + .expect(200, done); + }) + + it('should not set a trailing comma when appending and new rel values are all empty arrays', function (done) { + var app = express(); + + app.use(function (req, res) { + res.links({ next: 'http://api.example.com/users?page=2' }); + res.links({ prev: [] }); + res.end(); + }); + + request(app) + .get('/') + .expect('Link', '; rel="next"') + .expect(200, done); + }) }) })