X-Git-Url: https://git.distorted.org.uk/~mdw/disorder/blobdiff_plain/d22b8a59995829bc14b4490b9bddd95346e0585d..9d61799376c6d4a51b5386ddb432693ad31129a6:/server/actions.c diff --git a/server/actions.c b/server/actions.c index 4754a8c..2468b77 100644 --- a/server/actions.c +++ b/server/actions.c @@ -33,7 +33,7 @@ static void redirect(const char *url) { /* By default use the 'back' argument */ if(!url) url = cgi_get("back"); - if(url) { + if(url && *url) { if(strncmp(url, "http", 4)) /* If the target is not a full URL assume it's the action */ url = cgi_makeurl(config->url, "action", url, (char *)0); @@ -47,7 +47,20 @@ static void redirect(const char *url) { fatal(errno, "error writing to stdout"); } -/* 'playing' and 'manage' just add a Refresh: header */ +/*! playing + * + * Expands \fIplaying.tmpl\fR as if there was no special 'playing' action, but + * adds a Refresh: field to the HTTP header. The maximum refresh interval is + * defined by \fBrefresh\fR (see \fBdisorder_config\fR(5)) but may be less if + * the end of the track is near. + */ +/*! manage + * + * Expands \fIplaying.tmpl\fR (NB not \fImanage.tmpl\fR) as if there was no + * special 'playing' action, and adds a Refresh: field to the HTTP header. The + * maximum refresh interval is defined by \Bfrefresh\fR (see + * \fBdisorder_config\fR(5)) but may be less if the end of the track is near. + */ static void act_playing(void) { long refresh = config->refresh; long length; @@ -92,42 +105,71 @@ static void act_playing(void) { dcgi_expand("playing", 1); } +/*! disable + * + * Disables play. + */ static void act_disable(void) { if(dcgi_client) disorder_disable(dcgi_client); redirect(0); } +/*! enable + * + * Enables play. + */ static void act_enable(void) { if(dcgi_client) disorder_enable(dcgi_client); redirect(0); } +/*! random-disable + * + * Disables random play. + */ static void act_random_disable(void) { if(dcgi_client) disorder_random_disable(dcgi_client); redirect(0); } +/*! random-enable + * + * Enables random play. + */ static void act_random_enable(void) { if(dcgi_client) disorder_random_enable(dcgi_client); redirect(0); } +/*! pause + * + * Pauses the current track (if there is one and it's not paused already). + */ static void act_pause(void) { if(dcgi_client) disorder_pause(dcgi_client); redirect(0); } +/*! resume + * + * Resumes the current track (if there is one and it's paused). + */ static void act_resume(void) { if(dcgi_client) disorder_resume(dcgi_client); redirect(0); } +/*! remove + * + * Removes the track given by the \fBid\fR argument. If this is the currently + * playing track then it is scratched. + */ static void act_remove(void) { const char *id; struct queue_entry *q; @@ -159,6 +201,12 @@ static void act_remove(void) { redirect(0); } +/*! move + * + * Moves the track given by the \fBid\fR argument the distance given by the + * \fBdelta\fR argument. If this is positive the track is moved earlier in the + * queue and if negative, later. + */ static void act_move(void) { const char *id, *delta; struct queue_entry *q; @@ -183,6 +231,11 @@ static void act_move(void) { redirect(0); } +/*! play + * + * Play the track given by the \fBtrack\fR argument, or if that is not set all + * the tracks in the directory given by the \fBdir\fR argument. + */ static void act_play(void) { const char *track, *dir; char **tracks; @@ -190,7 +243,7 @@ static void act_play(void) { struct dcgi_entry *e; if(dcgi_client) { - if((track = cgi_get("file"))) { + if((track = cgi_get("track"))) { disorder_play(dcgi_client, track); } else if((dir = cgi_get("dir"))) { if(disorder_files(dcgi_client, dir, 0, &tracks, &ntracks)) @@ -217,6 +270,14 @@ static int clamp(int n, int min, int max) { return n; } +/*! volume + * + * If the \fBdelta\fR argument is set: adjust both channels by that amount (up + * if positive, down if negative). + * + * Otherwise if \fBleft\fR and \fBright\fR are set, set the channels + * independently to those values. + */ static void act_volume(void) { const char *l, *r, *d; int nd; @@ -235,10 +296,10 @@ static void act_volume(void) { } /** @brief Expand the login template with @b @@error set to @p error - * @param error Error keyword + * @param e Error keyword */ -static void login_error(const char *error) { - dcgi_error_string = error; +static void login_error(const char *e) { + dcgi_error_string = e; dcgi_expand("login", 1); } @@ -271,6 +332,15 @@ static int login_as(const char *username, const char *password) { return 0; /* OK */ } +/*! login + * + * If \fBusername\fR and \fBpassword\fR are set (and the username isn't + * "guest") then attempt to log in using those credentials. On success, + * redirects to the \fBback\fR argument if that is set, or just expands + * \fIlogin.tmpl\fI otherwise, with \fB@status\fR set to \fBloginok\fR. + * + * If they aren't set then just expands \fIlogin.tmpl\fI. + */ static void act_login(void) { const char *username, *password; @@ -289,10 +359,19 @@ static void act_login(void) { if(!login_as(username, password)) { /* Report the succesful login */ dcgi_status_string = "loginok"; - dcgi_expand("login", 1); + /* Redirect back to where we came from, if necessary */ + if(cgi_get("back")) + redirect(0); + else + dcgi_expand("login", 1); } } +/*! logout + * + * Logs out the current user and expands \fIlogin.tmpl\fR with \fBstatus\fR or + * \fB@error\fR set according to the result. + */ static void act_logout(void) { if(dcgi_client) { /* Ask the server to revoke the cookie */ @@ -313,6 +392,12 @@ static void act_logout(void) { dcgi_expand("login", 1); } +/*! register + * + * Register a new user using \fBusername\fR, \fBpassword1\fR, \fBpassword2\fR + * and \fBemail\fR and expands \fIlogin.tmpl\fR with \fBstatus\fR or + * \fB@error\fR set according to the result. + */ static void act_register(void) { const char *username, *password, *password2, *email; char *confirm, *content_type; @@ -374,6 +459,12 @@ static void act_register(void) { dcgi_expand("login", 1); } +/*! confirm + * + * Confirm a user registration using the nonce supplied in \fBc\fR and expands + * \fIlogin.tmpl\fR with \fBstatus\fR or \fB@error\fR set according to the + * result. + */ static void act_confirm(void) { const char *confirmation; @@ -404,6 +495,12 @@ static void act_confirm(void) { dcgi_expand("login", 1); } +/*! edituser + * + * Edit user details using \fBusername\fR, \fBchangepassword1\fR, + * \fBchangepassword2\fR and \fBemail\fR and expands \fIlogin.tmpl\fR with + * \fBstatus\fR or \fB@error\fR set according to the result. + */ static void act_edituser(void) { const char *email = cgi_get("email"), *password = cgi_get("changepassword1"); const char *password2 = cgi_get("changepassword2"); @@ -459,6 +556,12 @@ static void act_edituser(void) { dcgi_expand("login", 1); } +/*! reminder + * + * Issue an email password reminder to \fBusername\fR and expands + * \fIlogin.tmpl\fR with \fBstatus\fR or \fB@error\fR set according to the + * result. + */ static void act_reminder(void) { const char *const username = cgi_get("username"); @@ -481,31 +584,122 @@ static void act_reminder(void) { dcgi_expand("login", 1); } +/** @brief Get the numbered version of an argument + * @param argname Base argument name + * @param numfile File number + * @return cgi_get(NUMFILE_ARGNAME) + */ +static const char *numbered_arg(const char *argname, int numfile) { + char *fullname; + + byte_xasprintf(&fullname, "%d_%s", numfile, argname); + return cgi_get(fullname); +} + +/** @brief Set preferences for file @p numfile + * @return 0 on success, -1 if there is no such track number + * + * The old @b nfiles parameter has been abolished, we just keep look for more + * files until we run out. + */ +static int process_prefs(int numfile) { + const char *file, *name, *value, *part, *parts, *context; + char **partslist; + + if(!(file = numbered_arg("track", numfile))) + return -1; + if(!(parts = cgi_get("parts"))) + parts = "artist album title"; + if(!(context = cgi_get("context"))) + context = "display"; + partslist = split(parts, 0, 0, 0, 0); + while((part = *partslist++)) { + if(!(value = numbered_arg(part, numfile))) + continue; + byte_xasprintf((char **)&name, "trackname_%s_%s", context, part); + disorder_set(dcgi_client, file, name, value); + } + if((value = numbered_arg("random", numfile))) + disorder_unset(dcgi_client, file, "pick_at_random"); + else + disorder_set(dcgi_client, file, "pick_at_random", "0"); + if((value = numbered_arg("tags", numfile))) { + if(!*value) + disorder_unset(dcgi_client, file, "tags"); + else + disorder_set(dcgi_client, file, "tags", value); + } + if((value = numbered_arg("weight", numfile))) { + if(!*value) + disorder_unset(dcgi_client, file, "weight"); + else + disorder_set(dcgi_client, file, "weight", value); + } + return 0; +} + +/*! prefs + * + * Set preferences on a number of tracks. + * + * The tracks to modify are specified in arguments \fB0_track\fR, \fB1_track\fR + * etc. The number sequence must be contiguous and start from 0. + * + * For each track \fIINDEX\fB_track\fR: + * - \fIINDEX\fB_\fIPART\fR is used to set the trackname preference for + * that part. (See \fBparts\fR below.) + * - \fIINDEX\fB_\fIrandom\fR if present enables random play for this track + * or disables it if absent. + * - \fIINDEX\fB_\fItags\fR sets the list of tags for this track. + * - \fIINDEX\fB_\fIweight\fR sets the weight for this track. + * + * \fBparts\fR can be set to the track name parts to modify. The default is + * "artist album title". + * + * \fBcontext\fR can be set to the context to modify. The default is + * "display". + * + * If the server detects a preference being set to its default, it removes the + * preference, thus keeping the database tidy. + */ +static void act_set(void) { + int numfile; + + if(dcgi_client) { + for(numfile = 0; !process_prefs(numfile); ++numfile) + ; + } + redirect(0); +} + /** @brief Table of actions */ static const struct action { /** @brief Action name */ const char *name; /** @brief Action handler */ void (*handler)(void); + /** @brief Union of suitable rights */ + rights_type rights; } actions[] = { - { "confirm", act_confirm }, - { "disable", act_disable }, - { "edituser", act_edituser }, - { "enable", act_enable }, - { "login", act_login }, - { "logout", act_logout }, - { "manage", act_playing }, - { "move", act_move }, - { "pause", act_pause }, - { "play", act_play }, - { "playing", act_playing }, - { "randomdisable", act_random_disable }, - { "randomenable", act_random_enable }, - { "register", act_register }, - { "reminder", act_reminder }, - { "remove", act_remove }, - { "resume", act_resume }, - { "volume", act_volume }, + { "confirm", act_confirm, 0 }, + { "disable", act_disable, RIGHT_GLOBAL_PREFS }, + { "edituser", act_edituser, 0 }, + { "enable", act_enable, RIGHT_GLOBAL_PREFS }, + { "login", act_login, 0 }, + { "logout", act_logout, 0 }, + { "manage", act_playing, 0 }, + { "move", act_move, RIGHT_MOVE__MASK }, + { "pause", act_pause, RIGHT_PAUSE }, + { "play", act_play, RIGHT_PLAY }, + { "playing", act_playing, 0 }, + { "randomdisable", act_random_disable, RIGHT_GLOBAL_PREFS }, + { "randomenable", act_random_enable, RIGHT_GLOBAL_PREFS }, + { "register", act_register, 0 }, + { "reminder", act_reminder, 0 }, + { "remove", act_remove, RIGHT_MOVE__MASK|RIGHT_SCRATCH__MASK }, + { "resume", act_resume, RIGHT_PAUSE }, + { "set", act_set, RIGHT_PREFS }, + { "volume", act_volume, RIGHT_VOLUME }, }; /** @brief Check that an action name is valid @@ -537,13 +731,15 @@ void dcgi_expand(const char *name, int header) { const char *p, *found; /* Parse macros first */ - if((found = mx_find("macros.tmpl"))) + if((found = mx_find("macros.tmpl", 1/*report*/))) + mx_expand_file(found, sink_discard(), 0); + if((found = mx_find("user.tmpl", 0/*report*/))) mx_expand_file(found, sink_discard(), 0); /* For unknown actions check that they aren't evil */ if(!dcgi_valid_action(name)) fatal(0, "invalid action name '%s'", name); byte_xasprintf((char **)&p, "%s.tmpl", name); - if(!(found = mx_find(p))) + if(!(found = mx_find(p, 0/*report*/))) fatal(errno, "cannot find %s", p); if(header) { if(printf("Content-Type: text/html\n" @@ -579,10 +775,24 @@ void dcgi_action(const char *action) { /* Make sure 'action' is always set */ cgi_set("action", action); } - if((n = TABLE_FIND(actions, struct action, name, action)) >= 0) - /* Its a known action */ + if((n = TABLE_FIND(actions, struct action, name, action)) >= 0) { + if(actions[n].rights) { + /* Some right or other is required */ + dcgi_lookup(DCGI_RIGHTS); + if(!(actions[n].rights & dcgi_rights)) { + const char *back = cgi_thisurl(config->url); + /* Failed operations jump you to the login screen with an error + * message. On success, the user comes back to the page they were + * after. */ + cgi_clear(); + cgi_set("back", back); + login_error("noright"); + return; + } + } + /* It's a known action */ actions[n].handler(); - else { + } else { /* Just expand the template */ dcgi_expand(action, 1/*header*/); }