koa-files 4.1.6 → 5.0.0
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/README.md +1 -1
- package/cjs/ReadStream.cjs +66 -62
- package/cjs/ReadStream.d.cts +3 -45
- package/cjs/Service.cjs +25 -22
- package/cjs/Service.d.cts +2 -12
- package/cjs/index.cjs +3 -2
- package/cjs/utils/fs.cjs +2 -1
- package/cjs/utils/fs.d.cts +18 -5
- package/cjs/utils/hash.cjs +4 -3
- package/cjs/utils/http.cjs +8 -5
- package/cjs/utils/http.d.cts +2 -2
- package/cjs/utils/path.cjs +6 -5
- package/cjs/utils/type.cjs +2 -1
- package/esm/ReadStream.d.ts +3 -45
- package/esm/ReadStream.js +64 -60
- package/esm/Service.d.ts +2 -12
- package/esm/Service.js +17 -14
- package/esm/index.js +3 -2
- package/esm/utils/fs.d.ts +18 -5
- package/esm/utils/fs.js +2 -1
- package/esm/utils/hash.js +3 -2
- package/esm/utils/http.d.ts +2 -2
- package/esm/utils/http.js +7 -4
- package/esm/utils/path.js +3 -2
- package/esm/utils/type.js +2 -1
- package/package.json +16 -16
package/README.md
CHANGED
package/cjs/ReadStream.cjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
+
* @module koa-files
|
|
2
3
|
* @package koa-files
|
|
3
4
|
* @license MIT
|
|
4
|
-
* @version
|
|
5
|
+
* @version 5.0.0
|
|
5
6
|
* @author nuintun <nuintun@qq.com>
|
|
6
7
|
* @description A static files serving middleware for koa.
|
|
7
8
|
* @see https://github.com/nuintun/koa-files#readme
|
|
@@ -9,14 +10,22 @@
|
|
|
9
10
|
|
|
10
11
|
'use strict';
|
|
11
12
|
|
|
12
|
-
const
|
|
13
|
-
const
|
|
13
|
+
const node_buffer = require('node:buffer');
|
|
14
|
+
const node_stream = require('node:stream');
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* @module ReadStream
|
|
17
18
|
*/
|
|
18
19
|
const DISPOSE_EVENT = Symbol('dispose');
|
|
19
|
-
class ReadStream extends
|
|
20
|
+
class ReadStream extends node_stream.Readable {
|
|
21
|
+
#path;
|
|
22
|
+
#fs;
|
|
23
|
+
#ranges;
|
|
24
|
+
#bytesRead = 0;
|
|
25
|
+
#fd = null;
|
|
26
|
+
#reading = false;
|
|
27
|
+
#currentRangeIndex = 0;
|
|
28
|
+
#readState = 0 /* ReadState.PREFIX */;
|
|
20
29
|
/**
|
|
21
30
|
* @constructor
|
|
22
31
|
* @param path The file path.
|
|
@@ -26,14 +35,9 @@ class ReadStream extends stream.Readable {
|
|
|
26
35
|
constructor(path, ranges, options) {
|
|
27
36
|
const { fs, highWaterMark } = options;
|
|
28
37
|
super({ highWaterMark });
|
|
29
|
-
this
|
|
30
|
-
this
|
|
31
|
-
this
|
|
32
|
-
this.currentRangeIndex = 0;
|
|
33
|
-
this.readState = 0 /* ReadState.PREFIX */;
|
|
34
|
-
this.fs = fs;
|
|
35
|
-
this.path = path;
|
|
36
|
-
this.ranges = ranges;
|
|
38
|
+
this.#fs = fs;
|
|
39
|
+
this.#path = path;
|
|
40
|
+
this.#ranges = ranges;
|
|
37
41
|
}
|
|
38
42
|
/**
|
|
39
43
|
* @override
|
|
@@ -41,27 +45,27 @@ class ReadStream extends stream.Readable {
|
|
|
41
45
|
* @param callback The callback.
|
|
42
46
|
*/
|
|
43
47
|
_construct(callback) {
|
|
44
|
-
this
|
|
48
|
+
this.#fs.open(this.#path, 'r', (openError, fd) => {
|
|
45
49
|
if (openError == null) {
|
|
46
|
-
this
|
|
50
|
+
this.#fd = fd;
|
|
47
51
|
}
|
|
48
52
|
callback(openError);
|
|
49
53
|
});
|
|
50
54
|
}
|
|
51
55
|
/**
|
|
52
56
|
* @private
|
|
53
|
-
* @method getRange
|
|
57
|
+
* @method #getRange
|
|
54
58
|
*/
|
|
55
|
-
getRange() {
|
|
56
|
-
return this
|
|
59
|
+
#getRange() {
|
|
60
|
+
return this.#ranges[this.#currentRangeIndex];
|
|
57
61
|
}
|
|
58
62
|
/**
|
|
59
63
|
* @private
|
|
60
|
-
* @method getPadding
|
|
64
|
+
* @method #getPadding
|
|
61
65
|
* @param range The current range.
|
|
62
66
|
*/
|
|
63
|
-
getPadding(range) {
|
|
64
|
-
switch (this
|
|
67
|
+
#getPadding(range) {
|
|
68
|
+
switch (this.#readState) {
|
|
65
69
|
case 0 /* ReadState.PREFIX */:
|
|
66
70
|
return range.prefix;
|
|
67
71
|
case 2 /* ReadState.SUFFIX */:
|
|
@@ -70,42 +74,42 @@ class ReadStream extends stream.Readable {
|
|
|
70
74
|
}
|
|
71
75
|
/**
|
|
72
76
|
* @private
|
|
73
|
-
* @method readPadding
|
|
77
|
+
* @method #readPadding
|
|
74
78
|
* @param fd The file descriptor.
|
|
75
79
|
* @param range The current range.
|
|
76
80
|
* @param size The number of bytes to read.
|
|
77
81
|
*/
|
|
78
|
-
readPadding(fd, range, size) {
|
|
82
|
+
#readPadding(fd, range, size) {
|
|
79
83
|
let bytesRead = 0;
|
|
80
|
-
const padding = this
|
|
84
|
+
const padding = this.#getPadding(range);
|
|
81
85
|
// If padding exists.
|
|
82
86
|
if (padding != null) {
|
|
83
87
|
const { length } = padding;
|
|
84
|
-
const begin = this
|
|
88
|
+
const begin = this.#bytesRead;
|
|
85
89
|
if (begin < length) {
|
|
86
90
|
bytesRead = Math.min(size, length - begin);
|
|
87
91
|
const end = begin + bytesRead;
|
|
88
92
|
this.push(padding.subarray(begin, end));
|
|
89
|
-
this
|
|
93
|
+
this.#bytesRead = end;
|
|
90
94
|
}
|
|
91
95
|
}
|
|
92
96
|
// If no padding or read completed.
|
|
93
97
|
if (bytesRead < size) {
|
|
94
|
-
this
|
|
98
|
+
this.#bytesRead = 0;
|
|
95
99
|
// Change read state.
|
|
96
|
-
switch (this
|
|
100
|
+
switch (this.#readState) {
|
|
97
101
|
case 0 /* ReadState.PREFIX */:
|
|
98
|
-
this
|
|
99
|
-
this
|
|
102
|
+
this.#readState = 1 /* ReadState.RANGE */;
|
|
103
|
+
this.#readFileRange(fd, range, size - bytesRead);
|
|
100
104
|
break;
|
|
101
105
|
case 2 /* ReadState.SUFFIX */:
|
|
102
|
-
this
|
|
103
|
-
this
|
|
104
|
-
const nextRange = this
|
|
106
|
+
this.#currentRangeIndex++;
|
|
107
|
+
this.#readState = 0 /* ReadState.PREFIX */;
|
|
108
|
+
const nextRange = this.#getRange();
|
|
105
109
|
if (nextRange == null) {
|
|
106
110
|
this.push(null);
|
|
107
111
|
} else {
|
|
108
|
-
this
|
|
112
|
+
this.#readPadding(fd, nextRange, size - bytesRead);
|
|
109
113
|
}
|
|
110
114
|
break;
|
|
111
115
|
}
|
|
@@ -113,23 +117,23 @@ class ReadStream extends stream.Readable {
|
|
|
113
117
|
}
|
|
114
118
|
/**
|
|
115
119
|
* @private
|
|
116
|
-
* @method readFileRange
|
|
120
|
+
* @method #readFileRange
|
|
117
121
|
* @param fd The file descriptor.
|
|
118
122
|
* @param range The current range.
|
|
119
123
|
* @param size The number of bytes to read.
|
|
120
124
|
*/
|
|
121
|
-
readFileRange(fd, range, size) {
|
|
125
|
+
#readFileRange(fd, range, size) {
|
|
122
126
|
const { length } = range;
|
|
123
|
-
const
|
|
127
|
+
const bytesRead = this.#bytesRead;
|
|
124
128
|
// File range not finished.
|
|
125
129
|
if (bytesRead < length) {
|
|
126
|
-
this
|
|
130
|
+
this.#reading = true;
|
|
127
131
|
const bytesToRead = Math.min(size, length - bytesRead);
|
|
128
|
-
const buffer
|
|
132
|
+
const buffer = node_buffer.Buffer.allocUnsafeSlow(bytesToRead);
|
|
129
133
|
const position = range.offset + bytesRead;
|
|
130
134
|
// Read file range.
|
|
131
|
-
this
|
|
132
|
-
this
|
|
135
|
+
this.#fs.read(fd, buffer, 0, bytesToRead, position, (readError, bytesRead, buffer) => {
|
|
136
|
+
this.#reading = false;
|
|
133
137
|
// Tell ._destroy() that it's safe to close the fd now.
|
|
134
138
|
if (this.destroyed) {
|
|
135
139
|
this.emit(DISPOSE_EVENT, readError);
|
|
@@ -139,19 +143,19 @@ class ReadStream extends stream.Readable {
|
|
|
139
143
|
this.destroy(new RangeError('invalid read operation'));
|
|
140
144
|
} else {
|
|
141
145
|
this.push(buffer);
|
|
142
|
-
this
|
|
146
|
+
this.#bytesRead += bytesRead;
|
|
143
147
|
// file range read completed.
|
|
144
148
|
if (bytesRead < size) {
|
|
145
|
-
this
|
|
146
|
-
this
|
|
147
|
-
this
|
|
149
|
+
this.#bytesRead = 0;
|
|
150
|
+
this.#readState = 2 /* ReadState.SUFFIX */;
|
|
151
|
+
this.#readPadding(fd, range, size - bytesRead);
|
|
148
152
|
}
|
|
149
153
|
}
|
|
150
154
|
});
|
|
151
155
|
} else {
|
|
152
|
-
this
|
|
153
|
-
this
|
|
154
|
-
this
|
|
156
|
+
this.#bytesRead = 0;
|
|
157
|
+
this.#readState = 2 /* ReadState.SUFFIX */;
|
|
158
|
+
this.#readPadding(fd, range, size);
|
|
155
159
|
}
|
|
156
160
|
}
|
|
157
161
|
/**
|
|
@@ -160,21 +164,21 @@ class ReadStream extends stream.Readable {
|
|
|
160
164
|
* @param size The number of bytes to read.
|
|
161
165
|
*/
|
|
162
166
|
_read(size) {
|
|
163
|
-
if (!this
|
|
164
|
-
const
|
|
165
|
-
const range = this
|
|
167
|
+
if (!this.#reading) {
|
|
168
|
+
const fd = this.#fd;
|
|
169
|
+
const range = this.#getRange();
|
|
166
170
|
// If fd or range is null, finish stream.
|
|
167
171
|
if (fd == null || range == null) {
|
|
168
172
|
this.push(null);
|
|
169
173
|
} else {
|
|
170
174
|
// Read bytes from range.
|
|
171
|
-
switch (this
|
|
175
|
+
switch (this.#readState) {
|
|
172
176
|
case 0 /* ReadState.PREFIX */:
|
|
173
177
|
case 2 /* ReadState.SUFFIX */:
|
|
174
|
-
this
|
|
178
|
+
this.#readPadding(fd, range, size);
|
|
175
179
|
break;
|
|
176
180
|
case 1 /* ReadState.RANGE */:
|
|
177
|
-
this
|
|
181
|
+
this.#readFileRange(fd, range, size);
|
|
178
182
|
break;
|
|
179
183
|
}
|
|
180
184
|
}
|
|
@@ -182,17 +186,17 @@ class ReadStream extends stream.Readable {
|
|
|
182
186
|
}
|
|
183
187
|
/**
|
|
184
188
|
* @private
|
|
185
|
-
* @method dispose
|
|
189
|
+
* @method #dispose
|
|
186
190
|
* @param error The error.
|
|
187
191
|
* @param callback The callback.
|
|
188
192
|
*/
|
|
189
|
-
dispose(error, callback) {
|
|
190
|
-
const
|
|
193
|
+
#dispose(error, callback) {
|
|
194
|
+
const fd = this.#fd;
|
|
191
195
|
if (fd != null) {
|
|
192
|
-
this
|
|
196
|
+
this.#fd = null;
|
|
193
197
|
// Close the fd.
|
|
194
|
-
this
|
|
195
|
-
callback(error
|
|
198
|
+
this.#fs.close(fd, closeError => {
|
|
199
|
+
callback(error ?? closeError);
|
|
196
200
|
});
|
|
197
201
|
} else {
|
|
198
202
|
callback(error);
|
|
@@ -206,12 +210,12 @@ class ReadStream extends stream.Readable {
|
|
|
206
210
|
*/
|
|
207
211
|
_destroy(error, callback) {
|
|
208
212
|
// Wait I/O completion.
|
|
209
|
-
if (this
|
|
213
|
+
if (this.#reading) {
|
|
210
214
|
this.once(DISPOSE_EVENT, disposeError => {
|
|
211
|
-
this
|
|
215
|
+
this.#dispose(error ?? disposeError, callback);
|
|
212
216
|
});
|
|
213
217
|
} else {
|
|
214
|
-
this
|
|
218
|
+
this.#dispose(error, callback);
|
|
215
219
|
}
|
|
216
220
|
}
|
|
217
221
|
}
|
package/cjs/ReadStream.d.cts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module ReadStream
|
|
3
3
|
*/
|
|
4
|
-
import { PathLike } from 'fs';
|
|
5
|
-
import { Readable } from 'stream';
|
|
6
4
|
import { Range } from './utils/http.cjs';
|
|
5
|
+
import { Readable } from 'node:stream';
|
|
7
6
|
import { FileSystem } from './utils/fs.cjs';
|
|
8
7
|
export interface Options {
|
|
9
8
|
fs: FileSystem;
|
|
@@ -13,67 +12,26 @@ interface Callback {
|
|
|
13
12
|
(error?: Error | null): void;
|
|
14
13
|
}
|
|
15
14
|
export declare class ReadStream extends Readable {
|
|
16
|
-
private
|
|
17
|
-
private readonly path;
|
|
18
|
-
private readonly ranges;
|
|
19
|
-
private bytesRead;
|
|
20
|
-
private fd;
|
|
21
|
-
private reading;
|
|
22
|
-
private currentRangeIndex;
|
|
23
|
-
private readState;
|
|
15
|
+
#private;
|
|
24
16
|
/**
|
|
25
17
|
* @constructor
|
|
26
18
|
* @param path The file path.
|
|
27
19
|
* @param ranges The ranges to read.
|
|
28
20
|
* @param options The stream options.
|
|
29
21
|
*/
|
|
30
|
-
constructor(path:
|
|
22
|
+
constructor(path: string, ranges: Range[], options: Options);
|
|
31
23
|
/**
|
|
32
24
|
* @override
|
|
33
25
|
* @method _construct
|
|
34
26
|
* @param callback The callback.
|
|
35
27
|
*/
|
|
36
28
|
_construct(callback: Callback): void;
|
|
37
|
-
/**
|
|
38
|
-
* @private
|
|
39
|
-
* @method getRange
|
|
40
|
-
*/
|
|
41
|
-
private getRange;
|
|
42
|
-
/**
|
|
43
|
-
* @private
|
|
44
|
-
* @method getPadding
|
|
45
|
-
* @param range The current range.
|
|
46
|
-
*/
|
|
47
|
-
private getPadding;
|
|
48
|
-
/**
|
|
49
|
-
* @private
|
|
50
|
-
* @method readPadding
|
|
51
|
-
* @param fd The file descriptor.
|
|
52
|
-
* @param range The current range.
|
|
53
|
-
* @param size The number of bytes to read.
|
|
54
|
-
*/
|
|
55
|
-
private readPadding;
|
|
56
|
-
/**
|
|
57
|
-
* @private
|
|
58
|
-
* @method readFileRange
|
|
59
|
-
* @param fd The file descriptor.
|
|
60
|
-
* @param range The current range.
|
|
61
|
-
* @param size The number of bytes to read.
|
|
62
|
-
*/
|
|
63
|
-
private readFileRange;
|
|
64
29
|
/**
|
|
65
30
|
* @override
|
|
66
31
|
* @method _read
|
|
67
32
|
* @param size The number of bytes to read.
|
|
68
33
|
*/
|
|
69
34
|
_read(size: number): void;
|
|
70
|
-
/**
|
|
71
|
-
* @private
|
|
72
|
-
* @method dispose
|
|
73
|
-
* @param error The error.
|
|
74
|
-
* @param callback The callback.
|
|
75
|
-
*/
|
|
76
|
-
private dispose;
|
|
77
35
|
/**
|
|
78
36
|
* @override
|
|
79
37
|
* @method _destroy
|
package/cjs/Service.cjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
+
* @module koa-files
|
|
2
3
|
* @package koa-files
|
|
3
4
|
* @license MIT
|
|
4
|
-
* @version
|
|
5
|
+
* @version 5.0.0
|
|
5
6
|
* @author nuintun <nuintun@qq.com>
|
|
6
7
|
* @description A static files serving middleware for koa.
|
|
7
8
|
* @see https://github.com/nuintun/koa-files#readme
|
|
@@ -10,11 +11,11 @@
|
|
|
10
11
|
'use strict';
|
|
11
12
|
|
|
12
13
|
const createETag = require('etag');
|
|
13
|
-
const fs = require('fs');
|
|
14
|
+
const fs = require('node:fs');
|
|
14
15
|
const ReadStream = require('./ReadStream.cjs');
|
|
15
16
|
const type = require('./utils/type.cjs');
|
|
16
17
|
const fs$1 = require('./utils/fs.cjs');
|
|
17
|
-
const
|
|
18
|
+
const node_path = require('node:path');
|
|
18
19
|
const path = require('./utils/path.cjs');
|
|
19
20
|
const http = require('./utils/http.cjs');
|
|
20
21
|
|
|
@@ -32,6 +33,8 @@ const fs__default = /*#__PURE__*/ _interopDefault(fs);
|
|
|
32
33
|
* @class Service
|
|
33
34
|
*/
|
|
34
35
|
class Service {
|
|
36
|
+
#root;
|
|
37
|
+
#options;
|
|
35
38
|
/**
|
|
36
39
|
* @constructor
|
|
37
40
|
* @description Create file service.
|
|
@@ -39,30 +42,30 @@ class Service {
|
|
|
39
42
|
* @param options The file service options.
|
|
40
43
|
*/
|
|
41
44
|
constructor(root, options = {}) {
|
|
42
|
-
|
|
43
|
-
this.root = path.unixify(path$1.resolve(root));
|
|
45
|
+
this.#root = path.unixify(node_path.resolve(root));
|
|
44
46
|
const { ignore, headers, highWaterMark = 65536 } = options;
|
|
45
|
-
this
|
|
46
|
-
|
|
47
|
+
this.#options = {
|
|
48
|
+
...options,
|
|
49
|
+
fs: options.fs ?? fs__default.default,
|
|
47
50
|
ignore: type.isFunction(ignore) ? ignore : () => false,
|
|
48
51
|
headers: type.isFunction(headers) ? headers : () => headers,
|
|
49
52
|
highWaterMark: type.isFunction(highWaterMark) ? highWaterMark : () => highWaterMark
|
|
50
|
-
}
|
|
53
|
+
};
|
|
51
54
|
}
|
|
52
55
|
/**
|
|
53
56
|
* @private
|
|
54
|
-
* @method setupHeaders
|
|
57
|
+
* @method #setupHeaders
|
|
55
58
|
* @description Setup headers.
|
|
56
59
|
* @param context The koa context.
|
|
57
60
|
* @param path The file path.
|
|
58
61
|
* @param stats The file stats.
|
|
59
62
|
*/
|
|
60
|
-
async setupHeaders({ response }, path, stats) {
|
|
61
|
-
const
|
|
63
|
+
async #setupHeaders({ response }, path, stats) {
|
|
64
|
+
const options = this.#options;
|
|
62
65
|
// Set status.
|
|
63
66
|
response.status = 200;
|
|
64
67
|
// Set Content-Type.
|
|
65
|
-
response.type =
|
|
68
|
+
response.type = node_path.extname(path);
|
|
66
69
|
// Get headers.
|
|
67
70
|
const headers = await options.headers(path, stats);
|
|
68
71
|
// If headers not empty, set headers.
|
|
@@ -114,21 +117,21 @@ class Service {
|
|
|
114
117
|
return context.throw(400);
|
|
115
118
|
}
|
|
116
119
|
// Get service root.
|
|
117
|
-
const
|
|
120
|
+
const root = this.#root;
|
|
118
121
|
// Get file path.
|
|
119
|
-
const path$
|
|
122
|
+
const path$1 = path.unixify(node_path.join(root, pathname));
|
|
120
123
|
// Malicious path (403).
|
|
121
|
-
if (path.isOutRoot(path$
|
|
124
|
+
if (path.isOutRoot(path$1, root)) {
|
|
122
125
|
return false;
|
|
123
126
|
}
|
|
124
127
|
// Get options.
|
|
125
|
-
const
|
|
128
|
+
const options = this.#options;
|
|
126
129
|
// Is ignore path or file (403).
|
|
127
|
-
if (await options.ignore(path$
|
|
130
|
+
if (await options.ignore(path$1)) {
|
|
128
131
|
return false;
|
|
129
132
|
}
|
|
130
133
|
// File stats.
|
|
131
|
-
const stats = await fs$1.stat(options.fs, path$
|
|
134
|
+
const stats = await fs$1.stat(options.fs, path$1);
|
|
132
135
|
// Check file stats.
|
|
133
136
|
if (
|
|
134
137
|
// File not exist (404 | 500).
|
|
@@ -136,14 +139,14 @@ class Service {
|
|
|
136
139
|
// Is directory (403).
|
|
137
140
|
stats.isDirectory() ||
|
|
138
141
|
// Not a directory but has trailing slash (404).
|
|
139
|
-
path.hasTrailingSlash(path$
|
|
142
|
+
path.hasTrailingSlash(path$1)
|
|
140
143
|
) {
|
|
141
144
|
return false;
|
|
142
145
|
}
|
|
143
146
|
// Koa response.
|
|
144
147
|
const { response } = context;
|
|
145
148
|
// Setup headers.
|
|
146
|
-
await this
|
|
149
|
+
await this.#setupHeaders(context, path$1, stats);
|
|
147
150
|
// Conditional get support.
|
|
148
151
|
if (http.isConditionalGET(context)) {
|
|
149
152
|
// Request precondition failed.
|
|
@@ -183,9 +186,9 @@ class Service {
|
|
|
183
186
|
return context.throw(400);
|
|
184
187
|
}
|
|
185
188
|
// Set response body.
|
|
186
|
-
response.body = new ReadStream.ReadStream(path$
|
|
189
|
+
response.body = new ReadStream.ReadStream(path$1, ranges, {
|
|
187
190
|
fs: options.fs,
|
|
188
|
-
highWaterMark: await options.highWaterMark(path$
|
|
191
|
+
highWaterMark: await options.highWaterMark(path$1, stats)
|
|
189
192
|
});
|
|
190
193
|
// File found.
|
|
191
194
|
return true;
|
package/cjs/Service.d.cts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @module Service
|
|
3
3
|
*/
|
|
4
4
|
import { Context } from 'koa';
|
|
5
|
-
import { Stats } from 'fs';
|
|
5
|
+
import { Stats } from 'node:fs';
|
|
6
6
|
import { FileSystem } from './utils/fs.cjs';
|
|
7
7
|
interface Headers {
|
|
8
8
|
[key: string]: string | string[];
|
|
@@ -29,8 +29,7 @@ export interface Options {
|
|
|
29
29
|
* @class Service
|
|
30
30
|
*/
|
|
31
31
|
export declare class Service {
|
|
32
|
-
private
|
|
33
|
-
private readonly options;
|
|
32
|
+
#private;
|
|
34
33
|
/**
|
|
35
34
|
* @constructor
|
|
36
35
|
* @description Create file service.
|
|
@@ -38,15 +37,6 @@ export declare class Service {
|
|
|
38
37
|
* @param options The file service options.
|
|
39
38
|
*/
|
|
40
39
|
constructor(root: string, options?: Options);
|
|
41
|
-
/**
|
|
42
|
-
* @private
|
|
43
|
-
* @method setupHeaders
|
|
44
|
-
* @description Setup headers.
|
|
45
|
-
* @param context The koa context.
|
|
46
|
-
* @param path The file path.
|
|
47
|
-
* @param stats The file stats.
|
|
48
|
-
*/
|
|
49
|
-
private setupHeaders;
|
|
50
40
|
/**
|
|
51
41
|
* @public
|
|
52
42
|
* @method respond
|
package/cjs/index.cjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
+
* @module koa-files
|
|
2
3
|
* @package koa-files
|
|
3
4
|
* @license MIT
|
|
4
|
-
* @version
|
|
5
|
+
* @version 5.0.0
|
|
5
6
|
* @author nuintun <nuintun@qq.com>
|
|
6
7
|
* @description A static files serving middleware for koa.
|
|
7
8
|
* @see https://github.com/nuintun/koa-files#readme
|
|
@@ -22,7 +23,7 @@ const Service = require('./Service.cjs');
|
|
|
22
23
|
*/
|
|
23
24
|
function server(root, options) {
|
|
24
25
|
const service = new Service.Service(root, options);
|
|
25
|
-
if (options
|
|
26
|
+
if (options?.defer) {
|
|
26
27
|
return async (context, next) => {
|
|
27
28
|
await next();
|
|
28
29
|
await service.respond(context);
|
package/cjs/utils/fs.cjs
CHANGED
package/cjs/utils/fs.d.cts
CHANGED
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module fs
|
|
3
3
|
*/
|
|
4
|
-
import
|
|
4
|
+
import { PathLike, Stats } from 'node:fs';
|
|
5
|
+
type Position = number | bigint | null;
|
|
6
|
+
type OpenMode = string | number | undefined;
|
|
7
|
+
interface Callback<T extends unknown[] = []> {
|
|
8
|
+
(error?: Error | null, ...rest: T): void;
|
|
9
|
+
}
|
|
5
10
|
export interface FileSystem {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
11
|
+
close(fd: number, callback?: Callback): void;
|
|
12
|
+
read<T extends ArrayBufferView>(
|
|
13
|
+
fd: number,
|
|
14
|
+
buffer: T,
|
|
15
|
+
offset: number,
|
|
16
|
+
length: number,
|
|
17
|
+
position: Position,
|
|
18
|
+
callback: Callback<[bytesRead: number, buffer: T]>
|
|
19
|
+
): void;
|
|
20
|
+
stat(path: PathLike, callback: Callback<[stats: Stats]>): void;
|
|
21
|
+
open(path: PathLike, flags: OpenMode, callback: Callback<[fd: number]>): void;
|
|
10
22
|
}
|
|
11
23
|
/**
|
|
12
24
|
* @function stat
|
|
@@ -15,3 +27,4 @@ export interface FileSystem {
|
|
|
15
27
|
* @param path The file path.
|
|
16
28
|
*/
|
|
17
29
|
export declare function stat(fs: FileSystem, path: string): Promise<Stats | null>;
|
|
30
|
+
export {};
|
package/cjs/utils/hash.cjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
+
* @module koa-files
|
|
2
3
|
* @package koa-files
|
|
3
4
|
* @license MIT
|
|
4
|
-
* @version
|
|
5
|
+
* @version 5.0.0
|
|
5
6
|
* @author nuintun <nuintun@qq.com>
|
|
6
7
|
* @description A static files serving middleware for koa.
|
|
7
8
|
* @see https://github.com/nuintun/koa-files#readme
|
|
@@ -9,7 +10,7 @@
|
|
|
9
10
|
|
|
10
11
|
'use strict';
|
|
11
12
|
|
|
12
|
-
const
|
|
13
|
+
const node_crypto = require('node:crypto');
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* @module hash
|
|
@@ -34,7 +35,7 @@ const CHARS = [
|
|
|
34
35
|
*/
|
|
35
36
|
function generate(length) {
|
|
36
37
|
let hash = '';
|
|
37
|
-
const randomValues =
|
|
38
|
+
const randomValues = node_crypto.getRandomValues(new Uint8Array(length));
|
|
38
39
|
// Create hash.
|
|
39
40
|
for (const value of randomValues) {
|
|
40
41
|
hash += CHARS[value % 62];
|
package/cjs/utils/http.cjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
+
* @module koa-files
|
|
2
3
|
* @package koa-files
|
|
3
4
|
* @license MIT
|
|
4
|
-
* @version
|
|
5
|
+
* @version 5.0.0
|
|
5
6
|
* @author nuintun <nuintun@qq.com>
|
|
6
7
|
* @description A static files serving middleware for koa.
|
|
7
8
|
* @see https://github.com/nuintun/koa-files#readme
|
|
@@ -9,8 +10,8 @@
|
|
|
9
10
|
|
|
10
11
|
'use strict';
|
|
11
12
|
|
|
12
|
-
const buffer = require('buffer');
|
|
13
13
|
const hash = require('./hash.cjs');
|
|
14
|
+
const node_buffer = require('node:buffer');
|
|
14
15
|
const parseRange = require('range-parser');
|
|
15
16
|
|
|
16
17
|
function _interopDefault(e) {
|
|
@@ -32,7 +33,7 @@ const SINGLE_ETAG_RE = /^(?:W\/)?"[ !#-\x7E\x80-\xFF]+"$/;
|
|
|
32
33
|
function decodeURI(URI) {
|
|
33
34
|
try {
|
|
34
35
|
return decodeURIComponent(URI);
|
|
35
|
-
} catch
|
|
36
|
+
} catch {
|
|
36
37
|
return -1;
|
|
37
38
|
}
|
|
38
39
|
}
|
|
@@ -155,7 +156,7 @@ function parseRanges(context, stats) {
|
|
|
155
156
|
// Multipart Content-Type.
|
|
156
157
|
const contentType = `Content-Type: ${response.type}`;
|
|
157
158
|
// Range suffix.
|
|
158
|
-
const suffix =
|
|
159
|
+
const suffix = node_buffer.Buffer.from(`\r\n--${boundary}--\r\n`);
|
|
159
160
|
// Override Content-Type.
|
|
160
161
|
// https://httpwg.org/specs/rfc9110.html#multipart.byteranges
|
|
161
162
|
response.type = `multipart/byteranges; boundary=${boundary}`;
|
|
@@ -164,7 +165,9 @@ function parseRanges(context, stats) {
|
|
|
164
165
|
const length = end - start + 1;
|
|
165
166
|
const head = index > 0 ? '\r\n' : '';
|
|
166
167
|
const contentRange = `Content-Range: bytes ${start}-${end}/${size}`;
|
|
167
|
-
const prefix =
|
|
168
|
+
const prefix = node_buffer.Buffer.from(
|
|
169
|
+
`${head}--${boundary}\r\n${contentType}\r\n${contentRange}\r\n\r\n`
|
|
170
|
+
);
|
|
168
171
|
// Compute Content-Length
|
|
169
172
|
contentLength += prefix.length + length;
|
|
170
173
|
// Cache range.
|
package/cjs/utils/http.d.cts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module http
|
|
3
3
|
*/
|
|
4
|
-
import { Stats } from 'fs';
|
|
5
4
|
import { Context } from 'koa';
|
|
6
|
-
import {
|
|
5
|
+
import { Stats } from 'node:fs';
|
|
6
|
+
import { Buffer } from 'node:buffer';
|
|
7
7
|
export interface Range {
|
|
8
8
|
offset: number;
|
|
9
9
|
length: number;
|