mirror of
https://github.com/parse-community/parse-server
synced 2026-08-09 13:03:18 +00:00
fix: Cloud Function multipart requests bypass the maxUploadSize limit (#10498)
This commit is contained in:
@@ -363,4 +363,62 @@ describe('Cloud Code Multipart', () => {
|
||||
expect(result.status).toBe(200);
|
||||
expect(result.data.result.isMaster).toBe(false);
|
||||
});
|
||||
|
||||
it('should reject multipart request with many empty parts whose wire size exceeds maxUploadSize', async () => {
|
||||
await reconfigureServer({ maxUploadSize: '1kb' });
|
||||
|
||||
Parse.Cloud.define('multipartManyEmptyParts', req => {
|
||||
return { count: Object.keys(req.params).length };
|
||||
});
|
||||
|
||||
const boundary = '----TestBoundaryManyEmptyParts';
|
||||
const parts = [];
|
||||
for (let i = 0; i < 2000; i++) {
|
||||
parts.push({ name: `f${i}`, value: '' });
|
||||
}
|
||||
const body = buildMultipartBody(boundary, parts);
|
||||
// The wire body is far larger than maxUploadSize even though every field
|
||||
// value is empty, so the value/chunk byte counters alone never trip.
|
||||
expect(body.length).toBeGreaterThan(100 * 1024);
|
||||
|
||||
const result = await postMultipart(
|
||||
`http://localhost:8378/1/functions/multipartManyEmptyParts`,
|
||||
{
|
||||
'Content-Type': `multipart/form-data; boundary=${boundary}`,
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
},
|
||||
body
|
||||
);
|
||||
|
||||
expect(result.data.code).toBe(Parse.Error.OBJECT_TOO_LARGE);
|
||||
});
|
||||
|
||||
it('should reject multipart request whose Content-Length exceeds maxUploadSize', async () => {
|
||||
await reconfigureServer({ maxUploadSize: '1kb' });
|
||||
|
||||
Parse.Cloud.define('multipartContentLength', req => {
|
||||
return { count: Object.keys(req.params).length };
|
||||
});
|
||||
|
||||
const boundary = '----TestBoundaryContentLength';
|
||||
const parts = [];
|
||||
for (let i = 0; i < 2000; i++) {
|
||||
parts.push({ name: `f${i}`, value: '' });
|
||||
}
|
||||
const body = buildMultipartBody(boundary, parts);
|
||||
|
||||
const result = await postMultipart(
|
||||
`http://localhost:8378/1/functions/multipartContentLength`,
|
||||
{
|
||||
'Content-Type': `multipart/form-data; boundary=${boundary}`,
|
||||
'Content-Length': String(body.length),
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
},
|
||||
body
|
||||
);
|
||||
|
||||
expect(result.data.code).toBe(Parse.Error.OBJECT_TOO_LARGE);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -201,6 +201,16 @@ export class FunctionsRouter extends PromiseRouter {
|
||||
return Promise.resolve();
|
||||
}
|
||||
const maxBytes = Utils.parseSizeToBytes(req.config.maxUploadSize);
|
||||
// Reject early when the declared request size already exceeds the limit.
|
||||
const contentLength = Number(req.headers['content-length']);
|
||||
if (Number.isFinite(contentLength) && contentLength > maxBytes) {
|
||||
return Promise.reject(
|
||||
new Parse.Error(
|
||||
Parse.Error.OBJECT_TOO_LARGE,
|
||||
'Multipart request exceeds maximum upload size.'
|
||||
)
|
||||
);
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
const fields = Object.create(null);
|
||||
let totalBytes = 0;
|
||||
@@ -213,11 +223,12 @@ export class FunctionsRouter extends PromiseRouter {
|
||||
new Parse.Error(Parse.Error.INVALID_JSON, `Invalid multipart request: ${err.message}`)
|
||||
);
|
||||
}
|
||||
const safeReject = (err) => {
|
||||
const safeReject = err => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
req.unpipe(busboy);
|
||||
busboy.destroy();
|
||||
reject(err);
|
||||
};
|
||||
@@ -280,6 +291,23 @@ export class FunctionsRouter extends PromiseRouter {
|
||||
new Parse.Error(Parse.Error.INVALID_JSON, `Invalid multipart request: ${err.message}`)
|
||||
);
|
||||
});
|
||||
// Enforce `maxUploadSize` against the raw request bytes (multipart
|
||||
// boundaries, part headers, field names and part count included), not only
|
||||
// the parsed field values and file contents. This mirrors how
|
||||
// `express.json` bounds non-multipart bodies and stops a request composed
|
||||
// of many empty parts from exceeding the limit on the wire.
|
||||
let rawBytes = 0;
|
||||
req.on('data', chunk => {
|
||||
rawBytes += chunk.length;
|
||||
if (rawBytes > maxBytes) {
|
||||
safeReject(
|
||||
new Parse.Error(
|
||||
Parse.Error.OBJECT_TOO_LARGE,
|
||||
'Multipart request exceeds maximum upload size.'
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
req.pipe(busboy);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user