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