#include "fileserv.h" #include #include #include #include #include #include "dirls.h" #include "wordsplit.h" enum tmpl_node_type { NODE_TEXT, NODE_LOOP, NODE_COND, NODE_EXPR }; typedef STAILQ_HEAD(tmpl_node_list, tmpl_node) TMPL_NODE_LIST; struct tmpl_cond { char *expr; TMPL_NODE_LIST branch[2]; }; struct tmpl_node { int type; STAILQ_ENTRY(tmpl_node) next; union { char *text; struct tmpl_node_list loop; struct tmpl_cond cond; } v; }; typedef struct tmpl_node TMPL_NODE; struct condition { TMPL_NODE *node; int brn; STAILQ_ENTRY(condition) next; }; typedef STAILQ_HEAD(, condition) COND_LIST; struct eval_env; /* Type-agnostic node functions */ struct node_class { void (*node_init)(TMPL_NODE *); void (*node_free)(TMPL_NODE *); int (*node_eval)(TMPL_NODE *, struct eval_env *); }; static int node_text_eval(TMPL_NODE *, struct eval_env *); static int node_loop_eval(TMPL_NODE *, struct eval_env *); static int node_cond_eval(TMPL_NODE *, struct eval_env *); static int node_expr_eval(TMPL_NODE *, struct eval_env *); static void node_loop_init(TMPL_NODE *); static void node_loop_free(TMPL_NODE *); static void node_cond_init(TMPL_NODE *); static void node_cond_free(TMPL_NODE *); static struct node_class node_class[] = { [NODE_TEXT] = { NULL, NULL, node_text_eval }, [NODE_LOOP] = { node_loop_init, node_loop_free, node_loop_eval }, [NODE_COND] = { node_cond_init, node_cond_free, node_cond_eval }, [NODE_EXPR] = { NULL, NULL, node_expr_eval } }; #define ASSERT_NODE_TYPE(n) \ assert((n)->type >= 0 && (n)->type <= sizeof(node_class)/sizeof(node_class[0])) static void node_init(TMPL_NODE *node) { ASSERT_NODE_TYPE(node); if (node_class[node->type].node_init) return node_class[node->type].node_init(node); } static void node_free(TMPL_NODE *node) { ASSERT_NODE_TYPE(node); if (node_class[node->type].node_free) node_class[node->type].node_free(node); free(node); } static int node_eval(TMPL_NODE *node, struct eval_env *env) { ASSERT_NODE_TYPE(node); assert(node_class[node->type].node_eval); return node_class[node->type].node_eval(node, env); } /* Node list functions */ static void node_list_free(TMPL_NODE_LIST *nlist) { TMPL_NODE *node; while ((node = STAILQ_FIRST(nlist))) { STAILQ_REMOVE_HEAD(nlist, next); node_free(node); } } static int node_list_eval(TMPL_NODE_LIST *nlist, struct eval_env *env) { TMPL_NODE *node; int rc = 0; STAILQ_FOREACH(node, nlist, next) { rc = node_eval(node, env); if (rc) break; } return rc; } /* Node-specific initialization and deallocation functions */ static void node_loop_init(TMPL_NODE *node) { STAILQ_INIT(&node->v.loop); } static void node_loop_free(TMPL_NODE *node) { node_list_free(&node->v.loop); } static void node_cond_init(TMPL_NODE *node) { STAILQ_INIT(&node->v.cond.branch[0]); STAILQ_INIT(&node->v.cond.branch[1]); } static void node_cond_free(TMPL_NODE *node) { node_list_free(&node->v.cond.branch[0]); node_list_free(&node->v.cond.branch[1]); } /* Parser error codes */ enum template_error { TMPL_NOERR, TMPL_ERR_LOOP_NESTING, TMPL_ERR_ENDLOOP_WITHOUT_LOOP, TMPL_ERR_ENDIF_WITHOUT_IF, TMPL_ERR_ELSE_WITHOUT_IF, TMPL_ERR_DUP_ELSE, TMPL_ERR_ESCAPE_NOT_CLOSED, TMPL_ERR_LOOP_NOT_CLOSED, TMPL_ERR_COND_NOT_CLOSED, TMPL_ERR_NOMEM }; /* Corresponding textual descriptions */ static char const *tmpl_err_str[] = { "no error", "attempt to nest loops", "endloop without loop", "endif without if", "else without if", "duplicate else", "escape not closed", "loop not closed", "conditional not closed", "not enough memory" }; /* Template parser state */ typedef struct template_parser_state { int escape:1; /* True if in a {% %} construct */ int loop:1; /* True if within a loop / endloop block */ COND_LIST cond; /* Stack of nested conditionals */ char const *start; /* Start of text fragment being parsed */ char const *cur; /* Current position in the text */ int error; /* Error state */ } TEMPLATE_PARSER_STATE; /* Allocate and initialize single node */ static inline TMPL_NODE * node_alloc(TEMPLATE_PARSER_STATE *st, int type, size_t extra) { TMPL_NODE *np = calloc(1, sizeof(*np) + extra); if (np) np->type = type; else st->error = TMPL_ERR_NOMEM; node_init(np); return np; } /* Template parser */ static void parse_template(TMPL_NODE_LIST *nodes, TEMPLATE_PARSER_STATE *st); static TMPL_NODE * new_text_node(TEMPLATE_PARSER_STATE *st, int type, size_t len) { TMPL_NODE *np = node_alloc(st, type, len + 1); if (np) { np->v.text = (char*)(np + 1); memcpy(np->v.text, st->start, len); np->v.text[len] = 0; } return np; } static TMPL_NODE * next_text_node(TEMPLATE_PARSER_STATE *st) { st->start = st->cur; while (*st->cur) { if (*st->cur == '{' && st->cur[1] == '%') { st->escape = 1; break; } st->cur++; } return new_text_node(st, NODE_TEXT, st->cur - st->start); } static TMPL_NODE * parse_cond(TEMPLATE_PARSER_STATE *st, int brn, size_t off, size_t len) { struct condition cond; TMPL_NODE *np; while (off < len && isspace(st->start[off])) off++; len -= off; np = node_alloc(st, NODE_COND, len + 1); if (np) { np->v.cond.expr = (char*)(np + 1); memcpy(np->v.cond.expr, st->start + off, len); np->v.cond.expr[len] = 0; cond.node = np; cond.brn = brn; STAILQ_INSERT_HEAD(&st->cond, &cond, next); STAILQ_INIT(&np->v.cond.branch[!cond.brn]); parse_template(&np->v.cond.branch[cond.brn], st); } return np; } static TMPL_NODE * next_escape_node(TEMPLATE_PARSER_STATE *st) { size_t len; st->start = st->cur + 2; while (*st->cur) { if (*st->cur == '%' && st->cur[1] == '}') { st->escape = 0; break; } st->cur++; } if (st->escape) { st->escape = 0; return new_text_node(st, NODE_TEXT, st->cur - st->start); } while (st->start < st->cur && isspace(st->start[0])) st->start++; len = st->cur - st->start; while (len > 0 && isspace(st->start[len-1])) len--; st->cur += 2; if (memcmp(st->start, "loop", len) == 0) { TMPL_NODE *np; if (st->loop) { st->error = TMPL_ERR_LOOP_NESTING; return NULL; } st->loop = 1; np = node_alloc(st, NODE_LOOP, 0); if (np) parse_template(&np->v.loop, st); return np; } if (memcmp(st->start, "endloop", len) == 0) { if (!st->loop) { st->error = TMPL_ERR_ENDLOOP_WITHOUT_LOOP; return NULL; } st->loop = 0; return NULL; } if (len > 2 && (memcmp(st->start, "if", 2) == 0 && isspace(st->start[2]))) return parse_cond(st, 1, 2, len); if (len > 6 && (memcmp(st->start, "unless", 6) == 0 && isspace(st->start[6]))) return parse_cond(st, 0, 6, len); if (memcmp(st->start, "endif", len) == 0) { if (STAILQ_FIRST(&st->cond)) STAILQ_REMOVE_HEAD(&st->cond, next); else st->error = TMPL_ERR_ENDIF_WITHOUT_IF; return NULL; } if (memcmp(st->start, "else", len) == 0) { TMPL_NODE *np; struct condition *cond = STAILQ_FIRST(&st->cond); if (!cond) { st->error = TMPL_ERR_ELSE_WITHOUT_IF; return NULL; } np = cond->node; if (STAILQ_FIRST(&np->v.cond.branch[!cond->brn])) { st->error = TMPL_ERR_DUP_ELSE; return NULL; } parse_template(&np->v.cond.branch[!cond->brn], st); return NULL; } return new_text_node(st, NODE_EXPR, len); } static TMPL_NODE * next_node(TEMPLATE_PARSER_STATE *st) { if (st->error) return NULL; if (*st->cur == 0) return NULL; return (st->escape ? next_escape_node : next_text_node)(st); } static void parse_template(TMPL_NODE_LIST *nodes, TEMPLATE_PARSER_STATE *st) { TMPL_NODE *np; STAILQ_INIT(nodes); while ((np = next_node(st))) STAILQ_INSERT_TAIL(nodes, np, next); } TMPL_NODE_LIST node_list = STAILQ_HEAD_INITIALIZER(node_list); /* index_disabled inhibits creation of directory indexes. It is set only if the built-in index template cannot be compiled and the custom template was not supplied or failed to compile too. */ static int index_disabled; int parse_template_string(char const *str) { TEMPLATE_PARSER_STATE state; state.escape = 0; state.loop = 0; STAILQ_INIT(&state.cond); state.start = str; state.cur = str; state.error = TMPL_NOERR; parse_template(&node_list, &state); if (state.error != TMPL_NOERR) { error("error parsing template near character %d: %s", state.start - str, tmpl_err_str[state.error]); node_list_free(&node_list); return -1; } if (state.escape) state.error = TMPL_ERR_ESCAPE_NOT_CLOSED; else if (state.loop) state.error = TMPL_ERR_LOOP_NOT_CLOSED; else if (STAILQ_FIRST(&state.cond)) state.error = TMPL_ERR_COND_NOT_CLOSED; if (state.error != TMPL_NOERR) { error("error parsing template (at end): %s", tmpl_err_str[state.error]); node_list_free(&node_list); return -1; } return 0; } int parse_template_file(char const *file_name) { struct stat st; char *buf; FILE *fp; ssize_t n; int rc; if (stat(file_name, &st)) { error("can't stat %s: %s", file_name, strerror(errno)); return -1; } fp = fopen(file_name, "r"); if (!fp) { error("can't open %s: %s", file_name, strerror(errno)); return -1; } buf = malloc(st.st_size + 1); if (!buf) { error("can't open %s: %s", file_name, strerror(errno)); fclose(fp); return -1; } n = fread(buf, st.st_size, 1, fp); if (n == 1) { buf[st.st_size] = 0; rc = parse_template_string(buf); } else { error("error reading from %s: %s", file_name, strerror(errno)); rc = 1; } fclose(fp); free(buf); return rc; } /* Directory tmpl scanner */ /* Run-time template evaluator */ typedef struct eval_env { int fd; /* Output file descriptor */ CONFIG const *conf; /* Associated configuration settings */ DIRLSENT *ent; /* Current listing entry */ int n; /* Ordinal number of the entry in the listing */ ICON const *icon; /* Icon associated with the entry */ struct wordsplit ws; /* Word-splitter tool */ int wsflags; /* Its flags */ INDEX_SORT_COL col; /* Sorting column */ INDEX_SORT_ORD ord; /* Sorting order */ #define expansion ws.ws_wordv[0] } EVAL_ENV; static int eval_expand(char const *str, EVAL_ENV *env) { if (wordsplit(str, &env->ws, env->wsflags)) return -1; env->wsflags |= WRDSF_REUSE; return 0; } static int node_text_eval(TMPL_NODE *node, EVAL_ENV *env) { size_t len = strlen(node->v.text); ssize_t n = write(env->fd, node->v.text, len); if (n != len) { if (n == -1) error("tmpl write error: %s", strerror(errno)); else error("tmpl write error: %s", "disk full?"); return -1; } return 0; } static int node_expr_eval(TMPL_NODE *node, EVAL_ENV *env) { size_t len; ssize_t n; if (eval_expand(node->v.text, env)) return -1; len = strlen(env->expansion); n = write(env->fd, env->expansion, len); if (n != len) { if (n == -1) error("tmpl write error: %s", strerror(errno)); else error("tmpl write error: %s", "disk full?"); return -1; } return 0; } static int node_cond_eval(TMPL_NODE *node, EVAL_ENV *env) { if (eval_expand(node->v.cond.expr, env)) return -1; return node_list_eval(&node->v.cond.branch[env->expansion[0] != 0], env); } static int node_loop_eval(TMPL_NODE *node, EVAL_ENV *env) { while (env->ent) { if (node_list_eval(&node->v.loop, env)) return -1; env->ent = STAILQ_NEXT(env->ent, next); env->n++; } return 0; } static inline int streq(const char *v, const char *s, size_t l) { return strncmp(v, s, l) == 0 && v[l] == 0; } static inline int retstr(char **ret, char const *str) { *ret = strdup(str); return *ret ? WRDSE_OK : WRDSE_NOSPACE; } static int exp_rowclass(char **ret, EVAL_ENV *env) { return retstr(ret, env->n % 2 ? "odd" : "even"); } static int exp_filename(char **ret, EVAL_ENV *env) { if (env->ent) return retstr(ret, env->ent->name); else return WRDSE_UNDEF; } static int exp_filesize(char **ret, EVAL_ENV *env) { if (env->ent) { char const *suf[] = { "", "K", "M", "G", "T" }; int i; double s = env->ent->st.st_size; char buf[80]; for (i = 0; i < sizeof(suf)/sizeof(suf[0]); i++) { if (s < 1024) break; s /= 1024; } snprintf(buf, sizeof(buf), "%.1f%s", s, suf[i]); return retstr(ret, buf); } else return WRDSE_UNDEF; } static int exp_mimetype(char **ret, EVAL_ENV *env) { if (env->ent && env->ent->type) return retstr(ret, env->ent->type); else return WRDSE_UNDEF; } static int exp_filetime(char **ret, EVAL_ENV *env) { if (env->ent) { time_t t = env->ent->st.st_mtime; char buf[80]; strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M", gmtime(&t)); return retstr(ret, buf); } else return WRDSE_UNDEF; } static int exp_filetype(char **ret, EVAL_ENV *env) { char const *s = " "; if (env->ent) s = S_ISDIR(env->ent->st.st_mode) ? "DIRECTORY" : "FILE"; return retstr(ret, s); } struct var_dcl { char const *name; int (*exp)(char **, EVAL_ENV *); }; static struct var_dcl var_dcl[] = { { "ROWCLASS", exp_rowclass }, { "FILENAME", exp_filename }, { "FILESIZE", exp_filesize }, { "FILETIME", exp_filetime }, { "FILETYPE", exp_filetype }, { "MIMETYPE", exp_mimetype }, { NULL } }; static int template_getvar(char **ret, const char *var, size_t len, void *clos) { EVAL_ENV *env = clos; struct var_dcl *vd; for (vd = var_dcl; vd->name; vd++) { if (streq(vd->name, var, len)) return vd->exp(ret, env); } return WRDSE_UNDEF; } static int cmd_iconlookup(char **ret, char **argv, EVAL_ENV *env) { ICON const *icon = NULL; if (argv[1]) { icon = icon_by_name(argv[1]); if (!icon && argv[2]) { icon = icon_by_mime(argv[2]); if (!icon && argv[3]) icon = icon_by_type(argv[3]); } } env->icon = icon; return retstr(ret, icon ? icon->src : ""); } static int cmd_iconsrc(char **ret, char **argv, EVAL_ENV *env) { char const *s = ""; if (env->icon) s = env->icon->src; return retstr(ret, s); } static int cmd_iconalt(char **ret, char **argv, EVAL_ENV *env) { char const *s = ""; if (env->icon && env->icon->alt) s = env->icon->alt; return retstr(ret, s); } static int cmd_updir(char **ret, char **argv, EVAL_ENV *env) { char *dir = argv[1]; size_t len; if (!dir) dir = "/"; len = strlen(dir); if (dir[len-1] == '/') len--; while (len > 0 && dir[len-1] != '/') len--; *ret = malloc(len + 1); if (!*ret) return WRDSE_NOSPACE; memcpy(*ret, dir, len); (*ret)[len] = 0; return WRDSE_OK; } static int cmd_sortorder(char **ret, char **argv, EVAL_ENV *env) { *ret = malloc(2); if (!*ret) return WRDSE_NOSPACE; (*ret)[0] = index_sort_ord_to_arg( (argv[1] && index_sort_col_to_arg(env->col) == argv[1][0]) ? !env->ord : ISO_ASC); (*ret)[1] = 0; return WRDSE_OK; } static int template_command(char **ret, const char *cmd, size_t len, char **argv, void *clos) { if (strcmp(argv[0], "iconlookup") == 0) { return cmd_iconlookup(ret, argv, clos); } if (strcmp(argv[0], "iconsrc") == 0) { return cmd_iconsrc(ret, argv, clos); } if (strcmp(argv[0], "iconalt") == 0) { return cmd_iconalt(ret, argv, clos); } if (strcmp(argv[0], "updir") == 0) { return cmd_updir(ret, argv, clos); } if (strcmp(argv[0], "sortorder") == 0) { return cmd_sortorder(ret, argv, clos); } return WRDSE_UNDEF; } static char defidx[] = #include "defidx.h" ; int directory_index(int fd, CONFIG const *conf, char const *uri, char const *path, INDEX_SORT_COL col, INDEX_SORT_ORD ord) { int rc; DIRLS dirls; EVAL_ENV env; char const *varenv[5]; if (index_disabled) return ENOSYS; if (STAILQ_EMPTY(&node_list)) { if (parse_template_string(defidx)) { error("error compiling built-in template!"); index_disabled = 1; return ENOSYS; } } rc = dirls_scan(&dirls, path, conf); if (rc) return rc; dirls_sort(&dirls, col, ord); env.fd = fd; env.conf = conf; env.ent = STAILQ_FIRST(&dirls.list); env.n = 0; env.col = col; env.ord = ord; varenv[0] = "URI"; varenv[1] = uri; varenv[2] = "INDEXCSS"; varenv[3] = index_css; varenv[4] = NULL; env.ws.ws_error = error; env.ws.ws_getvar = template_getvar; env.ws.ws_command = template_command; env.ws.ws_closure = &env; env.ws.ws_env = varenv; env.ws.ws_options = WRDSO_ARGV; env.wsflags = WRDSF_NOSPLIT | WRDSF_WS | WRDSF_ERROR | WRDSF_SHOWERR | WRDSF_GETVAR | WRDSF_CLOSURE | WRDSF_OPTIONS | WRDSF_ENV | WRDSF_ENV_KV ; rc = node_list_eval(&node_list, &env); dirls_free(&dirls); if (env.wsflags & WRDSF_REUSE) wordsplit_free(&env.ws); return rc ? ENOSYS : 0; }