st 3.0.0 → 3.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/test.yml +23 -0
- package/bin/server.js +5 -4
- package/package.json +7 -7
- package/st.js +17 -21
- package/test/basic.js +10 -11
- package/test/cli/common.js +2 -2
- package/test/cli/host-test.js +12 -6
- package/test/common.js +1 -1
- package/test/gzip-after-no-gzip.js +4 -1
- package/test/multi-mount.js +6 -5
- package/test/preset-cache-control.js +1 -1
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
name: Test
|
|
2
|
+
on: [push, pull_request]
|
|
3
|
+
jobs:
|
|
4
|
+
test:
|
|
5
|
+
strategy:
|
|
6
|
+
fail-fast: false
|
|
7
|
+
matrix:
|
|
8
|
+
node: [lts/*, current]
|
|
9
|
+
os: [macos-latest, ubuntu-latest, windows-latest]
|
|
10
|
+
runs-on: ${{ matrix.os }}
|
|
11
|
+
steps:
|
|
12
|
+
- name: Checkout Repository
|
|
13
|
+
uses: actions/checkout@v4
|
|
14
|
+
- name: Use Node.js ${{ matrix.node }}
|
|
15
|
+
uses: actions/setup-node@v4
|
|
16
|
+
with:
|
|
17
|
+
node-version: ${{ matrix.node }}
|
|
18
|
+
- name: Install Dependencies
|
|
19
|
+
run: |
|
|
20
|
+
npm install --no-progress
|
|
21
|
+
- name: Run tests
|
|
22
|
+
run: |
|
|
23
|
+
npm test
|
package/bin/server.js
CHANGED
|
@@ -82,6 +82,7 @@ for (let i = 2; i < process.argv.length; i++) {
|
|
|
82
82
|
case '--help':
|
|
83
83
|
help()
|
|
84
84
|
process.exit(0)
|
|
85
|
+
break
|
|
85
86
|
|
|
86
87
|
case '-nc':
|
|
87
88
|
case '--no-cache':
|
|
@@ -150,9 +151,9 @@ if (isNaN(port)) {
|
|
|
150
151
|
|
|
151
152
|
const opt = {
|
|
152
153
|
path: dir,
|
|
153
|
-
url
|
|
154
|
-
index
|
|
155
|
-
dot
|
|
154
|
+
url,
|
|
155
|
+
index,
|
|
156
|
+
dot,
|
|
156
157
|
cache: {
|
|
157
158
|
fd: {},
|
|
158
159
|
stat: {},
|
|
@@ -160,7 +161,7 @@ const opt = {
|
|
|
160
161
|
readdir: {},
|
|
161
162
|
content: {}
|
|
162
163
|
},
|
|
163
|
-
cors
|
|
164
|
+
cors
|
|
164
165
|
}
|
|
165
166
|
|
|
166
167
|
if (cache === false) {
|
package/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "st",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.2",
|
|
4
4
|
"description": "A module for serving static files. Does etags, caching, etc.",
|
|
5
5
|
"main": "st.js",
|
|
6
6
|
"bin": "bin/server.js",
|
|
7
7
|
"dependencies": {
|
|
8
8
|
"async-cache": "^1.1.0",
|
|
9
|
-
"bl": "^
|
|
10
|
-
"fd": "
|
|
11
|
-
"mime": "^
|
|
12
|
-
"negotiator": "
|
|
9
|
+
"bl": "^6.1.0",
|
|
10
|
+
"fd": "^0.0.3",
|
|
11
|
+
"mime": "^3.0.0",
|
|
12
|
+
"negotiator": "^1.0.0"
|
|
13
13
|
},
|
|
14
14
|
"optionalDependencies": {
|
|
15
15
|
"graceful-fs": "^4.2.3"
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
"devDependencies": {
|
|
18
18
|
"request": "^2.88.2",
|
|
19
19
|
"rimraf": "^3.0.2",
|
|
20
|
-
"standard": "^
|
|
21
|
-
"tap": "^15.
|
|
20
|
+
"standard": "^17.1.2",
|
|
21
|
+
"tap": "^15.2.3"
|
|
22
22
|
},
|
|
23
23
|
"scripts": {
|
|
24
24
|
"lint": "standard",
|
package/st.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const mime = require('mime')
|
|
2
2
|
const path = require('path')
|
|
3
|
-
const url = require('url')
|
|
4
3
|
let fs
|
|
5
4
|
try {
|
|
6
5
|
fs = require('graceful-fs')
|
|
@@ -180,22 +179,13 @@ class Mount {
|
|
|
180
179
|
|
|
181
180
|
// get the path component from a URI
|
|
182
181
|
getUriPath (u) {
|
|
183
|
-
let p =
|
|
182
|
+
let p = new URL(u, 'http://base').pathname
|
|
184
183
|
|
|
185
|
-
//
|
|
186
|
-
p = p.replace(
|
|
184
|
+
// Convert any backslashes to forward slashes (for consistency)
|
|
185
|
+
p = p.replace(/\\/g, '/')
|
|
187
186
|
|
|
188
|
-
//
|
|
189
|
-
p = p.replace(/%2f|%5c/ig, '/')
|
|
190
|
-
|
|
191
|
-
// back slashes are slashes
|
|
192
|
-
p = p.replace(/[/\\]/g, '/')
|
|
193
|
-
|
|
194
|
-
// Make sure it starts with a slash
|
|
195
|
-
p = p.replace(/^\//, '/')
|
|
187
|
+
// Remove the redundant leading slash replacement - URL().pathname always starts with /
|
|
196
188
|
if ((/[/\\]\.\.([/\\]|$)/).test(p)) {
|
|
197
|
-
// traversal urls not ever even slightly allowed. clearly shenanigans
|
|
198
|
-
// send a 403 on that noise, do not pass go, do not collect $200
|
|
199
189
|
return 403
|
|
200
190
|
}
|
|
201
191
|
|
|
@@ -204,15 +194,18 @@ class Mount {
|
|
|
204
194
|
return false
|
|
205
195
|
}
|
|
206
196
|
|
|
197
|
+
// URL constructor already decoded, but we might need additional decoding
|
|
198
|
+
// for edge cases. Only do it if it would actually change something.
|
|
207
199
|
try {
|
|
208
|
-
|
|
200
|
+
const decoded = decodeURIComponent(u)
|
|
201
|
+
if (decoded !== u) {
|
|
202
|
+
u = decoded
|
|
203
|
+
}
|
|
209
204
|
} catch (e) {
|
|
210
|
-
|
|
205
|
+
// if decodeURIComponent failed, we weren't given a valid URL to begin with.
|
|
211
206
|
return false
|
|
212
207
|
}
|
|
213
208
|
|
|
214
|
-
// /a/b/c mounted on /path/to/z/d/x
|
|
215
|
-
// /a/b/c/d --> /path/to/z/d/x/d
|
|
216
209
|
u = u.substr(this.url.length)
|
|
217
210
|
if (u.charAt(0) !== '/') {
|
|
218
211
|
u = '/' + u
|
|
@@ -223,7 +216,10 @@ class Mount {
|
|
|
223
216
|
|
|
224
217
|
// get a path from a url
|
|
225
218
|
getPath (u) {
|
|
226
|
-
|
|
219
|
+
// trailing slash removal to fix Node.js v23 bug
|
|
220
|
+
// https://github.com/nodejs/node/pull/55527
|
|
221
|
+
// can be removed when this is resolved and released
|
|
222
|
+
return path.join(this.path, u.replace(/\/+$/, ''))
|
|
227
223
|
}
|
|
228
224
|
|
|
229
225
|
// get a url from a path
|
|
@@ -442,7 +438,7 @@ class Mount {
|
|
|
442
438
|
}
|
|
443
439
|
|
|
444
440
|
streamFile (p, fd, stat, etag, req, res, end) {
|
|
445
|
-
const streamOpt = { fd
|
|
441
|
+
const streamOpt = { fd, start: 0, end: stat.size }
|
|
446
442
|
let stream = fs.createReadStream(p, streamOpt)
|
|
447
443
|
stream.destroy = () => {}
|
|
448
444
|
|
|
@@ -454,7 +450,7 @@ class Mount {
|
|
|
454
450
|
|
|
455
451
|
// need a gzipped version for the cache, so do it regardless of what the client wants
|
|
456
452
|
if (gz || (gzOpt && cachable)) {
|
|
457
|
-
gzstr = zlib.
|
|
453
|
+
gzstr = zlib.createGzip()
|
|
458
454
|
}
|
|
459
455
|
|
|
460
456
|
// too late to effectively handle any errors.
|
package/test/basic.js
CHANGED
|
@@ -97,6 +97,14 @@ test('multiball!', (t) => {
|
|
|
97
97
|
}
|
|
98
98
|
})
|
|
99
99
|
|
|
100
|
+
test('trailing slash', (t) => {
|
|
101
|
+
req('/test/test/fixtures/', (er, res, body) => {
|
|
102
|
+
t.equal(res.statusCode, 200)
|
|
103
|
+
t.ok(/<html>.*Index of \/test\/fixtures<[\s\S]+index\.html[\s\S]+space in filename\.txt[\s\S]+<\/html>/.test(body.toString()))
|
|
104
|
+
t.end()
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
|
|
100
108
|
test('space in filename', (t) => {
|
|
101
109
|
req('/test/test/fixtures/space in filename.txt', (er, res, body) => {
|
|
102
110
|
t.equal(res.statusCode, 200)
|
|
@@ -117,17 +125,8 @@ test('shenanigans', (t) => {
|
|
|
117
125
|
if (er) {
|
|
118
126
|
throw er
|
|
119
127
|
}
|
|
120
|
-
|
|
121
|
-
t.
|
|
122
|
-
})
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
test('shenanigans2', (t) => {
|
|
126
|
-
req('/test//foo/%2e%2E', (er, res) => {
|
|
127
|
-
if (er) {
|
|
128
|
-
throw er
|
|
129
|
-
}
|
|
130
|
-
t.equal(res.statusCode, 403)
|
|
128
|
+
// resolves to simply ./etc/passwd
|
|
129
|
+
t.equal(res.statusCode, 404)
|
|
131
130
|
t.end()
|
|
132
131
|
})
|
|
133
132
|
})
|
package/test/cli/common.js
CHANGED
package/test/cli/host-test.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const os = require('os')
|
|
2
|
+
const dns = require('dns')
|
|
2
3
|
let { test, fail, comment } = require('tap')
|
|
3
4
|
const { serve } = require('./common')
|
|
4
5
|
const port = 1338
|
|
@@ -73,12 +74,6 @@ testServer(
|
|
|
73
74
|
['127.0.0.1', 'localhost', otherAddress], []
|
|
74
75
|
)
|
|
75
76
|
|
|
76
|
-
testServer(
|
|
77
|
-
'Restricted to localhost',
|
|
78
|
-
['--localhost'], 'localhost',
|
|
79
|
-
['127.0.0.1', 'localhost'], [otherAddress]
|
|
80
|
-
)
|
|
81
|
-
|
|
82
77
|
testServer(
|
|
83
78
|
'Restricted to non-local host',
|
|
84
79
|
['--host', otherAddress], otherAddress,
|
|
@@ -90,3 +85,14 @@ testServer(
|
|
|
90
85
|
['--host', '127.0.0.1'], '127.0.0.1',
|
|
91
86
|
['127.0.0.1'], ['::1']
|
|
92
87
|
)
|
|
88
|
+
|
|
89
|
+
dns.lookup('localhost', (err, address) => {
|
|
90
|
+
if (err) {
|
|
91
|
+
throw err
|
|
92
|
+
}
|
|
93
|
+
testServer(
|
|
94
|
+
'Restricted to localhost',
|
|
95
|
+
['--localhost'], 'localhost',
|
|
96
|
+
[address, 'localhost'], [otherAddress]
|
|
97
|
+
)
|
|
98
|
+
})
|
package/test/common.js
CHANGED
|
@@ -12,7 +12,10 @@ test('does not gzip first response', (t) => {
|
|
|
12
12
|
t.notOk(res.headers['content-encoding'])
|
|
13
13
|
t.notOk(res.headers['x-from-cache'])
|
|
14
14
|
t.equal(body.toString(), stExpect)
|
|
15
|
-
|
|
15
|
+
// response is delivered before cache is set, so we'll give it a tiny bit
|
|
16
|
+
// of grace to close out on the server side before starting the next
|
|
17
|
+
// test
|
|
18
|
+
setTimeout(t.end, 100)
|
|
16
19
|
})
|
|
17
20
|
})
|
|
18
21
|
|
package/test/multi-mount.js
CHANGED
|
@@ -47,13 +47,13 @@ function req (url, headers, cb) {
|
|
|
47
47
|
request({
|
|
48
48
|
encoding: null,
|
|
49
49
|
url: `http://localhost:${port}${url}`,
|
|
50
|
-
headers
|
|
50
|
+
headers,
|
|
51
51
|
agentOptions: { maxSockets: 50 }
|
|
52
52
|
}, next)
|
|
53
53
|
request({
|
|
54
54
|
encoding: null,
|
|
55
55
|
url: `http://localhost:${(port + 1)}${url}`,
|
|
56
|
-
headers
|
|
56
|
+
headers,
|
|
57
57
|
agentOptions: { maxSockets: 50 }
|
|
58
58
|
}, next)
|
|
59
59
|
|
|
@@ -76,7 +76,7 @@ function req (url, headers, cb) {
|
|
|
76
76
|
assert.strictEqual(`${body}`, `${prev.body}`)
|
|
77
77
|
return cb(er, res, body)
|
|
78
78
|
}
|
|
79
|
-
prev = { res
|
|
79
|
+
prev = { res, body }
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
|
|
@@ -90,7 +90,7 @@ test('setup middleware server', function (t) {
|
|
|
90
90
|
})
|
|
91
91
|
})
|
|
92
92
|
})
|
|
93
|
-
middlewareServer.listen(port, '
|
|
93
|
+
middlewareServer.listen(port, 'localhost', function () {
|
|
94
94
|
t.pass('listening')
|
|
95
95
|
t.end()
|
|
96
96
|
})
|
|
@@ -103,7 +103,7 @@ test('setup regular server', function (t) {
|
|
|
103
103
|
return res.end(`Not a match: ${req.url}`)
|
|
104
104
|
}
|
|
105
105
|
})
|
|
106
|
-
server.listen(port + 1, '
|
|
106
|
+
server.listen(port + 1, 'localhost', function () {
|
|
107
107
|
t.pass('listening')
|
|
108
108
|
t.end()
|
|
109
109
|
})
|
|
@@ -114,6 +114,7 @@ const stExpect = fs.readFileSync(require.resolve('../st.js')).toString()
|
|
|
114
114
|
|
|
115
115
|
test('/test/st.js', function (t) {
|
|
116
116
|
req('/test/st.js', function (er, res, body) {
|
|
117
|
+
t.error(er)
|
|
117
118
|
t.equal(res.statusCode, 200)
|
|
118
119
|
t.ok(res.headers.etag)
|
|
119
120
|
stEtag = res.headers.etag
|