node-http-server – Diff between revs 6 and 7
?pathlinks?
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 |
|
- | 63 | .validity |
||
- | 64 | .notAfter |
||
- | 65 | .setFullYear( |
||
- | 66 | cert |
||
46 | cert.validity.notBefore = new Date(); |
67 | .validity |
|
- | 68 | .notBefore |
||
47 | cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1); |
69 | .getFullYear() + 1 |
|
48 | cert.setSubject([ |
70 | ); |
|
49 | { |
- | ||
50 | name: 'commonName', |
71 | cert.setSubject([{ |
|
51 | value: domain |
72 | name: 'commonName', |
|
52 | }, |
73 | value: domain |
|
53 | { |
- | ||
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 | |
|
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(); |
|
Line -... | Line 141... | |||
- | 141 | const requestedURL = url.parse(request.url, true); |
||
123 | const requestedURL = url.parse(request.url, true); |
142 | |
|
- | 143 | log.info('Client: ' + |
||
- | 144 | requestAddress.address + ':' + |
||
- | 145 | requestAddress.port + |
||
- | 146 | ' accessing: ' + |
||
124 | |
147 | requestedURL.pathname |
|
125 | log.info('Client: ' + requestAddress.address + ':' + requestAddress.port + ' accessing: ' + requestedURL.pathname); |
148 | ); |
|
- | 149 | |
||
- | 150 | const trimmedPath = requestedURL |
||
- | 151 | .pathname |
||
- | 152 | .split('/') |
||
126 | |
153 | .filter(Boolean) |
|
127 | const trimmedPath = requestedURL.pathname.split('/').filter(Boolean).join('/'); |
154 | .join('/'); |
|
128 | const filesystemPath = trimmedPath === '/' ? |
155 | const filesystemPath = trimmedPath === '/' ? |
|
Line 129... | Line 156... | |||
129 | path.join(documentRoot, trimmedPath) : |
156 | path.join(documentRoot, trimmedPath) : |
|
130 | path.resolve(documentRoot, trimmedPath); |
157 | path.resolve(documentRoot, trimmedPath); |
|
- | 158 | |
||
- | 159 | if (!isRooted(filesystemPath, documentRoot, path.sep)) { |
||
- | 160 | log.warn('Attempted path traversal: ' + |
||
- | 161 | requestAddress.address + ':' + |
||
- | 162 | requestAddress.port + |
||
131 | |
163 | ' requesting: ' + |
|
132 | if (!isRooted(filesystemPath, documentRoot, path.sep)) { |
164 | requestedURL.pathname |
|
133 | log.warn('Attempted path traversal: ' + requestAddress.address + ':' + requestAddress.port + ' requesting: ' + requestedURL.pathname); |
165 | ); |
|
134 | response.statusCode = 403; |
166 | response.statusCode = 403; |
|
Line 143... | Line 175... | |||
143 | response.end(); |
175 | response.end(); |
|
144 | return; |
176 | return; |
|
145 | } |
177 | } |
|
Line 146... | Line 178... | |||
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) { |
|
Line 213... | Line 245... | |||
213 | |
245 | |
|
214 | }); |
246 | }); |
|
215 | break; |
247 | break; |
|
216 | } |
248 | } |
|
- | 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); |
||
Line -... | Line 256... | |||
- | 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, |
||
217 | }); |
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 | }, |
||
218 | |
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 + |
||
- | 300 | ' and port: ' + |
||
- | 301 | config.net.port + |
||
- | 302 | ' whilst serving files from: ' + |
||
219 | }).listen(config.port, config.address, () => { |
303 | documentRoot |
|
- | 304 | ); |
||
- | 305 | }); |
||
220 | log.info('Server is listening on: ' + config.address + ' and port: ' + config.port + ' whilst serving files from: ' + documentRoot); |
306 | })(); |