summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorSergey Poznyakoff <gray@gnu.org>2018-02-13 09:13:40 +0200
committerSergey Poznyakoff <gray@gnu.org>2018-02-13 09:19:08 +0200
commit89aa3e4621f3ab255a59091d3a4d6686fd89516a (patch)
tree85b98688aaddb1e4420172cf977e5282bb97c41b /src
parent2fa19a04b42557ffc78e47a7d53f2911553c8124 (diff)
downloadfileserv-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.
Diffstat (limited to 'src')
-rw-r--r--src/.gitignore1
-rw-r--r--src/Makefile.am7
-rw-r--r--src/defidx.html71
-rw-r--r--src/dirconfig.c14
-rw-r--r--src/fileserv.c192
-rw-r--r--src/fileserv.h2
-rw-r--r--src/ftoc.sed23
-rw-r--r--src/idx.c610
-rw-r--r--src/wordsplit.c11
-rw-r--r--src/wordsplit.h4
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">&nbsp;</td>
+ <td class="indexcolsize"> - </td>
+ <td>&nbsp;</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>&nbsp;</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
@@ -219,13 +219,15 @@ dirconfig_init(void)
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
@@ -42,6 +42,8 @@ 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"
@@ -320,56 +322,111 @@ 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;
}
@@ -732,6 +789,27 @@ http_error(struct MHD_Connection *connection,
}
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,
@@ -743,11 +821,9 @@ fileserv_handler(void *cls,
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;
@@ -761,35 +837,39 @@ fileserv_handler(void *cls,
}
*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;
}
@@ -827,6 +907,9 @@ main(int argc, char **argv)
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) {
@@ -845,6 +928,9 @@ main(int argc, char **argv)
case 'h':
usage();
exit(0);
+ case 'i':
+ parse_template_file(optarg);
+ break;
case 'm':
mime_types_file = optarg;
break;
diff --git a/src/fileserv.h b/src/fileserv.h
index 9502144..816864d 100644
--- a/src/fileserv.h
+++ b/src/fileserv.h
@@ -24,6 +24,8 @@
extern char *progname;
extern char *pidfile;
+extern char *index_css;
+extern char *tmpdir;
void error(char const *fmt, ...);
void info(char const *fmt, ...);
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
@@ -1034,9 +1034,14 @@ expvar (struct wordsplit *wsp, const char *str, size_t len,
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);
diff --git a/src/wordsplit.h b/src/wordsplit.h
index eed88bc..b570c9f 100644
--- a/src/wordsplit.h
+++ b/src/wordsplit.h
@@ -81,8 +81,8 @@ struct wordsplit
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 to:

Send suggestions and report system problems to the System administrator.