chiark / gitweb /
341f881cbb33137959eb9518b32c49248d073ac5
[disorder] / server / actions.c
1 /*
2  * This file is part of DisOrder.
3  * Copyright (C) 2004-2008 Richard Kettlewell
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 <string.h>
25 #include <stdio.h>
26 #include <errno.h>
27 #include <time.h>
28 #include <ctype.h>
29 #include <stddef.h>
30
31 #include "hash.h"
32 #include "table.h"
33 #include "client.h"
34 #include "rights.h"
35 #include "mem.h"
36 #include "sink.h"
37 #include "vector.h"
38 #include "printf.h"
39 #include "actions.h"
40 #include "lookup.h"
41 #include "url.h"
42 #include "configuration.h"
43 #include "cgi.h"
44 #include "log.h"
45 #include "queue.h"
46 #include "macros.h"
47 #include "macros-disorder.h"
48
49 /** @brief Login cookie */
50 char *login_cookie;
51
52 /** @brief Return a Cookie: header */
53 static char *cookie(void) {
54   struct dynstr d[1];
55   struct url u;
56   char *s;
57
58   memset(&u, 0, sizeof u);
59   dynstr_init(d);
60   parse_url(config->url, &u);
61   if(login_cookie) {
62     dynstr_append_string(d, "disorder=");
63     dynstr_append_string(d, login_cookie);
64   } else {
65     /* Force browser to discard cookie */
66     dynstr_append_string(d, "disorder=none;Max-Age=0");
67   }
68   if(u.path) {
69     /* The default domain matches the request host, so we need not override
70      * that.  But the default path only goes up to the rightmost /, which would
71      * cause the browser to expose the cookie to other CGI programs on the same
72      * web server. */
73     dynstr_append_string(d, ";Version=1;Path=");
74     /* Formally we are supposed to quote the path, since it invariably has a
75      * slash in it.  However Safari does not parse quoted paths correctly, so
76      * this won't work.  Fortunately nothing else seems to care about proper
77      * quoting of paths, so in practice we get with it.  (See also
78      * parse_cookie() where we are liberal about cookie paths on the way back
79      * in.) */
80     dynstr_append_string(d, u.path);
81   }
82   dynstr_terminate(d);
83   byte_xasprintf(&s, "Set-Cookie: %s", d->vec);
84   return s;
85 }
86
87 /** @brief Redirect to some other action or URL */
88 static void redirect(const char *url) {
89   /* By default use the 'back' argument */
90   if(!url)
91     url = cgi_get("back");
92   if(url) {
93     if(!strncmp(url, "http", 4))
94       /* If the target is not a full URL assume it's the action */
95       url = cgi_makeurl(config->url, "action", url, (char *)0);
96   } else {
97     /* If back= is not set just go back to the front page */
98     url = config->url;
99   }
100   if(printf("Location: %s\n"
101             "%s\n"
102             "\n", url, cookie()) < 0)
103     fatal(errno, "error writing to stdout");
104 }
105
106 /* 'playing' and 'manage' just add a Refresh: header */
107 static void act_playing(void) {
108   long refresh = config->refresh;
109   long length;
110   time_t now, fin;
111   char *url;
112   const char *action;
113
114   lookup(DC_PLAYING|DC_QUEUE|DC_ENABLED|DC_RANDOM_ENABLED);
115   if(playing
116      && playing->state == playing_started /* i.e. not paused */
117      && !disorder_length(client, playing->track, &length)
118      && length
119      && playing->sofar >= 0) {
120     /* Try to put the next refresh at the start of the next track. */
121     time(&now);
122     fin = now + length - playing->sofar + config->gap;
123     if(now + refresh > fin)
124       refresh = fin - now;
125   }
126   if(queue && queue->state == playing_isscratch) {
127     /* next track is a scratch, don't leave more than the inter-track gap */
128     if(refresh > config->gap)
129       refresh = config->gap;
130   }
131   if(!playing
132      && ((queue
133           && queue->state != playing_random)
134          || random_enabled)
135      && enabled) {
136     /* no track playing but playing is enabled and there is something coming
137      * up, must be in a gap */
138     if(refresh > config->gap)
139       refresh = config->gap;
140   }
141   if((action = cgi_get("action")))
142     url = cgi_makeurl(config->url, "action", action, (char *)0);
143   else
144     url = config->url;
145   if(printf("Content-Type: text/html\n"
146             "Refresh: %ld;url=%s\n"
147             "%s\n"
148             "\n",
149             refresh, url, cookie()) < 0)
150     fatal(errno, "error writing to stdout");
151   disorder_cgi_expand(action ? action : "playing");
152 }
153
154 /** @brief Table of actions */
155 static const struct action {
156   /** @brief Action name */
157   const char *name;
158   /** @brief Action handler */
159   void (*handler)(void);
160 } actions[] = {
161 #if 0
162   { "confirm", act_confirm },
163   { "disable", act_disable },
164   { "edituser", act_edituser },
165   { "enable", act_enable },
166   { "login", act_login },
167   { "logout", act_logout },
168   { "manage", act_playing },
169   { "move", act_move },
170   { "pause", act_pause },
171   { "play", act_play },
172 #endif
173   { "playing", act_playing },
174 #if 0
175   { "prefs", act_prefs },
176   { "random-disable", act_random_disable },
177   { "random-enable", act_random_enable },
178   { "register", act_register },
179   { "reminder", act_reminder },
180   { "remove", act_remove },
181   { "resume", act_resume },
182   { "scratch", act_scratch },
183   { "volume", act_volume },
184 #endif
185 };
186
187 /** @brief Expand a template
188  * @param name Base name of template, or NULL to consult CGI args
189  */
190 void disorder_cgi_expand(const char *name) {
191   const char *p;
192   
193   /* For unknown actions check that they aren't evil */
194   for(p = name; *p && isalnum((unsigned char)*p); ++p)
195     ;
196   if(*p)
197     fatal(0, "invalid action name '%s'", name);
198   byte_xasprintf((char **)&p, "%s.tmpl", name);
199   if(mx_expand_file(p, sink_stdio("stdout", stdout), 0) == -1
200      || fflush(stdout) < 0)
201     fatal(errno, "error writing to stdout");
202 }
203
204 /** @brief Execute a web action
205  * @param action Action to perform, or NULL to consult CGI args
206  *
207  * If no recognized action is specified then 'playing' is assumed.
208  */
209 void disorder_cgi_action(const char *action) {
210   int n;
211
212   /* Consult CGI args if caller had no view */
213   if(!action)
214     action = cgi_get("action");
215   /* Pick a default if nobody cares at all */
216   if(!action) {
217     /* We allow URLs which are just c=... in order to keep confirmation URLs,
218      * which are user-facing, as short as possible.  Actually we could lose the
219      * c= for this... */
220     if(cgi_get("c"))
221       action = "confirm";
222     else
223       action = "playing";
224     /* Make sure 'action' is always set */
225     cgi_set("action", action);
226   }
227   if((n = TABLE_FIND(actions, struct action, name, action)) >= 0)
228     /* Its a known action */
229     actions[n].handler();
230   else {
231     /* Just expand the template */
232     if(printf("Content-Type: text/html\n"
233               "%s\n"
234               "\n", cookie()) < 0)
235       fatal(errno, "error writing to stdout");
236     disorder_cgi_expand(action);
237   }
238 }
239
240 /** @brief Generate an error page */
241 void disorder_cgi_error(const char *msg, ...) {
242   va_list ap;
243
244   va_start(ap, msg);
245   byte_xvasprintf(&error_string, msg, ap);
246   va_end(ap);
247   disorder_cgi_expand("error");
248 }
249
250 /** @brief Log in as the current user or guest if none */
251 void disorder_cgi_login(void) {
252   /* Junk old data */
253   lookup_reset();
254   /* Reconnect */
255   if(disorder_connect_cookie(client, login_cookie)) {
256     disorder_cgi_error("Cannot connect to server");
257     exit(0);
258   }
259   /* If there was a cookie but it went bad, we forget it */
260   if(login_cookie && !strcmp(disorder_user(client), "guest"))
261     login_cookie = 0;
262 }
263
264 /*
265 Local Variables:
266 c-basic-offset:2
267 comment-column:40
268 fill-column:79
269 indent-tabs-mode:nil
270 End:
271 */