@tknf/matchbox 0.2.6 → 0.3.1

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 (37) hide show
  1. package/README.md +212 -46
  2. package/dist/cgi.d.ts +10 -21
  3. package/dist/cgi.js +26 -97
  4. package/dist/htaccess/access-control.d.ts +9 -0
  5. package/dist/htaccess/access-control.js +91 -0
  6. package/dist/htaccess/error-document.d.ts +9 -0
  7. package/dist/htaccess/error-document.js +16 -0
  8. package/dist/htaccess/headers.d.ts +32 -0
  9. package/dist/htaccess/headers.js +98 -0
  10. package/dist/htaccess/index.d.ts +8 -0
  11. package/dist/htaccess/index.js +20 -0
  12. package/dist/htaccess/parser.d.ts +9 -0
  13. package/dist/htaccess/parser.js +365 -0
  14. package/dist/htaccess/rewrite.d.ts +13 -0
  15. package/dist/htaccess/rewrite.js +69 -0
  16. package/dist/htaccess/types.d.ts +156 -0
  17. package/dist/htaccess/types.js +0 -0
  18. package/dist/htaccess/utils.d.ts +21 -0
  19. package/dist/htaccess/utils.js +69 -0
  20. package/dist/html.d.ts +1 -0
  21. package/dist/index.d.ts +3 -0
  22. package/dist/index.js +5 -1
  23. package/dist/middleware/auth.d.ts +8 -0
  24. package/dist/middleware/auth.js +28 -0
  25. package/dist/middleware/htaccess.d.ts +13 -0
  26. package/dist/middleware/htaccess.js +42 -0
  27. package/dist/middleware/index.d.ts +10 -0
  28. package/dist/middleware/index.js +14 -0
  29. package/dist/middleware/protected-files.d.ts +8 -0
  30. package/dist/middleware/protected-files.js +14 -0
  31. package/dist/middleware/session.d.ts +16 -0
  32. package/dist/middleware/session.js +37 -0
  33. package/dist/middleware/trailing-slash.d.ts +8 -0
  34. package/dist/middleware/trailing-slash.js +12 -0
  35. package/dist/with-defaults.d.ts +2 -1
  36. package/dist/with-defaults.js +17 -28
  37. package/package.json +1 -1
