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