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

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