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