]> git.immae.eu Git - perso/Immae/Projets/Nodejs/Surfer.git/commitdiff
initial commit
authorJohannes Zellner <johannes@nebulon.de>
Sat, 27 Jun 2015 13:39:28 +0000 (15:39 +0200)
committerJohannes Zellner <johannes@nebulon.de>
Sat, 27 Jun 2015 13:39:28 +0000 (15:39 +0200)
.gitignore [new file with mode: 0644]
.jshintrc [new file with mode: 0644]
app.js [new file with mode: 0755]
files/foobar [new file with mode: 0644]
package.json [new file with mode: 0644]
src/files.js [new file with mode: 0644]
src/multipart.js [new file with mode: 0644]
views/directory.ejs [new file with mode: 0644]
views/file.ejs [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..c2658d7
--- /dev/null
@@ -0,0 +1 @@
+node_modules/
diff --git a/.jshintrc b/.jshintrc
new file mode 100644 (file)
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 (executable)
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 (file)
index 0000000..cf08457
--- /dev/null
@@ -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 <johannes@nebulon.de>",
+  "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 (file)
index 0000000..cf08457
--- /dev/null
@@ -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 <johannes@nebulon.de>",
+  "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 (file)
index 0000000..fd9c8fd
--- /dev/null
@@ -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 (file)
index 0000000..7b994cc
--- /dev/null
@@ -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 (file)
index 0000000..7317794
--- /dev/null
@@ -0,0 +1,17 @@
+<html>
+<head>
+    <title> <%= dirPath %> </title>
+</head>
+
+<body>
+
+Directory <%= dirPath %>:
+
+<ul>
+<% entries.forEach(function (entry) { %>
+       <li><a href="/api/files/<%= dirPath %>/<%= entry %>"><%= entry %></a></li>
+<% }); %>
+</ul>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/views/file.ejs b/views/file.ejs
new file mode 100644 (file)
index 0000000..0b62b98
--- /dev/null
@@ -0,0 +1,15 @@
+<html>
+<head>
+    <title> File </title>
+</head>
+
+<body>
+
+File <%= filePath %>:
+
+<pre>
+<%= fileContent %>
+</pre>
+
+</body>
+</html>
\ No newline at end of file