@deepkit/http 1.0.19 → 1.1.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/CHANGELOG.md +33 -0
- package/dist/cjs/index.d.ts +2 -0
- package/dist/cjs/index.js +2 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/src/decorator.js +11 -7
- package/dist/cjs/src/decorator.js.map +1 -1
- package/dist/cjs/src/filter.js +1 -1
- package/dist/cjs/src/filter.js.map +1 -1
- package/dist/cjs/src/http.d.ts +3 -0
- package/dist/cjs/src/http.js +90 -61
- package/dist/cjs/src/http.js.map +1 -1
- package/dist/cjs/src/logger.js +0 -7
- package/dist/cjs/src/logger.js.map +1 -1
- package/dist/cjs/src/middleware.d.ts +2 -2
- package/dist/cjs/src/middleware.js +2 -2
- package/dist/cjs/src/mime.d.ts +19 -0
- package/dist/cjs/src/mime.js +147 -0
- package/dist/cjs/src/mime.js.map +1 -0
- package/dist/cjs/src/model.d.ts +7 -0
- package/dist/cjs/src/model.js +16 -7
- package/dist/cjs/src/model.js.map +1 -1
- package/dist/cjs/src/module.js +9 -5
- package/dist/cjs/src/module.js.map +1 -1
- package/dist/cjs/src/query-string.d.ts +6 -0
- package/dist/cjs/src/query-string.js +229 -0
- package/dist/cjs/src/query-string.js.map +1 -0
- package/dist/cjs/src/request-parser.js +12 -10
- package/dist/cjs/src/request-parser.js.map +1 -1
- package/dist/cjs/src/router.js +14 -15
- package/dist/cjs/src/router.js.map +1 -1
- package/dist/cjs/src/static-serving.d.ts +6 -0
- package/dist/cjs/src/static-serving.js +232 -28
- package/dist/cjs/src/static-serving.js.map +1 -1
- package/dist/cjs/src/utils.js +1 -1
- package/dist/cjs/src/utils.js.map +1 -1
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/src/decorator.js +11 -7
- package/dist/esm/src/decorator.js.map +1 -1
- package/dist/esm/src/filter.js +1 -1
- package/dist/esm/src/filter.js.map +1 -1
- package/dist/esm/src/http.d.ts +3 -0
- package/dist/esm/src/http.js +55 -60
- package/dist/esm/src/http.js.map +1 -1
- package/dist/esm/src/logger.js +0 -7
- package/dist/esm/src/logger.js.map +1 -1
- package/dist/esm/src/middleware.d.ts +2 -2
- package/dist/esm/src/middleware.js +2 -2
- package/dist/esm/src/mime.d.ts +19 -0
- package/dist/esm/src/mime.js +142 -0
- package/dist/esm/src/mime.js.map +1 -0
- package/dist/esm/src/model.d.ts +7 -0
- package/dist/esm/src/model.js +16 -7
- package/dist/esm/src/model.js.map +1 -1
- package/dist/esm/src/module.js +9 -5
- package/dist/esm/src/module.js.map +1 -1
- package/dist/esm/src/query-string.d.ts +6 -0
- package/dist/esm/src/query-string.js +226 -0
- package/dist/esm/src/query-string.js.map +1 -0
- package/dist/esm/src/request-parser.js +12 -10
- package/dist/esm/src/request-parser.js.map +1 -1
- package/dist/esm/src/router.js +14 -12
- package/dist/esm/src/router.js.map +1 -1
- package/dist/esm/src/static-serving.d.ts +6 -0
- package/dist/esm/src/static-serving.js +234 -27
- package/dist/esm/src/static-serving.js.map +1 -1
- package/dist/esm/src/utils.js +1 -1
- package/dist/esm/src/utils.js.map +1 -1
- package/index.ts +2 -0
- package/package.json +22 -25
- package/src/http.ts +40 -6
- package/src/middleware.ts +2 -2
- package/src/mime.ts +141 -0
- package/src/model.ts +10 -2
- package/src/module.ts +4 -2
- package/src/query-string.ts +228 -0
- package/src/request-parser.ts +6 -4
- package/src/router.ts +3 -3
- package/src/static-serving.ts +221 -8
- package/tsconfig.json +3 -17
- package/tsconfig.spec.json +4 -1
- package/LICENSE +0 -21
- package/dist/cjs/tsconfig.tsbuildinfo +0 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +0 -1
package/src/mime.ts
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Deepkit Framework
|
|
3
|
+
* Copyright (C) 2021 Deepkit UG, Marc J. Schmidt
|
|
4
|
+
*
|
|
5
|
+
* This program is free software: you can redistribute it and/or modify
|
|
6
|
+
* it under the terms of the MIT License.
|
|
7
|
+
*
|
|
8
|
+
* You should have received a copy of the MIT License along with this program.
|
|
9
|
+
*/
|
|
10
|
+
import { extname } from 'path';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* File extension to MIME type map of common web types.
|
|
14
|
+
* Add more types at runtime by setting `mimeTypes[extension] = type`.
|
|
15
|
+
*/
|
|
16
|
+
export const mimeTypes: { [extension: string]: string } = {
|
|
17
|
+
// text
|
|
18
|
+
html: 'text/html',
|
|
19
|
+
htm: 'text/html',
|
|
20
|
+
css: 'text/css',
|
|
21
|
+
js: 'text/javascript',
|
|
22
|
+
mjs: 'text/javascript',
|
|
23
|
+
cjs: 'application/node',
|
|
24
|
+
txt: 'text/plain',
|
|
25
|
+
text: 'text/plain',
|
|
26
|
+
csv: 'text/csv',
|
|
27
|
+
tsv: 'text/tab-separated-values',
|
|
28
|
+
md: 'text/markdown',
|
|
29
|
+
markdown: 'text/markdown',
|
|
30
|
+
yaml: 'text/yaml',
|
|
31
|
+
yml: 'text/yaml',
|
|
32
|
+
ics: 'text/calendar',
|
|
33
|
+
vtt: 'text/vtt',
|
|
34
|
+
appcache: 'text/cache-manifest',
|
|
35
|
+
// application
|
|
36
|
+
json: 'application/json',
|
|
37
|
+
map: 'application/json',
|
|
38
|
+
jsonld: 'application/ld+json',
|
|
39
|
+
webmanifest: 'application/manifest+json',
|
|
40
|
+
xml: 'application/xml',
|
|
41
|
+
xsl: 'application/xml',
|
|
42
|
+
rtf: 'application/rtf',
|
|
43
|
+
wasm: 'application/wasm',
|
|
44
|
+
pdf: 'application/pdf',
|
|
45
|
+
zip: 'application/zip',
|
|
46
|
+
gz: 'application/gzip',
|
|
47
|
+
tar: 'application/x-tar',
|
|
48
|
+
bz2: 'application/x-bzip2',
|
|
49
|
+
'7z': 'application/x-7z-compressed',
|
|
50
|
+
rar: 'application/vnd.rar',
|
|
51
|
+
xz: 'application/x-xz',
|
|
52
|
+
bin: 'application/octet-stream',
|
|
53
|
+
epub: 'application/epub+zip',
|
|
54
|
+
doc: 'application/msword',
|
|
55
|
+
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
56
|
+
xls: 'application/vnd.ms-excel',
|
|
57
|
+
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
58
|
+
ppt: 'application/vnd.ms-powerpoint',
|
|
59
|
+
pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
60
|
+
odt: 'application/vnd.oasis.opendocument.text',
|
|
61
|
+
ods: 'application/vnd.oasis.opendocument.spreadsheet',
|
|
62
|
+
odp: 'application/vnd.oasis.opendocument.presentation',
|
|
63
|
+
m3u8: 'application/vnd.apple.mpegurl',
|
|
64
|
+
mpd: 'application/dash+xml',
|
|
65
|
+
// images
|
|
66
|
+
png: 'image/png',
|
|
67
|
+
apng: 'image/apng',
|
|
68
|
+
jpg: 'image/jpeg',
|
|
69
|
+
jpeg: 'image/jpeg',
|
|
70
|
+
jpe: 'image/jpeg',
|
|
71
|
+
gif: 'image/gif',
|
|
72
|
+
webp: 'image/webp',
|
|
73
|
+
avif: 'image/avif',
|
|
74
|
+
svg: 'image/svg+xml',
|
|
75
|
+
svgz: 'image/svg+xml',
|
|
76
|
+
ico: 'image/vnd.microsoft.icon',
|
|
77
|
+
bmp: 'image/bmp',
|
|
78
|
+
tif: 'image/tiff',
|
|
79
|
+
tiff: 'image/tiff',
|
|
80
|
+
heic: 'image/heic',
|
|
81
|
+
heif: 'image/heif',
|
|
82
|
+
jxl: 'image/jxl',
|
|
83
|
+
psd: 'image/vnd.adobe.photoshop',
|
|
84
|
+
// fonts
|
|
85
|
+
woff: 'font/woff',
|
|
86
|
+
woff2: 'font/woff2',
|
|
87
|
+
ttf: 'font/ttf',
|
|
88
|
+
otf: 'font/otf',
|
|
89
|
+
eot: 'application/vnd.ms-fontobject',
|
|
90
|
+
// audio
|
|
91
|
+
mp3: 'audio/mpeg',
|
|
92
|
+
m4a: 'audio/mp4',
|
|
93
|
+
aac: 'audio/aac',
|
|
94
|
+
oga: 'audio/ogg',
|
|
95
|
+
ogg: 'audio/ogg',
|
|
96
|
+
opus: 'audio/ogg',
|
|
97
|
+
wav: 'audio/wav',
|
|
98
|
+
flac: 'audio/x-flac',
|
|
99
|
+
// video
|
|
100
|
+
mp4: 'video/mp4',
|
|
101
|
+
m4v: 'video/x-m4v',
|
|
102
|
+
webm: 'video/webm',
|
|
103
|
+
ogv: 'video/ogg',
|
|
104
|
+
mov: 'video/quicktime',
|
|
105
|
+
avi: 'video/x-msvideo',
|
|
106
|
+
mkv: 'video/x-matroska',
|
|
107
|
+
mpeg: 'video/mpeg',
|
|
108
|
+
mpg: 'video/mpeg',
|
|
109
|
+
'3gp': 'video/3gpp',
|
|
110
|
+
ts: 'video/mp2t',
|
|
111
|
+
// 3d models
|
|
112
|
+
glb: 'model/gltf-binary',
|
|
113
|
+
gltf: 'model/gltf+json',
|
|
114
|
+
obj: 'model/obj',
|
|
115
|
+
stl: 'model/stl',
|
|
116
|
+
usdz: 'model/vnd.usdz+zip',
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Returns the MIME type for a file path, file name, or extension (with or without leading dot).
|
|
121
|
+
* Returns undefined for unknown extensions.
|
|
122
|
+
*
|
|
123
|
+
* `getMimeType('/assets/logo.svg')` → `image/svg+xml`
|
|
124
|
+
*/
|
|
125
|
+
export function getMimeType(path: string): string | undefined {
|
|
126
|
+
const extension = extname('x.' + path)
|
|
127
|
+
.slice(1)
|
|
128
|
+
.toLowerCase();
|
|
129
|
+
return Object.prototype.hasOwnProperty.call(mimeTypes, extension) ? mimeTypes[extension] : undefined;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Returns the value for a Content-Type header, including `charset=utf-8` for text types.
|
|
134
|
+
* Returns undefined for unknown extensions.
|
|
135
|
+
*/
|
|
136
|
+
export function getContentType(path: string): string | undefined {
|
|
137
|
+
const type = getMimeType(path);
|
|
138
|
+
if (!type) return;
|
|
139
|
+
const isText = type.startsWith('text/') || type === 'application/json' || type === 'application/manifest+json';
|
|
140
|
+
return isText ? type + '; charset=utf-8' : type;
|
|
141
|
+
}
|
package/src/model.ts
CHANGED
|
@@ -312,13 +312,21 @@ export class RequestBuilder {
|
|
|
312
312
|
}, this.contentBuffer);
|
|
313
313
|
}
|
|
314
314
|
|
|
315
|
+
/**
|
|
316
|
+
* Replaces all headers. Names are lowercased like Node does for incoming requests.
|
|
317
|
+
*/
|
|
315
318
|
headers(headers: { [name: string]: string }): this {
|
|
316
|
-
this._headers =
|
|
319
|
+
this._headers = {};
|
|
320
|
+
for (const [name, value] of Object.entries(headers)) this.header(name, value);
|
|
317
321
|
return this;
|
|
318
322
|
}
|
|
319
323
|
|
|
324
|
+
/**
|
|
325
|
+
* Sets a header. The name is lowercased like Node does for incoming requests,
|
|
326
|
+
* so `header('Authorization', ...)` is found by `authorization: HttpHeader<string>`.
|
|
327
|
+
*/
|
|
320
328
|
header(name: string, value: string | number): this {
|
|
321
|
-
this._headers[name] = String(value);
|
|
329
|
+
this._headers[name.toLowerCase()] = String(value);
|
|
322
330
|
return this;
|
|
323
331
|
}
|
|
324
332
|
|
package/src/module.ts
CHANGED
|
@@ -32,8 +32,9 @@ export class HttpModule extends createModuleClass({
|
|
|
32
32
|
HttpResultFormatter,
|
|
33
33
|
HttpRouterRegistry,
|
|
34
34
|
HttpRouterFilterResolver,
|
|
35
|
-
{ provide:
|
|
36
|
-
{ provide:
|
|
35
|
+
{ provide: InjectorContext, useValue: undefined, scope: 'http' },
|
|
36
|
+
{ provide: HttpResponse, useValue: undefined, scope: 'http' },
|
|
37
|
+
{ provide: HttpRequest, useValue: undefined, scope: 'http' },
|
|
37
38
|
{ provide: RouteConfig, useValue: undefined, scope: 'http' },
|
|
38
39
|
{ provide: Logger, useValue: new Logger([new ConsoleTransport()]) },
|
|
39
40
|
],
|
|
@@ -49,6 +50,7 @@ export class HttpModule extends createModuleClass({
|
|
|
49
50
|
HttpKernel,
|
|
50
51
|
HttpResultFormatter,
|
|
51
52
|
HttpRouterFilterResolver,
|
|
53
|
+
InjectorContext,
|
|
52
54
|
HttpResponse,
|
|
53
55
|
HttpRequest,
|
|
54
56
|
HttpControllers,
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Deepkit Framework
|
|
3
|
+
* Copyright (C) 2021 Deepkit UG, Marc J. Schmidt
|
|
4
|
+
*
|
|
5
|
+
* This program is free software: you can redistribute it and/or modify
|
|
6
|
+
* it under the terms of the MIT License.
|
|
7
|
+
*
|
|
8
|
+
* You should have received a copy of the MIT License along with this program.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Query string parser compatible with the default behavior of `qs.parse`.
|
|
13
|
+
*
|
|
14
|
+
* Supports nested objects (`a[b]=1`), arrays (`a[]=1`, `a[0]=1`) and duplicate keys (`a=1&a=2` becomes an array).
|
|
15
|
+
*
|
|
16
|
+
* Limits:
|
|
17
|
+
* - at most 1000 parameters are parsed, the rest is ignored
|
|
18
|
+
* - at most 5 levels of nesting, the remainder is used as literal key (`a[b][c][d][e][f][g]` → `a.b.c.d.e.f['[g]']`)
|
|
19
|
+
* - array indices above 20 create object keys instead of sparse arrays (`a[100]=1` → `{a: {100: '1'}}`)
|
|
20
|
+
*
|
|
21
|
+
* Keys that exist on Object.prototype (`__proto__`, `constructor`, `toString`, `hasOwnProperty`, ...) are dropped.
|
|
22
|
+
*/
|
|
23
|
+
const parameterLimit = 1000;
|
|
24
|
+
const depthLimit = 5;
|
|
25
|
+
const arrayLimit = 20;
|
|
26
|
+
|
|
27
|
+
const has = Object.prototype.hasOwnProperty;
|
|
28
|
+
const isArray = Array.isArray;
|
|
29
|
+
|
|
30
|
+
function decode(str: string): string {
|
|
31
|
+
if (str.indexOf('+') !== -1) str = str.replace(/\+/g, ' ');
|
|
32
|
+
if (str.indexOf('%') === -1) return str;
|
|
33
|
+
try {
|
|
34
|
+
return decodeURIComponent(str);
|
|
35
|
+
} catch {
|
|
36
|
+
return str;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Query strings without brackets, the common case: `a=1&b=2&a=3` → `{a: ['1', '3'], b: '2'}`.
|
|
42
|
+
* Same result as the general parser, without its nesting and compaction steps.
|
|
43
|
+
*/
|
|
44
|
+
function parseFlat(str: string): Record<string, any> {
|
|
45
|
+
const obj: Record<string, any> = {};
|
|
46
|
+
for (const part of str.split('&', parameterLimit)) {
|
|
47
|
+
const pos = part.indexOf('=');
|
|
48
|
+
const key = decode(pos === -1 ? part : part.slice(0, pos));
|
|
49
|
+
if (!key || has.call(Object.prototype, key)) continue;
|
|
50
|
+
const val = pos === -1 ? '' : decode(part.slice(pos + 1));
|
|
51
|
+
obj[key] = has.call(obj, key) ? ([] as any[]).concat(obj[key], val) : val;
|
|
52
|
+
}
|
|
53
|
+
return obj;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const nesting = /[[\]]|%5[bd]/i;
|
|
57
|
+
|
|
58
|
+
function parseValues(str: string): Record<string, any> {
|
|
59
|
+
const obj: Record<string, any> = Object.create(null);
|
|
60
|
+
const parts = str.replace(/%5B/gi, '[').replace(/%5D/gi, ']').split('&', parameterLimit);
|
|
61
|
+
|
|
62
|
+
for (const part of parts) {
|
|
63
|
+
const bracketEqualsPos = part.indexOf(']=');
|
|
64
|
+
const pos = bracketEqualsPos === -1 ? part.indexOf('=') : bracketEqualsPos + 1;
|
|
65
|
+
|
|
66
|
+
const key = decode(pos === -1 ? part : part.slice(0, pos));
|
|
67
|
+
const val = pos === -1 ? '' : decode(part.slice(pos + 1));
|
|
68
|
+
|
|
69
|
+
obj[key] = has.call(obj, key) ? ([] as any[]).concat(obj[key], val) : val;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return obj;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function parseObject(chain: string[], val: any): any {
|
|
76
|
+
let leaf = val;
|
|
77
|
+
|
|
78
|
+
for (let i = chain.length - 1; i >= 0; --i) {
|
|
79
|
+
let obj: any;
|
|
80
|
+
const root = chain[i];
|
|
81
|
+
|
|
82
|
+
if (root === '[]') {
|
|
83
|
+
obj = ([] as any[]).concat(leaf);
|
|
84
|
+
} else {
|
|
85
|
+
obj = {};
|
|
86
|
+
const cleanRoot = root.charAt(0) === '[' && root.charAt(root.length - 1) === ']' ? root.slice(1, -1) : root;
|
|
87
|
+
const index = parseInt(cleanRoot, 10);
|
|
88
|
+
if (
|
|
89
|
+
!isNaN(index) &&
|
|
90
|
+
root !== cleanRoot &&
|
|
91
|
+
String(index) === cleanRoot &&
|
|
92
|
+
index >= 0 &&
|
|
93
|
+
index <= arrayLimit
|
|
94
|
+
) {
|
|
95
|
+
obj = [];
|
|
96
|
+
obj[index] = leaf;
|
|
97
|
+
} else if (cleanRoot !== '__proto__') {
|
|
98
|
+
obj[cleanRoot] = leaf;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
leaf = obj;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return leaf;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function parseKeys(key: string, val: any): any {
|
|
109
|
+
if (!key) return;
|
|
110
|
+
|
|
111
|
+
const brackets = /(\[[^[\]]*])/;
|
|
112
|
+
const child = /(\[[^[\]]*])/g;
|
|
113
|
+
|
|
114
|
+
let segment = brackets.exec(key);
|
|
115
|
+
const parent = segment ? key.slice(0, segment.index) : key;
|
|
116
|
+
|
|
117
|
+
const keys: string[] = [];
|
|
118
|
+
if (parent) {
|
|
119
|
+
if (has.call(Object.prototype, parent)) return;
|
|
120
|
+
keys.push(parent);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
let i = 0;
|
|
124
|
+
while ((segment = child.exec(key)) !== null && i < depthLimit) {
|
|
125
|
+
i += 1;
|
|
126
|
+
if (has.call(Object.prototype, segment[1].slice(1, -1))) return;
|
|
127
|
+
keys.push(segment[1]);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// depth limit reached, the remainder is used as literal key
|
|
131
|
+
if (segment) keys.push('[' + key.slice(segment.index) + ']');
|
|
132
|
+
|
|
133
|
+
return parseObject(keys, val);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function arrayToObject(source: any[]): Record<string, any> {
|
|
137
|
+
const obj: Record<string, any> = {};
|
|
138
|
+
for (let i = 0; i < source.length; ++i) {
|
|
139
|
+
if (source[i] !== undefined) obj[i] = source[i];
|
|
140
|
+
}
|
|
141
|
+
return obj;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function merge(target: any, source: any): any {
|
|
145
|
+
if (!source) return target;
|
|
146
|
+
|
|
147
|
+
if (typeof source !== 'object') {
|
|
148
|
+
if (isArray(target)) {
|
|
149
|
+
target.push(source);
|
|
150
|
+
} else if (target && typeof target === 'object') {
|
|
151
|
+
if (!has.call(Object.prototype, source)) target[source] = true;
|
|
152
|
+
} else {
|
|
153
|
+
return [target, source];
|
|
154
|
+
}
|
|
155
|
+
return target;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (!target || typeof target !== 'object') {
|
|
159
|
+
return [target].concat(source);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (isArray(target) && isArray(source)) {
|
|
163
|
+
source.forEach((item, i) => {
|
|
164
|
+
if (has.call(target, i)) {
|
|
165
|
+
const targetItem = target[i];
|
|
166
|
+
if (targetItem && typeof targetItem === 'object' && item && typeof item === 'object') {
|
|
167
|
+
target[i] = merge(targetItem, item);
|
|
168
|
+
} else {
|
|
169
|
+
target.push(item);
|
|
170
|
+
}
|
|
171
|
+
} else {
|
|
172
|
+
target[i] = item;
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
return target;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const mergeTarget = isArray(target) && !isArray(source) ? arrayToObject(target) : target;
|
|
179
|
+
for (const key of Object.keys(source)) {
|
|
180
|
+
mergeTarget[key] = has.call(mergeTarget, key) ? merge(mergeTarget[key], source[key]) : source[key];
|
|
181
|
+
}
|
|
182
|
+
return mergeTarget;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Removes holes from (sparse) arrays, e.g. `a[1]=x` results in `['x']`, not `[undefined, 'x']`.
|
|
187
|
+
*/
|
|
188
|
+
function compact(value: Record<string, any>): Record<string, any> {
|
|
189
|
+
const queue: { obj: any; prop: string }[] = [{ obj: { o: value }, prop: 'o' }];
|
|
190
|
+
const refs: any[] = [];
|
|
191
|
+
|
|
192
|
+
for (let i = 0; i < queue.length; ++i) {
|
|
193
|
+
const obj = queue[i].obj[queue[i].prop];
|
|
194
|
+
for (const key of Object.keys(obj)) {
|
|
195
|
+
const val = obj[key];
|
|
196
|
+
if (typeof val === 'object' && val !== null && refs.indexOf(val) === -1) {
|
|
197
|
+
queue.push({ obj, prop: key });
|
|
198
|
+
refs.push(val);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
while (queue.length > 1) {
|
|
204
|
+
const item = queue.pop()!;
|
|
205
|
+
const obj = item.obj[item.prop];
|
|
206
|
+
if (isArray(obj)) item.obj[item.prop] = obj.filter(v => v !== undefined);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return value;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Parses a query string (without leading `?`) into an object.
|
|
214
|
+
*
|
|
215
|
+
* `a=1&b[c]=2&d[]=3&d[]=4` → `{a: '1', b: {c: '2'}, d: ['3', '4']}`
|
|
216
|
+
*/
|
|
217
|
+
export function parseQueryString(str: string): Record<string, any> {
|
|
218
|
+
if (!str) return {};
|
|
219
|
+
if (!nesting.test(str)) return parseFlat(str);
|
|
220
|
+
|
|
221
|
+
const values = parseValues(str);
|
|
222
|
+
let obj: Record<string, any> = {};
|
|
223
|
+
for (const key of Object.keys(values)) {
|
|
224
|
+
obj = merge(obj, parseKeys(key, values[key]));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return compact(obj);
|
|
228
|
+
}
|
package/src/request-parser.ts
CHANGED
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
} from '@deepkit/type';
|
|
20
20
|
import { BodyValidationError, createRequestWithCachedBody, getRegExp, HttpRequest, ValidatedBody } from './model.js';
|
|
21
21
|
import { getRouteActionLabel, RouteConfig, UploadedFile, UploadedFileSymbol } from './router.js';
|
|
22
|
-
import
|
|
22
|
+
import { parseQueryString } from './query-string.js';
|
|
23
23
|
import formidable, { Fields, Files, Options } from 'formidable';
|
|
24
24
|
import { HttpParserOptions } from './module.config.js';
|
|
25
25
|
import type IncomingForm from 'formidable/Formidable.js';
|
|
@@ -231,10 +231,10 @@ export function buildRequestParser(parseOptions: HttpParserOptions, parameters:
|
|
|
231
231
|
routeConfig
|
|
232
232
|
});
|
|
233
233
|
compiler.context.set('ValidationError', ValidationError);
|
|
234
|
-
compiler.context.set('
|
|
234
|
+
compiler.context.set('parseQueryString', parseQueryString);
|
|
235
235
|
|
|
236
236
|
let needsQueryString = !!params.find(v => v.query || v.queries || v.requestParser);
|
|
237
|
-
const query = needsQueryString ? '_qPosition === -1 ? {} :
|
|
237
|
+
const query = needsQueryString ? '_qPosition === -1 ? {} : parseQueryString(_url.substr(_qPosition + 1))' : '{}';
|
|
238
238
|
|
|
239
239
|
const regexVar = compiler.reserveVariable('regex', new RegExp('^' + pathRegex + '$'));
|
|
240
240
|
|
|
@@ -329,7 +329,9 @@ export function getRequestParserCodeForParameters(
|
|
|
329
329
|
|
|
330
330
|
const queryPath = parameter.typePath === undefined && !parameter.queries ? parameter.parameter.name : parameter.typePath;
|
|
331
331
|
const accessor = queryPath ? `['` + (queryPath.replace(/\./g, `']['`)) + `']` : '';
|
|
332
|
-
|
|
332
|
+
// header names are case-insensitive, Node lowercases them in request.headers
|
|
333
|
+
const headerAccessor = queryPath ? `_headers[${JSON.stringify(queryPath.toLowerCase())}]` : '_headers';
|
|
334
|
+
const queryAccessor = parameter.header ? headerAccessor : queryPath ? `_query${accessor}` : '_query';
|
|
333
335
|
|
|
334
336
|
if (isOptional(parameter.parameter.parameter) || hasDefaultValue(parameter.parameter.parameter)) {
|
|
335
337
|
setParameters.push(`parameters.${parameter.parameter.name} = ${queryAccessor} === undefined ? undefined : ${converterVar}(${queryAccessor}, {loosely: true});`);
|
package/src/router.ts
CHANGED
|
@@ -18,7 +18,7 @@ import { MiddlewareRegistry, MiddlewareRegistryEntry } from '@deepkit/app';
|
|
|
18
18
|
import { HttpMiddlewareConfig, HttpMiddlewareFn } from './middleware.js';
|
|
19
19
|
|
|
20
20
|
//@ts-ignore
|
|
21
|
-
import
|
|
21
|
+
import { parseQueryString } from './query-string.js';
|
|
22
22
|
import { HtmlResponse, JSONResponse, Redirect, Response } from './http.js';
|
|
23
23
|
import { getRequestParserCodeForParameters, ParameterForRequestParser, parseRoutePathToRegex } from './request-parser.js';
|
|
24
24
|
import { HttpConfig } from './module.config.js';
|
|
@@ -699,7 +699,7 @@ export class HttpRouter {
|
|
|
699
699
|
this.buildId = this.registry.getBuildId();
|
|
700
700
|
const compiler = new CompilerContext;
|
|
701
701
|
compiler.context.set('ValidationError', ValidationError);
|
|
702
|
-
compiler.context.set('
|
|
702
|
+
compiler.context.set('parseQueryString', parseQueryString);
|
|
703
703
|
|
|
704
704
|
const code: string[] = [];
|
|
705
705
|
|
|
@@ -715,7 +715,7 @@ export class HttpRouter {
|
|
|
715
715
|
const _qPosition = _url.indexOf('?');
|
|
716
716
|
let uploadedFiles = {};
|
|
717
717
|
const _path = _qPosition === -1 ? _url : _url.substr(0, _qPosition);
|
|
718
|
-
const _query = _qPosition === -1 ? {} :
|
|
718
|
+
const _query = _qPosition === -1 ? {} : parseQueryString(_url.substr(_qPosition + 1));
|
|
719
719
|
${code.join('\n')}
|
|
720
720
|
`, 'request') as any;
|
|
721
721
|
}
|