expediate 1.0.4 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +138 -0
- package/CONTRIBUTING.md +150 -0
- package/LICENSE +16 -16
- package/README.md +330 -444
- package/dist/apis.d.ts +504 -27
- package/dist/apis.d.ts.map +1 -1
- package/dist/apis.js +618 -107
- package/dist/apis.js.map +1 -1
- package/dist/cjs/index.js +4066 -0
- package/dist/cjs/package.json +1 -0
- package/dist/git.d.ts +72 -9
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js +129 -74
- package/dist/git.js.map +1 -1
- package/dist/http-objects.d.ts +26 -0
- package/dist/http-objects.d.ts.map +1 -0
- package/dist/http-objects.js +588 -0
- package/dist/http-objects.js.map +1 -0
- package/dist/index.d.ts +18 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -24
- package/dist/index.js.map +1 -1
- package/dist/jwt-auth.d.ts +158 -57
- package/dist/jwt-auth.d.ts.map +1 -1
- package/dist/jwt-auth.js +447 -207
- package/dist/jwt-auth.js.map +1 -1
- package/dist/middleware.d.ts +476 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +647 -0
- package/dist/middleware.js.map +1 -0
- package/dist/mimetypes.json +882 -1
- package/dist/misc.d.ts +268 -25
- package/dist/misc.d.ts.map +1 -1
- package/dist/misc.js +449 -168
- package/dist/misc.js.map +1 -1
- package/dist/openapi.d.ts +433 -0
- package/dist/openapi.d.ts.map +1 -0
- package/dist/openapi.js +624 -0
- package/dist/openapi.js.map +1 -0
- package/dist/router-types.d.ts +760 -0
- package/dist/router-types.d.ts.map +1 -0
- package/dist/router-types.js +23 -0
- package/dist/router-types.js.map +1 -0
- package/dist/router.d.ts +37 -201
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +502 -244
- package/dist/router.js.map +1 -1
- package/dist/static.d.ts +3 -3
- package/dist/static.d.ts.map +1 -1
- package/dist/static.js +164 -105
- package/dist/static.js.map +1 -1
- package/docs/THREAT_MODEL.md +52 -0
- package/docs/api-builder-v2-design.md +644 -0
- package/docs/api-builder-v3-design.md +397 -0
- package/docs/api-builder.md +454 -0
- package/docs/benchmark.md +27 -0
- package/docs/body-parsing.md +223 -0
- package/docs/errors.md +359 -0
- package/docs/expediate.png +0 -0
- package/docs/git.md +139 -0
- package/docs/jwt-auth.md +251 -0
- package/docs/logo.svg +12 -0
- package/docs/middleware.md +264 -0
- package/docs/openapi.md +180 -0
- package/docs/router.md +356 -0
- package/docs/static.md +128 -0
- package/docs/wiki.json +123 -0
- package/package.json +47 -8
- package/.npmignore +0 -16
|
@@ -0,0 +1,4066 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
DESCRIBE_META: () => DESCRIBE_META,
|
|
34
|
+
apiBuilder: () => apis_default,
|
|
35
|
+
cacheControl: () => cacheControl,
|
|
36
|
+
compress: () => compress,
|
|
37
|
+
conditionalGet: () => conditionalGet,
|
|
38
|
+
cors: () => cors,
|
|
39
|
+
createJwtPlugin: () => jwt_auth_default,
|
|
40
|
+
createMapTokenStore: () => createMapTokenStore,
|
|
41
|
+
createRouter: () => router_default,
|
|
42
|
+
csrf: () => csrf,
|
|
43
|
+
defineController: () => defineController,
|
|
44
|
+
describe: () => describe,
|
|
45
|
+
formData: () => formData,
|
|
46
|
+
formEncoded: () => formEncoded,
|
|
47
|
+
gitCreate: () => gitCreate,
|
|
48
|
+
gitHandler: () => gitHandler,
|
|
49
|
+
json: () => json,
|
|
50
|
+
logger: () => logger,
|
|
51
|
+
mime: () => mime,
|
|
52
|
+
openApiSpec: () => openApiSpec,
|
|
53
|
+
parseBody: () => parseBody,
|
|
54
|
+
parseMultipartBody: () => parseMultipartBody,
|
|
55
|
+
rateLimit: () => rateLimit,
|
|
56
|
+
raw: () => raw,
|
|
57
|
+
requestId: () => requestId,
|
|
58
|
+
securityHeaders: () => securityHeaders,
|
|
59
|
+
sendFile: () => sendFile,
|
|
60
|
+
serializeSpec: () => serializeSpec,
|
|
61
|
+
serveFile: () => serveFile,
|
|
62
|
+
serveStatic: () => serveStatic,
|
|
63
|
+
streamFormData: () => streamFormData,
|
|
64
|
+
text: () => text
|
|
65
|
+
});
|
|
66
|
+
module.exports = __toCommonJS(index_exports);
|
|
67
|
+
|
|
68
|
+
// src/router.ts
|
|
69
|
+
var http = __toESM(require("http"), 1);
|
|
70
|
+
var https = __toESM(require("https"), 1);
|
|
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);
|
|
76
|
+
var path = __toESM(require("path"), 1);
|
|
77
|
+
|
|
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"]
|
|
964
|
+
};
|
|
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]);
|
|
976
|
+
}
|
|
977
|
+
}
|
|
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();
|
|
1030
|
+
});
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
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);
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
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;
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
list.push(str.substring(start, end));
|
|
1061
|
+
return list;
|
|
1062
|
+
}
|
|
1063
|
+
function parseHttpDate(date) {
|
|
1064
|
+
const timestamp = date ? Date.parse(date) : NaN;
|
|
1065
|
+
return typeof timestamp === "number" ? timestamp : NaN;
|
|
1066
|
+
}
|
|
1067
|
+
function htmlEscape(str) {
|
|
1068
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1069
|
+
}
|
|
1070
|
+
function encodePath(urlPath) {
|
|
1071
|
+
return urlPath.split("/").map((s) => encodeURIComponent(s)).join("/");
|
|
1072
|
+
}
|
|
1073
|
+
function conditionMatch(reqHeaders, resHeaders) {
|
|
1074
|
+
const match = reqHeaders["if-match"];
|
|
1075
|
+
if (match) {
|
|
1076
|
+
const etag = resHeaders.etag;
|
|
1077
|
+
if (match === "*" || match === etag) return true;
|
|
1078
|
+
for (const tag of parseTokenList(match)) {
|
|
1079
|
+
if (tag === etag || `W/${tag}` === etag || tag === `W/${etag}`)
|
|
1080
|
+
return true;
|
|
1081
|
+
}
|
|
1082
|
+
return false;
|
|
1083
|
+
}
|
|
1084
|
+
const lastModified = parseHttpDate(resHeaders["last-modified"]);
|
|
1085
|
+
const unmodifiedSince = parseHttpDate(reqHeaders["if-unmodified-since"]);
|
|
1086
|
+
if (!isNaN(unmodifiedSince) && !isNaN(lastModified))
|
|
1087
|
+
return lastModified <= unmodifiedSince;
|
|
1088
|
+
return false;
|
|
1089
|
+
}
|
|
1090
|
+
function isCacheFresh(reqHeaders, resHeaders) {
|
|
1091
|
+
const CACHE_CONTROL_NO_CACHE_REGEXP = /(?:^|,)\s*?no-cache\s*?(?:,|$)/;
|
|
1092
|
+
const modifiedSince = reqHeaders["if-modified-since"];
|
|
1093
|
+
const noneMatch = reqHeaders["if-none-match"];
|
|
1094
|
+
if (!modifiedSince && !noneMatch) return false;
|
|
1095
|
+
const cacheControl2 = reqHeaders["cache-control"];
|
|
1096
|
+
if (cacheControl2 && CACHE_CONTROL_NO_CACHE_REGEXP.test(cacheControl2))
|
|
1097
|
+
return false;
|
|
1098
|
+
if (noneMatch && noneMatch !== "*") {
|
|
1099
|
+
const etag = resHeaders.etag;
|
|
1100
|
+
if (!etag) return false;
|
|
1101
|
+
let etagStale = true;
|
|
1102
|
+
for (const match of parseTokenList(noneMatch)) {
|
|
1103
|
+
if (match === etag || `W/${match}` === etag || match === `W/${etag}`) {
|
|
1104
|
+
etagStale = false;
|
|
1105
|
+
break;
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
if (etagStale) return false;
|
|
1109
|
+
}
|
|
1110
|
+
if (modifiedSince) {
|
|
1111
|
+
const lastModified = resHeaders["last-modified"];
|
|
1112
|
+
if (!lastModified) return false;
|
|
1113
|
+
if (!(parseHttpDate(lastModified) <= parseHttpDate(modifiedSince)))
|
|
1114
|
+
return false;
|
|
1115
|
+
}
|
|
1116
|
+
return true;
|
|
1117
|
+
}
|
|
1118
|
+
function resolveOptions(root, options) {
|
|
1119
|
+
if (!root)
|
|
1120
|
+
throw new TypeError("root path required");
|
|
1121
|
+
if (typeof root !== "string")
|
|
1122
|
+
throw new TypeError("root path must be a string");
|
|
1123
|
+
const mergedHeaders = { ...DEFAULT_OPTIONS.headers, ...options?.headers ?? {} };
|
|
1124
|
+
const opts = { ...DEFAULT_OPTIONS, ...options, headers: mergedHeaders };
|
|
1125
|
+
opts.fallthrough = options?.fallthrough !== false;
|
|
1126
|
+
opts.redirect = options?.redirect !== false;
|
|
1127
|
+
opts.maxage = options?.maxage ?? options?.maxAge ?? 0;
|
|
1128
|
+
opts.root = import_path.default.resolve(root);
|
|
1129
|
+
return opts;
|
|
1130
|
+
}
|
|
1131
|
+
function writeIndexOf(urlPath, directoryPath, parentUrlPath, queryString, callback) {
|
|
1132
|
+
import_fs.default.readdir(directoryPath, (err, files) => {
|
|
1133
|
+
if (err) return callback(null, err);
|
|
1134
|
+
const params = new URLSearchParams(queryString.replace(/;/g, "&"));
|
|
1135
|
+
const rawCol = params.get("C") ?? "N";
|
|
1136
|
+
const rawOrd = params.get("O") ?? "A";
|
|
1137
|
+
const col = ["N", "M", "S"].includes(rawCol) ? rawCol : "N";
|
|
1138
|
+
const ord = rawOrd === "D" ? "D" : "A";
|
|
1139
|
+
const entries = [];
|
|
1140
|
+
for (const file of files) {
|
|
1141
|
+
const fullPath = import_path.default.join(directoryPath, file);
|
|
1142
|
+
let stat;
|
|
1143
|
+
try {
|
|
1144
|
+
stat = import_fs.default.statSync(fullPath);
|
|
1145
|
+
} catch {
|
|
1146
|
+
continue;
|
|
1147
|
+
}
|
|
1148
|
+
entries.push({ file, stat, isDir: stat.isDirectory() });
|
|
1149
|
+
}
|
|
1150
|
+
const sign = ord === "A" ? 1 : -1;
|
|
1151
|
+
entries.sort((a, b) => {
|
|
1152
|
+
if (a.isDir !== b.isDir) return a.isDir ? -1 : 1;
|
|
1153
|
+
let cmp = 0;
|
|
1154
|
+
if (col === "N") cmp = a.file.localeCompare(b.file);
|
|
1155
|
+
else if (col === "M") cmp = a.stat.mtime.getTime() - b.stat.mtime.getTime();
|
|
1156
|
+
else if (col === "S") cmp = (a.isDir ? 0 : a.stat.size) - (b.isDir ? 0 : b.stat.size);
|
|
1157
|
+
return cmp * sign;
|
|
1158
|
+
});
|
|
1159
|
+
function thLink(c, label) {
|
|
1160
|
+
const nextOrd = c === col && ord === "A" ? "D" : "A";
|
|
1161
|
+
return `<a href="?C=${c};O=${nextOrd}">${label}</a>`;
|
|
1162
|
+
}
|
|
1163
|
+
let html = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">\n';
|
|
1164
|
+
html += "<html>\n";
|
|
1165
|
+
html += `<head><title>Index of ${htmlEscape(urlPath)}</title></head>
|
|
1166
|
+
`;
|
|
1167
|
+
html += `<body><h1>Index of ${htmlEscape(urlPath)}</h1><table>
|
|
1168
|
+
`;
|
|
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>
|
|
1170
|
+
`;
|
|
1171
|
+
html += '<tr><th colspan="5"><hr></th></tr>\n';
|
|
1172
|
+
if (parentUrlPath)
|
|
1173
|
+
html += `<tr><td valign="top"><img src="/icons/back.gif" alt="[PARENTDIR]"></td><td><a href="${encodePath(parentUrlPath)}">Parent Directory</a></td><td> </td><td align="right"> - </td><td> </td></tr>
|
|
1174
|
+
`;
|
|
1175
|
+
for (const { file, stat, isDir } of entries) {
|
|
1176
|
+
const fullPath = import_path.default.join(directoryPath, file);
|
|
1177
|
+
const mimeType = mime.lookup(fullPath, "");
|
|
1178
|
+
const mediaType = mimeType.includes("/") ? mimeType.split("/")[0] : "";
|
|
1179
|
+
const alt = isDir ? "folder" : mediaType || "unknown";
|
|
1180
|
+
const icon = `/icons/${alt}.gif`;
|
|
1181
|
+
const name = file + (isDir ? "/" : "");
|
|
1182
|
+
const hrefName = encodeURIComponent(file) + (isDir ? "/" : "");
|
|
1183
|
+
const displayName = htmlEscape(name);
|
|
1184
|
+
const modified = stat.mtime.toUTCString();
|
|
1185
|
+
const size = isDir ? "-" : String(stat.size);
|
|
1186
|
+
html += `<tr><td valign="top"><img src="${icon}" alt="[${alt.toUpperCase()}]"></td><td><a href="${hrefName}">${displayName}</a></td><td align="right">${modified}</td><td align="right">${size}</td><td> </td></tr>
|
|
1187
|
+
`;
|
|
1188
|
+
}
|
|
1189
|
+
html += '<tr><th colspan="5"><hr></th></tr>\n';
|
|
1190
|
+
html += "</table><address>Expediate/1.0.0</address></body></html>\n";
|
|
1191
|
+
return callback(html, null);
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1194
|
+
function sendIt(req, res, pathname, stat, opts) {
|
|
1195
|
+
const len = stat.size;
|
|
1196
|
+
const etag = createETag(stat);
|
|
1197
|
+
if (opts.headers) {
|
|
1198
|
+
for (const [key, value] of Object.entries(opts.headers))
|
|
1199
|
+
res.setHeader(key, value);
|
|
1200
|
+
}
|
|
1201
|
+
if (!res.getHeader("Cache-Control") && opts.maxage) {
|
|
1202
|
+
let cacheControl2 = `public, max-age=${Math.floor(opts.maxage / 1e3)}`;
|
|
1203
|
+
if (opts.immutable) cacheControl2 += ", immutable";
|
|
1204
|
+
res.setHeader("Cache-Control", cacheControl2);
|
|
1205
|
+
}
|
|
1206
|
+
if (!res.getHeader("Last-Modified") && opts.lastModified !== false)
|
|
1207
|
+
res.setHeader("Last-Modified", stat.mtime.toUTCString());
|
|
1208
|
+
if (!res.getHeader("ETag") && opts.etag !== false)
|
|
1209
|
+
res.setHeader("ETag", etag);
|
|
1210
|
+
if (!res.getHeader("Content-Type")) {
|
|
1211
|
+
if (opts.contentType) {
|
|
1212
|
+
res.setHeader("Content-Type", opts.contentType);
|
|
1213
|
+
} else {
|
|
1214
|
+
const type = mime.lookup(pathname, "");
|
|
1215
|
+
if (type) {
|
|
1216
|
+
const charset = mime.charsets(type);
|
|
1217
|
+
res.setHeader("Content-Type", charset ? `${type}; charset=${charset}` : type);
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
if (isCacheFresh(
|
|
1222
|
+
req.headers,
|
|
1223
|
+
res.getHeaders()
|
|
1224
|
+
)) {
|
|
1225
|
+
removeContentHeaders(res);
|
|
1226
|
+
HTTP.NOT_MODIFIED(res, opts);
|
|
1227
|
+
return;
|
|
1228
|
+
}
|
|
1229
|
+
const hasPrecondition = !!(req.headers["if-match"] ?? req.headers["if-unmodified-since"]);
|
|
1230
|
+
if (hasPrecondition && !conditionMatch(
|
|
1231
|
+
req.headers,
|
|
1232
|
+
res.getHeaders()
|
|
1233
|
+
)) {
|
|
1234
|
+
HTTP.PRECONDITION_FAILS(res, opts);
|
|
1235
|
+
return;
|
|
1236
|
+
}
|
|
1237
|
+
res.setHeader("Content-Length", len);
|
|
1238
|
+
if (req.method === "HEAD") {
|
|
1239
|
+
res.end();
|
|
1240
|
+
return;
|
|
1241
|
+
}
|
|
1242
|
+
let finished = false;
|
|
1243
|
+
const stream = import_fs.default.createReadStream(pathname);
|
|
1244
|
+
res.on("finish", () => {
|
|
1245
|
+
finished = true;
|
|
1246
|
+
destroyReadStream(stream);
|
|
1247
|
+
});
|
|
1248
|
+
stream.on("error", (err) => {
|
|
1249
|
+
if (finished) return;
|
|
1250
|
+
console.warn("static stream error:", pathname, err);
|
|
1251
|
+
HTTP.INTERNAL_ERROR(res, opts, err.code ?? "UNKNOWN");
|
|
1252
|
+
finished = true;
|
|
1253
|
+
destroyReadStream(stream);
|
|
1254
|
+
});
|
|
1255
|
+
stream.on("end", () => res.end());
|
|
1256
|
+
stream.pipe(res);
|
|
1257
|
+
}
|
|
1258
|
+
function sendFile(req, res, pathname, opts) {
|
|
1259
|
+
import_fs.default.stat(pathname, (err, stat) => {
|
|
1260
|
+
if (err) {
|
|
1261
|
+
if (err.code === "ENOENT" || err.code === "ENAMETOOLONG" || err.code === "ENOTDIR") return HTTP.NOT_FOUND(res, opts);
|
|
1262
|
+
return HTTP.INTERNAL_ERROR(res, opts, err.code ?? "UNKNOWN");
|
|
1263
|
+
}
|
|
1264
|
+
if (stat.isDirectory()) {
|
|
1265
|
+
if (opts.indexOf) {
|
|
1266
|
+
const parentUrl = req.path !== "/" ? import_path.default.dirname(req.path) : null;
|
|
1267
|
+
const queryString = (req.url ?? "").split("?")[1] ?? "";
|
|
1268
|
+
return writeIndexOf(req.path, pathname, parentUrl, queryString, (html, indexErr) => {
|
|
1269
|
+
if (indexErr)
|
|
1270
|
+
return HTTP.INTERNAL_ERROR(res, opts, indexErr.code ?? "UNKNOWN");
|
|
1271
|
+
return res.status(200, opts.headers).send(html);
|
|
1272
|
+
});
|
|
1273
|
+
}
|
|
1274
|
+
return HTTP.NOT_FOUND(res, opts);
|
|
1275
|
+
}
|
|
1276
|
+
sendIt(req, res, pathname, stat, opts);
|
|
1277
|
+
});
|
|
1278
|
+
}
|
|
1279
|
+
function serveStatic(root, options) {
|
|
1280
|
+
const opts = resolveOptions(root, options);
|
|
1281
|
+
return function(req, res, next) {
|
|
1282
|
+
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
1283
|
+
if (opts.fallthrough)
|
|
1284
|
+
return next();
|
|
1285
|
+
return HTTP.NOT_ALLOWED(res, opts);
|
|
1286
|
+
}
|
|
1287
|
+
let originalUrl;
|
|
1288
|
+
try {
|
|
1289
|
+
originalUrl = decodeURIComponent(req.path ?? req.url ?? "/");
|
|
1290
|
+
} catch {
|
|
1291
|
+
return HTTP.BAD_REQUEST(res, opts);
|
|
1292
|
+
}
|
|
1293
|
+
let pathname = originalUrl;
|
|
1294
|
+
if (pathname === "/" && !originalUrl.endsWith("/"))
|
|
1295
|
+
pathname = "";
|
|
1296
|
+
if (CONTROL_CHAR_REGEXP.test(pathname)) {
|
|
1297
|
+
if (opts.fallthrough)
|
|
1298
|
+
return next();
|
|
1299
|
+
return HTTP.NOT_FOUND(res, opts);
|
|
1300
|
+
}
|
|
1301
|
+
if (UP_PATH_REGEXP.test(pathname))
|
|
1302
|
+
return HTTP.FORBIDDEN(res, opts);
|
|
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);
|
|
1306
|
+
if (opts.dotfiles !== "allow" && pathname.includes("/.")) {
|
|
1307
|
+
if (opts.dotfiles === "deny")
|
|
1308
|
+
return HTTP.FORBIDDEN(res, opts);
|
|
1309
|
+
return HTTP.NOT_FOUND(res, opts);
|
|
1310
|
+
}
|
|
1311
|
+
import_fs.default.stat(pathname, (err, stat) => {
|
|
1312
|
+
if (err) {
|
|
1313
|
+
if (err.code === "ENOENT" || err.code === "ENAMETOOLONG" || err.code === "ENOTDIR") {
|
|
1314
|
+
if (opts.fallthrough)
|
|
1315
|
+
return next();
|
|
1316
|
+
return HTTP.NOT_FOUND(res, opts);
|
|
1317
|
+
}
|
|
1318
|
+
return HTTP.INTERNAL_ERROR(res, opts, err.code ?? "UNKNOWN");
|
|
1319
|
+
}
|
|
1320
|
+
if (stat.isDirectory()) {
|
|
1321
|
+
if (!opts.redirect) {
|
|
1322
|
+
if (opts.fallthrough)
|
|
1323
|
+
return next();
|
|
1324
|
+
return HTTP.NOT_FOUND(res, opts);
|
|
1325
|
+
}
|
|
1326
|
+
return sendFile(req, res, import_path.default.join(pathname, "index.html"), opts);
|
|
1327
|
+
}
|
|
1328
|
+
sendIt(req, res, pathname, stat, opts);
|
|
1329
|
+
});
|
|
1330
|
+
};
|
|
1331
|
+
}
|
|
1332
|
+
function serveFile(filePath, options) {
|
|
1333
|
+
const opts = resolveOptions(filePath, options);
|
|
1334
|
+
return function(req, res, next) {
|
|
1335
|
+
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
1336
|
+
if (opts.fallthrough)
|
|
1337
|
+
return next();
|
|
1338
|
+
return HTTP.NOT_ALLOWED(res, opts);
|
|
1339
|
+
}
|
|
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");
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
// src/router.ts
|
|
2127
|
+
function isGlobPattern(pattern) {
|
|
2128
|
+
let i = 0;
|
|
2129
|
+
while (i < pattern.length) {
|
|
2130
|
+
const ch = pattern[i];
|
|
2131
|
+
if (ch === "\\") {
|
|
2132
|
+
i += 2;
|
|
2133
|
+
continue;
|
|
2134
|
+
}
|
|
2135
|
+
if (ch === ":") {
|
|
2136
|
+
i++;
|
|
2137
|
+
while (i < pattern.length && /\w/.test(pattern[i])) i++;
|
|
2138
|
+
if (i < pattern.length && pattern[i] === "(") {
|
|
2139
|
+
let depth = 1;
|
|
2140
|
+
i++;
|
|
2141
|
+
while (i < pattern.length && depth > 0) {
|
|
2142
|
+
if (pattern[i] === "\\") {
|
|
2143
|
+
i += 2;
|
|
2144
|
+
continue;
|
|
2145
|
+
}
|
|
2146
|
+
if (pattern[i] === "(") depth++;
|
|
2147
|
+
else if (pattern[i] === ")") depth--;
|
|
2148
|
+
i++;
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2151
|
+
continue;
|
|
2152
|
+
}
|
|
2153
|
+
if (ch === "*" || ch === "?") return true;
|
|
2154
|
+
i++;
|
|
2155
|
+
}
|
|
2156
|
+
return false;
|
|
2157
|
+
}
|
|
2158
|
+
function compileGlob(glob, exact = false) {
|
|
2159
|
+
let src = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
2160
|
+
src = src.replace(/\*\*/g, "\0GLOBSTAR\0").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]").replace(/\x00GLOBSTAR\x00/g, ".*");
|
|
2161
|
+
return new RegExp("^" + src + (exact ? "$" : ""));
|
|
2162
|
+
}
|
|
2163
|
+
function extractInlinePattern(str, openIdx) {
|
|
2164
|
+
let depth = 0;
|
|
2165
|
+
let i = openIdx;
|
|
2166
|
+
for (; i < str.length; i++) {
|
|
2167
|
+
if (str[i] === "\\") {
|
|
2168
|
+
i++;
|
|
2169
|
+
continue;
|
|
2170
|
+
}
|
|
2171
|
+
if (str[i] === "(") {
|
|
2172
|
+
depth++;
|
|
2173
|
+
continue;
|
|
2174
|
+
}
|
|
2175
|
+
if (str[i] === ")") {
|
|
2176
|
+
depth--;
|
|
2177
|
+
if (depth === 0) break;
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
if (depth !== 0)
|
|
2181
|
+
throw new SyntaxError(`Unbalanced parentheses in route segment '${str}'`);
|
|
2182
|
+
return { pattern: str.slice(openIdx + 1, i), closeIdx: i };
|
|
2183
|
+
}
|
|
2184
|
+
function compilePlainPath(path2, exact = false) {
|
|
2185
|
+
const segments = path2.split("/").filter((s) => s.length > 0);
|
|
2186
|
+
const src = segments.map((seg) => {
|
|
2187
|
+
if (!seg.startsWith(":"))
|
|
2188
|
+
return seg.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
2189
|
+
const parenIdx = seg.indexOf("(", 1);
|
|
2190
|
+
if (parenIdx === -1) {
|
|
2191
|
+
return `(?<${seg.slice(1)}>[^/]+)`;
|
|
2192
|
+
}
|
|
2193
|
+
const name = seg.slice(1, parenIdx);
|
|
2194
|
+
if (!name)
|
|
2195
|
+
throw new SyntaxError(`Route parameter missing name before '(' in segment '${seg}'`);
|
|
2196
|
+
const { pattern, closeIdx } = extractInlinePattern(seg, parenIdx);
|
|
2197
|
+
if (/\(\?<[^>]+>/.test(pattern))
|
|
2198
|
+
throw new SyntaxError(
|
|
2199
|
+
`Inline constraint for ':${name}' must not contain named capture groups.`
|
|
2200
|
+
);
|
|
2201
|
+
const suffix = seg.slice(closeIdx + 1);
|
|
2202
|
+
const escapedSuffix = suffix.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
2203
|
+
return `(?<${name}>${pattern})${escapedSuffix}`;
|
|
2204
|
+
}).join("/");
|
|
2205
|
+
try {
|
|
2206
|
+
return new RegExp(exact ? "^/?" + src + (src ? "/?" : "") + "$" : "^/?" + src + "(?=/|$)");
|
|
2207
|
+
} catch (e) {
|
|
2208
|
+
throw new SyntaxError(
|
|
2209
|
+
`Invalid inline regex constraint in path '${path2}': ${e.message}`,
|
|
2210
|
+
{ cause: e }
|
|
2211
|
+
);
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
function compilePattern(path2, exact = false) {
|
|
2215
|
+
if (path2 instanceof RegExp) return path2;
|
|
2216
|
+
if (isGlobPattern(path2)) return compileGlob(path2, exact);
|
|
2217
|
+
return compilePlainPath(path2, exact);
|
|
2218
|
+
}
|
|
2219
|
+
function buildRouteLayer(method, path2, middleware, stripPath) {
|
|
2220
|
+
if (typeof middleware !== "function")
|
|
2221
|
+
throw new TypeError("Incorrect middleware type: expected a function");
|
|
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 };
|
|
2227
|
+
}
|
|
2228
|
+
function matchRouteLayer(layer, req, path2) {
|
|
2229
|
+
if (layer.method && layer.method !== req.method && !(req.method === "HEAD" && layer.method === "GET"))
|
|
2230
|
+
return false;
|
|
2231
|
+
const m = layer.regex.exec(path2);
|
|
2232
|
+
if (m === null) return false;
|
|
2233
|
+
const captured = m.groups ?? {};
|
|
2234
|
+
if (layer.stripPath) {
|
|
2235
|
+
req.path = path2.slice(m[0].length) || "/";
|
|
2236
|
+
}
|
|
2237
|
+
req.queries.route = captured;
|
|
2238
|
+
Object.assign(req.params, captured);
|
|
2239
|
+
return true;
|
|
2240
|
+
}
|
|
2241
|
+
function pathMatchesLayer(layer, path2) {
|
|
2242
|
+
return layer.regex.test(path2);
|
|
2243
|
+
}
|
|
2244
|
+
function registerRoute(routes, method, path2, arg, stripPath) {
|
|
2245
|
+
if (Array.isArray(arg)) {
|
|
2246
|
+
for (const item of arg) registerRoute(routes, method, path2, item, stripPath);
|
|
2247
|
+
} else if (typeof arg === "function") {
|
|
2248
|
+
routes.push(buildRouteLayer(method, path2, arg, stripPath));
|
|
2249
|
+
} else if (arg && typeof arg.listener === "function") {
|
|
2250
|
+
routes.push(buildRouteLayer(method, path2, arg.listener, stripPath));
|
|
2251
|
+
} else {
|
|
2252
|
+
throw new TypeError(
|
|
2253
|
+
"Unexpected value registered as middleware: expected a Middleware function, a Router instance, or an array of either"
|
|
2254
|
+
);
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
function extractRouterPrefix(arg) {
|
|
2258
|
+
if (Array.isArray(arg) || typeof arg === "function") return void 0;
|
|
2259
|
+
return arg.prefix;
|
|
2260
|
+
}
|
|
2261
|
+
function createRouter(prefixOrOpts, opts) {
|
|
2262
|
+
const routerPrefix = typeof prefixOrOpts === "string" ? prefixOrOpts : void 0;
|
|
2263
|
+
const options = typeof prefixOrOpts === "object" ? prefixOrOpts : opts ?? {};
|
|
2264
|
+
const secret = options.secret;
|
|
2265
|
+
const timeoutMs = options.timeout;
|
|
2266
|
+
const trustProxy = options.trustProxy ?? false;
|
|
2267
|
+
const routes = [];
|
|
2268
|
+
const errorHandlers = [];
|
|
2269
|
+
let errorHandler;
|
|
2270
|
+
let activeServer = null;
|
|
2271
|
+
const activeSockets = /* @__PURE__ */ new Set();
|
|
2272
|
+
const listener = (req, res, done) => {
|
|
2273
|
+
const method = req.method;
|
|
2274
|
+
const url = req.url;
|
|
2275
|
+
let idx = 0;
|
|
2276
|
+
updateHttpObjects(req, res, secret, trustProxy);
|
|
2277
|
+
if (timeoutMs) {
|
|
2278
|
+
if (req.socket) req.socket.setTimeout(timeoutMs);
|
|
2279
|
+
const timer = setTimeout(() => {
|
|
2280
|
+
if (!res.writableEnded) res.status(408).end("Request Timeout");
|
|
2281
|
+
}, timeoutMs);
|
|
2282
|
+
res.once("finish", () => {
|
|
2283
|
+
clearTimeout(timer);
|
|
2284
|
+
if (req.socket) req.socket.setTimeout(0);
|
|
2285
|
+
});
|
|
2286
|
+
}
|
|
2287
|
+
const invokeErrorHandler = (e) => {
|
|
2288
|
+
if (res.writableEnded) return;
|
|
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;
|
|
2312
|
+
}
|
|
2313
|
+
res.status(500).end(`Error ${method} ${url}`);
|
|
2314
|
+
};
|
|
2315
|
+
runNext(e);
|
|
2316
|
+
};
|
|
2317
|
+
const invoke = (mw, nextFn) => {
|
|
2318
|
+
try {
|
|
2319
|
+
const ret = mw(req, res, nextFn);
|
|
2320
|
+
if (ret instanceof Promise) ret.catch(invokeErrorHandler);
|
|
2321
|
+
} catch (e) {
|
|
2322
|
+
invokeErrorHandler(e);
|
|
2323
|
+
}
|
|
2324
|
+
};
|
|
2325
|
+
const allowedMethods = /* @__PURE__ */ new Set();
|
|
2326
|
+
const next = (err) => {
|
|
2327
|
+
if (err != null) {
|
|
2328
|
+
invokeErrorHandler(err);
|
|
2329
|
+
return;
|
|
2330
|
+
}
|
|
2331
|
+
while (idx < routes.length) {
|
|
2332
|
+
const layer = routes[idx++];
|
|
2333
|
+
const pathBefore = req.path;
|
|
2334
|
+
if (matchRouteLayer(layer, req, req.path)) {
|
|
2335
|
+
if (layer.stripPath) {
|
|
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) => {
|
|
2340
|
+
req.path = pathBefore;
|
|
2341
|
+
req.baseUrl = baseUrlBefore;
|
|
2342
|
+
next(err2);
|
|
2343
|
+
});
|
|
2344
|
+
return;
|
|
2345
|
+
}
|
|
2346
|
+
invoke(layer.middleware, next);
|
|
2347
|
+
return;
|
|
2348
|
+
}
|
|
2349
|
+
if (layer.method !== null && pathMatchesLayer(layer, pathBefore)) {
|
|
2350
|
+
allowedMethods.add(layer.method);
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
if (allowedMethods.size > 0) {
|
|
2354
|
+
if (allowedMethods.has("GET")) allowedMethods.add("HEAD");
|
|
2355
|
+
allowedMethods.add("OPTIONS");
|
|
2356
|
+
const allow = [...allowedMethods].sort().join(", ");
|
|
2357
|
+
if (method === "OPTIONS") {
|
|
2358
|
+
res.status(204, { Allow: allow }).end();
|
|
2359
|
+
return;
|
|
2360
|
+
}
|
|
2361
|
+
res.status(405, { Allow: allow }).end(`Cannot ${method} ${url}`);
|
|
2362
|
+
return;
|
|
2363
|
+
}
|
|
2364
|
+
if (done) return done();
|
|
2365
|
+
res.status(404).end(`Cannot ${method} ${url}`);
|
|
2366
|
+
};
|
|
2367
|
+
try {
|
|
2368
|
+
next();
|
|
2369
|
+
} catch (e) {
|
|
2370
|
+
invokeErrorHandler(e);
|
|
2371
|
+
}
|
|
2372
|
+
};
|
|
2373
|
+
function makeRegister(method, stripPath) {
|
|
2374
|
+
return (path2, ...args) => {
|
|
2375
|
+
for (const arg of args) registerRoute(routes, method, path2, arg, stripPath);
|
|
2376
|
+
};
|
|
2377
|
+
}
|
|
2378
|
+
const use = (pathOrFirst, ...args) => {
|
|
2379
|
+
if (typeof pathOrFirst === "string" || pathOrFirst instanceof RegExp) {
|
|
2380
|
+
for (const arg of args) registerRoute(routes, null, pathOrFirst, arg, true);
|
|
2381
|
+
} else {
|
|
2382
|
+
const inferredPath = extractRouterPrefix(pathOrFirst) ?? "/";
|
|
2383
|
+
registerRoute(routes, null, inferredPath, pathOrFirst, true);
|
|
2384
|
+
for (const arg of args) registerRoute(routes, null, "/", arg, true);
|
|
2385
|
+
}
|
|
2386
|
+
};
|
|
2387
|
+
const router = {
|
|
2388
|
+
prefix: routerPrefix,
|
|
2389
|
+
listener,
|
|
2390
|
+
use,
|
|
2391
|
+
all: makeRegister(null, false),
|
|
2392
|
+
get: makeRegister("GET", false),
|
|
2393
|
+
put: makeRegister("PUT", false),
|
|
2394
|
+
post: makeRegister("POST", false),
|
|
2395
|
+
delete: makeRegister("DELETE", false),
|
|
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
|
+
},
|
|
2437
|
+
// ── onError ─────────────────────────────────────────────────────────────
|
|
2438
|
+
onError(handler) {
|
|
2439
|
+
errorHandler = handler;
|
|
2440
|
+
},
|
|
2441
|
+
// ── error ────────────────────────────────────────────────────────────────
|
|
2442
|
+
error(handler) {
|
|
2443
|
+
errorHandlers.push(handler);
|
|
2444
|
+
},
|
|
2445
|
+
// ── routes ───────────────────────────────────────────────────────────────
|
|
2446
|
+
routes() {
|
|
2447
|
+
return routes.map((l) => ({
|
|
2448
|
+
method: l.method,
|
|
2449
|
+
path: l.path,
|
|
2450
|
+
stripPath: l.stripPath
|
|
2451
|
+
}));
|
|
2452
|
+
},
|
|
2453
|
+
// ── shutdown ─────────────────────────────────────────────────────────────
|
|
2454
|
+
shutdown(timeout = 5e3) {
|
|
2455
|
+
if (!activeServer) return Promise.resolve();
|
|
2456
|
+
return new Promise((resolve, reject) => {
|
|
2457
|
+
activeServer.close((err) => {
|
|
2458
|
+
if (err) reject(err);
|
|
2459
|
+
else resolve();
|
|
2460
|
+
});
|
|
2461
|
+
if (timeout > 0) {
|
|
2462
|
+
setTimeout(() => {
|
|
2463
|
+
for (const socket of activeSockets) socket.destroy();
|
|
2464
|
+
activeSockets.clear();
|
|
2465
|
+
}, timeout);
|
|
2466
|
+
}
|
|
2467
|
+
});
|
|
2468
|
+
},
|
|
2469
|
+
// ── listen ───────────────────────────────────────────────────────────────
|
|
2470
|
+
listen(port, opts2, cb) {
|
|
2471
|
+
if (typeof opts2 === "function") {
|
|
2472
|
+
cb = opts2;
|
|
2473
|
+
opts2 = void 0;
|
|
2474
|
+
}
|
|
2475
|
+
const rawListener = listener;
|
|
2476
|
+
let server;
|
|
2477
|
+
const tlsOpts = opts2;
|
|
2478
|
+
if (tlsOpts?.key && tlsOpts?.cert) {
|
|
2479
|
+
if (tlsOpts.http2) {
|
|
2480
|
+
server = http2.createSecureServer(tlsOpts, rawListener);
|
|
2481
|
+
} else {
|
|
2482
|
+
server = https.createServer(tlsOpts, rawListener);
|
|
2483
|
+
}
|
|
2484
|
+
} else {
|
|
2485
|
+
server = http.createServer(rawListener);
|
|
2486
|
+
}
|
|
2487
|
+
server.on("connection", (socket) => {
|
|
2488
|
+
activeSockets.add(socket);
|
|
2489
|
+
socket.once("close", () => activeSockets.delete(socket));
|
|
2490
|
+
});
|
|
2491
|
+
activeServer = server;
|
|
2492
|
+
server.listen(port, cb);
|
|
2493
|
+
return server;
|
|
2494
|
+
}
|
|
2495
|
+
};
|
|
2496
|
+
return router;
|
|
2497
|
+
}
|
|
2498
|
+
var router_default = createRouter;
|
|
2499
|
+
|
|
2500
|
+
// src/jwt-auth.ts
|
|
2501
|
+
var import_crypto = __toESM(require("crypto"), 1);
|
|
2502
|
+
function hashPassword(password) {
|
|
2503
|
+
return import_crypto.default.createHash("sha256").update(password).digest("hex");
|
|
2504
|
+
}
|
|
2505
|
+
var userDatabase = /* @__PURE__ */ new Map([
|
|
2506
|
+
["alice", {
|
|
2507
|
+
id: "usr_001",
|
|
2508
|
+
username: "alice",
|
|
2509
|
+
passwordHash: hashPassword("password123"),
|
|
2510
|
+
roles: ["admin", "editor"],
|
|
2511
|
+
permissions: ["read", "write", "delete", "manage_users"]
|
|
2512
|
+
}],
|
|
2513
|
+
["bob", {
|
|
2514
|
+
id: "usr_002",
|
|
2515
|
+
username: "bob",
|
|
2516
|
+
passwordHash: hashPassword("secret456"),
|
|
2517
|
+
roles: ["editor"],
|
|
2518
|
+
permissions: ["read", "write"]
|
|
2519
|
+
}],
|
|
2520
|
+
["charlie", {
|
|
2521
|
+
id: "usr_003",
|
|
2522
|
+
username: "charlie",
|
|
2523
|
+
passwordHash: hashPassword("pass789"),
|
|
2524
|
+
roles: ["viewer"],
|
|
2525
|
+
permissions: ["read"]
|
|
2526
|
+
}]
|
|
2527
|
+
]);
|
|
2528
|
+
function createMapTokenStore() {
|
|
2529
|
+
const map = /* @__PURE__ */ new Map();
|
|
2530
|
+
return {
|
|
2531
|
+
set(jti, record) {
|
|
2532
|
+
map.set(jti, record);
|
|
2533
|
+
},
|
|
2534
|
+
get(jti) {
|
|
2535
|
+
const record = map.get(jti);
|
|
2536
|
+
if (!record) return void 0;
|
|
2537
|
+
if (Date.now() > record.expiresAt) {
|
|
2538
|
+
map.delete(jti);
|
|
2539
|
+
return void 0;
|
|
2540
|
+
}
|
|
2541
|
+
return record;
|
|
2542
|
+
},
|
|
2543
|
+
delete(jti) {
|
|
2544
|
+
map.delete(jti);
|
|
2545
|
+
},
|
|
2546
|
+
deleteBySubject(sub) {
|
|
2547
|
+
for (const [jti, record] of map) {
|
|
2548
|
+
if (record.sub === sub) map.delete(jti);
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2551
|
+
};
|
|
2552
|
+
}
|
|
2553
|
+
var DEFAULT_CONFIG = {
|
|
2554
|
+
accessTokenSecret: "access-secret-change-in-production",
|
|
2555
|
+
refreshTokenSecret: "refresh-secret-change-in-production",
|
|
2556
|
+
accessTokenExpiry: 15 * 60,
|
|
2557
|
+
// 15 minutes
|
|
2558
|
+
refreshTokenExpiry: 7 * 24 * 3600,
|
|
2559
|
+
// 7 days
|
|
2560
|
+
issuer: "jwt-auth",
|
|
2561
|
+
checkIssuer: false,
|
|
2562
|
+
alg: "HS256",
|
|
2563
|
+
username: (user) => user.username,
|
|
2564
|
+
fetchUser: (sub) => userDatabase.get(sub),
|
|
2565
|
+
isPasswordValid: (user, password) => user.passwordHash === hashPassword(password),
|
|
2566
|
+
payload: (user) => ({
|
|
2567
|
+
sub: user.username,
|
|
2568
|
+
roles: user.roles,
|
|
2569
|
+
permissions: user.permissions
|
|
2570
|
+
})
|
|
2571
|
+
};
|
|
2572
|
+
function isHmac(alg) {
|
|
2573
|
+
return alg.startsWith("H");
|
|
2574
|
+
}
|
|
2575
|
+
function isEc(alg) {
|
|
2576
|
+
return alg.startsWith("E");
|
|
2577
|
+
}
|
|
2578
|
+
function nodeHashAlg(alg) {
|
|
2579
|
+
return `SHA${alg.slice(2)}`;
|
|
2580
|
+
}
|
|
2581
|
+
function ecByteLength(alg) {
|
|
2582
|
+
if (alg === "ES256") return 32;
|
|
2583
|
+
if (alg === "ES384") return 48;
|
|
2584
|
+
if (alg === "ES512") return 66;
|
|
2585
|
+
throw new Error(`Not an EC algorithm: ${alg}`);
|
|
2586
|
+
}
|
|
2587
|
+
function encodeDerLength(len) {
|
|
2588
|
+
if (len < 128) return Buffer.from([len]);
|
|
2589
|
+
if (len < 256) return Buffer.from([129, len]);
|
|
2590
|
+
return Buffer.from([130, len >> 8, len & 255]);
|
|
2591
|
+
}
|
|
2592
|
+
function derToJose(der, alg) {
|
|
2593
|
+
const n = ecByteLength(alg);
|
|
2594
|
+
let i = 0;
|
|
2595
|
+
if (der[i++] !== 48) throw new Error("ECDSA DER: expected SEQUENCE tag 0x30");
|
|
2596
|
+
if (der[i] & 128) i += (der[i] & 127) + 1;
|
|
2597
|
+
else i++;
|
|
2598
|
+
function readInt() {
|
|
2599
|
+
if (der[i++] !== 2) throw new Error("ECDSA DER: expected INTEGER tag 0x02");
|
|
2600
|
+
let len = der[i++];
|
|
2601
|
+
if (len & 128) {
|
|
2602
|
+
const nb = len & 127;
|
|
2603
|
+
len = 0;
|
|
2604
|
+
for (let k = 0; k < nb; k++) len = len << 8 | der[i++];
|
|
2605
|
+
}
|
|
2606
|
+
if (der[i] === 0 && len > 1) {
|
|
2607
|
+
i++;
|
|
2608
|
+
len--;
|
|
2609
|
+
}
|
|
2610
|
+
const val = der.slice(i, i + len);
|
|
2611
|
+
i += len;
|
|
2612
|
+
return val;
|
|
2613
|
+
}
|
|
2614
|
+
const r = readInt();
|
|
2615
|
+
const s = readInt();
|
|
2616
|
+
const out = Buffer.alloc(2 * n, 0);
|
|
2617
|
+
r.copy(out, n - r.length);
|
|
2618
|
+
s.copy(out, 2 * n - s.length);
|
|
2619
|
+
return out;
|
|
2620
|
+
}
|
|
2621
|
+
function joseToDer(jose, alg) {
|
|
2622
|
+
const n = ecByteLength(alg);
|
|
2623
|
+
const r = jose.slice(0, n);
|
|
2624
|
+
const s = jose.slice(n);
|
|
2625
|
+
function encodeInt(buf) {
|
|
2626
|
+
let start = 0;
|
|
2627
|
+
while (start < buf.length - 1 && buf[start] === 0) start++;
|
|
2628
|
+
const trimmed = buf.slice(start);
|
|
2629
|
+
const padded = trimmed[0] & 128 ? Buffer.concat([Buffer.from([0]), trimmed]) : trimmed;
|
|
2630
|
+
return Buffer.concat([Buffer.from([2, padded.length]), padded]);
|
|
2631
|
+
}
|
|
2632
|
+
const rDer = encodeInt(r);
|
|
2633
|
+
const sDer = encodeInt(s);
|
|
2634
|
+
const content = Buffer.concat([rDer, sDer]);
|
|
2635
|
+
const lenBuf = encodeDerLength(content.length);
|
|
2636
|
+
return Buffer.concat([Buffer.from([48]), lenBuf, content]);
|
|
2637
|
+
}
|
|
2638
|
+
function base64UrlEncode(data) {
|
|
2639
|
+
return Buffer.from(JSON.stringify(data)).toString("base64").replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
2640
|
+
}
|
|
2641
|
+
function base64UrlDecode(str) {
|
|
2642
|
+
const padded = str + "=".repeat((4 - str.length % 4) % 4);
|
|
2643
|
+
return JSON.parse(
|
|
2644
|
+
Buffer.from(padded.replace(/-/g, "+").replace(/_/g, "/"), "base64").toString("utf8")
|
|
2645
|
+
);
|
|
2646
|
+
}
|
|
2647
|
+
function computeSignature(encodedHeader, encodedPayload, key, alg) {
|
|
2648
|
+
const signingInput = `${encodedHeader}.${encodedPayload}`;
|
|
2649
|
+
if (isHmac(alg)) {
|
|
2650
|
+
return import_crypto.default.createHmac(`sha${alg.slice(2)}`, key).update(signingInput).digest("base64").replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
2651
|
+
}
|
|
2652
|
+
const hashAlg = nodeHashAlg(alg);
|
|
2653
|
+
const derOrRaw = import_crypto.default.sign(hashAlg, Buffer.from(signingInput), key);
|
|
2654
|
+
const sigBytes = isEc(alg) ? derToJose(derOrRaw, alg) : derOrRaw;
|
|
2655
|
+
return sigBytes.toString("base64").replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
2656
|
+
}
|
|
2657
|
+
function checkSignature(encodedHeader, encodedPayload, b64sig, key, alg) {
|
|
2658
|
+
if (isHmac(alg)) {
|
|
2659
|
+
const expected = computeSignature(encodedHeader, encodedPayload, key, alg);
|
|
2660
|
+
const sigBuf = Buffer.from(b64sig);
|
|
2661
|
+
const expectedBuf = Buffer.from(expected);
|
|
2662
|
+
return sigBuf.length === expectedBuf.length && import_crypto.default.timingSafeEqual(sigBuf, expectedBuf);
|
|
2663
|
+
}
|
|
2664
|
+
try {
|
|
2665
|
+
const hashAlg = nodeHashAlg(alg);
|
|
2666
|
+
const rawSig = Buffer.from(b64sig.replace(/-/g, "+").replace(/_/g, "/"), "base64");
|
|
2667
|
+
const verBuf = isEc(alg) ? joseToDer(rawSig, alg) : rawSig;
|
|
2668
|
+
const input = Buffer.from(`${encodedHeader}.${encodedPayload}`);
|
|
2669
|
+
return import_crypto.default.verify(hashAlg, input, key, verBuf);
|
|
2670
|
+
} catch {
|
|
2671
|
+
return false;
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2674
|
+
function signToken(payload, key, expiresIn, alg = "HS256") {
|
|
2675
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
2676
|
+
const encodedHeader = base64UrlEncode({ alg, typ: "JWT" });
|
|
2677
|
+
const fullPayload = base64UrlEncode({ ...payload, iat: now, exp: now + expiresIn });
|
|
2678
|
+
const signature = computeSignature(encodedHeader, fullPayload, key, alg);
|
|
2679
|
+
return `${encodedHeader}.${fullPayload}.${signature}`;
|
|
2680
|
+
}
|
|
2681
|
+
function verifyToken(token, key, alg) {
|
|
2682
|
+
try {
|
|
2683
|
+
const parts = token.split(".");
|
|
2684
|
+
if (parts.length !== 3)
|
|
2685
|
+
return { valid: false, error: "Invalid token format" };
|
|
2686
|
+
const [encodedHeader, encodedPayload, signature] = parts;
|
|
2687
|
+
const decodedHeader = base64UrlDecode(encodedHeader);
|
|
2688
|
+
if (decodedHeader.alg !== alg)
|
|
2689
|
+
return { valid: false, error: "Unauthorised signing algorithm" };
|
|
2690
|
+
if (!checkSignature(encodedHeader, encodedPayload, signature, key, alg))
|
|
2691
|
+
return { valid: false, error: "Invalid signature" };
|
|
2692
|
+
const payload = base64UrlDecode(encodedPayload);
|
|
2693
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
2694
|
+
if (payload.exp && payload.exp < now)
|
|
2695
|
+
return { valid: false, error: "Token expired" };
|
|
2696
|
+
return { valid: true, payload };
|
|
2697
|
+
} catch {
|
|
2698
|
+
return { valid: false, error: "Malformed token" };
|
|
2699
|
+
}
|
|
2700
|
+
}
|
|
2701
|
+
function accessSignKey(cfg) {
|
|
2702
|
+
return isHmac(cfg.alg) ? cfg.accessTokenSecret : cfg.accessTokenPrivateKey;
|
|
2703
|
+
}
|
|
2704
|
+
function accessVerifyKey(cfg) {
|
|
2705
|
+
return isHmac(cfg.alg) ? cfg.accessTokenSecret : cfg.accessTokenPublicKey;
|
|
2706
|
+
}
|
|
2707
|
+
function refreshSignKey(cfg) {
|
|
2708
|
+
return isHmac(cfg.alg) ? cfg.refreshTokenSecret : cfg.refreshTokenPrivateKey ?? cfg.accessTokenPrivateKey;
|
|
2709
|
+
}
|
|
2710
|
+
function refreshVerifyKey(cfg) {
|
|
2711
|
+
return isHmac(cfg.alg) ? cfg.refreshTokenSecret : cfg.refreshTokenPublicKey ?? cfg.accessTokenPublicKey;
|
|
2712
|
+
}
|
|
2713
|
+
async function authenticateUser(username, password, config) {
|
|
2714
|
+
const user = await config.fetchUser(username);
|
|
2715
|
+
if (!user) return { success: false, error: "User not found" };
|
|
2716
|
+
if (!await config.isPasswordValid(user, password))
|
|
2717
|
+
return { success: false, error: "Incorrect password" };
|
|
2718
|
+
return issueTokenPair(user, config);
|
|
2719
|
+
}
|
|
2720
|
+
async function issueTokenPair(user, config) {
|
|
2721
|
+
const claims = await config.payload(user);
|
|
2722
|
+
const fullClaims = {
|
|
2723
|
+
sub: config.username(user),
|
|
2724
|
+
// fallback subject — overridden by payload() if it sets sub
|
|
2725
|
+
...claims,
|
|
2726
|
+
iss: config.issuer
|
|
2727
|
+
};
|
|
2728
|
+
const accessToken = signToken(fullClaims, accessSignKey(config), config.accessTokenExpiry, config.alg);
|
|
2729
|
+
if (!config.refreshTokenStore) {
|
|
2730
|
+
return { success: true, accessToken, expiresIn: config.accessTokenExpiry, tokenType: "Bearer" };
|
|
2731
|
+
}
|
|
2732
|
+
const jti = import_crypto.default.randomUUID();
|
|
2733
|
+
const sub = fullClaims.sub;
|
|
2734
|
+
const refreshClaims = {
|
|
2735
|
+
sub,
|
|
2736
|
+
jti,
|
|
2737
|
+
type: "refresh",
|
|
2738
|
+
iss: config.issuer
|
|
2739
|
+
};
|
|
2740
|
+
const refreshToken = signToken(refreshClaims, refreshSignKey(config), config.refreshTokenExpiry, config.alg);
|
|
2741
|
+
await config.refreshTokenStore.set(jti, {
|
|
2742
|
+
sub,
|
|
2743
|
+
issuedAt: Date.now(),
|
|
2744
|
+
expiresAt: Date.now() + config.refreshTokenExpiry * 1e3
|
|
2745
|
+
});
|
|
2746
|
+
return {
|
|
2747
|
+
success: true,
|
|
2748
|
+
accessToken,
|
|
2749
|
+
refreshToken,
|
|
2750
|
+
expiresIn: config.accessTokenExpiry,
|
|
2751
|
+
tokenType: "Bearer"
|
|
2752
|
+
};
|
|
2753
|
+
}
|
|
2754
|
+
async function renewAccessToken(refreshToken, config) {
|
|
2755
|
+
if (!config.refreshTokenStore)
|
|
2756
|
+
return { success: false, error: "Refresh tokens are not configured" };
|
|
2757
|
+
const result = verifyToken(refreshToken, refreshVerifyKey(config), config.alg);
|
|
2758
|
+
if (!result.valid)
|
|
2759
|
+
return { success: false, error: "Invalid or revoked refresh token" };
|
|
2760
|
+
const refreshPayload = result.payload;
|
|
2761
|
+
if (refreshPayload.type !== "refresh")
|
|
2762
|
+
return { success: false, error: "Invalid token type" };
|
|
2763
|
+
const jti = refreshPayload.jti;
|
|
2764
|
+
if (!jti)
|
|
2765
|
+
return { success: false, error: "Invalid refresh token: missing jti" };
|
|
2766
|
+
const record = await config.refreshTokenStore.get(jti);
|
|
2767
|
+
if (!record)
|
|
2768
|
+
return { success: false, error: "Invalid or revoked refresh token" };
|
|
2769
|
+
const user = await config.fetchUser(record.sub);
|
|
2770
|
+
if (!user) {
|
|
2771
|
+
await config.refreshTokenStore.delete(jti);
|
|
2772
|
+
return { success: false, error: "User not found" };
|
|
2773
|
+
}
|
|
2774
|
+
await config.refreshTokenStore.delete(jti);
|
|
2775
|
+
return issueTokenPair(user, config);
|
|
2776
|
+
}
|
|
2777
|
+
async function revokeRefreshToken(refreshToken, config) {
|
|
2778
|
+
if (!config.refreshTokenStore) return;
|
|
2779
|
+
const result = verifyToken(refreshToken, refreshVerifyKey(config), config.alg);
|
|
2780
|
+
if (!result.valid) return;
|
|
2781
|
+
const jti = result.payload.jti;
|
|
2782
|
+
if (jti) await config.refreshTokenStore.delete(jti);
|
|
2783
|
+
}
|
|
2784
|
+
function createJwtPlugin(userConfig = {}) {
|
|
2785
|
+
const config = { ...DEFAULT_CONFIG, ...userConfig };
|
|
2786
|
+
if (!isHmac(config.alg)) {
|
|
2787
|
+
if (!config.accessTokenPrivateKey || !config.accessTokenPublicKey) {
|
|
2788
|
+
throw new Error(
|
|
2789
|
+
`Algorithm '${config.alg}' requires both 'accessTokenPrivateKey' and 'accessTokenPublicKey' in JwtConfig.`
|
|
2790
|
+
);
|
|
2791
|
+
}
|
|
2792
|
+
}
|
|
2793
|
+
function sendJson2(res, status, data) {
|
|
2794
|
+
const body = JSON.stringify(data);
|
|
2795
|
+
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
2796
|
+
res.status(status).send(body);
|
|
2797
|
+
}
|
|
2798
|
+
const login = (req, res) => {
|
|
2799
|
+
(async () => {
|
|
2800
|
+
const { username, password } = req.body ?? {};
|
|
2801
|
+
if (!username || !password) {
|
|
2802
|
+
sendJson2(res, 400, { error: "Fields 'username' and 'password' are required" });
|
|
2803
|
+
return;
|
|
2804
|
+
}
|
|
2805
|
+
const result = await authenticateUser(username, password, config);
|
|
2806
|
+
if (!result.success) {
|
|
2807
|
+
sendJson2(res, 401, { error: result.error });
|
|
2808
|
+
return;
|
|
2809
|
+
}
|
|
2810
|
+
const body = {
|
|
2811
|
+
message: "Authentication successful",
|
|
2812
|
+
accessToken: result.accessToken,
|
|
2813
|
+
expiresIn: result.expiresIn,
|
|
2814
|
+
tokenType: result.tokenType
|
|
2815
|
+
};
|
|
2816
|
+
if (result.refreshToken !== void 0) body.refreshToken = result.refreshToken;
|
|
2817
|
+
sendJson2(res, 200, body);
|
|
2818
|
+
})().catch(() => sendJson2(res, 500, { error: "Internal server error" }));
|
|
2819
|
+
};
|
|
2820
|
+
const refresh = (req, res) => {
|
|
2821
|
+
(async () => {
|
|
2822
|
+
if (!config.refreshTokenStore) {
|
|
2823
|
+
sendJson2(res, 501, { error: "Refresh tokens are not configured" });
|
|
2824
|
+
return;
|
|
2825
|
+
}
|
|
2826
|
+
const { refreshToken } = req.body ?? {};
|
|
2827
|
+
if (!refreshToken) {
|
|
2828
|
+
sendJson2(res, 400, { error: "Field 'refreshToken' is required" });
|
|
2829
|
+
return;
|
|
2830
|
+
}
|
|
2831
|
+
const result = await renewAccessToken(refreshToken, config);
|
|
2832
|
+
if (!result.success) {
|
|
2833
|
+
sendJson2(res, 401, { error: result.error });
|
|
2834
|
+
return;
|
|
2835
|
+
}
|
|
2836
|
+
sendJson2(res, 200, {
|
|
2837
|
+
message: "Token renewed successfully",
|
|
2838
|
+
accessToken: result.accessToken,
|
|
2839
|
+
refreshToken: result.refreshToken,
|
|
2840
|
+
expiresIn: result.expiresIn,
|
|
2841
|
+
tokenType: result.tokenType
|
|
2842
|
+
});
|
|
2843
|
+
})().catch(() => sendJson2(res, 500, { error: "Internal server error" }));
|
|
2844
|
+
};
|
|
2845
|
+
const logout = (req, res) => {
|
|
2846
|
+
(async () => {
|
|
2847
|
+
const { refreshToken } = req.body ?? {};
|
|
2848
|
+
if (refreshToken && config.refreshTokenStore) {
|
|
2849
|
+
await revokeRefreshToken(refreshToken, config);
|
|
2850
|
+
}
|
|
2851
|
+
sendJson2(res, 200, { message: "Logged out successfully" });
|
|
2852
|
+
})().catch(() => sendJson2(res, 500, { error: "Internal server error" }));
|
|
2853
|
+
};
|
|
2854
|
+
const authenticate = (req, res, next) => {
|
|
2855
|
+
delete req.user;
|
|
2856
|
+
const authHeader = req.headers.authorization;
|
|
2857
|
+
if (!authHeader?.startsWith("Bearer ")) return next();
|
|
2858
|
+
const token = authHeader.slice(7);
|
|
2859
|
+
const result = verifyToken(token, accessVerifyKey(config), config.alg);
|
|
2860
|
+
if (!result.valid) return next();
|
|
2861
|
+
if (config.checkIssuer && result.payload.iss !== config.issuer) return next();
|
|
2862
|
+
req.user = result.payload;
|
|
2863
|
+
next();
|
|
2864
|
+
};
|
|
2865
|
+
const authorize = (req, res, next) => {
|
|
2866
|
+
if (!req.user) {
|
|
2867
|
+
sendJson2(res, 401, { error: "Authentication required" });
|
|
2868
|
+
return;
|
|
2869
|
+
}
|
|
2870
|
+
next();
|
|
2871
|
+
};
|
|
2872
|
+
function requireRole(...roles) {
|
|
2873
|
+
return [
|
|
2874
|
+
authenticate,
|
|
2875
|
+
(req, res, next) => {
|
|
2876
|
+
const user = req.user;
|
|
2877
|
+
if (!user) {
|
|
2878
|
+
sendJson2(res, 401, { error: "Authentication required" });
|
|
2879
|
+
return;
|
|
2880
|
+
}
|
|
2881
|
+
const userRoles = user.roles ?? [];
|
|
2882
|
+
if (!roles.some((r) => userRoles.includes(r))) {
|
|
2883
|
+
sendJson2(res, 403, {
|
|
2884
|
+
error: `Access denied. Required role(s): ${roles.join(", ")}`,
|
|
2885
|
+
yourRoles: userRoles
|
|
2886
|
+
});
|
|
2887
|
+
return;
|
|
2888
|
+
}
|
|
2889
|
+
next();
|
|
2890
|
+
}
|
|
2891
|
+
];
|
|
2892
|
+
}
|
|
2893
|
+
function requirePermission(...permissions) {
|
|
2894
|
+
return [
|
|
2895
|
+
authenticate,
|
|
2896
|
+
(req, res, next) => {
|
|
2897
|
+
const user = req.user;
|
|
2898
|
+
if (!user) {
|
|
2899
|
+
sendJson2(res, 401, { error: "Authentication required" });
|
|
2900
|
+
return;
|
|
2901
|
+
}
|
|
2902
|
+
const userPerms = user.permissions ?? [];
|
|
2903
|
+
if (!permissions.every((p) => userPerms.includes(p))) {
|
|
2904
|
+
sendJson2(res, 403, {
|
|
2905
|
+
error: `Insufficient permissions. Required: ${permissions.join(", ")}`,
|
|
2906
|
+
yourPermissions: userPerms
|
|
2907
|
+
});
|
|
2908
|
+
return;
|
|
2909
|
+
}
|
|
2910
|
+
next();
|
|
2911
|
+
}
|
|
2912
|
+
];
|
|
2913
|
+
}
|
|
2914
|
+
return {
|
|
2915
|
+
login,
|
|
2916
|
+
refresh,
|
|
2917
|
+
logout,
|
|
2918
|
+
authenticate,
|
|
2919
|
+
authorize,
|
|
2920
|
+
requireRole,
|
|
2921
|
+
requirePermission
|
|
2922
|
+
};
|
|
2923
|
+
}
|
|
2924
|
+
var jwt_auth_default = createJwtPlugin;
|
|
2925
|
+
|
|
2926
|
+
// src/git.ts
|
|
2927
|
+
var import_child_process = require("child_process");
|
|
2928
|
+
var import_zlib2 = require("zlib");
|
|
2929
|
+
var import_fs2 = require("fs");
|
|
2930
|
+
function pktLine(str) {
|
|
2931
|
+
const byteLen = Buffer.byteLength(str, "utf8") + 4;
|
|
2932
|
+
return byteLen.toString(16).padStart(4, "0") + str;
|
|
2933
|
+
}
|
|
2934
|
+
var PKT_FLUSH = "0000";
|
|
2935
|
+
function gitHandler(opt) {
|
|
2936
|
+
if (typeof opt.repository !== "function")
|
|
2937
|
+
throw new TypeError("gitHandler: opt.repository must be a function");
|
|
2938
|
+
const gitHome = opt.gitPath ?? "";
|
|
2939
|
+
return async (req, res) => {
|
|
2940
|
+
const gitDirectory = await opt.repository(req);
|
|
2941
|
+
if (!gitDirectory) {
|
|
2942
|
+
res.status(404).send("Repository not found");
|
|
2943
|
+
return;
|
|
2944
|
+
}
|
|
2945
|
+
const urlPath = req.path;
|
|
2946
|
+
if (req.method === "GET" && urlPath === "/info/refs") {
|
|
2947
|
+
let args;
|
|
2948
|
+
const service = req.queries?.url?.service;
|
|
2949
|
+
if (service === "git-upload-pack")
|
|
2950
|
+
args = buildArgs(opt, ["--stateless-rpc", "--advertise-refs", gitDirectory]);
|
|
2951
|
+
else if (service === "git-receive-pack")
|
|
2952
|
+
args = ["--stateless-rpc", "--advertise-refs", gitDirectory];
|
|
2953
|
+
else {
|
|
2954
|
+
res.status(403).send(`Service ${String(service)} is not supported`);
|
|
2955
|
+
return;
|
|
2956
|
+
}
|
|
2957
|
+
res.setHeader("Content-Type", `application/x-${service}-advertisement`);
|
|
2958
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
2959
|
+
res.write(pktLine(`# service=${service}
|
|
2960
|
+
`));
|
|
2961
|
+
res.write(PKT_FLUSH);
|
|
2962
|
+
const proc = (0, import_child_process.spawn)(gitHome + service, args, {
|
|
2963
|
+
env: { ...process.env, GIT_PROTOCOL: req.headers["git-protocol"] || "" }
|
|
2964
|
+
});
|
|
2965
|
+
proc.on("error", (err) => {
|
|
2966
|
+
console.error(`[${service} GET] spawn error:`, err.message);
|
|
2967
|
+
if (!res.writableEnded) res.status(500).send(`${service} unavailable: ${err.message}`);
|
|
2968
|
+
});
|
|
2969
|
+
proc.stdout.pipe(res);
|
|
2970
|
+
proc.stdout.on("error", (err) => {
|
|
2971
|
+
console.warn(`[${service} GET] stdout error:`, err.message);
|
|
2972
|
+
});
|
|
2973
|
+
proc.stderr.on(
|
|
2974
|
+
"data",
|
|
2975
|
+
(d) => console.error(`[${service} GET]`, d.toString())
|
|
2976
|
+
);
|
|
2977
|
+
proc.on("close", (code) => {
|
|
2978
|
+
if (code !== 0) {
|
|
2979
|
+
console.error(`[${service} GET] exited with code ${code}`);
|
|
2980
|
+
if (!res.writableEnded) res.status(500).send(`${service} failed`);
|
|
2981
|
+
}
|
|
2982
|
+
});
|
|
2983
|
+
return;
|
|
2984
|
+
}
|
|
2985
|
+
if (req.method === "POST" && (urlPath === "/git-upload-pack" || urlPath === "/git-receive-pack")) {
|
|
2986
|
+
const contentType = req.headers["content-type"] || "";
|
|
2987
|
+
const service = urlPath.substring(1);
|
|
2988
|
+
if (contentType !== `application/x-${service}-request`) {
|
|
2989
|
+
res.status(415).send("Unsupported Media Type");
|
|
2990
|
+
return;
|
|
2991
|
+
}
|
|
2992
|
+
let args;
|
|
2993
|
+
if (service === "git-upload-pack")
|
|
2994
|
+
args = buildArgs(opt, ["--stateless-rpc", gitDirectory]);
|
|
2995
|
+
else if (service === "git-receive-pack")
|
|
2996
|
+
args = ["--stateless-rpc", gitDirectory];
|
|
2997
|
+
else {
|
|
2998
|
+
res.status(403).send(`Service ${service} is not supported`);
|
|
2999
|
+
return;
|
|
3000
|
+
}
|
|
3001
|
+
res.setHeader("Content-Type", `application/x-${service}-result`);
|
|
3002
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
3003
|
+
const proc = (0, import_child_process.spawn)(gitHome + service, args, {
|
|
3004
|
+
env: { ...process.env, GIT_PROTOCOL: req.headers["git-protocol"] || "" }
|
|
3005
|
+
});
|
|
3006
|
+
proc.on("error", (err) => {
|
|
3007
|
+
console.error(`[${service} POST] spawn error:`, err.message);
|
|
3008
|
+
if (!res.writableEnded) res.status(500).send(`${service} unavailable: ${err.message}`);
|
|
3009
|
+
});
|
|
3010
|
+
const encoding = req.headers["content-encoding"];
|
|
3011
|
+
if (encoding === "gzip") {
|
|
3012
|
+
const gunzip = (0, import_zlib2.createGunzip)();
|
|
3013
|
+
gunzip.on("error", (err) => {
|
|
3014
|
+
console.warn(`[${service} POST] gunzip error:`, err.message);
|
|
3015
|
+
if (!res.writableEnded) res.status(400).send("Failed to decompress request body");
|
|
3016
|
+
});
|
|
3017
|
+
req.pipe(gunzip).pipe(proc.stdin);
|
|
3018
|
+
} else {
|
|
3019
|
+
req.pipe(proc.stdin);
|
|
3020
|
+
}
|
|
3021
|
+
proc.stdout.pipe(res);
|
|
3022
|
+
proc.stdout.on("error", (err) => {
|
|
3023
|
+
console.warn(`[${service} POST] stdout error:`, err.message);
|
|
3024
|
+
});
|
|
3025
|
+
proc.stderr.on(
|
|
3026
|
+
"data",
|
|
3027
|
+
(d) => console.error(`[${service} POST]`, d.toString())
|
|
3028
|
+
);
|
|
3029
|
+
proc.on("close", (code) => {
|
|
3030
|
+
if (code !== 0) {
|
|
3031
|
+
console.error(`[${service} POST] exited with code ${code}`);
|
|
3032
|
+
if (!res.writableEnded) res.status(500).send(`${service} failed`);
|
|
3033
|
+
}
|
|
3034
|
+
});
|
|
3035
|
+
proc.stdin.on("error", (err) => {
|
|
3036
|
+
if (err.code !== "EPIPE")
|
|
3037
|
+
console.warn(`[${service} POST] stdin error:`, err.message);
|
|
3038
|
+
});
|
|
3039
|
+
return;
|
|
3040
|
+
}
|
|
3041
|
+
res.status(404).send("Not found");
|
|
3042
|
+
};
|
|
3043
|
+
}
|
|
3044
|
+
function gitCreate(gitDirectory, opt) {
|
|
3045
|
+
return new Promise((resolve, reject) => {
|
|
3046
|
+
const gitHome = opt.gitPath ?? "";
|
|
3047
|
+
const isBare = opt.bare !== false;
|
|
3048
|
+
const args = isBare ? ["init", "--bare", gitDirectory] : ["init", gitDirectory];
|
|
3049
|
+
const proc = (0, import_child_process.spawn)(gitHome + "git", args, {
|
|
3050
|
+
env: { ...process.env }
|
|
3051
|
+
});
|
|
3052
|
+
proc.on("error", (err) => {
|
|
3053
|
+
console.error(`[git init] spawn error:`, err.message);
|
|
3054
|
+
reject(`git unavailable: ${err.message}`);
|
|
3055
|
+
});
|
|
3056
|
+
proc.stdout.on("error", (err) => {
|
|
3057
|
+
console.warn(`[git init] stdout error:`, err.message);
|
|
3058
|
+
});
|
|
3059
|
+
proc.stderr.on(
|
|
3060
|
+
"data",
|
|
3061
|
+
(d) => console.error(`[git init]`, d.toString())
|
|
3062
|
+
);
|
|
3063
|
+
proc.on("close", (code) => {
|
|
3064
|
+
if (code !== 0) {
|
|
3065
|
+
return reject("git failed");
|
|
3066
|
+
}
|
|
3067
|
+
if (opt.description) {
|
|
3068
|
+
const descPath = isBare ? `${gitDirectory}/description` : `${gitDirectory}/.git/description`;
|
|
3069
|
+
(0, import_fs2.writeFileSync)(descPath, opt.description);
|
|
3070
|
+
}
|
|
3071
|
+
resolve();
|
|
3072
|
+
});
|
|
3073
|
+
});
|
|
3074
|
+
}
|
|
3075
|
+
function buildArgs(opt, trailing) {
|
|
3076
|
+
const args = [];
|
|
3077
|
+
if (opt.timeout) {
|
|
3078
|
+
const seconds = parseInt(String(opt.timeout), 10);
|
|
3079
|
+
if (!isNaN(seconds) && seconds > 0)
|
|
3080
|
+
args.push(`--timeout=${seconds}`);
|
|
3081
|
+
}
|
|
3082
|
+
if (!opt.strict)
|
|
3083
|
+
args.push("--no-strict");
|
|
3084
|
+
return [...args, ...trailing];
|
|
3085
|
+
}
|
|
3086
|
+
|
|
3087
|
+
// src/openapi.ts
|
|
3088
|
+
var YAML_KW = /* @__PURE__ */ new Set([
|
|
3089
|
+
"true",
|
|
3090
|
+
"false",
|
|
3091
|
+
"yes",
|
|
3092
|
+
"no",
|
|
3093
|
+
"on",
|
|
3094
|
+
"off",
|
|
3095
|
+
"null",
|
|
3096
|
+
"~"
|
|
3097
|
+
]);
|
|
3098
|
+
var LOOKS_LIKE_NUMBER = /^[-+]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$|^0x[0-9a-fA-F]+$|^0o[0-7]+$/;
|
|
3099
|
+
function yamlString(s) {
|
|
3100
|
+
if (s === "") return '""';
|
|
3101
|
+
const needsQuote = (
|
|
3102
|
+
// Would be misinterpreted as a YAML typed value.
|
|
3103
|
+
YAML_KW.has(s.toLowerCase()) || LOOKS_LIKE_NUMBER.test(s) || // Starts with a YAML indicator that has special meaning at the start of a
|
|
3104
|
+
// plain scalar (block context).
|
|
3105
|
+
/^[-?:,[\]{}#&*!|>'"%@`~]/.test(s) || // Inline sequences that break block-mapping parsing.
|
|
3106
|
+
s.includes(": ") || s.endsWith(":") || s.includes(" #") || // Leading / trailing whitespace.
|
|
3107
|
+
s !== s.trim() || // Flow indicator characters anywhere — present in path patterns such as
|
|
3108
|
+
// `/items/{id}` and must be quoted to avoid flow-collection ambiguity.
|
|
3109
|
+
/[{}[\]]/.test(s) || // Control characters.
|
|
3110
|
+
/[\x00-\x1f\x7f]/.test(s)
|
|
3111
|
+
);
|
|
3112
|
+
if (!needsQuote) return s;
|
|
3113
|
+
return '"' + s.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t").replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, "0")}`) + '"';
|
|
3114
|
+
}
|
|
3115
|
+
function yamlKey(key) {
|
|
3116
|
+
return yamlString(key);
|
|
3117
|
+
}
|
|
3118
|
+
function toYamlLines(value) {
|
|
3119
|
+
if (value === null || value === void 0) return ["null"];
|
|
3120
|
+
if (typeof value === "boolean") return [String(value)];
|
|
3121
|
+
if (typeof value === "number") return [isFinite(value) ? String(value) : ".inf"];
|
|
3122
|
+
if (typeof value === "string") return [yamlString(value)];
|
|
3123
|
+
if (Array.isArray(value)) {
|
|
3124
|
+
if (value.length === 0) return ["[]"];
|
|
3125
|
+
const lines = [];
|
|
3126
|
+
for (const item of value) {
|
|
3127
|
+
const itemLines = toYamlLines(item);
|
|
3128
|
+
lines.push(`- ${itemLines[0]}`);
|
|
3129
|
+
for (const l of itemLines.slice(1)) lines.push(` ${l}`);
|
|
3130
|
+
}
|
|
3131
|
+
return lines;
|
|
3132
|
+
}
|
|
3133
|
+
if (typeof value === "object") {
|
|
3134
|
+
const obj = value;
|
|
3135
|
+
const entries = Object.entries(obj).filter(([, v]) => v !== void 0);
|
|
3136
|
+
if (entries.length === 0) return ["{}"];
|
|
3137
|
+
const lines = [];
|
|
3138
|
+
for (const [key, val] of entries) {
|
|
3139
|
+
const k = yamlKey(key);
|
|
3140
|
+
const valLines = toYamlLines(val);
|
|
3141
|
+
const isComplex = typeof val === "object" && val !== null;
|
|
3142
|
+
const isEmptyCollection = valLines.length === 1 && (valLines[0] === "{}" || valLines[0] === "[]");
|
|
3143
|
+
if (!isComplex || isEmptyCollection) {
|
|
3144
|
+
lines.push(`${k}: ${valLines[0]}`);
|
|
3145
|
+
} else {
|
|
3146
|
+
lines.push(`${k}:`);
|
|
3147
|
+
for (const l of valLines) lines.push(` ${l}`);
|
|
3148
|
+
}
|
|
3149
|
+
}
|
|
3150
|
+
return lines;
|
|
3151
|
+
}
|
|
3152
|
+
return [String(value)];
|
|
3153
|
+
}
|
|
3154
|
+
function serializeSpec(doc, format = "json") {
|
|
3155
|
+
if (format === "yaml") return toYamlLines(doc).join("\n") + "\n";
|
|
3156
|
+
return JSON.stringify(doc, null, 2);
|
|
3157
|
+
}
|
|
3158
|
+
var DESCRIBE_META = /* @__PURE__ */ Symbol("expediate.openapi.meta");
|
|
3159
|
+
function describe(handler, meta) {
|
|
3160
|
+
const described = function(ctx, body) {
|
|
3161
|
+
return handler.apply(this, [ctx, body]);
|
|
3162
|
+
};
|
|
3163
|
+
Object.defineProperty(described, DESCRIBE_META, {
|
|
3164
|
+
value: meta,
|
|
3165
|
+
enumerable: false,
|
|
3166
|
+
configurable: true,
|
|
3167
|
+
writable: false
|
|
3168
|
+
});
|
|
3169
|
+
return described;
|
|
3170
|
+
}
|
|
3171
|
+
function toOpenApiPath(pattern, basePath) {
|
|
3172
|
+
const converted = pattern.replace(/:([A-Za-z_][A-Za-z0-9_]*)/g, "{$1}");
|
|
3173
|
+
if (!basePath) return converted;
|
|
3174
|
+
const base = basePath.replace(/\/+$/, "");
|
|
3175
|
+
const path2 = converted.replace(/^\/+/, "/");
|
|
3176
|
+
return base + (path2.startsWith("/") ? path2 : `/${path2}`);
|
|
3177
|
+
}
|
|
3178
|
+
function extractPathParams(pattern, annotated) {
|
|
3179
|
+
const annotatedNames = new Set(annotated.map((p) => p.name));
|
|
3180
|
+
const params = [];
|
|
3181
|
+
for (const match of pattern.matchAll(/:([A-Za-z_][A-Za-z0-9_]*)/g)) {
|
|
3182
|
+
const name = match[1];
|
|
3183
|
+
if (!annotatedNames.has(name)) {
|
|
3184
|
+
params.push({
|
|
3185
|
+
name,
|
|
3186
|
+
in: "path",
|
|
3187
|
+
required: true,
|
|
3188
|
+
schema: { type: "string" }
|
|
3189
|
+
});
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
3192
|
+
return params;
|
|
3193
|
+
}
|
|
3194
|
+
function buildOperationId(verb, pattern) {
|
|
3195
|
+
const parts = pattern.split(/[/:{} ]+/).filter(Boolean);
|
|
3196
|
+
let result = verb.toLowerCase();
|
|
3197
|
+
for (const part of parts) {
|
|
3198
|
+
const isParam = pattern.includes(`:${part}`) || pattern.includes(`{${part}}`);
|
|
3199
|
+
const pascal = part.charAt(0).toUpperCase() + part.slice(1);
|
|
3200
|
+
result += isParam ? `By${pascal}` : pascal;
|
|
3201
|
+
}
|
|
3202
|
+
return result;
|
|
3203
|
+
}
|
|
3204
|
+
function buildDefaultResponses(verb) {
|
|
3205
|
+
const ok = verb === "POST" ? { description: "Created" } : { description: "OK", content: { "application/json": {} } };
|
|
3206
|
+
return {
|
|
3207
|
+
[verb === "POST" ? "201" : "200"]: ok,
|
|
3208
|
+
"500": { $ref: "#/components/responses/ApiError" }
|
|
3209
|
+
};
|
|
3210
|
+
}
|
|
3211
|
+
function buildAnnotatedResponses(responses) {
|
|
3212
|
+
const result = { ...responses };
|
|
3213
|
+
if (!result["500"]) {
|
|
3214
|
+
result["500"] = { $ref: "#/components/responses/ApiError" };
|
|
3215
|
+
}
|
|
3216
|
+
return result;
|
|
3217
|
+
}
|
|
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
|
+
};
|
|
3273
|
+
function openApiSpec(service, opts) {
|
|
3274
|
+
const sources = Array.isArray(service) ? service : [service];
|
|
3275
|
+
const basePath = opts.basePath ?? "";
|
|
3276
|
+
const builtinSchemas = {
|
|
3277
|
+
ApiError: {
|
|
3278
|
+
type: "object",
|
|
3279
|
+
properties: {
|
|
3280
|
+
status: { type: "integer", description: "HTTP status code" },
|
|
3281
|
+
message: { type: "string", description: "Human-readable error message" },
|
|
3282
|
+
data: { description: "Structured error payload (overrides message when present)" }
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
3285
|
+
};
|
|
3286
|
+
const builtinResponses = {
|
|
3287
|
+
ApiError: {
|
|
3288
|
+
description: "API error response",
|
|
3289
|
+
content: {
|
|
3290
|
+
"application/json": { schema: { $ref: "#/components/schemas/ApiError" } }
|
|
3291
|
+
}
|
|
3292
|
+
}
|
|
3293
|
+
};
|
|
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 };
|
|
3300
|
+
const tags = [];
|
|
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
|
+
}
|
|
3308
|
+
}
|
|
3309
|
+
const securityScheme = sources.find((s) => s.auth?.scheme)?.auth?.scheme ?? DEFAULT_SECURITY_SCHEME;
|
|
3310
|
+
const paths = {};
|
|
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;
|
|
3335
|
+
}
|
|
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;
|
|
3343
|
+
}
|
|
3344
|
+
const doc = {
|
|
3345
|
+
openapi: "3.1.0",
|
|
3346
|
+
info: {
|
|
3347
|
+
title: opts.title,
|
|
3348
|
+
version: opts.version,
|
|
3349
|
+
...opts.description && { description: opts.description }
|
|
3350
|
+
},
|
|
3351
|
+
...opts.servers && { servers: opts.servers },
|
|
3352
|
+
...tags.length > 0 && { tags },
|
|
3353
|
+
paths,
|
|
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
|
+
}
|
|
3364
|
+
};
|
|
3365
|
+
return doc;
|
|
3366
|
+
}
|
|
3367
|
+
|
|
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
|
+
}
|
|
3573
|
+
async function buildModule(service, key) {
|
|
3574
|
+
const instance = service.data ? service.data(key) : { $key: key };
|
|
3575
|
+
if (service.methods) {
|
|
3576
|
+
for (const methodName of Object.keys(service.methods)) {
|
|
3577
|
+
const method = service.methods[methodName];
|
|
3578
|
+
instance[methodName] = function(...args) {
|
|
3579
|
+
return method.apply(instance, args);
|
|
3580
|
+
};
|
|
3581
|
+
}
|
|
3582
|
+
}
|
|
3583
|
+
if (service.setup)
|
|
3584
|
+
await service.setup.apply(instance, []);
|
|
3585
|
+
return instance;
|
|
3586
|
+
}
|
|
3587
|
+
async function resolveInstance(service, modules, building, req) {
|
|
3588
|
+
if (typeof service.scope !== "function") {
|
|
3589
|
+
return modules.singleton;
|
|
3590
|
+
}
|
|
3591
|
+
const key = service.scope(req);
|
|
3592
|
+
if (key === null) {
|
|
3593
|
+
return buildModule(service, null);
|
|
3594
|
+
}
|
|
3595
|
+
if (modules[key]) return modules[key];
|
|
3596
|
+
building[key] ??= buildModule(service, key).then((instance) => {
|
|
3597
|
+
modules[key] = instance;
|
|
3598
|
+
delete building[key];
|
|
3599
|
+
return instance;
|
|
3600
|
+
});
|
|
3601
|
+
return building[key];
|
|
3602
|
+
}
|
|
3603
|
+
function sendJson(res, data) {
|
|
3604
|
+
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
3605
|
+
res.send(JSON.stringify(data));
|
|
3606
|
+
}
|
|
3607
|
+
function sendError(res, err) {
|
|
3608
|
+
const e = err;
|
|
3609
|
+
const status = e?.status ?? 500;
|
|
3610
|
+
if (e?.data !== void 0) {
|
|
3611
|
+
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
3612
|
+
res.status(status).send(JSON.stringify(e.data));
|
|
3613
|
+
} else {
|
|
3614
|
+
res.status(status).send(e?.message ?? "Internal error");
|
|
3615
|
+
}
|
|
3616
|
+
}
|
|
3617
|
+
function apiBuilder(service, options) {
|
|
3618
|
+
const api = router_default();
|
|
3619
|
+
const modules = {};
|
|
3620
|
+
const building = {};
|
|
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(", ")}`
|
|
3633
|
+
};
|
|
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 ?? {};
|
|
3648
|
+
const ctx = {
|
|
3649
|
+
query: {
|
|
3650
|
+
route: routeParams,
|
|
3651
|
+
url: req.queries?.url ?? {}
|
|
3652
|
+
},
|
|
3653
|
+
params: routeParams,
|
|
3654
|
+
path: req.path,
|
|
3655
|
+
user: req.user,
|
|
3656
|
+
state: {}
|
|
3657
|
+
};
|
|
3658
|
+
const body = req.body;
|
|
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);
|
|
3668
|
+
}
|
|
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 {
|
|
3675
|
+
res.status(201).end();
|
|
3676
|
+
}
|
|
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
|
+
}
|
|
3691
|
+
sendError(res, err);
|
|
3692
|
+
});
|
|
3693
|
+
});
|
|
3694
|
+
}
|
|
3695
|
+
}
|
|
3696
|
+
function registerAllRoutes() {
|
|
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));
|
|
3702
|
+
}
|
|
3703
|
+
if (typeof service.scope !== "function") {
|
|
3704
|
+
let ready = false;
|
|
3705
|
+
api.use("/", (_req, res, next) => {
|
|
3706
|
+
if (!ready) {
|
|
3707
|
+
res.statusCode = 503;
|
|
3708
|
+
res.end("Service not ready");
|
|
3709
|
+
return;
|
|
3710
|
+
}
|
|
3711
|
+
next();
|
|
3712
|
+
});
|
|
3713
|
+
buildModule(service, "singleton").then((instance) => {
|
|
3714
|
+
modules.singleton = instance;
|
|
3715
|
+
ready = true;
|
|
3716
|
+
registerAllRoutes();
|
|
3717
|
+
}).catch((err) => {
|
|
3718
|
+
console.error("[apiBuilder] singleton setup() rejected:", err);
|
|
3719
|
+
});
|
|
3720
|
+
} else {
|
|
3721
|
+
registerAllRoutes();
|
|
3722
|
+
}
|
|
3723
|
+
api.spec = function(opts) {
|
|
3724
|
+
return openApiSpec(service, opts);
|
|
3725
|
+
};
|
|
3726
|
+
api.specHandler = function(opts, format = "json") {
|
|
3727
|
+
let cached = null;
|
|
3728
|
+
const contentType = format === "yaml" ? "application/yaml; charset=utf-8" : "application/json; charset=utf-8";
|
|
3729
|
+
return function(_req, res) {
|
|
3730
|
+
cached ??= serializeSpec(openApiSpec(service, opts), format);
|
|
3731
|
+
res.setHeader("Content-Type", contentType);
|
|
3732
|
+
res.end(cached);
|
|
3733
|
+
};
|
|
3734
|
+
};
|
|
3735
|
+
return api;
|
|
3736
|
+
}
|
|
3737
|
+
var apis_default = apiBuilder;
|
|
3738
|
+
|
|
3739
|
+
// src/middleware.ts
|
|
3740
|
+
var crypto3 = __toESM(require("crypto"), 1);
|
|
3741
|
+
var zlib2 = __toESM(require("zlib"), 1);
|
|
3742
|
+
function toBuffer(chunk, encoding = "utf8") {
|
|
3743
|
+
return Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, encoding);
|
|
3744
|
+
}
|
|
3745
|
+
function compress(opts) {
|
|
3746
|
+
const threshold = opts?.threshold ?? 1024;
|
|
3747
|
+
const brEnabled = opts?.br !== false;
|
|
3748
|
+
const brotliQuality = opts?.brotliQuality ?? 4;
|
|
3749
|
+
const gzipLevel = opts?.gzipLevel ?? zlib2.constants.Z_DEFAULT_COMPRESSION;
|
|
3750
|
+
return (req, res, next) => {
|
|
3751
|
+
if (opts?.filter && !opts.filter(req, res)) return next();
|
|
3752
|
+
const ae = req.headers["accept-encoding"] ?? "";
|
|
3753
|
+
let compressor = null;
|
|
3754
|
+
let encoding = "";
|
|
3755
|
+
if (brEnabled && /\bbr\b/.test(ae)) {
|
|
3756
|
+
compressor = zlib2.createBrotliCompress({
|
|
3757
|
+
params: { [zlib2.constants.BROTLI_PARAM_QUALITY]: brotliQuality }
|
|
3758
|
+
});
|
|
3759
|
+
encoding = "br";
|
|
3760
|
+
} else if (/\bgzip\b/.test(ae)) {
|
|
3761
|
+
compressor = zlib2.createGzip({ level: gzipLevel });
|
|
3762
|
+
encoding = "gzip";
|
|
3763
|
+
} else if (/\bdeflate\b/.test(ae)) {
|
|
3764
|
+
compressor = zlib2.createDeflate({ level: gzipLevel });
|
|
3765
|
+
encoding = "deflate";
|
|
3766
|
+
}
|
|
3767
|
+
if (!compressor) return next();
|
|
3768
|
+
const comp = compressor;
|
|
3769
|
+
const rawRes = res;
|
|
3770
|
+
const origWrite = rawRes.write.bind(rawRes);
|
|
3771
|
+
const origEnd = rawRes.end.bind(rawRes);
|
|
3772
|
+
comp.on("data", (chunk) => {
|
|
3773
|
+
origWrite(chunk);
|
|
3774
|
+
});
|
|
3775
|
+
comp.on("end", () => {
|
|
3776
|
+
origEnd();
|
|
3777
|
+
});
|
|
3778
|
+
comp.on("error", (e) => {
|
|
3779
|
+
console.error("[compress] stream error:", e.message);
|
|
3780
|
+
if (!rawRes.writableEnded) origEnd();
|
|
3781
|
+
});
|
|
3782
|
+
const pending = [];
|
|
3783
|
+
let totalBytes = 0;
|
|
3784
|
+
let decided = false;
|
|
3785
|
+
let doCompress = false;
|
|
3786
|
+
const startCompressing = (andEnd) => {
|
|
3787
|
+
decided = true;
|
|
3788
|
+
doCompress = true;
|
|
3789
|
+
res.setHeader("Content-Encoding", encoding);
|
|
3790
|
+
res.setHeader("Vary", "Accept-Encoding");
|
|
3791
|
+
res.removeHeader("Content-Length");
|
|
3792
|
+
for (const buf of pending) comp.write(buf);
|
|
3793
|
+
if (andEnd) comp.end();
|
|
3794
|
+
};
|
|
3795
|
+
const skipCompression = () => {
|
|
3796
|
+
decided = true;
|
|
3797
|
+
doCompress = false;
|
|
3798
|
+
comp.destroy();
|
|
3799
|
+
if (totalBytes > 0) res.setHeader("Content-Length", totalBytes);
|
|
3800
|
+
for (const buf of pending) origWrite(buf);
|
|
3801
|
+
};
|
|
3802
|
+
res.write = (chunk, encOrCb, _cb) => {
|
|
3803
|
+
const enc = typeof encOrCb === "string" ? encOrCb : "utf8";
|
|
3804
|
+
const buf = toBuffer(chunk, enc);
|
|
3805
|
+
if (decided) {
|
|
3806
|
+
return doCompress ? comp.write(buf) : origWrite(buf);
|
|
3807
|
+
}
|
|
3808
|
+
pending.push(buf);
|
|
3809
|
+
totalBytes += buf.length;
|
|
3810
|
+
if (totalBytes >= threshold) startCompressing(false);
|
|
3811
|
+
return true;
|
|
3812
|
+
};
|
|
3813
|
+
res.end = (chunk, encOrCb, _cb) => {
|
|
3814
|
+
if (typeof chunk === "function") chunk = void 0;
|
|
3815
|
+
if (typeof encOrCb === "function") encOrCb = void 0;
|
|
3816
|
+
if (chunk != null) {
|
|
3817
|
+
const enc = typeof encOrCb === "string" ? encOrCb : "utf8";
|
|
3818
|
+
const buf = toBuffer(chunk, enc);
|
|
3819
|
+
if (decided) {
|
|
3820
|
+
if (doCompress) comp.write(buf);
|
|
3821
|
+
else origWrite(buf);
|
|
3822
|
+
} else {
|
|
3823
|
+
pending.push(buf);
|
|
3824
|
+
totalBytes += buf.length;
|
|
3825
|
+
}
|
|
3826
|
+
}
|
|
3827
|
+
if (!decided) {
|
|
3828
|
+
if (totalBytes >= threshold) startCompressing(true);
|
|
3829
|
+
else {
|
|
3830
|
+
skipCompression();
|
|
3831
|
+
origEnd();
|
|
3832
|
+
}
|
|
3833
|
+
} else if (doCompress) {
|
|
3834
|
+
comp.end();
|
|
3835
|
+
} else {
|
|
3836
|
+
origEnd();
|
|
3837
|
+
}
|
|
3838
|
+
return rawRes;
|
|
3839
|
+
};
|
|
3840
|
+
next();
|
|
3841
|
+
};
|
|
3842
|
+
}
|
|
3843
|
+
function requestId(opts) {
|
|
3844
|
+
const header = (opts?.header ?? "x-request-id").toLowerCase();
|
|
3845
|
+
const allowFromHeader = opts?.allowFromHeader !== false;
|
|
3846
|
+
const generator = opts?.generator ?? (() => crypto3.randomUUID());
|
|
3847
|
+
return (req, res, next) => {
|
|
3848
|
+
const incoming = req.headers[header];
|
|
3849
|
+
const id = allowFromHeader && incoming ? incoming : generator();
|
|
3850
|
+
req.id = id;
|
|
3851
|
+
res.setHeader(header, id);
|
|
3852
|
+
next();
|
|
3853
|
+
};
|
|
3854
|
+
}
|
|
3855
|
+
function rateLimit(opts) {
|
|
3856
|
+
const windowMs = opts.windowMs;
|
|
3857
|
+
const max = opts.max;
|
|
3858
|
+
const keyBy = opts.keyBy ?? ((req) => req.ip ?? "");
|
|
3859
|
+
const message = opts.message ?? "Too Many Requests";
|
|
3860
|
+
const statusCode = opts.statusCode ?? 429;
|
|
3861
|
+
const sendHeaders = opts.headers !== false;
|
|
3862
|
+
const store = /* @__PURE__ */ new Map();
|
|
3863
|
+
return (req, res, next) => {
|
|
3864
|
+
const key = keyBy(req);
|
|
3865
|
+
const now = Date.now();
|
|
3866
|
+
const windowStart = now - windowMs;
|
|
3867
|
+
const timestamps = (store.get(key) ?? []).filter((t) => t > windowStart);
|
|
3868
|
+
timestamps.push(now);
|
|
3869
|
+
store.set(key, timestamps);
|
|
3870
|
+
const count = timestamps.length;
|
|
3871
|
+
const remaining = Math.max(0, max - count);
|
|
3872
|
+
const resetAt = timestamps.length > 0 ? Math.ceil((timestamps[0] + windowMs) / 1e3) : Math.ceil((now + windowMs) / 1e3);
|
|
3873
|
+
if (sendHeaders) {
|
|
3874
|
+
res.setHeader("X-RateLimit-Limit", String(max));
|
|
3875
|
+
res.setHeader("X-RateLimit-Remaining", String(remaining));
|
|
3876
|
+
res.setHeader("X-RateLimit-Reset", String(resetAt));
|
|
3877
|
+
}
|
|
3878
|
+
if (count > max) {
|
|
3879
|
+
res.setHeader("Retry-After", String(Math.ceil(windowMs / 1e3)));
|
|
3880
|
+
res.statusCode = statusCode;
|
|
3881
|
+
res.end(message);
|
|
3882
|
+
return;
|
|
3883
|
+
}
|
|
3884
|
+
next();
|
|
3885
|
+
};
|
|
3886
|
+
}
|
|
3887
|
+
function cacheControl(opts = {}) {
|
|
3888
|
+
const directives = [];
|
|
3889
|
+
if (opts.private) directives.push("private");
|
|
3890
|
+
if (opts.public) directives.push("public");
|
|
3891
|
+
if (opts.noStore) directives.push("no-store");
|
|
3892
|
+
if (opts.noCache) directives.push("no-cache");
|
|
3893
|
+
if (opts.mustRevalidate) directives.push("must-revalidate");
|
|
3894
|
+
if (opts.immutable) directives.push("immutable");
|
|
3895
|
+
if (opts.maxAge != null) directives.push(`max-age=${opts.maxAge}`);
|
|
3896
|
+
if (opts.sMaxAge != null) directives.push(`s-maxage=${opts.sMaxAge}`);
|
|
3897
|
+
const cacheControlValue = directives.join(", ");
|
|
3898
|
+
const varyValue = Array.isArray(opts.vary) ? opts.vary.join(", ") : opts.vary ?? null;
|
|
3899
|
+
return (_req, res, next) => {
|
|
3900
|
+
if (cacheControlValue) res.setHeader("Cache-Control", cacheControlValue);
|
|
3901
|
+
if (opts.maxAge != null) {
|
|
3902
|
+
const expires = new Date(Date.now() + opts.maxAge * 1e3);
|
|
3903
|
+
res.setHeader("Expires", expires.toUTCString());
|
|
3904
|
+
}
|
|
3905
|
+
if (varyValue) res.setHeader("Vary", varyValue);
|
|
3906
|
+
next();
|
|
3907
|
+
};
|
|
3908
|
+
}
|
|
3909
|
+
var SAFE_CSRF_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS", "TRACE"]);
|
|
3910
|
+
function csrf(opts) {
|
|
3911
|
+
const cookieName = opts?.cookieName ?? "_csrf";
|
|
3912
|
+
const headerName = (opts?.headerName ?? "x-csrf-token").toLowerCase();
|
|
3913
|
+
const fieldName = opts?.fieldName ?? "_csrf";
|
|
3914
|
+
const secure = opts?.secure ?? false;
|
|
3915
|
+
const sameSite = opts?.sameSite ?? "Strict";
|
|
3916
|
+
return (req, res, next) => {
|
|
3917
|
+
const existing = req.cookies?.[cookieName];
|
|
3918
|
+
const token = typeof existing === "string" && existing.length > 0 ? existing : crypto3.randomBytes(32).toString("hex");
|
|
3919
|
+
if (!existing) {
|
|
3920
|
+
let cookieStr = `${cookieName}=${token}; Path=/; SameSite=${sameSite}`;
|
|
3921
|
+
if (secure) cookieStr += "; Secure";
|
|
3922
|
+
const prev = res.getHeader("Set-Cookie");
|
|
3923
|
+
if (prev == null) res.setHeader("Set-Cookie", cookieStr);
|
|
3924
|
+
else if (Array.isArray(prev)) res.setHeader("Set-Cookie", [...prev, cookieStr]);
|
|
3925
|
+
else res.setHeader("Set-Cookie", [prev, cookieStr]);
|
|
3926
|
+
}
|
|
3927
|
+
req.csrfToken = () => token;
|
|
3928
|
+
if (SAFE_CSRF_METHODS.has(req.method ?? "")) return next();
|
|
3929
|
+
const submitted = req.headers[headerName] ?? req.body?.[fieldName] ?? "";
|
|
3930
|
+
if (!submitted || submitted !== token) {
|
|
3931
|
+
res.statusCode = 403;
|
|
3932
|
+
res.end("Forbidden: invalid CSRF token");
|
|
3933
|
+
return;
|
|
3934
|
+
}
|
|
3935
|
+
next();
|
|
3936
|
+
};
|
|
3937
|
+
}
|
|
3938
|
+
function isFreshResponse(etag, lastModified, ifNoneMatch, ifModSince) {
|
|
3939
|
+
const inm = Array.isArray(ifNoneMatch) ? ifNoneMatch[0] : ifNoneMatch;
|
|
3940
|
+
if (inm) {
|
|
3941
|
+
if (!etag) return false;
|
|
3942
|
+
if (inm.trim() === "*") return true;
|
|
3943
|
+
const normalize = (tag) => tag.startsWith("W/") ? tag.slice(2) : tag;
|
|
3944
|
+
const tags = inm.split(",").map((t) => normalize(t.trim()));
|
|
3945
|
+
return tags.includes(normalize(etag));
|
|
3946
|
+
}
|
|
3947
|
+
const ims = Array.isArray(ifModSince) ? ifModSince[0] : ifModSince;
|
|
3948
|
+
if (ims && lastModified) {
|
|
3949
|
+
const imsMs = new Date(ims).getTime();
|
|
3950
|
+
const lmMs = new Date(lastModified).getTime();
|
|
3951
|
+
if (!isNaN(imsMs) && !isNaN(lmMs)) return lmMs <= imsMs;
|
|
3952
|
+
}
|
|
3953
|
+
return false;
|
|
3954
|
+
}
|
|
3955
|
+
function conditionalGet() {
|
|
3956
|
+
return (req, res, next) => {
|
|
3957
|
+
const method = req.method ?? "GET";
|
|
3958
|
+
if (method !== "GET" && method !== "HEAD") return next();
|
|
3959
|
+
const rawRes = res;
|
|
3960
|
+
const origWrite = rawRes.write.bind(rawRes);
|
|
3961
|
+
const origEnd = rawRes.end.bind(rawRes);
|
|
3962
|
+
const pending = [];
|
|
3963
|
+
res.write = (chunk, encOrCb, _cb) => {
|
|
3964
|
+
const enc = typeof encOrCb === "string" ? encOrCb : "utf8";
|
|
3965
|
+
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, enc);
|
|
3966
|
+
pending.push(buf);
|
|
3967
|
+
return true;
|
|
3968
|
+
};
|
|
3969
|
+
res.end = (chunk, encOrCb, _cb) => {
|
|
3970
|
+
if (typeof chunk === "function") chunk = void 0;
|
|
3971
|
+
if (typeof encOrCb === "function") encOrCb = void 0;
|
|
3972
|
+
if (chunk != null) {
|
|
3973
|
+
const enc = typeof encOrCb === "string" ? encOrCb : "utf8";
|
|
3974
|
+
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, enc);
|
|
3975
|
+
pending.push(buf);
|
|
3976
|
+
}
|
|
3977
|
+
const etag = rawRes.getHeader("etag");
|
|
3978
|
+
const lastMod = rawRes.getHeader("last-modified");
|
|
3979
|
+
const ifNoneMatch = req.headers["if-none-match"];
|
|
3980
|
+
const ifModSince = req.headers["if-modified-since"];
|
|
3981
|
+
if (isFreshResponse(etag, lastMod, ifNoneMatch, ifModSince)) {
|
|
3982
|
+
rawRes.removeHeader("content-type");
|
|
3983
|
+
rawRes.removeHeader("content-length");
|
|
3984
|
+
rawRes.removeHeader("content-encoding");
|
|
3985
|
+
rawRes.statusCode = 304;
|
|
3986
|
+
origEnd();
|
|
3987
|
+
} else {
|
|
3988
|
+
for (const buf of pending) origWrite(buf);
|
|
3989
|
+
origEnd();
|
|
3990
|
+
}
|
|
3991
|
+
return rawRes;
|
|
3992
|
+
};
|
|
3993
|
+
next();
|
|
3994
|
+
};
|
|
3995
|
+
}
|
|
3996
|
+
function securityHeaders(opts) {
|
|
3997
|
+
const headers = [];
|
|
3998
|
+
const hsts = opts?.hsts;
|
|
3999
|
+
if (hsts !== false) {
|
|
4000
|
+
const h = typeof hsts === "object" ? hsts : {};
|
|
4001
|
+
const maxAge = h.maxAge ?? 15552e3;
|
|
4002
|
+
const subdomain = h.includeSubDomains !== false;
|
|
4003
|
+
let value = `max-age=${maxAge}`;
|
|
4004
|
+
if (subdomain) value += "; includeSubDomains";
|
|
4005
|
+
if (h.preload) value += "; preload";
|
|
4006
|
+
headers.push(["Strict-Transport-Security", value]);
|
|
4007
|
+
}
|
|
4008
|
+
const fo = opts?.frameOptions;
|
|
4009
|
+
if (fo !== false) {
|
|
4010
|
+
headers.push(["X-Frame-Options", fo ?? "SAMEORIGIN"]);
|
|
4011
|
+
}
|
|
4012
|
+
if (opts?.contentTypeOptions !== false) {
|
|
4013
|
+
headers.push(["X-Content-Type-Options", "nosniff"]);
|
|
4014
|
+
}
|
|
4015
|
+
const rp = opts?.referrerPolicy;
|
|
4016
|
+
if (rp !== false) {
|
|
4017
|
+
headers.push(["Referrer-Policy", typeof rp === "string" ? rp : "strict-origin-when-cross-origin"]);
|
|
4018
|
+
}
|
|
4019
|
+
const pp = opts?.permissionsPolicy;
|
|
4020
|
+
if (pp !== false) {
|
|
4021
|
+
headers.push(["Permissions-Policy", typeof pp === "string" ? pp : "geolocation=(), microphone=(), camera=()"]);
|
|
4022
|
+
}
|
|
4023
|
+
const xxp = opts?.xssProtection;
|
|
4024
|
+
if (xxp !== false) {
|
|
4025
|
+
headers.push(["X-XSS-Protection", typeof xxp === "string" ? xxp : "0"]);
|
|
4026
|
+
}
|
|
4027
|
+
return (_req, res, next) => {
|
|
4028
|
+
for (const [name, value] of headers) res.setHeader(name, value);
|
|
4029
|
+
next();
|
|
4030
|
+
};
|
|
4031
|
+
}
|
|
4032
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
4033
|
+
0 && (module.exports = {
|
|
4034
|
+
DESCRIBE_META,
|
|
4035
|
+
apiBuilder,
|
|
4036
|
+
cacheControl,
|
|
4037
|
+
compress,
|
|
4038
|
+
conditionalGet,
|
|
4039
|
+
cors,
|
|
4040
|
+
createJwtPlugin,
|
|
4041
|
+
createMapTokenStore,
|
|
4042
|
+
createRouter,
|
|
4043
|
+
csrf,
|
|
4044
|
+
defineController,
|
|
4045
|
+
describe,
|
|
4046
|
+
formData,
|
|
4047
|
+
formEncoded,
|
|
4048
|
+
gitCreate,
|
|
4049
|
+
gitHandler,
|
|
4050
|
+
json,
|
|
4051
|
+
logger,
|
|
4052
|
+
mime,
|
|
4053
|
+
openApiSpec,
|
|
4054
|
+
parseBody,
|
|
4055
|
+
parseMultipartBody,
|
|
4056
|
+
rateLimit,
|
|
4057
|
+
raw,
|
|
4058
|
+
requestId,
|
|
4059
|
+
securityHeaders,
|
|
4060
|
+
sendFile,
|
|
4061
|
+
serializeSpec,
|
|
4062
|
+
serveFile,
|
|
4063
|
+
serveStatic,
|
|
4064
|
+
streamFormData,
|
|
4065
|
+
text
|
|
4066
|
+
});
|