3 * jQuery File Upload Plugin Node.js Example 1.0.1
4 * https://github.com/blueimp/jQuery-File-Upload
6 * Copyright 2012, Sebastian Tschan
9 * Licensed under the MIT license:
10 * http://www.opensource.org/licenses/MIT
13 /*jslint nomen: true, regexp: true */
14 /*global require, __dirname, unescape */
18 var util = require('util'),
19 path = require('path'),
21 formidable = require('formidable'),
22 nodeStatic = require('node-static'),
23 imageMagick = require('imagemagick'),
25 tmpDir: __dirname + '/tmp',
26 publicDir: __dirname + '/public',
27 uploadDir: __dirname + '/public/files',
29 maxPostSize: 500000000, // 500 MB
31 maxFileSize: 100000000, // 100 MB
32 acceptFileTypes: /.+/i,
33 // Files not matched by this regular expression force a download dialog,
34 // to prevent executing any scripts in the context of the service domain:
35 safeFileTypes: /\.(gif|jpe?g|png)$/i,
36 imageTypes: /\.(gif|jpe?g|png)$/i,
45 allowMethods: 'OPTIONS, HEAD, GET, POST, PUT, DELETE'
47 /* Uncomment and edit this section to provide the service via HTTPS:
49 key: fs.readFileSync('/Applications/XAMPP/etc/ssl.key/server.key'),
50 cert: fs.readFileSync('/Applications/XAMPP/etc/ssl.crt/server.crt')
54 cache: 3600 // seconds to cache served files
57 utf8encode = function (str) {
58 return unescape(encodeURIComponent(str));
60 fileServer = new nodeStatic.Server(options.publicDir, options.nodeStatic),
61 nameCountRegexp = /(?:(?: \(([\d]+)\))?(\.[^.]+))?$/,
62 nameCountFunc = function (s, index, ext) {
63 return ' (' + ((parseInt(index, 10) || 0) + 1) + ')' + (ext || '');
65 FileInfo = function (file) {
66 this.name = file.name;
67 this.size = file.size;
68 this.type = file.type;
69 this.delete_type = 'DELETE';
71 UploadHandler = function (req, res, callback) {
74 this.callback = callback;
76 serve = function (req, res) {
78 'Access-Control-Allow-Origin',
79 options.accessControl.allowOrigin
82 'Access-Control-Allow-Methods',
83 options.accessControl.allowMethods
85 var handleResult = function (result, redirect) {
88 'Location': redirect.replace(
90 encodeURIComponent(JSON.stringify(result))
96 'Content-Type': req.headers.accept
97 .indexOf('application/json') !== -1 ?
98 'application/json' : 'text/plain'
100 res.end(JSON.stringify(result));
103 setNoCacheHeaders = function () {
104 res.setHeader('Pragma', 'no-cache');
105 res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate');
106 res.setHeader('Content-Disposition', 'inline; filename="files.json"');
108 handler = new UploadHandler(req, res, handleResult);
109 switch (req.method) {
115 if (req.url === '/') {
117 if (req.method === 'GET') {
123 fileServer.serve(req, res);
134 res.statusCode = 405;
138 fileServer.respond = function (pathname, status, _headers, files, stat, req, res, finish) {
139 if (!options.safeFileTypes.test(files[0])) {
140 // Force a download dialog for unsafe file extensions:
142 'Content-Disposition',
143 'attachment; filename="' + utf8encode(path.basename(files[0])) + '"'
146 // Prevent Internet Explorer from MIME-sniffing the content-type:
147 res.setHeader('X-Content-Type-Options', 'nosniff');
149 nodeStatic.Server.prototype.respond
150 .call(this, pathname, status, _headers, files, stat, req, res, finish);
152 FileInfo.prototype.validate = function () {
153 if (options.minFileSize && options.minFileSize > this.size) {
154 this.error = 'minFileSize';
155 } else if (options.maxFileSize && options.maxFileSize < this.size) {
156 this.error = 'maxFileSize';
157 } else if (!options.acceptFileTypes.test(this.name)) {
158 this.error = 'acceptFileTypes';
162 FileInfo.prototype.safeName = function () {
163 // Prevent directory traversal and creating hidden system files:
164 this.name = path.basename(this.name).replace(/^\.+/, '');
165 // Prevent overwriting existing files:
166 while (path.existsSync(options.uploadDir + '/' + this.name)) {
167 this.name = this.name.replace(nameCountRegexp, nameCountFunc);
170 FileInfo.prototype.initUrls = function (req) {
173 baseUrl = (options.ssl ? 'https:' : 'http:') +
174 '//' + req.headers.host + options.uploadUrl;
175 this.url = this.delete_url = baseUrl + encodeURIComponent(this.name);
176 Object.keys(options.imageVersions).forEach(function (version) {
178 options.uploadDir + '/' + version + '/' + that.name
180 that[version + '_url'] = baseUrl + version + '/' +
181 encodeURIComponent(that.name);
186 UploadHandler.prototype.get = function () {
189 fs.readdir(options.uploadDir, function (err, list) {
190 list.forEach(function (name) {
191 var stats = fs.statSync(options.uploadDir + '/' + name),
193 if (stats.isFile()) {
194 fileInfo = new FileInfo({
198 fileInfo.initUrls(handler.req);
199 files.push(fileInfo);
202 handler.callback(files);
205 UploadHandler.prototype.post = function () {
207 form = new formidable.IncomingForm(),
213 finish = function () {
216 files.forEach(function (fileInfo) {
217 fileInfo.initUrls(handler.req);
219 handler.callback(files, redirect);
222 form.uploadDir = options.tmpDir;
223 form.on('fileBegin', function (name, file) {
224 tmpFiles.push(file.path);
225 var fileInfo = new FileInfo(file, handler.req, true);
227 map[path.basename(file.path)] = fileInfo;
228 files.push(fileInfo);
229 }).on('field', function (name, value) {
230 if (name === 'redirect') {
233 }).on('file', function (name, file) {
234 var fileInfo = map[path.basename(file.path)];
235 fileInfo.size = file.size;
236 if (!fileInfo.validate()) {
237 fs.unlink(file.path);
240 fs.renameSync(file.path, options.uploadDir + '/' + fileInfo.name);
241 if (options.imageTypes.test(fileInfo.name)) {
242 Object.keys(options.imageVersions).forEach(function (version) {
244 var opts = options.imageVersions[version];
248 srcPath: options.uploadDir + '/' + fileInfo.name,
249 dstPath: options.uploadDir + '/' + version + '/' +
254 }).on('aborted', function () {
255 tmpFiles.forEach(function (file) {
258 }).on('progress', function (bytesReceived, bytesExpected) {
259 if (bytesReceived > options.maxPostSize) {
260 handler.req.connection.destroy();
262 }).on('end', finish).parse(handler.req);
264 UploadHandler.prototype.destroy = function () {
267 if (handler.req.url.slice(0, options.uploadUrl.length) === options.uploadUrl) {
268 fileName = path.basename(decodeURIComponent(handler.req.url));
269 fs.unlink(options.uploadDir + '/' + fileName, function (ex) {
270 Object.keys(options.imageVersions).forEach(function (version) {
271 fs.unlink(options.uploadDir + '/' + version + '/' + fileName);
273 handler.callback(!ex);
276 handler.callback(false);
280 require('https').createServer(options.ssl, serve).listen(port);
282 require('http').createServer(serve).listen(port);