Details | Last modification | View Log | RSS feed
| Rev | Author | Line No. | Line |
|---|---|---|---|
| 1 | espaco | 1 | #!/usr/bin/env node |
| 2 | /* |
||
| 3 | * jQuery File Upload Plugin Node.js Example 2.1.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 | /* jshint nomen:false */ |
||
| 14 | /* global require, __dirname, unescape, console */ |
||
| 15 | |||
| 16 | (function (port) { |
||
| 17 | 'use strict'; |
||
| 18 | var path = require('path'), |
||
| 19 | fs = require('fs'), |
||
| 20 | // Since Node 0.8, .existsSync() moved from path to fs: |
||
| 21 | _existsSync = fs.existsSync || path.existsSync, |
||
| 22 | formidable = require('formidable'), |
||
| 23 | nodeStatic = require('node-static'), |
||
| 24 | imageMagick = require('imagemagick'), |
||
| 25 | options = { |
||
| 26 | tmpDir: __dirname + '/tmp', |
||
| 27 | publicDir: __dirname + '/public', |
||
| 28 | uploadDir: __dirname + '/public/files', |
||
| 29 | uploadUrl: '/files/', |
||
| 30 | maxPostSize: 11000000000, // 11 GB |
||
| 31 | minFileSize: 1, |
||
| 32 | maxFileSize: 10000000000, // 10 GB |
||
| 33 | acceptFileTypes: /.+/i, |
||
| 34 | // Files not matched by this regular expression force a download dialog, |
||
| 35 | // to prevent executing any scripts in the context of the service domain: |
||
| 36 | inlineFileTypes: /\.(gif|jpe?g|png)$/i, |
||
| 37 | imageTypes: /\.(gif|jpe?g|png)$/i, |
||
| 38 | imageVersions: { |
||
| 39 | 'thumbnail': { |
||
| 40 | width: 80, |
||
| 41 | height: 80 |
||
| 42 | } |
||
| 43 | }, |
||
| 44 | accessControl: { |
||
| 45 | allowOrigin: '*', |
||
| 46 | allowMethods: 'OPTIONS, HEAD, GET, POST, PUT, DELETE', |
||
| 47 | allowHeaders: 'Content-Type, Content-Range, Content-Disposition' |
||
| 48 | }, |
||
| 49 | /* Uncomment and edit this section to provide the service via HTTPS: |
||
| 50 | ssl: { |
||
| 51 | key: fs.readFileSync('/Applications/XAMPP/etc/ssl.key/server.key'), |
||
| 52 | cert: fs.readFileSync('/Applications/XAMPP/etc/ssl.crt/server.crt') |
||
| 53 | }, |
||
| 54 | */ |
||
| 55 | nodeStatic: { |
||
| 56 | cache: 3600 // seconds to cache served files |
||
| 57 | } |
||
| 58 | }, |
||
| 59 | utf8encode = function (str) { |
||
| 60 | return unescape(encodeURIComponent(str)); |
||
| 61 | }, |
||
| 62 | fileServer = new nodeStatic.Server(options.publicDir, options.nodeStatic), |
||
| 63 | nameCountRegexp = /(?:(?: \(([\d]+)\))?(\.[^.]+))?$/, |
||
| 64 | nameCountFunc = function (s, index, ext) { |
||
| 65 | return ' (' + ((parseInt(index, 10) || 0) + 1) + ')' + (ext || ''); |
||
| 66 | }, |
||
| 67 | FileInfo = function (file) { |
||
| 68 | this.name = file.name; |
||
| 69 | this.size = file.size; |
||
| 70 | this.type = file.type; |
||
| 71 | this.deleteType = 'DELETE'; |
||
| 72 | }, |
||
| 73 | UploadHandler = function (req, res, callback) { |
||
| 74 | this.req = req; |
||
| 75 | this.res = res; |
||
| 76 | this.callback = callback; |
||
| 77 | }, |
||
| 78 | serve = function (req, res) { |
||
| 79 | res.setHeader( |
||
| 80 | 'Access-Control-Allow-Origin', |
||
| 81 | options.accessControl.allowOrigin |
||
| 82 | ); |
||
| 83 | res.setHeader( |
||
| 84 | 'Access-Control-Allow-Methods', |
||
| 85 | options.accessControl.allowMethods |
||
| 86 | ); |
||
| 87 | res.setHeader( |
||
| 88 | 'Access-Control-Allow-Headers', |
||
| 89 | options.accessControl.allowHeaders |
||
| 90 | ); |
||
| 91 | var handleResult = function (result, redirect) { |
||
| 92 | if (redirect) { |
||
| 93 | res.writeHead(302, { |
||
| 94 | 'Location': redirect.replace( |
||
| 95 | /%s/, |
||
| 96 | encodeURIComponent(JSON.stringify(result)) |
||
| 97 | ) |
||
| 98 | }); |
||
| 99 | res.end(); |
||
| 100 | } else { |
||
| 101 | res.writeHead(200, { |
||
| 102 | 'Content-Type': req.headers.accept |
||
| 103 | .indexOf('application/json') !== -1 ? |
||
| 104 | 'application/json' : 'text/plain' |
||
| 105 | }); |
||
| 106 | res.end(JSON.stringify(result)); |
||
| 107 | } |
||
| 108 | }, |
||
| 109 | setNoCacheHeaders = function () { |
||
| 110 | res.setHeader('Pragma', 'no-cache'); |
||
| 111 | res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate'); |
||
| 112 | res.setHeader('Content-Disposition', 'inline; filename="files.json"'); |
||
| 113 | }, |
||
| 114 | handler = new UploadHandler(req, res, handleResult); |
||
| 115 | switch (req.method) { |
||
| 116 | case 'OPTIONS': |
||
| 117 | res.end(); |
||
| 118 | break; |
||
| 119 | case 'HEAD': |
||
| 120 | case 'GET': |
||
| 121 | if (req.url === '/') { |
||
| 122 | setNoCacheHeaders(); |
||
| 123 | if (req.method === 'GET') { |
||
| 124 | handler.get(); |
||
| 125 | } else { |
||
| 126 | res.end(); |
||
| 127 | } |
||
| 128 | } else { |
||
| 129 | fileServer.serve(req, res); |
||
| 130 | } |
||
| 131 | break; |
||
| 132 | case 'POST': |
||
| 133 | setNoCacheHeaders(); |
||
| 134 | handler.post(); |
||
| 135 | break; |
||
| 136 | case 'DELETE': |
||
| 137 | handler.destroy(); |
||
| 138 | break; |
||
| 139 | default: |
||
| 140 | res.statusCode = 405; |
||
| 141 | res.end(); |
||
| 142 | } |
||
| 143 | }; |
||
| 144 | fileServer.respond = function (pathname, status, _headers, files, stat, req, res, finish) { |
||
| 145 | // Prevent browsers from MIME-sniffing the content-type: |
||
| 146 | _headers['X-Content-Type-Options'] = 'nosniff'; |
||
| 147 | if (!options.inlineFileTypes.test(files[0])) { |
||
| 148 | // Force a download dialog for unsafe file extensions: |
||
| 149 | _headers['Content-Type'] = 'application/octet-stream'; |
||
| 150 | _headers['Content-Disposition'] = 'attachment; filename="' + |
||
| 151 | utf8encode(path.basename(files[0])) + '"'; |
||
| 152 | } |
||
| 153 | nodeStatic.Server.prototype.respond |
||
| 154 | .call(this, pathname, status, _headers, files, stat, req, res, finish); |
||
| 155 | }; |
||
| 156 | FileInfo.prototype.validate = function () { |
||
| 157 | if (options.minFileSize && options.minFileSize > this.size) { |
||
| 158 | this.error = 'File is too small'; |
||
| 159 | } else if (options.maxFileSize && options.maxFileSize < this.size) { |
||
| 160 | this.error = 'File is too big'; |
||
| 161 | } else if (!options.acceptFileTypes.test(this.name)) { |
||
| 162 | this.error = 'Filetype not allowed'; |
||
| 163 | } |
||
| 164 | return !this.error; |
||
| 165 | }; |
||
| 166 | FileInfo.prototype.safeName = function () { |
||
| 167 | // Prevent directory traversal and creating hidden system files: |
||
| 168 | this.name = path.basename(this.name).replace(/^\.+/, ''); |
||
| 169 | // Prevent overwriting existing files: |
||
| 170 | while (_existsSync(options.uploadDir + '/' + this.name)) { |
||
| 171 | this.name = this.name.replace(nameCountRegexp, nameCountFunc); |
||
| 172 | } |
||
| 173 | }; |
||
| 174 | FileInfo.prototype.initUrls = function (req) { |
||
| 175 | if (!this.error) { |
||
| 176 | var that = this, |
||
| 177 | baseUrl = (options.ssl ? 'https:' : 'http:') + |
||
| 178 | '//' + req.headers.host + options.uploadUrl; |
||
| 179 | this.url = this.deleteUrl = baseUrl + encodeURIComponent(this.name); |
||
| 180 | Object.keys(options.imageVersions).forEach(function (version) { |
||
| 181 | if (_existsSync( |
||
| 182 | options.uploadDir + '/' + version + '/' + that.name |
||
| 183 | )) { |
||
| 184 | that[version + 'Url'] = baseUrl + version + '/' + |
||
| 185 | encodeURIComponent(that.name); |
||
| 186 | } |
||
| 187 | }); |
||
| 188 | } |
||
| 189 | }; |
||
| 190 | UploadHandler.prototype.get = function () { |
||
| 191 | var handler = this, |
||
| 192 | files = []; |
||
| 193 | fs.readdir(options.uploadDir, function (err, list) { |
||
| 194 | list.forEach(function (name) { |
||
| 195 | var stats = fs.statSync(options.uploadDir + '/' + name), |
||
| 196 | fileInfo; |
||
| 197 | if (stats.isFile() && name[0] !== '.') { |
||
| 198 | fileInfo = new FileInfo({ |
||
| 199 | name: name, |
||
| 200 | size: stats.size |
||
| 201 | }); |
||
| 202 | fileInfo.initUrls(handler.req); |
||
| 203 | files.push(fileInfo); |
||
| 204 | } |
||
| 205 | }); |
||
| 206 | handler.callback({files: files}); |
||
| 207 | }); |
||
| 208 | }; |
||
| 209 | UploadHandler.prototype.post = function () { |
||
| 210 | var handler = this, |
||
| 211 | form = new formidable.IncomingForm(), |
||
| 212 | tmpFiles = [], |
||
| 213 | files = [], |
||
| 214 | map = {}, |
||
| 215 | counter = 1, |
||
| 216 | redirect, |
||
| 217 | finish = function () { |
||
| 218 | counter -= 1; |
||
| 219 | if (!counter) { |
||
| 220 | files.forEach(function (fileInfo) { |
||
| 221 | fileInfo.initUrls(handler.req); |
||
| 222 | }); |
||
| 223 | handler.callback({files: files}, redirect); |
||
| 224 | } |
||
| 225 | }; |
||
| 226 | form.uploadDir = options.tmpDir; |
||
| 227 | form.on('fileBegin', function (name, file) { |
||
| 228 | tmpFiles.push(file.path); |
||
| 229 | var fileInfo = new FileInfo(file, handler.req, true); |
||
| 230 | fileInfo.safeName(); |
||
| 231 | map[path.basename(file.path)] = fileInfo; |
||
| 232 | files.push(fileInfo); |
||
| 233 | }).on('field', function (name, value) { |
||
| 234 | if (name === 'redirect') { |
||
| 235 | redirect = value; |
||
| 236 | } |
||
| 237 | }).on('file', function (name, file) { |
||
| 238 | var fileInfo = map[path.basename(file.path)]; |
||
| 239 | fileInfo.size = file.size; |
||
| 240 | if (!fileInfo.validate()) { |
||
| 241 | fs.unlink(file.path); |
||
| 242 | return; |
||
| 243 | } |
||
| 244 | fs.renameSync(file.path, options.uploadDir + '/' + fileInfo.name); |
||
| 245 | if (options.imageTypes.test(fileInfo.name)) { |
||
| 246 | Object.keys(options.imageVersions).forEach(function (version) { |
||
| 247 | counter += 1; |
||
| 248 | var opts = options.imageVersions[version]; |
||
| 249 | imageMagick.resize({ |
||
| 250 | width: opts.width, |
||
| 251 | height: opts.height, |
||
| 252 | srcPath: options.uploadDir + '/' + fileInfo.name, |
||
| 253 | dstPath: options.uploadDir + '/' + version + '/' + |
||
| 254 | fileInfo.name |
||
| 255 | }, finish); |
||
| 256 | }); |
||
| 257 | } |
||
| 258 | }).on('aborted', function () { |
||
| 259 | tmpFiles.forEach(function (file) { |
||
| 260 | fs.unlink(file); |
||
| 261 | }); |
||
| 262 | }).on('error', function (e) { |
||
| 263 | console.log(e); |
||
| 264 | }).on('progress', function (bytesReceived) { |
||
| 265 | if (bytesReceived > options.maxPostSize) { |
||
| 266 | handler.req.connection.destroy(); |
||
| 267 | } |
||
| 268 | }).on('end', finish).parse(handler.req); |
||
| 269 | }; |
||
| 270 | UploadHandler.prototype.destroy = function () { |
||
| 271 | var handler = this, |
||
| 272 | fileName; |
||
| 273 | if (handler.req.url.slice(0, options.uploadUrl.length) === options.uploadUrl) { |
||
| 274 | fileName = path.basename(decodeURIComponent(handler.req.url)); |
||
| 275 | if (fileName[0] !== '.') { |
||
| 276 | fs.unlink(options.uploadDir + '/' + fileName, function (ex) { |
||
| 277 | Object.keys(options.imageVersions).forEach(function (version) { |
||
| 278 | fs.unlink(options.uploadDir + '/' + version + '/' + fileName); |
||
| 279 | }); |
||
| 280 | handler.callback({success: !ex}); |
||
| 281 | }); |
||
| 282 | return; |
||
| 283 | } |
||
| 284 | } |
||
| 285 | handler.callback({success: false}); |
||
| 286 | }; |
||
| 287 | if (options.ssl) { |
||
| 288 | require('https').createServer(options.ssl, serve).listen(port); |
||
| 289 | } else { |
||
| 290 | require('http').createServer(serve).listen(port); |
||
| 291 | } |
||
| 292 | }(8888)); |