123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424 |
- "use strict";
- const busboy = require("busboy");
- const { WriteStream } = require("fs-capacitor");
- const createError = require("http-errors");
- const objectPath = require("object-path");
- const GRAPHQL_MULTIPART_REQUEST_SPEC_URL = require("./GRAPHQL_MULTIPART_REQUEST_SPEC_URL.js");
- const ignoreStream = require("./ignoreStream.js");
- const Upload = require("./Upload.js");
- function processRequest(
- request,
- response,
- {
- maxFieldSize = 1000000, // 1 MB
- maxFileSize = Infinity,
- maxFiles = Infinity,
- } = {}
- ) {
- return new Promise((resolve, reject) => {
-
- let released;
-
- let exitError;
-
- let operations;
-
- let operationsPath;
-
- let map;
- const parser = busboy({
-
- headers: request.headers,
- limits: {
- fieldSize: maxFieldSize,
- fields: 2,
- fileSize: maxFileSize,
- files: maxFiles,
- },
- });
-
- function exit(error, isParserError = false) {
- if (exitError) return;
- exitError = error;
- if (map)
- for (const upload of map.values())
- if (!upload.file) upload.reject(exitError);
-
- isParserError ? parser.destroy() : parser.destroy(exitError);
- request.unpipe(parser);
-
-
-
-
- setImmediate(() => {
- request.resume();
- });
- reject(exitError);
- }
- parser.on("field", (fieldName, value, { valueTruncated }) => {
- if (valueTruncated)
- return exit(
- createError(
- 413,
- `The ‘${fieldName}’ multipart field value exceeds the ${maxFieldSize} byte size limit.`
- )
- );
- switch (fieldName) {
- case "operations":
- try {
- operations = JSON.parse(value);
- } catch (error) {
- return exit(
- createError(
- 400,
- `Invalid JSON in the ‘operations’ multipart field (${GRAPHQL_MULTIPART_REQUEST_SPEC_URL}).`
- )
- );
- }
-
-
- if (typeof operations !== "object" || !operations)
- return exit(
- createError(
- 400,
- `Invalid type for the ‘operations’ multipart field (${GRAPHQL_MULTIPART_REQUEST_SPEC_URL}).`
- )
- );
- operationsPath = objectPath(operations);
- break;
- case "map": {
- if (!operations)
- return exit(
- createError(
- 400,
- `Misordered multipart fields; ‘map’ should follow ‘operations’ (${GRAPHQL_MULTIPART_REQUEST_SPEC_URL}).`
- )
- );
- let parsedMap;
- try {
- parsedMap = JSON.parse(value);
- } catch (error) {
- return exit(
- createError(
- 400,
- `Invalid JSON in the ‘map’ multipart field (${GRAPHQL_MULTIPART_REQUEST_SPEC_URL}).`
- )
- );
- }
-
- if (
- typeof parsedMap !== "object" ||
- !parsedMap ||
- Array.isArray(parsedMap)
- )
- return exit(
- createError(
- 400,
- `Invalid type for the ‘map’ multipart field (${GRAPHQL_MULTIPART_REQUEST_SPEC_URL}).`
- )
- );
- const mapEntries = Object.entries(parsedMap);
-
-
- if (mapEntries.length > maxFiles)
- return exit(
- createError(413, `${maxFiles} max file uploads exceeded.`)
- );
- map = new Map();
- for (const [fieldName, paths] of mapEntries) {
- if (!Array.isArray(paths))
- return exit(
- createError(
- 400,
- `Invalid type for the ‘map’ multipart field entry key ‘${fieldName}’ array (${GRAPHQL_MULTIPART_REQUEST_SPEC_URL}).`
- )
- );
- map.set(fieldName, new Upload());
- for (const [index, path] of paths.entries()) {
- if (typeof path !== "string")
- return exit(
- createError(
- 400,
- `Invalid type for the ‘map’ multipart field entry key ‘${fieldName}’ array index ‘${index}’ value (${GRAPHQL_MULTIPART_REQUEST_SPEC_URL}).`
- )
- );
- try {
- operationsPath.set(path, map.get(fieldName));
- } catch (error) {
- return exit(
- createError(
- 400,
- `Invalid object path for the ‘map’ multipart field entry key ‘${fieldName}’ array index ‘${index}’ value ‘${path}’ (${GRAPHQL_MULTIPART_REQUEST_SPEC_URL}).`
- )
- );
- }
- }
- }
- resolve(operations);
- }
- }
- });
- parser.on(
- "file",
- (fieldName, stream, { filename, encoding, mimeType: mimetype }) => {
- if (!map) {
- ignoreStream(stream);
- return exit(
- createError(
- 400,
- `Misordered multipart fields; files should follow ‘map’ (${GRAPHQL_MULTIPART_REQUEST_SPEC_URL}).`
- )
- );
- }
- const upload = map.get(fieldName);
- if (!upload) {
-
-
- ignoreStream(stream);
- return;
- }
-
- let fileError;
- const capacitor = new WriteStream();
- capacitor.on("error", () => {
- stream.unpipe();
- stream.resume();
- });
- stream.on("limit", () => {
- fileError = createError(
- 413,
- `File truncated as it exceeds the ${maxFileSize} byte size limit.`
- );
- stream.unpipe();
- capacitor.destroy(fileError);
- });
- stream.on("error", (error) => {
- fileError = error;
- stream.unpipe();
- capacitor.destroy(fileError);
- });
-
- const file = {
- filename,
- mimetype,
- encoding,
- createReadStream(options) {
- const error = fileError || (released ? exitError : null);
- if (error) throw error;
- return capacitor.createReadStream(options);
- },
- capacitor,
- };
- Object.defineProperty(file, "capacitor", {
- enumerable: false,
- configurable: false,
- writable: false,
- });
- stream.pipe(capacitor);
- upload.resolve(file);
- }
- );
- parser.once("filesLimit", () =>
- exit(createError(413, `${maxFiles} max file uploads exceeded.`))
- );
- parser.once("finish", () => {
- request.unpipe(parser);
- request.resume();
- if (!operations)
- return exit(
- createError(
- 400,
- `Missing multipart field ‘operations’ (${GRAPHQL_MULTIPART_REQUEST_SPEC_URL}).`
- )
- );
- if (!map)
- return exit(
- createError(
- 400,
- `Missing multipart field ‘map’ (${GRAPHQL_MULTIPART_REQUEST_SPEC_URL}).`
- )
- );
- for (const upload of map.values())
- if (!upload.file)
- upload.reject(createError(400, "File missing in the request."));
- });
-
-
-
-
- parser.on("error", ( error) => {
- exit(error, true);
- });
- response.once("close", () => {
- released = true;
- if (map)
- for (const upload of map.values())
- if (upload.file)
-
- upload.file.capacitor.release();
- });
- request.once("close", () => {
- if (!request.readableEnded)
- exit(
- createError(
- 499,
- "Request disconnected during file upload stream parsing."
- )
- );
- });
- request.pipe(parser);
- });
- }
- module.exports = processRequest;
|