2 #include <openssl/evp.h>
4 #include <lib/base/httpstream.h>
5 #include <lib/base/eerror.h>
7 DEFINE_REF(eHttpStream);
9 eHttpStream::eHttpStream()
12 connectionStatus = FAILED;
17 tmpBuf = (char*)malloc(tmpBufSize);
21 eHttpStream::~eHttpStream()
28 int eHttpStream::openUrl(const std::string &url, std::string &newurl)
32 std::string uri = url;
40 bool playlist = false;
41 bool contenttypeparsed = false;
45 int pathindex = uri.find("/", 7);
48 hostname = uri.substr(7, pathindex - 7);
49 uri = uri.substr(pathindex, uri.length() - pathindex);
53 hostname = uri.substr(7, uri.length() - 7);
56 int authenticationindex = hostname.find("@");
57 if (authenticationindex > 0)
59 BIO *mbio, *b64bio, *bio;
60 char *p = (char*)NULL;
62 authorizationData = hostname.substr(0, authenticationindex);
63 hostname = hostname.substr(authenticationindex + 1);
64 mbio = BIO_new(BIO_s_mem());
65 b64bio = BIO_new(BIO_f_base64());
66 bio = BIO_push(b64bio, mbio);
67 BIO_write(bio, authorizationData.c_str(), authorizationData.length());
69 length = BIO_ctrl(mbio, BIO_CTRL_INFO, 0, (char*)&p);
70 authorizationData = "";
73 /* base64 output contains a linefeed, which we ignore */
74 authorizationData.append(p, length - 1);
78 int customportindex = hostname.find(":");
79 if (customportindex > 0)
81 port = atoi(hostname.substr(customportindex + 1, hostname.length() - customportindex - 1).c_str());
82 hostname = hostname.substr(0, customportindex);
84 else if (customportindex == 0)
86 port = atoi(hostname.substr(1, hostname.length() - 1).c_str());
87 hostname = "localhost";
93 streamSocket = connect(hostname.c_str(), port, 10);
98 request.append(uri).append(" HTTP/1.1\r\n");
99 request.append("Host: ").append(hostname).append("\r\n");
100 request.append("User-Agent: ").append("Enigma2").append("\r\n");
101 if (authorizationData != "")
103 request.append("Authorization: Basic ").append(authorizationData).append("\r\n");
106 request.append("Accept: */*\r\n");
107 request.append("Connection: close\r\n");
108 request.append("\r\n");
110 writeAll(streamSocket, request.c_str(), request.length());
112 linebuf = (char*)malloc(buflen);
114 result = readLine(streamSocket, &linebuf, &buflen);
118 result = sscanf(linebuf, "%99s %d %99s", proto, &statuscode, statusmsg);
119 if (result != 3 || (statuscode != 200 && statuscode != 206 && statuscode != 302 &&
120 statuscode != 301 && statuscode != 303 && statuscode != 307 && statuscode != 308))
122 eDebug("%s: wrong http response code: %d", __FUNCTION__, statuscode);
128 result = readLine(streamSocket, &linebuf, &buflen);
129 if (!contenttypeparsed)
131 char contenttype[33];
132 if (sscanf(linebuf, "Content-Type: %32s", contenttype) == 1)
134 contenttypeparsed = true;
135 if (!strcasecmp(contenttype, "application/text")
136 || !strcasecmp(contenttype, "audio/x-mpegurl")
137 || !strcasecmp(contenttype, "audio/mpegurl")
138 || !strcasecmp(contenttype, "application/m3u"))
140 /* assume we'll get a playlist, some text file containing a stream url */
147 if (playlist && !strncasecmp(linebuf, "http://", 7))
150 eDebug("%s: playlist entry: %s", __FUNCTION__, newurl.c_str());
154 if (((statuscode == 301) || (statuscode == 302) || (statuscode == 303) || (statuscode == 307) || (statuscode == 308)) &&
155 strncasecmp(linebuf, "location: ", 10) == 0)
157 newurl = &linebuf[10];
158 eDebug("%s: redirecting to: %s", __FUNCTION__, newurl.c_str());
162 if (((statuscode == 200) || (statuscode == 206)) && !strncasecmp(linebuf, "transfer-encoding: chunked", strlen("transfer-encoding: chunked")))
167 if (!playlist && result == 0)
177 eDebug("%s failed", __FUNCTION__);
183 int eHttpStream::open(const char *url)
187 * We're in gui thread context here, and establishing
188 * a connection might block for up to 10 seconds.
189 * Spawn a new thread to establish the connection.
191 connectionStatus = BUSY;
192 eDebug("eHttpStream::Start thread");
197 void eHttpStream::thread()
200 std::string currenturl, newurl;
201 currenturl = streamUrl;
202 for (unsigned int i = 0; i < 5; i++)
204 if (openUrl(currenturl, newurl) < 0)
206 /* connection failed */
207 eDebug("eHttpStream::Thread end NO connection");
208 connectionStatus = FAILED;
213 /* we have a valid stream connection */
214 eDebug("eHttpStream::Thread end connection");
215 connectionStatus = CONNECTED;
218 /* switch to new url */
223 /* too many redirect / playlist levels */
224 eDebug("eHttpStream::Thread end NO connection");
225 connectionStatus = FAILED;
230 off_t eHttpStream::lseek(off_t offset, int whence)
235 int eHttpStream::close()
238 if (streamSocket >= 0)
240 retval = ::close(streamSocket);
246 ssize_t eHttpStream::syncNextRead(void *buf, ssize_t length)
248 unsigned char *b = (unsigned char*)buf;
249 unsigned char *e = b + length;
252 if (*(char*)buf != 0x47)
254 // the current read is not aligned
255 // get the head position of the last packet
256 // so we'll try to align the next read
257 while (e != b && *e != 0x47) e--;
261 // the current read is aligned
262 // get the last incomplete packet position
263 e -= length % packetSize;
266 if (e != b && e != (b + length))
268 partialPktSz = (b + length) - e;
269 // if the last packet is read partially save it to align the next read
270 if (partialPktSz > 0 && partialPktSz < packetSize)
272 memcpy(partialPkt, e, partialPktSz);
275 return (length - partialPktSz);
278 ssize_t eHttpStream::httpChunkedRead(void *buf, size_t count)
281 size_t total_read = partialPktSz;
283 // write partial packet from the previous read
284 if (partialPktSz > 0)
286 memcpy(buf, partialPkt, partialPktSz);
292 ret = timedRead(streamSocket,((char*)buf) + total_read , count - total_read, 5000, 100);
296 ret = syncNextRead(buf, ret);
301 while (total_read < count)
303 if (0 == currentChunkSize)
307 ret = readLine(streamSocket, &tmpBuf, &tmpBufSize);
308 if (ret < 0) return -1;
309 } while (!*tmpBuf && ret > 0); /* skip CR LF from last chunk */
312 currentChunkSize = strtol(tmpBuf, NULL, 16);
313 if (currentChunkSize == 0) return -1;
316 size_t to_read = count - total_read;
317 if (currentChunkSize < to_read)
318 to_read = currentChunkSize;
320 // do not wait too long if we have something in the buffer already
321 ret = timedRead(streamSocket, ((char*)buf) + total_read, to_read, ((total_read)? 100 : 5000), 100);
324 currentChunkSize -= ret;
329 ret = syncNextRead(buf, total_read);
335 ssize_t eHttpStream::read(off_t offset, void *buf, size_t count)
337 if (connectionStatus == BUSY)
339 else if (connectionStatus == FAILED)
341 return httpChunkedRead(buf, count);
344 int eHttpStream::valid()
346 if (connectionStatus == BUSY)
348 return streamSocket >= 0;
351 off_t eHttpStream::length()