home
readme
diff
tree
note
docs
0commit f89fa8d0a90f5645f89e3ef511ef9b896fd93a97
1Author: lakefox <mason@lakefox.net>
2Date: Fri May 22 20:48:20 2026 -0600
3
4 CGI.awk
5
6diff --git a/cgi-bin/cgi.awk b/cgi-bin/cgi.awk
7new file mode 100755
8index 0000000..0dc95e4
9--- /dev/null
10+++ b/cgi-bin/cgi.awk
11@@ -0,0 +1,177 @@
12+#!/bin/awk -f
13+
14+# /var/git
15+
16+BEGIN {
17+ split(ENVIRON["QUERY_STRING"], pairs, "&")
18+ for (i in pairs) {
19+ split(pairs[i], kv, "=")
20+
21+ if (kv[1] == "action") {
22+ action = sanitize(kv[2])
23+ } else if (kv[1] == "name") {
24+ name = sanitize(kv[2])
25+ } else if (kv[1] == "hash") {
26+ hash = sanitize(kv[2])
27+ } else if (kv[1] == "path") {
28+ path = sanitize(kv[2])
29+ }
30+ }
31+
32+ print "Content-Type: text/html\r\n"
33+ print "<!DOCTYPE html>"
34+ print "<html>"
35+ print "<head>"
36+
37+ if (name == "") name = "all"
38+ if (hash == "") hash = "HEAD"
39+
40+ print "<title>Git: " name "</title>"
41+ print "<meta http-equiv='Content-Type' content='text/html;charset=UTF-8'>"
42+ print "<meta name='viewport' content='width=device-width, initial-scale=1'>"
43+ print "</head>"
44+ print "<body>"
45+
46+ if (name != "all") {
47+ print "<a href='?name=" name "'>home</a>"
48+ print "<a href='?name=" name "&hash=" hash "&action=readme'>readme</a>"
49+ print "<a href='?name=" name "&hash=" hash "&action=diff'>diff</a>"
50+ print "<a href='?name=" name "&hash=" hash "&action=tree'>tree</a>"
51+ print "<a href='?name=" name "&hash=" hash "&action=notes'>note</a>"
52+ print "<a href='?name=" name "&hash=" hash "&action=docs'>docs</a>"
53+ }
54+
55+ if (action == "") {
56+ if (name == "all") {
57+ cmd = "ls /var/git/"
58+ print "<h1>Git</h1>"
59+ print "<p>Select a repo to view.</p>"
60+
61+ print "<ul>"
62+ while (cmd | getline file) {
63+ split(file, k, ".")
64+ print "<li><a href='/cgi-bin/git.awk?name=" k[1] "'>/" k[1] "</a></li>"
65+ }
66+ close(cmd)
67+ print "</ul>"
68+ } else {
69+ cmd = "git --git-dir=/var/git/" name ".git/ log"
70+ print "<pre><code>"
71+ while (cmd | getline line) {
72+
73+ if (line ~ /^commit/) {
74+ split(line, commit, " ")
75+ print "<a href='/cgi-bin/git.awk?name=" name "&hash=" commit[2] "&action=diff'>commit " commit[2] "</a>"
76+ } else {
77+ print line
78+ }
79+ }
80+ close(cmd)
81+ print "</code></pre>"
82+ }
83+ } else if (action == "tree") {
84+ cmd = "git --git-dir=/var/git/" name ".git/ show " hash ":" path
85+ print "<br/>"
86+ print "<b>" hash ":" path "</b>"
87+ print "<pre><code>"
88+ if (substr(path, length(path), 1) == "/" || path == "") {
89+ while (cmd | getline line) {
90+ if (line !~ /^tree/) {
91+ print "<a href='/cgi-bin/git.awk?name=" name "&hash=" hash "&action=tree&path=" path line "'>" line "</a>"
92+ }
93+ }
94+ } else {
95+ counter = 0
96+ while (cmd | getline line) {
97+ print "<span id='" counter "'><a href='#" counter "' style='user-select:none;'>" counter "</a></span>" escape_html(line)
98+ counter += 1
99+ }
100+ }
101+ close(cmd)
102+ print "</code></pre>"
103+ } if (action == "diff") {
104+ cmd = "git --git-dir=/var/git/" name ".git/ show " hash
105+ print "<pre><code>"
106+ counter = 0
107+ while (cmd | getline line) {
108+ print "<span id='" counter "'><a href='#" counter "' style='user-select:none;'>" counter "</a></span>" escape_html(line)
109+ counter += 1
110+ }
111+ close(cmd)
112+ print "</code></pre>"
113+
114+ } if (action == "readme") {
115+ # 1. discover the README file name (single match assumed)
116+ cmd = "git --git-dir=/var/git/" name ".git ls-tree -r " hash " --name-only | grep '^README\\.'"
117+ fn = ""
118+ while ((cmd | getline fn) > 0) break # take first match
119+ close(cmd)
120+ if (fn == "") { print "<br/>README not found for: " hash; exit 1 }
121+
122+ cmd = "git --git-dir=/var/git/" name ".git show " hash ":" fn
123+ print "<pre><code>"
124+ while ((cmd | getline line) > 0) {
125+ print escape_html(line)
126+ }
127+ close(cmd)
128+ print "</code></pre>"
129+ } else if (action == "notes") {
130+ cmd = "git --git-dir=/var/git/" name ".git/ notes show " hash
131+
132+ print "<pre><code>"
133+ while (cmd | getline line) {
134+ print escape_html(line)
135+ }
136+ close(cmd)
137+ print "</code></pre>"
138+ } else if (action == "docs") {
139+ if (path == "") {
140+ cmd = "git --git-dir=/var/git/" name ".git/ ls-tree -r --name-only " hash ":docs/";
141+
142+ print "<h1>Index</h1>"
143+ print "<ul>"
144+ while ((cmd | getline) > 0) {
145+ print "<li><a href=\"/cgi-bin/git.awk?name=" name "&hash=" hash "&action=docs&path=" $0 "\">" escape_html($0) "</a></li>";
146+ }
147+ print "</ul>"
148+
149+ close(cmd);
150+ } else {
151+ cmd = "git --git-dir=/var/git/" name ".git/ show " hash ":docs/" path " | mandoc -O width=120";
152+
153+ print "<pre><code>"
154+ while ((cmd | getline) > 0) {
155+ gsub(/.\010/, "", $0);
156+ print escape_html($0);
157+ }
158+ print "</code></pre>"
159+
160+ close(cmd);
161+
162+ }
163+ }
164+
165+ print "</body>"
166+ print "</html>"
167+}
168+
169+
170+function escape_html(str) {
171+ gsub(/&/, "\\&", str)
172+ gsub(/</, "\\<", str)
173+ gsub(/>/, "\\>", str)
174+ gsub(/"/, "\\"", str)
175+ gsub(/'/, "\\'", str)
176+ return str
177+}
178+
179+function sanitize(str) {
180+ # Remove any character that isn't a letter, number, dot, dash, or underscore
181+ # This prevents shell metacharacters like ; | & $ > < ( ) from reaching the shell
182+ gsub(/[^a-zA-Z0-9.\/_-]/, "", str)
183+
184+ # Prevent Directory Traversal (e.g., ../../../etc/passwd)
185+ gsub(/\.\./, "", str)
186+
187+ return str
188+}
189diff --git a/cgi.awk b/cgi.awk
190new file mode 100644
191index 0000000..8b3ae82
192--- /dev/null
193+++ b/cgi.awk
194@@ -0,0 +1,56 @@
195+#!/bin/awk -f
196+
197+BEGIN {
198+ # 1. Parse Query String (reuse your logic)
199+ split(ENVIRON["QUERY_STRING"], pairs, "&")
200+ for (i in pairs) {
201+ split(pairs[i], kv, "=")
202+ if (kv[1] == "name") name = sanitize(kv[2])
203+ else if (kv[1] == "hash") hash = sanitize(kv[2])
204+ else if (kv[1] == "action") action = sanitize_path(kv[2])
205+ # Capture all other params to pass as arguments
206+ else params[kv[1]] = sanitize(kv[2])
207+ }
208+
209+ # Defaults
210+ if (name == "") { print "Status: 400 Bad Request\r\n\r\nMissing repository."; exit }
211+ if (hash == "") hash = "HEAD"
212+
213+ # 2. Setup environment
214+ repo_path = "/var/git/" name ".git"
215+ script_path = repo_path "/cgi/" action
216+
217+ # 3. Security: Check if file exists and is executable
218+ # Use 'test -x' via system() or stat (simplified here)
219+ if (action == "" || system("test -f " script_path " -a -x " script_path) != 0) {
220+ print "Status: 404 Not Found\r\n\r\nScript not found or not executable."
221+ exit
222+ }
223+
224+ # 4. Prepare execution
225+ # Build query string from remaining params for the script
226+ query_args = ""
227+ for (p in params) query_args = query_args p "=" params[p] " "
228+ # 5. Execute and capture output
229+ print "Content-Type: text/plain\r\n"
230+
231+ # Exporting environment variables for the script to use
232+ cmd = "GIT_DIR=" repo_path " HASH=" hash " " script_path " " query_args
233+ while ((cmd | getline line) > 0) {
234+ print line
235+ }
236+ close(cmd)
237+}
238+
239+function sanitize(str) {
240+ gsub(/[^a-zA-Z0-9._=-]/, "", str)
241+ return str
242+}
243+
244+function sanitize_path(str) {
245+ # Remove directory traversal
246+ gsub(/\.\./, "", str)
247+ # Only allow safe characters for paths
248+ gsub(/[^a-zA-Z0-9._/-]/, "", str)
249+ return str
250+}
251diff --git a/index.html b/index.html
252index 3410ec8..f3c9fd9 100644
253--- a/index.html
254+++ b/index.html
255@@ -15,15 +15,15 @@
256 <h2>Projects</h2>
257
258 <dl>
259- <dt><b><a href=""http://git.ws/cgi-bin/git.awk?name=grim&hash=HEAD&action=readme>grim/</a></b></dt>
260+ <dt><b><a href="http://git.ws/cgi-bin/git.awk?name=grim&hash=HEAD&action=readme">grim/</a></b></dt>
261 <dd>HTML/CSS renderer with a C++ DOM API</dd>
262 <dd>IRC: <a href="https://web.libera.chat/gamja/?autojoin=#grim">#grim</a></dd>
263 <dd>Clone: <a href="http://git.ws/repo/grim.git">http://git.ws/repo/grim.git</a></dd>
264- <dt><b><a href="http://git.ws/cgi-bin/git.awk?name=gitws&hash=HEAD&action=readme">git.ws/</a></b></dt>
265+ <dt><b><a href="http://git.ws/cgi-bin/git.awk?name=gitws&hash=HEAD&action=readme">git.ws/</a></b></dt>
266 <dd>All the scripts that run git.ws (mailto.awk/git.awk)</dd>
267 <dd>IRC: <a href="https://web.libera.chat/gamja/?autojoin=#gitws">#gitws</a></dd>
268 <dd>Clone: <a href="http://git.ws/repo/gitws.git">http://git.ws/repo/gitws.git</a></dd>
269- <dt><b><a href="http://git.ws/cgi-bin/git.awk?name=spectre&hash=HEAD&action=readme">spectre/</a></b></dt>
270+ <dt><b><a href="http://git.ws/cgi-bin/git.awk?name=spectre&hash=HEAD&action=readme">spectre/</a></b></dt>
271 <dd>Fast router OS</dd>
272 <dd>IRC: <a href="https://web.libera.chat/gamja/?autojoin=#spectre">#spectre</a></dd>
273 <dd>Clone: <a href="http://git.ws/repo/spectre.git">http://git.ws/repo/spectre.git</a></dd>