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

Subversion Repositories:
Rev:
Only display areas with differencesRegard whitespace
Rev 6 Rev 7
1 #!/usr/bin/env node 1 #!/usr/bin/env node
-   2  
2 /////////////////////////////////////////////////////////////////////////// 3 ///////////////////////////////////////////////////////////////////////////
3 // Copyright (C) 2017 Wizardry and Steamworks - License: GNU GPLv3 // 4 // Copyright (C) 2017 Wizardry and Steamworks - License: GNU GPLv3 //
4 /////////////////////////////////////////////////////////////////////////// 5 ///////////////////////////////////////////////////////////////////////////
5   6  
6 // Import packages. 7 // Import packages.
7 const auth = require("http-auth"); 8 const auth = require("http-auth");
8 const https = require('https'); 9 const https = require('https');
-   10 const http = require('http');
9 const path = require('path'); 11 const path = require('path');
10 const fs = require('fs'); 12 const fs = require('fs');
11 const mime = require('mime'); 13 const mime = require('mime');
12 const url = require('url'); 14 const url = require('url');
13 const moment = require('moment'); 15 const moment = require('moment');
14 const winston = require('winston'); 16 const winston = require('winston');
15 const yargs = require('yargs'); 17 const yargs = require('yargs');
16 const forge = require('node-forge'); 18 const forge = require('node-forge');
-   19 const dns = require('dns');
17   20  
18 // Get command-line arguments. 21 // Get command-line arguments.
19 const argv = yargs 22 const argv = yargs
20 .version() 23 .version()
21 .option('root', { 24 .option('root', {
22 alias: 'd', 25 alias: 'd',
23 describe: 'Path to the document root', 26 describe: 'Path to the document root',
24 demandOption: true 27 demandOption: true
25 }) 28 })
26 .help() 29 .help()
27 .argv 30 .argv
28   31  
29 // Configuration file. 32 // Configuration file.
-   33 const config = require(
-   34 path
30 const config = require(path.resolve(__dirname, 'config')); 35 .resolve(__dirname, 'config')
-   36 );
31   37  
32 // Check for path traversal. 38 // Check for path traversal.
33 function isRooted(userPath, rootPath, separator) { 39 function isRooted(userPath, rootPath, separator) {
34 userPath = userPath.split(separator).filter(Boolean); 40 userPath = userPath.split(separator).filter(Boolean);
35 rootPath = rootPath.split(separator).filter(Boolean); 41 rootPath = rootPath.split(separator).filter(Boolean);
36 return userPath.length >= rootPath.length && 42 return userPath.length >= rootPath.length &&
37 rootPath.every((e, i) => e === userPath[i]); 43 rootPath.every((e, i) => e === userPath[i]);
38 } 44 }
-   45  
39   46 // Generate certificates on the fly using incremental serials.
40 function generateCertificates(name, domain) { 47 function generateCertificates(name, domain, size) {
-   48 // Generate 1024-bit key-pair.
-   49 const keys = forge
-   50 .pki
41 // Generate 1024-bit key-pair. 51 .rsa
42 var keys = forge.pki.rsa.generateKeyPair(1024); 52 .generateKeyPair(size);
-   53 // Create self-signed certificate.
-   54 const cert = forge
43 // Create self-signed certificate. 55 .pki
-   56 .createCertificate();
44 var cert = forge.pki.createCertificate(); 57 cert.serialNumber = moment().format('x');
-   58 cert.publicKey = keys.publicKey;
-   59 cert
45 cert.publicKey = keys.publicKey; 60 .validity
-   61 .notBefore = moment().toDate();
-   62 cert
46 cert.validity.notBefore = new Date(); 63 .validity
47 cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1); 64 .notAfter
-   65 .setFullYear(
-   66 cert
-   67 .validity
-   68 .notBefore
48 cert.setSubject([ 69 .getFullYear() + 1
-   70 );
49 { 71 cert.setSubject([{
50 name: 'commonName', 72 name: 'commonName',
51 value: domain -  
52 }, 73 value: domain
53 { 74 }, {
54 name: 'organizationName', 75 name: 'organizationName',
55 value: name -  
56 } 76 value: name
57 ]); 77 }]);
58 cert.setIssuer([ -  
59 { 78 cert.setIssuer([{
60 name: 'commonName', 79 name: 'commonName',
61 value: name -  
62 }, 80 value: domain
63 { 81 }, {
64 name: 'organizationName', 82 name: 'organizationName',
65 value: name -  
66 } 83 value: name
67 ]); 84 }]);
-   85  
68   86 // Self-sign certificate.
-   87 cert.sign(
-   88 keys.privateKey,
-   89 forge
-   90 .md
-   91 .sha256
69 // Self-sign certificate. 92 .create()
70 cert.sign(keys.privateKey); 93 );
71   94  
72 // Return PEM-format keys and certificates. 95 // Return PEM-format keys and certificates.
73 return { 96 return {
-   97 privateKey: forge
-   98 .pki
74 privateKey: forge.pki.privateKeyToPem(keys.privateKey), 99 .privateKeyToPem(
-   100 keys
-   101 .privateKey
-   102 ),
-   103 publicKey: forge
-   104 .pki
75 publicKey: forge.pki.publicKeyToPem(keys.publicKey), 105 .publicKeyToPem(
-   106 keys
-   107 .publicKey
-   108 ),
-   109 certificate: forge
-   110 .pki
76 certificate: forge.pki.certificateToPem(cert) 111 .certificateToPem(cert)
77 }; 112 };
78 } 113 }
79   114  
80 // Create various logging mechanisms. 115 // Create various logging mechanisms.
81 const log = new winston.Logger({ 116 const log = new winston.Logger({
82 transports: [ 117 transports: [
83 new winston.transports.File({ 118 new winston.transports.File({
84 level: 'info', 119 level: 'info',
85 filename: path.resolve(__dirname, config.server_log), 120 filename: path.resolve(__dirname, config.log.file),
86 handleExceptions: true, 121 handleExceptions: true,
87 json: false, 122 json: false,
88 maxsize: 1048576, // 1MiB. 123 maxsize: 1048576, // 1MiB.
89 maxFiles: 10, // Ten rotations. 124 maxFiles: 10, // Ten rotations.
90 colorize: false, 125 colorize: false,
91 timestamp: () => moment().format('YYYYMMDDTHHmmss') 126 timestamp: () => moment().format('YYYYMMDDTHHmmss')
92 }), 127 }),
93 new winston.transports.Console({ 128 new winston.transports.Console({
94 level: 'info', 129 level: 'info',
95 handleExceptions: true, 130 handleExceptions: true,
96 json: false, 131 json: false,
97 colorize: true, 132 colorize: true,
98 timestamp: () => moment().format('YYYYMMDDTHHmmss') 133 timestamp: () => moment().format('YYYYMMDDTHHmmss')
99 }) 134 })
100 ], 135 ],
101 exitOnError: false 136 exitOnError: false
102 }); 137 });
103   138  
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 }); -  
114 -  
115 const certs = generateCertificates("was", 'localhost'); -  
116   -  
117 // HTTPs server using digest authentication. -  
118 https.createServer(authentication, { -  
119 key: certs.privateKey, -  
120 cert: certs.certificate, -  
121 }, (request, response) => { 139 function handleClient(request, response, documentRoot) {
122 const requestAddress = request.socket.address(); 140 const requestAddress = request.socket.address();
123 const requestedURL = url.parse(request.url, true); 141 const requestedURL = url.parse(request.url, true);
-   142  
124   143 log.info('Client: ' +
-   144 requestAddress.address + ':' +
-   145 requestAddress.port +
-   146 ' accessing: ' +
-   147 requestedURL.pathname
125 log.info('Client: ' + requestAddress.address + ':' + requestAddress.port + ' accessing: ' + requestedURL.pathname); 148 );
126   149  
-   150 const trimmedPath = requestedURL
-   151 .pathname
-   152 .split('/')
-   153 .filter(Boolean)
127 const trimmedPath = requestedURL.pathname.split('/').filter(Boolean).join('/'); 154 .join('/');
128 const filesystemPath = trimmedPath === '/' ? 155 const filesystemPath = trimmedPath === '/' ?
129 path.join(documentRoot, trimmedPath) : 156 path.join(documentRoot, trimmedPath) :
130 path.resolve(documentRoot, trimmedPath); 157 path.resolve(documentRoot, trimmedPath);
131   158  
132 if (!isRooted(filesystemPath, documentRoot, path.sep)) { 159 if (!isRooted(filesystemPath, documentRoot, path.sep)) {
133 log.warn('Attempted path traversal: ' + requestAddress.address + ':' + requestAddress.port + ' requesting: ' + requestedURL.pathname); 160 log.warn('Attempted path traversal: ' +
-   161 requestAddress.address + ':' +
-   162 requestAddress.port +
-   163 ' requesting: ' +
-   164 requestedURL.pathname
-   165 );
134 response.statusCode = 403; 166 response.statusCode = 403;
135 response.end(); 167 response.end();
136 return; 168 return;
137 } 169 }
138   170  
139 fs.stat(filesystemPath, (error, stats) => { 171 fs.stat(filesystemPath, (error, stats) => {
140 // Document does not exist. 172 // Document does not exist.
141 if (error) { 173 if (error) {
142 response.statusCode = 404; 174 response.statusCode = 404;
143 response.end(); 175 response.end();
144 return; 176 return;
145 } 177 }
146   178  
147 switch (stats.isDirectory()) { 179 switch (stats.isDirectory()) {
148 case true: // Browser requesting directory. 180 case true: // Directory is requested so provide directory indexes.
149 const documentRoot = path.resolve(filesystemPath, config.default_document); 181 const documentRoot = path.resolve(filesystemPath, config.site.index);
150 fs.stat(documentRoot, (error, stats) => { 182 fs.stat(documentRoot, (error, stats) => {
151 if (error) { 183 if (error) {
152 fs.readdir(filesystemPath, (error, paths) => { 184 fs.readdir(filesystemPath, (error, paths) => {
153 if (error) { 185 if (error) {
154 log.warn('Could not list directory: ' + filesystemPath); 186 log.warn('Could not list directory: ' + filesystemPath);
155 response.statusCode = 500; 187 response.statusCode = 500;
156 response.end(); 188 response.end();
157 return; 189 return;
158 } 190 }
159 log.info('Directory listing requested for: ' + filesystemPath); 191 log.info('Directory listing requested for: ' + filesystemPath);
160 response.statusCode = 200; 192 response.statusCode = 200;
161 response.write(JSON.stringify(paths)); 193 response.write(JSON.stringify(paths));
162 response.end(); 194 response.end();
163 }); 195 });
164   196  
165 return; 197 return;
166 } 198 }
167   199  
168 fs.access(filesystemPath, fs.constants.R_OK, (error) => { 200 fs.access(filesystemPath, fs.constants.R_OK, (error) => {
169 if (error) { 201 if (error) {
170 log.warn('The server was unable to access the filesystem path: ' + filesystemPath); 202 log.warn('The server was unable to access the filesystem path: ' + filesystemPath);
171 response.statusCode = 403; 203 response.statusCode = 403;
172 response.end(); 204 response.end();
173 return; 205 return;
174 } 206 }
175   207  
176 // Set MIME content type. 208 // Set MIME content type.
177 response.setHeader('Content-Type', mime.lookup(documentRoot)); 209 response.setHeader('Content-Type', mime.lookup(documentRoot));
178   210  
179 var readStream = fs.createReadStream(documentRoot) 211 var readStream = fs.createReadStream(documentRoot)
180 .on('open', () => { 212 .on('open', () => {
181 response.statusCode = 200; 213 response.statusCode = 200;
182 readStream.pipe(response); 214 readStream.pipe(response);
183 }) 215 })
184 .on('error', () => { 216 .on('error', () => {
185 response.statusCode = 500; 217 response.statusCode = 500;
186 response.end(); 218 response.end();
187 }); 219 });
188   220  
189 }); 221 });
190   222  
191 }); 223 });
192 break; 224 break;
193 default: // Browser requesting file. 225 default: // Browser requesting file.
194 // Check if the file is accessible. 226 // Check if the file is accessible.
195 fs.access(filesystemPath, fs.constants.R_OK, (error) => { 227 fs.access(filesystemPath, fs.constants.R_OK, (error) => {
196 if (error) { 228 if (error) {
197 response.statusCode = 403; 229 response.statusCode = 403;
198 response.end(); 230 response.end();
199 return; 231 return;
200 } 232 }
201   233  
202 response.setHeader('Content-Type', mime.lookup(filesystemPath)); 234 response.setHeader('Content-Type', mime.lookup(filesystemPath));
203   235  
204 var readStream = fs.createReadStream(filesystemPath) 236 var readStream = fs.createReadStream(filesystemPath)
205 .on('open', () => { 237 .on('open', () => {
206 response.statusCode = 200; 238 response.statusCode = 200;
207 readStream.pipe(response); 239 readStream.pipe(response);
208 }) 240 })
209 .on('error', () => { 241 .on('error', () => {
210 response.statusCode = 500; 242 response.statusCode = 500;
211 response.end(); 243 response.end();
212 }); 244 });
213   245  
214 }); 246 });
215 break; 247 break;
216 } 248 }
217 }); 249 });
-   250 }
-   251  
-   252 fs.realpath(argv.root, (error, documentRoot) => {
-   253 if (error) {
-   254 log.error('Could not find document root: ' + argv.root);
-   255 process.exit(1);
-   256 }
-   257  
-   258 // Create digest authentication.
-   259 const authentication = auth.digest({
-   260 realm: config.auth.realm,
-   261 file: path.resolve(__dirname, config.auth.digest)
-   262 });
-   263  
-   264 // Start HTTP server.
-   265 http.createServer(
-   266 // authentication,
-   267 (request, response) =>
218   268 handleClient(request, response, documentRoot)
-   269 ).listen(config.net.port, config.net.address, () => {
-   270 log.info('HTTP Server is listening on: ' +
-   271 config.net.address +
-   272 ' and port: ' +
-   273 config.net.port +
-   274 ' whilst serving files from: ' +
-   275 documentRoot
-   276 );
-   277 });
-   278  
-   279 // Start HTTPs server if enabled.
-   280 config.ssl.enable && (() => {
-   281 // Generate certificates for HTTPs.
-   282 const certs = generateCertificates(
-   283 config.site.name,
-   284 config.net.address,
-   285 config.ssl.privateKeySize
-   286 );
-   287  
-   288 https.createServer(
-   289 // authentication,
-   290 {
-   291 key: certs.privateKey,
-   292 cert: certs.certificate,
-   293 },
-   294 (request, response) =>
219 }).listen(config.port, config.address, () => { 295 handleClient(request, response, documentRoot)
-   296 ).listen(config.ssl.port, config.ssl.address, () => {
-   297 // Print information on server startup.
-   298 log.info('HTTPs Server is listening on: ' +
-   299 config.net.address +
-   300 ' and port: ' +
-   301 config.net.port +
-   302 ' whilst serving files from: ' +
-   303 documentRoot
220 log.info('Server is listening on: ' + config.address + ' and port: ' + config.port + ' whilst serving files from: ' + documentRoot); 304 );
-   305 });
-   306 })();
221 }); 307  
222 }); 308 });
223   309