node-http-server – Diff between revs 1 and 6

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