chiark / gitweb /
never set an expired cookie; on logout, re-login as guest
[disorder] / server / dcgi.c
CommitLineData
460b9539 1/*
2 * This file is part of DisOrder.
78efa64e 3 * Copyright (C) 2004, 2005, 2006, 2007 Richard Kettlewell
460b9539 4 *
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.
9 *
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.
14 *
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
18 * USA
19 */
20
21#include <config.h>
22#include "types.h"
23
24#include <stdio.h>
25#include <errno.h>
26#include <sys/types.h>
27#include <sys/socket.h>
28#include <stddef.h>
29#include <stdlib.h>
30#include <time.h>
31#include <unistd.h>
32#include <string.h>
33#include <sys/wait.h>
34#include <pcre.h>
35#include <assert.h>
36
37#include "client.h"
38#include "mem.h"
39#include "vector.h"
40#include "sink.h"
41#include "cgi.h"
42#include "dcgi.h"
43#include "log.h"
44#include "configuration.h"
45#include "table.h"
46#include "queue.h"
47#include "plugin.h"
48#include "split.h"
460b9539 49#include "wstat.h"
50#include "kvp.h"
51#include "syscalls.h"
52#include "printf.h"
53#include "regsub.h"
54#include "defs.h"
55#include "trackname.h"
61507e3c 56#include "charset.h"
460b9539 57
fdf98378 58char *login_cookie;
59
460b9539 60static void expand(cgi_sink *output,
61 const char *template,
62 dcgi_state *ds);
63static void expandstring(cgi_sink *output,
64 const char *string,
65 dcgi_state *ds);
66
67struct entry {
68 const char *path;
69 const char *sort;
70 const char *display;
71};
72
73static const char *nonce(void) {
74 static unsigned long count;
75 char *s;
76
77 byte_xasprintf(&s, "%lx%lx%lx",
78 (unsigned long)time(0),
79 (unsigned long)getpid(),
80 count++);
81 return s;
82}
83
84static int compare_entry(const void *a, const void *b) {
85 const struct entry *ea = a, *eb = b;
86
87 return compare_tracks(ea->sort, eb->sort,
88 ea->display, eb->display,
89 ea->path, eb->path);
90}
91
92static const char *front_url(void) {
93 char *url;
94 const char *mgmt;
95
96 /* preserve management interface visibility */
97 if((mgmt = cgi_get("mgmt")) && !strcmp(mgmt, "true")) {
98 byte_xasprintf(&url, "%s?mgmt=true", config->url);
99 return url;
100 }
101 return config->url;
102}
103
7f66f58b 104static void header_cookie(struct sink *output) {
fdf98378 105 struct dynstr d[1];
106 char *s;
107
108 if(login_cookie) {
109 dynstr_init(d);
110 for(s = login_cookie; *s; ++s) {
111 if(*s == '"')
112 dynstr_append(d, '\\');
113 dynstr_append(d, *s);
114 }
115 dynstr_terminate(d);
116 byte_xasprintf(&s, "disorder=\"%s\"", d->vec); /* TODO domain, path, expiry */
7f66f58b 117 cgi_header(output, "Set-Cookie", s);
118 } else
119 /* Force browser to discard cookie */
120 cgi_header(output, "Set-Cookie", "disorder=none;Max-Age=0");
121}
122
123static void redirect(struct sink *output) {
124 const char *back;
125
126 back = cgi_get("back");
127 cgi_header(output, "Location", back && *back ? back : front_url());
128 header_cookie(output);
129 cgi_body(output);
fdf98378 130}
131
132static void expand_template(dcgi_state *ds, cgi_sink *output,
133 const char *action) {
134 cgi_header(output->sink, "Content-Type", "text/html");
7f66f58b 135 header_cookie(output->sink);
fdf98378 136 cgi_body(output->sink);
137 expand(output, action, ds);
138}
139
460b9539 140static void lookups(dcgi_state *ds, unsigned want) {
141 unsigned need;
142 struct queue_entry *r, *rnext;
143 const char *dir, *re;
144
145 if(ds->g->client && (need = want ^ (ds->g->flags & want)) != 0) {
146 if(need & DC_QUEUE)
147 disorder_queue(ds->g->client, &ds->g->queue);
148 if(need & DC_PLAYING)
149 disorder_playing(ds->g->client, &ds->g->playing);
78efa64e
RK
150 if(need & DC_NEW)
151 disorder_new_tracks(ds->g->client, &ds->g->new, &ds->g->nnew, 0);
460b9539 152 if(need & DC_RECENT) {
153 /* we need to reverse the order of the list */
154 disorder_recent(ds->g->client, &r);
155 while(r) {
156 rnext = r->next;
157 r->next = ds->g->recent;
158 ds->g->recent = r;
159 r = rnext;
160 }
161 }
162 if(need & DC_VOLUME)
163 disorder_get_volume(ds->g->client,
164 &ds->g->volume_left, &ds->g->volume_right);
165 if(need & (DC_FILES|DC_DIRS)) {
166 if(!(dir = cgi_get("directory")))
167 dir = "";
168 re = cgi_get("regexp");
169 if(need & DC_DIRS)
170 if(disorder_directories(ds->g->client, dir, re,
171 &ds->g->dirs, &ds->g->ndirs))
172 ds->g->ndirs = 0;
173 if(need & DC_FILES)
174 if(disorder_files(ds->g->client, dir, re,
175 &ds->g->files, &ds->g->nfiles))
176 ds->g->nfiles = 0;
177 }
178 ds->g->flags |= need;
179 }
180}
181
182/* actions ********************************************************************/
183
184static void act_disable(cgi_sink *output,
185 dcgi_state *ds) {
186 if(ds->g->client)
187 disorder_disable(ds->g->client);
188 redirect(output->sink);
189}
190
191static void act_enable(cgi_sink *output,
192 dcgi_state *ds) {
193 if(ds->g->client)
194 disorder_enable(ds->g->client);
195 redirect(output->sink);
196}
197
198static void act_random_disable(cgi_sink *output,
199 dcgi_state *ds) {
200 if(ds->g->client)
201 disorder_random_disable(ds->g->client);
202 redirect(output->sink);
203}
204
205static void act_random_enable(cgi_sink *output,
206 dcgi_state *ds) {
207 if(ds->g->client)
208 disorder_random_enable(ds->g->client);
209 redirect(output->sink);
210}
211
212static void act_remove(cgi_sink *output,
213 dcgi_state *ds) {
214 const char *id;
215
216 if(!(id = cgi_get("id"))) fatal(0, "missing id argument");
217 if(ds->g->client)
218 disorder_remove(ds->g->client, id);
219 redirect(output->sink);
220}
221
222static void act_move(cgi_sink *output,
223 dcgi_state *ds) {
224 const char *id, *delta;
225
226 if(!(id = cgi_get("id"))) fatal(0, "missing id argument");
227 if(!(delta = cgi_get("delta"))) fatal(0, "missing delta argument");
228 if(ds->g->client)
229 disorder_move(ds->g->client, id, atoi(delta));
230 redirect(output->sink);
231}
232
233static void act_scratch(cgi_sink *output,
234 dcgi_state *ds) {
235 if(ds->g->client)
236 disorder_scratch(ds->g->client, cgi_get("id"));
237 redirect(output->sink);
238}
239
240static void act_playing(cgi_sink *output, dcgi_state *ds) {
241 char r[1024];
242 long refresh = config->refresh, length;
243 time_t now, fin;
244 int random_enabled = 0;
245 int enabled = 0;
246
247 lookups(ds, DC_PLAYING|DC_QUEUE);
248 cgi_header(output->sink, "Content-Type", "text/html");
249 disorder_random_enabled(ds->g->client, &random_enabled);
250 disorder_enabled(ds->g->client, &enabled);
251 if(ds->g->playing
252 && ds->g->playing->state == playing_started /* i.e. not paused */
253 && !disorder_length(ds->g->client, ds->g->playing->track, &length)
254 && length
255 && ds->g->playing->sofar >= 0) {
256 /* Try to put the next refresh at the start of the next track. */
257 time(&now);
258 fin = now + length - ds->g->playing->sofar + config->gap;
259 if(now + refresh > fin)
260 refresh = fin - now;
261 }
262 if(ds->g->queue && ds->g->queue->state == playing_isscratch) {
263 /* next track is a scratch, don't leave more than the inter-track gap */
264 if(refresh > config->gap)
265 refresh = config->gap;
266 }
267 if(!ds->g->playing && ((ds->g->queue
268 && ds->g->queue->state != playing_random)
269 || random_enabled) && enabled) {
270 /* no track playing but playing is enabled and there is something coming
271 * up, must be in a gap */
272 if(refresh > config->gap)
273 refresh = config->gap;
274 }
275 byte_snprintf(r, sizeof r, "%ld;url=%s", refresh > 0 ? refresh : 1,
276 front_url());
277 cgi_header(output->sink, "Refresh", r);
7f66f58b 278 header_cookie(output->sink);
460b9539 279 cgi_body(output->sink);
280 expand(output, "playing", ds);
281}
282
283static void act_play(cgi_sink *output,
284 dcgi_state *ds) {
285 const char *track, *dir;
286 char **tracks;
287 int ntracks, n;
288 struct entry *e;
289
290 if((track = cgi_get("file"))) {
291 disorder_play(ds->g->client, track);
292 } else if((dir = cgi_get("directory"))) {
293 if(disorder_files(ds->g->client, dir, 0, &tracks, &ntracks)) ntracks = 0;
294 if(ntracks) {
295 e = xmalloc(ntracks * sizeof (struct entry));
296 for(n = 0; n < ntracks; ++n) {
297 e[n].path = tracks[n];
298 e[n].sort = trackname_transform("track", tracks[n], "sort");
299 e[n].display = trackname_transform("track", tracks[n], "display");
300 }
301 qsort(e, ntracks, sizeof (struct entry), compare_entry);
302 for(n = 0; n < ntracks; ++n)
303 disorder_play(ds->g->client, e[n].path);
304 }
305 }
306 /* XXX error handling */
307 redirect(output->sink);
308}
309
310static int clamp(int n, int min, int max) {
311 if(n < min)
312 return min;
313 if(n > max)
314 return max;
315 return n;
316}
317
318static const char *volume_url(void) {
319 char *url;
320
321 byte_xasprintf(&url, "%s?action=volume", config->url);
322 return url;
323}
324
325static void act_volume(cgi_sink *output, dcgi_state *ds) {
326 const char *l, *r, *d, *back;
327 int nd, changed = 0;;
328
329 if((d = cgi_get("delta"))) {
330 lookups(ds, DC_VOLUME);
331 nd = clamp(atoi(d), -255, 255);
332 disorder_set_volume(ds->g->client,
333 clamp(ds->g->volume_left + nd, 0, 255),
334 clamp(ds->g->volume_right + nd, 0, 255));
335 changed = 1;
336 } else if((l = cgi_get("left")) && (r = cgi_get("right"))) {
337 disorder_set_volume(ds->g->client, atoi(l), atoi(r));
338 changed = 1;
339 }
340 if(changed) {
341 /* redirect back to ourselves (but without the volume-changing bits in the
342 * URL) */
343 cgi_header(output->sink, "Location",
344 (back = cgi_get("back")) ? back : volume_url());
7f66f58b 345 header_cookie(output->sink);
460b9539 346 cgi_body(output->sink);
347 } else {
348 cgi_header(output->sink, "Content-Type", "text/html");
7f66f58b 349 header_cookie(output->sink);
460b9539 350 cgi_body(output->sink);
351 expand(output, "volume", ds);
352 }
353}
354
355static void act_prefs_errors(const char *msg,
356 void attribute((unused)) *u) {
357 fatal(0, "error splitting parts list: %s", msg);
358}
359
360static const char *numbered_arg(const char *argname, int numfile) {
361 char *fullname;
362
363 byte_xasprintf(&fullname, "%d_%s", numfile, argname);
364 return cgi_get(fullname);
365}
366
367static void process_prefs(dcgi_state *ds, int numfile) {
368 const char *file, *name, *value, *part, *parts, *current, *context;
369 char **partslist;
370
371 if(!(file = numbered_arg("file", numfile)))
372 /* The first file doesn't need numbering. */
373 if(numfile > 0 || !(file = cgi_get("file")))
374 return;
375 if((parts = numbered_arg("parts", numfile))
376 || (parts = cgi_get("parts"))) {
377 /* Default context is display. Other contexts not actually tested. */
378 if(!(context = numbered_arg("context", numfile))) context = "display";
379 partslist = split(parts, 0, 0, act_prefs_errors, 0);
380 while((part = *partslist++)) {
381 if(!(value = numbered_arg(part, numfile)))
382 continue;
383 /* If it's already right (whether regexps or db) don't change anything,
384 * so we don't fill the database up with rubbish. */
385 if(disorder_part(ds->g->client, (char **)&current,
386 file, context, part))
387 fatal(0, "disorder_part() failed");
388 if(!strcmp(current, value))
389 continue;
390 byte_xasprintf((char **)&name, "trackname_%s_%s", context, part);
391 disorder_set(ds->g->client, file, name, value);
392 }
393 if((value = numbered_arg("random", numfile)))
394 disorder_unset(ds->g->client, file, "pick_at_random");
395 else
396 disorder_set(ds->g->client, file, "pick_at_random", "0");
397 if((value = numbered_arg("tags", numfile)))
398 disorder_set(ds->g->client, file, "tags", value);
399 } else if((name = cgi_get("name"))) {
400 /* Raw preferences. Not well supported in the templates at the moment. */
401 value = cgi_get("value");
402 if(value)
403 disorder_set(ds->g->client, file, name, value);
404 else
405 disorder_unset(ds->g->client, file, name);
406 }
407}
408
409static void act_prefs(cgi_sink *output, dcgi_state *ds) {
410 const char *files;
411 int nfiles, numfile;
412
413 if((files = cgi_get("files"))) nfiles = atoi(files);
414 else nfiles = 1;
415 for(numfile = 0; numfile < nfiles; ++numfile)
416 process_prefs(ds, numfile);
417 cgi_header(output->sink, "Content-Type", "text/html");
7f66f58b 418 header_cookie(output->sink);
460b9539 419 cgi_body(output->sink);
420 expand(output, "prefs", ds);
421}
422
423static void act_pause(cgi_sink *output,
424 dcgi_state *ds) {
425 if(ds->g->client)
426 disorder_pause(ds->g->client);
427 redirect(output->sink);
428}
429
430static void act_resume(cgi_sink *output,
431 dcgi_state *ds) {
432 if(ds->g->client)
433 disorder_resume(ds->g->client);
434 redirect(output->sink);
435}
436
fdf98378 437static void act_login(cgi_sink *output,
438 dcgi_state *ds) {
439 const char *username, *password, *back;
440 disorder_client *c;
441
442 username = cgi_get("username");
443 password = cgi_get("password");
444 if(!username || !password
445 || !strcmp(username, "guest")/*bodge to avoid guest cookies*/) {
446 /* We're just visiting the login page */
447 expand_template(ds, output, "login");
448 return;
449 }
450 c = disorder_new(1);
451 if(disorder_connect_user(c, username, password)) {
452 cgi_set_option("error", "loginfailed");
453 expand_template(ds, output, "login");
454 return;
455 }
456 if(disorder_make_cookie(c, &login_cookie)) {
457 cgi_set_option("error", "cookiefailed");
458 expand_template(ds, output, "login");
459 return;
460 }
461 /* We have a new cookie */
7f66f58b 462 header_cookie(output->sink);
fdf98378 463 if((back = cgi_get("back")) && back)
464 /* Redirect back to somewhere or other */
465 redirect(output->sink);
466 else
467 /* Stick to the login page */
468 expand_template(ds, output, "login");
469}
470
b42ffad6 471static void act_logout(cgi_sink *output,
472 dcgi_state *ds) {
473 disorder_revoke(ds->g->client);
474 login_cookie = 0;
7f66f58b 475 /* Reconnect as guest */
476 ds->g->client = disorder_new(0);
477 if(disorder_connect_cookie(ds->g->client, 0)) {
478 disorder_cgi_error(output, ds, "connect");
479 exit(0);
480 }
b42ffad6 481 /* Back to the login page */
482 expand_template(ds, output, "login");
483}
484
fdf98378 485static void act_register(cgi_sink *output,
486 dcgi_state *ds) {
487 const char *username, *password, *email;
488 char *confirm;
489
490 username = cgi_get("username");
491 password = cgi_get("password");
492 email = cgi_get("email");
493
494 if(!username || !*username) {
495 cgi_set_option("error", "nousername");
496 expand_template(ds, output, "login");
497 return;
498 }
499 if(!password || !*password) {
500 cgi_set_option("error", "nopassword");
501 expand_template(ds, output, "login");
502 return;
503 }
504 if(!email || !*email) {
505 cgi_set_option("error", "noemail");
506 expand_template(ds, output, "login");
507 return;
508 }
509 /* We could well do better address validation but for now we'll just do the
510 * minimum */
511 if(!strchr(email, '@')) {
512 cgi_set_option("error", "bademail");
513 expand_template(ds, output, "login");
514 return;
515 }
516 if(disorder_register(ds->g->client, username, password, email, &confirm)) {
517 cgi_set_option("error", "cannotregister");
518 expand_template(ds, output, "login");
519 return;
520 }
521 /* We'll go back to the login page with a suitable message */
522 cgi_set_option("registered", "registeredok");
523 expand_template(ds, output, "login");
524}
525
460b9539 526static const struct action {
527 const char *name;
528 void (*handler)(cgi_sink *output, dcgi_state *ds);
529} actions[] = {
530 { "disable", act_disable },
531 { "enable", act_enable },
fdf98378 532 { "login", act_login },
b42ffad6 533 { "logout", act_logout },
460b9539 534 { "move", act_move },
535 { "pause", act_pause },
536 { "play", act_play },
537 { "playing", act_playing },
538 { "prefs", act_prefs },
539 { "random-disable", act_random_disable },
540 { "random-enable", act_random_enable },
fdf98378 541 { "register", act_register },
460b9539 542 { "remove", act_remove },
543 { "resume", act_resume },
544 { "scratch", act_scratch },
545 { "volume", act_volume },
546};
547
548/* expansions *****************************************************************/
549
550static void exp_include(int attribute((unused)) nargs,
551 char **args,
552 cgi_sink *output,
553 void *u) {
554 expand(output, args[0], u);
555}
556
557static void exp_server_version(int attribute((unused)) nargs,
558 char attribute((unused)) **args,
559 cgi_sink *output,
560 void *u) {
561 dcgi_state *ds = u;
562 const char *v;
563
564 if(ds->g->client) {
565 if(disorder_version(ds->g->client, (char **)&v)) v = "(cannot get version)";
566 } else
567 v = "(server not running)";
568 cgi_output(output, "%s", v);
569}
570
571static void exp_version(int attribute((unused)) nargs,
572 char attribute((unused)) **args,
573 cgi_sink *output,
574 void attribute((unused)) *u) {
a05e4467 575 cgi_output(output, "%s", disorder_short_version_string);
460b9539 576}
577
578static void exp_nonce(int attribute((unused)) nargs,
579 char attribute((unused)) **args,
580 cgi_sink *output,
581 void attribute((unused)) *u) {
582 cgi_output(output, "%s", nonce());
583}
584
585static void exp_label(int attribute((unused)) nargs,
586 char **args,
587 cgi_sink *output,
588 void attribute((unused)) *u) {
589 cgi_output(output, "%s", cgi_label(args[0]));
590}
591
592struct trackinfo_state {
593 dcgi_state *ds;
594 const struct queue_entry *q;
595 long length;
596 time_t when;
597};
598
599static void exp_who(int attribute((unused)) nargs,
600 char attribute((unused)) **args,
601 cgi_sink *output,
602 void *u) {
603 dcgi_state *ds = u;
604
605 if(ds->track && ds->track->submitter)
606 cgi_output(output, "%s", ds->track->submitter);
607}
608
609static void exp_length(int attribute((unused)) nargs,
610 char attribute((unused)) **args,
611 cgi_sink *output,
612 void *u) {
613 dcgi_state *ds = u;
78efa64e 614 long length = 0;
460b9539 615
616 if(ds->track
617 && (ds->track->state == playing_started
618 || ds->track->state == playing_paused)
619 && ds->track->sofar >= 0)
620 cgi_output(output, "%ld:%02ld/",
621 ds->track->sofar / 60, ds->track->sofar % 60);
78efa64e
RK
622 length = 0;
623 if(ds->track)
624 disorder_length(ds->g->client, ds->track->track, &length);
625 else if(ds->tracks)
626 disorder_length(ds->g->client, ds->tracks[0], &length);
460b9539 627 if(length)
628 cgi_output(output, "%ld:%02ld", length / 60, length % 60);
629 else
630 sink_printf(output->sink, "%s", "&nbsp;");
631}
632
633static void exp_when(int attribute((unused)) nargs,
634 char attribute((unused)) **args,
635 cgi_sink *output,
636 void *u) {
637 dcgi_state *ds = u;
638 const struct tm *w = 0;
639
640 if(ds->track)
641 switch(ds->track->state) {
642 case playing_isscratch:
643 case playing_unplayed:
644 case playing_random:
645 if(ds->track->expected)
646 w = localtime(&ds->track->expected);
647 break;
648 case playing_failed:
649 case playing_no_player:
650 case playing_ok:
651 case playing_scratched:
652 case playing_started:
653 case playing_paused:
654 case playing_quitting:
655 if(ds->track->played)
656 w = localtime(&ds->track->played);
657 break;
658 }
659 if(w)
660 cgi_output(output, "%d:%02d", w->tm_hour, w->tm_min);
661 else
662 sink_printf(output->sink, "&nbsp;");
663}
664
665static void exp_part(int nargs,
666 char **args,
667 cgi_sink *output,
668 void *u) {
669 dcgi_state *ds = u;
670 const char *s, *track, *part, *context;
671
672 if(nargs == 3)
673 track = args[2];
674 else {
675 if(ds->track)
676 track = ds->track->track;
677 else if(ds->tracks)
678 track = ds->tracks[0];
679 else
680 track = 0;
681 }
682 if(track) {
683 switch(nargs) {
684 case 1:
685 context = "display";
686 part = args[0];
687 break;
688 case 2:
689 case 3:
690 context = args[0];
691 part = args[1];
692 break;
693 default:
694 abort();
695 }
61507e3c
RK
696 if(disorder_part(ds->g->client, (char **)&s, track,
697 !strcmp(context, "short") ? "display" : context, part))
460b9539 698 fatal(0, "disorder_part() failed");
61507e3c
RK
699 if(!strcmp(context, "short"))
700 s = truncate_for_display(s, config->short_display);
460b9539 701 cgi_output(output, "%s", s);
702 } else
703 sink_printf(output->sink, "&nbsp;");
704}
705
706static void exp_playing(int attribute((unused)) nargs,
707 char **args,
708 cgi_sink *output,
709 void *u) {
710 dcgi_state *ds = u;
711 dcgi_state s;
712
713 lookups(ds, DC_PLAYING);
714 memset(&s, 0, sizeof s);
715 s.g = ds->g;
716 if(ds->g->playing) {
717 s.track = ds->g->playing;
718 expandstring(output, args[0], &s);
719 }
720}
721
722static void exp_queue(int attribute((unused)) nargs,
723 char **args,
724 cgi_sink *output,
725 void *u) {
726 dcgi_state *ds = u;
727 dcgi_state s;
728 struct queue_entry *q;
729
730 lookups(ds, DC_QUEUE);
731 memset(&s, 0, sizeof s);
732 s.g = ds->g;
733 s.first = 1;
734 for(q = ds->g->queue; q; q = q->next) {
735 s.last = !q->next;
736 s.track = q;
737 expandstring(output, args[0], &s);
738 s.index++;
739 s.first = 0;
740 }
741}
742
743static void exp_recent(int attribute((unused)) nargs,
744 char **args,
745 cgi_sink *output,
746 void *u) {
747 dcgi_state *ds = u;
748 dcgi_state s;
749 struct queue_entry *q;
750
751 lookups(ds, DC_RECENT);
752 memset(&s, 0, sizeof s);
753 s.g = ds->g;
754 s.first = 1;
755 for(q = ds->g->recent; q; q = q->next) {
756 s.last = !q;
757 s.track = q;
758 expandstring(output, args[0], &s);
759 s.index++;
760 s.first = 0;
761 }
762}
763
78efa64e
RK
764static void exp_new(int attribute((unused)) nargs,
765 char **args,
766 cgi_sink *output,
767 void *u) {
768 dcgi_state *ds = u;
769 dcgi_state s;
770
771 lookups(ds, DC_NEW);
772 memset(&s, 0, sizeof s);
773 s.g = ds->g;
774 s.first = 1;
775 for(s.index = 0; s.index < ds->g->nnew; ++s.index) {
776 s.last = s.index + 1 < ds->g->nnew;
777 s.tracks = &ds->g->new[s.index];
778 expandstring(output, args[0], &s);
779 s.first = 0;
780 }
781}
782
460b9539 783static void exp_url(int attribute((unused)) nargs,
784 char attribute((unused)) **args,
785 cgi_sink *output,
786 void attribute((unused)) *u) {
787 cgi_output(output, "%s", config->url);
788}
789
790struct result {
791 char *track;
792 const char *sort;
793};
794
795static int compare_result(const void *a, const void *b) {
796 const struct result *ra = a, *rb = b;
797 int c;
798
799 if(!(c = strcmp(ra->sort, rb->sort)))
800 c = strcmp(ra->track, rb->track);
801 return c;
802}
803
804static void exp_search(int nargs,
805 char **args,
806 cgi_sink *output,
807 void *u) {
808 dcgi_state *ds = u, substate;
809 char **tracks;
810 const char *q, *context, *part, *template;
811 int ntracks, n, m;
812 struct result *r;
813
814 switch(nargs) {
815 case 2:
816 part = args[0];
817 context = "sort";
818 template = args[1];
819 break;
820 case 3:
821 part = args[0];
822 context = args[1];
823 template = args[2];
824 break;
825 default:
826 assert(!"should never happen");
827 part = context = template = 0; /* quieten compiler */
828 }
829 if(ds->tracks == 0) {
830 /* we are the top level, let's get some search results */
831 if(!(q = cgi_get("query"))) return; /* no results yet */
832 if(disorder_search(ds->g->client, q, &tracks, &ntracks)) return;
833 if(!ntracks) return;
834 } else {
835 tracks = ds->tracks;
836 ntracks = ds->ntracks;
837 }
838 assert(ntracks != 0);
839 /* sort tracks by the appropriate part */
840 r = xmalloc(ntracks * sizeof *r);
841 for(n = 0; n < ntracks; ++n) {
842 r[n].track = tracks[n];
843 if(disorder_part(ds->g->client, (char **)&r[n].sort,
844 tracks[n], context, part))
845 fatal(0, "disorder_part() failed");
846 }
847 qsort(r, ntracks, sizeof (struct result), compare_result);
848 /* expand the 2nd arg once for each group. We re-use the passed-in tracks
849 * array as we know it's guaranteed to be big enough and isn't going to be
850 * used for anything else any more. */
851 memset(&substate, 0, sizeof substate);
852 substate.g = ds->g;
853 substate.first = 1;
854 n = 0;
855 while(n < ntracks) {
856 substate.tracks = tracks;
857 substate.ntracks = 0;
858 m = n;
859 while(m < ntracks
860 && !strcmp(r[m].sort, r[n].sort))
861 tracks[substate.ntracks++] = r[m++].track;
862 substate.last = (m == ntracks);
863 expandstring(output, template, &substate);
864 substate.index++;
865 substate.first = 0;
866 n = m;
867 }
868 assert(substate.last != 0);
869}
870
871static void exp_arg(int attribute((unused)) nargs,
872 char **args,
873 cgi_sink *output,
874 void attribute((unused)) *u) {
875 const char *v;
876
877 if((v = cgi_get(args[0])))
878 cgi_output(output, "%s", v);
879}
880
881static void exp_stats(int attribute((unused)) nargs,
882 char attribute((unused)) **args,
883 cgi_sink *output,
884 void *u) {
885 dcgi_state *ds = u;
886 char **v;
887
888 cgi_opentag(output->sink, "pre", "class", "stats", (char *)0);
889 if(!disorder_stats(ds->g->client, &v, 0)) {
890 while(*v)
891 cgi_output(output, "%s\n", *v++);
892 }
893 cgi_closetag(output->sink, "pre");
894}
895
896static void exp_volume(int attribute((unused)) nargs,
897 char **args,
898 cgi_sink *output,
899 void *u) {
900 dcgi_state *ds = u;
901
902 lookups(ds, DC_VOLUME);
903 if(!strcmp(args[0], "left"))
904 cgi_output(output, "%d", ds->g->volume_left);
905 else
906 cgi_output(output, "%d", ds->g->volume_right);
907}
908
909static void exp_shell(int attribute((unused)) nargs,
910 char **args,
911 cgi_sink *output,
912 void attribute((unused)) *u) {
913 int w, p[2], n;
914 char buffer[4096];
915 pid_t pid;
916
917 xpipe(p);
918 if(!(pid = xfork())) {
919 exitfn = _exit;
920 xclose(p[0]);
921 xdup2(p[1], 1);
922 xclose(p[1]);
923 execlp("sh", "sh", "-c", args[0], (char *)0);
924 fatal(errno, "error executing sh");
925 }
926 xclose(p[1]);
927 while((n = read(p[0], buffer, sizeof buffer))) {
928 if(n < 0) {
929 if(errno == EINTR) continue;
930 else fatal(errno, "error reading from pipe");
931 }
932 output->sink->write(output->sink, buffer, n);
933 }
934 xclose(p[0]);
935 while((n = waitpid(pid, &w, 0)) < 0 && errno == EINTR)
936 ;
937 if(n < 0) fatal(errno, "error calling waitpid");
938 if(w)
939 error(0, "shell command '%s' %s", args[0], wstat(w));
940}
941
942static inline int str2bool(const char *s) {
943 return !strcmp(s, "true");
944}
945
946static inline const char *bool2str(int n) {
947 return n ? "true" : "false";
948}
949
950static char *expandarg(const char *arg, dcgi_state *ds) {
951 struct dynstr d;
952 cgi_sink output;
953
954 dynstr_init(&d);
955 output.quote = 0;
956 output.sink = sink_dynstr(&d);
957 expandstring(&output, arg, ds);
958 dynstr_terminate(&d);
959 return d.vec;
960}
961
962static void exp_prefs(int attribute((unused)) nargs,
963 char **args,
964 cgi_sink *output,
965 void *u) {
966 dcgi_state *ds = u;
967 dcgi_state substate;
968 struct kvp *k;
969 const char *file = expandarg(args[0], ds);
970
971 memset(&substate, 0, sizeof substate);
972 substate.g = ds->g;
973 substate.first = 1;
974 if(disorder_prefs(ds->g->client, file, &k)) return;
975 while(k) {
976 substate.last = !k->next;
977 substate.pref = k;
978 expandstring(output, args[1], &substate);
979 ++substate.index;
980 k = k->next;
981 substate.first = 0;
982 }
983}
984
985static void exp_pref(int attribute((unused)) nargs,
986 char **args,
987 cgi_sink *output,
988 void *u) {
989 char *value;
990 dcgi_state *ds = u;
991
992 if(!disorder_get(ds->g->client, args[0], args[1], &value))
993 cgi_output(output, "%s", value);
994}
995
996static void exp_if(int nargs,
997 char **args,
998 cgi_sink *output,
999 void *u) {
1000 dcgi_state *ds = u;
1001 int n = str2bool(expandarg(args[0], ds)) ? 1 : 2;
1002
1003 if(n < nargs)
1004 expandstring(output, args[n], ds);
1005}
1006
1007static void exp_and(int nargs,
1008 char **args,
1009 cgi_sink *output,
1010 void *u) {
1011 dcgi_state *ds = u;
1012 int n, result = 1;
1013
1014 for(n = 0; n < nargs; ++n)
1015 if(!str2bool(expandarg(args[n], ds))) {
1016 result = 0;
1017 break;
1018 }
1019 sink_printf(output->sink, "%s", bool2str(result));
1020}
1021
1022static void exp_or(int nargs,
1023 char **args,
1024 cgi_sink *output,
1025 void *u) {
1026 dcgi_state *ds = u;
1027 int n, result = 0;
1028
1029 for(n = 0; n < nargs; ++n)
1030 if(str2bool(expandarg(args[n], ds))) {
1031 result = 1;
1032 break;
1033 }
1034 sink_printf(output->sink, "%s", bool2str(result));
1035}
1036
1037static void exp_not(int attribute((unused)) nargs,
1038 char **args,
1039 cgi_sink *output,
1040 void attribute((unused)) *u) {
1041 sink_printf(output->sink, "%s", bool2str(!str2bool(args[0])));
1042}
1043
1044static void exp_isplaying(int attribute((unused)) nargs,
1045 char attribute((unused)) **args,
1046 cgi_sink *output,
1047 void *u) {
1048 dcgi_state *ds = u;
1049
1050 lookups(ds, DC_PLAYING);
1051 sink_printf(output->sink, "%s", bool2str(!!ds->g->playing));
1052}
1053
1054static void exp_isqueue(int attribute((unused)) nargs,
1055 char attribute((unused)) **args,
1056 cgi_sink *output,
1057 void *u) {
1058 dcgi_state *ds = u;
1059
1060 lookups(ds, DC_QUEUE);
1061 sink_printf(output->sink, "%s", bool2str(!!ds->g->queue));
1062}
1063
1064static void exp_isrecent(int attribute((unused)) nargs,
1065 char attribute((unused)) **args,
1066 cgi_sink *output,
1067 void *u) {
1068 dcgi_state *ds = u;
1069
1070 lookups(ds, DC_RECENT);
1071 sink_printf(output->sink, "%s", bool2str(!!ds->g->recent));
1072}
1073
78efa64e
RK
1074static void exp_isnew(int attribute((unused)) nargs,
1075 char attribute((unused)) **args,
1076 cgi_sink *output,
1077 void *u) {
1078 dcgi_state *ds = u;
1079
1080 lookups(ds, DC_NEW);
1081 sink_printf(output->sink, "%s", bool2str(!!ds->g->nnew));
1082}
1083
460b9539 1084static void exp_id(int attribute((unused)) nargs,
1085 char attribute((unused)) **args,
1086 cgi_sink *output,
1087 void *u) {
1088 dcgi_state *ds = u;
1089
1090 if(ds->track)
1091 cgi_output(output, "%s", ds->track->id);
1092}
1093
1094static void exp_track(int attribute((unused)) nargs,
1095 char attribute((unused)) **args,
1096 cgi_sink *output,
1097 void *u) {
1098 dcgi_state *ds = u;
1099
1100 if(ds->track)
1101 cgi_output(output, "%s", ds->track->track);
1102}
1103
1104static void exp_parity(int attribute((unused)) nargs,
1105 char attribute((unused)) **args,
1106 cgi_sink *output,
1107 void *u) {
1108 dcgi_state *ds = u;
1109
1110 cgi_output(output, "%s", ds->index % 2 ? "odd" : "even");
1111}
1112
1113static void exp_comment(int attribute((unused)) nargs,
1114 char attribute((unused)) **args,
1115 cgi_sink attribute((unused)) *output,
1116 void attribute((unused)) *u) {
1117 /* do nothing */
1118}
1119
1120static void exp_prefname(int attribute((unused)) nargs,
1121 char attribute((unused)) **args,
1122 cgi_sink *output,
1123 void *u) {
1124 dcgi_state *ds = u;
1125
1126 if(ds->pref && ds->pref->name)
1127 cgi_output(output, "%s", ds->pref->name);
1128}
1129
1130static void exp_prefvalue(int attribute((unused)) nargs,
1131 char attribute((unused)) **args,
1132 cgi_sink *output,
1133 void *u) {
1134 dcgi_state *ds = u;
1135
1136 if(ds->pref && ds->pref->value)
1137 cgi_output(output, "%s", ds->pref->value);
1138}
1139
1140static void exp_isfiles(int attribute((unused)) nargs,
1141 char attribute((unused)) **args,
1142 cgi_sink *output,
1143 void *u) {
1144 dcgi_state *ds = u;
1145
1146 lookups(ds, DC_FILES);
1147 sink_printf(output->sink, "%s", bool2str(!!ds->g->nfiles));
1148}
1149
1150static void exp_isdirectories(int attribute((unused)) nargs,
1151 char attribute((unused)) **args,
1152 cgi_sink *output,
1153 void *u) {
1154 dcgi_state *ds = u;
1155
1156 lookups(ds, DC_DIRS);
1157 sink_printf(output->sink, "%s", bool2str(!!ds->g->ndirs));
1158}
1159
1160static void exp_choose(int attribute((unused)) nargs,
1161 char **args,
1162 cgi_sink *output,
1163 void *u) {
1164 dcgi_state *ds = u;
1165 dcgi_state substate;
1166 int nfiles, n;
1167 char **files;
1168 struct entry *e;
1169 const char *type, *what = expandarg(args[0], ds);
1170
1171 if(!strcmp(what, "files")) {
1172 lookups(ds, DC_FILES);
1173 files = ds->g->files;
1174 nfiles = ds->g->nfiles;
1175 type = "track";
1176 } else if(!strcmp(what, "directories")) {
1177 lookups(ds, DC_DIRS);
1178 files = ds->g->dirs;
1179 nfiles = ds->g->ndirs;
1180 type = "dir";
1181 } else {
1182 error(0, "unknown @choose@ argument '%s'", what);
1183 return;
1184 }
1185 e = xmalloc(nfiles * sizeof (struct entry));
1186 for(n = 0; n < nfiles; ++n) {
1187 e[n].path = files[n];
1188 e[n].sort = trackname_transform(type, files[n], "sort");
1189 e[n].display = trackname_transform(type, files[n], "display");
1190 }
1191 qsort(e, nfiles, sizeof (struct entry), compare_entry);
1192 memset(&substate, 0, sizeof substate);
1193 substate.g = ds->g;
1194 substate.first = 1;
1195 for(n = 0; n < nfiles; ++n) {
1196 substate.last = (n == nfiles - 1);
1197 substate.index = n;
1198 substate.entry = &e[n];
1199 expandstring(output, args[1], &substate);
1200 substate.first = 0;
1201 }
1202}
1203
1204static void exp_file(int attribute((unused)) nargs,
1205 char attribute((unused)) **args,
1206 cgi_sink *output,
1207 void *u) {
1208 dcgi_state *ds = u;
1209
1210 if(ds->entry)
1211 cgi_output(output, "%s", ds->entry->path);
1212 else if(ds->track)
1213 cgi_output(output, "%s", ds->track->track);
1214 else if(ds->tracks)
1215 cgi_output(output, "%s", ds->tracks[0]);
1216}
1217
1218static void exp_transform(int nargs,
1219 char **args,
1220 cgi_sink *output,
1221 void attribute((unused)) *u) {
1222 const char *context = nargs > 2 ? args[2] : "display";
1223
1224 cgi_output(output, "%s", trackname_transform(args[1], args[0], context));
1225}
1226
1227static void exp_urlquote(int attribute((unused)) nargs,
1228 char **args,
1229 cgi_sink *output,
1230 void attribute((unused)) *u) {
1231 cgi_output(output, "%s", urlencodestring(args[0]));
1232}
1233
1234static void exp_scratchable(int attribute((unused)) nargs,
1235 char attribute((unused)) **args,
1236 cgi_sink *output,
1237 void attribute((unused)) *u) {
1238 dcgi_state *ds = u;
1239 int result;
1240
1241 if(config->restrictions & RESTRICT_SCRATCH) {
1242 lookups(ds, DC_PLAYING);
1243 result = (ds->g->playing
1244 && (!ds->g->playing->submitter
1245 || !strcmp(ds->g->playing->submitter,
1246 disorder_user(ds->g->client))));
1247 } else
1248 result = 1;
1249 sink_printf(output->sink, "%s", bool2str(result));
1250}
1251
1252static void exp_removable(int attribute((unused)) nargs,
1253 char attribute((unused)) **args,
1254 cgi_sink *output,
1255 void attribute((unused)) *u) {
1256 dcgi_state *ds = u;
1257 int result;
1258
1259 if(config->restrictions & RESTRICT_REMOVE)
1260 result = (ds->track
1261 && ds->track->submitter
1262 && !strcmp(ds->track->submitter,
1263 disorder_user(ds->g->client)));
1264 else
1265 result = 1;
1266 sink_printf(output->sink, "%s", bool2str(result));
1267}
1268
1269static void exp_navigate(int attribute((unused)) nargs,
1270 char **args,
1271 cgi_sink *output,
1272 void *u) {
1273 dcgi_state *ds = u;
1274 dcgi_state substate;
1275 const char *path = expandarg(args[0], ds);
1276 const char *ptr;
1277 int dirlen;
1278
1279 if(*path) {
1280 memset(&substate, 0, sizeof substate);
1281 substate.g = ds->g;
1282 ptr = path + 1; /* skip root */
1283 dirlen = 0;
1284 substate.nav_path = path;
1285 substate.first = 1;
1286 while(*ptr) {
1287 while(*ptr && *ptr != '/')
1288 ++ptr;
1289 substate.last = !*ptr;
1290 substate.nav_len = ptr - path;
1291 substate.nav_dirlen = dirlen;
1292 expandstring(output, args[1], &substate);
1293 dirlen = substate.nav_len;
1294 if(*ptr) ++ptr;
1295 substate.first = 0;
1296 }
1297 }
1298}
1299
1300static void exp_fullname(int attribute((unused)) nargs,
1301 char attribute((unused)) **args,
1302 cgi_sink *output,
1303 void *u) {
1304 dcgi_state *ds = u;
1305 cgi_output(output, "%.*s", ds->nav_len, ds->nav_path);
1306}
1307
1308static void exp_basename(int nargs,
1309 char **args,
1310 cgi_sink *output,
1311 void *u) {
1312 dcgi_state *ds = u;
1313 const char *s;
1314
1315 if(nargs) {
1316 if((s = strrchr(args[0], '/'))) ++s;
1317 else s = args[0];
1318 cgi_output(output, "%s", s);
1319 } else
1320 cgi_output(output, "%.*s", ds->nav_len - ds->nav_dirlen - 1,
1321 ds->nav_path + ds->nav_dirlen + 1);
1322}
1323
1324static void exp_dirname(int nargs,
1325 char **args,
1326 cgi_sink *output,
1327 void *u) {
1328 dcgi_state *ds = u;
1329 const char *s;
1330
1331 if(nargs) {
1332 if((s = strrchr(args[0], '/')))
1333 cgi_output(output, "%.*s", (int)(s - args[0]), args[0]);
1334 } else
1335 cgi_output(output, "%.*s", ds->nav_dirlen, ds->nav_path);
1336}
1337
1338static void exp_eq(int attribute((unused)) nargs,
1339 char **args,
1340 cgi_sink *output,
1341 void attribute((unused)) *u) {
1342 cgi_output(output, "%s", bool2str(!strcmp(args[0], args[1])));
1343}
1344
1345static void exp_ne(int attribute((unused)) nargs,
1346 char **args,
1347 cgi_sink *output,
1348 void attribute((unused)) *u) {
1349 cgi_output(output, "%s", bool2str(strcmp(args[0], args[1])));
1350}
1351
1352static void exp_enabled(int attribute((unused)) nargs,
1353 char attribute((unused)) **args,
1354 cgi_sink *output,
1355 void *u) {
1356 dcgi_state *ds = u;
1357 int enabled = 0;
1358
1359 if(ds->g->client)
1360 disorder_enabled(ds->g->client, &enabled);
1361 cgi_output(output, "%s", bool2str(enabled));
1362}
1363
1364static void exp_random_enabled(int attribute((unused)) nargs,
1365 char attribute((unused)) **args,
1366 cgi_sink *output,
1367 void *u) {
1368 dcgi_state *ds = u;
1369 int enabled = 0;
1370
1371 if(ds->g->client)
1372 disorder_random_enabled(ds->g->client, &enabled);
1373 cgi_output(output, "%s", bool2str(enabled));
1374}
1375
1376static void exp_trackstate(int attribute((unused)) nargs,
1377 char **args,
1378 cgi_sink *output,
1379 void *u) {
1380 dcgi_state *ds = u;
1381 struct queue_entry *q;
1382 char *track;
1383
1384 if(disorder_resolve(ds->g->client, &track, args[0])) return;
1385 lookups(ds, DC_QUEUE|DC_PLAYING);
1386 if(ds->g->playing && !strcmp(ds->g->playing->track, track))
1387 cgi_output(output, "playing");
1388 else {
1389 for(q = ds->g->queue; q && strcmp(q->track, track); q = q->next)
1390 ;
1391 if(q)
1392 cgi_output(output, "queued");
1393 }
1394}
1395
1396static void exp_thisurl(int attribute((unused)) nargs,
1397 char attribute((unused)) **args,
1398 cgi_sink *output,
1399 void attribute((unused)) *u) {
1400 kvp_set(&cgi_args, "nonce", nonce()); /* nonces had better differ! */
1401 cgi_output(output, "%s?%s", config->url, kvp_urlencode(cgi_args, 0));
1402}
1403
1404static void exp_isfirst(int attribute((unused)) nargs,
1405 char attribute((unused)) **args,
1406 cgi_sink *output,
1407 void *u) {
1408 dcgi_state *ds = u;
1409
1410 sink_printf(output->sink, "%s", bool2str(!!ds->first));
1411}
1412
1413static void exp_islast(int attribute((unused)) nargs,
1414 char attribute((unused)) **args,
1415 cgi_sink *output,
1416 void *u) {
1417 dcgi_state *ds = u;
1418
1419 sink_printf(output->sink, "%s", bool2str(!!ds->last));
1420}
1421
1422static void exp_action(int attribute((unused)) nargs,
1423 char attribute((unused)) **args,
1424 cgi_sink *output,
1425 void attribute((unused)) *u) {
1426 const char *action = cgi_get("action"), *mgmt;
1427
1428 if(!action) action = "playing";
1429 if(!strcmp(action, "playing")
1430 && (mgmt = cgi_get("mgmt"))
1431 && !strcmp(mgmt, "true"))
1432 action = "manage";
1433 sink_printf(output->sink, "%s", action);
1434}
1435
1436static void exp_resolve(int attribute((unused)) nargs,
1437 char **args,
1438 cgi_sink *output,
1439 void attribute((unused)) *u) {
1440 dcgi_state *ds = u;
1441 char *track;
1442
1443 if(!disorder_resolve(ds->g->client, &track, args[0]))
1444 sink_printf(output->sink, "%s", track);
1445}
1446
1447static void exp_paused(int attribute((unused)) nargs,
1448 char attribute((unused)) **args,
1449 cgi_sink *output,
1450 void *u) {
1451 dcgi_state *ds = u;
1452 int paused = 0;
1453
1454 lookups(ds, DC_PLAYING);
1455 if(ds->g->playing && ds->g->playing->state == playing_paused)
1456 paused = 1;
1457 cgi_output(output, "%s", bool2str(paused));
1458}
1459
1460static void exp_state(int attribute((unused)) nargs,
1461 char attribute((unused)) **args,
1462 cgi_sink *output,
1463 void *u) {
1464 dcgi_state *ds = u;
1465
1466 if(ds->track)
1467 cgi_output(output, "%s", playing_states[ds->track->state]);
1468}
1469
1470static void exp_files(int attribute((unused)) nargs,
1471 char **args,
1472 cgi_sink *output,
1473 void *u) {
1474 dcgi_state *ds = u;
1475 dcgi_state substate;
1476 const char *nfiles_arg, *directory;
1477 int nfiles, numfile;
1478 struct kvp *k;
1479
1480 memset(&substate, 0, sizeof substate);
1481 substate.g = ds->g;
1482 if((directory = cgi_get("directory"))) {
1483 /* Prefs for whole directory. */
1484 lookups(ds, DC_FILES);
1485 /* Synthesize args for the file list. */
1486 nfiles = ds->g->nfiles;
1487 for(numfile = 0; numfile < nfiles; ++numfile) {
1488 k = xmalloc(sizeof *k);
1489 byte_xasprintf((char **)&k->name, "%d_file", numfile);
1490 k->value = ds->g->files[numfile];
1491 k->next = cgi_args;
1492 cgi_args = k;
1493 }
1494 } else {
1495 /* Args already present. */
1496 if((nfiles_arg = cgi_get("files"))) nfiles = atoi(nfiles_arg);
1497 else nfiles = 1;
1498 }
1499 for(numfile = 0; numfile < nfiles; ++numfile) {
1500 substate.index = numfile;
1501 expandstring(output, args[0], &substate);
1502 }
1503}
1504
1505static void exp_index(int attribute((unused)) nargs,
1506 char attribute((unused)) **args,
1507 cgi_sink *output,
1508 void *u) {
1509 dcgi_state *ds = u;
1510
1511 cgi_output(output, "%d", ds->index);
1512}
1513
1514static void exp_nfiles(int attribute((unused)) nargs,
1515 char attribute((unused)) **args,
1516 cgi_sink *output,
1517 void *u) {
1518 dcgi_state *ds = u;
1519 const char *files_arg;
1520
1521 if(cgi_get("directory")) {
1522 lookups(ds, DC_FILES);
1523 cgi_output(output, "%d", ds->g->nfiles);
1524 } else if((files_arg = cgi_get("files")))
1525 cgi_output(output, "%s", files_arg);
1526 else
1527 cgi_output(output, "1");
1528}
1529
fdf98378 1530static void exp_user(int attribute((unused)) nargs,
1531 char attribute((unused)) **args,
1532 cgi_sink *output,
1533 void *u) {
1534 dcgi_state *const ds = u;
1535
1536 cgi_output(output, "%s", disorder_user(ds->g->client));
1537}
1538
460b9539 1539static const struct cgi_expansion expansions[] = {
1540 { "#", 0, INT_MAX, EXP_MAGIC, exp_comment },
1541 { "action", 0, 0, 0, exp_action },
1542 { "and", 0, INT_MAX, EXP_MAGIC, exp_and },
1543 { "arg", 1, 1, 0, exp_arg },
1544 { "basename", 0, 1, 0, exp_basename },
1545 { "choose", 2, 2, EXP_MAGIC, exp_choose },
1546 { "dirname", 0, 1, 0, exp_dirname },
1547 { "enabled", 0, 0, 0, exp_enabled },
1548 { "eq", 2, 2, 0, exp_eq },
1549 { "file", 0, 0, 0, exp_file },
1550 { "files", 1, 1, EXP_MAGIC, exp_files },
1551 { "fullname", 0, 0, 0, exp_fullname },
1552 { "id", 0, 0, 0, exp_id },
1553 { "if", 2, 3, EXP_MAGIC, exp_if },
1554 { "include", 1, 1, 0, exp_include },
1555 { "index", 0, 0, 0, exp_index },
1556 { "isdirectories", 0, 0, 0, exp_isdirectories },
1557 { "isfiles", 0, 0, 0, exp_isfiles },
1558 { "isfirst", 0, 0, 0, exp_isfirst },
1559 { "islast", 0, 0, 0, exp_islast },
78efa64e 1560 { "isnew", 0, 0, 0, exp_isnew },
460b9539 1561 { "isplaying", 0, 0, 0, exp_isplaying },
1562 { "isqueue", 0, 0, 0, exp_isqueue },
1563 { "isrecent", 0, 0, 0, exp_isrecent },
1564 { "label", 1, 1, 0, exp_label },
1565 { "length", 0, 0, 0, exp_length },
1566 { "navigate", 2, 2, EXP_MAGIC, exp_navigate },
1567 { "ne", 2, 2, 0, exp_ne },
78efa64e 1568 { "new", 1, 1, EXP_MAGIC, exp_new },
460b9539 1569 { "nfiles", 0, 0, 0, exp_nfiles },
1570 { "nonce", 0, 0, 0, exp_nonce },
1571 { "not", 1, 1, 0, exp_not },
1572 { "or", 0, INT_MAX, EXP_MAGIC, exp_or },
1573 { "parity", 0, 0, 0, exp_parity },
1574 { "part", 1, 3, 0, exp_part },
1575 { "paused", 0, 0, 0, exp_paused },
1576 { "playing", 1, 1, EXP_MAGIC, exp_playing },
1577 { "pref", 2, 2, 0, exp_pref },
1578 { "prefname", 0, 0, 0, exp_prefname },
1579 { "prefs", 2, 2, EXP_MAGIC, exp_prefs },
1580 { "prefvalue", 0, 0, 0, exp_prefvalue },
1581 { "queue", 1, 1, EXP_MAGIC, exp_queue },
1582 { "random-enabled", 0, 0, 0, exp_random_enabled },
1583 { "recent", 1, 1, EXP_MAGIC, exp_recent },
1584 { "removable", 0, 0, 0, exp_removable },
1585 { "resolve", 1, 1, 0, exp_resolve },
1586 { "scratchable", 0, 0, 0, exp_scratchable },
1587 { "search", 2, 3, EXP_MAGIC, exp_search },
1588 { "server-version", 0, 0, 0, exp_server_version },
1589 { "shell", 1, 1, 0, exp_shell },
1590 { "state", 0, 0, 0, exp_state },
1591 { "stats", 0, 0, 0, exp_stats },
1592 { "thisurl", 0, 0, 0, exp_thisurl },
1593 { "track", 0, 0, 0, exp_track },
1594 { "trackstate", 1, 1, 0, exp_trackstate },
1595 { "transform", 2, 3, 0, exp_transform },
1596 { "url", 0, 0, 0, exp_url },
1597 { "urlquote", 1, 1, 0, exp_urlquote },
fdf98378 1598 { "user", 0, 0, 0, exp_user },
460b9539 1599 { "version", 0, 0, 0, exp_version },
1600 { "volume", 1, 1, 0, exp_volume },
1601 { "when", 0, 0, 0, exp_when },
1602 { "who", 0, 0, 0, exp_who }
1603};
1604
1605static void expand(cgi_sink *output,
1606 const char *template,
1607 dcgi_state *ds) {
1608 cgi_expand(template,
1609 expansions, sizeof expansions / sizeof *expansions,
1610 output,
1611 ds);
1612}
1613
1614static void expandstring(cgi_sink *output,
1615 const char *string,
1616 dcgi_state *ds) {
1617 cgi_expand_string("",
1618 string,
1619 expansions, sizeof expansions / sizeof *expansions,
1620 output,
1621 ds);
1622}
1623
1624static void perform_action(cgi_sink *output, dcgi_state *ds,
1625 const char *action) {
1626 int n;
1627
fdf98378 1628 /* We don't ever want anything to be cached */
1629 cgi_header(output->sink, "Cache-Control", "no-cache");
460b9539 1630 if((n = TABLE_FIND(actions, struct action, name, action)) >= 0)
1631 actions[n].handler(output, ds);
fdf98378 1632 else
1633 expand_template(ds, output, action);
460b9539 1634}
1635
1636void disorder_cgi(cgi_sink *output, dcgi_state *ds) {
1637 const char *action = cgi_get("action");
1638
1639 if(!action) action = "playing";
1640 perform_action(output, ds, action);
1641}
1642
1643void disorder_cgi_error(cgi_sink *output, dcgi_state *ds,
1644 const char *msg) {
1645 cgi_set_option("error", msg);
1646 perform_action(output, ds, "error");
1647}
1648
1649/*
1650Local Variables:
1651c-basic-offset:2
1652comment-column:40
1653fill-column:79
1654End:
1655*/