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