/* filter.c: filter framework functions * * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com> * * Licensed under GNU General Public License v2 * (see COPYING for full license text) */#include"cgit.h"#include"html.h"#include<sys/types.h>#include<sys/wait.h>#include<unistd.h>#include<string.h>#include<stdlib.h>#include<dlfcn.h>#include<errno.h>#ifndef NO_LUA#include<lua.h>#include<lualib.h>#include<lauxlib.h>#endifstaticssize_t(*libc_write)(intfd,constvoid*buf,size_tcount);staticssize_t(*filter_write)(structcgit_filter*base,constvoid*buf,size_tcount)=NULL;staticstructcgit_filter*current_write_filter=NULL;staticinlinevoidreap_filter(structcgit_filter*filter){if(filter&&filter->cleanup)filter->cleanup(filter);}voidcgit_cleanup_filters(void){inti;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.owner_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);reap_filter(cgit_repolist.repos[i].owner_filter);}}voidcgit_init_filters(void){libc_write=dlsym(RTLD_NEXT,"write");if(!libc_write)die("Could not locate libc's write function");}ssize_twrite(intfd,constvoid*buf,size_tcount){if(fd!=STDOUT_FILENO||!filter_write)returnlibc_write(fd,buf,count);returnfilter_write(current_write_filter,buf,count);}staticinlinevoidhook_write(structcgit_filter*filter,ssize_t(*new_write)(structcgit_filter*base,constvoid*buf,size_tcount)){/* We want to avoid buggy nested patterns. */assert(filter_write==NULL);assert(current_write_filter==NULL);current_write_filter=filter;filter_write=new_write;}staticinlinevoidunhook_write(){assert(filter_write!=NULL);assert(current_write_filter!=NULL);filter_write=NULL;current_write_filter=NULL;}staticintopen_exec_filter(structcgit_filter*base,va_listap){structcgit_exec_filter*filter=(structcgit_exec_filter*)base;inti;for(i=0;i<filter->base.argument_count;i++)filter->argv[i+1]=va_arg(ap,char*);filter->old_stdout=chk_positive(dup(STDOUT_FILENO),"Unable to duplicate STDOUT");chk_zero(pipe(filter->pipe_fh),"Unable to create pipe to subprocess");filter->pid=chk_non_negative(fork(),"Unable to create subprocess");if(filter->pid==0){close(filter->pipe_fh[1]);chk_non_negative(dup2(filter->pipe_fh[0],STDIN_FILENO),"Unable to use pipe as STDIN");execvp(filter->cmd,filter->argv);die_errno("Unable to exec subprocess %s",filter->cmd);}close(filter->pipe_fh[0]);chk_non_negative(dup2(filter->pipe_fh[1],STDOUT_FILENO),"Unable to use pipe as STDOUT");close(filter->pipe_fh[1]);return0;}staticintclose_exec_filter(structcgit_filter*base){structcgit_exec_filter*filter=(structcgit_exec_filter*)base;inti,exit_status=0;chk_non_negative(dup2(filter->old_stdout,STDOUT_FILENO),"Unable to restore STDOUT");close(filter->old_stdout);if(filter->pid<0)gotodone;waitpid(filter->pid,&exit_status,0);if(WIFEXITED(exit_status))gotodone;die("Subprocess %s exited abnormally",filter->cmd);done:for(i=0;i<filter->base.argument_count;i++)filter->argv[i+1]=NULL;returnWEXITSTATUS(exit_status);}staticvoidfprintf_exec_filter(structcgit_filter*base,FILE*f,constchar*prefix){structcgit_exec_filter*filter=(structcgit_exec_filter*)base;fprintf(f,"%sexec:%s\n",prefix,filter->cmd);}staticvoidcleanup_exec_filter(structcgit_filter*base){structcgit_exec_filter*filter=(structcgit_exec_filter*)base;if(filter->argv){free(filter->argv);filter->argv=NULL;}if(filter->cmd){free(filter->cmd);filter->cmd=NULL;}}staticstructcgit_filter*new_exec_filter(constchar*cmd,intargument_count){structcgit_exec_filter*f;intargs_size=0;f=xmalloc(sizeof(*f));/* We leave argv for now and assign it below. */cgit_exec_filter_init(f,xstrdup(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;}voidcgit_exec_filter_init(structcgit_exec_filter*filter,char*cmd,char**argv){memset(filter,0,sizeof(*filter));filter->base.open=open_exec_filter;filter->base.close=close_exec_filter;filter->base.fprintf=fprintf_exec_filter;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;}#ifndef NO_LUAstructlua_filter{structcgit_filterbase;char*script_file;lua_State*lua_state;};staticvoiderror_lua_filter(structlua_filter*filter){die("Lua error in %s: %s",filter->script_file,lua_tostring(filter->lua_state,-1));lua_pop(filter->lua_state,1);}staticssize_twrite_lua_filter(structcgit_filter*base,constvoid*buf,size_tcount){structlua_filter*filter=(structlua_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);errno=EIO;return-1;}returncount;}staticinlineinthook_lua_filter(lua_State*lua_state,void(*fn)(constchar*txt)){constchar*str;ssize_t(*save_filter_write)(structcgit_filter*base,constvoid*buf,size_tcount);structcgit_filter*save_filter;str=lua_tostring(lua_state,1);if(!str)return0;save_filter_write=filter_write;save_filter=current_write_filter;unhook_write();fn(str);hook_write(save_filter,save_filter_write);return0;}staticinthtml_lua_filter(lua_State*lua_state){returnhook_lua_filter(lua_state,html);}staticinthtml_txt_lua_filter(lua_State*lua_state){returnhook_lua_filter(lua_state,html_txt);}staticinthtml_attr_lua_filter(lua_State*lua_state){returnhook_lua_filter(lua_state,html_attr);}staticinthtml_url_path_lua_filter(lua_State*lua_state){returnhook_lua_filter(lua_state,html_url_path);}staticinthtml_url_arg_lua_filter(lua_State*lua_state){returnhook_lua_filter(lua_state,html_url_arg);}staticinthtml_include_lua_filter(lua_State*lua_state){returnhook_lua_filter(lua_state,(void(*)(constchar*))html_include);}staticvoidcleanup_lua_filter(structcgit_filter*base){structlua_filter*filter=(structlua_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;}}staticintinit_lua_filter(structlua_filter*filter){if(filter->lua_state)return0;if(!(filter->lua_state=luaL_newstate()))return1;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");if(luaL_dofile(filter->lua_state,filter->script_file)){error_lua_filter(filter);lua_close(filter->lua_state);filter->lua_state=NULL;return1;}return0;}staticintopen_lua_filter(structcgit_filter*base,va_listap){structlua_filter*filter=(structlua_filter*)base;inti;if(init_lua_filter(filter))return1;hook_write(base,write_lua_filter);lua_getglobal(filter->lua_state,"filter_open");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);return1;}return0;}staticintclose_lua_filter(structcgit_filter*base){structlua_filter*filter=(structlua_filter*)base;intret=0;lua_getglobal(filter->lua_state,"filter_close");if(lua_pcall(filter->lua_state,0,1,0)){error_lua_filter(filter);ret=-1;}else{ret=lua_tonumber(filter->lua_state,-1);lua_pop(filter->lua_state,1);}unhook_write();returnret;}staticvoidfprintf_lua_filter(structcgit_filter*base,FILE*f,constchar*prefix){structlua_filter*filter=(structlua_filter*)base;fprintf(f,"%slua:%s\n",prefix,filter->script_file);}staticstructcgit_filter*new_lua_filter(constchar*cmd,intargument_count){structlua_filter*filter;filter=xmalloc(sizeof(*filter));memset(filter,0,sizeof(*filter));filter->base.open=open_lua_filter;filter->base.close=close_lua_filter;filter->base.fprintf=fprintf_lua_filter;filter->base.cleanup=cleanup_lua_filter;filter->base.argument_count=argument_count;filter->script_file=xstrdup(cmd);return&filter->base;}#endifintcgit_open_filter(structcgit_filter*filter,...){intresult;va_listap;if(!filter)return0;va_start(ap,filter);result=filter->open(filter,ap);va_end(ap);returnresult;}intcgit_close_filter(structcgit_filter*filter){if(!filter)return0;returnfilter->close(filter);}voidcgit_fprintf_filter(structcgit_filter*filter,FILE*f,constchar*prefix){filter->fprintf(filter,f,prefix);}staticconststruct{constchar*prefix;structcgit_filter*(*ctor)(constchar*cmd,intargument_count);}filter_specs[]={{"exec",new_exec_filter},#ifndef NO_LUA{"lua",new_lua_filter},#endif};structcgit_filter*cgit_new_filter(constchar*cmd,filter_typefiltertype){char*colon;inti;size_tlen;intargument_count;if(!cmd||!cmd[0])returnNULL;colon=strchr(cmd,':');len=colon-cmd;/* * In case we're running on Windows, don't allow a single letter before * the colon. */if(len==1)colon=NULL;switch(filtertype){caseAUTH:argument_count=12;break;caseEMAIL:argument_count=2;break;caseOWNER:argument_count=0;break;caseSOURCE:caseABOUT:argument_count=1;break;caseCOMMIT:default:argument_count=0;break;}/* If no prefix is given, exec filter is the default. */if(!colon)returnnew_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))returnfilter_specs[i].ctor(colon+1,argument_count);}die("Invalid filter type: %.*s",(int)len,cmd);}