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

Subversion Repositories:
Rev:
Show entire fileIgnore whitespace
Rev 6 Rev 7
Line 1... Line 1...
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 ///////////////////////////////////////////////////////////////////////////
Line 5... Line 6...
5   6  
6 // Import packages. 7 // Import packages.
7 const auth = require("http-auth"); 8 const auth = require("http-auth");
-   9 const https = require('https');
8 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');
-   18 const forge = require('node-forge');
Line 16... Line 19...
16 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
Line 25... Line 28...
25 }) 28 })
26 .help() 29 .help()
27 .argv 30 .argv
Line 28... Line 31...
28   31  
-   32 // Configuration file.
-   33 const config = require(
29 // Configuration file. 34 path
-   35 .resolve(__dirname, 'config')
Line 30... Line 36...
30 const config = require(path.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 &&
Line -... Line 43...
-   43 rootPath.every((e, i) => e === userPath[i]);
37 rootPath.every((e, i) => e === userPath[i]); 44 }
38 } 45  
-   46 // Generate certificates on the fly using incremental serials.
-   47 function generateCertificates(name, domain, size) {
-   48 // Generate 1024-bit key-pair.
39   49 const keys = forge
40 function generateCertificates(name, domain) { 50 .pki
-   51 .rsa
-   52 .generateKeyPair(size);
41 // Generate 1024-bit key-pair. 53 // Create self-signed certificate.
-   54 const cert = forge
42 var keys = forge.pki.rsa.generateKeyPair(1024); 55 .pki
-   56 .createCertificate();
-   57 cert.serialNumber = moment().format('x');
43 // Create self-signed certificate. 58 cert.publicKey = keys.publicKey;
-   59 cert
-   60 .validity
44 var cert = forge.pki.createCertificate(); 61 .notBefore = moment().toDate();
45 cert.publicKey = keys.publicKey; 62 cert
46 cert.validity.notBefore = new Date(); 63 .validity
-   64 .notAfter
47 cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1); 65 .setFullYear(
48 cert.setSubject([ 66 cert
49 { 67 .validity
50 name: 'commonName', 68 .notBefore
51 value: domain 69 .getFullYear() + 1
52 }, 70 );
53 { 71 cert.setSubject([{
-   72 name: 'commonName',
-   73 value: domain
54 name: 'organizationName', 74 }, {
55 value: name 75 name: 'organizationName',
56 } -  
57 ]); 76 value: name
58 cert.setIssuer([ 77 }]);
59 { -  
60 name: 'commonName', 78 cert.setIssuer([{
61 value: name 79 name: 'commonName',
62 }, 80 value: domain
63 { -  
64 name: 'organizationName', 81 }, {
Line 65... Line 82...
65 value: name 82 name: 'organizationName',
-   83 value: name
66 } 84 }]);
-   85  
-   86 // Self-sign certificate.
-   87 cert.sign(
-   88 keys.privateKey,
-   89 forge
Line 67... Line 90...
67 ]); 90 .md
68   91 .sha256
-   92 .create()
-   93 );
69 // Self-sign certificate. 94  
-   95 // Return PEM-format keys and certificates.
-   96 return {
-   97 privateKey: forge
-   98 .pki
-   99 .privateKeyToPem(
70 cert.sign(keys.privateKey); 100 keys
-   101 .privateKey
-   102 ),
-   103 publicKey: forge
-   104 .pki
-   105 .publicKeyToPem(
71   106 keys
72 // Return PEM-format keys and certificates. 107 .publicKey
73 return { 108 ),
Line 74... Line 109...
74 privateKey: forge.pki.privateKeyToPem(keys.privateKey), 109 certificate: forge
75 publicKey: forge.pki.publicKeyToPem(keys.publicKey), 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',
Line 99... Line 134...
99 }) 134 })
100 ], 135 ],
101 exitOnError: false 136 exitOnError: false
102 }); 137 });
Line 103... Line 138...
103   138  
-   139 function handleClient(request, response, documentRoot) {
-   140 const requestAddress = request.socket.address();
-   141 const requestedURL = url.parse(request.url, true);
-   142  
-   143 log.info('Client: ' +
-   144 requestAddress.address + ':' +
-   145 requestAddress.port +
-   146 ' accessing: ' +
-   147 requestedURL.pathname
-   148 );
-   149  
104 fs.realpath(argv.root, (error, documentRoot) => { 150 const trimmedPath = requestedURL
-   151 .pathname
-   152 .split('/')
-   153 .filter(Boolean)
-   154 .join('/');
-   155 const filesystemPath = trimmedPath === '/' ?
-   156 path.join(documentRoot, trimmedPath) :
-   157 path.resolve(documentRoot, trimmedPath);
105 if (error) { 158  
-   159 if (!isRooted(filesystemPath, documentRoot, path.sep)) {
-   160 log.warn('Attempted path traversal: ' +
-   161 requestAddress.address + ':' +
-   162 requestAddress.port +
-   163 ' requesting: ' +
-   164 requestedURL.pathname
-   165 );
106 log.error('Could not find document root: ' + argv.root); 166 response.statusCode = 403;
-   167 response.end();
107 process.exit(1); 168 return;
Line 108... Line -...
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   169 }
117 // HTTPs server using digest authentication. 170  
118 https.createServer(authentication, { 171 fs.stat(filesystemPath, (error, stats) => {
119 key: certs.privateKey, -  
120 cert: certs.certificate, -  
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)) { 172 // Document does not exist.
133 log.warn('Attempted path traversal: ' + requestAddress.address + ':' + requestAddress.port + ' requesting: ' + requestedURL.pathname); 173 if (error) {
134 response.statusCode = 403; 174 response.statusCode = 404;
135 response.end(); 175 response.end();
Line 136... Line -...
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; 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) => { -  
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 } 182 fs.stat(documentRoot, (error, stats) => {
167   183 if (error) {
168 fs.access(filesystemPath, fs.constants.R_OK, (error) => { 184 fs.readdir(filesystemPath, (error, paths) => {
169 if (error) { 185 if (error) {
170 log.warn('The server was unable to access the filesystem path: ' + filesystemPath); 186 log.warn('Could not list directory: ' + filesystemPath);
171 response.statusCode = 403; 187 response.statusCode = 500;
172 response.end(); -  
173 return; -  
174 } 188 response.end();
175   -  
176 // Set MIME content type. -  
177 response.setHeader('Content-Type', mime.lookup(documentRoot)); -  
178   189 return;
179 var readStream = fs.createReadStream(documentRoot) 190 }
180 .on('open', () => { -  
181 response.statusCode = 200; -  
182 readStream.pipe(response); -  
183 }) 191 log.info('Directory listing requested for: ' + filesystemPath);
184 .on('error', () => { -  
185 response.statusCode = 500; -  
186 response.end(); 192 response.statusCode = 200;
Line 187... Line 193...
187 }); 193 response.write(JSON.stringify(paths));
188   194 response.end();
189 }); -  
190   -  
-   195 });
191 }); 196  
192 break; 197 return;
-   198 }
193 default: // Browser requesting file. 199  
194 // Check if the file is accessible. 200 fs.access(filesystemPath, fs.constants.R_OK, (error) => {
195 fs.access(filesystemPath, fs.constants.R_OK, (error) => { 201 if (error) {
196 if (error) { 202 log.warn('The server was unable to access the filesystem path: ' + filesystemPath);
Line -... Line 203...
-   203 response.statusCode = 403;
197 response.statusCode = 403; 204 response.end();
Line 198... Line 205...
198 response.end(); 205 return;
199 return; 206 }
200 } 207  
201   208 // Set MIME content type.
202 response.setHeader('Content-Type', mime.lookup(filesystemPath)); 209 response.setHeader('Content-Type', mime.lookup(documentRoot));
203   210  
204 var readStream = fs.createReadStream(filesystemPath) 211 var readStream = fs.createReadStream(documentRoot)
205 .on('open', () => { 212 .on('open', () => {
206 response.statusCode = 200; 213 response.statusCode = 200;
Line 207... Line 214...
207 readStream.pipe(response); 214 readStream.pipe(response);
208 }) -  
209 .on('error', () => { -  
210 response.statusCode = 500; -  
Line -... Line 215...
-   215 })
-   216 .on('error', () => {
-   217 response.statusCode = 500;
-   218 response.end();
-   219 });
-   220  
-   221 });
-   222  
-   223 });
-   224 break;
-   225 default: // Browser requesting file.
-   226 // Check if the file is accessible.
-   227 fs.access(filesystemPath, fs.constants.R_OK, (error) => {
-   228 if (error) {
-   229 response.statusCode = 403;
-   230 response.end();
-   231 return;
-   232 }
211 response.end(); 233  
-   234 response.setHeader('Content-Type', mime.lookup(filesystemPath));
-   235  
-   236 var readStream = fs.createReadStream(filesystemPath)
-   237 .on('open', () => {
-   238 response.statusCode = 200;
-   239 readStream.pipe(response);
-   240 })
-   241 .on('error', () => {
-   242 response.statusCode = 500;
-   243 response.end();
-   244 });
-   245  
212 }); 246 });
-   247 break;
-   248 }
-   249 });
-   250 }
-   251  
-   252 fs.realpath(argv.root, (error, documentRoot) => {
-   253 if (error) {
213   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) =>
-   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) =>
-   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 +
214 }); 300 ' and port: ' +