node-http-server – Blame information for rev 16

Subversion Repositories:
Rev:
Rev Author Line No. Line
8 office 1 #!/usr/bin/env node
2  
9 office 3 /*************************************************************************/
4 /* Copyright (C) 2017 Wizardry and Steamworks - License: GNU GPLv3 */
5 /*************************************************************************/
6  
8 office 7 const url = require('url');
8 const path = require('path');
9 const fs = require('fs');
10 const mime = require('mime');
14 office 11 const auth = require("http-auth");
8 office 12  
14 office 13 // Checks whether userPath is a child of rootPath.
15 office 14 function isRooted(userPath, rootPath, separator, callback) {
15 process.nextTick(() => {
16 userPath = userPath.split(separator).filter(Boolean);
17 rootPath = rootPath.split(separator).filter(Boolean);
18 callback(userPath.length >= rootPath.length &&
19 rootPath.every((e, i) => e === userPath[i]));
20 });
8 office 21 }
22  
14 office 23 // Serves files.
24 function files(config, request, response, resource, callback) {
25 // Check if the file is accessible.
15 office 26 fs.access(resource, fs.constants.R_OK, (error) => {
27 if (error) {
28 response.statusCode = 403;
29 response.end();
30 return;
31 }
32  
33 response.setHeader(
34 'Content-Type',
35 mime.lookup(resource)
36 );
37  
38 var readStream = fs.createReadStream(resource)
39 .on('open', () => {
40 response.statusCode = 200;
41 readStream.pipe(response);
42 })
43 .on('error', () => {
44 response.statusCode = 500;
45 response.end();
46 });
47  
48 });
49 }
50  
51 // Serves a directory listing or the document index in case it exists.
52 function index(config, request, response, resource, root, callback) {
16 office 53 const root = path.resolve(resource, config.site.index);
15 office 54 fs.stat(root, (error, stats) => {
16 office 55 if (error && config.site.indexing
56 .some((directory) => directory.toUpperCase() == resource.toUpperCase())) {
15 office 57 fs.readdir(resource, (error, paths) => {
58 if (error) {
59 process.nextTick(() => {
60 callback('Could not list directory: ' +
61 resource,
62 module.exports.error.level.ERROR
63 );
64 });
65 response.statusCode = 500;
66 response.end();
67 return;
68 }
69 process.nextTick(() => {
70 callback('Directory listing requested for: ' +
71 resource,
72 module.exports.error.level.INFO
73 );
74 });
75 response.statusCode = 200;
76 response.write(JSON.stringify(paths));
77 response.end();
78 });
79  
80 return;
81 }
82  
83 // Serve the document index.
14 office 84 fs.access(resource, fs.constants.R_OK, (error) => {
85 if (error) {
15 office 86 process.nextTick(() => {
87 callback('The server was unable to access the filesystem path: ' +
88 resource,
89 module.exports.error.level.WARN
90 );
91 });
14 office 92 response.statusCode = 403;
93 response.end();
94 return;
95 }
96  
15 office 97 // Set MIME content type.
14 office 98 response.setHeader(
99 'Content-Type',
15 office 100 mime.lookup(root)
14 office 101 );
102  
15 office 103 var readStream = fs.createReadStream(root)
14 office 104 .on('open', () => {
105 response.statusCode = 200;
106 readStream.pipe(response);
107 })
108 .on('error', () => {
109 response.statusCode = 500;
110 response.end();
111 });
112  
113 });
114 });
115 }
116  
117 // Determines whether the requested resource is a directory or a file.
118 function serve(config, request, response, resource, callback) {
15 office 119 fs.stat(resource, (error, stats) => {
120 // Document does not exist.
121 if (error) {
122 response.statusCode = 404;
123 response.end();
124 return;
125 }
14 office 126  
15 office 127 switch (stats.isDirectory()) {
128 case true: // Directory is requested so provide directory indexes.
16 office 129 index(config, request, response, resource, callback);
15 office 130 break;
131 default: // Browser requesting file.
132 files(config, request, response, resource, callback);
133 break;
134 }
14 office 135 });
136 }
137  
8 office 138 module.exports = {
139 error: {
140 level: {
141 INFO: 1,
142 WARN: 2,
143 ERROR: 3
144 }
145 },
11 office 146 process: (config, request, response, root, callback) => {
8 office 147 process.nextTick(() => {
14 office 148 // Create digest authentication.
149 const authentication = auth.digest({
150 realm: config.auth.realm,
151 file: path.resolve(
152 path.dirname(require.main.filename),
153 config.auth.digest
154 )
155 });
156  
8 office 157 const requestAddress = request.socket.address();
14 office 158 const requestedURL = url.parse(
159 request.url, true
160 );
8 office 161  
162 const trimmedPath = requestedURL
163 .pathname
164 .split('/')
165 .filter(Boolean)
166 .join('/');
14 office 167 const resource = trimmedPath === '/' ?
8 office 168 path.join(root, trimmedPath) :
169 path.resolve(root, trimmedPath);
170  
15 office 171 isRooted(resource, root, path.sep, (rooted) => {
172 if (!rooted) {
173 process.nextTick(() => {
174 callback('Attempted path traversal: ' +
175 requestAddress.address + ':' +
176 requestAddress.port +
177 ' requesting: ' +
178 requestedURL.pathname,
179 module.exports.error.level.WARN
180 );
181 });
182 response.statusCode = 404;
183 response.end();
184 return;
185 }
8 office 186  
15 office 187 // Check if the requested path requires authentication.
188 switch (config.auth.locations.some(
189 (authPath) => authPath.toUpperCase() === requestedURL.pathname.toUpperCase())) {
190 case true:
191 // Requested location requires authentication.
192 authentication.check(request, response, (request, response) => {
193 process.nextTick(() => {
194 callback('Authenticated client: ' +
195 requestAddress.address + ':' +
196 requestAddress.port +
197 ' accessing: ' +
198 requestedURL.pathname,
199 module.exports.error.level.INFO
200 );
201 });
202 serve(config, request, response, resource, callback);
203 });
204 break;
205 default:
14 office 206 process.nextTick(() => {
15 office 207 callback('Client: ' +
14 office 208 requestAddress.address + ':' +
209 requestAddress.port +
210 ' accessing: ' +
211 requestedURL.pathname,
212 module.exports.error.level.INFO
213 );
8 office 214 });
14 office 215 serve(config, request, response, resource, callback);
15 office 216 break;
217 }
218 });
8 office 219 });
220 }
221 };