node-http-server – Blame information for rev 9

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');
11  
12 // Check for path traversal.
13 function isRooted(userPath, rootPath, separator) {
14 userPath = userPath.split(separator).filter(Boolean);
15 rootPath = rootPath.split(separator).filter(Boolean);
16 return userPath.length >= rootPath.length &&
17 rootPath.every((e, i) => e === userPath[i]);
18 }
19  
20 module.exports = {
21 error: {
22 level: {
23 INFO: 1,
24 WARN: 2,
25 ERROR: 3
26 }
27 },
28 handleClient: (config, request, response, root, callback) => {
29 process.nextTick(() => {
30 const requestAddress = request.socket.address();
31 const requestedURL = url.parse(request.url, true);
32  
33 callback('Client: ' +
34 requestAddress.address + ':' +
35 requestAddress.port +
36 ' accessing: ' +
37 requestedURL.pathname,
38 module.exports.error.level.INFO
39 );
40  
41 const trimmedPath = requestedURL
42 .pathname
43 .split('/')
44 .filter(Boolean)
45 .join('/');
46 const filesystemPath = trimmedPath === '/' ?
47 path.join(root, trimmedPath) :
48 path.resolve(root, trimmedPath);
49  
50 if (!isRooted(filesystemPath, root, path.sep)) {
51 callback('Attempted path traversal: ' +
52 requestAddress.address + ':' +
53 requestAddress.port +
54 ' requesting: ' +
55 requestedURL.pathname,
56 module.exports.error.level.WARN
57 );
58 response.statusCode = 403;
59 response.end();
60 return;
61 }
62  
63 fs.stat(filesystemPath, (error, stats) => {
64 // Document does not exist.
65 if (error) {
66 response.statusCode = 404;
67 response.end();
68 return;
69 }
70  
71 switch (stats.isDirectory()) {
72 case true: // Directory is requested so provide directory indexes.
73 const root = path.resolve(filesystemPath, config.site.index);
74 fs.stat(root, (error, stats) => {
75 if (error) {
76 fs.readdir(filesystemPath, (error, paths) => {
77 if (error) {
78 callback('Could not list directory: ' +
79 filesystemPath,
80 module.exports.error.level.ERROR
81 );
82 response.statusCode = 500;
83 response.end();
84 return;
85 }
86 callback('Directory listing requested for: ' +
87 filesystemPath,
88 module.exports.error.level.INFO
89 );
90 response.statusCode = 200;
91 response.write(JSON.stringify(paths));
92 response.end();
93 });
94  
95 return;
96 }
97  
98 fs.access(filesystemPath, fs.constants.R_OK, (error) => {
99 if (error) {
100 callback('The server was unable to access the filesystem path: ' +
101 filesystemPath,
102 module.exports.error.level.WARN
103 );
104 response.statusCode = 403;
105 response.end();
106 return;
107 }
108  
109 // Set MIME content type.
110 response.setHeader('Content-Type', mime.lookup(root));
111  
112 var readStream = fs.createReadStream(root)
113 .on('open', () => {
114 response.statusCode = 200;
115 readStream.pipe(response);
116 })
117 .on('error', () => {
118 response.statusCode = 500;
119 response.end();
120 });
121  
122 });
123  
124 });
125 break;
126 default: // Browser requesting file.
127 // Check if the file is accessible.
128 fs.access(filesystemPath, fs.constants.R_OK, (error) => {
129 if (error) {
130 response.statusCode = 403;
131 response.end();
132 return;
133 }
134  
135 response.setHeader('Content-Type', mime.lookup(filesystemPath));
136  
137 var readStream = fs.createReadStream(filesystemPath)
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 break;
149 }
150 })
151 });
152 }
153 };