Update httpstream
[vuplus_dvbapp] / lib / base / httpstream.cpp
index 343109e..8f55425 100644 (file)
@@ -1,4 +1,5 @@
 #include <cstdio>
+#include <openssl/evp.h>
 
 #include <lib/base/httpstream.h>
 #include <lib/base/eerror.h>
@@ -8,14 +9,23 @@ DEFINE_REF(eHttpStream);
 eHttpStream::eHttpStream()
 {
        streamSocket = -1;
+       connectionStatus = FAILED;
+       isChunked = false;
+       currentChunkSize = 0;
+       partialPktSz = 0;
+       tmpBufSize = 32;
+       tmpBuf = (char*)malloc(tmpBufSize);
+       packetSize = 188;
 }
 
 eHttpStream::~eHttpStream()
 {
+       free(tmpBuf);
+       kill(true);
        close();
 }
 
-int eHttpStream::open(const char *url)
+int eHttpStream::openUrl(const std::string &url, std::string &newurl)
 {
        int port;
        std::string hostname;
@@ -27,27 +37,51 @@ int eHttpStream::open(const char *url)
        char proto[100];
        int statuscode = 0;
        char statusmsg[100];
+       bool playlist = false;
+       bool contenttypeparsed = false;
 
        close();
 
        int pathindex = uri.find("/", 7);
-       if (pathindex > 0) 
+       if (pathindex > 0)
        {
                hostname = uri.substr(7, pathindex - 7);
                uri = uri.substr(pathindex, uri.length() - pathindex);
-       } 
-       else 
+       }
+       else
        {
                hostname = uri.substr(7, uri.length() - 7);
-               uri = "";
+               uri = "/";
+       }
+       int authenticationindex = hostname.find("@");
+       if (authenticationindex > 0)
+       {
+               BIO *mbio, *b64bio, *bio;
+               char *p = (char*)NULL;
+               int length = 0;
+               authorizationData = hostname.substr(0, authenticationindex);
+               hostname = hostname.substr(authenticationindex + 1);
+               mbio = BIO_new(BIO_s_mem());
+               b64bio = BIO_new(BIO_f_base64());
+               bio = BIO_push(b64bio, mbio);
+               BIO_write(bio, authorizationData.c_str(), authorizationData.length());
+               BIO_flush(bio);
+               length = BIO_ctrl(mbio, BIO_CTRL_INFO, 0, (char*)&p);
+               authorizationData = "";
+               if (p && length > 0)
+               {
+                       /* base64 output contains a linefeed, which we ignore */
+                       authorizationData.append(p, length - 1);
+               }
+               BIO_free_all(bio);
        }
        int customportindex = hostname.find(":");
-       if (customportindex > 0) 
+       if (customportindex > 0)
        {
                port = atoi(hostname.substr(customportindex + 1, hostname.length() - customportindex - 1).c_str());
                hostname = hostname.substr(0, customportindex);
-       } 
-       else if (customportindex == 0) 
+       }
+       else if (customportindex == 0)
        {
                port = atoi(hostname.substr(1, hostname.length() - 1).c_str());
                hostname = "localhost";
@@ -57,41 +91,142 @@ int eHttpStream::open(const char *url)
                port = 80;
        }
        streamSocket = connect(hostname.c_str(), port, 10);
-       if (streamSocket < 0) goto error;
+       if (streamSocket < 0)
+               goto error;
 
        request = "GET ";
        request.append(uri).append(" HTTP/1.1\r\n");
        request.append("Host: ").append(hostname).append("\r\n");
+       request.append("User-Agent: ").append("Enigma2").append("\r\n");
+       if (authorizationData != "")
+       {
+               request.append("Authorization: Basic ").append(authorizationData).append("\r\n");
+       }
+
        request.append("Accept: */*\r\n");
        request.append("Connection: close\r\n");
        request.append("\r\n");
+
        writeAll(streamSocket, request.c_str(), request.length());
 
        linebuf = (char*)malloc(buflen);
 
        result = readLine(streamSocket, &linebuf, &buflen);
-       if (result <= 0) goto error;
+       if (result <= 0)
+               goto error;
 
        result = sscanf(linebuf, "%99s %d %99s", proto, &statuscode, statusmsg);
-       if (result != 3 || statuscode != 200) 
+       if (result != 3 || (statuscode != 200 && statuscode != 206 && statuscode != 302 && 
+                       statuscode != 301 && statuscode != 303 && statuscode != 307 && statuscode != 308))
        {
-               eDebug("eHttpStream::open: wrong http response code: %d", statuscode);
+               eDebug("%s: wrong http response code: %d", __FUNCTION__, statuscode);
                goto error;
        }
-       while (result > 0)
+
+       while (1)
        {
                result = readLine(streamSocket, &linebuf, &buflen);
+               if (!contenttypeparsed)
+               {
+                       char contenttype[33];
+                       if (sscanf(linebuf, "Content-Type: %32s", contenttype) == 1)
+                       {
+                               contenttypeparsed = true;
+                               if (!strcasecmp(contenttype, "application/text")
+                               || !strcasecmp(contenttype, "audio/x-mpegurl")
+                               || !strcasecmp(contenttype, "audio/mpegurl")
+                               || !strcasecmp(contenttype, "application/m3u"))
+                               {
+                                       /* assume we'll get a playlist, some text file containing a stream url */
+                                       playlist = true;
+                               }
+                               continue;
+                       }
+               }
+
+               if (playlist && !strncasecmp(linebuf, "http://", 7))
+               {
+                       newurl = linebuf;
+                       eDebug("%s: playlist entry: %s", __FUNCTION__, newurl.c_str());
+                       break;
+               }
+
+               if (((statuscode == 301) || (statuscode == 302) || (statuscode == 303) || (statuscode == 307) || (statuscode == 308)) &&
+                               strncasecmp(linebuf, "location: ", 10) == 0)
+               {
+                       newurl = &linebuf[10];
+                       eDebug("%s: redirecting to: %s", __FUNCTION__, newurl.c_str());
+                       break;
+               }
+
+               if (((statuscode == 200) || (statuscode == 206)) && !strncasecmp(linebuf, "transfer-encoding: chunked", strlen("transfer-encoding: chunked")))
+               {
+                       isChunked = true;
+               }
+
+               if (!playlist && result == 0)
+                       break;
+
+               if (result < 0)
+                       break;
        }
 
        free(linebuf);
        return 0;
 error:
