1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
|
# cgit — Lua Integration
## Overview
cgit supports Lua as an in-process scripting language for content filters.
Lua filters avoid the fork/exec overhead of shell-based filters and have
direct access to cgit's HTML output functions. Lua support is optional and
auto-detected at compile time.
Source files: `filter.c` (Lua filter implementation), `cgit.mk` (Lua detection).
## Compile-Time Detection
Lua support is detected by `cgit.mk` using `pkg-config`:
```makefile
ifndef NO_LUA
LUAPKGS := luajit lua lua5.2 lua5.1
LUAPKG := $(shell for p in $(LUAPKGS); do \
$(PKG_CONFIG) --exists $$p 2>/dev/null && echo $$p && break; done)
ifneq ($(LUAPKG),)
CGIT_CFLAGS += -DHAVE_LUA $(shell $(PKG_CONFIG) --cflags $(LUAPKG))
CGIT_LIBS += $(shell $(PKG_CONFIG) --libs $(LUAPKG))
endif
endif
```
Detection order: `luajit` → `lua` → `lua5.2` → `lua5.1`.
To disable Lua even when available:
```bash
make NO_LUA=1
```
The `HAVE_LUA` preprocessor define gates all Lua-related code:
```c
#ifdef HAVE_LUA
/* Lua filter implementation */
#else
/* stub: cgit_new_filter() returns NULL for lua: prefix */
#endif
```
## Lua Filter Structure
```c
struct cgit_lua_filter {
struct cgit_filter base; /* common filter fields */
char *script_file; /* path to Lua script */
lua_State *lua_state; /* Lua interpreter state */
};
```
The `lua_State` is lazily initialized on first use and reused for subsequent
invocations of the same filter.
## C API Exposed to Lua
cgit registers these C functions in the Lua environment:
### `html(str)`
Writes raw HTML to stdout (no escaping):
```c
static int lua_html(lua_State *L)
{
const char *str = luaL_checkstring(L, 1);
html(str);
return 0;
}
```
### `html_txt(str)`
Writes HTML-escaped text:
```c
static int lua_html_txt(lua_State *L)
{
const char *str = luaL_checkstring(L, 1);
html_txt(str);
return 0;
}
```
### `html_attr(str)`
Writes attribute-escaped text:
```c
static int lua_html_attr(lua_State *L)
{
const char *str = luaL_checkstring(L, 1);
html_attr(str);
return 0;
}
```
### `html_url_path(str)`
Writes a URL-encoded path:
```c
static int lua_html_url_path(lua_State *L)
{
const char *str = luaL_checkstring(L, 1);
html_url_path(str);
return 0;
}
```
### `html_url_arg(str)`
Writes a URL-encoded query argument:
```c
static int lua_html_url_arg(lua_State *L)
{
const char *str = luaL_checkstring(L, 1);
html_url_arg(str);
return 0;
}
```
### `html_include(filename)`
Includes a file's contents in the output:
```c
static int lua_html_include(lua_State *L)
{
const char *filename = luaL_checkstring(L, 1);
html_include(filename);
return 0;
}
```
## Lua Filter Lifecycle
### Initialization
On first `open()`, the Lua state is created and the script is loaded:
```c
static int open_lua_filter(struct cgit_filter *base, ...)
{
struct cgit_lua_filter *f = (struct cgit_lua_filter *)base;
if (!f->lua_state) {
/* Create new Lua state */
f->lua_state = luaL_newstate();
luaL_openlibs(f->lua_state);
/* Register C functions */
lua_pushcfunction(f->lua_state, lua_html);
lua_setglobal(f->lua_state, "html");
lua_pushcfunction(f->lua_state, lua_html_txt);
lua_setglobal(f->lua_state, "html_txt");
lua_pushcfunction(f->lua_state, lua_html_attr);
lua_setglobal(f->lua_state, "html_attr");
lua_pushcfunction(f->lua_state, lua_html_url_path);
lua_setglobal(f->lua_state, "html_url_path");
lua_pushcfunction(f->lua_state, lua_html_url_arg);
lua_setglobal(f->lua_state, "html_url_arg");
lua_pushcfunction(f->lua_state, lua_html_include);
lua_setglobal(f->lua_state, "include");
/* Load and execute the script file */
if (luaL_dofile(f->lua_state, f->script_file))
die("lua error: %s",
lua_tostring(f->lua_state, -1));
}
/* Redirect stdout writes to lua write() function */
/* Call filter_open() with filter-specific arguments */
lua_getglobal(f->lua_state, "filter_open");
/* push arguments from va_list */
lua_call(f->lua_state, nargs, 0);
return 0;
}
```
### Data Flow
While the filter is open, data written to stdout is intercepted via a custom
`write()` function:
```c
/* The fprintf callback for Lua filters */
static void lua_fprintf(struct cgit_filter *base, FILE *f,
const char *fmt, ...)
{
struct cgit_lua_filter *lf = (struct cgit_lua_filter *)base;
/* format the string */
/* call the Lua write() function with the formatted text */
lua_getglobal(lf->lua_state, "write");
lua_pushstring(lf->lua_state, buf);
lua_call(lf->lua_state, 1, 0);
}
```
### Close
```c
static int close_lua_filter(struct cgit_filter *base)
{
struct cgit_lua_filter *f = (struct cgit_lua_filter *)base;
/* Call filter_close() */
lua_getglobal(f->lua_state, "filter_close");
lua_call(f->lua_state, 0, 1);
/* Get return code */
int rc = lua_tointeger(f->lua_state, -1);
lua_pop(f->lua_state, 1);
return rc;
}
```
### Cleanup
```c
static void cleanup_lua_filter(struct cgit_filter *base)
{
struct cgit_lua_filter *f = (struct cgit_lua_filter *)base;
if (f->lua_state)
lua_close(f->lua_state);
}
```
## Lua Script Interface
### Required Functions
A Lua filter script must define these functions:
```lua
function filter_open(...)
-- Called when the filter opens
-- Arguments are filter-type specific
end
function write(str)
-- Called with content chunks to process
-- Transform and output using html() functions
end
function filter_close()
-- Called when filtering is complete
return 0 -- return exit code
end
```
### Available Global Functions
| Function | Description |
|----------|-------------|
| `html(str)` | Output raw HTML |
| `html_txt(str)` | Output HTML-escaped text |
| `html_attr(str)` | Output attribute-escaped text |
| `html_url_path(str)` | Output URL-path-encoded text |
| `html_url_arg(str)` | Output URL-argument-encoded text |
| `include(filename)` | Include file contents in output |
All standard Lua libraries are available (`string`, `table`, `math`, `io`,
`os`, etc.).
## Example Filters
### Source Highlighting Filter
```lua
-- syntax-highlighting.lua
local filename = ""
local buffer = {}
function filter_open(fn)
filename = fn
buffer = {}
end
function write(str)
table.insert(buffer, str)
end
function filter_close()
local content = table.concat(buffer)
local ext = filename:match("%.(%w+)$") or ""
-- Simple keyword highlighting
local keywords = {
["function"] = true, ["local"] = true,
["if"] = true, ["then"] = true,
["end"] = true, ["return"] = true,
["for"] = true, ["while"] = true,
["do"] = true, ["else"] = true,
}
html("<pre><code>")
for line in content:gmatch("([^\n]*)\n?") do
html_txt(line)
html("\n")
end
html("</code></pre>")
return 0
end
```
### Email Obfuscation Filter
```lua
-- email-obfuscate.lua
function filter_open(email, page)
-- email = the email address
-- page = current page name
end
function write(str)
-- Replace @ with [at] for display
local obfuscated = str:gsub("@", " [at] ")
html_txt(obfuscated)
end
function filter_close()
return 0
end
```
### About/README Filter
```lua
-- about-markdown.lua
local buffer = {}
function filter_open(filename)
buffer = {}
end
function write(str)
table.insert(buffer, str)
end
function filter_close()
local content = table.concat(buffer)
-- Process markdown (using a Lua markdown library)
-- or shell out to a converter
local handle = io.popen("cmark", "w")
handle:write(content)
local result = handle:read("*a")
handle:close()
html(result)
return 0
end
```
### Auth Filter (Lua)
```lua
-- auth.lua
-- The auth filter receives 12 arguments
function filter_open(cookie, method, query, referer, path,
host, https, repo, page, accept, phase)
if phase == "cookie" then
-- Validate session cookie
if valid_session(cookie) then
return 0 -- authenticated
end
return 1 -- not authenticated
elseif phase == "post" then
-- Handle login form submission
elseif phase == "authorize" then
-- Check repository access
end
end
function write(str)
html(str)
end
function filter_close()
return 0
end
```
## Performance
Lua filters offer significant performance advantages over exec filters:
| Aspect | Exec Filter | Lua Filter |
|--------|-------------|------------|
| Startup | fork() + exec() per request | One-time Lua state creation |
| Process | New process per invocation | In-process |
| Memory | Separate address space | Shared memory |
| Latency | ~1-5ms fork overhead | ~0.01ms function call |
| Libraries | Any language | Lua libraries only |
## Limitations
- Lua scripts run in the same process as cgit — a crash in the script
crashes cgit
- Standard Lua I/O functions (`print`, `io.write`) bypass cgit's output
pipeline — use `html()` and friends instead
- The Lua state persists between invocations within the same CGI process,
but CGI processes are typically short-lived
- Error handling is via `die()` — a Lua error terminates the CGI process
## Configuration
```ini
# Use Lua filter for source highlighting
source-filter=lua:/usr/share/cgit/filters/syntax-highlight.lua
# Use Lua filter for about pages
about-filter=lua:/usr/share/cgit/filters/about-markdown.lua
# Use Lua filter for authentication
auth-filter=lua:/usr/share/cgit/filters/simple-hierarchical-auth.lua
# Use Lua filter for email display
email-filter=lua:/usr/share/cgit/filters/email-libravatar.lua
```
|