Implementing redirection codes (http 3xx).
[cmus.git] / input.c
blobacd1051039ea30edca1c1c0747ed87de66a0970c
1 /*
2 * Copyright 2004-2005 Timo Hirvonen
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2 of the
7 * License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
17 * 02111-1307, USA.
20 #include "input.h"
21 #include "ip.h"
22 #include "pcm.h"
23 #include "http.h"
24 #include "xmalloc.h"
25 #include "file.h"
26 #include "utils.h"
27 #include "cmus.h"
28 #include "list.h"
29 #include "misc.h"
30 #include "debug.h"
31 #include "ui_curses.h"
32 #include "config/libdir.h"
34 #include <unistd.h>
35 #include <string.h>
36 #include <errno.h>
37 #include <stdarg.h>
38 #include <sys/types.h>
39 #include <sys/stat.h>
40 #include <fcntl.h>
41 #include <dirent.h>
42 #include <dlfcn.h>
44 struct input_plugin {
45 const struct input_plugin_ops *ops;
46 struct input_plugin_data data;
47 unsigned int open : 1;
48 unsigned int eof : 1;
49 int http_code;
50 char *http_reason;
52 /* cached duration, -1 = unset */
53 int duration;
56 * pcm is converted to 16-bit signed little-endian stereo
57 * NOTE: no conversion is done if channels > 2 or bits > 16
59 void (*pcm_convert)(char *, const char *, int);
60 void (*pcm_convert_in_place)(char *, int);
62 * 4 if 8-bit mono
63 * 2 if 8-bit stereo or 16-bit mono
64 * 1 otherwise
66 int pcm_convert_scale;
69 struct ip {
70 struct list_head node;
71 char *name;
72 void *handle;
74 const char * const *extensions;
75 const char * const *mime_types;
76 const struct input_plugin_ops *ops;
79 static const char * const plugin_dir = LIBDIR "/cmus/ip";
80 static LIST_HEAD(ip_head);
82 /* timeouts (ms) */
83 static int http_connection_timeout = 5e3;
84 static int http_read_timeout = 5e3;
86 static const char *pl_mime_types[] = {
87 "audio/m3u",
88 "audio/x-scpls",
89 "audio/x-mpegurl"
92 static const char *get_extension(const char *filename)
94 const char *ext;
96 ext = filename + strlen(filename) - 1;
97 while (ext >= filename && *ext != '/') {
98 if (*ext == '.') {
99 ext++;
100 return ext;
102 ext--;
104 return NULL;
107 static const struct input_plugin_ops *get_ops_by_filename(const char *filename)
109 struct ip *ip;
110 const char *ext;
112 ext = get_extension(filename);
113 if (ext == NULL)
114 return NULL;
115 list_for_each_entry(ip, &ip_head, node) {
116 const char * const *exts = ip->extensions;
117 int i;
119 for (i = 0; exts[i]; i++) {
120 if (strcasecmp(ext, exts[i]) == 0)
121 return ip->ops;
124 return NULL;
127 static const struct input_plugin_ops *get_ops_by_mime_type(const char *mime_type)
129 struct ip *ip;
131 list_for_each_entry(ip, &ip_head, node) {
132 const char * const *types = ip->mime_types;
133 int i;
135 for (i = 0; types[i]; i++) {
136 if (strcasecmp(mime_type, types[i]) == 0)
137 return ip->ops;
140 return NULL;
143 static int do_http_get(const char *uri, struct http_header **headersp, int *codep, char **reasonp)
145 struct http_uri u;
146 struct http_header *h;
147 int sock, i, rc, code;
148 char *reason, *redirloc;
150 *headersp = NULL;
151 *codep = -1;
152 *reasonp = NULL;
154 if (http_parse_uri(uri, &u))
155 return -IP_ERROR_INVALID_URI;
157 d_print("%s -> '%s':'%s'@'%s':%d'%s'\n", uri, u.user, u.pass, u.host, u.port, u.path);
159 sock = http_open(u.host, u.port, http_connection_timeout);
160 if (sock == -1) {
161 http_free_uri(&u);
162 return -IP_ERROR_ERRNO;
165 h = xnew(struct http_header, 5);
166 i = 0;
167 h[i].key = xstrdup("Host");
168 h[i].val = xstrdup(u.host);
169 i++;
170 h[i].key = xstrdup("User-Agent");
171 h[i].val = xstrdup("cmus/" VERSION);
172 i++;
173 h[i].key = xstrdup("Icy-MetaData");
174 h[i].val = xstrdup("1");
175 i++;
176 if (u.user && u.pass) {
177 char buf[256];
178 char *encoded;
180 snprintf(buf, sizeof(buf), "%s:%s", u.user, u.pass);
181 encoded = base64_encode(buf);
182 if (encoded == NULL) {
183 d_print("couldn't base64 encode '%s'\n", buf);
184 } else {
185 snprintf(buf, sizeof(buf), "Basic %s", encoded);
186 free(encoded);
187 h[i].key = xstrdup("Authorization");
188 h[i].val = xstrdup(buf);
189 i++;
192 h[i].key = NULL;
193 h[i].val = NULL;
194 i++;
196 rc = http_get(sock, u.path, h, &code, &reason, headersp, http_read_timeout);
197 http_headers_free(h);
198 http_free_uri(&u);
199 switch (rc) {
200 case -1:
201 d_print("error: %s\n", strerror(errno));
202 close(sock);
203 return -IP_ERROR_ERRNO;
204 case -2:
205 d_print("error parsing HTTP response\n");
206 close(sock);
207 return -IP_ERROR_HTTP_RESPONSE;
211 * FIXME: Use information from the headers, we read.
213 * especially interesting:
214 * + icy-name
215 * + icy-url
217 d_print("HTTP response: %d %s\n", code, reason);
218 for (i = 0; (*headersp)[i].key != NULL; i++)
219 d_print("%s: %s\n", (*headersp)[i].key, (*headersp)[i].val);
221 switch (code) {
222 case 200: /* OK */
223 break;
226 * 3xx Codes (Redirections)
227 * unhandled: 300 Multiple Choices
229 case 301: /* Moved Permanently */
230 case 302: /* Found */
231 case 303: /* See Other */
232 case 307: /* Temporary Redirect */
233 redirloc = xstrdup(http_headers_get_value(*headersp, "location"));
234 http_headers_free(*headersp);
236 close(sock);
237 d_print("Redirected to %s\n", redirloc);
238 sock = do_http_get(redirloc, headersp, codep, reasonp);
239 free(redirloc);
241 break;
243 default:
244 *codep = code;
245 *reasonp = reason;
246 close(sock);
247 return -IP_ERROR_HTTP_STATUS;
249 free(reason);
250 return sock;
253 static int setup_remote(struct input_plugin *ip, const struct http_header *headers, int sock)
255 const char *val;
257 val = http_headers_get_value(headers, "Content-Type");
258 if (val) {
259 d_print("Content-Type: %s\n", val);
260 ip->ops = get_ops_by_mime_type(val);
261 if (ip->ops == NULL) {
262 d_print("unsupported content type: %s\n", val);
263 close(sock);
264 return -IP_ERROR_FILE_FORMAT;
266 } else {
267 const char *type = "audio/mpeg";
269 d_print("assuming %s content type\n", type);
270 ip->ops = get_ops_by_mime_type(type);
271 if (ip->ops == NULL) {
272 d_print("unsupported content type: %s\n", type);
273 close(sock);
274 return -IP_ERROR_FILE_FORMAT;
278 ip->data.fd = sock;
279 ip->data.metadata = (char *)xmalloc(16 * 255 + 1);
281 val = http_headers_get_value(headers, "icy-metaint");
282 if (val) {
283 long int lint;
285 if (str_to_int(val, &lint) == 0 && lint >= 0) {
286 ip->data.metaint = lint;
287 d_print("metaint: %d\n", ip->data.metaint);
290 return 0;
293 struct read_playlist_data {
294 struct input_plugin *ip;
295 int rc;
298 static int handle_line(void *data, const char *line)
300 struct read_playlist_data *rpd;
301 struct http_header *headers;
302 int sock, code;
303 char *reason;
304 const char *uri = line;
306 rpd = (struct read_playlist_data *)data;
308 if (uri == NULL) {
309 d_print("empty playlist\n");
310 rpd->rc = -IP_ERROR_HTTP_RESPONSE;
311 return 1;
314 sock = do_http_get(uri, &headers, &code, &reason);
315 if (sock < 0) {
317 * URI in the _playlist_ is invalid, not our fault
318 * Try next.
320 rpd->ip->http_code = code;
321 rpd->ip->http_reason = reason;
322 if (sock == -IP_ERROR_INVALID_URI)
323 sock = -IP_ERROR_HTTP_RESPONSE;
324 rpd->rc = sock;
325 return 0;
328 rpd->rc = setup_remote(rpd->ip, headers, sock);
329 http_headers_free(headers);
330 return 1;
333 static int read_playlist(struct input_plugin *ip, int sock)
335 struct read_playlist_data rpd;
336 char *body;
337 int rc;
339 rc = http_read_body(sock, &body, http_read_timeout);
340 close(sock);
341 if (rc)
342 return -IP_ERROR_ERRNO;
344 rpd.ip = ip;
345 cmus_playlist_for_each(body, strlen(body), 0, handle_line, &rpd);
346 free(body);
348 return rpd.rc;
351 static int open_remote(struct input_plugin *ip)
353 struct input_plugin_data *d = &ip->data;
354 char *reason;
355 int sock, rc, code;
356 struct http_header *headers;
357 const char *val;
359 sock = do_http_get(d->filename, &headers, &code, &reason);
360 if (sock < 0) {
361 ip->http_code = code;
362 ip->http_reason = reason;
363 return sock;
366 val = http_headers_get_value(headers, "Content-Type");
367 if (val) {
368 int i;
370 for (i = 0; i < sizeof(pl_mime_types) / sizeof(pl_mime_types[0]); i++) {
371 if (!strcasecmp(val, pl_mime_types[i])) {
372 d_print("Content-Type: %s\n", val);
373 http_headers_free(headers);
374 return read_playlist(ip, sock);
379 rc = setup_remote(ip, headers, sock);
380 http_headers_free(headers);
381 return rc;
384 static int open_file(struct input_plugin *ip)
386 ip->ops = get_ops_by_filename(ip->data.filename);
387 if (ip->ops == NULL)
388 return -IP_ERROR_UNRECOGNIZED_FILE_TYPE;
389 ip->data.fd = open(ip->data.filename, O_RDONLY);
390 if (ip->data.fd == -1) {
391 ip->ops = NULL;
392 return -IP_ERROR_ERRNO;
394 return 0;
397 void ip_load_plugins(void)
399 DIR *dir;
400 struct dirent *d;
402 dir = opendir(plugin_dir);
403 if (dir == NULL) {
404 error_msg("couldn't open directory `%s': %s", plugin_dir, strerror(errno));
405 return;
407 while ((d = readdir(dir)) != NULL) {
408 char filename[256];
409 struct ip *ip;
410 void *so;
411 char *ext;
412 const char *sym;
414 if (d->d_name[0] == '.')
415 continue;
416 ext = strrchr(d->d_name, '.');
417 if (ext == NULL)
418 continue;
419 if (strcmp(ext, ".so"))
420 continue;
422 snprintf(filename, sizeof(filename), "%s/%s", plugin_dir, d->d_name);
424 so = dlopen(filename, RTLD_NOW);
425 if (so == NULL) {
426 error_msg("%s", dlerror());
427 continue;
430 ip = xnew(struct ip, 1);
432 sym = "ip_extensions";
433 if (!(ip->extensions = dlsym(so, sym)))
434 goto sym_err;
436 sym = "ip_mime_types";
437 if (!(ip->mime_types = dlsym(so, sym)))
438 goto sym_err;
440 sym = "ip_ops";
441 if (!(ip->ops = dlsym(so, sym)))
442 goto sym_err;
444 ip->name = xstrndup(d->d_name, ext - d->d_name);
445 ip->handle = so;
447 list_add_tail(&ip->node, &ip_head);
448 continue;
449 sym_err:
450 error_msg("%s: symbol %s not found", filename, sym);
451 free(ip);
452 dlclose(so);
454 closedir(dir);
457 static void ip_init(struct input_plugin *ip, char *filename)
459 memset(ip, 0, sizeof(*ip));
460 ip->http_code = -1;
461 ip->pcm_convert_scale = -1;
462 ip->duration = -1;
463 ip->data.fd = -1;
464 ip->data.filename = filename;
465 ip->data.remote = is_url(filename);
468 struct input_plugin *ip_new(const char *filename)
470 struct input_plugin *ip = xnew(struct input_plugin, 1);
472 ip_init(ip, xstrdup(filename));
473 return ip;
476 void ip_delete(struct input_plugin *ip)
478 if (ip->open)
479 ip_close(ip);
480 free(ip->data.filename);
481 free(ip);
484 int ip_open(struct input_plugin *ip)
486 int rc;
488 BUG_ON(ip->open);
490 /* set fd and ops */
491 if (ip->data.remote) {
492 rc = open_remote(ip);
493 } else {
494 rc = open_file(ip);
497 if (rc) {
498 d_print("opening `%s' failed: %d %s\n", ip->data.filename, rc,
499 rc == -1 ? strerror(errno) : "");
500 return rc;
503 rc = ip->ops->open(&ip->data);
504 if (rc) {
505 d_print("opening file `%s' failed: %d %s\n", ip->data.filename, rc,
506 rc == -1 ? strerror(errno) : "");
507 if (ip->data.fd != -1)
508 close(ip->data.fd);
509 free(ip->data.metadata);
510 ip_init(ip, ip->data.filename);
511 return rc;
513 ip->open = 1;
514 return 0;
517 void ip_setup(struct input_plugin *ip)
519 unsigned int bits, is_signed, channels;
520 sample_format_t sf = ip->data.sf;
522 bits = sf_get_bits(sf);
523 is_signed = sf_get_signed(sf);
524 channels = sf_get_channels(sf);
526 ip->pcm_convert_scale = 1;
527 ip->pcm_convert = NULL;
528 ip->pcm_convert_in_place = NULL;
530 if (bits <= 16 && channels <= 2) {
531 unsigned int mask = ((bits >> 2) & 4) | (is_signed << 1);
533 ip->pcm_convert = pcm_conv[mask | (channels - 1)];
534 ip->pcm_convert_in_place = pcm_conv_in_place[mask | sf_get_bigendian(sf)];
536 ip->pcm_convert_scale = (3 - channels) * (3 - bits / 8);
539 d_print("pcm convert: scale=%d convert=%d convert_in_place=%d\n",
540 ip->pcm_convert_scale,
541 ip->pcm_convert != NULL,
542 ip->pcm_convert_in_place != NULL);
545 int ip_close(struct input_plugin *ip)
547 int rc;
549 rc = ip->ops->close(&ip->data);
550 BUG_ON(ip->data.private);
551 if (ip->data.fd != -1)
552 close(ip->data.fd);
553 free(ip->data.metadata);
554 free(ip->http_reason);
556 ip_init(ip, ip->data.filename);
557 return rc;
560 int ip_read(struct input_plugin *ip, char *buffer, int count)
562 struct timeval tv;
563 fd_set readfds;
564 /* 4608 seems to be optimal for mp3s, 4096 for oggs */
565 char tmp[8 * 1024];
566 char *buf;
567 int sample_size;
568 int rc;
570 BUG_ON(count <= 0);
572 FD_ZERO(&readfds);
573 FD_SET(ip->data.fd, &readfds);
574 /* zero timeout -> return immediately */
575 tv.tv_sec = 0;
576 tv.tv_usec = 50e3;
577 rc = select(ip->data.fd + 1, &readfds, NULL, NULL, &tv);
578 if (rc == -1) {
579 if (errno == EINTR)
580 errno = EAGAIN;
581 return -1;
583 if (rc == 0) {
584 errno = EAGAIN;
585 return -1;
588 buf = buffer;
589 if (ip->pcm_convert_scale > 1) {
590 /* use tmp buffer for 16-bit mono and 8-bit */
591 buf = tmp;
592 count /= ip->pcm_convert_scale;
593 if (count > sizeof(tmp))
594 count = sizeof(tmp);
597 rc = ip->ops->read(&ip->data, buf, count);
598 if (rc == -1 && (errno == EAGAIN || errno == EINTR)) {
599 errno = EAGAIN;
600 return -1;
602 if (rc <= 0) {
603 ip->eof = 1;
604 return rc;
607 BUG_ON((rc & ~((unsigned int)sf_get_frame_size(ip->data.sf) - 1U)) != rc);
609 sample_size = sf_get_sample_size(ip->data.sf);
610 if (ip->pcm_convert_in_place != NULL)
611 ip->pcm_convert_in_place(buf, rc / sample_size);
612 if (ip->pcm_convert != NULL)
613 ip->pcm_convert(buffer, tmp, rc / sample_size);
614 return rc * ip->pcm_convert_scale;
617 int ip_seek(struct input_plugin *ip, double offset)
619 int rc;
621 if (ip->data.remote)
622 return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
623 rc = ip->ops->seek(&ip->data, offset);
624 if (rc == 0)
625 ip->eof = 0;
626 return rc;
629 int ip_read_comments(struct input_plugin *ip, struct keyval **comments)
631 return ip->ops->read_comments(&ip->data, comments);
634 int ip_duration(struct input_plugin *ip)
636 if (ip->data.remote)
637 return -1;
638 if (ip->duration == -1)
639 ip->duration = ip->ops->duration(&ip->data);
640 if (ip->duration < 0)
641 return -1;
642 return ip->duration;
645 sample_format_t ip_get_sf(struct input_plugin *ip)
647 BUG_ON(!ip->open);
648 return ip->data.sf;
651 const char *ip_get_filename(struct input_plugin *ip)
653 return ip->data.filename;
656 const char *ip_get_metadata(struct input_plugin *ip)
658 BUG_ON(!ip->open);
659 return ip->data.metadata;
662 int ip_is_remote(struct input_plugin *ip)
664 return ip->data.remote;
667 int ip_metadata_changed(struct input_plugin *ip)
669 int ret = ip->data.metadata_changed;
671 BUG_ON(!ip->open);
672 ip->data.metadata_changed = 0;
673 return ret;
676 int ip_eof(struct input_plugin *ip)
678 BUG_ON(!ip->open);
679 return ip->eof;
682 char *ip_get_error_msg(struct input_plugin *ip, int rc, const char *arg)
684 char buffer[1024];
686 switch (-rc) {
687 case IP_ERROR_ERRNO:
688 snprintf(buffer, sizeof(buffer), "%s: %s", arg, strerror(errno));
689 break;
690 case IP_ERROR_UNRECOGNIZED_FILE_TYPE:
691 snprintf(buffer, sizeof(buffer),
692 "%s: unrecognized filename extension", arg);
693 break;
694 case IP_ERROR_FUNCTION_NOT_SUPPORTED:
695 snprintf(buffer, sizeof(buffer),
696 "%s: function not supported", arg);
697 break;
698 case IP_ERROR_FILE_FORMAT:
699 snprintf(buffer, sizeof(buffer),
700 "%s: file format not supported or corrupted file",
701 arg);
702 break;
703 case IP_ERROR_INVALID_URI:
704 snprintf(buffer, sizeof(buffer), "%s: invalid URI", arg);
705 break;
706 case IP_ERROR_SAMPLE_FORMAT:
707 snprintf(buffer, sizeof(buffer),
708 "%s: input plugin doesn't support the sample format",
709 arg);
710 break;
711 case IP_ERROR_HTTP_RESPONSE:
712 snprintf(buffer, sizeof(buffer), "%s: invalid HTTP response", arg);
713 break;
714 case IP_ERROR_HTTP_STATUS:
715 snprintf(buffer, sizeof(buffer), "%s: %d %s", arg, ip->http_code, ip->http_reason);
716 free(ip->http_reason);
717 ip->http_reason = NULL;
718 ip->http_code = -1;
719 break;
720 case IP_ERROR_INTERNAL:
721 snprintf(buffer, sizeof(buffer), "%s: internal error", arg);
722 break;
723 case IP_ERROR_SUCCESS:
724 default:
725 snprintf(buffer, sizeof(buffer),
726 "%s: this is not an error (%d), this is a bug",
727 arg, rc);
728 break;
730 return xstrdup(buffer);
733 char **ip_get_supported_extensions(void)
735 struct ip *ip;
736 char **exts;
737 int i, size;
738 int count = 0;
740 size = 8;
741 exts = xnew(char *, size);
742 list_for_each_entry(ip, &ip_head, node) {
743 const char * const *e = ip->extensions;
745 for (i = 0; e[i]; i++) {
746 if (count == size - 1) {
747 size *= 2;
748 exts = xrenew(char *, exts, size);
750 exts[count++] = xstrdup(e[i]);
753 exts[count] = NULL;
754 qsort(exts, count, sizeof(char *), strptrcmp);
755 return exts;
758 void ip_dump_plugins(void)
760 struct ip *ip;
761 int i;
763 printf("Input Plugins: %s\n", plugin_dir);
764 list_for_each_entry(ip, &ip_head, node) {
765 printf(" %s:\n File Types:", ip->name);
766 for (i = 0; ip->extensions[i]; i++)
767 printf(" %s", ip->extensions[i]);
768 printf("\n MIME Types:");
769 for (i = 0; ip->mime_types[i]; i++)
770 printf(" %s", ip->mime_types[i]);
771 printf("\n");