node-http-server – Diff between revs 29 and 30
?pathlinks?
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({ |
27 | callback({ |
|
29 | message: 'Client: ' + |
28 | message: 'Client: ' + |
|
30 | client.address + ':' + |
29 | client.address + ':' + |
|
31 | client.port + |
30 | client.port + |
|
32 | ' requesting inaccessible path: ' + |
31 | ' requesting inaccessible path: ' + |
|
33 | file, |
32 | file, |
|
34 | severity: 'warning', |
33 | severity: 'warning' |
|
- | 34 | }, { |
||
35 | status: 403 |
35 | status: 403, |
|
- | 36 | data: new stream.Readable({ |
||
- | 37 | read(size) { |
||
- | 38 | this.push(null); |
||
36 | |
39 | } |
|
37 | }); |
40 | }), |
|
- | 41 | type: 'text/plain' |
||
38 | }); |
42 | }); |
|
39 | return; |
43 | return; |
|
40 | } |
44 | } |
|
41 | process.nextTick(() => { |
- | ||
42 | callback({ |
45 | callback({ |
|
43 | message: 'Client: ' + |
46 | message: 'Client: ' + |
|
44 | client.address + ':' + |
47 | client.address + ':' + |
|
45 | client.port + |
48 | client.port + |
|
46 | ' sent file: ' + |
49 | ' sent file: ' + |
|
47 | file, |
50 | file, |
|
48 | severity: 'info', |
51 | severity: 'info' |
|
- | 52 | }, { |
||
49 | status: 200, |
53 | status: 200, |
|
50 | data: fs |
54 | data: fs |
|
51 | .createReadStream(file), |
55 | .createReadStream(file), |
|
52 | type: mime |
56 | type: mime |
|
53 | .lookup(file) |
57 | .lookup(file) |
|
54 | }); |
58 | }); |
|
55 | }); |
59 | }); |
|
56 | |
- | ||
57 | }); |
- | ||
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. |
|
61 | function index(config, directory, href, client, callback) { |
63 | function index(config, directory, href, client, callback) { |
|
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(() => { |
72 | console.log("listing forbidden..."); |
|
71 | callback({ |
73 | callback({ |
|
72 | message: 'Client: ' + |
74 | message: 'Client: ' + |
|
73 | client.address + ':' + |
75 | client.address + ':' + |
|
74 | client.port + |
76 | client.port + |
|
75 | ' could not access directory: ' + |
77 | ' could not access directory: ' + |
|
76 | directory, |
78 | directory, |
|
77 | severity: 'warning', |
79 | severity: 'warning' |
|
- | 80 | }, { |
||
78 | status: 500 |
81 | status: 500, |
|
- | 82 | data: new stream.Readable({ |
||
- | 83 | read(size) { |
||
- | 84 | this.push(null); |
||
- | 85 | } |
||
79 | }); |
86 | }), |
|
- | 87 | type: 'text/plain' |
||
80 | }); |
88 | }); |
|
81 | return; |
89 | return; |
|
82 | } |
90 | } |
|
83 | process.nextTick(() => { |
91 | console.log("sending listing..."); |
|
84 | callback({ |
92 | callback({ |
|
85 | message: 'Client: ' + |
93 | message: 'Client: ' + |
|
86 | client.address + ':' + |
94 | client.address + ':' + |
|
87 | client.port + |
95 | client.port + |
|
88 | ' accessed directory listing: ' + |
96 | ' accessed directory listing: ' + |
|
89 | directory, |
97 | directory, |
|
90 | severity: 'warning', |
98 | severity: 'info' |
|
- | 99 | }, { |
||
91 | status: 200, |
100 | status: 200, |
|
92 | data: JSONStream.parse(paths) |
101 | data: new stream.Readable({ |
|
- | 102 | read(size) { |
||
- | 103 | this.push(JSON.stringify(paths)); |
||
- | 104 | this.push(null); |
||
- | 105 | } |
||
93 | }); |
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(() => { |
135 | if (error) { |
|
118 | callback({ |
136 | callback({ |
|
119 | message: 'Client: ' + |
137 | message: 'Client: ' + |
|
120 | client.address + ':' + |
138 | client.address + ':' + |
|
121 | client.port + |
139 | client.port + |
|
122 | ' unable to access path: ' + |
140 | ' unable to access path: ' + |
|
123 | directory, |
141 | directory, |
|
- | 142 | severity: 'warning' |
||
124 | severity: 'warning', |
143 | }, { |
|
- | 144 | status: 403, |
||
- | 145 | data: new stream.Readable({ |
||
- | 146 | read(size) { |
||
- | 147 | this.push(null); |
||
125 | status: 403 |
148 | } |
|
- | 149 | }), |
||
126 | }); |
150 | type: 'text/plain' |
|
127 | }); |
151 | }); |
|
128 | return; |
152 | return; |
|
129 | } |
- | ||
130 | process.nextTick(() => { |
153 | } |
|
131 | callback({ |
154 | callback({ |
|
132 | message: 'Client: ' + |
155 | message: 'Client: ' + |
|
133 | client.address + ':' + |
156 | client.address + ':' + |
|
134 | client.port + |
157 | client.port + |
|
135 | ' sent file: ' + |
158 | ' sent file: ' + |
|
136 | root, |
159 | root, |
|
- | 160 | severity: 'info' |
||
137 | severity: 'info', |
161 | }, { |
|
138 | status: 200, |
162 | status: 200, |
|
139 | data: fs.createReadStream(root), |
163 | data: fs.createReadStream(root), |
|
140 | type: mime.lookup(root) |
164 | type: mime.lookup(root) |
|
141 | }); |
165 | }); |
|
142 | }); |
166 | }); |
|
143 | }); |
- | ||
144 | }); |
167 | }); |
|
Line 145... | Line 168... | |||
145 | } |
168 | } |
|
146 | |
169 | |
|
147 | // Determines whether the requested filesystem request path is a directory or a file. |
170 | // Determines whether the requested filesystem request path is a directory or a file. |
|
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 |
|
Line 239... | Line 273... | |||
239 | path.resolve(root, trimmedPath); |
273 | path.resolve(root, trimmedPath); |
|
Line 240... | Line 274... | |||
240 | |
274 | |
|
241 | fs.realpath(requestPath, (error, resolvedPath) => { |
275 | fs.realpath(requestPath, (error, resolvedPath) => { |
|
242 | // If the path does not exist, then return early. |
276 | // If the path does not exist, then return early. |
|
243 | if (error) { |
- | ||
244 | process.nextTick(() => { |
277 | if (error) { |
|
245 | callback({ |
278 | callback({ |
|
246 | message: 'Unknown path requested: ' + |
279 | message: 'Unknown path requested: ' + |
|
247 | requestAddress.address + ':' + |
280 | requestAddress.address + ':' + |
|
248 | requestAddress.port + |
281 | requestAddress.port + |
|
249 | ' requesting: ' + |
282 | ' requesting: ' + |
|
250 | requestURL.pathname, |
283 | requestURL.pathname, |
|
- | 284 | severity: 'warning' |
||
251 | severity: 'warning', |
285 | }, { |
|
- | 286 | status: 404, |
||
- | 287 | data: new stream.Readable({ |
||
- | 288 | read(size) { |
||
- | 289 | this.push(null); |
||
252 | status: 404 |
290 | } |
|
- | 291 | }), |
||
253 | }); |
292 | type: 'text/plain' |
|
254 | }); |
293 | }); |
|
255 | return; |
294 | return; |
|
256 | } |
295 | } |
|
257 | // Check for path traversals early on and bail if the requested path does not |
296 | // Check for path traversals early on and bail if the requested path does not |
|
258 | // lie within the specified document root. |
297 | // lie within the specified document root. |
|
259 | isRooted(resolvedPath, root, path.sep, (rooted) => { |
298 | isRooted(resolvedPath, root, path.sep, (rooted) => { |
|
260 | if (!rooted) { |
- | ||
261 | process.nextTick(() => { |
299 | if (!rooted) { |
|
262 | callback({ |
300 | callback({ |
|
263 | message: 'Attempted path traversal: ' + |
301 | message: 'Attempted path traversal: ' + |
|
264 | requestAddress.address + ':' + |
302 | requestAddress.address + ':' + |
|
265 | requestAddress.port + |
303 | requestAddress.port + |
|
266 | ' requesting: ' + |
304 | ' requesting: ' + |
|
267 | requestURL.pathname, |
305 | requestURL.pathname, |
|
- | 306 | severity: 'warning' |
||
268 | severity: 'warning', |
307 | }, { |
|
- | 308 | status: 404, |
||
- | 309 | data: new stream.Readable({ |
||
- | 310 | read(size) { |
||
- | 311 | this.push(null); |
||
269 | status: 404 |
312 | } |
|
- | 313 | }), |
||
270 | }); |
314 | type: 'text/plain' |
|
271 | }); |
315 | }); |
|
272 | return; |
316 | return; |
|
Line 273... | Line 317... | |||
273 | } |
317 | } |
|
Line 283... | Line 327... | |||
283 | config.auth.digest |
327 | config.auth.digest |
|
284 | ) |
328 | ) |
|
285 | }); |
329 | }); |
|
286 | // Requested location requires authentication. |
330 | // Requested location requires authentication. |
|
287 | authentication.check(request, response, (request, response) => { |
331 | authentication.check(request, response, (request, response) => { |
|
288 | process.nextTick(() => { |
- | ||
289 | callback({ |
332 | callback({ |
|
290 | message: 'Authenticated client: ' + |
333 | message: 'Authenticated client: ' + |
|
291 | requestAddress.address + ':' + |
334 | requestAddress.address + ':' + |
|
292 | requestAddress.port + |
335 | requestAddress.port + |
|
293 | ' accessing: ' + |
336 | ' accessing: ' + |
|
294 | requestURL.pathname, |
337 | requestURL.pathname, |
|
295 | severity: 'info' |
338 | severity: 'info' |
|
296 | }); |
339 | }); |
|
297 | }); |
- | ||
298 | serve(config, |
340 | serve(config, |
|
299 | requestPath, |
341 | requestPath, |
|
300 | requestURL.pathname, |
342 | requestURL.pathname, |
|
301 | requestAddress, |
343 | requestAddress, |
|
302 | callback |
344 | callback |
|
Line 304... | Line 346... | |||
304 | }); |
346 | }); |
|
305 | return; |
347 | return; |
|
306 | } |
348 | } |
|
Line 307... | Line 349... | |||
307 | |
349 | |
|
308 | // If no authentication is required then serve the request. |
- | ||
309 | process.nextTick(() => { |
350 | // If no authentication is required then serve the request. |
|
310 | callback({ |
351 | callback({ |
|
311 | message: 'Client: ' + |
352 | message: 'Client: ' + |
|
312 | requestAddress.address + ':' + |
353 | requestAddress.address + ':' + |
|
313 | requestAddress.port + |
354 | requestAddress.port + |
|
314 | ' accessing: ' + |
355 | ' accessing: ' + |
|
315 | requestURL.pathname, |
356 | requestURL.pathname, |
|
316 | severity: 'info' |
357 | severity: 'info' |
|
317 | }); |
- | ||
318 | }); |
358 | }); |
|
319 | serve(config, |
359 | serve(config, |
|
320 | requestPath, |
360 | requestPath, |
|
321 | requestURL.pathname, |
361 | requestURL.pathname, |
|
322 | requestAddress, |
362 | requestAddress, |
|
323 | callback |
363 | callback |
|
324 | ); |
364 | ); |
|
325 | }); |
365 | }); |
|
326 | }); |
- | ||
327 | }); |
366 | }); |
|
328 | } |
367 | } |