webhoster 0.3.2 → 0.3.4
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/publish.yml +29 -0
- package/.test/index.js +1 -1
- package/README.md +4 -4
- package/data/CookieObject.js +1 -1
- package/{types/index.js → data/custom-types.js} +1 -1
- package/{types/typings.d.ts → data/middleware.d.ts} +1 -1
- package/helpers/HttpListener.js +20 -94
- package/helpers/RequestReader.js +1 -12
- package/helpers/ResponseHeaders.js +9 -10
- package/lib/HttpHandler.js +17 -7
- package/lib/HttpRequest.js +7 -4
- package/lib/HttpResponse.js +11 -5
- package/lib/HttpTransaction.js +2 -2
- package/middleware/AutoHeadersMiddleware.js +2 -2
- package/middleware/CORSMiddleware.js +3 -3
- package/middleware/CaseInsensitiveHeadersMiddleware.js +2 -2
- package/middleware/ContentDecoderMiddleware.js +12 -8
- package/middleware/ContentEncoderMiddleware.js +6 -5
- package/middleware/ContentLengthMiddleware.js +2 -2
- package/middleware/HashMiddleware.js +2 -2
- package/middleware/HeadMethodMiddleware.js +3 -3
- package/middleware/MethodMiddleware.js +3 -3
- package/middleware/PathMiddleware.js +4 -4
- package/middleware/ReadFormData.js +1 -1
- package/middleware/SendJsonMiddleware.js +4 -4
- package/middleware/SendStringMiddleware.js +3 -3
- package/package.json +20 -3
- package/templates/starter.js +2 -2
- package/tsconfig.json +18 -1
- package/utils/headers.js +1 -1
- package/errata/index.js +0 -1
- package/index.js +0 -4
- package/lib/index.js +0 -3
- package/middleware/ContentReaderMiddleware.js +0 -249
- package/middleware/ContentWriterMiddleware.js +0 -161
- package/middleware/SendHeadersMiddleware.js +0 -47
- package/middleware/index.js +0 -11
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Transform } from 'node:stream';
|
|
2
2
|
import {
|
|
3
|
+
// @ts-expect-error Bad typings
|
|
3
4
|
BrotliDecompress, Gunzip, Inflate,
|
|
4
5
|
} from 'node:zlib';
|
|
5
6
|
|
|
6
|
-
/** @typedef {import('../types').MiddlewareFunction} MiddlewareFunction */
|
|
7
|
+
/** @typedef {import('../data/custom-types.js').MiddlewareFunction} MiddlewareFunction */
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* @typedef ContentDecoderMiddlewareOptions
|
|
@@ -69,17 +70,20 @@ export default class ContentDecoderMiddleware {
|
|
|
69
70
|
const gzipOptions = { chunkSize: this.chunkSize };
|
|
70
71
|
const newDownstream = new Transform({
|
|
71
72
|
|
|
72
|
-
read: (...
|
|
73
|
+
read: (...arguments_) => {
|
|
73
74
|
if (!initialized) {
|
|
74
75
|
/** @type {import("zlib").Gzip} */
|
|
75
76
|
switch (contentEncoding) {
|
|
76
77
|
case 'deflate':
|
|
78
|
+
// @ts-expect-error Bad typings
|
|
77
79
|
gzipStream = new Inflate(gzipOptions);
|
|
78
80
|
break;
|
|
79
81
|
case 'gzip':
|
|
82
|
+
// @ts-expect-error Bad typings
|
|
80
83
|
gzipStream = new Gunzip(gzipOptions);
|
|
81
84
|
break;
|
|
82
85
|
case 'br':
|
|
86
|
+
// @ts-expect-error Bad typings
|
|
83
87
|
gzipStream = new BrotliDecompress(gzipOptions);
|
|
84
88
|
break;
|
|
85
89
|
default:
|
|
@@ -89,7 +93,7 @@ export default class ContentDecoderMiddleware {
|
|
|
89
93
|
// To newDownstream <= gzipStream < =inputStream
|
|
90
94
|
|
|
91
95
|
// Forward errors
|
|
92
|
-
gzipStream.on('error', (
|
|
96
|
+
gzipStream.on('error', (error) => inputStream.emit('error', error));
|
|
93
97
|
gzipStream.on('data', (chunk) => newDownstream.push(chunk));
|
|
94
98
|
|
|
95
99
|
inputStream.on('end', () => gzipStream.end());
|
|
@@ -104,12 +108,13 @@ export default class ContentDecoderMiddleware {
|
|
|
104
108
|
initialized = true;
|
|
105
109
|
}
|
|
106
110
|
|
|
107
|
-
|
|
111
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
112
|
+
Transform.prototype._read.call(this, ...arguments_);
|
|
108
113
|
},
|
|
109
114
|
transform: (chunk, chunkEncoding, callback) => {
|
|
110
|
-
gzipStream.write(chunk, (
|
|
111
|
-
if (
|
|
112
|
-
callback(
|
|
115
|
+
gzipStream.write(chunk, (error) => {
|
|
116
|
+
if (error) console.error(error);
|
|
117
|
+
callback(error);
|
|
113
118
|
});
|
|
114
119
|
},
|
|
115
120
|
flush: (callback) => {
|
|
@@ -129,7 +134,6 @@ export default class ContentDecoderMiddleware {
|
|
|
129
134
|
},
|
|
130
135
|
});
|
|
131
136
|
|
|
132
|
-
newDownstream.tag = 'ContentDecoder';
|
|
133
137
|
inputStream = request.addDownstream(newDownstream, { autoPause: true });
|
|
134
138
|
|
|
135
139
|
return CONTINUE;
|
|
@@ -15,8 +15,8 @@ const { BROTLI_OPERATION_FLUSH, Z_SYNC_FLUSH } = ZlibContants;
|
|
|
15
15
|
/** @typedef {import('http').IncomingHttpHeaders} IncomingHttpHeaders */
|
|
16
16
|
/** @typedef {import('../lib/HttpRequest.js').default} HttpRequest */
|
|
17
17
|
/** @typedef {import('../lib/HttpResponse.js').default} HttpResponse */
|
|
18
|
-
/** @typedef {import('../types').MiddlewareFunction} MiddlewareFunction */
|
|
19
|
-
/** @typedef {import('../types').ResponseFinalizer} ResponseFinalizer */
|
|
18
|
+
/** @typedef {import('../data/custom-types.js').MiddlewareFunction} MiddlewareFunction */
|
|
19
|
+
/** @typedef {import('../data/custom-types.js').ResponseFinalizer} ResponseFinalizer */
|
|
20
20
|
|
|
21
21
|
/** @typedef {'br'|'gzip'|'deflate'|'identity'|'*'} COMPATIBLE_ENCODING */
|
|
22
22
|
|
|
@@ -75,8 +75,9 @@ export default class ContentEncoderMiddleware {
|
|
|
75
75
|
let encoding = COMPATIBLE_ENCODINGS[0];
|
|
76
76
|
const allowWildcards = (encodings.get('*')?.q !== 0);
|
|
77
77
|
const encodingEntries = [...encodings.entries()];
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
encoding = /** @type {COMPATIBLE_ENCODING} */ (encodingEntries
|
|
79
|
+
.find(([value, spec]) => spec.q !== 0
|
|
80
|
+
&& COMPATIBLE_ENCODINGS.includes(/** @type {COMPATIBLE_ENCODING} */ (value)))?.[0]);
|
|
80
81
|
if (allowWildcards && (encoding === '*' || !encoding)) {
|
|
81
82
|
// Server preference
|
|
82
83
|
// Get first compatible encoding not specified
|
|
@@ -154,7 +155,7 @@ export default class ContentEncoderMiddleware {
|
|
|
154
155
|
encoding = getContentEncoding().toLowerCase?.();
|
|
155
156
|
}
|
|
156
157
|
|
|
157
|
-
const isEventStream = response.headers['content-type']?.includes('text/event-stream');
|
|
158
|
+
const isEventStream = /** @type {string|null} */ (response.headers['content-type'])?.includes('text/event-stream');
|
|
158
159
|
|
|
159
160
|
let newStream;
|
|
160
161
|
switch (encoding) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** @typedef {import('../lib/HttpResponse.js').default} HttpResponse */
|
|
2
|
-
/** @typedef {import('../types').MiddlewareFunction} MiddlewareFunction */
|
|
3
|
-
/** @typedef {import('../types').ResponseFinalizer} ResponseFinalizer */
|
|
2
|
+
/** @typedef {import('../data/custom-types.js').MiddlewareFunction} MiddlewareFunction */
|
|
3
|
+
/** @typedef {import('../data/custom-types.js').ResponseFinalizer} ResponseFinalizer */
|
|
4
4
|
|
|
5
5
|
import { Transform } from 'node:stream';
|
|
6
6
|
|
|
@@ -4,8 +4,8 @@ import { Transform } from 'node:stream';
|
|
|
4
4
|
/** @typedef {import('node:crypto').BinaryToTextEncoding} BinaryToTextEncoding */
|
|
5
5
|
/** @typedef {import('../lib/HttpRequest.js').default} HttpRequest */
|
|
6
6
|
/** @typedef {import('../lib/HttpResponse.js').default} HttpResponse */
|
|
7
|
-
/** @typedef {import('../types
|
|
8
|
-
/** @typedef {import('../types
|
|
7
|
+
/** @typedef {import('../data/custom-types.js').MiddlewareFunction} MiddlewareFunction */
|
|
8
|
+
/** @typedef {import('../data/custom-types.js').ResponseFinalizer} ResponseFinalizer */
|
|
9
9
|
|
|
10
10
|
const DEFAULT_ALGORITHM = 'sha1';
|
|
11
11
|
/** @type {BinaryToTextEncoding} */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
/** @typedef {import('../types
|
|
2
|
-
/** @typedef {import('../types
|
|
3
|
-
/** @typedef {import('../types
|
|
1
|
+
/** @typedef {import('../data/custom-types.js').HttpTransaction} HttpTransaction */
|
|
2
|
+
/** @typedef {import('../data/custom-types.js').MiddlewareFunction} MiddlewareFunction */
|
|
3
|
+
/** @typedef {import('../data/custom-types.js').ResponseFinalizer} ResponseFinalizer */
|
|
4
4
|
|
|
5
5
|
import { Transform } from 'node:stream';
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
/** @typedef {import('../types').HttpTransaction} HttpTransaction */
|
|
2
|
-
/** @typedef {import('../types').MiddlewareFunction} MiddlewareFunction */
|
|
3
|
-
/** @typedef {import('../types').RequestMethod} RequestMethod */
|
|
1
|
+
/** @typedef {import('../data/custom-types.js').HttpTransaction} HttpTransaction */
|
|
2
|
+
/** @typedef {import('../data/custom-types.js').MiddlewareFunction} MiddlewareFunction */
|
|
3
|
+
/** @typedef {import('../data/custom-types.js').RequestMethod} RequestMethod */
|
|
4
4
|
|
|
5
5
|
/** @typedef {RegExp|RequestMethod} MethodEntry */
|
|
6
6
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { posix } from 'node:path';
|
|
2
2
|
|
|
3
|
-
/** @typedef {import('../types').HttpTransaction} HttpTransaction */
|
|
4
|
-
/** @typedef {import('../types').IMiddleware} IMiddleware */
|
|
5
|
-
/** @typedef {import('../types').MiddlewareFunction} MiddlewareFunction */
|
|
6
|
-
/** @typedef {import('../types').RequestMethod} RequestMethod */
|
|
3
|
+
/** @typedef {import('../data/custom-types.js').HttpTransaction} HttpTransaction */
|
|
4
|
+
/** @typedef {import('../data/custom-types.js').IMiddleware} IMiddleware */
|
|
5
|
+
/** @typedef {import('../data/custom-types.js').MiddlewareFunction} MiddlewareFunction */
|
|
6
|
+
/** @typedef {import('../data/custom-types.js').RequestMethod} RequestMethod */
|
|
7
7
|
|
|
8
8
|
/** @typedef {RegExp|string} PathEntry */
|
|
9
9
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** @typedef {import('../lib/HttpRequest.js').default} HttpRequest */
|
|
2
|
-
/** @typedef {import('../types').MiddlewareFunction} MiddlewareFunction */
|
|
2
|
+
/** @typedef {import('../data/custom-types.js').MiddlewareFunction} MiddlewareFunction */
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* The application/x-www-form-urlencoded format is in many ways an aberrant monstrosity,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
/** @typedef {import('../types').IMiddleware} IMiddleware */
|
|
2
|
-
/** @typedef {import('../types').MiddlewareResponseFunction} MiddlewareResponseFunction */
|
|
3
|
-
/** @typedef {import('../types').MiddlewareFunction} MiddlewareFunction */
|
|
4
|
-
/** @typedef {import('../types
|
|
1
|
+
/** @typedef {import('../data/custom-types.js').IMiddleware} IMiddleware */
|
|
2
|
+
/** @typedef {import('../data/custom-types.js').MiddlewareResponseFunction} MiddlewareResponseFunction */
|
|
3
|
+
/** @typedef {import('../data/custom-types.js').MiddlewareFunction} MiddlewareFunction */
|
|
4
|
+
/** @typedef {import('../data/custom-types.js').ResponseFinalizer} ResponseFinalizer */
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* @typedef {Object} SendJsonMiddlewareOptions
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
/** @typedef {import('../types
|
|
2
|
-
/** @typedef {import('../types
|
|
3
|
-
/** @typedef {import('../types
|
|
1
|
+
/** @typedef {import('../data/custom-types.js').IMiddleware} IMiddleware */
|
|
2
|
+
/** @typedef {import('../data/custom-types.js').MiddlewareFunction} MiddlewareFunction */
|
|
3
|
+
/** @typedef {import('../data/custom-types.js').ResponseFinalizer} ResponseFinalizer */
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @typedef {Object} SendStringMiddlewareOptions
|
package/package.json
CHANGED
|
@@ -24,7 +24,16 @@
|
|
|
24
24
|
"node": ">v16.13"
|
|
25
25
|
},
|
|
26
26
|
"exports": {
|
|
27
|
-
"
|
|
27
|
+
"./data/*": "./data/*",
|
|
28
|
+
"./errata/*": "./errata/*",
|
|
29
|
+
"./examples/*": "./examples/*",
|
|
30
|
+
"./helpers/*": "./helpers/*",
|
|
31
|
+
"./lib/*": "./lib/*",
|
|
32
|
+
"./middleware/*": "./middleware/*",
|
|
33
|
+
"./polyfills/*": "./polyfills/*",
|
|
34
|
+
"./templates/*": "./templates/*",
|
|
35
|
+
"./types/*": "./types/*",
|
|
36
|
+
"./utils/*": "./utils/*"
|
|
28
37
|
},
|
|
29
38
|
"keywords": [
|
|
30
39
|
"http",
|
|
@@ -42,6 +51,7 @@
|
|
|
42
51
|
"scripts": {
|
|
43
52
|
"debug-test": "ava --serial",
|
|
44
53
|
"test": "c8 ava",
|
|
54
|
+
"prepublishOnly": "rm -Rf types && tsc --emitDeclarationOnly",
|
|
45
55
|
"pretestallsync": "rimraf coverage",
|
|
46
56
|
"testallsync": "scripts/test-all-sync.sh",
|
|
47
57
|
"posttestallsync": "c8 report",
|
|
@@ -50,5 +60,12 @@
|
|
|
50
60
|
"posttestall": "c8 report"
|
|
51
61
|
},
|
|
52
62
|
"type": "module",
|
|
53
|
-
"
|
|
54
|
-
|
|
63
|
+
"typesVersions": {
|
|
64
|
+
"*": {
|
|
65
|
+
"*": [
|
|
66
|
+
"types/*"
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
"version": "0.3.4"
|
|
71
|
+
}
|
package/templates/starter.js
CHANGED
|
@@ -13,8 +13,8 @@ import HeadMethodMiddleware from '../middleware/HeadMethodMiddleware.js';
|
|
|
13
13
|
* @param {Object} options
|
|
14
14
|
* @param {string} [options.host='0.0.0.0']
|
|
15
15
|
* @param {number} [options.port=8080]
|
|
16
|
-
* @param {import('../types').Middleware[]} [options.middleware]
|
|
17
|
-
* @param {import('../types').MiddlewareErrorHandler[]} [options.errorHandlers]
|
|
16
|
+
* @param {import('../data/custom-types.js').Middleware[]} [options.middleware]
|
|
17
|
+
* @param {import('../data/custom-types.js').MiddlewareErrorHandler[]} [options.errorHandlers]
|
|
18
18
|
* @return {Promise<import('../helpers/HttpListener.js').default>}
|
|
19
19
|
*/
|
|
20
20
|
export async function start(options) {
|
package/tsconfig.json
CHANGED
|
@@ -1,12 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"compilerOptions": {
|
|
3
|
+
"allowJs": true,
|
|
3
4
|
"checkJs": true,
|
|
4
5
|
"noImplicitAny": true,
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"declarationMap": true,
|
|
8
|
+
"declarationDir": "types",
|
|
9
|
+
"outDir": "/dev/null",
|
|
5
10
|
"target": "es2022",
|
|
6
11
|
"module": "esnext",
|
|
7
12
|
"moduleResolution": "node"
|
|
8
13
|
},
|
|
14
|
+
"include": [
|
|
15
|
+
"./data/*",
|
|
16
|
+
"./errata/*",
|
|
17
|
+
"./examples/*",
|
|
18
|
+
"./helpers/*",
|
|
19
|
+
"./lib/*",
|
|
20
|
+
"./middleware/*",
|
|
21
|
+
"./polyfills/*",
|
|
22
|
+
"./templates/*",
|
|
23
|
+
"./utils/*"
|
|
24
|
+
],
|
|
9
25
|
"exclude": [
|
|
10
|
-
"**/node_modules/*"
|
|
26
|
+
"**/node_modules/*",
|
|
27
|
+
"**/test/*"
|
|
11
28
|
]
|
|
12
29
|
}
|
package/utils/headers.js
CHANGED
package/errata/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * as socketio from './socketio.js';
|
package/index.js
DELETED
package/lib/index.js
DELETED
|
@@ -1,249 +0,0 @@
|
|
|
1
|
-
import { PassThrough, Transform } from 'stream';
|
|
2
|
-
|
|
3
|
-
/** @typedef {import('stream').Readable} Readable */
|
|
4
|
-
/** @typedef {import('../types').IMiddleware} IMiddleware */
|
|
5
|
-
/** @typedef {import('../types').MiddlewareFunction} MiddlewareFunction */
|
|
6
|
-
/** @typedef {import('../types').MiddlewareFunctionParams} MiddlewareFunctionParams */
|
|
7
|
-
/** @typedef {import('../types').MiddlewareFunctionResult} MiddlewareFunctionResult */
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* @typedef {Object} ContentReaderMiddlewareOptions
|
|
11
|
-
* @prop {string} [defaultMediaType]
|
|
12
|
-
* Assumed mediatype if not specified
|
|
13
|
-
* @prop {boolean} [parseJSON=false]
|
|
14
|
-
* Automatically parses JSON if `application/json` mediatype
|
|
15
|
-
* @prop {'entries'|'object'|'string'|'none'} [formURLEncodedFormat='none']
|
|
16
|
-
* Automatically converts to object if `application/x-www-form-urlencoded` mediatype
|
|
17
|
-
* @prop {boolean} [buildString=false]
|
|
18
|
-
* Automatically builds string into one `read()` response
|
|
19
|
-
* @prop {boolean|string} [cache=false]
|
|
20
|
-
* Caches content in req.local.content or req.local[cacheName]
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
/** @implements {IMiddleware} */
|
|
24
|
-
export default class ContentReaderMiddleware {
|
|
25
|
-
/** @param {ContentReaderMiddlewareOptions} [options] */
|
|
26
|
-
constructor(options = {}) {
|
|
27
|
-
this.defaultMediaType = options.defaultMediaType;
|
|
28
|
-
this.parseJSON = options.parseJSON === true;
|
|
29
|
-
this.formURLEncodedFormat = options.formURLEncodedFormat || 'none';
|
|
30
|
-
this.buildString = options.buildString === true;
|
|
31
|
-
this.cache = options.cache;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* @param {string} charset
|
|
36
|
-
* @return {BufferEncoding}
|
|
37
|
-
*/
|
|
38
|
-
static charsetAsBufferEncoding(charset) {
|
|
39
|
-
switch (charset) {
|
|
40
|
-
case 'iso-8859-1':
|
|
41
|
-
case 'ascii':
|
|
42
|
-
case 'binary':
|
|
43
|
-
case 'latin1':
|
|
44
|
-
return 'latin1';
|
|
45
|
-
case 'utf-16le':
|
|
46
|
-
case 'ucs-2':
|
|
47
|
-
case 'ucs2':
|
|
48
|
-
case 'utf16le':
|
|
49
|
-
return 'utf16le';
|
|
50
|
-
default:
|
|
51
|
-
case 'utf-8':
|
|
52
|
-
case 'utf8':
|
|
53
|
-
return 'utf-8';
|
|
54
|
-
case 'base64':
|
|
55
|
-
case 'hex':
|
|
56
|
-
return /** @type {BufferEncoding} */ (charset);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* The application/x-www-form-urlencoded format is in many ways an aberrant monstrosity,
|
|
62
|
-
* the result of many years of implementation accidents and compromises leading to a set of
|
|
63
|
-
* requirements necessary for interoperability, but in no way representing good design practices.
|
|
64
|
-
* In particular, readers are cautioned to pay close attention to the twisted details
|
|
65
|
-
* involving repeated (and in some cases nested) conversions between character encodings and byte sequences.
|
|
66
|
-
* Unfortunately the format is in widespread use due to the prevalence of HTML forms. [HTML]
|
|
67
|
-
* @param {Buffer} buffer
|
|
68
|
-
* @param {string} charset
|
|
69
|
-
* @return {[string, string][]} Tuple
|
|
70
|
-
*/
|
|
71
|
-
static readUrlEncoded(buffer, charset) {
|
|
72
|
-
// https://url.spec.whatwg.org/#urlencoded-parsing
|
|
73
|
-
const bufferEncoding = ContentReaderMiddleware.charsetAsBufferEncoding(charset);
|
|
74
|
-
|
|
75
|
-
const sequences = [];
|
|
76
|
-
let startIndex = 0;
|
|
77
|
-
for (let i = 0; i < buffer.length; i += 1) {
|
|
78
|
-
if (buffer[i] === 0x26) {
|
|
79
|
-
sequences.push(buffer.subarray(startIndex, i));
|
|
80
|
-
startIndex = i + 1;
|
|
81
|
-
}
|
|
82
|
-
if (i === buffer.length - 1) {
|
|
83
|
-
sequences.push(buffer.subarray(startIndex, i + 1));
|
|
84
|
-
break;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
/** @type {[string, string][]} */
|
|
88
|
-
const output = [];
|
|
89
|
-
sequences.forEach((bytes) => {
|
|
90
|
-
if (!bytes.length) return;
|
|
91
|
-
|
|
92
|
-
// Find 0x3D and replace 0x2B in one loop for better performance
|
|
93
|
-
let indexOf0x3D = -1;
|
|
94
|
-
for (let i = 0; i < bytes.length; i += 1) {
|
|
95
|
-
switch (bytes[i]) {
|
|
96
|
-
case 0x3D:
|
|
97
|
-
if (indexOf0x3D === -1) {
|
|
98
|
-
indexOf0x3D = i;
|
|
99
|
-
}
|
|
100
|
-
break;
|
|
101
|
-
case 0x2B:
|
|
102
|
-
// Replace bytes on original stream for memory conservation
|
|
103
|
-
// eslint-disable-next-line no-param-reassign
|
|
104
|
-
bytes[i] = 0x20;
|
|
105
|
-
break;
|
|
106
|
-
default:
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
let name;
|
|
110
|
-
let value;
|
|
111
|
-
if (indexOf0x3D === -1) {
|
|
112
|
-
name = bytes;
|
|
113
|
-
value = bytes.subarray(bytes.length, 0);
|
|
114
|
-
} else {
|
|
115
|
-
name = bytes.subarray(0, indexOf0x3D);
|
|
116
|
-
value = bytes.subarray(indexOf0x3D + 1);
|
|
117
|
-
}
|
|
118
|
-
const nameString = decodeURIComponent(name.toString(bufferEncoding));
|
|
119
|
-
const valueString = decodeURIComponent(value.toString(bufferEncoding));
|
|
120
|
-
output.push([nameString, valueString]);
|
|
121
|
-
});
|
|
122
|
-
return output;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* @param {MiddlewareFunctionParams} params
|
|
127
|
-
* @return {MiddlewareFunctionResult}
|
|
128
|
-
*/
|
|
129
|
-
execute({ req }) {
|
|
130
|
-
switch (req.method) {
|
|
131
|
-
case 'HEAD':
|
|
132
|
-
case 'GET':
|
|
133
|
-
return 'continue';
|
|
134
|
-
default:
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const contentType = (req.headers['content-type']);
|
|
138
|
-
/** @type {string} */
|
|
139
|
-
let mediaType;
|
|
140
|
-
/** @type {string} */
|
|
141
|
-
let charset;
|
|
142
|
-
if (contentType) {
|
|
143
|
-
contentType.split(';').forEach((directive) => {
|
|
144
|
-
const parameters = directive.split('=');
|
|
145
|
-
if (parameters.length === 1) {
|
|
146
|
-
mediaType = directive.trim().toLowerCase();
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
if (parameters[0].trim().toLowerCase() !== 'charset') {
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
charset = parameters[1]?.trim().toLowerCase();
|
|
153
|
-
const firstQuote = charset.indexOf('"');
|
|
154
|
-
const lastQuote = charset.lastIndexOf('"');
|
|
155
|
-
if (firstQuote !== -1 && lastQuote !== -1) {
|
|
156
|
-
charset = charset.substring(firstQuote + 1, lastQuote);
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
if (!mediaType) {
|
|
162
|
-
mediaType = this.defaultMediaType;
|
|
163
|
-
}
|
|
164
|
-
const isFormUrlEncoded = mediaType === 'application/x-www-form-urlencoded';
|
|
165
|
-
const isJSON = /application\/(.+\+)?json/i.test(mediaType);
|
|
166
|
-
if (!charset) {
|
|
167
|
-
if (!mediaType) {
|
|
168
|
-
return 'continue';
|
|
169
|
-
}
|
|
170
|
-
if (isFormUrlEncoded && (this.formURLEncodedFormat || 'none') === 'none') {
|
|
171
|
-
return 'continue';
|
|
172
|
-
}
|
|
173
|
-
if (!isFormUrlEncoded && !isJSON && !mediaType.startsWith('text/')) {
|
|
174
|
-
return 'continue';
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
const readAll = isJSON || isFormUrlEncoded;
|
|
179
|
-
|
|
180
|
-
let fullString = '';
|
|
181
|
-
/** @type {Buffer[]} */
|
|
182
|
-
const pendingChunks = [];
|
|
183
|
-
const source = req.stream;
|
|
184
|
-
const {
|
|
185
|
-
buildString, formURLEncodedFormat, cache, parseJSON,
|
|
186
|
-
} = this;
|
|
187
|
-
const newReadable = new Transform({
|
|
188
|
-
objectMode: true,
|
|
189
|
-
read(...args) {
|
|
190
|
-
if (source.isPaused()) source.resume();
|
|
191
|
-
// eslint-disable-next-line no-underscore-dangle
|
|
192
|
-
Transform.prototype._read.call(this, ...args);
|
|
193
|
-
},
|
|
194
|
-
transform(chunk, encoding, callback) {
|
|
195
|
-
if (typeof chunk === 'string') {
|
|
196
|
-
if (readAll || buildString) {
|
|
197
|
-
fullString += chunk;
|
|
198
|
-
} else {
|
|
199
|
-
this.push(chunk);
|
|
200
|
-
}
|
|
201
|
-
} else if (isFormUrlEncoded) {
|
|
202
|
-
pendingChunks.push(chunk);
|
|
203
|
-
} else {
|
|
204
|
-
this.push(chunk);
|
|
205
|
-
}
|
|
206
|
-
callback();
|
|
207
|
-
},
|
|
208
|
-
flush(callback) {
|
|
209
|
-
let result = null;
|
|
210
|
-
if (isFormUrlEncoded) {
|
|
211
|
-
const combinedBuffer = Buffer.concat(pendingChunks);
|
|
212
|
-
if (formURLEncodedFormat === 'object') {
|
|
213
|
-
result = Object.fromEntries(ContentReaderMiddleware.readUrlEncoded(combinedBuffer, charset));
|
|
214
|
-
} else if (formURLEncodedFormat === 'string') {
|
|
215
|
-
result = combinedBuffer.toString(ContentReaderMiddleware.charsetAsBufferEncoding(charset));
|
|
216
|
-
} else {
|
|
217
|
-
result = ContentReaderMiddleware.readUrlEncoded(combinedBuffer, charset);
|
|
218
|
-
}
|
|
219
|
-
} else if (isJSON && parseJSON) {
|
|
220
|
-
try {
|
|
221
|
-
result = JSON.parse(fullString);
|
|
222
|
-
} catch {
|
|
223
|
-
result = fullString;
|
|
224
|
-
}
|
|
225
|
-
} else if (fullString) {
|
|
226
|
-
result = fullString;
|
|
227
|
-
}
|
|
228
|
-
if (cache && result) {
|
|
229
|
-
const cacheName = cache === true ? 'content' : cache;
|
|
230
|
-
req.locals[cacheName] = result;
|
|
231
|
-
}
|
|
232
|
-
callback(null, result);
|
|
233
|
-
},
|
|
234
|
-
});
|
|
235
|
-
req.replaceStream(newReadable);
|
|
236
|
-
if (!isFormUrlEncoded) {
|
|
237
|
-
// Data read from source will be decoded as a string
|
|
238
|
-
const encoding = ContentReaderMiddleware.charsetAsBufferEncoding(charset);
|
|
239
|
-
const stringDecoder = new PassThrough({ encoding });
|
|
240
|
-
newReadable.setDefaultEncoding(encoding);
|
|
241
|
-
source.pipe(stringDecoder).pipe(newReadable);
|
|
242
|
-
} else {
|
|
243
|
-
source.pipe(newReadable);
|
|
244
|
-
}
|
|
245
|
-
source.pause();
|
|
246
|
-
|
|
247
|
-
return 'continue';
|
|
248
|
-
}
|
|
249
|
-
}
|