node-http-server – Diff between revs 27 and 29
?pathlinks?
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 | }); |