From 212f5540daa1fd8ac98eca3188c7f247f92bb514 Mon Sep 17 00:00:00 2001 From: chamidu_sumanasekara Date: Mon, 17 Feb 2025 18:18:24 +0530 Subject: [PATCH] fix by sasva --- README.md | 15 +++++++-------- lib/fileFactory.js | 17 ++++++++++------- lib/index.js | 19 +++++++++++++++---- lib/processMultipart.js | 11 +++++++++-- lib/utilities.js | Bin 11060 -> 11457 bytes test/multipartUploads.spec.js | 2 +- 6 files changed, 42 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 66ddfe4..7d5b378 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Simple express middleware for uploading files. [![Coverage Status](https://img.shields.io/coveralls/richardgirges/express-fileupload.svg)](https://coveralls.io/r/richardgirges/express-fileupload) # Help us Improve express-fileupload -This package is still very much supported and maintained. But the more help the better. If you're interested any of the following: +This package is still very much supported and maintained. But the more help the better. If you're interested in any of the following: * Ticket and PR triage * Feature scoping and implementation * Maintenance (upgrading packages, fixing security vulnerabilities, etc) @@ -53,7 +53,6 @@ The **req.files.foo** object will contain the following: * From 1.1.1 until 1.5.1, `md5` is reverted back to MD5 checksum value and also added full MD5 support in case you are using temporary files. * From 1.5.1 onward, `md5` still holds the checksum value, but the checksum is generated with the provided `hashAlgorithm` option. The property name remains `md5` for backwards compatibility. - ### Examples * [Example Project](https://github.com/richardgirges/express-fileupload/tree/master/example) * [Basic File Upload](https://github.com/richardgirges/express-fileupload/tree/master/example#basic-file-upload) @@ -72,7 +71,7 @@ app.use(fileUpload({ Use temp files instead of memory for managing the upload process. ```javascript -// Note that this option available for versions 1.0.0 and newer. +// Note that this option is available for versions 1.0.0 and newer. app.use(fileUpload({ useTempFiles : true, tempFileDir : '/tmp/' @@ -81,8 +80,8 @@ app.use(fileUpload({ ### Using debug option -You can set `debug` option to `true` to see some logging about upload process. -In this case middleware uses `console.log` and adds `Express-file-upload` prefix for outputs. +You can set `debug` option to `true` to see some logging about the upload process. +In this case, middleware uses `console.log` and adds `Express-file-upload` prefix for outputs. You can set a custom logger having `.log()` method to the `logger` option. It will show you whether the request is invalid and also common events triggered during upload. @@ -105,7 +104,7 @@ Express-file-upload: Cleaning up temporary file /node/express-fileupload/test/te * `New upload started testFile->car.png` says that new upload started with field `testFile` and file name `car.png`. * `Uploading testFile->car.png, bytes:21232...` shows current progress for each new data chunk. * `Upload timeout` means that no data came during `uploadTimeout`. -* `Cleaning up temporary file` Here finaly we see cleaning up of the temporary file because of upload timeout reached. +* `Cleaning up temporary file` Here finally we see cleaning up of the temporary file because of upload timeout reached. ### Available Options Pass in non-Busboy options directly to the middleware. These are express-fileupload specific options. @@ -117,9 +116,9 @@ uriDecodeFileNames | | Strips characters from the upload's filename. You can use custom regex to determine what to strip. If set to `true`, non-alphanumeric characters _except_ dashes and underscores will be stripped. This option is off by default.

**Example #1 (strip slashes from file names):** `app.use(fileUpload({ safeFileNames: /\\/g }))`
**Example #2:** `app.use(fileUpload({ safeFileNames: true }))` preserveExtension | | Preserves filename extension when using safeFileNames option. If set to true, will default to an extension length of 3. If set to *Number*, this will be the max allowable extension length. If an extension is smaller than the extension length, it remains untouched. If the extension is longer, it is shifted.

**Example #1 (true):**
app.use(fileUpload({ safeFileNames: true, preserveExtension: true }));
*myFileName.ext* --> *myFileName.ext*

**Example #2 (max extension length 2, extension shifted):**
app.use(fileUpload({ safeFileNames: true, preserveExtension: 2 }));
*myFileName.ext* --> *myFileNamee.xt* abortOnLimit | | Returns a HTTP 413 when the file is bigger than the size limit if true. Otherwise, it will add a truncated = true to the resulting file structure. -responseOnLimit | | Response which will be send to client if file size limit exceeded when abortOnLimit set to true. +responseOnLimit | | Response which will be sent to client if file size limit exceeded when abortOnLimit set to true. limitHandler | | User defined limit handler which will be invoked if the file is bigger than configured limits. -useTempFiles | | By default this module uploads files into RAM. Setting this option to True turns on using temporary files instead of utilising RAM. This avoids memory overflow issues when uploading large files or in case of uploading lots of files at same time. +useTempFiles | | By default this module uploads files into RAM. Setting this option to True turns on using temporary files instead of utilizing RAM. This avoids memory overflow issues when uploading large files or in case of uploading lots of files at the same time. tempFileDir | | Path to store temporary files.
Used along with the useTempFiles option. By default this module uses 'tmp' folder in the current working directory.
You can use trailing slash, but it is not necessary. tempFilePermissions | | Permissions applied to temporary files.
Used along with the useTempFiles option. By default this module uses '644' permissions.
You should use this option if using shared hosting - to protect user data from being accessed by other users on the server. parseNested | | By default, req.body and req.files are flattened like this: {'name': 'John', 'hobbies[0]': 'Cinema', 'hobbies[1]': 'Bike'}

When this option is enabled they are parsed in order to be nested like this: {'name': 'John', 'hobbies': ['Cinema', 'Bike']} diff --git a/lib/fileFactory.js b/lib/fileFactory.js index 02b82df..24cc1a5 100644 --- a/lib/fileFactory.js +++ b/lib/fileFactory.js @@ -6,7 +6,8 @@ const { moveFile, promiseCallback, checkAndMakeDir, - saveBufferToFile + saveBufferToFile, + parseFileName } = require('./utilities'); /** @@ -18,8 +19,9 @@ const { * @returns {Function} */ const moveFromTemp = (filePath, options, fileUploadOptions) => (resolve, reject) => { - debugLog(fileUploadOptions, `Moving temporary file ${options.tempFilePath} to ${filePath}`); - moveFile(options.tempFilePath, filePath, promiseCallback(resolve, reject)); + const sanitizedFilePath = parseFileName(fileUploadOptions, filePath); + debugLog(fileUploadOptions, `Moving temporary file ${options.tempFilePath} to ${sanitizedFilePath}`); + moveFile(options.tempFilePath, sanitizedFilePath, promiseCallback(resolve, reject)); }; /** @@ -31,8 +33,9 @@ const moveFromTemp = (filePath, options, fileUploadOptions) => (resolve, reject) * @returns {Function} */ const moveFromBuffer = (filePath, options, fileUploadOptions) => (resolve, reject) => { - debugLog(fileUploadOptions, `Moving uploaded buffer to ${filePath}`); - saveBufferToFile(options.buffer, filePath, promiseCallback(resolve, reject)); + const sanitizedFilePath = parseFileName(fileUploadOptions, filePath); + debugLog(fileUploadOptions, `Moving uploaded buffer to ${sanitizedFilePath}`); + saveBufferToFile(options.buffer, sanitizedFilePath, promiseCallback(resolve, reject)); }; module.exports = (options, fileUploadOptions = {}) => { @@ -52,7 +55,7 @@ module.exports = (options, fileUploadOptions = {}) => { mimetype: options.mimetype, md5: options.hash, mv: (filePath, callback) => { - // Define a propper move function. + // Define a proper move function. const moveFunc = fileUploadOptions.useTempFiles ? moveFromTemp(filePath, options, fileUploadOptions) : moveFromBuffer(filePath, options, fileUploadOptions); @@ -62,4 +65,4 @@ module.exports = (options, fileUploadOptions = {}) => { return isFunc(callback) ? moveFunc(callback) : new Promise(moveFunc); } }; -}; +}; \ No newline at end of file diff --git a/lib/index.js b/lib/index.js index a08588b..f690937 100644 --- a/lib/index.js +++ b/lib/index.js @@ -4,7 +4,6 @@ const path = require('path'); const processMultipart = require('./processMultipart'); const isEligibleRequest = require('./isEligibleRequest'); const { buildOptions, debugLog } = require('./utilities'); -const busboy = require('busboy'); // eslint-disable-line no-unused-vars const DEFAULT_OPTIONS = { debug: false, @@ -22,7 +21,11 @@ const DEFAULT_OPTIONS = { useTempFiles: false, tempFileDir: path.join(process.cwd(), 'tmp'), tempFilePermissions: 0o644, - hashAlgorithm: 'md5' + hashAlgorithm: 'md5', + enableMimeTypeValidation: false, // New option for enabling MIME type validation + acceptableMimeTypes: [], // New option to specify acceptable MIME types + rejectPolyglotFiles: false, // New option to reject polyglot files + enablePdfSanitization: false // New option to enable PDF sanitization }; /** @@ -37,6 +40,14 @@ module.exports = (options) => { debugLog(uploadOptions, 'Request is not eligible for file upload!'); return next(); } - processMultipart(uploadOptions, req, res, next); + processMultipart(uploadOptions, req, res, (err) => { + if (err && err.message.includes('Polyglot file detected')) { + debugLog(uploadOptions, 'Polyglot file detected and rejected.'); + if (uploadOptions.rejectPolyglotFiles) { + return res.status(400).send('Polyglot files are not allowed.'); + } + } + next(err); + }); }; -}; +}; \ No newline at end of file diff --git a/lib/processMultipart.js b/lib/processMultipart.js index 5f3d24c..b662822 100644 --- a/lib/processMultipart.js +++ b/lib/processMultipart.js @@ -1,4 +1,5 @@ const Busboy = require('busboy'); +const mime = require('mime-types'); const UploadTimer = require('./uploadtimer'); const fileFactory = require('./fileFactory'); const memHandler = require('./memHandler'); @@ -60,8 +61,14 @@ module.exports = (options, req, res, next) => { // Build req.files fields busboy.on('file', (field, file, info) => { // Parse file name(cutting huge names, decoding, etc..). - const {filename:name, encoding, mimeType: mime} = info; + const {filename: name, encoding, mimeType: mime} = info; const filename = parseFileName(options, name); + // Validate MIME type + const expectedMimeType = mime.lookup(filename); + if (expectedMimeType !== mime) { + debugLog(options, `MIME type mismatch: expected ${expectedMimeType}, got ${mime}`); + return closeConnection(400, 'Invalid file type.'); + } // Define methods and handlers for upload process. const { dataHandler, @@ -182,4 +189,4 @@ module.exports = (options, req, res, next) => { }); req.pipe(busboy); -}; +}; \ No newline at end of file diff --git a/lib/utilities.js b/lib/utilities.js index ddafae85d9e953db05642b86d58f427a8de83066..0cc71de50f1fc5e4719c76ef422126cc81c52fd5 100644 GIT binary patch delta 2960 zcmai0O>7%g5XMQG|BaLQFF&^P65H9eliDGHCZ#k@nx8fyKMgGv{ZqQOU*c`;U3Yh# zwp0-yBn})<+XuZMBr1eZFNi}qP!3HItq?*&AcPPgey#{AaX>;KerDe8CJr3%VZ1wU z-n^M_zL~c-kKQ@fES3yP3{zJ!rnpC_7p^pQxP&WG7{qiBh@4c3+A9foey{uZ13RBS zd4A`q)ao9RN|7guS=ppnVroQDGx<`M5>3rtB;$uCW@bnxC+l*?q`D#0XEfC?$^7_} z%TrGtnOZnJv$(K4H??r!$U(A~4B^47$~Ei~*d7-FdBW8h7V7iVBql8sC$y3ZZoqN? zE$dT>gupJlM!@iPEWo~V`Qhoh+so=IeDD;mNL35Qbi0DYZZ_Vd#U0$$omz8 zL0BX@HA}imOuauwSaZd=G)26%Mx~1VX_rq}-E* z5=+&Lw&EJ1RfgD8HFFT>y&4hZ&ou{~+;`XZ1klpS*IbAzXrgUA?C4t?lTaH+ILi)(*H6RzjciK{+11kYG zTt5R|o~eHvzhC7KyZba}ycMJ(y z&XUFS8JaO2j#M`fg4swjGW%3BQskB9B=`PNa|+&;TYMvSq6C|CK~s0ix-MTND+fsWeC9gUYeVK1~`df&Hr*@rEH!0iYH z!dYGhVw*c{q@B|otgS9iwLS`7&bDGQ*WLb_JUt|vIqW09gY3Q5J+SEVL_yAb+S#bb z&7Y7v&wDWAcRX0H&plWHmsbKA_b!2a%8RQvyiq9p7GDwDNeaC}4I%?3d79*Bv%kDJ zdcwZ2bMlyPe~;6>xNAZyB8YiP))X0sCY1{Y?)9>pz7XvF)Hm$xZS?nnV%U#EO7pjM zA{^MCdP$u~LmQO$;%oknSzL!+Nl(c6Jnvq6GI1^F(sJf3-(zu{ODOIH#9&fjvz)a9 z64>AT=tQ84AG&39URS`>3R__5KtF``VgPe~E6~O(4Q+wpm7N1E#l8t3FaHQdBn%Rd zlG6ZMQ7KXhec~Io-WJ4crVVSc)`k;z8$9q10JEd*aR;d9+7G(XFH(}vCt%^1?O3>) zj^p+WC>v1g0z22S2&)?%ZNNHfXjokOp#x}ewR0Cg)6eEQ+nh6Uok*eAJK;iOcRS}% z4Ren9gUFT9U?<39LFCYdAeQmvV0V{Q4-2eG*_5GVYAK(0&}X0Vy}t(sz&;U@y3u~c zGEZu%Btj~hF7^}nwA0=Q04#D6LEesgxA!$kdJ*8YOw3EX==|lw1Y~3T{s-v zK1%k7F}>R{93@x7L;oHkhvAM_SDhj9Z5RV@>guyYvYQmiy&xB?TLTbU5(7->LZZCf zwaBNmsb9nn|EVkPcy&J^=_-ph*xcBY5t1pJ18OyO4 zcV`2!{i3o6|7vWj;#Puntg+Vvo6A00Mtb`>nQ;!}Xyz;*U|3@!>#L;#RZW3C+xr-7 z`nWgR3IP{1of0!At7P!uhha3j#6z&SB<=By3eGW%#fmWFOIr zrjaz{6*_H`2}a}(5<>r{gu^BjKMK-_&v6Vl;v+*&)e2g+1cQZMC~CTC;I{%|q8Rv< z)Q^u@nl7!*X{-HEqePTnF$HanAH>&r)0J7{kZ1jg(9^g%T~hMdX?_KAWB$Ha#Lq8- zD~hsiYzECWtJFLK-=CO>3W!-&x>isOigOxgGS_7g*l{&GFQ288iq3V0yhitzR#vEf bM7!FzgZFbuS0-sj%WgY`z1`rgLJ0l=S!d^W delta 2451 zcmai0TWlL;7FOI`CutHVPU1^!|B3C)*iP)w^ads&Y3lTrHf<>tw1sry{7Fo0j~S2C z(kKEXc%ke=^etB*F5+d;zHG&c7ho@1X(b-mu7m_a>;eyno1h3G1TP@Y|Iaw700~Q( z%b9ci&N<(of8O=i)9q@t!db;K^Epe+C>x#rjbv!!@Zd&Kt5Y_fN^JaVpy}Mjk-O8& z83gKBoq~zFJy@+{yOf+!s#xq)_UQ5X<0rE-hwfzKOtp$-a#P?Ivv{#QZB$DZH|fJ@ zMp4p<1pZsM7bmuR5On!4+3&#-R}XYox?W+4G@fw9@VYC6_g!bOd)tmag)x&`RkOq_ zv&u8dy5r>GZCPpj!M2;wS>IzDC+m0OeEk9ZseY_^Q}fgM5pwTCszJ52&NiHBurqn1 zA&ZY1`)o05JnV?g#tB?)Ol^Jou`!L2Chy6uWHfG?aW+1|)=0)!eu-%-dSxbW^2N9# zt0kV18taO@)szShSr49Tn%GWXsH|FL*PCWB5%G4IyizS#%qUxVLOF$8^Fce&tIdNw zWMX6QXpOVA^$fmf9)i2&W*lj`tw}hIXB7OgWk23)IqsI>trgR_$g-wsm>O|E?}n@WcARcMhgaGY_@G_KWXC}ou`3;uTP=LI zV@giUNT;8g-iv!W-6(g)eB_Ny+PPHN+T6muJZG)TF}m8h10Q$BptvV+$ekjGm)+^K ztj)8=DyT?4Jc zE#n2+tTjB_H5zup{3?2_?ca3WDr$5u39S=j_f?#r-tEy>|2e(4E1X-BDcc z*67cNQ4hljPZ|$-!g$d$f@>ZPKX^{ya8DYSd!m8bSP7{1b6QGXVLH`U;3;zK#XCKr zK?!47H}xW0o2iy^V&K+UPA?P|^xOrBr>}QkNbZc#oi@q>q5@~Dd0h|(*NYXojsN!r z<-NlnvD5vsHwkYa?R%ehj1Ul{U{*`h3pD?~u|m0pX$mS<$c8C{=lgoR68%~>(n|I$ zgKK?WeAbuTOA3_2qP{>y86{1n-84*f*p4JenVLIIDi&uK%{*Q0oN>%Mj5ROAvtDnn zj3RT}3K7rXEw74TI0Bz9ur-g9z7dpsXMR~S#{38DvLEyx6k9E5K@!Cp-u0)^6gbmq zhmnjXtAG`l8zA9keWhxlFf7S|G8Y>xIrH;C7%ly~DPqcwVD)G4m;OP#-G7t>Y#l>E z4RgT&E(F845)9(y;6T3&X4kr3w{!wscz4kIoD8}`qnHk9D1^dzEEL=$4K`B|sY_*f zU9ufXgmVR>!UJ?WtsQWa)k%tUGyRb*2=AH0)B2l!y^NcsPx8I zs;HMWjY_zIwM87;$F+gP-Hzg~JZ;K1lTzt;6Sm?fs6*c4&O_>6BTqA^+5ptn59bHz zSQ=cQ26_>R_=(|%?M9X(`|wKSHhde|fuZPO+!u}E$>?tUCmO&P(Ks{=NX-7| z&`$h)NKj}E>Kn*hj+{%~>XXR2p!&B?+H%B$S5+^rsX=_D9>L98)V*S{qG57ot?&}t zv1?ZfhIX8KHjbaPofwPH;Bq{Lm*Z)?7tdil;j`!S7Iyp8Om=?e&}?@8*woSN{K@Qr z?421{35LfLr}1TCmP*eg_wJNn7LCO!?K!_xHcYERBfUl?Z8OpLSSNXysOT?!r`Q@u+@*@6Ve;RMA)YzUtBvH%}Q(I>2zZOJU+X ItQQsj48_vG)c^nh diff --git a/test/multipartUploads.spec.js b/test/multipartUploads.spec.js index 67e9f68..e390660 100644 --- a/test/multipartUploads.spec.js +++ b/test/multipartUploads.spec.js @@ -472,4 +472,4 @@ describe('multipartUploads: Test Aborting/Canceling during upload', function() { }); }); }); -}); +}); \ No newline at end of file