expediate 1.0.4 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/LICENSE +16 -16
  2. package/README.md +417 -30
  3. package/dist/apis.d.ts +138 -21
  4. package/dist/apis.d.ts.map +1 -1
  5. package/dist/apis.js +172 -79
  6. package/dist/apis.js.map +1 -1
  7. package/dist/cjs/apis.js +327 -0
  8. package/dist/cjs/git.js +293 -0
  9. package/dist/cjs/index.js +2583 -0
  10. package/dist/cjs/jwt-auth.js +532 -0
  11. package/dist/cjs/middleware.js +511 -0
  12. package/dist/cjs/mimetypes.json +1 -0
  13. package/dist/cjs/misc.js +787 -0
  14. package/dist/cjs/openapi.js +485 -0
  15. package/dist/cjs/package.json +1 -0
  16. package/dist/cjs/router.js +898 -0
  17. package/dist/cjs/static.js +669 -0
  18. package/dist/git.d.ts +71 -8
  19. package/dist/git.d.ts.map +1 -1
  20. package/dist/git.js +127 -72
  21. package/dist/git.js.map +1 -1
  22. package/dist/index.d.ts +17 -13
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +14 -24
  25. package/dist/index.js.map +1 -1
  26. package/dist/jwt-auth.d.ts +147 -57
  27. package/dist/jwt-auth.d.ts.map +1 -1
  28. package/dist/jwt-auth.js +445 -205
  29. package/dist/jwt-auth.js.map +1 -1
  30. package/dist/middleware.d.ts +476 -0
  31. package/dist/middleware.d.ts.map +1 -0
  32. package/dist/middleware.js +647 -0
  33. package/dist/middleware.js.map +1 -0
  34. package/dist/mimetypes.json +1 -1
  35. package/dist/misc.d.ts +112 -5
  36. package/dist/misc.d.ts.map +1 -1
  37. package/dist/misc.js +235 -102
  38. package/dist/misc.js.map +1 -1
  39. package/dist/openapi.d.ts +290 -0
  40. package/dist/openapi.d.ts.map +1 -0
  41. package/dist/openapi.js +481 -0
  42. package/dist/openapi.js.map +1 -0
  43. package/dist/router.d.ts +405 -46
  44. package/dist/router.d.ts.map +1 -1
  45. package/dist/router.js +658 -153
  46. package/dist/router.js.map +1 -1
  47. package/dist/static.d.ts +1 -1
  48. package/dist/static.d.ts.map +1 -1
  49. package/dist/static.js +88 -84
  50. package/dist/static.js.map +1 -1
  51. package/package.json +21 -4
  52. package/.npmignore +0 -16
