1 /*
2 * MOC - music on console
3 * Copyright (C) 2005 Damian Pietras <daper@daper.net>
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 */
11
12 #ifdef HAVE_CONFIG_H
13 # include "config.h"
14 #endif
15
16 #include <curl/curl.h>
17 #include <string.h>
18 #include <strings.h>
19 #include <stdlib.h>
20 #ifdef HAVE_SYS_SELECT_H
21 # include <sys/select.h>
22 #endif
23 #include <sys/time.h>
24 #include <sys/types.h>
25 #include <unistd.h>
26 #include <errno.h>
27 #include <assert.h>
28 #include <stdint.h>
29
30 #define DEBUG
31
32 #include "common.h"
33 #include "log.h"
34 #include "io.h"
35 #include "io_curl.h"
36 #include "options.h"
37 #include "lists.h"
38
39 static char user_agent[] = PACKAGE_NAME"/"PACKAGE_VERSION;
40
io_curl_init()41 void io_curl_init ()
42 {
43 char *ptr;
44
45 for (ptr = user_agent; *ptr; ptr += 1) {
46 if (*ptr == ' ')
47 *ptr = '-';
48 }
49
50 curl_global_init (CURL_GLOBAL_NOTHING);
51 }
52
io_curl_cleanup()53 void io_curl_cleanup ()
54 {
55 curl_global_cleanup ();
56 }
57
write_callback(void * data,size_t size,size_t nmemb,void * stream)58 static size_t write_callback (void *data, size_t size, size_t nmemb,
59 void *stream)
60 {
61 struct io_stream *s = (struct io_stream *)stream;
62 size_t buf_start = s->curl.buf_fill;
63 size_t data_size = size * nmemb;
64
65 s->curl.buf_fill += data_size;
66 debug ("Got %zu bytes", data_size);
67 s->curl.buf = (char *)xrealloc (s->curl.buf, s->curl.buf_fill);
68 memcpy (s->curl.buf + buf_start, data, data_size);
69
70 return data_size;
71 }
72
header_callback(void * data,size_t size,size_t nmemb,void * stream)73 static size_t header_callback (void *data, size_t size, size_t nmemb,
74 void *stream)
75 {
76 struct io_stream *s = (struct io_stream *)stream;
77 char *header;
78 size_t header_size;
79
80 assert (s != NULL);
81
82 if (size * nmemb <= 2)
83 return size * nmemb;
84
85 /* we dont need '\r\n', so cut it. */
86 header_size = sizeof(char) * (size * nmemb + 1 - 2);
87
88 /* copy the header to char* array */
89 header = (char *)xmalloc (header_size);
90 memcpy (header, data, size * nmemb - 2);
91 header[header_size-1] = 0;
92
93 if (!strncasecmp(header, "Location:", sizeof("Location:")-1)) {
94 s->curl.got_locn = 1;
95 }
96 else if (!strncasecmp(header, "Content-Type:", sizeof("Content-Type:")-1)) {
97 /* If we got redirected then use the last MIME type. */
98 if (s->curl.got_locn && s->curl.mime_type) {
99 free (s->curl.mime_type);
100 s->curl.mime_type = NULL;
101 }
102 if (s->curl.mime_type)
103 logit ("Another Content-Type header!");
104 else {
105 char *value = header + sizeof("Content-Type:") - 1;
106
107 while (isblank(value[0]))
108 value++;
109
110 s->curl.mime_type = xstrdup (value);
111 debug ("Mime type: '%s'", s->curl.mime_type);
112 }
113 }
114 else if (!strncasecmp(header, "icy-name:", sizeof("icy-name:")-1)
115 || !strncasecmp(header, "x-audiocast-name",
116 sizeof("x-audiocast-name")-1)) {
117 char *value = strchr (header, ':') + 1;
118
119 while (isblank(value[0]))
120 value++;
121
122 io_set_metadata_title (s, value);
123 }
124 else if (!strncasecmp(header, "icy-url:", sizeof("icy-url:")-1)) {
125 char *value = strchr (header, ':') + 1;
126
127 while (isblank(value[0]))
128 value++;
129
130 io_set_metadata_url (s, value);
131 }
132 else if (!strncasecmp(header, "icy-metaint:",
133 sizeof("icy-metaint:")-1)) {
134 char *end;
135 char *value = strchr (header, ':') + 1;
136
137 while (isblank(value[0]))
138 value++;
139
140 s->curl.icy_meta_int = strtol (value, &end, 10);
141 if (*end) {
142 s->curl.icy_meta_int = 0;
143 logit ("Bad icy-metaint value");
144 }
145 else
146 debug ("Icy metadata interval: %zu", s->curl.icy_meta_int);
147 }
148
149 free (header);
150
151 return size * nmemb;
152 }
153
154 #ifdef DEBUG
debug_callback(CURL * curl ATTR_UNUSED,curl_infotype i,char * msg,size_t size,void * d ATTR_UNUSED)155 static int debug_callback (CURL *curl ATTR_UNUSED, curl_infotype i, char *msg,
156 size_t size, void *d ATTR_UNUSED)
157 {
158 int ix;
159 char *log;
160 const char *type;
161 lists_t_strs *lines;
162
163 switch (i) {
164 case CURLINFO_TEXT:
165 type = "INFO";
166 break;
167 case CURLINFO_HEADER_IN:
168 type = "RECV HEADER";
169 break;
170 case CURLINFO_HEADER_OUT:
171 type = "SEND HEADER";
172 break;
173 default:
174 return 0;
175 }
176
177 log = (char *)xmalloc (size + 1);
178 strncpy (log, msg, size);
179 log[size] = 0;
180
181 lines = lists_strs_new (8);
182 lists_strs_split (lines, log, "\n");
183 for (ix = 0; ix < lists_strs_size (lines); ix += 1)
184 debug ("CURL: [%s] %s", type, lists_strs_at (lines, ix));
185 lists_strs_free (lines);
186 free (log);
187
188 return 0;
189 }
190 #endif
191
192 /* Read messages given by curl and set the stream status. Return 0 on error. */
check_curl_stream(struct io_stream * s)193 static int check_curl_stream (struct io_stream *s)
194 {
195 CURLMsg *msg;
196 int msg_queue_num;
197 int res = 1;
198
199 while ((msg = curl_multi_info_read (s->curl.multi_handle,
200 &msg_queue_num))) {
201 if (msg->msg == CURLMSG_DONE) {
202 s->curl.status = msg->data.result;
203 if (s->curl.status != CURLE_OK) {
204 debug ("Read error");
205 res = 0;
206 }
207 curl_multi_remove_handle (s->curl.multi_handle, s->curl.handle);
208 curl_easy_cleanup (s->curl.handle);
209 s->curl.handle = NULL;
210 debug ("EOF");
211 break;
212 }
213 }
214
215 return res;
216 }
217
io_curl_open(struct io_stream * s,const char * url)218 void io_curl_open (struct io_stream *s, const char *url)
219 {
220 s->source = IO_SOURCE_CURL;
221 s->curl.url = NULL;
222 s->curl.http_headers = NULL;
223 s->curl.buf = NULL;
224 s->curl.buf_fill = 0;
225 s->curl.need_perform_loop = 1;
226 s->curl.got_locn = 0;
227
228 s->curl.wake_up_pipe[0] = -1;
229 s->curl.wake_up_pipe[1] = -1;
230
231 if (!(s->curl.multi_handle = curl_multi_init())) {
232 logit ("curl_multi_init() returned NULL");
233 s->errno_val = EINVAL;
234 return;
235 }
236
237 if (!(s->curl.handle = curl_easy_init())) {
238 logit ("curl_easy_init() returned NULL");
239 s->errno_val = EINVAL;
240 return;
241 }
242
243 s->curl.multi_status = CURLM_OK;
244 s->curl.status = CURLE_OK;
245
246 s->curl.url = xstrdup (url);
247 s->curl.icy_meta_int = 0;
248 s->curl.icy_meta_count = 0;
249
250 s->curl.http200_aliases = curl_slist_append (NULL, "ICY");
251 s->curl.http_headers = curl_slist_append (NULL, "Icy-MetaData: 1");
252
253 curl_easy_setopt (s->curl.handle, CURLOPT_NOPROGRESS, 1);
254 curl_easy_setopt (s->curl.handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
255 curl_easy_setopt (s->curl.handle, CURLOPT_WRITEFUNCTION,
256 write_callback);
257 curl_easy_setopt (s->curl.handle, CURLOPT_WRITEDATA, s);
258 curl_easy_setopt (s->curl.handle, CURLOPT_HEADERFUNCTION,
259 header_callback);
260 curl_easy_setopt (s->curl.handle, CURLOPT_WRITEHEADER, s);
261 curl_easy_setopt (s->curl.handle, CURLOPT_USERAGENT, user_agent);
262 curl_easy_setopt (s->curl.handle, CURLOPT_URL, s->curl.url);
263 curl_easy_setopt (s->curl.handle, CURLOPT_FOLLOWLOCATION, 1);
264 curl_easy_setopt (s->curl.handle, CURLOPT_FAILONERROR, 1);
265 curl_easy_setopt (s->curl.handle, CURLOPT_MAXREDIRS, 15);
266 curl_easy_setopt (s->curl.handle, CURLOPT_HTTP200ALIASES,
267 s->curl.http200_aliases);
268 curl_easy_setopt (s->curl.handle, CURLOPT_HTTPHEADER,
269 s->curl.http_headers);
270 if (options_get_str("HTTPProxy"))
271 curl_easy_setopt (s->curl.handle, CURLOPT_PROXY,
272 options_get_str("HTTPProxy"));
273 #ifdef DEBUG
274 curl_easy_setopt (s->curl.handle, CURLOPT_VERBOSE, 1);
275 curl_easy_setopt (s->curl.handle, CURLOPT_DEBUGFUNCTION,
276 debug_callback);
277 #endif
278
279 if ((s->curl.multi_status = curl_multi_add_handle(s->curl.multi_handle,
280 s->curl.handle)) != CURLM_OK) {
281 logit ("curl_multi_add_handle() failed");
282 s->errno_val = EINVAL;
283 return;
284 }
285
286 if (pipe(s->curl.wake_up_pipe) < 0) {
287 logit ("pipe() failed: %s", strerror(errno));
288 s->errno_val = EINVAL;
289 return;
290 }
291
292 s->opened = 1;
293 }
294
io_curl_close(struct io_stream * s)295 void io_curl_close (struct io_stream *s)
296 {
297 assert (s != NULL);
298 assert (s->source == IO_SOURCE_CURL);
299
300 if (s->curl.url)
301 free (s->curl.url);
302 if (s->curl.http_headers)
303 curl_slist_free_all (s->curl.http_headers);
304 if (s->curl.buf)
305 free (s->curl.buf);
306 if (s->curl.mime_type)
307 free (s->curl.mime_type);
308
309 if (s->curl.multi_handle && s->curl.handle)
310 curl_multi_remove_handle (s->curl.multi_handle, s->curl.handle);
311 if (s->curl.handle)
312 curl_easy_cleanup (s->curl.handle);
313 if (s->curl.multi_handle)
314 curl_multi_cleanup (s->curl.multi_handle);
315
316 if (s->curl.wake_up_pipe[0] != -1) {
317 close (s->curl.wake_up_pipe[0]);
318 close (s->curl.wake_up_pipe[1]);
319 }
320
321 if (s->curl.http200_aliases)
322 curl_slist_free_all (s->curl.http200_aliases);
323 }
324
325 /* Get data using curl and put them into the internal buffer.
326 * Even if the internal buffer is not empty, more data will be read.
327 * Return 0 on error. */
curl_read_internal(struct io_stream * s)328 static int curl_read_internal (struct io_stream *s)
329 {
330 int running = 1;
331 long buf_fill_before = s->curl.buf_fill;
332
333 if (s->curl.need_perform_loop) {
334 debug ("Starting curl...");
335
336 do {
337 s->curl.multi_status = curl_multi_perform (
338 s->curl.multi_handle, &running);
339 } while (s->curl.multi_status == CURLM_CALL_MULTI_PERFORM);
340
341 if (!check_curl_stream(s))
342 return 0;
343
344 s->curl.need_perform_loop = 0;
345 }
346
347 while (s->opened && running && buf_fill_before == s->curl.buf_fill
348 && s->curl.handle
349 && (s->curl.multi_status == CURLM_CALL_MULTI_PERFORM
350 || s->curl.multi_status == CURLM_OK)) {
351 if (s->curl.multi_status != CURLM_CALL_MULTI_PERFORM) {
352 fd_set read_fds, write_fds, exc_fds;
353 int max_fd, ret;
354 long milliseconds;
355 struct timeval timeout;
356
357 logit ("Doing select()...");
358
359 FD_ZERO (&read_fds);
360 FD_ZERO (&write_fds);
361 FD_ZERO (&exc_fds);
362
363 s->curl.multi_status = curl_multi_fdset (
364 s->curl.multi_handle,
365 &read_fds, &write_fds, &exc_fds,
366 &max_fd);
367 if (s->curl.multi_status != CURLM_OK)
368 logit ("curl_multi_fdset() failed");
369
370 FD_SET (s->curl.wake_up_pipe[0], &read_fds);
371 if (s->curl.wake_up_pipe[0] > max_fd)
372 max_fd = s->curl.wake_up_pipe[0];
373
374 curl_multi_timeout (s->curl.multi_handle, &milliseconds);
375 if (milliseconds <= 0)
376 milliseconds = 1000;
377 timeout.tv_sec = milliseconds / 1000;
378 timeout.tv_usec = (milliseconds % 1000) * 1000;
379
380 ret = select (max_fd + 1, &read_fds, &write_fds,
381 &exc_fds, &timeout);
382
383 if (ret < 0 && errno == EINTR) {
384 logit ("Interrupted");
385 return 0;
386 }
387
388 if (ret < 0) {
389 s->errno_val = errno;
390 logit ("select() failed");
391 return 0;
392 }
393
394 if (s->stop_read_thread)
395 return 1;
396
397 if (FD_ISSET(s->curl.wake_up_pipe[0], &read_fds)) {
398 logit ("Got wake up - exiting");
399 return 1;
400 }
401
402 }
403
404 s->curl.multi_status = curl_multi_perform (s->curl.multi_handle,
405 &running);
406
407 if (!check_curl_stream(s))
408 return 0;
409 }
410
411 return 1;
412 }
413
414 /* Read data from the internal buffer to buf. Return the number of bytes read.
415 */
read_from_buffer(struct io_stream * s,char * buf,size_t count)416 static size_t read_from_buffer (struct io_stream *s, char *buf, size_t count)
417 {
418 if (s->curl.buf_fill) {
419 long to_copy = MIN ((long)count, s->curl.buf_fill);
420
421 /*debug ("Copying %ld bytes", to_copy);*/
422
423 memcpy (buf, s->curl.buf, to_copy);
424 s->curl.buf_fill -= to_copy;
425
426 if (s->curl.buf_fill) {
427 memmove (s->curl.buf, s->curl.buf + to_copy,
428 s->curl.buf_fill);
429 s->curl.buf = (char *)xrealloc (s->curl.buf,
430 s->curl.buf_fill);
431 }
432 else {
433 free (s->curl.buf);
434 s->curl.buf = NULL;
435 }
436
437 return to_copy;
438 }
439
440 return 0;
441 }
442
443 /* Parse icy string in form: StreamTitle='my music';StreamUrl='www.x.com' */
parse_icy_string(struct io_stream * s,const char * str)444 static void parse_icy_string (struct io_stream *s, const char *str)
445 {
446 const char *c = str;
447
448 debug ("Got metadata string: %s", str);
449
450 while (*c) {
451 char name[64];
452 char value[256];
453 const char *t;
454
455 /* get the name */
456 t = c;
457 while (*c && *c != '=')
458 c++;
459 if (*c != '=' || c - t >= (int)sizeof(name)) {
460 logit ("malformed metadata");
461 return;
462 }
463 strncpy (name, t, c - t);
464 name[c - t] = 0;
465
466 /* move to a char after ' */
467 c++;
468 if (*c != '\'') {
469 logit ("malformed metadata");
470 return;
471 }
472 c++;
473
474 /* read the value - it can contain a quotation mark so we
475 * recognize if it ends the value by checking if there is a
476 * semicolon after it or if it's the end of the string */
477 t = c;
478 while (*c && (*c != '\'' || (*(c+1) != ';' && *(c+1))))
479 c++;
480
481 if (!*c) {
482 logit ("malformed metadata");
483 return;
484 }
485
486 strncpy (value, t, MIN(c - t, (int)sizeof(value) - 1));
487 value[MIN(c - t, (int)sizeof(value) - 1)] = 0;
488
489 /* eat ' */
490 c++;
491
492 /* eat semicolon */
493 if (*c == ';')
494 c++;
495
496 debug ("METADATA name: '%s' value: '%s'", name, value);
497
498 if (!strcasecmp(name, "StreamTitle"))
499 io_set_metadata_title (s, value);
500 else if (!strcasecmp(name, "StreamUrl"))
501 io_set_metadata_url (s, value);
502 else
503 logit ("Unknown metadata element '%s'", name);
504 }
505 }
506
507 /* Parse the IceCast metadata packet. */
parse_icy_metadata(struct io_stream * s,const char * packet,const int size)508 static void parse_icy_metadata (struct io_stream *s, const char *packet,
509 const int size)
510 {
511 const char *c = packet;
512
513 while (c - packet < size) {
514 const char *p = c;
515
516 while (*c && c - packet < size)
517 c++;
518 if (c - packet < size && !*c)
519 parse_icy_string (s, p);
520
521 /* pass the padding */
522 while (c - packet < size && !*c)
523 c++;
524 }
525 }
526
527 /* Read icy metadata from the curl stream. The stream should be at the
528 * beginning of the metadata. Return 0 on error. */
read_icy_metadata(struct io_stream * s)529 static int read_icy_metadata (struct io_stream *s)
530 {
531 uint8_t size_packet;
532 int size;
533 char *packet;
534
535 /* read the packet size */
536 if (s->curl.buf_fill == 0 && !curl_read_internal(s))
537 return 0;
538 if (read_from_buffer(s, (char *)&size_packet, sizeof(size_packet))
539 == 0) {
540 debug ("Got empty metadata packet");
541 return 1;
542 }
543
544 if (size_packet == 0) {
545 debug ("Got empty metadata packet");
546 return 1;
547 }
548
549 size = size_packet * 16;
550
551 /* make sure that the whole packet is in the buffer */
552 while (s->curl.buf_fill < size && s->curl.handle
553 && !s->stop_read_thread)
554 if (!curl_read_internal(s))
555 return 0;
556
557 if (s->curl.buf_fill < size) {
558 logit ("Icy metadata packet broken");
559 return 0;
560 }
561
562 packet = (char *)xmalloc (size);
563 read_from_buffer (s, packet, size);
564 debug ("Received metadata packet %d bytes long", size);
565 parse_icy_metadata (s, packet, size);
566 free (packet);
567
568 return 1;
569 }
570
io_curl_read(struct io_stream * s,char * buf,size_t count)571 ssize_t io_curl_read (struct io_stream *s, char *buf, size_t count)
572 {
573 size_t nread = 0;
574
575 assert (s != NULL);
576 assert (s->source == IO_SOURCE_CURL);
577 assert (s->curl.multi_handle != NULL);
578
579 do {
580 size_t to_read;
581 size_t res;
582
583 if (s->curl.icy_meta_int && s->curl.icy_meta_count
584 == s->curl.icy_meta_int) {
585 s->curl.icy_meta_count = 0;
586 if (!read_icy_metadata(s))
587 return -1;
588 }
589
590 if (s->curl.icy_meta_int)
591 to_read = MIN (count - nread, s->curl.icy_meta_int -
592 s->curl.icy_meta_count);
593 else
594 to_read = count - nread;
595
596 res = read_from_buffer (s, buf + nread, to_read);
597 if (s->curl.icy_meta_int)
598 s->curl.icy_meta_count += res;
599 nread += res;
600 debug ("Read %zu bytes from the buffer (%zu bytes full)", res, nread);
601
602 if (nread < count && !curl_read_internal(s))
603 return -1;
604 } while (nread < count && !s->stop_read_thread
605 && s->curl.handle); /* s->curl.handle == NULL on EOF */
606
607 return nread;
608 }
609
610 /* Set the error string for the stream. */
io_curl_strerror(struct io_stream * s)611 void io_curl_strerror (struct io_stream *s)
612 {
613 const char *err = "OK";
614
615 assert (s != NULL);
616 assert (s->source == IO_SOURCE_CURL);
617
618 if (s->curl.multi_status != CURLM_OK)
619 err = curl_multi_strerror(s->curl.multi_status);
620 else if (s->curl.status != CURLE_OK)
621 err = curl_easy_strerror(s->curl.status);
622
623 s->strerror = xstrdup (err);
624 }
625
io_curl_wake_up(struct io_stream * s)626 void io_curl_wake_up (struct io_stream *s)
627 {
628 int w = 1;
629
630 if (write(s->curl.wake_up_pipe[1], &w, sizeof(w)) < 0)
631 logit ("Can't wake up curl thread: write() failed: %s",
632 strerror(errno));
633 }
634