Blame


1 04e4e993 2023-08-14 op /*
2 04e4e993 2023-08-14 op * Copyright (c) 2023 Omar Polo <op@omarpolo.com>
3 04e4e993 2023-08-14 op * Copyright (c) 2014 Reyk Floeter <reyk@openbsd.org>
4 04e4e993 2023-08-14 op *
5 04e4e993 2023-08-14 op * Permission to use, copy, modify, and distribute this software for any
6 04e4e993 2023-08-14 op * purpose with or without fee is hereby granted, provided that the above
7 04e4e993 2023-08-14 op * copyright notice and this permission notice appear in all copies.
8 04e4e993 2023-08-14 op *
9 04e4e993 2023-08-14 op * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 04e4e993 2023-08-14 op * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 04e4e993 2023-08-14 op * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 04e4e993 2023-08-14 op * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 04e4e993 2023-08-14 op * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 04e4e993 2023-08-14 op * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 04e4e993 2023-08-14 op * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 04e4e993 2023-08-14 op */
17 04e4e993 2023-08-14 op
18 04e4e993 2023-08-14 op #include "config.h"
19 04e4e993 2023-08-14 op
20 04e4e993 2023-08-14 op #include <sys/socket.h>
21 04e4e993 2023-08-14 op #include <sys/types.h>
22 04e4e993 2023-08-14 op #include <sys/un.h>
23 04e4e993 2023-08-14 op
24 04e4e993 2023-08-14 op #include <ctype.h>
25 04e4e993 2023-08-14 op #include <errno.h>
26 04e4e993 2023-08-14 op #include <fnmatch.h>
27 04e4e993 2023-08-14 op #include <limits.h>
28 04e4e993 2023-08-14 op #include <locale.h>
29 04e4e993 2023-08-14 op #include <netdb.h>
30 04e4e993 2023-08-14 op #include <poll.h>
31 04e4e993 2023-08-14 op #include <signal.h>
32 04e4e993 2023-08-14 op #include <stdio.h>
33 04e4e993 2023-08-14 op #include <stdlib.h>
34 04e4e993 2023-08-14 op #include <string.h>
35 04e4e993 2023-08-14 op #include <syslog.h>
36 04e4e993 2023-08-14 op #include <unistd.h>
37 04e4e993 2023-08-14 op
38 04e4e993 2023-08-14 op #include "amused.h"
39 3634fa70 2023-08-31 op #include "bufio.h"
40 80d5f5ad 2023-08-25 op #include "ev.h"
41 04e4e993 2023-08-14 op #include "http.h"
42 04e4e993 2023-08-14 op #include "log.h"
43 04e4e993 2023-08-14 op #include "playlist.h"
44 b42d807f 2023-09-02 op #include "ws.h"
45 04e4e993 2023-08-14 op #include "xmalloc.h"
46 04e4e993 2023-08-14 op
47 04e4e993 2023-08-14 op #ifndef nitems
48 04e4e993 2023-08-14 op #define nitems(x) (sizeof(x)/sizeof(x[0]))
49 04e4e993 2023-08-14 op #endif
50 04e4e993 2023-08-14 op
51 04e4e993 2023-08-14 op #define FORM_URLENCODED "application/x-www-form-urlencoded"
52 04e4e993 2023-08-14 op
53 04e4e993 2023-08-14 op #define ICON_REPEAT_ALL "🔁"
54 04e4e993 2023-08-14 op #define ICON_REPEAT_ONE "🔂"
55 04e4e993 2023-08-14 op #define ICON_PREV "⏮"
56 04e4e993 2023-08-14 op #define ICON_NEXT "⏭"
57 04e4e993 2023-08-14 op #define ICON_STOP "⏹"
58 04e4e993 2023-08-14 op #define ICON_PAUSE "⏸"
59 04e4e993 2023-08-14 op #define ICON_TOGGLE "⏯"
60 04e4e993 2023-08-14 op #define ICON_PLAY "⏵"
61 04e4e993 2023-08-14 op
62 b42d807f 2023-09-02 op static struct clthead clients;
63 04e4e993 2023-08-14 op static struct imsgbuf ibuf;
64 20dff2d2 2023-08-31 op static struct playlist playlist_tmp;
65 20dff2d2 2023-08-31 op static struct player_status player_status;
66 20dff2d2 2023-08-31 op static uint64_t position, duration;
67 04e4e993 2023-08-14 op
68 b42d807f 2023-09-02 op static void client_ev(int, int, void *);
69 b42d807f 2023-09-02 op
70 04e4e993 2023-08-14 op const char *head = "<!doctype html>"
71 04e4e993 2023-08-14 op "<html>"
72 04e4e993 2023-08-14 op "<head>"
73 04e4e993 2023-08-14 op "<meta name='viewport' content='width=device-width, initial-scale=1'/>"
74 04e4e993 2023-08-14 op "<title>Amused Web</title>"
75 cc932200 2023-08-31 op "<link rel='stylesheet' href='/style.css?v=0'>"
76 cc932200 2023-08-31 op "</style>"
77 cc932200 2023-08-31 op "</head>"
78 cc932200 2023-08-31 op "<body>";
79 cc932200 2023-08-31 op
80 cc932200 2023-08-31 op const char *css = "*{box-sizing:border-box}"
81 04e4e993 2023-08-14 op "html,body{"
82 04e4e993 2023-08-14 op " padding: 0;"
83 04e4e993 2023-08-14 op " border: 0;"
84 04e4e993 2023-08-14 op " margin: 0;"
85 04e4e993 2023-08-14 op "}"
86 04e4e993 2023-08-14 op "main{"
87 04e4e993 2023-08-14 op " display: flex;"
88 04e4e993 2023-08-14 op " flex-direction: column;"
89 04e4e993 2023-08-14 op "}"
90 04e4e993 2023-08-14 op "button{cursor:pointer}"
91 04e4e993 2023-08-14 op ".searchbox{"
92 04e4e993 2023-08-14 op " position: sticky;"
93 04e4e993 2023-08-14 op " top: 0;"
94 04e4e993 2023-08-14 op "}"
95 04e4e993 2023-08-14 op ".searchbox input{"
96 04e4e993 2023-08-14 op " width: 100%;"
97 04e4e993 2023-08-14 op " padding: 9px;"
98 04e4e993 2023-08-14 op "}"
99 04e4e993 2023-08-14 op ".playlist-wrapper{min-height:80vh}"
100 04e4e993 2023-08-14 op ".playlist{"
101 04e4e993 2023-08-14 op " list-style: none;"
102 04e4e993 2023-08-14 op " padding: 0;"
103 04e4e993 2023-08-14 op " margin: 0;"
104 04e4e993 2023-08-14 op "}"
105 04e4e993 2023-08-14 op ".playlist button{"
106 04e4e993 2023-08-14 op " font-family: monospace;"
107 04e4e993 2023-08-14 op " text-align: left;"
108 04e4e993 2023-08-14 op " width: 100%;"
109 04e4e993 2023-08-14 op " padding: 5px;"
110 04e4e993 2023-08-14 op " border: 0;"
111 04e4e993 2023-08-14 op " background: transparent;"
112 04e4e993 2023-08-14 op " transition: background-color .25s ease-in-out;"
113 04e4e993 2023-08-14 op "}"
114 04e4e993 2023-08-14 op ".playlist button::before{"
115 04e4e993 2023-08-14 op " content: \"\";"
116 04e4e993 2023-08-14 op " width: 2ch;"
117 04e4e993 2023-08-14 op " display: inline-block;"
118 04e4e993 2023-08-14 op "}"
119 04e4e993 2023-08-14 op ".playlist button:hover{"
120 04e4e993 2023-08-14 op " background-color: #dfdddd;"
121 04e4e993 2023-08-14 op "}"
122 04e4e993 2023-08-14 op ".playlist #current button{"
123 04e4e993 2023-08-14 op " font-weight: bold;"
124 04e4e993 2023-08-14 op "}"
125 04e4e993 2023-08-14 op ".playlist #current button::before{"
126 04e4e993 2023-08-14 op " content: \"→ \";"
127 04e4e993 2023-08-14 op " font-weight: bold;"
128 04e4e993 2023-08-14 op "}"
129 04e4e993 2023-08-14 op ".controls{"
130 04e4e993 2023-08-14 op " position: sticky;"
131 04e4e993 2023-08-14 op " width: 100%;"
132 04e4e993 2023-08-14 op " max-width: 800px;"
133 04e4e993 2023-08-14 op " margin: 0 auto;"
134 04e4e993 2023-08-14 op " bottom: 0;"
135 04e4e993 2023-08-14 op " background-color: white;"
136 04e4e993 2023-08-14 op " background: #3d3d3d;"
137 04e4e993 2023-08-14 op " color: white;"
138 04e4e993 2023-08-14 op " border-radius: 10px 10px 0 0;"
139 04e4e993 2023-08-14 op " padding: 10px;"
140 04e4e993 2023-08-14 op " text-align: center;"
141 04e4e993 2023-08-14 op " order: 2;"
142 04e4e993 2023-08-14 op "}"
143 04e4e993 2023-08-14 op ".controls p{"
144 04e4e993 2023-08-14 op " margin: .4rem;"
145 04e4e993 2023-08-14 op "}"
146 04e4e993 2023-08-14 op ".controls a{"
147 04e4e993 2023-08-14 op " color: white;"
148 04e4e993 2023-08-14 op "}"
149 04e4e993 2023-08-14 op ".controls .status{"
150 04e4e993 2023-08-14 op " font-size: 0.9rem;"
151 04e4e993 2023-08-14 op "}"
152 04e4e993 2023-08-14 op ".controls button{"
153 04e4e993 2023-08-14 op " margin: 5px;"
154 04e4e993 2023-08-14 op " padding: 5px 20px;"
155 04e4e993 2023-08-14 op "}"
156 04e4e993 2023-08-14 op ".mode-active{"
157 04e4e993 2023-08-14 op " color: #0064ff;"
158 cc932200 2023-08-31 op "}";
159 04e4e993 2023-08-14 op
160 cc932200 2023-08-31 op const char *js =
161 b42d807f 2023-09-02 op "var ws;"
162 b42d807f 2023-09-02 op "let pos=0, dur=0;"
163 b42d807f 2023-09-02 op "const playlist=document.querySelector('.playlist');"
164 04e4e993 2023-08-14 op "function cur(e) {"
165 04e4e993 2023-08-14 op " if (e) {e.preventDefault()}"
166 04e4e993 2023-08-14 op " let cur = document.querySelector('#current');"
167 04e4e993 2023-08-14 op " if (cur) {cur.scrollIntoView(); window.scrollBy(0, -100);}"
168 b42d807f 2023-09-02 op "};"
169 b42d807f 2023-09-02 op "function b(x){return x=='on'};"
170 b42d807f 2023-09-02 op "function c(p, c){"
171 b42d807f 2023-09-02 op " const l=document.createElement('li');"
172 b42d807f 2023-09-02 op " if(c){l.id='current'};"
173 b42d807f 2023-09-02 op " const b=document.createElement('button');"
174 b42d807f 2023-09-02 op " b.type='submit'; b.name='jump'; b.value=p;"
175 b42d807f 2023-09-02 op " b.innerText=p;"
176 b42d807f 2023-09-02 op " l.appendChild(b);"
177 b42d807f 2023-09-02 op " playlist.appendChild(l);"
178 04e4e993 2023-08-14 op "}"
179 b42d807f 2023-09-02 op "function d(t){"
180 b42d807f 2023-09-02 op " const [, type, payload] = t.split(/^(.):(.*)$/);"
181 b42d807f 2023-09-02 op " if (type=='s'){"
182 b42d807f 2023-09-02 op " let s=payload.split(' ');"
183 b42d807f 2023-09-02 op " pos=s[0], dur=s[1];"
184 b42d807f 2023-09-02 op " } else if (type=='S') {"
185 b42d807f 2023-09-02 op " const btn=document.querySelector('#toggle');"
186 b42d807f 2023-09-02 op " if (payload=='playing') {"
187 b42d807f 2023-09-02 op " btn.innerHTML='"ICON_PAUSE"';"
188 b42d807f 2023-09-02 op " btn.value='pause';"
189 b42d807f 2023-09-02 op " } else {"
190 b42d807f 2023-09-02 op " btn.innerHTML='"ICON_PLAY"';"
191 b42d807f 2023-09-02 op " btn.value='play';"
192 b42d807f 2023-09-02 op " }"
193 b42d807f 2023-09-02 op " } else if (type=='r') {"
194 b42d807f 2023-09-02 op " const btn=document.querySelector('#rone');"
195 b42d807f 2023-09-02 op " btn.className=b(payload)?'mode-active':'';"
196 b42d807f 2023-09-02 op " } else if (type=='R') {"
197 b42d807f 2023-09-02 op " const btn=document.querySelector('#rall');"
198 b42d807f 2023-09-02 op " btn.className=b(payload)?'mode-active':'';"
199 b42d807f 2023-09-02 op " } else if (type=='c') {"
200 b42d807f 2023-09-02 op /* consume */
201 b42d807f 2023-09-02 op " } else if (type=='x') {"
202 b42d807f 2023-09-02 op " playlist.innerHTML='';"
203 b42d807f 2023-09-02 op " } else if (type=='A') {"
204 b42d807f 2023-09-02 op " c(payload, true);"
205 b42d807f 2023-09-02 op " } else if (type=='a') {"
206 b42d807f 2023-09-02 op " c(payload, false);"
207 b42d807f 2023-09-02 op " } else if (type=='C') {"
208 b42d807f 2023-09-02 op " const t=document.querySelector('.controls>p>a');"
209 b42d807f 2023-09-02 op " t.innerText = payload.replace(/.*\\//, '');"
210 b42d807f 2023-09-02 op " cur();"
211 b42d807f 2023-09-02 op " } else {"
212 b42d807f 2023-09-02 op " console.log('unknown:',t);"
213 b42d807f 2023-09-02 op " }"
214 b42d807f 2023-09-02 op "};"
215 b42d807f 2023-09-02 op "function w(){"
216 b42d807f 2023-09-02 op " ws = new WebSocket((location.protocol=='http:'?'ws://':'wss://')"
217 b42d807f 2023-09-02 op " + location.host + '/ws');"
218 b42d807f 2023-09-02 op " ws.addEventListener('open', () => console.log('ws: connected'));"
219 b42d807f 2023-09-02 op " ws.addEventListener('close', () => console.log('ws: closed'));"
220 b42d807f 2023-09-02 op " ws.addEventListener('message', e => d(e.data))"
221 b42d807f 2023-09-02 op "};"
222 b42d807f 2023-09-02 op "w();"
223 04e4e993 2023-08-14 op "cur();"
224 b42d807f 2023-09-02 op "document.querySelector('.controls a').addEventListener('click',cur);"
225 b42d807f 2023-09-02 op "document.querySelectorAll('form').forEach(f => {"
226 b42d807f 2023-09-02 op " f.action='/a/'+f.getAttribute('action');"
227 b42d807f 2023-09-02 op " f.addEventListener('submit', e => {"
228 b42d807f 2023-09-02 op " e.preventDefault();"
229 b42d807f 2023-09-02 op " const fd = new FormData(f);"
230 b42d807f 2023-09-02 op " if (e.submitter && e.submitter.value && e.submitter.value != '')"
231 b42d807f 2023-09-02 op " fd.append(e.submitter.name, e.submitter.value);"
232 b42d807f 2023-09-02 op " fetch(f.action, {"
233 b42d807f 2023-09-02 op " method:'POST',"
234 b42d807f 2023-09-02 op " body: new URLSearchParams(fd)"
235 b42d807f 2023-09-02 op " })"
236 b42d807f 2023-09-02 op " .catch(x => console.log('failed to submit form:', x));"
237 b42d807f 2023-09-02 op " });"
238 b42d807f 2023-09-02 op "});";
239 04e4e993 2023-08-14 op
240 cc932200 2023-08-31 op const char *foot = "<script src='/app.js?v=0'></script></body></html>";
241 cc932200 2023-08-31 op
242 04e4e993 2023-08-14 op static int
243 04e4e993 2023-08-14 op dial(const char *sock)
244 04e4e993 2023-08-14 op {
245 04e4e993 2023-08-14 op struct sockaddr_un sa;
246 04e4e993 2023-08-14 op size_t len;
247 04e4e993 2023-08-14 op int s;
248 04e4e993 2023-08-14 op
249 04e4e993 2023-08-14 op memset(&sa, 0, sizeof(sa));
250 04e4e993 2023-08-14 op sa.sun_family = AF_UNIX;
251 04e4e993 2023-08-14 op len = strlcpy(sa.sun_path, sock, sizeof(sa.sun_path));
252 04e4e993 2023-08-14 op if (len >= sizeof(sa.sun_path))
253 04e4e993 2023-08-14 op err(1, "path too long: %s", sock);
254 04e4e993 2023-08-14 op
255 04e4e993 2023-08-14 op if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
256 04e4e993 2023-08-14 op err(1, "socket");
257 04e4e993 2023-08-14 op if (connect(s, (struct sockaddr *)&sa, sizeof(sa)) == -1)
258 04e4e993 2023-08-14 op err(1, "failed to connect to %s", sock);
259 04e4e993 2023-08-14 op
260 04e4e993 2023-08-14 op return s;
261 04e4e993 2023-08-14 op }
262 04e4e993 2023-08-14 op
263 04e4e993 2023-08-14 op /*
264 04e4e993 2023-08-14 op * Adapted from usr.sbin/httpd/httpd.c' url_decode.
265 04e4e993 2023-08-14 op */
266 04e4e993 2023-08-14 op static int
267 04e4e993 2023-08-14 op url_decode(char *url)
268 04e4e993 2023-08-14 op {
269 04e4e993 2023-08-14 op char*p, *q;
270 04e4e993 2023-08-14 op char hex[3] = {0};
271 04e4e993 2023-08-14 op unsigned long x;
272 04e4e993 2023-08-14 op
273 04e4e993 2023-08-14 op p = q = url;
274 04e4e993 2023-08-14 op while (*p != '\0') {
275 04e4e993 2023-08-14 op switch (*p) {
276 04e4e993 2023-08-14 op case '%':
277 04e4e993 2023-08-14 op /* Encoding character is followed by two hex chars */
278 04e4e993 2023-08-14 op if (!isxdigit((unsigned char)p[1]) ||
279 04e4e993 2023-08-14 op !isxdigit((unsigned char)p[2]) ||
280 04e4e993 2023-08-14 op (p[1] == '0' && p[2] == '0'))
281 04e4e993 2023-08-14 op return (-1);
282 04e4e993 2023-08-14 op
283 04e4e993 2023-08-14 op hex[0] = p[1];
284 04e4e993 2023-08-14 op hex[1] = p[2];
285 04e4e993 2023-08-14 op
286 04e4e993 2023-08-14 op /*
287 04e4e993 2023-08-14 op * We don't have to validate "hex" because it is
288 04e4e993 2023-08-14 op * guaranteed to include two hex chars followed
289 04e4e993 2023-08-14 op * by NUL.
290 04e4e993 2023-08-14 op */
291 04e4e993 2023-08-14 op x = strtoul(hex, NULL, 16);
292 04e4e993 2023-08-14 op *q = (char)x;
293 04e4e993 2023-08-14 op p += 2;
294 04e4e993 2023-08-14 op break;
295 04e4e993 2023-08-14 op case '+':
296 04e4e993 2023-08-14 op *q = ' ';
297 04e4e993 2023-08-14 op break;
298 04e4e993 2023-08-14 op default:
299 04e4e993 2023-08-14 op *q = *p;
300 04e4e993 2023-08-14 op break;
301 04e4e993 2023-08-14 op }
302 04e4e993 2023-08-14 op p++;
303 04e4e993 2023-08-14 op q++;
304 04e4e993 2023-08-14 op }
305 04e4e993 2023-08-14 op *q = '\0';
306 04e4e993 2023-08-14 op
307 04e4e993 2023-08-14 op return (0);
308 04e4e993 2023-08-14 op }
309 04e4e993 2023-08-14 op
310 b42d807f 2023-09-02 op static int
311 b42d807f 2023-09-02 op dispatch_event(const char *msg)
312 b42d807f 2023-09-02 op {
313 b42d807f 2023-09-02 op struct client *clt;
314 b42d807f 2023-09-02 op size_t len;
315 b42d807f 2023-09-02 op int ret = 0;
316 b42d807f 2023-09-02 op
317 b42d807f 2023-09-02 op len = strlen(msg);
318 b42d807f 2023-09-02 op TAILQ_FOREACH(clt, &clients, clients) {
319 b42d807f 2023-09-02 op if (!clt->ws || clt->done || clt->err)
320 b42d807f 2023-09-02 op continue;
321 b42d807f 2023-09-02 op
322 b42d807f 2023-09-02 op if (ws_compose(clt, WST_TEXT, msg, len) == -1)
323 b42d807f 2023-09-02 op ret = -1;
324 b42d807f 2023-09-02 op
325 b42d807f 2023-09-02 op ev_add(clt->bio.fd, POLLIN|POLLOUT, client_ev, clt);
326 b42d807f 2023-09-02 op }
327 b42d807f 2023-09-02 op
328 b42d807f 2023-09-02 op return (ret);
329 b42d807f 2023-09-02 op }
330 b42d807f 2023-09-02 op
331 b42d807f 2023-09-02 op static int
332 b42d807f 2023-09-02 op dispatch_event_status(void)
333 b42d807f 2023-09-02 op {
334 b42d807f 2023-09-02 op const char *status;
335 b42d807f 2023-09-02 op char buf[PATH_MAX + 2];
336 b42d807f 2023-09-02 op int r;
337 b42d807f 2023-09-02 op
338 b42d807f 2023-09-02 op switch (player_status.status) {
339 b42d807f 2023-09-02 op case STATE_STOPPED: status = "stopped"; break;
340 b42d807f 2023-09-02 op case STATE_PLAYING: status = "playing"; break;
341 b42d807f 2023-09-02 op case STATE_PAUSED: status = "paused"; break;
342 b42d807f 2023-09-02 op default: status = "unknown";
343 b42d807f 2023-09-02 op }
344 b42d807f 2023-09-02 op
345 b42d807f 2023-09-02 op r = snprintf(buf, sizeof(buf), "S:%s", status);
346 b42d807f 2023-09-02 op if (r < 0 || (size_t)r >= sizeof(buf)) {
347 b42d807f 2023-09-02 op log_warn("snprintf");
348 b42d807f 2023-09-02 op return -1;
349 b42d807f 2023-09-02 op }
350 b42d807f 2023-09-02 op dispatch_event(buf);
351 b42d807f 2023-09-02 op
352 b42d807f 2023-09-02 op r = snprintf(buf, sizeof(buf), "r:%s",
353 b42d807f 2023-09-02 op player_status.mode.repeat_one == MODE_ON ? "on" : "off");
354 b42d807f 2023-09-02 op if (r < 0 || (size_t)r >= sizeof(buf)) {
355 b42d807f 2023-09-02 op log_warn("snprintf");
356 b42d807f 2023-09-02 op return -1;
357 b42d807f 2023-09-02 op }
358 b42d807f 2023-09-02 op dispatch_event(buf);
359 b42d807f 2023-09-02 op
360 b42d807f 2023-09-02 op r = snprintf(buf, sizeof(buf), "R:%s",
361 b42d807f 2023-09-02 op player_status.mode.repeat_all == MODE_ON ? "on" : "off");
362 b42d807f 2023-09-02 op if (r < 0 || (size_t)r >= sizeof(buf)) {
363 b42d807f 2023-09-02 op log_warn("snprintf");
364 b42d807f 2023-09-02 op return -1;
365 b42d807f 2023-09-02 op }
366 b42d807f 2023-09-02 op dispatch_event(buf);
367 b42d807f 2023-09-02 op
368 b42d807f 2023-09-02 op r = snprintf(buf, sizeof(buf), "c:%s",
369 b42d807f 2023-09-02 op player_status.mode.consume == MODE_ON ? "on" : "off");
370 b42d807f 2023-09-02 op if (r < 0 || (size_t)r >= sizeof(buf)) {
371 b42d807f 2023-09-02 op log_warn("snprintf");
372 b42d807f 2023-09-02 op return -1;
373 b42d807f 2023-09-02 op }
374 b42d807f 2023-09-02 op dispatch_event(buf);
375 b42d807f 2023-09-02 op
376 b42d807f 2023-09-02 op r = snprintf(buf, sizeof(buf), "C:%s", player_status.path);
377 b42d807f 2023-09-02 op if (r < 0 || (size_t)r >= sizeof(buf)) {
378 b42d807f 2023-09-02 op log_warn("snprintf");
379 b42d807f 2023-09-02 op return -1;
380 b42d807f 2023-09-02 op }
381 b42d807f 2023-09-02 op dispatch_event(buf);
382 b42d807f 2023-09-02 op
383 b42d807f 2023-09-02 op return 0;
384 b42d807f 2023-09-02 op }
385 b42d807f 2023-09-02 op
386 b42d807f 2023-09-02 op static int
387 b42d807f 2023-09-02 op dispatch_event_track(struct player_status *ps)
388 b42d807f 2023-09-02 op {
389 b42d807f 2023-09-02 op char p[PATH_MAX + 2];
390 b42d807f 2023-09-02 op int r;
391 b42d807f 2023-09-02 op
392 b42d807f 2023-09-02 op r = snprintf(p, sizeof(p), "%c:%s",
393 b42d807f 2023-09-02 op ps->status == STATE_PLAYING ? 'A' : 'a', ps->path);
394 b42d807f 2023-09-02 op if (r < 0 || (size_t)r >= sizeof(p))
395 b42d807f 2023-09-02 op return (-1);
396 b42d807f 2023-09-02 op
397 b42d807f 2023-09-02 op return dispatch_event(p);
398 b42d807f 2023-09-02 op }
399 b42d807f 2023-09-02 op
400 04e4e993 2023-08-14 op static void
401 20dff2d2 2023-08-31 op imsg_dispatch(int fd, int ev, void *d)
402 b478a4bd 2023-08-14 op {
403 20dff2d2 2023-08-31 op static ssize_t off;
404 20dff2d2 2023-08-31 op static int off_found;
405 b42d807f 2023-09-02 op char seekmsg[128];
406 20dff2d2 2023-08-31 op struct imsg imsg;
407 20dff2d2 2023-08-31 op struct player_status ps;
408 20dff2d2 2023-08-31 op struct player_event event;
409 20dff2d2 2023-08-31 op const char *msg;
410 20dff2d2 2023-08-31 op ssize_t n;
411 20dff2d2 2023-08-31 op size_t datalen;
412 b42d807f 2023-09-02 op int r;
413 b478a4bd 2023-08-14 op
414 20dff2d2 2023-08-31 op if (ev & (POLLIN|POLLHUP)) {
415 20dff2d2 2023-08-31 op if ((n = imsg_read(&ibuf)) == -1 && errno != EAGAIN)
416 20dff2d2 2023-08-31 op fatal("imsg_read");
417 20dff2d2 2023-08-31 op if (n == 0)
418 20dff2d2 2023-08-31 op fatalx("pipe closed");
419 b478a4bd 2023-08-14 op }
420 20dff2d2 2023-08-31 op if (ev & POLLOUT) {
421 20dff2d2 2023-08-31 op if ((n = msgbuf_write(&ibuf.w)) == -1 && errno != EAGAIN)
422 20dff2d2 2023-08-31 op fatal("msgbuf_write");
423 20dff2d2 2023-08-31 op if (n == 0)
424 20dff2d2 2023-08-31 op fatalx("pipe closed");
425 20dff2d2 2023-08-31 op }
426 b478a4bd 2023-08-14 op
427 20dff2d2 2023-08-31 op for (;;) {
428 20dff2d2 2023-08-31 op if ((n = imsg_get(&ibuf, &imsg)) == -1)
429 20dff2d2 2023-08-31 op fatal("imsg_get");
430 20dff2d2 2023-08-31 op if (n == 0)
431 20dff2d2 2023-08-31 op break;
432 20dff2d2 2023-08-31 op
433 20dff2d2 2023-08-31 op datalen = IMSG_DATA_SIZE(imsg);
434 20dff2d2 2023-08-31 op
435 20dff2d2 2023-08-31 op switch (imsg.hdr.type) {
436 20dff2d2 2023-08-31 op case IMSG_CTL_ERR:
437 20dff2d2 2023-08-31 op msg = imsg.data;
438 20dff2d2 2023-08-31 op if (datalen == 0 || msg[datalen - 1] != '\0')
439 20dff2d2 2023-08-31 op fatalx("malformed error message");
440 20dff2d2 2023-08-31 op log_warnx("error: %s", msg);
441 20dff2d2 2023-08-31 op break;
442 20dff2d2 2023-08-31 op
443 20dff2d2 2023-08-31 op case IMSG_CTL_ADD:
444 20dff2d2 2023-08-31 op playlist_free(&playlist_tmp);
445 20dff2d2 2023-08-31 op imsg_compose(&ibuf, IMSG_CTL_SHOW, 0, 0, -1, NULL, 0);
446 20dff2d2 2023-08-31 op break;
447 20dff2d2 2023-08-31 op
448 20dff2d2 2023-08-31 op case IMSG_CTL_MONITOR:
449 20dff2d2 2023-08-31 op if (datalen != sizeof(event))
450 20dff2d2 2023-08-31 op fatalx("corrupted IMSG_CTL_MONITOR");
451 20dff2d2 2023-08-31 op memcpy(&event, imsg.data, sizeof(event));
452 20dff2d2 2023-08-31 op switch (event.event) {
453 20dff2d2 2023-08-31 op case IMSG_CTL_PLAY:
454 20dff2d2 2023-08-31 op case IMSG_CTL_PAUSE:
455 20dff2d2 2023-08-31 op case IMSG_CTL_STOP:
456 20dff2d2 2023-08-31 op case IMSG_CTL_MODE:
457 20dff2d2 2023-08-31 op imsg_compose(&ibuf, IMSG_CTL_STATUS, 0, 0, -1,
458 20dff2d2 2023-08-31 op NULL, 0);
459 20dff2d2 2023-08-31 op break;
460 20dff2d2 2023-08-31 op
461 20dff2d2 2023-08-31 op case IMSG_CTL_NEXT:
462 20dff2d2 2023-08-31 op case IMSG_CTL_PREV:
463 20dff2d2 2023-08-31 op case IMSG_CTL_JUMP:
464 20dff2d2 2023-08-31 op case IMSG_CTL_COMMIT:
465 20dff2d2 2023-08-31 op imsg_compose(&ibuf, IMSG_CTL_SHOW, 0, 0, -1,
466 20dff2d2 2023-08-31 op NULL, 0);
467 20dff2d2 2023-08-31 op imsg_compose(&ibuf, IMSG_CTL_STATUS, 0, 0, -1,
468 20dff2d2 2023-08-31 op NULL, 0);
469 20dff2d2 2023-08-31 op break;
470 20dff2d2 2023-08-31 op
471 20dff2d2 2023-08-31 op case IMSG_CTL_SEEK:
472 20dff2d2 2023-08-31 op position = event.position;
473 20dff2d2 2023-08-31 op duration = event.duration;
474 b42d807f 2023-09-02 op r = snprintf(seekmsg, sizeof(seekmsg),
475 b42d807f 2023-09-02 op "s:%lld %lld", (long long)position,
476 b42d807f 2023-09-02 op (long long)duration);
477 b42d807f 2023-09-02 op if (r < 0 || (size_t)r >= sizeof(seekmsg)) {
478 b42d807f 2023-09-02 op log_warn("snprintf failed");
479 b42d807f 2023-09-02 op break;
480 b42d807f 2023-09-02 op }
481 b42d807f 2023-09-02 op dispatch_event(seekmsg);
482 20dff2d2 2023-08-31 op break;
483 20dff2d2 2023-08-31 op
484 20dff2d2 2023-08-31 op default:
485 20dff2d2 2023-08-31 op log_debug("ignoring event %d", event.event);
486 20dff2d2 2023-08-31 op break;
487 20dff2d2 2023-08-31 op }
488 20dff2d2 2023-08-31 op break;
489 20dff2d2 2023-08-31 op
490 20dff2d2 2023-08-31 op case IMSG_CTL_SHOW:
491 20dff2d2 2023-08-31 op if (datalen == 0) {
492 b42d807f 2023-09-02 op if (playlist_tmp.len == 0) {
493 b42d807f 2023-09-02 op dispatch_event("x:");
494 b42d807f 2023-09-02 op off = -1;
495 03069bc3 2023-09-02 op } else if (playlist_tmp.len == off)
496 03069bc3 2023-09-02 op off = -1;
497 20dff2d2 2023-08-31 op playlist_swap(&playlist_tmp, off);
498 20dff2d2 2023-08-31 op memset(&playlist_tmp, 0, sizeof(playlist_tmp));
499 20dff2d2 2023-08-31 op off = 0;
500 20dff2d2 2023-08-31 op off_found = 0;
501 20dff2d2 2023-08-31 op break;
502 20dff2d2 2023-08-31 op }
503 20dff2d2 2023-08-31 op if (datalen != sizeof(ps))
504 20dff2d2 2023-08-31 op fatalx("corrupted IMSG_CTL_SHOW");
505 20dff2d2 2023-08-31 op memcpy(&ps, imsg.data, sizeof(ps));
506 20dff2d2 2023-08-31 op if (ps.path[sizeof(ps.path) - 1] != '\0')
507 20dff2d2 2023-08-31 op fatalx("corrupted IMSG_CTL_SHOW");
508 b42d807f 2023-09-02 op if (playlist_tmp.len == 0)
509 b42d807f 2023-09-02 op dispatch_event("x:");
510 b42d807f 2023-09-02 op dispatch_event_track(&ps);
511 20dff2d2 2023-08-31 op playlist_push(&playlist_tmp, ps.path);
512 20dff2d2 2023-08-31 op if (ps.status == STATE_PLAYING)
513 20dff2d2 2023-08-31 op off_found = 1;
514 20dff2d2 2023-08-31 op if (!off_found)
515 20dff2d2 2023-08-31 op off++;
516 20dff2d2 2023-08-31 op break;
517 20dff2d2 2023-08-31 op
518 20dff2d2 2023-08-31 op case IMSG_CTL_STATUS:
519 20dff2d2 2023-08-31 op if (datalen != sizeof(player_status))
520 20dff2d2 2023-08-31 op fatalx("corrupted IMSG_CTL_STATUS");
521 20dff2d2 2023-08-31 op memcpy(&player_status, imsg.data, datalen);
522 20dff2d2 2023-08-31 op if (player_status.path[sizeof(player_status.path) - 1]
523 20dff2d2 2023-08-31 op != '\0')
524 20dff2d2 2023-08-31 op fatalx("corrupted IMSG_CTL_STATUS");
525 b42d807f 2023-09-02 op dispatch_event_status();
526 20dff2d2 2023-08-31 op break;
527 20dff2d2 2023-08-31 op }
528 20dff2d2 2023-08-31 op }
529 20dff2d2 2023-08-31 op
530 20dff2d2 2023-08-31 op ev = POLLIN;
531 20dff2d2 2023-08-31 op if (ibuf.w.queued)
532 20dff2d2 2023-08-31 op ev |= POLLOUT;
533 20dff2d2 2023-08-31 op ev_add(fd, ev, imsg_dispatch, NULL);
534 b478a4bd 2023-08-14 op }
535 b478a4bd 2023-08-14 op
536 b478a4bd 2023-08-14 op static void
537 3634fa70 2023-08-31 op route_notfound(struct client *clt)
538 04e4e993 2023-08-14 op {
539 3634fa70 2023-08-31 op if (http_reply(clt, 404, "Not Found", "text/plain") == -1 ||
540 3634fa70 2023-08-31 op http_writes(clt, "Page not found\n") == -1)
541 04e4e993 2023-08-14 op return;
542 04e4e993 2023-08-14 op }
543 04e4e993 2023-08-14 op
544 04e4e993 2023-08-14 op static void
545 3634fa70 2023-08-31 op render_playlist(struct client *clt)
546 04e4e993 2023-08-14 op {
547 20dff2d2 2023-08-31 op ssize_t i;
548 9ae51b1f 2023-09-02 op const char *path;
549 20dff2d2 2023-08-31 op int current;
550 04e4e993 2023-08-14 op
551 3634fa70 2023-08-31 op http_writes(clt, "<section class='playlist-wrapper'>");
552 3634fa70 2023-08-31 op http_writes(clt, "<form action=jump method=post"
553 04e4e993 2023-08-14 op " enctype='"FORM_URLENCODED"'>");
554 3634fa70 2023-08-31 op http_writes(clt, "<ul class=playlist>");
555 04e4e993 2023-08-14 op
556 20dff2d2 2023-08-31 op for (i = 0; i < playlist.len; ++i) {
557 20dff2d2 2023-08-31 op current = play_off == i;
558 04e4e993 2023-08-14 op
559 9ae51b1f 2023-09-02 op path = playlist.songs[i];
560 04e4e993 2023-08-14 op
561 20dff2d2 2023-08-31 op http_fmt(clt, "<li%s>", current ? " id=current" : "");
562 20dff2d2 2023-08-31 op http_writes(clt, "<button type=submit name=jump value=\"");
563 20dff2d2 2023-08-31 op http_htmlescape(clt, path);
564 20dff2d2 2023-08-31 op http_writes(clt, "\">");
565 9ae51b1f 2023-09-02 op http_htmlescape(clt, path);
566 20dff2d2 2023-08-31 op http_writes(clt, "</button></li>");
567 04e4e993 2023-08-14 op }
568 04e4e993 2023-08-14 op
569 3634fa70 2023-08-31 op http_writes(clt, "</ul>");
570 3634fa70 2023-08-31 op http_writes(clt, "</form>");
571 3634fa70 2023-08-31 op http_writes(clt, "</section>");
572 04e4e993 2023-08-14 op }
573 04e4e993 2023-08-14 op
574 04e4e993 2023-08-14 op static void
575 3634fa70 2023-08-31 op render_controls(struct client *clt)
576 04e4e993 2023-08-14 op {
577 04e4e993 2023-08-14 op const char *oc, *ac, *p;
578 04e4e993 2023-08-14 op int playing;
579 04e4e993 2023-08-14 op
580 20dff2d2 2023-08-31 op ac = player_status.mode.repeat_all ? " class='mode-active'" : "";
581 20dff2d2 2023-08-31 op oc = player_status.mode.repeat_one ? " class='mode-active'" : "";
582 20dff2d2 2023-08-31 op playing = player_status.status == STATE_PLAYING;
583 04e4e993 2023-08-14 op
584 20dff2d2 2023-08-31 op if ((p = strrchr(player_status.path, '/')) != NULL)
585 04e4e993 2023-08-14 op p++;
586 04e4e993 2023-08-14 op else
587 20dff2d2 2023-08-31 op p = player_status.path;
588 04e4e993 2023-08-14 op
589 3634fa70 2023-08-31 op if (http_writes(clt, "<section class=controls>") == -1 ||
590 3634fa70 2023-08-31 op http_writes(clt, "<p><a href='#current'>") == -1 ||
591 3634fa70 2023-08-31 op http_htmlescape(clt, p) == -1 ||
592 3634fa70 2023-08-31 op http_writes(clt, "</a></p>") == -1 ||
593 3634fa70 2023-08-31 op http_writes(clt, "<form action=ctrls method=post"
594 04e4e993 2023-08-14 op " enctype='"FORM_URLENCODED"'>") == -1 ||
595 3634fa70 2023-08-31 op http_writes(clt, "<button type=submit name=ctl value=prev>"
596 04e4e993 2023-08-14 op ICON_PREV"</button>") == -1 ||
597 b42d807f 2023-09-02 op http_fmt(clt, "<button id='toggle' type=submit name=ctl value=%s>"
598 04e4e993 2023-08-14 op "%s</button>", playing ? "pause" : "play",
599 04e4e993 2023-08-14 op playing ? ICON_PAUSE : ICON_PLAY) == -1 ||
600 3634fa70 2023-08-31 op http_writes(clt, "<button type=submit name=ctl value=next>"
601 04e4e993 2023-08-14 op ICON_NEXT"</button>") == -1 ||
602 3634fa70 2023-08-31 op http_writes(clt, "</form>") == -1 ||
603 3634fa70 2023-08-31 op http_writes(clt, "<form action=mode method=post"
604 3634fa70 2023-08-31 op " enctype='"FORM_URLENCODED"'>") == -1 ||
605 b42d807f 2023-09-02 op http_fmt(clt, "<button%s id=rall type=submit name=mode value=all>"
606 04e4e993 2023-08-14 op ICON_REPEAT_ALL"</button>", ac) == -1 ||
607 b42d807f 2023-09-02 op http_fmt(clt, "<button%s id=rone type=submit name=mode value=one>"
608 04e4e993 2023-08-14 op ICON_REPEAT_ONE"</button>", oc) == -1 ||
609 3634fa70 2023-08-31 op http_writes(clt, "</form>") == -1 ||
610 3634fa70 2023-08-31 op http_writes(clt, "</section>") == -1)
611 04e4e993 2023-08-14 op return;
612 04e4e993 2023-08-14 op }
613 04e4e993 2023-08-14 op
614 04e4e993 2023-08-14 op static void
615 3634fa70 2023-08-31 op route_home(struct client *clt)
616 04e4e993 2023-08-14 op {
617 3634fa70 2023-08-31 op if (http_reply(clt, 200, "OK", "text/html;charset=UTF-8") == -1)
618 04e4e993 2023-08-14 op return;
619 04e4e993 2023-08-14 op
620 3634fa70 2023-08-31 op if (http_write(clt, head, strlen(head)) == -1)
621 04e4e993 2023-08-14 op return;
622 04e4e993 2023-08-14 op
623 3634fa70 2023-08-31 op if (http_writes(clt, "<main>") == -1)
624 04e4e993 2023-08-14 op return;
625 04e4e993 2023-08-14 op
626 3634fa70 2023-08-31 op if (http_writes(clt, "<section class=searchbox>"
627 04e4e993 2023-08-14 op "<input type=search name=filter aria-label='Filter playlist'"
628 04e4e993 2023-08-14 op " placeholder='Filter playlist' id=search />"
629 04e4e993 2023-08-14 op "</section>") == -1)
630 04e4e993 2023-08-14 op return;
631 04e4e993 2023-08-14 op
632 3634fa70 2023-08-31 op render_controls(clt);
633 3634fa70 2023-08-31 op render_playlist(clt);
634 04e4e993 2023-08-14 op
635 3634fa70 2023-08-31 op if (http_writes(clt, "</main>") == -1)
636 04e4e993 2023-08-14 op return;
637 04e4e993 2023-08-14 op
638 3634fa70 2023-08-31 op http_write(clt, foot, strlen(foot));
639 04e4e993 2023-08-14 op }
640 04e4e993 2023-08-14 op
641 04e4e993 2023-08-14 op static void
642 3634fa70 2023-08-31 op route_jump(struct client *clt)
643 04e4e993 2023-08-14 op {
644 04e4e993 2023-08-14 op char path[PATH_MAX];
645 04e4e993 2023-08-14 op char *form, *field;
646 04e4e993 2023-08-14 op int found = 0;
647 04e4e993 2023-08-14 op
648 3634fa70 2023-08-31 op form = clt->buf;
649 04e4e993 2023-08-14 op while ((field = strsep(&form, "&")) != NULL) {
650 04e4e993 2023-08-14 op if (url_decode(field) == -1)
651 04e4e993 2023-08-14 op goto badreq;
652 04e4e993 2023-08-14 op
653 04e4e993 2023-08-14 op if (strncmp(field, "jump=", 5) != 0)
654 04e4e993 2023-08-14 op continue;
655 04e4e993 2023-08-14 op field += 5;
656 04e4e993 2023-08-14 op found = 1;
657 04e4e993 2023-08-14 op
658 99b28969 2023-08-14 op memset(&path, 0, sizeof(path));
659 04e4e993 2023-08-14 op if (strlcpy(path, field, sizeof(path)) >= sizeof(path))
660 04e4e993 2023-08-14 op goto badreq;
661 04e4e993 2023-08-14 op
662 04e4e993 2023-08-14 op log_warnx("path is %s", path);
663 04e4e993 2023-08-14 op imsg_compose(&ibuf, IMSG_CTL_JUMP, 0, 0, -1,
664 04e4e993 2023-08-14 op path, sizeof(path));
665 20dff2d2 2023-08-31 op ev_add(ibuf.w.fd, POLLIN|POLLOUT, imsg_dispatch, NULL);
666 04e4e993 2023-08-14 op break;
667 04e4e993 2023-08-14 op }
668 04e4e993 2023-08-14 op
669 04e4e993 2023-08-14 op if (!found)
670 04e4e993 2023-08-14 op goto badreq;
671 04e4e993 2023-08-14 op
672 b42d807f 2023-09-02 op if (!strncmp(clt->req.path, "/a/", 2))
673 b42d807f 2023-09-02 op http_reply(clt, 200, "OK", "text/plain");
674 b42d807f 2023-09-02 op else
675 b42d807f 2023-09-02 op http_reply(clt, 302, "See Other", "/");
676 04e4e993 2023-08-14 op return;
677 04e4e993 2023-08-14 op
678 04e4e993 2023-08-14 op badreq:
679 3634fa70 2023-08-31 op http_reply(clt, 400, "Bad Request", "text/plain");
680 3634fa70 2023-08-31 op http_writes(clt, "Bad Request.\n");
681 04e4e993 2023-08-14 op }
682 04e4e993 2023-08-14 op
683 04e4e993 2023-08-14 op static void
684 3634fa70 2023-08-31 op route_controls(struct client *clt)
685 04e4e993 2023-08-14 op {
686 04e4e993 2023-08-14 op char *form, *field;
687 04e4e993 2023-08-14 op int cmd, found = 0;
688 04e4e993 2023-08-14 op
689 3634fa70 2023-08-31 op form = clt->buf;
690 04e4e993 2023-08-14 op while ((field = strsep(&form, "&")) != NULL) {
691 04e4e993 2023-08-14 op if (url_decode(field) == -1)
692 04e4e993 2023-08-14 op goto badreq;
693 04e4e993 2023-08-14 op
694 04e4e993 2023-08-14 op if (strncmp(field, "ctl=", 4) != 0)
695 04e4e993 2023-08-14 op continue;
696 04e4e993 2023-08-14 op field += 4;
697 04e4e993 2023-08-14 op found = 1;
698 04e4e993 2023-08-14 op
699 04e4e993 2023-08-14 op if (!strcmp(field, "play"))
700 04e4e993 2023-08-14 op cmd = IMSG_CTL_PLAY;
701 04e4e993 2023-08-14 op else if (!strcmp(field, "pause"))
702 04e4e993 2023-08-14 op cmd = IMSG_CTL_PAUSE;
703 04e4e993 2023-08-14 op else if (!strcmp(field, "next"))
704 04e4e993 2023-08-14 op cmd = IMSG_CTL_NEXT;
705 04e4e993 2023-08-14 op else if (!strcmp(field, "prev"))
706 04e4e993 2023-08-14 op cmd = IMSG_CTL_PREV;
707 04e4e993 2023-08-14 op else
708 04e4e993 2023-08-14 op goto badreq;
709 04e4e993 2023-08-14 op
710 04e4e993 2023-08-14 op imsg_compose(&ibuf, cmd, 0, 0, -1, NULL, 0);
711 04e4e993 2023-08-14 op imsg_flush(&ibuf);
712 04e4e993 2023-08-14 op break;
713 04e4e993 2023-08-14 op }
714 04e4e993 2023-08-14 op
715 04e4e993 2023-08-14 op if (!found)
716 04e4e993 2023-08-14 op goto badreq;
717 04e4e993 2023-08-14 op
718 b42d807f 2023-09-02 op if (!strncmp(clt->req.path, "/a/", 2))
719 b42d807f 2023-09-02 op http_reply(clt, 200, "OK", "text/plain");
720 b42d807f 2023-09-02 op else
721 b42d807f 2023-09-02 op http_reply(clt, 302, "See Other", "/");
722 04e4e993 2023-08-14 op return;
723 04e4e993 2023-08-14 op
724 04e4e993 2023-08-14 op badreq:
725 3634fa70 2023-08-31 op http_reply(clt, 400, "Bad Request", "text/plain");
726 3634fa70 2023-08-31 op http_writes(clt, "Bad Request.\n");
727 04e4e993 2023-08-14 op }
728 04e4e993 2023-08-14 op
729 04e4e993 2023-08-14 op static void
730 3634fa70 2023-08-31 op route_mode(struct client *clt)
731 04e4e993 2023-08-14 op {
732 04e4e993 2023-08-14 op char *form, *field;
733 04e4e993 2023-08-14 op int found = 0;
734 04e4e993 2023-08-14 op struct player_mode pm;
735 04e4e993 2023-08-14 op
736 04e4e993 2023-08-14 op pm.repeat_one = pm.repeat_all = pm.consume = MODE_UNDEF;
737 04e4e993 2023-08-14 op
738 3634fa70 2023-08-31 op form = clt->buf;
739 04e4e993 2023-08-14 op while ((field = strsep(&form, "&")) != NULL) {
740 04e4e993 2023-08-14 op if (url_decode(field) == -1)
741 04e4e993 2023-08-14 op goto badreq;
742 04e4e993 2023-08-14 op
743 04e4e993 2023-08-14 op if (strncmp(field, "mode=", 5) != 0)
744 04e4e993 2023-08-14 op continue;
745 04e4e993 2023-08-14 op field += 5;
746 04e4e993 2023-08-14 op found = 1;
747 04e4e993 2023-08-14 op
748 04e4e993 2023-08-14 op if (!strcmp(field, "all"))
749 04e4e993 2023-08-14 op pm.repeat_all = MODE_TOGGLE;
750 04e4e993 2023-08-14 op else if (!strcmp(field, "one"))
751 04e4e993 2023-08-14 op pm.repeat_one = MODE_TOGGLE;
752 04e4e993 2023-08-14 op else
753 04e4e993 2023-08-14 op goto badreq;
754 04e4e993 2023-08-14 op
755 04e4e993 2023-08-14 op imsg_compose(&ibuf, IMSG_CTL_MODE, 0, 0, -1, &pm, sizeof(pm));
756 20dff2d2 2023-08-31 op ev_add(ibuf.w.fd, POLLIN|POLLOUT, imsg_dispatch, NULL);
757 04e4e993 2023-08-14 op break;
758 04e4e993 2023-08-14 op }
759 04e4e993 2023-08-14 op
760 04e4e993 2023-08-14 op if (!found)
761 04e4e993 2023-08-14 op goto badreq;
762 04e4e993 2023-08-14 op
763 b42d807f 2023-09-02 op if (!strncmp(clt->req.path, "/a/", 2))
764 b42d807f 2023-09-02 op http_reply(clt, 200, "OK", "text/plain");
765 b42d807f 2023-09-02 op else
766 b42d807f 2023-09-02 op http_reply(clt, 302, "See Other", "/");
767 04e4e993 2023-08-14 op return;
768 04e4e993 2023-08-14 op
769 04e4e993 2023-08-14 op badreq:
770 3634fa70 2023-08-31 op http_reply(clt, 400, "Bad Request", "text/plain");
771 3634fa70 2023-08-31 op http_writes(clt, "Bad Request.\n");
772 b42d807f 2023-09-02 op }
773 b42d807f 2023-09-02 op
774 b42d807f 2023-09-02 op static void
775 b42d807f 2023-09-02 op route_handle_ws(struct client *clt)
776 b42d807f 2023-09-02 op {
777 b42d807f 2023-09-02 op struct buffer *rbuf = &clt->bio.rbuf;
778 b42d807f 2023-09-02 op int type;
779 b42d807f 2023-09-02 op size_t len;
780 b42d807f 2023-09-02 op
781 b42d807f 2023-09-02 op if (ws_read(clt, &type, &len) == -1) {
782 b42d807f 2023-09-02 op if (errno != EAGAIN) {
783 b42d807f 2023-09-02 op log_warn("ws_read");
784 b42d807f 2023-09-02 op clt->done = 1;
785 b42d807f 2023-09-02 op }
786 b42d807f 2023-09-02 op return;
787 b42d807f 2023-09-02 op }
788 b42d807f 2023-09-02 op
789 b42d807f 2023-09-02 op switch (type) {
790 b42d807f 2023-09-02 op case WST_PING:
791 b42d807f 2023-09-02 op ws_compose(clt, WST_PONG, rbuf->buf, len);
792 b42d807f 2023-09-02 op break;
793 b42d807f 2023-09-02 op case WST_TEXT:
794 b42d807f 2023-09-02 op /* log_info("<<< %.*s", (int)len, rbuf->buf); */
795 b42d807f 2023-09-02 op break;
796 b42d807f 2023-09-02 op case WST_CLOSE:
797 b42d807f 2023-09-02 op /* TODO send a close too (ack) */
798 b42d807f 2023-09-02 op clt->done = 1;
799 b42d807f 2023-09-02 op break;
800 b42d807f 2023-09-02 op default:
801 b42d807f 2023-09-02 op log_info("got unexpected ws frame type 0x%02x", type);
802 b42d807f 2023-09-02 op break;
803 b42d807f 2023-09-02 op }
804 b42d807f 2023-09-02 op
805 b42d807f 2023-09-02 op buf_drain(rbuf, len);
806 b42d807f 2023-09-02 op }
807 b42d807f 2023-09-02 op
808 b42d807f 2023-09-02 op static void
809 b42d807f 2023-09-02 op route_init_ws(struct client *clt)
810 b42d807f 2023-09-02 op {
811 b42d807f 2023-09-02 op if (!(clt->req.flags & (R_CONNUPGR|R_UPGRADEWS|R_WSVERSION)) ||
812 b42d807f 2023-09-02 op clt->req.secret == NULL) {
813 b42d807f 2023-09-02 op http_reply(clt, 400, "Bad Request", "text/plain");
814 b42d807f 2023-09-02 op http_writes(clt, "Invalid websocket handshake.\r\n");
815 b42d807f 2023-09-02 op return;
816 b42d807f 2023-09-02 op }
817 b42d807f 2023-09-02 op
818 b42d807f 2023-09-02 op clt->ws = 1;
819 b42d807f 2023-09-02 op clt->done = 0;
820 b42d807f 2023-09-02 op clt->route = route_handle_ws;
821 b42d807f 2023-09-02 op http_reply(clt, 101, "Switching Protocols", NULL);
822 04e4e993 2023-08-14 op }
823 04e4e993 2023-08-14 op
824 04e4e993 2023-08-14 op static void
825 cc932200 2023-08-31 op route_assets(struct client *clt)
826 cc932200 2023-08-31 op {
827 cc932200 2023-08-31 op if (!strcmp(clt->req.path, "/style.css")) {
828 cc932200 2023-08-31 op http_reply(clt, 200, "OK", "text/css");
829 cc932200 2023-08-31 op http_write(clt, css, strlen(css));
830 cc932200 2023-08-31 op return;
831 cc932200 2023-08-31 op }
832 cc932200 2023-08-31 op
833 cc932200 2023-08-31 op if (!strcmp(clt->req.path, "/app.js")) {
834 cc932200 2023-08-31 op http_reply(clt, 200, "OK", "application/javascript");
835 cc932200 2023-08-31 op http_write(clt, js, strlen(js));
836 cc932200 2023-08-31 op return;
837 cc932200 2023-08-31 op }
838 cc932200 2023-08-31 op
839 cc932200 2023-08-31 op route_notfound(clt);
840 cc932200 2023-08-31 op }
841 cc932200 2023-08-31 op
842 cc932200 2023-08-31 op static void
843 3634fa70 2023-08-31 op route_dispatch(struct client *clt)
844 04e4e993 2023-08-14 op {
845 04e4e993 2023-08-14 op static const struct route {
846 3634fa70 2023-08-31 op int method;
847 3634fa70 2023-08-31 op const char *path;
848 3634fa70 2023-08-31 op route_fn route;
849 04e4e993 2023-08-14 op } routes[] = {
850 04e4e993 2023-08-14 op { METHOD_GET, "/", &route_home },
851 b42d807f 2023-09-02 op
852 04e4e993 2023-08-14 op { METHOD_POST, "/jump", &route_jump },
853 04e4e993 2023-08-14 op { METHOD_POST, "/ctrls", &route_controls },
854 04e4e993 2023-08-14 op { METHOD_POST, "/mode", &route_mode },
855 04e4e993 2023-08-14 op
856 b42d807f 2023-09-02 op { METHOD_POST, "/a/jump", &route_jump },
857 b42d807f 2023-09-02 op { METHOD_POST, "/a/ctrls", &route_controls },
858 b42d807f 2023-09-02 op { METHOD_POST, "/a/mode", &route_mode },
859 b42d807f 2023-09-02 op
860 b42d807f 2023-09-02 op { METHOD_GET, "/ws", &route_init_ws },
861 b42d807f 2023-09-02 op
862 cc932200 2023-08-31 op { METHOD_GET, "/style.css", &route_assets },
863 cc932200 2023-08-31 op { METHOD_GET, "/app.js", &route_assets },
864 cc932200 2023-08-31 op
865 04e4e993 2023-08-14 op { METHOD_GET, "*", &route_notfound },
866 04e4e993 2023-08-14 op { METHOD_POST, "*", &route_notfound },
867 04e4e993 2023-08-14 op };
868 3634fa70 2023-08-31 op struct request *req = &clt->req;
869 04e4e993 2023-08-14 op size_t i;
870 04e4e993 2023-08-14 op
871 04e4e993 2023-08-14 op if ((req->method != METHOD_GET && req->method != METHOD_POST) ||
872 04e4e993 2023-08-14 op (req->ctype != NULL && strcmp(req->ctype, FORM_URLENCODED) != 0) ||
873 04e4e993 2023-08-14 op req->path == NULL) {
874 3634fa70 2023-08-31 op http_reply(clt, 400, "Bad Request", NULL);
875 04e4e993 2023-08-14 op return;
876 04e4e993 2023-08-14 op }
877 04e4e993 2023-08-14 op
878 04e4e993 2023-08-14 op for (i = 0; i < nitems(routes); ++i) {
879 04e4e993 2023-08-14 op if (req->method != routes[i].method ||
880 04e4e993 2023-08-14 op fnmatch(routes[i].path, req->path, 0) != 0)
881 04e4e993 2023-08-14 op continue;
882 3634fa70 2023-08-31 op clt->done = 1; /* assume with one round is done */
883 3634fa70 2023-08-31 op clt->route = routes[i].route;
884 3634fa70 2023-08-31 op clt->route(clt);
885 3634fa70 2023-08-31 op if (clt->done)
886 3634fa70 2023-08-31 op http_close(clt);
887 04e4e993 2023-08-14 op return;
888 04e4e993 2023-08-14 op }
889 04e4e993 2023-08-14 op }
890 04e4e993 2023-08-14 op
891 04e4e993 2023-08-14 op static void
892 3634fa70 2023-08-31 op client_ev(int fd, int ev, void *d)
893 3634fa70 2023-08-31 op {
894 3634fa70 2023-08-31 op struct client *clt = d;
895 3634fa70 2023-08-31 op
896 3634fa70 2023-08-31 op if (ev & (POLLIN|POLLHUP)) {
897 3634fa70 2023-08-31 op if (bufio_read(&clt->bio) == -1 && errno != EAGAIN) {
898 3634fa70 2023-08-31 op log_warn("bufio_read");
899 3634fa70 2023-08-31 op goto err;
900 3634fa70 2023-08-31 op }
901 3634fa70 2023-08-31 op }
902 3634fa70 2023-08-31 op
903 3634fa70 2023-08-31 op if (ev & POLLOUT) {
904 3634fa70 2023-08-31 op if (bufio_write(&clt->bio) == -1 && errno != EAGAIN) {
905 3634fa70 2023-08-31 op log_warn("bufio_read");
906 3634fa70 2023-08-31 op goto err;
907 3634fa70 2023-08-31 op }
908 3634fa70 2023-08-31 op }
909 3634fa70 2023-08-31 op
910 3634fa70 2023-08-31 op if (clt->route == NULL) {
911 3634fa70 2023-08-31 op if (http_parse(clt) == -1) {
912 3634fa70 2023-08-31 op if (errno == EAGAIN)
913 3634fa70 2023-08-31 op goto again;
914 3634fa70 2023-08-31 op log_warnx("HTTP parse request failed");
915 3634fa70 2023-08-31 op goto err;
916 3634fa70 2023-08-31 op }
917 3634fa70 2023-08-31 op if (clt->req.method == METHOD_POST &&
918 3634fa70 2023-08-31 op http_read(clt) == -1) {
919 3634fa70 2023-08-31 op if (errno == EAGAIN)
920 3634fa70 2023-08-31 op goto again;
921 3634fa70 2023-08-31 op log_warnx("failed to read POST data");
922 3634fa70 2023-08-31 op goto err;
923 3634fa70 2023-08-31 op }
924 3634fa70 2023-08-31 op route_dispatch(clt);
925 3634fa70 2023-08-31 op goto again;
926 3634fa70 2023-08-31 op }
927 3634fa70 2023-08-31 op
928 6d777267 2023-09-02 op if (!clt->done && !clt->err)
929 3634fa70 2023-08-31 op clt->route(clt);
930 3634fa70 2023-08-31 op
931 3634fa70 2023-08-31 op again:
932 3634fa70 2023-08-31 op ev = bufio_pollev(&clt->bio);
933 6d777267 2023-09-02 op if (ev == POLLIN && (clt->done || clt->err)) {
934 3634fa70 2023-08-31 op goto err; /* done with this client */
935 3634fa70 2023-08-31 op }
936 3634fa70 2023-08-31 op
937 3634fa70 2023-08-31 op ev_add(fd, ev, client_ev, clt);
938 3634fa70 2023-08-31 op return;
939 3634fa70 2023-08-31 op
940 3634fa70 2023-08-31 op err:
941 3634fa70 2023-08-31 op ev_del(fd);
942 b42d807f 2023-09-02 op TAILQ_REMOVE(&clients, clt, clients);
943 3634fa70 2023-08-31 op http_free(clt);
944 3634fa70 2023-08-31 op }
945 3634fa70 2023-08-31 op
946 3634fa70 2023-08-31 op static void
947 703c260b 2023-08-25 op web_accept(int psock, int ev, void *d)
948 04e4e993 2023-08-14 op {
949 3634fa70 2023-08-31 op struct client *clt;
950 077802c1 2023-08-25 op int sock;
951 04e4e993 2023-08-14 op
952 04e4e993 2023-08-14 op if ((sock = accept(psock, NULL, NULL)) == -1) {
953 04e4e993 2023-08-14 op warn("accept");
954 04e4e993 2023-08-14 op return;
955 04e4e993 2023-08-14 op }
956 3634fa70 2023-08-31 op if ((clt = calloc(1, sizeof(*clt))) == NULL ||
957 3634fa70 2023-08-31 op http_init(clt, sock) == -1) {
958 3634fa70 2023-08-31 op log_warn("failed to initialize client");
959 3634fa70 2023-08-31 op free(clt);
960 04e4e993 2023-08-14 op close(sock);
961 04e4e993 2023-08-14 op return;
962 04e4e993 2023-08-14 op }
963 3634fa70 2023-08-31 op
964 b42d807f 2023-09-02 op TAILQ_INSERT_TAIL(&clients, clt, clients);
965 b42d807f 2023-09-02 op
966 3634fa70 2023-08-31 op client_ev(sock, POLLIN, clt);
967 04e4e993 2023-08-14 op return;
968 04e4e993 2023-08-14 op }
969 04e4e993 2023-08-14 op
970 04e4e993 2023-08-14 op void __dead
971 04e4e993 2023-08-14 op usage(void)
972 04e4e993 2023-08-14 op {
973 9ae51b1f 2023-09-02 op fprintf(stderr, "usage: %s [-v] [-s sock] [[host] port]\n",
974 04e4e993 2023-08-14 op getprogname());
975 04e4e993 2023-08-14 op exit(1);
976 04e4e993 2023-08-14 op }
977 04e4e993 2023-08-14 op
978 04e4e993 2023-08-14 op int
979 04e4e993 2023-08-14 op main(int argc, char **argv)
980 04e4e993 2023-08-14 op {
981 04e4e993 2023-08-14 op struct addrinfo hints, *res, *res0;
982 04e4e993 2023-08-14 op const char *cause = NULL;
983 04e4e993 2023-08-14 op const char *host = NULL;
984 04e4e993 2023-08-14 op const char *port = "9090";
985 04e4e993 2023-08-14 op char *sock = NULL;
986 80d5f5ad 2023-08-25 op size_t nsock, error, save_errno;
987 80d5f5ad 2023-08-25 op int ch, v, amused_sock, fd;
988 04e4e993 2023-08-14 op int verbose = 0;
989 04e4e993 2023-08-14 op
990 b42d807f 2023-09-02 op TAILQ_INIT(&clients);
991 04e4e993 2023-08-14 op setlocale(LC_ALL, NULL);
992 04e4e993 2023-08-14 op
993 04e4e993 2023-08-14 op log_init(1, LOG_DAEMON);
994 04e4e993 2023-08-14 op
995 04e4e993 2023-08-14 op if (pledge("stdio rpath unix inet dns", NULL) == -1)
996 04e4e993 2023-08-14 op err(1, "pledge");
997 04e4e993 2023-08-14 op
998 9ae51b1f 2023-09-02 op while ((ch = getopt(argc, argv, "s:v")) != -1) {
999 04e4e993 2023-08-14 op switch (ch) {
1000 04e4e993 2023-08-14 op case 's':
1001 04e4e993 2023-08-14 op sock = optarg;
1002 04e4e993 2023-08-14 op break;
1003 04e4e993 2023-08-14 op case 'v':
1004 04e4e993 2023-08-14 op verbose = 1;
1005 04e4e993 2023-08-14 op break;
1006 04e4e993 2023-08-14 op default:
1007 04e4e993 2023-08-14 op usage();
1008 04e4e993 2023-08-14 op }
1009 04e4e993 2023-08-14 op }
1010 04e4e993 2023-08-14 op argc -= optind;
1011 04e4e993 2023-08-14 op argv += optind;
1012 04e4e993 2023-08-14 op
1013 04e4e993 2023-08-14 op if (argc == 1)
1014 04e4e993 2023-08-14 op port = argv[0];
1015 04e4e993 2023-08-14 op if (argc == 2) {
1016 04e4e993 2023-08-14 op host = argv[0];
1017 04e4e993 2023-08-14 op port = argv[1];
1018 04e4e993 2023-08-14 op }
1019 04e4e993 2023-08-14 op if (argc > 2)
1020 04e4e993 2023-08-14 op usage();
1021 04e4e993 2023-08-14 op
1022 04e4e993 2023-08-14 op log_setverbose(verbose);
1023 04e4e993 2023-08-14 op
1024 04e4e993 2023-08-14 op if (sock == NULL)
1025 04e4e993 2023-08-14 op xasprintf(&sock, "/tmp/amused-%d", getuid());
1026 04e4e993 2023-08-14 op
1027 04e4e993 2023-08-14 op signal(SIGPIPE, SIG_IGN);
1028 04e4e993 2023-08-14 op
1029 80d5f5ad 2023-08-25 op if (ev_init() == -1)
1030 80d5f5ad 2023-08-25 op fatal("ev_init");
1031 80d5f5ad 2023-08-25 op
1032 04e4e993 2023-08-14 op amused_sock = dial(sock);
1033 04e4e993 2023-08-14 op imsg_init(&ibuf, amused_sock);
1034 20dff2d2 2023-08-31 op imsg_compose(&ibuf, IMSG_CTL_SHOW, 0, 0, -1, NULL, 0);
1035 20dff2d2 2023-08-31 op imsg_compose(&ibuf, IMSG_CTL_STATUS, 0, 0, -1, NULL, 0);
1036 20dff2d2 2023-08-31 op imsg_compose(&ibuf, IMSG_CTL_MONITOR, 0, 0, -1, NULL, 0);
1037 20dff2d2 2023-08-31 op ev_add(amused_sock, POLLIN|POLLOUT, imsg_dispatch, NULL);
1038 04e4e993 2023-08-14 op
1039 04e4e993 2023-08-14 op memset(&hints, 0, sizeof(hints));
1040 04e4e993 2023-08-14 op hints.ai_family = AF_UNSPEC;
1041 04e4e993 2023-08-14 op hints.ai_socktype = SOCK_STREAM;
1042 04e4e993 2023-08-14 op hints.ai_flags = AI_PASSIVE;
1043 04e4e993 2023-08-14 op error = getaddrinfo(host, port, &hints, &res0);
1044 04e4e993 2023-08-14 op if (error)
1045 04e4e993 2023-08-14 op errx(1, "%s", gai_strerror(error));
1046 04e4e993 2023-08-14 op
1047 04e4e993 2023-08-14 op nsock = 0;
1048 80d5f5ad 2023-08-25 op for (res = res0; res; res = res->ai_next) {
1049 80d5f5ad 2023-08-25 op fd = socket(res->ai_family, res->ai_socktype,
1050 04e4e993 2023-08-14 op res->ai_protocol);
1051 80d5f5ad 2023-08-25 op if (fd == -1) {
1052 04e4e993 2023-08-14 op cause = "socket";
1053 04e4e993 2023-08-14 op continue;
1054 04e4e993 2023-08-14 op }
1055 04e4e993 2023-08-14 op
1056 04e4e993 2023-08-14 op v = 1;
1057 80d5f5ad 2023-08-25 op if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
1058 04e4e993 2023-08-14 op &v, sizeof(v)) == -1)
1059 04e4e993 2023-08-14 op fatal("setsockopt(SO_REUSEADDR)");
1060 04e4e993 2023-08-14 op
1061 80d5f5ad 2023-08-25 op if (bind(fd, res->ai_addr, res->ai_addrlen) == -1) {
1062 04e4e993 2023-08-14 op cause = "bind";
1063 04e4e993 2023-08-14 op save_errno = errno;
1064 80d5f5ad 2023-08-25 op close(fd);
1065 04e4e993 2023-08-14 op errno = save_errno;
1066 04e4e993 2023-08-14 op continue;
1067 04e4e993 2023-08-14 op }
1068 04e4e993 2023-08-14 op
1069 80d5f5ad 2023-08-25 op if (listen(fd, 5) == -1)
1070 04e4e993 2023-08-14 op err(1, "listen");
1071 04e4e993 2023-08-14 op
1072 703c260b 2023-08-25 op if (ev_add(fd, POLLIN, web_accept, NULL) == -1)
1073 80d5f5ad 2023-08-25 op fatal("ev_add");
1074 04e4e993 2023-08-14 op nsock++;
1075 04e4e993 2023-08-14 op }
1076 04e4e993 2023-08-14 op if (nsock == 0)
1077 04e4e993 2023-08-14 op err(1, "%s", cause);
1078 04e4e993 2023-08-14 op freeaddrinfo(res0);
1079 04e4e993 2023-08-14 op
1080 04e4e993 2023-08-14 op if (pledge("stdio inet", NULL) == -1)
1081 04e4e993 2023-08-14 op err(1, "pledge");
1082 04e4e993 2023-08-14 op
1083 04e4e993 2023-08-14 op log_info("starting");
1084 80d5f5ad 2023-08-25 op ev_loop();
1085 80d5f5ad 2023-08-25 op return (1);
1086 04e4e993 2023-08-14 op }