ultimate-express 1.2.15 → 1.2.16
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/EXPRESS_LICENSE +25 -25
- package/LICENSE +201 -201
- package/README.md +314 -314
- package/package.json +87 -86
- package/src/application.js +302 -302
- package/src/index.js +43 -43
- package/src/middlewares.js +316 -316
- package/src/request.js +402 -402
- package/src/response.js +762 -762
- package/src/router.js +541 -541
- package/src/utils.js +338 -338
- package/src/view.js +118 -118
- package/src/worker.js +30 -30
package/src/utils.js
CHANGED
|
@@ -1,339 +1,339 @@
|
|
|
1
|
-
/*
|
|
2
|
-
Copyright 2024 dimden.dev
|
|
3
|
-
|
|
4
|
-
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
-
you may not use this file except in compliance with the License.
|
|
6
|
-
You may obtain a copy of the License at
|
|
7
|
-
|
|
8
|
-
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
|
|
10
|
-
Unless required by applicable law or agreed to in writing, software
|
|
11
|
-
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
-
See the License for the specific language governing permissions and
|
|
14
|
-
limitations under the License.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
const mime = require("mime-types");
|
|
18
|
-
const path = require("path");
|
|
19
|
-
const proxyaddr = require("proxy-addr");
|
|
20
|
-
const qs = require("qs");
|
|
21
|
-
const querystring = require("fast-querystring");
|
|
22
|
-
const etag = require("etag");
|
|
23
|
-
const { Stats } = require("fs");
|
|
24
|
-
|
|
25
|
-
function fastQueryParse(query, options) {
|
|
26
|
-
if(query.length <= 128) {
|
|
27
|
-
if(!query.includes('[') && !query.includes('%5B') && !query.includes('.') && !query.includes('%2E')) {
|
|
28
|
-
return querystring.parse(query);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
return qs.parse(query, options);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function removeDuplicateSlashes(path) {
|
|
35
|
-
return path.replace(/\/{2,}/g, '/');
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function patternToRegex(pattern, isPrefix = false) {
|
|
39
|
-
if(pattern instanceof RegExp) {
|
|
40
|
-
return pattern;
|
|
41
|
-
}
|
|
42
|
-
if(isPrefix && pattern === '') {
|
|
43
|
-
return new RegExp(``);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
let regexPattern = pattern
|
|
47
|
-
.replace(/\./g, '\\.')
|
|
48
|
-
.replace(/\-/g, '\\-')
|
|
49
|
-
.replace(/\*/g, '(.*)') // Convert * to .*
|
|
50
|
-
.replace(/:(\w+)(\(.+?\))?/g, (match, param, regex) => {
|
|
51
|
-
return `(?<${param}>${regex ? regex + '($|\\/)' : '[^/]+'})`;
|
|
52
|
-
}); // Convert :param to capture group
|
|
53
|
-
|
|
54
|
-
return new RegExp(`^${regexPattern}${isPrefix ? '(?=$|\/)' : '$'}`);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function needsConversionToRegex(pattern) {
|
|
58
|
-
if(pattern instanceof RegExp) {
|
|
59
|
-
return false;
|
|
60
|
-
}
|
|
61
|
-
if(pattern === '/*') {
|
|
62
|
-
return false;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return pattern.includes('*') ||
|
|
66
|
-
pattern.includes('?') ||
|
|
67
|
-
pattern.includes('+') ||
|
|
68
|
-
pattern.includes('(') ||
|
|
69
|
-
pattern.includes(')') ||
|
|
70
|
-
pattern.includes(':') ||
|
|
71
|
-
pattern.includes('{') ||
|
|
72
|
-
pattern.includes('}') ||
|
|
73
|
-
pattern.includes('[') ||
|
|
74
|
-
pattern.includes(']');
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function canBeOptimized(pattern) {
|
|
78
|
-
if(pattern === '/*') {
|
|
79
|
-
return false;
|
|
80
|
-
}
|
|
81
|
-
if(pattern instanceof RegExp) {
|
|
82
|
-
return false;
|
|
83
|
-
}
|
|
84
|
-
if(
|
|
85
|
-
pattern.includes('*') ||
|
|
86
|
-
pattern.includes('?') ||
|
|
87
|
-
pattern.includes('+') ||
|
|
88
|
-
pattern.includes('(') ||
|
|
89
|
-
pattern.includes(')') ||
|
|
90
|
-
pattern.includes('{') ||
|
|
91
|
-
pattern.includes('}') ||
|
|
92
|
-
pattern.includes('[') ||
|
|
93
|
-
pattern.includes(']')
|
|
94
|
-
) {
|
|
95
|
-
return false;
|
|
96
|
-
}
|
|
97
|
-
return true;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function acceptParams(str) {
|
|
101
|
-
const parts = str.split(/ *; */);
|
|
102
|
-
const ret = { value: parts[0], quality: 1, params: {} }
|
|
103
|
-
|
|
104
|
-
for (let i = 1; i < parts.length; ++i) {
|
|
105
|
-
const pms = parts[i].split(/ *= */);
|
|
106
|
-
if ('q' === pms[0]) {
|
|
107
|
-
ret.quality = parseFloat(pms[1]);
|
|
108
|
-
} else {
|
|
109
|
-
ret.params[pms[0]] = pms[1];
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
return ret;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function normalizeType(type) {
|
|
117
|
-
return ~type.indexOf('/') ?
|
|
118
|
-
acceptParams(type) :
|
|
119
|
-
{ value: (mime.lookup(type) || 'application/octet-stream'), params: {} };
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function stringify(value, replacer, spaces, escape) {
|
|
123
|
-
let json = replacer || spaces
|
|
124
|
-
? JSON.stringify(value, replacer, spaces)
|
|
125
|
-
: JSON.stringify(value);
|
|
126
|
-
|
|
127
|
-
if (escape && typeof json === 'string') {
|
|
128
|
-
json = json.replace(/[<>&]/g, function (c) {
|
|
129
|
-
switch (c.charCodeAt(0)) {
|
|
130
|
-
case 0x3c:
|
|
131
|
-
return '\\u003c'
|
|
132
|
-
case 0x3e:
|
|
133
|
-
return '\\u003e'
|
|
134
|
-
case 0x26:
|
|
135
|
-
return '\\u0026'
|
|
136
|
-
default:
|
|
137
|
-
return c
|
|
138
|
-
}
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return json;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const defaultSettings = {
|
|
146
|
-
'jsonp callback name': 'callback',
|
|
147
|
-
'env': () => process.env.NODE_ENV ?? 'development',
|
|
148
|
-
'etag': 'weak',
|
|
149
|
-
'etag fn': () => createETagGenerator({ weak: true }),
|
|
150
|
-
'query parser': 'extended',
|
|
151
|
-
'query parser fn': () => fastQueryParse,
|
|
152
|
-
'subdomain offset': 2,
|
|
153
|
-
'trust proxy': false,
|
|
154
|
-
'views': () => path.join(process.cwd(), 'views'),
|
|
155
|
-
'view cache': () => process.env.NODE_ENV === 'production',
|
|
156
|
-
'x-powered-by': true,
|
|
157
|
-
'case sensitive routing': true
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
function compileTrust(val) {
|
|
161
|
-
if (typeof val === 'function') return val;
|
|
162
|
-
|
|
163
|
-
if (val === true) {
|
|
164
|
-
// Support plain true/false
|
|
165
|
-
return function(){ return true };
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (typeof val === 'number') {
|
|
169
|
-
// Support trusting hop count
|
|
170
|
-
return function(a, i){ return i < val };
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
if (typeof val === 'string') {
|
|
174
|
-
// Support comma-separated values
|
|
175
|
-
val = val.split(',')
|
|
176
|
-
.map(function (v) { return v.trim() })
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
return proxyaddr.compile(val || []);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const shownWarnings = new Set();
|
|
183
|
-
function deprecated(oldMethod, newMethod, full = false) {
|
|
184
|
-
const err = new Error();
|
|
185
|
-
const pos = full ? err.stack.split('\n').slice(1).join('\n') : err.stack.split('\n')[3].trim().split('(').slice(1).join('(').split(')').slice(0, -1).join(')');
|
|
186
|
-
if(shownWarnings.has(pos)) return;
|
|
187
|
-
shownWarnings.add(pos);
|
|
188
|
-
console.warn(`${new Date().toLocaleString('en-UK', {
|
|
189
|
-
weekday: 'short',
|
|
190
|
-
year: 'numeric',
|
|
191
|
-
month: 'short',
|
|
192
|
-
day: 'numeric',
|
|
193
|
-
hour: 'numeric',
|
|
194
|
-
minute: 'numeric',
|
|
195
|
-
second: 'numeric',
|
|
196
|
-
timeZone: 'GMT',
|
|
197
|
-
timeZoneName: 'short'
|
|
198
|
-
})} u-express deprecated ${oldMethod}: Use ${newMethod} instead at ${pos}`);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
function findIndexStartingFrom(arr, fn, index = 0) {
|
|
202
|
-
for(let i = index; i < arr.length; i++) {
|
|
203
|
-
if(fn(arr[i], i, arr)) {
|
|
204
|
-
return i;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
return -1;
|
|
208
|
-
};
|
|
209
|
-
|
|
210
|
-
function decode (path) {
|
|
211
|
-
try {
|
|
212
|
-
return decodeURIComponent(path)
|
|
213
|
-
} catch (err) {
|
|
214
|
-
return -1
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
const UP_PATH_REGEXP = /(?:^|[\\/])\.\.(?:[\\/]|$)/;
|
|
219
|
-
|
|
220
|
-
function containsDotFile(parts) {
|
|
221
|
-
for(let i = 0; i < parts.length; i++) {
|
|
222
|
-
const part = parts[i];
|
|
223
|
-
if(part.length > 1 && part[0] === '.') {
|
|
224
|
-
return true;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
return false;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
function parseTokenList(str) {
|
|
232
|
-
let end = 0;
|
|
233
|
-
const list = [];
|
|
234
|
-
let start = 0;
|
|
235
|
-
|
|
236
|
-
// gather tokens
|
|
237
|
-
for (let i = 0, len = str.length; i < len; i++) {
|
|
238
|
-
switch(str.charCodeAt(i)) {
|
|
239
|
-
case 0x20: /* */
|
|
240
|
-
if (start === end) {
|
|
241
|
-
start = end = i + 1;
|
|
242
|
-
}
|
|
243
|
-
break;
|
|
244
|
-
case 0x2c: /* , */
|
|
245
|
-
if (start !== end) {
|
|
246
|
-
list.push(str.substring(start, end));
|
|
247
|
-
}
|
|
248
|
-
start = end = i + 1;
|
|
249
|
-
break;
|
|
250
|
-
default:
|
|
251
|
-
end = i + 1;
|
|
252
|
-
break;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// final token
|
|
257
|
-
if (start !== end) {
|
|
258
|
-
list.push(str.substring(start, end));
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
return list;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
function parseHttpDate(date) {
|
|
266
|
-
const timestamp = date && Date.parse(date);
|
|
267
|
-
return typeof timestamp === 'number' ? timestamp : NaN;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
function isPreconditionFailure(req, res) {
|
|
271
|
-
const match = req.headers['if-match'];
|
|
272
|
-
|
|
273
|
-
// if-match
|
|
274
|
-
if(match) {
|
|
275
|
-
const etag = res.get('etag');
|
|
276
|
-
return !etag || (match !== '*' && parseTokenList(match).every(match => {
|
|
277
|
-
return match !== etag && match !== 'W/' + etag && 'W/' + match !== etag;
|
|
278
|
-
}));
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// if-unmodified-since
|
|
282
|
-
const unmodifiedSince = parseHttpDate(req.headers['if-unmodified-since']);
|
|
283
|
-
if(!isNaN(unmodifiedSince)) {
|
|
284
|
-
const lastModified = parseHttpDate(res.get('Last-Modified'));
|
|
285
|
-
return isNaN(lastModified) || lastModified > unmodifiedSince;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
return false;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
function createETagGenerator(options) {
|
|
292
|
-
return function generateETag (body, encoding) {
|
|
293
|
-
if(body instanceof Stats) {
|
|
294
|
-
return etag(body, options);
|
|
295
|
-
}
|
|
296
|
-
const buf = !Buffer.isBuffer(body) ? Buffer.from(body, encoding) : body;
|
|
297
|
-
return etag(buf, options);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
function isRangeFresh(req, res) {
|
|
302
|
-
const ifRange = req.headers['if-range'];
|
|
303
|
-
if(!ifRange) {
|
|
304
|
-
return true;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// if-range as etag
|
|
308
|
-
if(ifRange.indexOf('"') !== -1) {
|
|
309
|
-
const etag = res.get('etag');
|
|
310
|
-
return Boolean(etag && ifRange.indexOf(etag) !== -1);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
// if-range as modified date
|
|
314
|
-
const lastModified = res.get('Last-Modified');
|
|
315
|
-
return parseHttpDate(lastModified) <= parseHttpDate(ifRange);
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
module.exports = {
|
|
319
|
-
removeDuplicateSlashes,
|
|
320
|
-
patternToRegex,
|
|
321
|
-
needsConversionToRegex,
|
|
322
|
-
acceptParams,
|
|
323
|
-
normalizeType,
|
|
324
|
-
stringify,
|
|
325
|
-
defaultSettings,
|
|
326
|
-
compileTrust,
|
|
327
|
-
deprecated,
|
|
328
|
-
UP_PATH_REGEXP,
|
|
329
|
-
decode,
|
|
330
|
-
containsDotFile,
|
|
331
|
-
parseTokenList,
|
|
332
|
-
parseHttpDate,
|
|
333
|
-
isPreconditionFailure,
|
|
334
|
-
createETagGenerator,
|
|
335
|
-
isRangeFresh,
|
|
336
|
-
findIndexStartingFrom,
|
|
337
|
-
fastQueryParse,
|
|
338
|
-
canBeOptimized
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2024 dimden.dev
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const mime = require("mime-types");
|
|
18
|
+
const path = require("path");
|
|
19
|
+
const proxyaddr = require("proxy-addr");
|
|
20
|
+
const qs = require("qs");
|
|
21
|
+
const querystring = require("fast-querystring");
|
|
22
|
+
const etag = require("etag");
|
|
23
|
+
const { Stats } = require("fs");
|
|
24
|
+
|
|
25
|
+
function fastQueryParse(query, options) {
|
|
26
|
+
if(query.length <= 128) {
|
|
27
|
+
if(!query.includes('[') && !query.includes('%5B') && !query.includes('.') && !query.includes('%2E')) {
|
|
28
|
+
return querystring.parse(query);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return qs.parse(query, options);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function removeDuplicateSlashes(path) {
|
|
35
|
+
return path.replace(/\/{2,}/g, '/');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function patternToRegex(pattern, isPrefix = false) {
|
|
39
|
+
if(pattern instanceof RegExp) {
|
|
40
|
+
return pattern;
|
|
41
|
+
}
|
|
42
|
+
if(isPrefix && pattern === '') {
|
|
43
|
+
return new RegExp(``);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let regexPattern = pattern
|
|
47
|
+
.replace(/\./g, '\\.')
|
|
48
|
+
.replace(/\-/g, '\\-')
|
|
49
|
+
.replace(/\*/g, '(.*)') // Convert * to .*
|
|
50
|
+
.replace(/:(\w+)(\(.+?\))?/g, (match, param, regex) => {
|
|
51
|
+
return `(?<${param}>${regex ? regex + '($|\\/)' : '[^/]+'})`;
|
|
52
|
+
}); // Convert :param to capture group
|
|
53
|
+
|
|
54
|
+
return new RegExp(`^${regexPattern}${isPrefix ? '(?=$|\/)' : '$'}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function needsConversionToRegex(pattern) {
|
|
58
|
+
if(pattern instanceof RegExp) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
if(pattern === '/*') {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return pattern.includes('*') ||
|
|
66
|
+
pattern.includes('?') ||
|
|
67
|
+
pattern.includes('+') ||
|
|
68
|
+
pattern.includes('(') ||
|
|
69
|
+
pattern.includes(')') ||
|
|
70
|
+
pattern.includes(':') ||
|
|
71
|
+
pattern.includes('{') ||
|
|
72
|
+
pattern.includes('}') ||
|
|
73
|
+
pattern.includes('[') ||
|
|
74
|
+
pattern.includes(']');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function canBeOptimized(pattern) {
|
|
78
|
+
if(pattern === '/*') {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
if(pattern instanceof RegExp) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
if(
|
|
85
|
+
pattern.includes('*') ||
|
|
86
|
+
pattern.includes('?') ||
|
|
87
|
+
pattern.includes('+') ||
|
|
88
|
+
pattern.includes('(') ||
|
|
89
|
+
pattern.includes(')') ||
|
|
90
|
+
pattern.includes('{') ||
|
|
91
|
+
pattern.includes('}') ||
|
|
92
|
+
pattern.includes('[') ||
|
|
93
|
+
pattern.includes(']')
|
|
94
|
+
) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function acceptParams(str) {
|
|
101
|
+
const parts = str.split(/ *; */);
|
|
102
|
+
const ret = { value: parts[0], quality: 1, params: {} }
|
|
103
|
+
|
|
104
|
+
for (let i = 1; i < parts.length; ++i) {
|
|
105
|
+
const pms = parts[i].split(/ *= */);
|
|
106
|
+
if ('q' === pms[0]) {
|
|
107
|
+
ret.quality = parseFloat(pms[1]);
|
|
108
|
+
} else {
|
|
109
|
+
ret.params[pms[0]] = pms[1];
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return ret;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function normalizeType(type) {
|
|
117
|
+
return ~type.indexOf('/') ?
|
|
118
|
+
acceptParams(type) :
|
|
119
|
+
{ value: (mime.lookup(type) || 'application/octet-stream'), params: {} };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function stringify(value, replacer, spaces, escape) {
|
|
123
|
+
let json = replacer || spaces
|
|
124
|
+
? JSON.stringify(value, replacer, spaces)
|
|
125
|
+
: JSON.stringify(value);
|
|
126
|
+
|
|
127
|
+
if (escape && typeof json === 'string') {
|
|
128
|
+
json = json.replace(/[<>&]/g, function (c) {
|
|
129
|
+
switch (c.charCodeAt(0)) {
|
|
130
|
+
case 0x3c:
|
|
131
|
+
return '\\u003c'
|
|
132
|
+
case 0x3e:
|
|
133
|
+
return '\\u003e'
|
|
134
|
+
case 0x26:
|
|
135
|
+
return '\\u0026'
|
|
136
|
+
default:
|
|
137
|
+
return c
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return json;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const defaultSettings = {
|
|
146
|
+
'jsonp callback name': 'callback',
|
|
147
|
+
'env': () => process.env.NODE_ENV ?? 'development',
|
|
148
|
+
'etag': 'weak',
|
|
149
|
+
'etag fn': () => createETagGenerator({ weak: true }),
|
|
150
|
+
'query parser': 'extended',
|
|
151
|
+
'query parser fn': () => fastQueryParse,
|
|
152
|
+
'subdomain offset': 2,
|
|
153
|
+
'trust proxy': false,
|
|
154
|
+
'views': () => path.join(process.cwd(), 'views'),
|
|
155
|
+
'view cache': () => process.env.NODE_ENV === 'production',
|
|
156
|
+
'x-powered-by': true,
|
|
157
|
+
'case sensitive routing': true
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
function compileTrust(val) {
|
|
161
|
+
if (typeof val === 'function') return val;
|
|
162
|
+
|
|
163
|
+
if (val === true) {
|
|
164
|
+
// Support plain true/false
|
|
165
|
+
return function(){ return true };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (typeof val === 'number') {
|
|
169
|
+
// Support trusting hop count
|
|
170
|
+
return function(a, i){ return i < val };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (typeof val === 'string') {
|
|
174
|
+
// Support comma-separated values
|
|
175
|
+
val = val.split(',')
|
|
176
|
+
.map(function (v) { return v.trim() })
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return proxyaddr.compile(val || []);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const shownWarnings = new Set();
|
|
183
|
+
function deprecated(oldMethod, newMethod, full = false) {
|
|
184
|
+
const err = new Error();
|
|
185
|
+
const pos = full ? err.stack.split('\n').slice(1).join('\n') : err.stack.split('\n')[3].trim().split('(').slice(1).join('(').split(')').slice(0, -1).join(')');
|
|
186
|
+
if(shownWarnings.has(pos)) return;
|
|
187
|
+
shownWarnings.add(pos);
|
|
188
|
+
console.warn(`${new Date().toLocaleString('en-UK', {
|
|
189
|
+
weekday: 'short',
|
|
190
|
+
year: 'numeric',
|
|
191
|
+
month: 'short',
|
|
192
|
+
day: 'numeric',
|
|
193
|
+
hour: 'numeric',
|
|
194
|
+
minute: 'numeric',
|
|
195
|
+
second: 'numeric',
|
|
196
|
+
timeZone: 'GMT',
|
|
197
|
+
timeZoneName: 'short'
|
|
198
|
+
})} u-express deprecated ${oldMethod}: Use ${newMethod} instead at ${pos}`);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function findIndexStartingFrom(arr, fn, index = 0) {
|
|
202
|
+
for(let i = index; i < arr.length; i++) {
|
|
203
|
+
if(fn(arr[i], i, arr)) {
|
|
204
|
+
return i;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return -1;
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
function decode (path) {
|
|
211
|
+
try {
|
|
212
|
+
return decodeURIComponent(path)
|
|
213
|
+
} catch (err) {
|
|
214
|
+
return -1
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const UP_PATH_REGEXP = /(?:^|[\\/])\.\.(?:[\\/]|$)/;
|
|
219
|
+
|
|
220
|
+
function containsDotFile(parts) {
|
|
221
|
+
for(let i = 0; i < parts.length; i++) {
|
|
222
|
+
const part = parts[i];
|
|
223
|
+
if(part.length > 1 && part[0] === '.') {
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function parseTokenList(str) {
|
|
232
|
+
let end = 0;
|
|
233
|
+
const list = [];
|
|
234
|
+
let start = 0;
|
|
235
|
+
|
|
236
|
+
// gather tokens
|
|
237
|
+
for (let i = 0, len = str.length; i < len; i++) {
|
|
238
|
+
switch(str.charCodeAt(i)) {
|
|
239
|
+
case 0x20: /* */
|
|
240
|
+
if (start === end) {
|
|
241
|
+
start = end = i + 1;
|
|
242
|
+
}
|
|
243
|
+
break;
|
|
244
|
+
case 0x2c: /* , */
|
|
245
|
+
if (start !== end) {
|
|
246
|
+
list.push(str.substring(start, end));
|
|
247
|
+
}
|
|
248
|
+
start = end = i + 1;
|
|
249
|
+
break;
|
|
250
|
+
default:
|
|
251
|
+
end = i + 1;
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// final token
|
|
257
|
+
if (start !== end) {
|
|
258
|
+
list.push(str.substring(start, end));
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return list;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
function parseHttpDate(date) {
|
|
266
|
+
const timestamp = date && Date.parse(date);
|
|
267
|
+
return typeof timestamp === 'number' ? timestamp : NaN;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function isPreconditionFailure(req, res) {
|
|
271
|
+
const match = req.headers['if-match'];
|
|
272
|
+
|
|
273
|
+
// if-match
|
|
274
|
+
if(match) {
|
|
275
|
+
const etag = res.get('etag');
|
|
276
|
+
return !etag || (match !== '*' && parseTokenList(match).every(match => {
|
|
277
|
+
return match !== etag && match !== 'W/' + etag && 'W/' + match !== etag;
|
|
278
|
+
}));
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// if-unmodified-since
|
|
282
|
+
const unmodifiedSince = parseHttpDate(req.headers['if-unmodified-since']);
|
|
283
|
+
if(!isNaN(unmodifiedSince)) {
|
|
284
|
+
const lastModified = parseHttpDate(res.get('Last-Modified'));
|
|
285
|
+
return isNaN(lastModified) || lastModified > unmodifiedSince;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function createETagGenerator(options) {
|
|
292
|
+
return function generateETag (body, encoding) {
|
|
293
|
+
if(body instanceof Stats) {
|
|
294
|
+
return etag(body, options);
|
|
295
|
+
}
|
|
296
|
+
const buf = !Buffer.isBuffer(body) ? Buffer.from(body, encoding) : body;
|
|
297
|
+
return etag(buf, options);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function isRangeFresh(req, res) {
|
|
302
|
+
const ifRange = req.headers['if-range'];
|
|
303
|
+
if(!ifRange) {
|
|
304
|
+
return true;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// if-range as etag
|
|
308
|
+
if(ifRange.indexOf('"') !== -1) {
|
|
309
|
+
const etag = res.get('etag');
|
|
310
|
+
return Boolean(etag && ifRange.indexOf(etag) !== -1);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// if-range as modified date
|
|
314
|
+
const lastModified = res.get('Last-Modified');
|
|
315
|
+
return parseHttpDate(lastModified) <= parseHttpDate(ifRange);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
module.exports = {
|
|
319
|
+
removeDuplicateSlashes,
|
|
320
|
+
patternToRegex,
|
|
321
|
+
needsConversionToRegex,
|
|
322
|
+
acceptParams,
|
|
323
|
+
normalizeType,
|
|
324
|
+
stringify,
|
|
325
|
+
defaultSettings,
|
|
326
|
+
compileTrust,
|
|
327
|
+
deprecated,
|
|
328
|
+
UP_PATH_REGEXP,
|
|
329
|
+
decode,
|
|
330
|
+
containsDotFile,
|
|
331
|
+
parseTokenList,
|
|
332
|
+
parseHttpDate,
|
|
333
|
+
isPreconditionFailure,
|
|
334
|
+
createETagGenerator,
|
|
335
|
+
isRangeFresh,
|
|
336
|
+
findIndexStartingFrom,
|
|
337
|
+
fastQueryParse,
|
|
338
|
+
canBeOptimized
|
|
339
339
|
};
|