@@ -0,0 +1,2583 @@
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
+ describe: () => describe,
44
+ formData: () => formData,
45
+ formEncoded: () => formEncoded,
46
+ gitCreate: () => gitCreate,
47
+ gitHandler: () => gitHandler,
48
+ json: () => json,
49
+ logger: () => logger,
50
+ mime: () => mime,
51
+ openApiSpec: () => openApiSpec,
52
+ parseBody: () => parseBody,
53
+ parseMultipartBody: () => parseMultipartBody,
54
+ rateLimit: () => rateLimit,
55
+ requestId: () => requestId,
56
+ securityHeaders: () => securityHeaders,
57
+ sendFile: () => sendFile,
58
+ serializeSpec: () => serializeSpec,
59
+ serveFile: () => serveFile,
60
+ serveStatic: () => serveStatic,
61
+ streamFormData: () => streamFormData
62
+ });
63
+ module.exports = __toCommonJS(index_exports);
64
+
65
+ // src/router.ts
66
+ var crypto = __toESM(require("crypto"), 1);
67
+ var fs2 = __toESM(require("fs"), 1);
68
+ var http = __toESM(require("http"), 1);
69
+ var https = __toESM(require("https"), 1);
70
+ var http2 = __toESM(require("http2"), 1);
71
+ var path = __toESM(require("path"), 1);
72
+
73
+ // src/misc.ts
74
+ var import_stream = require("stream");
75
+ var import_zlib = __toESM(require("zlib"), 1);
76
+ var DECOMPRESS_ALGO = {
77
+ gzip: import_zlib.default.gunzip,
78
+ deflate: import_zlib.default.inflate
79
+ };
80
+ function readSize(value) {
81
+ if (typeof value === "number") return value;
82
+ const fmt = /^(\d+(\.\d+)?)([kmg]?b?)?$/i.exec(value);
83
+ if (!fmt) return 0;
84
+ const num = parseFloat(fmt[1] ?? "0");
85
+ const sfx = (fmt[3] ?? "b").toLowerCase();
86
+ if (sfx[0] === "k") return num * 1024;
87
+ if (sfx[0] === "m") return num * 1024 * 1024;
88
+ if (sfx[0] === "g") return num * 1024 * 1024 * 1024;
89
+ return num;
90
+ }
91
+ function splitBuffer(buffer, delimiter) {
92
+ const result = [];
93
+ let start = 0;
94
+ let index;
95
+ while ((index = buffer.indexOf(delimiter, start)) !== -1) {
96
+ result.push(buffer.slice(start, index));
97
+ start = index + delimiter.length;
98
+ }
99
+ result.push(buffer.slice(start));
100
+ return result;
101
+ }
102
+ function extractCharset(contentType) {
103
+ const param = contentType.split(";").map((s) => s.replace(/^\s+|\s+$/g, "")).find((s) => s.startsWith("charset="));
104
+ return param ? param.substring("charset=".length) : "utf8";
105
+ }
106
+ function readBody(req, res, opts, mimetype, next, callback) {
107
+ const length = parseInt(req.headers["content-length"] ?? "0", 10);
108
+ const isChunked = req.headers["transfer-encoding"]?.split(",").map((v) => v.trim()).some((v) => v.toLowerCase() === "chunked") ?? false;
109
+ if (!isChunked && (!length || length === 0)) return next();
110
+ const maxLength = readSize(opts.limit) || 102400;
111
+ if (!isChunked && length > maxLength)
112
+ return void res.status(413).send("Content Too Large");
113
+ const encoding = req.headers["content-encoding"];
114
+ if (encoding && (opts.inflate === false || !DECOMPRESS_ALGO[encoding]))
115
+ return void res.status(415).send("Unsupported Media Type: Wrong Content-Encoding");
116
+ const decompress = (encoding ? DECOMPRESS_ALGO[encoding] : void 0) ?? ((d, c) => c(null, d));
117
+ const contentType = req.headers["content-type"] ?? "";
118
+ if (mimetype && contentType.split(";")[0].trim() !== mimetype)
119
+ return void res.status(415).send("Unsupported Media Type: Wrong Content-Type");
120
+ let data = Buffer.alloc(0);
121
+ req.on("data", (chunk) => {
122
+ if (data === null) return;
123
+ const next_ = Buffer.concat([data, chunk]);
124
+ if (next_.length > maxLength) {
125
+ data = null;
126
+ res.status(413).send("Content Too Large");
127
+ return;
128
+ }
129
+ data = next_;
130
+ });
131
+ req.on("end", () => {
132
+ if (data === null) return;
133
+ decompress(data, (err, decompressed) => {
134
+ if (err) return void res.status(500).send(err.message);
135
+ callback(contentType, decompressed);
136
+ });
137
+ });
138
+ }
139
+ function readReqBody(req, opts, mimetype) {
140
+ return new Promise((resolve, reject) => {
141
+ const length = parseInt(req.headers["content-length"] ?? "0", 10);
142
+ const isChunked = req.headers["transfer-encoding"]?.split(",").map((v) => v.trim()).some((v) => v.toLowerCase() === "chunked") ?? false;
143
+ if (!isChunked && (!length || length === 0)) return resolve(null);
144
+ const maxLength = readSize(opts.limit) || 102400;
145
+ if (!isChunked && length > maxLength)
146
+ return reject({ status: 413, message: "Content Too Large" });
147
+ const encoding = req.headers["content-encoding"];
148
+ if (encoding && (opts.inflate === false || !DECOMPRESS_ALGO[encoding]))
149
+ return reject({ status: 415, message: "Unsupported Media Type: Wrong Content-Encoding" });
150
+ const decompress = (encoding ? DECOMPRESS_ALGO[encoding] : void 0) ?? ((d, c) => c(null, d));
151
+ const contentType = req.headers["content-type"] ?? "";
152
+ if (mimetype && contentType.split(";")[0].trim() !== mimetype)
153
+ return reject({ status: 415, message: "Unsupported Media Type: Wrong Content-Type" });
154
+ let data = Buffer.alloc(0);
155
+ req.on("data", (chunk) => {
156
+ if (data === null) return;
157
+ const next_ = Buffer.concat([data, chunk]);
158
+ if (next_.length > maxLength) {
159
+ data = null;
160
+ reject({ status: 413, message: "Content Too Large" });
161
+ return;
162
+ }
163
+ data = next_;
164
+ });
165
+ req.on("end", () => {
166
+ if (data === null) return;
167
+ decompress(data, (err, decompressed) => {
168
+ if (err) return reject({ status: 500, message: err.message });
169
+ resolve({ mimetype: contentType ?? "", content: decompressed });
170
+ });
171
+ });
172
+ });
173
+ }
174
+ function readBodyAsPlainText(req, res, next, contentType, data) {
175
+ const charset = extractCharset(contentType);
176
+ try {
177
+ req.body = data.toString(charset);
178
+ next();
179
+ } catch (ex) {
180
+ res.status(500).send(ex.message);
181
+ }
182
+ }
183
+ function readBodyAsJson(req, res, next, opts, contentType, data) {
184
+ const charset = extractCharset(contentType);
185
+ try {
186
+ const parsed = JSON.parse(
187
+ data.toString(charset),
188
+ opts.reviver ?? void 0
189
+ );
190
+ if (opts.strict && (typeof parsed !== "object" || parsed === null)) {
191
+ return void res.status(400).send("Bad Request: JSON body must be an object or array");
192
+ }
193
+ req.body = parsed;
194
+ next();
195
+ } catch (ex) {
196
+ res.status(400).send("Bad Request: " + ex.message);
197
+ }
198
+ }
199
+ function parseMultipartBody(contentType, data) {
200
+ const boundary = contentType.split(";").map((s) => s.replace(/^\s+|\s+$/g, "")).find((s) => s.startsWith("boundary="))?.substring("boundary=".length);
201
+ if (!boundary)
202
+ throw { status: 400, message: "Bad Request: missing multipart boundary" };
203
+ const delimiter = Buffer.from(`\r
204
+ --${boundary}`);
205
+ const normalized = Buffer.concat([Buffer.from("\r\n"), data]);
206
+ const rawParts = splitBuffer(normalized, delimiter);
207
+ const parts = [];
208
+ for (const part of rawParts) {
209
+ if (part.toString("utf8", 0, 2) === "--") continue;
210
+ const partContent = part.slice(2);
211
+ const blankLine = Buffer.from("\r\n\r\n");
212
+ const blankIdx = partContent.indexOf(blankLine);
213
+ if (blankIdx === -1) continue;
214
+ const headerSection = partContent.slice(0, blankIdx).toString("utf8");
215
+ const content = partContent.slice(blankIdx + blankLine.length);
216
+ const headers = {};
217
+ for (const line of headerSection.split("\r\n")) {
218
+ if (!line) continue;
219
+ const colonIdx = line.indexOf(":");
220
+ if (colonIdx === -1) continue;
221
+ const key = line.substring(0, colonIdx).replace(/^\s+|\s+$/g, "").toLowerCase();
222
+ const value = line.substring(colonIdx + 1).replace(/^\s+|\s+$/g, "");
223
+ headers[key] = value;
224
+ }
225
+ parts.push({ headers, content });
226
+ }
227
+ return parts;
228
+ }
229
+ function readBodyAsFormData(req, res, next, contentType, data) {
230
+ try {
231
+ req.body = parseMultipartBody(contentType, data);
232
+ next();
233
+ } catch (ex) {
234
+ const status = ex.status ?? 500;
235
+ res.status(status).send(ex.message ?? String(ex));
236
+ }
237
+ }
238
+ function readBodyAsFormEncoded(req, res, next, contentType, data) {
239
+ const charset = extractCharset(contentType);
240
+ try {
241
+ const params = new URLSearchParams(data.toString(charset));
242
+ const result = {};
243
+ for (const [key, value] of params.entries()) {
244
+ const existing = result[key];
245
+ if (existing === void 0) {
246
+ result[key] = value;
247
+ } else if (Array.isArray(existing)) {
248
+ existing.push(value);
249
+ } else {
250
+ result[key] = [existing, value];
251
+ }
252
+ }
253
+ req.body = result;
254
+ next();
255
+ } catch (ex) {
256
+ res.status(400).send("Bad Request: " + ex.message);
257
+ }
258
+ }
259
+ var BODY_READERS = {
260
+ "multipart/form-data": (req, res, next, _opts, ct, data) => readBodyAsFormData(req, res, next, ct, data),
261
+ "application/json": (req, res, next, opts, ct, data) => readBodyAsJson(req, res, next, opts, ct, data),
262
+ "application/x-www-form-urlencoded": (req, res, next, _opts, ct, data) => readBodyAsFormEncoded(req, res, next, ct, data),
263
+ "text/plain": (req, res, next, _opts, ct, data) => readBodyAsPlainText(req, res, next, ct, data)
264
+ };
265
+ function json(opts) {
266
+ const resolved = {
267
+ inflate: true,
268
+ limit: "100kb",
269
+ reviver: null,
270
+ strict: true,
271
+ ...opts
272
+ };
273
+ return (req, res, next) => {
274
+ res.json = (data) => {
275
+ res.write(JSON.stringify(data));
276
+ res.end();
277
+ };
278
+ readBody(req, res, resolved, "application/json", next, (contentType, body) => {
279
+ readBodyAsJson(req, res, next, resolved, contentType, body);
280
+ });
281
+ };
282
+ }
283
+ function formData(opts) {
284
+ const resolved = {
285
+ inflate: true,
286
+ limit: "100kb",
287
+ reviver: null,
288
+ strict: true,
289
+ ...opts
290
+ };
291
+ return (req, res, next) => {
292
+ readBody(req, res, resolved, "multipart/form-data", next, (contentType, body) => {
293
+ readBodyAsFormData(req, res, next, contentType, body);
294
+ });
295
+ };
296
+ }
297
+ function formEncoded(opts) {
298
+ const resolved = {
299
+ inflate: true,
300
+ limit: "100kb",
301
+ reviver: null,
302
+ strict: true,
303
+ ...opts
304
+ };
305
+ return (req, res, next) => {
306
+ readBody(req, res, resolved, "application/x-www-form-urlencoded", next, (contentType, body) => {
307
+ readBodyAsFormEncoded(req, res, next, contentType, body);
308
+ });
309
+ };
310
+ }
311
+ function parseBody(opts) {
312
+ const resolved = {
313
+ inflate: true,
314
+ limit: "100kb",
315
+ reviver: null,
316
+ strict: true,
317
+ ...opts
318
+ };
319
+ return (req, res, next) => {
320
+ readBody(req, res, resolved, null, next, (contentType, body) => {
321
+ const mimetype = contentType.split(";")[0].trim();
322
+ if (!BODY_READERS[mimetype])
323
+ return res.status(415).send("Unsupported Media Type");
324
+ BODY_READERS[mimetype](req, res, next, resolved, contentType, body);
325
+ });
326
+ };
327
+ }
328
+ async function* streamFormData(req, opts) {
329
+ const maxSize = (opts?.limit !== void 0 ? readSize(opts.limit) : 0) || 102400;
330
+ const chunks = [];
331
+ let totalSize = 0;
332
+ for await (const chunk of req) {
333
+ totalSize += chunk.length;
334
+ if (totalSize > maxSize) throw { status: 413, message: "Content Too Large" };
335
+ chunks.push(chunk);
336
+ }
337
+ const body = Buffer.concat(chunks);
338
+ const contentType = req.headers["content-type"] ?? "";
339
+ const parts = parseMultipartBody(contentType, body);
340
+ for (const part of parts) {
341
+ yield { headers: part.headers, stream: import_stream.Readable.from(part.content) };
342
+ }
343
+ }
344
+ var STATUS_COLORS = [
345
+ "\x1B[0m",
346
+ // 0 — fallback / unknown
347
+ "\x1B[33m",
348
+ // 1xx — informational (yellow)
349
+ "\x1B[32m",
350
+ // 2xx — success (green)
351
+ "\x1B[33m",
352
+ // 3xx — redirection (yellow)
353
+ "\x1B[31m",
354
+ // 4xx — client error (red)
355
+ "\x1B[91m"
356
+ // 5xx — server error (bright red)
357
+ ];
358
+ var ANSI_RESET = "\x1B[0m";
359
+ function logger(opts) {
360
+ const options = opts ?? {};
361
+ const log = options.logger ?? console.log;
362
+ const formatter = new Intl.DateTimeFormat(
363
+ options.locale ?? "en-GB",
364
+ options.dateFormat ?? {
365
+ month: "short",
366
+ day: "2-digit",
367
+ hour: "2-digit",
368
+ minute: "2-digit"
369
+ }
370
+ );
371
+ return (req, res, next) => {
372
+ const timestamp = formatter.format(/* @__PURE__ */ new Date());
373
+ const requestPath = req.path ?? req.url ?? "/";
374
+ const ip = req.ip ?? "";
375
+ const user = options.user?.(req) ?? "-";
376
+ const receivedAt = Date.now();
377
+ const tracker = options.track === true ? setTimeout(() => {
378
+ log(`${timestamp} LOST ${req.method} ${requestPath} ${ip} <${user}>`);
379
+ }, options.trackTimeout ?? 3e4) : null;
380
+ res.on("finish", () => {
381
+ if (tracker !== null) clearTimeout(tracker);
382
+ const host = req.headers.host;
383
+ const elapsed = Date.now() - receivedAt;
384
+ const statusClass = Math.floor(res.statusCode / 100);
385
+ const colour = STATUS_COLORS[statusClass] ?? STATUS_COLORS[0];
386
+ const statusStr = `${colour}${res.statusCode}${ANSI_RESET}`;
387
+ const contentLen = res.getHeader("content-length") ?? "-";
388
+ if (options.json === true)
389
+ log({
390
+ timestamp,
391
+ status: res.statusCode,
392
+ method: req.method,
393
+ path: requestPath,
394
+ ip,
395
+ user,
396
+ elapsed,
397
+ host,
398
+ length: contentLen
399
+ });
400
+ else
401
+ log(`${timestamp} ${statusStr} ${req.method} ${requestPath} ${ip} <${user}> ${elapsed}ms (${contentLen})`);
402
+ });
403
+ next();
404
+ };
405
+ }
406
+ function cors(opts) {
407
+ const options = {
408
+ origin: opts?.origin || "*",
409
+ allowHeaders: opts?.allowHeaders || "Accept, Content-Type, Authorization",
410
+ allowMethods: opts?.allowMethods || "GET,HEAD,PUT,PATCH,POST,DELETE",
411
+ allowCredentials: opts?.allowCredentials,
412
+ maxAge: opts?.maxAge,
413
+ vary: opts?.vary,
414
+ optionsStatus: opts?.optionsStatus || 204,
415
+ preflight: opts?.preflight
416
+ };
417
+ return (req, res, next) => {
418
+ if (options.preflight && !options.preflight(req)) {
419
+ res.status(req.method == "OPTIONS" ? 403 : 400).end();
420
+ return;
421
+ }
422
+ if (req.headers.origin) {
423
+ res.setHeader("Access-Control-Allow-Origin", options.origin);
424
+ if (options.vary !== void 0)
425
+ res.setHeader("Vary", options.vary);
426
+ }
427
+ if (req.method == "OPTIONS") {
428
+ res.setHeader("Access-Control-Allow-Headers", options.allowHeaders);
429
+ res.setHeader("Access-Control-Allow-Methods", options.allowMethods);
430
+ if (options.allowCredentials !== void 0)
431
+ res.setHeader("Access-Control-Allow-Credentials", options.allowCredentials ? "true" : "false");
432
+ if (options.maxAge !== void 0)
433
+ res.setHeader("Access-Control-Max-Age", options.maxAge.toFixed(0));
434
+ res.status(options.optionsStatus).end();
435
+ return;
436
+ }
437
+ next();
438
+ };
439
+ }
440
+
441
+ // src/static.ts
442
+ var import_fs = __toESM(require("fs"), 1);
443
+ var import_path = __toESM(require("path"), 1);
444
+
445
+ // src/mimetypes.json
446
+ var mimetypes_default = { "application/andrew-inset": ["ez"], "application/applixware": ["aw"], "application/atom+xml": ["atom"], "application/atomcat+xml": ["atomcat"], "application/atomsvc+xml": ["atomsvc"], "application/bdoc": ["bdoc"], "application/ccxml+xml": ["ccxml"], "application/cdmi-capability": ["cdmia"], "application/cdmi-container": ["cdmic"], "application/cdmi-domain": ["cdmid"], "application/cdmi-object": ["cdmio"], "application/cdmi-queue": ["cdmiq"], "application/cu-seeme": ["cu"], "application/dash+xml": ["mpd"], "application/davmount+xml": ["davmount"], "application/docbook+xml": ["dbk"], "application/dssc+der": ["dssc"], "application/dssc+xml": ["xdssc"], "application/ecmascript": ["ecma"], "application/emma+xml": ["emma"], "application/epub+zip": ["epub"], "application/exi": ["exi"], "application/font-tdpfr": ["pfr"], "application/font-woff": [], "application/font-woff2": [], "application/geo+json": ["geojson"], "application/gml+xml": ["gml"], "application/gpx+xml": ["gpx"], "application/gxf": ["gxf"], "application/gzip": ["gz"], "application/hyperstudio": ["stk"], "application/inkml+xml": ["ink", "inkml"], "application/ipfix": ["ipfix"], "application/java-archive": ["jar", "war", "ear"], "application/java-serialized-object": ["ser"], "application/java-vm": ["class"], "application/javascript": ["js", "mjs"], "application/json": ["json", "map"], "application/json5": ["json5"], "application/jsonml+json": ["jsonml"], "application/ld+json": ["jsonld"], "application/lost+xml": ["lostxml"], "application/mac-binhex40": ["hqx"], "application/mac-compactpro": ["cpt"], "application/mads+xml": ["mads"], "application/manifest+json": ["webmanifest"], "application/marc": ["mrc"], "application/marcxml+xml": ["mrcx"], "application/mathematica": ["ma", "nb", "mb"], "application/mathml+xml": ["mathml"], "application/mbox": ["mbox"], "application/mediaservercontrol+xml": ["mscml"], "application/metalink+xml": ["metalink"], "application/metalink4+xml": ["meta4"], "application/mets+xml": ["mets"], "application/mods+xml": ["mods"], "application/mp21": ["m21", "mp21"], "application/mp4": ["mp4s", "m4p"], "application/msword": ["doc", "dot"], "application/mxf": ["mxf"], "application/octet-stream": ["bin", "dms", "lrf", "mar", "so", "dist", "distz", "pkg", "bpk", "dump", "elc", "deploy", "exe", "dll", "deb", "dmg", "iso", "img", "msi", "msp", "msm", "buffer"], "application/oda": ["oda"], "application/oebps-package+xml": ["opf"], "application/ogg": ["ogx"], "application/omdoc+xml": ["omdoc"], "application/onenote": ["onetoc", "onetoc2", "onetmp", "onepkg"], "application/oxps": ["oxps"], "application/patch-ops-error+xml": ["xer"], "application/pdf": ["pdf"], "application/pgp-encrypted": ["pgp"], "application/pgp-signature": ["asc", "sig"], "application/pics-rules": ["prf"], "application/pkcs10": ["p10"], "application/pkcs7-mime": ["p7m", "p7c"], "application/pkcs7-signature": ["p7s"], "application/pkcs8": ["p8"], "application/pkix-attr-cert": ["ac"], "application/pkix-cert": ["cer"], "application/pkix-crl": ["crl"], "application/pkix-pkipath": ["pkipath"], "application/pkixcmp": ["pki"], "application/pls+xml": ["pls"], "application/postscript": ["ai", "eps", "ps"], "application/prs.cww": ["cww"], "application/pskc+xml": ["pskcxml"], "application/raml+yaml": ["raml"], "application/rdf+xml": ["rdf"], "application/reginfo+xml": ["rif"], "application/relax-ng-compact-syntax": ["rnc"], "application/resource-lists+xml": ["rl"], "application/resource-lists-diff+xml": ["rld"], "application/rls-services+xml": ["rs"], "application/rpki-ghostbusters": ["gbr"], "application/rpki-manifest": ["mft"], "application/rpki-roa": ["roa"], "application/rsd+xml": ["rsd"], "application/rss+xml": ["rss"], "application/rtf": ["rtf"], "application/sbml+xml": ["sbml"], "application/scvp-cv-request": ["scq"], "application/scvp-cv-response": ["scs"], "application/scvp-vp-request": ["spq"], "application/scvp-vp-response": ["spp"], "application/sdp": ["sdp"], "application/set-payment-initiation": ["setpay"], "application/set-registration-initiation": ["setreg"], "application/shf+xml": ["shf"], "application/smil+xml": ["smi", "smil"], "application/sparql-query": ["rq"], "application/sparql-results+xml": ["srx"], "application/srgs": ["gram"], "application/srgs+xml": ["grxml"], "application/sru+xml": ["sru"], "application/ssdl+xml": ["ssdl"], "application/ssml+xml": ["ssml"], "application/tei+xml": ["tei", "teicorpus"], "application/thraud+xml": ["tfi"], "application/timestamped-data": ["tsd"], "application/vnd.3gpp.pic-bw-large": ["plb"], "application/vnd.3gpp.pic-bw-small": ["psb"], "application/vnd.3gpp.pic-bw-var": ["pvb"], "application/vnd.3gpp2.tcap": ["tcap"], "application/vnd.3m.post-it-notes": ["pwn"], "application/vnd.accpac.simply.aso": ["aso"], "application/vnd.accpac.simply.imp": ["imp"], "application/vnd.acucobol": ["acu"], "application/vnd.acucorp": ["atc", "acutc"], "application/vnd.adobe.air-application-installer-package+zip": ["air"], "application/vnd.adobe.formscentral.fcdt": ["fcdt"], "application/vnd.adobe.fxp": ["fxp", "fxpl"], "application/vnd.adobe.xdp+xml": ["xdp"], "application/vnd.adobe.xfdf": ["xfdf"], "application/vnd.ahead.space": ["ahead"], "application/vnd.airzip.filesecure.azf": ["azf"], "application/vnd.airzip.filesecure.azs": ["azs"], "application/vnd.amazon.ebook": ["azw"], "application/vnd.americandynamics.acc": ["acc"], "application/vnd.amiga.ami": ["ami"], "application/vnd.android.package-archive": ["apk"], "application/vnd.anser-web-certificate-issue-initiation": ["cii"], "application/vnd.anser-web-funds-transfer-initiation": ["fti"], "application/vnd.antix.game-component": ["atx"], "application/vnd.apple.installer+xml": ["mpkg"], "application/vnd.apple.mpegurl": ["m3u8"], "application/vnd.apple.pkpass": ["pkpass"], "application/vnd.aristanetworks.swi": ["swi"], "application/vnd.astraea-software.iota": ["iota"], "application/vnd.audiograph": ["aep"], "application/vnd.blueice.multipass": ["mpm"], "application/vnd.bmi": ["bmi"], "application/vnd.businessobjects": ["rep"], "application/vnd.chemdraw+xml": ["cdxml"], "application/vnd.chipnuts.karaoke-mmd": ["mmd"], "application/vnd.cinderella": ["cdy"], "application/vnd.claymore": ["cla"], "application/vnd.cloanto.rp9": ["rp9"], "application/vnd.clonk.c4group": ["c4g", "c4d", "c4f", "c4p", "c4u"], "application/vnd.cluetrust.cartomobile-config": ["c11amc"], "application/vnd.cluetrust.cartomobile-config-pkg": ["c11amz"], "application/vnd.commonspace": ["csp"], "application/vnd.contact.cmsg": ["cdbcmsg"], "application/vnd.cosmocaller": ["cmc"], "application/vnd.crick.clicker": ["clkx"], "application/vnd.crick.clicker.keyboard": ["clkk"], "application/vnd.crick.clicker.palette": ["clkp"], "application/vnd.crick.clicker.template": ["clkt"], "application/vnd.crick.clicker.wordbank": ["clkw"], "application/vnd.criticaltools.wbs+xml": ["wbs"], "application/vnd.ctc-posml": ["pml"], "application/vnd.cups-ppd": ["ppd"], "application/vnd.curl.car": ["car"], "application/vnd.curl.pcurl": ["pcurl"], "application/vnd.dart": ["dart"], "application/vnd.data-vision.rdz": ["rdz"], "application/vnd.dece.data": ["uvf", "uvvf", "uvd", "uvvd"], "application/vnd.dece.ttml+xml": ["uvt", "uvvt"], "application/vnd.dece.unspecified": ["uvx", "uvvx"], "application/vnd.dece.zip": ["uvz", "uvvz"], "application/vnd.denovo.fcselayout-link": ["fe_launch"], "application/vnd.dna": ["dna"], "application/vnd.dolby.mlp": ["mlp"], "application/vnd.dpgraph": ["dpg"], "application/vnd.dreamfactory": ["dfac"], "application/vnd.ds-keypoint": ["kpxx"], "application/vnd.dvb.ait": ["ait"], "application/vnd.dvb.service": ["svc"], "application/vnd.dynageo": ["geo"], "application/vnd.ecowin.chart": ["mag"], "application/vnd.enliven": ["nml"], "application/vnd.epson.esf": ["esf"], "application/vnd.epson.msf": ["msf"], "application/vnd.epson.quickanime": ["qam"], "application/vnd.epson.salt": ["slt"], "application/vnd.epson.ssf": ["ssf"], "application/vnd.eszigno3+xml": ["es3", "et3"], "application/vnd.ezpix-album": ["ez2"], "application/vnd.ezpix-package": ["ez3"], "application/vnd.fdf": ["fdf"], "application/vnd.fdsn.mseed": ["mseed"], "application/vnd.fdsn.seed": ["seed", "dataless"], "application/vnd.flographit": ["gph"], "application/vnd.fluxtime.clip": ["ftc"], "application/vnd.framemaker": ["fm", "frame", "maker", "book"], "application/vnd.frogans.fnc": ["fnc"], "application/vnd.frogans.ltf": ["ltf"], "application/vnd.fsc.weblaunch": ["fsc"], "application/vnd.fujitsu.oasys": ["oas"], "application/vnd.fujitsu.oasys2": ["oa2"], "application/vnd.fujitsu.oasys3": ["oa3"], "application/vnd.fujitsu.oasysgp": ["fg5"], "application/vnd.fujitsu.oasysprs": ["bh2"], "application/vnd.fujixerox.ddd": ["ddd"], "application/vnd.fujixerox.docuworks": ["xdw"], "application/vnd.fujixerox.docuworks.binder": ["xbd"], "application/vnd.fuzzysheet": ["fzs"], "application/vnd.genomatix.tuxedo": ["txd"], "application/vnd.geogebra.file": ["ggb"], "application/vnd.geogebra.tool": ["ggt"], "application/vnd.geometry-explorer": ["gex", "gre"], "application/vnd.geonext": ["gxt"], "application/vnd.geoplan": ["g2w"], "application/vnd.geospace": ["g3w"], "application/vnd.gmx": ["gmx"], "application/vnd.google-apps.document": ["gdoc"], "application/vnd.google-apps.presentation": ["gslides"], "application/vnd.google-apps.spreadsheet": ["gsheet"], "application/vnd.google-earth.kml+xml": ["kml"], "application/vnd.google-earth.kmz": ["kmz"], "application/vnd.grafeq": ["gqf", "gqs"], "application/vnd.groove-account": ["gac"], "application/vnd.groove-help": ["ghf"], "application/vnd.groove-identity-message": ["gim"], "application/vnd.groove-injector": ["grv"], "application/vnd.groove-tool-message": ["gtm"], "application/vnd.groove-tool-template": ["tpl"], "application/vnd.groove-vcard": ["vcg"], "application/vnd.hal+xml": ["hal"], "application/vnd.handheld-entertainment+xml": ["zmm"], "application/vnd.hbci": ["hbci"], "application/vnd.hhe.lesson-player": ["les"], "application/vnd.hp-hpgl": ["hpgl"], "application/vnd.hp-hpid": ["hpid"], "application/vnd.hp-hps": ["hps"], "application/vnd.hp-jlyt": ["jlt"], "application/vnd.hp-pcl": ["pcl"], "application/vnd.hp-pclxl": ["pclxl"], "application/vnd.hydrostatix.sof-data": ["sfd-hdstx"], "application/vnd.ibm.minipay": ["mpy"], "application/vnd.ibm.modcap": ["afp", "listafp", "list3820"], "application/vnd.ibm.rights-management": ["irm"], "application/vnd.ibm.secure-container": ["sc"], "application/vnd.iccprofile": ["icc", "icm"], "application/vnd.igloader": ["igl"], "application/vnd.immervision-ivp": ["ivp"], "application/vnd.immervision-ivu": ["ivu"], "application/vnd.insors.igm": ["igm"], "application/vnd.intercon.formnet": ["xpw", "xpx"], "application/vnd.intergeo": ["i2g"], "application/vnd.intu.qbo": ["qbo"], "application/vnd.intu.qfx": ["qfx"], "application/vnd.ipunplugged.rcprofile": ["rcprofile"], "application/vnd.irepository.package+xml": ["irp"], "application/vnd.is-xpr": ["xpr"], "application/vnd.isac.fcs": ["fcs"], "application/vnd.jam": ["jam"], "application/vnd.jcp.javame.midlet-rms": ["rms"], "application/vnd.jisp": ["jisp"], "application/vnd.joost.joda-archive": ["joda"], "application/vnd.kahootz": ["ktz", "ktr"], "application/vnd.kde.karbon": ["karbon"], "application/vnd.kde.kchart": ["chrt"], "application/vnd.kde.kformula": ["kfo"], "application/vnd.kde.kivio": ["flw"], "application/vnd.kde.kontour": ["kon"], "application/vnd.kde.kpresenter": ["kpr", "kpt"], "application/vnd.kde.kspread": ["ksp"], "application/vnd.kde.kword": ["kwd", "kwt"], "application/vnd.kenameaapp": ["htke"], "application/vnd.kidspiration": ["kia"], "application/vnd.kinar": ["kne", "knp"], "application/vnd.koan": ["skp", "skd", "skt", "skm"], "application/vnd.kodak-descriptor": ["sse"], "application/vnd.las.las+xml": ["lasxml"], "application/vnd.llamagraphics.life-balance.desktop": ["lbd"], "application/vnd.llamagraphics.life-balance.exchange+xml": ["lbe"], "application/vnd.lotus-1-2-3": ["123"], "application/vnd.lotus-approach": ["apr"], "application/vnd.lotus-freelance": ["pre"], "application/vnd.lotus-notes": ["nsf"], "application/vnd.lotus-organizer": ["org"], "application/vnd.lotus-screencam": ["scm"], "application/vnd.lotus-wordpro": ["lwp"], "application/vnd.macports.portpkg": ["portpkg"], "application/vnd.mcd": ["mcd"], "application/vnd.medcalcdata": ["mc1"], "application/vnd.mediastation.cdkey": ["cdkey"], "application/vnd.mfer": ["mwf"], "application/vnd.mfmp": ["mfm"], "application/vnd.micrografx.flo": ["flo"], "application/vnd.micrografx.igx": ["igx"], "application/vnd.mif": ["mif"], "application/vnd.mobius.daf": ["daf"], "application/vnd.mobius.dis": ["dis"], "application/vnd.mobius.mbk": ["mbk"], "application/vnd.mobius.mqy": ["mqy"], "application/vnd.mobius.msl": ["msl"], "application/vnd.mobius.plc": ["plc"], "application/vnd.mobius.txf": ["txf"], "application/vnd.mophun.application": ["mpn"], "application/vnd.mophun.certificate": ["mpc"], "application/vnd.mozilla.xul+xml": ["xul"], "application/vnd.ms-artgalry": ["cil"], "application/vnd.ms-cab-compressed": ["cab"], "application/vnd.ms-excel": ["xls", "xlm", "xla", "xlc", "xlt", "xlw"], "application/vnd.ms-excel.addin.macroenabled.12": ["xlam"], "application/vnd.ms-excel.sheet.binary.macroenabled.12": ["xlsb"], "application/vnd.ms-excel.sheet.macroenabled.12": ["xlsm"], "application/vnd.ms-excel.template.macroenabled.12": ["xltm"], "application/vnd.ms-fontobject": ["eot"], "application/vnd.ms-htmlhelp": ["chm"], "application/vnd.ms-ims": ["ims"], "application/vnd.ms-lrm": ["lrm"], "application/vnd.ms-officetheme": ["thmx"], "application/vnd.ms-outlook": ["msg"], "application/vnd.ms-pki.seccat": ["cat"], "application/vnd.ms-pki.stl": ["stl"], "application/vnd.ms-powerpoint": ["ppt", "pps", "pot"], "application/vnd.ms-powerpoint.addin.macroenabled.12": ["ppam"], "application/vnd.ms-powerpoint.presentation.macroenabled.12": ["pptm"], "application/vnd.ms-powerpoint.slide.macroenabled.12": ["sldm"], "application/vnd.ms-powerpoint.slideshow.macroenabled.12": ["ppsm"], "application/vnd.ms-powerpoint.template.macroenabled.12": ["potm"], "application/vnd.ms-project": ["mpp", "mpt"], "application/vnd.ms-word.document.macroenabled.12": ["docm"], "application/vnd.ms-word.template.macroenabled.12": ["dotm"], "application/vnd.ms-works": ["wps", "wks", "wcm", "wdb"], "application/vnd.ms-wpl": ["wpl"], "application/vnd.ms-xpsdocument": ["xps"], "application/vnd.mseq": ["mseq"], "application/vnd.musician": ["mus"], "application/vnd.muvee.style": ["msty"], "application/vnd.mynfc": ["taglet"], "application/vnd.neurolanguage.nlu": ["nlu"], "application/vnd.nitf": ["ntf", "nitf"], "application/vnd.noblenet-directory": ["nnd"], "application/vnd.noblenet-sealer": ["nns"], "application/vnd.noblenet-web": ["nnw"], "application/vnd.nokia.n-gage.data": ["ngdat"], "application/vnd.nokia.n-gage.symbian.install": ["n-gage"], "application/vnd.nokia.radio-preset": ["rpst"], "application/vnd.nokia.radio-presets": ["rpss"], "application/vnd.novadigm.edm": ["edm"], "application/vnd.novadigm.edx": ["edx"], "application/vnd.novadigm.ext": ["ext"], "application/vnd.oasis.opendocument.chart": ["odc"], "application/vnd.oasis.opendocument.chart-template": ["otc"], "application/vnd.oasis.opendocument.database": ["odb"], "application/vnd.oasis.opendocument.formula": ["odf"], "application/vnd.oasis.opendocument.formula-template": ["odft"], "application/vnd.oasis.opendocument.graphics": ["odg"], "application/vnd.oasis.opendocument.graphics-template": ["otg"], "application/vnd.oasis.opendocument.image": ["odi"], "application/vnd.oasis.opendocument.image-template": ["oti"], "application/vnd.oasis.opendocument.presentation": ["odp"], "application/vnd.oasis.opendocument.presentation-template": ["otp"], "application/vnd.oasis.opendocument.spreadsheet": ["ods"], "application/vnd.oasis.opendocument.spreadsheet-template": ["ots"], "application/vnd.oasis.opendocument.text": ["odt"], "application/vnd.oasis.opendocument.text-master": ["odm"], "application/vnd.oasis.opendocument.text-template": ["ott"], "application/vnd.oasis.opendocument.text-web": ["oth"], "application/vnd.olpc-sugar": ["xo"], "application/vnd.oma.dd2+xml": ["dd2"], "application/vnd.openofficeorg.extension": ["oxt"], "application/vnd.openxmlformats-officedocument.presentationml.presentation": ["pptx"], "application/vnd.openxmlformats-officedocument.presentationml.slide": ["sldx"], "application/vnd.openxmlformats-officedocument.presentationml.slideshow": ["ppsx"], "application/vnd.openxmlformats-officedocument.presentationml.template": ["potx"], "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": ["xlsx"], "application/vnd.openxmlformats-officedocument.spreadsheetml.template": ["xltx"], "application/vnd.openxmlformats-officedocument.wordprocessingml.document": ["docx"], "application/vnd.openxmlformats-officedocument.wordprocessingml.template": ["dotx"], "application/vnd.osgeo.mapguide.package": ["mgp"], "application/vnd.osgi.dp": ["dp"], "application/vnd.osgi.subsystem": ["esa"], "application/vnd.palm": ["pdb", "pqa", "oprc"], "application/vnd.pawaafile": ["paw"], "application/vnd.pg.format": ["str"], "application/vnd.pg.osasli": ["ei6"], "application/vnd.picsel": ["efif"], "application/vnd.pmi.widget": ["wg"], "application/vnd.pocketlearn": ["plf"], "application/vnd.powerbuilder6": ["pbd"], "application/vnd.previewsystems.box": ["box"], "application/vnd.proteus.magazine": ["mgz"], "application/vnd.publishare-delta-tree": ["qps"], "application/vnd.pvi.ptid1": ["ptid"], "application/vnd.quark.quarkxpress": ["qxd", "qxt", "qwd", "qwt", "qxl", "qxb"], "application/vnd.realvnc.bed": ["bed"], "application/vnd.recordare.musicxml": ["mxl"], "application/vnd.recordare.musicxml+xml": ["musicxml"], "application/vnd.rig.cryptonote": ["cryptonote"], "application/vnd.rim.cod": ["cod"], "application/vnd.rn-realmedia": ["rm"], "application/vnd.rn-realmedia-vbr": ["rmvb"], "application/vnd.route66.link66+xml": ["link66"], "application/vnd.sailingtracker.track": ["st"], "application/vnd.seemail": ["see"], "application/vnd.sema": ["sema"], "application/vnd.semd": ["semd"], "application/vnd.semf": ["semf"], "application/vnd.shana.informed.formdata": ["ifm"], "application/vnd.shana.informed.formtemplate": ["itp"], "application/vnd.shana.informed.interchange": ["iif"], "application/vnd.shana.informed.package": ["ipk"], "application/vnd.simtech-mindmapper": ["twd", "twds"], "application/vnd.smaf": ["mmf"], "application/vnd.smart.teacher": ["teacher"], "application/vnd.solent.sdkm+xml": ["sdkm", "sdkd"], "application/vnd.spotfire.dxp": ["dxp"], "application/vnd.spotfire.sfs": ["sfs"], "application/vnd.stardivision.calc": ["sdc"], "application/vnd.stardivision.draw": ["sda"], "application/vnd.stardivision.impress": ["sdd"], "application/vnd.stardivision.math": ["smf"], "application/vnd.stardivision.writer": ["sdw", "vor"], "application/vnd.stardivision.writer-global": ["sgl"], "application/vnd.stepmania.package": ["smzip"], "application/vnd.stepmania.stepchart": ["sm"], "application/vnd.sun.wadl+xml": ["wadl"], "application/vnd.sun.xml.calc": ["sxc"], "application/vnd.sun.xml.calc.template": ["stc"], "application/vnd.sun.xml.draw": ["sxd"], "application/vnd.sun.xml.draw.template": ["std"], "application/vnd.sun.xml.impress": ["sxi"], "application/vnd.sun.xml.impress.template": ["sti"], "application/vnd.sun.xml.math": ["sxm"], "application/vnd.sun.xml.writer": ["sxw"], "application/vnd.sun.xml.writer.global": ["sxg"], "application/vnd.sun.xml.writer.template": ["stw"], "application/vnd.sus-calendar": ["sus", "susp"], "application/vnd.svd": ["svd"], "application/vnd.symbian.install": ["sis", "sisx"], "application/vnd.syncml+xml": ["xsm"], "application/vnd.syncml.dm+wbxml": ["bdm"], "application/vnd.syncml.dm+xml": ["xdm"], "application/vnd.tao.intent-module-archive": ["tao"], "application/vnd.tcpdump.pcap": ["pcap", "cap", "dmp"], "application/vnd.tmobile-livetv": ["tmo"], "application/vnd.trid.tpt": ["tpt"], "application/vnd.triscape.mxs": ["mxs"], "application/vnd.trueapp": ["tra"], "application/vnd.ufdl": ["ufd", "ufdl"], "application/vnd.uiq.theme": ["utz"], "application/vnd.umajin": ["umj"], "application/vnd.unity": ["unityweb"], "application/vnd.uoml+xml": ["uoml"], "application/vnd.vcx": ["vcx"], "application/vnd.visio": ["vsd", "vst", "vss", "vsw"], "application/vnd.visionary": ["vis"], "application/vnd.vsf": ["vsf"], "application/vnd.wap.wbxml": ["wbxml"], "application/vnd.wap.wmlc": ["wmlc"], "application/vnd.wap.wmlscriptc": ["wmlsc"], "application/vnd.webturbo": ["wtb"], "application/vnd.wolfram.player": ["nbp"], "application/vnd.wordperfect": ["wpd"], "application/vnd.wqd": ["wqd"], "application/vnd.wt.stf": ["stf"], "application/vnd.xara": ["xar"], "application/vnd.xfdl": ["xfdl"], "application/vnd.yamaha.hv-dic": ["hvd"], "application/vnd.yamaha.hv-script": ["hvs"], "application/vnd.yamaha.hv-voice": ["hvp"], "application/vnd.yamaha.openscoreformat": ["osf"], "application/vnd.yamaha.openscoreformat.osfpvg+xml": ["osfpvg"], "application/vnd.yamaha.smaf-audio": ["saf"], "application/vnd.yamaha.smaf-phrase": ["spf"], "application/vnd.yellowriver-custom-menu": ["cmp"], "application/vnd.zul": ["zir", "zirz"], "application/vnd.zzazz.deck+xml": ["zaz"], "application/voicexml+xml": ["vxml"], "application/wasm": ["wasm"], "application/widget": ["wgt"], "application/winhlp": ["hlp"], "application/wsdl+xml": ["wsdl"], "application/wspolicy+xml": ["wspolicy"], "application/x-7z-compressed": ["7z"], "application/x-abiword": ["abw"], "application/x-ace-compressed": ["ace"], "application/x-apple-diskimage": [], "application/x-arj": ["arj"], "application/x-authorware-bin": ["aab", "x32", "u32", "vox"], "application/x-authorware-map": ["aam"], "application/x-authorware-seg": ["aas"], "application/x-bcpio": ["bcpio"], "application/x-bdoc": [], "application/x-bittorrent": ["torrent"], "application/x-blorb": ["blb", "blorb"], "application/x-bzip": ["bz"], "application/x-bzip2": ["bz2", "boz"], "application/x-cbr": ["cbr", "cba", "cbt", "cbz", "cb7"], "application/x-cdlink": ["vcd"], "application/x-cfs-compressed": ["cfs"], "application/x-chat": ["chat"], "application/x-chess-pgn": ["pgn"], "application/x-chrome-extension": ["crx"], "application/x-cocoa": ["cco"], "application/x-conference": ["nsc"], "application/x-cpio": ["cpio"], "application/x-csh": ["csh"], "application/x-debian-package": ["udeb"], "application/x-dgc-compressed": ["dgc"], "application/x-director": ["dir", "dcr", "dxr", "cst", "cct", "cxt", "w3d", "fgd", "swa"], "application/x-doom": ["wad"], "application/x-dtbncx+xml": ["ncx"], "application/x-dtbook+xml": ["dtb"], "application/x-dtbresource+xml": ["res"], "application/x-dvi": ["dvi"], "application/x-envoy": ["evy"], "application/x-eva": ["eva"], "application/x-font-bdf": ["bdf"], "application/x-font-ghostscript": ["gsf"], "application/x-font-linux-psf": ["psf"], "application/x-font-pcf": ["pcf"], "application/x-font-snf": ["snf"], "application/x-font-type1": ["pfa", "pfb", "pfm", "afm"], "application/x-freearc": ["arc"], "application/x-futuresplash": ["spl"], "application/x-gca-compressed": ["gca"], "application/x-glulx": ["ulx"], "application/x-gnumeric": ["gnumeric"], "application/x-gramps-xml": ["gramps"], "application/x-gtar": ["gtar"], "application/x-hdf": ["hdf"], "application/x-httpd-php": ["php"], "application/x-install-instructions": ["install"], "application/x-iso9660-image": [], "application/x-java-archive-diff": ["jardiff"], "application/x-java-jnlp-file": ["jnlp"], "application/x-latex": ["latex"], "application/x-lua-bytecode": ["luac"], "application/x-lzh-compressed": ["lzh", "lha"], "application/x-makeself": ["run"], "application/x-mie": ["mie"], "application/x-mobipocket-ebook": ["prc", "mobi"], "application/x-ms-application": ["application"], "application/x-ms-shortcut": ["lnk"], "application/x-ms-wmd": ["wmd"], "application/x-ms-wmz": ["wmz"], "application/x-ms-xbap": ["xbap"], "application/x-msaccess": ["mdb"], "application/x-msbinder": ["obd"], "application/x-mscardfile": ["crd"], "application/x-msclip": ["clp"], "application/x-msdos-program": [], "application/x-msdownload": ["com", "bat"], "application/x-msmediaview": ["mvb", "m13", "m14"], "application/x-msmetafile": ["wmf", "emf", "emz"], "application/x-msmoney": ["mny"], "application/x-mspublisher": ["pub"], "application/x-msschedule": ["scd"], "application/x-msterminal": ["trm"], "application/x-mswrite": ["wri"], "application/x-netcdf": ["nc", "cdf"], "application/x-ns-proxy-autoconfig": ["pac"], "application/x-nzb": ["nzb"], "application/x-perl": ["pl", "pm"], "application/x-pilot": [], "application/x-pkcs12": ["p12", "pfx"], "application/x-pkcs7-certificates": ["p7b", "spc"], "application/x-pkcs7-certreqresp": ["p7r"], "application/x-rar-compressed": ["rar"], "application/x-redhat-package-manager": ["rpm"], "application/x-research-info-systems": ["ris"], "application/x-sea": ["sea"], "application/x-sh": ["sh"], "application/x-shar": ["shar"], "application/x-shockwave-flash": ["swf"], "application/x-silverlight-app": ["xap"], "application/x-sql": ["sql"], "application/x-stuffit": ["sit"], "application/x-stuffitx": ["sitx"], "application/x-subrip": ["srt"], "application/x-sv4cpio": ["sv4cpio"], "application/x-sv4crc": ["sv4crc"], "application/x-t3vm-image": ["t3"], "application/x-tads": ["gam"], "application/x-tar": ["tar"], "application/x-tcl": ["tcl", "tk"], "application/x-tex": ["tex"], "application/x-tex-tfm": ["tfm"], "application/x-texinfo": ["texinfo", "texi"], "application/x-tgif": ["obj"], "application/x-ustar": ["ustar"], "application/x-virtualbox-hdd": ["hdd"], "application/x-virtualbox-ova": ["ova"], "application/x-virtualbox-ovf": ["ovf"], "application/x-virtualbox-vbox": ["vbox"], "application/x-virtualbox-vbox-extpack": ["vbox-extpack"], "application/x-virtualbox-vdi": ["vdi"], "application/x-virtualbox-vhd": ["vhd"], "application/x-virtualbox-vmdk": ["vmdk"], "application/x-wais-source": ["src"], "application/x-web-app-manifest+json": ["webapp"], "application/x-x509-ca-cert": ["der", "crt", "pem"], "application/x-xfig": ["fig"], "application/x-xliff+xml": ["xlf"], "application/x-xpinstall": ["xpi"], "application/x-xz": ["xz"], "application/x-zmachine": ["z1", "z2", "z3", "z4", "z5", "z6", "z7", "z8"], "application/xaml+xml": ["xaml"], "application/xcap-diff+xml": ["xdf"], "application/xenc+xml": ["xenc"], "application/xhtml+xml": ["xhtml", "xht"], "application/xml": ["xml", "xsl", "xsd", "rng"], "application/xml-dtd": ["dtd"], "application/xop+xml": ["xop"], "application/xproc+xml": ["xpl"], "application/xslt+xml": ["xslt"], "application/xspf+xml": ["xspf"], "application/xv+xml": ["mxml", "xhvml", "xvml", "xvm"], "application/yang": ["yang"], "application/yin+xml": ["yin"], "application/zip": ["zip"], "audio/3gpp": [], "audio/adpcm": ["adp"], "audio/basic": ["au", "snd"], "audio/midi": ["mid", "midi", "kar", "rmi"], "audio/mp3": [], "audio/mp4": ["m4a", "mp4a"], "audio/mpeg": ["mpga", "mp2", "mp2a", "mp3", "m2a", "m3a"], "audio/ogg": ["oga", "ogg", "spx"], "audio/s3m": ["s3m"], "audio/silk": ["sil"], "audio/vnd.dece.audio": ["uva", "uvva"], "audio/vnd.digital-winds": ["eol"], "audio/vnd.dra": ["dra"], "audio/vnd.dts": ["dts"], "audio/vnd.dts.hd": ["dtshd"], "audio/vnd.lucent.voice": ["lvp"], "audio/vnd.ms-playready.media.pya": ["pya"], "audio/vnd.nuera.ecelp4800": ["ecelp4800"], "audio/vnd.nuera.ecelp7470": ["ecelp7470"], "audio/vnd.nuera.ecelp9600": ["ecelp9600"], "audio/vnd.rip": ["rip"], "audio/wav": ["wav"], "audio/wave": [], "audio/webm": ["weba"], "audio/x-aac": ["aac"], "audio/x-aiff": ["aif", "aiff", "aifc"], "audio/x-caf": ["caf"], "audio/x-flac": ["flac"], "audio/x-m4a": [], "audio/x-matroska": ["mka"], "audio/x-mpegurl": ["m3u"], "audio/x-ms-wax": ["wax"], "audio/x-ms-wma": ["wma"], "audio/x-pn-realaudio": ["ram", "ra"], "audio/x-pn-realaudio-plugin": ["rmp"], "audio/x-realaudio": [], "audio/x-wav": [], "audio/xm": ["xm"], "chemical/x-cdx": ["cdx"], "chemical/x-cif": ["cif"], "chemical/x-cmdf": ["cmdf"], "chemical/x-cml": ["cml"], "chemical/x-csml": ["csml"], "chemical/x-xyz": ["xyz"], "font/collection": ["ttc"], "font/otf": ["otf"], "font/ttf": ["ttf"], "font/woff": ["woff"], "font/woff2": ["woff2"], "image/apng": ["apng"], "image/bmp": ["bmp"], "image/cgm": ["cgm"], "image/g3fax": ["g3"], "image/gif": ["gif"], "image/ief": ["ief"], "image/jp2": ["jp2", "jpg2"], "image/jpeg": ["jpeg", "jpg", "jpe"], "image/jpm": ["jpm"], "image/jpx": ["jpx", "jpf"], "image/ktx": ["ktx"], "image/png": ["png"], "image/prs.btif": ["btif"], "image/sgi": ["sgi"], "image/svg+xml": ["svg", "svgz"], "image/tiff": ["tiff", "tif"], "image/vnd.adobe.photoshop": ["psd"], "image/vnd.dece.graphic": ["uvi", "uvvi", "uvg", "uvvg"], "image/vnd.djvu": ["djvu", "djv"], "image/vnd.dvb.subtitle": [], "image/vnd.dwg": ["dwg"], "image/vnd.dxf": ["dxf"], "image/vnd.fastbidsheet": ["fbs"], "image/vnd.fpx": ["fpx"], "image/vnd.fst": ["fst"], "image/vnd.fujixerox.edmics-mmr": ["mmr"], "image/vnd.fujixerox.edmics-rlc": ["rlc"], "image/vnd.ms-modi": ["mdi"], "image/vnd.ms-photo": ["wdp"], "image/vnd.net-fpx": ["npx"], "image/vnd.wap.wbmp": ["wbmp"], "image/vnd.xiff": ["xif"], "image/webp": ["webp"], "image/x-3ds": ["3ds"], "image/x-cmu-raster": ["ras"], "image/x-cmx": ["cmx"], "image/x-freehand": ["fh", "fhc", "fh4", "fh5", "fh7"], "image/x-icon": ["ico"], "image/x-jng": ["jng"], "image/x-mrsid-image": ["sid"], "image/x-ms-bmp": [], "image/x-pcx": ["pcx"], "image/x-pict": ["pic", "pct"], "image/x-portable-anymap": ["pnm"], "image/x-portable-bitmap": ["pbm"], "image/x-portable-graymap": ["pgm"], "image/x-portable-pixmap": ["ppm"], "image/x-rgb": ["rgb"], "image/x-tga": ["tga"], "image/x-xbitmap": ["xbm"], "image/x-xpixmap": ["xpm"], "image/x-xwindowdump": ["xwd"], "message/rfc822": ["eml", "mime"], "model/gltf+json": ["gltf"], "model/gltf-binary": ["glb"], "model/iges": ["igs", "iges"], "model/mesh": ["msh", "mesh", "silo"], "model/vnd.collada+xml": ["dae"], "model/vnd.dwf": ["dwf"], "model/vnd.gdl": ["gdl"], "model/vnd.gtw": ["gtw"], "model/vnd.mts": ["mts"], "model/vnd.vtu": ["vtu"], "model/vrml": ["wrl", "vrml"], "model/x3d+binary": ["x3db", "x3dbz"], "model/x3d+vrml": ["x3dv", "x3dvz"], "model/x3d+xml": ["x3d", "x3dz"], "text/cache-manifest": ["appcache", "manifest"], "text/calendar": ["ics", "ifb"], "text/coffeescript": ["coffee", "litcoffee"], "text/css": ["css"], "text/csv": ["csv"], "text/hjson": ["hjson"], "text/html": ["html", "htm", "shtml"], "text/jade": ["jade"], "text/jsx": ["jsx"], "text/less": ["less"], "text/markdown": ["markdown", "md"], "text/mathml": ["mml"], "text/n3": ["n3"], "text/plain": ["txt", "text", "conf", "def", "list", "log", "in", "ini"], "text/prs.lines.tag": ["dsc"], "text/richtext": ["rtx"], "text/rtf": [], "text/sgml": ["sgml", "sgm"], "text/slim": ["slim", "slm"], "text/stylus": ["stylus", "styl"], "text/tab-separated-values": ["tsv"], "text/troff": ["t", "tr", "roff", "man", "me", "ms"], "text/turtle": ["ttl"], "text/uri-list": ["uri", "uris", "urls"], "text/vcard": ["vcard"], "text/vnd.curl": ["curl"], "text/vnd.curl.dcurl": ["dcurl"], "text/vnd.curl.mcurl": ["mcurl"], "text/vnd.curl.scurl": ["scurl"], "text/vnd.dvb.subtitle": ["sub"], "text/vnd.fly": ["fly"], "text/vnd.fmi.flexstor": ["flx"], "text/vnd.graphviz": ["gv"], "text/vnd.in3d.3dml": ["3dml"], "text/vnd.in3d.spot": ["spot"], "text/vnd.sun.j2me.app-descriptor": ["jad"], "text/vnd.wap.wml": ["wml"], "text/vnd.wap.wmlscript": ["wmls"], "text/vtt": ["vtt"], "text/x-asm": ["s", "asm"], "text/x-c": ["c", "cc", "cxx", "cpp", "h", "hh", "dic"], "text/x-component": ["htc"], "text/x-fortran": ["f", "for", "f77", "f90"], "text/x-handlebars-template": ["hbs"], "text/x-java-source": ["java"], "text/x-lua": ["lua"], "text/x-markdown": ["mkd"], "text/x-nfo": ["nfo"], "text/x-opml": ["opml"], "text/x-org": [], "text/x-pascal": ["p", "pas"], "text/x-processing": ["pde"], "text/x-sass": ["sass"], "text/x-scss": ["scss"], "text/x-setext": ["etx"], "text/x-sfv": ["sfv"], "text/x-suse-ymp": ["ymp"], "text/x-uuencode": ["uu"], "text/x-vcalendar": ["vcs"], "text/x-vcard": ["vcf"], "text/xml": [], "text/yaml": ["yaml", "yml"], "video/3gpp": ["3gp", "3gpp"], "video/3gpp2": ["3g2"], "video/h261": ["h261"], "video/h263": ["h263"], "video/h264": ["h264"], "video/jpeg": ["jpgv"], "video/jpm": ["jpgm"], "video/mj2": ["mj2", "mjp2"], "video/mp2t": ["ts"], "video/mp4": ["mp4", "mp4v", "mpg4"], "video/mpeg": ["mpeg", "mpg", "mpe", "m1v", "m2v"], "video/ogg": ["ogv"], "video/quicktime": ["qt", "mov"], "video/vnd.dece.hd": ["uvh", "uvvh"], "video/vnd.dece.mobile": ["uvm", "uvvm"], "video/vnd.dece.pd": ["uvp", "uvvp"], "video/vnd.dece.sd": ["uvs", "uvvs"], "video/vnd.dece.video": ["uvv", "uvvv"], "video/vnd.dvb.file": ["dvb"], "video/vnd.fvt": ["fvt"], "video/vnd.mpegurl": ["mxu", "m4u"], "video/vnd.ms-playready.media.pyv": ["pyv"], "video/vnd.uvvu.mp4": ["uvu", "uvvu"], "video/vnd.vivo": ["viv"], "video/webm": ["webm"], "video/x-f4v": ["f4v"], "video/x-fli": ["fli"], "video/x-flv": ["flv"], "video/x-m4v": ["m4v"], "video/x-matroska": ["mkv", "mk3d", "mks"], "video/x-mng": ["mng"], "video/x-ms-asf": ["asf", "asx"], "video/x-ms-vob": ["vob"], "video/x-ms-wm": ["wm"], "video/x-ms-wmv": ["wmv"], "video/x-ms-wmx": ["wmx"], "video/x-ms-wvx": ["wvx"], "video/x-msvideo": ["avi"], "video/x-sgi-movie": ["movie"], "video/x-smv": ["smv"], "x-conference/x-cooltalk": ["ice"] };
447
+
448
+ // src/static.ts
449
+ var mime_types = /* @__PURE__ */ new Map();
450
+ var mime_extensions = /* @__PURE__ */ new Map();
451
+ function mime_define(map) {
452
+ for (var type in map) {
453
+ var exts = map[type];
454
+ for (var i = 0; i < exts.length; i++)
455
+ mime_types.set(exts[i], type);
456
+ if (!mime_extensions.has(type))
457
+ mime_extensions.set(type, exts[0]);
458
+ }
459
+ }
460
+ var mime = {
461
+ lookup: (path2, fallback = null) => mime_types.get(path2.replace(/^.*[\.\/\\]/, "").toLowerCase()) ?? fallback ?? "application/octet-stream",
462
+ charsets: (mimeType) => /^text\/|^application\/(javascript|json)/.test(mimeType) ? "UTF-8" : null
463
+ };
464
+ mime_define(mimetypes_default);
465
+ var UP_PATH_REGEXP = /(?:^|[\\/])\.\.(?:[\\/]|$)/;
466
+ var DEFAULT_OPTIONS = {
467
+ headers: {
468
+ "Content-Security-Policy": "default-src 'self'",
469
+ "X-Content-Type-Options": "nosniff"
470
+ },
471
+ fallthrough: false,
472
+ maxage: 0,
473
+ immutable: false,
474
+ etag: true,
475
+ lastModified: true,
476
+ contentType: null,
477
+ dotfiles: "hide",
478
+ redirect: true,
479
+ indexOf: false
480
+ };
481
+ var HTTP = {
482
+ /** 304 Not Modified — sent for conditional GET cache hits. */
483
+ NOT_MODIFIED: (res, opts) => {
484
+ res.status(304, opts.headers).end();
485
+ },
486
+ /** 403 Forbidden — sent for denied dot-files or path traversal attempts. */
487
+ FORBIDDEN: (res, opts) => res.status(403, opts.headers).send("Forbidden"),
488
+ /** 404 Not Found — sent when the requested file does not exist. */
489
+ NOT_FOUND: (res, opts) => res.status(404, opts.headers).send("Not Found"),
490
+ /**
491
+ * 405 Method Not Allowed — sent when the HTTP method is neither GET nor
492
+ * HEAD and {@link ResolvedOptions.fallthrough} is `false`.
493
+ * Always includes an `Allow: GET, HEAD` header per RFC 7231 §6.5.5.
494
+ */
495
+ NOT_ALLOWED: (res, opts) => {
496
+ res.status(405, { ...opts.headers, Allow: "GET, HEAD" }).end();
497
+ },
498
+ /** 412 Precondition Failed — sent when `If-Match` / `If-Unmodified-Since` fails. */
499
+ PRECONDITION_FAILS: (res, opts) => res.status(412, opts.headers).send("Precondition Failed"),
500
+ /** 500 Internal Server Error — sent on unexpected filesystem or stream errors. */
501
+ INTERNAL_ERROR: (res, opts, err) => res.status(500, opts.headers).send(`Internal error: ${err}`)
502
+ };
503
+ function destroyReadStream(stream) {
504
+ stream.destroy();
505
+ if (typeof stream.close === "function") {
506
+ stream.on("open", () => {
507
+ if (typeof stream.fd === "number")
508
+ stream.close();
509
+ });
510
+ }
511
+ }
512
+ function removeContentHeaders(res) {
513
+ const keys = Object.keys(res.getHeaders() ?? {});
514
+ for (const key of keys) {
515
+ if (key.startsWith("content-") && key !== "content-location")
516
+ res.removeHeader(key);
517
+ }
518
+ }
519
+ function createETag(stat) {
520
+ const mtime = stat.mtime.getTime().toString(16);
521
+ const size = stat.size.toString(16);
522
+ return `W/"${size}-${mtime}"`;
523
+ }
524
+ function parseTokenList(str) {
525
+ const list = [];
526
+ let start = 0;
527
+ let end = 0;
528
+ for (let i = 0, len = str.length; i < len; i++) {
529
+ const ch = str.charCodeAt(i);
530
+ if (ch === 32) {
531
+ if (start === end) start = end = i + 1;
532
+ } else if (ch === 44) {
533
+ list.push(str.substring(start, end));
534
+ start = end = i + 1;
535
+ } else {
536
+ end = i + 1;
537
+ }
538
+ }
539
+ list.push(str.substring(start, end));
540
+ return list;
541
+ }
542
+ function parseHttpDate(date) {
543
+ const timestamp = date ? Date.parse(date) : NaN;
544
+ return typeof timestamp === "number" ? timestamp : NaN;
545
+ }
546
+ function conditionMatch(reqHeaders, resHeaders) {
547
+ const match = reqHeaders["if-match"];
548
+ if (match) {
549
+ const etag = resHeaders["etag"];
550
+ if (match === "*" || match === etag) return true;
551
+ for (const tag of parseTokenList(match)) {
552
+ if (tag === etag || `W/${tag}` === etag || tag === `W/${etag}`)
553
+ return true;
554
+ }
555
+ return false;
556
+ }
557
+ const lastModified = parseHttpDate(resHeaders["last-modified"]);
558
+ const unmodifiedSince = parseHttpDate(reqHeaders["if-unmodified-since"]);
559
+ if (!isNaN(unmodifiedSince) && !isNaN(lastModified))
560
+ return lastModified <= unmodifiedSince;
561
+ return false;
562
+ }
563
+ function isCacheFresh(reqHeaders, resHeaders) {
564
+ const CACHE_CONTROL_NO_CACHE_REGEXP = /(?:^|,)\s*?no-cache\s*?(?:,|$)/;
565
+ const modifiedSince = reqHeaders["if-modified-since"];
566
+ const noneMatch = reqHeaders["if-none-match"];
567
+ if (!modifiedSince && !noneMatch) return false;
568
+ const cacheControl2 = reqHeaders["cache-control"];
569
+ if (cacheControl2 && CACHE_CONTROL_NO_CACHE_REGEXP.test(cacheControl2))
570
+ return false;
571
+ if (noneMatch && noneMatch !== "*") {
572
+ const etag = resHeaders["etag"];
573
+ if (!etag) return false;
574
+ let etagStale = true;
575
+ for (const match of parseTokenList(noneMatch)) {
576
+ if (match === etag || `W/${match}` === etag || match === `W/${etag}`) {
577
+ etagStale = false;
578
+ break;
579
+ }
580
+ }
581
+ if (etagStale) return false;
582
+ }
583
+ if (modifiedSince) {
584
+ const lastModified = resHeaders["last-modified"];
585
+ if (!lastModified) return false;
586
+ if (!(parseHttpDate(lastModified) <= parseHttpDate(modifiedSince)))
587
+ return false;
588
+ }
589
+ return true;
590
+ }
591
+ function resolveOptions(root, options) {
592
+ if (!root)
593
+ throw new TypeError("root path required");
594
+ if (typeof root !== "string")
595
+ throw new TypeError("root path must be a string");
596
+ const mergedHeaders = { ...DEFAULT_OPTIONS.headers, ...options?.headers ?? {} };
597
+ const opts = { ...DEFAULT_OPTIONS, ...options, headers: mergedHeaders };
598
+ opts.fallthrough = options?.fallthrough !== false;
599
+ opts.redirect = options?.redirect !== false;
600
+ opts.maxage = options?.maxage ?? options?.maxAge ?? 0;
601
+ opts.root = import_path.default.resolve(root);
602
+ return opts;
603
+ }
604
+ function writeIndexOf(urlPath, directoryPath, parentUrlPath, queryString, callback) {
605
+ import_fs.default.readdir(directoryPath, (err, files) => {
606
+ if (err) return callback(null, err);
607
+ const params = new URLSearchParams(queryString.replace(/;/g, "&"));
608
+ const rawCol = params.get("C") ?? "N";
609
+ const rawOrd = params.get("O") ?? "A";
610
+ const col = ["N", "M", "S"].includes(rawCol) ? rawCol : "N";
611
+ const ord = rawOrd === "D" ? "D" : "A";
612
+ const entries = [];
613
+ for (const file of files) {
614
+ const fullPath = import_path.default.join(directoryPath, file);
615
+ let stat;
616
+ try {
617
+ stat = import_fs.default.statSync(fullPath);
618
+ } catch {
619
+ continue;
620
+ }
621
+ entries.push({ file, stat, isDir: stat.isDirectory() });
622
+ }
623
+ const sign = ord === "A" ? 1 : -1;
624
+ entries.sort((a, b) => {
625
+ if (a.isDir !== b.isDir) return a.isDir ? -1 : 1;
626
+ let cmp = 0;
627
+ if (col === "N") cmp = a.file.localeCompare(b.file);
628
+ else if (col === "M") cmp = a.stat.mtime.getTime() - b.stat.mtime.getTime();
629
+ else if (col === "S") cmp = (a.isDir ? 0 : a.stat.size) - (b.isDir ? 0 : b.stat.size);
630
+ return cmp * sign;
631
+ });
632
+ function thLink(c, label) {
633
+ const nextOrd = c === col && ord === "A" ? "D" : "A";
634
+ return `<a href="?C=${c};O=${nextOrd}">${label}</a>`;
635
+ }
636
+ let html = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">\n';
637
+ html += "<html>\n";
638
+ html += `<head><title>Index of ${urlPath}</title></head>
639
+ `;
640
+ html += `<body><h1>Index of ${urlPath}</h1><table>
641
+ `;
642
+ html += `<tr><th valign="top"><img src="/icons/blank.gif" alt="[ICO]"></th><th>${thLink("N", "Name")}</th><th>${thLink("M", "Last modified")}</th><th>${thLink("S", "Size")}</th><th><a href="?C=D;O=A">Description</a></th></tr>
643
+ `;
644
+ html += '<tr><th colspan="5"><hr></th></tr>\n';
645
+ if (parentUrlPath)
646
+ html += `<tr><td valign="top"><img src="/icons/back.gif" alt="[PARENTDIR]"></td><td><a href="${parentUrlPath}">Parent Directory</a></td><td>&nbsp;</td><td align="right"> - </td><td>&nbsp;</td></tr>
647
+ `;
648
+ for (const { file, stat, isDir } of entries) {
649
+ const fullPath = import_path.default.join(directoryPath, file);
650
+ const mimeType = mime.lookup(fullPath, "");
651
+ const mediaType = mimeType.includes("/") ? mimeType.split("/")[0] : "";
652
+ const alt = isDir ? "folder" : mediaType || "unknown";
653
+ const icon = `/icons/${alt}.gif`;
654
+ const name = file + (isDir ? "/" : "");
655
+ const modified = stat.mtime.toUTCString();
656
+ const size = isDir ? "-" : String(stat.size);
657
+ html += `<tr><td valign="top"><img src="${icon}" alt="[${alt.toUpperCase()}]"></td><td><a href="${name}">${name}</a></td><td align="right">${modified}</td><td align="right">${size}</td><td>&nbsp;</td></tr>
658
+ `;
659
+ }
660
+ html += '<tr><th colspan="5"><hr></th></tr>\n';
661
+ html += "</table><address>Expediate/1.0.0</address></body></html>\n";
662
+ return callback(html, null);
663
+ });
664
+ }
665
+ function sendIt(req, res, pathname, stat, opts) {
666
+ const len = stat.size;
667
+ const etag = createETag(stat);
668
+ if (opts.headers) {
669
+ for (const [key, value] of Object.entries(opts.headers))
670
+ res.setHeader(key, value);
671
+ }
672
+ if (!res.getHeader("Cache-Control") && opts.maxage) {
673
+ let cacheControl2 = `public, max-age=${Math.floor(opts.maxage / 1e3)}`;
674
+ if (opts.immutable) cacheControl2 += ", immutable";
675
+ res.setHeader("Cache-Control", cacheControl2);
676
+ }
677
+ if (!res.getHeader("Last-Modified") && opts.lastModified !== false)
678
+ res.setHeader("Last-Modified", stat.mtime.toUTCString());
679
+ if (!res.getHeader("ETag") && opts.etag !== false)
680
+ res.setHeader("ETag", etag);
681
+ if (!res.getHeader("Content-Type")) {
682
+ if (opts.contentType) {
683
+ res.setHeader("Content-Type", opts.contentType);
684
+ } else {
685
+ const type = mime.lookup(pathname, "");
686
+ if (type) {
687
+ const charset = mime.charsets(type);
688
+ res.setHeader("Content-Type", charset ? `${type}; charset=${charset}` : type);
689
+ }
690
+ }
691
+ }
692
+ if (isCacheFresh(
693
+ req.headers,
694
+ res.getHeaders()
695
+ )) {
696
+ removeContentHeaders(res);
697
+ HTTP.NOT_MODIFIED(res, opts);
698
+ return;
699
+ }
700
+ const hasPrecondition = !!(req.headers["if-match"] || req.headers["if-unmodified-since"]);
701
+ if (hasPrecondition && !conditionMatch(
702
+ req.headers,
703
+ res.getHeaders()
704
+ )) {
705
+ HTTP.PRECONDITION_FAILS(res, opts);
706
+ return;
707
+ }
708
+ res.setHeader("Content-Length", len);
709
+ if (req.method === "HEAD") {
710
+ res.end();
711
+ return;
712
+ }
713
+ let finished = false;
714
+ const stream = import_fs.default.createReadStream(pathname);
715
+ res.on("finish", () => {
716
+ finished = true;
717
+ destroyReadStream(stream);
718
+ });
719
+ stream.on("error", (err) => {
720
+ if (finished) return;
721
+ console.warn("static stream error:", pathname, err);
722
+ HTTP.INTERNAL_ERROR(res, opts, err.code ?? "UNKNOWN");
723
+ finished = true;
724
+ destroyReadStream(stream);
725
+ });
726
+ stream.on("end", () => res.end());
727
+ stream.pipe(res);
728
+ }
729
+ function sendFile(req, res, pathname, opts) {
730
+ import_fs.default.stat(pathname, (err, stat) => {
731
+ if (err) {
732
+ if (err.code === "ENOENT" || err.code === "ENAMETOOLONG" || err.code === "ENOTDIR") return HTTP.NOT_FOUND(res, opts);
733
+ return HTTP.INTERNAL_ERROR(res, opts, err.code ?? "UNKNOWN");
734
+ }
735
+ if (stat.isDirectory()) {
736
+ if (opts.indexOf) {
737
+ const parentUrl = req.path !== "/" ? import_path.default.dirname(req.path) : null;
738
+ const queryString = (req.url ?? "").split("?")[1] ?? "";
739
+ return writeIndexOf(req.path, pathname, parentUrl, queryString, (html, indexErr) => {
740
+ if (indexErr)
741
+ return HTTP.INTERNAL_ERROR(res, opts, indexErr.code ?? "UNKNOWN");
742
+ return res.status(200, opts.headers).send(html);
743
+ });
744
+ }
745
+ return HTTP.NOT_FOUND(res, opts);
746
+ }
747
+ sendIt(req, res, pathname, stat, opts);
748
+ });
749
+ }
750
+ function serveStatic(root, options) {
751
+ const opts = resolveOptions(root, options);
752
+ return function(req, res, next) {
753
+ if (req.method !== "GET" && req.method !== "HEAD") {
754
+ if (opts.fallthrough)
755
+ return next();
756
+ return HTTP.NOT_ALLOWED(res, opts);
757
+ }
758
+ const originalUrl = decodeURIComponent(req.path ?? req.url ?? "/");
759
+ let pathname = originalUrl;
760
+ if (pathname === "/" && !originalUrl.endsWith("/"))
761
+ pathname = "";
762
+ if (UP_PATH_REGEXP.test(pathname))
763
+ return HTTP.FORBIDDEN(res, opts);
764
+ pathname = import_path.default.resolve(import_path.default.normalize(`${opts.root}/${pathname}`));
765
+ if (opts.dotfiles !== "allow" && pathname.includes("/.")) {
766
+ if (opts.dotfiles === "deny")
767
+ return HTTP.FORBIDDEN(res, opts);
768
+ return HTTP.NOT_FOUND(res, opts);
769
+ }
770
+ import_fs.default.stat(pathname, (err, stat) => {
771
+ if (err) {
772
+ if (err.code === "ENOENT" || err.code === "ENAMETOOLONG" || err.code === "ENOTDIR") {
773
+ if (opts.fallthrough)
774
+ return next();
775
+ return HTTP.NOT_FOUND(res, opts);
776
+ }
777
+ return HTTP.INTERNAL_ERROR(res, opts, err.code ?? "UNKNOWN");
778
+ }
779
+ if (stat.isDirectory()) {
780
+ if (!opts.redirect) {
781
+ if (opts.fallthrough)
782
+ return next();
783
+ return HTTP.NOT_FOUND(res, opts);
784
+ }
785
+ return sendFile(req, res, import_path.default.join(pathname, "index.html"), opts);
786
+ }
787
+ sendIt(req, res, pathname, stat, opts);
788
+ });
789
+ };
790
+ }
791
+ function serveFile(filePath, options) {
792
+ const opts = resolveOptions(filePath, options);
793
+ return function(req, res, next) {
794
+ if (req.method !== "GET" && req.method !== "HEAD") {
795
+ if (opts.fallthrough)
796
+ return next();
797
+ return HTTP.NOT_ALLOWED(res, opts);
798
+ }
799
+ const pathname = opts.root;
800
+ import_fs.default.stat(pathname, (err, stat) => {
801
+ if (err)
802
+ return HTTP.INTERNAL_ERROR(res, opts, err.code ?? "UNKNOWN");
803
+ if (stat.isDirectory())
804
+ return HTTP.INTERNAL_ERROR(res, opts, "EISDIR");
805
+ sendIt(req, res, pathname, stat, opts);
806
+ });
807
+ };
808
+ }
809
+
810
+ // src/router.ts
811
+ function isGlobPattern(pattern) {
812
+ let i = 0;
813
+ while (i < pattern.length) {
814
+ const ch = pattern[i];
815
+ if (ch === "\\") {
816
+ i += 2;
817
+ continue;
818
+ }
819
+ if (ch === ":") {
820
+ i++;
821
+ while (i < pattern.length && /\w/.test(pattern[i])) i++;
822
+ if (i < pattern.length && pattern[i] === "(") {
823
+ let depth = 1;
824
+ i++;
825
+ while (i < pattern.length && depth > 0) {
826
+ if (pattern[i] === "\\") {
827
+ i += 2;
828
+ continue;
829
+ }
830
+ if (pattern[i] === "(") depth++;
831
+ else if (pattern[i] === ")") depth--;
832
+ i++;
833
+ }
834
+ }
835
+ continue;
836
+ }
837
+ if (ch === "*" || ch === "?") return true;
838
+ i++;
839
+ }
840
+ return false;
841
+ }
842
+ function compileGlob(glob) {
843
+ let src = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&");
844
+ src = src.replace(/\*\*/g, "\0GLOBSTAR\0").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]").replace(/\x00GLOBSTAR\x00/g, ".*");
845
+ return new RegExp("^" + src);
846
+ }
847
+ function extractInlinePattern(str, openIdx) {
848
+ let depth = 0;
849
+ let i = openIdx;
850
+ for (; i < str.length; i++) {
851
+ if (str[i] === "\\") {
852
+ i++;
853
+ continue;
854
+ }
855
+ if (str[i] === "(") {
856
+ depth++;
857
+ continue;
858
+ }
859
+ if (str[i] === ")") {
860
+ depth--;
861
+ if (depth === 0) break;
862
+ }
863
+ }
864
+ if (depth !== 0)
865
+ throw new SyntaxError(`Unbalanced parentheses in route segment '${str}'`);
866
+ return { pattern: str.slice(openIdx + 1, i), closeIdx: i };
867
+ }
868
+ function compilePlainPath(path2) {
869
+ const segments = path2.split("/").filter((s) => s.length > 0);
870
+ const src = segments.map((seg) => {
871
+ if (!seg.startsWith(":"))
872
+ return seg.replace(/[.+^${}()|[\]\\]/g, "\\$&");
873
+ const parenIdx = seg.indexOf("(", 1);
874
+ if (parenIdx === -1) {
875
+ return `(?<${seg.slice(1)}>[^/]+)`;
876
+ }
877
+ const name = seg.slice(1, parenIdx);
878
+ if (!name)
879
+ throw new SyntaxError(`Route parameter missing name before '(' in segment '${seg}'`);
880
+ const { pattern, closeIdx } = extractInlinePattern(seg, parenIdx);
881
+ if (/\(\?<[^>]+>/.test(pattern))
882
+ throw new SyntaxError(
883
+ `Inline constraint for ':${name}' must not contain named capture groups.`
884
+ );
885
+ const suffix = seg.slice(closeIdx + 1);
886
+ const escapedSuffix = suffix.replace(/[.+^${}()|[\]\\]/g, "\\$&");
887
+ return `(?<${name}>${pattern})${escapedSuffix}`;
888
+ }).join("/");
889
+ try {
890
+ return new RegExp("^/?" + src + "(?=/|$)");
891
+ } catch (e) {
892
+ throw new SyntaxError(
893
+ `Invalid inline regex constraint in path '${path2}': ${e.message}`
894
+ );
895
+ }
896
+ }
897
+ function compilePattern(path2) {
898
+ if (path2 instanceof RegExp) return path2;
899
+ if (isGlobPattern(path2)) return compileGlob(path2);
900
+ return compilePlainPath(path2);
901
+ }
902
+ function buildRouteLayer(method, path2, middleware, stripPath) {
903
+ if (typeof middleware !== "function")
904
+ throw new TypeError("Incorrect middleware type: expected a function");
905
+ return { method, path: path2, regex: compilePattern(path2), stripPath, middleware };
906
+ }
907
+ function matchRouteLayer(layer, req, path2) {
908
+ if (layer.method && layer.method !== req.method) return false;
909
+ const m = layer.regex.exec(path2);
910
+ if (m === null) return false;
911
+ const captured = m.groups ?? {};
912
+ if (layer.stripPath) {
913
+ req.path = path2.slice(m[0].length) || "/";
914
+ }
915
+ req.queries.route = captured;
916
+ Object.assign(req.params, captured);
917
+ return true;
918
+ }
919
+ function decodeJsonCookie(raw) {
920
+ if (!raw.startsWith("j:")) return raw;
921
+ try {
922
+ return JSON.parse(raw.slice(2));
923
+ } catch {
924
+ return raw;
925
+ }
926
+ }
927
+ function signCookieValue(value, secret) {
928
+ const sig = crypto.createHmac("sha256", secret).update(value).digest("base64url");
929
+ return `s:${value}.${sig}`;
930
+ }
931
+ function verifyCookieValue(signed, secret) {
932
+ if (!signed.startsWith("s:")) return false;
933
+ const withoutPrefix = signed.slice(2);
934
+ const lastDot = withoutPrefix.lastIndexOf(".");
935
+ if (lastDot === -1) return false;
936
+ const value = withoutPrefix.slice(0, lastDot);
937
+ const received = withoutPrefix.slice(lastDot + 1);
938
+ const expected = crypto.createHmac("sha256", secret).update(value).digest("base64url");
939
+ const receivedBuf = Buffer.from(received, "base64url");
940
+ const expectedBuf = Buffer.from(expected, "base64url");
941
+ if (receivedBuf.length !== expectedBuf.length) return false;
942
+ if (!crypto.timingSafeEqual(receivedBuf, expectedBuf)) return false;
943
+ return value;
944
+ }
945
+ function updateHttpObjects(req, res, secret, trustProxy) {
946
+ const rReq = req;
947
+ const rRes = res;
948
+ if (rReq.queries) return;
949
+ rReq.queries = {};
950
+ if (trustProxy) {
951
+ const xff = req.headers["x-forwarded-for"];
952
+ const first = Array.isArray(xff) ? xff[0] : xff;
953
+ rReq.ip = (first ? first.split(",")[0].trim() : req.socket?.remoteAddress) ?? "";
954
+ } else {
955
+ rReq.ip = req.socket?.remoteAddress ?? "";
956
+ }
957
+ const qry = new URL(`http://${req.headers.host}${req.url}`);
958
+ rReq.originalUrl = req.url;
959
+ rReq.path = qry.pathname;
960
+ const urlParams = {};
961
+ for (const [key, value] of qry.searchParams.entries()) {
962
+ const existing = urlParams[key];
963
+ if (existing === void 0) {
964
+ urlParams[key] = value;
965
+ } else if (Array.isArray(existing)) {
966
+ existing.push(value);
967
+ } else {
968
+ urlParams[key] = [existing, value];
969
+ }
970
+ }
971
+ rReq.queries.url = urlParams;
972
+ const flatParams = {};
973
+ for (const [key, value] of Object.entries(urlParams)) {
974
+ flatParams[key] = Array.isArray(value) ? value[0] : value;
975
+ }
976
+ rReq.params = flatParams;
977
+ if (rReq.cookies == null) {
978
+ rReq.cookies = {};
979
+ if (req.headers.cookie) {
980
+ for (const part of req.headers.cookie.split(";")) {
981
+ const eqIdx = part.indexOf("=");
982
+ if (eqIdx === -1) continue;
983
+ const name = part.slice(0, eqIdx).trim();
984
+ const rawVal = part.slice(eqIdx + 1).trim();
985
+ if (rawVal.startsWith("s:")) {
986
+ if (secret) {
987
+ const inner = verifyCookieValue(rawVal, secret);
988
+ if (inner === false) continue;
989
+ rReq.cookies[name] = decodeJsonCookie(inner);
990
+ } else {
991
+ rReq.cookies[name] = rawVal;
992
+ }
993
+ } else {
994
+ rReq.cookies[name] = decodeJsonCookie(rawVal);
995
+ }
996
+ }
997
+ }
998
+ }
999
+ const resolvedReqOpts = (opts) => ({
1000
+ limit: opts?.limit ?? "100kb",
1001
+ inflate: opts?.inflate ?? true,
1002
+ reviver: null,
1003
+ strict: opts?.strict ?? false
1004
+ });
1005
+ rReq.json = (opts) => {
1006
+ if ("body" in rReq) return Promise.resolve(rReq.body ?? null);
1007
+ return readReqBody(rReq, resolvedReqOpts(opts), "application/json").then((ret) => {
1008
+ if (ret == null) return null;
1009
+ const charset = extractCharset(ret.mimetype);
1010
+ try {
1011
+ const parsed = JSON.parse(
1012
+ ret.content.toString(charset),
1013
+ opts?.reviver ?? void 0
1014
+ );
1015
+ rReq.body = parsed;
1016
+ return parsed;
1017
+ } catch (ex) {
1018
+ return Promise.reject({ status: 400, message: "Bad Request: " + ex.message });
1019
+ }
1020
+ });
1021
+ };
1022
+ rReq.text = (opts) => {
1023
+ const cached = rReq.body;
1024
+ if (typeof cached === "string") return Promise.resolve(cached);
1025
+ return readReqBody(rReq, resolvedReqOpts(opts), null).then((ret) => {
1026
+ if (ret == null) return null;
1027
+ const charset = extractCharset(ret.mimetype);
1028
+ return ret.content.toString(charset);
1029
+ });
1030
+ };
1031
+ rReq.formData = (opts) => {
1032
+ const cached = rReq.body;
1033
+ if (Array.isArray(cached)) return Promise.resolve(cached);
1034
+ return readReqBody(rReq, resolvedReqOpts(opts), "multipart/form-data").then((ret) => {
1035
+ if (ret == null) return null;
1036
+ try {
1037
+ const parts = parseMultipartBody(ret.mimetype, ret.content);
1038
+ rReq.body = parts;
1039
+ return parts;
1040
+ } catch (ex) {
1041
+ return Promise.reject({ status: ex.status ?? 500, message: ex.message ?? String(ex) });
1042
+ }
1043
+ });
1044
+ };
1045
+ rRes.setHeader("X-Powered-By", "Expediate");
1046
+ rRes.send = (data) => {
1047
+ if (data) res.write(data);
1048
+ res.end();
1049
+ };
1050
+ rRes.json = (data) => {
1051
+ res.setHeader("Content-Type", "application/json");
1052
+ res.write(JSON.stringify(data));
1053
+ res.end();
1054
+ };
1055
+ rRes.status = (code, headers) => {
1056
+ res.statusCode = code;
1057
+ if (headers)
1058
+ for (const [k, v] of Object.entries(headers)) res.setHeader(k, v);
1059
+ return rRes;
1060
+ };
1061
+ rRes.redirect = (url) => {
1062
+ res.setHeader("location", url);
1063
+ res.writeHead(302);
1064
+ res.write(`Found. Redirecting to ${url}`);
1065
+ res.end();
1066
+ };
1067
+ rRes.cookie = (name, value, options) => {
1068
+ const opts = options ?? {};
1069
+ let val = typeof value === "object" ? "j:" + JSON.stringify(value) : String(value);
1070
+ if (opts.signed) {
1071
+ if (!secret)
1072
+ throw new Error(
1073
+ "Signed cookies require a secret \u2014 pass { secret } to createRouter()"
1074
+ );
1075
+ val = signCookieValue(val, secret);
1076
+ }
1077
+ let txt = `${name}=${val}`;
1078
+ if (opts.maxAge != null) {
1079
+ const maxAgeMs = opts.maxAge;
1080
+ const maxAgeSec = Math.floor(maxAgeMs / 1e3);
1081
+ opts.expires = new Date(Date.now() + maxAgeMs);
1082
+ txt += `; Max-Age=${maxAgeSec}`;
1083
+ }
1084
+ if (opts.expires) txt += `; Expires=${opts.expires.toUTCString()}`;
1085
+ txt += `; Path=${opts.path ?? "/"}`;
1086
+ if (opts.httpOnly) txt += "; HttpOnly";
1087
+ if (opts.secure) txt += "; Secure";
1088
+ if (opts.sameSite) txt += `; SameSite=${opts.sameSite}`;
1089
+ const existing = res.getHeader("Set-Cookie");
1090
+ if (existing == null) {
1091
+ res.setHeader("Set-Cookie", txt);
1092
+ } else if (Array.isArray(existing)) {
1093
+ res.setHeader("Set-Cookie", [...existing, txt]);
1094
+ } else {
1095
+ res.setHeader("Set-Cookie", [existing, txt]);
1096
+ }
1097
+ return rRes;
1098
+ };
1099
+ rRes.download = (filepath, filename) => {
1100
+ const name = filename ?? path.basename(filepath);
1101
+ const safeName = name.replace(/"/g, '\\"');
1102
+ res.setHeader("Content-Disposition", `attachment; filename="${safeName}"`);
1103
+ fs2.access(filepath, fs2.constants.F_OK, (err) => {
1104
+ if (err) {
1105
+ if (!rRes.writableEnded) rRes.status(404).end("Not Found");
1106
+ return;
1107
+ }
1108
+ serveFile(filepath)(rReq, rRes, () => {
1109
+ });
1110
+ });
1111
+ };
1112
+ rRes.type = (mime2) => {
1113
+ res.setHeader("Content-Type", mime2);
1114
+ return rRes;
1115
+ };
1116
+ rRes.etag = (value, strong = false) => {
1117
+ res.setHeader("ETag", strong ? `"${value}"` : `W/"${value}"`);
1118
+ return rRes;
1119
+ };
1120
+ }
1121
+ function pathMatchesLayer(layer, path2) {
1122
+ return layer.regex.test(path2);
1123
+ }
1124
+ function registerRoute(routes, method, path2, arg, stripPath) {
1125
+ if (Array.isArray(arg)) {
1126
+ for (const item of arg) registerRoute(routes, method, path2, item, stripPath);
1127
+ } else if (typeof arg === "function") {
1128
+ routes.push(buildRouteLayer(method, path2, arg, stripPath));
1129
+ } else if (arg && typeof arg.listener === "function") {
1130
+ routes.push(buildRouteLayer(method, path2, arg.listener, stripPath));
1131
+ } else {
1132
+ throw new TypeError(
1133
+ "Unexpected value registered as middleware: expected a Middleware function, a Router instance, or an array of either"
1134
+ );
1135
+ }
1136
+ }
1137
+ function extractRouterPrefix(arg) {
1138
+ if (Array.isArray(arg) || typeof arg === "function") return void 0;
1139
+ return arg.prefix;
1140
+ }
1141
+ function createRouter(prefixOrOpts, opts) {
1142
+ const routerPrefix = typeof prefixOrOpts === "string" ? prefixOrOpts : void 0;
1143
+ const options = typeof prefixOrOpts === "object" ? prefixOrOpts : opts ?? {};
1144
+ const secret = options.secret;
1145
+ const timeoutMs = options.timeout;
1146
+ const trustProxy = options.trustProxy ?? false;
1147
+ const routes = [];
1148
+ let errorHandler;
1149
+ let notFoundHandler;
1150
+ let activeServer = null;
1151
+ const activeSockets = /* @__PURE__ */ new Set();
1152
+ const listener = (req, res, done) => {
1153
+ const method = req.method;
1154
+ const url = req.url;
1155
+ let idx = 0;
1156
+ updateHttpObjects(req, res, secret, trustProxy);
1157
+ if (timeoutMs) {
1158
+ if (req.socket) req.socket.setTimeout(timeoutMs);
1159
+ const timer = setTimeout(() => {
1160
+ if (!res.writableEnded) res.status(408).end("Request Timeout");
1161
+ }, timeoutMs);
1162
+ res.once("finish", () => {
1163
+ clearTimeout(timer);
1164
+ if (req.socket) req.socket.setTimeout(0);
1165
+ });
1166
+ }
1167
+ const invokeErrorHandler = (e) => {
1168
+ if (res.writableEnded) return;
1169
+ if (errorHandler) {
1170
+ try {
1171
+ errorHandler(e, req, res);
1172
+ } catch (e2) {
1173
+ if (!res.writableEnded) res.status(500).end(`Error ${method} ${url}`);
1174
+ }
1175
+ } else {
1176
+ console.warn(e);
1177
+ res.status(500).end(`Error ${method} ${url}`);
1178
+ }
1179
+ };
1180
+ const invoke = (mw, nextFn) => {
1181
+ try {
1182
+ const ret = mw(req, res, nextFn);
1183
+ if (ret instanceof Promise) ret.catch(invokeErrorHandler);
1184
+ } catch (e) {
1185
+ invokeErrorHandler(e);
1186
+ }
1187
+ };
1188
+ const allowedMethods = /* @__PURE__ */ new Set();
1189
+ const next = (err) => {
1190
+ if (err != null) {
1191
+ invokeErrorHandler(err);
1192
+ return;
1193
+ }
1194
+ while (idx < routes.length) {
1195
+ const layer = routes[idx++];
1196
+ const pathBefore = req.path;
1197
+ if (matchRouteLayer(layer, req, req.path)) {
1198
+ if (layer.stripPath) {
1199
+ invoke(layer.middleware, () => {
1200
+ req.path = pathBefore;
1201
+ next();
1202
+ });
1203
+ return;
1204
+ }
1205
+ invoke(layer.middleware, next);
1206
+ return;
1207
+ }
1208
+ if (layer.method !== null && pathMatchesLayer(layer, pathBefore)) {
1209
+ allowedMethods.add(layer.method);
1210
+ }
1211
+ }
1212
+ if (allowedMethods.size > 0) {
1213
+ const allow = [...allowedMethods].sort().join(", ");
1214
+ res.status(405, { Allow: allow }).end(`Cannot ${method} ${url}`);
1215
+ return;
1216
+ }
1217
+ if (done) return done();
1218
+ if (notFoundHandler) {
1219
+ try {
1220
+ notFoundHandler(req, res, () => {
1221
+ });
1222
+ } catch (e) {
1223
+ invokeErrorHandler(e);
1224
+ }
1225
+ return;
1226
+ }
1227
+ res.status(404).end(`Cannot ${method} ${url}`);
1228
+ };
1229
+ try {
1230
+ next();
1231
+ } catch (e) {
1232
+ invokeErrorHandler(e);
1233
+ }
1234
+ };
1235
+ function makeRegister(method, stripPath) {
1236
+ return (path2, ...args) => {
1237
+ for (const arg of args) registerRoute(routes, method, path2, arg, stripPath);
1238
+ };
1239
+ }
1240
+ const use = (pathOrFirst, ...args) => {
1241
+ if (typeof pathOrFirst === "string" || pathOrFirst instanceof RegExp) {
1242
+ for (const arg of args) registerRoute(routes, null, pathOrFirst, arg, true);
1243
+ } else {
1244
+ const inferredPath = extractRouterPrefix(pathOrFirst) ?? "/";
1245
+ registerRoute(routes, null, inferredPath, pathOrFirst, true);
1246
+ for (const arg of args) registerRoute(routes, null, "/", arg, true);
1247
+ }
1248
+ };
1249
+ const router = {
1250
+ prefix: routerPrefix,
1251
+ listener,
1252
+ use,
1253
+ all: makeRegister(null, false),
1254
+ get: makeRegister("GET", false),
1255
+ put: makeRegister("PUT", false),
1256
+ post: makeRegister("POST", false),
1257
+ delete: makeRegister("DELETE", false),
1258
+ patch: makeRegister("PATCH", false),
1259
+ // ── onError ─────────────────────────────────────────────────────────────
1260
+ onError(handler) {
1261
+ errorHandler = handler;
1262
+ },
1263
+ // ── setNotFound ──────────────────────────────────────────────────────────
1264
+ setNotFound(handler) {
1265
+ notFoundHandler = handler;
1266
+ },
1267
+ // ── routes ───────────────────────────────────────────────────────────────
1268
+ routes() {
1269
+ return routes.map((l) => ({
1270
+ method: l.method,
1271
+ path: l.path,
1272
+ stripPath: l.stripPath
1273
+ }));
1274
+ },
1275
+ // ── shutdown ─────────────────────────────────────────────────────────────
1276
+ shutdown(timeout = 5e3) {
1277
+ if (!activeServer) return Promise.resolve();
1278
+ return new Promise((resolve, reject) => {
1279
+ activeServer.close((err) => {
1280
+ if (err) reject(err);
1281
+ else resolve();
1282
+ });
1283
+ if (timeout > 0) {
1284
+ setTimeout(() => {
1285
+ for (const socket of activeSockets) socket.destroy();
1286
+ activeSockets.clear();
1287
+ }, timeout);
1288
+ }
1289
+ });
1290
+ },
1291
+ // ── listen ───────────────────────────────────────────────────────────────
1292
+ listen(port, opts2, cb) {
1293
+ if (typeof opts2 === "function") {
1294
+ cb = opts2;
1295
+ opts2 = void 0;
1296
+ }
1297
+ const rawListener = listener;
1298
+ let server;
1299
+ const tlsOpts = opts2;
1300
+ if (tlsOpts?.key && tlsOpts?.cert) {
1301
+ if (tlsOpts.http2) {
1302
+ server = http2.createSecureServer(tlsOpts, rawListener);
1303
+ } else {
1304
+ server = https.createServer(tlsOpts, rawListener);
1305
+ }
1306
+ } else {
1307
+ server = http.createServer(rawListener);
1308
+ }
1309
+ server.on("connection", (socket) => {
1310
+ activeSockets.add(socket);
1311
+ socket.once("close", () => activeSockets.delete(socket));
1312
+ });
1313
+ activeServer = server;
1314
+ server.listen(port, cb);
1315
+ return server;
1316
+ }
1317
+ };
1318
+ return router;
1319
+ }
1320
+ var router_default = createRouter;
1321
+
1322
+ // src/jwt-auth.ts
1323
+ var import_crypto = __toESM(require("crypto"), 1);
1324
+ function hashPassword(password) {
1325
+ return import_crypto.default.createHash("sha256").update(password).digest("hex");
1326
+ }
1327
+ var userDatabase = /* @__PURE__ */ new Map([
1328
+ ["alice", {
1329
+ id: "usr_001",
1330
+ username: "alice",
1331
+ passwordHash: hashPassword("password123"),
1332
+ roles: ["admin", "editor"],
1333
+ permissions: ["read", "write", "delete", "manage_users"]
1334
+ }],
1335
+ ["bob", {
1336
+ id: "usr_002",
1337
+ username: "bob",
1338
+ passwordHash: hashPassword("secret456"),
1339
+ roles: ["editor"],
1340
+ permissions: ["read", "write"]
1341
+ }],
1342
+ ["charlie", {
1343
+ id: "usr_003",
1344
+ username: "charlie",
1345
+ passwordHash: hashPassword("pass789"),
1346
+ roles: ["viewer"],
1347
+ permissions: ["read"]
1348
+ }]
1349
+ ]);
1350
+ function createMapTokenStore() {
1351
+ const map = /* @__PURE__ */ new Map();
1352
+ return {
1353
+ set(jti, record) {
1354
+ map.set(jti, record);
1355
+ },
1356
+ get(jti) {
1357
+ const record = map.get(jti);
1358
+ if (!record) return void 0;
1359
+ if (Date.now() > record.expiresAt) {
1360
+ map.delete(jti);
1361
+ return void 0;
1362
+ }
1363
+ return record;
1364
+ },
1365
+ delete(jti) {
1366
+ map.delete(jti);
1367
+ },
1368
+ deleteBySubject(sub) {
1369
+ for (const [jti, record] of map) {
1370
+ if (record.sub === sub) map.delete(jti);
1371
+ }
1372
+ }
1373
+ };
1374
+ }
1375
+ var DEFAULT_CONFIG = {
1376
+ accessTokenSecret: "access-secret-change-in-production",
1377
+ refreshTokenSecret: "refresh-secret-change-in-production",
1378
+ accessTokenExpiry: 15 * 60,
1379
+ // 15 minutes
1380
+ refreshTokenExpiry: 7 * 24 * 3600,
1381
+ // 7 days
1382
+ issuer: "jwt-auth",
1383
+ checkIssuer: false,
1384
+ alg: "HS256",
1385
+ username: (user) => user.username,
1386
+ fetchUser: (sub) => userDatabase.get(sub),
1387
+ isPasswordValid: (user, password) => user.passwordHash === hashPassword(password),
1388
+ payload: (user) => ({
1389
+ sub: user.username,
1390
+ roles: user.roles,
1391
+ permissions: user.permissions
1392
+ })
1393
+ };
1394
+ function isHmac(alg) {
1395
+ return alg[0] === "H";
1396
+ }
1397
+ function isEc(alg) {
1398
+ return alg[0] === "E";
1399
+ }
1400
+ function nodeHashAlg(alg) {
1401
+ return `SHA${alg.slice(2)}`;
1402
+ }
1403
+ function ecByteLength(alg) {
1404
+ if (alg === "ES256") return 32;
1405
+ if (alg === "ES384") return 48;
1406
+ if (alg === "ES512") return 66;
1407
+ throw new Error(`Not an EC algorithm: ${alg}`);
1408
+ }
1409
+ function encodeDerLength(len) {
1410
+ if (len < 128) return Buffer.from([len]);
1411
+ if (len < 256) return Buffer.from([129, len]);
1412
+ return Buffer.from([130, len >> 8, len & 255]);
1413
+ }
1414
+ function derToJose(der, alg) {
1415
+ const n = ecByteLength(alg);
1416
+ let i = 0;
1417
+ if (der[i++] !== 48) throw new Error("ECDSA DER: expected SEQUENCE tag 0x30");
1418
+ if (der[i] & 128) i += (der[i] & 127) + 1;
1419
+ else i++;
1420
+ function readInt() {
1421
+ if (der[i++] !== 2) throw new Error("ECDSA DER: expected INTEGER tag 0x02");
1422
+ let len = der[i++];
1423
+ if (len & 128) {
1424
+ const nb = len & 127;
1425
+ len = 0;
1426
+ for (let k = 0; k < nb; k++) len = len << 8 | der[i++];
1427
+ }
1428
+ if (der[i] === 0 && len > 1) {
1429
+ i++;
1430
+ len--;
1431
+ }
1432
+ const val = der.slice(i, i + len);
1433
+ i += len;
1434
+ return val;
1435
+ }
1436
+ const r = readInt();
1437
+ const s = readInt();
1438
+ const out = Buffer.alloc(2 * n, 0);
1439
+ r.copy(out, n - r.length);
1440
+ s.copy(out, 2 * n - s.length);
1441
+ return out;
1442
+ }
1443
+ function joseToDer(jose, alg) {
1444
+ const n = ecByteLength(alg);
1445
+ const r = jose.slice(0, n);
1446
+ const s = jose.slice(n);
1447
+ function encodeInt(buf) {
1448
+ let start = 0;
1449
+ while (start < buf.length - 1 && buf[start] === 0) start++;
1450
+ const trimmed = buf.slice(start);
1451
+ const padded = trimmed[0] & 128 ? Buffer.concat([Buffer.from([0]), trimmed]) : trimmed;
1452
+ return Buffer.concat([Buffer.from([2, padded.length]), padded]);
1453
+ }
1454
+ const rDer = encodeInt(r);
1455
+ const sDer = encodeInt(s);
1456
+ const content = Buffer.concat([rDer, sDer]);
1457
+ const lenBuf = encodeDerLength(content.length);
1458
+ return Buffer.concat([Buffer.from([48]), lenBuf, content]);
1459
+ }
1460
+ function base64UrlEncode(data) {
1461
+ return Buffer.from(JSON.stringify(data)).toString("base64").replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
1462
+ }
1463
+ function base64UrlDecode(str) {
1464
+ const padded = str + "=".repeat((4 - str.length % 4) % 4);
1465
+ return JSON.parse(
1466
+ Buffer.from(padded.replace(/-/g, "+").replace(/_/g, "/"), "base64").toString("utf8")
1467
+ );
1468
+ }
1469
+ function computeSignature(encodedHeader, encodedPayload, key, alg) {
1470
+ const signingInput = `${encodedHeader}.${encodedPayload}`;
1471
+ if (isHmac(alg)) {
1472
+ return import_crypto.default.createHmac(`sha${alg.slice(2)}`, key).update(signingInput).digest("base64").replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
1473
+ }
1474
+ const hashAlg = nodeHashAlg(alg);
1475
+ const derOrRaw = import_crypto.default.sign(hashAlg, Buffer.from(signingInput), key);
1476
+ const sigBytes = isEc(alg) ? derToJose(derOrRaw, alg) : derOrRaw;
1477
+ return sigBytes.toString("base64").replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
1478
+ }
1479
+ function checkSignature(encodedHeader, encodedPayload, b64sig, key, alg) {
1480
+ if (isHmac(alg)) {
1481
+ const expected = computeSignature(encodedHeader, encodedPayload, key, alg);
1482
+ const sigBuf = Buffer.from(b64sig);
1483
+ const expectedBuf = Buffer.from(expected);
1484
+ return sigBuf.length === expectedBuf.length && import_crypto.default.timingSafeEqual(sigBuf, expectedBuf);
1485
+ }
1486
+ try {
1487
+ const hashAlg = nodeHashAlg(alg);
1488
+ const rawSig = Buffer.from(b64sig.replace(/-/g, "+").replace(/_/g, "/"), "base64");
1489
+ const verBuf = isEc(alg) ? joseToDer(rawSig, alg) : rawSig;
1490
+ const input = Buffer.from(`${encodedHeader}.${encodedPayload}`);
1491
+ return import_crypto.default.verify(hashAlg, input, key, verBuf);
1492
+ } catch {
1493
+ return false;
1494
+ }
1495
+ }
1496
+ function signToken(payload, key, expiresIn, alg = "HS256") {
1497
+ const now = Math.floor(Date.now() / 1e3);
1498
+ const encodedHeader = base64UrlEncode({ alg, typ: "JWT" });
1499
+ const fullPayload = base64UrlEncode({ ...payload, iat: now, exp: now + expiresIn });
1500
+ const signature = computeSignature(encodedHeader, fullPayload, key, alg);
1501
+ return `${encodedHeader}.${fullPayload}.${signature}`;
1502
+ }
1503
+ function verifyToken(token, key, alg) {
1504
+ try {
1505
+ const parts = token.split(".");
1506
+ if (parts.length !== 3)
1507
+ return { valid: false, error: "Invalid token format" };
1508
+ const [encodedHeader, encodedPayload, signature] = parts;
1509
+ const decodedHeader = base64UrlDecode(encodedHeader);
1510
+ if (decodedHeader.alg !== alg)
1511
+ return { valid: false, error: "Unauthorised signing algorithm" };
1512
+ if (!checkSignature(encodedHeader, encodedPayload, signature, key, alg))
1513
+ return { valid: false, error: "Invalid signature" };
1514
+ const payload = base64UrlDecode(encodedPayload);
1515
+ const now = Math.floor(Date.now() / 1e3);
1516
+ if (payload.exp && payload.exp < now)
1517
+ return { valid: false, error: "Token expired" };
1518
+ return { valid: true, payload };
1519
+ } catch {
1520
+ return { valid: false, error: "Malformed token" };
1521
+ }
1522
+ }
1523
+ function accessSignKey(cfg) {
1524
+ return isHmac(cfg.alg) ? cfg.accessTokenSecret : cfg.accessTokenPrivateKey;
1525
+ }
1526
+ function accessVerifyKey(cfg) {
1527
+ return isHmac(cfg.alg) ? cfg.accessTokenSecret : cfg.accessTokenPublicKey;
1528
+ }
1529
+ function refreshSignKey(cfg) {
1530
+ return isHmac(cfg.alg) ? cfg.refreshTokenSecret : cfg.refreshTokenPrivateKey ?? cfg.accessTokenPrivateKey;
1531
+ }
1532
+ function refreshVerifyKey(cfg) {
1533
+ return isHmac(cfg.alg) ? cfg.refreshTokenSecret : cfg.refreshTokenPublicKey ?? cfg.accessTokenPublicKey;
1534
+ }
1535
+ async function authenticateUser(username, password, config) {
1536
+ const user = await config.fetchUser(username);
1537
+ if (!user) return { success: false, error: "User not found" };
1538
+ if (!await config.isPasswordValid(user, password))
1539
+ return { success: false, error: "Incorrect password" };
1540
+ return issueTokenPair(user, config);
1541
+ }
1542
+ async function issueTokenPair(user, config) {
1543
+ const claims = config.payload(user);
1544
+ const fullClaims = {
1545
+ sub: config.username(user),
1546
+ // fallback subject — overridden by payload() if it sets sub
1547
+ ...claims,
1548
+ iss: config.issuer
1549
+ };
1550
+ const accessToken = signToken(fullClaims, accessSignKey(config), config.accessTokenExpiry, config.alg);
1551
+ if (!config.refreshTokenStore) {
1552
+ return { success: true, accessToken, expiresIn: config.accessTokenExpiry, tokenType: "Bearer" };
1553
+ }
1554
+ const jti = import_crypto.default.randomUUID();
1555
+ const sub = fullClaims.sub;
1556
+ const refreshClaims = {
1557
+ sub,
1558
+ jti,
1559
+ type: "refresh",
1560
+ iss: config.issuer
1561
+ };
1562
+ const refreshToken = signToken(refreshClaims, refreshSignKey(config), config.refreshTokenExpiry, config.alg);
1563
+ await config.refreshTokenStore.set(jti, {
1564
+ sub,
1565
+ issuedAt: Date.now(),
1566
+ expiresAt: Date.now() + config.refreshTokenExpiry * 1e3
1567
+ });
1568
+ return {
1569
+ success: true,
1570
+ accessToken,
1571
+ refreshToken,
1572
+ expiresIn: config.accessTokenExpiry,
1573
+ tokenType: "Bearer"
1574
+ };
1575
+ }
1576
+ async function renewAccessToken(refreshToken, config) {
1577
+ if (!config.refreshTokenStore)
1578
+ return { success: false, error: "Refresh tokens are not configured" };
1579
+ const result = verifyToken(refreshToken, refreshVerifyKey(config), config.alg);
1580
+ if (!result.valid)
1581
+ return { success: false, error: "Invalid or revoked refresh token" };
1582
+ const refreshPayload = result.payload;
1583
+ if (refreshPayload.type !== "refresh")
1584
+ return { success: false, error: "Invalid token type" };
1585
+ const jti = refreshPayload.jti;
1586
+ if (!jti)
1587
+ return { success: false, error: "Invalid refresh token: missing jti" };
1588
+ const record = await config.refreshTokenStore.get(jti);
1589
+ if (!record)
1590
+ return { success: false, error: "Invalid or revoked refresh token" };
1591
+ const user = await config.fetchUser(record.sub);
1592
+ if (!user) {
1593
+ await config.refreshTokenStore.delete(jti);
1594
+ return { success: false, error: "User not found" };
1595
+ }
1596
+ await config.refreshTokenStore.delete(jti);
1597
+ return issueTokenPair(user, config);
1598
+ }
1599
+ async function revokeRefreshToken(refreshToken, config) {
1600
+ if (!config.refreshTokenStore) return;
1601
+ const result = verifyToken(refreshToken, refreshVerifyKey(config), config.alg);
1602
+ if (!result.valid) return;
1603
+ const jti = result.payload.jti;
1604
+ if (jti) await config.refreshTokenStore.delete(jti);
1605
+ }
1606
+ function createJwtPlugin(userConfig = {}) {
1607
+ const config = { ...DEFAULT_CONFIG, ...userConfig };
1608
+ if (!isHmac(config.alg)) {
1609
+ if (!config.accessTokenPrivateKey || !config.accessTokenPublicKey) {
1610
+ throw new Error(
1611
+ `Algorithm '${config.alg}' requires both 'accessTokenPrivateKey' and 'accessTokenPublicKey' in JwtConfig.`
1612
+ );
1613
+ }
1614
+ }
1615
+ function sendJson2(res, status, data) {
1616
+ const body = JSON.stringify(data);
1617
+ res.setHeader("Content-Type", "application/json; charset=utf-8");
1618
+ res.status(status).send(body);
1619
+ }
1620
+ const login = (req, res) => {
1621
+ (async () => {
1622
+ const { username, password } = req.body ?? {};
1623
+ if (!username || !password) {
1624
+ sendJson2(res, 400, { error: "Fields 'username' and 'password' are required" });
1625
+ return;
1626
+ }
1627
+ const result = await authenticateUser(username, password, config);
1628
+ if (!result.success) {
1629
+ sendJson2(res, 401, { error: result.error });
1630
+ return;
1631
+ }
1632
+ const body = {
1633
+ message: "Authentication successful",
1634
+ accessToken: result.accessToken,
1635
+ expiresIn: result.expiresIn,
1636
+ tokenType: result.tokenType
1637
+ };
1638
+ if (result.refreshToken !== void 0) body.refreshToken = result.refreshToken;
1639
+ sendJson2(res, 200, body);
1640
+ })().catch(() => sendJson2(res, 500, { error: "Internal server error" }));
1641
+ };
1642
+ const refresh = (req, res) => {
1643
+ (async () => {
1644
+ if (!config.refreshTokenStore) {
1645
+ sendJson2(res, 501, { error: "Refresh tokens are not configured" });
1646
+ return;
1647
+ }
1648
+ const { refreshToken } = req.body ?? {};
1649
+ if (!refreshToken) {
1650
+ sendJson2(res, 400, { error: "Field 'refreshToken' is required" });
1651
+ return;
1652
+ }
1653
+ const result = await renewAccessToken(refreshToken, config);
1654
+ if (!result.success) {
1655
+ sendJson2(res, 401, { error: result.error });
1656
+ return;
1657
+ }
1658
+ sendJson2(res, 200, {
1659
+ message: "Token renewed successfully",
1660
+ accessToken: result.accessToken,
1661
+ refreshToken: result.refreshToken,
1662
+ expiresIn: result.expiresIn,
1663
+ tokenType: result.tokenType
1664
+ });
1665
+ })().catch(() => sendJson2(res, 500, { error: "Internal server error" }));
1666
+ };
1667
+ const logout = (req, res) => {
1668
+ (async () => {
1669
+ const { refreshToken } = req.body ?? {};
1670
+ if (refreshToken && config.refreshTokenStore) {
1671
+ await revokeRefreshToken(refreshToken, config);
1672
+ }
1673
+ sendJson2(res, 200, { message: "Logged out successfully" });
1674
+ })().catch(() => sendJson2(res, 500, { error: "Internal server error" }));
1675
+ };
1676
+ const authenticate = (req, res, next) => {
1677
+ delete req.user;
1678
+ const authHeader = req.headers["authorization"];
1679
+ if (!authHeader?.startsWith("Bearer ")) return next();
1680
+ const token = authHeader.slice(7);
1681
+ const result = verifyToken(token, accessVerifyKey(config), config.alg);
1682
+ if (!result.valid) return next();
1683
+ if (config.checkIssuer && result.payload.iss !== config.issuer) return next();
1684
+ req.user = result.payload;
1685
+ next();
1686
+ };
1687
+ const authorize = (req, res, next) => {
1688
+ if (!req.user) {
1689
+ sendJson2(res, 401, { error: "Authentication required" });
1690
+ return;
1691
+ }
1692
+ next();
1693
+ };
1694
+ function requireRole(...roles) {
1695
+ return [
1696
+ authenticate,
1697
+ (req, res, next) => {
1698
+ const user = req.user;
1699
+ if (!user) {
1700
+ sendJson2(res, 401, { error: "Authentication required" });
1701
+ return;
1702
+ }
1703
+ const userRoles = user.roles ?? [];
1704
+ if (!roles.some((r) => userRoles.includes(r))) {
1705
+ sendJson2(res, 403, {
1706
+ error: `Access denied. Required role(s): ${roles.join(", ")}`,
1707
+ yourRoles: userRoles
1708
+ });
1709
+ return;
1710
+ }
1711
+ next();
1712
+ }
1713
+ ];
1714
+ }
1715
+ function requirePermission(...permissions) {
1716
+ return [
1717
+ authenticate,
1718
+ (req, res, next) => {
1719
+ const user = req.user;
1720
+ if (!user) {
1721
+ sendJson2(res, 401, { error: "Authentication required" });
1722
+ return;
1723
+ }
1724
+ const userPerms = user.permissions ?? [];
1725
+ if (!permissions.every((p) => userPerms.includes(p))) {
1726
+ sendJson2(res, 403, {
1727
+ error: `Insufficient permissions. Required: ${permissions.join(", ")}`,
1728
+ yourPermissions: userPerms
1729
+ });
1730
+ return;
1731
+ }
1732
+ next();
1733
+ }
1734
+ ];
1735
+ }
1736
+ return {
1737
+ login,
1738
+ refresh,
1739
+ logout,
1740
+ authenticate,
1741
+ authorize,
1742
+ requireRole,
1743
+ requirePermission
1744
+ };
1745
+ }
1746
+ var jwt_auth_default = createJwtPlugin;
1747
+
1748
+ // src/git.ts
1749
+ var import_child_process = require("child_process");
1750
+ var import_zlib2 = require("zlib");
1751
+ var import_fs2 = require("fs");
1752
+ function pktLine(str) {
1753
+ const byteLen = Buffer.byteLength(str, "utf8") + 4;
1754
+ return byteLen.toString(16).padStart(4, "0") + str;
1755
+ }
1756
+ var PKT_FLUSH = "0000";
1757
+ function gitHandler(opt) {
1758
+ if (typeof opt.repository !== "function")
1759
+ throw new TypeError("gitHandler: opt.repository must be a function");
1760
+ const gitHome = opt.gitPath ?? "";
1761
+ return async (req, res) => {
1762
+ const gitDirectory = await opt.repository(req);
1763
+ if (!gitDirectory) {
1764
+ res.status(404).send("Repository not found");
1765
+ return;
1766
+ }
1767
+ const urlPath = req.path;
1768
+ if (req.method === "GET" && urlPath === "/info/refs") {
1769
+ let args = [];
1770
+ const service = req.queries?.url?.service;
1771
+ if (service === "git-upload-pack")
1772
+ args = buildArgs(opt, ["--stateless-rpc", "--advertise-refs", gitDirectory]);
1773
+ else if (service === "git-receive-pack")
1774
+ args = ["--stateless-rpc", "--advertise-refs", gitDirectory];
1775
+ else {
1776
+ res.status(403).send(`Service ${service} is not supported`);
1777
+ return;
1778
+ }
1779
+ res.setHeader("Content-Type", `application/x-${service}-advertisement`);
1780
+ res.setHeader("Cache-Control", "no-cache");
1781
+ res.write(pktLine(`# service=${service}
1782
+ `));
1783
+ res.write(PKT_FLUSH);
1784
+ const proc = (0, import_child_process.spawn)(gitHome + service, args, {
1785
+ env: { ...process.env, GIT_PROTOCOL: req.headers["git-protocol"] || "" }
1786
+ });
1787
+ proc.on("error", (err) => {
1788
+ console.error(`[${service} GET] spawn error:`, err.message);
1789
+ if (!res.writableEnded) res.status(500).send(`${service} unavailable: ${err.message}`);
1790
+ });
1791
+ proc.stdout.pipe(res);
1792
+ proc.stdout.on("error", (err) => {
1793
+ console.warn(`[${service} GET] stdout error:`, err.message);
1794
+ });
1795
+ proc.stderr.on(
1796
+ "data",
1797
+ (d) => console.error(`[${service} GET]`, d.toString())
1798
+ );
1799
+ proc.on("close", (code) => {
1800
+ if (code !== 0) {
1801
+ console.error(`[${service} GET] exited with code ${code}`);
1802
+ if (!res.writableEnded) res.status(500).send(`${service} failed`);
1803
+ }
1804
+ });
1805
+ return;
1806
+ }
1807
+ if (req.method === "POST" && (urlPath === "/git-upload-pack" || urlPath === "/git-receive-pack")) {
1808
+ const contentType = req.headers["content-type"] || "";
1809
+ const service = urlPath.substring(1);
1810
+ if (contentType !== `application/x-${service}-request`) {
1811
+ res.status(415).send("Unsupported Media Type");
1812
+ return;
1813
+ }
1814
+ let args = [];
1815
+ if (service === "git-upload-pack")
1816
+ args = buildArgs(opt, ["--stateless-rpc", gitDirectory]);
1817
+ else if (service === "git-receive-pack")
1818
+ args = ["--stateless-rpc", gitDirectory];
1819
+ else {
1820
+ res.status(403).send(`Service ${service} is not supported`);
1821
+ return;
1822
+ }
1823
+ res.setHeader("Content-Type", `application/x-${service}-result`);
1824
+ res.setHeader("Cache-Control", "no-cache");
1825
+ const proc = (0, import_child_process.spawn)(gitHome + service, args, {
1826
+ env: { ...process.env, GIT_PROTOCOL: req.headers["git-protocol"] || "" }
1827
+ });
1828
+ proc.on("error", (err) => {
1829
+ console.error(`[${service} POST] spawn error:`, err.message);
1830
+ if (!res.writableEnded) res.status(500).send(`${service} unavailable: ${err.message}`);
1831
+ });
1832
+ const encoding = req.headers["content-encoding"];
1833
+ if (encoding === "gzip") {
1834
+ const gunzip = (0, import_zlib2.createGunzip)();
1835
+ gunzip.on("error", (err) => {
1836
+ console.warn(`[${service} POST] gunzip error:`, err.message);
1837
+ if (!res.writableEnded) res.status(400).send("Failed to decompress request body");
1838
+ });
1839
+ req.pipe(gunzip).pipe(proc.stdin);
1840
+ } else {
1841
+ req.pipe(proc.stdin);
1842
+ }
1843
+ proc.stdout.pipe(res);
1844
+ proc.stdout.on("error", (err) => {
1845
+ console.warn(`[${service} POST] stdout error:`, err.message);
1846
+ });
1847
+ proc.stderr.on(
1848
+ "data",
1849
+ (d) => console.error(`[${service} POST]`, d.toString())
1850
+ );
1851
+ proc.on("close", (code) => {
1852
+ if (code !== 0) {
1853
+ console.error(`[${service} POST] exited with code ${code}`);
1854
+ if (!res.writableEnded) res.status(500).send(`${service} failed`);
1855
+ }
1856
+ });
1857
+ proc.stdin.on("error", (err) => {
1858
+ if (err.code !== "EPIPE")
1859
+ console.warn(`[${service} POST] stdin error:`, err.message);
1860
+ });
1861
+ return;
1862
+ }
1863
+ res.status(404).send("Not found");
1864
+ };
1865
+ }
1866
+ function gitCreate(gitDirectory, opt) {
1867
+ return new Promise((resolve, reject) => {
1868
+ const gitHome = opt.gitPath ?? "";
1869
+ const isBare = opt.bare !== false;
1870
+ const args = isBare ? ["init", "--bare", gitDirectory] : ["init", gitDirectory];
1871
+ const proc = (0, import_child_process.spawn)(gitHome + "git", args, {
1872
+ env: { ...process.env }
1873
+ });
1874
+ proc.on("error", (err) => {
1875
+ console.error(`[git init] spawn error:`, err.message);
1876
+ reject(`git unavailable: ${err.message}`);
1877
+ });
1878
+ proc.stdout.on("error", (err) => {
1879
+ console.warn(`[git init] stdout error:`, err.message);
1880
+ });
1881
+ proc.stderr.on(
1882
+ "data",
1883
+ (d) => console.error(`[git init]`, d.toString())
1884
+ );
1885
+ proc.on("close", (code) => {
1886
+ if (code !== 0) {
1887
+ return reject("git failed");
1888
+ }
1889
+ if (opt.description) {
1890
+ const descPath = isBare ? `${gitDirectory}/description` : `${gitDirectory}/.git/description`;
1891
+ (0, import_fs2.writeFileSync)(descPath, opt.description);
1892
+ }
1893
+ resolve();
1894
+ });
1895
+ });
1896
+ }
1897
+ function buildArgs(opt, trailing) {
1898
+ const args = [];
1899
+ if (opt.timeout) {
1900
+ const seconds = parseInt(String(opt.timeout), 10);
1901
+ if (!isNaN(seconds) && seconds > 0)
1902
+ args.push(`--timeout=${seconds}`);
1903
+ }
1904
+ if (!opt.strict)
1905
+ args.push("--no-strict");
1906
+ return [...args, ...trailing];
1907
+ }
1908
+
1909
+ // src/openapi.ts
1910
+ var YAML_KW = /* @__PURE__ */ new Set([
1911
+ "true",
1912
+ "false",
1913
+ "yes",
1914
+ "no",
1915
+ "on",
1916
+ "off",
1917
+ "null",
1918
+ "~"
1919
+ ]);
1920
+ var LOOKS_LIKE_NUMBER = /^[-+]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$|^0x[0-9a-fA-F]+$|^0o[0-7]+$/;
1921
+ function yamlString(s) {
1922
+ if (s === "") return '""';
1923
+ const needsQuote = (
1924
+ // Would be misinterpreted as a YAML typed value.
1925
+ YAML_KW.has(s.toLowerCase()) || LOOKS_LIKE_NUMBER.test(s) || // Starts with a YAML indicator that has special meaning at the start of a
1926
+ // plain scalar (block context).
1927
+ /^[-?:,[\]{}#&*!|>'"%@`~]/.test(s) || // Inline sequences that break block-mapping parsing.
1928
+ s.includes(": ") || s.endsWith(":") || s.includes(" #") || // Leading / trailing whitespace.
1929
+ s !== s.trim() || // Flow indicator characters anywhere — present in path patterns such as
1930
+ // `/items/{id}` and must be quoted to avoid flow-collection ambiguity.
1931
+ /[{}\[\]]/.test(s) || // Control characters.
1932
+ /[\x00-\x1f\x7f]/.test(s)
1933
+ );
1934
+ if (!needsQuote) return s;
1935
+ 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")}`) + '"';
1936
+ }
1937
+ function yamlKey(key) {
1938
+ return yamlString(key);
1939
+ }
1940
+ function toYamlLines(value) {
1941
+ if (value === null || value === void 0) return ["null"];
1942
+ if (typeof value === "boolean") return [String(value)];
1943
+ if (typeof value === "number") return [isFinite(value) ? String(value) : ".inf"];
1944
+ if (typeof value === "string") return [yamlString(value)];
1945
+ if (Array.isArray(value)) {
1946
+ if (value.length === 0) return ["[]"];
1947
+ const lines = [];
1948
+ for (const item of value) {
1949
+ const itemLines = toYamlLines(item);
1950
+ lines.push(`- ${itemLines[0]}`);
1951
+ for (const l of itemLines.slice(1)) lines.push(` ${l}`);
1952
+ }
1953
+ return lines;
1954
+ }
1955
+ if (typeof value === "object") {
1956
+ const obj = value;
1957
+ const entries = Object.entries(obj).filter(([, v]) => v !== void 0);
1958
+ if (entries.length === 0) return ["{}"];
1959
+ const lines = [];
1960
+ for (const [key, val] of entries) {
1961
+ const k = yamlKey(key);
1962
+ const valLines = toYamlLines(val);
1963
+ const isComplex = typeof val === "object" && val !== null;
1964
+ const isEmptyCollection = valLines.length === 1 && (valLines[0] === "{}" || valLines[0] === "[]");
1965
+ if (!isComplex || isEmptyCollection) {
1966
+ lines.push(`${k}: ${valLines[0]}`);
1967
+ } else {
1968
+ lines.push(`${k}:`);
1969
+ for (const l of valLines) lines.push(` ${l}`);
1970
+ }
1971
+ }
1972
+ return lines;
1973
+ }
1974
+ return [String(value)];
1975
+ }
1976
+ function serializeSpec(doc, format = "json") {
1977
+ if (format === "yaml") return toYamlLines(doc).join("\n") + "\n";
1978
+ return JSON.stringify(doc, null, 2);
1979
+ }
1980
+ var DESCRIBE_META = /* @__PURE__ */ Symbol("expediate.openapi.meta");
1981
+ function describe(handler, meta) {
1982
+ const described = function(ctx, body) {
1983
+ return handler.apply(this, [ctx, body]);
1984
+ };
1985
+ Object.defineProperty(described, DESCRIBE_META, {
1986
+ value: meta,
1987
+ enumerable: false,
1988
+ configurable: true,
1989
+ writable: false
1990
+ });
1991
+ return described;
1992
+ }
1993
+ function toOpenApiPath(pattern, basePath) {
1994
+ const converted = pattern.replace(/:([A-Za-z_][A-Za-z0-9_]*)/g, "{$1}");
1995
+ if (!basePath) return converted;
1996
+ const base = basePath.replace(/\/+$/, "");
1997
+ const path2 = converted.replace(/^\/+/, "/");
1998
+ return base + (path2.startsWith("/") ? path2 : `/${path2}`);
1999
+ }
2000
+ function extractPathParams(pattern, annotated) {
2001
+ const annotatedNames = new Set(annotated.map((p) => p.name));
2002
+ const params = [];
2003
+ for (const match of pattern.matchAll(/:([A-Za-z_][A-Za-z0-9_]*)/g)) {
2004
+ const name = match[1];
2005
+ if (!annotatedNames.has(name)) {
2006
+ params.push({
2007
+ name,
2008
+ in: "path",
2009
+ required: true,
2010
+ schema: { type: "string" }
2011
+ });
2012
+ }
2013
+ }
2014
+ return params;
2015
+ }
2016
+ function buildOperationId(verb, pattern) {
2017
+ const parts = pattern.split(/[/:{} ]+/).filter(Boolean);
2018
+ let result = verb.toLowerCase();
2019
+ for (const part of parts) {
2020
+ const isParam = pattern.includes(`:${part}`) || pattern.includes(`{${part}}`);
2021
+ const pascal = part.charAt(0).toUpperCase() + part.slice(1);
2022
+ result += isParam ? `By${pascal}` : pascal;
2023
+ }
2024
+ return result;
2025
+ }
2026
+ function buildDefaultResponses(verb) {
2027
+ const ok = verb === "POST" ? { description: "Created" } : { description: "OK", content: { "application/json": {} } };
2028
+ return {
2029
+ [verb === "POST" ? "201" : "200"]: ok,
2030
+ "500": { $ref: "#/components/responses/ApiError" }
2031
+ };
2032
+ }
2033
+ function buildAnnotatedResponses(responses) {
2034
+ const result = { ...responses };
2035
+ if (!result["500"]) {
2036
+ result["500"] = { $ref: "#/components/responses/ApiError" };
2037
+ }
2038
+ return result;
2039
+ }
2040
+ var VERBS = ["GET", "POST", "PUT", "DELETE", "PATCH"];
2041
+ function openApiSpec(service, opts) {
2042
+ const basePath = opts.basePath ?? "";
2043
+ const svcMeta = service.openapi;
2044
+ const defaultTag = svcMeta?.tag;
2045
+ const builtinSchemas = {
2046
+ ApiError: {
2047
+ type: "object",
2048
+ properties: {
2049
+ status: { type: "integer", description: "HTTP status code" },
2050
+ message: { type: "string", description: "Human-readable error message" },
2051
+ data: { description: "Structured error payload (overrides message when present)" }
2052
+ }
2053
+ }
2054
+ };
2055
+ const builtinResponses = {
2056
+ ApiError: {
2057
+ description: "API error response",
2058
+ content: {
2059
+ "application/json": { schema: { $ref: "#/components/schemas/ApiError" } }
2060
+ }
2061
+ }
2062
+ };
2063
+ const schemas = {
2064
+ ...builtinSchemas,
2065
+ ...svcMeta?.schemas,
2066
+ ...opts.schemas
2067
+ };
2068
+ const responses = {
2069
+ ...builtinResponses,
2070
+ ...svcMeta?.responses
2071
+ };
2072
+ const tags = [];
2073
+ if (defaultTag) {
2074
+ tags.push({ name: defaultTag, description: svcMeta?.tagDescription });
2075
+ }
2076
+ const paths = {};
2077
+ for (const verb of VERBS) {
2078
+ const routeMap = service[verb];
2079
+ if (!routeMap) continue;
2080
+ for (const [pattern, handler] of Object.entries(routeMap)) {
2081
+ const openApiPath = toOpenApiPath(pattern, basePath);
2082
+ if (!paths[openApiPath]) paths[openApiPath] = {};
2083
+ const meta = handler[DESCRIBE_META];
2084
+ const annotatedParams = meta?.parameters ?? [];
2085
+ const inferredParams = extractPathParams(pattern, annotatedParams);
2086
+ const parameters = [...annotatedParams, ...inferredParams];
2087
+ const operationResponses = meta?.responses ? buildAnnotatedResponses(meta.responses) : buildDefaultResponses(verb);
2088
+ const operation = {
2089
+ operationId: meta?.operationId ?? buildOperationId(verb, pattern),
2090
+ ...meta?.summary && { summary: meta.summary },
2091
+ ...meta?.description && { description: meta.description },
2092
+ ...meta?.deprecated && { deprecated: true },
2093
+ ...parameters.length > 0 && { parameters },
2094
+ ...meta?.requestBody && { requestBody: meta.requestBody },
2095
+ responses: operationResponses
2096
+ };
2097
+ const opTags = meta?.tags ?? (defaultTag ? [defaultTag] : void 0);
2098
+ if (opTags) operation["tags"] = opTags;
2099
+ if (meta) {
2100
+ for (const [k, v] of Object.entries(meta)) {
2101
+ if (k.startsWith("x-")) operation[k] = v;
2102
+ }
2103
+ }
2104
+ paths[openApiPath][verb.toLowerCase()] = operation;
2105
+ }
2106
+ }
2107
+ const doc = {
2108
+ openapi: "3.1.0",
2109
+ info: {
2110
+ title: opts.title,
2111
+ version: opts.version,
2112
+ ...opts.description && { description: opts.description }
2113
+ },
2114
+ ...opts.servers && { servers: opts.servers },
2115
+ ...tags.length > 0 && { tags },
2116
+ paths,
2117
+ components: { schemas, responses }
2118
+ };
2119
+ return doc;
2120
+ }
2121
+
2122
+ // src/apis.ts
2123
+ async function buildModule(service, key) {
2124
+ const instance = service.data ? service.data(key) : { $key: key };
2125
+ if (service.methods) {
2126
+ for (const methodName of Object.keys(service.methods)) {
2127
+ const method = service.methods[methodName];
2128
+ instance[methodName] = function(...args) {
2129
+ return method.apply(instance, args);
2130
+ };
2131
+ }
2132
+ }
2133
+ if (service.setup)
2134
+ await service.setup.apply(instance, []);
2135
+ return instance;
2136
+ }
2137
+ async function resolveInstance(service, modules, building, req) {
2138
+ if (typeof service.scope !== "function") {
2139
+ return modules["singleton"];
2140
+ }
2141
+ const key = service.scope(req);
2142
+ if (key === null) {
2143
+ return buildModule(service, null);
2144
+ }
2145
+ if (modules[key]) return modules[key];
2146
+ if (!building[key]) {
2147
+ building[key] = buildModule(service, key).then((instance) => {
2148
+ modules[key] = instance;
2149
+ delete building[key];
2150
+ return instance;
2151
+ });
2152
+ }
2153
+ return building[key];
2154
+ }
2155
+ function sendJson(res, data) {
2156
+ res.setHeader("Content-Type", "application/json; charset=utf-8");
2157
+ res.send(JSON.stringify(data));
2158
+ }
2159
+ function sendError(res, err) {
2160
+ const e = err;
2161
+ const status = e?.status ?? 500;
2162
+ if (e?.data !== void 0) {
2163
+ res.setHeader("Content-Type", "application/json; charset=utf-8");
2164
+ res.status(status).send(JSON.stringify(e.data));
2165
+ } else {
2166
+ res.status(status).send(e?.message ?? "Internal error");
2167
+ }
2168
+ }
2169
+ function apiBuilder(service) {
2170
+ const api = router_default();
2171
+ const modules = {};
2172
+ const building = {};
2173
+ function buildRoutes(routeMap, register) {
2174
+ if (!routeMap) return;
2175
+ const sortedPaths = Object.keys(routeMap).sort((a, b) => {
2176
+ const score = (p) => {
2177
+ const segs = p.split("/").filter((s) => s.length > 0);
2178
+ return segs.length * 100 - segs.filter((s) => s.startsWith(":")).length * 10;
2179
+ };
2180
+ return score(b) - score(a) || b.localeCompare(a);
2181
+ });
2182
+ for (const path2 of sortedPaths) {
2183
+ const method = routeMap[path2];
2184
+ register(path2, (req, res) => {
2185
+ const ctx = {
2186
+ query: {
2187
+ route: req.queries?.route ?? {},
2188
+ url: req.queries?.url ?? {}
2189
+ },
2190
+ path: req.path,
2191
+ user: req.user
2192
+ };
2193
+ const body = req.body;
2194
+ resolveInstance(service, modules, building, req).then((instance) => {
2195
+ const ret = method.apply(instance, [ctx, body]);
2196
+ if (ret instanceof Promise) {
2197
+ return ret.then((val) => {
2198
+ if (val !== void 0 && val !== null && val !== false && val !== 0 && val !== "")
2199
+ sendJson(res, val);
2200
+ else
2201
+ res.status(201).end();
2202
+ }).catch((err) => {
2203
+ sendError(res, err);
2204
+ });
2205
+ }
2206
+ if (ret !== void 0 && ret !== null && ret !== false && ret !== 0 && ret !== "")
2207
+ sendJson(res, ret);
2208
+ else
2209
+ res.status(201).end();
2210
+ }).catch((err) => {
2211
+ sendError(res, err);
2212
+ });
2213
+ });
2214
+ }
2215
+ }
2216
+ function registerAllRoutes() {
2217
+ buildRoutes(service.GET, (path2, h) => api.get(path2, h));
2218
+ buildRoutes(service.POST, (path2, h) => api.post(path2, h));
2219
+ buildRoutes(service.PUT, (path2, h) => api.put(path2, h));
2220
+ buildRoutes(service.DELETE, (path2, h) => api.delete(path2, h));
2221
+ buildRoutes(service.PATCH, (path2, h) => api.patch(path2, h));
2222
+ }
2223
+ if (typeof service.scope !== "function") {
2224
+ let ready = false;
2225
+ api.use("/", (_req, res, next) => {
2226
+ if (!ready) {
2227
+ res.statusCode = 503;
2228
+ res.end("Service not ready");
2229
+ return;
2230
+ }
2231
+ next();
2232
+ });
2233
+ buildModule(service, "singleton").then((instance) => {
2234
+ modules["singleton"] = instance;
2235
+ ready = true;
2236
+ registerAllRoutes();
2237
+ }).catch((err) => {
2238
+ console.error("[apiBuilder] singleton setup() rejected:", err);
2239
+ });
2240
+ } else {
2241
+ registerAllRoutes();
2242
+ }
2243
+ api.spec = function(opts) {
2244
+ return openApiSpec(service, opts);
2245
+ };
2246
+ api.specHandler = function(opts, format = "json") {
2247
+ let cached = null;
2248
+ const contentType = format === "yaml" ? "application/yaml; charset=utf-8" : "application/json; charset=utf-8";
2249
+ return function(_req, res) {
2250
+ if (!cached) cached = serializeSpec(openApiSpec(service, opts), format);
2251
+ res.setHeader("Content-Type", contentType);
2252
+ res.end(cached);
2253
+ };
2254
+ };
2255
+ return api;
2256
+ }
2257
+ var apis_default = apiBuilder;
2258
+
2259
+ // src/middleware.ts
2260
+ var crypto3 = __toESM(require("crypto"), 1);
2261
+ var zlib2 = __toESM(require("zlib"), 1);
2262
+ function toBuffer(chunk, encoding = "utf8") {
2263
+ return Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, encoding);
2264
+ }
2265
+ function compress(opts) {
2266
+ const threshold = opts?.threshold ?? 1024;
2267
+ const brEnabled = opts?.br !== false;
2268
+ const brotliQuality = opts?.brotliQuality ?? 4;
2269
+ const gzipLevel = opts?.gzipLevel ?? zlib2.constants.Z_DEFAULT_COMPRESSION;
2270
+ return (req, res, next) => {
2271
+ if (opts?.filter && !opts.filter(req, res)) return next();
2272
+ const ae = req.headers["accept-encoding"] ?? "";
2273
+ let compressor = null;
2274
+ let encoding = "";
2275
+ if (brEnabled && /\bbr\b/.test(ae)) {
2276
+ compressor = zlib2.createBrotliCompress({
2277
+ params: { [zlib2.constants.BROTLI_PARAM_QUALITY]: brotliQuality }
2278
+ });
2279
+ encoding = "br";
2280
+ } else if (/\bgzip\b/.test(ae)) {
2281
+ compressor = zlib2.createGzip({ level: gzipLevel });
2282
+ encoding = "gzip";
2283
+ } else if (/\bdeflate\b/.test(ae)) {
2284
+ compressor = zlib2.createDeflate({ level: gzipLevel });
2285
+ encoding = "deflate";
2286
+ }
2287
+ if (!compressor) return next();
2288
+ const comp = compressor;
2289
+ const rawRes = res;
2290
+ const origWrite = rawRes.write.bind(rawRes);
2291
+ const origEnd = rawRes.end.bind(rawRes);
2292
+ comp.on("data", (chunk) => {
2293
+ origWrite(chunk);
2294
+ });
2295
+ comp.on("end", () => {
2296
+ origEnd();
2297
+ });
2298
+ comp.on("error", (e) => {
2299
+ console.error("[compress] stream error:", e.message);
2300
+ if (!rawRes.writableEnded) origEnd();
2301
+ });
2302
+ const pending = [];
2303
+ let totalBytes = 0;
2304
+ let decided = false;
2305
+ let doCompress = false;
2306
+ const startCompressing = (andEnd) => {
2307
+ decided = true;
2308
+ doCompress = true;
2309
+ res.setHeader("Content-Encoding", encoding);
2310
+ res.setHeader("Vary", "Accept-Encoding");
2311
+ res.removeHeader("Content-Length");
2312
+ for (const buf of pending) comp.write(buf);
2313
+ if (andEnd) comp.end();
2314
+ };
2315
+ const skipCompression = () => {
2316
+ decided = true;
2317
+ doCompress = false;
2318
+ comp.destroy();
2319
+ if (totalBytes > 0) res.setHeader("Content-Length", totalBytes);
2320
+ for (const buf of pending) origWrite(buf);
2321
+ };
2322
+ res.write = (chunk, encOrCb, _cb) => {
2323
+ const enc = typeof encOrCb === "string" ? encOrCb : "utf8";
2324
+ const buf = toBuffer(chunk, enc);
2325
+ if (decided) {
2326
+ return doCompress ? comp.write(buf) : origWrite(buf);
2327
+ }
2328
+ pending.push(buf);
2329
+ totalBytes += buf.length;
2330
+ if (totalBytes >= threshold) startCompressing(false);
2331
+ return true;
2332
+ };
2333
+ res.end = (chunk, encOrCb, _cb) => {
2334
+ if (typeof chunk === "function") chunk = void 0;
2335
+ if (typeof encOrCb === "function") encOrCb = void 0;
2336
+ if (chunk != null) {
2337
+ const enc = typeof encOrCb === "string" ? encOrCb : "utf8";
2338
+ const buf = toBuffer(chunk, enc);
2339
+ if (decided) {
2340
+ if (doCompress) comp.write(buf);
2341
+ else origWrite(buf);
2342
+ } else {
2343
+ pending.push(buf);
2344
+ totalBytes += buf.length;
2345
+ }
2346
+ }
2347
+ if (!decided) {
2348
+ if (totalBytes >= threshold) startCompressing(true);
2349
+ else {
2350
+ skipCompression();
2351
+ origEnd();
2352
+ }
2353
+ } else if (doCompress) {
2354
+ comp.end();
2355
+ } else {
2356
+ origEnd();
2357
+ }
2358
+ return rawRes;
2359
+ };
2360
+ next();
2361
+ };
2362
+ }
2363
+ function requestId(opts) {
2364
+ const header = (opts?.header ?? "x-request-id").toLowerCase();
2365
+ const allowFromHeader = opts?.allowFromHeader !== false;
2366
+ const generator = opts?.generator ?? (() => crypto3.randomUUID());
2367
+ return (req, res, next) => {
2368
+ const incoming = req.headers[header];
2369
+ const id = allowFromHeader && incoming ? incoming : generator();
2370
+ req.id = id;
2371
+ res.setHeader(header, id);
2372
+ next();
2373
+ };
2374
+ }
2375
+ function rateLimit(opts) {
2376
+ const windowMs = opts.windowMs;
2377
+ const max = opts.max;
2378
+ const keyBy = opts.keyBy ?? ((req) => req.ip ?? "");
2379
+ const message = opts.message ?? "Too Many Requests";
2380
+ const statusCode = opts.statusCode ?? 429;
2381
+ const sendHeaders = opts.headers !== false;
2382
+ const store = /* @__PURE__ */ new Map();
2383
+ return (req, res, next) => {
2384
+ const key = keyBy(req);
2385
+ const now = Date.now();
2386
+ const windowStart = now - windowMs;
2387
+ const timestamps = (store.get(key) ?? []).filter((t) => t > windowStart);
2388
+ timestamps.push(now);
2389
+ store.set(key, timestamps);
2390
+ const count = timestamps.length;
2391
+ const remaining = Math.max(0, max - count);
2392
+ const resetAt = timestamps.length > 0 ? Math.ceil((timestamps[0] + windowMs) / 1e3) : Math.ceil((now + windowMs) / 1e3);
2393
+ if (sendHeaders) {
2394
+ res.setHeader("X-RateLimit-Limit", String(max));
2395
+ res.setHeader("X-RateLimit-Remaining", String(remaining));
2396
+ res.setHeader("X-RateLimit-Reset", String(resetAt));
2397
+ }
2398
+ if (count > max) {
2399
+ res.setHeader("Retry-After", String(Math.ceil(windowMs / 1e3)));
2400
+ res.statusCode = statusCode;
2401
+ res.end(message);
2402
+ return;
2403
+ }
2404
+ next();
2405
+ };
2406
+ }
2407
+ function cacheControl(opts = {}) {
2408
+ const directives = [];
2409
+ if (opts.private) directives.push("private");
2410
+ if (opts.public) directives.push("public");
2411
+ if (opts.noStore) directives.push("no-store");
2412
+ if (opts.noCache) directives.push("no-cache");
2413
+ if (opts.mustRevalidate) directives.push("must-revalidate");
2414
+ if (opts.immutable) directives.push("immutable");
2415
+ if (opts.maxAge != null) directives.push(`max-age=${opts.maxAge}`);
2416
+ if (opts.sMaxAge != null) directives.push(`s-maxage=${opts.sMaxAge}`);
2417
+ const cacheControlValue = directives.join(", ");
2418
+ const varyValue = Array.isArray(opts.vary) ? opts.vary.join(", ") : opts.vary ?? null;
2419
+ return (_req, res, next) => {
2420
+ if (cacheControlValue) res.setHeader("Cache-Control", cacheControlValue);
2421
+ if (opts.maxAge != null) {
2422
+ const expires = new Date(Date.now() + opts.maxAge * 1e3);
2423
+ res.setHeader("Expires", expires.toUTCString());
2424
+ }
2425
+ if (varyValue) res.setHeader("Vary", varyValue);
2426
+ next();
2427
+ };
2428
+ }
2429
+ var SAFE_CSRF_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS", "TRACE"]);
2430
+ function csrf(opts) {
2431
+ const cookieName = opts?.cookieName ?? "_csrf";
2432
+ const headerName = (opts?.headerName ?? "x-csrf-token").toLowerCase();
2433
+ const fieldName = opts?.fieldName ?? "_csrf";
2434
+ const secure = opts?.secure ?? false;
2435
+ const sameSite = opts?.sameSite ?? "Strict";
2436
+ return (req, res, next) => {
2437
+ const existing = req.cookies?.[cookieName];
2438
+ const token = typeof existing === "string" && existing.length > 0 ? existing : crypto3.randomBytes(32).toString("hex");
2439
+ if (!existing) {
2440
+ let cookieStr = `${cookieName}=${token}; Path=/; SameSite=${sameSite}`;
2441
+ if (secure) cookieStr += "; Secure";
2442
+ const prev = res.getHeader("Set-Cookie");
2443
+ if (prev == null) res.setHeader("Set-Cookie", cookieStr);
2444
+ else if (Array.isArray(prev)) res.setHeader("Set-Cookie", [...prev, cookieStr]);
2445
+ else res.setHeader("Set-Cookie", [prev, cookieStr]);
2446
+ }
2447
+ req.csrfToken = () => token;
2448
+ if (SAFE_CSRF_METHODS.has(req.method ?? "")) return next();
2449
+ const submitted = req.headers[headerName] ?? req.body?.[fieldName] ?? "";
2450
+ if (!submitted || submitted !== token) {
2451
+ res.statusCode = 403;
2452
+ res.end("Forbidden: invalid CSRF token");
2453
+ return;
2454
+ }
2455
+ next();
2456
+ };
2457
+ }
2458
+ function isFreshResponse(etag, lastModified, ifNoneMatch, ifModSince) {
2459
+ const inm = Array.isArray(ifNoneMatch) ? ifNoneMatch[0] : ifNoneMatch;
2460
+ if (inm) {
2461
+ if (!etag) return false;
2462
+ if (inm.trim() === "*") return true;
2463
+ const normalize = (tag) => tag.startsWith("W/") ? tag.slice(2) : tag;
2464
+ const tags = inm.split(",").map((t) => normalize(t.trim()));
2465
+ return tags.includes(normalize(etag));
2466
+ }
2467
+ const ims = Array.isArray(ifModSince) ? ifModSince[0] : ifModSince;
2468
+ if (ims && lastModified) {
2469
+ const imsMs = new Date(ims).getTime();
2470
+ const lmMs = new Date(lastModified).getTime();
2471
+ if (!isNaN(imsMs) && !isNaN(lmMs)) return lmMs <= imsMs;
2472
+ }
2473
+ return false;
2474
+ }
2475
+ function conditionalGet() {
2476
+ return (req, res, next) => {
2477
+ const method = req.method ?? "GET";
2478
+ if (method !== "GET" && method !== "HEAD") return next();
2479
+ const rawRes = res;
2480
+ const origWrite = rawRes.write.bind(rawRes);
2481
+ const origEnd = rawRes.end.bind(rawRes);
2482
+ const pending = [];
2483
+ res.write = (chunk, encOrCb, _cb) => {
2484
+ const enc = typeof encOrCb === "string" ? encOrCb : "utf8";
2485
+ const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, enc);
2486
+ pending.push(buf);
2487
+ return true;
2488
+ };
2489
+ res.end = (chunk, encOrCb, _cb) => {
2490
+ if (typeof chunk === "function") chunk = void 0;
2491
+ if (typeof encOrCb === "function") encOrCb = void 0;
2492
+ if (chunk != null) {
2493
+ const enc = typeof encOrCb === "string" ? encOrCb : "utf8";
2494
+ const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, enc);
2495
+ pending.push(buf);
2496
+ }
2497
+ const etag = rawRes.getHeader("etag");
2498
+ const lastMod = rawRes.getHeader("last-modified");
2499
+ const ifNoneMatch = req.headers["if-none-match"];
2500
+ const ifModSince = req.headers["if-modified-since"];
2501
+ if (isFreshResponse(etag, lastMod, ifNoneMatch, ifModSince)) {
2502
+ rawRes.removeHeader("content-type");
2503
+ rawRes.removeHeader("content-length");
2504
+ rawRes.removeHeader("content-encoding");
2505
+ rawRes.statusCode = 304;
2506
+ origEnd();
2507
+ } else {
2508
+ for (const buf of pending) origWrite(buf);
2509
+ origEnd();
2510
+ }
2511
+ return rawRes;
2512
+ };
2513
+ next();
2514
+ };
2515
+ }
2516
+ function securityHeaders(opts) {
2517
+ const headers = [];
2518
+ const hsts = opts?.hsts;
2519
+ if (hsts !== false) {
2520
+ const h = typeof hsts === "object" ? hsts : {};
2521
+ const maxAge = h.maxAge ?? 15552e3;
2522
+ const subdomain = h.includeSubDomains !== false;
2523
+ let value = `max-age=${maxAge}`;
2524
+ if (subdomain) value += "; includeSubDomains";
2525
+ if (h.preload) value += "; preload";
2526
+ headers.push(["Strict-Transport-Security", value]);
2527
+ }
2528
+ const fo = opts?.frameOptions;
2529
+ if (fo !== false) {
2530
+ headers.push(["X-Frame-Options", fo ?? "SAMEORIGIN"]);
2531
+ }
2532
+ if (opts?.contentTypeOptions !== false) {
2533
+ headers.push(["X-Content-Type-Options", "nosniff"]);
2534
+ }
2535
+ const rp = opts?.referrerPolicy;
2536
+ if (rp !== false) {
2537
+ headers.push(["Referrer-Policy", typeof rp === "string" ? rp : "strict-origin-when-cross-origin"]);
2538
+ }
2539
+ const pp = opts?.permissionsPolicy;
2540
+ if (pp !== false) {
2541
+ headers.push(["Permissions-Policy", typeof pp === "string" ? pp : "geolocation=(), microphone=(), camera=()"]);
2542
+ }
2543
+ const xxp = opts?.xssProtection;
2544
+ if (xxp !== false) {
2545
+ headers.push(["X-XSS-Protection", typeof xxp === "string" ? xxp : "0"]);
2546
+ }
2547
+ return (_req, res, next) => {
2548
+ for (const [name, value] of headers) res.setHeader(name, value);
2549
+ next();
2550
+ };
2551
+ }
2552
+ // Annotate the CommonJS export names for ESM import in node:
2553
+ 0 && (module.exports = {
2554
+ DESCRIBE_META,
2555
+ apiBuilder,
2556
+ cacheControl,
2557
+ compress,
2558
+ conditionalGet,
2559
+ cors,
2560
+ createJwtPlugin,
2561
+ createMapTokenStore,
2562
+ createRouter,
2563
+ csrf,
2564
+ describe,
2565
+ formData,
2566
+ formEncoded,
2567
+ gitCreate,
2568
+ gitHandler,
2569
+ json,
2570
+ logger,
2571
+ mime,
2572
+ openApiSpec,
2573
+ parseBody,
2574
+ parseMultipartBody,
2575
+ rateLimit,
2576
+ requestId,
2577
+ securityHeaders,
2578
+ sendFile,
2579
+ serializeSpec,
2580
+ serveFile,
2581
+ serveStatic,
2582
+ streamFormData
2583
+ });