Files
neutrino/src/driver/netfile.cpp
2013-05-30 11:20:27 +02:00

2038 lines
55 KiB
C++

/******************************************************************************\
| Neutrino-GUI - DBoxII-Project
|
| Copyright (C) 2004 by Sanaia <sanaia at freenet dot de>
| Copyright (C) 2010-2013 Stefan Seyfried
|
| netfile - remote file access mapper
|
|
| description:
| netfile - a URL capable wrapper for the fopen() call
|
| usage:
| include 'netfile.h' in your code as *LAST* file after all other
| include files and add netfile.c to your sources. That's it. The
| include file maps the common fopen() call onto a URL capable
| version that handles files hosted on http servers like local ones.
|
|
| examples:
|
| automatic redirection example:
|
| fd = fopen("remotefile.url", "r");
|
| This is a pretty straight forward implementation that should
| even work with applications which refuse to accept filenames
| starting with protocol headers (i.e. 'http://'). If the
| given file ends with '.url', then the file is opened and read
| and its content is assumed to be a valid URL which opened then.
| This works for all protocols provided by this code, e.g. 'http://',
| 'icy://' and 'scast://'
| All restrictions apply for the protocols themself as follows.
|
|
| http example:
|
| fd = fopen("http://find.me:666/somewhere/foo.bar", "r");
|
| This opens the specified file on a webserver (read only).
| NOTE: the stream itself is bidirectional, you can write
| into it (e.g. commands for communication with the server),
| but this is neither supported nor recommended. The result
| of such an action is rather undefined. You *CAN NOT* make
| any changes to the file of the webserver !
|
|
| shoutcast example:
|
| fd = fopen("scast://666", "r");
|
| This opens a shoutcast sation; all you need to know is the
| station number. The query of the shoutcast directory and the
| lookup of a working server is done automatically. The stream
| is opened read-only.
| I recommend this for official shoutcast stations.
|
| shoutcast example:
|
| fd = fopen("icy://find.me:666/funky/station/", "r");
| or
| fd = fopen("icy://username:password@find.me:666/station", "r");
|
| This is a low level mechanism that can be used for all
| shoutcast servers, but it mainly is intended to be used for
| private radio stations which are not listed in the official
| shoutcast directory. The stream is opened read-only.
| The second format contains a basic authentication string before '@'
| of username:passwort to be sent to the server.
|
| file access modes, selectable by the fopen 'access type':
|
| "r" read only, stream caching enabled
| "rc" read only, stream caching disabled (compatibility mode)
|
| NOTE: All network accesses are only possible read only. Although some
| protocols could allow write access to the remote resource, this
| is not (and rather unlikely to be anytime) implemented here.
| All remote accesses are made through a FIFO caching mechanism that
| uses read-ahead caching (as far as possible). The fopen() call starts
| a background fetching thread that fills the cache up to the ceiling.
|
|
| License: GPL
|
| This program is free software; you can redistribute it and/or modify
| it under the terms of the GNU General Public License as published by
| the Free Software Foundation; either version 2 of the License, or
| (at your option) any later version.
|
| This program is distributed in the hope that it will be useful,
| but WITHOUT ANY WARRANTY; without even the implied warranty of
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
| GNU General Public License for more details.
|
| You should have received a copy of the GNU General Public License
| along with this program; if not, write to the Free Software
| Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
\******************************************************************************/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "netfile.h"
#include <global.h>
#include <fcntl.h>
#include <poll.h>
#include <sys/types.h>
#include <driver/audioplay.h>
/*
TODO:
- ICECAST support
- follow redirection errors (server error codes 302, 301) (done for HTTP, 2005-05-16 ChakaZulu)
- support for automatic playlist processing (shoutcast pls files)
known bugs:
- HTTP POST requests - are implemented, but don't work with shoutcast.com !?
*/
#define STATIC /**/
#ifdef fopen
#undef fopen
#endif
#ifdef fclose
#undef fclose
#endif
#ifdef fread
#undef fread
#endif
#ifdef ftell
#undef ftell
#endif
#ifdef rewind
#undef rewind
#endif
#ifdef fseek
#undef fseek
#endif
typedef struct
{
const unsigned char mask[4];
const unsigned char mode[4];
const char *type;
} magic_t;
magic_t known_magic[] =
{
{{0xFF, 0xFF, 0xFF, 0x00}, {'I' , 'D' , '3' , 0x00}, "audio/mpeg"},
{{0xFF, 0xFF, 0xFF, 0x00}, {'O' , 'g' , 'g' , 0x00}, "audio/ogg" },
{{0xFF, 0xFE, 0x00, 0x00}, {0xFF, 0xFA, 0x00, 0x00}, "audio/mpeg"}
};
#if 0
#warning if a magic is contained in a file (for instance in .cdr) there is no way to play it correctly with neutrino
#warning the third magic is pretty short - hence disabled by default
/* 1111 1111 1111 1010 0000 0000 0000 0000
AAAA AAAA AAAB BCC
where A: frame sync
B: MPEG audio ID (11 = MPEG Version 1)
C: Layer description (01 = Layer III)
see http://mpgedit.org/mpgedit/mpeg_format/mpeghdr.htm */
#define known_magic_count (sizeof(known_magic) / sizeof(magic_t))
#else
#define known_magic_count 2
#endif
#define is_redirect(a) ((a == 301) || (a == 302))
char err_txt[2048]; /* human readable error message */
char redirect_url[2048]; /* new url if we've been redirected (HTTP 301/302) */
static int debug = 0; /* print debugging output or not */
static char logfile[255]; /* redirect errors from stderr */
static int retry_num = 2 /*10*/; /* number of retries for failed connections */
static int enable_metadata = 0; /* allow shoutcast meta data streaming */
static int got_opts = 0; /* is set to 1 if getOpts() was executed */
static int cache_size = 196608; /* default cache size; can be overridden at */
/* runtime with an option in the options file */
STATIC STREAM_CACHE cache[CACHEENTMAX];
STATIC STREAM_TYPE stream_type[CACHEENTMAX];
static int ConnectToServer(char *hostname, int port);
static int parse_response(URL *url, void *, CSTATE*);
static int request_file(URL *url);
static void readln(int, char *);
static int getCacheSlot(FILE *fd);
static int push(FILE *fd, char *buf, long len);
static int pop(FILE *fd, char *buf, long len);
static void CacheFillThread(void *url);
static void ShoutCAST_MetaFilter(STREAM_FILTER *);
static void ShoutCAST_DestroyFilter(void *a);
static STREAM_FILTER *ShoutCAST_InitFilter(int);
static void ShoutCAST_ParseMetaData(char *, CSTATE *);
static void getOpts(void);
static void parseURL_url(URL& url);
static int base64_encode(char *dest, const char *src);
/***************************************/
/* this is a simple options parser */
void getOpts()
{
const char *dirs[] = { "/var/etc", ".", NULL };
char buf[4096], *ptr;
int i;
FILE *fd = NULL;
#ifndef RADIOBOX //a not generic thing in a generic library
/* options which can be set from within neutrino */
enable_metadata = g_settings.audioplayer_enable_sc_metadata;
#endif /* RADIOBOX */
if (got_opts) /* prevent reading in options multiple times */
return;
else
got_opts = 1;
for(i=0; dirs[i] != NULL; i++)
{
sprintf(buf, "%s/.netfile", dirs[i]);
fd = fopen(buf, "r");
if(fd) break;
}
if(!fd)
return;
fread(buf, sizeof(char), 4095, fd);
fclose(fd);
if(strstr(buf, "debug=1"))
debug = 1;
if((ptr = strstr(buf, "cachesize=")))
cache_size = atoi(strchr(ptr, '=') + 1);
if((ptr = strstr(buf, "retries=")))
retry_num = atoi(strchr(ptr, '=') + 1);
if((ptr = strstr(buf, "logfile=")))
{
strcpy(logfile, strchr(ptr, '=') + 1);
CRLFCut(logfile);
freopen(logfile, "w", stderr);
}
}
/***************************************/
/* networking functions */
int ConnectToServer(char *hostname, int port)
{
struct hostent *host;
struct sockaddr_in sock;
int fd, addr;
struct pollfd pfd;
dprintf(stderr, "looking up hostname: %s\n", hostname);
host = gethostbyname(hostname);
if(host == NULL)
{
herror(err_txt);
return -1;
}
addr = htonl(*(int *)host->h_addr);
dprintf(stderr, "connecting to %s [%d.%d.%d.%d], port %d\n", host->h_name,
(addr & 0xff000000) >> 24,
(addr & 0x00ff0000) >> 16,
(addr & 0x0000ff00) >> 8,
(addr & 0x000000ff), port);
fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd == -1)
{
strcpy(err_txt, strerror(errno));
return -1;
}
memset(&sock, 0, sizeof(sock));
memmove((char*)&sock.sin_addr, host->h_addr, host->h_length);
sock.sin_family = AF_INET;
sock.sin_port = htons(port);
int flgs = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flgs | O_NONBLOCK);
if( connect(fd, (struct sockaddr *)&sock, sizeof(sock)) == -1 )
{
if(errno != EINPROGRESS) {
close(fd);
strcpy(err_txt, strerror(errno));
dprintf(stderr, "error connecting to %s: %s\n", hostname, err_txt);
return -1;
}
}
dprintf(stderr, "connected to %s\n", hostname);
pfd.fd = fd;
//pfd.events = POLLIN | POLLPRI;
pfd.events = POLLOUT | POLLERR | POLLHUP;
pfd.revents = 0;
int ret = poll(&pfd, 1, 5000);
if(ret != 1) {
strcpy(err_txt, strerror(errno));
dprintf(stderr, "error connecting to %s: %s\n", hostname, err_txt);
close(fd);
return -1;
}
if ((pfd.revents & POLLOUT) == POLLOUT) {
fcntl(fd, F_SETFL, flgs &~ O_NONBLOCK);
return fd;
}
return fd;
}
/*********************************************/
/* request a file from the HTTP server */
/* the network stream must be opened already */
/* and the URL structure be initialized */
/* properly ! */
int request_file(URL *url)
{
char str[255], *ptr;
int slot;
ID3 id3;
/* get the cache slot for this stream. A negative return value */
/* indicates that no cache has been set up for this stream */
slot = getCacheSlot(url->stream);
/* if the filename contains a line break, then use everything */
/* up to the line break as file name and everything beyond as */
/* entity to be sent with the request */
ptr = strchr(url->file, '\n');
if(ptr)
{
strcpy(url->entity, ptr + 1);
*ptr = 0;
}
switch(url->proto_version)
{
/* send a HTTP/1.0 request */
case HTTP10: {
snprintf(str, sizeof(str)-1, "GET http://%s:%d%s\n", url->host, url->port, url->file);
dprintf(stderr, "> %s", str);
send(url->fd, str, strlen(str), 0);
}
break;
/* send a HTTP/1.1 request */
case HTTP11: {
int meta_int;
CSTATE tmp;
snprintf(str, sizeof(str)-1, "%s %s HTTP/1.1\r\n", (url->entity[0]) ? "POST" : "GET", url->file);
dprintf(stderr, "> %s", str);
send(url->fd, str, strlen(str), 0);
snprintf(str, sizeof(str)-1, "Host: %s:%d\r\n", url->host, url->port);
dprintf(stderr, "> %s", str);
send(url->fd, str, strlen(str), 0);
snprintf(str, sizeof(str)-1, "User-Agent: WinampMPEG/5.52\r\n");
dprintf(stderr, "> %s", str);
send(url->fd, str, strlen(str), 0);
if (url->logindata[0])
{
sprintf(str, "Authorization: Basic %s\r\n", url->logindata);
dprintf(stderr, "> %s", str);
send(url->fd, str, strlen(str), 0);
}
snprintf(str, sizeof(str)-1, "Accept: */*\r\n");
dprintf(stderr, "> %s", str);
send(url->fd, str, strlen(str), 0);
if(enable_metadata)
{
snprintf(str, sizeof(str)-1, "Icy-MetaData: 1\r\n");
dprintf(stderr, "> %s", str);
send(url->fd, str, strlen(str), 0);
}
/* if we have a entity, announce it to the server */
if(url->entity[0])
{
snprintf(str, sizeof(str)-1, "Content-Length: %d\r\n", (int)strlen(url->entity));
dprintf(stderr, "> %s", str);
send(url->fd, str, strlen(str), 0);
}
snprintf(str, sizeof(str)-1, "Connection: close\r\n");
dprintf(stderr, "> %s", str);
send(url->fd, str, strlen(str), 0);
// empty line -> end of HTTP request
snprintf(str, sizeof(str)-1, "\r\n");
dprintf(stderr, "> %s", str);
send(url->fd, str, strlen(str), 0);
if( (meta_int = parse_response(url, &id3, &tmp)) < 0)
return -1;
if(meta_int)
{
if (slot < 0){
dprintf(stderr, "error: meta_int != 0 && slot < 0");
}else{
/* hook in the filter function if there is meta */
/* data present in the stream */
cache[slot].filter_arg = ShoutCAST_InitFilter(meta_int);
cache[slot].filter = ShoutCAST_MetaFilter;
/* this is a *really bad* way to pass along the argument, */
/* since we convert the integer into a pointer instead of */
/* passing along a pointer/reference !*/
if(cache[slot].filter_arg->state)
memmove(cache[slot].filter_arg->state, &tmp, sizeof(CSTATE));
}
}
/* push the created ID3 header into the stream cache */
push(url->stream, (char*)&id3, id3.len);
#if 0
rval = parse_response(url, NULL, NULL);
dprintf(stderr, "server response parser: return value = %d\n", rval);
/* if the header indicated a zero length document or an */
/* error, then close the cache, if there is any */
/* 25.04.05 ChakaZulu: zero length can be a stream so let's try playing */
if((slot >= 0) && (rval < 0))
cache[slot].closed = 1;
/* return on error */
if( rval < 0 )
return rval;
#endif
}
break;
/* send a SHOUTCAST request */
case SHOUTCAST:{
int meta_int;
CSTATE tmp;
sprintf(str, "GET %s HTTP/1.0\r\n", url->file);
dprintf(stderr, "> %s", str);
send(url->fd, str, strlen(str), 0);
sprintf(str, "Host: %s\r\n", url->host);
dprintf(stderr, "> %s", str);
send(url->fd, str, strlen(str), 0);
if(enable_metadata)
{
sprintf(str, "icy-metadata: 1\r\n");
dprintf(stderr, "> %s", str);
send(url->fd, str, strlen(str), 0);
}
sprintf(str, "User-Agent: %s\r\n", "RealPlayer/4.0");
dprintf(stderr, "> %s", str);
send(url->fd, str, strlen(str), 0);
if (url->logindata[0])
{
sprintf(str, "Authorization: Basic %s\r\n", url->logindata);
dprintf(stderr, "> %s", str);
send(url->fd, str, strlen(str), 0);
}
sprintf(str, "\r\n"); /* end of headers to send */
dprintf(stderr, "> %s", str);
send(url->fd, str, strlen(str), 0);
if( (meta_int = parse_response(url, &id3, &tmp)) < 0)
return -1;
if(meta_int)
{
/* hook in the filter function if there is meta */
/* data present in the stream */
cache[slot].filter_arg = ShoutCAST_InitFilter(meta_int);
cache[slot].filter = ShoutCAST_MetaFilter;
/* this is a *really bad* way to pass along the argument, */
/* since we convert the integer into a pointer instead of */
/* passing along a pointer/reference !*/
if(cache[slot].filter_arg->state)
memmove(cache[slot].filter_arg->state, &tmp, sizeof(CSTATE));
}
/* push the created ID3 header into the stream cache */
push(url->stream, (char*)&id3, id3.len);
}
break;
} /* end switch(url->proto_version) */
/* now it get's nasty ;) */
/* create a thread that continously feeds the cache */
/* with the data it fetches from the network stream */
/* but *ONLY* if there is a cache slot for this stream at all ! */
/* HINT: in compatibility mode no cache is configured */
if(slot >= 0)
{
pthread_attr_init(&cache[slot].attr);
pthread_create(
&cache[slot].fill_thread,
&cache[slot].attr,
(void *(*)(void*))&CacheFillThread, (void*)&cache[slot]);
dprintf(stderr, "request_file: slot %d fill_thread 0x%x\n", slot, (int)cache[slot].fill_thread);
}
/* now we do not care any longer about fetching new data,*/
/* but we can not be shure that the cache is filled with */
/* enough stuff ! */
return 0;
}
/***************************************/
/* parse the server response (header) */
/* and translate possible errors into */
/* local syserror numbers */
#define getHeaderVal(a,b) { \
char *_ptr; \
_ptr = strstr(header, a); \
if(_ptr) \
{ \
_ptr = strchr(_ptr, ':'); \
for(; !isalnum(*_ptr); _ptr++) {}; \
b = atoi(_ptr); \
} else b = -1; }
#define getHeaderStr(a,b) { \
char *_ptr; \
_ptr = strstr(header, a); \
if(_ptr) \
{ \
unsigned int i; \
_ptr = strchr(_ptr, ':'); \
for(_ptr++; isspace(*_ptr); _ptr++) {}; \
for (i=0; (_ptr[i]!='\n') && (_ptr[i]!='\r') && (_ptr[i]!='\0') && (i<sizeof(b)); i++) b[i] = _ptr[i]; \
b[i] = 0; \
} }
void readln(int fd, char *buf)
{
for(recv(fd, buf, 1, 0); (buf && isalnum(*buf)); recv(fd, ++buf, 1, 0)){};
if(buf)
*buf = 0;
}
int parse_response(URL *url, void *opt, CSTATE *state)
{
char header[2049], /*str[255]*/ str[2048]; // combined with 2nd local str from id3 part
char *ptr, chr=0, lastchr=0;
int hlen = 0, response;
int meta_interval = 0, rval;
int fd = url->fd;
ID3 *id3 = (ID3*)opt;
memset(header, 0, 2048);
ptr = header;
/* extract the http header from the stream */
do
{
recv(fd, ptr, 1, 0);
/* skip all 'CR' symbols */
if(*ptr != 13)
{
lastchr = chr; chr = *ptr++; hlen++;
}
} while((hlen < 2048) && ( ! ((chr == 10) && (lastchr == 10))));
*ptr = 0;
dprintf(stderr, "----------\n%s----------\n", header);
/* parse the header fields */
ptr = strstr(header, "HTTP/1.1");
if(!ptr)
ptr = strstr(header, "HTTP/1.0");
if(!ptr)
ptr = strstr(header, "ICY");
/* no valid HTTP/1.1 or SHOUTCAST response */
if(!ptr)
return -1;
response = atoi(strchr(ptr, ' ') + 1);
switch(response)
{
case 200: errno = 0;
break;
case 301: /* 'file moved' error */
case 302: /* 'file not found' error */
errno = ENOENT;
strcpy(err_txt, ptr);
getHeaderStr("Location", redirect_url);
return -1 * response;
break;
case 404: /* 'file not found' error */
errno = ENOENT;
strcpy(err_txt, ptr);
return -1;
break;
default: errno = ENOPROTOOPT;
dprintf(stderr, "unknown server response code: %d\n", response);
return -1;
}
/* use 'audio/mpeg' as default type, in case we are not told otherwise */
strcpy(str, "audio/mpeg");
getHeaderStr("Content-Type:", str);
f_type(url->stream, str);
dprintf(stderr, "type %s\n", str);
/* if we got a content length from the server, i.e. rval >= 0 */
/* then return now with the content length as return value */
getHeaderVal("Content-Length:", rval);
// dprintf(stderr, "file size: %d\n", rval);
if(rval >= 0)
return rval;
/* yet another hack: this is only implemented to be able to fetch */
/* the playlists from shoutcast */
if(strstr(header, "Transfer-Encoding: chunked"))
{
readln(fd, str);
sscanf(str, "%x", &rval);
return rval;
}
/* no content length indication from the server ? Then treat it as stream */
getHeaderVal("icy-metaint:", meta_interval);
if(meta_interval < 0)
meta_interval = 0;
if (state != NULL) {
getHeaderStr("icy-genre:", state->genre);
getHeaderStr("icy-name:", state->station);
getHeaderStr("icy-url:", state->station_url);
getHeaderVal("icy-br:", state->bitrate);
}
/********************* dirty hack **********************/
/* we parse the stream header sent by the server and */
/* build based on this information an arteficial id3 */
/* header that is pushed into the streamcache before */
/* any data from the stream is fed into the cache. This */
/* makes the stream look like an MP3 and we have the */
/* station information in the display of the player :)) */
#define SSIZE(a) (\
(((a) & 0x0000007f) << 0) | (((a) & 0x00003f80) << 1) | \
(((a) & 0x001fc000) << 2) | (((a) & 0xfe000000) << 3))
#define FRAME(b,c) {\
strcpy(id3frame.id, (b)); \
strcpy(id3frame.base, (c)); \
id3frame.size = strlen(id3frame.base); \
fcnt = 11 + id3frame.size; }
if(id3)
{
int cnt = 0, fcnt = 0;
ID3_frame id3frame;
uint32_t sz;
char station[2048], desc[2048];
memmove(id3->magic, "ID3", 3);
id3->version[0] = 3;
id3->version[1] = 0;
ptr = strstr(header, "icy-name:");
if(ptr)
{
ptr = strchr(ptr, ':') + 1;
for(; ((*ptr == '-') || (*ptr == ' ')); ptr++){};
strcpy(station, ptr);
*strchr(station, '\n') = 0;
ptr = strchr(station, '-');
if(ptr)
{
*ptr = 0;
for(ptr++; ((*ptr == '-') || (*ptr == ' ')); ptr++){};
strcpy(desc, ptr);
}
FRAME("TPE1", station);
id3frame.size = SSIZE(id3frame.size + 1);
memmove(id3->base + cnt, &id3frame, fcnt);
cnt += fcnt;
FRAME("TALB", desc);
id3frame.size = SSIZE(id3frame.size + 1);
memmove(id3->base + cnt, &id3frame, fcnt);
cnt += fcnt;
}
ptr = strstr(header, "icy-genre:");
if(ptr)
{
ptr = strchr(ptr, ':') + 1;
for(; ((*ptr == '-') || (*ptr == ' ')); ptr++){};
strcpy(str, ptr);
*strchr(str, '\n') = 0;
FRAME("TIT2", str);
id3frame.size = SSIZE(id3frame.size + 1);
memmove(id3->base + cnt, &id3frame, fcnt);
cnt += fcnt;
}
FRAME("COMM", "dbox streamconverter");
id3frame.size = SSIZE(id3frame.size + 1);
memmove(id3->base + cnt, &id3frame, fcnt);
cnt += fcnt;
sz = 14 + cnt - 10;
id3->size[0] = (sz & 0xfe000000) >> 21;
id3->size[1] = (sz & 0x001fc000) >> 14;
id3->size[2] = (sz & 0x00003f80) >> 7;
id3->size[3] = (sz & 0x0000007f) >> 0;
id3->len = 14 + cnt;
}
return meta_interval;
}
/******* wrapper functions for the f*() calls ************************/
#define transport(a,b,c) \
if(strstr(url.url, a)) \
{ \
url.access_mode = b; \
url.proto_version = c; \
}
FILE *f_open(const char *filename, const char *acctype)
{
URL url;
FILE *fd;
int /*i,*/ compatibility_mode = 0;
char *ptr = NULL, buf[4096] = {0}, type[10] = {0};
if(acctype)
strcpy(type, acctype);
/* read some options from the options-file */
getOpts();
/* test if compatibility mode has been requested. */
/* e.g. "rbc" = read, binary, without stream caching */
if(strchr(type, 'c'))
{
compatibility_mode = 1;
*strchr(type, 'c') = 0;
}
dprintf(stderr, "f_open: %s %s\n", (compatibility_mode) ? "(compatibility mode)" : "", filename);
/* set default protocol and port */
memset(&url, 0, sizeof(URL));
url.proto_version = HTTP11;
url.port = 80;
strcpy(url.url, filename);
/* remove leading spaces */
for (ptr = url.url; (ptr != NULL) && ((*ptr == ' ') || (*ptr == ' ')); ptr++){} ;
if(ptr != url.url)
strcpy(url.url, ptr);
/* did we get an URL file as argument ? If so, then open */
/* this file, read out the url and open the url instead */
#ifndef DISABLE_URLFILES
if( strstr(filename, ".url") || strstr(filename, ".imu"))
{
fd = fopen(filename, "r");
if(fd)
{
fread(buf, sizeof(char), 4095, fd);
fclose(fd);
ptr = strstr(buf, "://");
if(!ptr)
{
dprintf(stderr, "Ups! File doesn't seem to contain any URL !\nbuffer:%s\n", buf);
return NULL;
}
ptr = strchr(buf, '\n');
if(ptr)
*ptr = 0;
sprintf(url.url, "%s", buf);
}
else
return NULL;
}
#endif
/* now lets see what we have ... */
parseURL_url(url);
dprintf(stderr, "URL to open: %s, access mode %s%s\n",
url.url,
(url.access_mode == MODE_HTTP) ? "HTTP" :
(url.access_mode == MODE_SCAST) ? "SHOUTCAST" :
(url.access_mode == MODE_ICAST) ? "ICECAST" :
(url.access_mode == MODE_PLS) ? "PLAYLIST" :
"FILE",
(url.access_mode != MODE_FILE) ? (
(url.proto_version == HTTP10) ? "/1.0" :
(url.proto_version == HTTP11) ? "/1.1" :
(url.proto_version == SHOUTCAST) ? "/SHOUTCAST" :
"") : "" );
dprintf(stderr, "FILE to open: %s, access mode: %d\n", url.file, url.access_mode);
switch(url.access_mode)
{
case MODE_HTTP: {
int follow_url = 1; // used for redirects
int redirects = 0;
*redirect_url = '\0';
while (follow_url)
{
int retries = retry_num;
do
{
url.fd = ConnectToServer(url.host, url.port);
retries--;
} while( url.fd < 0 && retries > 0);
/* if the stream could not be opened, then indicate */
/* an 'No such device or address' error */
if(url.fd < 0)
{
fd = NULL;
errno = ENXIO;
printf("netfile: could not connect to server %s:%d\n",url.host, url.port);
return fd;
}
else
{
fd = fdopen(url.fd, "r+");
url.stream = fd;
if(!fd)
{
perror(err_txt);
}
else
{
/* in compatibility mode we must not use our own stream cache */
/* because the application makes use of their own f*() calls */
/* which we can not replace by our own functions and thus they'll */
/* interfere with each other. All we can do is to open the stream */
/* and return a valid stream descriptor */
if(!compatibility_mode)
{
int i;
/* look for a free cache slot */
for(i=0; ((i<CACHEENTMAX) && (cache[i].cache != NULL)); i++){};
/* no free cache slot ? return an error */
if(i == CACHEENTMAX)
{
sprintf(err_txt, "no more free cache slots. Too many open files.\n");
return NULL;
}
dprintf(stderr, "f_open: adding stream %p to cache[%d]\n", fd, i);
cache[i].fd = fd;
cache[i].csize = CACHESIZE;
cache[i].cache = (char*)malloc(cache[i].csize);
cache[i].ceiling = cache[i].cache + CACHESIZE;
cache[i].wptr = cache[i].cache;
cache[i].rptr = cache[i].cache;
cache[i].closed = 0;
cache[i].total_bytes_delivered = 0;
cache[i].filter = NULL;
/* create the readable/writeable mutex locks */
dprintf(stderr, "f_open: creating mutexes\n");
pthread_mutex_init(&cache[i].cache_lock, &cache[i].cache_lock_attr);
pthread_mutex_init(&cache[i].readable, &cache[i].readable_attr);
pthread_mutex_init(&cache[i].writeable, &cache[i].writeable_attr);
/* and set the empty cache to 'unreadable' */
dprintf(stderr, "f_open: locking read direction\n");
pthread_mutex_lock( &cache[i].readable );
/* but writeable. */
// dprintf(stderr, "f_open: unlocking write direction\n");
// mutex is not locked after init
// pthread_mutex_unlock( &cache[i].writeable );
}
/* send the file request and check it'S revurn value */
/* if it failed, then close the stream */
int request_res = request_file(&url);
if(request_res < 0)
{
/* we need out own f_close() function here, because everything */
/* already has been set up and f_close() deinitializes it all correctly */
f_close(url.stream);
fd = NULL;
}
follow_url = 0;
if (is_redirect(-1*request_res)) {
redirects++;
dprintf(stderr,"redirected to %s\n",redirect_url);
strcpy(url.url,redirect_url);
parseURL_url(url);
if (redirects <= MAX_REDIRECTS)
follow_url = 1;
else
dprintf(stderr, "too many redirects: %d (max: %d)\n",redirects,MAX_REDIRECTS);
}
}
}
}
} /* endcase MODE_HTTP */
break;
case MODE_ICAST:
case MODE_SCAST:
/* pseude transport mode; create the url to fetch the shoutcast */
/* directory playlist, fetch it, parse it and try to open the */
/* stream url in it until we find one that works. The we open */
/* this one and return with the opened stream. */
/* CAUTION: this is a nasty nested thing, because we call */
/* f_open() several times recursively - so the function should */
/* better be free from any bugs ;) */
/* if we didn't get a station number, query the shoutcast database */
/* and look in the reply for the station number */
char *endptr;
strtol(url.host, &endptr, 10);
if((endptr-url.host) < (int)strlen(url.host))
{
char buf2[32768];
FILE *_fd;
/* convert into shoutcast query format */
for(ptr=url.host; *ptr; ptr++)
*ptr = (*ptr != 32) ? *ptr : '+';
CRLFCut(url.host);
/* create either a shoutcast or an icecast query */
if(url.access_mode == MODE_SCAST)
sprintf(buf2, "http://classic.shoutcast.com/directory/?orderby=listeners&s=%s", url.host);
else
sprintf(buf2, "http://www.icecast.org/streamlist.php?search=%s", url.host);
//findme
// ICECAST: it ain't that simple. Icecast doesn't work yet */
_fd = f_open(buf2, "rc");
if(!_fd)
{
sprintf(err_txt, "%s database query failed\nfailed action: %s",
((url.access_mode == MODE_SCAST) ? "shoutcast" : "icecast"), buf2);
return NULL;
}
fread(buf2, 1, 32768, _fd);
f_close(_fd);
ptr = strstr(buf2, "rn=");
if(!ptr)
{
sprintf(err_txt, "failed to find station number");
dprintf(stderr, "%s\n", buf2);
return NULL;
}
sprintf(url.host, "%d", atoi(ptr + 3));
}
/* create the correct url from the station number */
CRLFCut(url.host);
sprintf(url.url, "http://classic.shoutcast.com/sbin/shoutcast-playlist.pls?rn=%s&file=filename.pls", url.host);
case MODE_PLS: {
char *ptr2, /*buf[4096], use local buf from function */ servers[25][1024];
int /*rval,*/ retries = retry_num;
ptr = NULL;
/* fetch the playlist from the shoutcast directory with our own */
/* url-capable f_open() call. We need the compatibility mode for */
/* this because we will read the data with the standard OS fread() call */
do
{
fd = f_open(url.url, "rc");
if(fd)
{
/* read the playlist (we use the standard fopen() call from the */
/* operating system because we don't need/want stream caching for */
/* this operation */
/*rval =*/ fread(buf, sizeof(char), 4096, fd);
f_close(fd);
ptr = strstr(buf, "http://");
}
retries--;
} while(!ptr && retries > 0);
/* extract the servers from the received playlist */
if(!ptr)
{
dprintf(stderr, "Ups! Playlist doesn't seem to contain any URL !\nbuffer:%s\n", buf);
sprintf(err_txt, "Ups! Playlist doesn't seem to contain any URL !");
return NULL;
}
for(int i=0; ((ptr != NULL) && (i<25)); ptr = strstr(ptr, "http://") )
{
strncpy(servers[i], ptr, 1023);
servers[i][1023] = '\0';
ptr2 = strchr(servers[i], '\n');
if(ptr2) *ptr2 = 0;
// change ptr so that next strstr searches in buf and not in servers[i]
ptr = strchr(ptr,'\n');
if (ptr != NULL)
ptr++;
dprintf(stderr, "server[%d]: %s\n", i, servers[i]);
i++;
}
/* try to connect to all servers until we find one that */
/* is willing to serve us */
{
int i;
for(i=0, fd = NULL; ((fd == NULL) && (i<25)); i++)
{
const char* const chptr = strstr(servers[i], "://");
if(chptr)
{
sprintf(url.url, "icy%s", chptr);
fd = f_open(url.url, "r");
}
}
}
}
break;
case MODE_FILE:
default:
{
unsigned char magic[4] = {0, 0, 0, 0};
fd = fopen(url.file, type);
if (fd == NULL)
return NULL;
fread(&magic, 4, 1, fd);
rewind(fd);
/* first stage: try to determine the filetype from the file */
/* magic, if there is any */
for (int i = 0; i < known_magic_count; i++)
{
if (((*(unsigned char *)&(magic[0])) & *(unsigned char *)&(known_magic[i].mask[0])) == *(unsigned char *)&(known_magic[i].mode[0]))
{
f_type(fd, known_magic[i].type);
goto magic_found;
}
}
/* stage two: try to determine the filetype from the file name */
/* a smarter solution would be to get this info from /etc/mime.types */
ptr = strrchr(url.file , '.');
//#FIXME warning what about filenames without dots?
if (ptr++)
{
if (strcasecmp(ptr, "cdr") == 0) f_type(fd, "audio/cdr");
if (strcasecmp(ptr, "wav") == 0) f_type(fd, "audio/wave");
if (strcasecmp(ptr, "aif") == 0) f_type(fd, "audio/aifc");
if (strcasecmp(ptr, "snd") == 0) f_type(fd, "audio/snd");
/* they should be obsolete now due to the file magic detection */
if (strcasecmp(ptr, "ogg") == 0) f_type(fd, "audio/ogg");
if (strcasecmp(ptr, "mp3") == 0) f_type(fd, "audio/mpeg");
if (strcasecmp(ptr, "mp2") == 0) f_type(fd, "audio/mpeg");
if (strcasecmp(ptr, "mpa") == 0) f_type(fd, "audio/mpeg");
}
magic_found:
;
}
break;
} /* endswitch */
return fd;
}
int f_close(FILE *stream)
{
int i, rval;
// dprintf(stderr, "f_close: stream 0x%x\n", stream);
/* at first, lookup the stream in the stream type table and remove it */
for(i=0 ; (i<CACHEENTMAX) && (stream_type[i].stream != stream); i++){};
if(i < CACHEENTMAX)
stream_type[i].stream = NULL;
/* lookup the stream ID in the cache table */
i = getCacheSlot(stream);
/* no associated cache slot ? Simply close the stream */
if(i < 0)
return( fclose(stream) );
if(cache[i].fd == stream)
{
dprintf(stderr, "f_close: removing stream %p from cache[%d]\n", stream, i);
cache[i].closed = 1; /* indicate that the cache is closed */
/* remove the cache looks */
pthread_mutex_unlock( &cache[i].writeable );
pthread_mutex_unlock( &cache[i].readable );
pthread_mutex_unlock( &cache[i].cache_lock );
/* wait for the fill thread to finish */
dprintf(stderr, "f_close: waiting for fill tread to finish, slot %d fill_thread 0x%x\n", i, (int) cache[i].fill_thread);
if(cache[i].fill_thread)
pthread_join(cache[i].fill_thread, NULL);
cache[i].fill_thread = 0;
dprintf(stderr, "f_close: closing cache\n");
rval = fclose(cache[i].fd); /* close the stream */
free(cache[i].cache); /* free the cache */
/* if this stream has a streamfilter, call it's destructor */
if(cache[i].filter_arg)
if(cache[i].filter_arg->destructor)
{
dprintf(stderr, "f_close: calling stream filter destructor\n");
cache[i].filter_arg->destructor(cache[i].filter_arg);
free(cache[i].filter_arg);
}
dprintf(stderr, "f_close: destroying mutexes\n");
pthread_mutex_destroy(&cache[i].cache_lock);
pthread_mutex_destroy(&cache[i].readable);
pthread_mutex_destroy(&cache[i].writeable);
/* completely blank out all data */
memset(&cache[i], 0, sizeof(STREAM_CACHE));
}
else
rval = fclose(stream);
return rval;
}
long f_tell(FILE *stream)
{
int i;
long rval;
/* lookup the stream ID in the cache table */
i = getCacheSlot(stream);
if(i < 0)
return( ftell(stream) );
if(cache[i].fd == stream)
rval = cache[i].total_bytes_delivered;
else
rval = ftell(stream);
return rval;
}
void f_rewind(FILE *stream)
{
int i;
/* lookup the stream ID in the cache table */
i = getCacheSlot(stream);
if(i < 0)
{
rewind(stream);
return;
}
if(cache[i].fd == stream)
{
/* nothing to do */
}
else
rewind(stream);
}
int f_seek(FILE *stream, long offset, int whence)
{
int i, rval;
/* lookup the stream ID in the cache table */
i = getCacheSlot(stream);
if(i < 0)
return( fseek(stream, offset, whence) );
if(cache[i].fd == stream)
{
dprintf(stderr, "WARNING: program tries to seek on a stream !! offset %d whence %d\n", (int) offset, whence);
rval = -1;
}
else
rval = fseek(stream, offset, whence);
return rval;
}
size_t f_read (void *ptr, size_t size, size_t nitems, FILE *stream)
{
int i, rval;
/* lookup the stream ID in the cache table */
i = getCacheSlot(stream);
if(i < 0)
return( fread(ptr, size, nitems, stream) );
if(cache[i].fd == stream) {
rval = pop(stream, (char*)ptr, size * nitems);
}
else
rval = fread(ptr, size, nitems, stream);
return rval;
}
const char *f_type(FILE *stream, const char *type)
{
int i;
/* lookup the stream in the stream type table */
for(i=0 ; (i<CACHEENTMAX) && (stream_type[i].stream != stream); i++){};
/* if the stream could not be found, look for a free slot ... */
if(i == CACHEENTMAX)
{
dprintf(stderr, "stream %p not in type table, ", stream);
for(i=0 ; (i<CACHEENTMAX) && (stream_type[i].stream != NULL); i++){};
/* ... and copy the supplied type into the table */
if(i < CACHEENTMAX)
{
if(type)
{
stream_type[i].stream = stream;
strncpy(stream_type[i].type, type, 64);
stream_type[i].type[64] = '\0';
dprintf(stderr, "added entry (%s) for %p\n", type, stream);
}
return type;
}
else
dprintf(stderr, "failed to add entry (%s)\n", type);
}
/* the stream is already in the table */
else
{
dprintf(stderr, "stream %p lookup in type table succeded\n", stream);
if(!type)
return stream_type[i].type;
else
if(strstr(stream_type[i].type, type))
return stream_type[i].type;
}
return NULL;
}
/************************************************************************/
/* stream cache functions */
/* */
/* this section implements some functions to cache data from a */
/* stream in a cache associated to the stream number. The cache */
/* is created in the f_open() call and destroyed in the f_close() */
/* call is the object to be opened is a network resource and not */
/* a local file. */
/* */
/* functions: */
/* push(FILE *fd, char *buf, long len) */
/* write the buffer pointed to by *buf */
/* with len bytes into the cache for */
/* stream fd */
/* */
/* pop(FILE *fd, char *buf, long len) */
/* fill the buffer pointed to by *buf */
/* with len bytes from the cache for */
/* stream fd */
/* */
/* getCacheSlot(FILE *fd) */
/* */
/* CacheFillThread(void *c) */
/* feeds the cache with data from the */
/* stream */
/************************************************************************/
int getCacheSlot(FILE *fd)
{
int i;
for(i=0; ( (i<CACHEENTMAX) && ((cache[i].fd != fd) || (!cache[i].cache))
); i++){};
return (i == CACHEENTMAX) ? -1 : i;
}
/* push a block of data into the stream cache */
int push(FILE *fd, char *buf, long len)
{
int rval = 0, i, j;
int blen = 0;
i = getCacheSlot(fd);
if(i < 0)
return -1;
// dprintf(stderr, "push: %d bytes to store [filled: %d of %d], stream: %x\n", len, cache[i].filled, CACHESIZE, fd);
if(cache[i].fd != fd) {
dprintf(stderr, "push: no cache present for stream %p\n", fd);
return -1;
}
do
{
if(cache[i].closed)
return -1;
/* try to obtain write permissions for the cache */
/* this will block if the cache is full */
pthread_mutex_lock( &cache[i].writeable );
if((cache[i].csize - cache[i].filled))
{
int amt[2];
/* prevent any modification to the cache by other threads, */
/* mainly by the pop() function, while we write data to it */
pthread_mutex_lock( &cache[i].cache_lock );
/* has the cache been closed while we were waiting for the */
/* lock to open ? */
if(cache[i].closed)
return -1;
/* block transfer length: get either what's there or */
/* only as much as we need */
blen = ((len - rval) > (cache[i].csize - cache[i].filled)) ? (cache[i].csize - cache[i].filled) : (len - rval);
if(cache[i].wptr < cache[i].rptr)
{
amt[0] = cache[i].rptr - cache[i].wptr;
amt[1] = 0;
}
else
{
amt[0] = cache[i].ceiling - cache[i].wptr;
amt[1] = cache[i].rptr - cache[i].cache;
}
for(j=0; j<2; j++)
{
if(amt[j] > blen)
amt[j] = blen;
if(amt[j])
{
memmove(cache[i].wptr, buf, amt[j]);
cache[i].wptr = cache[i].cache +
(((int)(cache[i].wptr - cache[i].cache) + amt[j]) % cache[i].csize);
buf += amt[j]; /* adjust the target buffer pointer */
rval += amt[j]; /* increase the 'total bytes' counter */
blen -= amt[j]; /* decrease the block length counter */
cache[i].filled += amt[j];
}
}
// dprintf(stderr, "push: %d/%d bytes written [filled: %d of %d], stream: %x\n", amt[0] + amt[1], rval, cache[i].filled, CACHESIZE, fd);
pthread_mutex_unlock( &cache[i].cache_lock );
/* unlock the cache for read access, if it */
/* contains some data */
if(cache[i].filled){
pthread_mutex_unlock( &cache[i].readable );
}
}
/* if there is still space in the cache, unlock */
/* it again for further writing by anyone */
if(cache[i].csize - cache[i].filled){
pthread_mutex_unlock( & cache[i].writeable );
}
else
dprintf(stderr, "push: buffer overrun; cache full - leaving cache locked\n");
} while(rval < len);
dprintf(stderr, "push: exitstate: [filled: %3.1f %%], stream: %p\r", 100.0 * (float)cache[i].filled / (float)cache[i].csize, fd);
return rval;
}
int pop(FILE *fd, char *buf, long len)
{
int rval = 0, i, j;
int blen = 0;
i = getCacheSlot(fd);
if(i < 0)
return -1;
dprintf(stderr, "pop: %d bytes requested [filled: %d of %d], stream: %p buf %p\n",
(int) len, (int) cache[i].filled, (int) CACHESIZE, fd, buf);
if(cache[i].fd == fd)
{
do
{
if(cache[i].closed && (!cache[i].filled) )
return 0;
if((!cache[i].filled) && feof(fd)){
return 0;
}
/* try to obtain read permissions for the cache */
/* this will block if the cache is empty */
#if 0 //FIXME no way to stop if connection dead ?
pthread_mutex_lock( & cache[i].readable );
#else
while(true) {
int lret = pthread_mutex_trylock(&cache[i].readable);
if((lret == 0) || (CAudioPlayer::getInstance()->getState() == CBaseDec::STOP_REQ))
break;
usleep(100);
}
#endif
if(cache[i].filled)
{
int amt[2];
/* prevent any modification to the cache by other threads, */
/* mainly by the push() function, while we read data from it */
pthread_mutex_lock( &cache[i].cache_lock );
/* block transfer length: get either what's there or */
/* only as much as we need */
blen = ((len - rval) > cache[i].filled) ? cache[i].filled : (len - rval);
if(cache[i].rptr < cache[i].wptr)
{
amt[0] = cache[i].wptr - cache[i].rptr;
amt[1] = 0;
}
else
{
amt[0] = cache[i].ceiling - cache[i].rptr;
amt[1] = cache[i].wptr - cache[i].cache;
}
for(j=0; j<2; j++)
{
if(amt[j] > blen) amt[j] = blen;
if(amt[j])
{
dprintf(stderr, "pop(): rptr: %p, buf: %p, amt[%d]=%d, blen=%d, len=%d, rval=%d\n",
cache[i].rptr, buf, j, amt[j], blen, (int) len, rval);
memmove(buf, cache[i].rptr, amt[j]);
cache[i].rptr = cache[i].cache +
(((int)(cache[i].rptr - cache[i].cache) + amt[j]) % cache[i].csize);
buf += amt[j]; /* adjust the target buffer pointer */
rval += amt[j]; /* increase the 'total bytes' counter */
blen -= amt[j]; /* decrease the block length counter */
cache[i].filled -= amt[j];
}
}
dprintf(stderr, "pop: %d/%d/%d bytes read [filled: %d of %d], stream: %p\n", amt[0] + amt[1], rval, (int) len, (int) cache[i].filled, (int) CACHESIZE, fd);
/* if the cache is closed and empty, then */
/* force the end condition to be met */
if(cache[i].closed && (! cache[i].filled))
break;//len = rval;
/* allow write access again */
pthread_mutex_unlock( &cache[i].cache_lock );
/* unlock the cache for write access, if it */
/* has some free space */
if(cache[i].csize - cache[i].filled){
pthread_mutex_unlock( &cache[i].writeable );
}
}
/* if there is still data in the cache, unlock */
/* it again for further reading by anyone */
if(cache[i].filled)
{
pthread_mutex_unlock( & cache[i].readable );
}
else
dprintf(stderr, "pop: buffer underrun; cache empty - leaving cache locked\n");
} while((rval < len) && (CAudioPlayer::getInstance()->getState() != CBaseDec::STOP_REQ));
}
else
{
dprintf(stderr, "pop: no cache present for stream %p\n", fd);
rval = -1;
}
// dprintf(stderr, "pop: exitstate: [filled: %3.1f %%], stream: %x\n", 100.0 * (float)cache[i].filled / (float)cache[i].csize, fd);
cache[i].total_bytes_delivered += rval;
if(cache[i].filter_arg)
if(cache[i].filter_arg->state)
cache[i].filter_arg->state->buffered = 65536L * (int64_t)cache[i].filled / (int64_t)CACHESIZE;
return rval;
}
void CacheFillThread(void *c)
{
char *buf;
STREAM_CACHE *scache = (STREAM_CACHE*)c;
int rval, datalen;
if(scache->closed)
return;
dprintf(stderr, "CacheFillThread: thread started, using stream %p\n", scache->fd);
buf = (char*)malloc(CACHEBTRANS);
if(!buf)
{
dprintf(stderr, "CacheFillThread: fatal error ! Could not allocate memory. Terminating.\n");
exit(-1);
}
/* endless loop; read a block of data from the stream */
/* and push it into the cache */
do
{
struct pollfd pfd;
/* has a f_close() call in an other thread already closed the cache ? */
datalen = CACHEBTRANS;
pfd.fd = fileno(scache->fd);
pfd.events = POLLIN | POLLPRI;
pfd.revents = 0;
int ret = poll(&pfd, 1, 1000);
if (ret > 0 && (pfd.revents & POLLIN) == POLLIN) {
#if 0 //FIXME: fread blocks i/o on dead connection
rval = fread(buf, 1, datalen, scache->fd);
if ((rval == 0) && feof(scache->fd))
break; /* exit cache fill thread if eof and nothing to push */
#else
rval = read(fileno(scache->fd), buf, datalen);
if (rval == 0)
break; /* exit cache fill thread if eof and nothing to push */
#endif
/* if there is a filter function set up for this stream, then */
/* we need to call it with the propper arguments */
if(scache->filter)
{
scache->filter_arg->buf = buf;
scache->filter_arg->len = &rval;
scache->filter(scache->filter_arg);
datalen = rval;
}
if( push(scache->fd, buf, rval) < 0)
break;
}
}
while(!scache->closed);
//while( (rval == datalen) && (!scache->closed) );
/* close the cache if the stream disrupted */
scache->closed = 1;
pthread_mutex_unlock( &scache->writeable );
pthread_mutex_unlock( &scache->readable );
/* ... and exit this thread. */
dprintf(stderr, "CacheFillThread: thread exited, stream %p \n", scache->fd);
free(buf);
pthread_exit(0);
}
/**************************** stream filter ******************************/
int f_status(FILE *stream, void (*cb)(void*))
{
int i, rval = -1;
if(!stream)
{
strcpy(err_txt, "NULL pointer as stream id\n");
return -1;
}
/* lookup the stream ID in the cache table */
i = getCacheSlot(stream);
if (i < 0)
return -1;
if(cache[i].fd == stream)
{
/* hook the users function into the steam filter */
if(cache[i].filter_arg)
{
if(cache[i].filter_arg->state)
{
cache[i].filter_arg->state->cb = cb;
rval = 0;
}
else
strcpy(err_txt, "no cache[].filter_arg->state hook\n");
}
else
strcpy(err_txt, "no cache[].filter_arg hook\n");
}
return rval;
}
/* parse the meta data block and copy all relevant */
/* information into the CSTATE structure */
void ShoutCAST_ParseMetaData(char *md, CSTATE *state)
{
#define SKIP(a) for(;(a && !isalnum(*a)); ++a) {};
char *ptr;
/* abort if we were submitted a NULL pointer */
if((!md) || (!state))
return;
dprintf(stderr, "ShoutCAST_ParseMetaData(%p : %s, %p)\n", md, md, state);
ptr = strstr(md, "StreamTitle=");
if(ptr)
{
/* look if there is a dash or a comma that separates artist and title */
ptr = strstr(md, " - ");
if(!ptr)
ptr = strstr(md, ", ");
/* no separator, simply copy everything into the 'title' field */
if(!ptr)
{
ptr = strchr(md, '=');
strncpy(state->title, ptr + 2, 4095);
state->title[4095] = '\0';
ptr = strchr(state->title, ';');
if(ptr)
*(ptr - 1) = 0;
state->artist[0] = 0;
}
else
{
SKIP(ptr);
strcpy(state->title, ptr);
ptr = strchr(state->title, ';');
if(ptr)
*(ptr - 1) = 0;
ptr = strstr(md, "StreamTitle=");
ptr = strchr(ptr, '\'');
strncpy(state->artist, ptr + 1, 4095);
state->artist[4095] = '\0';
ptr = strstr(state->artist, " - ");
if(!ptr)
ptr = strstr(state->artist, ", ");
if(ptr)
*ptr = 0;
}
state->state = RUNNING;
}
}
void ShoutCAST_DestroyFilter(void *a)
{
STREAM_FILTER *arg = (STREAM_FILTER*)a;
if(arg->state)
free(arg->state), arg->state = NULL;
if(arg->user)
free(arg->user), arg->user = NULL;
}
STREAM_FILTER *ShoutCAST_InitFilter(int meta_int)
{
STREAM_FILTER *arg;
arg = (STREAM_FILTER*)calloc(1, sizeof(STREAM_FILTER));
/* allocate our private data space, hook it into the */
/* stream filter structure and initialize the variables */
if(!arg->user)
{
arg->user = calloc(1, sizeof(FILTERDATA));
arg->state = (CSTATE*)calloc(1, sizeof(CSTATE));
((FILTERDATA*)arg->user)->meta_int = meta_int;
arg->destructor = ShoutCAST_DestroyFilter;
}
return arg;
}
void ShoutCAST_MetaFilter(STREAM_FILTER *arg)
{
/* bug trap */
if(!arg)
return;
FILTERDATA *filterdata = (FILTERDATA*)arg->user;
int meta_int = filterdata->meta_int;
int len = *arg->len;
char*buf = (char*)arg->buf;
int meta_start;
#if 0
dprintf(stderr, "filter : cnt : %d\n", filterdata->cnt);
dprintf(stderr, "filter : len : %d\n", filterdata->len);
dprintf(stderr, "filter : stored : %d\n", filterdata->stored);
dprintf(stderr, "filter : cnt + len: %d\n", filterdata->cnt + len);
dprintf(stderr, "filter : meta_int : %d\n", filterdata->meta_int);
#endif
/* not yet all meta data has been processed */
if(filterdata->stored < filterdata->len)
{
int bsize = (filterdata->len + 1) - filterdata->stored;
/* if there is some meta data, extract it */
/* there can be zero size blocks too */
if(filterdata->len)
{
dprintf(stderr, "filter : ********* partitioned metadata block part 2 ******\n");
memmove(filterdata->meta_data + filterdata->stored, buf, bsize);
ShoutCAST_ParseMetaData(filterdata->meta_data, arg->state);
/* call the users callback function */
if(arg->state->cb)
arg->state->cb(arg->state);
//dprintf(stderr, "filter : metadata : \n\n\n----------\n%s\n----------\n\n\n", filterdata->meta_data);
/* remove the metadata and it's size indicator from the buffer */
memmove(buf, buf + bsize, len - bsize);
*arg->len -= bsize;
filterdata->cnt = len - bsize;
filterdata->stored = 0;
filterdata->len = 0;
}
return;
}
if((filterdata->cnt < meta_int) && ((filterdata->cnt + len) <= meta_int))
{
/* do nothing; leave the data block and the length variable */
/* untouched, update our private counter and return */
filterdata->cnt += len;
return;
}
/* does a meta data block start in the current block ? */
if((filterdata->cnt <= meta_int) && ((filterdata->cnt + len) > meta_int))
{
meta_start = meta_int - filterdata->cnt;
/* the first byte of the meta data block tells us how long it is */
filterdata->len = 16 * (int)buf[meta_start];
dprintf(stderr, "filter : ---> metadata %d bytes @ %d\n", filterdata->len, meta_start);
/****************************************************************/
/* case A: the meta data is completely within the current block */
/****************************************************************/
if((meta_start + filterdata->len) <= len)
{
int b = meta_start + filterdata->len + 1;
/* if there is some meta data, extract it; */
/* there can be zero size blocks too */
if(filterdata->len)
{
memset(filterdata->meta_data, 0, 4096);
memmove(filterdata->meta_data, buf + meta_start, filterdata->len);
ShoutCAST_ParseMetaData(filterdata->meta_data, arg->state);
/* call the users callback function */
if(arg->state->cb)
arg->state->cb(arg->state);
//dprintf(stderr, "filter : metadata : \n\n\n----------\n%s\n----------\n\n\n", filterdata->meta_data);
}
/* remove the metadata and it's size indicator from the buffer */
memmove(buf + meta_start, buf + b, len - b );
/* adjust the buffersize */
*arg->len -= filterdata->len + 1;
filterdata->cnt = len - b;
filterdata->stored = 0;
filterdata->len = 0;
}
/************************************************************************/
/* case B: the meta data is partitioned and continues in the next block */
/************************************************************************/
else
{
dprintf(stderr, "filter : ********* partitioned metadata block part 1 ******\n");
/* if there is some meta data, extract it */
/* there can be zero size blocks too */
if(filterdata->len)
{
memset(filterdata->meta_data, 0, 4096);
filterdata->stored = len - meta_start;
memmove(filterdata->meta_data, buf + meta_start, filterdata->stored);
}
*arg->len = meta_start;
filterdata->cnt = 0;
}
}
}
/**************************** utility functions ******************************/
void parseURL_url(URL& url) {
/* now lets see what we have ... */
char buffer[2048];
// printf("parseURL_url: %s\n", url.url);
char *ptr = strstr(url.url, "://");
if (!ptr)
{
url.access_mode = MODE_FILE;
strcpy(url.file, url.url);
url.host[0] = 0;
url.port = 0;
url.logindata[0] = 0;
}
else
{
strcpy(url.host, ptr + 3);
/* select the appropriate transport modes */
transport("http", MODE_HTTP, HTTP11);
transport("icy", MODE_HTTP, SHOUTCAST);
transport("scast", MODE_SCAST, SHOUTCAST);
//findme
transport("icast", MODE_ICAST, SHOUTCAST);
/* if we fetch a playlist file, then set the access mode */
/* that it will be parsed and processed automatically. If */
/* it does not fail, then the call returns with an opened stream */
/* this currently results in an endless loop due to recursive calls of f_open()
if((url.access_mode == HTTP11) && (strstr(url.url, ".pls")))
{
url.access_mode = MODE_PLS;
url.proto_version = SHOUTCAST;
}
*/
/* extract the file path from the url */
ptr = strchr(ptr + 3, '/');
if(ptr)
strcpy(url.file, ptr);
else
strcpy(url.file, "/");
/* extract the host part from the url */
ptr = strchr(url.host, '/');
if(ptr)
*ptr = 0;
if ((ptr = strchr(url.host, '@')))
{
*ptr = 0;
base64_encode(buffer, url.host);
strcpy(url.logindata, buffer);
strcpy(buffer, ptr + 1);
strcpy(url.host, buffer);
}
else
url.logindata[0] = 0;
ptr = strrchr(url.host, ':');
if(ptr)
{
url.port = atoi(ptr + 1);
*ptr = 0;
}
}
}
int base64_encode(char *dest, const char *src)
{
int retval = 1;
int src_len, src_pos;
char symbols[65];
char mypart[3];
char mybits[24];
int part_pos;
int null_bytes = 0;
if (dest != NULL && src != NULL && (src_len = strlen(src)) > 0)
{
strcpy(symbols,"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/");
char buffer[src_len*2];
char *buf_ptr = buffer;
for (src_pos = 0; src_pos < src_len; src_pos +=3)
{
for (part_pos = 0; part_pos < 3; part_pos++)
{
if (src[src_pos+part_pos] != 0)
mypart[part_pos] = src[src_pos+part_pos];
else
{
null_bytes = 3 - part_pos;
mypart[part_pos] = 0;
if (part_pos < 2) /* here: part_pos == 1, cannot be 0 */
mypart[part_pos+1] = 0;
break;
}
}
for (part_pos = 0; part_pos < 24; part_pos++)
mybits[part_pos] = ( mypart[part_pos/8] >> (7 - (part_pos%8)) ) & 0x1;
for (part_pos = 0; part_pos < 24; part_pos+=6)
{
*buf_ptr = (mybits[part_pos] << 5) |
(mybits[part_pos+1] << 4) | (mybits[part_pos+2] << 3) | (mybits[part_pos+3] << 2) |
(mybits[part_pos+4] << 1) | mybits[part_pos+5];
buf_ptr++;
}
}
for (part_pos = 0 ; part_pos < buf_ptr - buffer - null_bytes; part_pos++)
dest[part_pos] = symbols[(int)buffer[part_pos]];
for (part_pos = buf_ptr - buffer - null_bytes ; part_pos < buf_ptr - buffer; part_pos++)
dest[part_pos] = '=';
}
else
retval = 0;
return retval;
}