expediate 1.0.5 → 1.0.6
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 +138 -0
- package/CONTRIBUTING.md +150 -0
- package/README.md +278 -779
- package/dist/apis.d.ts +372 -12
- package/dist/apis.d.ts.map +1 -1
- package/dist/apis.js +483 -65
- package/dist/apis.js.map +1 -1
- package/dist/cjs/index.js +2290 -807
- package/dist/git.d.ts +1 -1
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js +5 -5
- package/dist/git.js.map +1 -1
- package/dist/http-objects.d.ts +26 -0
- package/dist/http-objects.d.ts.map +1 -0
- package/dist/http-objects.js +588 -0
- package/dist/http-objects.js.map +1 -0
- package/dist/index.d.ts +6 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/jwt-auth.d.ts +11 -0
- package/dist/jwt-auth.d.ts.map +1 -1
- package/dist/jwt-auth.js +9 -9
- package/dist/jwt-auth.js.map +1 -1
- package/dist/middleware.js +2 -2
- package/dist/middleware.js.map +1 -1
- package/dist/mimetypes.json +882 -1
- package/dist/misc.d.ts +161 -25
- package/dist/misc.d.ts.map +1 -1
- package/dist/misc.js +228 -80
- package/dist/misc.js.map +1 -1
- package/dist/openapi.d.ts +156 -13
- package/dist/openapi.d.ts.map +1 -1
- package/dist/openapi.js +214 -71
- package/dist/openapi.js.map +1 -1
- package/dist/router-types.d.ts +760 -0
- package/dist/router-types.d.ts.map +1 -0
- package/dist/router-types.js +23 -0
- package/dist/router-types.js.map +1 -0
- package/dist/router.d.ts +7 -530
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +128 -375
- package/dist/router.js.map +1 -1
- package/dist/static.d.ts +2 -2
- package/dist/static.d.ts.map +1 -1
- package/dist/static.js +77 -22
- package/dist/static.js.map +1 -1
- package/docs/THREAT_MODEL.md +52 -0
- package/docs/api-builder-v2-design.md +644 -0
- package/docs/api-builder-v3-design.md +397 -0
- package/docs/api-builder.md +454 -0
- package/docs/benchmark.md +27 -0
- package/docs/body-parsing.md +223 -0
- package/docs/errors.md +359 -0
- package/docs/expediate.png +0 -0
- package/docs/git.md +139 -0
- package/docs/jwt-auth.md +251 -0
- package/docs/logo.svg +12 -0
- package/docs/middleware.md +264 -0
- package/docs/openapi.md +180 -0
- package/docs/router.md +356 -0
- package/docs/static.md +128 -0
- package/docs/wiki.json +123 -0
- package/package.json +30 -8
- package/dist/cjs/apis.js +0 -327
- package/dist/cjs/git.js +0 -293
- package/dist/cjs/jwt-auth.js +0 -532
- package/dist/cjs/middleware.js +0 -511
- package/dist/cjs/mimetypes.json +0 -1
- package/dist/cjs/misc.js +0 -787
- package/dist/cjs/openapi.js +0 -485
- package/dist/cjs/router.js +0 -898
- package/dist/cjs/static.js +0 -669
package/dist/cjs/index.js
CHANGED
|
@@ -40,6 +40,7 @@ __export(index_exports, {
|
|
|
40
40
|
createMapTokenStore: () => createMapTokenStore,
|
|
41
41
|
createRouter: () => router_default,
|
|
42
42
|
csrf: () => csrf,
|
|
43
|
+
defineController: () => defineController,
|
|
43
44
|
describe: () => describe,
|
|
44
45
|
formData: () => formData,
|
|
45
46
|
formEncoded: () => formEncoded,
|
|
@@ -52,501 +53,1027 @@ __export(index_exports, {
|
|
|
52
53
|
parseBody: () => parseBody,
|
|
53
54
|
parseMultipartBody: () => parseMultipartBody,
|
|
54
55
|
rateLimit: () => rateLimit,
|
|
56
|
+
raw: () => raw,
|
|
55
57
|
requestId: () => requestId,
|
|
56
58
|
securityHeaders: () => securityHeaders,
|
|
57
59
|
sendFile: () => sendFile,
|
|
58
60
|
serializeSpec: () => serializeSpec,
|
|
59
61
|
serveFile: () => serveFile,
|
|
60
62
|
serveStatic: () => serveStatic,
|
|
61
|
-
streamFormData: () => streamFormData
|
|
63
|
+
streamFormData: () => streamFormData,
|
|
64
|
+
text: () => text
|
|
62
65
|
});
|
|
63
66
|
module.exports = __toCommonJS(index_exports);
|
|
64
67
|
|
|
65
68
|
// src/router.ts
|
|
66
|
-
var crypto = __toESM(require("crypto"), 1);
|
|
67
|
-
var fs2 = __toESM(require("fs"), 1);
|
|
68
69
|
var http = __toESM(require("http"), 1);
|
|
69
70
|
var https = __toESM(require("https"), 1);
|
|
70
71
|
var http2 = __toESM(require("http2"), 1);
|
|
72
|
+
|
|
73
|
+
// src/http-objects.ts
|
|
74
|
+
var crypto = __toESM(require("crypto"), 1);
|
|
75
|
+
var fs2 = __toESM(require("fs"), 1);
|
|
71
76
|
var path = __toESM(require("path"), 1);
|
|
72
77
|
|
|
73
|
-
// src/
|
|
74
|
-
var
|
|
75
|
-
var
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
78
|
+
// src/static.ts
|
|
79
|
+
var import_fs = __toESM(require("fs"), 1);
|
|
80
|
+
var import_path = __toESM(require("path"), 1);
|
|
81
|
+
|
|
82
|
+
// src/mimetypes.json
|
|
83
|
+
var mimetypes_default = {
|
|
84
|
+
"application/andrew-inset": ["ez"],
|
|
85
|
+
"application/applixware": ["aw"],
|
|
86
|
+
"application/atom+xml": ["atom"],
|
|
87
|
+
"application/atomcat+xml": ["atomcat"],
|
|
88
|
+
"application/atomsvc+xml": ["atomsvc"],
|
|
89
|
+
"application/bdoc": ["bdoc"],
|
|
90
|
+
"application/ccxml+xml": ["ccxml"],
|
|
91
|
+
"application/cdmi-capability": ["cdmia"],
|
|
92
|
+
"application/cdmi-container": ["cdmic"],
|
|
93
|
+
"application/cdmi-domain": ["cdmid"],
|
|
94
|
+
"application/cdmi-object": ["cdmio"],
|
|
95
|
+
"application/cdmi-queue": ["cdmiq"],
|
|
96
|
+
"application/cu-seeme": ["cu"],
|
|
97
|
+
"application/dash+xml": ["mpd"],
|
|
98
|
+
"application/davmount+xml": ["davmount"],
|
|
99
|
+
"application/docbook+xml": ["dbk"],
|
|
100
|
+
"application/dssc+der": ["dssc"],
|
|
101
|
+
"application/dssc+xml": ["xdssc"],
|
|
102
|
+
"application/ecmascript": ["ecma"],
|
|
103
|
+
"application/emma+xml": ["emma"],
|
|
104
|
+
"application/epub+zip": ["epub"],
|
|
105
|
+
"application/exi": ["exi"],
|
|
106
|
+
"application/font-tdpfr": ["pfr"],
|
|
107
|
+
"application/font-woff": [],
|
|
108
|
+
"application/font-woff2": [],
|
|
109
|
+
"application/geo+json": ["geojson"],
|
|
110
|
+
"application/gml+xml": ["gml"],
|
|
111
|
+
"application/gpx+xml": ["gpx"],
|
|
112
|
+
"application/gxf": ["gxf"],
|
|
113
|
+
"application/gzip": ["gz"],
|
|
114
|
+
"application/hyperstudio": ["stk"],
|
|
115
|
+
"application/inkml+xml": ["ink", "inkml"],
|
|
116
|
+
"application/ipfix": ["ipfix"],
|
|
117
|
+
"application/java-archive": ["jar", "war", "ear"],
|
|
118
|
+
"application/java-serialized-object": ["ser"],
|
|
119
|
+
"application/java-vm": ["class"],
|
|
120
|
+
"application/javascript": ["js", "mjs"],
|
|
121
|
+
"application/json": ["json", "map"],
|
|
122
|
+
"application/json5": ["json5"],
|
|
123
|
+
"application/jsonml+json": ["jsonml"],
|
|
124
|
+
"application/ld+json": ["jsonld"],
|
|
125
|
+
"application/lost+xml": ["lostxml"],
|
|
126
|
+
"application/mac-binhex40": ["hqx"],
|
|
127
|
+
"application/mac-compactpro": ["cpt"],
|
|
128
|
+
"application/mads+xml": ["mads"],
|
|
129
|
+
"application/manifest+json": ["webmanifest"],
|
|
130
|
+
"application/marc": ["mrc"],
|
|
131
|
+
"application/marcxml+xml": ["mrcx"],
|
|
132
|
+
"application/mathematica": ["ma", "nb", "mb"],
|
|
133
|
+
"application/mathml+xml": ["mathml"],
|
|
134
|
+
"application/mbox": ["mbox"],
|
|
135
|
+
"application/mediaservercontrol+xml": ["mscml"],
|
|
136
|
+
"application/metalink+xml": ["metalink"],
|
|
137
|
+
"application/metalink4+xml": ["meta4"],
|
|
138
|
+
"application/mets+xml": ["mets"],
|
|
139
|
+
"application/mods+xml": ["mods"],
|
|
140
|
+
"application/mp21": ["m21", "mp21"],
|
|
141
|
+
"application/mp4": ["mp4s", "m4p"],
|
|
142
|
+
"application/msword": ["doc", "dot", "wiz"],
|
|
143
|
+
"application/mxf": ["mxf"],
|
|
144
|
+
"application/octet-stream": ["bin", "dms", "lrf", "mar", "so", "dist", "distz", "pkg", "bpk", "dump", "elc", "deploy", "exe", "dll", "deb", "dmg", "iso", "img", "msi", "msp", "msm", "buffer", "a", "o"],
|
|
145
|
+
"application/oda": ["oda"],
|
|
146
|
+
"application/oebps-package+xml": ["opf"],
|
|
147
|
+
"application/ogg": ["ogx"],
|
|
148
|
+
"application/omdoc+xml": ["omdoc"],
|
|
149
|
+
"application/onenote": ["onetoc", "onetoc2", "onetmp", "onepkg"],
|
|
150
|
+
"application/oxps": ["oxps"],
|
|
151
|
+
"application/patch-ops-error+xml": ["xer"],
|
|
152
|
+
"application/pdf": ["pdf"],
|
|
153
|
+
"application/pgp-encrypted": ["pgp"],
|
|
154
|
+
"application/pgp-signature": ["asc", "sig"],
|
|
155
|
+
"application/pics-rules": ["prf"],
|
|
156
|
+
"application/pkcs10": ["p10"],
|
|
157
|
+
"application/pkcs7-mime": ["p7m", "p7c"],
|
|
158
|
+
"application/pkcs7-signature": ["p7s"],
|
|
159
|
+
"application/pkcs8": ["p8"],
|
|
160
|
+
"application/pkix-attr-cert": ["ac"],
|
|
161
|
+
"application/pkix-cert": ["cer"],
|
|
162
|
+
"application/pkix-crl": ["crl"],
|
|
163
|
+
"application/pkix-pkipath": ["pkipath"],
|
|
164
|
+
"application/pkixcmp": ["pki"],
|
|
165
|
+
"application/pls+xml": ["pls"],
|
|
166
|
+
"application/postscript": ["ai", "eps", "ps"],
|
|
167
|
+
"application/prql": ["prql"],
|
|
168
|
+
"application/prs.cww": ["cww"],
|
|
169
|
+
"application/pskc+xml": ["pskcxml"],
|
|
170
|
+
"application/raml+yaml": ["raml"],
|
|
171
|
+
"application/rdf+xml": ["rdf"],
|
|
172
|
+
"application/reginfo+xml": ["rif"],
|
|
173
|
+
"application/relax-ng-compact-syntax": ["rnc"],
|
|
174
|
+
"application/resource-lists+xml": ["rl"],
|
|
175
|
+
"application/resource-lists-diff+xml": ["rld"],
|
|
176
|
+
"application/rls-services+xml": ["rs"],
|
|
177
|
+
"application/rpki-ghostbusters": ["gbr"],
|
|
178
|
+
"application/rpki-manifest": ["mft"],
|
|
179
|
+
"application/rpki-roa": ["roa"],
|
|
180
|
+
"application/rsd+xml": ["rsd"],
|
|
181
|
+
"application/rss+xml": ["rss"],
|
|
182
|
+
"application/rtf": ["rtf"],
|
|
183
|
+
"application/sbml+xml": ["sbml"],
|
|
184
|
+
"application/scvp-cv-request": ["scq"],
|
|
185
|
+
"application/scvp-cv-response": ["scs"],
|
|
186
|
+
"application/scvp-vp-request": ["spq"],
|
|
187
|
+
"application/scvp-vp-response": ["spp"],
|
|
188
|
+
"application/sdp": ["sdp"],
|
|
189
|
+
"application/set-payment-initiation": ["setpay"],
|
|
190
|
+
"application/set-registration-initiation": ["setreg"],
|
|
191
|
+
"application/shf+xml": ["shf"],
|
|
192
|
+
"application/smil+xml": ["smi", "smil"],
|
|
193
|
+
"application/sparql-query": ["rq"],
|
|
194
|
+
"application/sparql-results+xml": ["srx"],
|
|
195
|
+
"application/srgs": ["gram"],
|
|
196
|
+
"application/srgs+xml": ["grxml"],
|
|
197
|
+
"application/sru+xml": ["sru"],
|
|
198
|
+
"application/ssdl+xml": ["ssdl"],
|
|
199
|
+
"application/ssml+xml": ["ssml"],
|
|
200
|
+
"application/tei+xml": ["tei", "teicorpus"],
|
|
201
|
+
"application/thraud+xml": ["tfi"],
|
|
202
|
+
"application/timestamped-data": ["tsd"],
|
|
203
|
+
"application/vnd.3gpp.pic-bw-large": ["plb"],
|
|
204
|
+
"application/vnd.3gpp.pic-bw-small": ["psb"],
|
|
205
|
+
"application/vnd.3gpp.pic-bw-var": ["pvb"],
|
|
206
|
+
"application/vnd.3gpp2.tcap": ["tcap"],
|
|
207
|
+
"application/vnd.3m.post-it-notes": ["pwn"],
|
|
208
|
+
"application/vnd.accpac.simply.aso": ["aso"],
|
|
209
|
+
"application/vnd.accpac.simply.imp": ["imp"],
|
|
210
|
+
"application/vnd.acucobol": ["acu"],
|
|
211
|
+
"application/vnd.acucorp": ["atc", "acutc"],
|
|
212
|
+
"application/vnd.adobe.air-application-installer-package+zip": ["air"],
|
|
213
|
+
"application/vnd.adobe.formscentral.fcdt": ["fcdt"],
|
|
214
|
+
"application/vnd.adobe.fxp": ["fxp", "fxpl"],
|
|
215
|
+
"application/vnd.adobe.xdp+xml": ["xdp"],
|
|
216
|
+
"application/vnd.adobe.xfdf": ["xfdf"],
|
|
217
|
+
"application/vnd.ahead.space": ["ahead"],
|
|
218
|
+
"application/vnd.airzip.filesecure.azf": ["azf"],
|
|
219
|
+
"application/vnd.airzip.filesecure.azs": ["azs"],
|
|
220
|
+
"application/vnd.amazon.ebook": ["azw"],
|
|
221
|
+
"application/vnd.americandynamics.acc": ["acc"],
|
|
222
|
+
"application/vnd.amiga.ami": ["ami"],
|
|
223
|
+
"application/vnd.android.package-archive": ["apk"],
|
|
224
|
+
"application/vnd.anser-web-certificate-issue-initiation": ["cii"],
|
|
225
|
+
"application/vnd.anser-web-funds-transfer-initiation": ["fti"],
|
|
226
|
+
"application/vnd.antix.game-component": ["atx"],
|
|
227
|
+
"application/vnd.apple.installer+xml": ["mpkg"],
|
|
228
|
+
"application/vnd.apple.mpegurl": ["m3u8"],
|
|
229
|
+
"application/vnd.apple.pkpass": ["pkpass"],
|
|
230
|
+
"application/vnd.aristanetworks.swi": ["swi"],
|
|
231
|
+
"application/vnd.astraea-software.iota": ["iota"],
|
|
232
|
+
"application/vnd.audiograph": ["aep"],
|
|
233
|
+
"application/vnd.blueice.multipass": ["mpm"],
|
|
234
|
+
"application/vnd.bmi": ["bmi"],
|
|
235
|
+
"application/vnd.businessobjects": ["rep"],
|
|
236
|
+
"application/vnd.chemdraw+xml": ["cdxml"],
|
|
237
|
+
"application/vnd.chipnuts.karaoke-mmd": ["mmd"],
|
|
238
|
+
"application/vnd.cinderella": ["cdy"],
|
|
239
|
+
"application/vnd.claymore": ["cla"],
|
|
240
|
+
"application/vnd.cloanto.rp9": ["rp9"],
|
|
241
|
+
"application/vnd.clonk.c4group": ["c4g", "c4d", "c4f", "c4p", "c4u"],
|
|
242
|
+
"application/vnd.cluetrust.cartomobile-config": ["c11amc"],
|
|
243
|
+
"application/vnd.cluetrust.cartomobile-config-pkg": ["c11amz"],
|
|
244
|
+
"application/vnd.commonspace": ["csp"],
|
|
245
|
+
"application/vnd.contact.cmsg": ["cdbcmsg"],
|
|
246
|
+
"application/vnd.cosmocaller": ["cmc"],
|
|
247
|
+
"application/vnd.crick.clicker": ["clkx"],
|
|
248
|
+
"application/vnd.crick.clicker.keyboard": ["clkk"],
|
|
249
|
+
"application/vnd.crick.clicker.palette": ["clkp"],
|
|
250
|
+
"application/vnd.crick.clicker.template": ["clkt"],
|
|
251
|
+
"application/vnd.crick.clicker.wordbank": ["clkw"],
|
|
252
|
+
"application/vnd.criticaltools.wbs+xml": ["wbs"],
|
|
253
|
+
"application/vnd.ctc-posml": ["pml"],
|
|
254
|
+
"application/vnd.cups-ppd": ["ppd"],
|
|
255
|
+
"application/vnd.curl.car": ["car"],
|
|
256
|
+
"application/vnd.curl.pcurl": ["pcurl"],
|
|
257
|
+
"application/vnd.dart": ["dart"],
|
|
258
|
+
"application/vnd.data-vision.rdz": ["rdz"],
|
|
259
|
+
"application/vnd.dece.data": ["uvf", "uvvf", "uvd", "uvvd"],
|
|
260
|
+
"application/vnd.dece.ttml+xml": ["uvt", "uvvt"],
|
|
261
|
+
"application/vnd.dece.unspecified": ["uvx", "uvvx"],
|
|
262
|
+
"application/vnd.dece.zip": ["uvz", "uvvz"],
|
|
263
|
+
"application/vnd.denovo.fcselayout-link": ["fe_launch"],
|
|
264
|
+
"application/vnd.dna": ["dna"],
|
|
265
|
+
"application/vnd.dolby.mlp": ["mlp"],
|
|
266
|
+
"application/vnd.dpgraph": ["dpg"],
|
|
267
|
+
"application/vnd.dreamfactory": ["dfac"],
|
|
268
|
+
"application/vnd.ds-keypoint": ["kpxx"],
|
|
269
|
+
"application/vnd.dvb.ait": ["ait"],
|
|
270
|
+
"application/vnd.dvb.service": ["svc"],
|
|
271
|
+
"application/vnd.dynageo": ["geo"],
|
|
272
|
+
"application/vnd.ecowin.chart": ["mag"],
|
|
273
|
+
"application/vnd.enliven": ["nml"],
|
|
274
|
+
"application/vnd.epson.esf": ["esf"],
|
|
275
|
+
"application/vnd.epson.msf": ["msf"],
|
|
276
|
+
"application/vnd.epson.quickanime": ["qam"],
|
|
277
|
+
"application/vnd.epson.salt": ["slt"],
|
|
278
|
+
"application/vnd.epson.ssf": ["ssf"],
|
|
279
|
+
"application/vnd.eszigno3+xml": ["es3", "et3"],
|
|
280
|
+
"application/vnd.ezpix-album": ["ez2"],
|
|
281
|
+
"application/vnd.ezpix-package": ["ez3"],
|
|
282
|
+
"application/vnd.fdf": ["fdf"],
|
|
283
|
+
"application/vnd.fdsn.mseed": ["mseed"],
|
|
284
|
+
"application/vnd.fdsn.seed": ["seed", "dataless"],
|
|
285
|
+
"application/vnd.flographit": ["gph"],
|
|
286
|
+
"application/vnd.fluxtime.clip": ["ftc"],
|
|
287
|
+
"application/vnd.framemaker": ["fm", "frame", "maker", "book"],
|
|
288
|
+
"application/vnd.frogans.fnc": ["fnc"],
|
|
289
|
+
"application/vnd.frogans.ltf": ["ltf"],
|
|
290
|
+
"application/vnd.fsc.weblaunch": ["fsc"],
|
|
291
|
+
"application/vnd.fujitsu.oasys": ["oas"],
|
|
292
|
+
"application/vnd.fujitsu.oasys2": ["oa2"],
|
|
293
|
+
"application/vnd.fujitsu.oasys3": ["oa3"],
|
|
294
|
+
"application/vnd.fujitsu.oasysgp": ["fg5"],
|
|
295
|
+
"application/vnd.fujitsu.oasysprs": ["bh2"],
|
|
296
|
+
"application/vnd.fujixerox.ddd": ["ddd"],
|
|
297
|
+
"application/vnd.fujixerox.docuworks": ["xdw"],
|
|
298
|
+
"application/vnd.fujixerox.docuworks.binder": ["xbd"],
|
|
299
|
+
"application/vnd.fuzzysheet": ["fzs"],
|
|
300
|
+
"application/vnd.genomatix.tuxedo": ["txd"],
|
|
301
|
+
"application/vnd.geogebra.file": ["ggb"],
|
|
302
|
+
"application/vnd.geogebra.tool": ["ggt"],
|
|
303
|
+
"application/vnd.geometry-explorer": ["gex", "gre"],
|
|
304
|
+
"application/vnd.geonext": ["gxt"],
|
|
305
|
+
"application/vnd.geoplan": ["g2w"],
|
|
306
|
+
"application/vnd.geospace": ["g3w"],
|
|
307
|
+
"application/vnd.gmx": ["gmx"],
|
|
308
|
+
"application/vnd.google-apps.document": ["gdoc"],
|
|
309
|
+
"application/vnd.google-apps.presentation": ["gslides"],
|
|
310
|
+
"application/vnd.google-apps.spreadsheet": ["gsheet"],
|
|
311
|
+
"application/vnd.google-earth.kml+xml": ["kml"],
|
|
312
|
+
"application/vnd.google-earth.kmz": ["kmz"],
|
|
313
|
+
"application/vnd.grafeq": ["gqf", "gqs"],
|
|
314
|
+
"application/vnd.groove-account": ["gac"],
|
|
315
|
+
"application/vnd.groove-help": ["ghf"],
|
|
316
|
+
"application/vnd.groove-identity-message": ["gim"],
|
|
317
|
+
"application/vnd.groove-injector": ["grv"],
|
|
318
|
+
"application/vnd.groove-tool-message": ["gtm"],
|
|
319
|
+
"application/vnd.groove-tool-template": ["tpl"],
|
|
320
|
+
"application/vnd.groove-vcard": ["vcg"],
|
|
321
|
+
"application/vnd.hal+xml": ["hal"],
|
|
322
|
+
"application/vnd.handheld-entertainment+xml": ["zmm"],
|
|
323
|
+
"application/vnd.hbci": ["hbci"],
|
|
324
|
+
"application/vnd.hhe.lesson-player": ["les"],
|
|
325
|
+
"application/vnd.hp-hpgl": ["hpgl"],
|
|
326
|
+
"application/vnd.hp-hpid": ["hpid"],
|
|
327
|
+
"application/vnd.hp-hps": ["hps"],
|
|
328
|
+
"application/vnd.hp-jlyt": ["jlt"],
|
|
329
|
+
"application/vnd.hp-pcl": ["pcl"],
|
|
330
|
+
"application/vnd.hp-pclxl": ["pclxl"],
|
|
331
|
+
"application/vnd.hydrostatix.sof-data": ["sfd-hdstx"],
|
|
332
|
+
"application/vnd.ibm.minipay": ["mpy"],
|
|
333
|
+
"application/vnd.ibm.modcap": ["afp", "listafp", "list3820"],
|
|
334
|
+
"application/vnd.ibm.rights-management": ["irm"],
|
|
335
|
+
"application/vnd.ibm.secure-container": ["sc"],
|
|
336
|
+
"application/vnd.iccprofile": ["icc", "icm"],
|
|
337
|
+
"application/vnd.igloader": ["igl"],
|
|
338
|
+
"application/vnd.immervision-ivp": ["ivp"],
|
|
339
|
+
"application/vnd.immervision-ivu": ["ivu"],
|
|
340
|
+
"application/vnd.insors.igm": ["igm"],
|
|
341
|
+
"application/vnd.intercon.formnet": ["xpw", "xpx"],
|
|
342
|
+
"application/vnd.intergeo": ["i2g"],
|
|
343
|
+
"application/vnd.intu.qbo": ["qbo"],
|
|
344
|
+
"application/vnd.intu.qfx": ["qfx"],
|
|
345
|
+
"application/vnd.ipunplugged.rcprofile": ["rcprofile"],
|
|
346
|
+
"application/vnd.irepository.package+xml": ["irp"],
|
|
347
|
+
"application/vnd.is-xpr": ["xpr"],
|
|
348
|
+
"application/vnd.isac.fcs": ["fcs"],
|
|
349
|
+
"application/vnd.jam": ["jam"],
|
|
350
|
+
"application/vnd.jcp.javame.midlet-rms": ["rms"],
|
|
351
|
+
"application/vnd.jisp": ["jisp"],
|
|
352
|
+
"application/vnd.joost.joda-archive": ["joda"],
|
|
353
|
+
"application/vnd.kahootz": ["ktz", "ktr"],
|
|
354
|
+
"application/vnd.kde.karbon": ["karbon"],
|
|
355
|
+
"application/vnd.kde.kchart": ["chrt"],
|
|
356
|
+
"application/vnd.kde.kformula": ["kfo"],
|
|
357
|
+
"application/vnd.kde.kivio": ["flw"],
|
|
358
|
+
"application/vnd.kde.kontour": ["kon"],
|
|
359
|
+
"application/vnd.kde.kpresenter": ["kpr", "kpt"],
|
|
360
|
+
"application/vnd.kde.kspread": ["ksp"],
|
|
361
|
+
"application/vnd.kde.kword": ["kwd", "kwt"],
|
|
362
|
+
"application/vnd.kenameaapp": ["htke"],
|
|
363
|
+
"application/vnd.kidspiration": ["kia"],
|
|
364
|
+
"application/vnd.kinar": ["kne", "knp"],
|
|
365
|
+
"application/vnd.koan": ["skp", "skd", "skt", "skm"],
|
|
366
|
+
"application/vnd.kodak-descriptor": ["sse"],
|
|
367
|
+
"application/vnd.las.las+xml": ["lasxml"],
|
|
368
|
+
"application/vnd.llamagraphics.life-balance.desktop": ["lbd"],
|
|
369
|
+
"application/vnd.llamagraphics.life-balance.exchange+xml": ["lbe"],
|
|
370
|
+
"application/vnd.lotus-1-2-3": ["123"],
|
|
371
|
+
"application/vnd.lotus-approach": ["apr"],
|
|
372
|
+
"application/vnd.lotus-freelance": ["pre"],
|
|
373
|
+
"application/vnd.lotus-notes": ["nsf"],
|
|
374
|
+
"application/vnd.lotus-organizer": ["org"],
|
|
375
|
+
"application/vnd.lotus-screencam": ["scm"],
|
|
376
|
+
"application/vnd.lotus-wordpro": ["lwp"],
|
|
377
|
+
"application/vnd.macports.portpkg": ["portpkg"],
|
|
378
|
+
"application/vnd.mcd": ["mcd"],
|
|
379
|
+
"application/vnd.medcalcdata": ["mc1"],
|
|
380
|
+
"application/vnd.mediastation.cdkey": ["cdkey"],
|
|
381
|
+
"application/vnd.mfer": ["mwf"],
|
|
382
|
+
"application/vnd.mfmp": ["mfm"],
|
|
383
|
+
"application/vnd.micrografx.flo": ["flo"],
|
|
384
|
+
"application/vnd.micrografx.igx": ["igx"],
|
|
385
|
+
"application/vnd.mif": ["mif"],
|
|
386
|
+
"application/vnd.mobius.daf": ["daf"],
|
|
387
|
+
"application/vnd.mobius.dis": ["dis"],
|
|
388
|
+
"application/vnd.mobius.mbk": ["mbk"],
|
|
389
|
+
"application/vnd.mobius.mqy": ["mqy"],
|
|
390
|
+
"application/vnd.mobius.msl": ["msl"],
|
|
391
|
+
"application/vnd.mobius.plc": ["plc"],
|
|
392
|
+
"application/vnd.mobius.txf": ["txf"],
|
|
393
|
+
"application/vnd.mophun.application": ["mpn"],
|
|
394
|
+
"application/vnd.mophun.certificate": ["mpc"],
|
|
395
|
+
"application/vnd.mozilla.xul+xml": ["xul"],
|
|
396
|
+
"application/vnd.ms-artgalry": ["cil"],
|
|
397
|
+
"application/vnd.ms-cab-compressed": ["cab"],
|
|
398
|
+
"application/vnd.ms-excel": ["xls", "xlm", "xla", "xlc", "xlt", "xlw", "xlb"],
|
|
399
|
+
"application/vnd.ms-excel.addin.macroenabled.12": ["xlam"],
|
|
400
|
+
"application/vnd.ms-excel.sheet.binary.macroenabled.12": ["xlsb"],
|
|
401
|
+
"application/vnd.ms-excel.sheet.macroenabled.12": ["xlsm"],
|
|
402
|
+
"application/vnd.ms-excel.template.macroenabled.12": ["xltm"],
|
|
403
|
+
"application/vnd.ms-fontobject": ["eot"],
|
|
404
|
+
"application/vnd.ms-htmlhelp": ["chm"],
|
|
405
|
+
"application/vnd.ms-ims": ["ims"],
|
|
406
|
+
"application/vnd.ms-lrm": ["lrm"],
|
|
407
|
+
"application/vnd.ms-officetheme": ["thmx"],
|
|
408
|
+
"application/vnd.ms-outlook": ["msg"],
|
|
409
|
+
"application/vnd.ms-pki.seccat": ["cat"],
|
|
410
|
+
"application/vnd.ms-pki.stl": ["stl"],
|
|
411
|
+
"application/vnd.ms-powerpoint": ["ppt", "pps", "pot", "ppa", "pwz"],
|
|
412
|
+
"application/vnd.ms-powerpoint.addin.macroenabled.12": ["ppam"],
|
|
413
|
+
"application/vnd.ms-powerpoint.presentation.macroenabled.12": ["pptm"],
|
|
414
|
+
"application/vnd.ms-powerpoint.slide.macroenabled.12": ["sldm"],
|
|
415
|
+
"application/vnd.ms-powerpoint.slideshow.macroenabled.12": ["ppsm"],
|
|
416
|
+
"application/vnd.ms-powerpoint.template.macroenabled.12": ["potm"],
|
|
417
|
+
"application/vnd.ms-project": ["mpp", "mpt"],
|
|
418
|
+
"application/vnd.ms-word.document.macroenabled.12": ["docm"],
|
|
419
|
+
"application/vnd.ms-word.template.macroenabled.12": ["dotm"],
|
|
420
|
+
"application/vnd.ms-works": ["wps", "wks", "wcm", "wdb"],
|
|
421
|
+
"application/vnd.ms-wpl": ["wpl"],
|
|
422
|
+
"application/vnd.ms-xpsdocument": ["xps"],
|
|
423
|
+
"application/vnd.mseq": ["mseq"],
|
|
424
|
+
"application/vnd.musician": ["mus"],
|
|
425
|
+
"application/vnd.muvee.style": ["msty"],
|
|
426
|
+
"application/vnd.mynfc": ["taglet"],
|
|
427
|
+
"application/vnd.neurolanguage.nlu": ["nlu"],
|
|
428
|
+
"application/vnd.nitf": ["ntf", "nitf"],
|
|
429
|
+
"application/vnd.noblenet-directory": ["nnd"],
|
|
430
|
+
"application/vnd.noblenet-sealer": ["nns"],
|
|
431
|
+
"application/vnd.noblenet-web": ["nnw"],
|
|
432
|
+
"application/vnd.nokia.n-gage.data": ["ngdat"],
|
|
433
|
+
"application/vnd.nokia.n-gage.symbian.install": ["n-gage"],
|
|
434
|
+
"application/vnd.nokia.radio-preset": ["rpst"],
|
|
435
|
+
"application/vnd.nokia.radio-presets": ["rpss"],
|
|
436
|
+
"application/vnd.novadigm.edm": ["edm"],
|
|
437
|
+
"application/vnd.novadigm.edx": ["edx"],
|
|
438
|
+
"application/vnd.novadigm.ext": ["ext"],
|
|
439
|
+
"application/vnd.oasis.opendocument.chart": ["odc"],
|
|
440
|
+
"application/vnd.oasis.opendocument.chart-template": ["otc"],
|
|
441
|
+
"application/vnd.oasis.opendocument.database": ["odb"],
|
|
442
|
+
"application/vnd.oasis.opendocument.formula": ["odf"],
|
|
443
|
+
"application/vnd.oasis.opendocument.formula-template": ["odft"],
|
|
444
|
+
"application/vnd.oasis.opendocument.graphics": ["odg"],
|
|
445
|
+
"application/vnd.oasis.opendocument.graphics-template": ["otg"],
|
|
446
|
+
"application/vnd.oasis.opendocument.image": ["odi"],
|
|
447
|
+
"application/vnd.oasis.opendocument.image-template": ["oti"],
|
|
448
|
+
"application/vnd.oasis.opendocument.presentation": ["odp"],
|
|
449
|
+
"application/vnd.oasis.opendocument.presentation-template": ["otp"],
|
|
450
|
+
"application/vnd.oasis.opendocument.spreadsheet": ["ods"],
|
|
451
|
+
"application/vnd.oasis.opendocument.spreadsheet-template": ["ots"],
|
|
452
|
+
"application/vnd.oasis.opendocument.text": ["odt"],
|
|
453
|
+
"application/vnd.oasis.opendocument.text-master": ["odm", "otm"],
|
|
454
|
+
"application/vnd.oasis.opendocument.text-template": ["ott"],
|
|
455
|
+
"application/vnd.oasis.opendocument.text-web": ["oth"],
|
|
456
|
+
"application/vnd.olpc-sugar": ["xo"],
|
|
457
|
+
"application/vnd.oma.dd2+xml": ["dd2"],
|
|
458
|
+
"application/vnd.openofficeorg.extension": ["oxt"],
|
|
459
|
+
"application/vnd.openxmlformats-officedocument.presentationml.presentation": ["pptx"],
|
|
460
|
+
"application/vnd.openxmlformats-officedocument.presentationml.slide": ["sldx"],
|
|
461
|
+
"application/vnd.openxmlformats-officedocument.presentationml.slideshow": ["ppsx"],
|
|
462
|
+
"application/vnd.openxmlformats-officedocument.presentationml.template": ["potx"],
|
|
463
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": ["xlsx"],
|
|
464
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.template": ["xltx"],
|
|
465
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": ["docx"],
|
|
466
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.template": ["dotx"],
|
|
467
|
+
"application/vnd.osgeo.mapguide.package": ["mgp"],
|
|
468
|
+
"application/vnd.osgi.dp": ["dp"],
|
|
469
|
+
"application/vnd.osgi.subsystem": ["esa"],
|
|
470
|
+
"application/vnd.palm": ["pdb", "pqa", "oprc"],
|
|
471
|
+
"application/vnd.pawaafile": ["paw"],
|
|
472
|
+
"application/vnd.pg.format": ["str"],
|
|
473
|
+
"application/vnd.pg.osasli": ["ei6"],
|
|
474
|
+
"application/vnd.picsel": ["efif"],
|
|
475
|
+
"application/vnd.pmi.widget": ["wg"],
|
|
476
|
+
"application/vnd.pocketlearn": ["plf"],
|
|
477
|
+
"application/vnd.powerbuilder6": ["pbd"],
|
|
478
|
+
"application/vnd.previewsystems.box": ["box"],
|
|
479
|
+
"application/vnd.proteus.magazine": ["mgz"],
|
|
480
|
+
"application/vnd.publishare-delta-tree": ["qps"],
|
|
481
|
+
"application/vnd.pvi.ptid1": ["ptid"],
|
|
482
|
+
"application/vnd.quark.quarkxpress": ["qxd", "qxt", "qwd", "qwt", "qxl", "qxb"],
|
|
483
|
+
"application/vnd.realvnc.bed": ["bed"],
|
|
484
|
+
"application/vnd.recordare.musicxml": ["mxl"],
|
|
485
|
+
"application/vnd.recordare.musicxml+xml": ["musicxml"],
|
|
486
|
+
"application/vnd.rig.cryptonote": ["cryptonote"],
|
|
487
|
+
"application/vnd.rim.cod": ["cod"],
|
|
488
|
+
"application/vnd.rn-realmedia": ["rm"],
|
|
489
|
+
"application/vnd.rn-realmedia-vbr": ["rmvb"],
|
|
490
|
+
"application/vnd.route66.link66+xml": ["link66"],
|
|
491
|
+
"application/vnd.sailingtracker.track": ["st"],
|
|
492
|
+
"application/vnd.seemail": ["see"],
|
|
493
|
+
"application/vnd.sema": ["sema"],
|
|
494
|
+
"application/vnd.semd": ["semd"],
|
|
495
|
+
"application/vnd.semf": ["semf"],
|
|
496
|
+
"application/vnd.shana.informed.formdata": ["ifm"],
|
|
497
|
+
"application/vnd.shana.informed.formtemplate": ["itp"],
|
|
498
|
+
"application/vnd.shana.informed.interchange": ["iif"],
|
|
499
|
+
"application/vnd.shana.informed.package": ["ipk"],
|
|
500
|
+
"application/vnd.simtech-mindmapper": ["twd", "twds"],
|
|
501
|
+
"application/vnd.smaf": ["mmf"],
|
|
502
|
+
"application/vnd.smart.teacher": ["teacher"],
|
|
503
|
+
"application/vnd.solent.sdkm+xml": ["sdkm", "sdkd"],
|
|
504
|
+
"application/vnd.spotfire.dxp": ["dxp"],
|
|
505
|
+
"application/vnd.spotfire.sfs": ["sfs"],
|
|
506
|
+
"application/vnd.sqlite3": ["db", "sqlite", "sqlite3", "db-wal", "sqlite-wal", "db-shm", "sqlite-shm"],
|
|
507
|
+
"application/vnd.stardivision.calc": ["sdc"],
|
|
508
|
+
"application/vnd.stardivision.draw": ["sda"],
|
|
509
|
+
"application/vnd.stardivision.impress": ["sdd"],
|
|
510
|
+
"application/vnd.stardivision.math": ["smf"],
|
|
511
|
+
"application/vnd.stardivision.writer": ["sdw", "vor"],
|
|
512
|
+
"application/vnd.stardivision.writer-global": ["sgl"],
|
|
513
|
+
"application/vnd.stepmania.package": ["smzip"],
|
|
514
|
+
"application/vnd.stepmania.stepchart": ["sm"],
|
|
515
|
+
"application/vnd.sun.wadl+xml": ["wadl"],
|
|
516
|
+
"application/vnd.sun.xml.calc": ["sxc"],
|
|
517
|
+
"application/vnd.sun.xml.calc.template": ["stc"],
|
|
518
|
+
"application/vnd.sun.xml.draw": ["sxd"],
|
|
519
|
+
"application/vnd.sun.xml.draw.template": ["std"],
|
|
520
|
+
"application/vnd.sun.xml.impress": ["sxi"],
|
|
521
|
+
"application/vnd.sun.xml.impress.template": ["sti"],
|
|
522
|
+
"application/vnd.sun.xml.math": ["sxm"],
|
|
523
|
+
"application/vnd.sun.xml.writer": ["sxw"],
|
|
524
|
+
"application/vnd.sun.xml.writer.global": ["sxg"],
|
|
525
|
+
"application/vnd.sun.xml.writer.template": ["stw"],
|
|
526
|
+
"application/vnd.sus-calendar": ["sus", "susp"],
|
|
527
|
+
"application/vnd.svd": ["svd"],
|
|
528
|
+
"application/vnd.symbian.install": ["sis", "sisx"],
|
|
529
|
+
"application/vnd.syncml+xml": ["xsm"],
|
|
530
|
+
"application/vnd.syncml.dm+wbxml": ["bdm"],
|
|
531
|
+
"application/vnd.syncml.dm+xml": ["xdm"],
|
|
532
|
+
"application/vnd.tao.intent-module-archive": ["tao"],
|
|
533
|
+
"application/vnd.tcpdump.pcap": ["pcap", "cap", "dmp"],
|
|
534
|
+
"application/vnd.tmobile-livetv": ["tmo"],
|
|
535
|
+
"application/vnd.trid.tpt": ["tpt"],
|
|
536
|
+
"application/vnd.triscape.mxs": ["mxs"],
|
|
537
|
+
"application/vnd.trueapp": ["tra"],
|
|
538
|
+
"application/vnd.ufdl": ["ufd", "ufdl"],
|
|
539
|
+
"application/vnd.uiq.theme": ["utz"],
|
|
540
|
+
"application/vnd.umajin": ["umj"],
|
|
541
|
+
"application/vnd.unity": ["unityweb"],
|
|
542
|
+
"application/vnd.uoml+xml": ["uoml"],
|
|
543
|
+
"application/vnd.vcx": ["vcx"],
|
|
544
|
+
"application/vnd.visio": ["vsd", "vst", "vss", "vsw", "vsdx", "vssx", "vstx", "vssm", "vstm"],
|
|
545
|
+
"application/vnd.visionary": ["vis"],
|
|
546
|
+
"application/vnd.vsf": ["vsf"],
|
|
547
|
+
"application/vnd.wap.sic": ["sic"],
|
|
548
|
+
"application/vnd.wap.slc": ["slc"],
|
|
549
|
+
"application/vnd.wap.wbxml": ["wbxml"],
|
|
550
|
+
"application/vnd.wap.wmlc": ["wmlc"],
|
|
551
|
+
"application/vnd.wap.wmlscriptc": ["wmlsc"],
|
|
552
|
+
"application/vnd.webturbo": ["wtb"],
|
|
553
|
+
"application/vnd.wolfram.player": ["nbp"],
|
|
554
|
+
"application/vnd.wordperfect": ["wpd"],
|
|
555
|
+
"application/vnd.wqd": ["wqd"],
|
|
556
|
+
"application/vnd.wt.stf": ["stf"],
|
|
557
|
+
"application/vnd.xara": ["xar"],
|
|
558
|
+
"application/vnd.xfdl": ["xfdl"],
|
|
559
|
+
"application/vnd.yamaha.hv-dic": ["hvd"],
|
|
560
|
+
"application/vnd.yamaha.hv-script": ["hvs"],
|
|
561
|
+
"application/vnd.yamaha.hv-voice": ["hvp"],
|
|
562
|
+
"application/vnd.yamaha.openscoreformat": ["osf"],
|
|
563
|
+
"application/vnd.yamaha.openscoreformat.osfpvg+xml": ["osfpvg"],
|
|
564
|
+
"application/vnd.yamaha.smaf-audio": ["saf"],
|
|
565
|
+
"application/vnd.yamaha.smaf-phrase": ["spf"],
|
|
566
|
+
"application/vnd.yellowriver-custom-menu": ["cmp"],
|
|
567
|
+
"application/vnd.zul": ["zir", "zirz"],
|
|
568
|
+
"application/vnd.zzazz.deck+xml": ["zaz"],
|
|
569
|
+
"application/voicexml+xml": ["vxml"],
|
|
570
|
+
"application/wasm": ["wasm"],
|
|
571
|
+
"application/widget": ["wgt"],
|
|
572
|
+
"application/winhlp": ["hlp"],
|
|
573
|
+
"application/wsdl+xml": ["wsdl"],
|
|
574
|
+
"application/wspolicy+xml": ["wspolicy"],
|
|
575
|
+
"application/x-7z-compressed": ["7z"],
|
|
576
|
+
"application/x-abiword": ["abw", "zabw", "abw.gz"],
|
|
577
|
+
"application/x-ace-compressed": ["ace"],
|
|
578
|
+
"application/x-apple-diskimage": [],
|
|
579
|
+
"application/x-arj": ["arj"],
|
|
580
|
+
"application/x-authorware-bin": ["aab", "x32", "u32", "vox"],
|
|
581
|
+
"application/x-authorware-map": ["aam"],
|
|
582
|
+
"application/x-authorware-seg": ["aas"],
|
|
583
|
+
"application/x-bcpio": ["bcpio"],
|
|
584
|
+
"application/x-bdoc": [],
|
|
585
|
+
"application/x-bittorrent": ["torrent"],
|
|
586
|
+
"application/x-blorb": ["blb", "blorb"],
|
|
587
|
+
"application/x-bzip": ["bz"],
|
|
588
|
+
"application/x-bzip2": ["bz2", "boz"],
|
|
589
|
+
"application/x-cbr": ["cbr", "cba", "cbt", "cbz", "cb7"],
|
|
590
|
+
"application/x-cdlink": ["vcd"],
|
|
591
|
+
"application/x-cfs-compressed": ["cfs"],
|
|
592
|
+
"application/x-chat": ["chat"],
|
|
593
|
+
"application/x-chess-pgn": ["pgn"],
|
|
594
|
+
"application/x-chrome-extension": ["crx"],
|
|
595
|
+
"application/x-cocoa": ["cco"],
|
|
596
|
+
"application/x-conference": ["nsc"],
|
|
597
|
+
"application/x-cpio": ["cpio"],
|
|
598
|
+
"application/x-csh": ["csh"],
|
|
599
|
+
"application/x-debian-package": ["udeb"],
|
|
600
|
+
"application/x-dgc-compressed": ["dgc"],
|
|
601
|
+
"application/x-director": ["dir", "dcr", "dxr", "cst", "cct", "cxt", "w3d", "fgd", "swa"],
|
|
602
|
+
"application/x-doom": ["wad"],
|
|
603
|
+
"application/x-dtbncx+xml": ["ncx"],
|
|
604
|
+
"application/x-dtbook+xml": ["dtb"],
|
|
605
|
+
"application/x-dtbresource+xml": ["res"],
|
|
606
|
+
"application/x-dvi": ["dvi"],
|
|
607
|
+
"application/x-envoy": ["evy"],
|
|
608
|
+
"application/x-eva": ["eva"],
|
|
609
|
+
"application/x-font-bdf": ["bdf"],
|
|
610
|
+
"application/x-font-ghostscript": ["gsf"],
|
|
611
|
+
"application/x-font-linux-psf": ["psf"],
|
|
612
|
+
"application/x-font-pcf": ["pcf"],
|
|
613
|
+
"application/x-font-snf": ["snf"],
|
|
614
|
+
"application/x-font-type1": ["pfa", "pfb", "pfm", "afm"],
|
|
615
|
+
"application/x-freearc": ["arc"],
|
|
616
|
+
"application/x-futuresplash": ["spl"],
|
|
617
|
+
"application/x-gca-compressed": ["gca"],
|
|
618
|
+
"application/x-glulx": ["ulx"],
|
|
619
|
+
"application/x-gnumeric": ["gnumeric"],
|
|
620
|
+
"application/x-gramps-xml": ["gramps"],
|
|
621
|
+
"application/x-gtar": ["gtar"],
|
|
622
|
+
"application/x-gzip": ["tgz"],
|
|
623
|
+
"application/x-hdf": ["hdf"],
|
|
624
|
+
"application/x-httpd-php": ["php"],
|
|
625
|
+
"application/x-install-instructions": ["install"],
|
|
626
|
+
"application/x-iso9660-image": ["isoimg", "cdr"],
|
|
627
|
+
"application/x-java-archive-diff": ["jardiff"],
|
|
628
|
+
"application/x-java-jnlp-file": ["jnlp"],
|
|
629
|
+
"application/x-killustrator": ["kil"],
|
|
630
|
+
"application/x-krita": ["kra", "krz"],
|
|
631
|
+
"application/x-latex": ["latex"],
|
|
632
|
+
"application/x-lua-bytecode": ["luac"],
|
|
633
|
+
"application/x-lzh-compressed": ["lzh", "lha"],
|
|
634
|
+
"application/x-makeself": ["run"],
|
|
635
|
+
"application/x-mie": ["mie"],
|
|
636
|
+
"application/x-mobipocket-ebook": ["prc", "mobi"],
|
|
637
|
+
"application/x-ms-application": ["application"],
|
|
638
|
+
"application/x-ms-shortcut": ["lnk"],
|
|
639
|
+
"application/x-ms-wmd": ["wmd"],
|
|
640
|
+
"application/x-ms-wmz": ["wmz"],
|
|
641
|
+
"application/x-ms-xbap": ["xbap"],
|
|
642
|
+
"application/x-msaccess": ["mdb"],
|
|
643
|
+
"application/x-msbinder": ["obd"],
|
|
644
|
+
"application/x-mscardfile": ["crd"],
|
|
645
|
+
"application/x-msclip": ["clp"],
|
|
646
|
+
"application/x-msdos-program": [],
|
|
647
|
+
"application/x-msdownload": ["com", "bat"],
|
|
648
|
+
"application/x-msmediaview": ["mvb", "m13", "m14"],
|
|
649
|
+
"application/x-msmetafile": ["wmf", "emf", "emz"],
|
|
650
|
+
"application/x-msmoney": ["mny"],
|
|
651
|
+
"application/x-mspublisher": ["pub"],
|
|
652
|
+
"application/x-msschedule": ["scd"],
|
|
653
|
+
"application/x-msterminal": ["trm"],
|
|
654
|
+
"application/x-mswrite": ["wri"],
|
|
655
|
+
"application/x-netcdf": ["nc", "cdf"],
|
|
656
|
+
"application/x-ns-proxy-autoconfig": ["pac"],
|
|
657
|
+
"application/x-nzb": ["nzb"],
|
|
658
|
+
"application/x-perl": ["pl", "pm"],
|
|
659
|
+
"application/x-pilot": [],
|
|
660
|
+
"application/x-pkcs12": ["p12", "pfx"],
|
|
661
|
+
"application/x-pkcs7-certificates": ["p7b", "spc"],
|
|
662
|
+
"application/x-pkcs7-certreqresp": ["p7r"],
|
|
663
|
+
"application/x-rar-compressed": ["rar"],
|
|
664
|
+
"application/x-redhat-package-manager": ["rpm", "rpa"],
|
|
665
|
+
"application/x-research-info-systems": ["ris"],
|
|
666
|
+
"application/x-sea": ["sea"],
|
|
667
|
+
"application/x-sh": ["sh"],
|
|
668
|
+
"application/x-shar": ["shar"],
|
|
669
|
+
"application/x-shockwave-flash": ["swf"],
|
|
670
|
+
"application/x-silverlight-app": ["xap"],
|
|
671
|
+
"application/x-sql": ["sql"],
|
|
672
|
+
"application/x-stuffit": ["sit"],
|
|
673
|
+
"application/x-stuffitx": ["sitx"],
|
|
674
|
+
"application/x-subrip": ["srt"],
|
|
675
|
+
"application/x-sv4cpio": ["sv4cpio"],
|
|
676
|
+
"application/x-sv4crc": ["sv4crc"],
|
|
677
|
+
"application/x-t3vm-image": ["t3"],
|
|
678
|
+
"application/x-tads": ["gam"],
|
|
679
|
+
"application/x-tar": ["tar"],
|
|
680
|
+
"application/x-tcl": ["tcl", "tk"],
|
|
681
|
+
"application/x-tex": ["tex"],
|
|
682
|
+
"application/x-tex-tfm": ["tfm"],
|
|
683
|
+
"application/x-texinfo": ["texinfo", "texi"],
|
|
684
|
+
"application/x-tgif": ["obj"],
|
|
685
|
+
"application/x-ustar": ["ustar"],
|
|
686
|
+
"application/x-virtualbox-hdd": ["hdd"],
|
|
687
|
+
"application/x-virtualbox-ova": ["ova"],
|
|
688
|
+
"application/x-virtualbox-ovf": ["ovf"],
|
|
689
|
+
"application/x-virtualbox-vbox": ["vbox"],
|
|
690
|
+
"application/x-virtualbox-vbox-extpack": ["vbox-extpack"],
|
|
691
|
+
"application/x-virtualbox-vdi": ["vdi"],
|
|
692
|
+
"application/x-virtualbox-vhd": ["vhd"],
|
|
693
|
+
"application/x-virtualbox-vmdk": ["vmdk"],
|
|
694
|
+
"application/x-wais-source": ["src"],
|
|
695
|
+
"application/x-web-app-manifest+json": ["webapp"],
|
|
696
|
+
"application/x-x509-ca-cert": ["der", "crt", "pem"],
|
|
697
|
+
"application/x-xfig": ["fig"],
|
|
698
|
+
"application/x-xliff+xml": ["xlf"],
|
|
699
|
+
"application/x-xpinstall": ["xpi"],
|
|
700
|
+
"application/x-xz": ["xz"],
|
|
701
|
+
"application/x-zmachine": ["z1", "z2", "z3", "z4", "z5", "z6", "z7", "z8"],
|
|
702
|
+
"application/xaml+xml": ["xaml"],
|
|
703
|
+
"application/xcap-diff+xml": ["xdf"],
|
|
704
|
+
"application/xenc+xml": ["xenc"],
|
|
705
|
+
"application/xhtml+xml": ["xhtml", "xht"],
|
|
706
|
+
"application/xml": ["xml", "xsl", "xsd", "rng", "xpdl"],
|
|
707
|
+
"application/xml-dtd": ["dtd"],
|
|
708
|
+
"application/xop+xml": ["xop"],
|
|
709
|
+
"application/xproc+xml": ["xpl"],
|
|
710
|
+
"application/xslt+xml": ["xslt"],
|
|
711
|
+
"application/xspf+xml": ["xspf"],
|
|
712
|
+
"application/xv+xml": ["mxml", "xhvml", "xvml", "xvm"],
|
|
713
|
+
"application/yang": ["yang"],
|
|
714
|
+
"application/yin+xml": ["yin"],
|
|
715
|
+
"application/zip": ["zip"],
|
|
716
|
+
"audio/3gpp": [],
|
|
717
|
+
"audio/aacp": ["aacp"],
|
|
718
|
+
"audio/adpcm": ["adp"],
|
|
719
|
+
"audio/aiff": ["aff"],
|
|
720
|
+
"audio/basic": ["au", "snd"],
|
|
721
|
+
"audio/midi": ["mid", "midi", "kar", "rmi"],
|
|
722
|
+
"audio/mp3": [],
|
|
723
|
+
"audio/mp4": ["m4a", "mp4a", "m4b", "m4r", "3ga", "3gpa", "3gpp2", "3gp2"],
|
|
724
|
+
"audio/mpeg": ["mpga", "mp2", "mp2a", "mp3", "m2a", "m3a"],
|
|
725
|
+
"audio/ogg": ["oga", "ogg", "spx"],
|
|
726
|
+
"audio/opus": ["opus"],
|
|
727
|
+
"audio/s3m": ["s3m"],
|
|
728
|
+
"audio/silk": ["sil"],
|
|
729
|
+
"audio/vnd.dece.audio": ["uva", "uvva"],
|
|
730
|
+
"audio/vnd.digital-winds": ["eol"],
|
|
731
|
+
"audio/vnd.dra": ["dra"],
|
|
732
|
+
"audio/vnd.dts": ["dts"],
|
|
733
|
+
"audio/vnd.dts.hd": ["dtshd"],
|
|
734
|
+
"audio/vnd.lucent.voice": ["lvp"],
|
|
735
|
+
"audio/vnd.ms-playready.media.pya": ["pya"],
|
|
736
|
+
"audio/vnd.nuera.ecelp4800": ["ecelp4800"],
|
|
737
|
+
"audio/vnd.nuera.ecelp7470": ["ecelp7470"],
|
|
738
|
+
"audio/vnd.nuera.ecelp9600": ["ecelp9600"],
|
|
739
|
+
"audio/vnd.rip": ["rip"],
|
|
740
|
+
"audio/wav": ["wav"],
|
|
741
|
+
"audio/wave": [],
|
|
742
|
+
"audio/webm": ["weba"],
|
|
743
|
+
"audio/x-aac": ["aac"],
|
|
744
|
+
"audio/x-aiff": ["aif", "aiff", "aifc"],
|
|
745
|
+
"audio/x-caf": ["caf"],
|
|
746
|
+
"audio/x-flac": ["flac"],
|
|
747
|
+
"audio/x-m4a": [],
|
|
748
|
+
"audio/x-matroska": ["mka"],
|
|
749
|
+
"audio/x-mpegurl": ["m3u"],
|
|
750
|
+
"audio/x-ms-wax": ["wax"],
|
|
751
|
+
"audio/x-ms-wma": ["wma"],
|
|
752
|
+
"audio/x-pn-realaudio": ["ram", "ra"],
|
|
753
|
+
"audio/x-pn-realaudio-plugin": ["rmp"],
|
|
754
|
+
"audio/x-realaudio": [],
|
|
755
|
+
"audio/x-wav": [],
|
|
756
|
+
"audio/xm": ["xm"],
|
|
757
|
+
"chemical/x-cdx": ["cdx"],
|
|
758
|
+
"chemical/x-cif": ["cif"],
|
|
759
|
+
"chemical/x-cmdf": ["cmdf"],
|
|
760
|
+
"chemical/x-cml": ["cml"],
|
|
761
|
+
"chemical/x-csml": ["csml"],
|
|
762
|
+
"chemical/x-xyz": ["xyz"],
|
|
763
|
+
"font/collection": ["ttc"],
|
|
764
|
+
"font/otf": ["otf"],
|
|
765
|
+
"font/ttf": ["ttf"],
|
|
766
|
+
"font/woff": ["woff"],
|
|
767
|
+
"font/woff2": ["woff2"],
|
|
768
|
+
gcode: ["gcode"],
|
|
769
|
+
"image/apng": ["apng"],
|
|
770
|
+
"image/avif": ["avif"],
|
|
771
|
+
"image/avif-sequence": ["avifs"],
|
|
772
|
+
"image/bmp": ["bmp"],
|
|
773
|
+
"image/cgm": ["cgm"],
|
|
774
|
+
"image/g3fax": ["g3"],
|
|
775
|
+
"image/gif": ["gif"],
|
|
776
|
+
"image/heic": ["heif", "heic"],
|
|
777
|
+
"image/ief": ["ief"],
|
|
778
|
+
"image/jp2": ["jp2", "jpg2"],
|
|
779
|
+
"image/jpeg": ["jpeg", "jpg", "jpe", "pjpg", "jfif", "jfif-tbnl", "jif"],
|
|
780
|
+
"image/jpm": ["jpm"],
|
|
781
|
+
"image/jpx": ["jpx", "jpf"],
|
|
782
|
+
"image/ktx": ["ktx"],
|
|
783
|
+
"image/pjpeg": ["jfi"],
|
|
784
|
+
"image/png": ["png"],
|
|
785
|
+
"image/prs.btif": ["btif"],
|
|
786
|
+
"image/sgi": ["sgi"],
|
|
787
|
+
"image/svg+xml": ["svg", "svgz"],
|
|
788
|
+
"image/tiff": ["tiff", "tif"],
|
|
789
|
+
"image/vnd.adobe.photoshop": ["psd"],
|
|
790
|
+
"image/vnd.dece.graphic": ["uvi", "uvvi", "uvg", "uvvg"],
|
|
791
|
+
"image/vnd.djvu": ["djvu", "djv"],
|
|
792
|
+
"image/vnd.dvb.subtitle": [],
|
|
793
|
+
"image/vnd.dwg": ["dwg"],
|
|
794
|
+
"image/vnd.dxf": ["dxf"],
|
|
795
|
+
"image/vnd.fastbidsheet": ["fbs"],
|
|
796
|
+
"image/vnd.fpx": ["fpx"],
|
|
797
|
+
"image/vnd.fst": ["fst"],
|
|
798
|
+
"image/vnd.fujixerox.edmics-mmr": ["mmr"],
|
|
799
|
+
"image/vnd.fujixerox.edmics-rlc": ["rlc"],
|
|
800
|
+
"image/vnd.ms-modi": ["mdi"],
|
|
801
|
+
"image/vnd.ms-photo": ["wdp"],
|
|
802
|
+
"image/vnd.net-fpx": ["npx"],
|
|
803
|
+
"image/vnd.wap.wbmp": ["wbmp"],
|
|
804
|
+
"image/vnd.xiff": ["xif"],
|
|
805
|
+
"image/webp": ["webp"],
|
|
806
|
+
"image/x-3ds": ["3ds"],
|
|
807
|
+
"image/x-adobe-dng": ["dng"],
|
|
808
|
+
"image/x-canon-cr2": ["cr2"],
|
|
809
|
+
"image/x-canon-crw": ["crw"],
|
|
810
|
+
"image/x-cmu-raster": ["ras"],
|
|
811
|
+
"image/x-cmx": ["cmx"],
|
|
812
|
+
"image/x-epson-erf": ["erf"],
|
|
813
|
+
"image/x-freehand": ["fh", "fhc", "fh4", "fh5", "fh7"],
|
|
814
|
+
"image/x-fuji-raf": ["raf"],
|
|
815
|
+
"image/x-icns": ["icns"],
|
|
816
|
+
"image/x-icon": ["ico"],
|
|
817
|
+
"image/x-jng": ["jng"],
|
|
818
|
+
"image/x-kodak-k25": ["k25"],
|
|
819
|
+
"image/x-kodak-kdc": ["kdc"],
|
|
820
|
+
"image/x-minolta-mrw": ["mrw"],
|
|
821
|
+
"image/x-mrsid-image": ["sid"],
|
|
822
|
+
"image/x-ms-bmp": [],
|
|
823
|
+
"image/x-nikon-nef": ["nef"],
|
|
824
|
+
"image/x-olympus-orf": ["orf"],
|
|
825
|
+
"image/x-panasonic-raw": ["raw", "rw2", "rwl"],
|
|
826
|
+
"image/x-pcx": ["pcx"],
|
|
827
|
+
"image/x-pentax-pef": ["pef", "ptx"],
|
|
828
|
+
"image/x-pict": ["pic", "pct"],
|
|
829
|
+
"image/x-portable-anymap": ["pnm"],
|
|
830
|
+
"image/x-portable-bitmap": ["pbm"],
|
|
831
|
+
"image/x-portable-graymap": ["pgm"],
|
|
832
|
+
"image/x-portable-pixmap": ["ppm"],
|
|
833
|
+
"image/x-rgb": ["rgb"],
|
|
834
|
+
"image/x-sigma-x3f": ["x3f"],
|
|
835
|
+
"image/x-sony-arw": ["arw"],
|
|
836
|
+
"image/x-sony-sr2": ["sr2"],
|
|
837
|
+
"image/x-sony-srf": ["srf"],
|
|
838
|
+
"image/x-tga": ["tga"],
|
|
839
|
+
"image/x-xbitmap": ["xbm"],
|
|
840
|
+
"image/x-xpixmap": ["xpm"],
|
|
841
|
+
"image/x-xwindowdump": ["xwd"],
|
|
842
|
+
"message/rfc822": ["eml", "mime", "mht", "mhtml", "nws"],
|
|
843
|
+
"model/gltf+json": ["gltf"],
|
|
844
|
+
"model/gltf-binary": ["glb"],
|
|
845
|
+
"model/iges": ["igs", "iges"],
|
|
846
|
+
"model/mesh": ["msh", "mesh", "silo"],
|
|
847
|
+
"model/vnd.collada+xml": ["dae"],
|
|
848
|
+
"model/vnd.dwf": ["dwf"],
|
|
849
|
+
"model/vnd.gdl": ["gdl"],
|
|
850
|
+
"model/vnd.gtw": ["gtw"],
|
|
851
|
+
"model/vnd.mts": ["mts"],
|
|
852
|
+
"model/vnd.vtu": ["vtu"],
|
|
853
|
+
"model/vrml": ["wrl", "vrml"],
|
|
854
|
+
"model/x3d+binary": ["x3db", "x3dbz"],
|
|
855
|
+
"model/x3d+vrml": ["x3dv", "x3dvz"],
|
|
856
|
+
"model/x3d+xml": ["x3d", "x3dz"],
|
|
857
|
+
"test/mimetype": ["test"],
|
|
858
|
+
"text/cache-manifest": ["appcache", "manifest"],
|
|
859
|
+
"text/calendar": ["ics", "ifb"],
|
|
860
|
+
"text/coffeescript": ["coffee", "litcoffee"],
|
|
861
|
+
"text/css": ["css"],
|
|
862
|
+
"text/csv": ["csv"],
|
|
863
|
+
"text/hjson": ["hjson"],
|
|
864
|
+
"text/html": ["html", "htm", "shtml"],
|
|
865
|
+
"text/jade": ["jade"],
|
|
866
|
+
"text/jsx": ["jsx"],
|
|
867
|
+
"text/less": ["less"],
|
|
868
|
+
"text/markdown": ["markdown", "md", "mdown", "markdn"],
|
|
869
|
+
"text/mathml": ["mml"],
|
|
870
|
+
"text/n3": ["n3"],
|
|
871
|
+
"text/plain": ["txt", "text", "conf", "def", "list", "log", "in", "ini", "diff", "ksh"],
|
|
872
|
+
"text/prs.lines.tag": ["dsc"],
|
|
873
|
+
"text/richtext": ["rtx"],
|
|
874
|
+
"text/rtf": [],
|
|
875
|
+
"text/sgml": ["sgml", "sgm"],
|
|
876
|
+
"text/slim": ["slim", "slm"],
|
|
877
|
+
"text/stylus": ["stylus", "styl"],
|
|
878
|
+
"text/tab-separated-values": ["tsv"],
|
|
879
|
+
"text/troff": ["t", "tr", "roff", "man", "me", "ms"],
|
|
880
|
+
"text/turtle": ["ttl"],
|
|
881
|
+
"text/uri-list": ["uri", "uris", "urls"],
|
|
882
|
+
"text/vcard": ["vcard"],
|
|
883
|
+
"text/vnd.curl": ["curl"],
|
|
884
|
+
"text/vnd.curl.dcurl": ["dcurl"],
|
|
885
|
+
"text/vnd.curl.mcurl": ["mcurl"],
|
|
886
|
+
"text/vnd.curl.scurl": ["scurl"],
|
|
887
|
+
"text/vnd.dvb.subtitle": ["sub"],
|
|
888
|
+
"text/vnd.fly": ["fly"],
|
|
889
|
+
"text/vnd.fmi.flexstor": ["flx"],
|
|
890
|
+
"text/vnd.graphviz": ["gv"],
|
|
891
|
+
"text/vnd.in3d.3dml": ["3dml"],
|
|
892
|
+
"text/vnd.in3d.spot": ["spot"],
|
|
893
|
+
"text/vnd.sun.j2me.app-descriptor": ["jad"],
|
|
894
|
+
"text/vnd.wap.si": ["si"],
|
|
895
|
+
"text/vnd.wap.sl": ["sl"],
|
|
896
|
+
"text/vnd.wap.wml": ["wml"],
|
|
897
|
+
"text/vnd.wap.wmlscript": ["wmls"],
|
|
898
|
+
"text/vtt": ["vtt"],
|
|
899
|
+
"text/x-asm": ["s", "asm"],
|
|
900
|
+
"text/x-c": ["c", "cc", "cxx", "cpp", "h", "hh", "dic"],
|
|
901
|
+
"text/x-component": ["htc"],
|
|
902
|
+
"text/x-fortran": ["f", "for", "f77", "f90"],
|
|
903
|
+
"text/x-handlebars-template": ["hbs"],
|
|
904
|
+
"text/x-java-source": ["java"],
|
|
905
|
+
"text/x-lua": ["lua"],
|
|
906
|
+
"text/x-markdown": ["mkd"],
|
|
907
|
+
"text/x-nfo": ["nfo"],
|
|
908
|
+
"text/x-opml": ["opml"],
|
|
909
|
+
"text/x-org": [],
|
|
910
|
+
"text/x-pascal": ["p", "pas", "pp", "inc"],
|
|
911
|
+
"text/x-processing": ["pde"],
|
|
912
|
+
"text/x-python": ["py", "pyc", "pyo", "pyd", "whl"],
|
|
913
|
+
"text/x-sass": ["sass"],
|
|
914
|
+
"text/x-scss": ["scss"],
|
|
915
|
+
"text/x-setext": ["etx"],
|
|
916
|
+
"text/x-sfv": ["sfv"],
|
|
917
|
+
"text/x-suse-ymp": ["ymp"],
|
|
918
|
+
"text/x-uuencode": ["uu"],
|
|
919
|
+
"text/x-vcalendar": ["vcs"],
|
|
920
|
+
"text/x-vcard": ["vcf"],
|
|
921
|
+
"text/xml": [],
|
|
922
|
+
"text/yaml": ["yaml", "yml"],
|
|
923
|
+
"video/3gpp": ["3gp", "3gpp"],
|
|
924
|
+
"video/3gpp2": ["3g2"],
|
|
925
|
+
"video/h261": ["h261"],
|
|
926
|
+
"video/h263": ["h263"],
|
|
927
|
+
"video/h264": ["h264"],
|
|
928
|
+
"video/jpeg": ["jpgv"],
|
|
929
|
+
"video/jpm": ["jpgm"],
|
|
930
|
+
"video/mj2": ["mj2", "mjp2"],
|
|
931
|
+
"video/mp2t": ["ts"],
|
|
932
|
+
"video/mp4": ["mp4", "mp4v", "mpg4"],
|
|
933
|
+
"video/mpeg": ["mpeg", "mpg", "mpe", "m1v", "m2v", "mpa"],
|
|
934
|
+
"video/ogg": ["ogv"],
|
|
935
|
+
"video/quicktime": ["qt", "mov"],
|
|
936
|
+
"video/vnd.dece.hd": ["uvh", "uvvh"],
|
|
937
|
+
"video/vnd.dece.mobile": ["uvm", "uvvm"],
|
|
938
|
+
"video/vnd.dece.pd": ["uvp", "uvvp"],
|
|
939
|
+
"video/vnd.dece.sd": ["uvs", "uvvs"],
|
|
940
|
+
"video/vnd.dece.video": ["uvv", "uvvv"],
|
|
941
|
+
"video/vnd.dvb.file": ["dvb"],
|
|
942
|
+
"video/vnd.fvt": ["fvt"],
|
|
943
|
+
"video/vnd.mpegurl": ["mxu", "m4u"],
|
|
944
|
+
"video/vnd.ms-playready.media.pyv": ["pyv"],
|
|
945
|
+
"video/vnd.uvvu.mp4": ["uvu", "uvvu"],
|
|
946
|
+
"video/vnd.vivo": ["viv"],
|
|
947
|
+
"video/webm": ["webm"],
|
|
948
|
+
"video/x-f4v": ["f4v"],
|
|
949
|
+
"video/x-fli": ["fli"],
|
|
950
|
+
"video/x-flv": ["flv"],
|
|
951
|
+
"video/x-m4v": ["m4v"],
|
|
952
|
+
"video/x-matroska": ["mkv", "mk3d", "mks"],
|
|
953
|
+
"video/x-mng": ["mng"],
|
|
954
|
+
"video/x-ms-asf": ["asf", "asx"],
|
|
955
|
+
"video/x-ms-vob": ["vob"],
|
|
956
|
+
"video/x-ms-wm": ["wm"],
|
|
957
|
+
"video/x-ms-wmv": ["wmv"],
|
|
958
|
+
"video/x-ms-wmx": ["wmx"],
|
|
959
|
+
"video/x-ms-wvx": ["wvx"],
|
|
960
|
+
"video/x-msvideo": ["avi"],
|
|
961
|
+
"video/x-sgi-movie": ["movie"],
|
|
962
|
+
"video/x-smv": ["smv"],
|
|
963
|
+
"x-conference/x-cooltalk": ["ice"]
|
|
79
964
|
};
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
function splitBuffer(buffer, delimiter) {
|
|
92
|
-
const result = [];
|
|
93
|
-
let start = 0;
|
|
94
|
-
let index;
|
|
95
|
-
while ((index = buffer.indexOf(delimiter, start)) !== -1) {
|
|
96
|
-
result.push(buffer.slice(start, index));
|
|
97
|
-
start = index + delimiter.length;
|
|
965
|
+
|
|
966
|
+
// src/static.ts
|
|
967
|
+
var mime_types = /* @__PURE__ */ new Map();
|
|
968
|
+
var mime_extensions = /* @__PURE__ */ new Map();
|
|
969
|
+
function mime_define(map) {
|
|
970
|
+
for (const type in map) {
|
|
971
|
+
const exts = map[type];
|
|
972
|
+
for (const ext of exts)
|
|
973
|
+
mime_types.set(ext, type);
|
|
974
|
+
if (!mime_extensions.has(type))
|
|
975
|
+
mime_extensions.set(type, exts[0]);
|
|
98
976
|
}
|
|
99
|
-
result.push(buffer.slice(start));
|
|
100
|
-
return result;
|
|
101
|
-
}
|
|
102
|
-
function extractCharset(contentType) {
|
|
103
|
-
const param = contentType.split(";").map((s) => s.replace(/^\s+|\s+$/g, "")).find((s) => s.startsWith("charset="));
|
|
104
|
-
return param ? param.substring("charset=".length) : "utf8";
|
|
105
|
-
}
|
|
106
|
-
function readBody(req, res, opts, mimetype, next, callback) {
|
|
107
|
-
const length = parseInt(req.headers["content-length"] ?? "0", 10);
|
|
108
|
-
const isChunked = req.headers["transfer-encoding"]?.split(",").map((v) => v.trim()).some((v) => v.toLowerCase() === "chunked") ?? false;
|
|
109
|
-
if (!isChunked && (!length || length === 0)) return next();
|
|
110
|
-
const maxLength = readSize(opts.limit) || 102400;
|
|
111
|
-
if (!isChunked && length > maxLength)
|
|
112
|
-
return void res.status(413).send("Content Too Large");
|
|
113
|
-
const encoding = req.headers["content-encoding"];
|
|
114
|
-
if (encoding && (opts.inflate === false || !DECOMPRESS_ALGO[encoding]))
|
|
115
|
-
return void res.status(415).send("Unsupported Media Type: Wrong Content-Encoding");
|
|
116
|
-
const decompress = (encoding ? DECOMPRESS_ALGO[encoding] : void 0) ?? ((d, c) => c(null, d));
|
|
117
|
-
const contentType = req.headers["content-type"] ?? "";
|
|
118
|
-
if (mimetype && contentType.split(";")[0].trim() !== mimetype)
|
|
119
|
-
return void res.status(415).send("Unsupported Media Type: Wrong Content-Type");
|
|
120
|
-
let data = Buffer.alloc(0);
|
|
121
|
-
req.on("data", (chunk) => {
|
|
122
|
-
if (data === null) return;
|
|
123
|
-
const next_ = Buffer.concat([data, chunk]);
|
|
124
|
-
if (next_.length > maxLength) {
|
|
125
|
-
data = null;
|
|
126
|
-
res.status(413).send("Content Too Large");
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
data = next_;
|
|
130
|
-
});
|
|
131
|
-
req.on("end", () => {
|
|
132
|
-
if (data === null) return;
|
|
133
|
-
decompress(data, (err, decompressed) => {
|
|
134
|
-
if (err) return void res.status(500).send(err.message);
|
|
135
|
-
callback(contentType, decompressed);
|
|
136
|
-
});
|
|
137
|
-
});
|
|
138
977
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
978
|
+
var mime = {
|
|
979
|
+
lookup: (path2, fallback = null) => mime_types.get(path2.replace(/^.*[./\\]/, "").toLowerCase()) ?? fallback ?? "application/octet-stream",
|
|
980
|
+
charsets: (mimeType) => /^text\/|^application\/(javascript|json)/.test(mimeType) ? "UTF-8" : null
|
|
981
|
+
};
|
|
982
|
+
mime_define(mimetypes_default);
|
|
983
|
+
var UP_PATH_REGEXP = /(?:^|[\\/])\.\.(?:[\\/]|$)/;
|
|
984
|
+
var CONTROL_CHAR_REGEXP = /[\x00-\x1f]/;
|
|
985
|
+
var DEFAULT_OPTIONS = {
|
|
986
|
+
headers: {
|
|
987
|
+
"Content-Security-Policy": "default-src 'self'",
|
|
988
|
+
"X-Content-Type-Options": "nosniff"
|
|
989
|
+
},
|
|
990
|
+
fallthrough: false,
|
|
991
|
+
maxage: 0,
|
|
992
|
+
immutable: false,
|
|
993
|
+
etag: true,
|
|
994
|
+
lastModified: true,
|
|
995
|
+
contentType: null,
|
|
996
|
+
dotfiles: "hide",
|
|
997
|
+
redirect: true,
|
|
998
|
+
indexOf: false
|
|
999
|
+
};
|
|
1000
|
+
var HTTP = {
|
|
1001
|
+
/** 304 Not Modified — sent for conditional GET cache hits. */
|
|
1002
|
+
NOT_MODIFIED: (res, opts) => {
|
|
1003
|
+
res.status(304, opts.headers).end();
|
|
1004
|
+
},
|
|
1005
|
+
/** 400 Bad Request — sent when the URL path contains malformed percent-encoding. */
|
|
1006
|
+
BAD_REQUEST: (res, opts) => res.status(400, opts.headers).send("Bad Request"),
|
|
1007
|
+
/** 403 Forbidden — sent for denied dot-files or path traversal attempts. */
|
|
1008
|
+
FORBIDDEN: (res, opts) => res.status(403, opts.headers).send("Forbidden"),
|
|
1009
|
+
/** 404 Not Found — sent when the requested file does not exist. */
|
|
1010
|
+
NOT_FOUND: (res, opts) => res.status(404, opts.headers).send("Not Found"),
|
|
1011
|
+
/**
|
|
1012
|
+
* 405 Method Not Allowed — sent when the HTTP method is neither GET nor
|
|
1013
|
+
* HEAD and {@link ResolvedOptions.fallthrough} is `false`.
|
|
1014
|
+
* Always includes an `Allow: GET, HEAD` header per RFC 7231 §6.5.5.
|
|
1015
|
+
*/
|
|
1016
|
+
NOT_ALLOWED: (res, opts) => {
|
|
1017
|
+
res.status(405, { ...opts.headers, Allow: "GET, HEAD" }).end();
|
|
1018
|
+
},
|
|
1019
|
+
/** 412 Precondition Failed — sent when `If-Match` / `If-Unmodified-Since` fails. */
|
|
1020
|
+
PRECONDITION_FAILS: (res, opts) => res.status(412, opts.headers).send("Precondition Failed"),
|
|
1021
|
+
/** 500 Internal Server Error — sent on unexpected filesystem or stream errors. */
|
|
1022
|
+
INTERNAL_ERROR: (res, opts, err) => res.status(500, opts.headers).send(`Internal error: ${err}`)
|
|
1023
|
+
};
|
|
1024
|
+
function destroyReadStream(stream) {
|
|
1025
|
+
stream.destroy();
|
|
1026
|
+
if (typeof stream.close === "function") {
|
|
1027
|
+
stream.on("open", () => {
|
|
1028
|
+
if (typeof stream.fd === "number")
|
|
1029
|
+
stream.close();
|
|
171
1030
|
});
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
function readBodyAsPlainText(req, res, next, contentType, data) {
|
|
175
|
-
const charset = extractCharset(contentType);
|
|
176
|
-
try {
|
|
177
|
-
req.body = data.toString(charset);
|
|
178
|
-
next();
|
|
179
|
-
} catch (ex) {
|
|
180
|
-
res.status(500).send(ex.message);
|
|
181
1031
|
}
|
|
182
1032
|
}
|
|
183
|
-
function
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
opts.reviver ?? void 0
|
|
189
|
-
);
|
|
190
|
-
if (opts.strict && (typeof parsed !== "object" || parsed === null)) {
|
|
191
|
-
return void res.status(400).send("Bad Request: JSON body must be an object or array");
|
|
192
|
-
}
|
|
193
|
-
req.body = parsed;
|
|
194
|
-
next();
|
|
195
|
-
} catch (ex) {
|
|
196
|
-
res.status(400).send("Bad Request: " + ex.message);
|
|
1033
|
+
function removeContentHeaders(res) {
|
|
1034
|
+
const keys = Object.keys(res.getHeaders() ?? {});
|
|
1035
|
+
for (const key of keys) {
|
|
1036
|
+
if (key.startsWith("content-") && key !== "content-location")
|
|
1037
|
+
res.removeHeader(key);
|
|
197
1038
|
}
|
|
198
1039
|
}
|
|
199
|
-
function
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
for (
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
for (const line of headerSection.split("\r\n")) {
|
|
218
|
-
if (!line) continue;
|
|
219
|
-
const colonIdx = line.indexOf(":");
|
|
220
|
-
if (colonIdx === -1) continue;
|
|
221
|
-
const key = line.substring(0, colonIdx).replace(/^\s+|\s+$/g, "").toLowerCase();
|
|
222
|
-
const value = line.substring(colonIdx + 1).replace(/^\s+|\s+$/g, "");
|
|
223
|
-
headers[key] = value;
|
|
1040
|
+
function createETag(stat) {
|
|
1041
|
+
const mtime = stat.mtime.getTime().toString(16);
|
|
1042
|
+
const size = stat.size.toString(16);
|
|
1043
|
+
return `W/"${size}-${mtime}"`;
|
|
1044
|
+
}
|
|
1045
|
+
function parseTokenList(str) {
|
|
1046
|
+
const list = [];
|
|
1047
|
+
let start = 0;
|
|
1048
|
+
let end = 0;
|
|
1049
|
+
for (let i = 0, len = str.length; i < len; i++) {
|
|
1050
|
+
const ch = str.charCodeAt(i);
|
|
1051
|
+
if (ch === 32) {
|
|
1052
|
+
if (start === end) start = end = i + 1;
|
|
1053
|
+
} else if (ch === 44) {
|
|
1054
|
+
list.push(str.substring(start, end));
|
|
1055
|
+
start = end = i + 1;
|
|
1056
|
+
} else {
|
|
1057
|
+
end = i + 1;
|
|
224
1058
|
}
|
|
225
|
-
parts.push({ headers, content });
|
|
226
1059
|
}
|
|
227
|
-
|
|
1060
|
+
list.push(str.substring(start, end));
|
|
1061
|
+
return list;
|
|
228
1062
|
}
|
|
229
|
-
function
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
next();
|
|
233
|
-
} catch (ex) {
|
|
234
|
-
const status = ex.status ?? 500;
|
|
235
|
-
res.status(status).send(ex.message ?? String(ex));
|
|
236
|
-
}
|
|
1063
|
+
function parseHttpDate(date) {
|
|
1064
|
+
const timestamp = date ? Date.parse(date) : NaN;
|
|
1065
|
+
return typeof timestamp === "number" ? timestamp : NaN;
|
|
237
1066
|
}
|
|
238
|
-
function
|
|
239
|
-
|
|
240
|
-
try {
|
|
241
|
-
const params = new URLSearchParams(data.toString(charset));
|
|
242
|
-
const result = {};
|
|
243
|
-
for (const [key, value] of params.entries()) {
|
|
244
|
-
const existing = result[key];
|
|
245
|
-
if (existing === void 0) {
|
|
246
|
-
result[key] = value;
|
|
247
|
-
} else if (Array.isArray(existing)) {
|
|
248
|
-
existing.push(value);
|
|
249
|
-
} else {
|
|
250
|
-
result[key] = [existing, value];
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
req.body = result;
|
|
254
|
-
next();
|
|
255
|
-
} catch (ex) {
|
|
256
|
-
res.status(400).send("Bad Request: " + ex.message);
|
|
257
|
-
}
|
|
1067
|
+
function htmlEscape(str) {
|
|
1068
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
258
1069
|
}
|
|
259
|
-
|
|
260
|
-
"
|
|
261
|
-
"application/json": (req, res, next, opts, ct, data) => readBodyAsJson(req, res, next, opts, ct, data),
|
|
262
|
-
"application/x-www-form-urlencoded": (req, res, next, _opts, ct, data) => readBodyAsFormEncoded(req, res, next, ct, data),
|
|
263
|
-
"text/plain": (req, res, next, _opts, ct, data) => readBodyAsPlainText(req, res, next, ct, data)
|
|
264
|
-
};
|
|
265
|
-
function json(opts) {
|
|
266
|
-
const resolved = {
|
|
267
|
-
inflate: true,
|
|
268
|
-
limit: "100kb",
|
|
269
|
-
reviver: null,
|
|
270
|
-
strict: true,
|
|
271
|
-
...opts
|
|
272
|
-
};
|
|
273
|
-
return (req, res, next) => {
|
|
274
|
-
res.json = (data) => {
|
|
275
|
-
res.write(JSON.stringify(data));
|
|
276
|
-
res.end();
|
|
277
|
-
};
|
|
278
|
-
readBody(req, res, resolved, "application/json", next, (contentType, body) => {
|
|
279
|
-
readBodyAsJson(req, res, next, resolved, contentType, body);
|
|
280
|
-
});
|
|
281
|
-
};
|
|
282
|
-
}
|
|
283
|
-
function formData(opts) {
|
|
284
|
-
const resolved = {
|
|
285
|
-
inflate: true,
|
|
286
|
-
limit: "100kb",
|
|
287
|
-
reviver: null,
|
|
288
|
-
strict: true,
|
|
289
|
-
...opts
|
|
290
|
-
};
|
|
291
|
-
return (req, res, next) => {
|
|
292
|
-
readBody(req, res, resolved, "multipart/form-data", next, (contentType, body) => {
|
|
293
|
-
readBodyAsFormData(req, res, next, contentType, body);
|
|
294
|
-
});
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
function formEncoded(opts) {
|
|
298
|
-
const resolved = {
|
|
299
|
-
inflate: true,
|
|
300
|
-
limit: "100kb",
|
|
301
|
-
reviver: null,
|
|
302
|
-
strict: true,
|
|
303
|
-
...opts
|
|
304
|
-
};
|
|
305
|
-
return (req, res, next) => {
|
|
306
|
-
readBody(req, res, resolved, "application/x-www-form-urlencoded", next, (contentType, body) => {
|
|
307
|
-
readBodyAsFormEncoded(req, res, next, contentType, body);
|
|
308
|
-
});
|
|
309
|
-
};
|
|
310
|
-
}
|
|
311
|
-
function parseBody(opts) {
|
|
312
|
-
const resolved = {
|
|
313
|
-
inflate: true,
|
|
314
|
-
limit: "100kb",
|
|
315
|
-
reviver: null,
|
|
316
|
-
strict: true,
|
|
317
|
-
...opts
|
|
318
|
-
};
|
|
319
|
-
return (req, res, next) => {
|
|
320
|
-
readBody(req, res, resolved, null, next, (contentType, body) => {
|
|
321
|
-
const mimetype = contentType.split(";")[0].trim();
|
|
322
|
-
if (!BODY_READERS[mimetype])
|
|
323
|
-
return res.status(415).send("Unsupported Media Type");
|
|
324
|
-
BODY_READERS[mimetype](req, res, next, resolved, contentType, body);
|
|
325
|
-
});
|
|
326
|
-
};
|
|
327
|
-
}
|
|
328
|
-
async function* streamFormData(req, opts) {
|
|
329
|
-
const maxSize = (opts?.limit !== void 0 ? readSize(opts.limit) : 0) || 102400;
|
|
330
|
-
const chunks = [];
|
|
331
|
-
let totalSize = 0;
|
|
332
|
-
for await (const chunk of req) {
|
|
333
|
-
totalSize += chunk.length;
|
|
334
|
-
if (totalSize > maxSize) throw { status: 413, message: "Content Too Large" };
|
|
335
|
-
chunks.push(chunk);
|
|
336
|
-
}
|
|
337
|
-
const body = Buffer.concat(chunks);
|
|
338
|
-
const contentType = req.headers["content-type"] ?? "";
|
|
339
|
-
const parts = parseMultipartBody(contentType, body);
|
|
340
|
-
for (const part of parts) {
|
|
341
|
-
yield { headers: part.headers, stream: import_stream.Readable.from(part.content) };
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
var STATUS_COLORS = [
|
|
345
|
-
"\x1B[0m",
|
|
346
|
-
// 0 — fallback / unknown
|
|
347
|
-
"\x1B[33m",
|
|
348
|
-
// 1xx — informational (yellow)
|
|
349
|
-
"\x1B[32m",
|
|
350
|
-
// 2xx — success (green)
|
|
351
|
-
"\x1B[33m",
|
|
352
|
-
// 3xx — redirection (yellow)
|
|
353
|
-
"\x1B[31m",
|
|
354
|
-
// 4xx — client error (red)
|
|
355
|
-
"\x1B[91m"
|
|
356
|
-
// 5xx — server error (bright red)
|
|
357
|
-
];
|
|
358
|
-
var ANSI_RESET = "\x1B[0m";
|
|
359
|
-
function logger(opts) {
|
|
360
|
-
const options = opts ?? {};
|
|
361
|
-
const log = options.logger ?? console.log;
|
|
362
|
-
const formatter = new Intl.DateTimeFormat(
|
|
363
|
-
options.locale ?? "en-GB",
|
|
364
|
-
options.dateFormat ?? {
|
|
365
|
-
month: "short",
|
|
366
|
-
day: "2-digit",
|
|
367
|
-
hour: "2-digit",
|
|
368
|
-
minute: "2-digit"
|
|
369
|
-
}
|
|
370
|
-
);
|
|
371
|
-
return (req, res, next) => {
|
|
372
|
-
const timestamp = formatter.format(/* @__PURE__ */ new Date());
|
|
373
|
-
const requestPath = req.path ?? req.url ?? "/";
|
|
374
|
-
const ip = req.ip ?? "";
|
|
375
|
-
const user = options.user?.(req) ?? "-";
|
|
376
|
-
const receivedAt = Date.now();
|
|
377
|
-
const tracker = options.track === true ? setTimeout(() => {
|
|
378
|
-
log(`${timestamp} LOST ${req.method} ${requestPath} ${ip} <${user}>`);
|
|
379
|
-
}, options.trackTimeout ?? 3e4) : null;
|
|
380
|
-
res.on("finish", () => {
|
|
381
|
-
if (tracker !== null) clearTimeout(tracker);
|
|
382
|
-
const host = req.headers.host;
|
|
383
|
-
const elapsed = Date.now() - receivedAt;
|
|
384
|
-
const statusClass = Math.floor(res.statusCode / 100);
|
|
385
|
-
const colour = STATUS_COLORS[statusClass] ?? STATUS_COLORS[0];
|
|
386
|
-
const statusStr = `${colour}${res.statusCode}${ANSI_RESET}`;
|
|
387
|
-
const contentLen = res.getHeader("content-length") ?? "-";
|
|
388
|
-
if (options.json === true)
|
|
389
|
-
log({
|
|
390
|
-
timestamp,
|
|
391
|
-
status: res.statusCode,
|
|
392
|
-
method: req.method,
|
|
393
|
-
path: requestPath,
|
|
394
|
-
ip,
|
|
395
|
-
user,
|
|
396
|
-
elapsed,
|
|
397
|
-
host,
|
|
398
|
-
length: contentLen
|
|
399
|
-
});
|
|
400
|
-
else
|
|
401
|
-
log(`${timestamp} ${statusStr} ${req.method} ${requestPath} ${ip} <${user}> ${elapsed}ms (${contentLen})`);
|
|
402
|
-
});
|
|
403
|
-
next();
|
|
404
|
-
};
|
|
405
|
-
}
|
|
406
|
-
function cors(opts) {
|
|
407
|
-
const options = {
|
|
408
|
-
origin: opts?.origin || "*",
|
|
409
|
-
allowHeaders: opts?.allowHeaders || "Accept, Content-Type, Authorization",
|
|
410
|
-
allowMethods: opts?.allowMethods || "GET,HEAD,PUT,PATCH,POST,DELETE",
|
|
411
|
-
allowCredentials: opts?.allowCredentials,
|
|
412
|
-
maxAge: opts?.maxAge,
|
|
413
|
-
vary: opts?.vary,
|
|
414
|
-
optionsStatus: opts?.optionsStatus || 204,
|
|
415
|
-
preflight: opts?.preflight
|
|
416
|
-
};
|
|
417
|
-
return (req, res, next) => {
|
|
418
|
-
if (options.preflight && !options.preflight(req)) {
|
|
419
|
-
res.status(req.method == "OPTIONS" ? 403 : 400).end();
|
|
420
|
-
return;
|
|
421
|
-
}
|
|
422
|
-
if (req.headers.origin) {
|
|
423
|
-
res.setHeader("Access-Control-Allow-Origin", options.origin);
|
|
424
|
-
if (options.vary !== void 0)
|
|
425
|
-
res.setHeader("Vary", options.vary);
|
|
426
|
-
}
|
|
427
|
-
if (req.method == "OPTIONS") {
|
|
428
|
-
res.setHeader("Access-Control-Allow-Headers", options.allowHeaders);
|
|
429
|
-
res.setHeader("Access-Control-Allow-Methods", options.allowMethods);
|
|
430
|
-
if (options.allowCredentials !== void 0)
|
|
431
|
-
res.setHeader("Access-Control-Allow-Credentials", options.allowCredentials ? "true" : "false");
|
|
432
|
-
if (options.maxAge !== void 0)
|
|
433
|
-
res.setHeader("Access-Control-Max-Age", options.maxAge.toFixed(0));
|
|
434
|
-
res.status(options.optionsStatus).end();
|
|
435
|
-
return;
|
|
436
|
-
}
|
|
437
|
-
next();
|
|
438
|
-
};
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
// src/static.ts
|
|
442
|
-
var import_fs = __toESM(require("fs"), 1);
|
|
443
|
-
var import_path = __toESM(require("path"), 1);
|
|
444
|
-
|
|
445
|
-
// src/mimetypes.json
|
|
446
|
-
var mimetypes_default = { "application/andrew-inset": ["ez"], "application/applixware": ["aw"], "application/atom+xml": ["atom"], "application/atomcat+xml": ["atomcat"], "application/atomsvc+xml": ["atomsvc"], "application/bdoc": ["bdoc"], "application/ccxml+xml": ["ccxml"], "application/cdmi-capability": ["cdmia"], "application/cdmi-container": ["cdmic"], "application/cdmi-domain": ["cdmid"], "application/cdmi-object": ["cdmio"], "application/cdmi-queue": ["cdmiq"], "application/cu-seeme": ["cu"], "application/dash+xml": ["mpd"], "application/davmount+xml": ["davmount"], "application/docbook+xml": ["dbk"], "application/dssc+der": ["dssc"], "application/dssc+xml": ["xdssc"], "application/ecmascript": ["ecma"], "application/emma+xml": ["emma"], "application/epub+zip": ["epub"], "application/exi": ["exi"], "application/font-tdpfr": ["pfr"], "application/font-woff": [], "application/font-woff2": [], "application/geo+json": ["geojson"], "application/gml+xml": ["gml"], "application/gpx+xml": ["gpx"], "application/gxf": ["gxf"], "application/gzip": ["gz"], "application/hyperstudio": ["stk"], "application/inkml+xml": ["ink", "inkml"], "application/ipfix": ["ipfix"], "application/java-archive": ["jar", "war", "ear"], "application/java-serialized-object": ["ser"], "application/java-vm": ["class"], "application/javascript": ["js", "mjs"], "application/json": ["json", "map"], "application/json5": ["json5"], "application/jsonml+json": ["jsonml"], "application/ld+json": ["jsonld"], "application/lost+xml": ["lostxml"], "application/mac-binhex40": ["hqx"], "application/mac-compactpro": ["cpt"], "application/mads+xml": ["mads"], "application/manifest+json": ["webmanifest"], "application/marc": ["mrc"], "application/marcxml+xml": ["mrcx"], "application/mathematica": ["ma", "nb", "mb"], "application/mathml+xml": ["mathml"], "application/mbox": ["mbox"], "application/mediaservercontrol+xml": ["mscml"], "application/metalink+xml": ["metalink"], "application/metalink4+xml": ["meta4"], "application/mets+xml": ["mets"], "application/mods+xml": ["mods"], "application/mp21": ["m21", "mp21"], "application/mp4": ["mp4s", "m4p"], "application/msword": ["doc", "dot"], "application/mxf": ["mxf"], "application/octet-stream": ["bin", "dms", "lrf", "mar", "so", "dist", "distz", "pkg", "bpk", "dump", "elc", "deploy", "exe", "dll", "deb", "dmg", "iso", "img", "msi", "msp", "msm", "buffer"], "application/oda": ["oda"], "application/oebps-package+xml": ["opf"], "application/ogg": ["ogx"], "application/omdoc+xml": ["omdoc"], "application/onenote": ["onetoc", "onetoc2", "onetmp", "onepkg"], "application/oxps": ["oxps"], "application/patch-ops-error+xml": ["xer"], "application/pdf": ["pdf"], "application/pgp-encrypted": ["pgp"], "application/pgp-signature": ["asc", "sig"], "application/pics-rules": ["prf"], "application/pkcs10": ["p10"], "application/pkcs7-mime": ["p7m", "p7c"], "application/pkcs7-signature": ["p7s"], "application/pkcs8": ["p8"], "application/pkix-attr-cert": ["ac"], "application/pkix-cert": ["cer"], "application/pkix-crl": ["crl"], "application/pkix-pkipath": ["pkipath"], "application/pkixcmp": ["pki"], "application/pls+xml": ["pls"], "application/postscript": ["ai", "eps", "ps"], "application/prs.cww": ["cww"], "application/pskc+xml": ["pskcxml"], "application/raml+yaml": ["raml"], "application/rdf+xml": ["rdf"], "application/reginfo+xml": ["rif"], "application/relax-ng-compact-syntax": ["rnc"], "application/resource-lists+xml": ["rl"], "application/resource-lists-diff+xml": ["rld"], "application/rls-services+xml": ["rs"], "application/rpki-ghostbusters": ["gbr"], "application/rpki-manifest": ["mft"], "application/rpki-roa": ["roa"], "application/rsd+xml": ["rsd"], "application/rss+xml": ["rss"], "application/rtf": ["rtf"], "application/sbml+xml": ["sbml"], "application/scvp-cv-request": ["scq"], "application/scvp-cv-response": ["scs"], "application/scvp-vp-request": ["spq"], "application/scvp-vp-response": ["spp"], "application/sdp": ["sdp"], "application/set-payment-initiation": ["setpay"], "application/set-registration-initiation": ["setreg"], "application/shf+xml": ["shf"], "application/smil+xml": ["smi", "smil"], "application/sparql-query": ["rq"], "application/sparql-results+xml": ["srx"], "application/srgs": ["gram"], "application/srgs+xml": ["grxml"], "application/sru+xml": ["sru"], "application/ssdl+xml": ["ssdl"], "application/ssml+xml": ["ssml"], "application/tei+xml": ["tei", "teicorpus"], "application/thraud+xml": ["tfi"], "application/timestamped-data": ["tsd"], "application/vnd.3gpp.pic-bw-large": ["plb"], "application/vnd.3gpp.pic-bw-small": ["psb"], "application/vnd.3gpp.pic-bw-var": ["pvb"], "application/vnd.3gpp2.tcap": ["tcap"], "application/vnd.3m.post-it-notes": ["pwn"], "application/vnd.accpac.simply.aso": ["aso"], "application/vnd.accpac.simply.imp": ["imp"], "application/vnd.acucobol": ["acu"], "application/vnd.acucorp": ["atc", "acutc"], "application/vnd.adobe.air-application-installer-package+zip": ["air"], "application/vnd.adobe.formscentral.fcdt": ["fcdt"], "application/vnd.adobe.fxp": ["fxp", "fxpl"], "application/vnd.adobe.xdp+xml": ["xdp"], "application/vnd.adobe.xfdf": ["xfdf"], "application/vnd.ahead.space": ["ahead"], "application/vnd.airzip.filesecure.azf": ["azf"], "application/vnd.airzip.filesecure.azs": ["azs"], "application/vnd.amazon.ebook": ["azw"], "application/vnd.americandynamics.acc": ["acc"], "application/vnd.amiga.ami": ["ami"], "application/vnd.android.package-archive": ["apk"], "application/vnd.anser-web-certificate-issue-initiation": ["cii"], "application/vnd.anser-web-funds-transfer-initiation": ["fti"], "application/vnd.antix.game-component": ["atx"], "application/vnd.apple.installer+xml": ["mpkg"], "application/vnd.apple.mpegurl": ["m3u8"], "application/vnd.apple.pkpass": ["pkpass"], "application/vnd.aristanetworks.swi": ["swi"], "application/vnd.astraea-software.iota": ["iota"], "application/vnd.audiograph": ["aep"], "application/vnd.blueice.multipass": ["mpm"], "application/vnd.bmi": ["bmi"], "application/vnd.businessobjects": ["rep"], "application/vnd.chemdraw+xml": ["cdxml"], "application/vnd.chipnuts.karaoke-mmd": ["mmd"], "application/vnd.cinderella": ["cdy"], "application/vnd.claymore": ["cla"], "application/vnd.cloanto.rp9": ["rp9"], "application/vnd.clonk.c4group": ["c4g", "c4d", "c4f", "c4p", "c4u"], "application/vnd.cluetrust.cartomobile-config": ["c11amc"], "application/vnd.cluetrust.cartomobile-config-pkg": ["c11amz"], "application/vnd.commonspace": ["csp"], "application/vnd.contact.cmsg": ["cdbcmsg"], "application/vnd.cosmocaller": ["cmc"], "application/vnd.crick.clicker": ["clkx"], "application/vnd.crick.clicker.keyboard": ["clkk"], "application/vnd.crick.clicker.palette": ["clkp"], "application/vnd.crick.clicker.template": ["clkt"], "application/vnd.crick.clicker.wordbank": ["clkw"], "application/vnd.criticaltools.wbs+xml": ["wbs"], "application/vnd.ctc-posml": ["pml"], "application/vnd.cups-ppd": ["ppd"], "application/vnd.curl.car": ["car"], "application/vnd.curl.pcurl": ["pcurl"], "application/vnd.dart": ["dart"], "application/vnd.data-vision.rdz": ["rdz"], "application/vnd.dece.data": ["uvf", "uvvf", "uvd", "uvvd"], "application/vnd.dece.ttml+xml": ["uvt", "uvvt"], "application/vnd.dece.unspecified": ["uvx", "uvvx"], "application/vnd.dece.zip": ["uvz", "uvvz"], "application/vnd.denovo.fcselayout-link": ["fe_launch"], "application/vnd.dna": ["dna"], "application/vnd.dolby.mlp": ["mlp"], "application/vnd.dpgraph": ["dpg"], "application/vnd.dreamfactory": ["dfac"], "application/vnd.ds-keypoint": ["kpxx"], "application/vnd.dvb.ait": ["ait"], "application/vnd.dvb.service": ["svc"], "application/vnd.dynageo": ["geo"], "application/vnd.ecowin.chart": ["mag"], "application/vnd.enliven": ["nml"], "application/vnd.epson.esf": ["esf"], "application/vnd.epson.msf": ["msf"], "application/vnd.epson.quickanime": ["qam"], "application/vnd.epson.salt": ["slt"], "application/vnd.epson.ssf": ["ssf"], "application/vnd.eszigno3+xml": ["es3", "et3"], "application/vnd.ezpix-album": ["ez2"], "application/vnd.ezpix-package": ["ez3"], "application/vnd.fdf": ["fdf"], "application/vnd.fdsn.mseed": ["mseed"], "application/vnd.fdsn.seed": ["seed", "dataless"], "application/vnd.flographit": ["gph"], "application/vnd.fluxtime.clip": ["ftc"], "application/vnd.framemaker": ["fm", "frame", "maker", "book"], "application/vnd.frogans.fnc": ["fnc"], "application/vnd.frogans.ltf": ["ltf"], "application/vnd.fsc.weblaunch": ["fsc"], "application/vnd.fujitsu.oasys": ["oas"], "application/vnd.fujitsu.oasys2": ["oa2"], "application/vnd.fujitsu.oasys3": ["oa3"], "application/vnd.fujitsu.oasysgp": ["fg5"], "application/vnd.fujitsu.oasysprs": ["bh2"], "application/vnd.fujixerox.ddd": ["ddd"], "application/vnd.fujixerox.docuworks": ["xdw"], "application/vnd.fujixerox.docuworks.binder": ["xbd"], "application/vnd.fuzzysheet": ["fzs"], "application/vnd.genomatix.tuxedo": ["txd"], "application/vnd.geogebra.file": ["ggb"], "application/vnd.geogebra.tool": ["ggt"], "application/vnd.geometry-explorer": ["gex", "gre"], "application/vnd.geonext": ["gxt"], "application/vnd.geoplan": ["g2w"], "application/vnd.geospace": ["g3w"], "application/vnd.gmx": ["gmx"], "application/vnd.google-apps.document": ["gdoc"], "application/vnd.google-apps.presentation": ["gslides"], "application/vnd.google-apps.spreadsheet": ["gsheet"], "application/vnd.google-earth.kml+xml": ["kml"], "application/vnd.google-earth.kmz": ["kmz"], "application/vnd.grafeq": ["gqf", "gqs"], "application/vnd.groove-account": ["gac"], "application/vnd.groove-help": ["ghf"], "application/vnd.groove-identity-message": ["gim"], "application/vnd.groove-injector": ["grv"], "application/vnd.groove-tool-message": ["gtm"], "application/vnd.groove-tool-template": ["tpl"], "application/vnd.groove-vcard": ["vcg"], "application/vnd.hal+xml": ["hal"], "application/vnd.handheld-entertainment+xml": ["zmm"], "application/vnd.hbci": ["hbci"], "application/vnd.hhe.lesson-player": ["les"], "application/vnd.hp-hpgl": ["hpgl"], "application/vnd.hp-hpid": ["hpid"], "application/vnd.hp-hps": ["hps"], "application/vnd.hp-jlyt": ["jlt"], "application/vnd.hp-pcl": ["pcl"], "application/vnd.hp-pclxl": ["pclxl"], "application/vnd.hydrostatix.sof-data": ["sfd-hdstx"], "application/vnd.ibm.minipay": ["mpy"], "application/vnd.ibm.modcap": ["afp", "listafp", "list3820"], "application/vnd.ibm.rights-management": ["irm"], "application/vnd.ibm.secure-container": ["sc"], "application/vnd.iccprofile": ["icc", "icm"], "application/vnd.igloader": ["igl"], "application/vnd.immervision-ivp": ["ivp"], "application/vnd.immervision-ivu": ["ivu"], "application/vnd.insors.igm": ["igm"], "application/vnd.intercon.formnet": ["xpw", "xpx"], "application/vnd.intergeo": ["i2g"], "application/vnd.intu.qbo": ["qbo"], "application/vnd.intu.qfx": ["qfx"], "application/vnd.ipunplugged.rcprofile": ["rcprofile"], "application/vnd.irepository.package+xml": ["irp"], "application/vnd.is-xpr": ["xpr"], "application/vnd.isac.fcs": ["fcs"], "application/vnd.jam": ["jam"], "application/vnd.jcp.javame.midlet-rms": ["rms"], "application/vnd.jisp": ["jisp"], "application/vnd.joost.joda-archive": ["joda"], "application/vnd.kahootz": ["ktz", "ktr"], "application/vnd.kde.karbon": ["karbon"], "application/vnd.kde.kchart": ["chrt"], "application/vnd.kde.kformula": ["kfo"], "application/vnd.kde.kivio": ["flw"], "application/vnd.kde.kontour": ["kon"], "application/vnd.kde.kpresenter": ["kpr", "kpt"], "application/vnd.kde.kspread": ["ksp"], "application/vnd.kde.kword": ["kwd", "kwt"], "application/vnd.kenameaapp": ["htke"], "application/vnd.kidspiration": ["kia"], "application/vnd.kinar": ["kne", "knp"], "application/vnd.koan": ["skp", "skd", "skt", "skm"], "application/vnd.kodak-descriptor": ["sse"], "application/vnd.las.las+xml": ["lasxml"], "application/vnd.llamagraphics.life-balance.desktop": ["lbd"], "application/vnd.llamagraphics.life-balance.exchange+xml": ["lbe"], "application/vnd.lotus-1-2-3": ["123"], "application/vnd.lotus-approach": ["apr"], "application/vnd.lotus-freelance": ["pre"], "application/vnd.lotus-notes": ["nsf"], "application/vnd.lotus-organizer": ["org"], "application/vnd.lotus-screencam": ["scm"], "application/vnd.lotus-wordpro": ["lwp"], "application/vnd.macports.portpkg": ["portpkg"], "application/vnd.mcd": ["mcd"], "application/vnd.medcalcdata": ["mc1"], "application/vnd.mediastation.cdkey": ["cdkey"], "application/vnd.mfer": ["mwf"], "application/vnd.mfmp": ["mfm"], "application/vnd.micrografx.flo": ["flo"], "application/vnd.micrografx.igx": ["igx"], "application/vnd.mif": ["mif"], "application/vnd.mobius.daf": ["daf"], "application/vnd.mobius.dis": ["dis"], "application/vnd.mobius.mbk": ["mbk"], "application/vnd.mobius.mqy": ["mqy"], "application/vnd.mobius.msl": ["msl"], "application/vnd.mobius.plc": ["plc"], "application/vnd.mobius.txf": ["txf"], "application/vnd.mophun.application": ["mpn"], "application/vnd.mophun.certificate": ["mpc"], "application/vnd.mozilla.xul+xml": ["xul"], "application/vnd.ms-artgalry": ["cil"], "application/vnd.ms-cab-compressed": ["cab"], "application/vnd.ms-excel": ["xls", "xlm", "xla", "xlc", "xlt", "xlw"], "application/vnd.ms-excel.addin.macroenabled.12": ["xlam"], "application/vnd.ms-excel.sheet.binary.macroenabled.12": ["xlsb"], "application/vnd.ms-excel.sheet.macroenabled.12": ["xlsm"], "application/vnd.ms-excel.template.macroenabled.12": ["xltm"], "application/vnd.ms-fontobject": ["eot"], "application/vnd.ms-htmlhelp": ["chm"], "application/vnd.ms-ims": ["ims"], "application/vnd.ms-lrm": ["lrm"], "application/vnd.ms-officetheme": ["thmx"], "application/vnd.ms-outlook": ["msg"], "application/vnd.ms-pki.seccat": ["cat"], "application/vnd.ms-pki.stl": ["stl"], "application/vnd.ms-powerpoint": ["ppt", "pps", "pot"], "application/vnd.ms-powerpoint.addin.macroenabled.12": ["ppam"], "application/vnd.ms-powerpoint.presentation.macroenabled.12": ["pptm"], "application/vnd.ms-powerpoint.slide.macroenabled.12": ["sldm"], "application/vnd.ms-powerpoint.slideshow.macroenabled.12": ["ppsm"], "application/vnd.ms-powerpoint.template.macroenabled.12": ["potm"], "application/vnd.ms-project": ["mpp", "mpt"], "application/vnd.ms-word.document.macroenabled.12": ["docm"], "application/vnd.ms-word.template.macroenabled.12": ["dotm"], "application/vnd.ms-works": ["wps", "wks", "wcm", "wdb"], "application/vnd.ms-wpl": ["wpl"], "application/vnd.ms-xpsdocument": ["xps"], "application/vnd.mseq": ["mseq"], "application/vnd.musician": ["mus"], "application/vnd.muvee.style": ["msty"], "application/vnd.mynfc": ["taglet"], "application/vnd.neurolanguage.nlu": ["nlu"], "application/vnd.nitf": ["ntf", "nitf"], "application/vnd.noblenet-directory": ["nnd"], "application/vnd.noblenet-sealer": ["nns"], "application/vnd.noblenet-web": ["nnw"], "application/vnd.nokia.n-gage.data": ["ngdat"], "application/vnd.nokia.n-gage.symbian.install": ["n-gage"], "application/vnd.nokia.radio-preset": ["rpst"], "application/vnd.nokia.radio-presets": ["rpss"], "application/vnd.novadigm.edm": ["edm"], "application/vnd.novadigm.edx": ["edx"], "application/vnd.novadigm.ext": ["ext"], "application/vnd.oasis.opendocument.chart": ["odc"], "application/vnd.oasis.opendocument.chart-template": ["otc"], "application/vnd.oasis.opendocument.database": ["odb"], "application/vnd.oasis.opendocument.formula": ["odf"], "application/vnd.oasis.opendocument.formula-template": ["odft"], "application/vnd.oasis.opendocument.graphics": ["odg"], "application/vnd.oasis.opendocument.graphics-template": ["otg"], "application/vnd.oasis.opendocument.image": ["odi"], "application/vnd.oasis.opendocument.image-template": ["oti"], "application/vnd.oasis.opendocument.presentation": ["odp"], "application/vnd.oasis.opendocument.presentation-template": ["otp"], "application/vnd.oasis.opendocument.spreadsheet": ["ods"], "application/vnd.oasis.opendocument.spreadsheet-template": ["ots"], "application/vnd.oasis.opendocument.text": ["odt"], "application/vnd.oasis.opendocument.text-master": ["odm"], "application/vnd.oasis.opendocument.text-template": ["ott"], "application/vnd.oasis.opendocument.text-web": ["oth"], "application/vnd.olpc-sugar": ["xo"], "application/vnd.oma.dd2+xml": ["dd2"], "application/vnd.openofficeorg.extension": ["oxt"], "application/vnd.openxmlformats-officedocument.presentationml.presentation": ["pptx"], "application/vnd.openxmlformats-officedocument.presentationml.slide": ["sldx"], "application/vnd.openxmlformats-officedocument.presentationml.slideshow": ["ppsx"], "application/vnd.openxmlformats-officedocument.presentationml.template": ["potx"], "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": ["xlsx"], "application/vnd.openxmlformats-officedocument.spreadsheetml.template": ["xltx"], "application/vnd.openxmlformats-officedocument.wordprocessingml.document": ["docx"], "application/vnd.openxmlformats-officedocument.wordprocessingml.template": ["dotx"], "application/vnd.osgeo.mapguide.package": ["mgp"], "application/vnd.osgi.dp": ["dp"], "application/vnd.osgi.subsystem": ["esa"], "application/vnd.palm": ["pdb", "pqa", "oprc"], "application/vnd.pawaafile": ["paw"], "application/vnd.pg.format": ["str"], "application/vnd.pg.osasli": ["ei6"], "application/vnd.picsel": ["efif"], "application/vnd.pmi.widget": ["wg"], "application/vnd.pocketlearn": ["plf"], "application/vnd.powerbuilder6": ["pbd"], "application/vnd.previewsystems.box": ["box"], "application/vnd.proteus.magazine": ["mgz"], "application/vnd.publishare-delta-tree": ["qps"], "application/vnd.pvi.ptid1": ["ptid"], "application/vnd.quark.quarkxpress": ["qxd", "qxt", "qwd", "qwt", "qxl", "qxb"], "application/vnd.realvnc.bed": ["bed"], "application/vnd.recordare.musicxml": ["mxl"], "application/vnd.recordare.musicxml+xml": ["musicxml"], "application/vnd.rig.cryptonote": ["cryptonote"], "application/vnd.rim.cod": ["cod"], "application/vnd.rn-realmedia": ["rm"], "application/vnd.rn-realmedia-vbr": ["rmvb"], "application/vnd.route66.link66+xml": ["link66"], "application/vnd.sailingtracker.track": ["st"], "application/vnd.seemail": ["see"], "application/vnd.sema": ["sema"], "application/vnd.semd": ["semd"], "application/vnd.semf": ["semf"], "application/vnd.shana.informed.formdata": ["ifm"], "application/vnd.shana.informed.formtemplate": ["itp"], "application/vnd.shana.informed.interchange": ["iif"], "application/vnd.shana.informed.package": ["ipk"], "application/vnd.simtech-mindmapper": ["twd", "twds"], "application/vnd.smaf": ["mmf"], "application/vnd.smart.teacher": ["teacher"], "application/vnd.solent.sdkm+xml": ["sdkm", "sdkd"], "application/vnd.spotfire.dxp": ["dxp"], "application/vnd.spotfire.sfs": ["sfs"], "application/vnd.stardivision.calc": ["sdc"], "application/vnd.stardivision.draw": ["sda"], "application/vnd.stardivision.impress": ["sdd"], "application/vnd.stardivision.math": ["smf"], "application/vnd.stardivision.writer": ["sdw", "vor"], "application/vnd.stardivision.writer-global": ["sgl"], "application/vnd.stepmania.package": ["smzip"], "application/vnd.stepmania.stepchart": ["sm"], "application/vnd.sun.wadl+xml": ["wadl"], "application/vnd.sun.xml.calc": ["sxc"], "application/vnd.sun.xml.calc.template": ["stc"], "application/vnd.sun.xml.draw": ["sxd"], "application/vnd.sun.xml.draw.template": ["std"], "application/vnd.sun.xml.impress": ["sxi"], "application/vnd.sun.xml.impress.template": ["sti"], "application/vnd.sun.xml.math": ["sxm"], "application/vnd.sun.xml.writer": ["sxw"], "application/vnd.sun.xml.writer.global": ["sxg"], "application/vnd.sun.xml.writer.template": ["stw"], "application/vnd.sus-calendar": ["sus", "susp"], "application/vnd.svd": ["svd"], "application/vnd.symbian.install": ["sis", "sisx"], "application/vnd.syncml+xml": ["xsm"], "application/vnd.syncml.dm+wbxml": ["bdm"], "application/vnd.syncml.dm+xml": ["xdm"], "application/vnd.tao.intent-module-archive": ["tao"], "application/vnd.tcpdump.pcap": ["pcap", "cap", "dmp"], "application/vnd.tmobile-livetv": ["tmo"], "application/vnd.trid.tpt": ["tpt"], "application/vnd.triscape.mxs": ["mxs"], "application/vnd.trueapp": ["tra"], "application/vnd.ufdl": ["ufd", "ufdl"], "application/vnd.uiq.theme": ["utz"], "application/vnd.umajin": ["umj"], "application/vnd.unity": ["unityweb"], "application/vnd.uoml+xml": ["uoml"], "application/vnd.vcx": ["vcx"], "application/vnd.visio": ["vsd", "vst", "vss", "vsw"], "application/vnd.visionary": ["vis"], "application/vnd.vsf": ["vsf"], "application/vnd.wap.wbxml": ["wbxml"], "application/vnd.wap.wmlc": ["wmlc"], "application/vnd.wap.wmlscriptc": ["wmlsc"], "application/vnd.webturbo": ["wtb"], "application/vnd.wolfram.player": ["nbp"], "application/vnd.wordperfect": ["wpd"], "application/vnd.wqd": ["wqd"], "application/vnd.wt.stf": ["stf"], "application/vnd.xara": ["xar"], "application/vnd.xfdl": ["xfdl"], "application/vnd.yamaha.hv-dic": ["hvd"], "application/vnd.yamaha.hv-script": ["hvs"], "application/vnd.yamaha.hv-voice": ["hvp"], "application/vnd.yamaha.openscoreformat": ["osf"], "application/vnd.yamaha.openscoreformat.osfpvg+xml": ["osfpvg"], "application/vnd.yamaha.smaf-audio": ["saf"], "application/vnd.yamaha.smaf-phrase": ["spf"], "application/vnd.yellowriver-custom-menu": ["cmp"], "application/vnd.zul": ["zir", "zirz"], "application/vnd.zzazz.deck+xml": ["zaz"], "application/voicexml+xml": ["vxml"], "application/wasm": ["wasm"], "application/widget": ["wgt"], "application/winhlp": ["hlp"], "application/wsdl+xml": ["wsdl"], "application/wspolicy+xml": ["wspolicy"], "application/x-7z-compressed": ["7z"], "application/x-abiword": ["abw"], "application/x-ace-compressed": ["ace"], "application/x-apple-diskimage": [], "application/x-arj": ["arj"], "application/x-authorware-bin": ["aab", "x32", "u32", "vox"], "application/x-authorware-map": ["aam"], "application/x-authorware-seg": ["aas"], "application/x-bcpio": ["bcpio"], "application/x-bdoc": [], "application/x-bittorrent": ["torrent"], "application/x-blorb": ["blb", "blorb"], "application/x-bzip": ["bz"], "application/x-bzip2": ["bz2", "boz"], "application/x-cbr": ["cbr", "cba", "cbt", "cbz", "cb7"], "application/x-cdlink": ["vcd"], "application/x-cfs-compressed": ["cfs"], "application/x-chat": ["chat"], "application/x-chess-pgn": ["pgn"], "application/x-chrome-extension": ["crx"], "application/x-cocoa": ["cco"], "application/x-conference": ["nsc"], "application/x-cpio": ["cpio"], "application/x-csh": ["csh"], "application/x-debian-package": ["udeb"], "application/x-dgc-compressed": ["dgc"], "application/x-director": ["dir", "dcr", "dxr", "cst", "cct", "cxt", "w3d", "fgd", "swa"], "application/x-doom": ["wad"], "application/x-dtbncx+xml": ["ncx"], "application/x-dtbook+xml": ["dtb"], "application/x-dtbresource+xml": ["res"], "application/x-dvi": ["dvi"], "application/x-envoy": ["evy"], "application/x-eva": ["eva"], "application/x-font-bdf": ["bdf"], "application/x-font-ghostscript": ["gsf"], "application/x-font-linux-psf": ["psf"], "application/x-font-pcf": ["pcf"], "application/x-font-snf": ["snf"], "application/x-font-type1": ["pfa", "pfb", "pfm", "afm"], "application/x-freearc": ["arc"], "application/x-futuresplash": ["spl"], "application/x-gca-compressed": ["gca"], "application/x-glulx": ["ulx"], "application/x-gnumeric": ["gnumeric"], "application/x-gramps-xml": ["gramps"], "application/x-gtar": ["gtar"], "application/x-hdf": ["hdf"], "application/x-httpd-php": ["php"], "application/x-install-instructions": ["install"], "application/x-iso9660-image": [], "application/x-java-archive-diff": ["jardiff"], "application/x-java-jnlp-file": ["jnlp"], "application/x-latex": ["latex"], "application/x-lua-bytecode": ["luac"], "application/x-lzh-compressed": ["lzh", "lha"], "application/x-makeself": ["run"], "application/x-mie": ["mie"], "application/x-mobipocket-ebook": ["prc", "mobi"], "application/x-ms-application": ["application"], "application/x-ms-shortcut": ["lnk"], "application/x-ms-wmd": ["wmd"], "application/x-ms-wmz": ["wmz"], "application/x-ms-xbap": ["xbap"], "application/x-msaccess": ["mdb"], "application/x-msbinder": ["obd"], "application/x-mscardfile": ["crd"], "application/x-msclip": ["clp"], "application/x-msdos-program": [], "application/x-msdownload": ["com", "bat"], "application/x-msmediaview": ["mvb", "m13", "m14"], "application/x-msmetafile": ["wmf", "emf", "emz"], "application/x-msmoney": ["mny"], "application/x-mspublisher": ["pub"], "application/x-msschedule": ["scd"], "application/x-msterminal": ["trm"], "application/x-mswrite": ["wri"], "application/x-netcdf": ["nc", "cdf"], "application/x-ns-proxy-autoconfig": ["pac"], "application/x-nzb": ["nzb"], "application/x-perl": ["pl", "pm"], "application/x-pilot": [], "application/x-pkcs12": ["p12", "pfx"], "application/x-pkcs7-certificates": ["p7b", "spc"], "application/x-pkcs7-certreqresp": ["p7r"], "application/x-rar-compressed": ["rar"], "application/x-redhat-package-manager": ["rpm"], "application/x-research-info-systems": ["ris"], "application/x-sea": ["sea"], "application/x-sh": ["sh"], "application/x-shar": ["shar"], "application/x-shockwave-flash": ["swf"], "application/x-silverlight-app": ["xap"], "application/x-sql": ["sql"], "application/x-stuffit": ["sit"], "application/x-stuffitx": ["sitx"], "application/x-subrip": ["srt"], "application/x-sv4cpio": ["sv4cpio"], "application/x-sv4crc": ["sv4crc"], "application/x-t3vm-image": ["t3"], "application/x-tads": ["gam"], "application/x-tar": ["tar"], "application/x-tcl": ["tcl", "tk"], "application/x-tex": ["tex"], "application/x-tex-tfm": ["tfm"], "application/x-texinfo": ["texinfo", "texi"], "application/x-tgif": ["obj"], "application/x-ustar": ["ustar"], "application/x-virtualbox-hdd": ["hdd"], "application/x-virtualbox-ova": ["ova"], "application/x-virtualbox-ovf": ["ovf"], "application/x-virtualbox-vbox": ["vbox"], "application/x-virtualbox-vbox-extpack": ["vbox-extpack"], "application/x-virtualbox-vdi": ["vdi"], "application/x-virtualbox-vhd": ["vhd"], "application/x-virtualbox-vmdk": ["vmdk"], "application/x-wais-source": ["src"], "application/x-web-app-manifest+json": ["webapp"], "application/x-x509-ca-cert": ["der", "crt", "pem"], "application/x-xfig": ["fig"], "application/x-xliff+xml": ["xlf"], "application/x-xpinstall": ["xpi"], "application/x-xz": ["xz"], "application/x-zmachine": ["z1", "z2", "z3", "z4", "z5", "z6", "z7", "z8"], "application/xaml+xml": ["xaml"], "application/xcap-diff+xml": ["xdf"], "application/xenc+xml": ["xenc"], "application/xhtml+xml": ["xhtml", "xht"], "application/xml": ["xml", "xsl", "xsd", "rng"], "application/xml-dtd": ["dtd"], "application/xop+xml": ["xop"], "application/xproc+xml": ["xpl"], "application/xslt+xml": ["xslt"], "application/xspf+xml": ["xspf"], "application/xv+xml": ["mxml", "xhvml", "xvml", "xvm"], "application/yang": ["yang"], "application/yin+xml": ["yin"], "application/zip": ["zip"], "audio/3gpp": [], "audio/adpcm": ["adp"], "audio/basic": ["au", "snd"], "audio/midi": ["mid", "midi", "kar", "rmi"], "audio/mp3": [], "audio/mp4": ["m4a", "mp4a"], "audio/mpeg": ["mpga", "mp2", "mp2a", "mp3", "m2a", "m3a"], "audio/ogg": ["oga", "ogg", "spx"], "audio/s3m": ["s3m"], "audio/silk": ["sil"], "audio/vnd.dece.audio": ["uva", "uvva"], "audio/vnd.digital-winds": ["eol"], "audio/vnd.dra": ["dra"], "audio/vnd.dts": ["dts"], "audio/vnd.dts.hd": ["dtshd"], "audio/vnd.lucent.voice": ["lvp"], "audio/vnd.ms-playready.media.pya": ["pya"], "audio/vnd.nuera.ecelp4800": ["ecelp4800"], "audio/vnd.nuera.ecelp7470": ["ecelp7470"], "audio/vnd.nuera.ecelp9600": ["ecelp9600"], "audio/vnd.rip": ["rip"], "audio/wav": ["wav"], "audio/wave": [], "audio/webm": ["weba"], "audio/x-aac": ["aac"], "audio/x-aiff": ["aif", "aiff", "aifc"], "audio/x-caf": ["caf"], "audio/x-flac": ["flac"], "audio/x-m4a": [], "audio/x-matroska": ["mka"], "audio/x-mpegurl": ["m3u"], "audio/x-ms-wax": ["wax"], "audio/x-ms-wma": ["wma"], "audio/x-pn-realaudio": ["ram", "ra"], "audio/x-pn-realaudio-plugin": ["rmp"], "audio/x-realaudio": [], "audio/x-wav": [], "audio/xm": ["xm"], "chemical/x-cdx": ["cdx"], "chemical/x-cif": ["cif"], "chemical/x-cmdf": ["cmdf"], "chemical/x-cml": ["cml"], "chemical/x-csml": ["csml"], "chemical/x-xyz": ["xyz"], "font/collection": ["ttc"], "font/otf": ["otf"], "font/ttf": ["ttf"], "font/woff": ["woff"], "font/woff2": ["woff2"], "image/apng": ["apng"], "image/bmp": ["bmp"], "image/cgm": ["cgm"], "image/g3fax": ["g3"], "image/gif": ["gif"], "image/ief": ["ief"], "image/jp2": ["jp2", "jpg2"], "image/jpeg": ["jpeg", "jpg", "jpe"], "image/jpm": ["jpm"], "image/jpx": ["jpx", "jpf"], "image/ktx": ["ktx"], "image/png": ["png"], "image/prs.btif": ["btif"], "image/sgi": ["sgi"], "image/svg+xml": ["svg", "svgz"], "image/tiff": ["tiff", "tif"], "image/vnd.adobe.photoshop": ["psd"], "image/vnd.dece.graphic": ["uvi", "uvvi", "uvg", "uvvg"], "image/vnd.djvu": ["djvu", "djv"], "image/vnd.dvb.subtitle": [], "image/vnd.dwg": ["dwg"], "image/vnd.dxf": ["dxf"], "image/vnd.fastbidsheet": ["fbs"], "image/vnd.fpx": ["fpx"], "image/vnd.fst": ["fst"], "image/vnd.fujixerox.edmics-mmr": ["mmr"], "image/vnd.fujixerox.edmics-rlc": ["rlc"], "image/vnd.ms-modi": ["mdi"], "image/vnd.ms-photo": ["wdp"], "image/vnd.net-fpx": ["npx"], "image/vnd.wap.wbmp": ["wbmp"], "image/vnd.xiff": ["xif"], "image/webp": ["webp"], "image/x-3ds": ["3ds"], "image/x-cmu-raster": ["ras"], "image/x-cmx": ["cmx"], "image/x-freehand": ["fh", "fhc", "fh4", "fh5", "fh7"], "image/x-icon": ["ico"], "image/x-jng": ["jng"], "image/x-mrsid-image": ["sid"], "image/x-ms-bmp": [], "image/x-pcx": ["pcx"], "image/x-pict": ["pic", "pct"], "image/x-portable-anymap": ["pnm"], "image/x-portable-bitmap": ["pbm"], "image/x-portable-graymap": ["pgm"], "image/x-portable-pixmap": ["ppm"], "image/x-rgb": ["rgb"], "image/x-tga": ["tga"], "image/x-xbitmap": ["xbm"], "image/x-xpixmap": ["xpm"], "image/x-xwindowdump": ["xwd"], "message/rfc822": ["eml", "mime"], "model/gltf+json": ["gltf"], "model/gltf-binary": ["glb"], "model/iges": ["igs", "iges"], "model/mesh": ["msh", "mesh", "silo"], "model/vnd.collada+xml": ["dae"], "model/vnd.dwf": ["dwf"], "model/vnd.gdl": ["gdl"], "model/vnd.gtw": ["gtw"], "model/vnd.mts": ["mts"], "model/vnd.vtu": ["vtu"], "model/vrml": ["wrl", "vrml"], "model/x3d+binary": ["x3db", "x3dbz"], "model/x3d+vrml": ["x3dv", "x3dvz"], "model/x3d+xml": ["x3d", "x3dz"], "text/cache-manifest": ["appcache", "manifest"], "text/calendar": ["ics", "ifb"], "text/coffeescript": ["coffee", "litcoffee"], "text/css": ["css"], "text/csv": ["csv"], "text/hjson": ["hjson"], "text/html": ["html", "htm", "shtml"], "text/jade": ["jade"], "text/jsx": ["jsx"], "text/less": ["less"], "text/markdown": ["markdown", "md"], "text/mathml": ["mml"], "text/n3": ["n3"], "text/plain": ["txt", "text", "conf", "def", "list", "log", "in", "ini"], "text/prs.lines.tag": ["dsc"], "text/richtext": ["rtx"], "text/rtf": [], "text/sgml": ["sgml", "sgm"], "text/slim": ["slim", "slm"], "text/stylus": ["stylus", "styl"], "text/tab-separated-values": ["tsv"], "text/troff": ["t", "tr", "roff", "man", "me", "ms"], "text/turtle": ["ttl"], "text/uri-list": ["uri", "uris", "urls"], "text/vcard": ["vcard"], "text/vnd.curl": ["curl"], "text/vnd.curl.dcurl": ["dcurl"], "text/vnd.curl.mcurl": ["mcurl"], "text/vnd.curl.scurl": ["scurl"], "text/vnd.dvb.subtitle": ["sub"], "text/vnd.fly": ["fly"], "text/vnd.fmi.flexstor": ["flx"], "text/vnd.graphviz": ["gv"], "text/vnd.in3d.3dml": ["3dml"], "text/vnd.in3d.spot": ["spot"], "text/vnd.sun.j2me.app-descriptor": ["jad"], "text/vnd.wap.wml": ["wml"], "text/vnd.wap.wmlscript": ["wmls"], "text/vtt": ["vtt"], "text/x-asm": ["s", "asm"], "text/x-c": ["c", "cc", "cxx", "cpp", "h", "hh", "dic"], "text/x-component": ["htc"], "text/x-fortran": ["f", "for", "f77", "f90"], "text/x-handlebars-template": ["hbs"], "text/x-java-source": ["java"], "text/x-lua": ["lua"], "text/x-markdown": ["mkd"], "text/x-nfo": ["nfo"], "text/x-opml": ["opml"], "text/x-org": [], "text/x-pascal": ["p", "pas"], "text/x-processing": ["pde"], "text/x-sass": ["sass"], "text/x-scss": ["scss"], "text/x-setext": ["etx"], "text/x-sfv": ["sfv"], "text/x-suse-ymp": ["ymp"], "text/x-uuencode": ["uu"], "text/x-vcalendar": ["vcs"], "text/x-vcard": ["vcf"], "text/xml": [], "text/yaml": ["yaml", "yml"], "video/3gpp": ["3gp", "3gpp"], "video/3gpp2": ["3g2"], "video/h261": ["h261"], "video/h263": ["h263"], "video/h264": ["h264"], "video/jpeg": ["jpgv"], "video/jpm": ["jpgm"], "video/mj2": ["mj2", "mjp2"], "video/mp2t": ["ts"], "video/mp4": ["mp4", "mp4v", "mpg4"], "video/mpeg": ["mpeg", "mpg", "mpe", "m1v", "m2v"], "video/ogg": ["ogv"], "video/quicktime": ["qt", "mov"], "video/vnd.dece.hd": ["uvh", "uvvh"], "video/vnd.dece.mobile": ["uvm", "uvvm"], "video/vnd.dece.pd": ["uvp", "uvvp"], "video/vnd.dece.sd": ["uvs", "uvvs"], "video/vnd.dece.video": ["uvv", "uvvv"], "video/vnd.dvb.file": ["dvb"], "video/vnd.fvt": ["fvt"], "video/vnd.mpegurl": ["mxu", "m4u"], "video/vnd.ms-playready.media.pyv": ["pyv"], "video/vnd.uvvu.mp4": ["uvu", "uvvu"], "video/vnd.vivo": ["viv"], "video/webm": ["webm"], "video/x-f4v": ["f4v"], "video/x-fli": ["fli"], "video/x-flv": ["flv"], "video/x-m4v": ["m4v"], "video/x-matroska": ["mkv", "mk3d", "mks"], "video/x-mng": ["mng"], "video/x-ms-asf": ["asf", "asx"], "video/x-ms-vob": ["vob"], "video/x-ms-wm": ["wm"], "video/x-ms-wmv": ["wmv"], "video/x-ms-wmx": ["wmx"], "video/x-ms-wvx": ["wvx"], "video/x-msvideo": ["avi"], "video/x-sgi-movie": ["movie"], "video/x-smv": ["smv"], "x-conference/x-cooltalk": ["ice"] };
|
|
447
|
-
|
|
448
|
-
// src/static.ts
|
|
449
|
-
var mime_types = /* @__PURE__ */ new Map();
|
|
450
|
-
var mime_extensions = /* @__PURE__ */ new Map();
|
|
451
|
-
function mime_define(map) {
|
|
452
|
-
for (var type in map) {
|
|
453
|
-
var exts = map[type];
|
|
454
|
-
for (var i = 0; i < exts.length; i++)
|
|
455
|
-
mime_types.set(exts[i], type);
|
|
456
|
-
if (!mime_extensions.has(type))
|
|
457
|
-
mime_extensions.set(type, exts[0]);
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
var mime = {
|
|
461
|
-
lookup: (path2, fallback = null) => mime_types.get(path2.replace(/^.*[\.\/\\]/, "").toLowerCase()) ?? fallback ?? "application/octet-stream",
|
|
462
|
-
charsets: (mimeType) => /^text\/|^application\/(javascript|json)/.test(mimeType) ? "UTF-8" : null
|
|
463
|
-
};
|
|
464
|
-
mime_define(mimetypes_default);
|
|
465
|
-
var UP_PATH_REGEXP = /(?:^|[\\/])\.\.(?:[\\/]|$)/;
|
|
466
|
-
var DEFAULT_OPTIONS = {
|
|
467
|
-
headers: {
|
|
468
|
-
"Content-Security-Policy": "default-src 'self'",
|
|
469
|
-
"X-Content-Type-Options": "nosniff"
|
|
470
|
-
},
|
|
471
|
-
fallthrough: false,
|
|
472
|
-
maxage: 0,
|
|
473
|
-
immutable: false,
|
|
474
|
-
etag: true,
|
|
475
|
-
lastModified: true,
|
|
476
|
-
contentType: null,
|
|
477
|
-
dotfiles: "hide",
|
|
478
|
-
redirect: true,
|
|
479
|
-
indexOf: false
|
|
480
|
-
};
|
|
481
|
-
var HTTP = {
|
|
482
|
-
/** 304 Not Modified — sent for conditional GET cache hits. */
|
|
483
|
-
NOT_MODIFIED: (res, opts) => {
|
|
484
|
-
res.status(304, opts.headers).end();
|
|
485
|
-
},
|
|
486
|
-
/** 403 Forbidden — sent for denied dot-files or path traversal attempts. */
|
|
487
|
-
FORBIDDEN: (res, opts) => res.status(403, opts.headers).send("Forbidden"),
|
|
488
|
-
/** 404 Not Found — sent when the requested file does not exist. */
|
|
489
|
-
NOT_FOUND: (res, opts) => res.status(404, opts.headers).send("Not Found"),
|
|
490
|
-
/**
|
|
491
|
-
* 405 Method Not Allowed — sent when the HTTP method is neither GET nor
|
|
492
|
-
* HEAD and {@link ResolvedOptions.fallthrough} is `false`.
|
|
493
|
-
* Always includes an `Allow: GET, HEAD` header per RFC 7231 §6.5.5.
|
|
494
|
-
*/
|
|
495
|
-
NOT_ALLOWED: (res, opts) => {
|
|
496
|
-
res.status(405, { ...opts.headers, Allow: "GET, HEAD" }).end();
|
|
497
|
-
},
|
|
498
|
-
/** 412 Precondition Failed — sent when `If-Match` / `If-Unmodified-Since` fails. */
|
|
499
|
-
PRECONDITION_FAILS: (res, opts) => res.status(412, opts.headers).send("Precondition Failed"),
|
|
500
|
-
/** 500 Internal Server Error — sent on unexpected filesystem or stream errors. */
|
|
501
|
-
INTERNAL_ERROR: (res, opts, err) => res.status(500, opts.headers).send(`Internal error: ${err}`)
|
|
502
|
-
};
|
|
503
|
-
function destroyReadStream(stream) {
|
|
504
|
-
stream.destroy();
|
|
505
|
-
if (typeof stream.close === "function") {
|
|
506
|
-
stream.on("open", () => {
|
|
507
|
-
if (typeof stream.fd === "number")
|
|
508
|
-
stream.close();
|
|
509
|
-
});
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
function removeContentHeaders(res) {
|
|
513
|
-
const keys = Object.keys(res.getHeaders() ?? {});
|
|
514
|
-
for (const key of keys) {
|
|
515
|
-
if (key.startsWith("content-") && key !== "content-location")
|
|
516
|
-
res.removeHeader(key);
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
function createETag(stat) {
|
|
520
|
-
const mtime = stat.mtime.getTime().toString(16);
|
|
521
|
-
const size = stat.size.toString(16);
|
|
522
|
-
return `W/"${size}-${mtime}"`;
|
|
523
|
-
}
|
|
524
|
-
function parseTokenList(str) {
|
|
525
|
-
const list = [];
|
|
526
|
-
let start = 0;
|
|
527
|
-
let end = 0;
|
|
528
|
-
for (let i = 0, len = str.length; i < len; i++) {
|
|
529
|
-
const ch = str.charCodeAt(i);
|
|
530
|
-
if (ch === 32) {
|
|
531
|
-
if (start === end) start = end = i + 1;
|
|
532
|
-
} else if (ch === 44) {
|
|
533
|
-
list.push(str.substring(start, end));
|
|
534
|
-
start = end = i + 1;
|
|
535
|
-
} else {
|
|
536
|
-
end = i + 1;
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
list.push(str.substring(start, end));
|
|
540
|
-
return list;
|
|
541
|
-
}
|
|
542
|
-
function parseHttpDate(date) {
|
|
543
|
-
const timestamp = date ? Date.parse(date) : NaN;
|
|
544
|
-
return typeof timestamp === "number" ? timestamp : NaN;
|
|
1070
|
+
function encodePath(urlPath) {
|
|
1071
|
+
return urlPath.split("/").map((s) => encodeURIComponent(s)).join("/");
|
|
545
1072
|
}
|
|
546
1073
|
function conditionMatch(reqHeaders, resHeaders) {
|
|
547
1074
|
const match = reqHeaders["if-match"];
|
|
548
1075
|
if (match) {
|
|
549
|
-
const etag = resHeaders
|
|
1076
|
+
const etag = resHeaders.etag;
|
|
550
1077
|
if (match === "*" || match === etag) return true;
|
|
551
1078
|
for (const tag of parseTokenList(match)) {
|
|
552
1079
|
if (tag === etag || `W/${tag}` === etag || tag === `W/${etag}`)
|
|
@@ -569,7 +1096,7 @@ function isCacheFresh(reqHeaders, resHeaders) {
|
|
|
569
1096
|
if (cacheControl2 && CACHE_CONTROL_NO_CACHE_REGEXP.test(cacheControl2))
|
|
570
1097
|
return false;
|
|
571
1098
|
if (noneMatch && noneMatch !== "*") {
|
|
572
|
-
const etag = resHeaders
|
|
1099
|
+
const etag = resHeaders.etag;
|
|
573
1100
|
if (!etag) return false;
|
|
574
1101
|
let etagStale = true;
|
|
575
1102
|
for (const match of parseTokenList(noneMatch)) {
|
|
@@ -635,15 +1162,15 @@ function writeIndexOf(urlPath, directoryPath, parentUrlPath, queryString, callba
|
|
|
635
1162
|
}
|
|
636
1163
|
let html = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">\n';
|
|
637
1164
|
html += "<html>\n";
|
|
638
|
-
html += `<head><title>Index of ${urlPath}</title></head>
|
|
1165
|
+
html += `<head><title>Index of ${htmlEscape(urlPath)}</title></head>
|
|
639
1166
|
`;
|
|
640
|
-
html += `<body><h1>Index of ${urlPath}</h1><table>
|
|
1167
|
+
html += `<body><h1>Index of ${htmlEscape(urlPath)}</h1><table>
|
|
641
1168
|
`;
|
|
642
1169
|
html += `<tr><th valign="top"><img src="/icons/blank.gif" alt="[ICO]"></th><th>${thLink("N", "Name")}</th><th>${thLink("M", "Last modified")}</th><th>${thLink("S", "Size")}</th><th><a href="?C=D;O=A">Description</a></th></tr>
|
|
643
1170
|
`;
|
|
644
1171
|
html += '<tr><th colspan="5"><hr></th></tr>\n';
|
|
645
1172
|
if (parentUrlPath)
|
|
646
|
-
html += `<tr><td valign="top"><img src="/icons/back.gif" alt="[PARENTDIR]"></td><td><a href="${parentUrlPath}">Parent Directory</a></td><td> </td><td align="right"> - </td><td> </td></tr>
|
|
1173
|
+
html += `<tr><td valign="top"><img src="/icons/back.gif" alt="[PARENTDIR]"></td><td><a href="${encodePath(parentUrlPath)}">Parent Directory</a></td><td> </td><td align="right"> - </td><td> </td></tr>
|
|
647
1174
|
`;
|
|
648
1175
|
for (const { file, stat, isDir } of entries) {
|
|
649
1176
|
const fullPath = import_path.default.join(directoryPath, file);
|
|
@@ -652,9 +1179,11 @@ function writeIndexOf(urlPath, directoryPath, parentUrlPath, queryString, callba
|
|
|
652
1179
|
const alt = isDir ? "folder" : mediaType || "unknown";
|
|
653
1180
|
const icon = `/icons/${alt}.gif`;
|
|
654
1181
|
const name = file + (isDir ? "/" : "");
|
|
1182
|
+
const hrefName = encodeURIComponent(file) + (isDir ? "/" : "");
|
|
1183
|
+
const displayName = htmlEscape(name);
|
|
655
1184
|
const modified = stat.mtime.toUTCString();
|
|
656
1185
|
const size = isDir ? "-" : String(stat.size);
|
|
657
|
-
html += `<tr><td valign="top"><img src="${icon}" alt="[${alt.toUpperCase()}]"></td><td><a href="${
|
|
1186
|
+
html += `<tr><td valign="top"><img src="${icon}" alt="[${alt.toUpperCase()}]"></td><td><a href="${hrefName}">${displayName}</a></td><td align="right">${modified}</td><td align="right">${size}</td><td> </td></tr>
|
|
658
1187
|
`;
|
|
659
1188
|
}
|
|
660
1189
|
html += '<tr><th colspan="5"><hr></th></tr>\n';
|
|
@@ -697,7 +1226,7 @@ function sendIt(req, res, pathname, stat, opts) {
|
|
|
697
1226
|
HTTP.NOT_MODIFIED(res, opts);
|
|
698
1227
|
return;
|
|
699
1228
|
}
|
|
700
|
-
const hasPrecondition = !!(req.headers["if-match"]
|
|
1229
|
+
const hasPrecondition = !!(req.headers["if-match"] ?? req.headers["if-unmodified-since"]);
|
|
701
1230
|
if (hasPrecondition && !conditionMatch(
|
|
702
1231
|
req.headers,
|
|
703
1232
|
res.getHeaders()
|
|
@@ -755,13 +1284,25 @@ function serveStatic(root, options) {
|
|
|
755
1284
|
return next();
|
|
756
1285
|
return HTTP.NOT_ALLOWED(res, opts);
|
|
757
1286
|
}
|
|
758
|
-
|
|
1287
|
+
let originalUrl;
|
|
1288
|
+
try {
|
|
1289
|
+
originalUrl = decodeURIComponent(req.path ?? req.url ?? "/");
|
|
1290
|
+
} catch {
|
|
1291
|
+
return HTTP.BAD_REQUEST(res, opts);
|
|
1292
|
+
}
|
|
759
1293
|
let pathname = originalUrl;
|
|
760
1294
|
if (pathname === "/" && !originalUrl.endsWith("/"))
|
|
761
1295
|
pathname = "";
|
|
1296
|
+
if (CONTROL_CHAR_REGEXP.test(pathname)) {
|
|
1297
|
+
if (opts.fallthrough)
|
|
1298
|
+
return next();
|
|
1299
|
+
return HTTP.NOT_FOUND(res, opts);
|
|
1300
|
+
}
|
|
762
1301
|
if (UP_PATH_REGEXP.test(pathname))
|
|
763
1302
|
return HTTP.FORBIDDEN(res, opts);
|
|
764
1303
|
pathname = import_path.default.resolve(import_path.default.normalize(`${opts.root}/${pathname}`));
|
|
1304
|
+
if (pathname !== opts.root && !pathname.startsWith(opts.root + import_path.default.sep))
|
|
1305
|
+
return HTTP.FORBIDDEN(res, opts);
|
|
765
1306
|
if (opts.dotfiles !== "allow" && pathname.includes("/.")) {
|
|
766
1307
|
if (opts.dotfiles === "deny")
|
|
767
1308
|
return HTTP.FORBIDDEN(res, opts);
|
|
@@ -796,15 +1337,790 @@ function serveFile(filePath, options) {
|
|
|
796
1337
|
return next();
|
|
797
1338
|
return HTTP.NOT_ALLOWED(res, opts);
|
|
798
1339
|
}
|
|
799
|
-
const pathname = opts.root;
|
|
800
|
-
import_fs.default.stat(pathname, (err, stat) => {
|
|
801
|
-
if (err)
|
|
802
|
-
return HTTP.INTERNAL_ERROR(res, opts, err.code ?? "UNKNOWN");
|
|
803
|
-
if (stat.isDirectory())
|
|
804
|
-
return HTTP.INTERNAL_ERROR(res, opts, "EISDIR");
|
|
805
|
-
sendIt(req, res, pathname, stat, opts);
|
|
806
|
-
});
|
|
807
|
-
};
|
|
1340
|
+
const pathname = opts.root;
|
|
1341
|
+
import_fs.default.stat(pathname, (err, stat) => {
|
|
1342
|
+
if (err)
|
|
1343
|
+
return HTTP.INTERNAL_ERROR(res, opts, err.code ?? "UNKNOWN");
|
|
1344
|
+
if (stat.isDirectory())
|
|
1345
|
+
return HTTP.INTERNAL_ERROR(res, opts, "EISDIR");
|
|
1346
|
+
sendIt(req, res, pathname, stat, opts);
|
|
1347
|
+
});
|
|
1348
|
+
};
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
// src/misc.ts
|
|
1352
|
+
var import_stream = require("stream");
|
|
1353
|
+
var import_zlib = __toESM(require("zlib"), 1);
|
|
1354
|
+
function resolveBodyOptions(opts, defaultType) {
|
|
1355
|
+
return {
|
|
1356
|
+
inflate: opts?.inflate ?? true,
|
|
1357
|
+
limit: opts?.limit ?? "100kb",
|
|
1358
|
+
reviver: opts?.reviver ?? null,
|
|
1359
|
+
strict: opts?.strict ?? true,
|
|
1360
|
+
type: opts?.type ?? defaultType,
|
|
1361
|
+
verify: opts?.verify ?? null
|
|
1362
|
+
};
|
|
1363
|
+
}
|
|
1364
|
+
function matchesBodyType(req, matcher) {
|
|
1365
|
+
if (matcher === null) return true;
|
|
1366
|
+
if (typeof matcher === "function") return matcher(req);
|
|
1367
|
+
const actual = (req.headers["content-type"] ?? "").split(";")[0].trim().toLowerCase();
|
|
1368
|
+
if (!actual) return false;
|
|
1369
|
+
const patterns = Array.isArray(matcher) ? matcher : [matcher];
|
|
1370
|
+
return patterns.some((pattern) => matchMimePattern(actual, pattern.toLowerCase()));
|
|
1371
|
+
}
|
|
1372
|
+
function matchMimePattern(actual, pattern) {
|
|
1373
|
+
if (pattern === "*/*" || pattern === "*") return true;
|
|
1374
|
+
if (pattern === actual) return true;
|
|
1375
|
+
const [pType, pSub] = pattern.split("/");
|
|
1376
|
+
const [aType] = actual.split("/");
|
|
1377
|
+
return pSub === "*" && pType === aType;
|
|
1378
|
+
}
|
|
1379
|
+
var DECOMPRESS_ALGO = {
|
|
1380
|
+
gzip: import_zlib.default.gunzip,
|
|
1381
|
+
deflate: import_zlib.default.inflate,
|
|
1382
|
+
br: import_zlib.default.brotliDecompress
|
|
1383
|
+
};
|
|
1384
|
+
function readSize(value) {
|
|
1385
|
+
if (typeof value === "number") return value;
|
|
1386
|
+
const fmt = /^(\d+(\.\d+)?)([kmg]?b?)?$/i.exec(value);
|
|
1387
|
+
if (!fmt) return 0;
|
|
1388
|
+
const num = parseFloat(fmt[1] ?? "0");
|
|
1389
|
+
const sfx = (fmt[3] ?? "b").toLowerCase();
|
|
1390
|
+
if (sfx.startsWith("k")) return num * 1024;
|
|
1391
|
+
if (sfx.startsWith("m")) return num * 1024 * 1024;
|
|
1392
|
+
if (sfx.startsWith("g")) return num * 1024 * 1024 * 1024;
|
|
1393
|
+
return num;
|
|
1394
|
+
}
|
|
1395
|
+
function splitBuffer(buffer, delimiter) {
|
|
1396
|
+
const result = [];
|
|
1397
|
+
let start = 0;
|
|
1398
|
+
let index;
|
|
1399
|
+
while ((index = buffer.indexOf(delimiter, start)) !== -1) {
|
|
1400
|
+
result.push(buffer.slice(start, index));
|
|
1401
|
+
start = index + delimiter.length;
|
|
1402
|
+
}
|
|
1403
|
+
result.push(buffer.slice(start));
|
|
1404
|
+
return result;
|
|
1405
|
+
}
|
|
1406
|
+
function extractCharset(contentType) {
|
|
1407
|
+
const param = contentType.split(";").map((s) => s.replace(/^\s+|\s+$/g, "")).find((s) => s.startsWith("charset="));
|
|
1408
|
+
return param ? param.substring("charset=".length) : "utf8";
|
|
1409
|
+
}
|
|
1410
|
+
function readBody(req, res, opts, type, next, callback) {
|
|
1411
|
+
const length = parseInt(req.headers["content-length"] ?? "0", 10);
|
|
1412
|
+
const isChunked = req.headers["transfer-encoding"]?.split(",").map((v) => v.trim()).some((v) => v.toLowerCase() === "chunked") ?? false;
|
|
1413
|
+
if (!isChunked && (!length || length === 0)) return next();
|
|
1414
|
+
const maxLength = readSize(opts.limit) || 102400;
|
|
1415
|
+
if (!isChunked && length > maxLength)
|
|
1416
|
+
return void res.status(413).send("Content Too Large");
|
|
1417
|
+
const encoding = req.headers["content-encoding"];
|
|
1418
|
+
if (encoding && (opts.inflate === false || !DECOMPRESS_ALGO[encoding]))
|
|
1419
|
+
return void res.status(415).send("Unsupported Media Type: Wrong Content-Encoding");
|
|
1420
|
+
const decompress = (encoding ? DECOMPRESS_ALGO[encoding] : void 0) ?? ((d, c) => c(null, d));
|
|
1421
|
+
const contentType = req.headers["content-type"] ?? "";
|
|
1422
|
+
if (!matchesBodyType(req, type))
|
|
1423
|
+
return next();
|
|
1424
|
+
let data = Buffer.alloc(0);
|
|
1425
|
+
req.on("data", (chunk) => {
|
|
1426
|
+
if (data === null) return;
|
|
1427
|
+
const next_ = Buffer.concat([data, chunk]);
|
|
1428
|
+
if (next_.length > maxLength) {
|
|
1429
|
+
data = null;
|
|
1430
|
+
res.status(413).send("Content Too Large");
|
|
1431
|
+
return;
|
|
1432
|
+
}
|
|
1433
|
+
data = next_;
|
|
1434
|
+
});
|
|
1435
|
+
req.on("end", () => {
|
|
1436
|
+
if (data === null) return;
|
|
1437
|
+
decompress(data, (err, decompressed) => {
|
|
1438
|
+
if (err) return void res.status(500).send(err.message);
|
|
1439
|
+
const body = decompressed;
|
|
1440
|
+
if (opts.verify) {
|
|
1441
|
+
try {
|
|
1442
|
+
opts.verify(req, res, body, extractCharset(contentType));
|
|
1443
|
+
} catch (e) {
|
|
1444
|
+
const status = e.status ?? e.statusCode ?? 403;
|
|
1445
|
+
return void res.status(status).send(e.message ?? "Forbidden");
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
callback(contentType, body);
|
|
1449
|
+
});
|
|
1450
|
+
});
|
|
1451
|
+
}
|
|
1452
|
+
function readReqBody(req, opts, mimetype, res) {
|
|
1453
|
+
return new Promise((resolve, reject) => {
|
|
1454
|
+
const length = parseInt(req.headers["content-length"] ?? "0", 10);
|
|
1455
|
+
const isChunked = req.headers["transfer-encoding"]?.split(",").map((v) => v.trim()).some((v) => v.toLowerCase() === "chunked") ?? false;
|
|
1456
|
+
if (!isChunked && (!length || length === 0)) return resolve(null);
|
|
1457
|
+
const maxLength = readSize(opts.limit) || 102400;
|
|
1458
|
+
if (!isChunked && length > maxLength)
|
|
1459
|
+
return reject({ status: 413, message: "Content Too Large" });
|
|
1460
|
+
const encoding = req.headers["content-encoding"];
|
|
1461
|
+
if (encoding && (opts.inflate === false || !DECOMPRESS_ALGO[encoding]))
|
|
1462
|
+
return reject({ status: 415, message: "Unsupported Media Type: Wrong Content-Encoding" });
|
|
1463
|
+
const decompress = (encoding ? DECOMPRESS_ALGO[encoding] : void 0) ?? ((d, c) => c(null, d));
|
|
1464
|
+
const contentType = req.headers["content-type"] ?? "";
|
|
1465
|
+
if (mimetype && contentType.split(";")[0].trim() !== mimetype)
|
|
1466
|
+
return reject({ status: 415, message: "Unsupported Media Type: Wrong Content-Type" });
|
|
1467
|
+
let data = Buffer.alloc(0);
|
|
1468
|
+
req.on("data", (chunk) => {
|
|
1469
|
+
if (data === null) return;
|
|
1470
|
+
const next_ = Buffer.concat([data, chunk]);
|
|
1471
|
+
if (next_.length > maxLength) {
|
|
1472
|
+
data = null;
|
|
1473
|
+
reject({ status: 413, message: "Content Too Large" });
|
|
1474
|
+
return;
|
|
1475
|
+
}
|
|
1476
|
+
data = next_;
|
|
1477
|
+
});
|
|
1478
|
+
req.on("end", () => {
|
|
1479
|
+
if (data === null) return;
|
|
1480
|
+
decompress(data, (err, decompressed) => {
|
|
1481
|
+
if (err) return reject({ status: 500, message: err.message });
|
|
1482
|
+
const body = decompressed;
|
|
1483
|
+
if (opts.verify && res) {
|
|
1484
|
+
try {
|
|
1485
|
+
opts.verify(req, res, body, extractCharset(contentType));
|
|
1486
|
+
} catch (e) {
|
|
1487
|
+
const status = e.status ?? e.statusCode ?? 403;
|
|
1488
|
+
return reject({ status, message: e.message ?? "Forbidden" });
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
resolve({ mimetype: contentType ?? "", content: body });
|
|
1492
|
+
});
|
|
1493
|
+
});
|
|
1494
|
+
});
|
|
1495
|
+
}
|
|
1496
|
+
function readBodyAsPlainText(req, res, next, contentType, data) {
|
|
1497
|
+
const charset = extractCharset(contentType);
|
|
1498
|
+
try {
|
|
1499
|
+
req.body = data.toString(charset);
|
|
1500
|
+
next();
|
|
1501
|
+
} catch (ex) {
|
|
1502
|
+
res.status(500).send(ex.message);
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
function readBodyAsJson(req, res, next, opts, contentType, data) {
|
|
1506
|
+
const charset = extractCharset(contentType);
|
|
1507
|
+
try {
|
|
1508
|
+
const parsed = JSON.parse(
|
|
1509
|
+
data.toString(charset),
|
|
1510
|
+
opts.reviver ?? void 0
|
|
1511
|
+
);
|
|
1512
|
+
if (opts.strict && (typeof parsed !== "object" || parsed === null)) {
|
|
1513
|
+
return void res.status(400).send("Bad Request: JSON body must be an object or array");
|
|
1514
|
+
}
|
|
1515
|
+
req.body = parsed;
|
|
1516
|
+
next();
|
|
1517
|
+
} catch (ex) {
|
|
1518
|
+
res.status(400).send("Bad Request: " + ex.message);
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
function parseMultipartBody(contentType, data) {
|
|
1522
|
+
const boundary = contentType.split(";").map((s) => s.replace(/^\s+|\s+$/g, "")).find((s) => s.startsWith("boundary="))?.substring("boundary=".length);
|
|
1523
|
+
if (!boundary)
|
|
1524
|
+
throw { status: 400, message: "Bad Request: missing multipart boundary" };
|
|
1525
|
+
const delimiter = Buffer.from(`\r
|
|
1526
|
+
--${boundary}`);
|
|
1527
|
+
const normalized = Buffer.concat([Buffer.from("\r\n"), data]);
|
|
1528
|
+
const rawParts = splitBuffer(normalized, delimiter);
|
|
1529
|
+
const parts = [];
|
|
1530
|
+
for (const part of rawParts) {
|
|
1531
|
+
if (part.toString("utf8", 0, 2) === "--") continue;
|
|
1532
|
+
const partContent = part.slice(2);
|
|
1533
|
+
const blankLine = Buffer.from("\r\n\r\n");
|
|
1534
|
+
const blankIdx = partContent.indexOf(blankLine);
|
|
1535
|
+
if (blankIdx === -1) continue;
|
|
1536
|
+
const headerSection = partContent.slice(0, blankIdx).toString("utf8");
|
|
1537
|
+
const content = partContent.slice(blankIdx + blankLine.length);
|
|
1538
|
+
const headers = {};
|
|
1539
|
+
for (const line of headerSection.split("\r\n")) {
|
|
1540
|
+
if (!line) continue;
|
|
1541
|
+
const colonIdx = line.indexOf(":");
|
|
1542
|
+
if (colonIdx === -1) continue;
|
|
1543
|
+
const key = line.substring(0, colonIdx).replace(/^\s+|\s+$/g, "").toLowerCase();
|
|
1544
|
+
const value = line.substring(colonIdx + 1).replace(/^\s+|\s+$/g, "");
|
|
1545
|
+
headers[key] = value;
|
|
1546
|
+
}
|
|
1547
|
+
parts.push({ headers, content });
|
|
1548
|
+
}
|
|
1549
|
+
return parts;
|
|
1550
|
+
}
|
|
1551
|
+
function readBodyAsFormData(req, res, next, contentType, data) {
|
|
1552
|
+
try {
|
|
1553
|
+
req.body = parseMultipartBody(contentType, data);
|
|
1554
|
+
next();
|
|
1555
|
+
} catch (ex) {
|
|
1556
|
+
const status = ex.status ?? 500;
|
|
1557
|
+
res.status(status).send(ex.message ?? String(ex));
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
function readBodyAsFormEncoded(req, res, next, contentType, data) {
|
|
1561
|
+
const charset = extractCharset(contentType);
|
|
1562
|
+
try {
|
|
1563
|
+
const params = new URLSearchParams(data.toString(charset));
|
|
1564
|
+
const result = {};
|
|
1565
|
+
for (const [key, value] of params.entries()) {
|
|
1566
|
+
const existing = result[key];
|
|
1567
|
+
if (existing === void 0) {
|
|
1568
|
+
result[key] = value;
|
|
1569
|
+
} else if (Array.isArray(existing)) {
|
|
1570
|
+
existing.push(value);
|
|
1571
|
+
} else {
|
|
1572
|
+
result[key] = [existing, value];
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
req.body = result;
|
|
1576
|
+
next();
|
|
1577
|
+
} catch (ex) {
|
|
1578
|
+
res.status(400).send("Bad Request: " + ex.message);
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
var BODY_READERS = {
|
|
1582
|
+
"multipart/form-data": (req, res, next, _opts, ct, data) => readBodyAsFormData(req, res, next, ct, data),
|
|
1583
|
+
"application/json": (req, res, next, opts, ct, data) => readBodyAsJson(req, res, next, opts, ct, data),
|
|
1584
|
+
"application/x-www-form-urlencoded": (req, res, next, _opts, ct, data) => readBodyAsFormEncoded(req, res, next, ct, data),
|
|
1585
|
+
"text/plain": (req, res, next, _opts, ct, data) => readBodyAsPlainText(req, res, next, ct, data)
|
|
1586
|
+
};
|
|
1587
|
+
function json(opts) {
|
|
1588
|
+
const resolved = resolveBodyOptions(opts, "application/json");
|
|
1589
|
+
return (req, res, next) => {
|
|
1590
|
+
readBody(req, res, resolved, resolved.type, next, (contentType, body) => {
|
|
1591
|
+
readBodyAsJson(req, res, next, resolved, contentType, body);
|
|
1592
|
+
});
|
|
1593
|
+
};
|
|
1594
|
+
}
|
|
1595
|
+
function formData(opts) {
|
|
1596
|
+
const resolved = resolveBodyOptions(opts, "multipart/form-data");
|
|
1597
|
+
return (req, res, next) => {
|
|
1598
|
+
readBody(req, res, resolved, resolved.type, next, (contentType, body) => {
|
|
1599
|
+
readBodyAsFormData(req, res, next, contentType, body);
|
|
1600
|
+
});
|
|
1601
|
+
};
|
|
1602
|
+
}
|
|
1603
|
+
function formEncoded(opts) {
|
|
1604
|
+
const resolved = resolveBodyOptions(opts, "application/x-www-form-urlencoded");
|
|
1605
|
+
return (req, res, next) => {
|
|
1606
|
+
readBody(req, res, resolved, resolved.type, next, (contentType, body) => {
|
|
1607
|
+
readBodyAsFormEncoded(req, res, next, contentType, body);
|
|
1608
|
+
});
|
|
1609
|
+
};
|
|
1610
|
+
}
|
|
1611
|
+
function parseBody(opts) {
|
|
1612
|
+
const resolved = resolveBodyOptions(opts, null);
|
|
1613
|
+
return (req, res, next) => {
|
|
1614
|
+
readBody(req, res, resolved, resolved.type, next, (contentType, body) => {
|
|
1615
|
+
const mimetype = contentType.split(";")[0].trim();
|
|
1616
|
+
if (!BODY_READERS[mimetype])
|
|
1617
|
+
return res.status(415).send("Unsupported Media Type");
|
|
1618
|
+
BODY_READERS[mimetype](req, res, next, resolved, contentType, body);
|
|
1619
|
+
});
|
|
1620
|
+
};
|
|
1621
|
+
}
|
|
1622
|
+
function raw(opts) {
|
|
1623
|
+
const resolved = resolveBodyOptions(opts, "application/octet-stream");
|
|
1624
|
+
return (req, res, next) => {
|
|
1625
|
+
readBody(req, res, resolved, resolved.type, next, (_contentType, body) => {
|
|
1626
|
+
req.body = body;
|
|
1627
|
+
next();
|
|
1628
|
+
});
|
|
1629
|
+
};
|
|
1630
|
+
}
|
|
1631
|
+
function text(opts) {
|
|
1632
|
+
const resolved = resolveBodyOptions(opts, "text/plain");
|
|
1633
|
+
return (req, res, next) => {
|
|
1634
|
+
readBody(req, res, resolved, resolved.type, next, (contentType, body) => {
|
|
1635
|
+
readBodyAsPlainText(req, res, next, contentType, body);
|
|
1636
|
+
});
|
|
1637
|
+
};
|
|
1638
|
+
}
|
|
1639
|
+
async function* streamFormData(req, opts) {
|
|
1640
|
+
const maxSize = (opts?.limit !== void 0 ? readSize(opts.limit) : 0) || 102400;
|
|
1641
|
+
const chunks = [];
|
|
1642
|
+
let totalSize = 0;
|
|
1643
|
+
for await (const chunk of req) {
|
|
1644
|
+
totalSize += chunk.length;
|
|
1645
|
+
if (totalSize > maxSize) throw { status: 413, message: "Content Too Large" };
|
|
1646
|
+
chunks.push(chunk);
|
|
1647
|
+
}
|
|
1648
|
+
const body = Buffer.concat(chunks);
|
|
1649
|
+
const contentType = req.headers["content-type"] ?? "";
|
|
1650
|
+
const parts = parseMultipartBody(contentType, body);
|
|
1651
|
+
for (const part of parts) {
|
|
1652
|
+
yield { headers: part.headers, stream: import_stream.Readable.from(part.content) };
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
var STATUS_COLORS = [
|
|
1656
|
+
"\x1B[0m",
|
|
1657
|
+
// 0 — fallback / unknown
|
|
1658
|
+
"\x1B[33m",
|
|
1659
|
+
// 1xx — informational (yellow)
|
|
1660
|
+
"\x1B[32m",
|
|
1661
|
+
// 2xx — success (green)
|
|
1662
|
+
"\x1B[33m",
|
|
1663
|
+
// 3xx — redirection (yellow)
|
|
1664
|
+
"\x1B[31m",
|
|
1665
|
+
// 4xx — client error (red)
|
|
1666
|
+
"\x1B[91m"
|
|
1667
|
+
// 5xx — server error (bright red)
|
|
1668
|
+
];
|
|
1669
|
+
var ANSI_RESET = "\x1B[0m";
|
|
1670
|
+
function logger(opts) {
|
|
1671
|
+
const options = opts ?? {};
|
|
1672
|
+
const log = options.logger ?? console.log;
|
|
1673
|
+
const formatter = new Intl.DateTimeFormat(
|
|
1674
|
+
options.locale ?? "en-GB",
|
|
1675
|
+
options.dateFormat ?? {
|
|
1676
|
+
month: "short",
|
|
1677
|
+
day: "2-digit",
|
|
1678
|
+
hour: "2-digit",
|
|
1679
|
+
minute: "2-digit"
|
|
1680
|
+
}
|
|
1681
|
+
);
|
|
1682
|
+
return (req, res, next) => {
|
|
1683
|
+
const timestamp = formatter.format(/* @__PURE__ */ new Date());
|
|
1684
|
+
const requestPath = req.path ?? req.url ?? "/";
|
|
1685
|
+
const ip = req.ip ?? "";
|
|
1686
|
+
const user = options.user?.(req) ?? "-";
|
|
1687
|
+
const receivedAt = Date.now();
|
|
1688
|
+
const tracker = options.track === true ? setTimeout(() => {
|
|
1689
|
+
log(`${timestamp} LOST ${req.method} ${requestPath} ${ip} <${user}>`);
|
|
1690
|
+
}, options.trackTimeout ?? 3e4) : null;
|
|
1691
|
+
res.on("finish", () => {
|
|
1692
|
+
if (tracker !== null) clearTimeout(tracker);
|
|
1693
|
+
const host = req.headers.host;
|
|
1694
|
+
const elapsed = Date.now() - receivedAt;
|
|
1695
|
+
const statusClass = Math.floor(res.statusCode / 100);
|
|
1696
|
+
const colour = STATUS_COLORS[statusClass] ?? STATUS_COLORS[0];
|
|
1697
|
+
const statusStr = `${colour}${res.statusCode}${ANSI_RESET}`;
|
|
1698
|
+
const contentLen = res.getHeader("content-length") ?? "-";
|
|
1699
|
+
if (options.json === true)
|
|
1700
|
+
log({
|
|
1701
|
+
timestamp,
|
|
1702
|
+
status: res.statusCode,
|
|
1703
|
+
method: req.method,
|
|
1704
|
+
path: requestPath,
|
|
1705
|
+
ip,
|
|
1706
|
+
user,
|
|
1707
|
+
elapsed,
|
|
1708
|
+
host,
|
|
1709
|
+
length: contentLen
|
|
1710
|
+
});
|
|
1711
|
+
else
|
|
1712
|
+
log(`${timestamp} ${statusStr} ${req.method} ${requestPath} ${ip} <${user}> ${elapsed}ms (${String(contentLen)})`);
|
|
1713
|
+
});
|
|
1714
|
+
next();
|
|
1715
|
+
};
|
|
1716
|
+
}
|
|
1717
|
+
function resolveAllowOrigin(origin, requestOrigin) {
|
|
1718
|
+
if (typeof origin === "string") return origin;
|
|
1719
|
+
return origin.includes(requestOrigin) ? requestOrigin : void 0;
|
|
1720
|
+
}
|
|
1721
|
+
function cors(opts) {
|
|
1722
|
+
const options = {
|
|
1723
|
+
origin: opts?.origin ?? "*",
|
|
1724
|
+
allowHeaders: opts?.allowHeaders ?? "Accept, Content-Type, Authorization",
|
|
1725
|
+
allowMethods: opts?.allowMethods ?? "GET,HEAD,PUT,PATCH,POST,DELETE",
|
|
1726
|
+
allowCredentials: opts?.allowCredentials,
|
|
1727
|
+
maxAge: opts?.maxAge,
|
|
1728
|
+
vary: opts?.vary,
|
|
1729
|
+
optionsStatus: opts?.optionsStatus ?? 204,
|
|
1730
|
+
preflight: opts?.preflight
|
|
1731
|
+
};
|
|
1732
|
+
return (req, res, next) => {
|
|
1733
|
+
if (options.preflight && !options.preflight(req)) {
|
|
1734
|
+
res.status(req.method == "OPTIONS" ? 403 : 400).end();
|
|
1735
|
+
return;
|
|
1736
|
+
}
|
|
1737
|
+
if (req.headers.origin) {
|
|
1738
|
+
const allowOrigin = resolveAllowOrigin(options.origin, req.headers.origin);
|
|
1739
|
+
if (allowOrigin !== void 0) {
|
|
1740
|
+
res.setHeader("Access-Control-Allow-Origin", allowOrigin);
|
|
1741
|
+
if (options.vary !== void 0)
|
|
1742
|
+
res.setHeader("Vary", options.vary);
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
if (req.method == "OPTIONS") {
|
|
1746
|
+
res.setHeader("Access-Control-Allow-Headers", options.allowHeaders);
|
|
1747
|
+
res.setHeader("Access-Control-Allow-Methods", options.allowMethods);
|
|
1748
|
+
if (options.allowCredentials !== void 0)
|
|
1749
|
+
res.setHeader("Access-Control-Allow-Credentials", options.allowCredentials ? "true" : "false");
|
|
1750
|
+
if (options.maxAge !== void 0)
|
|
1751
|
+
res.setHeader("Access-Control-Max-Age", options.maxAge.toFixed(0));
|
|
1752
|
+
res.status(options.optionsStatus).end();
|
|
1753
|
+
return;
|
|
1754
|
+
}
|
|
1755
|
+
next();
|
|
1756
|
+
};
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
// src/http-objects.ts
|
|
1760
|
+
function decodeJsonCookie(raw2) {
|
|
1761
|
+
if (!raw2.startsWith("j:")) return raw2;
|
|
1762
|
+
try {
|
|
1763
|
+
return JSON.parse(raw2.slice(2));
|
|
1764
|
+
} catch {
|
|
1765
|
+
return raw2;
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
function encodeCookieValue(value) {
|
|
1769
|
+
return encodeURIComponent(value);
|
|
1770
|
+
}
|
|
1771
|
+
function decodeCookieValue(raw2) {
|
|
1772
|
+
let val = raw2;
|
|
1773
|
+
if (val.length >= 2 && val.charCodeAt(0) === 34 && val.charCodeAt(val.length - 1) === 34)
|
|
1774
|
+
val = val.slice(1, -1);
|
|
1775
|
+
try {
|
|
1776
|
+
return decodeURIComponent(val);
|
|
1777
|
+
} catch {
|
|
1778
|
+
return val;
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
function signCookieValue(value, secret) {
|
|
1782
|
+
const sig = crypto.createHmac("sha256", secret).update(value).digest("base64url");
|
|
1783
|
+
return `s:${value}.${sig}`;
|
|
1784
|
+
}
|
|
1785
|
+
function verifyCookieValue(signed, secret) {
|
|
1786
|
+
if (!signed.startsWith("s:")) return false;
|
|
1787
|
+
const withoutPrefix = signed.slice(2);
|
|
1788
|
+
const lastDot = withoutPrefix.lastIndexOf(".");
|
|
1789
|
+
if (lastDot === -1) return false;
|
|
1790
|
+
const value = withoutPrefix.slice(0, lastDot);
|
|
1791
|
+
const received = withoutPrefix.slice(lastDot + 1);
|
|
1792
|
+
const expected = crypto.createHmac("sha256", secret).update(value).digest("base64url");
|
|
1793
|
+
const receivedBuf = Buffer.from(received, "base64url");
|
|
1794
|
+
const expectedBuf = Buffer.from(expected, "base64url");
|
|
1795
|
+
if (receivedBuf.length !== expectedBuf.length) return false;
|
|
1796
|
+
if (!crypto.timingSafeEqual(receivedBuf, expectedBuf)) return false;
|
|
1797
|
+
return value;
|
|
1798
|
+
}
|
|
1799
|
+
var kSecret = /* @__PURE__ */ Symbol("expediate.secret");
|
|
1800
|
+
var kRes = /* @__PURE__ */ Symbol("expediate.res");
|
|
1801
|
+
function resolveReqOpts(opts) {
|
|
1802
|
+
return {
|
|
1803
|
+
limit: opts?.limit ?? "100kb",
|
|
1804
|
+
inflate: opts?.inflate ?? true,
|
|
1805
|
+
reviver: null,
|
|
1806
|
+
strict: opts?.strict ?? false,
|
|
1807
|
+
// readReqBody takes its expected mimetype as an explicit argument, so the
|
|
1808
|
+
// type matcher here is unused; null keeps the object shape-compatible.
|
|
1809
|
+
type: null,
|
|
1810
|
+
verify: opts?.verify ?? null
|
|
1811
|
+
};
|
|
1812
|
+
}
|
|
1813
|
+
var STATUS_MESSAGES = {
|
|
1814
|
+
100: "Continue",
|
|
1815
|
+
101: "Switching Protocols",
|
|
1816
|
+
102: "Processing",
|
|
1817
|
+
200: "OK",
|
|
1818
|
+
201: "Created",
|
|
1819
|
+
202: "Accepted",
|
|
1820
|
+
204: "No Content",
|
|
1821
|
+
206: "Partial Content",
|
|
1822
|
+
207: "Multi-Status",
|
|
1823
|
+
300: "Multiple Choices",
|
|
1824
|
+
301: "Moved Permanently",
|
|
1825
|
+
302: "Found",
|
|
1826
|
+
303: "See Other",
|
|
1827
|
+
304: "Not Modified",
|
|
1828
|
+
307: "Temporary Redirect",
|
|
1829
|
+
308: "Permanent Redirect",
|
|
1830
|
+
400: "Bad Request",
|
|
1831
|
+
401: "Unauthorized",
|
|
1832
|
+
402: "Payment Required",
|
|
1833
|
+
403: "Forbidden",
|
|
1834
|
+
404: "Not Found",
|
|
1835
|
+
405: "Method Not Allowed",
|
|
1836
|
+
406: "Not Acceptable",
|
|
1837
|
+
408: "Request Timeout",
|
|
1838
|
+
409: "Conflict",
|
|
1839
|
+
410: "Gone",
|
|
1840
|
+
411: "Length Required",
|
|
1841
|
+
413: "Payload Too Large",
|
|
1842
|
+
415: "Unsupported Media Type",
|
|
1843
|
+
422: "Unprocessable Entity",
|
|
1844
|
+
429: "Too Many Requests",
|
|
1845
|
+
500: "Internal Server Error",
|
|
1846
|
+
501: "Not Implemented",
|
|
1847
|
+
502: "Bad Gateway",
|
|
1848
|
+
503: "Service Unavailable",
|
|
1849
|
+
504: "Gateway Timeout"
|
|
1850
|
+
};
|
|
1851
|
+
var requestHelpers = {
|
|
1852
|
+
/** Read and parse the request body as JSON (cached after first read). */
|
|
1853
|
+
json(opts) {
|
|
1854
|
+
if ("body" in this) return Promise.resolve(this.body ?? null);
|
|
1855
|
+
return readReqBody(this, resolveReqOpts(opts), "application/json", this[kRes]).then((ret) => {
|
|
1856
|
+
if (ret == null) return null;
|
|
1857
|
+
const charset = extractCharset(ret.mimetype);
|
|
1858
|
+
try {
|
|
1859
|
+
const parsed = JSON.parse(
|
|
1860
|
+
ret.content.toString(charset),
|
|
1861
|
+
opts?.reviver ?? void 0
|
|
1862
|
+
);
|
|
1863
|
+
this.body = parsed;
|
|
1864
|
+
return parsed;
|
|
1865
|
+
} catch (ex) {
|
|
1866
|
+
return Promise.reject({ status: 400, message: "Bad Request: " + ex.message });
|
|
1867
|
+
}
|
|
1868
|
+
});
|
|
1869
|
+
},
|
|
1870
|
+
/** Read and decode the request body as plain text (cached after first read). */
|
|
1871
|
+
text(opts) {
|
|
1872
|
+
const cached = this.body;
|
|
1873
|
+
if (typeof cached === "string") return Promise.resolve(cached);
|
|
1874
|
+
return readReqBody(this, resolveReqOpts(opts), null, this[kRes]).then((ret) => {
|
|
1875
|
+
if (ret == null) return null;
|
|
1876
|
+
const charset = extractCharset(ret.mimetype);
|
|
1877
|
+
return ret.content.toString(charset);
|
|
1878
|
+
});
|
|
1879
|
+
},
|
|
1880
|
+
/** Read and parse the request body as `multipart/form-data` (cached). */
|
|
1881
|
+
formData(opts) {
|
|
1882
|
+
const cached = this.body;
|
|
1883
|
+
if (Array.isArray(cached)) return Promise.resolve(cached);
|
|
1884
|
+
return readReqBody(this, resolveReqOpts(opts), "multipart/form-data", this[kRes]).then((ret) => {
|
|
1885
|
+
if (ret == null) return null;
|
|
1886
|
+
try {
|
|
1887
|
+
const parts = parseMultipartBody(ret.mimetype, ret.content);
|
|
1888
|
+
this.body = parts;
|
|
1889
|
+
return parts;
|
|
1890
|
+
} catch (ex) {
|
|
1891
|
+
const e = ex;
|
|
1892
|
+
return Promise.reject({ status: e.status ?? 500, message: e.message ?? String(ex) });
|
|
1893
|
+
}
|
|
1894
|
+
});
|
|
1895
|
+
},
|
|
1896
|
+
/** Read a request header by name (case-insensitive; referer/referrer alias). */
|
|
1897
|
+
header(name) {
|
|
1898
|
+
const key = name.toLowerCase();
|
|
1899
|
+
if (key === "referer" || key === "referrer")
|
|
1900
|
+
return this.headers.referer ?? this.headers.referrer;
|
|
1901
|
+
return this.headers[key];
|
|
1902
|
+
}
|
|
1903
|
+
};
|
|
1904
|
+
var responseHelpers = {
|
|
1905
|
+
send(data) {
|
|
1906
|
+
if (data) this.write(data);
|
|
1907
|
+
this.end();
|
|
1908
|
+
},
|
|
1909
|
+
json(data) {
|
|
1910
|
+
this.setHeader("Content-Type", "application/json");
|
|
1911
|
+
this.write(JSON.stringify(data));
|
|
1912
|
+
this.end();
|
|
1913
|
+
},
|
|
1914
|
+
status(code, headers) {
|
|
1915
|
+
if (!Number.isInteger(code) || code < 100 || code > 999)
|
|
1916
|
+
throw new RangeError(`Invalid status code: ${code}. Must be an integer between 100 and 999.`);
|
|
1917
|
+
this.statusCode = code;
|
|
1918
|
+
if (headers)
|
|
1919
|
+
for (const [k, v] of Object.entries(headers)) this.setHeader(k, v);
|
|
1920
|
+
return this;
|
|
1921
|
+
},
|
|
1922
|
+
redirect(url) {
|
|
1923
|
+
this.setHeader("location", url);
|
|
1924
|
+
this.writeHead(302);
|
|
1925
|
+
this.write(`Found. Redirecting to ${url}`);
|
|
1926
|
+
this.end();
|
|
1927
|
+
},
|
|
1928
|
+
cookie(name, value, options) {
|
|
1929
|
+
const opts = options ?? {};
|
|
1930
|
+
let val = typeof value === "object" ? "j:" + JSON.stringify(value) : String(value);
|
|
1931
|
+
if (opts.signed) {
|
|
1932
|
+
const secret = this[kSecret];
|
|
1933
|
+
if (!secret)
|
|
1934
|
+
throw new Error(
|
|
1935
|
+
"Signed cookies require a secret \u2014 pass { secret } to createRouter()"
|
|
1936
|
+
);
|
|
1937
|
+
val = signCookieValue(val, secret);
|
|
1938
|
+
}
|
|
1939
|
+
let txt = `${name}=${encodeCookieValue(val)}`;
|
|
1940
|
+
if (opts.maxAge != null) {
|
|
1941
|
+
const maxAgeMs = opts.maxAge;
|
|
1942
|
+
const maxAgeSec = Math.floor(maxAgeMs / 1e3);
|
|
1943
|
+
if (maxAgeMs > 0) opts.expires = new Date(Date.now() + maxAgeMs);
|
|
1944
|
+
txt += `; Max-Age=${maxAgeSec}`;
|
|
1945
|
+
}
|
|
1946
|
+
if (opts.expires) txt += `; Expires=${opts.expires.toUTCString()}`;
|
|
1947
|
+
txt += `; Path=${opts.path ?? "/"}`;
|
|
1948
|
+
if (opts.httpOnly) txt += "; HttpOnly";
|
|
1949
|
+
if (opts.secure) txt += "; Secure";
|
|
1950
|
+
if (opts.sameSite) txt += `; SameSite=${opts.sameSite}`;
|
|
1951
|
+
const existing = this.getHeader("Set-Cookie");
|
|
1952
|
+
if (existing == null) {
|
|
1953
|
+
this.setHeader("Set-Cookie", txt);
|
|
1954
|
+
} else if (Array.isArray(existing)) {
|
|
1955
|
+
this.setHeader("Set-Cookie", [...existing, txt]);
|
|
1956
|
+
} else {
|
|
1957
|
+
this.setHeader("Set-Cookie", [existing, txt]);
|
|
1958
|
+
}
|
|
1959
|
+
return this;
|
|
1960
|
+
},
|
|
1961
|
+
download(filepath, filename) {
|
|
1962
|
+
const rReq = this.req;
|
|
1963
|
+
const name = filename ?? path.basename(filepath);
|
|
1964
|
+
const safeName = name.replace(/"/g, '\\"');
|
|
1965
|
+
this.setHeader("Content-Disposition", `attachment; filename="${safeName}"`);
|
|
1966
|
+
fs2.access(filepath, fs2.constants.F_OK, (err) => {
|
|
1967
|
+
if (err) {
|
|
1968
|
+
if (!this.writableEnded) this.status(404).end("Not Found");
|
|
1969
|
+
return;
|
|
1970
|
+
}
|
|
1971
|
+
serveFile(filepath)(rReq, this, () => {
|
|
1972
|
+
});
|
|
1973
|
+
});
|
|
1974
|
+
},
|
|
1975
|
+
type(mimeType) {
|
|
1976
|
+
this.setHeader("Content-Type", mimeType);
|
|
1977
|
+
return this;
|
|
1978
|
+
},
|
|
1979
|
+
etag(value, strong = false) {
|
|
1980
|
+
this.setHeader("ETag", strong ? `"${value}"` : `W/"${value}"`);
|
|
1981
|
+
return this;
|
|
1982
|
+
},
|
|
1983
|
+
header(field, value) {
|
|
1984
|
+
this.setHeader(field, value);
|
|
1985
|
+
return this;
|
|
1986
|
+
},
|
|
1987
|
+
append(field, value) {
|
|
1988
|
+
const existing = this.getHeader(field);
|
|
1989
|
+
if (existing == null) {
|
|
1990
|
+
this.setHeader(field, value);
|
|
1991
|
+
} else if (field.toLowerCase() === "set-cookie") {
|
|
1992
|
+
const prev = Array.isArray(existing) ? existing : [String(existing)];
|
|
1993
|
+
const next = Array.isArray(value) ? value : [value];
|
|
1994
|
+
this.setHeader(field, [...prev, ...next]);
|
|
1995
|
+
} else {
|
|
1996
|
+
const prev = Array.isArray(existing) ? existing.join(", ") : String(existing);
|
|
1997
|
+
const added = Array.isArray(value) ? value.join(", ") : value;
|
|
1998
|
+
this.setHeader(field, `${prev}, ${added}`);
|
|
1999
|
+
}
|
|
2000
|
+
return this;
|
|
2001
|
+
},
|
|
2002
|
+
vary(field) {
|
|
2003
|
+
const fields = Array.isArray(field) ? field : [field];
|
|
2004
|
+
const existing = this.getHeader("Vary");
|
|
2005
|
+
const current = existing ? (Array.isArray(existing) ? existing : [String(existing)]).join(", ").split(",").map((s) => s.trim().toLowerCase()) : [];
|
|
2006
|
+
for (const f of fields) {
|
|
2007
|
+
if (!current.includes(f.toLowerCase())) {
|
|
2008
|
+
current.push(f.toLowerCase());
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
this.setHeader("Vary", current.join(", "));
|
|
2012
|
+
return this;
|
|
2013
|
+
},
|
|
2014
|
+
location(url) {
|
|
2015
|
+
this.setHeader("Location", url);
|
|
2016
|
+
return this;
|
|
2017
|
+
},
|
|
2018
|
+
clearCookie(name, options) {
|
|
2019
|
+
const opts = { ...options, expires: /* @__PURE__ */ new Date(0), maxAge: 0 };
|
|
2020
|
+
delete opts.signed;
|
|
2021
|
+
this.cookie(name, "", opts);
|
|
2022
|
+
return this;
|
|
2023
|
+
},
|
|
2024
|
+
sendStatus(code) {
|
|
2025
|
+
this.setHeader("Content-Type", "text/plain");
|
|
2026
|
+
this.statusCode = code;
|
|
2027
|
+
this.end(STATUS_MESSAGES[code] ?? String(code));
|
|
2028
|
+
},
|
|
2029
|
+
attachment(filename) {
|
|
2030
|
+
if (filename) {
|
|
2031
|
+
const mimeType = mime.lookup(filename, "application/octet-stream");
|
|
2032
|
+
this.setHeader("Content-Type", mimeType);
|
|
2033
|
+
const safeName = path.basename(filename).replace(/"/g, '\\"');
|
|
2034
|
+
this.setHeader("Content-Disposition", `attachment; filename="${safeName}"`);
|
|
2035
|
+
} else {
|
|
2036
|
+
this.setHeader("Content-Disposition", "attachment");
|
|
2037
|
+
}
|
|
2038
|
+
return this;
|
|
2039
|
+
}
|
|
2040
|
+
};
|
|
2041
|
+
var reqProtoCache = /* @__PURE__ */ new WeakMap();
|
|
2042
|
+
var resProtoCache = /* @__PURE__ */ new WeakMap();
|
|
2043
|
+
function ensureProto(cache, obj, helpers) {
|
|
2044
|
+
const base = Object.getPrototypeOf(obj);
|
|
2045
|
+
let proto = cache.get(base);
|
|
2046
|
+
if (proto === void 0) {
|
|
2047
|
+
proto = Object.assign(Object.create(base), helpers);
|
|
2048
|
+
cache.set(base, proto);
|
|
2049
|
+
}
|
|
2050
|
+
return proto;
|
|
2051
|
+
}
|
|
2052
|
+
function updateHttpObjects(req, res, secret, trustProxy) {
|
|
2053
|
+
const rReq = req;
|
|
2054
|
+
const rRes = res;
|
|
2055
|
+
if (rReq.queries) return;
|
|
2056
|
+
Object.setPrototypeOf(req, ensureProto(reqProtoCache, req, requestHelpers));
|
|
2057
|
+
Object.setPrototypeOf(res, ensureProto(resProtoCache, res, responseHelpers));
|
|
2058
|
+
rReq.queries = {};
|
|
2059
|
+
if (trustProxy) {
|
|
2060
|
+
const xff = req.headers["x-forwarded-for"];
|
|
2061
|
+
const xffStr = Array.isArray(xff) ? xff.join(",") : xff ?? "";
|
|
2062
|
+
rReq.ips = xffStr ? xffStr.split(",").map((s) => s.trim()) : [];
|
|
2063
|
+
rReq.ip = rReq.ips[0] ?? req.socket?.remoteAddress ?? "";
|
|
2064
|
+
const xProto = req.headers["x-forwarded-proto"];
|
|
2065
|
+
rReq.protocol = (Array.isArray(xProto) ? xProto[0] : xProto)?.split(",")[0].trim() ?? "http";
|
|
2066
|
+
const xHost = req.headers["x-forwarded-host"];
|
|
2067
|
+
const hostHeader = (Array.isArray(xHost) ? xHost[0] : xHost) ?? req.headers.host ?? "";
|
|
2068
|
+
rReq.hostname = hostHeader.replace(/:\d+$/, "");
|
|
2069
|
+
} else {
|
|
2070
|
+
rReq.ip = req.socket?.remoteAddress ?? "";
|
|
2071
|
+
rReq.ips = [];
|
|
2072
|
+
rReq.protocol = req.socket?.encrypted ? "https" : "http";
|
|
2073
|
+
rReq.hostname = (req.headers.host ?? "").replace(/:\d+$/, "");
|
|
2074
|
+
}
|
|
2075
|
+
rReq.secure = rReq.protocol === "https";
|
|
2076
|
+
rReq.baseUrl = rReq.baseUrl ?? "";
|
|
2077
|
+
const qry = new URL(`http://${req.headers.host}${req.url}`);
|
|
2078
|
+
rReq.originalUrl = req.url;
|
|
2079
|
+
rReq.path = qry.pathname;
|
|
2080
|
+
const urlParams = {};
|
|
2081
|
+
for (const [key, value] of qry.searchParams.entries()) {
|
|
2082
|
+
const existing = urlParams[key];
|
|
2083
|
+
if (existing === void 0) {
|
|
2084
|
+
urlParams[key] = value;
|
|
2085
|
+
} else if (Array.isArray(existing)) {
|
|
2086
|
+
existing.push(value);
|
|
2087
|
+
} else {
|
|
2088
|
+
urlParams[key] = [existing, value];
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
rReq.queries.url = urlParams;
|
|
2092
|
+
const flatParams = {};
|
|
2093
|
+
for (const [key, value] of Object.entries(urlParams)) {
|
|
2094
|
+
flatParams[key] = Array.isArray(value) ? value[0] : value;
|
|
2095
|
+
}
|
|
2096
|
+
rReq.params = flatParams;
|
|
2097
|
+
rReq.query = urlParams;
|
|
2098
|
+
if (rReq.cookies == null) {
|
|
2099
|
+
rReq.cookies = {};
|
|
2100
|
+
if (req.headers.cookie) {
|
|
2101
|
+
for (const part of req.headers.cookie.split(";")) {
|
|
2102
|
+
const eqIdx = part.indexOf("=");
|
|
2103
|
+
if (eqIdx === -1) continue;
|
|
2104
|
+
const name = part.slice(0, eqIdx).trim();
|
|
2105
|
+
const rawVal = decodeCookieValue(part.slice(eqIdx + 1).trim());
|
|
2106
|
+
if (rawVal.startsWith("s:")) {
|
|
2107
|
+
if (secret) {
|
|
2108
|
+
const inner = verifyCookieValue(rawVal, secret);
|
|
2109
|
+
if (inner === false) continue;
|
|
2110
|
+
rReq.cookies[name] = decodeJsonCookie(inner);
|
|
2111
|
+
} else {
|
|
2112
|
+
rReq.cookies[name] = rawVal;
|
|
2113
|
+
}
|
|
2114
|
+
} else {
|
|
2115
|
+
rReq.cookies[name] = decodeJsonCookie(rawVal);
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
rRes[kSecret] = secret;
|
|
2121
|
+
rReq[kRes] = rRes;
|
|
2122
|
+
rRes.locals = {};
|
|
2123
|
+
rRes.setHeader("X-Powered-By", "Expediate");
|
|
808
2124
|
}
|
|
809
2125
|
|
|
810
2126
|
// src/router.ts
|
|
@@ -839,10 +2155,10 @@ function isGlobPattern(pattern) {
|
|
|
839
2155
|
}
|
|
840
2156
|
return false;
|
|
841
2157
|
}
|
|
842
|
-
function compileGlob(glob) {
|
|
2158
|
+
function compileGlob(glob, exact = false) {
|
|
843
2159
|
let src = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
844
2160
|
src = src.replace(/\*\*/g, "\0GLOBSTAR\0").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]").replace(/\x00GLOBSTAR\x00/g, ".*");
|
|
845
|
-
return new RegExp("^" + src);
|
|
2161
|
+
return new RegExp("^" + src + (exact ? "$" : ""));
|
|
846
2162
|
}
|
|
847
2163
|
function extractInlinePattern(str, openIdx) {
|
|
848
2164
|
let depth = 0;
|
|
@@ -865,7 +2181,7 @@ function extractInlinePattern(str, openIdx) {
|
|
|
865
2181
|
throw new SyntaxError(`Unbalanced parentheses in route segment '${str}'`);
|
|
866
2182
|
return { pattern: str.slice(openIdx + 1, i), closeIdx: i };
|
|
867
2183
|
}
|
|
868
|
-
function compilePlainPath(path2) {
|
|
2184
|
+
function compilePlainPath(path2, exact = false) {
|
|
869
2185
|
const segments = path2.split("/").filter((s) => s.length > 0);
|
|
870
2186
|
const src = segments.map((seg) => {
|
|
871
2187
|
if (!seg.startsWith(":"))
|
|
@@ -887,25 +2203,31 @@ function compilePlainPath(path2) {
|
|
|
887
2203
|
return `(?<${name}>${pattern})${escapedSuffix}`;
|
|
888
2204
|
}).join("/");
|
|
889
2205
|
try {
|
|
890
|
-
return new RegExp("^/?" + src + "(?=/|$)");
|
|
2206
|
+
return new RegExp(exact ? "^/?" + src + (src ? "/?" : "") + "$" : "^/?" + src + "(?=/|$)");
|
|
891
2207
|
} catch (e) {
|
|
892
2208
|
throw new SyntaxError(
|
|
893
|
-
`Invalid inline regex constraint in path '${path2}': ${e.message}
|
|
2209
|
+
`Invalid inline regex constraint in path '${path2}': ${e.message}`,
|
|
2210
|
+
{ cause: e }
|
|
894
2211
|
);
|
|
895
2212
|
}
|
|
896
2213
|
}
|
|
897
|
-
function compilePattern(path2) {
|
|
2214
|
+
function compilePattern(path2, exact = false) {
|
|
898
2215
|
if (path2 instanceof RegExp) return path2;
|
|
899
|
-
if (isGlobPattern(path2)) return compileGlob(path2);
|
|
900
|
-
return compilePlainPath(path2);
|
|
2216
|
+
if (isGlobPattern(path2)) return compileGlob(path2, exact);
|
|
2217
|
+
return compilePlainPath(path2, exact);
|
|
901
2218
|
}
|
|
902
2219
|
function buildRouteLayer(method, path2, middleware, stripPath) {
|
|
903
2220
|
if (typeof middleware !== "function")
|
|
904
2221
|
throw new TypeError("Incorrect middleware type: expected a function");
|
|
905
|
-
|
|
2222
|
+
if (path2 instanceof RegExp && (path2.global || path2.sticky))
|
|
2223
|
+
throw new TypeError(
|
|
2224
|
+
`Route RegExp /${path2.source}/${path2.flags} must not use the g (global) or y (sticky) flag \u2014 these flags make exec() stateful and cause intermittent routing failures.`
|
|
2225
|
+
);
|
|
2226
|
+
return { method, path: path2, regex: compilePattern(path2, !stripPath), stripPath, middleware };
|
|
906
2227
|
}
|
|
907
2228
|
function matchRouteLayer(layer, req, path2) {
|
|
908
|
-
if (layer.method && layer.method !== req.method
|
|
2229
|
+
if (layer.method && layer.method !== req.method && !(req.method === "HEAD" && layer.method === "GET"))
|
|
2230
|
+
return false;
|
|
909
2231
|
const m = layer.regex.exec(path2);
|
|
910
2232
|
if (m === null) return false;
|
|
911
2233
|
const captured = m.groups ?? {};
|
|
@@ -916,208 +2238,6 @@ function matchRouteLayer(layer, req, path2) {
|
|
|
916
2238
|
Object.assign(req.params, captured);
|
|
917
2239
|
return true;
|
|
918
2240
|
}
|
|
919
|
-
function decodeJsonCookie(raw) {
|
|
920
|
-
if (!raw.startsWith("j:")) return raw;
|
|
921
|
-
try {
|
|
922
|
-
return JSON.parse(raw.slice(2));
|
|
923
|
-
} catch {
|
|
924
|
-
return raw;
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
function signCookieValue(value, secret) {
|
|
928
|
-
const sig = crypto.createHmac("sha256", secret).update(value).digest("base64url");
|
|
929
|
-
return `s:${value}.${sig}`;
|
|
930
|
-
}
|
|
931
|
-
function verifyCookieValue(signed, secret) {
|
|
932
|
-
if (!signed.startsWith("s:")) return false;
|
|
933
|
-
const withoutPrefix = signed.slice(2);
|
|
934
|
-
const lastDot = withoutPrefix.lastIndexOf(".");
|
|
935
|
-
if (lastDot === -1) return false;
|
|
936
|
-
const value = withoutPrefix.slice(0, lastDot);
|
|
937
|
-
const received = withoutPrefix.slice(lastDot + 1);
|
|
938
|
-
const expected = crypto.createHmac("sha256", secret).update(value).digest("base64url");
|
|
939
|
-
const receivedBuf = Buffer.from(received, "base64url");
|
|
940
|
-
const expectedBuf = Buffer.from(expected, "base64url");
|
|
941
|
-
if (receivedBuf.length !== expectedBuf.length) return false;
|
|
942
|
-
if (!crypto.timingSafeEqual(receivedBuf, expectedBuf)) return false;
|
|
943
|
-
return value;
|
|
944
|
-
}
|
|
945
|
-
function updateHttpObjects(req, res, secret, trustProxy) {
|
|
946
|
-
const rReq = req;
|
|
947
|
-
const rRes = res;
|
|
948
|
-
if (rReq.queries) return;
|
|
949
|
-
rReq.queries = {};
|
|
950
|
-
if (trustProxy) {
|
|
951
|
-
const xff = req.headers["x-forwarded-for"];
|
|
952
|
-
const first = Array.isArray(xff) ? xff[0] : xff;
|
|
953
|
-
rReq.ip = (first ? first.split(",")[0].trim() : req.socket?.remoteAddress) ?? "";
|
|
954
|
-
} else {
|
|
955
|
-
rReq.ip = req.socket?.remoteAddress ?? "";
|
|
956
|
-
}
|
|
957
|
-
const qry = new URL(`http://${req.headers.host}${req.url}`);
|
|
958
|
-
rReq.originalUrl = req.url;
|
|
959
|
-
rReq.path = qry.pathname;
|
|
960
|
-
const urlParams = {};
|
|
961
|
-
for (const [key, value] of qry.searchParams.entries()) {
|
|
962
|
-
const existing = urlParams[key];
|
|
963
|
-
if (existing === void 0) {
|
|
964
|
-
urlParams[key] = value;
|
|
965
|
-
} else if (Array.isArray(existing)) {
|
|
966
|
-
existing.push(value);
|
|
967
|
-
} else {
|
|
968
|
-
urlParams[key] = [existing, value];
|
|
969
|
-
}
|
|
970
|
-
}
|
|
971
|
-
rReq.queries.url = urlParams;
|
|
972
|
-
const flatParams = {};
|
|
973
|
-
for (const [key, value] of Object.entries(urlParams)) {
|
|
974
|
-
flatParams[key] = Array.isArray(value) ? value[0] : value;
|
|
975
|
-
}
|
|
976
|
-
rReq.params = flatParams;
|
|
977
|
-
if (rReq.cookies == null) {
|
|
978
|
-
rReq.cookies = {};
|
|
979
|
-
if (req.headers.cookie) {
|
|
980
|
-
for (const part of req.headers.cookie.split(";")) {
|
|
981
|
-
const eqIdx = part.indexOf("=");
|
|
982
|
-
if (eqIdx === -1) continue;
|
|
983
|
-
const name = part.slice(0, eqIdx).trim();
|
|
984
|
-
const rawVal = part.slice(eqIdx + 1).trim();
|
|
985
|
-
if (rawVal.startsWith("s:")) {
|
|
986
|
-
if (secret) {
|
|
987
|
-
const inner = verifyCookieValue(rawVal, secret);
|
|
988
|
-
if (inner === false) continue;
|
|
989
|
-
rReq.cookies[name] = decodeJsonCookie(inner);
|
|
990
|
-
} else {
|
|
991
|
-
rReq.cookies[name] = rawVal;
|
|
992
|
-
}
|
|
993
|
-
} else {
|
|
994
|
-
rReq.cookies[name] = decodeJsonCookie(rawVal);
|
|
995
|
-
}
|
|
996
|
-
}
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
const resolvedReqOpts = (opts) => ({
|
|
1000
|
-
limit: opts?.limit ?? "100kb",
|
|
1001
|
-
inflate: opts?.inflate ?? true,
|
|
1002
|
-
reviver: null,
|
|
1003
|
-
strict: opts?.strict ?? false
|
|
1004
|
-
});
|
|
1005
|
-
rReq.json = (opts) => {
|
|
1006
|
-
if ("body" in rReq) return Promise.resolve(rReq.body ?? null);
|
|
1007
|
-
return readReqBody(rReq, resolvedReqOpts(opts), "application/json").then((ret) => {
|
|
1008
|
-
if (ret == null) return null;
|
|
1009
|
-
const charset = extractCharset(ret.mimetype);
|
|
1010
|
-
try {
|
|
1011
|
-
const parsed = JSON.parse(
|
|
1012
|
-
ret.content.toString(charset),
|
|
1013
|
-
opts?.reviver ?? void 0
|
|
1014
|
-
);
|
|
1015
|
-
rReq.body = parsed;
|
|
1016
|
-
return parsed;
|
|
1017
|
-
} catch (ex) {
|
|
1018
|
-
return Promise.reject({ status: 400, message: "Bad Request: " + ex.message });
|
|
1019
|
-
}
|
|
1020
|
-
});
|
|
1021
|
-
};
|
|
1022
|
-
rReq.text = (opts) => {
|
|
1023
|
-
const cached = rReq.body;
|
|
1024
|
-
if (typeof cached === "string") return Promise.resolve(cached);
|
|
1025
|
-
return readReqBody(rReq, resolvedReqOpts(opts), null).then((ret) => {
|
|
1026
|
-
if (ret == null) return null;
|
|
1027
|
-
const charset = extractCharset(ret.mimetype);
|
|
1028
|
-
return ret.content.toString(charset);
|
|
1029
|
-
});
|
|
1030
|
-
};
|
|
1031
|
-
rReq.formData = (opts) => {
|
|
1032
|
-
const cached = rReq.body;
|
|
1033
|
-
if (Array.isArray(cached)) return Promise.resolve(cached);
|
|
1034
|
-
return readReqBody(rReq, resolvedReqOpts(opts), "multipart/form-data").then((ret) => {
|
|
1035
|
-
if (ret == null) return null;
|
|
1036
|
-
try {
|
|
1037
|
-
const parts = parseMultipartBody(ret.mimetype, ret.content);
|
|
1038
|
-
rReq.body = parts;
|
|
1039
|
-
return parts;
|
|
1040
|
-
} catch (ex) {
|
|
1041
|
-
return Promise.reject({ status: ex.status ?? 500, message: ex.message ?? String(ex) });
|
|
1042
|
-
}
|
|
1043
|
-
});
|
|
1044
|
-
};
|
|
1045
|
-
rRes.setHeader("X-Powered-By", "Expediate");
|
|
1046
|
-
rRes.send = (data) => {
|
|
1047
|
-
if (data) res.write(data);
|
|
1048
|
-
res.end();
|
|
1049
|
-
};
|
|
1050
|
-
rRes.json = (data) => {
|
|
1051
|
-
res.setHeader("Content-Type", "application/json");
|
|
1052
|
-
res.write(JSON.stringify(data));
|
|
1053
|
-
res.end();
|
|
1054
|
-
};
|
|
1055
|
-
rRes.status = (code, headers) => {
|
|
1056
|
-
res.statusCode = code;
|
|
1057
|
-
if (headers)
|
|
1058
|
-
for (const [k, v] of Object.entries(headers)) res.setHeader(k, v);
|
|
1059
|
-
return rRes;
|
|
1060
|
-
};
|
|
1061
|
-
rRes.redirect = (url) => {
|
|
1062
|
-
res.setHeader("location", url);
|
|
1063
|
-
res.writeHead(302);
|
|
1064
|
-
res.write(`Found. Redirecting to ${url}`);
|
|
1065
|
-
res.end();
|
|
1066
|
-
};
|
|
1067
|
-
rRes.cookie = (name, value, options) => {
|
|
1068
|
-
const opts = options ?? {};
|
|
1069
|
-
let val = typeof value === "object" ? "j:" + JSON.stringify(value) : String(value);
|
|
1070
|
-
if (opts.signed) {
|
|
1071
|
-
if (!secret)
|
|
1072
|
-
throw new Error(
|
|
1073
|
-
"Signed cookies require a secret \u2014 pass { secret } to createRouter()"
|
|
1074
|
-
);
|
|
1075
|
-
val = signCookieValue(val, secret);
|
|
1076
|
-
}
|
|
1077
|
-
let txt = `${name}=${val}`;
|
|
1078
|
-
if (opts.maxAge != null) {
|
|
1079
|
-
const maxAgeMs = opts.maxAge;
|
|
1080
|
-
const maxAgeSec = Math.floor(maxAgeMs / 1e3);
|
|
1081
|
-
opts.expires = new Date(Date.now() + maxAgeMs);
|
|
1082
|
-
txt += `; Max-Age=${maxAgeSec}`;
|
|
1083
|
-
}
|
|
1084
|
-
if (opts.expires) txt += `; Expires=${opts.expires.toUTCString()}`;
|
|
1085
|
-
txt += `; Path=${opts.path ?? "/"}`;
|
|
1086
|
-
if (opts.httpOnly) txt += "; HttpOnly";
|
|
1087
|
-
if (opts.secure) txt += "; Secure";
|
|
1088
|
-
if (opts.sameSite) txt += `; SameSite=${opts.sameSite}`;
|
|
1089
|
-
const existing = res.getHeader("Set-Cookie");
|
|
1090
|
-
if (existing == null) {
|
|
1091
|
-
res.setHeader("Set-Cookie", txt);
|
|
1092
|
-
} else if (Array.isArray(existing)) {
|
|
1093
|
-
res.setHeader("Set-Cookie", [...existing, txt]);
|
|
1094
|
-
} else {
|
|
1095
|
-
res.setHeader("Set-Cookie", [existing, txt]);
|
|
1096
|
-
}
|
|
1097
|
-
return rRes;
|
|
1098
|
-
};
|
|
1099
|
-
rRes.download = (filepath, filename) => {
|
|
1100
|
-
const name = filename ?? path.basename(filepath);
|
|
1101
|
-
const safeName = name.replace(/"/g, '\\"');
|
|
1102
|
-
res.setHeader("Content-Disposition", `attachment; filename="${safeName}"`);
|
|
1103
|
-
fs2.access(filepath, fs2.constants.F_OK, (err) => {
|
|
1104
|
-
if (err) {
|
|
1105
|
-
if (!rRes.writableEnded) rRes.status(404).end("Not Found");
|
|
1106
|
-
return;
|
|
1107
|
-
}
|
|
1108
|
-
serveFile(filepath)(rReq, rRes, () => {
|
|
1109
|
-
});
|
|
1110
|
-
});
|
|
1111
|
-
};
|
|
1112
|
-
rRes.type = (mime2) => {
|
|
1113
|
-
res.setHeader("Content-Type", mime2);
|
|
1114
|
-
return rRes;
|
|
1115
|
-
};
|
|
1116
|
-
rRes.etag = (value, strong = false) => {
|
|
1117
|
-
res.setHeader("ETag", strong ? `"${value}"` : `W/"${value}"`);
|
|
1118
|
-
return rRes;
|
|
1119
|
-
};
|
|
1120
|
-
}
|
|
1121
2241
|
function pathMatchesLayer(layer, path2) {
|
|
1122
2242
|
return layer.regex.test(path2);
|
|
1123
2243
|
}
|
|
@@ -1145,8 +2265,8 @@ function createRouter(prefixOrOpts, opts) {
|
|
|
1145
2265
|
const timeoutMs = options.timeout;
|
|
1146
2266
|
const trustProxy = options.trustProxy ?? false;
|
|
1147
2267
|
const routes = [];
|
|
2268
|
+
const errorHandlers = [];
|
|
1148
2269
|
let errorHandler;
|
|
1149
|
-
let notFoundHandler;
|
|
1150
2270
|
let activeServer = null;
|
|
1151
2271
|
const activeSockets = /* @__PURE__ */ new Set();
|
|
1152
2272
|
const listener = (req, res, done) => {
|
|
@@ -1166,16 +2286,33 @@ function createRouter(prefixOrOpts, opts) {
|
|
|
1166
2286
|
}
|
|
1167
2287
|
const invokeErrorHandler = (e) => {
|
|
1168
2288
|
if (res.writableEnded) return;
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
2289
|
+
let i = 0;
|
|
2290
|
+
const runNext = (err) => {
|
|
2291
|
+
if (res.writableEnded) return;
|
|
2292
|
+
if (i < errorHandlers.length) {
|
|
2293
|
+
const handler = errorHandlers[i++];
|
|
2294
|
+
try {
|
|
2295
|
+
handler(err, req, res, (nextErr) => runNext(nextErr ?? err));
|
|
2296
|
+
} catch (e2) {
|
|
2297
|
+
runNext(e2);
|
|
2298
|
+
}
|
|
2299
|
+
return;
|
|
2300
|
+
}
|
|
2301
|
+
if (errorHandler) {
|
|
2302
|
+
try {
|
|
2303
|
+
errorHandler(err, req, res);
|
|
2304
|
+
} catch {
|
|
2305
|
+
if (!res.writableEnded) res.status(500).end(`Error ${method} ${url}`);
|
|
2306
|
+
}
|
|
2307
|
+
return;
|
|
2308
|
+
}
|
|
2309
|
+
if (done) {
|
|
2310
|
+
done(err);
|
|
2311
|
+
return;
|
|
1174
2312
|
}
|
|
1175
|
-
} else {
|
|
1176
|
-
console.warn(e);
|
|
1177
2313
|
res.status(500).end(`Error ${method} ${url}`);
|
|
1178
|
-
}
|
|
2314
|
+
};
|
|
2315
|
+
runNext(e);
|
|
1179
2316
|
};
|
|
1180
2317
|
const invoke = (mw, nextFn) => {
|
|
1181
2318
|
try {
|
|
@@ -1196,9 +2333,13 @@ function createRouter(prefixOrOpts, opts) {
|
|
|
1196
2333
|
const pathBefore = req.path;
|
|
1197
2334
|
if (matchRouteLayer(layer, req, req.path)) {
|
|
1198
2335
|
if (layer.stripPath) {
|
|
1199
|
-
|
|
2336
|
+
const baseUrlBefore = req.baseUrl;
|
|
2337
|
+
const strippedPrefix = pathBefore.slice(0, pathBefore.length - req.path.length);
|
|
2338
|
+
req.baseUrl = baseUrlBefore + strippedPrefix;
|
|
2339
|
+
invoke(layer.middleware, (err2) => {
|
|
1200
2340
|
req.path = pathBefore;
|
|
1201
|
-
|
|
2341
|
+
req.baseUrl = baseUrlBefore;
|
|
2342
|
+
next(err2);
|
|
1202
2343
|
});
|
|
1203
2344
|
return;
|
|
1204
2345
|
}
|
|
@@ -1210,20 +2351,17 @@ function createRouter(prefixOrOpts, opts) {
|
|
|
1210
2351
|
}
|
|
1211
2352
|
}
|
|
1212
2353
|
if (allowedMethods.size > 0) {
|
|
2354
|
+
if (allowedMethods.has("GET")) allowedMethods.add("HEAD");
|
|
2355
|
+
allowedMethods.add("OPTIONS");
|
|
1213
2356
|
const allow = [...allowedMethods].sort().join(", ");
|
|
2357
|
+
if (method === "OPTIONS") {
|
|
2358
|
+
res.status(204, { Allow: allow }).end();
|
|
2359
|
+
return;
|
|
2360
|
+
}
|
|
1214
2361
|
res.status(405, { Allow: allow }).end(`Cannot ${method} ${url}`);
|
|
1215
2362
|
return;
|
|
1216
2363
|
}
|
|
1217
2364
|
if (done) return done();
|
|
1218
|
-
if (notFoundHandler) {
|
|
1219
|
-
try {
|
|
1220
|
-
notFoundHandler(req, res, () => {
|
|
1221
|
-
});
|
|
1222
|
-
} catch (e) {
|
|
1223
|
-
invokeErrorHandler(e);
|
|
1224
|
-
}
|
|
1225
|
-
return;
|
|
1226
|
-
}
|
|
1227
2365
|
res.status(404).end(`Cannot ${method} ${url}`);
|
|
1228
2366
|
};
|
|
1229
2367
|
try {
|
|
@@ -1256,13 +2394,53 @@ function createRouter(prefixOrOpts, opts) {
|
|
|
1256
2394
|
post: makeRegister("POST", false),
|
|
1257
2395
|
delete: makeRegister("DELETE", false),
|
|
1258
2396
|
patch: makeRegister("PATCH", false),
|
|
2397
|
+
head: makeRegister("HEAD", false),
|
|
2398
|
+
options: makeRegister("OPTIONS", false),
|
|
2399
|
+
// ── route ────────────────────────────────────────────────────────────────
|
|
2400
|
+
route(path2) {
|
|
2401
|
+
const builder = {
|
|
2402
|
+
all(...args) {
|
|
2403
|
+
router.all(path2, ...args);
|
|
2404
|
+
return builder;
|
|
2405
|
+
},
|
|
2406
|
+
get(...args) {
|
|
2407
|
+
router.get(path2, ...args);
|
|
2408
|
+
return builder;
|
|
2409
|
+
},
|
|
2410
|
+
put(...args) {
|
|
2411
|
+
router.put(path2, ...args);
|
|
2412
|
+
return builder;
|
|
2413
|
+
},
|
|
2414
|
+
post(...args) {
|
|
2415
|
+
router.post(path2, ...args);
|
|
2416
|
+
return builder;
|
|
2417
|
+
},
|
|
2418
|
+
delete(...args) {
|
|
2419
|
+
router.delete(path2, ...args);
|
|
2420
|
+
return builder;
|
|
2421
|
+
},
|
|
2422
|
+
patch(...args) {
|
|
2423
|
+
router.patch(path2, ...args);
|
|
2424
|
+
return builder;
|
|
2425
|
+
},
|
|
2426
|
+
head(...args) {
|
|
2427
|
+
router.head(path2, ...args);
|
|
2428
|
+
return builder;
|
|
2429
|
+
},
|
|
2430
|
+
options(...args) {
|
|
2431
|
+
router.options(path2, ...args);
|
|
2432
|
+
return builder;
|
|
2433
|
+
}
|
|
2434
|
+
};
|
|
2435
|
+
return builder;
|
|
2436
|
+
},
|
|
1259
2437
|
// ── onError ─────────────────────────────────────────────────────────────
|
|
1260
2438
|
onError(handler) {
|
|
1261
2439
|
errorHandler = handler;
|
|
1262
2440
|
},
|
|
1263
|
-
// ──
|
|
1264
|
-
|
|
1265
|
-
|
|
2441
|
+
// ── error ────────────────────────────────────────────────────────────────
|
|
2442
|
+
error(handler) {
|
|
2443
|
+
errorHandlers.push(handler);
|
|
1266
2444
|
},
|
|
1267
2445
|
// ── routes ───────────────────────────────────────────────────────────────
|
|
1268
2446
|
routes() {
|
|
@@ -1392,10 +2570,10 @@ var DEFAULT_CONFIG = {
|
|
|
1392
2570
|
})
|
|
1393
2571
|
};
|
|
1394
2572
|
function isHmac(alg) {
|
|
1395
|
-
return alg
|
|
2573
|
+
return alg.startsWith("H");
|
|
1396
2574
|
}
|
|
1397
2575
|
function isEc(alg) {
|
|
1398
|
-
return alg
|
|
2576
|
+
return alg.startsWith("E");
|
|
1399
2577
|
}
|
|
1400
2578
|
function nodeHashAlg(alg) {
|
|
1401
2579
|
return `SHA${alg.slice(2)}`;
|
|
@@ -1540,7 +2718,7 @@ async function authenticateUser(username, password, config) {
|
|
|
1540
2718
|
return issueTokenPair(user, config);
|
|
1541
2719
|
}
|
|
1542
2720
|
async function issueTokenPair(user, config) {
|
|
1543
|
-
const claims = config.payload(user);
|
|
2721
|
+
const claims = await config.payload(user);
|
|
1544
2722
|
const fullClaims = {
|
|
1545
2723
|
sub: config.username(user),
|
|
1546
2724
|
// fallback subject — overridden by payload() if it sets sub
|
|
@@ -1675,7 +2853,7 @@ function createJwtPlugin(userConfig = {}) {
|
|
|
1675
2853
|
};
|
|
1676
2854
|
const authenticate = (req, res, next) => {
|
|
1677
2855
|
delete req.user;
|
|
1678
|
-
const authHeader = req.headers
|
|
2856
|
+
const authHeader = req.headers.authorization;
|
|
1679
2857
|
if (!authHeader?.startsWith("Bearer ")) return next();
|
|
1680
2858
|
const token = authHeader.slice(7);
|
|
1681
2859
|
const result = verifyToken(token, accessVerifyKey(config), config.alg);
|
|
@@ -1766,14 +2944,14 @@ function gitHandler(opt) {
|
|
|
1766
2944
|
}
|
|
1767
2945
|
const urlPath = req.path;
|
|
1768
2946
|
if (req.method === "GET" && urlPath === "/info/refs") {
|
|
1769
|
-
let args
|
|
2947
|
+
let args;
|
|
1770
2948
|
const service = req.queries?.url?.service;
|
|
1771
2949
|
if (service === "git-upload-pack")
|
|
1772
2950
|
args = buildArgs(opt, ["--stateless-rpc", "--advertise-refs", gitDirectory]);
|
|
1773
2951
|
else if (service === "git-receive-pack")
|
|
1774
2952
|
args = ["--stateless-rpc", "--advertise-refs", gitDirectory];
|
|
1775
2953
|
else {
|
|
1776
|
-
res.status(403).send(`Service ${service} is not supported`);
|
|
2954
|
+
res.status(403).send(`Service ${String(service)} is not supported`);
|
|
1777
2955
|
return;
|
|
1778
2956
|
}
|
|
1779
2957
|
res.setHeader("Content-Type", `application/x-${service}-advertisement`);
|
|
@@ -1811,7 +2989,7 @@ function gitHandler(opt) {
|
|
|
1811
2989
|
res.status(415).send("Unsupported Media Type");
|
|
1812
2990
|
return;
|
|
1813
2991
|
}
|
|
1814
|
-
let args
|
|
2992
|
+
let args;
|
|
1815
2993
|
if (service === "git-upload-pack")
|
|
1816
2994
|
args = buildArgs(opt, ["--stateless-rpc", gitDirectory]);
|
|
1817
2995
|
else if (service === "git-receive-pack")
|
|
@@ -1928,7 +3106,7 @@ function yamlString(s) {
|
|
|
1928
3106
|
s.includes(": ") || s.endsWith(":") || s.includes(" #") || // Leading / trailing whitespace.
|
|
1929
3107
|
s !== s.trim() || // Flow indicator characters anywhere — present in path patterns such as
|
|
1930
3108
|
// `/items/{id}` and must be quoted to avoid flow-collection ambiguity.
|
|
1931
|
-
/[{}
|
|
3109
|
+
/[{}[\]]/.test(s) || // Control characters.
|
|
1932
3110
|
/[\x00-\x1f\x7f]/.test(s)
|
|
1933
3111
|
);
|
|
1934
3112
|
if (!needsQuote) return s;
|
|
@@ -2038,10 +3216,63 @@ function buildAnnotatedResponses(responses) {
|
|
|
2038
3216
|
return result;
|
|
2039
3217
|
}
|
|
2040
3218
|
var VERBS = ["GET", "POST", "PUT", "DELETE", "PATCH"];
|
|
3219
|
+
function collectOpenApiRoutes(sources) {
|
|
3220
|
+
const routes = [];
|
|
3221
|
+
const seen = /* @__PURE__ */ new Map();
|
|
3222
|
+
sources.forEach((source, sourceIndex) => {
|
|
3223
|
+
const defaultTag = source.openapi?.tag;
|
|
3224
|
+
const permissionsExtension = source.auth?.permissionsExtension ?? "x-required-permissions";
|
|
3225
|
+
const rootController = {
|
|
3226
|
+
prefix: "",
|
|
3227
|
+
GET: source.GET,
|
|
3228
|
+
POST: source.POST,
|
|
3229
|
+
PUT: source.PUT,
|
|
3230
|
+
DELETE: source.DELETE,
|
|
3231
|
+
PATCH: source.PATCH
|
|
3232
|
+
};
|
|
3233
|
+
const controllers = [rootController, ...source.controllers ?? []];
|
|
3234
|
+
controllers.forEach((controller, controllerIndex) => {
|
|
3235
|
+
const label = controllerIndex === 0 ? `source #${sourceIndex}` : controller.tags?.[0] ?? controller.prefix ?? `source #${sourceIndex} controller #${controllerIndex}`;
|
|
3236
|
+
const prefix = controller.prefix ?? "";
|
|
3237
|
+
for (const verb of VERBS) {
|
|
3238
|
+
const routeMap = controller[verb];
|
|
3239
|
+
if (!routeMap) continue;
|
|
3240
|
+
for (const [pattern, value] of Object.entries(routeMap)) {
|
|
3241
|
+
const path2 = joinPath(prefix, pattern);
|
|
3242
|
+
const dupKey = `${verb} ${path2}`;
|
|
3243
|
+
const declarer = seen.get(dupKey);
|
|
3244
|
+
if (declarer !== void 0) {
|
|
3245
|
+
throw new Error(
|
|
3246
|
+
`openApiSpec: duplicate route ${verb} ${path2}
|
|
3247
|
+
declared by '${declarer}' and '${label}'`
|
|
3248
|
+
);
|
|
3249
|
+
}
|
|
3250
|
+
seen.set(dupKey, label);
|
|
3251
|
+
const meta = typeof value === "function" ? value[DESCRIBE_META] : value;
|
|
3252
|
+
routes.push({
|
|
3253
|
+
verb,
|
|
3254
|
+
path: path2,
|
|
3255
|
+
meta,
|
|
3256
|
+
tags: meta?.tags ?? controller.tags,
|
|
3257
|
+
permission: normalizePermission(meta?.permission ?? controller.permission),
|
|
3258
|
+
defaultTag,
|
|
3259
|
+
permissionsExtension
|
|
3260
|
+
});
|
|
3261
|
+
}
|
|
3262
|
+
}
|
|
3263
|
+
});
|
|
3264
|
+
});
|
|
3265
|
+
routes.sort((a, b) => routeScore(b.path) - routeScore(a.path) || b.path.localeCompare(a.path));
|
|
3266
|
+
return routes;
|
|
3267
|
+
}
|
|
3268
|
+
var DEFAULT_SECURITY_SCHEME = {
|
|
3269
|
+
type: "http",
|
|
3270
|
+
scheme: "bearer",
|
|
3271
|
+
bearerFormat: "JWT"
|
|
3272
|
+
};
|
|
2041
3273
|
function openApiSpec(service, opts) {
|
|
3274
|
+
const sources = Array.isArray(service) ? service : [service];
|
|
2042
3275
|
const basePath = opts.basePath ?? "";
|
|
2043
|
-
const svcMeta = service.openapi;
|
|
2044
|
-
const defaultTag = svcMeta?.tag;
|
|
2045
3276
|
const builtinSchemas = {
|
|
2046
3277
|
ApiError: {
|
|
2047
3278
|
type: "object",
|
|
@@ -2060,49 +3291,55 @@ function openApiSpec(service, opts) {
|
|
|
2060
3291
|
}
|
|
2061
3292
|
}
|
|
2062
3293
|
};
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
};
|
|
2068
|
-
const responses = {
|
|
2069
|
-
...builtinResponses,
|
|
2070
|
-
...svcMeta?.responses
|
|
2071
|
-
};
|
|
3294
|
+
let schemas = { ...builtinSchemas };
|
|
3295
|
+
for (const source of sources) schemas = { ...schemas, ...source.openapi?.schemas };
|
|
3296
|
+
schemas = { ...schemas, ...opts.schemas };
|
|
3297
|
+
for (const source of sources) schemas = { ...schemas, ...source.schemas };
|
|
3298
|
+
let responses = { ...builtinResponses };
|
|
3299
|
+
for (const source of sources) responses = { ...responses, ...source.openapi?.responses };
|
|
2072
3300
|
const tags = [];
|
|
2073
|
-
|
|
2074
|
-
|
|
3301
|
+
const seenTags = /* @__PURE__ */ new Set();
|
|
3302
|
+
for (const source of sources) {
|
|
3303
|
+
const tag = source.openapi?.tag;
|
|
3304
|
+
if (tag && !seenTags.has(tag)) {
|
|
3305
|
+
seenTags.add(tag);
|
|
3306
|
+
tags.push({ name: tag, description: source.openapi?.tagDescription });
|
|
3307
|
+
}
|
|
2075
3308
|
}
|
|
3309
|
+
const securityScheme = sources.find((s) => s.auth?.scheme)?.auth?.scheme ?? DEFAULT_SECURITY_SCHEME;
|
|
2076
3310
|
const paths = {};
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
if (k.startsWith("x-")) operation[k] = v;
|
|
2102
|
-
}
|
|
3311
|
+
const routes = collectOpenApiRoutes(sources);
|
|
3312
|
+
let securedRoutes = false;
|
|
3313
|
+
for (const route of routes) {
|
|
3314
|
+
const { verb, path: pattern, meta } = route;
|
|
3315
|
+
const openApiPath = toOpenApiPath(pattern, basePath);
|
|
3316
|
+
if (!paths[openApiPath]) paths[openApiPath] = {};
|
|
3317
|
+
const annotatedParams = meta?.parameters ?? [];
|
|
3318
|
+
const inferredParams = extractPathParams(pattern, annotatedParams);
|
|
3319
|
+
const parameters = [...annotatedParams, ...inferredParams];
|
|
3320
|
+
const operationResponses = meta?.responses ? buildAnnotatedResponses(meta.responses) : buildDefaultResponses(verb);
|
|
3321
|
+
const operation = {
|
|
3322
|
+
operationId: meta?.operationId ?? buildOperationId(verb, pattern),
|
|
3323
|
+
...meta?.summary && { summary: meta.summary },
|
|
3324
|
+
...meta?.description && { description: meta.description },
|
|
3325
|
+
...meta?.deprecated && { deprecated: true },
|
|
3326
|
+
...parameters.length > 0 && { parameters },
|
|
3327
|
+
...meta?.requestBody && { requestBody: meta.requestBody },
|
|
3328
|
+
responses: operationResponses
|
|
3329
|
+
};
|
|
3330
|
+
const opTags = route.tags ?? (route.defaultTag ? [route.defaultTag] : void 0);
|
|
3331
|
+
if (opTags) operation.tags = opTags;
|
|
3332
|
+
if (meta) {
|
|
3333
|
+
for (const [k, v] of Object.entries(meta)) {
|
|
3334
|
+
if (k.startsWith("x-")) operation[k] = v;
|
|
2103
3335
|
}
|
|
2104
|
-
paths[openApiPath][verb.toLowerCase()] = operation;
|
|
2105
3336
|
}
|
|
3337
|
+
if (route.permission) {
|
|
3338
|
+
securedRoutes = true;
|
|
3339
|
+
operation.security = [{ bearerAuth: [] }];
|
|
3340
|
+
operation[route.permissionsExtension] = route.permission;
|
|
3341
|
+
}
|
|
3342
|
+
paths[openApiPath][verb.toLowerCase()] = operation;
|
|
2106
3343
|
}
|
|
2107
3344
|
const doc = {
|
|
2108
3345
|
openapi: "3.1.0",
|
|
@@ -2114,12 +3351,225 @@ function openApiSpec(service, opts) {
|
|
|
2114
3351
|
...opts.servers && { servers: opts.servers },
|
|
2115
3352
|
...tags.length > 0 && { tags },
|
|
2116
3353
|
paths,
|
|
2117
|
-
components: {
|
|
3354
|
+
components: {
|
|
3355
|
+
schemas,
|
|
3356
|
+
responses,
|
|
3357
|
+
// Emitted once when at least one operation declares a permission.
|
|
3358
|
+
...securedRoutes && {
|
|
3359
|
+
securitySchemes: {
|
|
3360
|
+
bearerAuth: securityScheme
|
|
3361
|
+
}
|
|
3362
|
+
}
|
|
3363
|
+
}
|
|
2118
3364
|
};
|
|
2119
3365
|
return doc;
|
|
2120
3366
|
}
|
|
2121
3367
|
|
|
2122
3368
|
// src/apis.ts
|
|
3369
|
+
function defineController(c) {
|
|
3370
|
+
return c;
|
|
3371
|
+
}
|
|
3372
|
+
var VERBS2 = ["GET", "POST", "PUT", "DELETE", "PATCH"];
|
|
3373
|
+
function joinPath(prefix, path2) {
|
|
3374
|
+
const joined = `/${prefix}/${path2}`.replace(/\/+/g, "/");
|
|
3375
|
+
return joined.length > 1 ? joined.replace(/\/$/, "") : joined;
|
|
3376
|
+
}
|
|
3377
|
+
function routeScore(path2) {
|
|
3378
|
+
const segs = path2.split("/").filter((s) => s.length > 0);
|
|
3379
|
+
return segs.length * 100 - segs.filter((s) => s.startsWith(":")).length * 10;
|
|
3380
|
+
}
|
|
3381
|
+
function normalizePermission(permission) {
|
|
3382
|
+
if (permission === void 0) return void 0;
|
|
3383
|
+
return Array.isArray(permission) ? permission : [permission];
|
|
3384
|
+
}
|
|
3385
|
+
function collectRoutes(service) {
|
|
3386
|
+
const rootController = {
|
|
3387
|
+
prefix: "",
|
|
3388
|
+
GET: service.GET,
|
|
3389
|
+
POST: service.POST,
|
|
3390
|
+
PUT: service.PUT,
|
|
3391
|
+
DELETE: service.DELETE,
|
|
3392
|
+
PATCH: service.PATCH
|
|
3393
|
+
};
|
|
3394
|
+
const controllers = [rootController, ...service.controllers ?? []];
|
|
3395
|
+
const nameOf = (c, index) => index === 0 ? "<root>" : c.tags?.[0] ?? c.prefix ?? `#${index}`;
|
|
3396
|
+
const routes = [];
|
|
3397
|
+
const seen = /* @__PURE__ */ new Map();
|
|
3398
|
+
controllers.forEach((controller, index) => {
|
|
3399
|
+
const controllerName = nameOf(controller, index);
|
|
3400
|
+
const prefix = controller.prefix ?? "";
|
|
3401
|
+
for (const verb of VERBS2) {
|
|
3402
|
+
const routeMap = controller[verb];
|
|
3403
|
+
if (!routeMap) continue;
|
|
3404
|
+
for (const [pattern, handler] of Object.entries(routeMap)) {
|
|
3405
|
+
const path2 = joinPath(prefix, pattern);
|
|
3406
|
+
const dupKey = `${verb} ${path2}`;
|
|
3407
|
+
const declarer = seen.get(dupKey);
|
|
3408
|
+
if (declarer !== void 0) {
|
|
3409
|
+
throw new Error(
|
|
3410
|
+
`apiBuilder: duplicate route ${verb} ${path2}
|
|
3411
|
+
declared by controllers '${declarer}' and '${controllerName}'`
|
|
3412
|
+
);
|
|
3413
|
+
}
|
|
3414
|
+
seen.set(dupKey, controllerName);
|
|
3415
|
+
const meta = handler[DESCRIBE_META];
|
|
3416
|
+
routes.push({
|
|
3417
|
+
verb,
|
|
3418
|
+
path: path2,
|
|
3419
|
+
handler,
|
|
3420
|
+
meta,
|
|
3421
|
+
tags: meta?.tags ?? controller.tags,
|
|
3422
|
+
guards: [
|
|
3423
|
+
...service.guards ?? [],
|
|
3424
|
+
...controller.guards ?? [],
|
|
3425
|
+
...meta?.guards ?? []
|
|
3426
|
+
],
|
|
3427
|
+
permission: normalizePermission(meta?.permission ?? controller.permission),
|
|
3428
|
+
controller: controllerName
|
|
3429
|
+
});
|
|
3430
|
+
}
|
|
3431
|
+
}
|
|
3432
|
+
});
|
|
3433
|
+
routes.sort((a, b) => routeScore(b.path) - routeScore(a.path) || b.path.localeCompare(a.path));
|
|
3434
|
+
return routes;
|
|
3435
|
+
}
|
|
3436
|
+
function resolveRef(schema, components) {
|
|
3437
|
+
let current = schema;
|
|
3438
|
+
for (let depth = 0; depth < 16 && current.$ref; depth++) {
|
|
3439
|
+
const match = /^#\/components\/schemas\/(.+)$/.exec(current.$ref);
|
|
3440
|
+
const next = match ? components[match[1]] : void 0;
|
|
3441
|
+
if (!next) return {};
|
|
3442
|
+
current = next;
|
|
3443
|
+
}
|
|
3444
|
+
return current;
|
|
3445
|
+
}
|
|
3446
|
+
function addError(errors, path2, message) {
|
|
3447
|
+
const key = path2 || "$";
|
|
3448
|
+
if (!(key in errors)) errors[key] = message;
|
|
3449
|
+
}
|
|
3450
|
+
function childPath(path2, key) {
|
|
3451
|
+
return path2 ? `${path2}.${key}` : String(key);
|
|
3452
|
+
}
|
|
3453
|
+
function jsonTypeOf(value) {
|
|
3454
|
+
if (value === null) return "null";
|
|
3455
|
+
if (Array.isArray(value)) return "array";
|
|
3456
|
+
return typeof value;
|
|
3457
|
+
}
|
|
3458
|
+
function validateSchema(value, schema, components = {}, path2 = "", errors = {}) {
|
|
3459
|
+
const s = resolveRef(schema, components);
|
|
3460
|
+
if (s.allOf) {
|
|
3461
|
+
for (const sub of s.allOf) validateSchema(value, sub, components, path2, errors);
|
|
3462
|
+
}
|
|
3463
|
+
if (s.anyOf) {
|
|
3464
|
+
const passes = s.anyOf.some((sub) => Object.keys(validateSchema(value, sub, components, path2, {})).length === 0);
|
|
3465
|
+
if (!passes) addError(errors, path2, "does not match any of the expected schemas (anyOf)");
|
|
3466
|
+
}
|
|
3467
|
+
if (s.oneOf) {
|
|
3468
|
+
const matches = s.oneOf.filter((sub) => Object.keys(validateSchema(value, sub, components, path2, {})).length === 0).length;
|
|
3469
|
+
if (matches !== 1)
|
|
3470
|
+
addError(errors, path2, `must match exactly one schema (oneOf), matched ${matches}`);
|
|
3471
|
+
}
|
|
3472
|
+
if (s.type !== void 0) {
|
|
3473
|
+
const actual = jsonTypeOf(value);
|
|
3474
|
+
const ok = s.type === "integer" ? actual === "number" && Number.isInteger(value) : actual === s.type;
|
|
3475
|
+
if (!ok) {
|
|
3476
|
+
addError(errors, path2, `must be of type ${s.type}`);
|
|
3477
|
+
return errors;
|
|
3478
|
+
}
|
|
3479
|
+
}
|
|
3480
|
+
if (s.enum && !s.enum.some((e) => e === value || typeof e === "object" && JSON.stringify(e) === JSON.stringify(value))) {
|
|
3481
|
+
addError(errors, path2, `must be one of: ${s.enum.map((e) => JSON.stringify(e)).join(", ")}`);
|
|
3482
|
+
}
|
|
3483
|
+
if (typeof value === "string") {
|
|
3484
|
+
if (typeof s.pattern === "string" && !new RegExp(s.pattern).test(value))
|
|
3485
|
+
addError(errors, path2, `does not match pattern ${s.pattern}`);
|
|
3486
|
+
if (typeof s.minLength === "number" && value.length < s.minLength)
|
|
3487
|
+
addError(errors, path2, `must be at least ${s.minLength} characters`);
|
|
3488
|
+
if (typeof s.maxLength === "number" && value.length > s.maxLength)
|
|
3489
|
+
addError(errors, path2, `must be at most ${s.maxLength} characters`);
|
|
3490
|
+
}
|
|
3491
|
+
if (typeof value === "number") {
|
|
3492
|
+
if (typeof s.minimum === "number" && value < s.minimum)
|
|
3493
|
+
addError(errors, path2, `must be >= ${s.minimum}`);
|
|
3494
|
+
if (typeof s.maximum === "number" && value > s.maximum)
|
|
3495
|
+
addError(errors, path2, `must be <= ${s.maximum}`);
|
|
3496
|
+
}
|
|
3497
|
+
if (Array.isArray(value) && s.items) {
|
|
3498
|
+
value.forEach((item, i) => validateSchema(item, s.items, components, childPath(path2, i), errors));
|
|
3499
|
+
}
|
|
3500
|
+
if (jsonTypeOf(value) === "object") {
|
|
3501
|
+
const obj = value;
|
|
3502
|
+
if (s.required) {
|
|
3503
|
+
for (const prop of s.required) {
|
|
3504
|
+
if (obj[prop] === void 0)
|
|
3505
|
+
addError(errors, childPath(path2, prop), "is required");
|
|
3506
|
+
}
|
|
3507
|
+
}
|
|
3508
|
+
if (s.properties) {
|
|
3509
|
+
for (const [prop, sub] of Object.entries(s.properties)) {
|
|
3510
|
+
if (obj[prop] !== void 0)
|
|
3511
|
+
validateSchema(obj[prop], sub, components, childPath(path2, prop), errors);
|
|
3512
|
+
}
|
|
3513
|
+
}
|
|
3514
|
+
if (s.additionalProperties !== void 0 && s.additionalProperties !== true) {
|
|
3515
|
+
const known = new Set(Object.keys(s.properties ?? {}));
|
|
3516
|
+
for (const prop of Object.keys(obj)) {
|
|
3517
|
+
if (known.has(prop)) continue;
|
|
3518
|
+
if (s.additionalProperties === false)
|
|
3519
|
+
addError(errors, childPath(path2, prop), "unknown property");
|
|
3520
|
+
else
|
|
3521
|
+
validateSchema(
|
|
3522
|
+
obj[prop],
|
|
3523
|
+
s.additionalProperties,
|
|
3524
|
+
components,
|
|
3525
|
+
childPath(path2, prop),
|
|
3526
|
+
errors
|
|
3527
|
+
);
|
|
3528
|
+
}
|
|
3529
|
+
}
|
|
3530
|
+
}
|
|
3531
|
+
return errors;
|
|
3532
|
+
}
|
|
3533
|
+
function validateRequestBody(requestBody, body, components) {
|
|
3534
|
+
const content = requestBody.content?.["application/json"] ?? Object.values(requestBody.content ?? {})[0];
|
|
3535
|
+
const schema = content?.schema;
|
|
3536
|
+
if (body === void 0 || body === null) {
|
|
3537
|
+
if (requestBody.required) {
|
|
3538
|
+
throw {
|
|
3539
|
+
status: 400,
|
|
3540
|
+
data: {
|
|
3541
|
+
message: "Request body validation failed",
|
|
3542
|
+
fieldErrors: { $: "request body is required" }
|
|
3543
|
+
}
|
|
3544
|
+
};
|
|
3545
|
+
}
|
|
3546
|
+
return;
|
|
3547
|
+
}
|
|
3548
|
+
if (!schema) return;
|
|
3549
|
+
const fieldErrors = validateSchema(body, schema, components);
|
|
3550
|
+
if (Object.keys(fieldErrors).length > 0) {
|
|
3551
|
+
throw {
|
|
3552
|
+
status: 400,
|
|
3553
|
+
data: { message: "Request body validation failed", fieldErrors }
|
|
3554
|
+
};
|
|
3555
|
+
}
|
|
3556
|
+
}
|
|
3557
|
+
function validateResponseBody(responses, status, value, components, mode) {
|
|
3558
|
+
const response = responses[String(status)] ?? responses.default;
|
|
3559
|
+
const content = response?.content?.["application/json"] ?? Object.values(response?.content ?? {})[0];
|
|
3560
|
+
const schema = content?.schema;
|
|
3561
|
+
if (!schema) return;
|
|
3562
|
+
const fieldErrors = validateSchema(value, schema, components);
|
|
3563
|
+
if (Object.keys(fieldErrors).length === 0) return;
|
|
3564
|
+
if (mode === "warn") {
|
|
3565
|
+
console.warn("[apiBuilder] response body validation failed:", fieldErrors);
|
|
3566
|
+
return;
|
|
3567
|
+
}
|
|
3568
|
+
throw {
|
|
3569
|
+
status: 500,
|
|
3570
|
+
data: { message: "Response body validation failed", fieldErrors }
|
|
3571
|
+
};
|
|
3572
|
+
}
|
|
2123
3573
|
async function buildModule(service, key) {
|
|
2124
3574
|
const instance = service.data ? service.data(key) : { $key: key };
|
|
2125
3575
|
if (service.methods) {
|
|
@@ -2136,20 +3586,18 @@ async function buildModule(service, key) {
|
|
|
2136
3586
|
}
|
|
2137
3587
|
async function resolveInstance(service, modules, building, req) {
|
|
2138
3588
|
if (typeof service.scope !== "function") {
|
|
2139
|
-
return modules
|
|
3589
|
+
return modules.singleton;
|
|
2140
3590
|
}
|
|
2141
3591
|
const key = service.scope(req);
|
|
2142
3592
|
if (key === null) {
|
|
2143
3593
|
return buildModule(service, null);
|
|
2144
3594
|
}
|
|
2145
3595
|
if (modules[key]) return modules[key];
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
});
|
|
2152
|
-
}
|
|
3596
|
+
building[key] ??= buildModule(service, key).then((instance) => {
|
|
3597
|
+
modules[key] = instance;
|
|
3598
|
+
delete building[key];
|
|
3599
|
+
return instance;
|
|
3600
|
+
});
|
|
2153
3601
|
return building[key];
|
|
2154
3602
|
}
|
|
2155
3603
|
function sendJson(res, data) {
|
|
@@ -2166,59 +3614,91 @@ function sendError(res, err) {
|
|
|
2166
3614
|
res.status(status).send(e?.message ?? "Internal error");
|
|
2167
3615
|
}
|
|
2168
3616
|
}
|
|
2169
|
-
function apiBuilder(service) {
|
|
3617
|
+
function apiBuilder(service, options) {
|
|
2170
3618
|
const api = router_default();
|
|
2171
3619
|
const modules = {};
|
|
2172
3620
|
const building = {};
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
3621
|
+
const routes = collectRoutes(service);
|
|
3622
|
+
if (service.auth?.authenticate)
|
|
3623
|
+
api.use("/", service.auth.authenticate);
|
|
3624
|
+
const defaultCheck = (ctx, required) => {
|
|
3625
|
+
const user = ctx.user;
|
|
3626
|
+
if (!user)
|
|
3627
|
+
throw { status: 401, message: "Authentication required" };
|
|
3628
|
+
const perms = user.permissions ?? [];
|
|
3629
|
+
if (!required.every((p) => perms.includes(p))) {
|
|
3630
|
+
throw {
|
|
3631
|
+
status: 403,
|
|
3632
|
+
message: `Insufficient permissions. Required: ${required.join(", ")}`
|
|
2179
3633
|
};
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
3634
|
+
}
|
|
3635
|
+
};
|
|
3636
|
+
const check = service.auth?.check ?? defaultCheck;
|
|
3637
|
+
const validation = options ?? service.validate;
|
|
3638
|
+
const validateRequests = validation === true || typeof validation === "object" && validation.validateRequests !== false;
|
|
3639
|
+
const validateResponses = typeof validation === "object" ? validation.validateResponses ?? false : false;
|
|
3640
|
+
const schemaComponents = {
|
|
3641
|
+
...service.openapi?.schemas,
|
|
3642
|
+
...service.schemas
|
|
3643
|
+
};
|
|
3644
|
+
function buildRoutes(verbRoutes, register) {
|
|
3645
|
+
for (const route of verbRoutes) {
|
|
3646
|
+
register(route.path, (req, res, next) => {
|
|
3647
|
+
const routeParams = req.queries?.route ?? {};
|
|
2185
3648
|
const ctx = {
|
|
2186
3649
|
query: {
|
|
2187
|
-
route:
|
|
3650
|
+
route: routeParams,
|
|
2188
3651
|
url: req.queries?.url ?? {}
|
|
2189
3652
|
},
|
|
3653
|
+
params: routeParams,
|
|
2190
3654
|
path: req.path,
|
|
2191
|
-
user: req.user
|
|
3655
|
+
user: req.user,
|
|
3656
|
+
state: {}
|
|
2192
3657
|
};
|
|
2193
3658
|
const body = req.body;
|
|
2194
|
-
resolveInstance(service, modules, building, req).then((instance) => {
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
sendError(res, err);
|
|
2204
|
-
});
|
|
3659
|
+
resolveInstance(service, modules, building, req).then(async (instance) => {
|
|
3660
|
+
if (route.permission)
|
|
3661
|
+
await check(ctx, route.permission);
|
|
3662
|
+
if (validateRequests && route.meta?.requestBody)
|
|
3663
|
+
validateRequestBody(route.meta.requestBody, body, schemaComponents);
|
|
3664
|
+
for (const guard of route.guards) {
|
|
3665
|
+
const produced = await guard(ctx, req);
|
|
3666
|
+
if (produced && typeof produced === "object")
|
|
3667
|
+
Object.assign(ctx.state, produced);
|
|
2205
3668
|
}
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
3669
|
+
const val = await route.handler.apply(instance, [ctx, body]);
|
|
3670
|
+
if (val !== void 0 && val !== null && val !== false && val !== 0 && val !== "") {
|
|
3671
|
+
if (validateResponses && route.meta?.responses)
|
|
3672
|
+
validateResponseBody(route.meta.responses, 200, val, schemaComponents, validateResponses);
|
|
3673
|
+
sendJson(res, val);
|
|
3674
|
+
} else {
|
|
2209
3675
|
res.status(201).end();
|
|
3676
|
+
}
|
|
2210
3677
|
}).catch((err) => {
|
|
3678
|
+
if (service.onError) {
|
|
3679
|
+
let override;
|
|
3680
|
+
try {
|
|
3681
|
+
override = service.onError(err, ctx, req);
|
|
3682
|
+
} catch (hookErr) {
|
|
3683
|
+
next(hookErr);
|
|
3684
|
+
return;
|
|
3685
|
+
}
|
|
3686
|
+
if (override !== void 0) {
|
|
3687
|
+
sendError(res, override);
|
|
3688
|
+
return;
|
|
3689
|
+
}
|
|
3690
|
+
}
|
|
2211
3691
|
sendError(res, err);
|
|
2212
3692
|
});
|
|
2213
3693
|
});
|
|
2214
3694
|
}
|
|
2215
3695
|
}
|
|
2216
3696
|
function registerAllRoutes() {
|
|
2217
|
-
buildRoutes(
|
|
2218
|
-
buildRoutes(
|
|
2219
|
-
buildRoutes(
|
|
2220
|
-
buildRoutes(
|
|
2221
|
-
buildRoutes(
|
|
3697
|
+
buildRoutes(routes.filter((r) => r.verb === "GET"), (path2, h) => api.get(path2, h));
|
|
3698
|
+
buildRoutes(routes.filter((r) => r.verb === "POST"), (path2, h) => api.post(path2, h));
|
|
3699
|
+
buildRoutes(routes.filter((r) => r.verb === "PUT"), (path2, h) => api.put(path2, h));
|
|
3700
|
+
buildRoutes(routes.filter((r) => r.verb === "DELETE"), (path2, h) => api.delete(path2, h));
|
|
3701
|
+
buildRoutes(routes.filter((r) => r.verb === "PATCH"), (path2, h) => api.patch(path2, h));
|
|
2222
3702
|
}
|
|
2223
3703
|
if (typeof service.scope !== "function") {
|
|
2224
3704
|
let ready = false;
|
|
@@ -2231,7 +3711,7 @@ function apiBuilder(service) {
|
|
|
2231
3711
|
next();
|
|
2232
3712
|
});
|
|
2233
3713
|
buildModule(service, "singleton").then((instance) => {
|
|
2234
|
-
modules
|
|
3714
|
+
modules.singleton = instance;
|
|
2235
3715
|
ready = true;
|
|
2236
3716
|
registerAllRoutes();
|
|
2237
3717
|
}).catch((err) => {
|
|
@@ -2247,7 +3727,7 @@ function apiBuilder(service) {
|
|
|
2247
3727
|
let cached = null;
|
|
2248
3728
|
const contentType = format === "yaml" ? "application/yaml; charset=utf-8" : "application/json; charset=utf-8";
|
|
2249
3729
|
return function(_req, res) {
|
|
2250
|
-
|
|
3730
|
+
cached ??= serializeSpec(openApiSpec(service, opts), format);
|
|
2251
3731
|
res.setHeader("Content-Type", contentType);
|
|
2252
3732
|
res.end(cached);
|
|
2253
3733
|
};
|
|
@@ -2561,6 +4041,7 @@ function securityHeaders(opts) {
|
|
|
2561
4041
|
createMapTokenStore,
|
|
2562
4042
|
createRouter,
|
|
2563
4043
|
csrf,
|
|
4044
|
+
defineController,
|
|
2564
4045
|
describe,
|
|
2565
4046
|
formData,
|
|
2566
4047
|
formEncoded,
|
|
@@ -2573,11 +4054,13 @@ function securityHeaders(opts) {
|
|
|
2573
4054
|
parseBody,
|
|
2574
4055
|
parseMultipartBody,
|
|
2575
4056
|
rateLimit,
|
|
4057
|
+
raw,
|
|
2576
4058
|
requestId,
|
|
2577
4059
|
securityHeaders,
|
|
2578
4060
|
sendFile,
|
|
2579
4061
|
serializeSpec,
|
|
2580
4062
|
serveFile,
|
|
2581
4063
|
serveStatic,
|
|
2582
|
-
streamFormData
|
|
4064
|
+
streamFormData,
|
|
4065
|
+
text
|
|
2583
4066
|
});
|