2 * This file is part of DisOrder.
3 * Copyright (C) 2004-2008 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>
43 #include "configuration.h"
54 #include "trackname.h"
64 static void expand(cgi_sink
*output
,
67 static void expandstring(cgi_sink
*output
,
77 static const char nonce_base64_table
[] =
78 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-/*";
80 static const char *nonce(void) {
81 static uint32_t count
;
90 nd
.pid
= (uint32_t)getpid();
91 nd
.when
= (uint32_t)time(0);
92 return generic_to_base64((void *)&nd
, sizeof nd
,
96 static int compare_entry(const void *a
, const void *b
) {
97 const struct entry
*ea
= a
, *eb
= b
;
99 return compare_tracks(ea
->sort
, eb
->sort
,
100 ea
->display
, eb
->display
,
104 static const char *front_url(void) {
108 /* preserve management interface visibility */
109 if((mgmt
= cgi_get("mgmt")) && !strcmp(mgmt
, "true")) {
110 byte_xasprintf(&url
, "%s?mgmt=true", config
->url
);
116 static void header_cookie(struct sink
*output
) {
120 memset(&u
, 0, sizeof u
);
122 parse_url(config
->url
, &u
);
124 dynstr_append_string(d
, "disorder=");
125 dynstr_append_string(d
, login_cookie
);
127 /* Force browser to discard cookie */
128 dynstr_append_string(d
, "disorder=none;Max-Age=0");
131 /* The default domain matches the request host, so we need not override
132 * that. But the default path only goes up to the rightmost /, which would
133 * cause the browser to expose the cookie to other CGI programs on the same
135 dynstr_append_string(d
, ";Version=1;Path=");
136 /* Formally we are supposed to quote the path, since it invariably has a
137 * slash in it. However Safari does not parse quoted paths correctly, so
138 * this won't work. Fortunately nothing else seems to care about proper
139 * quoting of paths, so in practice we get with it. (See also
140 * parse_cookie() where we are liberal about cookie paths on the way back
142 dynstr_append_string(d
, u
.path
);
145 cgi_header(output
, "Set-Cookie", d
->vec
);
148 static void redirect(struct sink
*output
) {
151 back
= cgi_get("back");
152 cgi_header(output
, "Location", back
&& *back ? back
: front_url());
153 header_cookie(output
);
157 static void expand_template(dcgi_state
*ds
, cgi_sink
*output
,
158 const char *action
) {
159 cgi_header(output
->sink
, "Content-Type", "text/html");
160 header_cookie(output
->sink
);
161 cgi_body(output
->sink
);
162 expand(output
, action
, ds
);
165 static void lookups(dcgi_state
*ds
, unsigned want
) {
167 struct queue_entry
*r
, *rnext
;
168 const char *dir
, *re
;
171 if(ds
->g
->client
&& (need
= want
^ (ds
->g
->flags
& want
)) != 0) {
173 disorder_queue(ds
->g
->client
, &ds
->g
->queue
);
174 if(need
& DC_PLAYING
)
175 disorder_playing(ds
->g
->client
, &ds
->g
->playing
);
177 disorder_new_tracks(ds
->g
->client
, &ds
->g
->new, &ds
->g
->nnew
, 0);
178 if(need
& DC_RECENT
) {
179 /* we need to reverse the order of the list */
180 disorder_recent(ds
->g
->client
, &r
);
183 r
->next
= ds
->g
->recent
;
189 disorder_get_volume(ds
->g
->client
,
190 &ds
->g
->volume_left
, &ds
->g
->volume_right
);
191 if(need
& (DC_FILES
|DC_DIRS
)) {
192 if(!(dir
= cgi_get("directory")))
194 re
= cgi_get("regexp");
196 if(disorder_directories(ds
->g
->client
, dir
, re
,
197 &ds
->g
->dirs
, &ds
->g
->ndirs
))
200 if(disorder_files(ds
->g
->client
, dir
, re
,
201 &ds
->g
->files
, &ds
->g
->nfiles
))
204 if(need
& DC_RIGHTS
) {
205 ds
->g
->rights
= RIGHT_READ
; /* fail-safe */
206 if(!disorder_userinfo(ds
->g
->client
, disorder_user(ds
->g
->client
),
208 parse_rights(rights
, &ds
->g
->rights
, 1);
210 ds
->g
->flags
|= need
;
214 /* actions ********************************************************************/
216 static void act_disable(cgi_sink
*output
,
219 disorder_disable(ds
->g
->client
);
220 redirect(output
->sink
);
223 static void act_enable(cgi_sink
*output
,
226 disorder_enable(ds
->g
->client
);
227 redirect(output
->sink
);
230 static void act_random_disable(cgi_sink
*output
,
233 disorder_random_disable(ds
->g
->client
);
234 redirect(output
->sink
);
237 static void act_random_enable(cgi_sink
*output
,
240 disorder_random_enable(ds
->g
->client
);
241 redirect(output
->sink
);
244 static void act_remove(cgi_sink
*output
,
248 if(!(id
= cgi_get("id"))) fatal(0, "missing id argument");
250 disorder_remove(ds
->g
->client
, id
);
251 redirect(output
->sink
);
254 static void act_move(cgi_sink
*output
,
256 const char *id
, *delta
;
258 if(!(id
= cgi_get("id"))) fatal(0, "missing id argument");
259 if(!(delta
= cgi_get("delta"))) fatal(0, "missing delta argument");
261 disorder_move(ds
->g
->client
, id
, atoi(delta
));
262 redirect(output
->sink
);
265 static void act_scratch(cgi_sink
*output
,
268 disorder_scratch(ds
->g
->client
, cgi_get("id"));
269 redirect(output
->sink
);
272 static void act_playing(cgi_sink
*output
, dcgi_state
*ds
) {
274 long refresh
= config
->refresh
, length
;
276 int random_enabled
= 0;
279 lookups(ds
, DC_PLAYING
|DC_QUEUE
);
280 cgi_header(output
->sink
, "Content-Type", "text/html");
281 disorder_random_enabled(ds
->g
->client
, &random_enabled
);
282 disorder_enabled(ds
->g
->client
, &enabled
);
284 && ds
->g
->playing
->state
== playing_started
/* i.e. not paused */
285 && !disorder_length(ds
->g
->client
, ds
->g
->playing
->track
, &length
)
287 && ds
->g
->playing
->sofar
>= 0) {
288 /* Try to put the next refresh at the start of the next track. */
290 fin
= now
+ length
- ds
->g
->playing
->sofar
+ config
->gap
;
291 if(now
+ refresh
> fin
)
294 if(ds
->g
->queue
&& ds
->g
->queue
->state
== playing_isscratch
) {
295 /* next track is a scratch, don't leave more than the inter-track gap */
296 if(refresh
> config
->gap
)
297 refresh
= config
->gap
;
299 if(!ds
->g
->playing
&& ((ds
->g
->queue
300 && ds
->g
->queue
->state
!= playing_random
)
301 || random_enabled
) && enabled
) {
302 /* no track playing but playing is enabled and there is something coming
303 * up, must be in a gap */
304 if(refresh
> config
->gap
)
305 refresh
= config
->gap
;
307 byte_snprintf(r
, sizeof r
, "%ld;url=%s", refresh
> 0 ? refresh
: 1,
309 cgi_header(output
->sink
, "Refresh", r
);
310 header_cookie(output
->sink
);
311 cgi_body(output
->sink
);
312 expand(output
, "playing", ds
);
315 static void act_play(cgi_sink
*output
,
317 const char *track
, *dir
;
322 if((track
= cgi_get("file"))) {
323 disorder_play(ds
->g
->client
, track
);
324 } else if((dir
= cgi_get("directory"))) {
325 if(disorder_files(ds
->g
->client
, dir
, 0, &tracks
, &ntracks
)) ntracks
= 0;
327 e
= xmalloc(ntracks
* sizeof (struct entry
));
328 for(n
= 0; n
< ntracks
; ++n
) {
329 e
[n
].path
= tracks
[n
];
330 e
[n
].sort
= trackname_transform("track", tracks
[n
], "sort");
331 e
[n
].display
= trackname_transform("track", tracks
[n
], "display");
333 qsort(e
, ntracks
, sizeof (struct entry
), compare_entry
);
334 for(n
= 0; n
< ntracks
; ++n
)
335 disorder_play(ds
->g
->client
, e
[n
].path
);
338 /* XXX error handling */
339 redirect(output
->sink
);
342 static int clamp(int n
, int min
, int max
) {
350 static const char *volume_url(void) {
353 byte_xasprintf(&url
, "%s?action=volume", config
->url
);
357 static void act_volume(cgi_sink
*output
, dcgi_state
*ds
) {
358 const char *l
, *r
, *d
, *back
;
359 int nd
, changed
= 0;;
361 if((d
= cgi_get("delta"))) {
362 lookups(ds
, DC_VOLUME
);
363 nd
= clamp(atoi(d
), -255, 255);
364 disorder_set_volume(ds
->g
->client
,
365 clamp(ds
->g
->volume_left
+ nd
, 0, 255),
366 clamp(ds
->g
->volume_right
+ nd
, 0, 255));
368 } else if((l
= cgi_get("left")) && (r
= cgi_get("right"))) {
369 disorder_set_volume(ds
->g
->client
, atoi(l
), atoi(r
));
373 /* redirect back to ourselves (but without the volume-changing bits in the
375 cgi_header(output
->sink
, "Location",
376 (back
= cgi_get("back")) ? back
: volume_url());
377 header_cookie(output
->sink
);
378 cgi_body(output
->sink
);
380 cgi_header(output
->sink
, "Content-Type", "text/html");
381 header_cookie(output
->sink
);
382 cgi_body(output
->sink
);
383 expand(output
, "volume", ds
);
387 static void act_prefs_errors(const char *msg
,
388 void attribute((unused
)) *u
) {
389 fatal(0, "error splitting parts list: %s", msg
);
392 static const char *numbered_arg(const char *argname
, int numfile
) {
395 byte_xasprintf(&fullname
, "%d_%s", numfile
, argname
);
396 return cgi_get(fullname
);
399 static void process_prefs(dcgi_state
*ds
, int numfile
) {
400 const char *file
, *name
, *value
, *part
, *parts
, *current
, *context
;
403 if(!(file
= numbered_arg("file", numfile
)))
404 /* The first file doesn't need numbering. */
405 if(numfile
> 0 || !(file
= cgi_get("file")))
407 if((parts
= numbered_arg("parts", numfile
))
408 || (parts
= cgi_get("parts"))) {
409 /* Default context is display. Other contexts not actually tested. */
410 if(!(context
= numbered_arg("context", numfile
))) context
= "display";
411 partslist
= split(parts
, 0, 0, act_prefs_errors
, 0);
412 while((part
= *partslist
++)) {
413 if(!(value
= numbered_arg(part
, numfile
)))
415 /* If it's already right (whether regexps or db) don't change anything,
416 * so we don't fill the database up with rubbish. */
417 if(disorder_part(ds
->g
->client
, (char **)¤t
,
418 file
, context
, part
))
419 fatal(0, "disorder_part() failed");
420 if(!strcmp(current
, value
))
422 byte_xasprintf((char **)&name
, "trackname_%s_%s", context
, part
);
423 disorder_set(ds
->g
->client
, file
, name
, value
);
425 if((value
= numbered_arg("random", numfile
)))
426 disorder_unset(ds
->g
->client
, file
, "pick_at_random");
428 disorder_set(ds
->g
->client
, file
, "pick_at_random", "0");
429 if((value
= numbered_arg("tags", numfile
)))
430 disorder_set(ds
->g
->client
, file
, "tags", value
);
431 } else if((name
= cgi_get("name"))) {
432 /* Raw preferences. Not well supported in the templates at the moment. */
433 value
= cgi_get("value");
435 disorder_set(ds
->g
->client
, file
, name
, value
);
437 disorder_unset(ds
->g
->client
, file
, name
);
441 static void act_prefs(cgi_sink
*output
, dcgi_state
*ds
) {
445 if((files
= cgi_get("files"))) nfiles
= atoi(files
);
447 for(numfile
= 0; numfile
< nfiles
; ++numfile
)
448 process_prefs(ds
, numfile
);
449 cgi_header(output
->sink
, "Content-Type", "text/html");
450 header_cookie(output
->sink
);
451 cgi_body(output
->sink
);
452 expand(output
, "prefs", ds
);
455 static void act_pause(cgi_sink
*output
,
458 disorder_pause(ds
->g
->client
);
459 redirect(output
->sink
);
462 static void act_resume(cgi_sink
*output
,
465 disorder_resume(ds
->g
->client
);
466 redirect(output
->sink
);
469 static void act_login(cgi_sink
*output
,
471 const char *username
, *password
, *back
;
474 username
= cgi_get("username");
475 password
= cgi_get("password");
476 if(!username
|| !password
477 || !strcmp(username
, "guest")/*bodge to avoid guest cookies*/) {
478 /* We're just visiting the login page */
479 expand_template(ds
, output
, "login");
482 /* We'll need a new connection as we are going to stop being guest */
484 if(disorder_connect_user(c
, username
, password
)) {
485 cgi_set_option("error", "loginfailed");
486 expand_template(ds
, output
, "login");
489 if(disorder_make_cookie(c
, &login_cookie
)) {
490 cgi_set_option("error", "cookiefailed");
491 expand_template(ds
, output
, "login");
494 /* Use the new connection henceforth */
497 /* We have a new cookie */
498 header_cookie(output
->sink
);
499 cgi_set_option("status", "loginok");
500 if((back
= cgi_get("back")) && *back
)
501 /* Redirect back to somewhere or other */
502 redirect(output
->sink
);
504 /* Stick to the login page */
505 expand_template(ds
, output
, "login");
508 static void act_logout(cgi_sink
*output
,
510 disorder_revoke(ds
->g
->client
);
512 /* Reconnect as guest */
513 disorder_cgi_login(ds
, output
);
514 /* Back to the login page */
515 cgi_set_option("status", "logoutok");
516 expand_template(ds
, output
, "login");
519 static void act_register(cgi_sink
*output
,
521 const char *username
, *password
, *password2
, *email
;
522 char *confirm
, *content_type
;
523 const char *text
, *encoding
, *charset
;
525 username
= cgi_get("username");
526 password
= cgi_get("password1");
527 password2
= cgi_get("password2");
528 email
= cgi_get("email");
530 if(!username
|| !*username
) {
531 cgi_set_option("error", "nousername");
532 expand_template(ds
, output
, "login");
535 if(!password
|| !*password
) {
536 cgi_set_option("error", "nopassword");
537 expand_template(ds
, output
, "login");
540 if(!password2
|| !*password2
|| strcmp(password
, password2
)) {
541 cgi_set_option("error", "passwordmismatch");
542 expand_template(ds
, output
, "login");
545 if(!email
|| !*email
) {
546 cgi_set_option("error", "noemail");
547 expand_template(ds
, output
, "login");
550 /* We could well do better address validation but for now we'll just do the
552 if(!strchr(email
, '@')) {
553 cgi_set_option("error", "bademail");
554 expand_template(ds
, output
, "login");
557 if(disorder_register(ds
->g
->client
, username
, password
, email
, &confirm
)) {
558 cgi_set_option("error", "cannotregister");
559 expand_template(ds
, output
, "login");
562 /* Send the user a mail */
563 /* TODO templatize this */
564 byte_xasprintf((char **)&text
,
565 "Welcome to DisOrder. To active your login, please visit this URL:\n"
567 "%s?c=%s\n", config
->url
, urlencodestring(confirm
));
568 if(!(text
= mime_encode_text(text
, &charset
, &encoding
)))
569 fatal(0, "cannot encode email");
570 byte_xasprintf(&content_type
, "text/plain;charset=%s",
571 quote822(charset
, 0));
572 sendmail("", config
->mail_sender
, email
, "Welcome to DisOrder",
573 encoding
, content_type
, text
); /* TODO error checking */
574 /* We'll go back to the login page with a suitable message */
575 cgi_set_option("status", "registered");
576 expand_template(ds
, output
, "login");
579 static void act_confirm(cgi_sink
*output
,
581 const char *confirmation
;
583 if(!(confirmation
= cgi_get("c"))) {
584 cgi_set_option("error", "noconfirm");
585 expand_template(ds
, output
, "login");
587 /* Confirm our registration */
588 if(disorder_confirm(ds
->g
->client
, confirmation
)) {
589 cgi_set_option("error", "badconfirm");
590 expand_template(ds
, output
, "login");
593 if(disorder_make_cookie(ds
->g
->client
, &login_cookie
)) {
594 cgi_set_option("error", "cookiefailed");
595 expand_template(ds
, output
, "login");
598 /* Discard any cached data JIC */
600 /* We have a new cookie */
601 header_cookie(output
->sink
);
602 cgi_set_option("status", "confirmed");
603 expand_template(ds
, output
, "login");
606 static void act_edituser(cgi_sink
*output
,
608 const char *email
= cgi_get("email"), *password
= cgi_get("changepassword1");
609 const char *password2
= cgi_get("changepassword2");
613 if((password
&& *password
) || (password
&& *password2
)) {
614 if(!password
|| !password2
|| strcmp(password
, password2
)) {
615 cgi_set_option("error", "passwordmismatch");
616 expand_template(ds
, output
, "login");
620 password
= password2
= 0;
623 if(disorder_edituser(ds
->g
->client
, disorder_user(ds
->g
->client
),
625 cgi_set_option("error", "badedit");
626 expand_template(ds
, output
, "login");
631 if(disorder_edituser(ds
->g
->client
, disorder_user(ds
->g
->client
),
632 "password", password
)) {
633 cgi_set_option("error", "badedit");
634 expand_template(ds
, output
, "login");
640 login_cookie
= 0; /* it'll be invalid now */
641 /* This is a bit duplicative of act_login() */
643 if(disorder_connect_user(c
, disorder_user(ds
->g
->client
), password
)) {
644 cgi_set_option("error", "loginfailed");
645 expand_template(ds
, output
, "login");
648 if(disorder_make_cookie(c
, &login_cookie
)) {
649 cgi_set_option("error", "cookiefailed");
650 expand_template(ds
, output
, "login");
653 /* Use the new connection henceforth */
656 /* We have a new cookie */
657 header_cookie(output
->sink
);
659 cgi_set_option("status", "edited");
660 expand_template(ds
, output
, "login");
663 static void act_reminder(cgi_sink
*output
,
665 const char *const username
= cgi_get("username");
667 if(!username
|| !*username
) {
668 cgi_set_option("error", "nousername");
669 expand_template(ds
, output
, "login");
672 if(disorder_reminder(ds
->g
->client
, username
)) {
673 cgi_set_option("error", "reminderfailed");
674 expand_template(ds
, output
, "login");
677 cgi_set_option("status", "reminded");
678 expand_template(ds
, output
, "login");
681 static const struct action
{
683 void (*handler
)(cgi_sink
*output
, dcgi_state
*ds
);
685 { "confirm", act_confirm
},
686 { "disable", act_disable
},
687 { "edituser", act_edituser
},
688 { "enable", act_enable
},
689 { "login", act_login
},
690 { "logout", act_logout
},
691 { "move", act_move
},
692 { "pause", act_pause
},
693 { "play", act_play
},
694 { "playing", act_playing
},
695 { "prefs", act_prefs
},
696 { "random-disable", act_random_disable
},
697 { "random-enable", act_random_enable
},
698 { "register", act_register
},
699 { "reminder", act_reminder
},
700 { "remove", act_remove
},
701 { "resume", act_resume
},
702 { "scratch", act_scratch
},
703 { "volume", act_volume
},
706 /* expansions *****************************************************************/
708 static void exp_include(int attribute((unused
)) nargs
,
712 expand(output
, args
[0], u
);
715 static void exp_server_version(int attribute((unused
)) nargs
,
716 char attribute((unused
)) **args
,
723 if(disorder_version(ds
->g
->client
, (char **)&v
)) v
= "(cannot get version)";
725 v
= "(server not running)";
726 cgi_output(output
, "%s", v
);
729 static void exp_version(int attribute((unused
)) nargs
,
730 char attribute((unused
)) **args
,
732 void attribute((unused
)) *u
) {
733 cgi_output(output
, "%s", disorder_short_version_string
);
736 static void exp_nonce(int attribute((unused
)) nargs
,
737 char attribute((unused
)) **args
,
739 void attribute((unused
)) *u
) {
740 cgi_output(output
, "%s", nonce());
743 static void exp_label(int attribute((unused
)) nargs
,
746 void attribute((unused
)) *u
) {
747 cgi_output(output
, "%s", cgi_label(args
[0]));
750 struct trackinfo_state
{
752 const struct queue_entry
*q
;
757 static void exp_who(int attribute((unused
)) nargs
,
758 char attribute((unused
)) **args
,
763 if(ds
->track
&& ds
->track
->submitter
)
764 cgi_output(output
, "%s", ds
->track
->submitter
);
767 static void exp_length(int attribute((unused
)) nargs
,
768 char attribute((unused
)) **args
,
775 && (ds
->track
->state
== playing_started
776 || ds
->track
->state
== playing_paused
)
777 && ds
->track
->sofar
>= 0)
778 cgi_output(output
, "%ld:%02ld/",
779 ds
->track
->sofar
/ 60, ds
->track
->sofar
% 60);
782 disorder_length(ds
->g
->client
, ds
->track
->track
, &length
);
784 disorder_length(ds
->g
->client
, ds
->tracks
[0], &length
);
786 cgi_output(output
, "%ld:%02ld", length
/ 60, length
% 60);
788 sink_printf(output
->sink
, "%s", " ");
791 static void exp_when(int attribute((unused
)) nargs
,
792 char attribute((unused
)) **args
,
796 const struct tm
*w
= 0;
799 switch(ds
->track
->state
) {
800 case playing_isscratch
:
801 case playing_unplayed
:
803 if(ds
->track
->expected
)
804 w
= localtime(&ds
->track
->expected
);
807 case playing_no_player
:
809 case playing_scratched
:
810 case playing_started
:
812 case playing_quitting
:
813 if(ds
->track
->played
)
814 w
= localtime(&ds
->track
->played
);
818 cgi_output(output
, "%d:%02d", w
->tm_hour
, w
->tm_min
);
820 sink_printf(output
->sink
, " ");
823 static void exp_part(int nargs
,
828 const char *s
, *track
, *part
, *context
;
834 track
= ds
->track
->track
;
836 track
= ds
->tracks
[0];
854 if(disorder_part(ds
->g
->client
, (char **)&s
, track
,
855 !strcmp(context
, "short") ?
"display" : context
, part
))
856 fatal(0, "disorder_part() failed");
857 if(!strcmp(context
, "short"))
858 s
= truncate_for_display(s
, config
->short_display
);
859 cgi_output(output
, "%s", s
);
861 sink_printf(output
->sink
, " ");
864 static void exp_playing(int attribute((unused
)) nargs
,
871 lookups(ds
, DC_PLAYING
);
872 memset(&s
, 0, sizeof s
);
875 s
.track
= ds
->g
->playing
;
876 expandstring(output
, args
[0], &s
);
880 static void exp_queue(int attribute((unused
)) nargs
,
886 struct queue_entry
*q
;
888 lookups(ds
, DC_QUEUE
);
889 memset(&s
, 0, sizeof s
);
892 for(q
= ds
->g
->queue
; q
; q
= q
->next
) {
895 expandstring(output
, args
[0], &s
);
901 static void exp_recent(int attribute((unused
)) nargs
,
907 struct queue_entry
*q
;
909 lookups(ds
, DC_RECENT
);
910 memset(&s
, 0, sizeof s
);
913 for(q
= ds
->g
->recent
; q
; q
= q
->next
) {
916 expandstring(output
, args
[0], &s
);
922 static void exp_new(int attribute((unused
)) nargs
,
930 memset(&s
, 0, sizeof s
);
933 for(s
.index
= 0; s
.index
< ds
->g
->nnew
; ++s
.index
) {
934 s
.last
= s
.index
+ 1 < ds
->g
->nnew
;
935 s
.tracks
= &ds
->g
->new[s
.index
];
936 expandstring(output
, args
[0], &s
);
941 static void exp_url(int attribute((unused
)) nargs
,
942 char attribute((unused
)) **args
,
944 void attribute((unused
)) *u
) {
945 cgi_output(output
, "%s", config
->url
);
953 static int compare_result(const void *a
, const void *b
) {
954 const struct result
*ra
= a
, *rb
= b
;
957 if(!(c
= strcmp(ra
->sort
, rb
->sort
)))
958 c
= strcmp(ra
->track
, rb
->track
);
962 static void exp_search(int nargs
,
966 dcgi_state
*ds
= u
, substate
;
968 const char *q
, *context
, *part
, *template;
984 assert(!"should never happen");
985 part
= context
= template = 0; /* quieten compiler */
987 if(ds
->tracks
== 0) {
988 /* we are the top level, let's get some search results */
989 if(!(q
= cgi_get("query"))) return; /* no results yet */
990 if(disorder_search(ds
->g
->client
, q
, &tracks
, &ntracks
)) return;
994 ntracks
= ds
->ntracks
;
996 assert(ntracks
!= 0);
997 /* sort tracks by the appropriate part */
998 r
= xmalloc(ntracks
* sizeof *r
);
999 for(n
= 0; n
< ntracks
; ++n
) {
1000 r
[n
].track
= tracks
[n
];
1001 if(disorder_part(ds
->g
->client
, (char **)&r
[n
].sort
,
1002 tracks
[n
], context
, part
))
1003 fatal(0, "disorder_part() failed");
1005 qsort(r
, ntracks
, sizeof (struct result
), compare_result
);
1006 /* expand the 2nd arg once for each group. We re-use the passed-in tracks
1007 * array as we know it's guaranteed to be big enough and isn't going to be
1008 * used for anything else any more. */
1009 memset(&substate
, 0, sizeof substate
);
1013 while(n
< ntracks
) {
1014 substate
.tracks
= tracks
;
1015 substate
.ntracks
= 0;
1018 && !strcmp(r
[m
].sort
, r
[n
].sort
))
1019 tracks
[substate
.ntracks
++] = r
[m
++].track
;
1020 substate
.last
= (m
== ntracks
);
1021 expandstring(output
, template, &substate
);
1026 assert(substate
.last
!= 0);
1029 static void exp_arg(int attribute((unused
)) nargs
,
1032 void attribute((unused
)) *u
) {
1035 if((v
= cgi_get(args
[0])))
1036 cgi_output(output
, "%s", v
);
1039 static void exp_stats(int attribute((unused
)) nargs
,
1040 char attribute((unused
)) **args
,
1046 cgi_opentag(output
->sink
, "pre", "class", "stats", (char *)0);
1047 if(!disorder_stats(ds
->g
->client
, &v
, 0)) {
1049 cgi_output(output
, "%s\n", *v
++);
1051 cgi_closetag(output
->sink
, "pre");
1054 static void exp_volume(int attribute((unused
)) nargs
,
1060 lookups(ds
, DC_VOLUME
);
1061 if(!strcmp(args
[0], "left"))
1062 cgi_output(output
, "%d", ds
->g
->volume_left
);
1064 cgi_output(output
, "%d", ds
->g
->volume_right
);
1067 static void exp_shell(int attribute((unused
)) nargs
,
1070 void attribute((unused
)) *u
) {
1076 if(!(pid
= xfork())) {
1081 execlp("sh", "sh", "-c", args
[0], (char *)0);
1082 fatal(errno
, "error executing sh");
1085 while((n
= read(p
[0], buffer
, sizeof buffer
))) {
1087 if(errno
== EINTR
) continue;
1088 else fatal(errno
, "error reading from pipe");
1090 output
->sink
->write(output
->sink
, buffer
, n
);
1093 while((n
= waitpid(pid
, &w
, 0)) < 0 && errno
== EINTR
)
1095 if(n
< 0) fatal(errno
, "error calling waitpid");
1097 error(0, "shell command '%s' %s", args
[0], wstat(w
));
1100 static inline int str2bool(const char *s
) {
1101 return !strcmp(s
, "true");
1104 static inline const char *bool2str(int n
) {
1105 return n ?
"true" : "false";
1108 static char *expandarg(const char *arg
, dcgi_state
*ds
) {
1114 output
.sink
= sink_dynstr(&d
);
1115 expandstring(&output
, arg
, ds
);
1116 dynstr_terminate(&d
);
1120 static void exp_prefs(int attribute((unused
)) nargs
,
1125 dcgi_state substate
;
1127 const char *file
= expandarg(args
[0], ds
);
1129 memset(&substate
, 0, sizeof substate
);
1132 if(disorder_prefs(ds
->g
->client
, file
, &k
)) return;
1134 substate
.last
= !k
->next
;
1136 expandstring(output
, args
[1], &substate
);
1143 static void exp_pref(int attribute((unused
)) nargs
,
1150 if(!disorder_get(ds
->g
->client
, args
[0], args
[1], &value
))
1151 cgi_output(output
, "%s", value
);
1154 static void exp_if(int nargs
,
1159 int n
= str2bool(expandarg(args
[0], ds
)) ?
1 : 2;
1162 expandstring(output
, args
[n
], ds
);
1165 static void exp_and(int nargs
,
1172 for(n
= 0; n
< nargs
; ++n
)
1173 if(!str2bool(expandarg(args
[n
], ds
))) {
1177 sink_printf(output
->sink
, "%s", bool2str(result
));
1180 static void exp_or(int nargs
,
1187 for(n
= 0; n
< nargs
; ++n
)
1188 if(str2bool(expandarg(args
[n
], ds
))) {
1192 sink_printf(output
->sink
, "%s", bool2str(result
));
1195 static void exp_not(int attribute((unused
)) nargs
,
1198 void attribute((unused
)) *u
) {
1199 sink_printf(output
->sink
, "%s", bool2str(!str2bool(args
[0])));
1202 static void exp_isplaying(int attribute((unused
)) nargs
,
1203 char attribute((unused
)) **args
,
1208 lookups(ds
, DC_PLAYING
);
1209 sink_printf(output
->sink
, "%s", bool2str(!!ds
->g
->playing
));
1212 static void exp_isqueue(int attribute((unused
)) nargs
,
1213 char attribute((unused
)) **args
,
1218 lookups(ds
, DC_QUEUE
);
1219 sink_printf(output
->sink
, "%s", bool2str(!!ds
->g
->queue
));
1222 static void exp_isrecent(int attribute((unused
)) nargs
,
1223 char attribute((unused
)) **args
,
1228 lookups(ds
, DC_RECENT
);
1229 sink_printf(output
->sink
, "%s", bool2str(!!ds
->g
->recent
));
1232 static void exp_isnew(int attribute((unused
)) nargs
,
1233 char attribute((unused
)) **args
,
1238 lookups(ds
, DC_NEW
);
1239 sink_printf(output
->sink
, "%s", bool2str(!!ds
->g
->nnew
));
1242 static void exp_id(int attribute((unused
)) nargs
,
1243 char attribute((unused
)) **args
,
1249 cgi_output(output
, "%s", ds
->track
->id
);
1252 static void exp_track(int attribute((unused
)) nargs
,
1253 char attribute((unused
)) **args
,
1259 cgi_output(output
, "%s", ds
->track
->track
);
1262 static void exp_parity(int attribute((unused
)) nargs
,
1263 char attribute((unused
)) **args
,
1268 cgi_output(output
, "%s", ds
->index
% 2 ?
"odd" : "even");
1271 static void exp_comment(int attribute((unused
)) nargs
,
1272 char attribute((unused
)) **args
,
1273 cgi_sink
attribute((unused
)) *output
,
1274 void attribute((unused
)) *u
) {
1278 static void exp_prefname(int attribute((unused
)) nargs
,
1279 char attribute((unused
)) **args
,
1284 if(ds
->pref
&& ds
->pref
->name
)
1285 cgi_output(output
, "%s", ds
->pref
->name
);
1288 static void exp_prefvalue(int attribute((unused
)) nargs
,
1289 char attribute((unused
)) **args
,
1294 if(ds
->pref
&& ds
->pref
->value
)
1295 cgi_output(output
, "%s", ds
->pref
->value
);
1298 static void exp_isfiles(int attribute((unused
)) nargs
,
1299 char attribute((unused
)) **args
,
1304 lookups(ds
, DC_FILES
);
1305 sink_printf(output
->sink
, "%s", bool2str(!!ds
->g
->nfiles
));
1308 static void exp_isdirectories(int attribute((unused
)) nargs
,
1309 char attribute((unused
)) **args
,
1314 lookups(ds
, DC_DIRS
);
1315 sink_printf(output
->sink
, "%s", bool2str(!!ds
->g
->ndirs
));
1318 static void exp_choose(int attribute((unused
)) nargs
,
1323 dcgi_state substate
;
1327 const char *type
, *what
= expandarg(args
[0], ds
);
1329 if(!strcmp(what
, "files")) {
1330 lookups(ds
, DC_FILES
);
1331 files
= ds
->g
->files
;
1332 nfiles
= ds
->g
->nfiles
;
1334 } else if(!strcmp(what
, "directories")) {
1335 lookups(ds
, DC_DIRS
);
1336 files
= ds
->g
->dirs
;
1337 nfiles
= ds
->g
->ndirs
;
1340 error(0, "unknown @choose@ argument '%s'", what
);
1343 e
= xmalloc(nfiles
* sizeof (struct entry
));
1344 for(n
= 0; n
< nfiles
; ++n
) {
1345 e
[n
].path
= files
[n
];
1346 e
[n
].sort
= trackname_transform(type
, files
[n
], "sort");
1347 e
[n
].display
= trackname_transform(type
, files
[n
], "display");
1349 qsort(e
, nfiles
, sizeof (struct entry
), compare_entry
);
1350 memset(&substate
, 0, sizeof substate
);
1353 for(n
= 0; n
< nfiles
; ++n
) {
1354 substate
.last
= (n
== nfiles
- 1);
1356 substate
.entry
= &e
[n
];
1357 expandstring(output
, args
[1], &substate
);
1362 static void exp_file(int attribute((unused
)) nargs
,
1363 char attribute((unused
)) **args
,
1369 cgi_output(output
, "%s", ds
->entry
->path
);
1371 cgi_output(output
, "%s", ds
->track
->track
);
1373 cgi_output(output
, "%s", ds
->tracks
[0]);
1376 static void exp_transform(int nargs
,
1379 void attribute((unused
)) *u
) {
1380 const char *context
= nargs
> 2 ? args
[2] : "display";
1382 cgi_output(output
, "%s", trackname_transform(args
[1], args
[0], context
));
1385 static void exp_urlquote(int attribute((unused
)) nargs
,
1388 void attribute((unused
)) *u
) {
1389 cgi_output(output
, "%s", urlencodestring(args
[0]));
1392 static void exp_scratchable(int attribute((unused
)) nargs
,
1393 char attribute((unused
)) **args
,
1395 void attribute((unused
)) *u
) {
1398 lookups(ds
, DC_PLAYING
|DC_RIGHTS
);
1399 sink_printf(output
->sink
, "%s",
1400 bool2str(right_scratchable(ds
->g
->rights
,
1401 disorder_user(ds
->g
->client
),
1405 static void exp_removable(int attribute((unused
)) nargs
,
1406 char attribute((unused
)) **args
,
1408 void attribute((unused
)) *u
) {
1411 lookups(ds
, DC_RIGHTS
);
1412 sink_printf(output
->sink
, "%s",
1413 bool2str(right_removable(ds
->g
->rights
,
1414 disorder_user(ds
->g
->client
),
1418 static void exp_movable(int attribute((unused
)) nargs
,
1419 char attribute((unused
)) **args
,
1421 void attribute((unused
)) *u
) {
1424 lookups(ds
, DC_RIGHTS
);
1425 sink_printf(output
->sink
, "%s",
1426 bool2str(right_movable(ds
->g
->rights
,
1427 disorder_user(ds
->g
->client
),
1431 static void exp_navigate(int attribute((unused
)) nargs
,
1436 dcgi_state substate
;
1437 const char *path
= expandarg(args
[0], ds
);
1442 memset(&substate
, 0, sizeof substate
);
1444 ptr
= path
+ 1; /* skip root */
1446 substate
.nav_path
= path
;
1449 while(*ptr
&& *ptr
!= '/')
1451 substate
.last
= !*ptr
;
1452 substate
.nav_len
= ptr
- path
;
1453 substate
.nav_dirlen
= dirlen
;
1454 expandstring(output
, args
[1], &substate
);
1455 dirlen
= substate
.nav_len
;
1462 static void exp_fullname(int attribute((unused
)) nargs
,
1463 char attribute((unused
)) **args
,
1467 cgi_output(output
, "%.*s", ds
->nav_len
, ds
->nav_path
);
1470 static void exp_basename(int nargs
,
1478 if((s
= strrchr(args
[0], '/'))) ++s
;
1480 cgi_output(output
, "%s", s
);
1482 cgi_output(output
, "%.*s", ds
->nav_len
- ds
->nav_dirlen
- 1,
1483 ds
->nav_path
+ ds
->nav_dirlen
+ 1);
1486 static void exp_dirname(int nargs
,
1494 if((s
= strrchr(args
[0], '/')))
1495 cgi_output(output
, "%.*s", (int)(s
- args
[0]), args
[0]);
1497 cgi_output(output
, "%.*s", ds
->nav_dirlen
, ds
->nav_path
);
1500 static void exp_eq(int attribute((unused
)) nargs
,
1503 void attribute((unused
)) *u
) {
1504 cgi_output(output
, "%s", bool2str(!strcmp(args
[0], args
[1])));
1507 static void exp_ne(int attribute((unused
)) nargs
,
1510 void attribute((unused
)) *u
) {
1511 cgi_output(output
, "%s", bool2str(strcmp(args
[0], args
[1])));
1514 static void exp_enabled(int attribute((unused
)) nargs
,
1515 char attribute((unused
)) **args
,
1522 disorder_enabled(ds
->g
->client
, &enabled
);
1523 cgi_output(output
, "%s", bool2str(enabled
));
1526 static void exp_random_enabled(int attribute((unused
)) nargs
,
1527 char attribute((unused
)) **args
,
1534 disorder_random_enabled(ds
->g
->client
, &enabled
);
1535 cgi_output(output
, "%s", bool2str(enabled
));
1538 static void exp_trackstate(int attribute((unused
)) nargs
,
1543 struct queue_entry
*q
;
1546 if(disorder_resolve(ds
->g
->client
, &track
, args
[0])) return;
1547 lookups(ds
, DC_QUEUE
|DC_PLAYING
);
1548 if(ds
->g
->playing
&& !strcmp(ds
->g
->playing
->track
, track
))
1549 cgi_output(output
, "playing");
1551 for(q
= ds
->g
->queue
; q
&& strcmp(q
->track
, track
); q
= q
->next
)
1554 cgi_output(output
, "queued");
1558 static void exp_thisurl(int attribute((unused
)) nargs
,
1559 char attribute((unused
)) **args
,
1561 void attribute((unused
)) *u
) {
1562 kvp_set(&cgi_args
, "nonce", nonce()); /* nonces had better differ! */
1563 cgi_output(output
, "%s?%s", config
->url
, kvp_urlencode(cgi_args
, 0));
1566 static void exp_isfirst(int attribute((unused
)) nargs
,
1567 char attribute((unused
)) **args
,
1572 sink_printf(output
->sink
, "%s", bool2str(!!ds
->first
));
1575 static void exp_islast(int attribute((unused
)) nargs
,
1576 char attribute((unused
)) **args
,
1581 sink_printf(output
->sink
, "%s", bool2str(!!ds
->last
));
1584 static void exp_action(int attribute((unused
)) nargs
,
1585 char attribute((unused
)) **args
,
1587 void attribute((unused
)) *u
) {
1588 const char *action
= cgi_get("action"), *mgmt
;
1590 if(!action
) action
= "playing";
1591 if(!strcmp(action
, "playing")
1592 && (mgmt
= cgi_get("mgmt"))
1593 && !strcmp(mgmt
, "true"))
1595 sink_printf(output
->sink
, "%s", action
);
1598 static void exp_resolve(int attribute((unused
)) nargs
,
1601 void attribute((unused
)) *u
) {
1605 if(!disorder_resolve(ds
->g
->client
, &track
, args
[0]))
1606 sink_printf(output
->sink
, "%s", track
);
1609 static void exp_paused(int attribute((unused
)) nargs
,
1610 char attribute((unused
)) **args
,
1616 lookups(ds
, DC_PLAYING
);
1617 if(ds
->g
->playing
&& ds
->g
->playing
->state
== playing_paused
)
1619 cgi_output(output
, "%s", bool2str(paused
));
1622 static void exp_state(int attribute((unused
)) nargs
,
1623 char attribute((unused
)) **args
,
1629 cgi_output(output
, "%s", playing_states
[ds
->track
->state
]);
1632 static void exp_files(int attribute((unused
)) nargs
,
1637 dcgi_state substate
;
1638 const char *nfiles_arg
, *directory
;
1639 int nfiles
, numfile
;
1642 memset(&substate
, 0, sizeof substate
);
1644 if((directory
= cgi_get("directory"))) {
1645 /* Prefs for whole directory. */
1646 lookups(ds
, DC_FILES
);
1647 /* Synthesize args for the file list. */
1648 nfiles
= ds
->g
->nfiles
;
1649 for(numfile
= 0; numfile
< nfiles
; ++numfile
) {
1650 k
= xmalloc(sizeof *k
);
1651 byte_xasprintf((char **)&k
->name
, "%d_file", numfile
);
1652 k
->value
= ds
->g
->files
[numfile
];
1657 /* Args already present. */
1658 if((nfiles_arg
= cgi_get("files"))) nfiles
= atoi(nfiles_arg
);
1661 for(numfile
= 0; numfile
< nfiles
; ++numfile
) {
1662 substate
.index
= numfile
;
1663 expandstring(output
, args
[0], &substate
);
1667 static void exp_index(int attribute((unused
)) nargs
,
1668 char attribute((unused
)) **args
,
1673 cgi_output(output
, "%d", ds
->index
);
1676 static void exp_nfiles(int attribute((unused
)) nargs
,
1677 char attribute((unused
)) **args
,
1681 const char *files_arg
;
1683 if(cgi_get("directory")) {
1684 lookups(ds
, DC_FILES
);
1685 cgi_output(output
, "%d", ds
->g
->nfiles
);
1686 } else if((files_arg
= cgi_get("files")))
1687 cgi_output(output
, "%s", files_arg
);
1689 cgi_output(output
, "1");
1692 static void exp_user(int attribute((unused
)) nargs
,
1693 char attribute((unused
)) **args
,
1696 dcgi_state
*const ds
= u
;
1698 cgi_output(output
, "%s", disorder_user(ds
->g
->client
));
1701 static void exp_right(int attribute((unused
)) nargs
,
1705 dcgi_state
*const ds
= u
;
1706 const char *right
= expandarg(args
[0], ds
);
1709 lookups(ds
, DC_RIGHTS
);
1710 if(parse_rights(right
, &r
, 1/*report*/))
1713 cgi_output(output
, "%s", bool2str(!!(r
& ds
->g
->rights
)));
1714 else if(r
& ds
->g
->rights
)
1715 expandstring(output
, args
[1], ds
);
1717 expandstring(output
, args
[2], ds
);
1720 static void exp_userinfo(int attribute((unused
)) nargs
,
1724 dcgi_state
*const ds
= u
;
1727 if(disorder_userinfo(ds
->g
->client
, disorder_user(ds
->g
->client
), args
[0],
1730 cgi_output(output
, "%s", value
);
1733 static void exp_image(int attribute((unused
)) nargs
,
1736 void attribute((unused
)) *u
) {
1738 const char *imagestem
;
1740 byte_xasprintf(&labelname
, "images.%s", args
[0]);
1741 if(cgi_label_exists(labelname
))
1742 imagestem
= cgi_label(labelname
);
1743 else if(strchr(args
[0], '.'))
1744 imagestem
= args
[0];
1746 byte_xasprintf((char **)&imagestem
, "%s.png", args
[0]);
1747 if(cgi_label_exists("url.static"))
1748 cgi_output(output
, "%s/%s", cgi_label("url.static"), imagestem
);
1750 cgi_output(output
, "/disorder/%s", imagestem
);
1753 static const struct cgi_expansion expansions
[] = {
1754 { "#", 0, INT_MAX
, EXP_MAGIC
, exp_comment
},
1755 { "action", 0, 0, 0, exp_action
},
1756 { "and", 0, INT_MAX
, EXP_MAGIC
, exp_and
},
1757 { "arg", 1, 1, 0, exp_arg
},
1758 { "basename", 0, 1, 0, exp_basename
},
1759 { "choose", 2, 2, EXP_MAGIC
, exp_choose
},
1760 { "dirname", 0, 1, 0, exp_dirname
},
1761 { "enabled", 0, 0, 0, exp_enabled
},
1762 { "eq", 2, 2, 0, exp_eq
},
1763 { "file", 0, 0, 0, exp_file
},
1764 { "files", 1, 1, EXP_MAGIC
, exp_files
},
1765 { "fullname", 0, 0, 0, exp_fullname
},
1766 { "id", 0, 0, 0, exp_id
},
1767 { "if", 2, 3, EXP_MAGIC
, exp_if
},
1768 { "image", 1, 1, 0, exp_image
},
1769 { "include", 1, 1, 0, exp_include
},
1770 { "index", 0, 0, 0, exp_index
},
1771 { "isdirectories", 0, 0, 0, exp_isdirectories
},
1772 { "isfiles", 0, 0, 0, exp_isfiles
},
1773 { "isfirst", 0, 0, 0, exp_isfirst
},
1774 { "islast", 0, 0, 0, exp_islast
},
1775 { "isnew", 0, 0, 0, exp_isnew
},
1776 { "isplaying", 0, 0, 0, exp_isplaying
},
1777 { "isqueue", 0, 0, 0, exp_isqueue
},
1778 { "isrecent", 0, 0, 0, exp_isrecent
},
1779 { "label", 1, 1, 0, exp_label
},
1780 { "length", 0, 0, 0, exp_length
},
1781 { "movable", 0, 0, 0, exp_movable
},
1782 { "navigate", 2, 2, EXP_MAGIC
, exp_navigate
},
1783 { "ne", 2, 2, 0, exp_ne
},
1784 { "new", 1, 1, EXP_MAGIC
, exp_new
},
1785 { "nfiles", 0, 0, 0, exp_nfiles
},
1786 { "nonce", 0, 0, 0, exp_nonce
},
1787 { "not", 1, 1, 0, exp_not
},
1788 { "or", 0, INT_MAX
, EXP_MAGIC
, exp_or
},
1789 { "parity", 0, 0, 0, exp_parity
},
1790 { "part", 1, 3, 0, exp_part
},
1791 { "paused", 0, 0, 0, exp_paused
},
1792 { "playing", 1, 1, EXP_MAGIC
, exp_playing
},
1793 { "pref", 2, 2, 0, exp_pref
},
1794 { "prefname", 0, 0, 0, exp_prefname
},
1795 { "prefs", 2, 2, EXP_MAGIC
, exp_prefs
},
1796 { "prefvalue", 0, 0, 0, exp_prefvalue
},
1797 { "queue", 1, 1, EXP_MAGIC
, exp_queue
},
1798 { "random-enabled", 0, 0, 0, exp_random_enabled
},
1799 { "recent", 1, 1, EXP_MAGIC
, exp_recent
},
1800 { "removable", 0, 0, 0, exp_removable
},
1801 { "resolve", 1, 1, 0, exp_resolve
},
1802 { "right", 1, 3, EXP_MAGIC
, exp_right
},
1803 { "scratchable", 0, 0, 0, exp_scratchable
},
1804 { "search", 2, 3, EXP_MAGIC
, exp_search
},
1805 { "server-version", 0, 0, 0, exp_server_version
},
1806 { "shell", 1, 1, 0, exp_shell
},
1807 { "state", 0, 0, 0, exp_state
},
1808 { "stats", 0, 0, 0, exp_stats
},
1809 { "thisurl", 0, 0, 0, exp_thisurl
},
1810 { "track", 0, 0, 0, exp_track
},
1811 { "trackstate", 1, 1, 0, exp_trackstate
},
1812 { "transform", 2, 3, 0, exp_transform
},
1813 { "url", 0, 0, 0, exp_url
},
1814 { "urlquote", 1, 1, 0, exp_urlquote
},
1815 { "user", 0, 0, 0, exp_user
},
1816 { "userinfo", 1, 1, 0, exp_userinfo
},
1817 { "version", 0, 0, 0, exp_version
},
1818 { "volume", 1, 1, 0, exp_volume
},
1819 { "when", 0, 0, 0, exp_when
},
1820 { "who", 0, 0, 0, exp_who
}
1823 static void expand(cgi_sink
*output
,
1824 const char *template,
1826 cgi_expand(template,
1827 expansions
, sizeof expansions
/ sizeof *expansions
,
1832 static void expandstring(cgi_sink
*output
,
1835 cgi_expand_string("",
1837 expansions
, sizeof expansions
/ sizeof *expansions
,
1842 static void perform_action(cgi_sink
*output
, dcgi_state
*ds
,
1843 const char *action
) {
1846 /* We don't ever want anything to be cached */
1847 cgi_header(output
->sink
, "Cache-Control", "no-cache");
1848 if((n
= TABLE_FIND(actions
, struct action
, name
, action
)) >= 0)
1849 actions
[n
].handler(output
, ds
);
1851 expand_template(ds
, output
, action
);
1854 void disorder_cgi(cgi_sink
*output
, dcgi_state
*ds
) {
1855 const char *action
= cgi_get("action");
1858 /* We allow URLs which are just confirm=... in order to keep confirmation
1859 * URLs, which are user-facing, as short as possible. */
1865 perform_action(output
, ds
, action
);
1868 void disorder_cgi_error(cgi_sink
*output
, dcgi_state
*ds
,
1870 cgi_set_option("error", msg
);
1871 perform_action(output
, ds
, "error");
1874 /** @brief Log in as the current user or guest if none */
1875 void disorder_cgi_login(dcgi_state
*ds
, cgi_sink
*output
) {
1876 /* Create a new connection */
1877 ds
->g
->client
= disorder_new(0);
1878 /* Forget everything we knew */
1881 if(disorder_connect_cookie(ds
->g
->client
, login_cookie
)) {
1882 disorder_cgi_error(output
, ds
, "connect");
1885 /* If there was a cookie but it went bad, we forget it */
1886 if(login_cookie
&& !strcmp(disorder_user(ds
->g
->client
), "guest"))