UNPKG

8.21 kBJavaScriptView Raw
1/*!
2Kettle wrapping for Multer Express Middleware
3
4Copyright 2017-2018 OCAD University
5
6Licensed under the New BSD license. You may not use this file except in
7compliance with this License.
8
9You may obtain a copy of the License at
10https://github.com/fluid-project/kettle/blob/master/LICENSE.txt
11*/
12
13"use strict";
14
15// Wraps the standard Express Multer middleware, for handling multipart/form-data
16// (primarily for file uploads)
17
18var fluid = require("infusion"),
19 kettle = fluid.registerNamespace("kettle");
20
21fluid.require("multer", require, "kettle.npm.multer");
22
23fluid.defaults("kettle.middleware.multer", {
24 gradeNames: "kettle.plainAsyncMiddleware",
25 // See https://github.com/expressjs/multer#multeropts;
26 // Also see the storage and fileFilter components for supplying
27 // configuration to Multer's storage and fileFilter options
28 middlewareOptions: {
29 // These are the default limits multer uses
30 // See https://github.com/expressjs/multer#limits
31 // Recommended that these should be configured on a per-handler basis
32 // to prevent possible DDOS attacks via form submission, or similar
33 limits: {
34 fieldNameSize: 100,
35 fieldSize: "1MB",
36 fields: Infinity,
37 fileSize: Infinity,
38 files: Infinity,
39 parts: Infinity,
40 headerPairs: 2000
41 }
42 },
43 components: {
44 storage: {
45 type: "kettle.middleware.multer.storage.memory"
46 },
47 fileFilter: {
48 type: "kettle.middleware.multer.filter.allowAll"
49 }
50 },
51 // Configures the expected form fields to be handled, and the multer method
52 // to handle them; see https://github.com/expressjs/multer#usage for more
53 // documentation
54 formFieldOptions: {
55 method: "single",
56 // Relevant only for "single" and "array" methods
57 fieldName: "file"
58 // Relevant only for "array" method
59 // maxCount: 10
60 // Relevant only for "fields" method
61 // fields: [
62 // {name: "avatar", maxCount: 1},
63 // {name: "gallery", maxCount: 8}
64 // ]
65 },
66 middleware: "@expand:kettle.middleware.multer.createMiddleware({that}, {that}.options.middlewareOptions, {that}.storage, {that}.options.formFieldOptions, {that}.fileFilter)"
67});
68
69kettle.middleware.multer.createMiddleware = function (that, middlewareOptions, storage, formFieldOptions, fileFilter) {
70
71 middlewareOptions.storage = storage.multerStorage();
72 middlewareOptions.fileFilter = fileFilter.multerFileFilterResolver;
73
74 var multer = kettle.npm.multer(middlewareOptions);
75
76 var multerMethod = formFieldOptions.method;
77
78 var multerFields = multerMethod === "fields" ? formFieldOptions.fields : formFieldOptions.fieldName;
79
80 return multerMethod === "array" ? multer[multerMethod](multerFields, formFieldOptions.maxCount) : multer[multerMethod](multerFields);
81};
82
83fluid.defaults("kettle.middleware.multer.filter", {
84 gradeNames: ["fluid.component"],
85 // This must resolve to a filter function in Multer's
86 // expected style - see https://github.com/expressjs/multer#filefilter
87 //
88 // See the mimeType filter for an example of using argument-shifting
89 // to supply configuration material while maintaining Multer's expected
90 // signature
91 invokers: {
92 multerFileFilterResolver: {
93 funcName: "fluid.notImplemented"
94 }
95 }
96});
97
98fluid.defaults("kettle.middleware.multer.filter.allowAll", {
99 gradeNames: ["fluid.component"],
100 invokers: {
101 multerFileFilterResolver: {
102 funcName: "kettle.middleware.multer.filter.allowAll.filterFunc"
103 }
104 }
105});
106
107// Duplicate of Multer's internal allowAll function; Multer doesn't export it,
108// making it inaccessible to the kettle.npm.multer namespace
109
110kettle.middleware.multer.filter.allowAll.filterFunc = function (req, file, cb) {
111 cb(null, true);
112};
113
114fluid.defaults("kettle.middleware.multer.filter.mimeType", {
115 gradeNames: ["fluid.component"],
116 // an array of mimetypes to accept, FE:
117 // acceptedMimeTypes: ["image/gif", "image/jpg", "image/gif"],
118 acceptedMimeTypes: [],
119 invokers: {
120 multerFileFilterResolver: {
121 funcName: "kettle.middleware.multer.filter.mimeType.filterFunc",
122 args: ["{arguments}.0", "{arguments}.1", "{that}.options.acceptedMimeTypes", "{arguments}.2"]
123 }
124 }
125});
126
127// Filters by mimeType based on a list of accepted mime types
128kettle.middleware.multer.filter.mimeType.filterFunc = function (req, file, acceptedMimeTypes, cb) {
129 var isAcceptableMimeType = fluid.contains(acceptedMimeTypes, file.mimetype);
130 cb(null, isAcceptableMimeType);
131};
132
133// Abstract grade for storage
134fluid.defaults("kettle.middleware.multer.storage", {
135 gradeNames: ["fluid.component"],
136 // This must be one of the supported Multer storage functions (memoryStorage
137 // or diskStorage, unless using additional plug-ins for Multer); see the
138 // concrete grades below
139 invokers: {
140 multerStorage: {
141 funcName: "fluid.notImplemented"
142 }
143 }
144});
145
146// Storage grade using Multer's memoryStorage storage function - uploaded files
147// are only stored in memory for the duration of the request, although a handler
148// or other middleware can process them from request.req.file or
149// request.req.files
150fluid.defaults("kettle.middleware.multer.storage.memory", {
151 gradeNames: ["kettle.middleware.multer.storage"],
152 invokers: {
153 multerStorage: {
154 funcName: "kettle.npm.multer.memoryStorage",
155 args: []
156 }
157 }
158});
159
160// Storage grade using Multer's diskStorage storage function, with destination
161// and filename as configurable options
162//
163// This grade should be adequate for most common uses of disk storage - complex
164// uses should derive from this grade and replace the destination and filename
165// invokers as necessary to return appropriate functions in Multer's expected
166// style
167fluid.defaults("kettle.middleware.multer.storage.disk", {
168 gradeNames: ["kettle.middleware.multer.storage"],
169 // "destination" can be a plain string or a string using fluid.stringTemplate
170 // syntax - the template will receive the "file information" key-values
171 // described at https://github.com/expressjs/multer#file-information
172 destination: "./tests/data/uploads",
173 // "filename" should be a string using fluid.stringTemplate
174 // syntax - the template will receive the "file information" key-values
175 // described at https://github.com/expressjs/multer#file-information
176 //
177 // Note that simply using a plain string will result in every uploaded
178 // file overwriting the previous one - this is unlikely to be desired
179 // behaviour
180 filename: "%originalname",
181 invokers: {
182 // Invokers overriding the "destinationResolver" and "filenameResolver" functions should
183 // use argument-shifting as below to supply a function to Multer with its
184 // expected signature of (req, file, cb)
185 // See https://github.com/expressjs/multer#diskstorage
186 destinationResolver: {
187 funcName: "kettle.middleware.multer.storage.disk.destinationResolver",
188 args: ["{arguments}.0", "{arguments}.1", "{that}.options.destination", "{arguments}.2"]
189 },
190 filenameResolver: {
191 funcName: "kettle.middleware.multer.storage.disk.filenameResolver",
192 args: ["{arguments}.0", "{arguments}.1", "{that}.options.filename", "{arguments}.2"]
193 },
194 multerStorage: {
195 funcName: "kettle.npm.multer.diskStorage",
196 "args": [{
197 destination: "{that}.destinationResolver",
198 filename: "{that}.filenameResolver"
199 }]
200 }
201 }
202});
203
204kettle.middleware.multer.storage.disk.destinationResolver = function (req, file, destinationTemplate, cb) {
205 var destination = fluid.stringTemplate(destinationTemplate, file);
206 cb(null, destination);
207};
208
209kettle.middleware.multer.storage.disk.filenameResolver = function (req, file, filenameTemplate, cb) {
210 var filename = fluid.stringTemplate(filenameTemplate, file);
211 cb(null, filename);
212};