.B @length@
Expands to the length of the current track.
.TP
+.B @movable@
+Expands to \fBtrue\fR if the current track is movable, otherwise to
+\fBfalse\fR.
+.TP
.B @navigate{\fIDIRECTORY\fB}{\fITEMPLATE\fB}
Expands \fITEMPLATE\fR for each component of \fIDIRECTORY\fR in turn.
Use \fB@dirname\fR and \fB@basename@\fR to get the components of the path to
Expands \fITEMPLATE\fR repeatedly using the each recently played track in turn
as the current track. The most recently played track comes first.
.TP
+.B @removable@
+Expands to \fBtrue\fR if the current track is removable, otherwise to
+\fBfalse\fR.
+.TP
.B @resolve{\fITRACK\fB}@
Resolve aliases for \fITRACK\fR and expands to the result.
.TP
+.B @right{\fIRIGHT\fB}@
+Exapnds to \fBtrue\fR if the user has right \fIRIGHT\fR, otherwise to
+\fBfalse\fR.
+.TP
+.B @right{\fIRIGHT\fB}{\fITRUEPART\fB}{\fIFALSEPART\fB}@
+Expands to \fITRUEPART\fR if the user right \fIRIGHT\fR, otherwise to
+\fIFALSEPART\fR (which may be omitted).
+.TP
+.B @scratchable@
+Expands to \fBtrue\fR if the currently playing track is scratchable, otherwise
+to \fBfalse\fR.
+.TP
.B @search{\fIPART\fB}\fR[\fB{\fICONTEXT\fB}\fR]\fB{\fITEMPLATE\fB}@
Expands \fITEMPLATE\fR once for each group of search results that have
a common value of track part \fIPART\fR.
asprintf.c fprintf.c snprintf.c \
queue.c queue.h \
regsub.c regsub.h \
- rights.c rights.h \
+ rights.c queue-rights.c rights.h \
rtp.h \
selection.c selection.h \
signame.c signame.h \
--- /dev/null
+/*
+ * This file is part of DisOrder.
+ * Copyright (C) 2007 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 lib/queue-rights.c
+ * @brief Various rights-checking operations
+ */
+
+#include <config.h>
+#include "types.h"
+
+#include <string.h>
+
+#include "queue.h"
+#include "rights.h"
+
+/** @brief Test for scratchability
+ * @param rights User rights
+ * @param who Username
+ * @param q Queue entry or NULL
+ * @return non-0 if scratchable, else 0
+ */
+int right_scratchable(rights_type rights, const char *who,
+ const struct queue_entry *q) {
+ rights_type r;
+
+ if(!q)
+ return 0;
+ if(q->submitter)
+ if(!strcmp(q->submitter, who))
+ r = RIGHT_SCRATCH_MINE|RIGHT_SCRATCH_ANY;
+ else
+ r = RIGHT_SCRATCH_ANY;
+ else
+ r = RIGHT_SCRATCH_RANDOM|RIGHT_SCRATCH_ANY;
+ return !!(rights & r);
+}
+
+/** @brief Test for movability
+ * @param rights User rights
+ * @param who Username
+ * @param q Queue entry or NULL
+ * @return non-0 if movable, else 0
+ */
+int right_movable(rights_type rights, const char *who,
+ const struct queue_entry *q) {
+ rights_type r;
+
+ if(!q)
+ return 0;
+ if(q->submitter)
+ if(!strcmp(q->submitter, who))
+ r = RIGHT_MOVE_MINE|RIGHT_MOVE_ANY;
+ else
+ r = RIGHT_MOVE_ANY;
+ else
+ r = RIGHT_MOVE_RANDOM|RIGHT_MOVE_ANY;
+ return !!(rights & r);
+}
+
+/** @brief Test for removability
+ * @param rights User rights
+ * @param who Username
+ * @param q Queue entry or NULL
+ * @return non-0 if removable, else 0
+ */
+int right_removable(rights_type rights, const char *who,
+ const struct queue_entry *q) {
+ rights_type r;
+
+ if(!q)
+ return 0;
+ if(q->submitter)
+ if(!strcmp(q->submitter, who))
+ r = RIGHT_REMOVE_MINE|RIGHT_REMOVE_ANY;
+ else
+ r = RIGHT_REMOVE_ANY;
+ else
+ r = RIGHT_REMOVE_RANDOM|RIGHT_REMOVE_ANY;
+ return !!(rights & r);
+}
+
+/*
+Local Variables:
+c-basic-offset:2
+comment-column:40
+fill-column:79
+indent-tabs-mode:nil
+End:
+*/
#ifndef RIGHTS_H
#define RIGHTS_H
+struct queue_entry;
+
/** @brief User can perform read-only operations */
#define RIGHT_READ 0x00000001
char *rights_string(rights_type r);
int parse_rights(const char *s, rights_type *rp, int report);
+int right_scratchable(rights_type rights, const char *who,
+ const struct queue_entry *q);
+int right_movable(rights_type rights, const char *who,
+ const struct queue_entry *q);
+int right_removable(rights_type rights, const char *who,
+ const struct queue_entry *q);
#endif /* RIGHTS_H */
#include "client.h"
#include "sink.h"
#include "cgi.h"
-#include "dcgi.h"
#include "mem.h"
#include "log.h"
#include "configuration.h"
#include "api-client.h"
#include "mime.h"
#include "printf.h"
+#include "dcgi.h"
/** @brief Infer the base URL for the web interface if it's not set
*
login_cookie = cd.cookies[n].value;
}
}
- /* Log in with the cookie if possible otherwise as guest */
- if(disorder_connect_cookie(g.client, login_cookie)) {
- disorder_cgi_error(&output, &s, "connect");
- return 0;
- }
- /* If there was a cookie but it went bad, we forget it */
- if(login_cookie && !strcmp(disorder_user(g.client), "guest"))
- login_cookie = 0;
+ disorder_cgi_login(&s, &output);
/* TODO RFC 3875 s8.2 recommendations e.g. concerning PATH_INFO */
disorder_cgi(&output, &s);
if(fclose(stdout) < 0) fatal(errno, "error closing stdout");
#include "vector.h"
#include "sink.h"
#include "cgi.h"
-#include "dcgi.h"
#include "log.h"
#include "configuration.h"
#include "table.h"
#include "defs.h"
#include "trackname.h"
#include "charset.h"
+#include "dcgi.h"
char *login_cookie;
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)
&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;
}
}
disorder_revoke(ds->g->client);
login_cookie = 0;
/* Reconnect as guest */
- ds->g->client = disorder_new(0);
- if(disorder_connect_cookie(ds->g->client, 0)) {
- disorder_cgi_error(output, ds, "connect");
- exit(0);
- }
+ disorder_cgi_login(ds, output);
/* Back to the login page */
expand_template(ds, output, "login");
}
cgi_sink *output,
void attribute((unused)) *u) {
dcgi_state *ds = u;
- int result;
-
- if(config->restrictions & RESTRICT_SCRATCH) {
- lookups(ds, DC_PLAYING);
- result = (ds->g->playing
- && (!ds->g->playing->submitter
- || !strcmp(ds->g->playing->submitter,
- disorder_user(ds->g->client))));
- } else
- result = 1;
- sink_printf(output->sink, "%s", bool2str(result));
+
+ 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,
cgi_sink *output,
void attribute((unused)) *u) {
dcgi_state *ds = u;
- int result;
- if(config->restrictions & RESTRICT_REMOVE)
- result = (ds->track
- && ds->track->submitter
- && !strcmp(ds->track->submitter,
- disorder_user(ds->g->client)));
- else
- result = 1;
- sink_printf(output->sink, "%s", bool2str(result));
+ 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,
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 const struct cgi_expansion expansions[] = {
{ "#", 0, INT_MAX, EXP_MAGIC, exp_comment },
{ "action", 0, 0, 0, exp_action },
{ "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 },
{ "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 },
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
#define DC_DIRS 0x0010
#define DC_FILES 0x0020
#define DC_NEW 0x0040
+#define DC_RIGHTS 0x0080
struct queue_entry *queue, *playing, *recent;
int volume_left, volume_right;
char **files, **dirs;
int nfiles, ndirs;
char **new;
int nnew;
+ rights_type rights;
} dcgi_global;
typedef struct dcgi_state {
void disorder_cgi(cgi_sink *output, dcgi_state *ds);
void disorder_cgi_error(cgi_sink *output, dcgi_state *ds,
const char *msg);
+void disorder_cgi_login(dcgi_state *ds, cgi_sink *output);
extern char *login_cookie;
static int c_remove(struct conn *c, char **vec,
int attribute((unused)) nvec) {
struct queue_entry *q;
- rights_type r;
if(!(q = queue_find(vec[0]))) {
sink_writes(ev_writer_sink(c->w), "550 no such track on the queue\n");
return 1;
}
- if(q->submitter)
- if(!strcmp(q->submitter, c->who))
- r = RIGHT_REMOVE_MINE|RIGHT_REMOVE_ANY;
- else
- r = RIGHT_REMOVE_ANY;
- else
- r = RIGHT_REMOVE_RANDOM|RIGHT_REMOVE_ANY;
- if(!(c->rights & r)) {
+ if(!right_removable(c->rights, c->who, q)) {
error(0, "%s attempted remove but lacks required rights", c->who);
sink_writes(ev_writer_sink(c->w),
"510 Not authorized to remove that track\n");
static int c_scratch(struct conn *c,
char **vec,
int nvec) {
- rights_type r;
-
if(!playing) {
sink_writes(ev_writer_sink(c->w), "250 nothing is playing\n");
return 1; /* completed */
/* TODO there is a bug here: if we specify an ID but it's not the currently
* playing track then you will get 550 if you weren't authorized to scratch
* the currently playing track. */
- if(playing->submitter)
- if(!strcmp(playing->submitter, c->who))
- r = RIGHT_SCRATCH_MINE|RIGHT_SCRATCH_ANY;
- else
- r = RIGHT_SCRATCH_ANY;
- else
- r = RIGHT_SCRATCH_RANDOM|RIGHT_SCRATCH_ANY;
- if(!(c->rights & r)) {
+ if(!right_scratchable(c->rights, c->who, playing)) {
error(0, "%s attempted scratch but lacks required rights", c->who);
sink_writes(ev_writer_sink(c->w),
"510 Not authorized to scratch that track\n");
* @return 0 if move is prohibited, non-0 if it is allowed
*/
static int has_move_rights(struct conn *c, struct queue_entry **qs, int nqs) {
- rights_type r = 0;
-
for(; nqs > 0; ++qs, --nqs) {
struct queue_entry *const q = *qs;
- if(q->submitter)
- if(!strcmp(q->submitter, c->who))
- r |= RIGHT_MOVE_MINE|RIGHT_MOVE_ANY;
- else
- r |= RIGHT_MOVE_ANY;
- else
- r |= RIGHT_MOVE_RANDOM|RIGHT_MOVE_ANY;
+ if(!right_movable(c->rights, c->who, q))
+ return 0;
}
- return !!(c->rights & r);
+ return 1;
}
static int c_move(struct conn *c,
href="@url@?action=remove&nonce=@nonce@&id=@id@&mgmt=@arg:mgmt@"><img
class=button src="@label:images.scratch@"
title="@label:playing.removeverbose@"
- alt="@label:playing.remove@"></a>}{ }@</td>
+ alt="@label:playing.remove@"></a>}{<img
+ class=button src="@label:images.noscratch@"
+ title="@label:playing.removeverbose@"
+ alt="@label:playing.remove@">}@</td>
@if{@arg:mgmt@}{
@if{@isfirst@}
{<td class=imgbutton>