node-http-server – Diff between revs 27 and 29

Subversion Repositories:
Rev:
Show entire fileIgnore whitespace
Rev 27 Rev 29
Line 7... Line 7...
7 const url = require('url'); 7 const url = require('url');
8 const path = require('path'); 8 const path = require('path');
9 const fs = require('fs'); 9 const fs = require('fs');
10 const mime = require('mime'); 10 const mime = require('mime');
11 const auth = require("http-auth"); 11 const auth = require("http-auth");
-   12 const JSONStream = require('JSONStream');
Line 12... Line 13...
12   13  
13 // Checks whether userPath is a child of rootPath. 14 // Checks whether userPath is a child of rootPath.
14 function isRooted(userPath, rootPath, separator, callback) { 15 function isRooted(userPath, rootPath, separator, callback) {
15 userPath = userPath.split(separator).filter(Boolean); 16 userPath = userPath.split(separator).filter(Boolean);
16 rootPath = rootPath.split(separator).filter(Boolean); 17 rootPath = rootPath.split(separator).filter(Boolean);
17 callback(userPath.length >= rootPath.length && 18 callback(userPath.length >= rootPath.length &&
18 rootPath.every((e, i) => e === userPath[i])); 19 rootPath.every((e, i) => e === userPath[i]));
Line 19... Line 20...
19 } 20 }
20   21  
21 // Serves files. 22 // Serves files.
22 function files(config, request, response, requestPath, callback) { 23 function files(config, file, client, callback) {
23 // Check if the file is accessible. 24 // Check if the file is accessible.
24 fs.access(requestPath, fs.constants.R_OK, (error) => { 25 fs.access(file, fs.constants.R_OK, (error) => {
25 if (error) { 26 if (error) {
26 process.nextTick(() => { 27 process.nextTick(() => {
27 const requestAddress = request.socket.address(); 28 callback({
28 callback('Client: ' + 29 message: 'Client: ' +
29 requestAddress.address + ':' + 30 client.address + ':' +
30 requestAddress.port + 31 client.port +
31 ' requesting inaccessible path: ' + 32 ' requesting inaccessible path: ' +
-   33 file,
-   34 severity: 'warning',
32 requestPath, 35 status: 403
33 module.exports.error.level.WARN 36
34 ); -  
35 }); -  
36 response.statusCode = 403; 37 });
37 response.end(); 38 });
38 return; -  
39 } 39 return;
40   40 }
41 response.setHeader( 41 process.nextTick(() => {
42 'Content-Type', -  
43 mime.lookup(requestPath) -  
44 ); 42 callback({
45   43 message: 'Client: ' +
46 var readStream = fs.createReadStream(requestPath) 44 client.address + ':' +
-   45 client.port +
47 .on('open', () => { 46 ' sent file: ' +
-   47 file,
48 response.statusCode = 200; 48 severity: 'info',
49 readStream.pipe(response); 49 status: 200,
50 }) 50 data: fs
51 .on('error', () => { 51 .createReadStream(file),
52 response.statusCode = 500; 52 type: mime
-   53 .lookup(file)
Line 53... Line 54...
53 response.end(); 54 });
54 }); 55 });
Line 55... Line 56...
55   56  
56 }); 57 });
57 } 58 }
58   59  
59 // Serves a directory listing or the document index in case it exists. 60 // Serves a directory listing or the document index in case it exists.
60 function index(config, request, response, requestPath, requestURL, callback) { 61 function index(config, directory, href, client, callback) {
61 const root = path.resolve(requestPath, config.site.index); 62 const root = path.resolve(directory, config.site.index);
62 fs.stat(root, (error, stats) => { 63 fs.stat(root, (error, stats) => {
63 if (error) { 64 if (error) {
64 if (config.site.indexing 65 if (config.site.indexing
65 .some((directory) => 66 .some((directory) =>
66 directory.toUpperCase() === requestURL.toUpperCase())) { 67 directory.toUpperCase() === href.toUpperCase())) {
67 fs.readdir(requestPath, (error, paths) => { 68 fs.readdir(directory, (error, paths) => {
68 if (error) { 69 if (error) {
69 process.nextTick(() => { 70 process.nextTick(() => {
70 const requestAddress = request.socket.address(); 71 callback({
71 callback('Client: ' + 72 message: 'Client: ' +
72 requestAddress.address + ':' + 73 client.address + ':' +
-   74 client.port +
73 requestAddress.port + 75 ' could not access directory: ' +
74 ' could not access directory: ' + 76 directory,
75 requestPath, -  
76 module.exports.error.level.WARN -  
77 ); 77 severity: 'warning',
78 }); 78 status: 500
79 response.statusCode = 500; 79 });
80 response.end(); 80 });
81 return; 81 return;
82 } 82 }
83 process.nextTick(() => { 83 process.nextTick(() => {
84 const requestAddress = request.socket.address(); 84 callback({
85 callback('Client: ' + 85 message: 'Client: ' +
-   86 client.address + ':' +
-   87 client.port +
86 requestAddress.address + ':' + 88 ' accessed directory listing: ' +
87 requestAddress.port + 89 directory,
88 ' accessed directory listing: ' + 90 severity: 'warning',
89 requestPath, -  
90 module.exports.error.level.WARN -  
91 ); -  
92 }); 91 status: 200,
93 response.statusCode = 200; -  
94 response.write(JSON.stringify(paths)); 92 data: JSONStream.parse(paths)
95 response.end(); 93 });
-   94 });
96 }); 95 });
97   96 return;
98 return; 97 }
99 } 98 // Could not access directory index file and directory listing not allowed.
100 process.nextTick(() => { 99 process.nextTick(() => {
101 const requestAddress = request.socket.address(); 100 callback({
102 callback('Client: ' + 101 message: 'Client: ' +
103 requestAddress.address + ':' + 102 client.address + ':' +
-   103 client.port +
104 requestAddress.port + 104 ' no index file found and accessing forbiden index: ' +
105 ' accessing forbiden index: ' + 105 href,
106 requestURL, -  
107 module.exports.error.level.WARN -  
108 ); -  
109 }); 106 severity: 'warning',
Line 110... Line 107...
110 // Could not access directory index file and directory listing not allowed. 107 status: 400
Line 111... Line 108...
111 response.statusCode = 404; 108 });
112 response.end(); 109 });
113 return; 110 return;
114   111  
115 } 112 }
116   113  
117 // Serve the document index. 114 // Serve the document index.
118 fs.access(root, fs.constants.R_OK, (error) => { 115 fs.access(root, fs.constants.R_OK, (error) => {
119 if (error) { 116 if (error) {
120 process.nextTick(() => { 117 process.nextTick(() => {
121 const requestAddress = request.socket.address(); 118 callback({
-   119 message: 'Client: ' +
122 callback('Client: ' + 120 client.address + ':' +
123 requestAddress.address + ':' + 121 client.port +
124 requestAddress.port + -  
125 ' unable to access path: ' + -  
126 requestPath, 122 ' unable to access path: ' +
127 module.exports.error.level.WARN 123 directory,
128 ); -  
129 }); 124 severity: 'warning',
130 response.statusCode = 403; 125 status: 403
131 response.end(); 126 });
132 return; 127 });
133 } -  
134   -  
135 // Set MIME content type. -  
136 response.setHeader( 128 return;
137 'Content-Type', 129 }
138 mime.lookup(root) -  
139 ); 130 process.nextTick(() => {
140   131 callback({
141 var readStream = fs.createReadStream(root) 132 message: 'Client: ' +
-   133 client.address + ':' +
142 .on('open', () => { 134 client.port +
143 response.statusCode = 200; 135 ' sent file: ' +
144 readStream.pipe(response); 136 root,
145 }) 137 severity: 'info',
146 .on('error', () => { 138 status: 200,
147 response.statusCode = 500; 139 data: fs.createReadStream(root),
Line 148... Line 140...
148 response.end(); 140 type: mime.lookup(root)
149 }); 141 });
150   142 });
151 }); 143 });
152 }); 144 });
-   145 }
-   146  
153 } 147 // Determines whether the requested filesystem request path is a directory or a file.
154   148 function serve(config, local, href, address, callback) {
-   149 fs.stat(local, (error, stats) => {
-   150 // Document does not exist.
-   151 if (error) {
-   152 callback({
-   153 message: 'Client: ' +
155 // Determines whether the requested filesystem request path is a directory or a file. 154 address.address + ':' +
156 function serve(config, request, response, requestPath, requestURL, callback) { 155 address.port +
Line 157... Line 156...
157 fs.stat(requestPath, (error, stats) => { 156 ' accessing non-existent document: ' +
158 // Document does not exist. 157 local,
159 if (error) { 158 severity: 'warning',
160 response.statusCode = 404; 159 status: 404
161 response.end(); 160 });
162 return; 161 return;
163 } 162 }
Line 164... Line 163...
164   163  
165 if (stats.isDirectory()) { 164 if (stats.isDirectory()) {
166 // Directory is requested so provide directory indexes. 165 // Directory is requested so provide directory indexes.
167 index(config, request, response, requestPath, requestURL, callback); 166 index(config, local, href, address, callback);
168 return; 167 return;
169 } 168 }
170 if (stats.isFile()) { 169 if (stats.isFile()) {
171 const file = path.parse(requestPath).base; 170 const file = path.parse(local).base;
172   171  
173 // If the file matches the reject list or is not in the accept list, 172 // If the file matches the reject list or is not in the accept list,
174 // then there is no file to serve. 173 // then there is no file to serve.
175 if (config.site.reject.some((expression) => expression.test(file)) || 174 if (config.site.reject.some((expression) => expression.test(file)) ||
-   175 !config.site.accept.some((expression) => expression.test(file))) {
176 !config.site.accept.some((expression) => expression.test(file))) { 176 process.nextTick(() => {
177 process.nextTick(() => { 177 callback({
178 const requestAddress = request.socket.address(); -  
179 callback('Client: ' + -  
180 requestAddress.address + ':' + 178 message: 'Client: ' +
181 requestAddress.port + 179 address.address + ':' +
Line 182... Line 180...
182 ' requested disallowed file: ' + 180 address.port +
183 file, 181 ' requested disallowed file: ' +
184 module.exports.error.level.WARN 182 file,
185 ); 183 severity: 'warning',
186 }); 184 status: 404
Line 187... Line 185...
187 response.statusCode = 404; 185 });
188 response.end(); -  
189 return; -  
190 } -  
191   -  
192 // A file was requested so provide the file. -  
193 files(config, request, response, requestPath, callback); -  
194 } -  
195 }); 186 });
196 } 187 return;
197   188 }
198 module.exports = { 189  
199 error: { 190 // A file was requested so provide the file.
Line 226... Line 217...
226 config.site.rewrite[key], key 217 config.site.rewrite[key], key
227 ), 218 ),
228 true 219 true
229 ) 220 )
230 .pathname; 221 .pathname;
-   222 callback({
231 callback('Rewrite path: ' + 223 message: 'Rewrite path: ' +
232 originalPath + 224 originalPath +
233 ' to: ' + 225 ' to: ' +
234 requestURL.path, 226 requestURL.path,
235 module.exports.error.level.INFO 227 severity: 'info'
236 ); 228 });
237 } 229 }
238 }); 230 });
Line 239... Line 231...
239   231  
240 const trimmedPath = requestURL 232 const trimmedPath = requestURL
Line 247... Line 239...
247 path.resolve(root, trimmedPath); 239 path.resolve(root, trimmedPath);
Line 248... Line 240...
248   240  
249 fs.realpath(requestPath, (error, resolvedPath) => { 241 fs.realpath(requestPath, (error, resolvedPath) => {
250 // If the path does not exist, then return early. 242 // If the path does not exist, then return early.
-   243 if (error) {
-   244 process.nextTick(() => {
251 if (error) { 245 callback({
252 callback('Unknown path requested: ' + 246 message: 'Unknown path requested: ' +
253 requestAddress.address + ':' + 247 requestAddress.address + ':' +
254 requestAddress.port + 248 requestAddress.port +
255 ' requesting: ' + 249 ' requesting: ' +
256 requestURL.pathname, 250 requestURL.pathname,
-   251 severity: 'warning',
257 module.exports.error.level.WARN 252 status: 404
258 ); -  
259 response.statusCode = 404; 253 });
260 response.end(); 254 });
261 return; 255 return;
262 } 256 }
263 // Check for path traversals early on and bail if the requested path does not 257 // Check for path traversals early on and bail if the requested path does not
264 // lie within the specified document root. 258 // lie within the specified document root.
265 isRooted(resolvedPath, root, path.sep, (rooted) => { 259 isRooted(resolvedPath, root, path.sep, (rooted) => {
266 if (!rooted) { 260 if (!rooted) {
-   261 process.nextTick(() => {
267 process.nextTick(() => { 262 callback({
268 callback('Attempted path traversal: ' + 263 message: 'Attempted path traversal: ' +
269 requestAddress.address + ':' + 264 requestAddress.address + ':' +
270 requestAddress.port + 265 requestAddress.port +
271 ' requesting: ' + 266 ' requesting: ' +
272 requestURL.pathname, 267 requestURL.pathname,
-   268 severity: 'warning',
273 module.exports.error.level.WARN 269 status: 404
274 ); 270 });
275 }); -  
276 response.statusCode = 404; -  
277 response.end(); 271 });
278 return; 272 return;
Line 279... Line 273...
279 } 273 }
280   274  
Line 290... Line 284...
290 ) 284 )
291 }); 285 });
292 // Requested location requires authentication. 286 // Requested location requires authentication.
293 authentication.check(request, response, (request, response) => { 287 authentication.check(request, response, (request, response) => {
294 process.nextTick(() => { 288 process.nextTick(() => {
-   289 callback({
295 callback('Authenticated client: ' + 290 message: 'Authenticated client: ' +
296 requestAddress.address + ':' + 291 requestAddress.address + ':' +
297 requestAddress.port + 292 requestAddress.port +
298 ' accessing: ' + 293 ' accessing: ' +
299 requestURL.pathname, 294 requestURL.pathname,
300 module.exports.error.level.INFO 295 severity: 'info'
301 ); 296 });
302 }); 297 });
303 serve(config, 298 serve(config,
304 request, -  
305 response, -  
306 requestPath, 299 requestPath,
307 requestURL.pathname, 300 requestURL.pathname,
-   301 requestAddress,
308 callback 302 callback
309 ); 303 );
310 }); 304 });
311 return; 305 return;
312 } 306 }
Line 313... Line 307...
313   307  
314 // If no authentication is required then serve the request. 308 // If no authentication is required then serve the request.
-   309 process.nextTick(() => {
315 process.nextTick(() => { 310 callback({
316 callback('Client: ' + 311 message: 'Client: ' +
317 requestAddress.address + ':' + 312 requestAddress.address + ':' +
318 requestAddress.port + 313 requestAddress.port +
319 ' accessing: ' + 314 ' accessing: ' +
320 requestURL.pathname, 315 requestURL.pathname,
321 module.exports.error.level.INFO 316 severity: 'info'
322 ); 317 });
323 }); 318 });
324 serve(config, -  
325 request, -  
326 response, 319 serve(config,
327 requestPath, 320 requestPath,
-   321 requestURL.pathname,
328 requestURL.pathname, 322 requestAddress,
329 callback 323 callback
330 ); 324 );
331 }); 325 });
332 }); 326 });