chiark / gitweb /
Fill in some missing actions
[disorder] / server / dcgi.c
CommitLineData
460b9539 1
2#include <stdio.h>
3#include <errno.h>
4#include <sys/types.h>
5#include <sys/socket.h>
6#include <stddef.h>
7#include <stdlib.h>
8#include <time.h>
9#include <unistd.h>
10#include <string.h>
11#include <sys/wait.h>
12#include <pcre.h>
13#include <assert.h>
14
15#include "client.h"
16#include "mem.h"
17#include "vector.h"
18#include "sink.h"
9faa7a88 19#include "server-cgi.h"
460b9539 20#include "log.h"
21#include "configuration.h"
22#include "table.h"
23#include "queue.h"
24#include "plugin.h"
25#include "split.h"
460b9539 26#include "wstat.h"
27#include "kvp.h"
28#include "syscalls.h"
29#include "printf.h"
30#include "regsub.h"
31#include "defs.h"
32#include "trackname.h"
61507e3c 33#include "charset.h"
938d8157 34#include "dcgi.h"
36bde473 35#include "url.h"
36#include "mime.h"
bb6ae3fb 37#include "sendmail.h"
f902253a 38#include "base64.h"
460b9539 39
460b9539 40struct entry {
41 const char *path;
42 const char *sort;
43 const char *display;
44};
45
460b9539 46static int compare_entry(const void *a, const void *b) {
47 const struct entry *ea = a, *eb = b;
48
49 return compare_tracks(ea->sort, eb->sort,
50 ea->display, eb->display,
51 ea->path, eb->path);
52}
53
54static const char *front_url(void) {
55 char *url;
56 const char *mgmt;
57
58 /* preserve management interface visibility */
59 if((mgmt = cgi_get("mgmt")) && !strcmp(mgmt, "true")) {
60 byte_xasprintf(&url, "%s?mgmt=true", config->url);
61 return url;
62 }
63 return config->url;
64}
65
7f66f58b 66static void redirect(struct sink *output) {
67 const char *back;
68
69 back = cgi_get("back");
70 cgi_header(output, "Location", back && *back ? back : front_url());
71 header_cookie(output);
72 cgi_body(output);
fdf98378 73}
74
75static void expand_template(dcgi_state *ds, cgi_sink *output,
76 const char *action) {
77 cgi_header(output->sink, "Content-Type", "text/html");
7f66f58b 78 header_cookie(output->sink);
fdf98378 79 cgi_body(output->sink);
80 expand(output, action, ds);
81}
82
460b9539 83/* actions ********************************************************************/
84
460b9539 85static void act_prefs_errors(const char *msg,
86 void attribute((unused)) *u) {
87 fatal(0, "error splitting parts list: %s", msg);
88}
89
90static const char *numbered_arg(const char *argname, int numfile) {
91 char *fullname;
92
93 byte_xasprintf(&fullname, "%d_%s", numfile, argname);
94 return cgi_get(fullname);
95}
96
97static void process_prefs(dcgi_state *ds, int numfile) {
98 const char *file, *name, *value, *part, *parts, *current, *context;
99 char **partslist;
100
101 if(!(file = numbered_arg("file", numfile)))
102 /* The first file doesn't need numbering. */
103 if(numfile > 0 || !(file = cgi_get("file")))
104 return;
105 if((parts = numbered_arg("parts", numfile))
106 || (parts = cgi_get("parts"))) {
107 /* Default context is display. Other contexts not actually tested. */
108 if(!(context = numbered_arg("context", numfile))) context = "display";
109 partslist = split(parts, 0, 0, act_prefs_errors, 0);
110 while((part = *partslist++)) {
111 if(!(value = numbered_arg(part, numfile)))
112 continue;
113 /* If it's already right (whether regexps or db) don't change anything,
114 * so we don't fill the database up with rubbish. */
115 if(disorder_part(ds->g->client, (char **)&current,
116 file, context, part))
117 fatal(0, "disorder_part() failed");
118 if(!strcmp(current, value))
119 continue;
120 byte_xasprintf((char **)&name, "trackname_%s_%s", context, part);
121 disorder_set(ds->g->client, file, name, value);
122 }
123 if((value = numbered_arg("random", numfile)))
124 disorder_unset(ds->g->client, file, "pick_at_random");
125 else
126 disorder_set(ds->g->client, file, "pick_at_random", "0");
2d0cdf2b
RK
127 if((value = numbered_arg("tags", numfile))) {
128 if(!*value)
129 disorder_unset(ds->g->client, file, "tags");
130 else
131 disorder_set(ds->g->client, file, "tags", value);
132 }
133 if((value = numbered_arg("weight", numfile))) {
134 if(!*value || !strcmp(value, "90000"))
135 disorder_unset(ds->g->client, file, "weight");
136 else
137 disorder_set(ds->g->client, file, "weight", value);
138 }
460b9539 139 } else if((name = cgi_get("name"))) {
140 /* Raw preferences. Not well supported in the templates at the moment. */
141 value = cgi_get("value");
142 if(value)
143 disorder_set(ds->g->client, file, name, value);
144 else
145 disorder_unset(ds->g->client, file, name);
146 }
147}
148
149static void act_prefs(cgi_sink *output, dcgi_state *ds) {
150 const char *files;
151 int nfiles, numfile;
152
153 if((files = cgi_get("files"))) nfiles = atoi(files);
154 else nfiles = 1;
155 for(numfile = 0; numfile < nfiles; ++numfile)
156 process_prefs(ds, numfile);
157 cgi_header(output->sink, "Content-Type", "text/html");
7f66f58b 158 header_cookie(output->sink);
460b9539 159 cgi_body(output->sink);
160 expand(output, "prefs", ds);
161}
162
fdf98378 163static void act_login(cgi_sink *output,
164 dcgi_state *ds) {
165 const char *username, *password, *back;
166 disorder_client *c;
167
168 username = cgi_get("username");
169 password = cgi_get("password");
170 if(!username || !password
171 || !strcmp(username, "guest")/*bodge to avoid guest cookies*/) {
172 /* We're just visiting the login page */
173 expand_template(ds, output, "login");
174 return;
175 }
fb6aa8d1 176 /* We'll need a new connection as we are going to stop being guest */
177 c = disorder_new(0);
fdf98378 178 if(disorder_connect_user(c, username, password)) {
179 cgi_set_option("error", "loginfailed");
180 expand_template(ds, output, "login");
181 return;
182 }
183 if(disorder_make_cookie(c, &login_cookie)) {
184 cgi_set_option("error", "cookiefailed");
185 expand_template(ds, output, "login");
186 return;
187 }
fb6aa8d1 188 /* Use the new connection henceforth */
189 ds->g->client = c;
190 ds->g->flags = 0;
fdf98378 191 /* We have a new cookie */
7f66f58b 192 header_cookie(output->sink);
ac152d06 193 cgi_set_option("status", "loginok");
194 if((back = cgi_get("back")) && *back)
fdf98378 195 /* Redirect back to somewhere or other */
196 redirect(output->sink);
197 else
198 /* Stick to the login page */
199 expand_template(ds, output, "login");
200}
201
b42ffad6 202static void act_logout(cgi_sink *output,
203 dcgi_state *ds) {
204 disorder_revoke(ds->g->client);
205 login_cookie = 0;
7f66f58b 206 /* Reconnect as guest */
938d8157 207 disorder_cgi_login(ds, output);
b42ffad6 208 /* Back to the login page */
ac152d06 209 cgi_set_option("status", "logoutok");
b42ffad6 210 expand_template(ds, output, "login");
211}
212
fdf98378 213static void act_register(cgi_sink *output,
214 dcgi_state *ds) {
968f044a 215 const char *username, *password, *password2, *email;
bb6ae3fb 216 char *confirm, *content_type;
217 const char *text, *encoding, *charset;
fdf98378 218
219 username = cgi_get("username");
968f044a 220 password = cgi_get("password1");
221 password2 = cgi_get("password2");
fdf98378 222 email = cgi_get("email");
223
224 if(!username || !*username) {
225 cgi_set_option("error", "nousername");
226 expand_template(ds, output, "login");
227 return;
228 }
229 if(!password || !*password) {
230 cgi_set_option("error", "nopassword");
231 expand_template(ds, output, "login");
232 return;
233 }
968f044a 234 if(!password2 || !*password2 || strcmp(password, password2)) {
235 cgi_set_option("error", "passwordmismatch");
236 expand_template(ds, output, "login");
237 return;
238 }
fdf98378 239 if(!email || !*email) {
240 cgi_set_option("error", "noemail");
241 expand_template(ds, output, "login");
242 return;
243 }
244 /* We could well do better address validation but for now we'll just do the
245 * minimum */
246 if(!strchr(email, '@')) {
247 cgi_set_option("error", "bademail");
248 expand_template(ds, output, "login");
249 return;
250 }
251 if(disorder_register(ds->g->client, username, password, email, &confirm)) {
252 cgi_set_option("error", "cannotregister");
253 expand_template(ds, output, "login");
254 return;
255 }
bb6ae3fb 256 /* Send the user a mail */
257 /* TODO templatize this */
258 byte_xasprintf((char **)&text,
259 "Welcome to DisOrder. To active your login, please visit this URL:\n"
260 "\n"
10114017 261 "%s?c=%s\n", config->url, urlencodestring(confirm));
bb6ae3fb 262 if(!(text = mime_encode_text(text, &charset, &encoding)))
263 fatal(0, "cannot encode email");
264 byte_xasprintf(&content_type, "text/plain;charset=%s",
265 quote822(charset, 0));
266 sendmail("", config->mail_sender, email, "Welcome to DisOrder",
267 encoding, content_type, text); /* TODO error checking */
fdf98378 268 /* We'll go back to the login page with a suitable message */
ac152d06 269 cgi_set_option("status", "registered");
fdf98378 270 expand_template(ds, output, "login");
271}
272
230209f7 273static void act_confirm(cgi_sink *output,
274 dcgi_state *ds) {
275 const char *confirmation;
276
10114017 277 if(!(confirmation = cgi_get("c"))) {
230209f7 278 cgi_set_option("error", "noconfirm");
279 expand_template(ds, output, "login");
280 }
30365519 281 /* Confirm our registration */
230209f7 282 if(disorder_confirm(ds->g->client, confirmation)) {
283 cgi_set_option("error", "badconfirm");
284 expand_template(ds, output, "login");
285 }
30365519 286 /* Get a cookie */
287 if(disorder_make_cookie(ds->g->client, &login_cookie)) {
288 cgi_set_option("error", "cookiefailed");
289 expand_template(ds, output, "login");
290 return;
291 }
292 /* Discard any cached data JIC */
293 ds->g->flags = 0;
294 /* We have a new cookie */
295 header_cookie(output->sink);
ac152d06 296 cgi_set_option("status", "confirmed");
230209f7 297 expand_template(ds, output, "login");
298}
299
968f044a 300static void act_edituser(cgi_sink *output,
301 dcgi_state *ds) {
302 const char *email = cgi_get("email"), *password = cgi_get("changepassword1");
303 const char *password2 = cgi_get("changepassword2");
304 int newpassword = 0;
305 disorder_client *c;
306
307 if((password && *password) || (password && *password2)) {
308 if(!password || !password2 || strcmp(password, password2)) {
309 cgi_set_option("error", "passwordmismatch");
310 expand_template(ds, output, "login");
311 return;
312 }
313 } else
314 password = password2 = 0;
315
316 if(email) {
317 if(disorder_edituser(ds->g->client, disorder_user(ds->g->client),
318 "email", email)) {
319 cgi_set_option("error", "badedit");
320 expand_template(ds, output, "login");
321 return;
322 }
323 }
324 if(password) {
325 if(disorder_edituser(ds->g->client, disorder_user(ds->g->client),
326 "password", password)) {
327 cgi_set_option("error", "badedit");
328 expand_template(ds, output, "login");
329 return;
330 }
331 newpassword = 1;
332 }
333 if(newpassword) {
334 login_cookie = 0; /* it'll be invalid now */
335 /* This is a bit duplicative of act_login() */
336 c = disorder_new(0);
337 if(disorder_connect_user(c, disorder_user(ds->g->client), password)) {
338 cgi_set_option("error", "loginfailed");
339 expand_template(ds, output, "login");
340 return;
341 }
342 if(disorder_make_cookie(c, &login_cookie)) {
343 cgi_set_option("error", "cookiefailed");
344 expand_template(ds, output, "login");
345 return;
346 }
347 /* Use the new connection henceforth */
348 ds->g->client = c;
349 ds->g->flags = 0;
350 /* We have a new cookie */
351 header_cookie(output->sink);
352 }
353 cgi_set_option("status", "edited");
354 expand_template(ds, output, "login");
355}
356
6207d2f3 357static void act_reminder(cgi_sink *output,
358 dcgi_state *ds) {
359 const char *const username = cgi_get("username");
360
361 if(!username || !*username) {
362 cgi_set_option("error", "nousername");
363 expand_template(ds, output, "login");
364 return;
365 }
366 if(disorder_reminder(ds->g->client, username)) {
367 cgi_set_option("error", "reminderfailed");
368 expand_template(ds, output, "login");
369 return;
370 }
371 cgi_set_option("status", "reminded");
372 expand_template(ds, output, "login");
373}
b28ce5d6 374
460b9539 375/* expansions *****************************************************************/
376
460b9539 377static void exp_label(int attribute((unused)) nargs,
378 char **args,
379 cgi_sink *output,
380 void attribute((unused)) *u) {
381 cgi_output(output, "%s", cgi_label(args[0]));
382}
383
384struct trackinfo_state {
385 dcgi_state *ds;
386 const struct queue_entry *q;
387 long length;
388 time_t when;
389};
390
460b9539 391struct result {
392 char *track;
393 const char *sort;
394};
395
396static int compare_result(const void *a, const void *b) {
397 const struct result *ra = a, *rb = b;
398 int c;
399
400 if(!(c = strcmp(ra->sort, rb->sort)))
401 c = strcmp(ra->track, rb->track);
402 return c;
403}
404
460b9539 405static void exp_stats(int attribute((unused)) nargs,
406 char attribute((unused)) **args,
407 cgi_sink *output,
408 void *u) {
409 dcgi_state *ds = u;
410 char **v;
411
412 cgi_opentag(output->sink, "pre", "class", "stats", (char *)0);
413 if(!disorder_stats(ds->g->client, &v, 0)) {
414 while(*v)
415 cgi_output(output, "%s\n", *v++);
416 }
417 cgi_closetag(output->sink, "pre");
418}
419
460b9539 420static char *expandarg(const char *arg, dcgi_state *ds) {
421 struct dynstr d;
422 cgi_sink output;
423
424 dynstr_init(&d);
425 output.quote = 0;
426 output.sink = sink_dynstr(&d);
427 expandstring(&output, arg, ds);
428 dynstr_terminate(&d);
429 return d.vec;
430}
431
460b9539 432static void exp_navigate(int attribute((unused)) nargs,
433 char **args,
434 cgi_sink *output,
435 void *u) {
436 dcgi_state *ds = u;
437 dcgi_state substate;
438 const char *path = expandarg(args[0], ds);
439 const char *ptr;
440 int dirlen;
441
442 if(*path) {
443 memset(&substate, 0, sizeof substate);
444 substate.g = ds->g;
445 ptr = path + 1; /* skip root */
446 dirlen = 0;
447 substate.nav_path = path;
448 substate.first = 1;
449 while(*ptr) {
450 while(*ptr && *ptr != '/')
451 ++ptr;
452 substate.last = !*ptr;
453 substate.nav_len = ptr - path;
454 substate.nav_dirlen = dirlen;
455 expandstring(output, args[1], &substate);
456 dirlen = substate.nav_len;
457 if(*ptr) ++ptr;
458 substate.first = 0;
459 }
460 }
461}
462
463static void exp_fullname(int attribute((unused)) nargs,
464 char attribute((unused)) **args,
465 cgi_sink *output,
466 void *u) {
467 dcgi_state *ds = u;
468 cgi_output(output, "%.*s", ds->nav_len, ds->nav_path);
469}
470
460b9539 471/*
472Local Variables:
473c-basic-offset:2
474comment-column:40
475fill-column:79
476End:
477*/