aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJohannes Zellner <johannes@nebulon.de>2015-06-27 15:39:28 +0200
committerJohannes Zellner <johannes@nebulon.de>2015-06-27 15:39:28 +0200
commitca2d3b4df536086a81d3dcc2203f23c2b4c8f822 (patch)
tree95b7d8cfbdbaa065f09c4756d8a72b92b974c885
downloadSurfer-ca2d3b4df536086a81d3dcc2203f23c2b4c8f822.tar.gz
Surfer-ca2d3b4df536086a81d3dcc2203f23c2b4c8f822.tar.zst
Surfer-ca2d3b4df536086a81d3dcc2203f23c2b4c8f822.zip
initial commit
-rw-r--r--.gitignore1
-rw-r--r--.jshintrc7
-rwxr-xr-xapp.js33
-rw-r--r--files/foobar26
-rw-r--r--package.json26
-rw-r--r--src/files.js112
-rw-r--r--src/multipart.js47
-rw-r--r--views/directory.ejs17
-rw-r--r--views/file.ejs15
9 files changed, 284 insertions, 0 deletions
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 @@
1{
2 "node": true,
3 "browser": true,
4 "unused": true,
5 "globalstrict": true,
6 "predef": [ "angular", "$", "describe", "it", "before", "after" ]
7}
diff --git a/app.js b/app.js
new file mode 100755
index 0000000..a985db7
--- /dev/null
+++ b/app.js
@@ -0,0 +1,33 @@
1#!/usr/bin/env node
2
3'use strict';
4
5var express = require('express'),
6 morgan = require('morgan'),
7 compression = require('compression'),
8 bodyParser = require('body-parser'),
9 lastMile = require('connect-lastmile'),
10 multipart = require('./src/multipart'),
11 files = require('./src/files.js');
12
13var app = express();
14var router = new express.Router();
15
16var multipart = multipart({ maxFieldsSize: 2 * 1024, limit: '512mb', timeout: 3 * 60 * 1000 });
17
18router.get('/api/files/*', files.get);
19router.put('/api/files/*', multipart, files.put);
20router.delete('/api/files/*', files.del);
21
22app.use(morgan('dev'));
23app.use(compression());
24app.use(bodyParser.json());
25app.use(router);
26app.use(lastMile());
27
28var server = app.listen(3000, function () {
29 var host = server.address().address;
30 var port = server.address().port;
31
32 console.log('Surfer listening at http://%s:%s', host, port);
33}); \ 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 @@
1{
2 "name": "surfer",
3 "version": "1.0.0",
4 "description": "Simple file server",
5 "main": "app.js",
6 "scripts": {
7 "test": "echo \"Error: no test specified\" && exit 1"
8 },
9 "keywords": [
10 "file",
11 "server"
12 ],
13 "author": "Johannes Zellner <johannes@nebulon.de>",
14 "license": "MIT",
15 "dependencies": {
16 "body-parser": "^1.13.1",
17 "compression": "^1.5.0",
18 "connect-lastmile": "0.0.10",
19 "connect-timeout": "^1.6.2",
20 "ejs": "^2.3.1",
21 "express": "^4.12.4",
22 "morgan": "^1.6.0",
23 "multiparty": "^4.1.2",
24 "rimraf": "^2.4.0"
25 }
26}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..cf08457
--- /dev/null
+++ b/package.json
@@ -0,0 +1,26 @@
1{
2 "name": "surfer",
3 "version": "1.0.0",
4 "description": "Simple file server",
5 "main": "app.js",
6 "scripts": {
7 "test": "echo \"Error: no test specified\" && exit 1"
8 },
9 "keywords": [
10 "file",
11 "server"
12 ],
13 "author": "Johannes Zellner <johannes@nebulon.de>",
14 "license": "MIT",
15 "dependencies": {
16 "body-parser": "^1.13.1",
17 "compression": "^1.5.0",
18 "connect-lastmile": "0.0.10",
19 "connect-timeout": "^1.6.2",
20 "ejs": "^2.3.1",
21 "express": "^4.12.4",
22 "morgan": "^1.6.0",
23 "multiparty": "^4.1.2",
24 "rimraf": "^2.4.0"
25 }
26}
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 @@
1'use strict';
2
3var fs = require('fs'),
4 path = require('path'),
5 ejs = require('ejs'),
6 rimraf = require('rimraf'),
7 HttpError = require('connect-lastmile').HttpError,
8 HttpSuccess = require('connect-lastmile').HttpSuccess;
9
10exports = module.exports = {
11 get: get,
12 put: put,
13 del: del
14};
15
16var FILE_BASE = path.resolve(__dirname, '../files');
17
18// http://stackoverflow.com/questions/11293857/fastest-way-to-copy-file-in-node-js
19function copyFile(source, target, cb) {
20 var cbCalled = false;
21
22 var rd = fs.createReadStream(source);
23 rd.on("error", function(err) {
24 done(err);
25 });
26
27 var wr = fs.createWriteStream(target);
28 wr.on("error", function(err) {
29 done(err);
30 });
31
32 wr.on("close", function(ex) {
33 done();
34 });
35
36 rd.pipe(wr);
37
38 function done(err) {
39 if (!cbCalled) {
40 cb(err);
41 cbCalled = true;
42 }
43 }
44}
45
46function render(view, options) {
47 return ejs.render(fs.readFileSync(view, 'utf8'), options);
48}
49
50function getAbsolutePath(filePath) {
51 var absoluteFilePath = path.resolve(FILE_BASE, filePath);
52
53 if (absoluteFilePath.indexOf(FILE_BASE) !== 0) return null;
54 return absoluteFilePath;
55}
56
57function get(req, res, next) {
58 var filePath = req.params[0];
59 var absoluteFilePath = getAbsolutePath(filePath);
60 if (!absoluteFilePath) return next(new HttpError(403, 'Path not allowed'));
61
62 fs.stat(absoluteFilePath, function (error, result) {
63 if (error) return next(new HttpError(404, error));
64
65 console.log('get', absoluteFilePath, result);
66
67 if (result.isFile()) return res.sendfile(absoluteFilePath);
68 if (result.isDirectory()) return res.status(200).send({ entries: fs.readdirSync(absoluteFilePath) });
69
70 return next(new HttpError(500, 'unsupported type'));
71 });
72}
73
74function put(req, res, next) {
75 var filePath = req.params[0];
76
77 if (!req.files.file) return next(new HttpError(400, 'missing file'));
78
79 var absoluteFilePath = getAbsolutePath(filePath);
80 if (!absoluteFilePath) return next(new HttpError(403, 'Path not allowed'));
81
82 fs.stat(absoluteFilePath, function (error, result) {
83 if (error && error.code !== 'ENOENT') return next(new HttpError(500, error));
84
85 console.log('put', absoluteFilePath, result, req.files.file);
86
87 if (result && result.isDirectory()) return next(new HttpError(409, 'cannot put on directories'));
88 if (!result || result.isFile()) {
89 return copyFile(req.files.file.path, absoluteFilePath, function (error) {
90 if (error) return next(new HttpError(500, error));
91 next(new HttpSuccess(201, {}));
92 });
93 }
94
95 return next(new HttpError(500, 'unsupported type'));
96 });
97}
98
99function del(req, res, next) {
100 var filePath = req.params[0];
101 var absoluteFilePath = getAbsolutePath(filePath);
102 if (!absoluteFilePath) return next(new HttpError(403, 'Path not allowed'));
103
104 fs.stat(absoluteFilePath, function (error, result) {
105 if (error) return next(new HttpError(404, error));
106
107 rimraf(absoluteFilePath, function (error) {
108 if (error) return next(new HttpError(500, 'Unable to remove'));
109 next(new HttpError(200, {}));
110 });
111 });
112}
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 @@
1/* jshint node:true */
2
3'use strict';
4
5var multiparty = require('multiparty'),
6 timeout = require('connect-timeout');
7
8function _mime(req) {
9 var str = req.headers['content-type'] || '';
10 return str.split(';')[0];
11}
12
13module.exports = function multipart(options) {
14 return function (req, res, next) {
15 if (_mime(req) !== 'multipart/form-data') return res.status(400).send('Invalid content-type. Expecting multipart');
16
17 var form = new multiparty.Form({
18 uploadDir: '/tmp',
19 keepExtensions: true,
20 maxFieldsSize: options.maxFieldsSize || (2 * 1024), // only field size, not files
21 limit: options.limit || '500mb', // file sizes
22 autoFiles: true
23 });
24
25 // increase timeout of file uploads by default to 3 mins
26 if (req.clearTimeout) req.clearTimeout(); // clear any previous installed timeout middleware
27
28 timeout(options.timeout || (3 * 60 * 1000))(req, res, function () {
29 req.fields = { };
30 req.files = { };
31
32 form.parse(req, function (err, fields, files) {
33 if (err) return res.status(400).send('Error parsing request');
34 next(null);
35 });
36
37 form.on('file', function (name, file) {
38 req.files[name] = file;
39 });
40
41 form.on('field', function (name, value) {
42 req.fields[name] = value; // otherwise fields.name is an array
43 });
44 });
45 };
46};
47
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 @@
1<html>
2<head>
3 <title> <%= dirPath %> </title>
4</head>
5
6<body>
7
8Directory <%= dirPath %>:
9
10<ul>
11<% entries.forEach(function (entry) { %>
12 <li><a href="/api/files/<%= dirPath %>/<%= entry %>"><%= entry %></a></li>
13<% }); %>
14</ul>
15
16</body>
17</html> \ 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 @@
1<html>
2<head>
3 <title> File </title>
4</head>
5
6<body>
7
8File <%= filePath %>:
9
10<pre>
11<%= fileContent %>
12</pre>
13
14</body>
15</html> \ No newline at end of file