initial commit
[namibia] / public / scripts / blueimp-jQuery-File-Upload / server / node / server.js
1 #!/usr/bin/env node
2 /*
3  * jQuery File Upload Plugin Node.js Example 1.0.1
4  * https://github.com/blueimp/jQuery-File-Upload
5  *
6  * Copyright 2012, Sebastian Tschan
7  * https://blueimp.net
8  *
9  * Licensed under the MIT license:
10  * http://www.opensource.org/licenses/MIT
11  */
12
13 /*jslint nomen: true, regexp: true */
14 /*global require, __dirname, unescape */
15
16 (function (port) {
17     'use strict';
18     var util = require('util'),
19         path = require('path'),
20         fs = require('fs'),
21         formidable = require('formidable'),
22         nodeStatic = require('node-static'),
23         imageMagick = require('imagemagick'),
24         options = {
25             tmpDir: __dirname + '/tmp',
26             publicDir: __dirname + '/public',
27             uploadDir: __dirname + '/public/files',
28             uploadUrl: '/files/',
29             maxPostSize: 500000000, // 500 MB
30             minFileSize: 1,
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,
37             imageVersions: {
38                 'thumbnail': {
39                     width: 80,
40                     height: 80
41                 }
42             },
43             accessControl: {
44                 allowOrigin: '*',
45                 allowMethods: 'OPTIONS, HEAD, GET, POST, PUT, DELETE'
46             },
47             /* Uncomment and edit this section to provide the service via HTTPS:
48             ssl: {
49                 key: fs.readFileSync('/Applications/XAMPP/etc/ssl.key/server.key'),
50                 cert: fs.readFileSync('/Applications/XAMPP/etc/ssl.crt/server.crt')
51             },
52             */
53             nodeStatic: {
54                 cache: 3600 // seconds to cache served files
55             }
56         },
57         utf8encode = function (str) {
58             return unescape(encodeURIComponent(str));
59         },
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 || '');
64         },
65         FileInfo = function (file) {
66             this.name = file.name;
67             this.size = file.size;
68             this.type = file.type;
69             this.delete_type = 'DELETE';
70         },
71         UploadHandler = function (req, res, callback) {
72             this.req = req;
73             this.res = res;
74             this.callback = callback;
75         },
76         serve = function (req, res) {
77             res.setHeader(
78                 'Access-Control-Allow-Origin',
79                 options.accessControl.allowOrigin
80             );
81             res.setHeader(
82                 'Access-Control-Allow-Methods',
83                 options.accessControl.allowMethods
84             );
85             var handleResult = function (result, redirect) {
86                     if (redirect) {
87                         res.writeHead(302, {
88                             'Location': redirect.replace(
89                                 /%s/,
90                                 encodeURIComponent(JSON.stringify(result))
91                             )
92                         });
93                         res.end();
94                     } else {
95                         res.writeHead(200, {
96                             'Content-Type': req.headers.accept
97                                 .indexOf('application/json') !== -1 ?
98                                         'application/json' : 'text/plain'
99                         });
100                         res.end(JSON.stringify(result));
101                     }
102                 },
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"');
107                 },
108                 handler = new UploadHandler(req, res, handleResult);
109             switch (req.method) {
110             case 'OPTIONS':
111                 res.end();
112                 break;
113             case 'HEAD':
114             case 'GET':
115                 if (req.url === '/') {
116                     setNoCacheHeaders();
117                     if (req.method === 'GET') {
118                         handler.get();
119                     } else {
120                         res.end();
121                     }
122                 } else {
123                     fileServer.serve(req, res);
124                 }
125                 break;
126             case 'POST':
127                 setNoCacheHeaders();
128                 handler.post();
129                 break;
130             case 'DELETE':
131                 handler.destroy();
132                 break;
133             default:
134                 res.statusCode = 405;
135                 res.end();
136             }
137         };
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:
141             res.setHeader(
142                 'Content-Disposition',
143                 'attachment; filename="' + utf8encode(path.basename(files[0])) + '"'
144             );
145         } else {
146             // Prevent Internet Explorer from MIME-sniffing the content-type:
147             res.setHeader('X-Content-Type-Options', 'nosniff');
148         }
149         nodeStatic.Server.prototype.respond
150             .call(this, pathname, status, _headers, files, stat, req, res, finish);
151     };
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';
159         }
160         return !this.error;
161     };
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);
168         }
169     };
170     FileInfo.prototype.initUrls = function (req) {
171         if (!this.error) {
172             var that = this,
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) {
177                 if (path.existsSync(
178                         options.uploadDir + '/' + version + '/' + that.name
179                     )) {
180                     that[version + '_url'] = baseUrl + version + '/' +
181                         encodeURIComponent(that.name);
182                 }
183             });
184         }
185     };
186     UploadHandler.prototype.get = function () {
187         var handler = this,
188             files = [];
189         fs.readdir(options.uploadDir, function (err, list) {
190             list.forEach(function (name) {
191                 var stats = fs.statSync(options.uploadDir + '/' + name),
192                     fileInfo;
193                 if (stats.isFile()) {
194                     fileInfo = new FileInfo({
195                         name: name,
196                         size: stats.size
197                     });
198                     fileInfo.initUrls(handler.req);
199                     files.push(fileInfo);
200                 }
201             });
202             handler.callback(files);
203         });
204     };
205     UploadHandler.prototype.post = function () {
206         var handler = this,
207             form = new formidable.IncomingForm(),
208             tmpFiles = [],
209             files = [],
210             map = {},
211             counter = 1,
212             redirect,
213             finish = function () {
214                 counter -= 1;
215                 if (!counter) {
216                     files.forEach(function (fileInfo) {
217                         fileInfo.initUrls(handler.req);
218                     });
219                     handler.callback(files, redirect);
220                 }
221             };
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);
226             fileInfo.safeName();
227             map[path.basename(file.path)] = fileInfo;
228             files.push(fileInfo);
229         }).on('field', function (name, value) {
230             if (name === 'redirect') {
231                 redirect = value;
232             }
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);
238                 return;
239             }
240             fs.renameSync(file.path, options.uploadDir + '/' + fileInfo.name);
241             if (options.imageTypes.test(fileInfo.name)) {
242                 Object.keys(options.imageVersions).forEach(function (version) {
243                     counter += 1;
244                     var opts = options.imageVersions[version];
245                     imageMagick.resize({
246                         width: opts.width,
247                         height: opts.height,
248                         srcPath: options.uploadDir + '/' + fileInfo.name,
249                         dstPath: options.uploadDir + '/' + version + '/' +
250                             fileInfo.name
251                     }, finish);
252                 });
253             }
254         }).on('aborted', function () {
255             tmpFiles.forEach(function (file) {
256                 fs.unlink(file);
257             });
258         }).on('progress', function (bytesReceived, bytesExpected) {
259             if (bytesReceived > options.maxPostSize) {
260                 handler.req.connection.destroy();
261             }
262         }).on('end', finish).parse(handler.req);
263     };
264     UploadHandler.prototype.destroy = function () {
265         var handler = this,
266             fileName;
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);
272                 });
273                 handler.callback(!ex);
274             });
275         } else {
276             handler.callback(false);
277         }
278     };
279     if (options.ssl) {
280         require('https').createServer(options.ssl, serve).listen(port);
281     } else {
282         require('http').createServer(serve).listen(port);
283     }
284 }(8080));