-       eDebug("eHttpStream::open failed");
+       eDebug("%s failed", __FUNCTION__);
        free(linebuf);
        close();
        return -1;
 }
 
+int eHttpStream::open(const char *url)
+{
+       streamUrl = url;
+       /*
+        * We're in gui thread context here, and establishing
+        * a connection might block for up to 10 seconds.
+        * Spawn a new thread to establish the connection.
+        */
+       connectionStatus = BUSY;
+       eDebug("eHttpStream::Start thread");
+       run();
+       return 0;
+}
+
+void eHttpStream::thread()
+{
+       hasStarted();
+       std::string currenturl, newurl;
+       currenturl = streamUrl;
+       for (unsigned int i = 0; i < 5; i++)
+       {
+               if (openUrl(currenturl, newurl) < 0)
+               {
+                       /* connection failed */
+                       eDebug("eHttpStream::Thread end NO connection");
+                       connectionStatus = FAILED;
+                       return;
+               }
+               if (newurl == "")
+               {
+                       /* we have a valid stream connection */
+                       eDebug("eHttpStream::Thread end connection");
+                       connectionStatus = CONNECTED;
+                       return;
+               }
+               /* switch to new url */
+               close();
+               currenturl = newurl;
+               newurl = "";
+       }
+       /* too many redirect / playlist levels */
+       eDebug("eHttpStream::Thread end NO connection");
+       connectionStatus = FAILED;
+       return;
+}
+
+
 off_t eHttpStream::lseek(off_t offset, int whence)
 {
        return (off_t)-1;
@@ -108,13 +243,108 @@ int eHttpStream::close()
        return retval;
 }
 
+ssize_t eHttpStream::syncNextRead(void *buf, ssize_t length)
+{
+       unsigned char *b = (unsigned char*)buf;
+       unsigned char *e = b + length;
+       partialPktSz = 0;
+
+       if (*(char*)buf != 0x47)
+       {
+               // the current read is not aligned
+               // get the head position of the last packet
+               // so we'll try to align the next read
+               while (e != b && *e != 0x47) e--;
+       }
+       else
+       {
+               // the current read is aligned
+               // get the last incomplete packet position
+               e -= length % packetSize;
+       }
+
+       if (e != b && e != (b + length))
+       {
+               partialPktSz = (b + length) - e;
+               // if the last packet is read partially save it to align the next read
+               if (partialPktSz > 0 && partialPktSz < packetSize)
+               {
+                       memcpy(partialPkt, e, partialPktSz);
+               }
+       }
+       return (length - partialPktSz);
+}
+
+ssize_t eHttpStream::httpChunkedRead(void *buf, size_t count)
+{
+       ssize_t ret = -1;
+       size_t total_read = partialPktSz;
+
+       // write partial packet from the previous read
+       if (partialPktSz > 0)
+       {
+               memcpy(buf, partialPkt, partialPktSz);
+               partialPktSz = 0;
+       }
+
+       if (!isChunked)
+       {
+               ret = timedRead(streamSocket,((char*)buf) + total_read , count - total_read, 5000, 100);
+               if (ret > 0)
+               {
+                       ret += total_read;
+                       ret = syncNextRead(buf, ret);
+               }
+       }
+       else
+       {
+               while (total_read < count)
+               {
+                       if (0 == currentChunkSize)
+                       {
+                               do
+                               {
+                                       ret = readLine(streamSocket, &tmpBuf, &tmpBufSize);
+                                       if (ret < 0) return -1;
+                               } while (!*tmpBuf && ret > 0); /* skip CR LF from last chunk */
+                               if (ret == 0)
+                                       break;
+                               currentChunkSize = strtol(tmpBuf, NULL, 16);
+                               if (currentChunkSize == 0) return -1;
+                       }
+
+                       size_t to_read = count - total_read;
+                       if (currentChunkSize < to_read)
+                               to_read = currentChunkSize;
+
+                       // do not wait too long if we have something in the buffer already
+                       ret = timedRead(streamSocket, ((char*)buf) + total_read, to_read, ((total_read)? 100 : 5000), 100);
+                       if (ret <= 0)
+                               break;
+                       currentChunkSize -= ret;
+                       total_read += ret;
+               }
+               if (total_read > 0)
+               {
+                       ret = syncNextRead(buf, total_read);
+               }
+       }
+       return ret;
+}
+
 ssize_t eHttpStream::read(off_t offset, void *buf, size_t count)
 {
-       return timedRead(streamSocket, buf, count, 5000, 500);
+       if (connectionStatus == BUSY)
+               return 0;
+       else if (connectionStatus == FAILED)
+               return -1;
+       return httpChunkedRead(buf, count);
 }
 
 int eHttpStream::valid()
 {
+       if (connectionStatus == BUSY)
+               return 0;
        return streamSocket >= 0;
 }