ultimate-express 1.2.16 → 1.2.17

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/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
  };