2 * This file is part of DisOrder.
3 * Copyright (C) 2004, 2005, 2006, 2007 Richard Kettlewell
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
26 #include <sys/types.h>
27 #include <sys/socket.h>
44 #include "configuration.h"
55 #include "trackname.h"
60 static void expand(cgi_sink
*output
,
63 static void expandstring(cgi_sink
*output
,
73 static const char *nonce(void) {
74 static unsigned long count
;
77 byte_xasprintf(&s
, "%lx%lx%lx",
78 (unsigned long)time(0),
79 (unsigned long)getpid(),
84 static int compare_entry(const void *a
, const void *b
) {
85 const struct entry
*ea
= a
, *eb
= b
;
87 return compare_tracks(ea
->sort
, eb
->sort
,
88 ea
->display
, eb
->display
,
92 static const char *front_url(void) {
96 /* preserve management interface visibility */
97 if((mgmt
= cgi_get("mgmt")) && !strcmp(mgmt
, "true")) {
98 byte_xasprintf(&url
, "%s?mgmt=true", config
->url
);
104 static void redirect(struct sink
*output
) {
107 cgi_header(output
, "Location",
108 (back
= cgi_get("back")) ? back
: front_url());
112 static void header_cookie(cgi_sink
*output
) {
118 for(s
= login_cookie
; *s
; ++s
) {
120 dynstr_append(d
, '\\');
121 dynstr_append(d
, *s
);
124 byte_xasprintf(&s
, "disorder=\"%s\"", d
->vec
); /* TODO domain, path, expiry */
125 cgi_header(output
->sink
, "Set-Cookie", s
);
129 static void expand_template(dcgi_state
*ds
, cgi_sink
*output
,
130 const char *action
) {
131 cgi_header(output
->sink
, "Content-Type", "text/html");
132 cgi_body(output
->sink
);
133 expand(output
, action
, ds
);
136 static void lookups(dcgi_state
*ds
, unsigned want
) {
138 struct queue_entry
*r
, *rnext
;
139 const char *dir
, *re
;
141 if(ds
->g
->client
&& (need
= want
^ (ds
->g
->flags
& want
)) != 0) {
143 disorder_queue(ds
->g
->client
, &ds
->g
->queue
);
144 if(need
& DC_PLAYING
)
145 disorder_playing(ds
->g
->client
, &ds
->g
->playing
);
147 disorder_new_tracks(ds
->g
->client
, &ds
->g
->new, &ds
->g
->nnew
, 0);
148 if(need
& DC_RECENT
) {
149 /* we need to reverse the order of the list */
150 disorder_recent(ds
->g
->client
, &r
);
153 r
->next
= ds
->g
->recent
;
159 disorder_get_volume(ds
->g
->client
,
160 &ds
->g
->volume_left
, &ds
->g
->volume_right
);
161 if(need
& (DC_FILES
|DC_DIRS
)) {
162 if(!(dir
= cgi_get("directory")))
164 re
= cgi_get("regexp");
166 if(disorder_directories(ds
->g
->client
, dir
, re
,
167 &ds
->g
->dirs
, &ds
->g
->ndirs
))
170 if(disorder_files(ds
->g
->client
, dir
, re
,
171 &ds
->g
->files
, &ds
->g
->nfiles
))
174 ds
->g
->flags
|= need
;
178 /* actions ********************************************************************/
180 static void act_disable(cgi_sink
*output
,
183 disorder_disable(ds
->g
->client
);
184 redirect(output
->sink
);
187 static void act_enable(cgi_sink
*output
,
190 disorder_enable(ds
->g
->client
);
191 redirect(output
->sink
);
194 static void act_random_disable(cgi_sink
*output
,
197 disorder_random_disable(ds
->g
->client
);
198 redirect(output
->sink
);
201 static void act_random_enable(cgi_sink
*output
,
204 disorder_random_enable(ds
->g
->client
);
205 redirect(output
->sink
);
208 static void act_remove(cgi_sink
*output
,
212 if(!(id
= cgi_get("id"))) fatal(0, "missing id argument");
214 disorder_remove(ds
->g
->client
, id
);
215 redirect(output
->sink
);
218 static void act_move(cgi_sink
*output
,
220 const char *id
, *delta
;
222 if(!(id
= cgi_get("id"))) fatal(0, "missing id argument");
223 if(!(delta
= cgi_get("delta"))) fatal(0, "missing delta argument");
225 disorder_move(ds
->g
->client
, id
, atoi(delta
));
226 redirect(output
->sink
);
229 static void act_scratch(cgi_sink
*output
,
232 disorder_scratch(ds
->g
->client
, cgi_get("id"));
233 redirect(output
->sink
);
236 static void act_playing(cgi_sink
*output
, dcgi_state
*ds
) {
238 long refresh
= config
->refresh
, length
;
240 int random_enabled
= 0;
243 lookups(ds
, DC_PLAYING
|DC_QUEUE
);
244 cgi_header(output
->sink
, "Content-Type", "text/html");
245 disorder_random_enabled(ds
->g
->client
, &random_enabled
);
246 disorder_enabled(ds
->g
->client
, &enabled
);
248 && ds
->g
->playing
->state
== playing_started
/* i.e. not paused */
249 && !disorder_length(ds
->g
->client
, ds
->g
->playing
->track
, &length
)
251 && ds
->g
->playing
->sofar
>= 0) {
252 /* Try to put the next refresh at the start of the next track. */
254 fin
= now
+ length
- ds
->g
->playing
->sofar
+ config
->gap
;
255 if(now
+ refresh
> fin
)
258 if(ds
->g
->queue
&& ds
->g
->queue
->state
== playing_isscratch
) {
259 /* next track is a scratch, don't leave more than the inter-track gap */
260 if(refresh
> config
->gap
)
261 refresh
= config
->gap
;
263 if(!ds
->g
->playing
&& ((ds
->g
->queue
264 && ds
->g
->queue
->state
!= playing_random
)
265 || random_enabled
) && enabled
) {
266 /* no track playing but playing is enabled and there is something coming
267 * up, must be in a gap */
268 if(refresh
> config
->gap
)
269 refresh
= config
->gap
;
271 byte_snprintf(r
, sizeof r
, "%ld;url=%s", refresh
> 0 ? refresh
: 1,
273 cgi_header(output
->sink
, "Refresh", r
);
274 cgi_body(output
->sink
);
275 expand(output
, "playing", ds
);
278 static void act_play(cgi_sink
*output
,
280 const char *track
, *dir
;
285 if((track
= cgi_get("file"))) {
286 disorder_play(ds
->g
->client
, track
);
287 } else if((dir
= cgi_get("directory"))) {
288 if(disorder_files(ds
->g
->client
, dir
, 0, &tracks
, &ntracks
)) ntracks
= 0;
290 e
= xmalloc(ntracks
* sizeof (struct entry
));
291 for(n
= 0; n
< ntracks
; ++n
) {
292 e
[n
].path
= tracks
[n
];
293 e
[n
].sort
= trackname_transform("track", tracks
[n
], "sort");
294 e
[n
].display
= trackname_transform("track", tracks
[n
], "display");
296 qsort(e
, ntracks
, sizeof (struct entry
), compare_entry
);
297 for(n
= 0; n
< ntracks
; ++n
)
298 disorder_play(ds
->g
->client
, e
[n
].path
);
301 /* XXX error handling */
302 redirect(output
->sink
);
305 static int clamp(int n
, int min
, int max
) {
313 static const char *volume_url(void) {
316 byte_xasprintf(&url
, "%s?action=volume", config
->url
);
320 static void act_volume(cgi_sink
*output
, dcgi_state
*ds
) {
321 const char *l
, *r
, *d
, *back
;
322 int nd
, changed
= 0;;
324 if((d
= cgi_get("delta"))) {
325 lookups(ds
, DC_VOLUME
);
326 nd
= clamp(atoi(d
), -255, 255);
327 disorder_set_volume(ds
->g
->client
,
328 clamp(ds
->g
->volume_left
+ nd
, 0, 255),
329 clamp(ds
->g
->volume_right
+ nd
, 0, 255));
331 } else if((l
= cgi_get("left")) && (r
= cgi_get("right"))) {
332 disorder_set_volume(ds
->g
->client
, atoi(l
), atoi(r
));
336 /* redirect back to ourselves (but without the volume-changing bits in the
338 cgi_header(output
->sink
, "Location",
339 (back
= cgi_get("back")) ? back
: volume_url());
340 cgi_body(output
->sink
);
342 cgi_header(output
->sink
, "Content-Type", "text/html");
343 cgi_body(output
->sink
);
344 expand(output
, "volume", ds
);
348 static void act_prefs_errors(const char *msg
,
349 void attribute((unused
)) *u
) {
350 fatal(0, "error splitting parts list: %s", msg
);
353 static const char *numbered_arg(const char *argname
, int numfile
) {
356 byte_xasprintf(&fullname
, "%d_%s", numfile
, argname
);
357 return cgi_get(fullname
);
360 static void process_prefs(dcgi_state
*ds
, int numfile
) {
361 const char *file
, *name
, *value
, *part
, *parts
, *current
, *context
;
364 if(!(file
= numbered_arg("file", numfile
)))
365 /* The first file doesn't need numbering. */
366 if(numfile
> 0 || !(file
= cgi_get("file")))
368 if((parts
= numbered_arg("parts", numfile
))
369 || (parts
= cgi_get("parts"))) {
370 /* Default context is display. Other contexts not actually tested. */
371 if(!(context
= numbered_arg("context", numfile
))) context
= "display";
372 partslist
= split(parts
, 0, 0, act_prefs_errors
, 0);
373 while((part
= *partslist
++)) {
374 if(!(value
= numbered_arg(part
, numfile
)))
376 /* If it's already right (whether regexps or db) don't change anything,
377 * so we don't fill the database up with rubbish. */
378 if(disorder_part(ds
->g
->client
, (char **)¤t
,
379 file
, context
, part
))
380 fatal(0, "disorder_part() failed");
381 if(!strcmp(current
, value
))
383 byte_xasprintf((char **)&name
, "trackname_%s_%s", context
, part
);
384 disorder_set(ds
->g
->client
, file
, name
, value
);
386 if((value
= numbered_arg("random", numfile
)))
387 disorder_unset(ds
->g
->client
, file
, "pick_at_random");
389 disorder_set(ds
->g
->client
, file
, "pick_at_random", "0");
390 if((value
= numbered_arg("tags", numfile
)))
391 disorder_set(ds
->g
->client
, file
, "tags", value
);
392 } else if((name
= cgi_get("name"))) {
393 /* Raw preferences. Not well supported in the templates at the moment. */
394 value
= cgi_get("value");
396 disorder_set(ds
->g
->client
, file
, name
, value
);
398 disorder_unset(ds
->g
->client
, file
, name
);
402 static void act_prefs(cgi_sink
*output
, dcgi_state
*ds
) {
406 if((files
= cgi_get("files"))) nfiles
= atoi(files
);
408 for(numfile
= 0; numfile
< nfiles
; ++numfile
)
409 process_prefs(ds
, numfile
);
410 cgi_header(output
->sink
, "Content-Type", "text/html");
411 cgi_body(output
->sink
);
412 expand(output
, "prefs", ds
);
415 static void act_pause(cgi_sink
*output
,
418 disorder_pause(ds
->g
->client
);
419 redirect(output
->sink
);
422 static void act_resume(cgi_sink
*output
,
425 disorder_resume(ds
->g
->client
);
426 redirect(output
->sink
);
429 static void act_login(cgi_sink
*output
,
431 const char *username
, *password
, *back
;
434 username
= cgi_get("username");
435 password
= cgi_get("password");
436 if(!username
|| !password
437 || !strcmp(username
, "guest")/*bodge to avoid guest cookies*/) {
438 /* We're just visiting the login page */
439 expand_template(ds
, output
, "login");
443 if(disorder_connect_user(c
, username
, password
)) {
444 cgi_set_option("error", "loginfailed");
445 expand_template(ds
, output
, "login");
448 if(disorder_make_cookie(c
, &login_cookie
)) {
449 cgi_set_option("error", "cookiefailed");
450 expand_template(ds
, output
, "login");
453 /* We have a new cookie */
454 header_cookie(output
);
455 if((back
= cgi_get("back")) && back
)
456 /* Redirect back to somewhere or other */
457 redirect(output
->sink
);
459 /* Stick to the login page */
460 expand_template(ds
, output
, "login");
463 static void act_register(cgi_sink
*output
,
465 const char *username
, *password
, *email
;
468 username
= cgi_get("username");
469 password
= cgi_get("password");
470 email
= cgi_get("email");
472 if(!username
|| !*username
) {
473 cgi_set_option("error", "nousername");
474 expand_template(ds
, output
, "login");
477 if(!password
|| !*password
) {
478 cgi_set_option("error", "nopassword");
479 expand_template(ds
, output
, "login");
482 if(!email
|| !*email
) {
483 cgi_set_option("error", "noemail");
484 expand_template(ds
, output
, "login");
487 /* We could well do better address validation but for now we'll just do the
489 if(!strchr(email
, '@')) {
490 cgi_set_option("error", "bademail");
491 expand_template(ds
, output
, "login");
494 if(disorder_register(ds
->g
->client
, username
, password
, email
, &confirm
)) {
495 cgi_set_option("error", "cannotregister");
496 expand_template(ds
, output
, "login");
499 /* We'll go back to the login page with a suitable message */
500 cgi_set_option("registered", "registeredok");
501 expand_template(ds
, output
, "login");
504 static const struct action
{
506 void (*handler
)(cgi_sink
*output
, dcgi_state
*ds
);
508 { "disable", act_disable
},
509 { "enable", act_enable
},
510 { "login", act_login
},
511 { "move", act_move
},
512 { "pause", act_pause
},
513 { "play", act_play
},
514 { "playing", act_playing
},
515 { "prefs", act_prefs
},
516 { "random-disable", act_random_disable
},
517 { "random-enable", act_random_enable
},
518 { "register", act_register
},
519 { "remove", act_remove
},
520 { "resume", act_resume
},
521 { "scratch", act_scratch
},
522 { "volume", act_volume
},
525 /* expansions *****************************************************************/
527 static void exp_include(int attribute((unused
)) nargs
,
531 expand(output
, args
[0], u
);
534 static void exp_server_version(int attribute((unused
)) nargs
,
535 char attribute((unused
)) **args
,
542 if(disorder_version(ds
->g
->client
, (char **)&v
)) v
= "(cannot get version)";
544 v
= "(server not running)";
545 cgi_output(output
, "%s", v
);
548 static void exp_version(int attribute((unused
)) nargs
,
549 char attribute((unused
)) **args
,
551 void attribute((unused
)) *u
) {
552 cgi_output(output
, "%s", disorder_short_version_string
);
555 static void exp_nonce(int attribute((unused
)) nargs
,
556 char attribute((unused
)) **args
,
558 void attribute((unused
)) *u
) {
559 cgi_output(output
, "%s", nonce());
562 static void exp_label(int attribute((unused
)) nargs
,
565 void attribute((unused
)) *u
) {
566 cgi_output(output
, "%s", cgi_label(args
[0]));
569 struct trackinfo_state
{
571 const struct queue_entry
*q
;
576 static void exp_who(int attribute((unused
)) nargs
,
577 char attribute((unused
)) **args
,
582 if(ds
->track
&& ds
->track
->submitter
)
583 cgi_output(output
, "%s", ds
->track
->submitter
);
586 static void exp_length(int attribute((unused
)) nargs
,
587 char attribute((unused
)) **args
,
594 && (ds
->track
->state
== playing_started
595 || ds
->track
->state
== playing_paused
)
596 && ds
->track
->sofar
>= 0)
597 cgi_output(output
, "%ld:%02ld/",
598 ds
->track
->sofar
/ 60, ds
->track
->sofar
% 60);
601 disorder_length(ds
->g
->client
, ds
->track
->track
, &length
);
603 disorder_length(ds
->g
->client
, ds
->tracks
[0], &length
);
605 cgi_output(output
, "%ld:%02ld", length
/ 60, length
% 60);
607 sink_printf(output
->sink
, "%s", " ");
610 static void exp_when(int attribute((unused
)) nargs
,
611 char attribute((unused
)) **args
,
615 const struct tm
*w
= 0;
618 switch(ds
->track
->state
) {
619 case playing_isscratch
:
620 case playing_unplayed
:
622 if(ds
->track
->expected
)
623 w
= localtime(&ds
->track
->expected
);
626 case playing_no_player
:
628 case playing_scratched
:
629 case playing_started
:
631 case playing_quitting
:
632 if(ds
->track
->played
)
633 w
= localtime(&ds
->track
->played
);
637 cgi_output(output
, "%d:%02d", w
->tm_hour
, w
->tm_min
);
639 sink_printf(output
->sink
, " ");
642 static void exp_part(int nargs
,
647 const char *s
, *track
, *part
, *context
;
653 track
= ds
->track
->track
;
655 track
= ds
->tracks
[0];
673 if(disorder_part(ds
->g
->client
, (char **)&s
, track
,
674 !strcmp(context
, "short") ?
"display" : context
, part
))
675 fatal(0, "disorder_part() failed");
676 if(!strcmp(context
, "short"))
677 s
= truncate_for_display(s
, config
->short_display
);
678 cgi_output(output
, "%s", s
);
680 sink_printf(output
->sink
, " ");
683 static void exp_playing(int attribute((unused
)) nargs
,
690 lookups(ds
, DC_PLAYING
);
691 memset(&s
, 0, sizeof s
);
694 s
.track
= ds
->g
->playing
;
695 expandstring(output
, args
[0], &s
);
699 static void exp_queue(int attribute((unused
)) nargs
,
705 struct queue_entry
*q
;
707 lookups(ds
, DC_QUEUE
);
708 memset(&s
, 0, sizeof s
);
711 for(q
= ds
->g
->queue
; q
; q
= q
->next
) {
714 expandstring(output
, args
[0], &s
);
720 static void exp_recent(int attribute((unused
)) nargs
,
726 struct queue_entry
*q
;
728 lookups(ds
, DC_RECENT
);
729 memset(&s
, 0, sizeof s
);
732 for(q
= ds
->g
->recent
; q
; q
= q
->next
) {
735 expandstring(output
, args
[0], &s
);
741 static void exp_new(int attribute((unused
)) nargs
,
749 memset(&s
, 0, sizeof s
);
752 for(s
.index
= 0; s
.index
< ds
->g
->nnew
; ++s
.index
) {
753 s
.last
= s
.index
+ 1 < ds
->g
->nnew
;
754 s
.tracks
= &ds
->g
->new[s
.index
];
755 expandstring(output
, args
[0], &s
);
760 static void exp_url(int attribute((unused
)) nargs
,
761 char attribute((unused
)) **args
,
763 void attribute((unused
)) *u
) {
764 cgi_output(output
, "%s", config
->url
);
772 static int compare_result(const void *a
, const void *b
) {
773 const struct result
*ra
= a
, *rb
= b
;
776 if(!(c
= strcmp(ra
->sort
, rb
->sort
)))
777 c
= strcmp(ra
->track
, rb
->track
);
781 static void exp_search(int nargs
,
785 dcgi_state
*ds
= u
, substate
;
787 const char *q
, *context
, *part
, *template;
803 assert(!"should never happen");
804 part
= context
= template = 0; /* quieten compiler */
806 if(ds
->tracks
== 0) {
807 /* we are the top level, let's get some search results */
808 if(!(q
= cgi_get("query"))) return; /* no results yet */
809 if(disorder_search(ds
->g
->client
, q
, &tracks
, &ntracks
)) return;
813 ntracks
= ds
->ntracks
;
815 assert(ntracks
!= 0);
816 /* sort tracks by the appropriate part */
817 r
= xmalloc(ntracks
* sizeof *r
);
818 for(n
= 0; n
< ntracks
; ++n
) {
819 r
[n
].track
= tracks
[n
];
820 if(disorder_part(ds
->g
->client
, (char **)&r
[n
].sort
,
821 tracks
[n
], context
, part
))
822 fatal(0, "disorder_part() failed");
824 qsort(r
, ntracks
, sizeof (struct result
), compare_result
);
825 /* expand the 2nd arg once for each group. We re-use the passed-in tracks
826 * array as we know it's guaranteed to be big enough and isn't going to be
827 * used for anything else any more. */
828 memset(&substate
, 0, sizeof substate
);
833 substate
.tracks
= tracks
;
834 substate
.ntracks
= 0;
837 && !strcmp(r
[m
].sort
, r
[n
].sort
))
838 tracks
[substate
.ntracks
++] = r
[m
++].track
;
839 substate
.last
= (m
== ntracks
);
840 expandstring(output
, template, &substate
);
845 assert(substate
.last
!= 0);
848 static void exp_arg(int attribute((unused
)) nargs
,
851 void attribute((unused
)) *u
) {
854 if((v
= cgi_get(args
[0])))
855 cgi_output(output
, "%s", v
);
858 static void exp_stats(int attribute((unused
)) nargs
,
859 char attribute((unused
)) **args
,
865 cgi_opentag(output
->sink
, "pre", "class", "stats", (char *)0);
866 if(!disorder_stats(ds
->g
->client
, &v
, 0)) {
868 cgi_output(output
, "%s\n", *v
++);
870 cgi_closetag(output
->sink
, "pre");
873 static void exp_volume(int attribute((unused
)) nargs
,
879 lookups(ds
, DC_VOLUME
);
880 if(!strcmp(args
[0], "left"))
881 cgi_output(output
, "%d", ds
->g
->volume_left
);
883 cgi_output(output
, "%d", ds
->g
->volume_right
);
886 static void exp_shell(int attribute((unused
)) nargs
,
889 void attribute((unused
)) *u
) {
895 if(!(pid
= xfork())) {
900 execlp("sh", "sh", "-c", args
[0], (char *)0);
901 fatal(errno
, "error executing sh");
904 while((n
= read(p
[0], buffer
, sizeof buffer
))) {
906 if(errno
== EINTR
) continue;
907 else fatal(errno
, "error reading from pipe");
909 output
->sink
->write(output
->sink
, buffer
, n
);
912 while((n
= waitpid(pid
, &w
, 0)) < 0 && errno
== EINTR
)
914 if(n
< 0) fatal(errno
, "error calling waitpid");
916 error(0, "shell command '%s' %s", args
[0], wstat(w
));
919 static inline int str2bool(const char *s
) {
920 return !strcmp(s
, "true");
923 static inline const char *bool2str(int n
) {
924 return n ?
"true" : "false";
927 static char *expandarg(const char *arg
, dcgi_state
*ds
) {
933 output
.sink
= sink_dynstr(&d
);
934 expandstring(&output
, arg
, ds
);
935 dynstr_terminate(&d
);
939 static void exp_prefs(int attribute((unused
)) nargs
,
946 const char *file
= expandarg(args
[0], ds
);
948 memset(&substate
, 0, sizeof substate
);
951 if(disorder_prefs(ds
->g
->client
, file
, &k
)) return;
953 substate
.last
= !k
->next
;
955 expandstring(output
, args
[1], &substate
);
962 static void exp_pref(int attribute((unused
)) nargs
,
969 if(!disorder_get(ds
->g
->client
, args
[0], args
[1], &value
))
970 cgi_output(output
, "%s", value
);
973 static void exp_if(int nargs
,
978 int n
= str2bool(expandarg(args
[0], ds
)) ?
1 : 2;
981 expandstring(output
, args
[n
], ds
);
984 static void exp_and(int nargs
,
991 for(n
= 0; n
< nargs
; ++n
)
992 if(!str2bool(expandarg(args
[n
], ds
))) {
996 sink_printf(output
->sink
, "%s", bool2str(result
));
999 static void exp_or(int nargs
,
1006 for(n
= 0; n
< nargs
; ++n
)
1007 if(str2bool(expandarg(args
[n
], ds
))) {
1011 sink_printf(output
->sink
, "%s", bool2str(result
));
1014 static void exp_not(int attribute((unused
)) nargs
,
1017 void attribute((unused
)) *u
) {
1018 sink_printf(output
->sink
, "%s", bool2str(!str2bool(args
[0])));
1021 static void exp_isplaying(int attribute((unused
)) nargs
,
1022 char attribute((unused
)) **args
,
1027 lookups(ds
, DC_PLAYING
);
1028 sink_printf(output
->sink
, "%s", bool2str(!!ds
->g
->playing
));
1031 static void exp_isqueue(int attribute((unused
)) nargs
,
1032 char attribute((unused
)) **args
,
1037 lookups(ds
, DC_QUEUE
);
1038 sink_printf(output
->sink
, "%s", bool2str(!!ds
->g
->queue
));
1041 static void exp_isrecent(int attribute((unused
)) nargs
,
1042 char attribute((unused
)) **args
,
1047 lookups(ds
, DC_RECENT
);
1048 sink_printf(output
->sink
, "%s", bool2str(!!ds
->g
->recent
));
1051 static void exp_isnew(int attribute((unused
)) nargs
,
1052 char attribute((unused
)) **args
,
1057 lookups(ds
, DC_NEW
);
1058 sink_printf(output
->sink
, "%s", bool2str(!!ds
->g
->nnew
));
1061 static void exp_id(int attribute((unused
)) nargs
,
1062 char attribute((unused
)) **args
,
1068 cgi_output(output
, "%s", ds
->track
->id
);
1071 static void exp_track(int attribute((unused
)) nargs
,
1072 char attribute((unused
)) **args
,
1078 cgi_output(output
, "%s", ds
->track
->track
);
1081 static void exp_parity(int attribute((unused
)) nargs
,
1082 char attribute((unused
)) **args
,
1087 cgi_output(output
, "%s", ds
->index
% 2 ?
"odd" : "even");
1090 static void exp_comment(int attribute((unused
)) nargs
,
1091 char attribute((unused
)) **args
,
1092 cgi_sink
attribute((unused
)) *output
,
1093 void attribute((unused
)) *u
) {
1097 static void exp_prefname(int attribute((unused
)) nargs
,
1098 char attribute((unused
)) **args
,
1103 if(ds
->pref
&& ds
->pref
->name
)
1104 cgi_output(output
, "%s", ds
->pref
->name
);
1107 static void exp_prefvalue(int attribute((unused
)) nargs
,
1108 char attribute((unused
)) **args
,
1113 if(ds
->pref
&& ds
->pref
->value
)
1114 cgi_output(output
, "%s", ds
->pref
->value
);
1117 static void exp_isfiles(int attribute((unused
)) nargs
,
1118 char attribute((unused
)) **args
,
1123 lookups(ds
, DC_FILES
);
1124 sink_printf(output
->sink
, "%s", bool2str(!!ds
->g
->nfiles
));
1127 static void exp_isdirectories(int attribute((unused
)) nargs
,
1128 char attribute((unused
)) **args
,
1133 lookups(ds
, DC_DIRS
);
1134 sink_printf(output
->sink
, "%s", bool2str(!!ds
->g
->ndirs
));
1137 static void exp_choose(int attribute((unused
)) nargs
,
1142 dcgi_state substate
;
1146 const char *type
, *what
= expandarg(args
[0], ds
);
1148 if(!strcmp(what
, "files")) {
1149 lookups(ds
, DC_FILES
);
1150 files
= ds
->g
->files
;
1151 nfiles
= ds
->g
->nfiles
;
1153 } else if(!strcmp(what
, "directories")) {
1154 lookups(ds
, DC_DIRS
);
1155 files
= ds
->g
->dirs
;
1156 nfiles
= ds
->g
->ndirs
;
1159 error(0, "unknown @choose@ argument '%s'", what
);
1162 e
= xmalloc(nfiles
* sizeof (struct entry
));
1163 for(n
= 0; n
< nfiles
; ++n
) {
1164 e
[n
].path
= files
[n
];
1165 e
[n
].sort
= trackname_transform(type
, files
[n
], "sort");
1166 e
[n
].display
= trackname_transform(type
, files
[n
], "display");
1168 qsort(e
, nfiles
, sizeof (struct entry
), compare_entry
);
1169 memset(&substate
, 0, sizeof substate
);
1172 for(n
= 0; n
< nfiles
; ++n
) {
1173 substate
.last
= (n
== nfiles
- 1);
1175 substate
.entry
= &e
[n
];
1176 expandstring(output
, args
[1], &substate
);
1181 static void exp_file(int attribute((unused
)) nargs
,
1182 char attribute((unused
)) **args
,
1188 cgi_output(output
, "%s", ds
->entry
->path
);
1190 cgi_output(output
, "%s", ds
->track
->track
);
1192 cgi_output(output
, "%s", ds
->tracks
[0]);
1195 static void exp_transform(int nargs
,
1198 void attribute((unused
)) *u
) {
1199 const char *context
= nargs
> 2 ? args
[2] : "display";
1201 cgi_output(output
, "%s", trackname_transform(args
[1], args
[0], context
));
1204 static void exp_urlquote(int attribute((unused
)) nargs
,
1207 void attribute((unused
)) *u
) {
1208 cgi_output(output
, "%s", urlencodestring(args
[0]));
1211 static void exp_scratchable(int attribute((unused
)) nargs
,
1212 char attribute((unused
)) **args
,
1214 void attribute((unused
)) *u
) {
1218 if(config
->restrictions
& RESTRICT_SCRATCH
) {
1219 lookups(ds
, DC_PLAYING
);
1220 result
= (ds
->g
->playing
1221 && (!ds
->g
->playing
->submitter
1222 || !strcmp(ds
->g
->playing
->submitter
,
1223 disorder_user(ds
->g
->client
))));
1226 sink_printf(output
->sink
, "%s", bool2str(result
));
1229 static void exp_removable(int attribute((unused
)) nargs
,
1230 char attribute((unused
)) **args
,
1232 void attribute((unused
)) *u
) {
1236 if(config
->restrictions
& RESTRICT_REMOVE
)
1238 && ds
->track
->submitter
1239 && !strcmp(ds
->track
->submitter
,
1240 disorder_user(ds
->g
->client
)));
1243 sink_printf(output
->sink
, "%s", bool2str(result
));
1246 static void exp_navigate(int attribute((unused
)) nargs
,
1251 dcgi_state substate
;
1252 const char *path
= expandarg(args
[0], ds
);
1257 memset(&substate
, 0, sizeof substate
);
1259 ptr
= path
+ 1; /* skip root */
1261 substate
.nav_path
= path
;
1264 while(*ptr
&& *ptr
!= '/')
1266 substate
.last
= !*ptr
;
1267 substate
.nav_len
= ptr
- path
;
1268 substate
.nav_dirlen
= dirlen
;
1269 expandstring(output
, args
[1], &substate
);
1270 dirlen
= substate
.nav_len
;
1277 static void exp_fullname(int attribute((unused
)) nargs
,
1278 char attribute((unused
)) **args
,
1282 cgi_output(output
, "%.*s", ds
->nav_len
, ds
->nav_path
);
1285 static void exp_basename(int nargs
,
1293 if((s
= strrchr(args
[0], '/'))) ++s
;
1295 cgi_output(output
, "%s", s
);
1297 cgi_output(output
, "%.*s", ds
->nav_len
- ds
->nav_dirlen
- 1,
1298 ds
->nav_path
+ ds
->nav_dirlen
+ 1);
1301 static void exp_dirname(int nargs
,
1309 if((s
= strrchr(args
[0], '/')))
1310 cgi_output(output
, "%.*s", (int)(s
- args
[0]), args
[0]);
1312 cgi_output(output
, "%.*s", ds
->nav_dirlen
, ds
->nav_path
);
1315 static void exp_eq(int attribute((unused
)) nargs
,
1318 void attribute((unused
)) *u
) {
1319 cgi_output(output
, "%s", bool2str(!strcmp(args
[0], args
[1])));
1322 static void exp_ne(int attribute((unused
)) nargs
,
1325 void attribute((unused
)) *u
) {
1326 cgi_output(output
, "%s", bool2str(strcmp(args
[0], args
[1])));
1329 static void exp_enabled(int attribute((unused
)) nargs
,
1330 char attribute((unused
)) **args
,
1337 disorder_enabled(ds
->g
->client
, &enabled
);
1338 cgi_output(output
, "%s", bool2str(enabled
));
1341 static void exp_random_enabled(int attribute((unused
)) nargs
,
1342 char attribute((unused
)) **args
,
1349 disorder_random_enabled(ds
->g
->client
, &enabled
);
1350 cgi_output(output
, "%s", bool2str(enabled
));
1353 static void exp_trackstate(int attribute((unused
)) nargs
,
1358 struct queue_entry
*q
;
1361 if(disorder_resolve(ds
->g
->client
, &track
, args
[0])) return;
1362 lookups(ds
, DC_QUEUE
|DC_PLAYING
);
1363 if(ds
->g
->playing
&& !strcmp(ds
->g
->playing
->track
, track
))
1364 cgi_output(output
, "playing");
1366 for(q
= ds
->g
->queue
; q
&& strcmp(q
->track
, track
); q
= q
->next
)
1369 cgi_output(output
, "queued");
1373 static void exp_thisurl(int attribute((unused
)) nargs
,
1374 char attribute((unused
)) **args
,
1376 void attribute((unused
)) *u
) {
1377 kvp_set(&cgi_args
, "nonce", nonce()); /* nonces had better differ! */
1378 cgi_output(output
, "%s?%s", config
->url
, kvp_urlencode(cgi_args
, 0));
1381 static void exp_isfirst(int attribute((unused
)) nargs
,
1382 char attribute((unused
)) **args
,
1387 sink_printf(output
->sink
, "%s", bool2str(!!ds
->first
));
1390 static void exp_islast(int attribute((unused
)) nargs
,
1391 char attribute((unused
)) **args
,
1396 sink_printf(output
->sink
, "%s", bool2str(!!ds
->last
));
1399 static void exp_action(int attribute((unused
)) nargs
,
1400 char attribute((unused
)) **args
,
1402 void attribute((unused
)) *u
) {
1403 const char *action
= cgi_get("action"), *mgmt
;
1405 if(!action
) action
= "playing";
1406 if(!strcmp(action
, "playing")
1407 && (mgmt
= cgi_get("mgmt"))
1408 && !strcmp(mgmt
, "true"))
1410 sink_printf(output
->sink
, "%s", action
);
1413 static void exp_resolve(int attribute((unused
)) nargs
,
1416 void attribute((unused
)) *u
) {
1420 if(!disorder_resolve(ds
->g
->client
, &track
, args
[0]))
1421 sink_printf(output
->sink
, "%s", track
);
1424 static void exp_paused(int attribute((unused
)) nargs
,
1425 char attribute((unused
)) **args
,
1431 lookups(ds
, DC_PLAYING
);
1432 if(ds
->g
->playing
&& ds
->g
->playing
->state
== playing_paused
)
1434 cgi_output(output
, "%s", bool2str(paused
));
1437 static void exp_state(int attribute((unused
)) nargs
,
1438 char attribute((unused
)) **args
,
1444 cgi_output(output
, "%s", playing_states
[ds
->track
->state
]);
1447 static void exp_files(int attribute((unused
)) nargs
,
1452 dcgi_state substate
;
1453 const char *nfiles_arg
, *directory
;
1454 int nfiles
, numfile
;
1457 memset(&substate
, 0, sizeof substate
);
1459 if((directory
= cgi_get("directory"))) {
1460 /* Prefs for whole directory. */
1461 lookups(ds
, DC_FILES
);
1462 /* Synthesize args for the file list. */
1463 nfiles
= ds
->g
->nfiles
;
1464 for(numfile
= 0; numfile
< nfiles
; ++numfile
) {
1465 k
= xmalloc(sizeof *k
);
1466 byte_xasprintf((char **)&k
->name
, "%d_file", numfile
);
1467 k
->value
= ds
->g
->files
[numfile
];
1472 /* Args already present. */
1473 if((nfiles_arg
= cgi_get("files"))) nfiles
= atoi(nfiles_arg
);
1476 for(numfile
= 0; numfile
< nfiles
; ++numfile
) {
1477 substate
.index
= numfile
;
1478 expandstring(output
, args
[0], &substate
);
1482 static void exp_index(int attribute((unused
)) nargs
,
1483 char attribute((unused
)) **args
,
1488 cgi_output(output
, "%d", ds
->index
);
1491 static void exp_nfiles(int attribute((unused
)) nargs
,
1492 char attribute((unused
)) **args
,
1496 const char *files_arg
;
1498 if(cgi_get("directory")) {
1499 lookups(ds
, DC_FILES
);
1500 cgi_output(output
, "%d", ds
->g
->nfiles
);
1501 } else if((files_arg
= cgi_get("files")))
1502 cgi_output(output
, "%s", files_arg
);
1504 cgi_output(output
, "1");
1507 static void exp_user(int attribute((unused
)) nargs
,
1508 char attribute((unused
)) **args
,
1511 dcgi_state
*const ds
= u
;
1513 cgi_output(output
, "%s", disorder_user(ds
->g
->client
));
1516 static const struct cgi_expansion expansions
[] = {
1517 { "#", 0, INT_MAX
, EXP_MAGIC
, exp_comment
},
1518 { "action", 0, 0, 0, exp_action
},
1519 { "and", 0, INT_MAX
, EXP_MAGIC
, exp_and
},
1520 { "arg", 1, 1, 0, exp_arg
},
1521 { "basename", 0, 1, 0, exp_basename
},
1522 { "choose", 2, 2, EXP_MAGIC
, exp_choose
},
1523 { "dirname", 0, 1, 0, exp_dirname
},
1524 { "enabled", 0, 0, 0, exp_enabled
},
1525 { "eq", 2, 2, 0, exp_eq
},
1526 { "file", 0, 0, 0, exp_file
},
1527 { "files", 1, 1, EXP_MAGIC
, exp_files
},
1528 { "fullname", 0, 0, 0, exp_fullname
},
1529 { "id", 0, 0, 0, exp_id
},
1530 { "if", 2, 3, EXP_MAGIC
, exp_if
},
1531 { "include", 1, 1, 0, exp_include
},
1532 { "index", 0, 0, 0, exp_index
},
1533 { "isdirectories", 0, 0, 0, exp_isdirectories
},
1534 { "isfiles", 0, 0, 0, exp_isfiles
},
1535 { "isfirst", 0, 0, 0, exp_isfirst
},
1536 { "islast", 0, 0, 0, exp_islast
},
1537 { "isnew", 0, 0, 0, exp_isnew
},
1538 { "isplaying", 0, 0, 0, exp_isplaying
},
1539 { "isqueue", 0, 0, 0, exp_isqueue
},
1540 { "isrecent", 0, 0, 0, exp_isrecent
},
1541 { "label", 1, 1, 0, exp_label
},
1542 { "length", 0, 0, 0, exp_length
},
1543 { "navigate", 2, 2, EXP_MAGIC
, exp_navigate
},
1544 { "ne", 2, 2, 0, exp_ne
},
1545 { "new", 1, 1, EXP_MAGIC
, exp_new
},
1546 { "nfiles", 0, 0, 0, exp_nfiles
},
1547 { "nonce", 0, 0, 0, exp_nonce
},
1548 { "not", 1, 1, 0, exp_not
},
1549 { "or", 0, INT_MAX
, EXP_MAGIC
, exp_or
},
1550 { "parity", 0, 0, 0, exp_parity
},
1551 { "part", 1, 3, 0, exp_part
},
1552 { "paused", 0, 0, 0, exp_paused
},
1553 { "playing", 1, 1, EXP_MAGIC
, exp_playing
},
1554 { "pref", 2, 2, 0, exp_pref
},
1555 { "prefname", 0, 0, 0, exp_prefname
},
1556 { "prefs", 2, 2, EXP_MAGIC
, exp_prefs
},
1557 { "prefvalue", 0, 0, 0, exp_prefvalue
},
1558 { "queue", 1, 1, EXP_MAGIC
, exp_queue
},
1559 { "random-enabled", 0, 0, 0, exp_random_enabled
},
1560 { "recent", 1, 1, EXP_MAGIC
, exp_recent
},
1561 { "removable", 0, 0, 0, exp_removable
},
1562 { "resolve", 1, 1, 0, exp_resolve
},
1563 { "scratchable", 0, 0, 0, exp_scratchable
},
1564 { "search", 2, 3, EXP_MAGIC
, exp_search
},
1565 { "server-version", 0, 0, 0, exp_server_version
},
1566 { "shell", 1, 1, 0, exp_shell
},
1567 { "state", 0, 0, 0, exp_state
},
1568 { "stats", 0, 0, 0, exp_stats
},
1569 { "thisurl", 0, 0, 0, exp_thisurl
},
1570 { "track", 0, 0, 0, exp_track
},
1571 { "trackstate", 1, 1, 0, exp_trackstate
},
1572 { "transform", 2, 3, 0, exp_transform
},
1573 { "url", 0, 0, 0, exp_url
},
1574 { "urlquote", 1, 1, 0, exp_urlquote
},
1575 { "user", 0, 0, 0, exp_user
},
1576 { "version", 0, 0, 0, exp_version
},
1577 { "volume", 1, 1, 0, exp_volume
},
1578 { "when", 0, 0, 0, exp_when
},
1579 { "who", 0, 0, 0, exp_who
}
1582 static void expand(cgi_sink
*output
,
1583 const char *template,
1585 cgi_expand(template,
1586 expansions
, sizeof expansions
/ sizeof *expansions
,
1591 static void expandstring(cgi_sink
*output
,
1594 cgi_expand_string("",
1596 expansions
, sizeof expansions
/ sizeof *expansions
,
1601 static void perform_action(cgi_sink
*output
, dcgi_state
*ds
,
1602 const char *action
) {
1605 /* If we have a login cookie it'd better appear in all responses */
1606 header_cookie(output
);
1607 /* We don't ever want anything to be cached */
1608 cgi_header(output
->sink
, "Cache-Control", "no-cache");
1609 if((n
= TABLE_FIND(actions
, struct action
, name
, action
)) >= 0)
1610 actions
[n
].handler(output
, ds
);
1612 expand_template(ds
, output
, action
);
1615 void disorder_cgi(cgi_sink
*output
, dcgi_state
*ds
) {
1616 const char *action
= cgi_get("action");
1618 if(!action
) action
= "playing";
1619 perform_action(output
, ds
, action
);
1622 void disorder_cgi_error(cgi_sink
*output
, dcgi_state
*ds
,
1624 cgi_set_option("error", msg
);
1625 perform_action(output
, ds
, "error");