winamp/Src/nsv/nsvplay/readers.cpp

950 lines
20 KiB
C++
Raw Normal View History

2024-09-24 14:54:57 +02:00
#include <windows.h>
#include "main.h"
#include <sys/stat.h>
#ifdef NO_WASABI
#include "../../jnetlib/httpget.h"
api_httpreceiver *CreateGet()
{
return new JNL_HTTPGet;
}
void ReleaseGet(api_httpreceiver *&get)
{
delete (JNL_HTTPGet *)get;
get=0;
}
#else
#include "../..\Components\wac_network\wac_network_http_receiver_api.h"
#include <api.h>
#include <api/service/waservicefactory.h>
#include "../../Winamp/in2.h"
extern In_Module mod;
waServiceFactory *httpFactory = 0;
api_httpreceiver *CreateGet()
{
api_httpreceiver *get = 0;
if (!httpFactory && mod.service)
httpFactory = mod.service->service_getServiceByGuid(httpreceiverGUID);
if (httpFactory)
get = (api_httpreceiver *)httpFactory->getInterface();
return get;
}
void ReleaseGet(api_httpreceiver *&get)
{
if (!get)
return ;
if (!httpFactory && mod.service)
waServiceFactory *sf = mod.service->service_getServiceByGuid(httpreceiverGUID);
if (httpFactory)
httpFactory->releaseInterface(get);
get = 0;
}
#endif
#define MAX_MULTICONNECTS 8
class HTTPReader : public IDataReader
{
public:
HTTPReader(const char *url);
~HTTPReader();
size_t read(char *buf, size_t len);
bool iseof() { return !!m_eof; }
char *gettitle() { return m_title; }
char *geterror() { return m_err; }
bool canseek() { return m_content_length != 0xFFFFFFFF && m_accept_ranges && !m_meta_interval; }
int seek(unsigned __int64 newpos)
{
if (!canseek()) return 1;
doConnect((int)newpos);
return 0;
}
unsigned __int64 getsize() { return m_content_length; }
char *getheader(char *header_name)
{
return m_get ? (char *)m_get->getheader(header_name) : NULL;
}
private:
int serialconnect( int seekto , int timeout);
void doConnect(int seekto);
int getProxyInfo(const char *url, char *out);
char *m_url;
char *m_err;
char *m_title;
int m_eof;
int m_meta_init, m_meta_interval, m_meta_pos, m_meta_size, m_meta_buf_pos;
char m_meta_buf[4096];
int m_read_headers;
unsigned int m_content_length;
int m_accept_ranges;
int m_is_uvox;
int m_uvox_readpos;
int m_uvox_enough_bytes;
char proxybuf[8400], *proxy;
api_httpreceiver *m_get;
// this structure is allocated once, and freed once
struct
{
char *url; //pointers into m_url
// these two are only active temporarily.
api_httpreceiver *get;
char *error;
}
m_cons[MAX_MULTICONNECTS];
int m_numcons;
int m_newcons;
int m_serialized;
int m_mstimeout;
int m_contryptr;
int m_serialfailed;
int m_useaproxy;
};
HTTPReader::HTTPReader(const char *url)
{
m_title = 0;
m_is_uvox = m_uvox_readpos = 0;
m_meta_init = m_meta_interval = m_meta_pos = m_meta_size = m_meta_buf_pos = 0;
m_meta_buf[0] = 0;
m_err = NULL;
m_eof = 0;
m_read_headers = 0;
m_content_length = 0xFFFFFFFF;
m_accept_ranges = 0;
m_get = NULL;
m_serialized = 0;
m_mstimeout = 0;
m_contryptr = 0;
m_newcons = 0;
m_serialfailed = 0;
m_useaproxy = 0;
// TCP multiconnect
// JF> using ; as a delimiter is vomit inducing and breaks a lot of other
// code. I petition we use <> to delimit, and I'm making it do that.
m_numcons = 0;
m_url = _strdup(url);
int allowproxy = 1;
char *tmpurl = m_url;
while (m_numcons < MAX_MULTICONNECTS)
{
char *next = strstr( tmpurl, "<>" );
if ( next ) *next = '\0';
if (tmpurl[0])
{
m_cons[m_numcons].error = NULL;
m_cons[m_numcons].get = NULL;
m_cons[m_numcons++].url = tmpurl;
if (!_strnicmp(tmpurl, "uvox:", 5)) allowproxy = 0;
if (!_strnicmp(tmpurl, "order://", 8))
{
char *p = tmpurl + 8;
// serialized mctp
m_serialized = 1;
m_numcons--;
m_mstimeout = atoi(p);
if ( m_mstimeout < 1 )
{
m_serialized = 0;
m_mstimeout = 0;
}
}
}
if (!next) break;
tmpurl = next + 2;
}
memset(proxybuf, 0, sizeof(proxybuf));
proxy = NULL;
if (allowproxy && getProxyInfo(url, proxybuf))
{
proxy = strstr(proxybuf, "http=");
if (!proxy) proxy = proxybuf;
else
{
proxy += 5;
char *tp = strstr(proxy, ";");
if (tp) *tp = 0;
}
}
m_is_uvox = 0;
if ( m_serialized && m_numcons > 1 ) // sanity check
{
int rval = 0, i;
m_newcons = 1;
// walk the list, set the url such that m_cons[0].url points to each item. try to connect
// serialconnect returns error codes -1 on error, 0 on timeout, 1 on successfull connect
for ( i = 0; i < m_numcons; i++ )
{
if ( i )
{
m_cons[0].url = m_cons[i].url;
}
rval = serialconnect(0, m_mstimeout);
if ( rval == 1 ) break;
}
if ( rval < 1 )
{
// we didnt get a connection so...
m_serialfailed = 1;
}
}
else
doConnect(0);
}
void HTTPReader::doConnect(int seekto)
{
ReleaseGet(m_get);
m_uvox_readpos = 0;
m_eof = 0;
int i;
for (i = 0; i < m_numcons; i++ )
{
free(m_cons[i].error);
m_cons[i].error = NULL;
ReleaseGet(m_cons[i].get);
m_cons[i].get = CreateGet();
if (!m_cons[i].get)
break;
m_cons[i].get->open(API_DNS_AUTODNS, 65536, (proxy && proxy[0]) ? proxy : NULL);
#ifdef WINAMP_PLUGIN
m_cons[i].get->addheader("User-Agent:Winamp NSV Player/5.12 (ultravox/2.0)");
#else
# ifdef WINAMPX
m_cons[i].get->addheader("User-Agent:" UNAGI_USER_AGENT " (ultravox/2.0)");
# else
m_cons[i].get->addheader("User-Agent:NSV Player/0.0 (ultravox/2.0)");
# endif
#endif
m_cons[i].get->addheader("Accept:*/*");
m_cons[i].get->addheader("Connection:close");
m_cons[i].get->addheader("Ultravox-transport-type: TCP");
if (seekto)
{
char buf[64] = {0};
wsprintfA(buf, "Range:bytes=%d-", seekto);
m_cons[i].get->addheader(buf);
}
else
m_cons[i].get->addheader("icy-metadata:1");
m_cons[i].get->connect(m_cons[i].url, !!seekto);
}
m_uvox_enough_bytes = 1;
}
HTTPReader::~HTTPReader()
{
ReleaseGet(m_get);
free(m_title);
free(m_err);
free(m_url);
int i;
for (i = 0; i < m_numcons; i++)
{
ReleaseGet(m_cons[i].get);
free(m_cons[i].error);
}
}
int HTTPReader::serialconnect(int seekto , int timeout)
{
ReleaseGet(m_get);
m_uvox_readpos = 0;
m_eof = 0;
int64_t mythen, mynow , myref;
LARGE_INTEGER then, now, ref;
QueryPerformanceFrequency( &ref);
myref = ref.QuadPart;
QueryPerformanceCounter( &then );
mythen = then.QuadPart;
int timer = 0;
int i = 0;
{
ReleaseGet(m_cons[i].get);
m_cons[i].get = CreateGet();
if (m_cons[i].get == NULL)
return 0;
m_cons[i].get->open(API_DNS_AUTODNS, 65536, (proxy && proxy[0]) ? proxy : NULL);
#ifdef WINAMP_PLUGIN
m_cons[i].get->addheader("User-Agent:Winamp NSV Player/5.12 (ultravox/2.0)");
#else
# ifdef WINAMPX
m_cons[i].get->addheader("User-Agent:" UNAGI_USER_AGENT " (ultravox/2.0)");
# else
m_cons[i].get->addheader("User-Agent:NSV Player/0.0 (ultravox/2.0)");
# endif
#endif
m_cons[i].get->addheader("Accept:*/*");
m_cons[i].get->addheader("Connection:close");
m_cons[i].get->addheader("Ultravox-transport-type: TCP");
if (seekto)
{
char buf[64] = {0};
wsprintfA(buf, "Range:bytes=%d-", seekto);
m_cons[i].get->addheader(buf);
}
else m_cons[i].get->addheader("icy-metadata:1");
m_cons[i].get->connect(m_cons[i].url, !!seekto);
}
m_uvox_enough_bytes = 1;
int ret, status;
if (!m_get)
{
if (m_err) return 0;
int i;
int found = 0;
i = 0;
while ( timer < timeout )
{
if (!m_cons[i].get) return 0;
found = 1;
QueryPerformanceCounter( &now );
mynow = now.QuadPart;
float profiletime = (float)(mynow - mythen);
profiletime /= myref;
profiletime *= 1000.0;
timer = (int) profiletime;
ret = m_cons[i].get->run();
status = m_cons[i].get->get_status();
if (ret < 0 || status < 0)
{
const char *t = m_cons[i].get->geterrorstr();
if (t)
{}
ReleaseGet(m_cons[i].get);
break;
}
if ( status > 0 )
{
int code = m_cons[i].get->getreplycode();
if ( code < 200 || code > 299 )
{
ReleaseGet(m_cons[i].get);
//wsprintf( m_cons[i].error, "Error: Server returned %d", code );
break;
}
else
{
// we're in good shape, make our getter current, and delete all the gay ones
ReleaseGet(m_get); // just in case, probably zero anyway
m_get = m_cons[i].get;
m_cons[i].get = NULL;
// trash i here, but we are breaking anyway :)
/* for (i = 0; i < m_numcons; i++)
{
delete m_cons[i].get;
m_cons[i].get = NULL;
free( m_cons[i].error );
m_cons[i].error = NULL;
}*/
break;
}
}
#ifdef _WIN32
Sleep(1);
#else
usleep(1000);
#endif
} // while
if ( timer > timeout )
{
ReleaseGet(m_cons[i].get);
ReleaseGet(m_get);
return 0;
}
if (!m_get)
{
return 0;
}
}
if ( m_get ) return 1;
else return 0;
}
size_t HTTPReader::read(char *buffer, size_t len)
{
int ret, status;
if (!m_get)
{
if (m_err) return 0;
int i;
int found = 0;
for (i = 0; !m_get && i < m_numcons; i++)
{
if (!m_cons[i].get) continue;
found = 1;
ret = m_cons[i].get->run();
status = m_cons[i].get->get_status();
if (ret < 0 || status < 0)
{
const char *t = m_cons[i].get->geterrorstr();
if (t)
{
free(m_cons[i].error);
m_cons[i].error = _strdup( t );
}
ReleaseGet(m_cons[i].get);
}
if ( status > 0 )
{
int code = m_cons[i].get->getreplycode();
if ( code < 200 || code > 299 )
{
ReleaseGet(m_cons[i].get);
m_cons[i].get = NULL;
free(m_cons[i].error);
m_cons[i].error = (char *)malloc( 100 );
wsprintfA( m_cons[i].error, "Error: Server returned %d", code );
}
else
{
// we're in good shape, make our getter current, and delete all the gay ones
ReleaseGet(m_get); // just in case, probably zero anyway
m_get = m_cons[i].get;
m_cons[i].get = NULL;
// trash i here, but we are breaking anyway :)
for (i = 0; i < m_numcons; i++)
{
ReleaseGet(m_cons[i].get);
free( m_cons[i].error );
m_cons[i].error = NULL;
}
break; // exit loop of connections
}
}
} // loop of connections
if (!found) // out of attempted connections heh
{
free( m_err );
if (m_numcons > 1)
{
size_t size = 0;
for (i = 0; i < m_numcons; i++)
if ( m_cons[i].error ) size += strlen( m_cons[i].error ) + 1;
m_err = (char *)malloc(size + 100);
wsprintfA( m_err, "No Valid Multiconnect URLs (%d);", m_numcons );
for (i = 0; i < m_numcons; i++)
{
strcat( m_err, m_cons[i].error );
strcat( m_err, ";" );
free(m_cons[i].error);
m_cons[i].error = NULL;
}
}
else
{
m_err = m_cons[0].error;
m_cons[0].error = NULL;
if (!m_err) m_err = _strdup("Connection error (Invalid URL?)");
}
}
if (!m_get) return 0;
}
ret = m_get->run();
status = m_get->get_status();
if (ret > 0 && (!m_get->bytes_available() || !m_uvox_enough_bytes) && status > 1)
{
m_eof = 1;
}
if (ret < 0 || status < 0)
{
const char *t = m_get->geterrorstr();
if (t)
{
free( m_err );
m_err = (char *)malloc( strlen( t) + 16 );
wsprintfA( m_err, "Error: %s", t );
return 0;
}
}
if (status > 0)
{
if (!m_read_headers)
{
if (status > 1)
{
const char *v = m_get->getheader("Content-Length");
if (v) m_content_length = atoi(v);
v = m_get->getheader("Accept-Ranges");
if (v) while (v && *v)
{
if (!_strnicmp(v, "bytes", 5))
{
m_accept_ranges = 1;
break;
}
v++;
}
v = m_get->getheader("icy-metaint");
if (v)
{
m_meta_interval = atoi(v);
}
if (!m_title)
{
v = m_get->getheader("icy-name");
if (v)
m_title = _strdup(v);
}
#ifdef WINAMP_PLUGIN
extern void process_url(char *url);
v = m_get->getheader("icy-url");
if (v && !strstr(v, "shoutcast.com"))
{
char *p = (char *)v; while (p && *p && *p == ' ') p++;
process_url(p);
}
#endif
v = m_get->getheader("content-type");
if (v && !_stricmp(v, "misc/ultravox"))
{
v = m_get->getheader("ultravox-max-msg");
if (v) m_is_uvox = atoi(v);
if (!m_is_uvox) m_is_uvox = 16000;
}
if (!m_title)
{
v = m_get->getheader("content-disposition");
if (v) v = strstr(v, "filename=");
if (v)
{
m_title = _strdup(v + 9);
}
}
m_read_headers = 1;
}
}
size_t l = m_get->bytes_available();
if (m_is_uvox)
{
again:
if (l >= 6)
{
unsigned char buf[32768*2] = {0};
m_get->peek_bytes((char *)buf, 6);
if (buf[0] != 0x5A)
{
l--;
m_get->get_bytes((char *)buf, 1);
goto again;
}
int resqos = buf[1];
int classtype = (buf[2] << 8) | buf[3];
int msglen = (buf[4] << 8) | buf[5];
if (msglen > m_is_uvox) // length is too long
{
m_get->get_bytes((char *)buf, 1);
l--;
goto again;
}
if (msglen + 7 <= (int)l)
{
m_uvox_enough_bytes = 1;
m_get->peek_bytes((char *)buf, msglen + 7);
if (buf[msglen + 6])
{
m_get->get_bytes((char *)buf, 1);
l--;
goto again;
}
if (classtype == 0x7777) // take any data for now, ignore all other frames
{
l = msglen - m_uvox_readpos;
if (l > len) l = len;
memcpy(buffer, buf + 6 + m_uvox_readpos, l);
m_uvox_readpos += (int)l;
if (m_uvox_readpos >= msglen)
{
m_uvox_readpos = 0;
m_get->get_bytes((char *)buf, msglen + 7);
}
return l;
#ifdef WINAMP_PLUGIN
}
else if ( classtype == 0x3001 )
{
extern void process_metadata(char *buf, int size);
m_get->get_bytes((char *)buf, msglen + 7);
process_metadata((char*)buf + 6, msglen + 1);
#endif
}
else
{
m_get->get_bytes((char *)buf, msglen + 7);
}
}
else
{
m_uvox_enough_bytes = 0;
}
}
return 0;
}
else
{
if (l > len) l = len;
m_get->get_bytes(buffer, (int)l);
if (m_meta_interval)
{
int x = (int)l;
unsigned char *buf = (unsigned char *)buffer;
if (m_meta_size) // already in meta block
{
int len = min(x, m_meta_size - m_meta_buf_pos);
memcpy(m_meta_buf + m_meta_buf_pos, buf, len);
m_meta_buf_pos += len;
if (m_meta_buf_pos == m_meta_size)
{
// if(metacb) metacb->metaDataReader_onData(m_meta_buf,m_meta_size);
m_meta_buf_pos = 0;
m_meta_size = 0;
m_meta_pos = 0;
}
x -= len;
if (x) memcpy(buf, buf + len, x);
}
else if (m_meta_pos + x > m_meta_interval) // block contains meta data somewhere in it, and we're not alreayd reading a block
{
int start_offs = m_meta_interval - m_meta_pos;
int len;
m_meta_size = ((unsigned char *)buf)[start_offs] * 16;
len = min(x - start_offs - 1, m_meta_size);
if (len) memcpy(m_meta_buf, buf + start_offs + 1, len);
m_meta_buf_pos = len;
if (m_meta_buf_pos == m_meta_size) // full read of metadata successful
{
x -= m_meta_size + 1;
if (x > start_offs) memcpy(buf + start_offs, buf + start_offs + 1 + m_meta_size, x - start_offs);
#ifdef WINAMP_PLUGIN
extern void process_metadata(char *buf, int size);
process_metadata(m_meta_buf, m_meta_size);
#endif
//if(metacb) metacb->metaDataReader_onData(m_meta_buf,m_meta_size);
m_meta_buf_pos = 0;
m_meta_pos = -start_offs;
m_meta_size = 0;
}
else
{
x = start_offs; // otherwise, there's only the first block of data
}
}
if (x > 0)
{
m_meta_pos += x;
}
l = x;
} // end of poopie metadata
} // !uvox
#if 0
{
FILE *fh = fopen("c:\\dump.nsv", "ab");
fwrite(buffer, 1, l, fh);
fclose(fh);
}
#endif
return l;
}
return 0;
}
static void parseURL(char *url, char *lp, char *host, int *port, char *req)
{
char *p, *np;
/* if (_strnicmp(url,"http://",4) &&
_strnicmp(url,"icy://",6) &&
_strnicmp(url,"sc://",6) &&
_strnicmp(url,"shoutcast://",12)) return;
*/
np = p = strstr(url, "://");
if (!np) np = (char*)url;
else np += 3;
if (!p) p = (char*)url;
else p += 3;
while (np && *np != '/' && *np) *np++;
if (np && *np)
{
lstrcpynA(req, np, 2048);
*np++ = 0;
}
else strcpy(req, "/");
np = p;
while (np && *np != '@' && *np) np++;
if (np && *np)
{
*np++ = 0;
lstrcpynA(lp, p, 256);
p = np;
}
else lp[0] = 0;
np = p;
while (np && *np != ':' && *np) np++;
if (*np)
{
*np++ = 0;
*port = atoi(np);
}
else *port = 80;
lstrcpynA(host, p, 256);
}
int HTTPReader::getProxyInfo(const char *url, char *out)
{
#ifndef WINAMPX
char INI_FILE[MAX_PATH] = {0};
char *p;
GetModuleFileNameA(NULL, INI_FILE, sizeof(INI_FILE));
p = INI_FILE + strlen(INI_FILE);
while (p >= INI_FILE && *p != '.') p--;
strcpy(++p, "ini");
GetPrivateProfileStringA("Winamp", "proxy", "", out, 8192, INI_FILE);
return !!out[0];
#else
DWORD v = 0;
HKEY hKey;
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "Software\\AOL\\Unagi", 0, KEY_READ, &hKey) == ERROR_SUCCESS)
{
DWORD l = 4;
DWORD t;
if (RegQueryValueEx(hKey, "ProxyEnable", NULL, &t, (unsigned char *)&v, &l) == ERROR_SUCCESS && t == REG_DWORD)
{
if ( v != 2 )
{
l = 8192;
if (RegQueryValueEx(hKey, "ProxyServer", NULL, &t, (unsigned char *)out, &l ) != ERROR_SUCCESS || t != REG_SZ)
{
v = 0;
*out = 0;
}
}
else return 0;
}
else v = 0;
out[512 - 1] = 0;
RegCloseKey(hKey);
}
if ( !v && m_useaproxy )
{
char blah[8192] = "";
lstrcpyn(blah, url, 8192);
char plp[512] = {0};
char phost[512] = {0};
int pport = 80;
char pthereq[1024] = {0};
parseURL(blah, plp, phost, &pport, pthereq);
v = ResolvProxyFromURL(url, phost, out);
if ( v < 0 ) v = 0; // error getting proxy
}
if ( v > 0)
{
char prox[1024] = {0};
wsprintf(prox, "PROXY: %s", out);
SendMetadata(prox, 1);
}
return v;
#endif
}
class Win32FileReader : public IDataReader
{
public:
Win32FileReader(HANDLE file) { m_hFile = file; m_eof = 0; m_err = NULL; }
~Win32FileReader() { CloseHandle(m_hFile); }
size_t read(char *buf, size_t len)
{
DWORD ob = 0;
if (!len) return 0;
if (!ReadFile(m_hFile, buf, (DWORD)len, &ob, NULL))
{
m_err = "Error calling ReadFile()!";
return 0;
}
else if (!ob) m_eof = true;
return ob;
}
bool iseof() { return m_eof; }
bool canseek() { return 1; }
int seek(uint64_t newpos)
{
LARGE_INTEGER li;
li.QuadPart = newpos;
li.LowPart = SetFilePointer (m_hFile, li.LowPart, &li.HighPart, SEEK_SET);
if (li.LowPart == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR)
{
li.QuadPart = -1;
}
return li.QuadPart== ~0;
}
uint64_t getsize()
{
LARGE_INTEGER position;
position.QuadPart=0;
position.LowPart = GetFileSize(m_hFile, (LPDWORD)&position.HighPart);
if (position.LowPart == INVALID_FILE_SIZE && GetLastError() != NO_ERROR)
return INVALID_FILE_SIZE;
else
return position.QuadPart;
}
char *geterror() { return m_err; }
private:
HANDLE m_hFile;
bool m_eof;
char *m_err;
};
#define VAR_TO_FPOS(fpos, var) (fpos) = (var)
#define FPOS_TO_VAR(fpos, typed, var) (var) = (typed)(fpos)
class FileReader : public IDataReader
{
public:
FileReader(FILE *file) { fp = file; m_err = NULL; }
~FileReader() { fclose(fp); }
size_t read(char *buf, size_t len)
{
size_t ob;
if (!len) return 0;
ob = fread(buf, 1, len, fp);
if (!ob && ferror(fp))
{
m_err = "Error calling fread()!";
return 0;
}
return ob;
}
bool iseof() { return !!feof(fp); }
bool canseek() { return 1; }
int seek(uint64_t newpos)
{
fpos_t pos= newpos;
VAR_TO_FPOS(pos, newpos);
return fsetpos(fp, &pos);
}
unsigned __int64 getsize()
{
struct stat s;
if (fstat(fileno(fp), &s) < 0)
{
m_err = "Error calling fread()!";
return 0;
}
return s.st_size;
}
char *geterror() { return m_err; }
private:
FILE *fp;
char *m_err;
};
IDataReader *CreateReader(const char *url)
{
if (strstr(url, "://")) return new HTTPReader(url);
#ifdef _WIN32
HANDLE hFile = CreateFileA(url, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
if (hFile != INVALID_HANDLE_VALUE)
return new Win32FileReader(hFile);
#else
FILE *fp = fopen(url, "r");
if (fp)
return new FileReader(fp);
#endif
return NULL;
}