Skip to content

Commit a221f9f

Browse files
committed
1 parent 0588da3 commit a221f9f

2 files changed

Lines changed: 46 additions & 9 deletions

File tree

spec/ParseFile.spec.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1741,6 +1741,38 @@ describe('Parse.File testing', () => {
17411741
).toBeResolved();
17421742
});
17431743

1744+
it('default should block a malformed content type with invalid token characters', async () => {
1745+
await reconfigureServer({
1746+
fileUpload: {
1747+
enableForPublic: true,
1748+
},
1749+
});
1750+
const htmlContent = Buffer.from('<!DOCTYPE html><script>alert(1)</script>').toString(
1751+
'base64'
1752+
);
1753+
// Non-empty but malformed media types (extra slash, comma-separated values,
1754+
// whitespace) are not valid `type/subtype` tokens (RFC 9110 §5.6.2) and are
1755+
// sniffed by browsers, so they must be rejected too.
1756+
for (const contentType of ['image//svg+xml', 'text/plain,text/html', 'image/sv g']) {
1757+
await expectAsync(
1758+
request({
1759+
method: 'POST',
1760+
url: 'http://localhost:8378/1/files/note.foo',
1761+
body: JSON.stringify({
1762+
_ApplicationId: 'test',
1763+
_JavaScriptKey: 'test',
1764+
_ContentType: contentType,
1765+
base64: htmlContent,
1766+
}),
1767+
}).catch(e => {
1768+
throw new Error(e.data.error);
1769+
})
1770+
).toBeRejectedWith(
1771+
new Parse.Error(Parse.Error.FILE_SAVE_ERROR, 'Invalid Content-Type.')
1772+
);
1773+
}
1774+
});
1775+
17441776
it('works with a period in the file name', async () => {
17451777
await reconfigureServer({
17461778
fileUpload: {

src/Routers/FilesRouter.js

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -454,15 +454,20 @@ export class FilesRouter {
454454
const type = slashIndex > 0 ? contentType.slice(0, slashIndex).trim() : '';
455455
const subtype =
456456
slashIndex > 0 ? contentType.slice(slashIndex + 1).split(';')[0].trim() : '';
457-
if (!type || !subtype) {
458-
// A Content-Type that does not parse as `type/subtype` with a non-empty
459-
// type AND subtype is malformed: there is no valid MIME type without a
460-
// subtype (RFC 9110 §8.3.1). Browsers cannot parse it and fall back to
461-
// MIME-sniffing the file body, which can render HTML/script markers as
462-
// active content on storage adapters that serve the stored Content-Type
463-
// (e.g. `image`, `image/`). Surface the precise blocklist message when
464-
// the bare token names a blocked extension (e.g. a no-slash `svg`),
465-
// otherwise reject the unparseable Content-Type.
457+
// A valid media type is `type/subtype` where both are non-empty `token`s
458+
// (RFC 9110 §5.6.2). Reject anything else.
459+
const token = /^[!#$%&'*+\-.^_`|~A-Za-z0-9]+$/;
460+
if (!token.test(type) || !token.test(subtype)) {
461+
// A Content-Type that does not parse as `type/subtype` with valid,
462+
// non-empty type AND subtype tokens is malformed: there is no valid MIME
463+
// type without a subtype (RFC 9110 §8.3.1), and malformed tokens such as
464+
// `image//svg+xml` or `text/plain,text/html` are equally unparseable.
465+
// Browsers cannot parse such values and fall back to MIME-sniffing the
466+
// file body, which can render HTML/script markers as active content on
467+
// storage adapters that serve the stored Content-Type (e.g. `image`,
468+
// `image/`). Surface the precise blocklist message when the bare token
469+
// names a blocked extension (e.g. a no-slash `svg`), otherwise reject the
470+
// unparseable Content-Type.
466471
const bareToken = (slashIndex < 0 ? contentType.split(';')[0] : type).replace(
467472
/\s+/g,
468473
''

0 commit comments

Comments
 (0)