node-http-server – Diff between revs 30 and 31

Subversion Repositories:
Rev:
Show entire fileIgnore whitespace
Rev 30 Rev 31
Line 8... Line 8...
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 stream = require('stream'); 12 const stream = require('stream');
-   13 const util = require('util');
-   14 const EventEmitter = require('events').EventEmitter;
Line 13... Line 15...
13   15  
14 // Checks whether userPath is a child of rootPath. 16 // Checks whether userPath is a child of rootPath.
15 function isRooted(userPath, rootPath, separator, callback) { 17 function isRooted(userPath, rootPath, separator, callback) {
16 userPath = userPath.split(separator).filter(Boolean); 18 userPath = userPath.split(separator).filter(Boolean);
17 rootPath = rootPath.split(separator).filter(Boolean); 19 rootPath = rootPath.split(separator).filter(Boolean);
18 callback(userPath.length >= rootPath.length && 20 callback(userPath.length >= rootPath.length &&
19 rootPath.every((e, i) => e === userPath[i])); 21 rootPath.every((e, i) => e === userPath[i]));
Line 20... Line 22...
20 } 22 }
21   23  
22 // Serves files. 24 // Serves files.
23 function files(config, file, client, callback) { 25 function files(self, config, file, client) {
24 // Check if the file is accessible. 26 // Check if the file is accessible.
25 fs.access(file, fs.constants.R_OK, (error) => { 27 fs.access(file, fs.constants.R_OK, (error) => {
26 if (error) { 28 if (error) {
27 callback({ 29 self.emit('log', {
28 message: 'Client: ' + 30 message: 'Client: ' +
29 client.address + ':' + 31 client.address + ':' +
30 client.port + 32 client.port +
31 ' requesting inaccessible path: ' + 33 ' requesting inaccessible path: ' +
32 file, 34 file,
-   35 severity: 'warning'
33 severity: 'warning' 36 });
34 }, { 37 self.emit('data', {
35 status: 403, 38 status: 403,
36 data: new stream.Readable({ 39 data: new stream.Readable({
37 read(size) { 40 read(size) {
38 this.push(null); 41 this.push(null);
39 } 42 }
40 }), 43 }),
41 type: 'text/plain' 44 type: 'text/plain'
42 }); 45 });
43 return; 46 return;
44 } 47 }
45 callback({ 48 self.emit('log', {
46 message: 'Client: ' + 49 message: 'Client: ' +
47 client.address + ':' + 50 client.address + ':' +
48 client.port + 51 client.port +
49 ' sent file: ' + 52 ' sent file: ' +
50 file, 53 file,
-   54 severity: 'info'
51 severity: 'info' 55 });
52 }, { 56 self.emit('data', {
53 status: 200, 57 status: 200,
54 data: fs 58 data: fs
55 .createReadStream(file), 59 .createReadStream(file),
56 type: mime 60 type: mime
57 .lookup(file) 61 .lookup(file)
58 }); 62 });
Line 59... Line 63...
59 }); 63 });
60 } 64 }
61   65  
62 // Serves a directory listing or the document index in case it exists. 66 // Serves a directory listing or the document index in case it exists.
63 function index(config, directory, href, client, callback) { 67 function index(self, config, directory, href, client) {
64 const root = path.resolve(directory, config.site.index); 68 const root = path.resolve(directory, config.site.index);
65 fs.stat(root, (error, stats) => { 69 fs.stat(root, (error, stats) => {
66 if (error) { 70 if (error) {
67 if (config.site.indexing 71 if (config.site.indexing
68 .some((directory) => 72 .some((directory) =>
69 directory.toUpperCase() === href.toUpperCase())) { -  
70 fs.readdir(directory, (error, paths) => { 73 directory.toUpperCase() === href.toUpperCase())) {
71 if (error) { 74 fs.readdir(directory, (error, paths) => {
72 console.log("listing forbidden..."); 75 if (error) {
73 callback({ 76 self.emit('log', {
74 message: 'Client: ' + 77 message: 'Client: ' +
75 client.address + ':' + 78 client.address + ':' +
76 client.port + 79 client.port +
77 ' could not access directory: ' + 80 ' could not access directory: ' +
-   81 directory,
78 directory, 82 severity: 'warning'
79 severity: 'warning' 83 });
80 }, { 84 self.emit('data', {
81 status: 500, 85 status: 500,
82 data: new stream.Readable({ 86 data: new stream.Readable({
83 read(size) { 87 read(size) {
84 this.push(null); 88 this.push(null);
85 } 89 }
86 }), 90 }),
87 type: 'text/plain' 91 type: 'text/plain'
88 }); -  
89 return; 92 });
90 } 93 return;
91 console.log("sending listing..."); 94 }
92 callback({ 95 self.emit('log', {
93 message: 'Client: ' + 96 message: 'Client: ' +
94 client.address + ':' + 97 client.address + ':' +
95 client.port + 98 client.port +
96 ' accessed directory listing: ' + 99 ' accessed directory listing: ' +
-   100 directory,
97 directory, 101 severity: 'info'
98 severity: 'info' 102 });
99 }, { 103 self.emit('data', {
100 status: 200, 104 status: 200,
101 data: new stream.Readable({ 105 data: new stream.Readable({
Line 108... Line 112...
108 }); 112 });
109 }); 113 });
110 return; 114 return;
111 } 115 }
112 // Could not access directory index file and directory listing not allowed. 116 // Could not access directory index file and directory listing not allowed.
113 console.log("no dirindex..."); -  
114 callback({ 117 self.emit('log', {
115 message: 'Client: ' + 118 message: 'Client: ' +
116 client.address + ':' + 119 client.address + ':' +
117 client.port + 120 client.port +
118 ' no index file found and accessing forbiden index: ' + 121 ' no index file found and accessing forbiden index: ' +
119 href, 122 href,
120 severity: 'warning' 123 severity: 'warning'
121 }, { 124 });
-   125 self.emit('data', {
122 status: 403, 126 status: 403,
123 data: new stream.Readable({ 127 data: new stream.Readable({
124 read(size) { 128 read(size) {
125 this.push(null); 129 this.push(null);
126 } 130 }
Line 131... Line 135...
131 } 135 }
Line 132... Line 136...
132   136  
133 // Serve the document index. 137 // Serve the document index.
134 fs.access(root, fs.constants.R_OK, (error) => { 138 fs.access(root, fs.constants.R_OK, (error) => {
135 if (error) { 139 if (error) {
136 callback({ 140 self.emit('log', {
137 message: 'Client: ' + 141 message: 'Client: ' +
138 client.address + ':' + 142 client.address + ':' +
139 client.port + 143 client.port +
140 ' unable to access path: ' + 144 ' unable to access path: ' +
141 directory, 145 directory,
142 severity: 'warning' 146 severity: 'warning'
-   147 });
143 }, { 148 self.emit('data', {
144 status: 403, 149 status: 403,
145 data: new stream.Readable({ 150 data: new stream.Readable({
146 read(size) { 151 read(size) {
147 this.push(null); 152 this.push(null);
148 } 153 }
149 }), 154 }),
150 type: 'text/plain' 155 type: 'text/plain'
151 }); 156 });
152 return; 157 return;
153 } 158 }
154 callback({ 159 self.emit('log', {
155 message: 'Client: ' + 160 message: 'Client: ' +
156 client.address + ':' + 161 client.address + ':' +
157 client.port + 162 client.port +
158 ' sent file: ' + 163 ' sent file: ' +
159 root, 164 root,
160 severity: 'info' 165 severity: 'info'
-   166 });
161 }, { 167 self.emit('data', {
162 status: 200, 168 status: 200,
163 data: fs.createReadStream(root), 169 data: fs.createReadStream(root),
164 type: mime.lookup(root) 170 type: mime.lookup(root)
165 }); 171 });
166 }); 172 });
167 }); 173 });
Line 168... Line 174...
168 } 174 }
169   175  
170 // Determines whether the requested filesystem request path is a directory or a file. 176 // Determines whether the requested filesystem request path is a directory or a file.
171 function serve(config, local, href, address, callback) { 177 function serve(self, config, local, href, address) {
172 fs.stat(local, (error, stats) => { 178 fs.stat(local, (error, stats) => {
173 // Document does not exist. 179 // Document does not exist.
174 if (error) { 180 if (error) {
175 callback({ 181 self.emit('log', {
176 message: 'Client: ' + 182 message: 'Client: ' +
177 address.address + ':' + 183 address.address + ':' +
178 address.port + 184 address.port +
179 ' accessing non-existent document: ' + 185 ' accessing non-existent document: ' +
180 local, 186 local,
-   187 severity: 'warning'
181 severity: 'warning' 188 });
182 }, { 189 self.emit('data', {
183 status: 404, 190 status: 404,
184 data: new stream.Readable({ 191 data: new stream.Readable({
185 read(size) { 192 read(size) {
Line 191... Line 198...
191 return; 198 return;
192 } 199 }
Line 193... Line 200...
193   200  
194 if (stats.isDirectory()) { 201 if (stats.isDirectory()) {
195 // Directory is requested so provide directory indexes. 202 // Directory is requested so provide directory indexes.
196 index(config, local, href, address, callback); 203 index(self, config, local, href, address);
197 return; 204 return;
198 } 205 }
199 if (stats.isFile()) { 206 if (stats.isFile()) {
Line 200... Line 207...
200 const file = path.parse(local).base; 207 const file = path.parse(local).base;
201   208  
202 // If the file matches the reject list or is not in the accept list, 209 // If the file matches the reject list or is not in the accept list,
203 // then there is no file to serve. 210 // then there is no file to serve.
204 if (config.site.reject.some((expression) => expression.test(file)) || 211 if (config.site.reject.some((expression) => expression.test(file)) ||
205 !config.site.accept.some((expression) => expression.test(file))) { 212 !config.site.accept.some((expression) => expression.test(file))) {
206 callback({ 213 self.emit('log', {
207 message: 'Client: ' + 214 message: 'Client: ' +
208 address.address + ':' + 215 address.address + ':' +
209 address.port + 216 address.port +
210 ' requested disallowed file: ' + 217 ' requested disallowed file: ' +
211 file, 218 file,
-   219 severity: 'warning'
212 severity: 'warning' 220 });
213 }, { 221 self.emit('data', {
214 status: 404, 222 status: 404,
215 data: new stream.Readable({ 223 data: new stream.Readable({
216 read(size) { 224 read(size) {
Line 221... Line 229...
221 }); 229 });
222 return; 230 return;
223 } 231 }
Line 224... Line 232...
224   232  
225 // A file was requested so provide the file. 233 // A file was requested so provide the file.
226 files(config, local, address, callback); 234 files(self, config, local, address);
227 } 235 }
228 }); 236 });
Line -... Line 237...
-   237 }
229 } 238  
-   239 // Constructor.
-   240 function Handler() {
-   241 // Create events emitters for logging and data.
-   242 EventEmitter.call(this);
-   243 };
230   244  
-   245 // Process a request.
-   246 Handler.prototype.process = function(config, request, response, root) {
-   247 EventEmitter.call(this);
-   248 var self = this;
231 module.exports = { 249  
-   250 // Get client details.
232 process: (config, request, response, root, callback) => { 251 const requestAddress = request.socket.address();
233 const requestAddress = request.socket.address(); 252 // Get requested URL.
234 const requestURL = url.parse( 253 const requestURL = url.parse(
235 request.url, true 254 request.url, true
236 ); 255 );
237   256  
238 // Perform URL re-writes. 257 // Perform URL re-writes.
239 Object.keys(config.site.rewrite).forEach((key, index) => { 258 Object.keys(config.site.rewrite).forEach((key, index) => {
240 if (config.site.rewrite[key].test(requestURL.path)) { 259 if (config.site.rewrite[key].test(requestURL.path)) {
241 const originalPath = requestURL.path; 260 const originalPath = requestURL.path;
-   261 requestURL.path = requestURL
-   262 .path
-   263 .replace(
-   264 config.site.rewrite[key], key
-   265 );
-   266 requestURL.pathname = url.parse(
242 requestURL.path = requestURL 267 requestURL
243 .path 268 .pathname
244 .replace( -  
245 config.site.rewrite[key], key -  
246 ); -  
247 requestURL.pathname = url.parse( -  
248 requestURL -  
249 .pathname -  
250 .replace( 269 .replace(
251 config.site.rewrite[key], key 270 config.site.rewrite[key], key
252 ), 271 ),
253 true 272 true
254 ) 273 )
255 .pathname; 274 .pathname;
256 callback({ 275 self.emit('log', {
257 message: 'Rewrite path: ' + 276 message: 'Rewrite path: ' +
258 originalPath + 277 originalPath +
259 ' to: ' + 278 ' to: ' +
260 requestURL.path, 279 requestURL.path,
261 severity: 'info' 280 severity: 'info'
262 }); 281 });
Line 263... Line 282...
263 } 282 }
264 }); 283 });
265   284  
266 const trimmedPath = requestURL 285 const trimmedPath = requestURL
267 .pathname 286 .pathname
268 .split('/') 287 .split('/')
269 .filter(Boolean) 288 .filter(Boolean)
270 .join('/'); 289 .join('/');
Line 271... Line 290...
271 const requestPath = trimmedPath === '/' ? 290 const requestPath = trimmedPath === '/' ?
272 path.join(root, trimmedPath) : 291 path.join(root, trimmedPath) :
273 path.resolve(root, trimmedPath); 292 path.resolve(root, trimmedPath);
274   293  
275 fs.realpath(requestPath, (error, resolvedPath) => { 294 fs.realpath(requestPath, (error, resolvedPath) => {
-   295 // If the path does not exist, then return early.
-   296 if (error) {
-   297 self.emit('log', {
-   298 message: 'Unknown path requested: ' +
-   299 requestAddress.address + ':' +
-   300 requestAddress.port +
-   301 ' requesting: ' +
-   302 requestURL.pathname,
-   303 severity: 'warning'
-   304 });
-   305 self.emit('done', {
-   306 status: 404,
-   307 data: new stream.Readable({
-   308 read(size) {
-   309 this.push(null);
-   310 }
-   311 }),
-   312 type: 'text/plain'
-   313 });
-   314 return;
-   315 }
-   316 // Check for path traversals early on and bail if the requested path does not
-   317 // lie within the specified document root.
276 // If the path does not exist, then return early. 318 isRooted(resolvedPath, root, path.sep, (rooted) => {
277 if (error) { 319 if (!rooted) {
278 callback({ 320 self.emit('log', {
279 message: 'Unknown path requested: ' + 321 message: 'Attempted path traversal: ' +
280 requestAddress.address + ':' + 322 requestAddress.address + ':' +
281 requestAddress.port + 323 requestAddress.port +
-   324 ' requesting: ' +
282 ' requesting: ' + 325 requestURL.pathname,
283 requestURL.pathname, 326 severity: 'warning'
284 severity: 'warning' 327 });
285 }, { 328 self.emit('done', {
286 status: 404, 329 status: 404,
287 data: new stream.Readable({ 330 data: new stream.Readable({
288 read(size) { 331 read(size) {
289 this.push(null); 332 this.push(null);
290 } 333 }
291 }), 334 }),
-   335 type: 'text/plain'
292 type: 'text/plain' 336 });
-   337 return;
-   338 }
293 }); 339  
294 return; 340 // If authentication is required for this path then perform authentication.
-   341 if (config.auth.locations.some(
295 } 342 (authPath) => authPath.toUpperCase() === requestURL.pathname.toUpperCase())) {
-   343 // Create digest authentication.
-   344 const authentication = auth.digest({
-   345 realm: config.auth.realm,
-   346 file: path.resolve(
-   347 path.dirname(require.main.filename),
-   348 config.auth.digest
296 // Check for path traversals early on and bail if the requested path does not 349 )
297 // lie within the specified document root. 350 });
298 isRooted(resolvedPath, root, path.sep, (rooted) => { 351 // Requested location requires authentication.
299 if (!rooted) { 352 authentication.check(request, response, (request, response) => {
300 callback({ 353 self.emit('log', {
301 message: 'Attempted path traversal: ' + 354 message: 'Authenticated client: ' +
302 requestAddress.address + ':' + 355 requestAddress.address + ':' +
303 requestAddress.port + -  
304 ' requesting: ' + -  
305 requestURL.pathname, -  
306 severity: 'warning' -  
307 }, { -  
308 status: 404, -  
309 data: new stream.Readable({ -  
310 read(size) { -  
311 this.push(null); -  
312 } -  
313 }), -  
314 type: 'text/plain' -  
315 }); -  
316 return; -  
317 } -  
318   -  
319 // If authentication is required for this path then perform authentication. -  
320 if (config.auth.locations.some( -  
321 (authPath) => authPath.toUpperCase() === requestURL.pathname.toUpperCase())) { -  
322 // Create digest authentication. -  
323 const authentication = auth.digest({ -  
324 realm: config.auth.realm, -  
325 file: path.resolve( 356 requestAddress.port +
326 path.dirname(require.main.filename), -  
327 config.auth.digest -  
328 ) -  
329 }); -  
330 // Requested location requires authentication. -  
331 authentication.check(request, response, (request, response) => { -  
332 callback({ 357 ' accessing: ' +
333 message: 'Authenticated client: ' + -  
334 requestAddress.address + ':' + 358 requestURL.pathname,
335 requestAddress.port + -  
336 ' accessing: ' + 359 severity: 'info'
337 requestURL.pathname, 360 });
338 severity: 'info' 361 process.nextTick(() =>
339 }); 362 serve(self,
340 serve(config, -  
341 requestPath, 363 config,
342 requestURL.pathname, 364 requestPath,
343 requestAddress, -  
344 callback -  
345 ); -  
346 }); -  
347 return; -  
348 } -  
349   -  
350 // If no authentication is required then serve the request. -  
351 callback({ -  
352 message: 'Client: ' + -  
353 requestAddress.address + ':' + -  
354 requestAddress.port + 365 requestURL.pathname,
355 ' accessing: ' + 366 requestAddress
-   367 )
-   368 );
-   369 });
-   370 return;
-   371 }
-   372  
356 requestURL.pathname, 373 // If no authentication is required then serve the request.
-   374 self.emit('log', {
357 severity: 'info' 375 message: 'Client: ' +
358 }); 376 requestAddress.address + ':' +
359 serve(config, -  
360 requestPath, -  
361 requestURL.pathname, 377 requestAddress.port +
-   378 ' accessing: ' +
-   379 requestURL.pathname,
-   380 severity: 'info'
-   381 });
-   382 process.nextTick(() =>
-   383 serve(self,
-   384 config,
-   385 requestPath,
362 requestAddress, 386 requestURL.pathname,
363 callback 387 requestAddress
-   388 )
-   389 );
364 ); 390 });
-   391 });
-   392
-   393 return this;