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(/&/, "\\&amp;", str)
172+    gsub(/</, "\\&lt;", str)
173+    gsub(/>/, "\\&gt;", str)
174+    gsub(/"/, "\\&quot;", str)
175+    gsub(/'/, "\\&#39;", 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&amp;hash=HEAD&amp;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&amp;hash=HEAD&amp;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&amp;hash=HEAD&amp;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>