mirror of
https://github.com/tuxbox-neutrino/neutrino.git
synced 2025-08-29 16:31:11 +02:00
2038 lines
55 KiB
C++
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;
|
|
}
|
|
|