Skip to content

Commit 72ebc09

Browse files
committed
initial upload
1 parent 620a85e commit 72ebc09

File tree

4 files changed

+314
-1
lines changed

4 files changed

+314
-1
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules/

README.md

+52-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,55 @@
11
jquery-file-upload-middleware
22
=============================
33

4-
jQuery-File-Upload Express.js middleware
4+
jQuery-File-Upload Express.js middleware. Based on the server code of [jQuery-File-Upload](https://github.com/blueimp/jQuery-File-Upload)
5+
6+
Usage:
7+
8+
```javascript
9+
var express = require("express"),
10+
upload = require('jquery-file-upload-middleware');
11+
12+
var app = express();
13+
app.configure(function () {
14+
...
15+
app.use('/upload', upload({
16+
uploadDir: __dirname + '/public/uploads'
17+
tmpDir: '/tmp',
18+
maxPostSize: 11000000000, // 11 GB
19+
minFileSize: 1,
20+
maxFileSize: 10000000000, // 10 GB
21+
acceptFileTypes: /.+/i,
22+
// Files not matched by this regular expression force a download dialog,
23+
// to prevent executing any scripts in the context of the service domain:
24+
safeFileTypes: /\.(gif|jpe?g|png)$/i,
25+
imageTypes: /\.(gif|jpe?g|png)$/i,
26+
imageVersions: {
27+
'thumbnail': {
28+
width: 80,
29+
height: 80
30+
}
31+
},
32+
accessControl: {
33+
allowOrigin: '*',
34+
allowMethods: 'OPTIONS, HEAD, GET, POST, PUT, DELETE'
35+
}
36+
}));
37+
app.use(express.bodyParser());
38+
...
39+
});
40+
```
41+
42+
On the frontend:
43+
44+
```html
45+
<input id="fileupload" type="file" name="files[]" data-url="/upload" multiple>
46+
<script>$('#fileupload').fileupload({ dataType: 'json' })</script>
47+
```
48+
49+
## License
50+
Copyright (c) 2012 [Aleksandr Guidrevitch](http://aguidrevitch.blogspot.com/)
51+
Released under the [MIT license](http://www.opensource.org/licenses/MIT).
52+
53+
54+
55+

index.js

+247
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
module.exports = function (options) {
2+
3+
var path = require('path'),
4+
fs = require('fs'),
5+
formidable = require('formidable'),
6+
imageMagick = require('imagemagick'),
7+
_ = require('lodash'),
8+
// Since Node 0.8, .existsSync() moved from path to fs:
9+
_existsSync = fs.existsSync || path.existsSync,
10+
utf8encode = function (str) {
11+
return unescape(encodeURIComponent(str));
12+
},
13+
nameCountRegexp = /(?:(?: \(([\d]+)\))?(\.[^.]+))?$/,
14+
nameCountFunc = function (s, index, ext) {
15+
return ' (' + ((parseInt(index, 10) || 0) + 1) + ')' + (ext || '');
16+
};
17+
18+
options = _.extend({
19+
tmpDir: '/tmp',
20+
uploadDir: __dirname + '/public/files',
21+
uploadUrl: '/files/',
22+
maxPostSize: 11000000000, // 11 GB
23+
minFileSize: 1,
24+
maxFileSize: 10000000000, // 10 GB
25+
acceptFileTypes: /.+/i,
26+
// Files not matched by this regular expression force a download dialog,
27+
// to prevent executing any scripts in the context of the service domain:
28+
safeFileTypes: /\.(gif|jpe?g|png)$/i,
29+
imageTypes: /\.(gif|jpe?g|png)$/i,
30+
imageVersions: {
31+
/*
32+
thumbnail: {
33+
width: 80,
34+
height: 80
35+
}
36+
*/
37+
},
38+
accessControl: {
39+
allowOrigin: '*',
40+
allowMethods: 'OPTIONS, HEAD, GET, POST, PUT, DELETE'
41+
}
42+
}, options);
43+
44+
var FileInfo = function (file) {
45+
this.name = file.name;
46+
this.size = file.size;
47+
this.type = file.type;
48+
this.delete_type = 'DELETE';
49+
};
50+
51+
FileInfo.prototype.validate = function () {
52+
if (options.minFileSize && options.minFileSize > this.size) {
53+
this.error = 'File is too small';
54+
} else if (options.maxFileSize && options.maxFileSize < this.size) {
55+
this.error = 'File is too big';
56+
} else if (!options.acceptFileTypes.test(this.name)) {
57+
this.error = 'Filetype not allowed';
58+
}
59+
return !this.error;
60+
};
61+
62+
FileInfo.prototype.safeName = function () {
63+
// Prevent directory traversal and creating hidden system files:
64+
this.name = path.basename(this.name).replace(/^\.+/, '');
65+
// Prevent overwriting existing files:
66+
while (_existsSync(options.uploadDir + '/' + this.name)) {
67+
this.name = this.name.replace(nameCountRegexp, nameCountFunc);
68+
}
69+
};
70+
71+
FileInfo.prototype.initUrls = function (req) {
72+
if (!this.error) {
73+
var that = this,
74+
baseUrl = (options.ssl ? 'https:' : 'http:') +
75+
'//' + req.headers.host;
76+
this.delete_url = baseUrl + req.originalUrl + '/' +encodeURIComponent(this.name);
77+
this.url = baseUrl + options.uploadUrl + '/' +encodeURIComponent(this.name);
78+
Object.keys(options.imageVersions).forEach(function (version) {
79+
if (_existsSync(
80+
options.uploadDir + '/' + version + '/' + that.name
81+
)) {
82+
that[version + '_url'] = baseUrl + options.uploadUrl + '/' + version + '/' + encodeURIComponent(that.name);
83+
}
84+
});
85+
}
86+
};
87+
88+
var UploadHandler = function (req, res, callback) {
89+
this.req = req;
90+
this.res = res;
91+
this.callback = callback;
92+
};
93+
94+
UploadHandler.prototype.noCache = function () {
95+
this.res.set({
96+
'Pragma': 'no-cache',
97+
'Cache-Control': 'no-store, no-cache, must-revalidate',
98+
'Content-Disposition': 'inline; filename="files.json"'
99+
});
100+
};
101+
102+
UploadHandler.prototype.get = function () {
103+
var handler = this,
104+
files = [];
105+
handler.noCache();
106+
fs.readdir(options.uploadDir, function (err, list) {
107+
_.each(list, function (name) {
108+
var stats = fs.statSync(options.uploadDir + '/' + name),
109+
fileInfo;
110+
if (stats.isFile()) {
111+
fileInfo = new FileInfo({
112+
name: name,
113+
size: stats.size
114+
});
115+
fileInfo.initUrls(handler.req);
116+
files.push(fileInfo);
117+
}
118+
});
119+
handler.callback(files);
120+
});
121+
};
122+
123+
UploadHandler.prototype.post = function () {
124+
125+
var handler = this,
126+
form = new formidable.IncomingForm(),
127+
tmpFiles = [],
128+
files = [],
129+
map = {},
130+
counter = 1,
131+
redirect,
132+
finish = function () {
133+
if (!--counter) {
134+
files.forEach(function (fileInfo) {
135+
fileInfo.initUrls(handler.req);
136+
});
137+
handler.callback(files, redirect);
138+
}
139+
};
140+
141+
handler.noCache();
142+
143+
form.uploadDir = options.tmpDir;
144+
form
145+
.on('fileBegin', function (name, file) {
146+
tmpFiles.push(file.path);
147+
var fileInfo = new FileInfo(file, handler.req, true);
148+
fileInfo.safeName();
149+
map[path.basename(file.path)] = fileInfo;
150+
files.push(fileInfo);
151+
})
152+
.on('field', function (name, value) {
153+
if (name === 'redirect') {
154+
redirect = value;
155+
}
156+
})
157+
.on('file', function (name, file) {
158+
var fileInfo = map[path.basename(file.path)];
159+
if (_existsSync(file.path)) {
160+
fileInfo.size = file.size;
161+
if (!fileInfo.validate()) {
162+
fs.unlink(file.path);
163+
return;
164+
}
165+
fs.renameSync(file.path, options.uploadDir + '/' + fileInfo.name);
166+
if (options.imageTypes.test(fileInfo.name) && _.keys(options.imageVersions).length) {
167+
Object.keys(options.imageVersions).forEach(function (version) {
168+
if (!_existsSync(options.uploadDir + '/' + version + '/'))
169+
throw new Error(options.uploadDir + '/' + version + '/' + ' not exists');
170+
counter++;
171+
var opts = options.imageVersions[version];
172+
imageMagick.resize({
173+
width: opts.width,
174+
height: opts.height,
175+
srcPath: options.uploadDir + '/' + fileInfo.name,
176+
dstPath: options.uploadDir + '/' + version + '/' + fileInfo.name
177+
}, finish);
178+
});
179+
}
180+
}
181+
})
182+
.on('aborted', function () {
183+
tmpFiles.forEach(function (file) {
184+
fs.unlink(file);
185+
});
186+
})
187+
.on('error', function (e) {
188+
console.log(e);
189+
})
190+
.on('progress', function (bytesReceived, bytesExpected) {
191+
if (bytesReceived > options.maxPostSize)
192+
handler.req.connection.destroy();
193+
})
194+
.on('end', finish)
195+
.parse(handler.req);
196+
};
197+
198+
UploadHandler.prototype.destroy = function () {
199+
var handler = this,
200+
fileName = path.basename(decodeURIComponent(this.req.url));
201+
202+
fs.unlink(options.uploadDir + '/' + fileName, function (ex) {
203+
Object.keys(options.imageVersions).forEach(function (version) {
204+
fs.unlink(options.uploadDir + '/' + version + '/' + fileName);
205+
});
206+
handler.callback(!ex);
207+
});
208+
};
209+
210+
return function (req, res, next) {
211+
res.set({
212+
'Access-Control-Allow-Origin': options.accessControl.allowOrigin,
213+
'Access-Control-Allow-Methods': options.accessControl.allowMethods
214+
});
215+
var handler = new UploadHandler(req, res, function (result, redirect) {
216+
if (redirect) {
217+
res.redirect(redirect.replace(/%s/, encodeURIComponent(JSON.stringify(result))));
218+
} else {
219+
res.set({
220+
'Content-Type': req.headers.accept.indexOf('application/json') !== -1
221+
? 'application/json'
222+
: 'text/plain'
223+
});
224+
res.json(200, result);
225+
}
226+
});
227+
switch (req.method) {
228+
case 'OPTIONS':
229+
res.end();
230+
break;
231+
case 'HEAD':
232+
case 'GET':
233+
handler.get();
234+
break;
235+
case 'POST':
236+
handler.post();
237+
break;
238+
case 'DELETE':
239+
handler.destroy();
240+
break;
241+
default:
242+
res.send(405);
243+
}
244+
245+
}
246+
247+
};

package.json

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"name": "jquery-file-upload-middleware",
3+
"description": "http://aguidrevitch.blogspot.com",
4+
"version": "0.0.1",
5+
"dependencies": {
6+
"formidable": ">=1.0.11",
7+
"imagemagick": ">=0.1.2",
8+
"lodash": ">= 0.9.2"
9+
},
10+
"engines": {
11+
"node": ">= 0.8.8"
12+
},
13+
"main": "./index.js"
14+
}

0 commit comments

Comments
 (0)