@fastify/multipart
Advanced tools
Comparing version 7.7.3 to 8.0.0
'use strict' | ||
const fastify = require('fastify')() | ||
const fs = require('fs') | ||
const util = require('util') | ||
const path = require('path') | ||
const { pipeline } = require('stream') | ||
const fs = require('node:fs') | ||
const util = require('node:util') | ||
const path = require('node:path') | ||
const { pipeline } = require('node:stream') | ||
const pump = util.promisify(pipeline) | ||
@@ -9,0 +9,0 @@ const form = path.join(__dirname, '..', 'form.html') |
220
index.js
'use strict' | ||
const Busboy = require('@fastify/busboy') | ||
const os = require('os') | ||
const os = require('node:os') | ||
const fp = require('fastify-plugin') | ||
const eos = require('end-of-stream') | ||
const { createWriteStream } = require('fs') | ||
const { unlink } = require('fs').promises | ||
const path = require('path') | ||
const { createWriteStream } = require('node:fs') | ||
const { unlink } = require('node:fs/promises') | ||
const path = require('node:path') | ||
const { generateId } = require('./lib/generateId') | ||
const util = require('util') | ||
const util = require('node:util') | ||
const createError = require('@fastify/error') | ||
const sendToWormhole = require('stream-wormhole') | ||
const deepmergeAll = require('@fastify/deepmerge')({ all: true }) | ||
const { PassThrough, pipeline, Readable } = require('stream') | ||
const { PassThrough, pipeline, Readable } = require('node:stream') | ||
const pump = util.promisify(pipeline) | ||
@@ -33,3 +32,2 @@ const secureJSON = require('secure-json-parse') | ||
function setMultipart (req, payload, done) { | ||
// nothing to do, it will be done by the Request.multipart object | ||
req.raw[kMultipart] = true | ||
@@ -39,64 +37,2 @@ done() | ||
function attachToBody (options, req, reply, next) { | ||
if (req.raw[kMultipart] !== true) { | ||
next() | ||
return | ||
} | ||
const consumerStream = options.onFile || defaultConsumer | ||
const body = {} | ||
const mp = req.multipart((field, file, filename, encoding, mimetype) => { | ||
body[field] = body[field] || [] | ||
body[field].push({ | ||
data: [], | ||
filename, | ||
encoding, | ||
mimetype, | ||
limit: false | ||
}) | ||
const result = consumerStream(field, file, filename, encoding, mimetype, body) | ||
if (result && typeof result.then === 'function') { | ||
result.catch((err) => { | ||
// continue with the workflow | ||
err.statusCode = 500 | ||
file.destroy(err) | ||
}) | ||
} | ||
}, function (err) { | ||
if (!err) { | ||
req.body = body | ||
} | ||
next(err) | ||
}, options) | ||
mp.on('field', (key, value) => { | ||
if (key === '__proto__' || key === 'constructor') { | ||
mp.destroy(new Error(`${key} is not allowed as field name`)) | ||
return | ||
} | ||
if (body[key] === undefined) { | ||
body[key] = value | ||
} else if (Array.isArray(body[key])) { | ||
body[key].push(value) | ||
} else { | ||
body[key] = [body[key], value] | ||
} | ||
}) | ||
} | ||
function defaultConsumer (field, file, filename, encoding, mimetype, body) { | ||
const fileData = [] | ||
const lastFile = body[field][body[field].length - 1] | ||
file.on('data', data => { if (!lastFile.limit) { fileData.push(data) } }) | ||
file.on('limit', () => { lastFile.limit = true }) | ||
file.on('end', () => { | ||
if (!lastFile.limit) { | ||
lastFile.data = Buffer.concat(fileData) | ||
} else { | ||
lastFile.data = undefined | ||
} | ||
}) | ||
} | ||
function busboy (options) { | ||
@@ -122,23 +58,5 @@ try { | ||
const attachFieldsToBody = options.attachFieldsToBody | ||
if (options.addToBody === true) { | ||
if (typeof options.sharedSchemaId === 'string') { | ||
fastify.addSchema({ | ||
$id: options.sharedSchemaId, | ||
type: 'object', | ||
properties: { | ||
encoding: { type: 'string' }, | ||
filename: { type: 'string' }, | ||
limit: { type: 'boolean' }, | ||
mimetype: { type: 'string' } | ||
} | ||
}) | ||
} | ||
fastify.addHook('preValidation', function (req, reply, next) { | ||
attachToBody(options, req, reply, next) | ||
}) | ||
} | ||
if (options.attachFieldsToBody === true || options.attachFieldsToBody === 'keyValues') { | ||
if (typeof options.sharedSchemaId === 'string') { | ||
if (attachFieldsToBody === true || attachFieldsToBody === 'keyValues') { | ||
if (typeof options.sharedSchemaId === 'string' && attachFieldsToBody === true) { | ||
fastify.addSchema({ | ||
@@ -155,2 +73,3 @@ $id: options.sharedSchemaId, | ||
} | ||
fastify.addHook('preValidation', async function (req, reply) { | ||
@@ -160,4 +79,6 @@ if (!req.isMultipart()) { | ||
} | ||
for await (const part of req.parts()) { | ||
req.body = part.fields | ||
if (part.file) { | ||
@@ -171,21 +92,37 @@ if (options.onFile) { | ||
} | ||
if (options.attachFieldsToBody === 'keyValues') { | ||
if (attachFieldsToBody === 'keyValues') { | ||
const body = {} | ||
if (req.body) { | ||
for (const key of Object.keys(req.body)) { | ||
const reqBodyKeys = Object.keys(req.body) | ||
for (let i = 0; i < reqBodyKeys.length; ++i) { | ||
const key = reqBodyKeys[i] | ||
const field = req.body[key] | ||
if (field.value !== undefined) { | ||
body[key] = field.value | ||
} else if (field._buf) { | ||
body[key] = field._buf | ||
} else if (Array.isArray(field)) { | ||
body[key] = field.map(item => { | ||
if (item._buf) { | ||
return item._buf.toString() | ||
const items = [] | ||
for (let i = 0; i < field.length; ++i) { | ||
const item = field[i] | ||
if (item.value !== undefined) { | ||
items.push(item.value) | ||
} else if (item._buf) { | ||
items.push(item._buf) | ||
} | ||
return item.value | ||
}) | ||
} else if (field._buf) { | ||
body[key] = field._buf.toString() | ||
} | ||
if (items.length) { | ||
body[key] = items | ||
} | ||
} | ||
} | ||
} | ||
req.body = body | ||
@@ -219,5 +156,2 @@ } | ||
// legacy | ||
fastify.decorateRequest('multipart', handleLegacyMultipartApi) | ||
// Stream mode | ||
@@ -236,78 +170,5 @@ fastify.decorateRequest('file', getMultipartFile) | ||
function isMultipart () { | ||
return this.raw[kMultipart] || false | ||
return this.raw[kMultipart] | ||
} | ||
// handler definition is in multipart-readstream | ||
// handler(field, file, filename, encoding, mimetype) | ||
// opts is a per-request override for the options object | ||
function handleLegacyMultipartApi (handler, done, opts) { | ||
if (typeof handler !== 'function') { | ||
throw new Error('handler must be a function') | ||
} | ||
if (typeof done !== 'function') { | ||
throw new Error('the callback must be a function') | ||
} | ||
if (!this.isMultipart()) { | ||
done(new Error('the request is not multipart')) | ||
return | ||
} | ||
const log = this.log | ||
log.warn('the multipart callback-based api is deprecated in favour of the new promise api') | ||
log.debug('starting multipart parsing') | ||
const req = this.raw | ||
const busboyOptions = deepmergeAll({ headers: req.headers }, options || {}, opts || {}) | ||
const stream = busboy(busboyOptions) | ||
let completed = false | ||
let files = 0 | ||
req.on('error', function (err) { | ||
stream.destroy() | ||
if (!completed) { | ||
completed = true | ||
done(err) | ||
} | ||
}) | ||
stream.on('finish', function () { | ||
log.debug('finished receiving stream, total %d files', files) | ||
if (!completed) { | ||
completed = true | ||
setImmediate(done) | ||
} | ||
}) | ||
stream.on('file', wrap) | ||
req.pipe(stream) | ||
.on('error', function (error) { | ||
req.emit('error', error) | ||
}) | ||
function wrap (field, file, filename, encoding, mimetype) { | ||
log.debug({ field, filename, encoding, mimetype }, 'parsing part') | ||
files++ | ||
eos(file, waitForFiles) | ||
if (field === '__proto__' || field === 'constructor') { | ||
file.destroy(new Error(`${field} is not allowed as field name`)) | ||
return | ||
} | ||
handler(field, file, filename, encoding, mimetype) | ||
} | ||
function waitForFiles (err) { | ||
if (err) { | ||
completed = true | ||
done(err) | ||
} | ||
} | ||
return stream | ||
} | ||
function handleMultipart (opts = {}) { | ||
@@ -571,3 +432,5 @@ if (!this.isMultipart()) { | ||
try { | ||
for (const field of Object.values(container)) { | ||
const fields = Array.isArray(container) ? container : Object.values(container) | ||
for (let i = 0; i < fields.length; ++i) { | ||
const field = fields[i] | ||
if (Array.isArray(field)) { | ||
@@ -597,3 +460,4 @@ for (const subField of filesFromFields.call(this, field)) { | ||
} | ||
for (const filepath of this.tmpUploads) { | ||
for (let i = 0; i < this.tmpUploads.length; ++i) { | ||
const filepath = this.tmpUploads[i] | ||
try { | ||
@@ -600,0 +464,0 @@ await unlink(filepath) |
{ | ||
"name": "@fastify/multipart", | ||
"version": "7.7.3", | ||
"version": "8.0.0", | ||
"description": "Multipart plugin for Fastify", | ||
"main": "index.js", | ||
"types": "index.d.ts", | ||
"types": "types/index.d.ts", | ||
"dependencies": { | ||
@@ -13,3 +13,2 @@ "@fastify/busboy": "^1.0.0", | ||
"@fastify/swagger-ui": "^1.8.0", | ||
"end-of-stream": "^1.4.4", | ||
"fastify-plugin": "^4.0.0", | ||
@@ -22,4 +21,4 @@ "secure-json-parse": "^2.4.0", | ||
"@types/node": "^20.1.0", | ||
"@typescript-eslint/eslint-plugin": "^5.30.7", | ||
"@typescript-eslint/parser": "^5.30.7", | ||
"@typescript-eslint/eslint-plugin": "^6.3.0", | ||
"@typescript-eslint/parser": "^6.3.0", | ||
"benchmark": "^2.1.4", | ||
@@ -29,3 +28,2 @@ "climem": "^1.0.3", | ||
"eslint": "^8.20.0", | ||
"eslint-config-standard-with-typescript": "^37.0.0", | ||
"eslint-plugin-import": "^2.26.0", | ||
@@ -43,13 +41,15 @@ "eslint-plugin-n": "^16.0.1", | ||
"tap": "^16.0.0", | ||
"tsd": "^0.28.0" | ||
"tsd": "^0.29.0" | ||
}, | ||
"scripts": { | ||
"coverage": "tap \"test/**/*.test.js\" --coverage-report=html", | ||
"coverage": "npm run test:unit -- --coverage-report=html", | ||
"climem": "climem 8999 localhost", | ||
"lint": "standard | snazzy", | ||
"lint:fix": "standard --fix", | ||
"lint": "npm run lint:javascript && npm run lint:typescript", | ||
"lint:javascript": "standard | snazzy", | ||
"lint:fix": "standard --fix && npm run lint:typescript -- --fix", | ||
"lint:typescript": "eslint -c .eslintrc.json types/**/*.d.ts types/**/*.test-d.ts", | ||
"start": "CLIMEM=8999 node -r climem ./examples/example", | ||
"test": "npm run lint && npm run unit && npm run typescript", | ||
"typescript": "tsd", | ||
"unit": "tap \"test/**/*.test.js\" -t 90" | ||
"test": "npm run test:unit && npm run test:typescript", | ||
"test:typescript": "tsd", | ||
"test:unit": "tap -t 90" | ||
}, | ||
@@ -56,0 +56,0 @@ "repository": { |
@@ -25,9 +25,7 @@ # @fastify/multipart | ||
If you are looking for the documentation for the legacy callback-api please see [here](./callback.md). | ||
```js | ||
const fastify = require('fastify')() | ||
const fs = require('fs') | ||
const util = require('util') | ||
const { pipeline } = require('stream') | ||
const fs = require('node:fs') | ||
const util = require('node:util') | ||
const { pipeline } = require('node:stream') | ||
const pump = util.promisify(pipeline) | ||
@@ -244,3 +242,3 @@ | ||
Request body key-value pairs can be assigned directly using `attachFieldsToBody: 'keyValues'`. Field values will be attached directly to the body object. By default, all files are converted to a string using `buffer.toString()` used as the value attached to the body. | ||
Request body key-value pairs can be assigned directly using `attachFieldsToBody: 'keyValues'`. Field values, including file buffers, will be attached to the body object. | ||
@@ -251,3 +249,3 @@ ```js | ||
fastify.post('/upload/files', async function (req, reply) { | ||
const uploadValue = req.body.upload // access file as string | ||
const uploadValue = req.body.upload // access file as buffer | ||
const fooValue = req.body.foo // other fields | ||
@@ -254,0 +252,0 @@ }) |
@@ -7,7 +7,7 @@ 'use strict' | ||
const multipart = require('..') | ||
const http = require('http') | ||
const http = require('node:http') | ||
const stream = require('readable-stream') | ||
const Readable = stream.Readable | ||
const pump = stream.pipeline | ||
const crypto = require('crypto') | ||
const crypto = require('node:crypto') | ||
const sendToWormhole = require('stream-wormhole') | ||
@@ -14,0 +14,0 @@ |
@@ -7,7 +7,7 @@ 'use strict' | ||
const multipart = require('..') | ||
const http = require('http') | ||
const path = require('path') | ||
const fs = require('fs') | ||
const { access } = require('fs').promises | ||
const EventEmitter = require('events') | ||
const http = require('node:http') | ||
const path = require('node:path') | ||
const fs = require('node:fs') | ||
const { access } = require('node:fs').promises | ||
const EventEmitter = require('node:events') | ||
const { once } = EventEmitter | ||
@@ -14,0 +14,0 @@ |
@@ -6,7 +6,7 @@ 'use strict' | ||
const FormData = require('form-data') | ||
const http = require('http') | ||
const http = require('node:http') | ||
const multipart = require('..') | ||
const { once } = require('events') | ||
const fs = require('fs') | ||
const path = require('path') | ||
const { once } = require('node:events') | ||
const fs = require('node:fs') | ||
const path = require('node:path') | ||
@@ -13,0 +13,0 @@ const filePath = path.join(__dirname, '../README.md') |
@@ -7,7 +7,7 @@ 'use strict' | ||
const multipart = require('..') | ||
const http = require('http') | ||
const path = require('path') | ||
const fs = require('fs') | ||
const { once } = require('events') | ||
const { Readable } = require('stream') | ||
const http = require('node:http') | ||
const path = require('node:path') | ||
const fs = require('node:fs') | ||
const { once } = require('node:events') | ||
const { Readable } = require('node:stream') | ||
const pump = require('pump') | ||
@@ -125,2 +125,4 @@ const { writableNoopStream } = require('noop-stream') | ||
req.body.upload = req.body.upload.toString('utf8') | ||
t.same(Object.keys(req.body), ['upload', 'hello']) | ||
@@ -312,2 +314,5 @@ | ||
req.body.upload[0] = req.body.upload[0].toString('utf8') | ||
req.body.upload[1] = req.body.upload[1].toString('utf8') | ||
t.same(req.body, { | ||
@@ -440,1 +445,48 @@ upload: [original, original], | ||
}) | ||
test('should pass the buffer instead of converting to string', async function (t) { | ||
t.plan(7) | ||
const fastify = Fastify() | ||
t.teardown(fastify.close.bind(fastify)) | ||
fastify.register(multipart, { attachFieldsToBody: 'keyValues' }) | ||
const original = fs.readFileSync(filePath) | ||
fastify.post('/', async function (req, reply) { | ||
t.ok(req.isMultipart()) | ||
t.same(Object.keys(req.body), ['upload', 'hello']) | ||
t.ok(req.body.upload instanceof Buffer) | ||
t.ok(Buffer.compare(req.body.upload, original) === 0) | ||
t.equal(req.body.hello, 'world') | ||
reply.code(200).send() | ||
}) | ||
await fastify.listen({ port: 0 }) | ||
// request | ||
const form = new FormData() | ||
const opts = { | ||
protocol: 'http:', | ||
hostname: 'localhost', | ||
port: fastify.server.address().port, | ||
path: '/', | ||
headers: form.getHeaders(), | ||
method: 'POST' | ||
} | ||
const req = http.request(opts) | ||
form.append('upload', fs.createReadStream(filePath)) | ||
form.append('hello', 'world') | ||
form.pipe(req) | ||
const [res] = await once(req, 'response') | ||
t.equal(res.statusCode, 200) | ||
res.resume() | ||
await once(res, 'end') | ||
t.pass('res ended successfully') | ||
}) |
@@ -7,7 +7,7 @@ 'use strict' | ||
const multipart = require('..') | ||
const http = require('http') | ||
const crypto = require('crypto') | ||
const http = require('node:http') | ||
const crypto = require('node:crypto') | ||
const { Readable } = require('readable-stream') | ||
const sendToWormhole = require('stream-wormhole') | ||
const EventEmitter = require('events') | ||
const EventEmitter = require('node:events') | ||
const { once } = EventEmitter | ||
@@ -14,0 +14,0 @@ |
@@ -7,5 +7,5 @@ 'use strict' | ||
const multipart = require('..') | ||
const http = require('http') | ||
const path = require('path') | ||
const fs = require('fs') | ||
const http = require('node:http') | ||
const path = require('node:path') | ||
const fs = require('node:fs') | ||
@@ -12,0 +12,0 @@ const filePath = path.join(__dirname, '../README.md') |
@@ -7,5 +7,5 @@ 'use strict' | ||
const multipart = require('..') | ||
const http = require('http') | ||
const path = require('path') | ||
const fs = require('fs') | ||
const http = require('node:http') | ||
const path = require('node:path') | ||
const fs = require('node:fs') | ||
@@ -12,0 +12,0 @@ const filePath = path.join(__dirname, '../README.md') |
@@ -7,9 +7,9 @@ 'use strict' | ||
const multipart = require('..') | ||
const http = require('http') | ||
const crypto = require('crypto') | ||
const http = require('node:http') | ||
const crypto = require('node:crypto') | ||
const { Readable } = require('readable-stream') | ||
const path = require('path') | ||
const fs = require('fs') | ||
const { access } = require('fs').promises | ||
const EventEmitter = require('events') | ||
const path = require('node:path') | ||
const fs = require('node:fs') | ||
const { access } = require('node:fs').promises | ||
const EventEmitter = require('node:events') | ||
const { once } = EventEmitter | ||
@@ -16,0 +16,0 @@ |
@@ -7,6 +7,6 @@ 'use strict' | ||
const multipart = require('..') | ||
const http = require('http') | ||
const path = require('path') | ||
const fs = require('fs') | ||
const EventEmitter = require('events') | ||
const http = require('node:http') | ||
const path = require('node:path') | ||
const fs = require('node:fs') | ||
const EventEmitter = require('node:events') | ||
const { once } = EventEmitter | ||
@@ -13,0 +13,0 @@ |
@@ -7,4 +7,4 @@ 'use strict' | ||
const multipart = require('..') | ||
const http = require('http') | ||
const { once } = require('events') | ||
const http = require('node:http') | ||
const { once } = require('node:events') | ||
@@ -11,0 +11,0 @@ test('should not break with a empty request body when attachFieldsToBody is true', async function (t) { |
'use strict' | ||
const fs = require('fs') | ||
const crypto = require('crypto') | ||
const fs = require('node:fs') | ||
const crypto = require('node:crypto') | ||
const test = require('tap').test | ||
@@ -9,4 +9,4 @@ const FormData = require('form-data') | ||
const multipart = require('..') | ||
const http = require('http') | ||
const EventEmitter = require('events') | ||
const http = require('node:http') | ||
const EventEmitter = require('node:events') | ||
const { once } = EventEmitter | ||
@@ -13,0 +13,0 @@ |
@@ -8,4 +8,4 @@ 'use strict' | ||
const h2url = require('h2url') | ||
const path = require('path') | ||
const fs = require('fs') | ||
const path = require('node:path') | ||
const fs = require('node:fs') | ||
const sendToWormhole = require('stream-wormhole') | ||
@@ -12,0 +12,0 @@ |
'use strict' | ||
const util = require('util') | ||
const util = require('node:util') | ||
const test = require('tap').test | ||
@@ -8,6 +8,6 @@ const FormData = require('form-data') | ||
const multipart = require('..') | ||
const http = require('http') | ||
const http = require('node:http') | ||
const sleep = util.promisify(setTimeout) | ||
const { writableNoopStream } = require('noop-stream') | ||
const stream = require('stream') | ||
const stream = require('node:stream') | ||
const pipeline = util.promisify(stream.pipeline) | ||
@@ -14,0 +14,0 @@ |
@@ -7,3 +7,3 @@ 'use strict' | ||
const multipart = require('..') | ||
const http = require('http') | ||
const http = require('node:http') | ||
@@ -10,0 +10,0 @@ test('should parse JSON fields forms if content-type is set', function (t) { |
@@ -7,7 +7,7 @@ 'use strict' | ||
const multipart = require('..') | ||
const http = require('http') | ||
const path = require('path') | ||
const fs = require('fs') | ||
const crypto = require('crypto') | ||
const EventEmitter = require('events') | ||
const http = require('node:http') | ||
const path = require('node:path') | ||
const fs = require('node:fs') | ||
const crypto = require('node:crypto') | ||
const EventEmitter = require('node:events') | ||
const { once } = EventEmitter | ||
@@ -14,0 +14,0 @@ |
@@ -7,6 +7,6 @@ 'use strict' | ||
const multipart = require('..') | ||
const http = require('http') | ||
const path = require('path') | ||
const fs = require('fs') | ||
const EventEmitter = require('events') | ||
const http = require('node:http') | ||
const path = require('node:path') | ||
const fs = require('node:fs') | ||
const EventEmitter = require('node:events') | ||
const sendToWormhole = require('stream-wormhole') | ||
@@ -13,0 +13,0 @@ const { once } = EventEmitter |
'use strict' | ||
const util = require('util') | ||
const util = require('node:util') | ||
const test = require('tap').test | ||
@@ -8,8 +8,8 @@ const FormData = require('form-data') | ||
const multipart = require('..') | ||
const http = require('http') | ||
const path = require('path') | ||
const fs = require('fs') | ||
const http = require('node:http') | ||
const path = require('node:path') | ||
const fs = require('node:fs') | ||
const concat = require('concat-stream') | ||
const stream = require('stream') | ||
const { once } = require('events') | ||
const stream = require('node:stream') | ||
const { once } = require('node:events') | ||
const pump = util.promisify(stream.pipeline) | ||
@@ -16,0 +16,0 @@ const sendToWormhole = require('stream-wormhole') |
'use strict' | ||
const http = require('http') | ||
const Readable = require('stream').Readable | ||
const http = require('node:http') | ||
const Readable = require('node:stream').Readable | ||
const FormData = require('form-data') | ||
@@ -6,0 +6,0 @@ const pump = require('pump') |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
8
21
1
0
142276
40
4018
527
- Removedend-of-stream@^1.4.4
- Removedend-of-stream@1.4.4(transitive)