node-http-server – Blame information for rev 1

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