diff --git a/.gitignore b/.gitignore index 40b878d..0ca9404 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,25 @@ -node_modules/ \ No newline at end of file +### intellij ### +*.iml +*.ipr +*.iws +.idea/ + +### node ### +lib-cov +*.seed +*.log +*.csv +*.dat +*.out +*.pid +*.gz + +pids +logs +results + +npm-debug.log +node_modules + +### example ### +public/lib/ \ No newline at end of file diff --git a/History.md b/History.md index 18202c8..8a03709 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,82 @@ +0.1.7 / 2014-10-09 +================== + + * #58 improved support for IE9 + +0.1.6 / 2014-10-04 +================== + + * #56 avoid path traversal in DELETE requests + +0.1.5 / 2014-08-02 +================== + + * #31 request and response objects passed to event handlers + +0.1.4 / 2014-07-06 +================== + + * #53 documentation update + +0.1.3 / 2014-07-05 +================== + + * #46 merged in (reduce synchronous disk IO operations) + +0.1.2 / 2014-06-08 +================== + + * #31 merged in + +0.1.1 / 2014-02-04 +================== + + * #5 fixed + +0.1.0 / 2013-10-07 +================== + + * documentation cleanup + + +0.0.9 / 2013-10-07 +================== + + * compatibility with jQuery File Upload Plugin 5.32.6 + +0.0.8 / 2013-01-01 +================== + + * #11 update resize with cropping custom options : imageArgs + thanks to https://github.com/soomtong + +0.0.7 / 2012-12-25 +================== + + * 'delete' event merged in, + thanks to https://github.com/soomtong + +0.0.6 / 2012-12-20 +================== + + * #6 hostname option added + * upload.fileManager() added, which allows moving file + +0.0.5 / 2012-12-12 +================== + + * #4 fixed - orientation for iphone / ipad photos + +0.0.4 / 2012-12-02 +================== + + * Syntax changed + * middleware now provides 'begin','end','abort' events + * if file was renamed during upload to avoid conflict, + new field 'originalName' keeps original name + * dynamic uploadDir and uploadUrl + * upload.getFiles() added + 0.0.3 / 2012-11-25 ================== diff --git a/README.md b/README.md index 9debff1..204c7a3 100644 --- a/README.md +++ b/README.md @@ -16,15 +16,26 @@ Usage: upload = require('jquery-file-upload-middleware'); var app = express(); + + // configure upload middleware + upload.configure({ + uploadDir: __dirname + '/public/uploads', + uploadUrl: '/uploads', + imageVersions: { + thumbnail: { + width: 80, + height: 80 + } + } + }); + app.configure(function () { ... - app.use('/upload', upload({ - uploadDir: __dirname + '/public/uploads', - uploadUrl: '/uploads/' - })); + app.use('/upload', upload.fileHandler()); app.use(express.bodyParser()); ... }); + ``` On the frontend: @@ -34,17 +45,220 @@ On the frontend: ``` +To prevent access to /upload except for post (for security) +```javascript +upload.configure({ + uploadDir: __dirname + '/public/uploads/', + uploadUrl: '/uploads' +}); + +/// Redirect all to home except post +app.get('/upload', function( req, res ){ + res.redirect('/'); +}); + +app.put('/upload', function( req, res ){ + res.redirect('/'); +}); + +app.delete('/upload', function( req, res ){ + res.redirect('/'); +}); + +app.use('/upload', function(req, res, next){ + upload.fileHandler({ + uploadDir: function () { + return __dirname + '/public/uploads/' + }, + uploadUrl: function () { + return '/uploads' + } + })(req, res, next); +}); +``` + +Overriding global configuration + +```javascript + + app.use('/upload2', upload.fileHandler({ + uploadDir: __dirname + '/public/uploads2', + uploadUrl: '/uploads2', + imageVersions: { + thumbnail: { + width: 100, + height: 100 + } + } + })); + +``` + +More sophisticated example - Events + +```javascript + app.use('/upload', upload.fileHandler()); + + // events + upload.on('begin', function (fileInfo, req, res) { + // fileInfo structure is the same as returned to browser + // { + // name: '3 (3).jpg', + // originalName: '3.jpg', + // size: 79262, + // type: 'image/jpeg', + // delete_type: 'DELETE', + // delete_url: 'http://yourhost/upload/3%20(3).jpg', + // url: 'http://yourhost/uploads/3%20(3).jpg', + // thumbnail_url: 'http://youhost/uploads/thumbnail/3%20(3).jpg' + // } + }); + upload.on('abort', function (fileInfo, req, res) { ... }); + upload.on('end', function (fileInfo, req, res) { ... }); + upload.on('delete', function (fileInfo, req, res) { ... }); + upload.on('error', function (e, req, res) { + console.log(e.message); + }); +``` + +Dynamic upload directory and url, isolating user files: + +```javascript + upload.configure({ + imageVersions: { + thumbnail: { + width: 80, + height: 80 + } + } + }); + + app.use('/upload', function (req, res, next) { + // imageVersions are taken from upload.configure() + upload.fileHandler({ + uploadDir: function () { + return __dirname + '/public/uploads/' + req.sessionID + }, + uploadUrl: function () { + return '/uploads/' + req.sessionID + } + })(req, res, next); + }); +``` + +Moving uploaded files to different dir: + +```javascript + app.use('/api', function (req, res, next) { + req.filemanager = upload.fileManager(); + next(); + }); + + app.use('/api/endpoint', function (req, res, next) { + // your real /api handler that will actually move the file + ... + // req.filemanager.move(filename, path, function (err, result)) + req.filemanager.move('SomeFile.jpg', 'project1', function (err, result) { + // SomeFile.jpg gets moved from uploadDir/SomeFile.jpg to + // uploadDir/project1/SomeFile.jpg + // if path is relative (no leading slash), uploadUrl will + // be used to generate relevant urls, + // for absolute paths urls are not generated + if (!err) { + // result structure + // { + // filename: 'SomeFile.jpg', + // url: '/uploads/project1/SomeFile.jpg', + // thumbail_url : '/uploads/project1/thumbnail/SomeFile.jpg' + // } + ... + } else { + console.log(err); + } + }); + }); +``` + +Moving uploaded files out of uploadDir: + +``` + app.use('/api', function (req, res, next) { + var user = db.find(...); + + req.filemanager = upload.fileManager({ + targetDir: __dirname + '/public/u/' + user._id, + targetUrl: '/u/' + user._id, + }); + + // or + req.filemanager = upload.fileManager({ + targetDir: function () { + return __dirname + '/public/u/' + user._id + }, + targetUrl: function () { + return'/u/' + user._id + } + }); + ... + req.filemanager.move(req.body.filename, 'profile', function (err, result) { + // file gets moved to __dirname + '/public/u/' + user._id + '/profile' + if (!err) { + + } + }); + }); +``` + +Getting uploaded files mapped to their fs locations: + +```javascript + app.use('/list', function (req, res, next) { + upload.fileManager().getFiles(function (files) { + // { + // "00001.MTS": { + // "path": "/home/.../public/uploads/ekE6k4j9PyrGtcg+SA6a5za3/00001.MTS" + // }, + // "DSC00030.JPG": { + // "path": "/home/.../public/uploads/ekE6k4j9PyrGtcg+SA6a5za3/DSC00030.JPG", + // "thumbnail": "/home/.../public/uploads/ekE6k4j9PyrGtcg+SA6a5za3/thumbnail/DSC00030.JPG" + // } + // } + res.json(files); + }); + }); + + // with dynamic upload directories + + app.use('/list', function (req, res, next) { + upload.fileManager({ + uploadDir: function () { + return __dirname + '/public/uploads/' + req.sessionID + }, + uploadUrl: function () { + return '/uploads/' + req.sessionID + } + }).getFiles(function (files) { + res.json(files); + }); + }); +``` + Other options and their default values: + ```javascript { tmpDir: '/tmp', + uploadDir: __dirname + '/public/uploads', + uploadUrl: '/uploads', + targetDir: uploadDir, + targetUrl: uploadUrl, + ssl: false, + hostname: null, // in case your reverse proxy doesn't set Host header + // eg 'google.com' maxPostSize: 11000000000, // 11 GB minFileSize: 1, maxFileSize: 10000000000, // 10 GB acceptFileTypes: /.+/i, - // Files not matched by this regular expression force a download dialog, - // to prevent executing any scripts in the context of the service domain: - safeFileTypes: /\.(gif|jpe?g|png)$/i, imageTypes: /\.(gif|jpe?g|png)$/i, imageVersions: { thumbnail: { @@ -52,6 +266,7 @@ Other options and their default values: height: 80 } }, + imageArgs: ['-auto-orient'], accessControl: { allowOrigin: '*', allowMethods: 'OPTIONS, HEAD, GET, POST, PUT, DELETE' @@ -59,10 +274,14 @@ Other options and their default values: } ``` +## Contributors + + * [@soomtong](http://github.com/soomtong) + * [@gsarwohadi](https://github.com/gsarwohadi) + * [@peecky](https://github.com/peecky) + * [@tonyspiro](https://github.com/tonyspiro) + * [@derjust](https://github.com/derjust) + ## License Copyright (c) 2012 [Aleksandr Guidrevitch](http://aguidrevitch.blogspot.com/) Released under the [MIT license](http://www.opensource.org/licenses/MIT). - - - - diff --git a/examples/.bowerrc b/examples/.bowerrc new file mode 100644 index 0000000..0f29f6a --- /dev/null +++ b/examples/.bowerrc @@ -0,0 +1,4 @@ +{ + "directory": "public/lib", + "json": "bower.json" +} \ No newline at end of file diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..32c2010 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,15 @@ +## YET ANOTHER JQUERY FILE UPLOAD MIDDLEWARE EXAMPLES + +- use bower to install jquery-file-upload script (tested version 8.8.5) + +to run + +``` +project > npm insatll +project > cd examples +project/examples > npm insatll +project/examples > bower insatll +project/examples > node app.js +``` + +- to image resize : need imagemagick `http://www.imagemagick.org/script/binary-releases.php` \ No newline at end of file diff --git a/examples/app.js b/examples/app.js new file mode 100644 index 0000000..429526d --- /dev/null +++ b/examples/app.js @@ -0,0 +1,144 @@ +/* +* to run : +* node app.js +* */ + +/* +* dependencies +* */ +var express = require('express'), + http = require('http'), + upload = require('../'); + +var swig = require('swig'); + + +// configuration +var resizeConf = require('./config').resizeVersion; +var dirs = require('./config').directors; + + + +// express setup +var app = express(); + + +// set template engine +app.engine('html', swig.renderFile); +swig.setDefaults({ + cache: false // default 'memory' +}); + + +// jquery-file-upload helper +app.use('/upload/default', function (req, res, next) { + upload.fileHandler({ + tmpDir: dirs.temp, + uploadDir: __dirname + dirs.default, + uploadUrl: dirs.default_url, + imageVersions: resizeConf.default + })(req, res, next); +}); + +app.use('/upload/location', upload.fileHandler({ + tmpDir: dirs.temp, + uploadDir: __dirname + dirs.location, + uploadUrl: dirs.location_url, + imageVersions: resizeConf.location +})); + +app.use('/upload/location/list', function (req, res, next) { + upload.fileManager({ + uploadDir: function () { + return __dirname + dirs.location; + }, + uploadUrl: function () { + return dirs.location_url; + } + }).getFiles(function (files) { + res.json(files); + }); +}); + +// bind event +upload.on('end', function (fileInfo) { + // insert file info + console.log("files upload complete"); + console.log(fileInfo); +}); + +upload.on('delete', function (fileName) { + // remove file info + console.log("files remove complete"); + console.log(fileName); +}); + +upload.on('error', function (e) { + console.log(e.message); +}); + + + +// Configuration +app.configure(function () { + app.set('port', process.env.PORT || 3001); + app.set('view engine', 'html'); + app.set('view options', { layout: false }); + app.set('views', __dirname + '/views'); + + app.use(express.bodyParser()); + app.use(express.methodOverride()); + app.use(express.cookieParser('token')); + app.use(express.session({ secret: 'secret' })); + app.use(express.favicon()); + app.use(express.static(__dirname + '/public')); +}); + +app.configure('development', function () { + app.use(express.errorHandler({ dumpExceptions: true, showStack: true })); + app.set('view cache', false); +}); + +app.configure('production', function () { + app.use(express.errorHandler()); + app.set('view cache', true); +}); + + + +/* +* routes +* */ +app.get('/', function (req, res) { + var html = [ + '
Call this url in browser : http://localhost:3001/location/input Go
', + 'Call this url in browser : http://localhost:3001/upload/location/list Go
' + ].join(''); + res.send(html); +}); + + +app.get('/location/input', function (req, res) { + var params = { + title: "jquery file upload example" + }; + + res.render('form', params); +}); + +app.post('/location/input', function (req, res) { + console.log('\n===============================================\n'); + console.log(req.body); + res.send(req.body); +}); + + + +/* + * start server + * */ +http.createServer(app).listen(app.get('port'), function () { + console.log("Express server listening on port " + app.get('port')); + console.log("access url /location/input"); + console.log("access url /upload/location/list"); +}); diff --git a/examples/bower.json b/examples/bower.json new file mode 100644 index 0000000..1c7639f --- /dev/null +++ b/examples/bower.json @@ -0,0 +1,13 @@ +{ + "name": "jquery-file-upload-middleware", + "version": "0.0.1", + "ignore": [ + ".jshintrc", + "**/*.txt" + ], + "dependencies": { + "jquery-file-upload": "8.8.5" + }, + "devDependencies": { + } +} \ No newline at end of file diff --git a/examples/config.js b/examples/config.js new file mode 100644 index 0000000..b4138cc --- /dev/null +++ b/examples/config.js @@ -0,0 +1,56 @@ +exports.resizeVersion = { + default: { + thumbnail:{ + width:80, + height:"80!" + }, + small: { + width:200, + height:"150!" + }, + medium:{ + width:400, + height:300 + }, + large: { + width: 800, + height: 600 + } + }, + location : { + thumbnail:{ + width:80, + height:"80^", + imageArgs: [ + "-gravity", "center", + "-extent", "80x80" + ] + }, + small: { + width:"200", + height:"150^", + imageArgs: [ + "-gravity", "center", + "-extent", "200x150" + ] + }, + medium:{ + width:400, + height:300 + }, + large: { + width: 800, + height: 600 + } + } +}; + +exports.directors = { + temp: './tmp', + + default: '/public/uploads/default', + default_url: '/uploads/default', + + location: '/public/uploads/location', + location_url: '/uploads/location' +}; \ No newline at end of file diff --git a/examples/package.json b/examples/package.json new file mode 100644 index 0000000..c435dee --- /dev/null +++ b/examples/package.json @@ -0,0 +1,8 @@ +{ + "name":"examples", + "version":"0.0.1", + "dependencies":{ + "express":"3.3.x", + "swig":"1.0.x" + } +} \ No newline at end of file diff --git a/examples/public/scripts/file_upload.js b/examples/public/scripts/file_upload.js new file mode 100644 index 0000000..603c48f --- /dev/null +++ b/examples/public/scripts/file_upload.js @@ -0,0 +1,174 @@ +$('#location_gallery').fileupload({ + dataType:'json', + url:'/upload/location', + autoUpload: true, + sequentialUploads:true, + acceptFileTypes:/(\.|\/)(gif|jpe?g|png)$/i, + process:[ + { + action:'load', + fileTypes:/^image\/(gif|jpeg|png)$/, + maxFileSize: 20000000 // 20MB + }, + { + action:'resize', + maxWidth:1200, + maxHeight:800 + }, + { + action:'save' + } + ], + dropZone: $('#gallery_dropzone'), + progressall:function (e, data) { + var progress = parseInt(data.loaded / data.total * 100, 10); + $('#gallery_progress .bar').css('width', progress + '%'); + }, + filesContainer:$('#upload_gallery_files'), + uploadTemplate:function (o) { + var rows = $(); + $.each(o.files, function (index, file) { + var row = $('