Update httpstream
[vuplus_dvbapp] / lib / base / httpstream.cpp
1 #include <cstdio>
2 #include <openssl/evp.h>
3
4 #include <lib/base/httpstream.h>
5 #include <lib/base/eerror.h>
6
7 DEFINE_REF(eHttpStream);
8
9 eHttpStream::eHttpStream()
10 {
11         streamSocket = -1;
12         connectionStatus = FAILED;
13         isChunked = false;
14         currentChunkSize = 0;
15         partialPktSz = 0;
16         tmpBufSize = 32;
17         tmpBuf = (char*)malloc(tmpBufSize);
18         packetSize = 188;
19 }
20
21 eHttpStream::~eHttpStream()
22 {
23         free(tmpBuf);
24         kill(true);
25         close();
26 }
27
28 int eHttpStream::openUrl(const std::string &url, std::string &newurl)
29 {
30         int port;
31         std::string hostname;
32         std::string uri = url;
33         std::string request;
34         size_t buflen = 1024;
35         char *linebuf = NULL;
36         int result;
37         char proto[100];
38         int statuscode = 0;
39         char statusmsg[100];
40         bool playlist = false;
41         bool contenttypeparsed = false;
42
43         close();
44
45         int pathindex = uri.find("/", 7);
46         if (pathindex > 0)
47         {
48                 hostname = uri.substr(7, pathindex - 7);
49                 uri = uri.substr(pathindex, uri.length() - pathindex);
50         }
51         else
52         {
53                 hostname = uri.substr(7, uri.length() - 7);
54                 uri = "/";
55         }
56         int authenticationindex = hostname.find("@");
57         if (authenticationindex > 0)
58         {
59                 BIO *mbio, *b64bio, *bio;
60                 char *p = (char*)NULL;
61                 int length = 0;
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());
68                 BIO_flush(bio);
69                 length = BIO_ctrl(mbio, BIO_CTRL_INFO, 0, (char*)&p);
70                 authorizationData = "";
71                 if (p && length > 0)
72                 {
73                         /* base64 output contains a linefeed, which we ignore */
74                         authorizationData.append(p, length - 1);
75                 }
76                 BIO_free_all(bio);
77         }
78         int customportindex = hostname.find(":");
79         if (customportindex > 0)
80         {
81                 port = atoi(hostname.substr(customportindex + 1, hostname.length() - customportindex - 1).c_str());
82                 hostname = hostname.substr(0, customportindex);
83         }
84         else if (customportindex == 0)
85         {
86                 port = atoi(hostname.substr(1, hostname.length() - 1).c_str());
87                 hostname = "localhost";
88         }
89         else
90         {
91                 port = 80;
92         }
93         streamSocket = connect(hostname.c_str(), port, 10);
94         if (streamSocket < 0)
95                 goto error;
96
97         request = "GET ";
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 != "")
102         {
103                 request.append("Authorization: Basic ").append(authorizationData).append("\r\n");
104         }
105
106         request.append("Accept: */*\r\n");
107         request.append("Connection: close\r\n");
108         request.append("\r\n");
109
110         writeAll(streamSocket, request.c_str(), request.length());
111
112         linebuf = (char*)malloc(buflen);
113
114         result = readLine(streamSocket, &linebuf, &buflen);
115         if (result <= 0)
116                 goto error;
117
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))
121         {
122                 eDebug("%s: wrong http response code: %d", __FUNCTION__, statuscode);
123                 goto error;
124         }
125
126         while (1)
127         {
128                 result = readLine(streamSocket, &linebuf, &buflen);
129                 if (!contenttypeparsed)
130                 {
131                         char contenttype[33];
132                         if (sscanf(linebuf, "Content-Type: %32s", contenttype) == 1)
133                         {
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"))
139                                 {
140                                         /* assume we'll get a playlist, some text file containing a stream url */
141                                         playlist = true;
142                                 }
143                                 continue;
144                         }
145                 }
146
147                 if (playlist && !strncasecmp(linebuf, "http://", 7))
148                 {
149                         newurl = linebuf;
150                         eDebug("%s: playlist entry: %s", __FUNCTION__, newurl.c_str());
151                         break;
152                 }
153
154                 if (((statuscode == 301) || (statuscode == 302) || (statuscode == 303) || (statuscode == 307) || (statuscode == 308)) &&
155                                 strncasecmp(linebuf, "location: ", 10) == 0)
156                 {
157                         newurl = &linebuf[10];
158                         eDebug("%s: redirecting to: %s", __FUNCTION__, newurl.c_str());
159                         break;
160                 }
161
162                 if (((statuscode == 200) || (statuscode == 206)) && !strncasecmp(linebuf, "transfer-encoding: chunked", strlen("transfer-encoding: chunked")))
163                 {
164                         isChunked = true;
165                 }
166
167                 if (!playlist && result == 0)
168                         break;
169
170                 if (result < 0)
171                         break;
172         }
173
174         free(linebuf);
175         return 0;
176 error:
177         eDebug("%s failed", __FUNCTION__);
178         free(linebuf);
179         close();
180         return -1;
181 }
182
183 int eHttpStream::open(const char *url)
184 {
185         streamUrl = url;
186         /*
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.
190          */
191         connectionStatus = BUSY;
192         eDebug("eHttpStream::Start thread");
193         run();
194         return 0;
195 }
196
197 void eHttpStream::thread()
198 {
199         hasStarted();
200         std::string currenturl, newurl;
201         currenturl = streamUrl;
202         for (unsigned int i = 0; i < 5; i++)
203         {
204                 if (openUrl(currenturl, newurl) < 0)
205                 {
206                         /* connection failed */
207                         eDebug("eHttpStream::Thread end NO connection");
208                         connectionStatus = FAILED;
209                         return;
210                 }
211                 if (newurl == "")
212                 {
213                         /* we have a valid stream connection */
214                         eDebug("eHttpStream::Thread end connection");
215                         connectionStatus = CONNECTED;
216                         return;
217                 }
218                 /* switch to new url */
219                 close();
220                 currenturl = newurl;
221                 newurl = "";
222         }
223         /* too many redirect / playlist levels */
224         eDebug("eHttpStream::Thread end NO connection");
225         connectionStatus = FAILED;
226         return;
227 }
228
229
230 off_t eHttpStream::lseek(off_t offset, int whence)
231 {
232         return (off_t)-1;
233 }
234
235 int eHttpStream::close()
236 {
237         int retval = -1;
238         if (streamSocket >= 0)
239         {
240                 retval = ::close(streamSocket);
241                 streamSocket = -1;
242         }
243         return retval;
244 }
245
246 ssize_t eHttpStream::syncNextRead(void *buf, ssize_t length)
247 {
248         unsigned char *b = (unsigned char*)buf;
249         unsigned char *e = b + length;
250         partialPktSz = 0;
251
252         if (*(char*)buf != 0x47)
253         {
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--;
258         }
259         else
260         {
261                 // the current read is aligned
262                 // get the last incomplete packet position
263                 e -= length % packetSize;
264         }
265
266         if (e != b && e != (b + length))
267         {
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)
271                 {
272                         memcpy(partialPkt, e, partialPktSz);
273                 }
274         }
275         return (length - partialPktSz);
276 }
277
278 ssize_t eHttpStream::httpChunkedRead(void *buf, size_t count)
279 {
280         ssize_t ret = -1;
281         size_t total_read = partialPktSz;
282
283         // write partial packet from the previous read
284         if (partialPktSz > 0)
285         {
286                 memcpy(buf, partialPkt, partialPktSz);
287                 partialPktSz = 0;
288         }
289
290         if (!isChunked)
291         {
292                 ret = timedRead(streamSocket,((char*)buf) + total_read , count - total_read, 5000, 100);
293                 if (ret > 0)
294                 {
295                         ret += total_read;
296                         ret = syncNextRead(buf, ret);
297                 }
298         }
299         else
300         {
301                 while (total_read < count)
302                 {
303                         if (0 == currentChunkSize)
304                         {
305                                 do
306                                 {
307                                         ret = readLine(streamSocket, &tmpBuf, &tmpBufSize);
308                                         if (ret < 0) return -1;
309                                 } while (!*tmpBuf && ret > 0); /* skip CR LF from last chunk */
310                                 if (ret == 0)
311                                         break;
312                                 currentChunkSize = strtol(tmpBuf, NULL, 16);
313                                 if (currentChunkSize == 0) return -1;
314                         }
315
316                         size_t to_read = count - total_read;
317                         if (currentChunkSize < to_read)
318                                 to_read = currentChunkSize;
319
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);
322                         if (ret <= 0)
323                                 break;
324                         currentChunkSize -= ret;
325                         total_read += ret;
326                 }
327                 if (total_read > 0)
328                 {
329                         ret = syncNextRead(buf, total_read);
330                 }
331         }
332         return ret;
333 }
334
335 ssize_t eHttpStream::read(off_t offset, void *buf, size_t count)
336 {
337         if (connectionStatus == BUSY)
338                 return 0;
339         else if (connectionStatus == FAILED)
340                 return -1;
341         return httpChunkedRead(buf, count);
342 }
343
344 int eHttpStream::valid()
345 {
346         if (connectionStatus == BUSY)
347                 return 0;
348         return streamSocket >= 0;
349 }
350
351 off_t eHttpStream::length()
352 {
353         return (off_t)-1;
354 }