node-http-server – Blame information for rev 6

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 office 1 #!/usr/bin/env node
6 office 2 ///////////////////////////////////////////////////////////////////////////
1 office 3 // Copyright (C) 2017 Wizardry and Steamworks - License: GNU GPLv3 //
4 ///////////////////////////////////////////////////////////////////////////
5  
6 // Import packages.
7 const auth = require("http-auth");
8 const https = require('https');
9 const path = require('path');
10 const fs = require('fs');
11 const mime = require('mime');
12 const url = require('url');
13 const moment = require('moment');
14 const winston = require('winston');
15 const yargs = require('yargs');
6 office 16 const forge = require('node-forge');
1 office 17  
18 // Get command-line arguments.
19 const argv = yargs
20 .version()
21 .option('root', {
22 alias: 'd',
23 describe: 'Path to the document root',
24 demandOption: true
25 })
26 .help()
27 .argv
28  
29 // Configuration file.
30 const config = require(path.resolve(__dirname, 'config'));
31  
32 // Check for path traversal.
33 function isRooted(userPath, rootPath, separator) {
34 userPath = userPath.split(separator).filter(Boolean);
35 rootPath = rootPath.split(separator).filter(Boolean);
36 return userPath.length >= rootPath.length &&
37 rootPath.every((e, i) => e === userPath[i]);
38 }
39  
6 office 40 function generateCertificates(name, domain) {
41 // Generate 1024-bit key-pair.
42 var keys = forge.pki.rsa.generateKeyPair(1024);
43 // Create self-signed certificate.
44 var cert = forge.pki.createCertificate();
45 cert.publicKey = keys.publicKey;
46 cert.validity.notBefore = new Date();
47 cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1);
48 cert.setSubject([
49 {
50 name: 'commonName',
51 value: domain
52 },
53 {
54 name: 'organizationName',
55 value: name
56 }
57 ]);
58 cert.setIssuer([
59 {
60 name: 'commonName',
61 value: name
62 },
63 {
64 name: 'organizationName',
65 value: name
66 }
67 ]);
68  
69 // Self-sign certificate.
70 cert.sign(keys.privateKey);
71  
72 // Return PEM-format keys and certificates.
73 return {
74 privateKey: forge.pki.privateKeyToPem(keys.privateKey),
75 publicKey: forge.pki.publicKeyToPem(keys.publicKey),
76 certificate: forge.pki.certificateToPem(cert)
77 };
78 }
79  
1 office 80 // Create various logging mechanisms.
81 const log = new winston.Logger({
82 transports: [
83 new winston.transports.File({
84 level: 'info',
85 filename: path.resolve(__dirname, config.server_log),
86 handleExceptions: true,
87 json: false,
88 maxsize: 1048576, // 1MiB.
89 maxFiles: 10, // Ten rotations.
90 colorize: false,
91 timestamp: () => moment().format('YYYYMMDDTHHmmss')
92 }),
93 new winston.transports.Console({
94 level: 'info',
95 handleExceptions: true,
96 json: false,
97 colorize: true,
98 timestamp: () => moment().format('YYYYMMDDTHHmmss')
99 })
100 ],
101 exitOnError: false
102 });
103  
104 fs.realpath(argv.root, (error, documentRoot) => {
105 if (error) {
106 log.error('Could not find document root: ' + argv.root);
107 process.exit(1);
108 }
109  
110 var authentication = auth.digest({
111 realm: "was",
112 file: path.resolve(__dirname, config.password_file)
113 });
6 office 114  
115 const certs = generateCertificates("was", 'localhost');
1 office 116  
117 // HTTPs server using digest authentication.
118 https.createServer(authentication, {
6 office 119 key: certs.privateKey,
120 cert: certs.certificate,
1 office 121 }, (request, response) => {
122 const requestAddress = request.socket.address();
123 const requestedURL = url.parse(request.url, true);
124  
125 log.info('Client: ' + requestAddress.address + ':' + requestAddress.port + ' accessing: ' + requestedURL.pathname);
126  
127 const trimmedPath = requestedURL.pathname.split('/').filter(Boolean).join('/');
128 const filesystemPath = trimmedPath === '/' ?
129 path.join(documentRoot, trimmedPath) :
130 path.resolve(documentRoot, trimmedPath);
131  
132 if (!isRooted(filesystemPath, documentRoot, path.sep)) {
133 log.warn('Attempted path traversal: ' + requestAddress.address + ':' + requestAddress.port + ' requesting: ' + requestedURL.pathname);
134 response.statusCode = 403;
135 response.end();
136 return;
137 }
138  
139 fs.stat(filesystemPath, (error, stats) => {
140 // Document does not exist.
141 if (error) {
142 response.statusCode = 404;
143 response.end();
144 return;
145 }
146  
147 switch (stats.isDirectory()) {
148 case true: // Browser requesting directory.
149 const documentRoot = path.resolve(filesystemPath, config.default_document);
150 fs.stat(documentRoot, (error, stats) => {
151 if (error) {
152 fs.readdir(filesystemPath, (error, paths) => {
153 if (error) {
154 log.warn('Could not list directory: ' + filesystemPath);
155 response.statusCode = 500;
156 response.end();
157 return;
158 }
159 log.info('Directory listing requested for: ' + filesystemPath);
160 response.statusCode = 200;
161 response.write(JSON.stringify(paths));
162 response.end();
163 });
164  
165 return;
166 }
167  
168 fs.access(filesystemPath, fs.constants.R_OK, (error) => {
169 if (error) {
170 log.warn('The server was unable to access the filesystem path: ' + filesystemPath);
171 response.statusCode = 403;
172 response.end();
173 return;
174 }
175  
176 // Set MIME content type.
177 response.setHeader('Content-Type', mime.lookup(documentRoot));
178  
179 var readStream = fs.createReadStream(documentRoot)
180 .on('open', () => {
181 response.statusCode = 200;
182 readStream.pipe(response);
183 })
184 .on('error', () => {
185 response.statusCode = 500;
186 response.end();
187 });
188  
189 });
190  
191 });
192 break;
193 default: // Browser requesting file.
194 // Check if the file is accessible.
195 fs.access(filesystemPath, fs.constants.R_OK, (error) => {
196 if (error) {
197 response.statusCode = 403;
198 response.end();
199 return;
200 }
201  
202 response.setHeader('Content-Type', mime.lookup(filesystemPath));
203  
204 var readStream = fs.createReadStream(filesystemPath)
205 .on('open', () => {
206 response.statusCode = 200;
207 readStream.pipe(response);
208 })
209 .on('error', () => {
210 response.statusCode = 500;
211 response.end();
212 });
213  
214 });
215 break;
216 }
217 });
218  
219 }).listen(config.port, config.address, () => {
220 log.info('Server is listening on: ' + config.address + ' and port: ' + config.port + ' whilst serving files from: ' + documentRoot);
221 });
222 });