aboutsummaryrefslogtreecommitdiff
path: root/Src/filereader/HTTPReader.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Src/filereader/HTTPReader.cpp')
-rw-r--r--Src/filereader/HTTPReader.cpp630
1 files changed, 630 insertions, 0 deletions
diff --git a/Src/filereader/HTTPReader.cpp b/Src/filereader/HTTPReader.cpp
new file mode 100644
index 00000000..12e0db7d
--- /dev/null
+++ b/Src/filereader/HTTPReader.cpp
@@ -0,0 +1,630 @@
+#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
+