koa-files 2.3.7 → 2.3.8
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 +9 -11
- package/cjs/Files.cjs +389 -0
- package/cjs/index.cjs +12 -12
- package/cjs/utils.cjs +75 -78
- package/esm/Files.js +378 -0
- package/esm/index.js +12 -12
- package/esm/utils.js +71 -76
- package/package.json +5 -5
- package/types/Files.d.ts +98 -0
- package/types/index.d.ts +3 -3
- package/types/utils.d.ts +23 -27
- package/cjs/Send.cjs +0 -376
- package/esm/Send.js +0 -367
- package/types/Send.d.ts +0 -86
package/cjs/utils.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @package koa-files
|
|
3
3
|
* @license MIT
|
|
4
|
-
* @version 2.3.
|
|
4
|
+
* @version 2.3.8
|
|
5
5
|
* @author nuintun <nuintun@qq.com>
|
|
6
6
|
* @description A static files serving middleware for koa.
|
|
7
7
|
* @see https://github.com/nuintun/koa-files#readme
|
|
@@ -12,9 +12,11 @@
|
|
|
12
12
|
const fs = require('fs');
|
|
13
13
|
const path = require('path');
|
|
14
14
|
|
|
15
|
-
function _interopDefault
|
|
15
|
+
function _interopDefault(e) {
|
|
16
|
+
return e && e.__esModule ? e : { default: e };
|
|
17
|
+
}
|
|
16
18
|
|
|
17
|
-
const fs__default = /*#__PURE__*/_interopDefault(fs);
|
|
19
|
+
const fs__default = /*#__PURE__*/ _interopDefault(fs);
|
|
18
20
|
|
|
19
21
|
/**
|
|
20
22
|
* @module utils
|
|
@@ -24,122 +26,117 @@ const fs__default = /*#__PURE__*/_interopDefault(fs);
|
|
|
24
26
|
const CHARS = Array.from('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz');
|
|
25
27
|
/**
|
|
26
28
|
* @function isOutRoot
|
|
27
|
-
* @description
|
|
28
|
-
* @param
|
|
29
|
-
* @param
|
|
30
|
-
* @returns {boolean}
|
|
29
|
+
* @description Check if path is out of root
|
|
30
|
+
* @param path The path to check
|
|
31
|
+
* @param root The root path
|
|
31
32
|
*/
|
|
32
33
|
function isOutRoot(path$1, root) {
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
path$1 = path.relative(root, path$1);
|
|
35
|
+
return /\.\.(?:[\\/]|$)/.test(path$1) || path.isAbsolute(path$1);
|
|
35
36
|
}
|
|
36
37
|
/**
|
|
37
38
|
* @function unixify
|
|
38
|
-
* @description Convert path
|
|
39
|
-
* @param
|
|
40
|
-
* @returns {string}
|
|
39
|
+
* @description Convert path to unix style
|
|
40
|
+
* @param path The path to convert
|
|
41
41
|
*/
|
|
42
42
|
function unixify(path) {
|
|
43
|
-
|
|
43
|
+
return path.replace(/\\/g, '/');
|
|
44
44
|
}
|
|
45
45
|
/**
|
|
46
46
|
* @function decodeURI
|
|
47
|
-
* @description Decode URI component
|
|
48
|
-
* @param
|
|
49
|
-
* @returns {string|-1}
|
|
47
|
+
* @description Decode URI component
|
|
48
|
+
* @param URI The URI to decode
|
|
50
49
|
*/
|
|
51
50
|
function decodeURI(URI) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
51
|
+
try {
|
|
52
|
+
return decodeURIComponent(URI);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
return -1;
|
|
55
|
+
}
|
|
58
56
|
}
|
|
59
57
|
/**
|
|
60
58
|
* @function boundaryGenerator
|
|
61
|
-
* @description
|
|
62
|
-
* @returns {string}
|
|
59
|
+
* @description Generate a boundary
|
|
63
60
|
*/
|
|
64
61
|
function boundaryGenerator() {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
62
|
+
let boundary = '';
|
|
63
|
+
// Create boundary
|
|
64
|
+
for (let i = 0; i < 38; i++) {
|
|
65
|
+
boundary += CHARS[Math.floor(Math.random() * 62)];
|
|
66
|
+
}
|
|
67
|
+
// Return boundary
|
|
68
|
+
return boundary;
|
|
72
69
|
}
|
|
73
70
|
/**
|
|
74
71
|
* @function fstat
|
|
75
|
-
* @
|
|
76
|
-
* @
|
|
72
|
+
* @description Get file stats
|
|
73
|
+
* @param path The file path
|
|
77
74
|
*/
|
|
78
75
|
function fstat(path) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
});
|
|
76
|
+
return new Promise((resolve, reject) => {
|
|
77
|
+
fs__default.default.stat(path, (error, stats) => {
|
|
78
|
+
error ? reject(error) : resolve(stats);
|
|
83
79
|
});
|
|
80
|
+
});
|
|
84
81
|
}
|
|
85
82
|
/**
|
|
86
83
|
* @function hasTrailingSlash
|
|
87
|
-
* @
|
|
88
|
-
* @
|
|
84
|
+
* @description Check if path has trailing slash
|
|
85
|
+
* @param path The path to check
|
|
89
86
|
*/
|
|
90
87
|
function hasTrailingSlash(path) {
|
|
91
|
-
|
|
88
|
+
return /\/$/.test(path);
|
|
92
89
|
}
|
|
93
90
|
/**
|
|
94
91
|
* @function parseTokens
|
|
95
|
-
* @description Parse
|
|
96
|
-
* @param
|
|
92
|
+
* @description Parse HTTP tokens
|
|
93
|
+
* @param value The tokens value string
|
|
97
94
|
*/
|
|
98
95
|
function parseTokens(value) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
break;
|
|
111
|
-
case 0x2c:
|
|
112
|
-
// ','
|
|
113
|
-
tokens.push(value.substring(start, end));
|
|
114
|
-
start = end = i + 1;
|
|
115
|
-
break;
|
|
116
|
-
default:
|
|
117
|
-
end = i + 1;
|
|
118
|
-
break;
|
|
96
|
+
let end = 0;
|
|
97
|
+
let start = 0;
|
|
98
|
+
let tokens = [];
|
|
99
|
+
// gather tokens
|
|
100
|
+
for (let i = 0, length = value.length; i < length; i++) {
|
|
101
|
+
switch (value.charCodeAt(i)) {
|
|
102
|
+
case 0x20:
|
|
103
|
+
// ' '
|
|
104
|
+
if (start === end) {
|
|
105
|
+
start = end = i + 1;
|
|
119
106
|
}
|
|
107
|
+
break;
|
|
108
|
+
case 0x2c:
|
|
109
|
+
// ','
|
|
110
|
+
tokens.push(value.substring(start, end));
|
|
111
|
+
start = end = i + 1;
|
|
112
|
+
break;
|
|
113
|
+
default:
|
|
114
|
+
end = i + 1;
|
|
115
|
+
break;
|
|
120
116
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
117
|
+
}
|
|
118
|
+
// final token
|
|
119
|
+
tokens.push(value.substring(start, end));
|
|
120
|
+
return tokens;
|
|
124
121
|
}
|
|
125
122
|
/**
|
|
126
|
-
* @function
|
|
127
|
-
* @
|
|
128
|
-
* @param
|
|
129
|
-
* @returns {boolean}
|
|
123
|
+
* @function isETag
|
|
124
|
+
* @description Check if etag is valid
|
|
125
|
+
* @param value The value to check
|
|
130
126
|
*/
|
|
131
|
-
function
|
|
132
|
-
|
|
133
|
-
return match === etag || match === 'W/' + etag || 'W/' + match === etag;
|
|
134
|
-
});
|
|
127
|
+
function isETag(value) {
|
|
128
|
+
return /^(?:W\/)?"[\s\S]+"$/.test(value);
|
|
135
129
|
}
|
|
136
130
|
/**
|
|
137
|
-
* @function
|
|
138
|
-
* @
|
|
139
|
-
* @
|
|
131
|
+
* @function isETagFresh
|
|
132
|
+
* @description Check if etag is fresh
|
|
133
|
+
* @param match The match value
|
|
134
|
+
* @param etag The etag value
|
|
140
135
|
*/
|
|
141
|
-
function
|
|
142
|
-
|
|
136
|
+
function isETagFresh(match, etag) {
|
|
137
|
+
return parseTokens(match).some(match => {
|
|
138
|
+
return match === etag || match === 'W/' + etag || 'W/' + match === etag;
|
|
139
|
+
});
|
|
143
140
|
}
|
|
144
141
|
|
|
145
142
|
exports.boundaryGenerator = boundaryGenerator;
|
package/esm/Files.js
ADDED
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @package koa-files
|
|
3
|
+
* @license MIT
|
|
4
|
+
* @version 2.3.8
|
|
5
|
+
* @author nuintun <nuintun@qq.com>
|
|
6
|
+
* @description A static files serving middleware for koa.
|
|
7
|
+
* @see https://github.com/nuintun/koa-files#readme
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import etag from 'etag';
|
|
11
|
+
import destroy from 'destroy';
|
|
12
|
+
import fs from 'fs';
|
|
13
|
+
import { PassThrough } from 'stream';
|
|
14
|
+
import parseRange from 'range-parser';
|
|
15
|
+
import { resolve, extname, join } from 'path';
|
|
16
|
+
import { unixify, isETagFresh, isETag, boundaryGenerator, decodeURI, isOutRoot, fstat, hasTrailingSlash } from './utils.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @module Files
|
|
20
|
+
* @license MIT
|
|
21
|
+
* @author nuintun
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* @class Files
|
|
25
|
+
*/
|
|
26
|
+
class Files {
|
|
27
|
+
/**
|
|
28
|
+
* @constructor
|
|
29
|
+
* @description Create files service
|
|
30
|
+
* @param root Files service root
|
|
31
|
+
* @param options Files service options
|
|
32
|
+
*/
|
|
33
|
+
constructor(root, options = {}) {
|
|
34
|
+
this.options = options;
|
|
35
|
+
this.root = unixify(resolve(root));
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* @private
|
|
39
|
+
* @method isConditionalGET
|
|
40
|
+
* @description Check if request is conditional GET
|
|
41
|
+
* @param context Koa context
|
|
42
|
+
*/
|
|
43
|
+
isConditionalGET(context) {
|
|
44
|
+
const { request } = context;
|
|
45
|
+
return !!(
|
|
46
|
+
request.get('If-Match') ||
|
|
47
|
+
request.get('If-None-Match') ||
|
|
48
|
+
request.get('If-Modified-Since') ||
|
|
49
|
+
request.get('if-Unmodified-Since')
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* @private
|
|
54
|
+
* @method isPreconditionFailure
|
|
55
|
+
* @description Check if request precondition failure
|
|
56
|
+
* @param context Koa context
|
|
57
|
+
*/
|
|
58
|
+
isPreconditionFailure(context) {
|
|
59
|
+
const { request, response } = context;
|
|
60
|
+
// If-Match
|
|
61
|
+
const match = request.get('If-Match');
|
|
62
|
+
if (match) {
|
|
63
|
+
const etag = response.get('ETag');
|
|
64
|
+
return !etag || (match !== '*' && !isETagFresh(match, etag));
|
|
65
|
+
}
|
|
66
|
+
// If-Unmodified-Since
|
|
67
|
+
const unmodifiedSince = Date.parse(request.get('If-Unmodified-Since'));
|
|
68
|
+
if (!isNaN(unmodifiedSince)) {
|
|
69
|
+
const lastModified = Date.parse(response.get('Last-Modified'));
|
|
70
|
+
return isNaN(lastModified) || lastModified > unmodifiedSince;
|
|
71
|
+
}
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* @private
|
|
76
|
+
* @method isRangeFresh
|
|
77
|
+
* @description Check if request range fresh
|
|
78
|
+
* @param context Koa context
|
|
79
|
+
*/
|
|
80
|
+
isRangeFresh(context) {
|
|
81
|
+
const { request, response } = context;
|
|
82
|
+
const ifRange = request.get('If-Range');
|
|
83
|
+
// No If-Range
|
|
84
|
+
if (!ifRange) {
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
// If-Range as etag
|
|
88
|
+
if (isETag(ifRange)) {
|
|
89
|
+
const etag = response.get('ETag');
|
|
90
|
+
return !!(etag && isETagFresh(ifRange, etag));
|
|
91
|
+
}
|
|
92
|
+
// If-Range as modified date
|
|
93
|
+
const lastModified = response.get('Last-Modified');
|
|
94
|
+
return Date.parse(lastModified) <= Date.parse(ifRange);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* @private
|
|
98
|
+
* @method isIgnore
|
|
99
|
+
* @description Check if path is ignore
|
|
100
|
+
* @param path File path
|
|
101
|
+
*/
|
|
102
|
+
isIgnore(path) {
|
|
103
|
+
const { ignore } = this.options;
|
|
104
|
+
return (typeof ignore === 'function' ? ignore(path) : false) === true;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* @private
|
|
108
|
+
* @method parseRange
|
|
109
|
+
* @description Parse range
|
|
110
|
+
* @param context Koa context
|
|
111
|
+
* @param stats File stats
|
|
112
|
+
*/
|
|
113
|
+
parseRange(context, stats) {
|
|
114
|
+
const { size } = stats;
|
|
115
|
+
const { request } = context;
|
|
116
|
+
// Content-Length
|
|
117
|
+
let contentLength = size;
|
|
118
|
+
// Ranges
|
|
119
|
+
const ranges = [];
|
|
120
|
+
// Range support
|
|
121
|
+
if (this.options.acceptRanges !== false) {
|
|
122
|
+
const range = request.get('Range');
|
|
123
|
+
// Range fresh
|
|
124
|
+
if (range && this.isRangeFresh(context)) {
|
|
125
|
+
// Parse range -1 -2 or []
|
|
126
|
+
const parsed = parseRange(size, range, { combine: true });
|
|
127
|
+
// -1 signals an unsatisfiable range
|
|
128
|
+
// -2 signals a malformed header string
|
|
129
|
+
if (parsed === -1 || parsed === -2) {
|
|
130
|
+
return parsed;
|
|
131
|
+
}
|
|
132
|
+
// Ranges ok, support multiple ranges
|
|
133
|
+
if (parsed.type === 'bytes') {
|
|
134
|
+
// Set 206 status
|
|
135
|
+
context.status = 206;
|
|
136
|
+
// Multiple ranges
|
|
137
|
+
if (parsed.length > 1) {
|
|
138
|
+
// Reset content-length
|
|
139
|
+
contentLength = 0;
|
|
140
|
+
// Range boundary
|
|
141
|
+
const boundary = `<${boundaryGenerator()}>`;
|
|
142
|
+
const suffix = `\r\n--${boundary}--\r\n`;
|
|
143
|
+
const contentType = `Content-Type: ${context.type}`;
|
|
144
|
+
context.type = `multipart/byteranges; boundary=${boundary}`;
|
|
145
|
+
// Map ranges
|
|
146
|
+
parsed.forEach(({ start, end }, index) => {
|
|
147
|
+
// The first prefix boundary no \r\n
|
|
148
|
+
const prefixHead = index > 0 ? '\r\n' : '';
|
|
149
|
+
const contentRange = `Content-Range: bytes ${start}-${end}/${size}`;
|
|
150
|
+
const prefix = `${prefixHead}--${boundary}\r\n${contentType}\r\n${contentRange}\r\n\r\n`;
|
|
151
|
+
// Compute content-length
|
|
152
|
+
contentLength += end - start + 1 + Buffer.byteLength(prefix);
|
|
153
|
+
// Cache range
|
|
154
|
+
ranges.push({ start, end, prefix });
|
|
155
|
+
});
|
|
156
|
+
// The last add suffix boundary
|
|
157
|
+
ranges[ranges.length - 1].suffix = suffix;
|
|
158
|
+
// Compute content-length
|
|
159
|
+
contentLength += Buffer.byteLength(suffix);
|
|
160
|
+
} else {
|
|
161
|
+
const { start, end } = parsed[0];
|
|
162
|
+
context.set('Content-Range', `bytes ${start}-${end}/${size}`);
|
|
163
|
+
// Compute content-length
|
|
164
|
+
contentLength = end - start + 1;
|
|
165
|
+
// Cache range
|
|
166
|
+
ranges.push({ start, end });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// Set Content-Length
|
|
172
|
+
context.length = contentLength;
|
|
173
|
+
return ranges.length ? ranges : [{ start: 0 }];
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* @private
|
|
177
|
+
* @method setupHeaders
|
|
178
|
+
* @description Setup headers
|
|
179
|
+
* @param context Koa context
|
|
180
|
+
* @param path File path
|
|
181
|
+
* @param stats File stats
|
|
182
|
+
*/
|
|
183
|
+
setupHeaders(context, path, stats) {
|
|
184
|
+
const { options } = this;
|
|
185
|
+
const { toString } = Object.prototype;
|
|
186
|
+
const { acceptRanges, cacheControl, lastModified } = options;
|
|
187
|
+
// Set status
|
|
188
|
+
context.status = 200;
|
|
189
|
+
// Set Content-Type
|
|
190
|
+
context.type = extname(path);
|
|
191
|
+
// ETag
|
|
192
|
+
if (options.etag !== false) {
|
|
193
|
+
// Set ETag
|
|
194
|
+
context.set('ETag', etag(stats));
|
|
195
|
+
}
|
|
196
|
+
// Accept-Ranges
|
|
197
|
+
if (acceptRanges !== false) {
|
|
198
|
+
// Set Accept-Ranges
|
|
199
|
+
context.set('Accept-Ranges', 'bytes');
|
|
200
|
+
}
|
|
201
|
+
// Last-Modified
|
|
202
|
+
if (lastModified !== false) {
|
|
203
|
+
// Set mtime utc string
|
|
204
|
+
context.set('Last-Modified', stats.mtime.toUTCString());
|
|
205
|
+
}
|
|
206
|
+
// Cache-Control
|
|
207
|
+
if (cacheControl && toString.call(cacheControl) === '[object String]') {
|
|
208
|
+
// Set Cache-Control
|
|
209
|
+
context.set('Cache-Control', cacheControl);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* @private
|
|
214
|
+
* @method read
|
|
215
|
+
* @description Read file
|
|
216
|
+
* @param path File path
|
|
217
|
+
* @param range Read range
|
|
218
|
+
* @param buffer Destination stream
|
|
219
|
+
* @param end Is destory destination stream after read
|
|
220
|
+
*/
|
|
221
|
+
read(path, range, buffer, end) {
|
|
222
|
+
return new Promise((resolve, reject) => {
|
|
223
|
+
// Create file stream
|
|
224
|
+
const file = fs.createReadStream(path, range);
|
|
225
|
+
// File read stream open
|
|
226
|
+
if (range.prefix) {
|
|
227
|
+
file.once('open', () => {
|
|
228
|
+
// Write prefix boundary
|
|
229
|
+
buffer.write(range.prefix);
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
// File read stream error
|
|
233
|
+
file.once('error', error => {
|
|
234
|
+
// Unpipe
|
|
235
|
+
file.unpipe(buffer);
|
|
236
|
+
// Destroy file stream
|
|
237
|
+
destroy(file);
|
|
238
|
+
// Reject
|
|
239
|
+
reject(error);
|
|
240
|
+
});
|
|
241
|
+
// File read stream end
|
|
242
|
+
if (range.suffix) {
|
|
243
|
+
file.once('end', () => {
|
|
244
|
+
// Push suffix boundary
|
|
245
|
+
buffer.write(range.suffix);
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
// File read stream close
|
|
249
|
+
file.once('close', () => {
|
|
250
|
+
// Unpipe
|
|
251
|
+
file.unpipe(buffer);
|
|
252
|
+
// Destroy file stream
|
|
253
|
+
destroy(file);
|
|
254
|
+
// Resolve
|
|
255
|
+
resolve(true);
|
|
256
|
+
});
|
|
257
|
+
// Write data to buffer
|
|
258
|
+
file.pipe(buffer, { end });
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* @private
|
|
263
|
+
* @method send
|
|
264
|
+
* @description Send file
|
|
265
|
+
* @param context Koa context
|
|
266
|
+
* @param path File path
|
|
267
|
+
* @param ranges Read ranges
|
|
268
|
+
*/
|
|
269
|
+
async send(context, path, ranges) {
|
|
270
|
+
// Ranges length
|
|
271
|
+
let { length } = ranges;
|
|
272
|
+
// Set stream body, highWaterMark 64kb
|
|
273
|
+
const stream = new PassThrough({ highWaterMark: 65536 });
|
|
274
|
+
// Set response body
|
|
275
|
+
context.body = stream;
|
|
276
|
+
// Read file ranges
|
|
277
|
+
try {
|
|
278
|
+
for (const range of ranges) {
|
|
279
|
+
await this.read(path, range, stream, --length === 0);
|
|
280
|
+
}
|
|
281
|
+
} catch (error) {
|
|
282
|
+
// End stream when read exception
|
|
283
|
+
stream.end();
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* @public
|
|
288
|
+
* @method response
|
|
289
|
+
* @description Response to koa context
|
|
290
|
+
* @param context Koa context
|
|
291
|
+
*/
|
|
292
|
+
async response(context) {
|
|
293
|
+
const { root } = this;
|
|
294
|
+
// Only support GET and HEAD (405)
|
|
295
|
+
if (context.method !== 'GET' && context.method !== 'HEAD') {
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
// Get path of file
|
|
299
|
+
const pathname = decodeURI(context.path);
|
|
300
|
+
const path = pathname === -1 ? pathname : unixify(join(root, pathname));
|
|
301
|
+
// Path -1 or null byte(s)
|
|
302
|
+
if (path === -1 || path.includes('\0')) {
|
|
303
|
+
return context.throw(400);
|
|
304
|
+
}
|
|
305
|
+
// Malicious path (403)
|
|
306
|
+
if (isOutRoot(path, root)) {
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
// Is ignore path or file (403)
|
|
310
|
+
if (this.isIgnore(path)) {
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
// File stats
|
|
314
|
+
let stats;
|
|
315
|
+
// Get file stats
|
|
316
|
+
try {
|
|
317
|
+
stats = await fstat(path);
|
|
318
|
+
} catch (error) {
|
|
319
|
+
// 404 | 500
|
|
320
|
+
return false;
|
|
321
|
+
}
|
|
322
|
+
// File not exist (404 | 500)
|
|
323
|
+
if (!stats) {
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
326
|
+
// Is directory (403)
|
|
327
|
+
if (stats.isDirectory()) {
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
330
|
+
// Not a directory but has trailing slash (404)
|
|
331
|
+
if (hasTrailingSlash(path)) {
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
// Setup headers
|
|
335
|
+
this.setupHeaders(context, path, stats);
|
|
336
|
+
// Conditional get support
|
|
337
|
+
if (this.isConditionalGET(context)) {
|
|
338
|
+
// Request precondition failure
|
|
339
|
+
if (this.isPreconditionFailure(context)) {
|
|
340
|
+
return context.throw(412);
|
|
341
|
+
}
|
|
342
|
+
// Request fresh (304)
|
|
343
|
+
if (context.fresh) {
|
|
344
|
+
// Set status
|
|
345
|
+
context.status = 304;
|
|
346
|
+
// Set body null
|
|
347
|
+
context.body = null;
|
|
348
|
+
return true;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
// Head request
|
|
352
|
+
if (context.method === 'HEAD') {
|
|
353
|
+
// Set content-length
|
|
354
|
+
context.length = stats.size;
|
|
355
|
+
// Set body null
|
|
356
|
+
context.body = null;
|
|
357
|
+
return true;
|
|
358
|
+
}
|
|
359
|
+
// Parsed ranges
|
|
360
|
+
const ranges = this.parseRange(context, stats);
|
|
361
|
+
// 416
|
|
362
|
+
if (ranges === -1) {
|
|
363
|
+
// Set content-range
|
|
364
|
+
context.set('Content-Range', `bytes */${stats.size}`);
|
|
365
|
+
// Unsatisfiable 416
|
|
366
|
+
return context.throw(416);
|
|
367
|
+
}
|
|
368
|
+
// 400
|
|
369
|
+
if (ranges === -2) {
|
|
370
|
+
return context.throw(400);
|
|
371
|
+
}
|
|
372
|
+
// Send file
|
|
373
|
+
this.send(context, path, ranges);
|
|
374
|
+
return true;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
export { Files as default };
|
package/esm/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @package koa-files
|
|
3
3
|
* @license MIT
|
|
4
|
-
* @version 2.3.
|
|
4
|
+
* @version 2.3.8
|
|
5
5
|
* @author nuintun <nuintun@qq.com>
|
|
6
6
|
* @description A static files serving middleware for koa.
|
|
7
7
|
* @see https://github.com/nuintun/koa-files#readme
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import
|
|
10
|
+
import Files from './Files.js';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* @module index
|
|
@@ -20,18 +20,18 @@ import Send from './Send.js';
|
|
|
20
20
|
* @param {Options} options
|
|
21
21
|
*/
|
|
22
22
|
function server(root, options) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
return async (context, next) => {
|
|
26
|
-
await next();
|
|
27
|
-
await send.response(context);
|
|
28
|
-
};
|
|
29
|
-
}
|
|
23
|
+
const files = new Files(root, options);
|
|
24
|
+
if (options && options.defer) {
|
|
30
25
|
return async (context, next) => {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
26
|
+
await next();
|
|
27
|
+
await files.response(context);
|
|
34
28
|
};
|
|
29
|
+
}
|
|
30
|
+
return async (context, next) => {
|
|
31
|
+
if (!(await files.response(context))) {
|
|
32
|
+
await next();
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
export { server as default };
|