Skip to content

fix: prevent Content-Type header from being set to 'false'#7035

Open
veeceey wants to merge 2 commits into
expressjs:masterfrom
veeceey:fix/content-type-false-value
Open

fix: prevent Content-Type header from being set to 'false'#7035
veeceey wants to merge 2 commits into
expressjs:masterfrom
veeceey:fix/content-type-false-value

Conversation

@veeceey

@veeceey veeceey commented Feb 14, 2026

Copy link
Copy Markdown

Fixes #7034

When res.set('Content-Type', value) is called with a value that mime.contentType() can't resolve (returns false), the header ends up being set to the literal string "false".

This happens because mime.contentType() returns false for unrecognized types, and that false gets passed straight to setHeader(), which coerces it to a string.

// before this fix:
res.set('Content-Type', 'custom-type');
// => Content-Type: false

The fix is a one-liner — fall back to the original value when mime.contentType() returns false. This is consistent with how res.type() already handles the same case (it falls back to 'application/octet-stream').

// after:
res.set('Content-Type', 'custom-type');
// => Content-Type: custom-type

Added a test to cover this edge case. Full test suite passes (1247 tests).

When res.set('Content-Type', value) is called with a value that
mime.contentType() cannot resolve, it returns false. This false
value was being passed directly to setHeader(), resulting in the
Content-Type header being set to the literal string "false".

Fall back to the original value when mime.contentType() returns
false, consistent with how res.type() already handles this case.

Fixes expressjs#7034

@erdinccurebal erdinccurebal left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean fix. The core issue is that mime.contentType() returns false for unrecognized types, which then gets set as the literal string "false" via setHeader.

The || value fallback is a good approach — it preserves the developer's original value when the MIME lookup fails, which is backwards-compatible.

One edge case to consider: mime.contentType('') returns false, so res.set('Content-Type', '') would set an empty string header instead of "false". This is arguably better behavior, but worth noting.

Also see #7036 which tackles the same issue with a stricter approach (throws TypeError). Both are valid — this one is safer for a patch release since it doesn't change the function's contract from "always sets a value" to "may throw".

@GroophyLifefor GroophyLifefor left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, clean smart fix

Comment thread test/res.set.js
.get('/')
.expect('Content-Type', 'custom-type')
.expect(200, done);
})

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
})
})
it('should preserve Content-Type value when mime lookup fails even has slash', function (done) {
var app = express();
app.use(function (req, res) {
res.set('Content-Type', 'custom-type/subtype');
res.end();
});
request(app)
.get('/')
.expect('Content-Type', 'custom-type/subtype')
.expect(200, done);
})

Since MIME types are closely related to slash based formats, adding a test case that includes a slash helps catch potential production issues before release.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the string passed to mime.contentType() contains a /, then it is treated as a mime type and returned as is (ref).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, that makes my additional test is unnecessary, thank you.

@GroophyLifefor GroophyLifefor left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@veeceey

veeceey commented Mar 10, 2026

Copy link
Copy Markdown
Author

just a quick nudge — ready to make updates if anything comes up in review

@veeceey

veeceey commented Mar 15, 2026

Copy link
Copy Markdown
Author

@GroophyLifefor great suggestion on the additional test case with the slash-based custom type — I'll add that in. Makes sense to verify we don't accidentally mangle valid MIME types that already contain slashes.

@krzysdz

krzysdz commented Mar 15, 2026

Copy link
Copy Markdown
Contributor

@GroophyLifefor great suggestion on the additional test case with the slash-based custom type — I'll add that in. Makes sense to verify we don't accidentally mangle valid MIME types that already contain slashes.

FYI this case is already tested:

express/test/res.type.js

Lines 33 to 44 in 6c4249f

it('should set the Content-Type with type/subtype', function(done){
var app = express();
app.use(function(req, res){
res.type('application/vnd.amazon.ebook')
.end('var name = "tj";');
});
request(app)
.get('/')
.expect('Content-Type', 'application/vnd.amazon.ebook', done);
})

@veeceey

veeceey commented Mar 18, 2026

Copy link
Copy Markdown
Author

Oh nice, thanks for the pointer @krzysdz — didn't realize res.type already had that covered. No need for a duplicate then.

@krzysdz krzysdz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As found in #7132 in the current state this will not work correctly:

app.get("/", (req, res) => {
    res.set("Content-Type", "invalid-value");
    res.send("any string");
});

When a string is sent in response, and Content-Type has already been set, res.send() tries to set charset, which involves using contentType.parse(). content-type, as expected, handles only valid Content-Type header values and throws on invalid Media Types (RFC 9110).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

res.set('Content-Type') silently sets header to literal string 'false' for unknown types

5 participants