From 9faa7a88b7419b2d6151ac2c3fa52261c876ee8d Mon Sep 17 00:00:00 2001 Message-Id: <9faa7a88b7419b2d6151ac2c3fa52261c876ee8d.1713875895.git.mdw@distorted.org.uk> From: Mark Wooding Date: Mon, 5 May 2008 20:26:38 +0100 Subject: [PATCH] Half way through rewriting web interface. Don't even think about trying to compile this. Organization: Straylight/Edgeware From: Richard Kettlewell --- lib/cgi.c | 23 + lib/cgi.h | 1 + lib/hash.c | 1 + lib/hash.h | 1 + lib/macros-builtin.c | 80 +- lib/macros.c | 44 +- lib/macros.h | 5 + lib/t-cgi.c | 5 +- scripts/macro-docs | 5 + server/Makefile.am | 2 +- server/cgi.c | 678 +------------- server/cgimain.c | 40 +- server/dcgi.c | 767 +--------------- server/macros-disorder.c | 854 ++++++++++++++++++ server/macros-disorder.h | 39 + server/options.c | 217 +++++ server/options.h | 39 + server/{cgi.h => server-cgi.h} | 0 templates/Makefile.am | 8 +- templates/{about.html => about.tmpl} | 0 templates/{choose.html => choose.tmpl} | 0 .../{choosealpha.html => choosealpha.tmpl} | 0 templates/{credits.html => credits.tmpl} | 0 templates/{error.html => error.tmpl} | 0 templates/{help.html => help.tmpl} | 0 templates/{login.html => login.tmpl} | 0 templates/macros.tmpl | 26 + templates/{new.html => new.tmpl} | 0 templates/{playing.html => playing.tmpl} | 0 templates/{prefs.html => prefs.tmpl} | 0 templates/{recent.html => recent.tmpl} | 0 templates/{search.html => search.tmpl} | 0 templates/{stdhead.html => stdhead.tmpl} | 0 .../{stylesheet.html => stylesheet.tmpl} | 0 templates/{topbar.html => topbar.tmpl} | 0 templates/{topbarend.html => topbarend.tmpl} | 0 36 files changed, 1338 insertions(+), 1497 deletions(-) create mode 100644 server/macros-disorder.c create mode 100644 server/macros-disorder.h create mode 100644 server/options.c create mode 100644 server/options.h rename server/{cgi.h => server-cgi.h} (100%) rename templates/{about.html => about.tmpl} (100%) rename templates/{choose.html => choose.tmpl} (100%) rename templates/{choosealpha.html => choosealpha.tmpl} (100%) rename templates/{credits.html => credits.tmpl} (100%) rename templates/{error.html => error.tmpl} (100%) rename templates/{help.html => help.tmpl} (100%) rename templates/{login.html => login.tmpl} (100%) create mode 100644 templates/macros.tmpl rename templates/{new.html => new.tmpl} (100%) rename templates/{playing.html => playing.tmpl} (100%) rename templates/{prefs.html => prefs.tmpl} (100%) rename templates/{recent.html => recent.tmpl} (100%) rename templates/{search.html => search.tmpl} (100%) rename templates/{stdhead.html => stdhead.tmpl} (100%) rename templates/{stylesheet.html => stylesheet.tmpl} (100%) rename templates/{topbar.html => topbar.tmpl} (100%) rename templates/{topbarend.html => topbarend.tmpl} (100%) diff --git a/lib/cgi.c b/lib/cgi.c index 9b4762f..1c1154d 100644 --- a/lib/cgi.c +++ b/lib/cgi.c @@ -333,6 +333,29 @@ char *cgi_makeurl(const char *url, ...) { return d.vec; } +/** @brief Construct a URL from current parameters + * @param url Base URL + * @return Constructed URL + */ +char *cgi_thisurl(const char *url) { + struct dynstr d[1]; + char **keys = hash_keys(cgi_args); + int n; + + dynstr_init(d); + dynstr_append_string(d, url); + if(*keys) { + dynstr_append(d, '?'); + for(n = 0; keys[n]; ++n) { + dynstr_append_string(d, urlencodestring(keys[n])); + dynstr_append(d, '='); + dynstr_append_string(d, cgi_get(keys[n])); + } + } + dynstr_terminate(d); + return d->vec; +} + /* Local Variables: c-basic-offset:2 diff --git a/lib/cgi.h b/lib/cgi.h index 1ad4167..a9944e4 100644 --- a/lib/cgi.h +++ b/lib/cgi.h @@ -33,6 +33,7 @@ void cgi_attr(struct sink *output, const char *name, const char *value); void cgi_opentag(struct sink *output, const char *name, ...); void cgi_closetag(struct sink *output, const char *name); char *cgi_makeurl(const char *url, ...); +char *cgi_thisurl(const char *url); #endif diff --git a/lib/hash.c b/lib/hash.c index a77bd3a..f38e1d9 100644 --- a/lib/hash.c +++ b/lib/hash.c @@ -26,6 +26,7 @@ #include "hash.h" #include "mem.h" #include "log.h" +#include "kvp.h" struct entry { struct entry *next; /* next entry same key */ diff --git a/lib/hash.h b/lib/hash.h index 484fda2..3dfb0ed 100644 --- a/lib/hash.h +++ b/lib/hash.h @@ -22,6 +22,7 @@ #define HASH_H typedef struct hash hash; +struct kvp; hash *hash_new(size_t valuesize); /* Create a new hash */ diff --git a/lib/macros-builtin.c b/lib/macros-builtin.c index b3f69f1..17ec99c 100644 --- a/lib/macros-builtin.c +++ b/lib/macros-builtin.c @@ -36,6 +36,7 @@ #include #include +#include "hash.h" #include "mem.h" #include "macros.h" #include "sink.h" @@ -43,7 +44,6 @@ #include "log.h" #include "wstat.h" #include "kvp.h" -#include "hash.h" #include "split.h" #include "printf.h" #include "vector.h" @@ -68,7 +68,33 @@ static int mx_bool_result(struct sink *output, int result) { return 0; } -/* @include{TEMPLATE} +/** @brief Search the include path */ +char *mx_find(const char *name) { + char *path; + int n; + + if(name[0] == '/') { + if(access(name, O_RDONLY) < 0) { + error(errno, "cannot read %s", name); + return 0; + } + path = xstrdup(name); + } else { + /* Search the include path */ + for(n = 0; n < include_path.nvec; ++n) { + byte_xasprintf(&path, "%s/%s", include_path.vec[n], name); + if(access(path, O_RDONLY) == 0) + break; + } + if(n >= include_path.nvec) { + error(0, "cannot find '%s' in search path", name); + return 0; + } + } + return path; +} + +/* @include{TEMPLATE}@ * * Includes TEMPLATE. * @@ -90,35 +116,15 @@ static int exp_include(int attribute((unused)) nargs, char **args, struct sink *output, void *u) { - const char *name = args[0]; - char *path; + const char *path; int fd, n; char buffer[4096]; struct stat sb; - - if(name[0] == '/') { - if(access(name, O_RDONLY) < 0) { - error(errno, "cannot read template %s", name); - if(sink_printf(output, "[[cannot open template '%s']]", name) < 0) - return -1; - return 0; - } - path = xstrdup(name); - } else { - int n; - /* Search the include path */ - for(n = 0; n < include_path.nvec; ++n) { - byte_xasprintf(&path, "%s/%s", include_path.vec[n], name); - if(access(path, O_RDONLY) == 0) - break; - } - if(n >= include_path.nvec) { - error(0, "cannot find template '%s'", name); - if(sink_printf(output, "[[cannot find template '%s']]", name) < 0) - return -1; + if(!(path = mx_find(args[0]))) { + if(sink_printf(output, "[[cannot find '%s']]", name) < 0) return 0; - } + return 0; } /* If it's a template expand it */ if(strlen(path) >= 5 && !strncmp(path + strlen(path) - 5, ".tmpl", 5)) @@ -143,7 +149,7 @@ static int exp_include(int attribute((unused)) nargs, return 0; } -/* @include{COMMAND} +/* @include{COMMAND}@ * * Executes COMMAND via the shell (using "sh -c") and copies its * standard output to the template output. The shell command output @@ -192,7 +198,7 @@ static int exp_shell(int attribute((unused)) nargs, return 0; } -/* @if{CONDITION}{IF-TRUE}{IF-FALSE} +/* @if{CONDITION}{IF-TRUE}{IF-FALSE}@ * * If CONDITION is "true" then evaluates to IF-TRUE. Otherwise * evaluates to IF-FALSE. The IF-FALSE part is optional. @@ -214,7 +220,7 @@ static int exp_if(int nargs, return 0; } -/* @and{BRANCH}{BRANCH}... +/* @and{BRANCH}{BRANCH}...@ * * Expands to "true" if all the branches are "true" otherwise to "false". If * there are no brances then the result is "true". Only as many branches as @@ -240,7 +246,7 @@ static int exp_and(int nargs, return mx_bool_result(output, result); } -/* @or{BRANCH}{BRANCH}... +/* @or{BRANCH}{BRANCH}...@ * * Expands to "true" if any of the branches are "true" otherwise to "false". * If there are no brances then the result is "false". Only as many branches @@ -266,7 +272,7 @@ static int exp_or(int nargs, return mx_bool_result(output, result); } -/* @not{CONDITION} +/* @not{CONDITION}@ * * Expands to "true" unless CONDITION is "true" in which case "false". */ @@ -277,7 +283,7 @@ static int exp_not(int attribute((unused)) nargs, return mx_bool_result(output, !mx_str2bool(args[0])); } -/* @#{...} +/* @#{...}@ * * Expands to nothing. The argument(s) are not fully evaluated, and no side * effects occur. @@ -289,7 +295,7 @@ static int exp_comment(int attribute((unused)) nargs, return 0; } -/* @urlquote{STRING} +/* @urlquote{STRING}@ * * URL-quotes a string, i.e. replaces any characters not safe to use unquoted * in a URL with %-encoded form. @@ -304,7 +310,7 @@ static int exp_urlquote(int attribute((unused)) nargs, return 0; } -/* @eq{S1}{S2}... +/* @eq{S1}{S2}...@ * * Expands to "true" if all the arguments are identical, otherwise to "false" * (i.e. if any pair of arguments differs). @@ -328,7 +334,7 @@ static int exp_eq(int nargs, return mx_bool_result(output, result); } -/* @ne{S1}{S2}... +/* @ne{S1}{S2}...@ * * Expands to "true" if all of the arguments differ from one another, otherwise * to "false" (i.e. if any value appears more than once). @@ -352,7 +358,7 @@ static int exp_ne(int nargs, return mx_bool_result(output, result); } -/* @discard{...} +/* @discard{...}@ * * Expands to nothing. Unlike the comment expansion @#{...}, side effects of * arguments are not suppressed. So this can be used to surround a collection @@ -365,7 +371,7 @@ static int exp_discard(int attribute((unused)) nargs, return 0; } -/* @define{NAME}{ARG1 ARG2...}{DEFINITION} +/* @define{NAME}{ARG1 ARG2...}{DEFINITION}@ * * Define a macro. The macro will be called NAME and will act like an * expansion. When it is expanded, the expansion is replaced by DEFINITION, diff --git a/lib/macros.c b/lib/macros.c index 2a5559c..4e92236 100644 --- a/lib/macros.c +++ b/lib/macros.c @@ -34,11 +34,11 @@ #include #include +#include "hash.h" #include "macros.h" #include "mem.h" #include "vector.h" #include "log.h" -#include "hash.h" #include "sink.h" #include "syscalls.h" #include "printf.h" @@ -552,13 +552,45 @@ int mx_expand_file(const char *path, return rc; } -/** @brief Rewrite a parse tree substituting in macro arguments +/* Macros ------------------------------------------------------------------- */ + +/** @brief Rewrite a parse tree substituting sub-expansions * @param m Parse tree to rewrite (from macro definition) + * @param ... Name/value pairs to rewrite + * @return Rewritten parse tree + * + * The name/value pair list consists of pairs of strings and is terminated by + * (char *)0. Names and values are both copied so need not survive the call. + */ +const struct mx_node *mx_rewritel(const struct mx_node *m, + ...) { + va_list ap; + hash *h = hash_new(sizeof (struct mx_node *)); + const char *n, *v; + struct mx_node *e; + + va_start(ap, m); + while((n = va_arg(ap, const char *))) { + v = va_arg(ap, const char *); + e = xmalloc(sizeof *e); + e->next = 0; + e->filename = m->filename; + e->line = m->line; + e->type = MX_TEXT; + e->text = xstrdup(v); + hash_add(h, n, &e, HASH_INSERT); + /* hash_add() copies n */ + } + return mx_rewrite(m, h); +} + +/** @brief Rewrite a parse tree substituting in macro arguments + * @param definition Parse tree to rewrite (from macro definition) * @param h Hash mapping argument names to argument values * @return Rewritten parse tree */ -static const struct mx_node *mx__rewrite(const struct mx_node *definition, - hash *h) { +const struct mx_node *mx_rewrite(const struct mx_node *definition, + hash *h) { const struct mx_node *head = 0, **tailp = &head, *argvalue, *m, *mm; struct mx_node *nm; int n; @@ -598,7 +630,7 @@ static const struct mx_node *mx__rewrite(const struct mx_node *definition, *nm = *m; nm->args = xcalloc(nm->nargs, sizeof (struct mx_node *)); for(n = 0; n < nm->nargs; ++n) - nm->args[n] = mx__rewrite(m->args[n], h); + nm->args[n] = mx_rewrite(m->args[n], h); nm->next = 0; *tailp = nm; tailp = (const struct mx_node **)&nm->next; @@ -632,7 +664,7 @@ static int mx__expand_macro(const struct expansion *e, for(n = 0; n < m->nargs; ++n) hash_add(h, e->args[n], &m->args[n], HASH_INSERT); /* Generate a rewritten parse tree */ - m = mx__rewrite(e->definition, h); + m = mx_rewrite(e->definition, h); /* Expand the result */ return mx_expand(m, output, u); /* mx_expand() will update the backtrace */ diff --git a/lib/macros.h b/lib/macros.h index 8eb6c4a..49c6834 100644 --- a/lib/macros.h +++ b/lib/macros.h @@ -106,6 +106,7 @@ int mx_register_macro(const char *name, void mx_register_builtin(void); void mx_search_path(const char *s); +char *mx_find(const char *name); int mx_expand_file(const char *path, struct sink *output, @@ -117,6 +118,10 @@ int mx_expandstr(const struct mx_node *m, char **sp, void *u, const char *what); +const struct mx_node *mx_rewrite(const struct mx_node *definition, + hash *h); +const struct mx_node *mx_rewritel(const struct mx_node *m, + ...); int mx_str2bool(const char *s); const char *mx_bool2str(int n); diff --git a/lib/t-cgi.c b/lib/t-cgi.c index a3b9f37..de44fcb 100644 --- a/lib/t-cgi.c +++ b/lib/t-cgi.c @@ -24,7 +24,10 @@ static void input_from(const char *s) { FILE *fp = tmpfile(); char buffer[64]; - if(fputs(s, fp) < 0 || fflush(fp) < 0) + if(fputs(s, fp) < 0 + || fputs("wibble wibble\r\nspong", fp) < 0 /* ensure CONTENT_LENGTH + * honored */ + || fflush(fp) < 0) fatal(errno, "writing to temporary file"); rewind(fp); xdup2(fileno(fp), 0); diff --git a/scripts/macro-docs b/scripts/macro-docs index 46b2a6e..30ad91f 100755 --- a/scripts/macro-docs +++ b/scripts/macro-docs @@ -40,6 +40,11 @@ for my $m (sort keys %macros) { print ".TP\n"; print ".B $heading\n"; for my $d (@docs) { + if($d =~ /^- /) { + $d = $'; + print ".TP\n"; + print ".B .\n"; + } if($d eq '') { print ".PP\n"; } else { diff --git a/server/Makefile.am b/server/Makefile.am index 78497a5..49d2d86 100644 --- a/server/Makefile.am +++ b/server/Makefile.am @@ -102,7 +102,7 @@ disorder_dbupgrade_DEPENDENCIES=../lib/libdisorder.a disorder_cgi_SOURCES=dcgi.c dcgi.h \ api.c api-client.c api-client.h \ - cgi.c cgi.h cgimain.c exports.c + cgi.c server-cgi.h cgimain.c exports.c disorder_cgi_LDADD=../lib/libdisorder.a \ $(LIBPCRE) $(LIBGCRYPT) $(LIBDL) $(LIBDB) disorder_cgi_LDFLAGS=-export-dynamic diff --git a/server/cgi.c b/server/cgi.c index ef693da..ff45ae7 100644 --- a/server/cgi.c +++ b/server/cgi.c @@ -49,7 +49,7 @@ #include "regsub.h" #include "defs.h" #include "sink.h" -#include "cgi.h" +#include "server-cgi.h" #include "printf.h" #include "mime.h" #include "unicode.h" @@ -72,34 +72,6 @@ struct cgi_macro { const char *value; }; -static hash *cgi_macros; - -/** @brief Parse of a template */ -struct cgi_element { - /** @brief Next element */ - struct cgi_element *next; - - /** @brief Element type */ - int type; -#define ELEMENT_TEXT 0 -#define ELEMENT_EXPANSION 1 - - /** @brief Line number at start of element */ - int line; - - /** @brief Plain text */ - char *text; - - /** @brief Expansion name */ - char *name; - - /** @brief Argument count */ - int nargs; - - /** @brief Argument values (NOT recursively expanded) */ - char **args; -}; - #define RELIST(x) struct re *x, **x##_tail = &x static int have_read_options; @@ -111,148 +83,9 @@ static void cgi_expand_parsed(const char *name, struct cgi_element *head, const struct cgi_expansion *expansions, size_t nexpansions, - cgi_sink *output, + cgi_1sink *output, void *u); -static void cgi_parse_get(void) { - const char *q; - - if(!(q = getenv("QUERY_STRING"))) fatal(0, "QUERY_STRING not set"); - cgi_args = kvp_urldecode(q, strlen(q)); -} - -static void cgi_input(char **ptrp, size_t *np) { - const char *cl; - char *q; - size_t n, m = 0; - int r; - - if(!(cl = getenv("CONTENT_LENGTH"))) fatal(0, "CONTENT_LENGTH not set"); - n = atol(cl); - q = xmalloc_noptr(n + 1); - while(m < n) { - r = read(0, q + m, n - m); - if(r > 0) - m += r; - else if(r == 0) - fatal(0, "unexpected end of file reading request body"); - else switch(errno) { - case EINTR: break; - default: fatal(errno, "error reading request body"); - } - } - if(memchr(q, 0, n)) fatal(0, "null character in request body"); - q[n + 1] = 0; - *ptrp = q; - if(np) *np = n; -} - -static int cgi_field_callback(const char *name, const char *value, - void *u) { - char *disposition, *pname, *pvalue; - char **namep = u; - - if(!strcmp(name, "content-disposition")) { - if(mime_rfc2388_content_disposition(value, - &disposition, - &pname, - &pvalue)) - fatal(0, "error parsing Content-Disposition field"); - if(!strcmp(disposition, "form-data") - && pname - && !strcmp(pname, "name")) { - if(*namep) - fatal(0, "duplicate Content-Disposition field"); - *namep = pvalue; - } - } - return 0; -} - -static int cgi_part_callback(const char *s, - void attribute((unused)) *u) { - char *name = 0; - struct kvp *k; - - if(!(s = mime_parse(s, cgi_field_callback, &name))) - fatal(0, "error parsing part header"); - if(!name) fatal(0, "no name found"); - k = xmalloc(sizeof *k); - k->next = cgi_args; - k->name = name; - k->value = s; - cgi_args = k; - return 0; -} - -static void cgi_parse_multipart(const char *boundary) { - char *q; - - cgi_input(&q, 0); - if(mime_multipart(q, cgi_part_callback, boundary, 0)) - fatal(0, "invalid multipart object"); -} - -static void cgi_parse_post(void) { - const char *ct, *boundary; - char *q, *type; - size_t n; - struct kvp *k; - - if(!(ct = getenv("CONTENT_TYPE"))) - ct = "application/x-www-form-urlencoded"; - if(mime_content_type(ct, &type, &k)) - fatal(0, "invalid content type '%s'", ct); - if(!strcmp(type, "application/x-www-form-urlencoded")) { - cgi_input(&q, &n); - cgi_args = kvp_urldecode(q, n); - return; - } - if(!strcmp(type, "multipart/form-data")) { - if(!(boundary = kvp_get(k, "boundary"))) - fatal(0, "no boundary parameter found"); - cgi_parse_multipart(boundary); - return; - } - fatal(0, "unrecognized content type '%s'", type); -} - -void cgi_parse(void) { - const char *p; - struct kvp *k; - - if(!(p = getenv("REQUEST_METHOD"))) fatal(0, "REQUEST_METHOD not set"); - if(!strcmp(p, "GET")) - cgi_parse_get(); - else if(!strcmp(p, "POST")) - cgi_parse_post(); - else - fatal(0, "unknown request method %s", p); - for(k = cgi_args; k; k = k->next) - if(!utf8_valid(k->name, strlen(k->name)) - || !utf8_valid(k->value, strlen(k->value))) - fatal(0, "invalid UTF-8 sequence in cgi argument"); -} - -const char *cgi_get(const char *name) { - return kvp_get(cgi_args, name); -} - -void cgi_output(cgi_sink *output, const char *fmt, ...) { - va_list ap; - int n; - char *r; - - va_start(ap, fmt); - n = byte_vasprintf(&r, fmt, ap); - if(n < 0) - fatal(errno, "error calling byte_vasprintf"); - if(output->quote) - r = cgi_sgmlquote(r, 0); - output->sink->write(output->sink, r, strlen(r)); - va_end(ap); -} - void cgi_header(struct sink *output, const char *name, const char *value) { sink_printf(output, "%s: %s\r\n", name, value); } @@ -261,499 +94,6 @@ void cgi_body(struct sink *output) { sink_printf(output, "\r\n"); } -char *cgi_sgmlquote(const char *s, int raw) { - uint32_t *ucs, *p, c; - char *b, *bp; - int n; - - if(!raw) { - if(!(ucs = utf8_to_utf32(s, strlen(s), 0))) exit(EXIT_FAILURE); - } else { - ucs = xmalloc_noptr((strlen(s) + 1) * sizeof(uint32_t)); - for(n = 0; s[n]; ++n) - ucs[n] = (unsigned char)s[n]; - ucs[n] = 0; - } - - n = 1; - /* estimate the length we'll need */ - for(p = ucs; (c = *p); ++p) { - switch(c) { - default: - if(c > 127 || c < 32) { - case '"': - case '&': - case '<': - case '>': - n += 12; - break; - } else - n++; - } - } - /* format the string */ - b = bp = xmalloc_noptr(n); - for(p = ucs; (c = *p); ++p) { - switch(c) { - default: - if(*p > 127 || *p < 32) { - case '"': - case '&': - case '<': - case '>': - bp += sprintf(bp, "&#%lu;", (unsigned long)c); - break; - } else - *bp++ = c; - } - } - *bp = 0; - return b; -} - -void cgi_attr(struct sink *output, const char *name, const char *value) { - if(!value[strspn(value, "abcdefghijklmnopqrstuvwxyz" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "0123456789")]) - sink_printf(output, "%s=%s", name, value); - else - sink_printf(output, "%s=\"%s\"", name, cgi_sgmlquote(value, 0)); -} - -void cgi_opentag(struct sink *output, const char *name, ...) { - va_list ap; - const char *n, *v; - - sink_printf(output, "<%s", name); - va_start(ap, name); - while((n = va_arg(ap, const char *))) { - sink_printf(output, " "); - v = va_arg(ap, const char *); - if(v) - cgi_attr(output, n, v); - else - sink_printf(output, n); - } - sink_printf(output, ">"); -} - -void cgi_closetag(struct sink *output, const char *name) { - sink_printf(output, "", name); -} - -static int template_open(const char *name, - const char *ext, - const char **filenamep) { - const char *dirs[2]; - int fd = -1, n; - char *fullpath; - - dirs[0] = pkgconfdir; - dirs[1] = pkgdatadir; - if(name[0] == '/') { - if((fd = open(name, O_RDONLY)) < 0) fatal(0, "cannot open %s", name); - *filenamep = name; - } else { - for(n = 0; n < config->templates.n + (int)(sizeof dirs / sizeof *dirs); ++n) { - byte_xasprintf(&fullpath, "%s/%s%s", - n < config->templates.n ? config->templates.s[n] - : dirs[n - config->templates.n], - name, ext); - if((fd = open(fullpath, O_RDONLY)) >= 0) break; - } - if(fd < 0) error(0, "cannot find %s%s in template path", name, ext); - *filenamep = fullpath; - } - return fd; -} - -static int valid_template_name(const char *name) { - if(strchr(name, '/') || name[0] == '.') - return 0; - return 1; -} - -void cgi_expand(const char *template, - const struct cgi_expansion *expansions, - size_t nexpansions, - cgi_sink *output, - void *u) { - int fd = -1; - int n; - off_t m; - char *b; - struct stat sb; - - if(!valid_template_name(template)) - fatal(0, "invalid template name '%s'", template); - if((fd = template_open(template, ".html", &template)) < 0) - exitfn(EXIT_FAILURE); - if(fstat(fd, &sb) < 0) fatal(errno, "cannot stat %s", template); - m = 0; - b = xmalloc_noptr(sb.st_size + 1); - while(m < sb.st_size) { - n = read(fd, b + m, sb.st_size - m); - if(n > 0) m += n; - else if(n == 0) fatal(0, "unexpected EOF reading %s", template); - else if(errno != EINTR) fatal(errno, "error reading %s", template); - } - b[sb.st_size] = 0; - xclose(fd); - cgi_expand_string(template, b, expansions, nexpansions, output, u); -} - -/** @brief Return a linked list of the parse of @p template */ -static struct cgi_element *cgi_parse_string(const char *name, - const char *template) { - int braces, line = 1, sline; - const char *p; - struct vector v; - struct dynstr d; - struct cgi_element *head = 0, **tailp = &head, *e; - - while(*template) { - if(*template != '@') { - sline = line; - dynstr_init(&d); - /* Gather up text without any expansions in. */ - while(*template && *template != '@') { - if(*template == '\n') - ++line; - dynstr_append(&d, *template++); - } - dynstr_terminate(&d); - e = xmalloc(sizeof *e); - e->next = 0; - e->line = sline; - e->type = ELEMENT_TEXT; - e->text = d.vec; - *tailp = e; - tailp = &e->next; - continue; - } - vector_init(&v); - braces = 0; - p = template; - ++template; - sline = line; - while(*template != '@') { - /* Skip whitespace */ - while(isspace((unsigned char)*template)) - ++template; - dynstr_init(&d); - if(*template == '{') { - /* bracketed arg */ - ++template; - while(*template && (*template != '}' || braces > 0)) { - switch(*template) { - case '{': ++braces; break; - case '}': --braces; break; - case '\n': ++line; break; - } - dynstr_append(&d, *template++); - } - if(!*template) fatal(0, "%s:%d: unterminated expansion '%.*s'", - name, sline, (int)(template - p), p); - ++template; - if(isspace((unsigned char)*template)) { - /* We have @{...} */ - for(p = template; isspace((unsigned char)*p); ++p) - ; - /* Now we are looking at . If it's "{" then that - * must be the next argument. Otherwise we infer that this - * is really the end of the expansion. */ - if(*p != '{') - goto finished_expansion; - } - } else { - /* unbracketed arg */ - while(*template - && *template != '@' && *template != '{' && *template != ':') { - if(*template == '\n') ++line; - dynstr_append(&d, *template++); - } - if(*template == ':') - ++template; - if(!*template) fatal(0, "%s:%d: unterminated expansion '%.*s'", - name, sline, (int)(template - p), p); - /* trailing whitespace is not significant in unquoted args */ - while(d.nvec && (isspace((unsigned char)d.vec[d.nvec - 1]))) - --d.nvec; - } - dynstr_terminate(&d); - vector_append(&v, d.vec); - } - ++template; - finished_expansion: - vector_terminate(&v); - /* @@ terminates this file */ - if(v.nvec == 0) - break; - e = xmalloc(sizeof *e); - e->next = 0; - e->line = sline; - e->type = ELEMENT_EXPANSION; - e->name = v.vec[0]; - e->nargs = v.nvec - 1; - e->args = &v.vec[1]; - *tailp = e; - tailp = &e->next; - } - return head; -} - -void cgi_expand_string(const char *name, - const char *template, - const struct cgi_expansion *expansions, - size_t nexpansions, - cgi_sink *output, - void *u) { - cgi_expand_parsed(name, cgi_parse_string(name, template), - expansions, nexpansions, output, u); -} - -/** @brief Expand a list of arguments in place */ -static void cgi_expand_all_args(const char *name, - int line, - const struct cgi_expansion *expansions, - size_t nexpansions, - void *u, - char **args, - int nargs) { - int n; - struct dynstr d; - char *argname; - cgi_sink parameter_output; - - for(n = 0; n < nargs; ++n) { - dynstr_init(&d); - byte_xasprintf(&argname, "<%s:%d arg #%d>", name, line,n); - parameter_output.quote = 0; - parameter_output.sink = sink_dynstr(&d); - cgi_expand_string(argname, args[n], - expansions, nexpansions, - ¶meter_output, u); - dynstr_terminate(&d); - args[n] = d.vec; - } -} - - -/** @brief Substitute macro arguments in place */ -static void cgi_substitute_args(const char *name, - struct cgi_element *head, - const struct cgi_macro *macro, - char **values) { - struct cgi_element *e; - int n; - - for(e = head; e; e = e->next) { - if(e->type != ELEMENT_EXPANSION) - continue; - /* See if this is an argument name */ - for(n = 0; n < macro->nargs; ++n) - if(!strcmp(e->name, macro->args[n])) - break; - if(n < macro->nargs) { - /* It is! */ - if(e->nargs != 0) - fatal(0, "%s:%d: macro argument (%s) cannot take parameters", - name, e->line, e->name); - /* Replace it with the argument text */ - e->type = ELEMENT_TEXT; - e->text = values[n]; - continue; - } - /* It's not a macro argument. We must recurse into its arguments to - * substitute the macro arguments. */ - /* TODO */ - /* In order to do this we must parse it and our callers must expect the - * parsed form. We're not ready for this yet... */ - } -} - -static void cgi_expand_parsed(const char *name, - struct cgi_element *head, - const struct cgi_expansion *expansions, - size_t nexpansions, - cgi_sink *output, - void *u) { - int n; - const struct cgi_macro *macro; - struct cgi_element *e, *macro_head; - - for(e = head; e; e = e->next) { - switch(e->type) { - case ELEMENT_TEXT: - output->sink->write(output->sink, e->text, strlen(e->text)); - break; - case ELEMENT_EXPANSION: - if((n = table_find(expansions, - offsetof(struct cgi_expansion, name), - sizeof (struct cgi_expansion), - nexpansions, - e->name)) >= 0) { - /* We found a built-in */ - if(e->nargs < expansions[n].minargs) - fatal(0, "%s:%d: insufficient arguments to @%s@ (min %d, got %d)", - name, e->line, e->name, expansions[n].minargs, e->nargs); - if(e->nargs > expansions[n].maxargs) - fatal(0, "%s:%d: too many arguments to @%s@ (max %d, got %d)", - name, e->line, e->name, expansions[n].maxargs, e->nargs); - /* for ordinary expansions, recursively expand the arguments */ - if(!(expansions[n].flags & EXP_MAGIC)) - cgi_expand_all_args(name, e->line, expansions, nexpansions, u, - e->args, e->nargs); - expansions[n].handler(e->nargs, e->args, output, u); - } else if(cgi_macros && (macro = hash_find(cgi_macros, e->name))) { - /* We found a macro */ - if(e->nargs != macro->nargs) - fatal(0, "%s:%d: wrong number of arguments to @%s@ (need %d, got %d)", - name, e->line, e->name, macro->nargs, e->nargs); - /* Expand arguments */ - cgi_expand_all_args(name, e->line, expansions, nexpansions, u, - e->args, e->nargs); - /* Parse the macro value. Doing this every time isn't very efficient, - * but NB that we mess with the result of the parse, so for the time - * being we do need to do it. */ - macro_head = cgi_parse_string(e->name, macro->value); - /* Substitute in argument values */ - cgi_substitute_args(name, macro_head, macro, e->args); - /* Expand the result */ - cgi_expand_parsed(e->name, - macro_head, - expansions, - nexpansions, - output, - u); - } else { - /* Totally undefined */ - fatal(0, "%s:%d: unknown expansion '%s'", name, e->line, e->name); - } - break; - } - } -} - -char *cgi_makeurl(const char *url, ...) { - va_list ap; - struct kvp *kvp, *k, **kk = &kvp; - struct dynstr d; - const char *n, *v; - - dynstr_init(&d); - dynstr_append_string(&d, url); - va_start(ap, url); - while((n = va_arg(ap, const char *))) { - v = va_arg(ap, const char *); - *kk = k = xmalloc(sizeof *k); - kk = &k->next; - k->name = n; - k->value = v; - } - *kk = 0; - if(kvp) { - dynstr_append(&d, '?'); - dynstr_append_string(&d, kvp_urlencode(kvp, 0)); - } - dynstr_terminate(&d); - return d.vec; -} - -void cgi_set_option(const char *name, const char *value) { - struct kvp *k = xmalloc(sizeof *k); - - k->next = labels; - k->name = name; - k->value = value; - labels = k; -} - -static void option_label(int attribute((unused)) nvec, - char **vec) { - cgi_set_option(vec[0], vec[1]); -} - -static void option_include(int attribute((unused)) nvec, - char **vec) { - include_options(vec[0]); -} - -static void option_columns(int nvec, - char **vec) { - struct column *c = xmalloc(sizeof *c); - - c->next = columns; - c->name = vec[0]; - c->ncolumns = nvec - 1; - c->columns = &vec[1]; - columns = c; -} - -static struct option { - const char *name; - int minargs, maxargs; - void (*handler)(int nvec, char **vec); -} options[] = { - { "columns", 1, INT_MAX, option_columns }, - { "include", 1, 1, option_include }, - { "label", 2, 2, option_label }, -}; - -struct read_options_state { - const char *name; - int line; -}; - -static void read_options_error(const char *msg, - void *u) { - struct read_options_state *cs = u; - - error(0, "%s:%d: %s", cs->name, cs->line, msg); -} - -static void include_options(const char *name) { - int n, i; - int fd; - FILE *fp; - char **vec, *buffer; - struct read_options_state cs; - - if((fd = template_open(name, "", &cs.name)) < 0) return; - if(!(fp = fdopen(fd, "r"))) fatal(errno, "error calling fdopen"); - cs.line = 0; - while(!inputline(cs.name, fp, &buffer, '\n')) { - ++cs.line; - if(!(vec = split(buffer, &n, SPLIT_COMMENTS|SPLIT_QUOTES, - read_options_error, &cs))) - continue; - if(!n) continue; - if((i = TABLE_FIND(options, struct option, name, vec[0])) == -1) { - error(0, "%s:%d: unknown option '%s'", cs.name, cs.line, vec[0]); - continue; - } - ++vec; - --n; - if(n < options[i].minargs) { - error(0, "%s:%d: too few arguments to '%s'", cs.name, cs.line, vec[-1]); - continue; - } - if(n > options[i].maxargs) { - error(0, "%s:%d: too many arguments to '%s'", cs.name, cs.line, vec[-1]); - continue; - } - options[i].handler(n, vec); - } - fclose(fp); -} - -static void read_options(void) { - if(!have_read_options) { - have_read_options = 1; - include_options("options"); - } -} - const char *cgi_label(const char *key) { const char *label; @@ -799,20 +139,6 @@ char **cgi_columns(const char *name, int *ncolumns) { } } -void cgi_define(const char *name, - int nargs, - char **args, - const char *value) { - struct cgi_macro m; - - if(!cgi_macros) - cgi_macros = hash_new(sizeof(struct cgi_macro)); - m.nargs = nargs; - m.args = args; - m.value = value; - hash_add(cgi_macros, name, &m, HASH_INSERT_OR_REPLACE); -} - /* Local Variables: c-basic-offset:2 diff --git a/server/cgimain.c b/server/cgimain.c index 5a79bb5..4a37c52 100644 --- a/server/cgimain.c +++ b/server/cgimain.c @@ -17,6 +17,9 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA */ +/** @file server/cgimain.c + * @brief DisOrder CGI + */ #include #include "types.h" @@ -32,7 +35,7 @@ #include "client.h" #include "sink.h" -#include "cgi.h" +#include "server-cgi.h" #include "mem.h" #include "log.h" #include "configuration.h" @@ -43,6 +46,9 @@ #include "dcgi.h" #include "url.h" +#include "macros.h" +#include "macros-disorder.h" + /** @brief Return true if @p a is better than @p b * * NB. We don't bother checking if the path is right, we merely check for the @@ -77,10 +83,12 @@ int main(int argc, char **argv) { int n, best_cookie; struct cookiedata cd; - if(argc > 0) progname = argv[0]; + if(argc > 0) + progname = argv[0]; /* RFC 3875 s8.2 recommends rejecting PATH_INFO if we don't make use of * it. */ if(getenv("PATH_INFO")) { + /* TODO it might be nice to link back to the right place... */ printf("Content-Type: text/html\n"); printf("Status: 404\n"); printf("\n"); @@ -88,9 +96,16 @@ int main(int argc, char **argv) { exit(0); } cgi_parse(); - if((conf = getenv("DISORDER_CONFIG"))) configfile = xstrdup(conf); - if(getenv("DISORDER_DEBUG")) debugging = 1; - if(config_read(0)) exit(EXIT_FAILURE); + /* We allow various things to be overridden from the environment. This is + * intended for debugging and is not a documented feature. */ + if((conf = getenv("DISORDER_CONFIG"))) + configfile = xstrdup(conf); + if(getenv("DISORDER_DEBUG")) + debugging = 1; + if(config_read(0)) + exit(EXIT_FAILURE); + /* Figure out our URL. This can still be overridden from the config file if + * necessary but it shouldn't be necessary in ordinary installations. */ if(!config->url) config->url = infer_url(); memset(&g, 0, sizeof g); @@ -120,9 +135,22 @@ int main(int argc, char **argv) { } else error(0, "could not parse cookie field '%s'", cookie_env); } + /* Register expansions */ + mx_register_builtin(); + register_disorder_expansions(); + /* Update search path. We look in the config directory first and the data + * directory second, so that the latter overrides the former. */ + mx_search_path(pkgconfdir); + mx_search_path(pkgdatadir); + /* Create the initial connection, trying the cookie if we found a suitable + * one. */ disorder_cgi_login(&s, &output); + /* The main program... */ disorder_cgi(&output, &s); - if(fclose(stdout) < 0) fatal(errno, "error closing stdout"); + /* In practice if a write fails that probably means the web server went away, + * but we log it anyway. */ + if(fclose(stdout) < 0) + fatal(errno, "error closing stdout"); return 0; } diff --git a/server/dcgi.c b/server/dcgi.c index 16a5a6c..15faf2f 100644 --- a/server/dcgi.c +++ b/server/dcgi.c @@ -38,7 +38,7 @@ #include "mem.h" #include "vector.h" #include "sink.h" -#include "cgi.h" +#include "server-cgi.h" #include "log.h" #include "configuration.h" #include "table.h" @@ -162,55 +162,6 @@ static void expand_template(dcgi_state *ds, cgi_sink *output, expand(output, action, ds); } -static void lookups(dcgi_state *ds, unsigned want) { - unsigned need; - struct queue_entry *r, *rnext; - const char *dir, *re; - char *rights; - - if(ds->g->client && (need = want ^ (ds->g->flags & want)) != 0) { - if(need & DC_QUEUE) - disorder_queue(ds->g->client, &ds->g->queue); - if(need & DC_PLAYING) - disorder_playing(ds->g->client, &ds->g->playing); - if(need & DC_NEW) - disorder_new_tracks(ds->g->client, &ds->g->new, &ds->g->nnew, 0); - if(need & DC_RECENT) { - /* we need to reverse the order of the list */ - disorder_recent(ds->g->client, &r); - while(r) { - rnext = r->next; - r->next = ds->g->recent; - ds->g->recent = r; - r = rnext; - } - } - if(need & DC_VOLUME) - disorder_get_volume(ds->g->client, - &ds->g->volume_left, &ds->g->volume_right); - if(need & (DC_FILES|DC_DIRS)) { - if(!(dir = cgi_get("directory"))) - dir = ""; - re = cgi_get("regexp"); - if(need & DC_DIRS) - if(disorder_directories(ds->g->client, dir, re, - &ds->g->dirs, &ds->g->ndirs)) - ds->g->ndirs = 0; - if(need & DC_FILES) - if(disorder_files(ds->g->client, dir, re, - &ds->g->files, &ds->g->nfiles)) - ds->g->nfiles = 0; - } - if(need & DC_RIGHTS) { - ds->g->rights = RIGHT_READ; /* fail-safe */ - if(!disorder_userinfo(ds->g->client, disorder_user(ds->g->client), - "rights", &rights)) - parse_rights(rights, &ds->g->rights, 1); - } - ds->g->flags |= need; - } -} - /* actions ********************************************************************/ static void act_disable(cgi_sink *output, @@ -715,41 +666,6 @@ static const struct action { /* expansions *****************************************************************/ -static void exp_include(int attribute((unused)) nargs, - char **args, - cgi_sink *output, - void *u) { - expand(output, args[0], u); -} - -static void exp_server_version(int attribute((unused)) nargs, - char attribute((unused)) **args, - cgi_sink *output, - void *u) { - dcgi_state *ds = u; - const char *v; - - if(ds->g->client) { - if(disorder_version(ds->g->client, (char **)&v)) v = "(cannot get version)"; - } else - v = "(server not running)"; - cgi_output(output, "%s", v); -} - -static void exp_version(int attribute((unused)) nargs, - char attribute((unused)) **args, - cgi_sink *output, - void attribute((unused)) *u) { - cgi_output(output, "%s", disorder_short_version_string); -} - -static void exp_nonce(int attribute((unused)) nargs, - char attribute((unused)) **args, - cgi_sink *output, - void attribute((unused)) *u) { - cgi_output(output, "%s", nonce()); -} - static void exp_label(int attribute((unused)) nargs, char **args, cgi_sink *output, @@ -764,197 +680,6 @@ struct trackinfo_state { time_t when; }; -static void exp_who(int attribute((unused)) nargs, - char attribute((unused)) **args, - cgi_sink *output, - void *u) { - dcgi_state *ds = u; - - if(ds->track && ds->track->submitter) - cgi_output(output, "%s", ds->track->submitter); -} - -static void exp_length(int attribute((unused)) nargs, - char attribute((unused)) **args, - cgi_sink *output, - void *u) { - dcgi_state *ds = u; - long length = 0; - - if(ds->track - && (ds->track->state == playing_started - || ds->track->state == playing_paused) - && ds->track->sofar >= 0) - cgi_output(output, "%ld:%02ld/", - ds->track->sofar / 60, ds->track->sofar % 60); - length = 0; - if(ds->track) - disorder_length(ds->g->client, ds->track->track, &length); - else if(ds->tracks) - disorder_length(ds->g->client, ds->tracks[0], &length); - if(length) - cgi_output(output, "%ld:%02ld", length / 60, length % 60); - else - sink_printf(output->sink, "%s", " "); -} - -static void exp_when(int attribute((unused)) nargs, - char attribute((unused)) **args, - cgi_sink *output, - void *u) { - dcgi_state *ds = u; - const struct tm *w = 0; - - if(ds->track) - switch(ds->track->state) { - case playing_isscratch: - case playing_unplayed: - case playing_random: - if(ds->track->expected) - w = localtime(&ds->track->expected); - break; - case playing_failed: - case playing_no_player: - case playing_ok: - case playing_scratched: - case playing_started: - case playing_paused: - case playing_quitting: - if(ds->track->played) - w = localtime(&ds->track->played); - break; - } - if(w) - cgi_output(output, "%d:%02d", w->tm_hour, w->tm_min); - else - sink_printf(output->sink, " "); -} - -static void exp_part(int nargs, - char **args, - cgi_sink *output, - void *u) { - dcgi_state *ds = u; - const char *s, *track, *part, *context; - - if(nargs == 3) - track = args[2]; - else { - if(ds->track) - track = ds->track->track; - else if(ds->tracks) - track = ds->tracks[0]; - else - track = 0; - } - if(track) { - switch(nargs) { - case 1: - context = "display"; - part = args[0]; - break; - case 2: - case 3: - context = args[0]; - part = args[1]; - break; - default: - abort(); - } - if(disorder_part(ds->g->client, (char **)&s, track, - !strcmp(context, "short") ? "display" : context, part)) - fatal(0, "disorder_part() failed"); - if(!strcmp(context, "short")) - s = truncate_for_display(s, config->short_display); - cgi_output(output, "%s", s); - } else - sink_printf(output->sink, " "); -} - -static void exp_playing(int attribute((unused)) nargs, - char **args, - cgi_sink *output, - void *u) { - dcgi_state *ds = u; - dcgi_state s; - - lookups(ds, DC_PLAYING); - memset(&s, 0, sizeof s); - s.g = ds->g; - if(ds->g->playing) { - s.track = ds->g->playing; - expandstring(output, args[0], &s); - } -} - -static void exp_queue(int attribute((unused)) nargs, - char **args, - cgi_sink *output, - void *u) { - dcgi_state *ds = u; - dcgi_state s; - struct queue_entry *q; - - lookups(ds, DC_QUEUE); - memset(&s, 0, sizeof s); - s.g = ds->g; - s.first = 1; - for(q = ds->g->queue; q; q = q->next) { - s.last = !q->next; - s.track = q; - expandstring(output, args[0], &s); - s.index++; - s.first = 0; - } -} - -static void exp_recent(int attribute((unused)) nargs, - char **args, - cgi_sink *output, - void *u) { - dcgi_state *ds = u; - dcgi_state s; - struct queue_entry *q; - - lookups(ds, DC_RECENT); - memset(&s, 0, sizeof s); - s.g = ds->g; - s.first = 1; - for(q = ds->g->recent; q; q = q->next) { - s.last = !q; - s.track = q; - expandstring(output, args[0], &s); - s.index++; - s.first = 0; - } -} - -static void exp_new(int attribute((unused)) nargs, - char **args, - cgi_sink *output, - void *u) { - dcgi_state *ds = u; - dcgi_state s; - - lookups(ds, DC_NEW); - memset(&s, 0, sizeof s); - s.g = ds->g; - s.first = 1; - for(s.index = 0; s.index < ds->g->nnew; ++s.index) { - s.last = s.index + 1 < ds->g->nnew; - s.tracks = &ds->g->new[s.index]; - expandstring(output, args[0], &s); - s.first = 0; - } -} - -static void exp_url(int attribute((unused)) nargs, - char attribute((unused)) **args, - cgi_sink *output, - void attribute((unused)) *u) { - cgi_output(output, "%s", config->url); -} - struct result { char *track; const char *sort; @@ -1036,16 +761,6 @@ static void exp_search(int nargs, assert(substate.last != 0); } -static void exp_arg(int attribute((unused)) nargs, - char **args, - cgi_sink *output, - void attribute((unused)) *u) { - const char *v; - - if((v = cgi_get(args[0]))) - cgi_output(output, "%s", v); -} - static void exp_stats(int attribute((unused)) nargs, char attribute((unused)) **args, cgi_sink *output, @@ -1061,60 +776,6 @@ static void exp_stats(int attribute((unused)) nargs, cgi_closetag(output->sink, "pre"); } -static void exp_volume(int attribute((unused)) nargs, - char **args, - cgi_sink *output, - void *u) { - dcgi_state *ds = u; - - lookups(ds, DC_VOLUME); - if(!strcmp(args[0], "left")) - cgi_output(output, "%d", ds->g->volume_left); - else - cgi_output(output, "%d", ds->g->volume_right); -} - -static void exp_shell(int attribute((unused)) nargs, - char **args, - cgi_sink *output, - void attribute((unused)) *u) { - int w, p[2], n; - char buffer[4096]; - pid_t pid; - - xpipe(p); - if(!(pid = xfork())) { - exitfn = _exit; - xclose(p[0]); - xdup2(p[1], 1); - xclose(p[1]); - execlp("sh", "sh", "-c", args[0], (char *)0); - fatal(errno, "error executing sh"); - } - xclose(p[1]); - while((n = read(p[0], buffer, sizeof buffer))) { - if(n < 0) { - if(errno == EINTR) continue; - else fatal(errno, "error reading from pipe"); - } - output->sink->write(output->sink, buffer, n); - } - xclose(p[0]); - while((n = waitpid(pid, &w, 0)) < 0 && errno == EINTR) - ; - if(n < 0) fatal(errno, "error calling waitpid"); - if(w) - error(0, "shell command '%s' %s", args[0], wstat(w)); -} - -static inline int str2bool(const char *s) { - return !strcmp(s, "true"); -} - -static inline const char *bool2str(int n) { - return n ? "true" : "false"; -} - static char *expandarg(const char *arg, dcgi_state *ds) { struct dynstr d; cgi_sink output; @@ -1127,184 +788,6 @@ static char *expandarg(const char *arg, dcgi_state *ds) { return d.vec; } -static void exp_prefs(int attribute((unused)) nargs, - char **args, - cgi_sink *output, - void *u) { - dcgi_state *ds = u; - dcgi_state substate; - struct kvp *k; - const char *file = expandarg(args[0], ds); - - memset(&substate, 0, sizeof substate); - substate.g = ds->g; - substate.first = 1; - if(disorder_prefs(ds->g->client, file, &k)) return; - while(k) { - substate.last = !k->next; - substate.pref = k; - expandstring(output, args[1], &substate); - ++substate.index; - k = k->next; - substate.first = 0; - } -} - -static void exp_pref(int attribute((unused)) nargs, - char **args, - cgi_sink *output, - void *u) { - char *value; - dcgi_state *ds = u; - - if(!disorder_get(ds->g->client, args[0], args[1], &value)) - cgi_output(output, "%s", value); -} - -static void exp_if(int nargs, - char **args, - cgi_sink *output, - void *u) { - dcgi_state *ds = u; - int n = str2bool(expandarg(args[0], ds)) ? 1 : 2; - - if(n < nargs) - expandstring(output, args[n], ds); -} - -static void exp_and(int nargs, - char **args, - cgi_sink *output, - void *u) { - dcgi_state *ds = u; - int n, result = 1; - - for(n = 0; n < nargs; ++n) - if(!str2bool(expandarg(args[n], ds))) { - result = 0; - break; - } - sink_printf(output->sink, "%s", bool2str(result)); -} - -static void exp_or(int nargs, - char **args, - cgi_sink *output, - void *u) { - dcgi_state *ds = u; - int n, result = 0; - - for(n = 0; n < nargs; ++n) - if(str2bool(expandarg(args[n], ds))) { - result = 1; - break; - } - sink_printf(output->sink, "%s", bool2str(result)); -} - -static void exp_not(int attribute((unused)) nargs, - char **args, - cgi_sink *output, - void attribute((unused)) *u) { - sink_printf(output->sink, "%s", bool2str(!str2bool(args[0]))); -} - -static void exp_isplaying(int attribute((unused)) nargs, - char attribute((unused)) **args, - cgi_sink *output, - void *u) { - dcgi_state *ds = u; - - lookups(ds, DC_PLAYING); - sink_printf(output->sink, "%s", bool2str(!!ds->g->playing)); -} - -static void exp_isqueue(int attribute((unused)) nargs, - char attribute((unused)) **args, - cgi_sink *output, - void *u) { - dcgi_state *ds = u; - - lookups(ds, DC_QUEUE); - sink_printf(output->sink, "%s", bool2str(!!ds->g->queue)); -} - -static void exp_isrecent(int attribute((unused)) nargs, - char attribute((unused)) **args, - cgi_sink *output, - void *u) { - dcgi_state *ds = u; - - lookups(ds, DC_RECENT); - sink_printf(output->sink, "%s", bool2str(!!ds->g->recent)); -} - -static void exp_isnew(int attribute((unused)) nargs, - char attribute((unused)) **args, - cgi_sink *output, - void *u) { - dcgi_state *ds = u; - - lookups(ds, DC_NEW); - sink_printf(output->sink, "%s", bool2str(!!ds->g->nnew)); -} - -static void exp_id(int attribute((unused)) nargs, - char attribute((unused)) **args, - cgi_sink *output, - void *u) { - dcgi_state *ds = u; - - if(ds->track) - cgi_output(output, "%s", ds->track->id); -} - -static void exp_track(int attribute((unused)) nargs, - char attribute((unused)) **args, - cgi_sink *output, - void *u) { - dcgi_state *ds = u; - - if(ds->track) - cgi_output(output, "%s", ds->track->track); -} - -static void exp_parity(int attribute((unused)) nargs, - char attribute((unused)) **args, - cgi_sink *output, - void *u) { - dcgi_state *ds = u; - - cgi_output(output, "%s", ds->index % 2 ? "odd" : "even"); -} - -static void exp_comment(int attribute((unused)) nargs, - char attribute((unused)) **args, - cgi_sink attribute((unused)) *output, - void attribute((unused)) *u) { - /* do nothing */ -} - -static void exp_prefname(int attribute((unused)) nargs, - char attribute((unused)) **args, - cgi_sink *output, - void *u) { - dcgi_state *ds = u; - - if(ds->pref && ds->pref->name) - cgi_output(output, "%s", ds->pref->name); -} - -static void exp_prefvalue(int attribute((unused)) nargs, - char attribute((unused)) **args, - cgi_sink *output, - void *u) { - dcgi_state *ds = u; - - if(ds->pref && ds->pref->value) - cgi_output(output, "%s", ds->pref->value); -} - static void exp_isfiles(int attribute((unused)) nargs, char attribute((unused)) **args, cgi_sink *output, @@ -1383,61 +866,6 @@ static void exp_file(int attribute((unused)) nargs, cgi_output(output, "%s", ds->tracks[0]); } -static void exp_transform(int nargs, - char **args, - cgi_sink *output, - void attribute((unused)) *u) { - const char *context = nargs > 2 ? args[2] : "display"; - - cgi_output(output, "%s", trackname_transform(args[1], args[0], context)); -} - -static void exp_urlquote(int attribute((unused)) nargs, - char **args, - cgi_sink *output, - void attribute((unused)) *u) { - cgi_output(output, "%s", urlencodestring(args[0])); -} - -static void exp_scratchable(int attribute((unused)) nargs, - char attribute((unused)) **args, - cgi_sink *output, - void attribute((unused)) *u) { - dcgi_state *ds = u; - - lookups(ds, DC_PLAYING|DC_RIGHTS); - sink_printf(output->sink, "%s", - bool2str(right_scratchable(ds->g->rights, - disorder_user(ds->g->client), - ds->g->playing))); -} - -static void exp_removable(int attribute((unused)) nargs, - char attribute((unused)) **args, - cgi_sink *output, - void attribute((unused)) *u) { - dcgi_state *ds = u; - - lookups(ds, DC_RIGHTS); - sink_printf(output->sink, "%s", - bool2str(right_removable(ds->g->rights, - disorder_user(ds->g->client), - ds->track))); -} - -static void exp_movable(int attribute((unused)) nargs, - char attribute((unused)) **args, - cgi_sink *output, - void attribute((unused)) *u) { - dcgi_state *ds = u; - - lookups(ds, DC_RIGHTS); - sink_printf(output->sink, "%s", - bool2str(right_movable(ds->g->rights, - disorder_user(ds->g->client), - ds->track))); -} - static void exp_navigate(int attribute((unused)) nargs, char **args, cgi_sink *output, @@ -1507,138 +935,6 @@ static void exp_dirname(int nargs, cgi_output(output, "%.*s", ds->nav_dirlen, ds->nav_path); } -static void exp_eq(int attribute((unused)) nargs, - char **args, - cgi_sink *output, - void attribute((unused)) *u) { - cgi_output(output, "%s", bool2str(!strcmp(args[0], args[1]))); -} - -static void exp_ne(int attribute((unused)) nargs, - char **args, - cgi_sink *output, - void attribute((unused)) *u) { - cgi_output(output, "%s", bool2str(strcmp(args[0], args[1]))); -} - -static void exp_enabled(int attribute((unused)) nargs, - char attribute((unused)) **args, - cgi_sink *output, - void *u) { - dcgi_state *ds = u; - int enabled = 0; - - if(ds->g->client) - disorder_enabled(ds->g->client, &enabled); - cgi_output(output, "%s", bool2str(enabled)); -} - -static void exp_random_enabled(int attribute((unused)) nargs, - char attribute((unused)) **args, - cgi_sink *output, - void *u) { - dcgi_state *ds = u; - int enabled = 0; - - if(ds->g->client) - disorder_random_enabled(ds->g->client, &enabled); - cgi_output(output, "%s", bool2str(enabled)); -} - -static void exp_trackstate(int attribute((unused)) nargs, - char **args, - cgi_sink *output, - void *u) { - dcgi_state *ds = u; - struct queue_entry *q; - char *track; - - if(disorder_resolve(ds->g->client, &track, args[0])) return; - lookups(ds, DC_QUEUE|DC_PLAYING); - if(ds->g->playing && !strcmp(ds->g->playing->track, track)) - cgi_output(output, "playing"); - else { - for(q = ds->g->queue; q && strcmp(q->track, track); q = q->next) - ; - if(q) - cgi_output(output, "queued"); - } -} - -static void exp_thisurl(int attribute((unused)) nargs, - char attribute((unused)) **args, - cgi_sink *output, - void attribute((unused)) *u) { - kvp_set(&cgi_args, "nonce", nonce()); /* nonces had better differ! */ - cgi_output(output, "%s?%s", config->url, kvp_urlencode(cgi_args, 0)); -} - -static void exp_isfirst(int attribute((unused)) nargs, - char attribute((unused)) **args, - cgi_sink *output, - void *u) { - dcgi_state *ds = u; - - sink_printf(output->sink, "%s", bool2str(!!ds->first)); -} - -static void exp_islast(int attribute((unused)) nargs, - char attribute((unused)) **args, - cgi_sink *output, - void *u) { - dcgi_state *ds = u; - - sink_printf(output->sink, "%s", bool2str(!!ds->last)); -} - -static void exp_action(int attribute((unused)) nargs, - char attribute((unused)) **args, - cgi_sink *output, - void attribute((unused)) *u) { - const char *action = cgi_get("action"), *mgmt; - - if(!action) action = "playing"; - if(!strcmp(action, "playing") - && (mgmt = cgi_get("mgmt")) - && !strcmp(mgmt, "true")) - action = "manage"; - sink_printf(output->sink, "%s", action); -} - -static void exp_resolve(int attribute((unused)) nargs, - char **args, - cgi_sink *output, - void attribute((unused)) *u) { - dcgi_state *ds = u; - char *track; - - if(!disorder_resolve(ds->g->client, &track, args[0])) - sink_printf(output->sink, "%s", track); -} - -static void exp_paused(int attribute((unused)) nargs, - char attribute((unused)) **args, - cgi_sink *output, - void *u) { - dcgi_state *ds = u; - int paused = 0; - - lookups(ds, DC_PLAYING); - if(ds->g->playing && ds->g->playing->state == playing_paused) - paused = 1; - cgi_output(output, "%s", bool2str(paused)); -} - -static void exp_state(int attribute((unused)) nargs, - char attribute((unused)) **args, - cgi_sink *output, - void *u) { - dcgi_state *ds = u; - - if(ds->track) - cgi_output(output, "%s", playing_states[ds->track->state]); -} - static void exp_files(int attribute((unused)) nargs, char **args, cgi_sink *output, @@ -1674,15 +970,6 @@ static void exp_files(int attribute((unused)) nargs, } } -static void exp_index(int attribute((unused)) nargs, - char attribute((unused)) **args, - cgi_sink *output, - void *u) { - dcgi_state *ds = u; - - cgi_output(output, "%d", ds->index); -} - static void exp_nfiles(int attribute((unused)) nargs, char attribute((unused)) **args, cgi_sink *output, @@ -1699,47 +986,6 @@ static void exp_nfiles(int attribute((unused)) nargs, cgi_output(output, "1"); } -static void exp_user(int attribute((unused)) nargs, - char attribute((unused)) **args, - cgi_sink *output, - void *u) { - dcgi_state *const ds = u; - - cgi_output(output, "%s", disorder_user(ds->g->client)); -} - -static void exp_right(int attribute((unused)) nargs, - char **args, - cgi_sink *output, - void *u) { - dcgi_state *const ds = u; - const char *right = expandarg(args[0], ds); - rights_type r; - - lookups(ds, DC_RIGHTS); - if(parse_rights(right, &r, 1/*report*/)) - r = 0; - if(args[1] == 0) - cgi_output(output, "%s", bool2str(!!(r & ds->g->rights))); - else if(r & ds->g->rights) - expandstring(output, args[1], ds); - else if(args[2]) - expandstring(output, args[2], ds); -} - -static void exp_userinfo(int attribute((unused)) nargs, - char **args, - cgi_sink *output, - void *u) { - dcgi_state *const ds = u; - const char *value; - - if(disorder_userinfo(ds->g->client, disorder_user(ds->g->client), args[0], - (char **)&value)) - value = ""; - cgi_output(output, "%s", value); -} - static void exp_image(int attribute((unused)) nargs, char **args, cgi_sink *output, @@ -1760,17 +1006,6 @@ static void exp_image(int attribute((unused)) nargs, cgi_output(output, "/disorder/%s", imagestem); } -static void exp_define(int attribute((unused)) nargs, - char **args, - cgi_sink attribute((unused)) *output, - void attribute((unused)) *u) { - const char *n = args[0], *a = args[1], *v = args[2]; - int nas; - char **as = split(a, &nas, 0, 0, 0); - - cgi_define(n, nas, as, v); -} - static const struct cgi_expansion expansions[] = { { "#", 0, INT_MAX, EXP_MAGIC, exp_comment }, { "action", 0, 0, 0, exp_action }, diff --git a/server/macros-disorder.c b/server/macros-disorder.c new file mode 100644 index 0000000..9b1f21f --- /dev/null +++ b/server/macros-disorder.c @@ -0,0 +1,854 @@ +/* + * This file is part of DisOrder. + * Copyright (C) 2004-2008 Richard Kettlewell + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ +/** @file server/macros-disorder.c + * @brief DisOrder-specific expansions + */ + +#include +#include "types.h" + +#include "sink.h" +#include "client.h" +#include "cgi.h" +#include "macros-disorder.h" + +/** @brief Client to use for DisOrder-specific expansions + * + * The caller should arrange for this to be created before any of + * these expansions are used (if it cannot connect then it's safe to + * leave it as NULL). + */ +disorder_client *client; + +/** @brief Cached data */ +static unsigned flags; + +#define DC_QUEUE 0x0001 +#define DC_PLAYING 0x0002 +#define DC_RECENT 0x0004 +#define DC_VOLUME 0x0008 +#define DC_DIRS 0x0010 +#define DC_FILES 0x0020 +#define DC_NEW 0x0040 +#define DC_RIGHTS 0x0080 + +static struct queue_entry *queue; +static struct queue_entry *playing; +static struct queue_entry *recent; + +static int volume_left; +static int volume_right; + +static char **files; +static int nfiles; + +static char **dirs; +static int ndirs; + +static char **new; +static int nnew; + +static rights_type rights; + +/** @brief Fetch cachable data */ +static void lookup(unsigned want) { + unsigned need = want ^ (flags & want); + struct queue_entry *r, *rnext; + const char *dir, *re; + char *rights; + + if(!client || !need) + return; + if(need & DC_QUEUE) + disorder_queue(client, &queue); + if(need & DC_PLAYING) + disorder_playing(client, &playing); + if(need & DC_NEW) + disorder_new_tracks(client, &new, &nnew, 0); + if(need & DC_RECENT) { + /* we need to reverse the order of the list */ + disorder_recent(client, &r); + while(r) { + rnext = r->next; + r->next = recent; + recent = r; + r = rnext; + } + } + if(need & DC_VOLUME) + disorder_get_volume(client, + &volume_left, &volume_right); + if(need & (DC_FILES|DC_DIRS)) { + if(!(dir = cgi_get("directory"))) + dir = ""; + re = cgi_get("regexp"); + if(need & DC_DIRS) + if(disorder_directories(client, dir, re, + &dirs, &ndirs)) + ndirs = 0; + if(need & DC_FILES) + if(disorder_files(client, dir, re, + &files, &nfiles)) + nfiles = 0; + } + if(need & DC_RIGHTS) { + rights = RIGHT_READ; /* fail-safe */ + if(!disorder_userinfo(client, disorder_user(client), + "rights", &rights)) + parse_rights(rights, &rights, 1); + } + flags |= need; +} + +/** @brief Locate a track by ID */ +static struct queue_entry *findtrack(const char *id) { + struct queue_entry *q; + + lookups(DC_PLAYING); + if(playing && !strcmp(playing->id, id)) + return playing; + lookups(DC_QUEUE); + for(q = queue; q; q = q->next) + if(!strcmp(q->id, id)) + return q; + lookups(DC_RECENT); + for(q = recent; q; q = q->next) + if(!strcmp(q->id, id)) + return q; +} + +/** @brief Return @p i as a string */ +static const char *make_index(int i) { + char *s; + + byte_xasprintf(&s, "%d", i); + return s; +} + +/* @server-version@ + * + * Expands to the server's version string, or a (safe to use) error + * value if the server is unavailable or broken. + */ +static int exp_server_version(int attribute((unused)) nargs, + char attribute((unused)) **args, + struct sink *output, + void attribute((unused)) *u) { + const char *v; + + if(client) { + if(disorder_version(client, (char **)&v)) + v = "(cannot get version)"; + } else + v = "(server not running)"; + return sink_write(output, cgi_sgmlquote(v)) < 0 ? -1 : 0; +} + +/* @version@ + * + * Expands to the local version string. + */ +static int exp_version(int attribute((unused)) nargs, + char attribute((unused)) **args, + struct sink *output, + void attribute((unused)) *u) { + return sink_write(output, + cgi_sgmlquote(disorder_short_version_string)) < 0 ? -1 : 0; +} + +/* @url@ + * + * Expands to the base URL of the web interface. + */ +static int exp_url(int attribute((unused)) nargs, + char attribute((unused)) **args, + struct sink *output, + void attribute((unused)) *u) { + return sink_write(output, + cgi_sgmlquote(config->url)) < 0 ? -1 : 0; +} + +/* @arg{NAME}@ + * + * Expands to the CGI argument NAME, or the empty string if there is + * no such argument. + */ +static int exp_arg(int attribute((unused)) nargs, + char **args, + struct sink *output, + void attribute((unused)) *u) { + const char *s = cgi_get(args[0]); + if(s) + return sink_write(output, + cgi_sgmlquote(s)) < 0 ? -1 : 0; + else + return 0; +} + +/* @user@ + * + * Expands to the logged-in username (which might be "guest"), or to + * the empty string if not connected. + */ +static int exp_user(int attribute((unused)) nargs, + char attribute((unused)) **args, + struct sink *output, + void attribute((unused)) *u) { + const char *u; + + if(client && (u = disorder_user(client))) + return sink_write(output, cgi_sgmlquote(u)) < 0 ? -1 : 0; + return 0; +} + +/* @part{TRACK|ID}{PART}{CONTEXT} + * + * Expands to a track name part. + * + * A track may be identified by name or by queue ID. + * + * CONTEXT may be omitted. If it is then 'display' is assumed. + * + * If the CONTEXT is 'short' then the 'display' part is looked up, and the + * result truncated according to the length defined by the short_display + * configuration directive. + */ +static int exp_part(int nargs, + char **args, + struct sink *output, + void attribute((unused)) *u) { + const char *track = args[0], *part = args[1]; + const char *context = nargs > 2 ? args[2] : "display"; + char *s; + + if(track[0] != '/') { + struct queue_entry *q = findtrack(track); + + if(q) + track = q->track; + else + return 0; + } + if(client + && !disorder_get(client, &s, track, + !strcmp(context, "short") ? "display" : context, + part)) + return sink_write(output, cgi_sgmlquote(s)) < 0 ? -1 : 0; + return 0; +} + +/* @quote{STRING} + * + * SGML-quotes STRING. Note that most expansion results are already suitable + * quoted, so this expansion is usually not required. + */ +static int exp_quote(int attribute((unused)) nargs, + char **args, + struct sink *output, + void attribute((unused)) *u) { + return sink_write(output, cgi_sgmlquote(args[0])) < 0 ? -1 : 0; +} + +/* @who{ID} + * + * Expands to the name of the submitter of track ID, which must be a playing + * track, in the queue, or in the recent list. + */ +static int exp_who(int attribute((unused)) nargs, + char **args, + struct sink *output, + void attribute((unused)) *u) { + struct queue_entry *q = findtrack(args[0]); + + if(q && q->submitter) + return sink_write(output, cgi_sgmlquote(q->submitter)) < 0 ? -1 : 0; + return 0; +} + +/* @when{ID} + * + * Expands to the time a track started or is expected to start. The track must + * be a playing track, in the queue, or in the recent list. + */ +static int exp_when(int attribute((unused)) nargs, + char **args, + struct sink *output, + void attribute((unused)) *u) { + struct queue_entry *q = findtrack(args[0]); + const struct tm *w = 0; + + if(q) { + switch(q->state) { + case playing_isscratch: + case playing_unplayed: + case playing_random: + if(q->expected) + w = localtime(&q->expected); + break; + case playing_failed: + case playing_no_player: + case playing_ok: + case playing_scratched: + case playing_started: + case playing_paused: + case playing_quitting: + if(q->played) + w = localtime(&q->played); + break; + } + if(w) + return sink_printf(output, "%d:%02d", w->tm_hour, w->tm_min) < 0 ? -1 : 0; + } + return sink_write(output, " ") < 0 ? -1 : 0; +} + +/* @length{ID|TRACK} + * + * Expands to the length of a track, identified by its queue ID or its name. + * If it is the playing track (identified by ID) then the amount played so far + * is included. + */ +static int exp_length(int attribute((unused)) nargs, + char **args, + struct sink *output, + void attribute((unused)) *u) { + struct queue_entry *q; + long length = 0; + const char *name; + + if(args[0][0] == '/') + /* Track identified by name */ + name = args[0]; + else { + /* Track identified by queue ID */ + if(!(q = findtrack(args[0]))) + return 0; + if(q->state == play_start || q->state == playing_paused) + if(sink_printf(output, "%ld:%02ld/", q->sofar / 60, q->sofar % 60) < 0) + return -1; + name = q->track; + } + if(client && disorder_length(client, name, &length)) + return sink_printf(output, "%ld:%02ld", + length / 60, length % 60) < 0 ? -1 : 0; + return sink_write(output, " ") < 0 ? -1 : 0; +} + +/* @removable{ID}@ + * + * Expands to "true" if track ID is removable (or scratchable, if it is the + * playing track) and "false" otherwise. + */ +static int exp_removable(int attribute((unused)) nargs, + char **args, + struct sink *output, + void attribute((unused)) *u) { + struct queue_entry *q = findtrack(args[0]); + /* TODO would be better to reject recent */ + + if(!q || !client) + return mx_bool_result(output, 0); + lookup(DC_RIGHTS); + return mx_bool_result(output, + (q == playing ? right_scratchable : right_removable) + (rights, disorder_user(client), q)); +} + +/* @movable{ID}@ + * + * Expands to "true" if track ID is movable and "false" otherwise. + */ +static int exp_movable(int attribute((unused)) nargs, + char **args, + struct sink *output, + void attribute((unused)) *u) { + struct queue_entry *q = findtrack(args[0]); + /* TODO would be better to recent playing/recent */ + + if(!q || !client) + return mx_bool_result(output, 0); + lookup(DC_RIGHTS); + return mx_bool_result(output, + right_movable(rights, disorder_user(client), q)); +} + +/* @playing{TEMPLATE} + * + * Expands to TEMPLATE, with: + * - @id@ expanded to the queue ID of the playing track + * - @track@ expanded to its UNQUOTED name + * + * If no track is playing expands to nothing. + * + * TEMPLATE is optional. If it is left out then instead expands to the queue + * ID of the playing track. + */ +static int exp_playing(int nargs, + const struct mx_node **args, + struct sink *output, + void attribute((unused)) *u) { + lookup(DC_PLAYING); + if(!playing) + return 0; + if(!nargs) + return sink_write(output, playing->id) < 0 ? -1 : 0; + return mx_rewritel(args[0], + "id", playing->id, + "track", playing->track, + (char *)0); +} + +/* @queue{TEMPLATE} + * + * For each track in the queue, expands TEMPLATE with the following expansions: + * - @id@ to the queue ID of the track + * - @track@ to the UNQUOTED track name + * - @index@ to the track number from 0 + * - @parity@ to "even" or "odd" alternately + * - @first@ to "true" on the first track and "false" otherwise + * - @last@ to "true" on the last track and "false" otherwise + */ +static int exp_queue(int attribute((unused)) nargs, + const struct mx_node **args, + struct sink *output, + void attribute((unused)) *u) { + struct queue_entry *q; + int rc, i; + + lookup(DC_QUEUE); + for(q = queue, i = 0; q; q = q->next, ++i) + if((rc = mx_rewritel(args[0], + "id", q->id, + "track", q->track, + "index", make_index(i), + "parity", i % 2 ? "odd" : "even", + "first", q == queue ? "true" : "false", + "last", q->next ? "false" : "true", + (char *)0))) + return rc; + return 0; +} + +/* @recent{TEMPLATE} + * + * For each track in the recently played list, expands TEMPLATE with the + * following expansions: + * - @id@ to the queue ID of the track + * - @track@ to the UNQUOTED track name + * - @index@ to the track number from 0 + * - @parity@ to "even" or "odd" alternately + * - @first@ to "true" on the first track and "false" otherwise + * - @last@ to "true" on the last track and "false" otherwise + */ +static int exp_recent(int attribute((unused)) nargs, + const struct mx_node **args, + struct sink *output, + void attribute((unused)) *u) { + struct queue_entry *q; + int rc, i; + + lookup(DC_RECENT); + for(q = recent, i = 0; q; q = q->next, ++i) + if((rc = mx_rewritel(args[0], + "id", q->id, + "track", q->track, + "index", make_index(i), + "parity", i % 2 ? "odd" : "even", + "first", q == recent ? "true" : "false", + "last", q->next ? "false" : "true", + (char *)0))) + return rc; + return 0; +} + +/* @new{TEMPLATE} + * + * For each track in the newly added list, expands TEMPLATE wit the following + * expansions: + * - @track@ to the UNQUOTED track name + * - @index@ to the track number from 0 + * - @parity@ to "even" or "odd" alternately + * - @first@ to "true" on the first track and "false" otherwise + * - @last@ to "true" on the last track and "false" otherwise + * + * Note that unlike @playing@, @queue@ and @recent@ which are otherwise + * superficially similar, there is no @id@ sub-expansion here. + */ +static int exp_new(int attribute((unused)) nargs, + const struct mx_node **args, + struct sink *output, + void attribute((unused)) *u) { + struct queue_entry *q; + int rc, i; + + lookup(DC_NEW); + /* TODO perhaps we should generate an ID value for tracks in the new list */ + for(i = 0; i < nnew; ++i) + if((rc = mx_rewritel(args[0], + "track", new[i], + "index", make_index(i), + "parity", i % 2 ? "odd" : "even", + "first", i == 0 ? "true" : "false", + "last", i == nnew - 1 ? "false" : "true", + (char *)0))) + return rc; + return 0; +} + +/* @volume{CHANNEL}@ + * + * Expands to the volume in a given channel. CHANNEL must be "left" or + * "right". + */ +static int exp_volume(int attribute((unused)) nargs, + char **args, + struct sink *output, + void attribute((unused)) *u) { + lookup(DC_VOLUME); + return sink_write(output, "%d", + !strcmp(args[0], "left") + ? volume_left : volume_right) < 0 ? -1 : 0; +} + +/* @isplaying@ + * + * Expands to "true" if there is a playing track, otherwise "false". + */ +static int exp_isplaying(int attribute((unused)) nargs, + char attribute((unused)) **args, + struct sink *output, + void attribute((unused)) *u) { + lookup(DC_PLAYING); + return mx_bool_result(output, !!playing); +} + +/* @isqueue@ + * + * Expands to "true" if there the queue is nonempty, otherwise "false". + */ +static int exp_isqueue(int attribute((unused)) nargs, + char attribute((unused)) **args, + struct sink *output, + void attribute((unused)) *u) { + lookup(DC_QUEUE); + return mx_bool_result(output, !!queue); +} + +/* @isrecent@ + * + * Expands to "true" if there the recently played list is nonempty, otherwise + * "false". + */ +static int exp_isrecent(int attribute((unused)) nargs, + char attribute((unused)) **args, + struct sink *output, + void attribute((unused)) *u) { + lookup(DC_RECENT); + return mx_bool_result(output, !!recent); +} + +/* @isnew@ + * + * Expands to "true" if there the newly added track list is nonempty, otherwise + * "false". + */ +static int exp_isnew(int attribute((unused)) nargs, + char attribute((unused)) **args, + struct sink *output, + void attribute((unused)) *u) { + lookup(DC_NEW); + return mx_bool_result(output, !!nnew); +} + +/* @pref{TRACK}{KEY}@ + * + * Expands to a track preference. + */ +static int exp_pref(int attribute((unused)) nargs, + char **args, + struct sink *output, + void attribute((unused)) *u) { + char *value; + + if(client && !disorder_get(client, args[0], args[1], &value)) + return sink_write(output, cgi_sgmlquote(value)) < 0 ? -1 : 0; +} + +/* @prefs{TRACK}{TEMPLATE}@ + * + * For each track preference of track TRACK, expands TEMPLATE with the + * following expansions: + * - @name@ to the UNQUOTED preference name + * - @index@ to the preference number from 0 + * - @value@ to the UNQUOTED preference value + * - @parity@ to "even" or "odd" alternately + * - @first@ to "true" on the first preference and "false" otherwise + * - @last@ to "true" on the last preference and "false" otherwise + * + * Use @quote@ to quote preference names and values where necessary; see below. + */ +static int exp_prefs(int attribute((unused)) nargs, + const struct mx_node **args, + struct sink *output, + void attribute((unused)) *u) { + int rc, i; + struct kvp *k, *head; + char *track; + + if((rc = mx_expandstr(args[0], &track, u, "argument #0 (TRACK)"))) + return rc; + if(!client || disorder_prefs(client, track, &head)) + return 0; + for(k = head, i = 0; k; k = k->next, ++i) + if((rc = mx_rewritel(args[1], + "index", make_index(i), + "parity", i % 2 ? "odd" : "even", + "name", k->name, + "value", k->value, + "first", k == head ? "true" : "false", + "last", k->next ? "false" : "true", + (char *)0))) + return rc; + return 0; +} + +/* @transform{TRACK}{TYPE}{CONTEXT}@ + * + * Transforms a track name (if TYPE is "track") or directory name (if type is + * "dir"). CONTEXT should be the context, if it is left out then "display" is + * assumed. + */ +static int exp_transform(int nargs, + char **args, + struct sink *output, + void attribute((unused)) *u) { + const char *t = trackname_transform(args[1], args[0], + (nargs > 2 ? args[2] : "display"))); + return sink_write(output, cgi_sgmlquote(t)) < 0 ? -1 : 0; +} + +/* @enabled@ + * + * Expands to "true" if playing is enabled, otherwise "false". + */ +static int exp_enabled(int attribute((unused)) nargs, + char attribute((unused)) **args, + struct sink *output, + void attribute((unused)) *u) { + int enabled = 0; + + if(client) + disorder_enabled(client, &enabled); + return mx_bool_result(output, enabled); +} + +/* @random-enabled@ + * + * Expands to "true" if random play is enabled, otherwise "false". + */ +static int exp_enabled(int attribute((unused)) nargs, + char attribute((unused)) **args, + struct sink *output, + void attribute((unused)) *u) { + int enabled = 0; + + if(client) + disorder_random_enabled(client, &enabled); + return mx_bool_result(output, enabled); +} + +/* @trackstate{TRACK}@ + * + * Expands to "playing" if TRACK is currently playing, or "queue" if it is in + * the queue, otherwise to nothing. + */ +static int exp_trackstate(int attribute((unused)) nargs, + char **args, + struct sink *output, + void attribute((unused)) *u) { + char *track; + struct queue_entry *q; + + if(!client) + return 0; + if(disorder_resolve(client, &track, args[0])) + return 0; + lookup(DC_PLAYING); + if(playing && !strcmp(track, playing->track)) + return sink_write(output, "playing") < 0 ? -1 : 0; + lookup(DC_QUEUE); + for(q = queue; q; q = q->next) + if(!strcmp(track, q->track)) + return sink_write(output, "queued") < 0 ? -1 : 0; + return 0; +} + +/* @thisurl@ + * + * Expands to an UNQUOTED URL which points back to the current page. (NB it + * might not be byte for byte identical - for instance, CGI arguments might be + * re-ordered.) + */ +static int exp_thisurl(int attribute((unused)) nargs, + char attribute((unused)) **args, + struct sink *output, + void attribute((unused)) *u) { + return cgi_thisurl(config->url); +} + +/* @resolve{TRACK}@ + * + * Expands to an UNQUOTED name for the TRACK that is not an alias, or to + * nothing if it is not a valid track. + */ +static int exp_resolve(int attribute((unused)) nargs, + char **args, + struct sink *output, + void attribute((unused)) *u) { + char *r; + + if(client && !disorder_resolve(client, &r, args[0])) + return sink_write(output, r) < 0 ? -1 : 0; + return 0; +} + +/* @paused@ + * + * Expands to "true" if the playing track is paused, to "false" if it is + * playing (or if there is no playing track at all). + */ +static int exp_paused(int attribute((unused)) nargs, + char attribute((unused)) **args, + struct sink *output, + void attribute((unused)) *u) { + lookup(DC_PLAYING); + return mx_bool_result(output, playing && playing->state == playing_paused); +} + +/* @state{ID}@ + * + * Expands to the current state of track ID. + */ +static int exp_state(int attribute((unused)) nargs, + char **args, + struct sink *output, + void attribute((unused)) *u) { + struct queue_entry *q = findtrack(args[0]); + + if(q) + return sink_write(output, playing_states[q->state]) < 0 ? -1 : 0; + return 0; +} + +/* @right{RIGHT}{WITH-RIGHT}{WITHOUT-RIGHT}@ + * + * Expands to WITH-RIGHT if the current user has right RIGHT, otherwise to + * WITHOUT-RIGHT. The WITHOUT-RIGHT argument may be left out. + * + * If both WITH-RIGHT and WITHOUT-RIGHT are left out then expands to "true" if + * the user has the right and "false" otherwise. + * + * If there is no connection to the server then expands to nothing (in all + * cases). + */ +static int exp_right(int nargs, + const struct mx_node **args, + struct sink *output, + void attribute((unused)) *u) { + char *right; + rights_type r; + + if(!client) + return 0; + lookup(DC_RIGHTS); + if((rc = mx_expandstr(args[0], &rightname, u, "argument #0 (RIGHT)"))) + return rc; + if(parse_rights(right, &r, 1/*report*/)) + return 0; + /* Single-argument form */ + if(nargs == 1) + return mx_bool_result(output, !!(r & rights)); + /* Multiple argument form */ + if(r & rights) + return mx_expandl(args[1], (char *)0); + if(nargs == 3) + return mx_expandl(args[2], (char *)0); + return 0; +} + +/* @userinfo{PROPERTY}@ + * + * Expands to the named property of the current user. + */ +static int exp_userinfo(int attribute((unused)) nargs, + char **args, + struct sink *output, + void attribute((unused)) *u) { + char *v; + + if(client && !disorder_userinfo(client, disorder_user(client), args[0], &v)) + return sink_write(output, v) < 0 ? -1 : 0; + return 0; +} + +/** @brief Register DisOrder-specific expansions */ +void register_disorder_expansions(void) { + mx_register(exp_arg, 1, 1, "arg"); + mx_register(exp_enabled, 0, 0, "enabled"); + mx_register(exp_isnew, 0, 0, "isnew"); + mx_register(exp_isplaying, 0, 0, "isplaying"); + mx_register(exp_isqueue, 0, 0, "isplaying"); + mx_register(exp_isrecent, 0, 0, "isrecent"); + mx_register(exp_length, 1, 1, "length"); + mx_register(exp_movable, 1, 1, "movable"); + mx_register(exp_part, 2, 3, "part"); + mx_register(exp_pref, 2, 2, "pref"); + mx_register(exp_quote, 1, 1, "quote"); + mx_register(exp_random_enabled, 0, 0, "random-enabled"); + mx_register(exp_removable, 1, 1, "removable"); + mx_register(exp_resolve, 1, 1, "resolve"); + mx_register(exp_right, 1, 3, "right"); + mx_register(exp_server_version, 0, 0, "server-version"); + mx_register(exp_state, 1, 1, "state"); + mx_register(exp_thisurl, 0, 0, "thisurl"); + mx_register(exp_trackstate, 1, 1, "trackstate"); + mx_register(exp_transform, 2, 3, "transform"); + mx_register(exp_url, 0, 0, "url"); + mx_register(exp_user, 0, 0, "user"); + mx_register(exp_userinfo, 1, 1, "userinfo"); + mx_register(exp_version, 0, 0, "version"); + mx_register(exp_volume, 1, 1, "volume"); + mx_register(exp_when, 1, 1, "when"); + mx_register(exp_who, 1, 1, "who"); + mx_register_magic(exp_new, 1, 1, "new"); + mx_register_magic(exp_playing, 0, 1, "playing"); + mx_register_magic(exp_prefs, 2, 2, "prefs"); + mx_register_magic(exp_queue, 1, 1, "queue"); + mx_register_magic(exp_recent, 1, 1, "recent"); +} + +/* +Local Variables: +c-basic-offset:2 +comment-column:40 +fill-column:79 +indent-tabs-mode:nil +End: +*/ diff --git a/server/macros-disorder.h b/server/macros-disorder.h new file mode 100644 index 0000000..bb8950f --- /dev/null +++ b/server/macros-disorder.h @@ -0,0 +1,39 @@ +/* + * This file is part of DisOrder. + * Copyright (C) 2008 Richard Kettlewell + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ +/** @file server/macros-disorder.h + * @brief DisOrder-specific expansions + */ + +#ifndef MACROS_DISORDER_H +#define MACROS_DISORDER_H + +extern disorder_client *client; +void register_disorder_expansions(void); + +#endif /* MACROS_DISORDER_H */ + +/* +Local Variables: +c-basic-offset:2 +comment-column:40 +fill-column:79 +indent-tabs-mode:nil +End: +*/ diff --git a/server/options.c b/server/options.c new file mode 100644 index 0000000..88fc9bf --- /dev/null +++ b/server/options.c @@ -0,0 +1,217 @@ +/* + * This file is part of DisOrder. + * Copyright (C) 2004-2008 Richard Kettlewell + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ +/** @file server/options.c + * @brief CGI options + */ + +#include +#include "types.h" + +#include + +#include "mem.h" +#include "hash.h" +#include "macros.h" +#include "options.h" +#include "split.h" +#include "table.h" + +struct column { + int ncolumns; + char **columns; +}; + +static hash *labels; +static hash *columns; + +static void option__label(int attribute((unused)) nvec, + char **vec) { + option_set(vec[0], vec[1]); +} + +static void option__include(int attribute((unused)) nvec, + char **vec) { + option__readfile(vec[0]); +} + +static void option__columns(int nvec, + char **vec) { + struct column c; + + c.ncolumns = nvec - 1; + c.columns = &vec[1]; + hash_add(columns, vec[0], &c, HASH_INSERT_OR_REPLACE); +} + +static struct option { + const char *name; + int minargs, maxargs; + void (*handler)(int nvec, char **vec); +} options[] = { + { "columns", 1, INT_MAX, option_columns }, + { "include", 1, 1, option_include }, + { "label", 2, 2, option_label }, +}; + +static void option__split_error(const char *msg, + void *u) { + struct read_options_state *cs = u; + + error(0, "%s:%d: %s", cs->name, cs->line, msg); +} + +static void option__readfile(const char *name) { + int n, i; + int fd; + FILE *fp; + char **vec, *buffer; + struct read_options_state cs; + const char *path; + + if(!(cs.name = mx_find(name))) + return; + if(!(fp = fopen(cs.name, "r"))) + fatal(errno, "error opening %s", cs.name); + cs.line = 0; + while(!inputline(cs.name, fp, &buffer, '\n')) { + ++cs.line; + if(!(vec = split(buffer, &n, SPLIT_COMMENTS|SPLIT_QUOTES, + option__split_error, &cs))) + continue; + if(!n) + continue; + if((i = TABLE_FIND(options, struct option, name, vec[0])) == -1) { + error(0, "%s:%d: unknown option '%s'", cs.name, cs.line, vec[0]); + continue; + } + ++vec; + --n; + if(n < options[i].minargs) { + error(0, "%s:%d: too few arguments to '%s'", cs.name, cs.line, vec[-1]); + continue; + } + if(n > options[i].maxargs) { + error(0, "%s:%d: too many arguments to '%s'", cs.name, cs.line, vec[-1]); + continue; + } + options[i].handler(n, vec); + } + fclose(fp); +} + +static void option__init(void) { + static int have_read_options; + + if(!have_read_options) { + have_read_options = 1; + labels = hash_new(sizeof (char *)); + columns = hash_new(sizeof (struct column)); + option__readfile("options"); + } +} + +/** @brief Set an option + * @param name Option name + * @param value Option value + * + * If the option was already set its value is replaced. + * + * @p name and @p value are copied. + */ +void option_set(const char *name, const char *value) { + char *v = xstrdup(value); + + option__init(); + hash_add(labels, name, &v, HASH_INSERT_OR_REPLACE); +} + +/** @brief Get a label + * @param key Name of label + * @return Value of label (never NULL) + * + * If label images.X isn't found then the return value is + * X.png, allowing url.static to be used to provide a base + * for all images with per-image overrides. + * + * Otherwise undefined labels expand to their last (dot-separated) + * component. + */ +const char *option_label(const char *key) { + const char *label; + + option__init(); + if(!(label = *(char **)hash_find(labels, key))) { + /* No label found */ + if(!strncmp(key, "images.", 7)) { + static const char *url_static; + /* images.X defaults to X.png */ + + if(!url_static) + url_static = option_label("url.static"); + byte_xasprintf((char **)&label, "%s%s.png", url_static, key + 7); + } else if((label = strrchr(key, '.'))) + /* X.Y defaults to Y */ + ++label; + else + /* otherwise default to label name */ + label = key; + } + return label; +} + +/** @brief Test whether a label exists + * @param key Name of label + * @return 1 if label exists, otherwise 0 + * + * Labels that don't exist still have an expansion (per option_label() + * documentation), and possibly not even a placeholder one. + */ +int option_label_exists(const char *key) { + option__init(); + return !!hash_find(labels, key); +} + +/** @brief Return a column list + * @param name Context (playing/recent/etc) + * @param ncolumns Where to store column count or NULL + * @return Pointer to column list + */ +char **option_columns(const char *name, int *ncolumns) { + struct column *c; + + option__init(); + c = hash_find(columns, name); + if(c) { + if(ncolumns) + *ncolumns = c->ncolumns; + return c->columns; + } else { + if(ncolumns) + *ncolumns = 0; + return 0; + } +} + +/* +Local Variables: +c-basic-offset:2 +comment-column:40 +End: +*/ diff --git a/server/options.h b/server/options.h new file mode 100644 index 0000000..0f16ff5 --- /dev/null +++ b/server/options.h @@ -0,0 +1,39 @@ +/* + * This file is part of DisOrder. + * Copyright (C) 2008 Richard Kettlewell + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ +/** @file server/options.ch + * @brief CGI options + */ + +#ifndef OPTIONS_H +#define OPTIONS_H + +void option_set(const char *name, const char *value); +const char *option_label(const char *key); +int option_label_exists(const char *key); +char **option_columns(const char *name, int *ncolumns); + +#endif /* OPTIONS_H */ + +/* +Local Variables: +c-basic-offset:2 +comment-column:40 +End: +*/ diff --git a/server/cgi.h b/server/server-cgi.h similarity index 100% rename from server/cgi.h rename to server/server-cgi.h diff --git a/templates/Makefile.am b/templates/Makefile.am index 886e6a8..93f8692 100644 --- a/templates/Makefile.am +++ b/templates/Makefile.am @@ -18,10 +18,10 @@ # USA # -pkgdata_DATA=about.html choose.html credits.html playing.html recent.html \ - stdhead.html stylesheet.html search.html about.html volume.html \ - prefs.html help.html choosealpha.html topbar.html \ - topbarend.html error.html new.html login.html \ +pkgdata_DATA=about.tmpl choose.tmpl credits.tmpl playing.tmpl recent.tmpl \ + stdhead.tmpl stylesheet.tmpl search.tmpl about.tmpl volume.tmpl \ + prefs.tmpl help.tmpl choosealpha.tmpl topbar.tmpl \ + topbarend.tmpl error.tmpl new.tmpl login.tmpl \ options options.labels \ options.columns static_DATA=disorder.css diff --git a/templates/about.html b/templates/about.tmpl similarity index 100% rename from templates/about.html rename to templates/about.tmpl diff --git a/templates/choose.html b/templates/choose.tmpl similarity index 100% rename from templates/choose.html rename to templates/choose.tmpl diff --git a/templates/choosealpha.html b/templates/choosealpha.tmpl similarity index 100% rename from templates/choosealpha.html rename to templates/choosealpha.tmpl diff --git a/templates/credits.html b/templates/credits.tmpl similarity index 100% rename from templates/credits.html rename to templates/credits.tmpl diff --git a/templates/error.html b/templates/error.tmpl similarity index 100% rename from templates/error.html rename to templates/error.tmpl diff --git a/templates/help.html b/templates/help.tmpl similarity index 100% rename from templates/help.html rename to templates/help.tmpl diff --git a/templates/login.html b/templates/login.tmpl similarity index 100% rename from templates/login.html rename to templates/login.tmpl diff --git a/templates/macros.tmpl b/templates/macros.tmpl new file mode 100644 index 0000000..450783e --- /dev/null +++ b/templates/macros.tmpl @@ -0,0 +1,26 @@ +@discard{ + +This file is part of DisOrder. +Copyright (C) 2004-2008 Richard Kettlewell + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +USA + +@define {action} {} + {@if {@arg:mgmt@} + {manage} + {@arg:action@}@}@ + +}@@@ diff --git a/templates/new.html b/templates/new.tmpl similarity index 100% rename from templates/new.html rename to templates/new.tmpl diff --git a/templates/playing.html b/templates/playing.tmpl similarity index 100% rename from templates/playing.html rename to templates/playing.tmpl diff --git a/templates/prefs.html b/templates/prefs.tmpl similarity index 100% rename from templates/prefs.html rename to templates/prefs.tmpl diff --git a/templates/recent.html b/templates/recent.tmpl similarity index 100% rename from templates/recent.html rename to templates/recent.tmpl diff --git a/templates/search.html b/templates/search.tmpl similarity index 100% rename from templates/search.html rename to templates/search.tmpl diff --git a/templates/stdhead.html b/templates/stdhead.tmpl similarity index 100% rename from templates/stdhead.html rename to templates/stdhead.tmpl diff --git a/templates/stylesheet.html b/templates/stylesheet.tmpl similarity index 100% rename from templates/stylesheet.html rename to templates/stylesheet.tmpl diff --git a/templates/topbar.html b/templates/topbar.tmpl similarity index 100% rename from templates/topbar.html rename to templates/topbar.tmpl diff --git a/templates/topbarend.html b/templates/topbarend.tmpl similarity index 100% rename from templates/topbarend.html rename to templates/topbarend.tmpl -- [mdw]