UNPKG

2.86 kBJavaScriptView Raw
1/*!
2 * Connect - multipart
3 * Copyright(c) 2010 Sencha Inc.
4 * Copyright(c) 2011 TJ Holowaychuk
5 * Copyright(c) 2013 Andrew Kelley
6 * MIT Licensed
7 */
8
9/**
10 * Module dependencies.
11 */
12
13var multiparty = require('multiparty');
14var qs = require('qs');
15var typeis = require('type-is');
16
17/**
18 * Multipart:
19 *
20 * Parse multipart/form-data request bodies,
21 * providing the parsed object as `req.body`
22 * and `req.files`.
23 *
24 * Configuration:
25 *
26 * The options passed are merged with [multiparty](https://github.com/andrewrk/node-multiparty)'s
27 * `Form` object, allowing you to configure the upload directory,
28 * size limits, etc. For example if you wish to change the upload dir do the following.
29 *
30 * app.use(connect.multipart({ uploadDir: path }));
31 *
32 * @param {Object} options
33 * @return {Function}
34 * @api public
35 */
36
37exports = module.exports = function(options){
38 options = options || {};
39
40 return function multipart(req, res, next) {
41 if (req._body) return next();
42 req.body = req.body || {};
43 req.files = req.files || {};
44
45 // ignore GET
46 if ('GET' === req.method || 'HEAD' === req.method) return next();
47
48 // check Content-Type
49 if (!typeis(req, 'multipart/form-data')) return next();
50
51 // flag as parsed
52 req._body = true;
53
54 // parse
55 var form = new multiparty.Form(options);
56 var data = {};
57 var files = {};
58 var waitend = true;
59 var done = false;
60
61 req.on('aborted', cleanup)
62 req.on('end', cleanup)
63 req.on('error', cleanup)
64
65 function cleanup() {
66 waitend = false;
67 req.removeListener('aborted', cleanup);
68 req.removeListener('end', cleanup);
69 req.removeListener('error', cleanup);
70 }
71
72 function ondata(name, val, data){
73 if (Array.isArray(data[name])) {
74 data[name].push(val);
75 } else if (data[name]) {
76 data[name] = [data[name], val];
77 } else {
78 data[name] = val;
79 }
80 }
81
82 form.on('field', function(name, val){
83 ondata(name, val, data);
84 });
85
86 form.on('file', function(name, val){
87 val.name = val.originalFilename;
88 val.type = val.headers['content-type'] || null;
89 ondata(name, val, files);
90 });
91
92 form.on('error', function(err){
93 if (done) return;
94
95 done = true;
96 err.status = 400;
97
98 process.nextTick(function(){
99 if (waitend && req.readable) {
100 // read off entire request
101 req.resume();
102 req.once('end', function onEnd() {
103 next(err)
104 });
105 return;
106 }
107
108 next(err);
109 });
110 });
111
112 form.on('close', function() {
113 if (done) return;
114
115 done = true;
116
117 try {
118 req.body = qs.parse(data);
119 req.files = qs.parse(files);
120 next();
121 } catch (err) {
122 err.status = 400;
123 next(err);
124 }
125 });
126
127 form.parse(req);
128 }
129};