node-http-server – Diff between revs 14 and 15
?pathlinks?
Rev 14 | Rev 15 | |||
---|---|---|---|---|
Line 9... | Line 9... | |||
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"); |
|
Line 12... | Line 12... | |||
12 | |
12 | |
|
13 | // Checks whether userPath is a child of rootPath. |
13 | // Checks whether userPath is a child of rootPath. |
|
- | 14 | function isRooted(userPath, rootPath, separator, callback) { |
||
14 | function isRooted(userPath, rootPath, separator) { |
15 | process.nextTick(() => { |
|
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 | return userPath.length >= rootPath.length && |
18 | callback(userPath.length >= rootPath.length && |
|
- | 19 | rootPath.every((e, i) => e === userPath[i])); |
||
18 | rootPath.every((e, i) => e === userPath[i]); |
20 | }); |
|
Line 19... | Line 21... | |||
19 | } |
21 | } |
|
20 | |
22 | |
|
21 | // Serves files. |
23 | // Serves files. |
|
- | 24 | function files(config, request, response, resource, callback) { |
||
- | 25 | // Check if the file is accessible. |
||
- | 26 | fs.access(resource, fs.constants.R_OK, (error) => { |
||
- | 27 | if (error) { |
||
- | 28 | response.statusCode = 403; |
||
- | 29 | response.end(); |
||
- | 30 | return; |
||
- | 31 | } |
||
- | 32 | |
||
- | 33 | response.setHeader( |
||
- | 34 | 'Content-Type', |
||
- | 35 | mime.lookup(resource) |
||
- | 36 | ); |
||
- | 37 | |
||
- | 38 | var readStream = fs.createReadStream(resource) |
||
- | 39 | .on('open', () => { |
||
- | 40 | response.statusCode = 200; |
||
- | 41 | readStream.pipe(response); |
||
- | 42 | }) |
||
- | 43 | .on('error', () => { |
||
- | 44 | response.statusCode = 500; |
||
- | 45 | response.end(); |
||
- | 46 | }); |
||
- | 47 | |
||
- | 48 | }); |
||
- | 49 | } |
||
- | 50 | |
||
- | 51 | // Serves a directory listing or the document index in case it exists. |
||
- | 52 | function index(config, request, response, resource, root, callback) { |
||
- | 53 | fs.stat(root, (error, stats) => { |
||
- | 54 | if (error) { |
||
- | 55 | fs.readdir(resource, (error, paths) => { |
||
- | 56 | if (error) { |
||
- | 57 | process.nextTick(() => { |
||
- | 58 | callback('Could not list directory: ' + |
||
- | 59 | resource, |
||
- | 60 | module.exports.error.level.ERROR |
||
- | 61 | ); |
||
- | 62 | }); |
||
- | 63 | response.statusCode = 500; |
||
- | 64 | response.end(); |
||
22 | function files(config, request, response, resource, callback) { |
65 | return; |
|
- | 66 | } |
||
- | 67 | process.nextTick(() => { |
||
- | 68 | callback('Directory listing requested for: ' + |
||
- | 69 | resource, |
||
- | 70 | module.exports.error.level.INFO |
||
- | 71 | ); |
||
- | 72 | }); |
||
- | 73 | response.statusCode = 200; |
||
- | 74 | response.write(JSON.stringify(paths)); |
||
- | 75 | response.end(); |
||
- | 76 | }); |
||
- | 77 | |
||
- | 78 | return; |
||
- | 79 | } |
||
23 | // Check if the file is accessible. |
80 | |
|
24 | process.nextTick(() => { |
81 | // Serve the document index. |
|
- | 82 | fs.access(resource, fs.constants.R_OK, (error) => { |
||
- | 83 | if (error) { |
||
- | 84 | process.nextTick(() => { |
||
- | 85 | callback('The server was unable to access the filesystem path: ' + |
||
- | 86 | resource, |
||
- | 87 | module.exports.error.level.WARN |
||
25 | fs.access(resource, fs.constants.R_OK, (error) => { |
88 | ); |
|
26 | if (error) { |
89 | }); |
|
27 | response.statusCode = 403; |
90 | response.statusCode = 403; |
|
28 | response.end(); |
91 | response.end(); |
|
Line -... | Line 92... | |||
- | 92 | return; |
||
29 | return; |
93 | } |
|
30 | } |
94 | |
|
31 | |
95 | // Set MIME content type. |
|
32 | response.setHeader( |
96 | response.setHeader( |
|
Line 33... | Line 97... | |||
33 | 'Content-Type', |
97 | 'Content-Type', |
|
34 | mime.lookup(resource) |
98 | mime.lookup(root) |
|
35 | ); |
99 | ); |
|
36 | |
100 | |
|
37 | var readStream = fs.createReadStream(resource) |
101 | var readStream = fs.createReadStream(root) |
|
38 | .on('open', () => { |
102 | .on('open', () => { |
|
Line 46... | Line 110... | |||
46 | |
110 | |
|
47 | }); |
111 | }); |
|
48 | }); |
112 | }); |
|
Line 49... | Line -... | |||
49 | } |
- | ||
50 | |
- | ||
51 | // Serves a directory index. |
- | ||
52 | function index(config, request, response, resource, callback) { |
- | ||
53 | process.nextTick(() => { |
- | ||
54 | const root = path.resolve(resource, config.site.index); |
- | ||
55 | fs.stat(root, (error, stats) => { |
- | ||
56 | if (error) { |
- | ||
57 | fs.readdir(resource, (error, paths) => { |
- | ||
58 | if (error) { |
- | ||
59 | process.nextTick(() => { |
- | ||
60 | callback('Could not list directory: ' + |
- | ||
61 | resource, |
- | ||
62 | module.exports.error.level.ERROR |
- | ||
63 | ); |
- | ||
64 | }); |
- | ||
65 | response.statusCode = 500; |
- | ||
66 | response.end(); |
- | ||
67 | return; |
- | ||
68 | } |
- | ||
69 | process.nextTick(() => { |
- | ||
70 | callback('Directory listing requested for: ' + |
- | ||
71 | resource, |
- | ||
72 | module.exports.error.level.INFO |
- | ||
73 | ); |
- | ||
74 | }); |
- | ||
75 | response.statusCode = 200; |
- | ||
76 | response.write(JSON.stringify(paths)); |
- | ||
77 | response.end(); |
- | ||
78 | }); |
- | ||
79 | |
- | ||
80 | return; |
- | ||
81 | } |
- | ||
82 | |
- | ||
83 | fs.access(resource, fs.constants.R_OK, (error) => { |
- | ||
84 | if (error) { |
- | ||
85 | process.nextTick(() => { |
- | ||
86 | callback('The server was unable to access the filesystem path: ' + |
- | ||
87 | resource, |
- | ||
88 | module.exports.error.level.WARN |
- | ||
89 | ); |
- | ||
90 | }); |
- | ||
91 | response.statusCode = 403; |
- | ||
92 | response.end(); |
- | ||
93 | return; |
- | ||
94 | } |
- | ||
95 | |
- | ||
96 | // Set MIME content type. |
- | ||
97 | response.setHeader( |
- | ||
98 | 'Content-Type', |
- | ||
99 | mime.lookup(root) |
- | ||
100 | ); |
- | ||
101 | |
- | ||
102 | var readStream = fs.createReadStream(root) |
- | ||
103 | .on('open', () => { |
- | ||
104 | response.statusCode = 200; |
- | ||
105 | readStream.pipe(response); |
- | ||
106 | }) |
- | ||
107 | .on('error', () => { |
- | ||
108 | response.statusCode = 500; |
- | ||
109 | response.end(); |
- | ||
110 | }); |
- | ||
111 | |
- | ||
112 | }); |
- | ||
113 | |
- | ||
114 | }); |
- | ||
115 | }); |
- | ||
116 | } |
113 | } |
|
117 | |
114 | |
|
118 | // Determines whether the requested resource is a directory or a file. |
- | ||
119 | function serve(config, request, response, resource, callback) { |
115 | // Determines whether the requested resource is a directory or a file. |
|
120 | process.nextTick(() => { |
116 | function serve(config, request, response, resource, callback) { |
|
121 | fs.stat(resource, (error, stats) => { |
117 | fs.stat(resource, (error, stats) => { |
|
122 | // Document does not exist. |
118 | // Document does not exist. |
|
123 | if (error) { |
119 | if (error) { |
|
124 | response.statusCode = 404; |
120 | response.statusCode = 404; |
|
125 | response.end(); |
121 | response.end(); |
|
Line 126... | Line 122... | |||
126 | return; |
122 | return; |
|
127 | } |
123 | } |
|
128 | |
124 | |
|
129 | switch (stats.isDirectory()) { |
125 | switch (stats.isDirectory()) { |
|
130 | case true: // Directory is requested so provide directory indexes. |
126 | case true: // Directory is requested so provide directory indexes. |
|
131 | index(config, request, response, resource, callback) |
127 | index(config, request, response, resource, path.resolve(resource, config.site.index), callback) |
|
132 | break; |
128 | break; |
|
133 | default: // Browser requesting file. |
- | ||
134 | files(config, request, response, resource, callback); |
129 | default: // Browser requesting file. |
|
135 | break; |
130 | files(config, request, response, resource, callback); |
|
136 | } |
131 | break; |
|
Line 137... | Line 132... | |||
137 | }); |
132 | } |
|
138 | }); |
133 | }); |
|
Line 169... | Line 164... | |||
169 | .join('/'); |
164 | .join('/'); |
|
170 | const resource = trimmedPath === '/' ? |
165 | const resource = trimmedPath === '/' ? |
|
171 | path.join(root, trimmedPath) : |
166 | path.join(root, trimmedPath) : |
|
172 | path.resolve(root, trimmedPath); |
167 | path.resolve(root, trimmedPath); |
|
Line 173... | Line 168... | |||
173 | |
168 | |
|
- | 169 | isRooted(resource, root, path.sep, (rooted) => { |
||
174 | if (!isRooted(resource, root, path.sep)) { |
170 | if (!rooted) { |
|
175 | process.nextTick(() => { |
171 | process.nextTick(() => { |
|
176 | callback('Attempted path traversal: ' + |
172 | callback('Attempted path traversal: ' + |
|
177 | requestAddress.address + ':' + |
173 | requestAddress.address + ':' + |
|
178 | requestAddress.port + |
174 | requestAddress.port + |
|
179 | ' requesting: ' + |
175 | ' requesting: ' + |
|
180 | requestedURL.pathname, |
176 | requestedURL.pathname, |
|
181 | module.exports.error.level.WARN |
177 | module.exports.error.level.WARN |
|
182 | ); |
178 | ); |
|
183 | }); |
179 | }); |
|
184 | response.statusCode = 404; |
180 | response.statusCode = 404; |
|
185 | response.end(); |
181 | response.end(); |
|
186 | return; |
182 | return; |
|
Line -... | Line 183... | |||
- | 183 | } |
||
187 | } |
184 | |
|
188 | |
185 | // Check if the requested path requires authentication. |
|
189 | switch (config.auth.locations.some( |
186 | switch (config.auth.locations.some( |
|
190 | (authPath) => authPath.toUpperCase() === requestedURL.pathname.toUpperCase())) { |
187 | (authPath) => authPath.toUpperCase() === requestedURL.pathname.toUpperCase())) { |
|
191 | case true: |
188 | case true: |
|
- | 189 | // Requested location requires authentication. |
||
- | 190 | authentication.check(request, response, (request, response) => { |
||
- | 191 | process.nextTick(() => { |
||
- | 192 | callback('Authenticated client: ' + |
||
- | 193 | requestAddress.address + ':' + |
||
- | 194 | requestAddress.port + |
||
- | 195 | ' accessing: ' + |
||
- | 196 | requestedURL.pathname, |
||
- | 197 | module.exports.error.level.INFO |
||
- | 198 | ); |
||
- | 199 | }); |
||
- | 200 | serve(config, request, response, resource, callback); |
||
- | 201 | }); |
||
192 | // Requested location requires authentication. |
202 | break; |
|
193 | authentication.check(request, response, (request, response) => { |
203 | default: |
|
194 | process.nextTick(() => { |
204 | process.nextTick(() => { |
|
195 | callback('Authenticated client: ' + |
205 | callback('Client: ' + |
|
196 | requestAddress.address + ':' + |
206 | requestAddress.address + ':' + |
|
197 | requestAddress.port + |
207 | requestAddress.port + |
|
198 | ' accessing: ' + |
208 | ' accessing: ' + |
|
199 | requestedURL.pathname, |
209 | requestedURL.pathname, |
|
200 | module.exports.error.level.INFO |
210 | module.exports.error.level.INFO |
|
201 | ); |
211 | ); |
|
202 | }); |
- | ||
203 | serve(config, request, response, resource, callback); |
212 | }); |
|
204 | }); |
- | ||
205 | break; |
- | ||
206 | default: |
- | ||
207 | process.nextTick(() => { |
- | ||
208 | callback('Client: ' + |
- | ||
209 | requestAddress.address + ':' + |
- | ||
210 | requestAddress.port + |
- | ||
211 | ' accessing: ' + |
- | ||
212 | requestedURL.pathname, |
- | ||
213 | module.exports.error.level.INFO |
213 | serve(config, request, response, resource, callback); |
|
214 | ); |
- | ||
215 | }); |
- | ||
216 | serve(config, request, response, resource, callback); |
214 | break; |
|
217 | break; |
215 | } |
|
218 | } |
216 | }); |
|
219 | }); |
217 | }); |