From ca2d3b4df536086a81d3dcc2203f23c2b4c8f822 Mon Sep 17 00:00:00 2001 From: Johannes Zellner Date: Sat, 27 Jun 2015 15:39:28 +0200 Subject: [PATCH] initial commit --- .gitignore | 1 + .jshintrc | 7 +++ app.js | 33 +++++++++++++ files/foobar | 26 ++++++++++ package.json | 26 ++++++++++ src/files.js | 112 ++++++++++++++++++++++++++++++++++++++++++++ src/multipart.js | 47 +++++++++++++++++++ views/directory.ejs | 17 +++++++ views/file.ejs | 15 ++++++ 9 files changed, 284 insertions(+) create mode 100644 .gitignore create mode 100644 .jshintrc create mode 100755 app.js create mode 100644 files/foobar create mode 100644 package.json create mode 100644 src/files.js create mode 100644 src/multipart.js create mode 100644 views/directory.ejs create mode 100644 views/file.ejs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..174ffa3 --- /dev/null +++ b/.jshintrc @@ -0,0 +1,7 @@ +{ + "node": true, + "browser": true, + "unused": true, + "globalstrict": true, + "predef": [ "angular", "$", "describe", "it", "before", "after" ] +} diff --git a/app.js b/app.js new file mode 100755 index 0000000..a985db7 --- /dev/null +++ b/app.js @@ -0,0 +1,33 @@ +#!/usr/bin/env node + +'use strict'; + +var express = require('express'), + morgan = require('morgan'), + compression = require('compression'), + bodyParser = require('body-parser'), + lastMile = require('connect-lastmile'), + multipart = require('./src/multipart'), + files = require('./src/files.js'); + +var app = express(); +var router = new express.Router(); + +var multipart = multipart({ maxFieldsSize: 2 * 1024, limit: '512mb', timeout: 3 * 60 * 1000 }); + +router.get('/api/files/*', files.get); +router.put('/api/files/*', multipart, files.put); +router.delete('/api/files/*', files.del); + +app.use(morgan('dev')); +app.use(compression()); +app.use(bodyParser.json()); +app.use(router); +app.use(lastMile()); + +var server = app.listen(3000, function () { + var host = server.address().address; + var port = server.address().port; + + console.log('Surfer listening at http://%s:%s', host, port); +}); \ No newline at end of file diff --git a/files/foobar b/files/foobar new file mode 100644 index 0000000..cf08457 --- /dev/null +++ b/files/foobar @@ -0,0 +1,26 @@ +{ + "name": "surfer", + "version": "1.0.0", + "description": "Simple file server", + "main": "app.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "file", + "server" + ], + "author": "Johannes Zellner ", + "license": "MIT", + "dependencies": { + "body-parser": "^1.13.1", + "compression": "^1.5.0", + "connect-lastmile": "0.0.10", + "connect-timeout": "^1.6.2", + "ejs": "^2.3.1", + "express": "^4.12.4", + "morgan": "^1.6.0", + "multiparty": "^4.1.2", + "rimraf": "^2.4.0" + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..cf08457 --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "surfer", + "version": "1.0.0", + "description": "Simple file server", + "main": "app.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "file", + "server" + ], + "author": "Johannes Zellner ", + "license": "MIT", + "dependencies": { + "body-parser": "^1.13.1", + "compression": "^1.5.0", + "connect-lastmile": "0.0.10", + "connect-timeout": "^1.6.2", + "ejs": "^2.3.1", + "express": "^4.12.4", + "morgan": "^1.6.0", + "multiparty": "^4.1.2", + "rimraf": "^2.4.0" + } +} diff --git a/src/files.js b/src/files.js new file mode 100644 index 0000000..fd9c8fd --- /dev/null +++ b/src/files.js @@ -0,0 +1,112 @@ +'use strict'; + +var fs = require('fs'), + path = require('path'), + ejs = require('ejs'), + rimraf = require('rimraf'), + HttpError = require('connect-lastmile').HttpError, + HttpSuccess = require('connect-lastmile').HttpSuccess; + +exports = module.exports = { + get: get, + put: put, + del: del +}; + +var FILE_BASE = path.resolve(__dirname, '../files'); + +// http://stackoverflow.com/questions/11293857/fastest-way-to-copy-file-in-node-js +function copyFile(source, target, cb) { + var cbCalled = false; + + var rd = fs.createReadStream(source); + rd.on("error", function(err) { + done(err); + }); + + var wr = fs.createWriteStream(target); + wr.on("error", function(err) { + done(err); + }); + + wr.on("close", function(ex) { + done(); + }); + + rd.pipe(wr); + + function done(err) { + if (!cbCalled) { + cb(err); + cbCalled = true; + } + } +} + +function render(view, options) { + return ejs.render(fs.readFileSync(view, 'utf8'), options); +} + +function getAbsolutePath(filePath) { + var absoluteFilePath = path.resolve(FILE_BASE, filePath); + + if (absoluteFilePath.indexOf(FILE_BASE) !== 0) return null; + return absoluteFilePath; +} + +function get(req, res, next) { + var filePath = req.params[0]; + var absoluteFilePath = getAbsolutePath(filePath); + if (!absoluteFilePath) return next(new HttpError(403, 'Path not allowed')); + + fs.stat(absoluteFilePath, function (error, result) { + if (error) return next(new HttpError(404, error)); + + console.log('get', absoluteFilePath, result); + + if (result.isFile()) return res.sendfile(absoluteFilePath); + if (result.isDirectory()) return res.status(200).send({ entries: fs.readdirSync(absoluteFilePath) }); + + return next(new HttpError(500, 'unsupported type')); + }); +} + +function put(req, res, next) { + var filePath = req.params[0]; + + if (!req.files.file) return next(new HttpError(400, 'missing file')); + + var absoluteFilePath = getAbsolutePath(filePath); + if (!absoluteFilePath) return next(new HttpError(403, 'Path not allowed')); + + fs.stat(absoluteFilePath, function (error, result) { + if (error && error.code !== 'ENOENT') return next(new HttpError(500, error)); + + console.log('put', absoluteFilePath, result, req.files.file); + + if (result && result.isDirectory()) return next(new HttpError(409, 'cannot put on directories')); + if (!result || result.isFile()) { + return copyFile(req.files.file.path, absoluteFilePath, function (error) { + if (error) return next(new HttpError(500, error)); + next(new HttpSuccess(201, {})); + }); + } + + return next(new HttpError(500, 'unsupported type')); + }); +} + +function del(req, res, next) { + var filePath = req.params[0]; + var absoluteFilePath = getAbsolutePath(filePath); + if (!absoluteFilePath) return next(new HttpError(403, 'Path not allowed')); + + fs.stat(absoluteFilePath, function (error, result) { + if (error) return next(new HttpError(404, error)); + + rimraf(absoluteFilePath, function (error) { + if (error) return next(new HttpError(500, 'Unable to remove')); + next(new HttpError(200, {})); + }); + }); +} diff --git a/src/multipart.js b/src/multipart.js new file mode 100644 index 0000000..7b994cc --- /dev/null +++ b/src/multipart.js @@ -0,0 +1,47 @@ +/* jshint node:true */ + +'use strict'; + +var multiparty = require('multiparty'), + timeout = require('connect-timeout'); + +function _mime(req) { + var str = req.headers['content-type'] || ''; + return str.split(';')[0]; +} + +module.exports = function multipart(options) { + return function (req, res, next) { + if (_mime(req) !== 'multipart/form-data') return res.status(400).send('Invalid content-type. Expecting multipart'); + + var form = new multiparty.Form({ + uploadDir: '/tmp', + keepExtensions: true, + maxFieldsSize: options.maxFieldsSize || (2 * 1024), // only field size, not files + limit: options.limit || '500mb', // file sizes + autoFiles: true + }); + + // increase timeout of file uploads by default to 3 mins + if (req.clearTimeout) req.clearTimeout(); // clear any previous installed timeout middleware + + timeout(options.timeout || (3 * 60 * 1000))(req, res, function () { + req.fields = { }; + req.files = { }; + + form.parse(req, function (err, fields, files) { + if (err) return res.status(400).send('Error parsing request'); + next(null); + }); + + form.on('file', function (name, file) { + req.files[name] = file; + }); + + form.on('field', function (name, value) { + req.fields[name] = value; // otherwise fields.name is an array + }); + }); + }; +}; + diff --git a/views/directory.ejs b/views/directory.ejs new file mode 100644 index 0000000..7317794 --- /dev/null +++ b/views/directory.ejs @@ -0,0 +1,17 @@ + + + <%= dirPath %> + + + + +Directory <%= dirPath %>: + +
    +<% entries.forEach(function (entry) { %> +
  • <%= entry %>
  • +<% }); %> +
+ + + \ No newline at end of file diff --git a/views/file.ejs b/views/file.ejs new file mode 100644 index 0000000..0b62b98 --- /dev/null +++ b/views/file.ejs @@ -0,0 +1,15 @@ + + + File + + + + +File <%= filePath %>: + +
+<%= fileContent %>
+
+ + + \ No newline at end of file -- 2.41.0