Skip to content

Commit 0588da3

Browse files
committed
1 parent bbf7731 commit 0588da3

3 files changed

Lines changed: 48 additions & 10 deletions

File tree

spec/ParseFile.spec.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1716,6 +1716,31 @@ describe('Parse.File testing', () => {
17161716
).toBeResolved();
17171717
});
17181718

1719+
it('default should allow a valid custom content type the mime package does not recognize', async () => {
1720+
await reconfigureServer({
1721+
fileUpload: {
1722+
enableForPublic: true,
1723+
},
1724+
});
1725+
// A well-formed `type/subtype` that `mime` does not recognize (e.g. a
1726+
// vendor type) must still be accepted; only malformed or blocked
1727+
// Content-Types are rejected.
1728+
await expectAsync(
1729+
request({
1730+
method: 'POST',
1731+
url: 'http://localhost:8378/1/files/note.foo',
1732+
body: JSON.stringify({
1733+
_ApplicationId: 'test',
1734+
_JavaScriptKey: 'test',
1735+
_ContentType: 'application/vnd.api+json',
1736+
base64: Buffer.from('{}').toString('base64'),
1737+
}),
1738+
}).catch(e => {
1739+
throw new Error(e.data.error);
1740+
})
1741+
).toBeResolved();
1742+
});
1743+
17191744
it('works with a period in the file name', async () => {
17201745
await reconfigureServer({
17211746
fileUpload: {

spec/PurchaseValidation.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ function createProduct() {
66
{
77
base64: new Buffer('download_file', 'utf-8').toString('base64'),
88
},
9-
'text'
9+
'text/plain'
1010
);
1111
return file.save().then(function () {
1212
const product = new Parse.Object('_Product');

src/Routers/FilesRouter.js

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -445,26 +445,39 @@ export class FilesRouter {
445445

446446
// When the filename extension is not recognized by `mime`,
447447
// `FilesController.createFile` cannot derive a Content-Type from the
448-
// filename and preserves the client-supplied Content-Type verbatim. The
449-
// type the file is actually served as must therefore be validated against
450-
// the blocklist. A Content-Type that does not parse as `type/subtype` with
451-
// a non-empty type AND subtype (e.g. `image`, `image/`) is unparseable:
452-
// browsers ignore it and fall back to MIME-sniffing the file body, which
453-
// can render HTML/script markers as active content on storage adapters
454-
// that serve the stored Content-Type. Reject such malformed values rather
455-
// than store them verbatim, unless extension filtering is disabled (`*`).
448+
// filename and preserves the client-supplied Content-Type verbatim, so the
449+
// type the file is actually served as must be validated. Skip this when
450+
// extension filtering is disabled (`*`).
456451
const allowsAllExtensions = fileExtensions.includes('*');
457452
if (!isExtensionRecognized && contentType && !allowsAllExtensions) {
458453
const slashIndex = contentType.indexOf('/');
459454
const type = slashIndex > 0 ? contentType.slice(0, slashIndex).trim() : '';
460455
const subtype =
461456
slashIndex > 0 ? contentType.slice(slashIndex + 1).split(';')[0].trim() : '';
462457
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.
466+
const bareToken = (slashIndex < 0 ? contentType.split(';')[0] : type).replace(
467+
/\s+/g,
468+
''
469+
);
470+
if (bareToken && !isValidExtension(bareToken)) {
471+
rejectExtension(bareToken);
472+
return;
473+
}
463474
next(new Parse.Error(Parse.Error.FILE_SAVE_ERROR, 'Invalid Content-Type.'));
464475
return;
465476
}
466-
// Validate the Content-Type subtype against the blocklist, e.g.
477+
// Validate the well-formed Content-Type subtype against the blocklist, e.g.
467478
// "image/svg+xml" -> "svg+xml", "image/svg+xml;charset=utf-8" -> "svg+xml".
479+
// Valid custom/vendor types (e.g. "application/vnd.api+json") parse and are
480+
// allowed; only blocked subtypes are rejected.
468481
const contentTypeExtension = subtype.replace(/\s+/g, '');
469482
if (!isValidExtension(contentTypeExtension)) {
470483
rejectExtension(contentTypeExtension);

0 commit comments

Comments
 (0)