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.
Files changed (73) hide show
  1. package/CHANGELOG.md +138 -0
  2. package/CONTRIBUTING.md +150 -0
  3. package/README.md +278 -779
  4. package/dist/apis.d.ts +372 -12
  5. package/dist/apis.d.ts.map +1 -1
  6. package/dist/apis.js +483 -65
  7. package/dist/apis.js.map +1 -1
  8. package/dist/cjs/index.js +2290 -807
  9. package/dist/git.d.ts +1 -1
  10. package/dist/git.d.ts.map +1 -1
  11. package/dist/git.js +5 -5
  12. package/dist/git.js.map +1 -1
  13. package/dist/http-objects.d.ts +26 -0
  14. package/dist/http-objects.d.ts.map +1 -0
  15. package/dist/http-objects.js +588 -0
  16. package/dist/http-objects.js.map +1 -0
  17. package/dist/index.d.ts +6 -5
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +2 -1
  20. package/dist/index.js.map +1 -1
  21. package/dist/jwt-auth.d.ts +11 -0
  22. package/dist/jwt-auth.d.ts.map +1 -1
  23. package/dist/jwt-auth.js +9 -9
  24. package/dist/jwt-auth.js.map +1 -1
  25. package/dist/middleware.js +2 -2
  26. package/dist/middleware.js.map +1 -1
  27. package/dist/mimetypes.json +882 -1
  28. package/dist/misc.d.ts +161 -25
  29. package/dist/misc.d.ts.map +1 -1
  30. package/dist/misc.js +228 -80
  31. package/dist/misc.js.map +1 -1
  32. package/dist/openapi.d.ts +156 -13
  33. package/dist/openapi.d.ts.map +1 -1
  34. package/dist/openapi.js +214 -71
  35. package/dist/openapi.js.map +1 -1
  36. package/dist/router-types.d.ts +760 -0
  37. package/dist/router-types.d.ts.map +1 -0
  38. package/dist/router-types.js +23 -0
  39. package/dist/router-types.js.map +1 -0
  40. package/dist/router.d.ts +7 -530
  41. package/dist/router.d.ts.map +1 -1
  42. package/dist/router.js +128 -375
  43. package/dist/router.js.map +1 -1
  44. package/dist/static.d.ts +2 -2
  45. package/dist/static.d.ts.map +1 -1
  46. package/dist/static.js +77 -22
  47. package/dist/static.js.map +1 -1
  48. package/docs/THREAT_MODEL.md +52 -0
  49. package/docs/api-builder-v2-design.md +644 -0
  50. package/docs/api-builder-v3-design.md +397 -0
  51. package/docs/api-builder.md +454 -0
  52. package/docs/benchmark.md +27 -0
  53. package/docs/body-parsing.md +223 -0
  54. package/docs/errors.md +359 -0
  55. package/docs/expediate.png +0 -0
  56. package/docs/git.md +139 -0
  57. package/docs/jwt-auth.md +251 -0
  58. package/docs/logo.svg +12 -0
  59. package/docs/middleware.md +264 -0
  60. package/docs/openapi.md +180 -0
  61. package/docs/router.md +356 -0
  62. package/docs/static.md +128 -0
  63. package/docs/wiki.json +123 -0
  64. package/package.json +30 -8
  65. package/dist/cjs/apis.js +0 -327
  66. package/dist/cjs/git.js +0 -293
  67. package/dist/cjs/jwt-auth.js +0 -532
  68. package/dist/cjs/middleware.js +0 -511
  69. package/dist/cjs/mimetypes.json +0 -1
  70. package/dist/cjs/misc.js +0 -787
  71. package/dist/cjs/openapi.js +0 -485
  72. package/dist/cjs/router.js +0 -898
  73. 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/misc.ts