@@ -0,0 +1,9 @@
1
+ import { Context } from 'hono';
2
+ import { ErrorDocumentConfig } from './types.js';
3
+
4
+ /**
5
+ * Create middleware for error documents
6
+ */
7
+ declare function createErrorDocumentMiddleware(errorDocs: ErrorDocumentConfig[], _basePath: string): (c: Context, next: () => Promise<void>) => Promise<Response | void>;
8
+
9
+ export { createErrorDocumentMiddleware };
@@ -0,0 +1,16 @@
1
+ function createErrorDocumentMiddleware(errorDocs, _basePath) {
2
+ return async (c, next) => {
3
+ await next();
4
+ const status = c.res.status;
5
+ if (status < 400) return;
6
+ const errorDoc = errorDocs.find((doc) => doc.statusCode === status);
7
+ if (!errorDoc) return;
8
+ if (errorDoc.target.startsWith("http://") || errorDoc.target.startsWith("https://")) {
9
+ return c.redirect(errorDoc.target);
10
+ }
11
+ return c.text(`Error ${status}: See ${errorDoc.target}`, status);
12
+ };
13
+ }
14
+ export {
15
+ createErrorDocumentMiddleware
16
+ };
@@ -0,0 +1,32 @@
1
+ import { Context } from 'hono';
2
+ import { HeaderConfig } from './types.js';
3
+
4
+ /**
5
+ * Create middleware for header directives
6
+ */
7
+ declare function createHeaderMiddleware(headers: HeaderConfig[]): (c: Context, next: () => Promise<void>) => Promise<void>;
8
+ /**
9
+ * Predefined security header helpers
10
+ */
11
+ declare const securityHeaders: {
12
+ xFrameOptions: (value: "DENY" | "SAMEORIGIN" | string) => HeaderConfig;
13
+ xContentTypeOptions: () => HeaderConfig;
14
+ xssProtection: (enabled?: boolean) => HeaderConfig;
15
+ hsts: (maxAge?: number, includeSubdomains?: boolean) => HeaderConfig;
16
+ csp: (policy: string) => HeaderConfig;
17
+ referrerPolicy: (policy: string) => HeaderConfig;
18
+ permissionsPolicy: (policy: string) => HeaderConfig;
19
+ };
20
+ /**
21
+ * CORS (Cross-Origin Resource Sharing) header helpers
22
+ */
23
+ declare const corsHeaders: {
24
+ allowOrigin: (origin: string) => HeaderConfig;
25
+ allowMethods: (methods: string[]) => HeaderConfig;
26
+ allowHeaders: (headers: string[]) => HeaderConfig;
27
+ allowCredentials: (allow?: boolean) => HeaderConfig;
28
+ maxAge: (seconds: number) => HeaderConfig;
29
+ exposeHeaders: (headers: string[]) => HeaderConfig;
30
+ };
31
+
32
+ export { corsHeaders, createHeaderMiddleware, securityHeaders };
@@ -0,0 +1,98 @@
1
+ function createHeaderMiddleware(headers) {
2
+ return async (c, next) => {
3
+ await next();
4
+ for (const header of headers) {
5
+ switch (header.action) {
6
+ case "set":
7
+ if (header.value) {
8
+ c.header(header.name, header.value);
9
+ }
10
+ break;
11
+ case "append":
12
+ if (header.value) {
13
+ const existing = c.res.headers.get(header.name);
14
+ const newValue = existing ? `${existing}, ${header.value}` : header.value;
15
+ c.header(header.name, newValue);
16
+ }
17
+ break;
18
+ case "unset":
19
+ c.res.headers.delete(header.name);
20
+ break;
21
+ }
22
+ }
23
+ };
24
+ }
25
+ const securityHeaders = {
26
+ xFrameOptions: (value) => ({
27
+ action: "set",
28
+ name: "X-Frame-Options",
29
+ value
30
+ }),
31
+ xContentTypeOptions: () => ({
32
+ action: "set",
33
+ name: "X-Content-Type-Options",
34
+ value: "nosniff"
35
+ }),
36
+ xssProtection: (enabled = true) => ({
37
+ action: "set",
38
+ name: "X-XSS-Protection",
39
+ value: enabled ? "1; mode=block" : "0"
40
+ }),
41
+ hsts: (maxAge = 31536e3, includeSubdomains = true) => ({
42
+ action: "set",
43
+ name: "Strict-Transport-Security",
44
+ value: includeSubdomains ? `max-age=${maxAge}; includeSubDomains` : `max-age=${maxAge}`
45
+ }),
46
+ csp: (policy) => ({
47
+ action: "set",
48
+ name: "Content-Security-Policy",
49
+ value: policy
50
+ }),
51
+ referrerPolicy: (policy) => ({
52
+ action: "set",
53
+ name: "Referrer-Policy",
54
+ value: policy
55
+ }),
56
+ permissionsPolicy: (policy) => ({
57
+ action: "set",
58
+ name: "Permissions-Policy",
59
+ value: policy
60
+ })
61
+ };
62
+ const corsHeaders = {
63
+ allowOrigin: (origin) => ({
64
+ action: "set",
65
+ name: "Access-Control-Allow-Origin",
66
+ value: origin
67
+ }),
68
+ allowMethods: (methods) => ({
69
+ action: "set",
70
+ name: "Access-Control-Allow-Methods",
71
+ value: methods.join(", ")
72
+ }),
73
+ allowHeaders: (headers) => ({
74
+ action: "set",
75
+ name: "Access-Control-Allow-Headers",
76
+ value: headers.join(", ")
77
+ }),
78
+ allowCredentials: (allow = true) => ({
79
+ action: "set",
80
+ name: "Access-Control-Allow-Credentials",
81
+ value: allow ? "true" : "false"
82
+ }),
83
+ maxAge: (seconds) => ({
84
+ action: "set",
85
+ name: "Access-Control-Max-Age",
86
+ value: String(seconds)
87
+ }),
88
+ exposeHeaders: (headers) => ({
89
+ action: "set",
90
+ name: "Access-Control-Expose-Headers",
91
+ value: headers.join(", ")
92
+ })
93
+ };
94
+ export {
95
+ corsHeaders,
96
+ createHeaderMiddleware,
97
+ securityHeaders
98
+ };
@@ -0,0 +1,8 @@
1
+ export { AccessControlConfig, AccessRule, AuthConfig, ConditionFlags, DirectoryConfig, ErrorDocumentConfig, HeaderConfig, HonoContext, HtaccessConfig, RedirectConfig, RequireConfig, RewriteCondition, RewriteFlags, RewriteResult, RewriteRuleConfig, VariableContext } from './types.js';
2
+ export { parseHtaccess } from './parser.js';
3
+ export { createRewriteMiddleware, evaluateConditions } from './rewrite.js';
4
+ export { corsHeaders, createHeaderMiddleware, securityHeaders } from './headers.js';
5
+ export { createErrorDocumentMiddleware } from './error-document.js';
6
+ export { createAccessControlMiddleware } from './access-control.js';
7
+ export { applyRewriteFlags, buildVariableContext, expandVariables } from './utils.js';
8
+ import 'hono';
@@ -0,0 +1,20 @@
1
+ export * from "./types.js";
2
+ import { parseHtaccess } from "./parser.js";
3
+ import { createRewriteMiddleware, evaluateConditions } from "./rewrite.js";
4
+ import { createHeaderMiddleware, securityHeaders, corsHeaders } from "./headers.js";
5
+ import { createErrorDocumentMiddleware } from "./error-document.js";
6
+ import { createAccessControlMiddleware } from "./access-control.js";
7
+ import { buildVariableContext, expandVariables, applyRewriteFlags } from "./utils.js";
8
+ export {
9
+ applyRewriteFlags,
10
+ buildVariableContext,
11
+ corsHeaders,
12
+ createAccessControlMiddleware,
13
+ createErrorDocumentMiddleware,
14
+ createHeaderMiddleware,
15
+ createRewriteMiddleware,
16
+ evaluateConditions,
17
+ expandVariables,
18
+ parseHtaccess,
19
+ securityHeaders
20
+ };
@@ -0,0 +1,9 @@
1
+ import { DirectoryConfig } from './types.js';
2
+ import 'hono';
3
+
4
+ /**
5
+ * Main parser for .htaccess files
6
+ */
7
+ declare function parseHtaccess(content: string): DirectoryConfig;
8
+
9
+ export { parseHtaccess };
@@ -0,0 +1,365 @@
1
+ function tokenize(line) {
2
+ const tokens = [];
3
+ let current = "";
4
+ let inQuotes = false;
5
+ let quoteChar = "";
6
+ let escaped = false;
7
+ for (let i = 0; i < line.length; i++) {
8
+ const char = line[i];
9
+ if (escaped) {
10
+ current += char;
11
+ escaped = false;
12
+ continue;
13
+ }
14
+ if (char === "\\") {
15
+ escaped = true;
16
+ continue;
17
+ }
18
+ if ((char === '"' || char === "'") && !inQuotes) {
19
+ inQuotes = true;
20
+ quoteChar = char;
21
+ continue;
22
+ }
23
+ if (char === quoteChar && inQuotes) {
24
+ inQuotes = false;
25
+ tokens.push(current);
26
+ current = "";
27
+ quoteChar = "";
28
+ continue;
29
+ }
30
+ if (/\s/.test(char) && !inQuotes) {
31
+ if (current) {
32
+ tokens.push(current);
33
+ current = "";
34
+ }
35
+ continue;
36
+ }
37
+ current += char;
38
+ }
39
+ if (current) tokens.push(current);
40
+ return tokens;
41
+ }
42
+ function parseRewriteFlags(flagString) {
43
+ const flags = {};
44
+ const cleaned = flagString.replace(/^\[|\]$/g, "").trim();
45
+ if (!cleaned) return flags;
46
+ const parts = cleaned.includes(",") ? cleaned.split(",") : cleaned.split(/\s+/);
47
+ for (const part of parts) {
48
+ const trimmed = part.trim();
49
+ if (!trimmed) continue;
50
+ if (trimmed === "L") flags.last = true;
51
+ else if (trimmed === "NC") flags.noCase = true;
52
+ else if (trimmed === "QSA") flags.qsAppend = true;
53
+ else if (trimmed === "QSD") flags.qsDiscard = true;
54
+ else if (trimmed === "NE") flags.noEscape = true;
55
+ else if (trimmed === "F") flags.forbidden = true;
56
+ else if (trimmed === "G") flags.gone = true;
57
+ else if (trimmed === "R" || trimmed.startsWith("R=")) {
58
+ const match = trimmed.match(/^R(?:=(\d+))?$/);
59
+ flags.redirect = match?.[1] ? Number.parseInt(match[1], 10) : 302;
60
+ }
61
+ }
62
+ return flags;
63
+ }
64
+ function parseConditionFlags(flagString) {
65
+ const flags = {};
66
+ const cleaned = flagString.replace(/^\[|\]$/g, "");
67
+ if (!cleaned) return flags;
68
+ const parts = cleaned.split(",");
69
+ for (const part of parts) {
70
+ const trimmed = part.trim();
71
+ if (trimmed === "NC") flags.noCase = true;
72
+ else if (trimmed === "OR") flags.or = true;
73
+ }
74
+ return flags;
75
+ }
76
+ function parseRewriteCond(tokens) {
77
+ if (tokens.length < 3) {
78
+ throw new Error("RewriteCond requires at least 2 arguments");
79
+ }
80
+ return {
81
+ testString: tokens[1],
82
+ pattern: tokens[2],
83
+ flags: parseConditionFlags(tokens[3] || "")
84
+ };
85
+ }
86
+ function parseRewriteRule(tokens) {
87
+ if (tokens.length < 3) {
88
+ throw new Error("RewriteRule requires at least 2 arguments");
89
+ }
90
+ return {
91
+ type: "rewrite",
92
+ pattern: tokens[1],
93
+ target: tokens[2],
94
+ flags: parseRewriteFlags(tokens[3] || ""),
95
+ conditions: []
96
+ // Will be populated by main parser
97
+ };
98
+ }
99
+ function parseErrorDocument(tokens) {
100
+ if (tokens.length < 3) {
101
+ throw new Error("ErrorDocument requires status code and target");
102
+ }
103
+ const statusCode = Number.parseInt(tokens[1], 10);
104
+ if (Number.isNaN(statusCode) || statusCode < 100 || statusCode >= 600) {
105
+ throw new Error(`Invalid status code: ${tokens[1]}`);
106
+ }
107
+ return {
108
+ statusCode,
109
+ target: tokens[2]
110
+ };
111
+ }
112
+ function parseHeader(tokens) {
113
+ if (tokens.length < 3) {
114
+ throw new Error("Header requires action and name");
115
+ }
116
+ const action = tokens[1].toLowerCase();
117
+ if (!["set", "append", "unset"].includes(action)) {
118
+ throw new Error(`Invalid header action: ${action}`);
119
+ }
120
+ return {
121
+ action,
122
+ name: tokens[2],
123
+ value: tokens[3] || void 0
124
+ };
125
+ }
126
+ function parseRedirect(tokens, directive) {
127
+ let code = 302;
128
+ let sourceIdx = 1;
129
+ if (directive === "RedirectPermanent") {
130
+ code = 301;
131
+ } else if (directive === "RedirectTemp") {
132
+ code = 302;
133
+ } else if (directive === "Redirect") {
134
+ if (tokens.length === 4) {
135
+ const maybeCode = Number.parseInt(tokens[1], 10);
136
+ code = !Number.isNaN(maybeCode) ? maybeCode : 302;
137
+ sourceIdx = 2;
138
+ }
139
+ }
140
+ if (tokens.length < sourceIdx + 2) {
141
+ throw new Error(`${directive} requires source and target paths`);
142
+ }
143
+ return {
144
+ type: "redirect",
145
+ code,
146
+ source: tokens[sourceIdx],
147
+ target: tokens[sourceIdx + 1]
148
+ };
149
+ }
150
+ function parseAuthType(tokens) {
151
+ if (tokens.length < 2) {
152
+ throw new Error("AuthType requires a type (Basic or Digest)");
153
+ }
154
+ const type = tokens[1];
155
+ if (type !== "Basic" && type !== "Digest") {
156
+ throw new Error(`Invalid AuthType: ${type}. Must be Basic or Digest`);
157
+ }
158
+ return type;
159
+ }
160
+ function parseRequire(tokens) {
161
+ if (tokens.length < 2) {
162
+ throw new Error("Require directive requires at least one argument");
163
+ }
164
+ const type = tokens[1];
165
+ if (type === "valid-user") {
166
+ return { type: "valid-user" };
167
+ }
168
+ if (type === "all") {
169
+ if (tokens.length < 3) {
170
+ throw new Error("Require all must specify granted or denied");
171
+ }
172
+ const granted = tokens[2] === "granted";
173
+ return { type: "all", granted };
174
+ }
175
+ if (type === "user") {
176
+ if (tokens.length < 3) {
177
+ throw new Error("Require user must specify at least one username");
178
+ }
179
+ return { type: "user", value: tokens.slice(2) };
180
+ }
181
+ if (type === "group") {
182
+ if (tokens.length < 3) {
183
+ throw new Error("Require group must specify at least one group name");
184
+ }
185
+ return { type: "group", value: tokens.slice(2) };
186
+ }
187
+ if (type === "ip") {
188
+ if (tokens.length < 3) {
189
+ throw new Error("Require ip must specify at least one IP or CIDR");
190
+ }
191
+ return { type: "ip", value: tokens.slice(2) };
192
+ }
193
+ if (type === "host") {
194
+ if (tokens.length < 3) {
195
+ throw new Error("Require host must specify at least one hostname");
196
+ }
197
+ return { type: "host", value: tokens.slice(2) };
198
+ }
199
+ throw new Error(`Unknown Require type: ${type}`);
200
+ }
201
+ function parseAccessRule(tokens) {
202
+ if (tokens.length < 3) {
203
+ throw new Error(`${tokens[0]} directive requires at least one argument`);
204
+ }
205
+ const fromKeyword = tokens[1];
206
+ if (fromKeyword !== "from") {
207
+ throw new Error(`${tokens[0]} directive must use 'from' keyword`);
208
+ }
209
+ const target = tokens[2];
210
+ if (target === "all") {
211
+ return { type: "all" };
212
+ }
213
+ if (target.startsWith("env=")) {
214
+ return { type: "env", value: target.slice(4) };
215
+ }
216
+ const isIP = /^[\d./]+$/.test(target) || target.includes(":");
217
+ if (isIP) {
218
+ return { type: "ip", value: tokens.slice(2) };
219
+ }
220
+ return { type: "host", value: tokens.slice(2) };
221
+ }
222
+ function parseHtaccess(content) {
223
+ const config = {
224
+ rewriteRules: [],
225
+ redirects: [],
226
+ errorDocuments: [],
227
+ headers: [],
228
+ authConfig: void 0,
229
+ accessControl: void 0
230
+ };
231
+ const lines = content.split("\n");
232
+ const pendingConditions = [];
233
+ let multiLineBuffer = "";
234
+ for (let i = 0; i < lines.length; i++) {
235
+ let line = lines[i].trim();
236
+ if (!line || line.startsWith("#")) continue;
237
+ if (line.endsWith("\\")) {
238
+ multiLineBuffer += line.slice(0, -1) + " ";
239
+ continue;
240
+ }
241
+ if (multiLineBuffer) {
242
+ line = multiLineBuffer + line;
243
+ multiLineBuffer = "";
244
+ }
245
+ const tokens = tokenize(line);
246
+ if (tokens.length === 0) continue;
247
+ const directive = tokens[0];
248
+ try {
249
+ switch (directive) {
250
+ case "RewriteCond":
251
+ pendingConditions.push(parseRewriteCond(tokens));
252
+ break;
253
+ case "RewriteRule": {
254
+ const rule = parseRewriteRule(tokens);
255
+ rule.conditions = [...pendingConditions];
256
+ config.rewriteRules.push(rule);
257
+ pendingConditions.length = 0;
258
+ break;
259
+ }
260
+ case "Redirect":
261
+ case "RedirectPermanent":
262
+ case "RedirectTemp":
263
+ config.redirects.push(parseRedirect(tokens, directive));
264
+ pendingConditions.length = 0;
265
+ break;
266
+ case "ErrorDocument":
267
+ config.errorDocuments.push(parseErrorDocument(tokens));
268
+ break;
269
+ case "Header":
270
+ config.headers.push(parseHeader(tokens));
271
+ break;
272
+ case "AuthType":
273
+ if (!config.authConfig) {
274
+ config.authConfig = {};
275
+ }
276
+ config.authConfig.authType = parseAuthType(tokens);
277
+ break;
278
+ case "AuthName":
279
+ if (!config.authConfig) {
280
+ config.authConfig = {};
281
+ }
282
+ if (tokens.length < 2) {
283
+ throw new Error("AuthName requires a realm name");
284
+ }
285
+ config.authConfig.authName = tokens.slice(1).join(" ");
286
+ break;
287
+ case "AuthUserFile":
288
+ if (!config.authConfig) {
289
+ config.authConfig = {};
290
+ }
291
+ if (tokens.length < 2) {
292
+ throw new Error("AuthUserFile requires a file path");
293
+ }
294
+ config.authConfig.authUserFile = tokens[1];
295
+ break;
296
+ case "AuthGroupFile":
297
+ if (!config.authConfig) {
298
+ config.authConfig = {};
299
+ }
300
+ if (tokens.length < 2) {
301
+ throw new Error("AuthGroupFile requires a file path");
302
+ }
303
+ config.authConfig.authGroupFile = tokens[1];
304
+ break;
305
+ case "AuthDigestProvider":
306
+ if (!config.authConfig) {
307
+ config.authConfig = {};
308
+ }
309
+ if (tokens.length < 2) {
310
+ throw new Error("AuthDigestProvider requires a provider name");
311
+ }
312
+ config.authConfig.authDigestProvider = tokens[1];
313
+ break;
314
+ case "Require":
315
+ if (!config.authConfig) {
316
+ config.authConfig = {};
317
+ }
318
+ if (!config.authConfig.require) {
319
+ config.authConfig.require = [];
320
+ }
321
+ config.authConfig.require.push(parseRequire(tokens));
322
+ break;
323
+ case "Order": {
324
+ if (!config.accessControl) {
325
+ config.accessControl = { allow: [], deny: [] };
326
+ }
327
+ if (tokens.length < 2) {
328
+ throw new Error("Order directive requires an argument");
329
+ }
330
+ const orderValue = tokens[1].toLowerCase();
331
+ if (orderValue !== "allow,deny" && orderValue !== "deny,allow" && orderValue !== "mutual-failure") {
332
+ throw new Error(`Invalid Order value: ${tokens[1]}`);
333
+ }
334
+ config.accessControl.order = orderValue;
335
+ break;
336
+ }
337
+ case "Allow":
338
+ if (!config.accessControl) {
339
+ config.accessControl = { allow: [], deny: [] };
340
+ }
341
+ config.accessControl.allow.push(parseAccessRule(tokens));
342
+ break;
343
+ case "Deny":
344
+ if (!config.accessControl) {
345
+ config.accessControl = { allow: [], deny: [] };
346
+ }
347
+ config.accessControl.deny.push(parseAccessRule(tokens));
348
+ break;
349
+ default:
350
+ break;
351
+ }
352
+ } catch (error) {
353
+ throw new Error(`Parse error at line ${i + 1}: ${error.message}`, {
354
+ cause: error
355
+ });
356
+ }
357
+ }
358
+ if (config.accessControl && !config.accessControl.order) {
359
+ config.accessControl.order = "allow,deny";
360
+ }
361
+ return config;
362
+ }
363
+ export {
364
+ parseHtaccess
365
+ };
@@ -0,0 +1,13 @@
1
+ import { Context } from 'hono';
2
+ import { RewriteRuleConfig, RewriteCondition } from './types.js';
3
+
4
+ /**
5
+ * Evaluate all conditions for a rewrite rule
6
+ */
7
+ declare function evaluateConditions(conditions: RewriteCondition[], context: Context): boolean;
8
+ /**
9
+ * Create middleware for rewrite rules
10
+ */
11
+ declare function createRewriteMiddleware(rules: RewriteRuleConfig[], basePath: string): (c: Context, next: () => Promise<void>) => Promise<Response | void>;
12
+
13
+ export { createRewriteMiddleware, evaluateConditions };
@@ -0,0 +1,69 @@
1
+ import {
2
+ applyRewriteFlags,
3
+ buildVariableContext,
4
+ expandVariables,
5
+ testCondition
6
+ } from "./utils.js";
7
+ function evaluateConditions(conditions, context) {
8
+ if (conditions.length === 0) return true;
9
+ let result = true;
10
+ let nextIsOr = false;
11
+ for (const condition of conditions) {
12
+ const varContext = buildVariableContext(context);
13
+ const match = testCondition(condition, varContext);
14
+ if (nextIsOr) {
15
+ result = result || match;
16
+ nextIsOr = condition.flags.or || false;
17
+ } else {
18
+ result = result && match;
19
+ nextIsOr = condition.flags.or || false;
20
+ }
21
+ }
22
+ return result;
23
+ }
24
+ function createRewriteMiddleware(rules, basePath) {
25
+ return async (c, next) => {
26
+ const relPath = c.req.path.replace(basePath, "") || "/";
27
+ for (const rule of rules) {
28
+ if (!evaluateConditions(rule.conditions, c)) {
29
+ continue;
30
+ }
31
+ const pattern = new RegExp(rule.pattern, rule.flags.noCase ? "i" : "");
32
+ const match = relPath.match(pattern);
33
+ if (!match) continue;
34
+ let target = rule.target;
35
+ for (let i = 0; i < match.length; i++) {
36
+ target = target.replace(new RegExp(`\\$${i}`, "g"), match[i] || "");
37
+ }
38
+ const varContext = buildVariableContext(c);
39
+ target = expandVariables(target, varContext);
40
+ if (target !== "-" && !target.startsWith("/") && !target.startsWith("http://") && !target.startsWith("https://")) {
41
+ target = basePath === "" ? `/${target}` : `${basePath}/${target}`;
42
+ }
43
+ const result = applyRewriteFlags(target, rule.flags, c);
44
+ switch (result.type) {
45
+ case "redirect":
46
+ return new Response(null, {
47
+ status: result.status,
48
+ headers: {
49
+ Location: result.url
50
+ }
51
+ });
52
+ case "forbidden":
53
+ return c.text("Forbidden", 403);
54
+ case "gone":
55
+ return c.text("Gone", 410);
56
+ case "rewrite":
57
+ break;
58
+ }
59
+ if (rule.flags.last) {
60
+ break;
61
+ }
62
+ }
63
+ await next();
64
+ };
65
+ }
66
+ export {
67
+ createRewriteMiddleware,
68
+ evaluateConditions
69
+ };