]>
Commit | Line | Data |
---|---|---|
ca2d3b4d JZ |
1 | #!/usr/bin/env node |
2 | ||
3 | 'use strict'; | |
4 | ||
5 | var express = require('express'), | |
6 | morgan = require('morgan'), | |
8c3ae071 | 7 | path = require('path'), |
552d44bb | 8 | fs = require('fs'), |
d34561d1 | 9 | archiver = require('archiver'), |
ca2d3b4d | 10 | compression = require('compression'), |
591ad40c | 11 | session = require('express-session'), |
d34561d1 IB |
12 | serveIndex = require('serve-index'), |
13 | escapeHtml = require('escape-html'), | |
ca2d3b4d | 14 | bodyParser = require('body-parser'), |
591ad40c | 15 | cookieParser = require('cookie-parser'), |
ca2d3b4d | 16 | lastMile = require('connect-lastmile'), |
552d44bb JZ |
17 | HttpError = require('connect-lastmile').HttpError, |
18 | HttpSuccess = require('connect-lastmile').HttpSuccess, | |
ca2d3b4d | 19 | multipart = require('./src/multipart'), |
7bb99aff | 20 | mkdirp = require('mkdirp'), |
591ad40c | 21 | auth = require('./src/auth.js'), |
7af3d855 | 22 | webdav = require('webdav-server').v2, |
fb372a32 | 23 | files = require('./src/files.js')(path.resolve(__dirname, process.argv[2] || 'files')); |
ca2d3b4d | 24 | |
552d44bb | 25 | |
b3ff26fb JZ |
26 | const ROOT_FOLDER = path.resolve(__dirname, process.argv[2] || 'files'); |
27 | const CONFIG_FILE = path.resolve(__dirname, process.argv[3] || '.config.json'); | |
552d44bb JZ |
28 | |
29 | // Ensure the root folder exists | |
b3ff26fb | 30 | mkdirp.sync(ROOT_FOLDER); |
552d44bb JZ |
31 | |
32 | var config = { | |
8a5c7d41 | 33 | folderListingEnabled: false |
552d44bb JZ |
34 | }; |
35 | ||
36 | function getSettings(req, res, next) { | |
37 | res.send({ folderListingEnabled: !!config.folderListingEnabled }); | |
38 | } | |
39 | ||
40 | function setSettings(req, res, next) { | |
ca2fe6ec | 41 | return next(new HttpError(400, 'not editable')); |
552d44bb JZ |
42 | if (typeof req.body.folderListingEnabled === 'undefined') return next(new HttpError(400, 'missing folderListingEnabled boolean')); |
43 | ||
44 | config.folderListingEnabled = !!req.body.folderListingEnabled; | |
45 | ||
b3ff26fb | 46 | fs.writeFile(CONFIG_FILE, JSON.stringify(config), function (error) { |
552d44bb JZ |
47 | if (error) return next(new HttpError(500, 'unable to save settings')); |
48 | ||
49 | next(new HttpSuccess(201, {})); | |
50 | }); | |
51 | } | |
52 | ||
53 | // Load the config file | |
54 | try { | |
313dfe99 | 55 | console.log(`Using config file at: ${CONFIG_FILE}`); |
b3ff26fb | 56 | config = require(CONFIG_FILE); |
552d44bb | 57 | } catch (e) { |
b3ff26fb JZ |
58 | if (e.code === 'MODULE_NOT_FOUND') console.log(`Config file ${CONFIG_FILE} not found`); |
59 | else console.log(`Cannot load config file ${CONFIG_FILE}`, e); | |
552d44bb JZ |
60 | } |
61 | ||
ca2fe6ec | 62 | if (typeof config.folderListingEnabled === 'undefined') config.folderListingEnabled = false; |
552d44bb | 63 | |
d34561d1 IB |
64 | function isRoot(p) { |
65 | return path.join(ROOT_FOLDER, p) === path.join(ROOT_FOLDER, '/'); | |
66 | } | |
67 | ||
68 | function sendArchive(format) { | |
69 | var mime, extension; | |
70 | if (format === "zip") { | |
71 | mime = "application/zip"; | |
72 | extension = "zip"; | |
73 | } else { | |
74 | mime = "application/tar+gzip"; | |
75 | extension = "tar.gz"; | |
76 | } | |
77 | return function(req, res, next) { | |
78 | if (isRoot(req.path) || !fs.existsSync(path.join(ROOT_FOLDER, req.path))) | |
79 | return res.status(404).sendFile(__dirname + '/frontend/404.html'); | |
80 | res.writeHead(200, { | |
81 | 'Content-Type': mime, | |
82 | 'Content-disposition': 'attachment; filename=' + path.basename(req.path) + '.' + extension | |
83 | }); | |
84 | var archive = archiver(format); | |
85 | archive.pipe(res); | |
86 | archive.directory(path.join(ROOT_FOLDER, req.path), path.basename(req.path)) | |
87 | archive.finalize(); | |
88 | } | |
89 | } | |
90 | ||
91 | function rawTemplate(locals, callback) { | |
92 | var html = '<!DOCTYPE html><html><head><meta charset="utf-8"><title>wget/curl friendly directory listing of '; | |
93 | html += locals.directory; | |
94 | html += '</title></head><body><ul>\n'; | |
95 | html += locals.fileList.map(function (file) { | |
96 | if (file.name === "..") { return ""; } | |
97 | var isDir = file.stat && file.stat.isDirectory(); | |
98 | var fpath = locals.directory.split('/').map(function (c) { return encodeURIComponent(c); }); | |
99 | if (!isDir) { fpath.shift(); fpath[0] = ""; } | |
100 | fpath.push(encodeURIComponent(file.name)); | |
101 | return '<li><a href="' + escapeHtml(path.normalize(fpath.join('/'))) + '">' + escapeHtml(file.name) + '</a></li>'; | |
102 | }).join("\n"); | |
103 | html += '\n</ul></body></html>'; | |
104 | callback(null, html); | |
105 | }; | |
106 | ||
552d44bb | 107 | // Setup the express server and routes |
ca2d3b4d JZ |
108 | var app = express(); |
109 | var router = new express.Router(); | |
110 | ||
7af3d855 JZ |
111 | var webdavServer = new webdav.WebDAVServer({ |
112 | requireAuthentification: true, | |
113 | httpAuthentication: new webdav.HTTPBasicAuthentication(new auth.WebdavUserManager(), 'Cloudron Surfer') | |
114 | }); | |
115 | ||
116 | webdavServer.setFileSystem('/', new webdav.PhysicalFileSystem(ROOT_FOLDER), function (success) { | |
313dfe99 | 117 | if (success) console.log(`Mounting webdav resource from: ${ROOT_FOLDER}`); |
7af3d855 JZ |
118 | }); |
119 | ||
ca2d3b4d JZ |
120 | var multipart = multipart({ maxFieldsSize: 2 * 1024, limit: '512mb', timeout: 3 * 60 * 1000 }); |
121 | ||
4a27fce7 JZ |
122 | router.post ('/api/login', auth.login); |
123 | router.post ('/api/logout', auth.verify, auth.logout); | |
552d44bb JZ |
124 | router.get ('/api/settings', auth.verify, getSettings); |
125 | router.put ('/api/settings', auth.verify, setSettings); | |
c2c00fca JZ |
126 | router.get ('/api/tokens', auth.verify, auth.getTokens); |
127 | router.post ('/api/tokens', auth.verify, auth.createToken); | |
128 | router.delete('/api/tokens/:token', auth.verify, auth.delToken); | |
4a27fce7 | 129 | router.get ('/api/profile', auth.verify, auth.getProfile); |
313dfe99 | 130 | router.get ('/api/files/*', auth.verifyIfNeeded, files.get); |
e628921a JZ |
131 | router.post ('/api/files/*', auth.verify, multipart, files.post); |
132 | router.put ('/api/files/*', auth.verify, files.put); | |
3d716d9e | 133 | router.delete('/api/files/*', auth.verify, files.del); |
ca2d3b4d | 134 | |
870c3b66 | 135 | app.use('/api/healthcheck', function (req, res) { res.status(200).send(); }); |
ca2d3b4d JZ |
136 | app.use(morgan('dev')); |
137 | app.use(compression()); | |
74c0064c JZ |
138 | app.use('/api', bodyParser.json()); |
139 | app.use('/api', bodyParser.urlencoded({ extended: false, limit: '100mb' })); | |
140 | app.use('/api', cookieParser()); | |
141 | app.use('/api', session({ secret: 'surfin surfin', resave: false, saveUninitialized: false })); | |
ca2d3b4d | 142 | app.use(router); |
f5f2a699 | 143 | app.use(webdav.extensions.express('/_webdav', webdavServer)); |
74c0064c | 144 | app.use('/_admin', express.static(__dirname + '/frontend')); |
d34561d1 IB |
145 | app.use('/raw', function serveRaw(req, res, next) { |
146 | if (isRoot(req.path) || !fs.existsSync(path.join(ROOT_FOLDER, req.path))) | |
147 | return res.status(404).sendFile(__dirname + '/frontend/404.html'); | |
148 | serveIndex(ROOT_FOLDER, { template: rawTemplate })(req, res, next); | |
149 | }); | |
150 | app.use('/zip', sendArchive("zip")); | |
151 | app.use('/tar', sendArchive("tar")); | |
b3ff26fb | 152 | app.use('/', express.static(ROOT_FOLDER)); |
552d44bb | 153 | app.use('/', function welcomePage(req, res, next) { |
ca2fe6ec | 154 | if (config.folderListingEnabled || !isRoot(req.path)) return next(); |
552d44bb JZ |
155 | res.status(200).sendFile(path.join(__dirname, '/frontend/welcome.html')); |
156 | }); | |
313dfe99 | 157 | app.use('/', function (req, res) { |
313dfe99 JZ |
158 | if (!fs.existsSync(path.join(ROOT_FOLDER, req.path))) return res.status(404).sendFile(__dirname + '/frontend/404.html'); |
159 | ||
160 | res.status(200).sendFile(__dirname + '/frontend/public.html'); | |
552d44bb | 161 | }); |
ca2d3b4d JZ |
162 | app.use(lastMile()); |
163 | ||
164 | var server = app.listen(3000, function () { | |
165 | var host = server.address().address; | |
166 | var port = server.address().port; | |
167 | ||
313dfe99 JZ |
168 | console.log(`Base path: ${ROOT_FOLDER}`); |
169 | console.log(); | |
170 | console.log(`Listening on http://${host}:${port}`); | |
171 | ||
172 | auth.init(config); | |
5c6f8c0a | 173 | }); |