74
- var import_stream = require("stream");
75
- var import_zlib = __toESM(require("zlib"), 1);
76
- var DECOMPRESS_ALGO = {
77
- gzip: import_zlib.default.gunzip,
78
- deflate: import_zlib.default.inflate
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
- function readSize(value) {
81
- if (typeof value === "number") return value;
82
- const fmt = /^(\d+(\.\d+)?)([kmg]?b?)?$/i.exec(value);
83
- if (!fmt) return 0;
84
- const num = parseFloat(fmt[1] ?? "0");
85
- const sfx = (fmt[3] ?? "b").toLowerCase();
86
- if (sfx[0] === "k") return num * 1024;
87
- if (sfx[0] === "m") return num * 1024 * 1024;
88
- if (sfx[0] === "g") return num * 1024 * 1024 * 1024;
89
- return num;
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
- function readReqBody(req, opts, mimetype) {
140
- return new Promise((resolve, reject) => {
141
- const length = parseInt(req.headers["content-length"] ?? "0", 10);
142
- const isChunked = req.headers["transfer-encoding"]?.split(",").map((v) => v.trim()).some((v) => v.toLowerCase() === "chunked") ?? false;
143
- if (!isChunked && (!length || length === 0)) return resolve(null);
144
- const maxLength = readSize(opts.limit) || 102400;
145
- if (!isChunked && length > maxLength)
146
- return reject({ status: 413, message: "Content Too Large" });
147
- const encoding = req.headers["content-encoding"];
148
- if (encoding && (opts.inflate === false || !DECOMPRESS_ALGO[encoding]))
149
- return reject({ status: 415, message: "Unsupported Media Type: Wrong Content-Encoding" });
150
- const decompress = (encoding ? DECOMPRESS_ALGO[encoding] : void 0) ?? ((d, c) => c(null, d));
151
- const contentType = req.headers["content-type"] ?? "";
152
- if (mimetype && contentType.split(";")[0].trim() !== mimetype)
153
- return reject({ status: 415, message: "Unsupported Media Type: Wrong Content-Type" });
154
- let data = Buffer.alloc(0);
155
- req.on("data", (chunk) => {
156
- if (data === null) return;
157
- const next_ = Buffer.concat([data, chunk]);
158
- if (next_.length > maxLength) {
159
- data = null;
160
- reject({ status: 413, message: "Content Too Large" });
161
- return;
162
- }
163
- data = next_;
164
- });
165
- req.on("end", () => {
166
- if (data === null) return;
167
- decompress(data, (err, decompressed) => {
168
- if (err) return reject({ status: 500, message: err.message });
169
- resolve({ mimetype: contentType ?? "", content: decompressed });
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 readBodyAsJson(req, res, next, opts, contentType, data) {
184
- const charset = extractCharset(contentType);
185
- try {
186
- const parsed = JSON.parse(
187
- data.toString(charset),
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 parseMultipartBody(contentType, data) {
200
- const boundary = contentType.split(";").map((s) => s.replace(/^\s+|\s+$/g, "")).find((s) => s.startsWith("boundary="))?.substring("boundary=".length);
201
- if (!boundary)
202
- throw { status: 400, message: "Bad Request: missing multipart boundary" };
203
- const delimiter = Buffer.from(`\r
204
- --${boundary}`);
205
- const normalized = Buffer.concat([Buffer.from("\r\n"), data]);
206
- const rawParts = splitBuffer(normalized, delimiter);
207
- const parts = [];
208
- for (const part of rawParts) {
209
- if (part.toString("utf8", 0, 2) === "--") continue;
210
- const partContent = part.slice(2);
211
- const blankLine = Buffer.from("\r\n\r\n");
212
- const blankIdx = partContent.indexOf(blankLine);
213
- if (blankIdx === -1) continue;
214
- const headerSection = partContent.slice(0, blankIdx).toString("utf8");
215
- const content = partContent.slice(blankIdx + blankLine.length);
216
- const headers = {};
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
- return parts;
1060
+ list.push(str.substring(start, end));
1061
+ return list;
228
1062
  }
229
- function readBodyAsFormData(req, res, next, contentType, data) {
230
- try {
231
- req.body = parseMultipartBody(contentType, data);
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 readBodyAsFormEncoded(req, res, next, contentType, data) {
239
- const charset = extractCharset(contentType);
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
258
1069
  }
259
- var BODY_READERS = {
260
- "multipart/form-data": (req, res, next, _opts, ct, data) => readBodyAsFormData(req, res, next, ct, data),
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["etag"];
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["etag"];
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>&nbsp;</td><td align="right"> - </td><td>&nbsp;</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>&nbsp;</td><td align="right"> - </td><td>&nbsp;</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="${name}">${name}</a></td><td align="right">${modified}</td><td align="right">${size}</td><td>&nbsp;</td></tr>
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>&nbsp;</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"] || req.headers["if-unmodified-since"]);
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
- const originalUrl = decodeURIComponent(req.path ?? req.url ?? "/");
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
- return { method, path: path2, regex: compilePattern(path2), stripPath, middleware };
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) return false;
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
- if (errorHandler) {
1170
- try {
1171
- errorHandler(e, req, res);
1172
- } catch (e2) {
1173
- if (!res.writableEnded) res.status(500).end(`Error ${method} ${url}`);
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
- invoke(layer.middleware, () => {
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
- next();
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
- // ── setNotFound ──────────────────────────────────────────────────────────
1264
- setNotFound(handler) {
1265
- notFoundHandler = handler;
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[0] === "H";
2573
+ return alg.startsWith("H");
1396
2574
  }
1397
2575
  function isEc(alg) {
1398
- return alg[0] === "E";
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["authorization"];
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
- /[{}\[\]]/.test(s) || // Control characters.
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
- const schemas = {
2064
- ...builtinSchemas,
2065
- ...svcMeta?.schemas,
2066
- ...opts.schemas
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
- if (defaultTag) {
2074
- tags.push({ name: defaultTag, description: svcMeta?.tagDescription });
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
- for (const verb of VERBS) {
2078
- const routeMap = service[verb];
2079
- if (!routeMap) continue;
2080
- for (const [pattern, handler] of Object.entries(routeMap)) {
2081
- const openApiPath = toOpenApiPath(pattern, basePath);
2082
- if (!paths[openApiPath]) paths[openApiPath] = {};
2083
- const meta = handler[DESCRIBE_META];
2084
- const annotatedParams = meta?.parameters ?? [];
2085
- const inferredParams = extractPathParams(pattern, annotatedParams);
2086
- const parameters = [...annotatedParams, ...inferredParams];
2087
- const operationResponses = meta?.responses ? buildAnnotatedResponses(meta.responses) : buildDefaultResponses(verb);
2088
- const operation = {
2089
- operationId: meta?.operationId ?? buildOperationId(verb, pattern),
2090
- ...meta?.summary && { summary: meta.summary },
2091
- ...meta?.description && { description: meta.description },
2092
- ...meta?.deprecated && { deprecated: true },
2093
- ...parameters.length > 0 && { parameters },
2094
- ...meta?.requestBody && { requestBody: meta.requestBody },
2095
- responses: operationResponses
2096
- };
2097
- const opTags = meta?.tags ?? (defaultTag ? [defaultTag] : void 0);
2098
- if (opTags) operation["tags"] = opTags;
2099
- if (meta) {
2100
- for (const [k, v] of Object.entries(meta)) {
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: { schemas, responses }
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["singleton"];
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
- if (!building[key]) {
2147
- building[key] = buildModule(service, key).then((instance) => {
2148
- modules[key] = instance;
2149
- delete building[key];
2150
- return instance;
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
- function buildRoutes(routeMap, register) {
2174
- if (!routeMap) return;
2175
- const sortedPaths = Object.keys(routeMap).sort((a, b) => {
2176
- const score = (p) => {
2177
- const segs = p.split("/").filter((s) => s.length > 0);
2178
- return segs.length * 100 - segs.filter((s) => s.startsWith(":")).length * 10;
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
- return score(b) - score(a) || b.localeCompare(a);
2181
- });
2182
- for (const path2 of sortedPaths) {
2183
- const method = routeMap[path2];
2184
- register(path2, (req, res) => {
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: req.queries?.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
- const ret = method.apply(instance, [ctx, body]);
2196
- if (ret instanceof Promise) {
2197
- return ret.then((val) => {
2198
- if (val !== void 0 && val !== null && val !== false && val !== 0 && val !== "")
2199
- sendJson(res, val);
2200
- else
2201
- res.status(201).end();
2202
- }).catch((err) => {
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
- if (ret !== void 0 && ret !== null && ret !== false && ret !== 0 && ret !== "")
2207
- sendJson(res, ret);
2208
- else
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(service.GET, (path2, h) => api.get(path2, h));
2218
- buildRoutes(service.POST, (path2, h) => api.post(path2, h));
2219
- buildRoutes(service.PUT, (path2, h) => api.put(path2, h));
2220
- buildRoutes(service.DELETE, (path2, h) => api.delete(path2, h));
2221
- buildRoutes(service.PATCH, (path2, h) => api.patch(path2, h));
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["singleton"] = instance;
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
- if (!cached) cached = serializeSpec(openApiSpec(service, opts), format);
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
  });