mirror of
https://github.com/WinampDesktop/winamp.git
synced 2024-09-24 15:54:12 +00:00
630 lines
14 KiB
C++
630 lines
14 KiB
C++
#include "HTTPReader.h"
|
|
#include "..\Components\wac_network\wac_network_http_receiver_api.h"
|
|
#include "api__filereader.h"
|
|
#include "../nu/AutoChar.h"
|
|
#include <api/service/waservicefactory.h>
|
|
#include <api/filereader/api_readercallback.h>
|
|
#include <wchar.h>
|
|
#include <bfc/platform/strcmp.h>
|
|
#include <bfc/platform/minmax.h>
|
|
#ifdef _WIN32
|
|
#include <shlwapi.h>
|
|
#endif
|
|
|
|
#ifdef __APPLE__
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#include <stdexcept>
|
|
|
|
// so we don't accidently call these CRT functions
|
|
#ifdef close
|
|
#undef close
|
|
#endif
|
|
#ifdef open
|
|
#undef open
|
|
#endif
|
|
#ifdef read
|
|
#undef read
|
|
#endif
|
|
|
|
#define config_guess_prebuffer true
|
|
#define config_buffer_size 64
|
|
#define config_prebuffer_size 24
|
|
#define config_prebuffer_min 0
|
|
#define config_allowseek true
|
|
#define config_fullseek true
|
|
#define config_seekprebuffer 1
|
|
#define config_suppressstatus false
|
|
|
|
// {C0A565DC-0CFE-405a-A27C-468B0C8A3A5C}
|
|
static const GUID internetConfigGroupGUID =
|
|
{
|
|
0xc0a565dc, 0xcfe, 0x405a, { 0xa2, 0x7c, 0x46, 0x8b, 0xc, 0x8a, 0x3a, 0x5c }
|
|
};
|
|
|
|
class HttpReader
|
|
{
|
|
public:
|
|
HttpReader(const char *url, uint64_t start_offset = 0, uint64_t total_len = 0, int is_seek = 0);
|
|
~HttpReader();
|
|
|
|
int connect();
|
|
int read(int8_t *buffer, int length);
|
|
|
|
void abort() { killswitch = 1; }
|
|
|
|
int bytesAvailable();
|
|
|
|
uint64_t getContentLength()
|
|
{
|
|
if (m_contentlength)
|
|
return m_contentlength;
|
|
|
|
return -1;
|
|
}
|
|
|
|
int canSeek()
|
|
{
|
|
return (m_contentlength &&
|
|
/* JF> this is correct but not as compatible: m_accept_ranges && */
|
|
!m_meta_interval);
|
|
}
|
|
|
|
uint64_t getPos() { return m_contentpos; }
|
|
|
|
const char *getHeader( const char *header ) { return httpGetter->getheader( (char *)header ); }
|
|
|
|
void setMetaCB( api_readercallback *cb ) { metacb = cb; }
|
|
|
|
//static BOOL CALLBACK httpDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
|
|
|
|
private:
|
|
api_httpreceiver *httpGetter = NULL;
|
|
api_dns *dns = NULL;
|
|
|
|
char *m_AllHeaders;
|
|
|
|
int buffer_size;
|
|
int prebuffer_size, prebuffer_min;
|
|
int need_prebuffer;
|
|
uint64_t m_contentlength, m_contentpos;
|
|
int m_accept_ranges;
|
|
|
|
int proxy_enabled;
|
|
char *proxy;
|
|
|
|
int killswitch = -1;
|
|
|
|
int m_meta_init, m_meta_interval, m_meta_pos, m_meta_size, m_meta_buf_pos;
|
|
char m_meta_buf[4096];
|
|
|
|
api_readercallback *metacb;
|
|
|
|
int guessed_prebuffer_size;
|
|
|
|
char lpinfo[256];
|
|
char force_lpinfo[256];
|
|
char *dlg_realm;
|
|
char *m_url;
|
|
};
|
|
|
|
|
|
HttpReader::HttpReader( const char *url, uint64_t start_offset, uint64_t total_len, int is_seek )
|
|
{
|
|
m_accept_ranges = 0;
|
|
buffer_size = (config_buffer_size * 1024);
|
|
prebuffer_size = (config_prebuffer_size * 1024);
|
|
prebuffer_min = (config_prebuffer_min * 1024);
|
|
guessed_prebuffer_size = !config_guess_prebuffer;
|
|
|
|
if (is_seek)
|
|
{
|
|
prebuffer_min = prebuffer_size = config_seekprebuffer;
|
|
guessed_prebuffer_size = 1;
|
|
}
|
|
|
|
proxy_enabled = 0;
|
|
killswitch = 0;
|
|
need_prebuffer = 0;
|
|
m_contentlength = total_len;
|
|
m_contentpos = start_offset;
|
|
m_meta_init = m_meta_interval = m_meta_pos = m_meta_size = m_meta_buf_pos = 0;
|
|
m_meta_buf[0] = 0;
|
|
metacb = NULL;
|
|
force_lpinfo[0] = 0;
|
|
lpinfo[0] = 0;
|
|
|
|
m_url = _strdup(url);
|
|
|
|
int use_proxy = 1;
|
|
bool proxy80 = AGAVE_API_CONFIG->GetBool(internetConfigGroupGUID, L"proxy80", false);
|
|
if (proxy80 && strstr(url, ":") && (!strstr(url, ":80/") && strstr(url, ":80") != (url + strlen(url) - 3)))
|
|
use_proxy = 0;
|
|
|
|
waServiceFactory *sf = WASABI_API_SVC->service_getServiceByGuid( httpreceiverGUID );
|
|
if (sf)
|
|
httpGetter = (api_httpreceiver *)sf->getInterface();
|
|
|
|
const wchar_t *proxy = AGAVE_API_CONFIG->GetString(internetConfigGroupGUID, L"proxy", 0);
|
|
|
|
httpGetter->open(API_DNS_AUTODNS, buffer_size, (use_proxy && proxy && proxy[0]) ? (char *)AutoChar(proxy) : NULL);
|
|
httpGetter->addheader("Accept:*/*");
|
|
if (!_strnicmp(url, "uvox://", 7))
|
|
{
|
|
httpGetter->addheader("User-Agent: ultravox/2.0");
|
|
}
|
|
else
|
|
{
|
|
httpGetter->AddHeaderValue("User-Agent", AutoChar(WASABI_API_APP->main_getVersionString()));
|
|
}
|
|
|
|
if (start_offset > 0)
|
|
{
|
|
char temp[128];
|
|
sprintf(temp, "Range: bytes=%d-", (int)start_offset);
|
|
httpGetter->addheader(temp);
|
|
}
|
|
else
|
|
httpGetter->addheader("Icy-Metadata:1");
|
|
|
|
httpGetter->connect((char *)m_url, start_offset > 0);
|
|
HttpReader::connect();
|
|
HttpReader::read(0, 0);
|
|
|
|
//if (!config_suppressstatus) api->core_setCustomMsg(0, StringPrintf("[Connecting] %s",url));
|
|
}
|
|
|
|
HttpReader::~HttpReader()
|
|
{
|
|
waServiceFactory *sf = WASABI_API_SVC->service_getServiceByGuid( httpreceiverGUID );
|
|
if ( sf )
|
|
sf->releaseInterface( httpGetter );
|
|
}
|
|
|
|
// TODO: BOOL CALLBACK HttpReader::httpDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
|
|
|
|
int HttpReader::connect()
|
|
{
|
|
try
|
|
{
|
|
while ( killswitch >= 0 && httpGetter->run() == 0 && httpGetter->get_status() == 0 )
|
|
{
|
|
#ifdef _WIN32
|
|
//Sleep( 50 );
|
|
#else
|
|
usleep( 50000 );
|
|
#endif
|
|
}
|
|
if ( killswitch )
|
|
return 0;
|
|
|
|
if ( httpGetter->get_status() == -1 )
|
|
{
|
|
int code = httpGetter->getreplycode();
|
|
if ( code == 401 )
|
|
{
|
|
/* TODO:
|
|
// authorization required
|
|
JNL_Connection *m_connection=httpGetter->get_con();
|
|
char str[4096];
|
|
while (m_connection->recv_lines_available() > 0) {
|
|
char *wwwa="WWW-Authenticate:";
|
|
m_connection->recv_line(str,4096);
|
|
if (!str[0]) break;
|
|
if (!_strnicmp(str,wwwa,strlen(wwwa))) {
|
|
char *s2="Basic realm=\"";
|
|
char *p=str+strlen(wwwa); while (p && *p== ' ') p++;
|
|
if (!_strnicmp(p,s2,strlen(s2))) {
|
|
p+=strlen(s2);
|
|
if (strstr(p,"\"")) {
|
|
strstr(p,"\"")[0]=0;
|
|
if (*p) {
|
|
if(force_lpinfo[0]) {
|
|
force_lpinfo[0]=0; // invalid l/p
|
|
} else WASABI_API_CONFIG->getStringPrivate(StringPrintf("HTTP-AUTH/%s",p),force_lpinfo,sizeof(force_lpinfo),"");
|
|
if (!force_lpinfo[0] || lpinfo[0]) {
|
|
dlg_realm = p;
|
|
api->pushModalWnd();
|
|
RootWnd *pr=api->main_getRootWnd();
|
|
while(pr->getParent()) pr=pr->getParent();
|
|
if (!DialogBoxParam(the->gethInstance(),MAKEINTRESOURCE(IDD_HTTPAUTH),pr->gethWnd(),httpDlgProc,(long)this)) {
|
|
force_lpinfo[0]=0;
|
|
} else {
|
|
WASABI_API_CONFIG->setStringPrivate(StringPrintf("HTTP-AUTH/%s",p),force_lpinfo);
|
|
}
|
|
api->popModalWnd();
|
|
}
|
|
if (force_lpinfo[0]) {
|
|
const char *p=STRSTR(m_url,"http://");
|
|
if(p) {
|
|
p+=7;
|
|
StringPrintf tmp("http://%s@%s",force_lpinfo,p);
|
|
httpGetter->connect((char *)tmp.getValue());
|
|
return connect(); // recursive city
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}*/
|
|
}
|
|
// TODO: api->core_setCustomMsg(0, StringPrintf("HTTP: can't connect (%i)",code));
|
|
return 0;
|
|
}
|
|
|
|
if ( httpGetter->getreplycode() < 200 || httpGetter->getreplycode() > 299 )
|
|
{
|
|
// TODO: api->core_setCustomMsg(0, StringPrintf("HTTP: returned %i",httpGetter->getreplycode()));
|
|
return 0;
|
|
}
|
|
|
|
need_prebuffer = 1;
|
|
}
|
|
catch ( const std::exception &e )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
|
|
return 1;
|
|
}
|
|
|
|
int HttpReader::bytesAvailable()
|
|
{
|
|
int code = httpGetter->run();
|
|
int ba = httpGetter->bytes_available();
|
|
|
|
if ( !ba && code )
|
|
return -1;
|
|
|
|
return ba;
|
|
}
|
|
|
|
int HttpReader::read(int8_t *buffer, int length)
|
|
{
|
|
if (!httpGetter->GetConnection())
|
|
return 0;
|
|
|
|
if ( httpGetter->GetConnection()->get_state() == CONNECTION_STATE_CONNECTED && httpGetter->bytes_available() < prebuffer_min )
|
|
need_prebuffer = 1;
|
|
|
|
if (need_prebuffer)
|
|
{
|
|
need_prebuffer = 0;
|
|
// TODO: if (!config_suppressstatus) api->core_setCustomMsg(0, "Prebuffering ...");
|
|
|
|
if (!guessed_prebuffer_size)
|
|
{
|
|
// wait for headers
|
|
int s;
|
|
do
|
|
{
|
|
s = httpGetter->run();
|
|
}
|
|
while (s == 0 && httpGetter->get_status() != 2);
|
|
|
|
// calculate the needed prebuffer size if it's a shoutcast stream
|
|
const char *icybr;
|
|
if (icybr = httpGetter->getheader("icy-br"))
|
|
{
|
|
prebuffer_size = (atoi(icybr) / 8) * 4096;
|
|
prebuffer_min = (atoi(icybr) / 8) * 1024;
|
|
|
|
if (prebuffer_size > buffer_size)
|
|
prebuffer_size = buffer_size;
|
|
}
|
|
|
|
guessed_prebuffer_size = 1;
|
|
}
|
|
|
|
int last_pre = -1;
|
|
while (httpGetter->bytes_available() < prebuffer_size && !killswitch)
|
|
{
|
|
int s = httpGetter->run();
|
|
// JNL_Connection::state s = getter->get_state();
|
|
// if (s == JNL_Connection::STATE_ERROR || s == JNL_Connection::STATE_CLOSED) break;
|
|
if (s == -1 || s == 1) break;
|
|
#ifdef _WIN32
|
|
Sleep(50);
|
|
#else
|
|
usleep(50000);
|
|
#endif
|
|
if (last_pre != httpGetter->bytes_available() && !killswitch)
|
|
{
|
|
// TODO: if (!config_suppressstatus) api->core_setCustomMsg(0, StringPrintf(0, "Prebuffering : %i/%i bytes",httpGetter->bytes_available(),prebuffer_size));
|
|
}
|
|
}
|
|
|
|
// if (!killswitch)
|
|
// {
|
|
//// TODO: if (!config_suppressstatus) api->core_setCustomMsg(0, "Prebuffering done.");
|
|
// }
|
|
}
|
|
|
|
if (killswitch) return 0;
|
|
|
|
// metadata filtering
|
|
if ( !m_meta_init )
|
|
{
|
|
const char *v;
|
|
if ( v = httpGetter->getheader( "icy-metaint:" ) )
|
|
m_meta_interval = atoi( v );
|
|
|
|
if ( !m_contentlength )
|
|
{
|
|
if ( v = httpGetter->getheader( "content-length:" ) )
|
|
m_contentlength = _strtoui64( v, NULL, 10 );//atoi(v);
|
|
}
|
|
|
|
v = httpGetter->getheader( "accept-ranges:" );
|
|
if ( v && strcasestr( v, "bytes" ) )
|
|
m_accept_ranges = 1;
|
|
|
|
m_meta_init = 1;
|
|
}
|
|
|
|
int error = 0, recvBytes = 0;
|
|
while (length && !error && !killswitch)
|
|
{
|
|
int code = httpGetter->run();
|
|
|
|
if (code != 0)
|
|
error = 1;
|
|
|
|
// old metadata parsing
|
|
/*if (httpGetter->bytes_available()>0) {
|
|
int l=httpGetter->get_bytes(buffer,length);
|
|
|
|
// metadata stuff
|
|
if (m_meta_interval) {
|
|
int x=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);
|
|
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;
|
|
}
|
|
|
|
length-=l;
|
|
buffer+=l;
|
|
recvBytes+=l;
|
|
} else Sleep(50);*/
|
|
|
|
while (1)
|
|
{
|
|
int len = httpGetter->bytes_available();
|
|
if (m_meta_interval && m_meta_pos >= m_meta_interval)
|
|
{
|
|
unsigned char b;
|
|
if (len > 0 && httpGetter->peek_bytes((char*)&b, 1) && len > (b << 4))
|
|
{
|
|
char metabuf[4096];
|
|
httpGetter->get_bytes(metabuf, 1);
|
|
httpGetter->get_bytes(metabuf, b << 4);
|
|
if (metacb) metacb->metaDataReader_onData(metabuf, b << 4);
|
|
//stream_metabytes_read+=(b<<4)+1;
|
|
m_meta_pos = 0;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
len = MIN(length, len);
|
|
if (m_meta_interval)
|
|
len = MIN(m_meta_interval - m_meta_pos, len);
|
|
|
|
if (len > 0)
|
|
{
|
|
len = httpGetter->get_bytes((char*)buffer, len);
|
|
m_meta_pos += len;
|
|
//stream_bytes_read+=len;
|
|
length -= len;
|
|
buffer += len;
|
|
recvBytes += len;
|
|
}
|
|
else
|
|
{
|
|
#ifdef _WIN32
|
|
Sleep(50);
|
|
#else
|
|
usleep(50000);
|
|
#endif
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* int s=httpGetter->get_con()->get_state();
|
|
if(code==0) {*/
|
|
/* char tmp[512];
|
|
wsprintf(tmp,"[Connected] Retrieving list (%i bytes)", recvBytes);
|
|
api->status_setText(tmp);*/
|
|
// } else error=1;
|
|
}
|
|
|
|
m_contentpos += recvBytes;
|
|
|
|
return recvBytes;
|
|
}
|
|
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
int HTTPReader::isMine(const wchar_t *filename, int mode)
|
|
{
|
|
if (!_wcsnicmp(filename, L"http://", 7) ||
|
|
!_wcsnicmp(filename, L"https://", 8) ||
|
|
!_wcsnicmp(filename, L"icy://", 6) ||
|
|
!_wcsnicmp(filename, L"sc://", 5) ||
|
|
!_wcsnicmp(filename, L"uvox://", 7)) return 1;
|
|
return 0;
|
|
}
|
|
|
|
int HTTPReader::open( const wchar_t *filename, int mode )
|
|
{
|
|
if ( !isMine( filename, mode ) )
|
|
return 0;
|
|
|
|
m_filename = _strdup( AutoChar( filename ) );
|
|
reader = new HttpReader( m_filename );
|
|
|
|
return 1;
|
|
}
|
|
|
|
uint64_t HTTPReader::bytesAvailable( uint64_t requested )
|
|
{
|
|
int v = reader ? reader->bytesAvailable() : 0;
|
|
if ( v > requested )
|
|
return requested;
|
|
|
|
return v;
|
|
}
|
|
|
|
size_t HTTPReader::read( int8_t *buffer, size_t length )
|
|
{
|
|
if ( !reader )
|
|
return 0;
|
|
|
|
if ( !hasConnected )
|
|
{
|
|
int res = reader->connect();
|
|
if ( !res )
|
|
return 0;
|
|
|
|
hasConnected = 1;
|
|
}
|
|
|
|
return reader->read( buffer, (int)length );
|
|
}
|
|
|
|
void HTTPReader::close()
|
|
{
|
|
delete reader;
|
|
reader = NULL;
|
|
}
|
|
|
|
void HTTPReader::abort()
|
|
{
|
|
if ( reader )
|
|
reader->abort();
|
|
}
|
|
|
|
uint64_t HTTPReader::getLength()
|
|
{
|
|
return reader ? reader->getContentLength() : -1;
|
|
}
|
|
|
|
uint64_t HTTPReader::getPos()
|
|
{
|
|
return reader ? reader->getPos() : 0;
|
|
}
|
|
|
|
int HTTPReader::canSeek()
|
|
{
|
|
return ( config_allowseek && reader && reader->canSeek() ) ? ( config_fullseek ? 1 : -1 ) : 0;
|
|
}
|
|
|
|
int HTTPReader::seek( uint64_t position )
|
|
{
|
|
if ( reader && reader->canSeek() && config_allowseek )
|
|
{
|
|
if ( position == getPos() ) return 0;
|
|
hasConnected = 0;
|
|
uint64_t cl = reader->getContentLength();
|
|
delete( (HttpReader *)reader );
|
|
reader = new HttpReader( m_filename, position, cl, 1 );
|
|
return 0;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int HTTPReader::hasHeaders()
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
const char *HTTPReader::getHeader( const char *header )
|
|
{
|
|
return reader ? reader->getHeader( header ) : NULL;
|
|
}
|
|
|
|
void HTTPReader::setMetaDataCallback( api_readercallback *cb )
|
|
{
|
|
if ( reader )
|
|
reader->setMetaCB( cb );
|
|
}
|
|
|
|
#define CBCLASS HTTPReader
|
|
START_DISPATCH;
|
|
CB(ISMINE, isMine);
|
|
CB(OPEN, open);
|
|
CB(READ, read);
|
|
CB(WRITE, write);
|
|
VCB(CLOSE, close);
|
|
VCB(ABORT, abort);
|
|
CB(GETLENGTH, getLength);
|
|
CB(GETPOS, getPos);
|
|
CB(CANSEEK, canSeek);
|
|
CB(SEEK, seek);
|
|
CB(HASHEADERS, hasHeaders);
|
|
CB(GETHEADER, getHeader);
|
|
CB(EXISTS, exists);
|
|
// CB(REMOVE,remove);
|
|
// CB(REMOVEUNDOABLE,removeUndoable);
|
|
// CB(MOVE,move);
|
|
CB(BYTESAVAILABLE, bytesAvailable);
|
|
VCB(SETMETADATACALLBACK, setMetaDataCallback);
|
|
CB(CANPREFETCH, canPrefetch);
|
|
// CB(CANSETEOF, canSetEOF);
|
|
// CB(SETEOF, setEOF);
|
|
END_DISPATCH;
|
|
#undef CBCLASS
|
|
|