diff options
author | Sergey Poznyakoff <gray@gnu.org> | 2018-02-13 09:13:40 +0200 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org> | 2018-02-13 09:19:08 +0200 |
commit | 89aa3e4621f3ab255a59091d3a4d6686fd89516a (patch) | |
tree | 85b98688aaddb1e4420172cf977e5282bb97c41b | |
parent | 2fa19a04b42557ffc78e47a7d53f2911553c8124 (diff) | |
download | fileserv-89aa3e4621f3ab255a59091d3a4d6686fd89516a.tar.gz fileserv-89aa3e4621f3ab255a59091d3a4d6686fd89516a.tar.bz2 |
Initial support for auto-generated directory indexes
* src/Makefile.am: Add new sources. Build defidx.h
* src/defidx.html: New file.
* src/dirconfig.c (dirconfig_free): Handle NULL argument.
* src/fileserv.c (index_css, tmpdir): New globals.
(get_file_name): Rewrite.
(fileserv_handler): Rewrite.
* src/fileserv.h (index_css, tmpdir): New externs.
* src/ftoc.sed: New file.
* src/idx.c: New file.
* src/wordsplit.c (expvar): Gracefully handle NULL values in
ENV_KV environment.
* src/wordsplit.h: Fix typo in a comment.
-rw-r--r-- | src/.gitignore | 1 | ||||
-rw-r--r-- | src/Makefile.am | 7 | ||||
-rw-r--r-- | src/defidx.html | 71 | ||||
-rw-r--r-- | src/dirconfig.c | 14 | ||||
-rw-r--r-- | src/fileserv.c | 192 | ||||
-rw-r--r-- | src/fileserv.h | 2 | ||||
-rw-r--r-- | src/ftoc.sed | 23 | ||||
-rw-r--r-- | src/idx.c | 610 | ||||
-rw-r--r-- | src/wordsplit.c | 11 | ||||
-rw-r--r-- | src/wordsplit.h | 4 |
10 files changed, 870 insertions, 65 deletions
diff --git a/src/.gitignore b/src/.gitignore index 31a0273..633d03f 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -1 +1,2 @@ fileserv +defidx.h diff --git a/src/Makefile.am b/src/Makefile.am index b9dcfe8..c2fabca 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,9 +1,14 @@ bin_PROGRAMS=fileserv fileserv_SOURCES=fileserv.c runas.c fileserv.h logger.c pidfile.c\ - wordsplit.c wordsplit.h catfile.c dirconfig.c + wordsplit.c wordsplit.h catfile.c dirconfig.c idx.c defidx.h +BUILT_SOURCES=defidx.h if FSRV_WRAP fileserv_SOURCES += wrapacl.c endif dist_man_MANS=fileserv.8 LDADD = ../mimetypes/libmimetypes.a AM_CPPFLAGS = -I $(top_srcdir)/mimetypes +EXTRA_DIST=ftoc.sed defidx.html +.html.h: + $(AM_V_GEN)sed -f $(srcdir)/ftoc.sed $< > $@ + diff --git a/src/defidx.html b/src/defidx.html new file mode 100644 index 0000000..83935bd --- /dev/null +++ b/src/defidx.html @@ -0,0 +1,71 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> +<html> + <head> + <title>Index of {% $URI %}</title> + {% if $INDEXCSS %} + <link rel="stylesheet" href="{% $INDEXCSS %}" type="text/css"> + {% endif %} + </head> + <body> + <h1 id="indextitle">Index of {% $URI %}</h1> + <table id="indexlist"> + <tr class="indexhead"> + <th class="indexcolicon"> + {% if $(icon blank) %} + <img src="{% $(icon blank) %}" alt="[ICO]"> + {% else %} + [ICO] + {% endif %} + </th> + <th class="indexcolname"> + <a href="?C=N;O=D">Name</a> + </th> + <th class="indexcollastmod"> + <a href="?C=M;O=A">Last modified</a> + </th> + <th class="indexcolsize"> + <a href="?C=S;O=A">Size</a> + </th> + <th class="indexcoldesc"> + <a href="?C=D;O=A">Description</a> + </th> + </tr> + <tr class="indexbreakrow"> + <th colspan="5"><hr></th> + </tr> + <tr class="even"> + <td class="indexcolicon"> + {% if $(icon back) %} + <img src="{% $(icon 'back') %}" alt="[PARENTDIR]"> + {% else %} + [PARENTDIR] + {% endif %} + </td> + <td class="indexcolname"> + <a href="/">Parent Directory</a> + </td> + <td class="indexcollastmod"> </td> + <td class="indexcolsize"> - </td> + <td> </td> + </tr> + {% loop %} + <tr class="{% $ROWCLASS %}"> + <td class="indexcolicon"> + {% if $(icon $FILETYPE) %} + <img src="{% $(icon $FILETYPE) %}" alt="[{% $(alt $FILETYPE) %}]"> + {% else %} + [{% $(alt $FILETYPE) %}] + {% endif %} + </td> + <td class="indexcolname"> + <a href="{% $FILENAME %}">{% $FILENAME %}</a> + </td> + <td class="indexcollastmod">{% $FILETIME %}</td> + <td class="indexcolsize">{% $FILESIZE %}</td> + <td> </td> + </tr> + {% endloop %} + <tr class="indexbreakrow"><th colspan="5"><hr></th></tr> + </table> +</body> +</html> diff --git a/src/dirconfig.c b/src/dirconfig.c index a761401..f5d1146 100644 --- a/src/dirconfig.c +++ b/src/dirconfig.c @@ -216,17 +216,19 @@ dirconfig_init(void) return p; } void dirconfig_free(DIRCONFIG *conf) { - if (conf->index_files) { - size_t i; - for (i = 0; conf->index_files[i]; i++) - free(conf->index_files[i]); - free(conf->index_files); + if (conf) { + if (conf->index_files) { + size_t i; + for (i = 0; conf->index_files[i]; i++) + free(conf->index_files[i]); + free(conf->index_files); + } + free(conf); } - free(conf); } diff --git a/src/fileserv.c b/src/fileserv.c index dc218de..f25b6ad 100644 --- a/src/fileserv.c +++ b/src/fileserv.c @@ -39,12 +39,14 @@ #include <sys/queue.h> char *progname; int verbose; /* reserved for future use */ char *address = "0.0.0.0"; char *forwarded_header = "X-Forwarded-For"; +char *index_css; +char *tmpdir; #ifndef DEFAULT_SERVICE # define DEFAULT_SERVICE "8080" #endif static void @@ -317,62 +319,117 @@ find_index_file(char const *dir, DIRCONFIG *conf, char **index_file, static inline int errno_to_http_code(int ec) { return ec == ENOENT ? MHD_HTTP_NOT_FOUND : MHD_HTTP_FORBIDDEN; } +typedef struct file_resp { + int fd; + char *file_name; + DIRCONFIG *conf; + struct stat st; + char *location; +} FILE_RESP; + +static void +file_resp_init(FILE_RESP *resp) +{ + memset(resp, 0, sizeof(*resp)); + resp->fd = -1; +} + +static void +file_resp_free(FILE_RESP *resp) +{ + if (resp->fd >= 0) + close(resp->fd); + free(resp->file_name); + dirconfig_free(resp->conf); + free(resp->location); +} + int -get_file_name(char const *host, char const *url, char **fname, struct stat *stp) +get_file_name(char const *host, char const *url, FILE_RESP *resp) { struct urimap const *map; - char *file_name; char *cf; - struct stat st; - DIRCONFIG *conf; + + file_resp_init(resp); map = urimap_find(host, url); if (!map) return MHD_HTTP_NOT_FOUND; - file_name = catfile_n(map->dir, map->dir_len, url + map->uri_len); - if (lstat(file_name, &st)) + resp->file_name = catfile_n(map->dir, map->dir_len, url + map->uri_len); + if (lstat(resp->file_name, &resp->st)) return errno_to_http_code(errno); + + resp->conf = dirconfig(resp->file_name, map->dir_len); - conf = dirconfig(file_name, map->dir_len); + if (S_ISDIR(resp->st.st_mode)) { + int res; + + if (url[strlen(url) - 1] != '/') { + resp->location = catfile(url, ""); + return MHD_HTTP_MOVED_PERMANENTLY; + } - if (S_ISDIR(st.st_mode)) { - int res = find_index_file(file_name, conf, &cf, &st); + res = find_index_file(resp->file_name, resp->conf, &cf, + &resp->st); if (res == MHD_HTTP_OK) { - free(file_name); - file_name = cf; - } else if (conf->listing) { + free(resp->file_name); + resp->file_name = cf; + } else if (resp->conf->listing) { //FIXME - return MHD_HTTP_FORBIDDEN; - } else + char *template = catfile(tmpdir, "idxXXXXXX"); + int fd = mkstemp(template); + if (fd == -1) { + error("can't create temporary file name: %s", + strerror(errno)); + /*FIXME: leak */ + return MHD_HTTP_INTERNAL_SERVER_ERROR; + } + unlink(template); + free(template); + res = directory_index(fd, resp->conf, url, + resp->file_name); + if (res) { + close(fd); + return MHD_HTTP_FORBIDDEN; + } + fstat(fd, &resp->st); + resp->fd = fd; + return MHD_HTTP_OK; + } else return res; } - if (S_ISLNK(st.st_mode)) { - if (!conf->follow) + if (S_ISLNK(resp->st.st_mode)) { + if (!resp->conf->follow) return MHD_HTTP_FORBIDDEN; - cf = cfname(file_name); + + cf = cfname(resp->file_name); if (!cf) { - free(file_name); return MHD_HTTP_NOT_FOUND; } else if (urimap_find_dir(host, cf)) { - free(file_name); - file_name = cf; + free(resp->file_name); + resp->file_name = cf; } else { - free(file_name); free(cf); return MHD_HTTP_NOT_FOUND; } - } else if (!S_ISREG(st.st_mode)) + } else if (!S_ISREG(resp->st.st_mode)) { return MHD_HTTP_FORBIDDEN; - - *fname = file_name; - *stp = st; + } + + resp->fd = open(resp->file_name, O_RDONLY); + if (resp->fd == -1) { + error("can't open %s: %s", + resp->file_name, strerror(errno)); + return errno_to_http_code(errno); + } + fstat(resp->fd, &resp->st); return MHD_HTTP_OK; } /* Returns 1 if ADDR is a valid string representation of IPv4 address */ static int @@ -729,28 +786,47 @@ http_error(struct MHD_Connection *connection, ret = MHD_queue_response(connection, status, response); MHD_destroy_response(response); return ret; } static int +http_redirect(struct MHD_Connection *connection, + char const *method, char const *url, + int status, char const *loc) +{ + int ret; + struct MHD_Response *response; + + http_log(connection, method, url, status, NULL); + response = MHD_create_response_from_buffer (strlen(loc), + xstrdup(loc), + MHD_RESPMEM_MUST_FREE); + MHD_add_response_header(response, + MHD_HTTP_HEADER_LOCATION, + loc); + + ret = MHD_queue_response(connection, status, response); + MHD_destroy_response(response); + return ret; +} + +static int fileserv_handler(void *cls, struct MHD_Connection *conn, const char *url, const char *method, const char *version, const char *upload_data, size_t *upload_data_size, void **con_cls) { static int aptr; char const *host = MHD_lookup_connection_value(conn, MHD_HEADER_KIND, MHD_HTTP_HEADER_HOST); - char *file_name; - struct stat st; + FILE_RESP resp; struct MHD_Response *response; int ret; - int fd; char const *type; int status; if (strcmp(method, MHD_HTTP_METHOD_GET) && strcmp(method, MHD_HTTP_METHOD_HEAD)) return MHD_NO; /* unexpected method */ @@ -758,41 +834,45 @@ fileserv_handler(void *cls, if (&aptr != *con_cls) { *con_cls = &aptr; return MHD_YES; } *con_cls = NULL; - status = get_file_name(host, url, &file_name, &st); - if (status != MHD_HTTP_OK) + status = get_file_name(host, url, &resp); + switch (status) { + case MHD_HTTP_OK: + break; + case MHD_HTTP_MOVED_PERMANENTLY: + case MHD_HTTP_FOUND: + ret = http_redirect(conn, method, url, status, + resp.location); + file_resp_free(&resp); + return ret; + default: + file_resp_free(&resp); return http_error(conn, method, url, status, NULL); - - fd = open(file_name, O_RDONLY); - if (fd == -1) { - free(file_name); - return http_error(conn, method, url, - errno_to_http_code(errno), - NULL); } - type = get_file_type(file_name); - free(file_name); + if (resp.file_name) + type = get_file_type(resp.file_name); + else + type = NULL; - response = MHD_create_response_from_fd64(st.st_size, fd); - if (!response) { - close(fd); - return MHD_NO; - } - - if (type) - MHD_add_response_header(response, - MHD_HTTP_HEADER_CONTENT_TYPE, - type); - - ret = MHD_queue_response(conn, MHD_HTTP_OK, response); - MHD_destroy_response(response); - http_log(conn, method, url, MHD_HTTP_OK, &st); + response = MHD_create_response_from_fd64(resp.st.st_size, resp.fd); + if (response) { + resp.fd = -1; + if (type) + MHD_add_response_header(response, + MHD_HTTP_HEADER_CONTENT_TYPE, + type); + ret = MHD_queue_response(conn, MHD_HTTP_OK, response); + MHD_destroy_response(response); + http_log(conn, method, url, MHD_HTTP_OK, &resp.st); + } else + ret = MHD_NO; + file_resp_free(&resp); return ret; } static void fileserv_error_printer (char const *msg) @@ -824,12 +904,15 @@ main(int argc, char **argv) if (p) progname = p + 1; else progname = argv[0]; mimetypes_error_printer = fileserv_error_printer; + tmpdir = getenv("TMP"); + if (!tmpdir) + tmpdir = "/tmp"; while ((c = getopt(argc, argv, "a:F:fg:i:hm:p:t:x:u:v")) != EOF) { switch (c) { case 'a': address = optarg; break; @@ -842,12 +925,15 @@ main(int argc, char **argv) case 'g': group = optarg; break; case 'h': usage(); exit(0); + case 'i': + parse_template_file(optarg); + break; case 'm': mime_types_file = optarg; break; case 'p': pidfile = optarg; break; diff --git a/src/fileserv.h b/src/fileserv.h index 9502144..816864d 100644 --- a/src/fileserv.h +++ b/src/fileserv.h @@ -21,12 +21,14 @@ #ifndef SIZE_T_MAX # define SIZE_T_MAX ((size_t)-1) #endif extern char *progname; extern char *pidfile; +extern char *index_css; +extern char *tmpdir; void error(char const *fmt, ...); void info(char const *fmt, ...); void syslog_enable(void); void set_log_facility(char const *arg); diff --git a/src/ftoc.sed b/src/ftoc.sed new file mode 100644 index 0000000..9e5d34f --- /dev/null +++ b/src/ftoc.sed @@ -0,0 +1,23 @@ +# This file is part of fileserv +# Copyright (C) 2009-2018 Sergey Poznyakoff +# Distributed under the terms of the GNU General Public License, either +# version 3, or (at your option) any later version. See file COPYING +# for the text of the license. + +# Provide leading quote +1i\ +"\\ + +# Provide trailing quote +$a\ +" + +# Remove empty lines and comments +/ *#/d +/^ *$/d +# Escape quotes and backslashes +s/["\]/\\&/g +# Add newline and continuation character at the end of each line +s/$/\\n\\/ +# End + diff --git a/src/idx.c b/src/idx.c new file mode 100644 index 0000000..623acbb --- /dev/null +++ b/src/idx.c @@ -0,0 +1,610 @@ +#include <stdlib.h> +#include <stdio.h> +#include <sys/stat.h> +#include <errno.h> +#include <assert.h> +#include <string.h> +#include <sys/queue.h> +#include <sys/types.h> +#include <dirent.h> +#include <unistd.h> +#include <time.h> +#include "fileserv.h" +#include "mimetypes.h" +#include "wordsplit.h" + +enum index_node_type { + NODE_TEXT, + NODE_LOOP, + NODE_COND, + NODE_EXPR +}; + +typedef STAILQ_HEAD(index_node_list, index_node) INDEX_NODE_LIST; + +struct index_cond { + char *expr; + INDEX_NODE_LIST branch[2]; +}; + +struct index_node { + int type; + STAILQ_ENTRY(index_node) next; + union { + char *text; + struct index_node_list loop; + struct index_cond cond; + } v; +}; + +typedef struct index_node INDEX_NODE; + +struct condition { + INDEX_NODE *node; + int brn; + STAILQ_ENTRY(condition) next; +}; +typedef STAILQ_HEAD(, condition) COND_LIST; + +struct template_state { + int escape:1; + int loop:1; + COND_LIST cond; + char const *start; + char const *cur; +}; + +static void parse_template(INDEX_NODE_LIST *nodes, struct template_state *st); + +static INDEX_NODE * +new_text_node(int type, char const *start, size_t len) +{ + INDEX_NODE *np = xmalloc(sizeof(*np)); + np->type = type; + np->v.text = xmalloc(len + 1); + memcpy(np->v.text, start, len); + np->v.text[len] = 0; + return np; +} + +static INDEX_NODE * +next_text_node(struct template_state *st) +{ + INDEX_NODE *np; + size_t len; + + st->start = st->cur; + while (*st->cur) { + if (*st->cur == '{' && st->cur[1] == '%') { + st->escape = 1; + break; + } + st->cur++; + } + + return new_text_node(NODE_TEXT, st->start, st->cur - st->start); +} + +static INDEX_NODE * +parse_cond(struct template_state *st, int brn, size_t off, size_t len) +{ + struct condition cond; + INDEX_NODE *np; + + while (off < len && isspace(st->start[off])) + off++; + + np = xmalloc(sizeof(*np)); + np->type = NODE_COND; + len -= off; + np->v.cond.expr = xmalloc(len + 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 INDEX_NODE * +next_escape_node(struct template_state *st) +{ + INDEX_NODE *np; + size_t len; + + st->start = st->cur + 2; + while (*st->cur) { + if (*st->cur == '%' && st->cur[1] == '}') { + st->escape = 0; + break; + } + st->cur++; + } + + np = xmalloc(sizeof(*np)); + if (st->escape) { + st->escape = 0; + return new_text_node(NODE_TEXT, st->start, + 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) { + assert(st->loop == 0); + st->loop = 1; + np = xmalloc(sizeof(*np)); + np->type = NODE_LOOP; + parse_template(&np->v.loop, st); + return np; + } + + if (memcmp(st->start, "endloop", len) == 0) { + assert(st->loop); + 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) { + STAILQ_REMOVE_HEAD(&st->cond, next); + return NULL; + } + + + if (memcmp(st->start, "else", len) == 0) { + struct condition *cond = STAILQ_FIRST(&st->cond); + assert(cond); + np = cond->node; + assert(STAILQ_FIRST(&np->v.cond.branch[!cond->brn]) == NULL); + parse_template(&np->v.cond.branch[!cond->brn], st); + return NULL; + } + + return new_text_node(NODE_EXPR, st->start, len); +} + +static INDEX_NODE * +next_node(struct template_state *st) +{ + if (*st->cur == 0) + return NULL; + return (st->escape ? next_escape_node : next_text_node)(st); +} + +static void +parse_template(INDEX_NODE_LIST *nodes, struct template_state *st) +{ + INDEX_NODE *np; + + STAILQ_INIT(nodes); + + while (np = next_node(st)) + STAILQ_INSERT_TAIL(nodes, np, next); +} + +INDEX_NODE_LIST node_list = STAILQ_HEAD_INITIALIZER(node_list); + +void +parse_template_string(char const *str) +{ + struct template_state state; + + state.escape = 0; + state.loop = 0; + STAILQ_INIT(&state.cond); + state.start = str; + state.cur = str; + + parse_template(&node_list, &state); + + assert(state.escape == 0); + assert(state.loop == 0); + assert(STAILQ_FIRST(&state.cond) == NULL); +} + +void +parse_template_file(char const *file_name) +{ + struct stat st; + char *buf; + FILE *fp; + ssize_t n; + + if (stat(file_name, &st)) { + error("can't stat %s: %s", file_name, strerror(errno)); + return; + } + fp = fopen(file_name, "r"); + if (!fp) { + error("can't open %s: %s", file_name, strerror(errno)); + return; + } + + buf = xmalloc(st.st_size + 1); + n = fread(buf, st.st_size, 1, fp); + if (n == 1) { + buf[st.st_size] = 0; + parse_template_string(buf); + } else + error("error reading from %s: %s", file_name, strerror(errno)); + fclose(fp); + free(buf); +} + +typedef struct idxent { + char *name; + struct stat st; + char const *type; + STAILQ_ENTRY(idxent) next; +} IDXENT; +typedef STAILQ_HEAD(, idxent) IDXLIST; + +void +idxlist_free(IDXLIST *lst) +{ + IDXENT *ent; + + while ((ent = STAILQ_FIRST(lst))) { + STAILQ_REMOVE_HEAD(lst, next); + free(ent->name); + free(ent); + } +} + +static int +idxlist_scan(IDXLIST *idx_list, char *path, DIRCONFIG *conf) +{ + DIR *dir; + struct dirent *ent; + IDXENT *idxent; + + dir = opendir(path); + if (!dir) { + int ec = errno; + error("can't open directory %s: %s", + path, strerror(ec)); + return ec; + } + STAILQ_INIT(idx_list); + while ((ent = readdir(dir))) { + char *name; + struct stat st; + + if (ent->d_name[0] == '.' + && (ent->d_name[1] == 0 + || (ent->d_name[1] == '.' && ent->d_name[2] == 0))) + continue; + + name = catfile(path, ent->d_name); + + if (stat(name, &st)) { + error("can't stat %s: %s", name, strerror(errno)); + free(name); + continue; + } + + if (conf->list_unreadable == 0) { + if (access(name, R_OK)) { + free(name); + continue; + } + } + + idxent = xmalloc(sizeof(*idxent)); + idxent->name = xstrdup(ent->d_name); + idxent->st = st; + idxent->type = get_file_type(name); + STAILQ_INSERT_TAIL(idx_list, idxent, next); + free(name); + } + closedir(dir); + + /* FIXME: sort list */ + return 0; +} + +typedef struct { + int fd; + DIRCONFIG *conf; + IDXENT *ent; + int n; + struct wordsplit ws; + int wsflags; +#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(INDEX_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("index write error: %s", strerror(errno)); + else + error("index write error: %s", "disk full?"); + return -1; + } + return 0; +} + +static int +node_expr_eval(INDEX_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("index write error: %s", strerror(errno)); + else + error("index write error: %s", "disk full?"); + return -1; + } + return 0; +} + +static int +node_cond_eval(INDEX_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(INDEX_NODE *node, EVAL_ENV *env) +{ + while (env->ent) { + //FIXME: set variables? + if (node_list_eval(&node->v.loop, env)) + return -1; + env->ent = STAILQ_NEXT(env->ent, next); + env->n++; + } + return 0; +} + +static int (*eval[])(INDEX_NODE *node, EVAL_ENV *env) = { + [NODE_TEXT] = node_text_eval, + [NODE_LOOP] = node_loop_eval, + [NODE_COND] = node_cond_eval, + [NODE_EXPR] = node_expr_eval +}; + +int +node_list_eval(INDEX_NODE_LIST *nodes, EVAL_ENV *env) +{ + INDEX_NODE *node; + + STAILQ_FOREACH(node, nodes, next) { + assert(node->type >= 0 + && node->type < sizeof(eval)/sizeof(eval[0]) + && eval[node->type]); + if (eval[node->type](node, env)) + return -1; + } + 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 int +exp_rowclass(char **ret, EVAL_ENV *env) +{ + *ret = xstrdup(env->n % 2 ? "odd" : "even"); + return WRDSE_OK; +} + +static int +exp_filename(char **ret, EVAL_ENV *env) +{ + if (env->ent) { + *ret = xstrdup(env->ent->name); + return WRDSE_OK; + } 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]); + *ret = strdup(buf); + return WRDSE_OK; + } else + return WRDSE_UNDEF; +} + +static int +exp_filetype(char **ret, EVAL_ENV *env) +{ + if (env->ent && env->ent->type) { + *ret = xstrdup(env->ent->type); + return WRDSE_OK; + } 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)); + *ret = xstrdup(buf); + return WRDSE_OK; + } else + return WRDSE_UNDEF; +} + +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 }, + { 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_alt(char **ret, char **argv, EVAL_ENV *env) +{ + char *s = strdup(argv[1] ? argv[1] : ""); + if (!s) + return WRDSE_NOSPACE; + *ret = s; + for (; *s; s++) + *s = toupper(*s); + return WRDSE_OK; +} + +static int +cmd_icon(char **ret, char **argv, EVAL_ENV *env) +{ + /* FIXME */ + *ret = strdup(""); + return *ret ? WRDSE_OK : WRDSE_NOSPACE; +} + +static int +template_command(char **ret, const char *cmd, size_t len, char **argv, + void *clos) +{ + EVAL_ENV *env = clos; + + if (strcmp(argv[0], "icon") == 0) { + return cmd_icon(ret, argv, clos); + } + + if (strcmp(argv[0], "alt") == 0) { + return cmd_alt(ret, argv, clos); + } + return WRDSE_UNDEF; +} + +static char defidx[] = +#include "defidx.h" + ; + +int +directory_index(int fd, DIRCONFIG *conf, char *uri, char *path) +{ + int rc; + IDXLIST idxlist; + EVAL_ENV env; + char const *varenv[5]; + + if (STAILQ_EMPTY(&node_list)) { + parse_template_string(defidx); + assert(!STAILQ_EMPTY(&node_list)); + } + + rc = idxlist_scan(&idxlist, path, conf); + if (rc) + return rc; + + env.fd = fd; + env.conf = conf; + env.ent = STAILQ_FIRST(&idxlist); + env.n = 0; + + 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); + + idxlist_free(&idxlist); + if (env.wsflags & WRDSF_REUSE) + wordsplit_free(&env.ws); + return rc; +} diff --git a/src/wordsplit.c b/src/wordsplit.c index 4884a22..5cd8daa 100644 --- a/src/wordsplit.c +++ b/src/wordsplit.c @@ -1031,15 +1031,20 @@ expvar (struct wordsplit *wsp, const char *str, size_t len, } else { rc = wordsplit_find_env (wsp, str, i, &vptr); if (rc == WRDSE_OK) { - value = strdup (vptr); - if (!value) - rc = WRDSE_NOSPACE; + if (vptr) + { + value = strdup (vptr); + if (!value) + rc = WRDSE_NOSPACE; + } + else + rc = WRDSE_UNDEF; } else if (wsp->ws_flags & WRDSF_GETVAR) rc = wsp->ws_getvar (&value, str, i, wsp->ws_closure); else rc = WRDSE_UNDEF; diff --git a/src/wordsplit.h b/src/wordsplit.h index eed88bc..b570c9f 100644 --- a/src/wordsplit.h +++ b/src/wordsplit.h @@ -78,14 +78,14 @@ struct wordsplit void *ws_closure; /* [Input] (WRDSF_CLOSURE) Passed as the CLOS argument to ws_getvar and ws_command. */ int (*ws_command) (char **ret, const char *cmd, size_t len, char **argv, void *clos); /* [Input] (!WRDSF_NOCMD) Returns in the memory location pointed to by RET the expansion of - the command CMD (LEN bytes nong). If WRDSF_ARGV - flag is set, ARGV contains CMD split out to + the command CMD (LEN bytes nong). If WRDSO_ARGV + option is set, ARGV contains CMD split out to words. Otherwise ARGV is NULL. See ws_getvar for a discussion of possible return values. */ const char *ws_input; /* Input string (the S argument to wordsplit. */ |