diff options
context:
space:
mode:
Diffstat (limited to 'source/filter.c')
-rw-r--r--source/filter.c324
1 file changed, 178 insertions, 146 deletions
diff --git a/source/filter.c b/source/filter.c
index f195534..3c0c7e4 100644
--- a/source/filter.c
+++ b/source/filter.c
@@ -1,13 +1,16 @@
-/* filter.c: filter framework functions
- *
- * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
- *
- * Licensed under GNU General Public License v2
- * (see LICENSE.txt for full license text)
+/*
+ * The two backends behind a cgit filter and the plumbing that hands stdout to
+ * whichever one is open. An exec filter forks a program and feeds it the page
+ * through a pipe, while a Lua filter runs a script inside cgit and feeds it
+ * the page through a write that has been interposed in front of libc's. A
+ * cgitrc command picks its backend with an exec or lua prefix, and a command
+ * with no prefix is taken as a program to run.
*/
#include "cgit.h"
+#include "filter.h"
#include "html.h"
+#include "shared.h"
#ifndef NO_LUA
#include <dlfcn.h>
#include <lua.h>
@@ -15,32 +18,10 @@
#include <lauxlib.h>
#endif
-static inline void reap_filter(struct cgit_filter *filter)
-{
- if (filter && filter->cleanup)
- filter->cleanup(filter);
-}
-
-void cgit_cleanup_filters(void)
-{
- int i;
- reap_filter(ctx.cfg.about_filter);
- reap_filter(ctx.cfg.commit_filter);
- reap_filter(ctx.cfg.source_filter);
- reap_filter(ctx.cfg.email_filter);
- reap_filter(ctx.cfg.auth_filter);
- for (i = 0; i < cgit_repolist.count; ++i) {
- reap_filter(cgit_repolist.repos[i].about_filter);
- reap_filter(cgit_repolist.repos[i].commit_filter);
- reap_filter(cgit_repolist.repos[i].source_filter);
- reap_filter(cgit_repolist.repos[i].email_filter);
- }
-}
-
static int open_exec_filter(struct cgit_filter *base, va_list ap)
{
struct cgit_exec_filter *filter = (struct cgit_exec_filter *)base;
- int pipe_fh[2];
+ int pipefd[2];
int i;
for (i = 0; i < filter->base.argument_count; i++)
@@ -48,19 +29,21 @@ static int open_exec_filter(struct cgit_filter *base, va_list ap)
filter->old_stdout = cgit_die_unless_positive(dup(STDOUT_FILENO),
"Unable to duplicate STDOUT");
- cgit_die_unless_zero(pipe(pipe_fh), "Unable to create pipe to subprocess");
- filter->pid = cgit_die_unless_non_negative(fork(), "Unable to create subprocess");
+ cgit_die_unless_zero(pipe(pipefd),
+ "Unable to create pipe to subprocess");
+ filter->pid = cgit_die_unless_non_negative(fork(),
+ "Unable to create subprocess");
if (filter->pid == 0) {
- close(pipe_fh[1]);
- cgit_die_unless_non_negative(dup2(pipe_fh[0], STDIN_FILENO),
+ close(pipefd[1]);
+ cgit_die_unless_non_negative(dup2(pipefd[0], STDIN_FILENO),
"Unable to use pipe as STDIN");
execvp(filter->cmd, filter->argv);
die_errno("Unable to exec subprocess %s", filter->cmd);
}
- close(pipe_fh[0]);
- cgit_die_unless_non_negative(dup2(pipe_fh[1], STDOUT_FILENO),
+ close(pipefd[0]);
+ cgit_die_unless_non_negative(dup2(pipefd[1], STDOUT_FILENO),
"Unable to use pipe as STDOUT");
- close(pipe_fh[1]);
+ close(pipefd[1]);
return 0;
}
@@ -83,10 +66,10 @@ done:
for (i = 0; i < filter->base.argument_count; i++)
filter->argv[i + 1] = NULL;
return WEXITSTATUS(exit_status);
-
}
-static void fprintf_exec_filter(struct cgit_filter *base, FILE *f, const char *prefix)
+static void fprintf_exec_filter(struct cgit_filter *base, FILE *f,
+ const char *prefix)
{
struct cgit_exec_filter *filter = (struct cgit_exec_filter *)base;
fprintf(f, "%sexec:%s\n", prefix, filter->cmd);
@@ -107,21 +90,23 @@ static void cleanup_exec_filter(struct cgit_filter *base)
static struct cgit_filter *new_exec_filter(const char *cmd, int argument_count)
{
- struct cgit_exec_filter *f;
- int args_size = 0;
+ struct cgit_exec_filter *filter;
+ int argv_size;
- f = xmalloc(sizeof(*f));
- /* We leave argv for now and assign it below. */
- cgit_exec_filter_init(f, cgit_strdup_first_line(cmd), NULL);
- f->base.argument_count = argument_count;
- args_size = (2 + argument_count) * sizeof(char *);
- f->argv = xmalloc(args_size);
- memset(f->argv, 0, args_size);
- f->argv[0] = f->cmd;
- return &f->base;
+ filter = xmalloc(sizeof(*filter));
+ cgit_exec_filter_init(filter, cgit_strdup_first_line(cmd), NULL);
+ filter->base.argument_count = argument_count;
+ // argv is the command, then a slot per argument, then the NULL that
+ // execvp needs to find the end.
+ argv_size = (2 + argument_count) * sizeof(char *);
+ filter->argv = xmalloc(argv_size);
+ memset(filter->argv, 0, argv_size);
+ filter->argv[0] = filter->cmd;
+ return &filter->base;
}
-void cgit_exec_filter_init(struct cgit_exec_filter *filter, char *cmd, char **argv)
+void cgit_exec_filter_init(struct cgit_exec_filter *filter, char *cmd,
+ char **argv)
{
memset(filter, 0, sizeof(*filter));
filter->base.open = open_exec_filter;
@@ -130,7 +115,6 @@ void cgit_exec_filter_init(struct cgit_exec_filter *filter, char *cmd, char **ar
filter->base.cleanup = cleanup_exec_filter;
filter->cmd = cmd;
filter->argv = argv;
- /* The argument count for open_filter is zero by default, unless called from new_filter, above. */
filter->base.argument_count = 0;
}
@@ -141,8 +125,17 @@ void cgit_init_filters(void)
#endif
#ifndef NO_LUA
+struct lua_filter {
+ struct cgit_filter base;
+ char *script_file;
+ lua_State *lua_state;
+};
+
+typedef ssize_t (*filter_write_fn)(struct cgit_filter *base, const void *buf,
+ size_t count);
+
static ssize_t (*libc_write)(int fd, const void *buf, size_t count);
-static ssize_t (*filter_write)(struct cgit_filter *base, const void *buf, size_t count) = NULL;
+static filter_write_fn filter_write = NULL;
static struct cgit_filter *current_write_filter = NULL;
void cgit_init_filters(void)
@@ -152,6 +145,10 @@ void cgit_init_filters(void)
die("Could not locate libc's write function");
}
+/*
+ * Interposing on write is what lets a Lua filter see output produced by code
+ * that has no idea a filter is running.
+ */
ssize_t write(int fd, const void *buf, size_t count)
{
if (fd != STDOUT_FILENO || !filter_write)
@@ -159,13 +156,15 @@ ssize_t write(int fd, const void *buf, size_t count)
return filter_write(current_write_filter, buf, count);
}
-static inline void hook_write(struct cgit_filter *filter, ssize_t (*new_write)(struct cgit_filter *base, const void *buf, size_t count))
+static inline void hook_write(struct cgit_filter *filter,
+ filter_write_fn write_fn)
{
- /* We want to avoid buggy nested patterns. */
+ // Filters cannot nest, because there is one stdout and one hook, so a
+ // second one would strand the first.
assert(filter_write == NULL);
assert(current_write_filter == NULL);
current_write_filter = filter;
- filter_write = new_write;
+ filter_write = write_fn;
}
static inline void unhook_write(void)
@@ -176,102 +175,113 @@ static inline void unhook_write(void)
current_write_filter = NULL;
}
-struct lua_filter {
- struct cgit_filter base;
- char *script_file;
- lua_State *lua_state;
-};
-
-static void error_lua_filter(struct lua_filter *filter)
+static void die_lua_error(struct lua_filter *filter)
{
- die("Lua error in %s: %s", filter->script_file, lua_tostring(filter->lua_state, -1));
+ die("Lua error in %s: %s", filter->script_file,
+ lua_tostring(filter->lua_state, -1));
lua_pop(filter->lua_state, 1);
}
-static ssize_t write_lua_filter(struct cgit_filter *base, const void *buf, size_t count)
+static ssize_t write_lua_filter(struct cgit_filter *base, const void *buf,
+ size_t count)
{
struct lua_filter *filter = (struct lua_filter *)base;
lua_getglobal(filter->lua_state, "filter_write");
lua_pushlstring(filter->lua_state, buf, count);
if (lua_pcall(filter->lua_state, 1, 0, 0)) {
- error_lua_filter(filter);
+ die_lua_error(filter);
errno = EIO;
return -1;
}
return count;
}
-static inline int hook_lua_filter(lua_State *lua_state, void (*fn)(const char *txt))
+/*
+ * Output a script asks for belongs on the page and not back in its own filter,
+ * so the hook comes off around the call. The zero returned is Lua's count of
+ * values pushed for the script, not a success code.
+ */
+static inline int emit_unfiltered(lua_State *lua_state,
+ void (*emit)(const char *text))
{
- const char *str;
- ssize_t (*save_filter_write)(struct cgit_filter *base, const void *buf, size_t count);
- struct cgit_filter *save_filter;
+ const char *text;
+ filter_write_fn saved_write;
+ struct cgit_filter *saved_filter;
- str = lua_tostring(lua_state, 1);
- if (!str)
+ text = lua_tostring(lua_state, 1);
+ if (!text)
return 0;
- save_filter_write = filter_write;
- save_filter = current_write_filter;
+ saved_write = filter_write;
+ saved_filter = current_write_filter;
unhook_write();
- fn(str);
- // fn buffers, so empty it while the hook is still off. Re-hooking first
- // would send the page's own bytes back into the filter that asked for
- // them to be written.
+ emit(text);
+ // emit leaves bytes in the html buffer, so empty it while the hook is
+ // still off. Re-hooking first would send the page's own bytes back into
+ // the filter.
html_flush();
- hook_write(save_filter, save_filter_write);
+ hook_write(saved_filter, saved_write);
return 0;
}
-static int html_lua_filter(lua_State *lua_state)
+static int script_html(lua_State *lua_state)
{
- return hook_lua_filter(lua_state, html);
+ return emit_unfiltered(lua_state, html);
}
-static int html_txt_lua_filter(lua_State *lua_state)
+static int script_html_txt(lua_State *lua_state)
{
- return hook_lua_filter(lua_state, html_txt);
+ return emit_unfiltered(lua_state, html_txt);
}
-static int html_attr_lua_filter(lua_State *lua_state)
+static int script_html_attr(lua_State *lua_state)
{
- return hook_lua_filter(lua_state, html_attr);
+ return emit_unfiltered(lua_state, html_attr);
}
-static int html_url_path_lua_filter(lua_State *lua_state)
+static int script_html_url_path(lua_State *lua_state)
{
- return hook_lua_filter(lua_state, html_url_path);
+ return emit_unfiltered(lua_state, html_url_path);
}
-static int html_url_arg_lua_filter(lua_State *lua_state)
+static int script_html_url_arg(lua_State *lua_state)
{
- return hook_lua_filter(lua_state, html_url_arg);
+ return emit_unfiltered(lua_state, html_url_arg);
}
-static int html_include_lua_filter(lua_State *lua_state)
+/*
+ * html_include returns whether it found the file, and calling it through a
+ * pointer that claims it returns nothing would be undefined, so the result is
+ * dropped in a wrapper of the right shape.
+ */
+static void include_and_discard_result(const char *filename)
{
- return hook_lua_filter(lua_state, (void (*)(const char *))html_include);
+ html_include(filename);
}
-static void cleanup_lua_filter(struct cgit_filter *base)
+static int script_html_include(lua_State *lua_state)
{
- struct lua_filter *filter = (struct lua_filter *)base;
-
- if (!filter->lua_state)
- return;
-
- lua_close(filter->lua_state);
- filter->lua_state = NULL;
- if (filter->script_file) {
- free(filter->script_file);
- filter->script_file = NULL;
- }
+ return emit_unfiltered(lua_state, include_and_discard_result);
}
+static const struct {
+ const char *name;
+ lua_CFunction fn;
+} script_globals[] = {
+ { "html", script_html },
+ { "html_txt", script_html_txt },
+ { "html_attr", script_html_attr },
+ { "html_url_path", script_html_url_path },
+ { "html_url_arg", script_html_url_arg },
+ { "html_include", script_html_include },
+};
+
static int init_lua_filter(struct lua_filter *filter)
{
+ size_t i;
+
if (filter->lua_state)
return 0;
@@ -280,21 +290,13 @@ static int init_lua_filter(struct lua_filter *filter)
luaL_openlibs(filter->lua_state);
- lua_pushcfunction(filter->lua_state, html_lua_filter);
- lua_setglobal(filter->lua_state, "html");
- lua_pushcfunction(filter->lua_state, html_txt_lua_filter);
- lua_setglobal(filter->lua_state, "html_txt");
- lua_pushcfunction(filter->lua_state, html_attr_lua_filter);
- lua_setglobal(filter->lua_state, "html_attr");
- lua_pushcfunction(filter->lua_state, html_url_path_lua_filter);
- lua_setglobal(filter->lua_state, "html_url_path");
- lua_pushcfunction(filter->lua_state, html_url_arg_lua_filter);
- lua_setglobal(filter->lua_state, "html_url_arg");
- lua_pushcfunction(filter->lua_state, html_include_lua_filter);
- lua_setglobal(filter->lua_state, "html_include");
+ for (i = 0; i < ARRAY_SIZE(script_globals); i++) {
+ lua_pushcfunction(filter->lua_state, script_globals[i].fn);
+ lua_setglobal(filter->lua_state, script_globals[i].name);
+ }
if (luaL_dofile(filter->lua_state, filter->script_file)) {
- error_lua_filter(filter);
+ die_lua_error(filter);
lua_close(filter->lua_state);
filter->lua_state = NULL;
return 1;
@@ -316,7 +318,7 @@ static int open_lua_filter(struct cgit_filter *base, va_list ap)
for (i = 0; i < filter->base.argument_count; ++i)
lua_pushstring(filter->lua_state, va_arg(ap, char *));
if (lua_pcall(filter->lua_state, filter->base.argument_count, 0, 0)) {
- error_lua_filter(filter);
+ die_lua_error(filter);
return 1;
}
return 0;
@@ -329,7 +331,7 @@ static int close_lua_filter(struct cgit_filter *base)
lua_getglobal(filter->lua_state, "filter_close");
if (lua_pcall(filter->lua_state, 0, 1, 0)) {
- error_lua_filter(filter);
+ die_lua_error(filter);
ret = -1;
} else {
ret = lua_tonumber(filter->lua_state, -1);
@@ -340,12 +342,27 @@ static int close_lua_filter(struct cgit_filter *base)
return ret;
}
-static void fprintf_lua_filter(struct cgit_filter *base, FILE *f, const char *prefix)
+static void fprintf_lua_filter(struct cgit_filter *base, FILE *f,
+ const char *prefix)
{
struct lua_filter *filter = (struct lua_filter *)base;
fprintf(f, "%slua:%s\n", prefix, filter->script_file);
}
+static void cleanup_lua_filter(struct cgit_filter *base)
+{
+ struct lua_filter *filter = (struct lua_filter *)base;
+
+ if (!filter->lua_state)
+ return;
+
+ lua_close(filter->lua_state);
+ filter->lua_state = NULL;
+ if (filter->script_file) {
+ free(filter->script_file);
+ filter->script_file = NULL;
+ }
+}
static struct cgit_filter *new_lua_filter(const char *cmd, int argument_count)
{
@@ -362,10 +379,8 @@ static struct cgit_filter *new_lua_filter(const char *cmd, int argument_count)
return &filter->base;
}
-
#endif
-
int cgit_open_filter(struct cgit_filter *filter, ...)
{
int result;
@@ -391,16 +406,37 @@ int cgit_close_filter(struct cgit_filter *filter)
return filter->close(filter);
}
-void cgit_fprintf_filter(struct cgit_filter *filter, FILE *f, const char *prefix)
+void cgit_fprintf_filter(struct cgit_filter *filter, FILE *f,
+ const char *prefix)
{
filter->fprintfp(filter, f, prefix);
}
+static inline void cleanup_filter(struct cgit_filter *filter)
+{
+ if (filter && filter->cleanup)
+ filter->cleanup(filter);
+}
+void cgit_cleanup_filters(void)
+{
+ int i;
+ cleanup_filter(ctx.cfg.about_filter);
+ cleanup_filter(ctx.cfg.commit_filter);
+ cleanup_filter(ctx.cfg.source_filter);
+ cleanup_filter(ctx.cfg.email_filter);
+ cleanup_filter(ctx.cfg.auth_filter);
+ for (i = 0; i < cgit_repolist.count; ++i) {
+ cleanup_filter(cgit_repolist.repos[i].about_filter);
+ cleanup_filter(cgit_repolist.repos[i].commit_filter);
+ cleanup_filter(cgit_repolist.repos[i].source_filter);
+ cleanup_filter(cgit_repolist.repos[i].email_filter);
+ }
+}
static const struct {
const char *prefix;
- struct cgit_filter *(*ctor)(const char *cmd, int argument_count);
+ struct cgit_filter *(*create)(const char *cmd, int argument_count);
} filter_specs[] = {
{ "exec", new_exec_filter },
#ifndef NO_LUA
@@ -419,42 +455,38 @@ struct cgit_filter *cgit_new_filter(const char *cmd, filter_type filtertype)
return NULL;
colon = strchr(cmd, ':');
- len = colon - cmd;
- /*
- * In case we're running on Windows, don't allow a single letter before
- * the colon.
- */
+ // Measured only once there is a colon to measure against, because
+ // subtracting from a null pointer is not something C defines.
+ len = colon ? (size_t)(colon - cmd) : 0;
+ // A single letter before the colon is a Windows drive letter, not a
+ // filter prefix.
if (len == 1)
colon = NULL;
switch (filtertype) {
- case AUTH:
- argument_count = 12;
- break;
-
- case EMAIL:
- argument_count = 2;
- break;
-
- case SOURCE:
- case ABOUT:
- argument_count = 1;
- break;
-
- case COMMIT:
- default:
- argument_count = 0;
- break;
+ case AUTH:
+ argument_count = 12;
+ break;
+ case EMAIL:
+ argument_count = 2;
+ break;
+ case SOURCE:
+ case ABOUT:
+ argument_count = 1;
+ break;
+ case COMMIT:
+ default:
+ argument_count = 0;
+ break;
}
- /* If no prefix is given, exec filter is the default. */
if (!colon)
return new_exec_filter(cmd, argument_count);
for (i = 0; i < ARRAY_SIZE(filter_specs); i++) {
if (len == strlen(filter_specs[i].prefix) &&
!strncmp(filter_specs[i].prefix, cmd, len))
- return filter_specs[i].ctor(colon + 1, argument_count);
+ return filter_specs[i].create(colon + 1, argument_count);
}
die("Invalid filter type: %.*s", (int) len, cmd);