@tknf/matchbox 0.2.4 → 0.2.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.
- package/dist/cgi.js +4 -136
- package/dist/html.js +2 -3
- package/dist/index.js +1 -438
- package/dist/plugin.js +3 -4
- package/dist/with-defaults.js +3 -366
- package/package.json +1 -1
package/dist/cgi.js
CHANGED
|
@@ -1,143 +1,11 @@
|
|
|
1
|
-
// src/cgi.ts
|
|
2
1
|
import { Hono } from "hono";
|
|
3
2
|
import { basicAuth } from "hono/basic-auth";
|
|
4
3
|
import { getCookie, setCookie } from "hono/cookie";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
var package_default = {
|
|
8
|
-
name: "@tknf/matchbox",
|
|
9
|
-
version: "0.2.4",
|
|
10
|
-
description: "A Simple Web Server Framework",
|
|
11
|
-
keywords: [
|
|
12
|
-
"cgi",
|
|
13
|
-
"framework",
|
|
14
|
-
"hono",
|
|
15
|
-
"server",
|
|
16
|
-
"web"
|
|
17
|
-
],
|
|
18
|
-
license: "MIT",
|
|
19
|
-
author: "tknf <dev@tknf.net>",
|
|
20
|
-
repository: {
|
|
21
|
-
url: "https://github.com/tknf/matchbox.git"
|
|
22
|
-
},
|
|
23
|
-
files: [
|
|
24
|
-
"dist"
|
|
25
|
-
],
|
|
26
|
-
type: "module",
|
|
27
|
-
module: "dist/index.js",
|
|
28
|
-
types: "dist/index.d.ts",
|
|
29
|
-
exports: {
|
|
30
|
-
".": {
|
|
31
|
-
import: {
|
|
32
|
-
types: "./dist/index.d.ts",
|
|
33
|
-
import: "./dist/index.js"
|
|
34
|
-
}
|
|
35
|
-
},
|
|
36
|
-
"./plugin": {
|
|
37
|
-
import: {
|
|
38
|
-
types: "./dist/plugin.d.ts",
|
|
39
|
-
import: "./dist/plugin.js"
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
},
|
|
43
|
-
publishConfig: {
|
|
44
|
-
access: "public",
|
|
45
|
-
registry: "https://registry.npmjs.org/"
|
|
46
|
-
},
|
|
47
|
-
scripts: {
|
|
48
|
-
test: "vitest run",
|
|
49
|
-
"test:coverage": "vitest run --coverage",
|
|
50
|
-
"lint:check": "oxlint",
|
|
51
|
-
"lint:fix": "oxlint --fix",
|
|
52
|
-
"format:check": "oxfmt --check",
|
|
53
|
-
"format:write": "oxfmt",
|
|
54
|
-
watch: "tsup --watch",
|
|
55
|
-
build: "tsup"
|
|
56
|
-
},
|
|
57
|
-
dependencies: {
|
|
58
|
-
glob: "^13.0.0"
|
|
59
|
-
},
|
|
60
|
-
devDependencies: {
|
|
61
|
-
"@types/node": "^25.0.3",
|
|
62
|
-
"@vitest/coverage-v8": "^4.0.16",
|
|
63
|
-
hono: "^4.11.1",
|
|
64
|
-
oxfmt: "^0.20.0",
|
|
65
|
-
oxlint: "^1.35.0",
|
|
66
|
-
tsup: "^8.5.1",
|
|
67
|
-
typescript: "^5.9.3",
|
|
68
|
-
vite: "^7.3.0",
|
|
69
|
-
vitest: "^4.0.16"
|
|
70
|
-
},
|
|
71
|
-
peerDependencies: {
|
|
72
|
-
hono: "*"
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
// src/html.ts
|
|
77
|
-
import { html } from "hono/html";
|
|
78
|
-
var generateCgiInfo = ({
|
|
79
|
-
$_SERVER,
|
|
80
|
-
$_SESSION,
|
|
81
|
-
$_REQUEST,
|
|
82
|
-
config
|
|
83
|
-
}) => {
|
|
84
|
-
return () => {
|
|
85
|
-
const infoSection = ({ title, data }) => {
|
|
86
|
-
return html`
|
|
87
|
-
<div style="margin-bottom: 20px; width: 100%; max-width: 900px;">
|
|
88
|
-
<h2 style="background: #ccccff; color: #000; padding: 5px 10px; margin: 0; font-size: 1.2em; border: 1px solid #000; font-family: 'MS PGothic', sans-serif;">${title}</h2>
|
|
89
|
-
<table style="width: 100%; border-collapse: collapse; border: 1px solid #000; table-layout: fixed; font-family: 'MS PGothic', sans-serif;">
|
|
90
|
-
${Object.entries(data).map(
|
|
91
|
-
([key, val], i) => html`
|
|
92
|
-
<tr style="background: ${i % 2 === 0 ? "#f0f0ff" : "#ffffff"}">
|
|
93
|
-
<td style="padding: 3px 10px; border: 1px solid #000; font-weight: bold; width: 30%; word-break: break-all;">${key}</td>
|
|
94
|
-
<td style="padding: 3px 10px; border: 1px solid #000; width: 70%; word-break: break-all;">
|
|
95
|
-
${typeof val === "object" ? JSON.stringify(val, null, 2) : String(val)}
|
|
96
|
-
</td>
|
|
97
|
-
</tr>
|
|
98
|
-
`
|
|
99
|
-
)}
|
|
100
|
-
</table>
|
|
101
|
-
</div>
|
|
102
|
-
`;
|
|
103
|
-
};
|
|
104
|
-
return html`
|
|
105
|
-
<div style="background: #ffffff; color: #333333; font-family: 'MS PGothic', sans-serif; padding: 20px; display: flex; flex-direction: column; align-items: center;">
|
|
106
|
-
<h1 style="color: #000000; border-bottom: 3px double #000000; padding-bottom: 10px; margin-bottom: 30px;">Matchbox CGI Version Information</h1>
|
|
107
|
-
<div style="width: 100%; max-width: 900px; padding: 15px; background: #ffffcc; border: 1px dashed #000000; margin-bottom: 20px; text-align: center;">
|
|
108
|
-
<strong>Server Software:</strong> Matchbox Engine on ${typeof process !== "undefined" ? "Node.js/Bun" : "Edge"}
|
|
109
|
-
</div>
|
|
110
|
-
${infoSection({ title: "$_SERVER (Environment)", data: $_SERVER })}
|
|
111
|
-
${infoSection({ title: "$_SESSION", data: $_SESSION })}
|
|
112
|
-
${infoSection({ title: "$_REQUEST", data: $_REQUEST })}
|
|
113
|
-
${infoSection({ title: "Site Configuration", data: config })}
|
|
114
|
-
<div style="margin-top: 40px; font-size: 0.9em; font-style: italic; border-top: 1px solid #ccc; width: 100%; max-width: 900px; text-align: right; padding-top: 10px;">
|
|
115
|
-
Generated by Matchbox Framework - Ignition for Hono
|
|
116
|
-
</div>
|
|
117
|
-
</div>
|
|
118
|
-
`;
|
|
119
|
-
};
|
|
120
|
-
};
|
|
121
|
-
var generateCgiError = ({
|
|
122
|
-
error,
|
|
123
|
-
$_SERVER
|
|
124
|
-
}) => {
|
|
125
|
-
return html`
|
|
126
|
-
<div style="padding:2rem; background:#fffafa; border:5px double #cc0000; font-family: 'MS PGothic', sans-serif;">
|
|
127
|
-
<h1 style="color:#cc0000; border-bottom: 2px solid #cc0000; padding-bottom: 5px;">Matchbox: Runtime Exception</h1>
|
|
128
|
-
<p><strong>Fatal Error:</strong> ${error.message}</p>
|
|
129
|
-
<pre style="background:#f0f0f0; padding:1rem; border:1px inset #ccc; overflow: auto;">${error.stack}</pre>
|
|
130
|
-
<hr style="border: 0; border-top: 1px solid #cc0000;" />
|
|
131
|
-
<div style="text-align: right; font-size: 0.8em;">Matchbox CGI Engine Server at ${$_SERVER.REMOTE_ADDR}</div>
|
|
132
|
-
</div>
|
|
133
|
-
`;
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
// src/cgi.ts
|
|
137
|
-
var isRedirectObject = (obj) => {
|
|
4
|
+
import { generateCgiError, generateCgiInfo } from "./html.js";
|
|
5
|
+
const isRedirectObject = (obj) => {
|
|
138
6
|
return obj && obj.__type === "redirect" && typeof obj.url === "string";
|
|
139
7
|
};
|
|
140
|
-
|
|
8
|
+
const createCgiWithPages = (pages, siteConfig = {}, authMap = {}, rewriteMap = {}, options = {}) => {
|
|
141
9
|
const app = new Hono();
|
|
142
10
|
const SESS_KEY = options.sessionCookie?.name || "_SESSION_ID";
|
|
143
11
|
if (options.middleware && options.middleware.length > 0) {
|
|
@@ -301,7 +169,7 @@ var createCgiWithPages = (pages, siteConfig = {}, authMap = {}, rewriteMap = {},
|
|
|
301
169
|
}
|
|
302
170
|
},
|
|
303
171
|
get_version: () => {
|
|
304
|
-
return `MatchboxCGI/v${
|
|
172
|
+
return `MatchboxCGI/v${"0.2.5"}`;
|
|
305
173
|
},
|
|
306
174
|
/**
|
|
307
175
|
* Returns information about all loaded CGI modules
|
package/dist/html.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
// src/html.ts
|
|
2
1
|
import { html } from "hono/html";
|
|
3
|
-
|
|
2
|
+
const generateCgiInfo = ({
|
|
4
3
|
$_SERVER,
|
|
5
4
|
$_SESSION,
|
|
6
5
|
$_REQUEST,
|
|
@@ -43,7 +42,7 @@ var generateCgiInfo = ({
|
|
|
43
42
|
`;
|
|
44
43
|
};
|
|
45
44
|
};
|
|
46
|
-
|
|
45
|
+
const generateCgiError = ({
|
|
47
46
|
error,
|
|
48
47
|
$_SERVER
|
|
49
48
|
}) => {
|
package/dist/index.js
CHANGED
|
@@ -1,441 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
import { Hono } from "hono";
|
|
3
|
-
import { basicAuth } from "hono/basic-auth";
|
|
4
|
-
import { getCookie, setCookie } from "hono/cookie";
|
|
5
|
-
|
|
6
|
-
// package.json
|
|
7
|
-
var package_default = {
|
|
8
|
-
name: "@tknf/matchbox",
|
|
9
|
-
version: "0.2.4",
|
|
10
|
-
description: "A Simple Web Server Framework",
|
|
11
|
-
keywords: [
|
|
12
|
-
"cgi",
|
|
13
|
-
"framework",
|
|
14
|
-
"hono",
|
|
15
|
-
"server",
|
|
16
|
-
"web"
|
|
17
|
-
],
|
|
18
|
-
license: "MIT",
|
|
19
|
-
author: "tknf <dev@tknf.net>",
|
|
20
|
-
repository: {
|
|
21
|
-
url: "https://github.com/tknf/matchbox.git"
|
|
22
|
-
},
|
|
23
|
-
files: [
|
|
24
|
-
"dist"
|
|
25
|
-
],
|
|
26
|
-
type: "module",
|
|
27
|
-
module: "dist/index.js",
|
|
28
|
-
types: "dist/index.d.ts",
|
|
29
|
-
exports: {
|
|
30
|
-
".": {
|
|
31
|
-
import: {
|
|
32
|
-
types: "./dist/index.d.ts",
|
|
33
|
-
import: "./dist/index.js"
|
|
34
|
-
}
|
|
35
|
-
},
|
|
36
|
-
"./plugin": {
|
|
37
|
-
import: {
|
|
38
|
-
types: "./dist/plugin.d.ts",
|
|
39
|
-
import: "./dist/plugin.js"
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
},
|
|
43
|
-
publishConfig: {
|
|
44
|
-
access: "public",
|
|
45
|
-
registry: "https://registry.npmjs.org/"
|
|
46
|
-
},
|
|
47
|
-
scripts: {
|
|
48
|
-
test: "vitest run",
|
|
49
|
-
"test:coverage": "vitest run --coverage",
|
|
50
|
-
"lint:check": "oxlint",
|
|
51
|
-
"lint:fix": "oxlint --fix",
|
|
52
|
-
"format:check": "oxfmt --check",
|
|
53
|
-
"format:write": "oxfmt",
|
|
54
|
-
watch: "tsup --watch",
|
|
55
|
-
build: "tsup"
|
|
56
|
-
},
|
|
57
|
-
dependencies: {
|
|
58
|
-
glob: "^13.0.0"
|
|
59
|
-
},
|
|
60
|
-
devDependencies: {
|
|
61
|
-
"@types/node": "^25.0.3",
|
|
62
|
-
"@vitest/coverage-v8": "^4.0.16",
|
|
63
|
-
hono: "^4.11.1",
|
|
64
|
-
oxfmt: "^0.20.0",
|
|
65
|
-
oxlint: "^1.35.0",
|
|
66
|
-
tsup: "^8.5.1",
|
|
67
|
-
typescript: "^5.9.3",
|
|
68
|
-
vite: "^7.3.0",
|
|
69
|
-
vitest: "^4.0.16"
|
|
70
|
-
},
|
|
71
|
-
peerDependencies: {
|
|
72
|
-
hono: "*"
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
// src/html.ts
|
|
77
|
-
import { html } from "hono/html";
|
|
78
|
-
var generateCgiInfo = ({
|
|
79
|
-
$_SERVER,
|
|
80
|
-
$_SESSION,
|
|
81
|
-
$_REQUEST,
|
|
82
|
-
config
|
|
83
|
-
}) => {
|
|
84
|
-
return () => {
|
|
85
|
-
const infoSection = ({ title, data }) => {
|
|
86
|
-
return html`
|
|
87
|
-
<div style="margin-bottom: 20px; width: 100%; max-width: 900px;">
|
|
88
|
-
<h2 style="background: #ccccff; color: #000; padding: 5px 10px; margin: 0; font-size: 1.2em; border: 1px solid #000; font-family: 'MS PGothic', sans-serif;">${title}</h2>
|
|
89
|
-
<table style="width: 100%; border-collapse: collapse; border: 1px solid #000; table-layout: fixed; font-family: 'MS PGothic', sans-serif;">
|
|
90
|
-
${Object.entries(data).map(
|
|
91
|
-
([key, val], i) => html`
|
|
92
|
-
<tr style="background: ${i % 2 === 0 ? "#f0f0ff" : "#ffffff"}">
|
|
93
|
-
<td style="padding: 3px 10px; border: 1px solid #000; font-weight: bold; width: 30%; word-break: break-all;">${key}</td>
|
|
94
|
-
<td style="padding: 3px 10px; border: 1px solid #000; width: 70%; word-break: break-all;">
|
|
95
|
-
${typeof val === "object" ? JSON.stringify(val, null, 2) : String(val)}
|
|
96
|
-
</td>
|
|
97
|
-
</tr>
|
|
98
|
-
`
|
|
99
|
-
)}
|
|
100
|
-
</table>
|
|
101
|
-
</div>
|
|
102
|
-
`;
|
|
103
|
-
};
|
|
104
|
-
return html`
|
|
105
|
-
<div style="background: #ffffff; color: #333333; font-family: 'MS PGothic', sans-serif; padding: 20px; display: flex; flex-direction: column; align-items: center;">
|
|
106
|
-
<h1 style="color: #000000; border-bottom: 3px double #000000; padding-bottom: 10px; margin-bottom: 30px;">Matchbox CGI Version Information</h1>
|
|
107
|
-
<div style="width: 100%; max-width: 900px; padding: 15px; background: #ffffcc; border: 1px dashed #000000; margin-bottom: 20px; text-align: center;">
|
|
108
|
-
<strong>Server Software:</strong> Matchbox Engine on ${typeof process !== "undefined" ? "Node.js/Bun" : "Edge"}
|
|
109
|
-
</div>
|
|
110
|
-
${infoSection({ title: "$_SERVER (Environment)", data: $_SERVER })}
|
|
111
|
-
${infoSection({ title: "$_SESSION", data: $_SESSION })}
|
|
112
|
-
${infoSection({ title: "$_REQUEST", data: $_REQUEST })}
|
|
113
|
-
${infoSection({ title: "Site Configuration", data: config })}
|
|
114
|
-
<div style="margin-top: 40px; font-size: 0.9em; font-style: italic; border-top: 1px solid #ccc; width: 100%; max-width: 900px; text-align: right; padding-top: 10px;">
|
|
115
|
-
Generated by Matchbox Framework - Ignition for Hono
|
|
116
|
-
</div>
|
|
117
|
-
</div>
|
|
118
|
-
`;
|
|
119
|
-
};
|
|
120
|
-
};
|
|
121
|
-
var generateCgiError = ({
|
|
122
|
-
error,
|
|
123
|
-
$_SERVER
|
|
124
|
-
}) => {
|
|
125
|
-
return html`
|
|
126
|
-
<div style="padding:2rem; background:#fffafa; border:5px double #cc0000; font-family: 'MS PGothic', sans-serif;">
|
|
127
|
-
<h1 style="color:#cc0000; border-bottom: 2px solid #cc0000; padding-bottom: 5px;">Matchbox: Runtime Exception</h1>
|
|
128
|
-
<p><strong>Fatal Error:</strong> ${error.message}</p>
|
|
129
|
-
<pre style="background:#f0f0f0; padding:1rem; border:1px inset #ccc; overflow: auto;">${error.stack}</pre>
|
|
130
|
-
<hr style="border: 0; border-top: 1px solid #cc0000;" />
|
|
131
|
-
<div style="text-align: right; font-size: 0.8em;">Matchbox CGI Engine Server at ${$_SERVER.REMOTE_ADDR}</div>
|
|
132
|
-
</div>
|
|
133
|
-
`;
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
// src/cgi.ts
|
|
137
|
-
var isRedirectObject = (obj) => {
|
|
138
|
-
return obj && obj.__type === "redirect" && typeof obj.url === "string";
|
|
139
|
-
};
|
|
140
|
-
var createCgiWithPages = (pages, siteConfig = {}, authMap = {}, rewriteMap = {}, options = {}) => {
|
|
141
|
-
const app = new Hono();
|
|
142
|
-
const SESS_KEY = options.sessionCookie?.name || "_SESSION_ID";
|
|
143
|
-
if (options.middleware && options.middleware.length > 0) {
|
|
144
|
-
for (const mw of options.middleware) {
|
|
145
|
-
app.use("*", async (c, next) => {
|
|
146
|
-
const result = await mw(c, next);
|
|
147
|
-
if (result instanceof Response) {
|
|
148
|
-
return result;
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
const protectedFiles = [".htaccess", ".htpasswd", ".htdigest", ".htgroup"];
|
|
154
|
-
app.use("*", async (c, next) => {
|
|
155
|
-
const path = c.req.path;
|
|
156
|
-
const lastSegment = path.slice(path.lastIndexOf("/") + 1);
|
|
157
|
-
if (protectedFiles.some((file) => lastSegment === file)) {
|
|
158
|
-
return c.text("Forbidden", 403);
|
|
159
|
-
}
|
|
160
|
-
await next();
|
|
161
|
-
});
|
|
162
|
-
if (options.enforceTrailingSlash) {
|
|
163
|
-
app.use("*", async (c, next) => {
|
|
164
|
-
const path = c.req.path;
|
|
165
|
-
if (!path.endsWith("/") && !path.includes(".")) {
|
|
166
|
-
return c.redirect(`${path}/`, 301);
|
|
167
|
-
}
|
|
168
|
-
await next();
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
Object.entries(rewriteMap).forEach(([dir, rules]) => {
|
|
172
|
-
const basePath = dir === "/" ? "" : dir.replace(/\/$/, "");
|
|
173
|
-
app.use(`${basePath}/*`, async (c, next) => {
|
|
174
|
-
const relPath = c.req.path.replace(basePath, "") || "/";
|
|
175
|
-
for (const rule of rules) {
|
|
176
|
-
if (rule.type === "redirect") {
|
|
177
|
-
if (relPath === rule.source) {
|
|
178
|
-
return c.redirect(
|
|
179
|
-
rule.target,
|
|
180
|
-
Number.parseInt(rule.code, 10) || 302
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
} else if (rule.type === "rewrite") {
|
|
184
|
-
const regex = new RegExp(rule.pattern);
|
|
185
|
-
if (regex.test(relPath)) {
|
|
186
|
-
const target = rule.target.startsWith("/") ? rule.target : `${basePath}/${rule.target}`;
|
|
187
|
-
if (rule.flags.includes("R")) {
|
|
188
|
-
const code = rule.flags.match(/R=(\d+)/)?.[1] || "302";
|
|
189
|
-
return c.redirect(target, Number.parseInt(code, 10));
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
await next();
|
|
195
|
-
});
|
|
196
|
-
});
|
|
197
|
-
Object.entries(authMap).forEach(([dir, content]) => {
|
|
198
|
-
const credentials = content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((line) => {
|
|
199
|
-
const [username, password] = line.split(":");
|
|
200
|
-
return { username, password };
|
|
201
|
-
});
|
|
202
|
-
if (credentials.length > 0) {
|
|
203
|
-
const authPath = dir === "/" ? "*" : `${dir.replace(/\/$/, "")}/*`;
|
|
204
|
-
app.use(authPath, async (c, next) => {
|
|
205
|
-
const handler = basicAuth({
|
|
206
|
-
verifyUser: (u, p) => credentials.some((cred) => cred.username === u && cred.password === p),
|
|
207
|
-
realm: "Restricted Area"
|
|
208
|
-
});
|
|
209
|
-
return handler(c, next);
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
});
|
|
213
|
-
pages.forEach(({ urlPath, dirPath, component }) => {
|
|
214
|
-
const routes = [urlPath, `${urlPath}/*`];
|
|
215
|
-
if (dirPath) {
|
|
216
|
-
routes.push(dirPath);
|
|
217
|
-
if (dirPath !== "/") {
|
|
218
|
-
routes.push(`${dirPath}/*`);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
routes.forEach((route) => {
|
|
222
|
-
app.all(route, async (c) => {
|
|
223
|
-
const $_GET = c.req.query();
|
|
224
|
-
const body = await c.req.parseBody({ all: true }).catch(() => ({}));
|
|
225
|
-
const $_POST = {};
|
|
226
|
-
const $_FILES = {};
|
|
227
|
-
for (const [key, value] of Object.entries(body)) {
|
|
228
|
-
if (value instanceof File || Array.isArray(value) && value[0] instanceof File) {
|
|
229
|
-
$_FILES[key] = value;
|
|
230
|
-
} else {
|
|
231
|
-
$_POST[key] = value;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
const $_COOKIE = getCookie(c);
|
|
235
|
-
const $_REQUEST = {
|
|
236
|
-
...$_COOKIE,
|
|
237
|
-
...$_GET,
|
|
238
|
-
...$_POST
|
|
239
|
-
};
|
|
240
|
-
const $_ENV = typeof process !== "undefined" && process.env ? process.env : c.env || {};
|
|
241
|
-
let $_SESSION = {};
|
|
242
|
-
const sRaw = getCookie(c, SESS_KEY);
|
|
243
|
-
if (sRaw) {
|
|
244
|
-
try {
|
|
245
|
-
$_SESSION = JSON.parse(decodeURIComponent(sRaw));
|
|
246
|
-
} catch {
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
let responseStatus = 200;
|
|
250
|
-
const responseHeaders = {
|
|
251
|
-
"Content-Type": "text/html; charset=utf-8"
|
|
252
|
-
};
|
|
253
|
-
const $_SERVER = {
|
|
254
|
-
...$_ENV,
|
|
255
|
-
REQUEST_METHOD: c.req.method,
|
|
256
|
-
REQUEST_URI: c.req.url,
|
|
257
|
-
REMOTE_ADDR: c.req.header("x-forwarded-for") || "127.0.0.1",
|
|
258
|
-
USER_AGENT: c.req.header("user-agent") || "",
|
|
259
|
-
SCRIPT_NAME: urlPath,
|
|
260
|
-
PATH_INFO: c.req.path.replace(urlPath, "") || "/",
|
|
261
|
-
QUERY_STRING: new URL(c.req.url).search.slice(1)
|
|
262
|
-
};
|
|
263
|
-
const cgiinfo = generateCgiInfo({
|
|
264
|
-
$_SERVER,
|
|
265
|
-
$_REQUEST,
|
|
266
|
-
$_SESSION,
|
|
267
|
-
config: siteConfig
|
|
268
|
-
});
|
|
269
|
-
const context = {
|
|
270
|
-
$_GET,
|
|
271
|
-
$_POST,
|
|
272
|
-
$_FILES,
|
|
273
|
-
$_REQUEST,
|
|
274
|
-
$_COOKIE,
|
|
275
|
-
$_ENV,
|
|
276
|
-
$_SERVER,
|
|
277
|
-
$_SESSION,
|
|
278
|
-
config: siteConfig,
|
|
279
|
-
c,
|
|
280
|
-
header: (name, value) => {
|
|
281
|
-
responseHeaders[name.toLocaleLowerCase()] = value;
|
|
282
|
-
},
|
|
283
|
-
status: (code) => {
|
|
284
|
-
responseStatus = code;
|
|
285
|
-
},
|
|
286
|
-
redirect: (url, status = 302) => {
|
|
287
|
-
return { __type: "redirect", url, status };
|
|
288
|
-
},
|
|
289
|
-
cgiinfo,
|
|
290
|
-
request_headers: () => {
|
|
291
|
-
return Object.fromEntries(c.req.raw.headers.entries());
|
|
292
|
-
},
|
|
293
|
-
response_headers: () => {
|
|
294
|
-
return responseHeaders;
|
|
295
|
-
},
|
|
296
|
-
log: (message) => {
|
|
297
|
-
if (options.logger) {
|
|
298
|
-
options.logger(message, "info");
|
|
299
|
-
} else {
|
|
300
|
-
console.log(`[CGI LOG] ${message}`);
|
|
301
|
-
}
|
|
302
|
-
},
|
|
303
|
-
get_version: () => {
|
|
304
|
-
return `MatchboxCGI/v${package_default.version}`;
|
|
305
|
-
},
|
|
306
|
-
/**
|
|
307
|
-
* Returns information about all loaded CGI modules
|
|
308
|
-
* @returns Array of module information containing urlPath and dirPath
|
|
309
|
-
*/
|
|
310
|
-
get_modules: () => {
|
|
311
|
-
return pages.map((page) => ({
|
|
312
|
-
urlPath: page.urlPath,
|
|
313
|
-
dirPath: page.dirPath
|
|
314
|
-
}));
|
|
315
|
-
}
|
|
316
|
-
};
|
|
317
|
-
try {
|
|
318
|
-
const result = await component(context);
|
|
319
|
-
const sessionValue = encodeURIComponent(JSON.stringify($_SESSION));
|
|
320
|
-
const sessionOptions = {
|
|
321
|
-
path: options.sessionCookie?.path || "/",
|
|
322
|
-
httpOnly: true,
|
|
323
|
-
sameSite: options.sessionCookie?.sameSite || "Lax"
|
|
324
|
-
};
|
|
325
|
-
if (options.sessionCookie?.secure !== void 0) {
|
|
326
|
-
sessionOptions.secure = options.sessionCookie.secure;
|
|
327
|
-
}
|
|
328
|
-
if (options.sessionCookie?.domain) {
|
|
329
|
-
sessionOptions.domain = options.sessionCookie.domain;
|
|
330
|
-
}
|
|
331
|
-
if (options.sessionCookie?.maxAge) {
|
|
332
|
-
sessionOptions.maxAge = options.sessionCookie.maxAge;
|
|
333
|
-
}
|
|
334
|
-
if (isRedirectObject(result)) {
|
|
335
|
-
setCookie(c, SESS_KEY, sessionValue, sessionOptions);
|
|
336
|
-
return c.redirect(result.url, result.status);
|
|
337
|
-
}
|
|
338
|
-
if (result instanceof Response) {
|
|
339
|
-
setCookie(c, SESS_KEY, sessionValue, sessionOptions);
|
|
340
|
-
return result;
|
|
341
|
-
}
|
|
342
|
-
setCookie(c, SESS_KEY, sessionValue, sessionOptions);
|
|
343
|
-
Object.entries(responseHeaders).forEach(([key, value]) => {
|
|
344
|
-
c.header(key, value);
|
|
345
|
-
});
|
|
346
|
-
const contentType = responseHeaders["content-type"];
|
|
347
|
-
if (contentType?.includes("application/json")) {
|
|
348
|
-
return c.json(
|
|
349
|
-
// biome-ignore lint/suspicious/noExplicitAny: to JSON response
|
|
350
|
-
result ?? { success: true },
|
|
351
|
-
responseStatus
|
|
352
|
-
);
|
|
353
|
-
}
|
|
354
|
-
return c.html(result, responseStatus);
|
|
355
|
-
} catch (error) {
|
|
356
|
-
return c.html(generateCgiError({ error, $_SERVER }), 500);
|
|
357
|
-
}
|
|
358
|
-
});
|
|
359
|
-
});
|
|
360
|
-
});
|
|
361
|
-
return app;
|
|
362
|
-
};
|
|
363
|
-
|
|
364
|
-
// src/with-defaults.ts
|
|
365
|
-
var loadPagesFromPublic = () => {
|
|
366
|
-
const modules = import.meta.glob("/public/**/*.cgi.{tsx,jsx}", {
|
|
367
|
-
eager: true
|
|
368
|
-
});
|
|
369
|
-
const htpasswds = import.meta.glob("/public/**/.htpasswd", {
|
|
370
|
-
eager: true,
|
|
371
|
-
query: "?raw",
|
|
372
|
-
import: "default"
|
|
373
|
-
});
|
|
374
|
-
const htaccessFiles = import.meta.glob("/public/**/.htaccess", {
|
|
375
|
-
eager: true,
|
|
376
|
-
query: "?raw",
|
|
377
|
-
import: "default"
|
|
378
|
-
});
|
|
379
|
-
void import.meta.glob("/public/**/.htdigest", {
|
|
380
|
-
eager: true,
|
|
381
|
-
query: "?raw",
|
|
382
|
-
import: "default"
|
|
383
|
-
});
|
|
384
|
-
void import.meta.glob("/public/**/.htgroup", {
|
|
385
|
-
eager: true,
|
|
386
|
-
query: "?raw",
|
|
387
|
-
import: "default"
|
|
388
|
-
});
|
|
389
|
-
const basePathRegex = /^\/public/;
|
|
390
|
-
const pages = Object.keys(modules).map((key) => {
|
|
391
|
-
const urlPath = key.replace(basePathRegex, "").replace(/.tsx$/, "").replace(/.jsx$/, "");
|
|
392
|
-
const isIndex = urlPath.endsWith("/index.cgi") || urlPath === "/index.cgi";
|
|
393
|
-
const dirPath = isIndex ? urlPath.replace(/\/index\.cgi$/, "/") : null;
|
|
394
|
-
return { urlPath, dirPath, component: modules[key].default };
|
|
395
|
-
});
|
|
396
|
-
const authMap = Object.keys(htpasswds).reduce(
|
|
397
|
-
(acc, key) => {
|
|
398
|
-
const dir = key.replace(basePathRegex, "").replace(/\.htpasswd$/, "") || "/";
|
|
399
|
-
acc[dir] = htpasswds[key];
|
|
400
|
-
return acc;
|
|
401
|
-
},
|
|
402
|
-
{}
|
|
403
|
-
);
|
|
404
|
-
const rewriteMap = Object.keys(htaccessFiles).reduce((acc, key) => {
|
|
405
|
-
const dir = key.replace(basePathRegex, "").replace(/\.htaccess$/, "") || "/";
|
|
406
|
-
const lines = htaccessFiles[key].split("\n");
|
|
407
|
-
const rules = lines.map((line) => {
|
|
408
|
-
const l = line.trim();
|
|
409
|
-
if (!l || l.startsWith("#")) return null;
|
|
410
|
-
const parts = l.split(/\s+/);
|
|
411
|
-
if (parts[0] === "RewriteRule") {
|
|
412
|
-
return {
|
|
413
|
-
type: "rewrite",
|
|
414
|
-
pattern: parts[1],
|
|
415
|
-
target: parts[2],
|
|
416
|
-
flags: parts[3] || ""
|
|
417
|
-
};
|
|
418
|
-
}
|
|
419
|
-
if (parts[0] === "Redirect") {
|
|
420
|
-
return {
|
|
421
|
-
type: "redirect",
|
|
422
|
-
code: parts[1],
|
|
423
|
-
source: parts[2],
|
|
424
|
-
target: parts[3]
|
|
425
|
-
};
|
|
426
|
-
}
|
|
427
|
-
return null;
|
|
428
|
-
}).filter(Boolean);
|
|
429
|
-
acc[dir] = rules;
|
|
430
|
-
return acc;
|
|
431
|
-
}, {});
|
|
432
|
-
return { pages, authMap, rewriteMap };
|
|
433
|
-
};
|
|
434
|
-
var createCgi = (options) => {
|
|
435
|
-
const resolvedConfig = typeof __MATCHBOX_CONFIG__ === "undefined" ? {} : __MATCHBOX_CONFIG__;
|
|
436
|
-
const { pages, authMap, rewriteMap } = loadPagesFromPublic();
|
|
437
|
-
return createCgiWithPages(pages, resolvedConfig, authMap, rewriteMap, options);
|
|
438
|
-
};
|
|
1
|
+
import { createCgi } from "./with-defaults.js";
|
|
439
2
|
export {
|
|
440
3
|
createCgi
|
|
441
4
|
};
|
package/dist/plugin.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
var MatchboxPlugin = (options = {}) => {
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
const MatchboxPlugin = (options = {}) => {
|
|
5
4
|
let viteConfig;
|
|
6
5
|
const siteConfig = options.config || {};
|
|
7
6
|
const publicDir = options.publicDir || "public";
|
package/dist/with-defaults.js
CHANGED
|
@@ -1,368 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import { basicAuth } from "hono/basic-auth";
|
|
4
|
-
import { getCookie, setCookie } from "hono/cookie";
|
|
5
|
-
|
|
6
|
-
// package.json
|
|
7
|
-
var package_default = {
|
|
8
|
-
name: "@tknf/matchbox",
|
|
9
|
-
version: "0.2.4",
|
|
10
|
-
description: "A Simple Web Server Framework",
|
|
11
|
-
keywords: [
|
|
12
|
-
"cgi",
|
|
13
|
-
"framework",
|
|
14
|
-
"hono",
|
|
15
|
-
"server",
|
|
16
|
-
"web"
|
|
17
|
-
],
|
|
18
|
-
license: "MIT",
|
|
19
|
-
author: "tknf <dev@tknf.net>",
|
|
20
|
-
repository: {
|
|
21
|
-
url: "https://github.com/tknf/matchbox.git"
|
|
22
|
-
},
|
|
23
|
-
files: [
|
|
24
|
-
"dist"
|
|
25
|
-
],
|
|
26
|
-
type: "module",
|
|
27
|
-
module: "dist/index.js",
|
|
28
|
-
types: "dist/index.d.ts",
|
|
29
|
-
exports: {
|
|
30
|
-
".": {
|
|
31
|
-
import: {
|
|
32
|
-
types: "./dist/index.d.ts",
|
|
33
|
-
import: "./dist/index.js"
|
|
34
|
-
}
|
|
35
|
-
},
|
|
36
|
-
"./plugin": {
|
|
37
|
-
import: {
|
|
38
|
-
types: "./dist/plugin.d.ts",
|
|
39
|
-
import: "./dist/plugin.js"
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
},
|
|
43
|
-
publishConfig: {
|
|
44
|
-
access: "public",
|
|
45
|
-
registry: "https://registry.npmjs.org/"
|
|
46
|
-
},
|
|
47
|
-
scripts: {
|
|
48
|
-
test: "vitest run",
|
|
49
|
-
"test:coverage": "vitest run --coverage",
|
|
50
|
-
"lint:check": "oxlint",
|
|
51
|
-
"lint:fix": "oxlint --fix",
|
|
52
|
-
"format:check": "oxfmt --check",
|
|
53
|
-
"format:write": "oxfmt",
|
|
54
|
-
watch: "tsup --watch",
|
|
55
|
-
build: "tsup"
|
|
56
|
-
},
|
|
57
|
-
dependencies: {
|
|
58
|
-
glob: "^13.0.0"
|
|
59
|
-
},
|
|
60
|
-
devDependencies: {
|
|
61
|
-
"@types/node": "^25.0.3",
|
|
62
|
-
"@vitest/coverage-v8": "^4.0.16",
|
|
63
|
-
hono: "^4.11.1",
|
|
64
|
-
oxfmt: "^0.20.0",
|
|
65
|
-
oxlint: "^1.35.0",
|
|
66
|
-
tsup: "^8.5.1",
|
|
67
|
-
typescript: "^5.9.3",
|
|
68
|
-
vite: "^7.3.0",
|
|
69
|
-
vitest: "^4.0.16"
|
|
70
|
-
},
|
|
71
|
-
peerDependencies: {
|
|
72
|
-
hono: "*"
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
// src/html.ts
|
|
77
|
-
import { html } from "hono/html";
|
|
78
|
-
var generateCgiInfo = ({
|
|
79
|
-
$_SERVER,
|
|
80
|
-
$_SESSION,
|
|
81
|
-
$_REQUEST,
|
|
82
|
-
config
|
|
83
|
-
}) => {
|
|
84
|
-
return () => {
|
|
85
|
-
const infoSection = ({ title, data }) => {
|
|
86
|
-
return html`
|
|
87
|
-
<div style="margin-bottom: 20px; width: 100%; max-width: 900px;">
|
|
88
|
-
<h2 style="background: #ccccff; color: #000; padding: 5px 10px; margin: 0; font-size: 1.2em; border: 1px solid #000; font-family: 'MS PGothic', sans-serif;">${title}</h2>
|
|
89
|
-
<table style="width: 100%; border-collapse: collapse; border: 1px solid #000; table-layout: fixed; font-family: 'MS PGothic', sans-serif;">
|
|
90
|
-
${Object.entries(data).map(
|
|
91
|
-
([key, val], i) => html`
|
|
92
|
-
<tr style="background: ${i % 2 === 0 ? "#f0f0ff" : "#ffffff"}">
|
|
93
|
-
<td style="padding: 3px 10px; border: 1px solid #000; font-weight: bold; width: 30%; word-break: break-all;">${key}</td>
|
|
94
|
-
<td style="padding: 3px 10px; border: 1px solid #000; width: 70%; word-break: break-all;">
|
|
95
|
-
${typeof val === "object" ? JSON.stringify(val, null, 2) : String(val)}
|
|
96
|
-
</td>
|
|
97
|
-
</tr>
|
|
98
|
-
`
|
|
99
|
-
)}
|
|
100
|
-
</table>
|
|
101
|
-
</div>
|
|
102
|
-
`;
|
|
103
|
-
};
|
|
104
|
-
return html`
|
|
105
|
-
<div style="background: #ffffff; color: #333333; font-family: 'MS PGothic', sans-serif; padding: 20px; display: flex; flex-direction: column; align-items: center;">
|
|
106
|
-
<h1 style="color: #000000; border-bottom: 3px double #000000; padding-bottom: 10px; margin-bottom: 30px;">Matchbox CGI Version Information</h1>
|
|
107
|
-
<div style="width: 100%; max-width: 900px; padding: 15px; background: #ffffcc; border: 1px dashed #000000; margin-bottom: 20px; text-align: center;">
|
|
108
|
-
<strong>Server Software:</strong> Matchbox Engine on ${typeof process !== "undefined" ? "Node.js/Bun" : "Edge"}
|
|
109
|
-
</div>
|
|
110
|
-
${infoSection({ title: "$_SERVER (Environment)", data: $_SERVER })}
|
|
111
|
-
${infoSection({ title: "$_SESSION", data: $_SESSION })}
|
|
112
|
-
${infoSection({ title: "$_REQUEST", data: $_REQUEST })}
|
|
113
|
-
${infoSection({ title: "Site Configuration", data: config })}
|
|
114
|
-
<div style="margin-top: 40px; font-size: 0.9em; font-style: italic; border-top: 1px solid #ccc; width: 100%; max-width: 900px; text-align: right; padding-top: 10px;">
|
|
115
|
-
Generated by Matchbox Framework - Ignition for Hono
|
|
116
|
-
</div>
|
|
117
|
-
</div>
|
|
118
|
-
`;
|
|
119
|
-
};
|
|
120
|
-
};
|
|
121
|
-
var generateCgiError = ({
|
|
122
|
-
error,
|
|
123
|
-
$_SERVER
|
|
124
|
-
}) => {
|
|
125
|
-
return html`
|
|
126
|
-
<div style="padding:2rem; background:#fffafa; border:5px double #cc0000; font-family: 'MS PGothic', sans-serif;">
|
|
127
|
-
<h1 style="color:#cc0000; border-bottom: 2px solid #cc0000; padding-bottom: 5px;">Matchbox: Runtime Exception</h1>
|
|
128
|
-
<p><strong>Fatal Error:</strong> ${error.message}</p>
|
|
129
|
-
<pre style="background:#f0f0f0; padding:1rem; border:1px inset #ccc; overflow: auto;">${error.stack}</pre>
|
|
130
|
-
<hr style="border: 0; border-top: 1px solid #cc0000;" />
|
|
131
|
-
<div style="text-align: right; font-size: 0.8em;">Matchbox CGI Engine Server at ${$_SERVER.REMOTE_ADDR}</div>
|
|
132
|
-
</div>
|
|
133
|
-
`;
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
// src/cgi.ts
|
|
137
|
-
var isRedirectObject = (obj) => {
|
|
138
|
-
return obj && obj.__type === "redirect" && typeof obj.url === "string";
|
|
139
|
-
};
|
|
140
|
-
var createCgiWithPages = (pages, siteConfig = {}, authMap = {}, rewriteMap = {}, options = {}) => {
|
|
141
|
-
const app = new Hono();
|
|
142
|
-
const SESS_KEY = options.sessionCookie?.name || "_SESSION_ID";
|
|
143
|
-
if (options.middleware && options.middleware.length > 0) {
|
|
144
|
-
for (const mw of options.middleware) {
|
|
145
|
-
app.use("*", async (c, next) => {
|
|
146
|
-
const result = await mw(c, next);
|
|
147
|
-
if (result instanceof Response) {
|
|
148
|
-
return result;
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
const protectedFiles = [".htaccess", ".htpasswd", ".htdigest", ".htgroup"];
|
|
154
|
-
app.use("*", async (c, next) => {
|
|
155
|
-
const path = c.req.path;
|
|
156
|
-
const lastSegment = path.slice(path.lastIndexOf("/") + 1);
|
|
157
|
-
if (protectedFiles.some((file) => lastSegment === file)) {
|
|
158
|
-
return c.text("Forbidden", 403);
|
|
159
|
-
}
|
|
160
|
-
await next();
|
|
161
|
-
});
|
|
162
|
-
if (options.enforceTrailingSlash) {
|
|
163
|
-
app.use("*", async (c, next) => {
|
|
164
|
-
const path = c.req.path;
|
|
165
|
-
if (!path.endsWith("/") && !path.includes(".")) {
|
|
166
|
-
return c.redirect(`${path}/`, 301);
|
|
167
|
-
}
|
|
168
|
-
await next();
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
Object.entries(rewriteMap).forEach(([dir, rules]) => {
|
|
172
|
-
const basePath = dir === "/" ? "" : dir.replace(/\/$/, "");
|
|
173
|
-
app.use(`${basePath}/*`, async (c, next) => {
|
|
174
|
-
const relPath = c.req.path.replace(basePath, "") || "/";
|
|
175
|
-
for (const rule of rules) {
|
|
176
|
-
if (rule.type === "redirect") {
|
|
177
|
-
if (relPath === rule.source) {
|
|
178
|
-
return c.redirect(
|
|
179
|
-
rule.target,
|
|
180
|
-
Number.parseInt(rule.code, 10) || 302
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
} else if (rule.type === "rewrite") {
|
|
184
|
-
const regex = new RegExp(rule.pattern);
|
|
185
|
-
if (regex.test(relPath)) {
|
|
186
|
-
const target = rule.target.startsWith("/") ? rule.target : `${basePath}/${rule.target}`;
|
|
187
|
-
if (rule.flags.includes("R")) {
|
|
188
|
-
const code = rule.flags.match(/R=(\d+)/)?.[1] || "302";
|
|
189
|
-
return c.redirect(target, Number.parseInt(code, 10));
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
await next();
|
|
195
|
-
});
|
|
196
|
-
});
|
|
197
|
-
Object.entries(authMap).forEach(([dir, content]) => {
|
|
198
|
-
const credentials = content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((line) => {
|
|
199
|
-
const [username, password] = line.split(":");
|
|
200
|
-
return { username, password };
|
|
201
|
-
});
|
|
202
|
-
if (credentials.length > 0) {
|
|
203
|
-
const authPath = dir === "/" ? "*" : `${dir.replace(/\/$/, "")}/*`;
|
|
204
|
-
app.use(authPath, async (c, next) => {
|
|
205
|
-
const handler = basicAuth({
|
|
206
|
-
verifyUser: (u, p) => credentials.some((cred) => cred.username === u && cred.password === p),
|
|
207
|
-
realm: "Restricted Area"
|
|
208
|
-
});
|
|
209
|
-
return handler(c, next);
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
});
|
|
213
|
-
pages.forEach(({ urlPath, dirPath, component }) => {
|
|
214
|
-
const routes = [urlPath, `${urlPath}/*`];
|
|
215
|
-
if (dirPath) {
|
|
216
|
-
routes.push(dirPath);
|
|
217
|
-
if (dirPath !== "/") {
|
|
218
|
-
routes.push(`${dirPath}/*`);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
routes.forEach((route) => {
|
|
222
|
-
app.all(route, async (c) => {
|
|
223
|
-
const $_GET = c.req.query();
|
|
224
|
-
const body = await c.req.parseBody({ all: true }).catch(() => ({}));
|
|
225
|
-
const $_POST = {};
|
|
226
|
-
const $_FILES = {};
|
|
227
|
-
for (const [key, value] of Object.entries(body)) {
|
|
228
|
-
if (value instanceof File || Array.isArray(value) && value[0] instanceof File) {
|
|
229
|
-
$_FILES[key] = value;
|
|
230
|
-
} else {
|
|
231
|
-
$_POST[key] = value;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
const $_COOKIE = getCookie(c);
|
|
235
|
-
const $_REQUEST = {
|
|
236
|
-
...$_COOKIE,
|
|
237
|
-
...$_GET,
|
|
238
|
-
...$_POST
|
|
239
|
-
};
|
|
240
|
-
const $_ENV = typeof process !== "undefined" && process.env ? process.env : c.env || {};
|
|
241
|
-
let $_SESSION = {};
|
|
242
|
-
const sRaw = getCookie(c, SESS_KEY);
|
|
243
|
-
if (sRaw) {
|
|
244
|
-
try {
|
|
245
|
-
$_SESSION = JSON.parse(decodeURIComponent(sRaw));
|
|
246
|
-
} catch {
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
let responseStatus = 200;
|
|
250
|
-
const responseHeaders = {
|
|
251
|
-
"Content-Type": "text/html; charset=utf-8"
|
|
252
|
-
};
|
|
253
|
-
const $_SERVER = {
|
|
254
|
-
...$_ENV,
|
|
255
|
-
REQUEST_METHOD: c.req.method,
|
|
256
|
-
REQUEST_URI: c.req.url,
|
|
257
|
-
REMOTE_ADDR: c.req.header("x-forwarded-for") || "127.0.0.1",
|
|
258
|
-
USER_AGENT: c.req.header("user-agent") || "",
|
|
259
|
-
SCRIPT_NAME: urlPath,
|
|
260
|
-
PATH_INFO: c.req.path.replace(urlPath, "") || "/",
|
|
261
|
-
QUERY_STRING: new URL(c.req.url).search.slice(1)
|
|
262
|
-
};
|
|
263
|
-
const cgiinfo = generateCgiInfo({
|
|
264
|
-
$_SERVER,
|
|
265
|
-
$_REQUEST,
|
|
266
|
-
$_SESSION,
|
|
267
|
-
config: siteConfig
|
|
268
|
-
});
|
|
269
|
-
const context = {
|
|
270
|
-
$_GET,
|
|
271
|
-
$_POST,
|
|
272
|
-
$_FILES,
|
|
273
|
-
$_REQUEST,
|
|
274
|
-
$_COOKIE,
|
|
275
|
-
$_ENV,
|
|
276
|
-
$_SERVER,
|
|
277
|
-
$_SESSION,
|
|
278
|
-
config: siteConfig,
|
|
279
|
-
c,
|
|
280
|
-
header: (name, value) => {
|
|
281
|
-
responseHeaders[name.toLocaleLowerCase()] = value;
|
|
282
|
-
},
|
|
283
|
-
status: (code) => {
|
|
284
|
-
responseStatus = code;
|
|
285
|
-
},
|
|
286
|
-
redirect: (url, status = 302) => {
|
|
287
|
-
return { __type: "redirect", url, status };
|
|
288
|
-
},
|
|
289
|
-
cgiinfo,
|
|
290
|
-
request_headers: () => {
|
|
291
|
-
return Object.fromEntries(c.req.raw.headers.entries());
|
|
292
|
-
},
|
|
293
|
-
response_headers: () => {
|
|
294
|
-
return responseHeaders;
|
|
295
|
-
},
|
|
296
|
-
log: (message) => {
|
|
297
|
-
if (options.logger) {
|
|
298
|
-
options.logger(message, "info");
|
|
299
|
-
} else {
|
|
300
|
-
console.log(`[CGI LOG] ${message}`);
|
|
301
|
-
}
|
|
302
|
-
},
|
|
303
|
-
get_version: () => {
|
|
304
|
-
return `MatchboxCGI/v${package_default.version}`;
|
|
305
|
-
},
|
|
306
|
-
/**
|
|
307
|
-
* Returns information about all loaded CGI modules
|
|
308
|
-
* @returns Array of module information containing urlPath and dirPath
|
|
309
|
-
*/
|
|
310
|
-
get_modules: () => {
|
|
311
|
-
return pages.map((page) => ({
|
|
312
|
-
urlPath: page.urlPath,
|
|
313
|
-
dirPath: page.dirPath
|
|
314
|
-
}));
|
|
315
|
-
}
|
|
316
|
-
};
|
|
317
|
-
try {
|
|
318
|
-
const result = await component(context);
|
|
319
|
-
const sessionValue = encodeURIComponent(JSON.stringify($_SESSION));
|
|
320
|
-
const sessionOptions = {
|
|
321
|
-
path: options.sessionCookie?.path || "/",
|
|
322
|
-
httpOnly: true,
|
|
323
|
-
sameSite: options.sessionCookie?.sameSite || "Lax"
|
|
324
|
-
};
|
|
325
|
-
if (options.sessionCookie?.secure !== void 0) {
|
|
326
|
-
sessionOptions.secure = options.sessionCookie.secure;
|
|
327
|
-
}
|
|
328
|
-
if (options.sessionCookie?.domain) {
|
|
329
|
-
sessionOptions.domain = options.sessionCookie.domain;
|
|
330
|
-
}
|
|
331
|
-
if (options.sessionCookie?.maxAge) {
|
|
332
|
-
sessionOptions.maxAge = options.sessionCookie.maxAge;
|
|
333
|
-
}
|
|
334
|
-
if (isRedirectObject(result)) {
|
|
335
|
-
setCookie(c, SESS_KEY, sessionValue, sessionOptions);
|
|
336
|
-
return c.redirect(result.url, result.status);
|
|
337
|
-
}
|
|
338
|
-
if (result instanceof Response) {
|
|
339
|
-
setCookie(c, SESS_KEY, sessionValue, sessionOptions);
|
|
340
|
-
return result;
|
|
341
|
-
}
|
|
342
|
-
setCookie(c, SESS_KEY, sessionValue, sessionOptions);
|
|
343
|
-
Object.entries(responseHeaders).forEach(([key, value]) => {
|
|
344
|
-
c.header(key, value);
|
|
345
|
-
});
|
|
346
|
-
const contentType = responseHeaders["content-type"];
|
|
347
|
-
if (contentType?.includes("application/json")) {
|
|
348
|
-
return c.json(
|
|
349
|
-
// biome-ignore lint/suspicious/noExplicitAny: to JSON response
|
|
350
|
-
result ?? { success: true },
|
|
351
|
-
responseStatus
|
|
352
|
-
);
|
|
353
|
-
}
|
|
354
|
-
return c.html(result, responseStatus);
|
|
355
|
-
} catch (error) {
|
|
356
|
-
return c.html(generateCgiError({ error, $_SERVER }), 500);
|
|
357
|
-
}
|
|
358
|
-
});
|
|
359
|
-
});
|
|
360
|
-
});
|
|
361
|
-
return app;
|
|
362
|
-
};
|
|
363
|
-
|
|
364
|
-
// src/with-defaults.ts
|
|
365
|
-
var loadPagesFromPublic = () => {
|
|
1
|
+
import { createCgiWithPages } from "./cgi.js";
|
|
2
|
+
const loadPagesFromPublic = () => {
|
|
366
3
|
const modules = import.meta.glob("/public/**/*.cgi.{tsx,jsx}", {
|
|
367
4
|
eager: true
|
|
368
5
|
});
|
|
@@ -431,7 +68,7 @@ var loadPagesFromPublic = () => {
|
|
|
431
68
|
}, {});
|
|
432
69
|
return { pages, authMap, rewriteMap };
|
|
433
70
|
};
|
|
434
|
-
|
|
71
|
+
const createCgi = (options) => {
|
|
435
72
|
const resolvedConfig = typeof __MATCHBOX_CONFIG__ === "undefined" ? {} : __MATCHBOX_CONFIG__;
|
|
436
73
|
const { pages, authMap, rewriteMap } = loadPagesFromPublic();
|
|
437
74
|
return createCgiWithPages(pages, resolvedConfig, authMap, rewriteMap, options);
|