5 #include <sys/socket.h>
19 #include "server-cgi.h"
21 #include "configuration.h"
32 #include "trackname.h"
46 static int compare_entry(const void *a
, const void *b
) {
47 const struct entry
*ea
= a
, *eb
= b
;
49 return compare_tracks(ea
->sort
, eb
->sort
,
50 ea
->display
, eb
->display
,
54 static const char *front_url(void) {
58 /* preserve management interface visibility */
59 if((mgmt
= cgi_get("mgmt")) && !strcmp(mgmt
, "true")) {
60 byte_xasprintf(&url
, "%s?mgmt=true", config
->url
);
66 static void redirect(struct sink
*output
) {
69 back
= cgi_get("back");
70 cgi_header(output
, "Location", back
&& *back ? back
: front_url());
71 header_cookie(output
);
75 static void expand_template(dcgi_state
*ds
, cgi_sink
*output
,
77 cgi_header(output
->sink
, "Content-Type", "text/html");
78 header_cookie(output
->sink
);
79 cgi_body(output
->sink
);
80 expand(output
, action
, ds
);
83 /* actions ********************************************************************/
85 static void act_disable(cgi_sink
*output
,
88 disorder_disable(ds
->g
->client
);
89 redirect(output
->sink
);
92 static void act_enable(cgi_sink
*output
,
95 disorder_enable(ds
->g
->client
);
96 redirect(output
->sink
);
99 static void act_random_disable(cgi_sink
*output
,
102 disorder_random_disable(ds
->g
->client
);
103 redirect(output
->sink
);
106 static void act_random_enable(cgi_sink
*output
,
109 disorder_random_enable(ds
->g
->client
);
110 redirect(output
->sink
);
113 static void act_remove(cgi_sink
*output
,
117 if(!(id
= cgi_get("id"))) fatal(0, "missing id argument");
119 disorder_remove(ds
->g
->client
, id
);
120 redirect(output
->sink
);
123 static void act_move(cgi_sink
*output
,
125 const char *id
, *delta
;
127 if(!(id
= cgi_get("id"))) fatal(0, "missing id argument");
128 if(!(delta
= cgi_get("delta"))) fatal(0, "missing delta argument");
130 disorder_move(ds
->g
->client
, id
, atoi(delta
));
131 redirect(output
->sink
);
134 static void act_scratch(cgi_sink
*output
,
137 disorder_scratch(ds
->g
->client
, cgi_get("id"));
138 redirect(output
->sink
);
141 static void act_play(cgi_sink
*output
,
143 const char *track
, *dir
;
148 if((track
= cgi_get("file"))) {
149 disorder_play(ds
->g
->client
, track
);
150 } else if((dir
= cgi_get("directory"))) {
151 if(disorder_files(ds
->g
->client
, dir
, 0, &tracks
, &ntracks
)) ntracks
= 0;
153 e
= xmalloc(ntracks
* sizeof (struct entry
));
154 for(n
= 0; n
< ntracks
; ++n
) {
155 e
[n
].path
= tracks
[n
];
156 e
[n
].sort
= trackname_transform("track", tracks
[n
], "sort");
157 e
[n
].display
= trackname_transform("track", tracks
[n
], "display");
159 qsort(e
, ntracks
, sizeof (struct entry
), compare_entry
);
160 for(n
= 0; n
< ntracks
; ++n
)
161 disorder_play(ds
->g
->client
, e
[n
].path
);
164 /* XXX error handling */
165 redirect(output
->sink
);
168 static int clamp(int n
, int min
, int max
) {
176 static const char *volume_url(void) {
179 byte_xasprintf(&url
, "%s?action=volume", config
->url
);
183 static void act_volume(cgi_sink
*output
, dcgi_state
*ds
) {
184 const char *l
, *r
, *d
, *back
;
185 int nd
, changed
= 0;;
187 if((d
= cgi_get("delta"))) {
188 lookups(ds
, DC_VOLUME
);
189 nd
= clamp(atoi(d
), -255, 255);
190 disorder_set_volume(ds
->g
->client
,
191 clamp(ds
->g
->volume_left
+ nd
, 0, 255),
192 clamp(ds
->g
->volume_right
+ nd
, 0, 255));
194 } else if((l
= cgi_get("left")) && (r
= cgi_get("right"))) {
195 disorder_set_volume(ds
->g
->client
, atoi(l
), atoi(r
));
199 /* redirect back to ourselves (but without the volume-changing bits in the
201 cgi_header(output
->sink
, "Location",
202 (back
= cgi_get("back")) ? back
: volume_url());
203 header_cookie(output
->sink
);
204 cgi_body(output
->sink
);
206 cgi_header(output
->sink
, "Content-Type", "text/html");
207 header_cookie(output
->sink
);
208 cgi_body(output
->sink
);
209 expand(output
, "volume", ds
);
213 static void act_prefs_errors(const char *msg
,
214 void attribute((unused
)) *u
) {
215 fatal(0, "error splitting parts list: %s", msg
);
218 static const char *numbered_arg(const char *argname
, int numfile
) {
221 byte_xasprintf(&fullname
, "%d_%s", numfile
, argname
);
222 return cgi_get(fullname
);
225 static void process_prefs(dcgi_state
*ds
, int numfile
) {
226 const char *file
, *name
, *value
, *part
, *parts
, *current
, *context
;
229 if(!(file
= numbered_arg("file", numfile
)))
230 /* The first file doesn't need numbering. */
231 if(numfile
> 0 || !(file
= cgi_get("file")))
233 if((parts
= numbered_arg("parts", numfile
))
234 || (parts
= cgi_get("parts"))) {
235 /* Default context is display. Other contexts not actually tested. */
236 if(!(context
= numbered_arg("context", numfile
))) context
= "display";
237 partslist
= split(parts
, 0, 0, act_prefs_errors
, 0);
238 while((part
= *partslist
++)) {
239 if(!(value
= numbered_arg(part
, numfile
)))
241 /* If it's already right (whether regexps or db) don't change anything,
242 * so we don't fill the database up with rubbish. */
243 if(disorder_part(ds
->g
->client
, (char **)¤t
,
244 file
, context
, part
))
245 fatal(0, "disorder_part() failed");
246 if(!strcmp(current
, value
))
248 byte_xasprintf((char **)&name
, "trackname_%s_%s", context
, part
);
249 disorder_set(ds
->g
->client
, file
, name
, value
);
251 if((value
= numbered_arg("random", numfile
)))
252 disorder_unset(ds
->g
->client
, file
, "pick_at_random");
254 disorder_set(ds
->g
->client
, file
, "pick_at_random", "0");
255 if((value
= numbered_arg("tags", numfile
))) {
257 disorder_unset(ds
->g
->client
, file
, "tags");
259 disorder_set(ds
->g
->client
, file
, "tags", value
);
261 if((value
= numbered_arg("weight", numfile
))) {
262 if(!*value
|| !strcmp(value
, "90000"))
263 disorder_unset(ds
->g
->client
, file
, "weight");
265 disorder_set(ds
->g
->client
, file
, "weight", value
);
267 } else if((name
= cgi_get("name"))) {
268 /* Raw preferences. Not well supported in the templates at the moment. */
269 value
= cgi_get("value");
271 disorder_set(ds
->g
->client
, file
, name
, value
);
273 disorder_unset(ds
->g
->client
, file
, name
);
277 static void act_prefs(cgi_sink
*output
, dcgi_state
*ds
) {
281 if((files
= cgi_get("files"))) nfiles
= atoi(files
);
283 for(numfile
= 0; numfile
< nfiles
; ++numfile
)
284 process_prefs(ds
, numfile
);
285 cgi_header(output
->sink
, "Content-Type", "text/html");
286 header_cookie(output
->sink
);
287 cgi_body(output
->sink
);
288 expand(output
, "prefs", ds
);
291 static void act_pause(cgi_sink
*output
,
294 disorder_pause(ds
->g
->client
);
295 redirect(output
->sink
);
298 static void act_resume(cgi_sink
*output
,
301 disorder_resume(ds
->g
->client
);
302 redirect(output
->sink
);
305 static void act_login(cgi_sink
*output
,
307 const char *username
, *password
, *back
;
310 username
= cgi_get("username");
311 password
= cgi_get("password");
312 if(!username
|| !password
313 || !strcmp(username
, "guest")/*bodge to avoid guest cookies*/) {
314 /* We're just visiting the login page */
315 expand_template(ds
, output
, "login");
318 /* We'll need a new connection as we are going to stop being guest */
320 if(disorder_connect_user(c
, username
, password
)) {
321 cgi_set_option("error", "loginfailed");
322 expand_template(ds
, output
, "login");
325 if(disorder_make_cookie(c
, &login_cookie
)) {
326 cgi_set_option("error", "cookiefailed");
327 expand_template(ds
, output
, "login");
330 /* Use the new connection henceforth */
333 /* We have a new cookie */
334 header_cookie(output
->sink
);
335 cgi_set_option("status", "loginok");
336 if((back
= cgi_get("back")) && *back
)
337 /* Redirect back to somewhere or other */
338 redirect(output
->sink
);
340 /* Stick to the login page */
341 expand_template(ds
, output
, "login");
344 static void act_logout(cgi_sink
*output
,
346 disorder_revoke(ds
->g
->client
);
348 /* Reconnect as guest */
349 disorder_cgi_login(ds
, output
);
350 /* Back to the login page */
351 cgi_set_option("status", "logoutok");
352 expand_template(ds
, output
, "login");
355 static void act_register(cgi_sink
*output
,
357 const char *username
, *password
, *password2
, *email
;
358 char *confirm
, *content_type
;
359 const char *text
, *encoding
, *charset
;
361 username
= cgi_get("username");
362 password
= cgi_get("password1");
363 password2
= cgi_get("password2");
364 email
= cgi_get("email");
366 if(!username
|| !*username
) {
367 cgi_set_option("error", "nousername");
368 expand_template(ds
, output
, "login");
371 if(!password
|| !*password
) {
372 cgi_set_option("error", "nopassword");
373 expand_template(ds
, output
, "login");
376 if(!password2
|| !*password2
|| strcmp(password
, password2
)) {
377 cgi_set_option("error", "passwordmismatch");
378 expand_template(ds
, output
, "login");
381 if(!email
|| !*email
) {
382 cgi_set_option("error", "noemail");
383 expand_template(ds
, output
, "login");
386 /* We could well do better address validation but for now we'll just do the
388 if(!strchr(email
, '@')) {
389 cgi_set_option("error", "bademail");
390 expand_template(ds
, output
, "login");
393 if(disorder_register(ds
->g
->client
, username
, password
, email
, &confirm
)) {
394 cgi_set_option("error", "cannotregister");
395 expand_template(ds
, output
, "login");
398 /* Send the user a mail */
399 /* TODO templatize this */
400 byte_xasprintf((char **)&text
,
401 "Welcome to DisOrder. To active your login, please visit this URL:\n"
403 "%s?c=%s\n", config
->url
, urlencodestring(confirm
));
404 if(!(text
= mime_encode_text(text
, &charset
, &encoding
)))
405 fatal(0, "cannot encode email");
406 byte_xasprintf(&content_type
, "text/plain;charset=%s",
407 quote822(charset
, 0));
408 sendmail("", config
->mail_sender
, email
, "Welcome to DisOrder",
409 encoding
, content_type
, text
); /* TODO error checking */
410 /* We'll go back to the login page with a suitable message */
411 cgi_set_option("status", "registered");
412 expand_template(ds
, output
, "login");
415 static void act_confirm(cgi_sink
*output
,
417 const char *confirmation
;
419 if(!(confirmation
= cgi_get("c"))) {
420 cgi_set_option("error", "noconfirm");
421 expand_template(ds
, output
, "login");
423 /* Confirm our registration */
424 if(disorder_confirm(ds
->g
->client
, confirmation
)) {
425 cgi_set_option("error", "badconfirm");
426 expand_template(ds
, output
, "login");
429 if(disorder_make_cookie(ds
->g
->client
, &login_cookie
)) {
430 cgi_set_option("error", "cookiefailed");
431 expand_template(ds
, output
, "login");
434 /* Discard any cached data JIC */
436 /* We have a new cookie */
437 header_cookie(output
->sink
);
438 cgi_set_option("status", "confirmed");
439 expand_template(ds
, output
, "login");
442 static void act_edituser(cgi_sink
*output
,
444 const char *email
= cgi_get("email"), *password
= cgi_get("changepassword1");
445 const char *password2
= cgi_get("changepassword2");
449 if((password
&& *password
) || (password
&& *password2
)) {
450 if(!password
|| !password2
|| strcmp(password
, password2
)) {
451 cgi_set_option("error", "passwordmismatch");
452 expand_template(ds
, output
, "login");
456 password
= password2
= 0;
459 if(disorder_edituser(ds
->g
->client
, disorder_user(ds
->g
->client
),
461 cgi_set_option("error", "badedit");
462 expand_template(ds
, output
, "login");
467 if(disorder_edituser(ds
->g
->client
, disorder_user(ds
->g
->client
),
468 "password", password
)) {
469 cgi_set_option("error", "badedit");
470 expand_template(ds
, output
, "login");
476 login_cookie
= 0; /* it'll be invalid now */
477 /* This is a bit duplicative of act_login() */
479 if(disorder_connect_user(c
, disorder_user(ds
->g
->client
), password
)) {
480 cgi_set_option("error", "loginfailed");
481 expand_template(ds
, output
, "login");
484 if(disorder_make_cookie(c
, &login_cookie
)) {
485 cgi_set_option("error", "cookiefailed");
486 expand_template(ds
, output
, "login");
489 /* Use the new connection henceforth */
492 /* We have a new cookie */
493 header_cookie(output
->sink
);
495 cgi_set_option("status", "edited");
496 expand_template(ds
, output
, "login");
499 static void act_reminder(cgi_sink
*output
,
501 const char *const username
= cgi_get("username");
503 if(!username
|| !*username
) {
504 cgi_set_option("error", "nousername");
505 expand_template(ds
, output
, "login");
508 if(disorder_reminder(ds
->g
->client
, username
)) {
509 cgi_set_option("error", "reminderfailed");
510 expand_template(ds
, output
, "login");
513 cgi_set_option("status", "reminded");
514 expand_template(ds
, output
, "login");
517 /* expansions *****************************************************************/
519 static void exp_label(int attribute((unused
)) nargs
,
522 void attribute((unused
)) *u
) {
523 cgi_output(output
, "%s", cgi_label(args
[0]));
526 struct trackinfo_state
{
528 const struct queue_entry
*q
;
538 static int compare_result(const void *a
, const void *b
) {
539 const struct result
*ra
= a
, *rb
= b
;
542 if(!(c
= strcmp(ra
->sort
, rb
->sort
)))
543 c
= strcmp(ra
->track
, rb
->track
);
547 static void exp_search(int nargs
,
551 dcgi_state
*ds
= u
, substate
;
553 const char *q
, *context
, *part
, *template;
569 assert(!"should never happen");
570 part
= context
= template = 0; /* quieten compiler */
572 if(ds
->tracks
== 0) {
573 /* we are the top level, let's get some search results */
574 if(!(q
= cgi_get("query"))) return; /* no results yet */
575 if(disorder_search(ds
->g
->client
, q
, &tracks
, &ntracks
)) return;
579 ntracks
= ds
->ntracks
;
581 assert(ntracks
!= 0);
582 /* sort tracks by the appropriate part */
583 r
= xmalloc(ntracks
* sizeof *r
);
584 for(n
= 0; n
< ntracks
; ++n
) {
585 r
[n
].track
= tracks
[n
];
586 if(disorder_part(ds
->g
->client
, (char **)&r
[n
].sort
,
587 tracks
[n
], context
, part
))
588 fatal(0, "disorder_part() failed");
590 qsort(r
, ntracks
, sizeof (struct result
), compare_result
);
591 /* expand the 2nd arg once for each group. We re-use the passed-in tracks
592 * array as we know it's guaranteed to be big enough and isn't going to be
593 * used for anything else any more. */
594 memset(&substate
, 0, sizeof substate
);
599 substate
.tracks
= tracks
;
600 substate
.ntracks
= 0;
603 && !strcmp(r
[m
].sort
, r
[n
].sort
))
604 tracks
[substate
.ntracks
++] = r
[m
++].track
;
605 substate
.last
= (m
== ntracks
);
606 expandstring(output
, template, &substate
);
611 assert(substate
.last
!= 0);
614 static void exp_stats(int attribute((unused
)) nargs
,
615 char attribute((unused
)) **args
,
621 cgi_opentag(output
->sink
, "pre", "class", "stats", (char *)0);
622 if(!disorder_stats(ds
->g
->client
, &v
, 0)) {
624 cgi_output(output
, "%s\n", *v
++);
626 cgi_closetag(output
->sink
, "pre");
629 static char *expandarg(const char *arg
, dcgi_state
*ds
) {
635 output
.sink
= sink_dynstr(&d
);
636 expandstring(&output
, arg
, ds
);
637 dynstr_terminate(&d
);
641 static void exp_isfiles(int attribute((unused
)) nargs
,
642 char attribute((unused
)) **args
,
647 lookups(ds
, DC_FILES
);
648 sink_printf(output
->sink
, "%s", bool2str(!!ds
->g
->nfiles
));
651 static void exp_isdirectories(int attribute((unused
)) nargs
,
652 char attribute((unused
)) **args
,
657 lookups(ds
, DC_DIRS
);
658 sink_printf(output
->sink
, "%s", bool2str(!!ds
->g
->ndirs
));
661 static void exp_choose(int attribute((unused
)) nargs
,
670 const char *type
, *what
= expandarg(args
[0], ds
);
672 if(!strcmp(what
, "files")) {
673 lookups(ds
, DC_FILES
);
674 files
= ds
->g
->files
;
675 nfiles
= ds
->g
->nfiles
;
677 } else if(!strcmp(what
, "directories")) {
678 lookups(ds
, DC_DIRS
);
680 nfiles
= ds
->g
->ndirs
;
683 error(0, "unknown @choose@ argument '%s'", what
);
686 e
= xmalloc(nfiles
* sizeof (struct entry
));
687 for(n
= 0; n
< nfiles
; ++n
) {
688 e
[n
].path
= files
[n
];
689 e
[n
].sort
= trackname_transform(type
, files
[n
], "sort");
690 e
[n
].display
= trackname_transform(type
, files
[n
], "display");
692 qsort(e
, nfiles
, sizeof (struct entry
), compare_entry
);
693 memset(&substate
, 0, sizeof substate
);
696 for(n
= 0; n
< nfiles
; ++n
) {
697 substate
.last
= (n
== nfiles
- 1);
699 substate
.entry
= &e
[n
];
700 expandstring(output
, args
[1], &substate
);
705 static void exp_file(int attribute((unused
)) nargs
,
706 char attribute((unused
)) **args
,
712 cgi_output(output
, "%s", ds
->entry
->path
);
714 cgi_output(output
, "%s", ds
->track
->track
);
716 cgi_output(output
, "%s", ds
->tracks
[0]);
719 static void exp_navigate(int attribute((unused
)) nargs
,
725 const char *path
= expandarg(args
[0], ds
);
730 memset(&substate
, 0, sizeof substate
);
732 ptr
= path
+ 1; /* skip root */
734 substate
.nav_path
= path
;
737 while(*ptr
&& *ptr
!= '/')
739 substate
.last
= !*ptr
;
740 substate
.nav_len
= ptr
- path
;
741 substate
.nav_dirlen
= dirlen
;
742 expandstring(output
, args
[1], &substate
);
743 dirlen
= substate
.nav_len
;
750 static void exp_fullname(int attribute((unused
)) nargs
,
751 char attribute((unused
)) **args
,
755 cgi_output(output
, "%.*s", ds
->nav_len
, ds
->nav_path
);
758 static void exp_basename(int nargs
,
766 if((s
= strrchr(args
[0], '/'))) ++s
;
768 cgi_output(output
, "%s", s
);
770 cgi_output(output
, "%.*s", ds
->nav_len
- ds
->nav_dirlen
- 1,
771 ds
->nav_path
+ ds
->nav_dirlen
+ 1);
774 static void exp_dirname(int nargs
,
782 if((s
= strrchr(args
[0], '/')))
783 cgi_output(output
, "%.*s", (int)(s
- args
[0]), args
[0]);
785 cgi_output(output
, "%.*s", ds
->nav_dirlen
, ds
->nav_path
);
788 static void exp_files(int attribute((unused
)) nargs
,
794 const char *nfiles_arg
, *directory
;
798 memset(&substate
, 0, sizeof substate
);
800 if((directory
= cgi_get("directory"))) {
801 /* Prefs for whole directory. */
802 lookups(ds
, DC_FILES
);
803 /* Synthesize args for the file list. */
804 nfiles
= ds
->g
->nfiles
;
805 for(numfile
= 0; numfile
< nfiles
; ++numfile
) {
806 k
= xmalloc(sizeof *k
);
807 byte_xasprintf((char **)&k
->name
, "%d_file", numfile
);
808 k
->value
= ds
->g
->files
[numfile
];
813 /* Args already present. */
814 if((nfiles_arg
= cgi_get("files"))) nfiles
= atoi(nfiles_arg
);
817 for(numfile
= 0; numfile
< nfiles
; ++numfile
) {
818 substate
.index
= numfile
;
819 expandstring(output
, args
[0], &substate
);
823 static void exp_nfiles(int attribute((unused
)) nargs
,
824 char attribute((unused
)) **args
,
828 const char *files_arg
;
830 if(cgi_get("directory")) {
831 lookups(ds
, DC_FILES
);
832 cgi_output(output
, "%d", ds
->g
->nfiles
);
833 } else if((files_arg
= cgi_get("files")))
834 cgi_output(output
, "%s", files_arg
);
836 cgi_output(output
, "1");
839 static void exp_image(int attribute((unused
)) nargs
,
842 void attribute((unused
)) *u
) {
844 const char *imagestem
;
846 byte_xasprintf(&labelname
, "images.%s", args
[0]);
847 if(cgi_label_exists(labelname
))
848 imagestem
= cgi_label(labelname
);
849 else if(strchr(args
[0], '.'))
852 byte_xasprintf((char **)&imagestem
, "%s.png", args
[0]);
853 if(cgi_label_exists("url.static"))
854 cgi_output(output
, "%s/%s", cgi_label("url.static"), imagestem
);
856 cgi_output(output
, "/disorder/%s", imagestem
);