From bca4e2b7171bae3d818248057cdc4ffbb5683bf0 Mon Sep 17 00:00:00 2001 Message-Id: From: Mark Wooding Date: Sat, 10 May 2008 09:30:04 +0100 Subject: [PATCH] Start conversion of CGI actions. Organization: Straylight/Edgeware From: Richard Kettlewell --- server/actions.c | 134 ++++++++++++++++++++++++ server/cgi.c | 31 ------ server/cgimain.c | 7 +- server/dcgi.c | 213 --------------------------------------- server/macros-disorder.c | 67 ++++++++---- server/macros-disorder.h | 2 + 6 files changed, 186 insertions(+), 268 deletions(-) create mode 100644 server/actions.c diff --git a/server/actions.c b/server/actions.c new file mode 100644 index 0000000..d6d227d --- /dev/null +++ b/server/actions.c @@ -0,0 +1,134 @@ +/* + * 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 + */ + +#include +#include "types.h" + +/** @brief Login cookie */ +char *login_cookie; + +/** @brief Table of actions */ +static const struct action { + /** @brief Action name */ + const char *name; + /** @brief Action handler */ + void (*handler)(void); +} actions[] = { + { "confirm", act_confirm }, + { "disable", act_disable }, + { "edituser", act_edituser }, + { "enable", act_enable }, + { "login", act_login }, + { "logout", act_logout }, + { "manage", act_manage }, + { "move", act_move }, + { "pause", act_pause }, + { "play", act_play }, + { "playing", act_playing }, + { "prefs", act_prefs }, + { "random-disable", act_random_disable }, + { "random-enable", act_random_enable }, + { "register", act_register }, + { "reminder", act_reminder }, + { "remove", act_remove }, + { "resume", act_resume }, + { "scratch", act_scratch }, + { "volume", act_volume }, +}; + +/** @brief Expand a template + * @param name Base name of template, or NULL to consult CGI args + */ +void disorder_cgi_expand(const char *name) { + const char *p; + + /* For unknown actions check that they aren't evil */ + for(p = name; *p && isalnum((unsigned char)*p); ++p) + ; + if(*p) + fatal(0, "invalid action name '%s'", action); + byte_xasprintf((char **)&p, "%s.tmpl", action); + if(mx_expand_file(p, sink_stdio(stdout), 0) == -1 + || fflush(stdout) < 0) + fatal(errno, "error writing to stdout"); +} + +/** @brief Execute a web action + * @param action Action to perform, or NULL to consult CGI args + * + * If no recognized action is specified then 'playing' is assumed. + */ +void disorder_cgi_action(const char *action) { + int n; + char *s; + + /* Consult CGI args if caller had no view */ + if(!action) + action = cgi_get("action"); + /* Pick a default if nobody cares at all */ + if(!action) { + /* We allow URLs which are just c=... in order to keep confirmation URLs, + * which are user-facing, as short as possible. Actually we could lose the + * c= for this... */ + if(cgi_get("c")) + action = "confirm"; + else + action = "playing"; + } + if((n = TABLE_FIND(actions, struct action, name, action)) >= 0) + /* Its a known action */ + actions[n].handler(); + else + /* Just expand the template */ + disorder_cgi_expand(action); +} + +/** @brief Generate an error page */ +void disorder_cgi_error(const char *msg, ...) { + va_list ap; + + va_start(ap, msg); + byte_xvasprintf(&error_string, msg, ap); + va_end(ap); + disorder_cgi_expand("error"); +} + +/** @brief Log in as the current user or guest if none */ +void disorder_cgi_login(dcgi_state *ds, struct sink *output) { + /* Junk old data */ + disorder_macros_reset(); + /* Reconnect */ + if(disorder_connect_cookie(client, login_cookie)) { + disorder_cgi_error("Cannot connect to server"); + exit(0); + } + /* If there was a cookie but it went bad, we forget it */ + if(login_cookie && !strcmp(disorder_user(>client), "guest")) + login_cookie = 0; +} + +/* +Local Variables: +c-basic-offset:2 +comment-column:40 +fill-column:79 +indent-tabs-mode:nil +End: +*/ diff --git a/server/cgi.c b/server/cgi.c index 92c39ec..5f9e17f 100644 --- a/server/cgi.c +++ b/server/cgi.c @@ -55,37 +55,6 @@ #include "unicode.h" #include "hash.h" -struct kvp *cgi_args; - -/* options */ -struct column { - struct column *next; - char *name; - int ncolumns; - char **columns; -}; - -/* macros */ -struct cgi_macro { - int nargs; - char **args; - const char *value; -}; - -#define RELIST(x) struct re *x, **x##_tail = &x - -static int have_read_options; -static struct kvp *labels; -static struct column *columns; - -static void include_options(const char *name); -static void cgi_expand_parsed(const char *name, - struct cgi_element *head, - const struct cgi_expansion *expansions, - size_t nexpansions, - cgi_1sink *output, - void *u); - void cgi_header(struct sink *output, const char *name, const char *value) { sink_printf(output, "%s: %s\r\n", name, value); } diff --git a/server/cgimain.c b/server/cgimain.c index 4a37c52..77764c0 100644 --- a/server/cgimain.c +++ b/server/cgimain.c @@ -110,10 +110,7 @@ int main(int argc, char **argv) { config->url = infer_url(); memset(&g, 0, sizeof g); memset(&s, 0, sizeof s); - s.g = &g; - g.client = disorder_get_client(); - output.quote = 1; - output.sink = sink_stdio("stdout", stdout); + output = sink_stdio("stdout", stdout); /* See if there's a cookie */ cookie_env = getenv("HTTP_COOKIE"); if(cookie_env) { @@ -142,6 +139,8 @@ int main(int argc, char **argv) { * directory second, so that the latter overrides the former. */ mx_search_path(pkgconfdir); mx_search_path(pkgdatadir); + /* Never cache anythging */ + cgi_header(output->sink, "Cache-Control", "no-cache"); /* Create the initial connection, trying the cookie if we found a suitable * one. */ disorder_cgi_login(&s, &output); diff --git a/server/dcgi.c b/server/dcgi.c index 15faf2f..65f0b33 100644 --- a/server/dcgi.c +++ b/server/dcgi.c @@ -1,25 +1,3 @@ -/* - * 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 - */ - -#include -#include "types.h" #include #include @@ -59,40 +37,12 @@ #include "sendmail.h" #include "base64.h" -char *login_cookie; - -static void expand(cgi_sink *output, - const char *template, - dcgi_state *ds); -static void expandstring(cgi_sink *output, - const char *string, - dcgi_state *ds); - struct entry { const char *path; const char *sort; const char *display; }; -static const char nonce_base64_table[] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-/*"; - -static const char *nonce(void) { - static uint32_t count; - - struct ndata { - uint16_t count; - uint16_t pid; - uint32_t when; - } nd; - - nd.count = count++; - nd.pid = (uint32_t)getpid(); - nd.when = (uint32_t)time(0); - return generic_to_base64((void *)&nd, sizeof nd, - nonce_base64_table); -} - static int compare_entry(const void *a, const void *b) { const struct entry *ea = a, *eb = b; @@ -639,31 +589,6 @@ static void act_reminder(cgi_sink *output, expand_template(ds, output, "login"); } -static const struct action { - const char *name; - void (*handler)(cgi_sink *output, dcgi_state *ds); -} actions[] = { - { "confirm", act_confirm }, - { "disable", act_disable }, - { "edituser", act_edituser }, - { "enable", act_enable }, - { "login", act_login }, - { "logout", act_logout }, - { "move", act_move }, - { "pause", act_pause }, - { "play", act_play }, - { "playing", act_playing }, - { "prefs", act_prefs }, - { "random-disable", act_random_disable }, - { "random-enable", act_random_enable }, - { "register", act_register }, - { "reminder", act_reminder }, - { "remove", act_remove }, - { "resume", act_resume }, - { "scratch", act_scratch }, - { "volume", act_volume }, -}; - /* expansions *****************************************************************/ static void exp_label(int attribute((unused)) nargs, @@ -1006,144 +931,6 @@ static void exp_image(int attribute((unused)) nargs, cgi_output(output, "/disorder/%s", imagestem); } -static const struct cgi_expansion expansions[] = { - { "#", 0, INT_MAX, EXP_MAGIC, exp_comment }, - { "action", 0, 0, 0, exp_action }, - { "and", 0, INT_MAX, EXP_MAGIC, exp_and }, - { "arg", 1, 1, 0, exp_arg }, - { "basename", 0, 1, 0, exp_basename }, - { "choose", 2, 2, EXP_MAGIC, exp_choose }, - { "define", 3, 3, EXP_MAGIC, exp_define }, - { "dirname", 0, 1, 0, exp_dirname }, - { "enabled", 0, 0, 0, exp_enabled }, - { "eq", 2, 2, 0, exp_eq }, - { "file", 0, 0, 0, exp_file }, - { "files", 1, 1, EXP_MAGIC, exp_files }, - { "fullname", 0, 0, 0, exp_fullname }, - { "id", 0, 0, 0, exp_id }, - { "if", 2, 3, EXP_MAGIC, exp_if }, - { "image", 1, 1, 0, exp_image }, - { "include", 1, 1, 0, exp_include }, - { "index", 0, 0, 0, exp_index }, - { "isdirectories", 0, 0, 0, exp_isdirectories }, - { "isfiles", 0, 0, 0, exp_isfiles }, - { "isfirst", 0, 0, 0, exp_isfirst }, - { "islast", 0, 0, 0, exp_islast }, - { "isnew", 0, 0, 0, exp_isnew }, - { "isplaying", 0, 0, 0, exp_isplaying }, - { "isqueue", 0, 0, 0, exp_isqueue }, - { "isrecent", 0, 0, 0, exp_isrecent }, - { "label", 1, 1, 0, exp_label }, - { "length", 0, 0, 0, exp_length }, - { "movable", 0, 0, 0, exp_movable }, - { "navigate", 2, 2, EXP_MAGIC, exp_navigate }, - { "ne", 2, 2, 0, exp_ne }, - { "new", 1, 1, EXP_MAGIC, exp_new }, - { "nfiles", 0, 0, 0, exp_nfiles }, - { "nonce", 0, 0, 0, exp_nonce }, - { "not", 1, 1, 0, exp_not }, - { "or", 0, INT_MAX, EXP_MAGIC, exp_or }, - { "parity", 0, 0, 0, exp_parity }, - { "part", 1, 3, 0, exp_part }, - { "paused", 0, 0, 0, exp_paused }, - { "playing", 1, 1, EXP_MAGIC, exp_playing }, - { "pref", 2, 2, 0, exp_pref }, - { "prefname", 0, 0, 0, exp_prefname }, - { "prefs", 2, 2, EXP_MAGIC, exp_prefs }, - { "prefvalue", 0, 0, 0, exp_prefvalue }, - { "queue", 1, 1, EXP_MAGIC, exp_queue }, - { "random-enabled", 0, 0, 0, exp_random_enabled }, - { "recent", 1, 1, EXP_MAGIC, exp_recent }, - { "removable", 0, 0, 0, exp_removable }, - { "resolve", 1, 1, 0, exp_resolve }, - { "right", 1, 3, EXP_MAGIC, exp_right }, - { "scratchable", 0, 0, 0, exp_scratchable }, - { "search", 2, 3, EXP_MAGIC, exp_search }, - { "server-version", 0, 0, 0, exp_server_version }, - { "shell", 1, 1, 0, exp_shell }, - { "state", 0, 0, 0, exp_state }, - { "stats", 0, 0, 0, exp_stats }, - { "thisurl", 0, 0, 0, exp_thisurl }, - { "track", 0, 0, 0, exp_track }, - { "trackstate", 1, 1, 0, exp_trackstate }, - { "transform", 2, 3, 0, exp_transform }, - { "url", 0, 0, 0, exp_url }, - { "urlquote", 1, 1, 0, exp_urlquote }, - { "user", 0, 0, 0, exp_user }, - { "userinfo", 1, 1, 0, exp_userinfo }, - { "version", 0, 0, 0, exp_version }, - { "volume", 1, 1, 0, exp_volume }, - { "when", 0, 0, 0, exp_when }, - { "who", 0, 0, 0, exp_who } -}; - -static void expand(cgi_sink *output, - const char *template, - dcgi_state *ds) { - cgi_expand(template, - expansions, sizeof expansions / sizeof *expansions, - output, - ds); -} - -static void expandstring(cgi_sink *output, - const char *string, - dcgi_state *ds) { - cgi_expand_string("", - string, - expansions, sizeof expansions / sizeof *expansions, - output, - ds); -} - -static void perform_action(cgi_sink *output, dcgi_state *ds, - const char *action) { - int n; - - /* We don't ever want anything to be cached */ - cgi_header(output->sink, "Cache-Control", "no-cache"); - if((n = TABLE_FIND(actions, struct action, name, action)) >= 0) - actions[n].handler(output, ds); - else - expand_template(ds, output, action); -} - -void disorder_cgi(cgi_sink *output, dcgi_state *ds) { - const char *action = cgi_get("action"); - - if(!action) { - /* We allow URLs which are just confirm=... in order to keep confirmation - * URLs, which are user-facing, as short as possible. */ - if(cgi_get("c")) - action = "confirm"; - else - action = "playing"; - } - perform_action(output, ds, action); -} - -void disorder_cgi_error(cgi_sink *output, dcgi_state *ds, - const char *msg) { - cgi_set_option("error", msg); - perform_action(output, ds, "error"); -} - -/** @brief Log in as the current user or guest if none */ -void disorder_cgi_login(dcgi_state *ds, cgi_sink *output) { - /* Create a new connection */ - ds->g->client = disorder_new(0); - /* Forget everything we knew */ - ds->g->flags = 0; - /* Reconnect */ - if(disorder_connect_cookie(ds->g->client, login_cookie)) { - disorder_cgi_error(output, ds, "connect"); - exit(0); - } - /* If there was a cookie but it went bad, we forget it */ - if(login_cookie && !strcmp(disorder_user(ds->g->client), "guest")) - login_cookie = 0; -} - /* Local Variables: c-basic-offset:2 diff --git a/server/macros-disorder.c b/server/macros-disorder.c index 9b1f21f..b4fdc29 100644 --- a/server/macros-disorder.c +++ b/server/macros-disorder.c @@ -37,6 +37,9 @@ */ disorder_client *client; +/** @brief For error template */ +char *error_string; + /** @brief Cached data */ static unsigned flags; @@ -142,7 +145,7 @@ static const char *make_index(int i) { return s; } -/* @server-version@ +/* @server-version * * Expands to the server's version string, or a (safe to use) error * value if the server is unavailable or broken. @@ -161,7 +164,7 @@ static int exp_server_version(int attribute((unused)) nargs, return sink_write(output, cgi_sgmlquote(v)) < 0 ? -1 : 0; } -/* @version@ +/* @version * * Expands to the local version string. */ @@ -173,7 +176,7 @@ static int exp_version(int attribute((unused)) nargs, cgi_sgmlquote(disorder_short_version_string)) < 0 ? -1 : 0; } -/* @url@ +/* @url * * Expands to the base URL of the web interface. */ @@ -185,7 +188,7 @@ static int exp_url(int attribute((unused)) nargs, cgi_sgmlquote(config->url)) < 0 ? -1 : 0; } -/* @arg{NAME}@ +/* @arg{NAME} * * Expands to the CGI argument NAME, or the empty string if there is * no such argument. @@ -202,7 +205,7 @@ static int exp_arg(int attribute((unused)) nargs, return 0; } -/* @user@ +/* @user * * Expands to the logged-in username (which might be "guest"), or to * the empty string if not connected. @@ -351,7 +354,7 @@ static int exp_length(int attribute((unused)) nargs, return sink_write(output, " ") < 0 ? -1 : 0; } -/* @removable{ID}@ +/* @removable{ID} * * Expands to "true" if track ID is removable (or scratchable, if it is the * playing track) and "false" otherwise. @@ -371,7 +374,7 @@ static int exp_removable(int attribute((unused)) nargs, (rights, disorder_user(client), q)); } -/* @movable{ID}@ +/* @movable{ID} * * Expands to "true" if track ID is movable and "false" otherwise. */ @@ -512,7 +515,7 @@ static int exp_new(int attribute((unused)) nargs, return 0; } -/* @volume{CHANNEL}@ +/* @volume{CHANNEL} * * Expands to the volume in a given channel. CHANNEL must be "left" or * "right". @@ -527,7 +530,7 @@ static int exp_volume(int attribute((unused)) nargs, ? volume_left : volume_right) < 0 ? -1 : 0; } -/* @isplaying@ +/* @isplaying * * Expands to "true" if there is a playing track, otherwise "false". */ @@ -539,7 +542,7 @@ static int exp_isplaying(int attribute((unused)) nargs, return mx_bool_result(output, !!playing); } -/* @isqueue@ +/* @isqueue * * Expands to "true" if there the queue is nonempty, otherwise "false". */ @@ -564,7 +567,7 @@ static int exp_isrecent(int attribute((unused)) nargs, return mx_bool_result(output, !!recent); } -/* @isnew@ +/* @isnew * * Expands to "true" if there the newly added track list is nonempty, otherwise * "false". @@ -577,7 +580,7 @@ static int exp_isnew(int attribute((unused)) nargs, return mx_bool_result(output, !!nnew); } -/* @pref{TRACK}{KEY}@ +/* @pref{TRACK}{KEY} * * Expands to a track preference. */ @@ -591,7 +594,7 @@ static int exp_pref(int attribute((unused)) nargs, return sink_write(output, cgi_sgmlquote(value)) < 0 ? -1 : 0; } -/* @prefs{TRACK}{TEMPLATE}@ +/* @prefs{TRACK}{TEMPLATE} * * For each track preference of track TRACK, expands TEMPLATE with the * following expansions: @@ -629,7 +632,7 @@ static int exp_prefs(int attribute((unused)) nargs, return 0; } -/* @transform{TRACK}{TYPE}{CONTEXT}@ +/* @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 @@ -659,7 +662,7 @@ static int exp_enabled(int attribute((unused)) nargs, return mx_bool_result(output, enabled); } -/* @random-enabled@ +/* @random-enabled * * Expands to "true" if random play is enabled, otherwise "false". */ @@ -674,7 +677,7 @@ static int exp_enabled(int attribute((unused)) nargs, return mx_bool_result(output, enabled); } -/* @trackstate{TRACK}@ +/* @trackstate{TRACK * * Expands to "playing" if TRACK is currently playing, or "queue" if it is in * the queue, otherwise to nothing. @@ -700,7 +703,7 @@ static int exp_trackstate(int attribute((unused)) nargs, return 0; } -/* @thisurl@ +/* @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 @@ -713,7 +716,7 @@ static int exp_thisurl(int attribute((unused)) nargs, return cgi_thisurl(config->url); } -/* @resolve{TRACK}@ +/* @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. @@ -729,7 +732,7 @@ static int exp_resolve(int attribute((unused)) nargs, return 0; } -/* @paused@ +/* @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). @@ -793,7 +796,7 @@ static int exp_right(int nargs, return 0; } -/* @userinfo{PROPERTY}@ +/* @userinfo{PROPERTY} * * Expands to the named property of the current user. */ @@ -808,10 +811,24 @@ static int exp_userinfo(int attribute((unused)) nargs, return 0; } +/* @error + * + * Expands to the latest error string. + */ +static int exp_error(int attribute((unused)) nargs, + char attribute((unused)) **args, + struct sink *output, + void attribute((unused)) *u) { + return sink_write(output, cgi_sgmlquote(error_string)) < 0 ? -1 : 0; +} + +/* @userinfo{PROPERTY}@ + * /** @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_error, 0, 0, "error"); mx_register(exp_isnew, 0, 0, "isnew"); mx_register(exp_isplaying, 0, 0, "isplaying"); mx_register(exp_isqueue, 0, 0, "isplaying"); @@ -844,6 +861,16 @@ void register_disorder_expansions(void) { mx_register_magic(exp_recent, 1, 1, "recent"); } +void disorder_macros_reset(void) { + /* Junk the old connection if there is one */ + if(client) + disorder_close(client); + /* Create a new connection */ + client = disorder_new(0); + /* Forget everything we knew */ + flags = 0; +} + /* Local Variables: c-basic-offset:2 diff --git a/server/macros-disorder.h b/server/macros-disorder.h index bb8950f..0556e0d 100644 --- a/server/macros-disorder.h +++ b/server/macros-disorder.h @@ -1,3 +1,4 @@ + /* * This file is part of DisOrder. * Copyright (C) 2008 Richard Kettlewell @@ -25,6 +26,7 @@ #define MACROS_DISORDER_H extern disorder_client *client; +extern char *error_string; void register_disorder_expansions(void); #endif /* MACROS_DISORDER_H */ -- [mdw]