diff options
Diffstat (limited to 'source/filter.c')
| -rw-r--r-- | source/filter.c | 324 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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); |
