1 /*
2  *  tvheadend, WEBUI / HTML user interface
3  *  Copyright (C) 2008 Andreas Öman
4  *  Copyright (C) 2014,2015 Jaroslav Kysela
5  *
6  *  This program is free software: you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation, either version 3 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #define _GNU_SOURCE /* for splice() */
21 #include <fcntl.h>
22 
23 #include <pthread.h>
24 #include <assert.h>
25 #include <stdio.h>
26 #include <unistd.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <assert.h>
30 #include <arpa/inet.h>
31 
32 #include <sys/stat.h>
33 
34 #include "tvheadend.h"
35 #include "config.h"
36 #include "http.h"
37 #include "tcp.h"
38 #include "webui.h"
39 #include "dvr/dvr.h"
40 #include "filebundle.h"
41 #include "streaming.h"
42 #include "imagecache.h"
43 #include "lang_codes.h"
44 #include "intlconv.h"
45 #if ENABLE_MPEGTS
46 #include "input.h"
47 #endif
48 #if ENABLE_SATIP_SERVER
49 #include "satip/server.h"
50 #endif
51 
52 #if defined(PLATFORM_LINUX)
53 #include <sys/sendfile.h>
54 #elif defined(PLATFORM_FREEBSD)
55 #include <sys/types.h>
56 #include <sys/socket.h>
57 #include <sys/uio.h>
58 #endif
59 
60 #if ENABLE_ANDROID
61 #include <sys/socket.h>
62 #endif
63 
64 typedef int (sortfcn_t)(const void *, const void *);
65 
66 enum {
67   PLAYLIST_M3U,
68   PLAYLIST_E2,
69   PLAYLIST_SATIP_M3U\
70 };
71 
72 static int webui_xspf;
73 
74 /*
75  *
76  */
77 static int
http_channel_playlist_cmp(const void * a,const void * b)78 http_channel_playlist_cmp(const void *a, const void *b)
79 {
80   int r;
81   channel_t *c1 = *(channel_t **)a, *c2 = *(channel_t **)b;
82   int64_t n1 = channel_get_number(c1), n2 = channel_get_number(c2);
83   if (n1 > n2)
84     r = 1;
85   else if (n1 < n2)
86     r = -1;
87   else
88     r = strcasecmp(channel_get_name(c1), channel_get_name(c2));
89   return r;
90 }
91 
92 static int
http_channel_playlist_cmp2(const void * a,const void * b)93 http_channel_playlist_cmp2(const void *a, const void *b)
94 {
95   channel_t *c1 = *(channel_t **)a, *c2 = *(channel_t **)b;
96   return strcasecmp(channel_get_name(c1), channel_get_name(c2));
97 }
98 
http_channel_playlist_sfcn(http_connection_t * hc)99 static sortfcn_t *http_channel_playlist_sfcn(http_connection_t *hc)
100 {
101   const char *sorttype = http_arg_get(&hc->hc_req_args, "sort");
102   if (sorttype) {
103     if (!strcmp(sorttype, "numname"))
104       return &http_channel_playlist_cmp;
105     if (!strcmp(sorttype, "name"))
106       return &http_channel_playlist_cmp2;
107   }
108   return &http_channel_playlist_cmp;
109 }
110 
111 /*
112  *
113  */
114 static int
http_channel_tag_playlist_cmp(const void * a,const void * b)115 http_channel_tag_playlist_cmp(const void *a, const void *b)
116 {
117   channel_tag_t *ct1 = *(channel_tag_t **)a, *ct2 = *(channel_tag_t **)b;
118   int r = ct1->ct_index - ct2->ct_index;
119   if (r == 0)
120     r = strcasecmp(ct1->ct_name, ct2->ct_name);
121   return r;
122 }
123 
124 static int
http_channel_tag_playlist_cmp2(const void * a,const void * b)125 http_channel_tag_playlist_cmp2(const void *a, const void *b)
126 {
127   channel_tag_t *ct1 = *(channel_tag_t **)a, *ct2 = *(channel_tag_t **)b;
128   return strcasecmp(ct1->ct_name, ct2->ct_name);
129 }
130 
http_channel_tag_playlist_sfcn(http_connection_t * hc)131 static sortfcn_t *http_channel_tag_playlist_sfcn(http_connection_t *hc)
132 {
133   const char *sorttype = http_arg_get(&hc->hc_req_args, "sort");
134   if (sorttype) {
135     if (!strcmp(sorttype, "idxname"))
136       return &http_channel_tag_playlist_cmp;
137     if (!strcmp(sorttype, "name"))
138       return &http_channel_tag_playlist_cmp2;
139   }
140   return &http_channel_tag_playlist_cmp;
141 }
142 
143 /**
144  *
145  */
146 static int
is_client_simple(http_connection_t * hc)147 is_client_simple(http_connection_t *hc)
148 {
149   char *c;
150 
151   if((c = http_arg_get(&hc->hc_args, "UA-OS")) != NULL) {
152     if(strstr(c, "Windows CE") || strstr(c, "Pocket PC"))
153       return 1;
154   }
155 
156   if((c = http_arg_get(&hc->hc_args, "x-wap-profile")) != NULL) {
157     return 1;
158   }
159   return 0;
160 }
161 
162 /**
163  * Root page, we direct the client to different pages depending
164  * on if it is a full blown browser or just some mobile app
165  */
166 static int
page_root(http_connection_t * hc,const char * remain,void * opaque)167 page_root(http_connection_t *hc, const char *remain, void *opaque)
168 {
169   if(is_client_simple(hc)) {
170     http_redirect(hc, "simple.html", &hc->hc_req_args, 0);
171   } else {
172     http_redirect(hc, "extjs.html", &hc->hc_req_args, 0);
173   }
174   return 0;
175 }
176 
177 static int
page_root2(http_connection_t * hc,const char * remain,void * opaque)178 page_root2(http_connection_t *hc, const char *remain, void *opaque)
179 {
180   if (!tvheadend_webroot) return HTTP_STATUS_NOT_FOUND;
181   http_redirect(hc, "/", &hc->hc_req_args, 0);
182   return 0;
183 }
184 
185 static int
page_no_webroot(http_connection_t * hc,const char * remain,void * opaque)186 page_no_webroot(http_connection_t *hc, const char *remain, void *opaque)
187 {
188   size_t l;
189   char *s;
190 
191   /* not found checks */
192   if (!tvheadend_webroot)
193     return HTTP_STATUS_NOT_FOUND;
194   l = strlen(tvheadend_webroot);
195   if (strncmp(hc->hc_url, tvheadend_webroot, l) == 0 &&
196       hc->hc_url[l] == '/')
197     return HTTP_STATUS_NOT_FOUND;
198 
199   /* redirect if url is not in the specified webroot */
200   if (!remain)
201     remain = "";
202   s = alloca(2 + strlen(remain));
203   s[0] = '/';
204   strcpy(s + 1, remain);
205   http_redirect(hc, s, &hc->hc_req_args, 0);
206   return 0;
207 }
208 
209 static int
page_login(http_connection_t * hc,const char * remain,void * opaque)210 page_login(http_connection_t *hc, const char *remain, void *opaque)
211 {
212   if (hc->hc_access != NULL &&
213       hc->hc_access->aa_username != NULL &&
214       *hc->hc_access->aa_username != '\0') {
215     http_redirect(hc, "/", &hc->hc_req_args, 0);
216     return 0;
217   } else {
218     return HTTP_STATUS_UNAUTHORIZED;
219   }
220 }
221 
222 static int
page_logout(http_connection_t * hc,const char * remain,void * opaque)223 page_logout(http_connection_t *hc, const char *remain, void *opaque)
224 {
225   const char *username, *busername, *lang, *title, *text, *logout;
226   char url[512];
227 
228   username = hc->hc_access ? hc->hc_access->aa_username : NULL;
229   busername = hc->hc_username ? hc->hc_username : NULL;
230 
231   tvhtrace(LS_HTTP, "logout: username '%s', busername '%s'\n", username, busername);
232 
233   if (http_arg_get(&hc->hc_req_args, "_logout"))
234     return HTTP_STATUS_UNAUTHORIZED;
235 
236   if (!http_arg_get(&hc->hc_args, "Authorization"))
237     return HTTP_STATUS_UNAUTHORIZED;
238 
239   lang = tvh_gettext_get_lang(hc->hc_access ? hc->hc_access->aa_lang_ui : NULL);
240   title = tvh_gettext_lang(lang, N_("Logout"));
241   htsbuf_qprintf(&hc->hc_reply,
242                  "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n"
243                  "<HTML><HEAD>\r\n"
244                  "<TITLE>%s</TITLE>\r\n"
245                  "</HEAD><BODY>\r\n"
246                  "<H1>%s</H1>\r\n"
247                  "<P>",
248                  title, title);
249 
250   text = tvh_gettext_lang(lang, N_("Authenticated user"));
251   htsbuf_qprintf(&hc->hc_reply, "<P>%s: %s</P>\r\n", text, username ?: "---");
252 
253   text = tvh_gettext_lang(lang, N_("\
254 Please, follow %s link and cancel the next authorization to correctly clear \
255 the cached browser credentals (login and password cache). Then click to \
256 the 'Default login' (anonymous access) or 'New login' link in the error page \
257 to reauthenticate."));
258   logout = tvh_gettext_lang(lang, N_("logout"));
259 
260   snprintf(url, sizeof(url), "<A HREF=\"%s/logout?_logout=1\">%s</A>",
261                              tvheadend_webroot ? tvheadend_webroot : "", logout);
262 
263   htsbuf_qprintf(&hc->hc_reply, text, url);
264 
265   snprintf(url, sizeof(url), "<A HREF=\"%s/logout?_logout=1\" "
266                              "STYLE=\"border: 1px solid; border-radius: 4px; padding: .6em\">%s</A>",
267                              tvheadend_webroot ? tvheadend_webroot : "", logout);
268 
269   text = tvh_gettext_lang(lang, N_("return"));
270 
271   htsbuf_qprintf(&hc->hc_reply, "</P>\r\n"
272                                 "<P STYLE=\"text-align: center; margin: 2em\">%s</P>\r\n"
273                                 "<P STYLE=\"text-align: center; margin: 2em\"><A HREF=\"%s/\" STYLE=\"border: 1px solid; border-radius: 4px; padding: .6em\">%s</A></P>\r\n"
274                                 "</BODY></HTML>\r\n",
275                                 url, tvheadend_webroot ? tvheadend_webroot : "", text);
276   http_output_html(hc);
277   return 0;
278 }
279 
280 /**
281  * Static download of a file from the filesystem
282  */
283 int
page_static_file(http_connection_t * hc,const char * _remain,void * opaque)284 page_static_file(http_connection_t *hc, const char *_remain, void *opaque)
285 {
286   int ret = 0;
287   const char *base = opaque;
288   char *remain, *postfix;
289   char path[500];
290   ssize_t size;
291   const char *content = NULL;
292   char buf[4096];
293   const char *gzip = NULL;
294   int nogzip = 0;
295 
296   if(_remain == NULL)
297     return HTTP_STATUS_NOT_FOUND;
298 
299   if(strstr(_remain, ".."))
300     return HTTP_STATUS_BAD_REQUEST;
301 
302   snprintf(path, sizeof(path), "%s/%s", base, _remain);
303 
304   remain = tvh_strdupa(_remain);
305   postfix = strrchr(remain, '.');
306   if(postfix != NULL && !strcmp(postfix + 1, "gz")) {
307     gzip = "gzip";
308     *postfix = '\0';
309     postfix = strrchr(remain, '.');
310   }
311   if(postfix != NULL) {
312     postfix++;
313     if(!strcmp(postfix, "js"))
314       content = "text/javascript; charset=UTF-8";
315     else if(!strcmp(postfix, "css"))
316       content = "text/css; charset=UTF-8";
317     else if(!strcmp(postfix, "git"))
318       nogzip = 1;
319     else if(!strcmp(postfix, "jpg"))
320       nogzip = 1;
321     else if(!strcmp(postfix, "png"))
322       nogzip = 1;
323   }
324 
325   fb_file *fp = fb_open(path, 0, (nogzip || gzip) ? 0 : 1);
326   if (!fp) {
327     tvherror(LS_WEBUI, "failed to open %s", path);
328     return HTTP_STATUS_INTERNAL;
329   }
330   size = fb_size(fp);
331   if (!gzip && fb_gzipped(fp))
332     gzip = "gzip";
333 
334   http_send_begin(hc);
335   http_send_header(hc, 200, content, size, gzip, NULL, 10, 0, NULL, NULL);
336   while (!fb_eof(fp)) {
337     ssize_t c = fb_read(fp, buf, sizeof(buf));
338     if (c < 0) {
339       ret = HTTP_STATUS_INTERNAL;
340       break;
341     }
342     if (tvh_write(hc->hc_fd, buf, c)) {
343       ret = HTTP_STATUS_INTERNAL;
344       break;
345     }
346   }
347   http_send_end(hc);
348   fb_close(fp);
349 
350   return ret;
351 }
352 
353 /**
354  * HTTP subscription handling
355  */
356 static void
http_stream_status(void * opaque,htsmsg_t * m)357 http_stream_status ( void *opaque, htsmsg_t *m )
358 {
359   http_connection_t *hc = opaque;
360   htsmsg_add_str(m, "type", "HTTP");
361   if (hc->hc_username)
362     htsmsg_add_str(m, "user", hc->hc_username);
363 }
364 
365 static inline void *
http_stream_preop(http_connection_t * hc)366 http_stream_preop ( http_connection_t *hc )
367 {
368   return tcp_connection_launch(hc->hc_fd, http_stream_status, hc->hc_access);
369 }
370 
371 static inline void
http_stream_postop(void * tcp_id)372 http_stream_postop ( void *tcp_id )
373 {
374   tcp_connection_land(tcp_id);
375 }
376 
377 /**
378  * HTTP stream loop
379  */
380 static void
http_stream_run(http_connection_t * hc,profile_chain_t * prch,const char * name,th_subscription_t * s)381 http_stream_run(http_connection_t *hc, profile_chain_t *prch,
382 		const char *name, th_subscription_t *s)
383 {
384   streaming_message_t *sm;
385   int run = 1, started = 0;
386   streaming_queue_t *sq = &prch->prch_sq;
387   muxer_t *mux = prch->prch_muxer;
388   int ptimeout, grace = 20, r;
389   struct timeval tp;
390   streaming_start_t *ss_copy;
391   int64_t lastpkt, mono;
392 
393   if(muxer_open_stream(mux, hc->hc_fd))
394     run = 0;
395 
396   /* reduce timeout on write() for streaming */
397   tp.tv_sec  = 5;
398   tp.tv_usec = 0;
399   setsockopt(hc->hc_fd, SOL_SOCKET, SO_SNDTIMEO, &tp, sizeof(tp));
400   if (config.dscp >= 0)
401     socket_set_dscp(hc->hc_fd, config.dscp, NULL, 0);
402 
403   lastpkt = mclk();
404   ptimeout = prch->prch_pro ? prch->prch_pro->pro_timeout : 5;
405 
406   if (hc->hc_no_output) {
407     pthread_mutex_lock(&sq->sq_mutex);
408     sq->sq_maxsize = 100000;
409     pthread_mutex_unlock(&sq->sq_mutex);
410   }
411 
412   while(!hc->hc_shutdown && run && tvheadend_is_running()) {
413     pthread_mutex_lock(&sq->sq_mutex);
414     sm = TAILQ_FIRST(&sq->sq_queue);
415     if(sm == NULL) {
416       mono = mclk() + sec2mono(1);
417       do {
418         r = tvh_cond_timedwait(&sq->sq_cond, &sq->sq_mutex, mono);
419         if (r == ETIMEDOUT) {
420           /* Check socket status */
421           if (tcp_socket_dead(hc->hc_fd)) {
422             tvhdebug(LS_WEBUI,  "Stop streaming %s, client hung up", hc->hc_url_orig);
423             run = 0;
424           } else if((!started && mclk() - lastpkt > sec2mono(grace)) ||
425                      (started && ptimeout > 0 && mclk() - lastpkt > sec2mono(ptimeout))) {
426             tvhwarn(LS_WEBUI,  "Stop streaming %s, timeout waiting for packets", hc->hc_url_orig);
427             run = 0;
428           }
429           break;
430         }
431       } while (ERRNO_AGAIN(r));
432       pthread_mutex_unlock(&sq->sq_mutex);
433       continue;
434     }
435 
436     streaming_queue_remove(sq, sm);
437     pthread_mutex_unlock(&sq->sq_mutex);
438 
439     switch(sm->sm_type) {
440     case SMT_MPEGTS:
441     case SMT_PACKET:
442       if(started) {
443         pktbuf_t *pb;
444         int len;
445         if (sm->sm_type == SMT_PACKET)
446           pb = ((th_pkt_t*)sm->sm_data)->pkt_payload;
447         else
448           pb = sm->sm_data;
449         subscription_add_bytes_out(s, len = pktbuf_len(pb));
450         if (len > 0)
451           lastpkt = mclk();
452         muxer_write_pkt(mux, sm->sm_type, sm->sm_data);
453         sm->sm_data = NULL;
454       }
455       break;
456 
457     case SMT_GRACE:
458       grace = sm->sm_code < 5 ? 5 : grace;
459       break;
460 
461     case SMT_START:
462       grace = 10;
463       if(!started) {
464         tvhdebug(LS_WEBUI, "%s streaming %s",
465                  hc->hc_no_output ? "Probe" : "Start", hc->hc_url_orig);
466         http_output_content(hc, muxer_mime(mux, sm->sm_data));
467 
468         if (hc->hc_no_output) {
469           streaming_msg_free(sm);
470           mono = mclk() + sec2mono(2);
471           while (mclk() < mono) {
472             if (tcp_socket_dead(hc->hc_fd))
473               break;
474             tvh_safe_usleep(50000);
475           }
476           return;
477         }
478 
479         ss_copy = streaming_start_copy((streaming_start_t *)sm->sm_data);
480         if(muxer_init(mux, ss_copy, name) < 0)
481           run = 0;
482         streaming_start_unref(ss_copy);
483 
484         started = 1;
485       } else if(muxer_reconfigure(mux, sm->sm_data) < 0) {
486         tvhwarn(LS_WEBUI,  "Unable to reconfigure stream %s", hc->hc_url_orig);
487       }
488       break;
489 
490     case SMT_STOP:
491       if(sm->sm_code != SM_CODE_SOURCE_RECONFIGURED) {
492         tvhwarn(LS_WEBUI,  "Stop streaming %s, %s", hc->hc_url_orig,
493                 streaming_code2txt(sm->sm_code));
494         run = 0;
495       }
496       break;
497 
498     case SMT_SERVICE_STATUS:
499     case SMT_SIGNAL_STATUS:
500     case SMT_DESCRAMBLE_INFO:
501       if(tcp_socket_dead(hc->hc_fd)) {
502         tvhdebug(LS_WEBUI,  "Stop streaming %s, client hung up",
503                  hc->hc_url_orig);
504         run = 0;
505       } else if((!started && mclk() - lastpkt > sec2mono(grace)) ||
506                  (started && ptimeout > 0 && mclk() - lastpkt > sec2mono(ptimeout))) {
507         tvhwarn(LS_WEBUI,  "Stop streaming %s, timeout waiting for packets", hc->hc_url_orig);
508         run = 0;
509       }
510       break;
511 
512     case SMT_NOSTART_WARN:
513     case SMT_SKIP:
514     case SMT_SPEED:
515     case SMT_TIMESHIFT_STATUS:
516       break;
517 
518     case SMT_NOSTART:
519       tvhwarn(LS_WEBUI,  "Couldn't start streaming %s, %s",
520               hc->hc_url_orig, streaming_code2txt(sm->sm_code));
521       run = 0;
522       break;
523 
524     case SMT_EXIT:
525       tvhwarn(LS_WEBUI,  "Stop streaming %s, %s", hc->hc_url_orig,
526               streaming_code2txt(sm->sm_code));
527       run = 0;
528       break;
529     }
530 
531     streaming_msg_free(sm);
532 
533     if(mux->m_errors) {
534       if (!mux->m_eos)
535         tvhwarn(LS_WEBUI,  "Stop streaming %s, muxer reported errors", hc->hc_url_orig);
536       run = 0;
537     }
538   }
539 
540   if(started)
541     muxer_close(mux);
542 }
543 
544 /*
545  *
546  */
547 static void
http_m3u_playlist_add(htsbuf_queue_t * hq,const char * hostpath,const char * url_remain,const char * profile,const char * svcname,const char * logo,const char * epgid,access_t * access)548 http_m3u_playlist_add(htsbuf_queue_t *hq, const char *hostpath,
549                       const char *url_remain, const char *profile,
550                       const char *svcname, const char *logo,
551                       const char *epgid, access_t *access)
552 {
553   htsbuf_append_str(hq, "#EXTINF:-1");
554   if (logo) {
555     if (strncmp(logo, "imagecache/", 11) == 0)
556       htsbuf_qprintf(hq, " logo=\"%s/%s\"", hostpath, logo);
557     else
558       htsbuf_qprintf(hq, " logo=\"%s\"", logo);
559   }
560   if (epgid)
561     htsbuf_qprintf(hq, " tvg-id=\"%s\"", epgid);
562   htsbuf_qprintf(hq, ",%s\n%s%s?ticket=%s", svcname, hostpath, url_remain,
563                      access_ticket_create(url_remain, access));
564   htsbuf_qprintf(hq, "&profile=%s\n", profile);
565 }
566 
567 /*
568  *
569  */
570 static void
http_e2_playlist_add(htsbuf_queue_t * hq,const char * hostpath,const char * url_remain,const char * profile,const char * svcname)571 http_e2_playlist_add(htsbuf_queue_t *hq, const char *hostpath,
572                      const char *url_remain, const char *profile,
573                      const char *svcname)
574 {
575   htsbuf_append_str(hq, "#SERVICE 1:0:0:0:0:0:0:0:0:0:");
576   htsbuf_append_and_escape_url(hq, hostpath);
577   htsbuf_append_and_escape_url(hq, url_remain);
578   htsbuf_qprintf(hq, "&profile=%s:%s\n", profile, svcname);
579   htsbuf_qprintf(hq, "#DESCRIPTION %s\n", svcname);
580 }
581 
582 /*
583  *
584  */
585 static void
http_satip_m3u_playlist_add(htsbuf_queue_t * hq,const char * hostpath,channel_t * ch)586 http_satip_m3u_playlist_add(htsbuf_queue_t *hq, const char *hostpath,
587                             channel_t *ch)
588 {
589   char buf[64];
590   const char *name, *logo;
591   idnode_list_mapping_t *ilm;
592   service_t *s = NULL;
593   int src;
594 
595   LIST_FOREACH(ilm, &ch->ch_services, ilm_in2_link) {
596     /* cannot handle channels with more services for SAT>IP */
597     if (s)
598       return;
599     s = (service_t *)ilm->ilm_in1;
600   }
601   src = (s && s->s_satip_source) ? s->s_satip_source(s) : -1;
602   if (src < 1)
603     return;
604   name = channel_get_name(ch);
605   logo = channel_get_icon(ch);
606   snprintf(buf, sizeof(buf), "/stream/channelid/%d", channel_get_id(ch));
607   htsbuf_append_str(hq, "#EXTINF:-1");
608   if (logo) {
609     if (strncmp(logo, "imagecache/", 11) == 0)
610       htsbuf_qprintf(hq, " logo=%s/%s", hostpath, logo);
611     else
612       htsbuf_qprintf(hq, " logo=%s", logo);
613   }
614   htsbuf_qprintf(hq, ",%s\n%s%s?profile=pass\n", name, hostpath, buf);
615 }
616 
617 /**
618  * Output a playlist containing a single channel
619  */
620 static int
http_channel_playlist(http_connection_t * hc,int pltype,channel_t * channel)621 http_channel_playlist(http_connection_t *hc, int pltype, channel_t *channel)
622 {
623   htsbuf_queue_t *hq;
624   char buf[255];
625   char *profile, *hostpath;
626   const char *name;
627   char ubuf[UUID_HEX_SIZE];
628 
629   if (http_access_verify_channel(hc, ACCESS_STREAMING, channel))
630     return HTTP_STATUS_UNAUTHORIZED;
631 
632   profile = profile_validate_name(http_arg_get(&hc->hc_req_args, "profile"));
633   hostpath = http_get_hostpath(hc);
634 
635   hq = &hc->hc_reply;
636 
637   snprintf(buf, sizeof(buf), "/stream/channelid/%d", channel_get_id(channel));
638 
639   name = channel_get_name(channel);
640 
641   if (pltype == PLAYLIST_M3U) {
642 
643     htsbuf_append_str(hq, "#EXTM3U\n");
644     http_m3u_playlist_add(hq, hostpath, buf, profile, name,
645                           channel_get_icon(channel),
646                           channel_get_uuid(channel, ubuf),
647                           hc->hc_access);
648 
649   } else if (pltype == PLAYLIST_E2) {
650 
651     htsbuf_qprintf(hq, "#NAME %s\n", name);
652     http_e2_playlist_add(hq, hostpath, buf, profile, name);
653 
654   } else if (pltype == PLAYLIST_SATIP_M3U) {
655 
656     http_satip_m3u_playlist_add(hq, hostpath, channel);
657 
658   }
659 
660   free(hostpath);
661   free(profile);
662   return 0;
663 }
664 
665 
666 /**
667  * Output a playlist containing all channels with a specific tag
668  */
669 static int
http_tag_playlist(http_connection_t * hc,int pltype,channel_tag_t * tag)670 http_tag_playlist(http_connection_t *hc, int pltype, channel_tag_t *tag)
671 {
672   htsbuf_queue_t *hq;
673   char buf[255], ubuf[UUID_HEX_SIZE];
674   idnode_list_mapping_t *ilm;
675   char *profile, *hostpath;
676   const char *name;
677   channel_t *ch;
678   channel_t **chlist;
679   int idx = 0, count = 0;
680 
681   if(hc->hc_access == NULL ||
682      access_verify2(hc->hc_access, ACCESS_STREAMING))
683     return HTTP_STATUS_UNAUTHORIZED;
684 
685   hq = &hc->hc_reply;
686 
687   profile = profile_validate_name(http_arg_get(&hc->hc_req_args, "profile"));
688   hostpath = http_get_hostpath(hc);
689 
690   LIST_FOREACH(ilm, &tag->ct_ctms, ilm_in1_link) {
691     ch = (channel_t *)ilm->ilm_in2;
692     if (ch->ch_enabled)
693       count++;
694   }
695 
696   chlist = malloc(count * sizeof(channel_t *));
697 
698   LIST_FOREACH(ilm, &tag->ct_ctms, ilm_in1_link) {
699     ch = (channel_t *)ilm->ilm_in2;
700     if (ch->ch_enabled)
701       chlist[idx++] = ch;
702   }
703 
704   assert(idx == count);
705 
706   qsort(chlist, count, sizeof(channel_t *), http_channel_playlist_sfcn(hc));
707 
708   if (pltype == PLAYLIST_M3U)
709     htsbuf_append_str(hq, "#EXTM3U\n");
710   else if (pltype == PLAYLIST_E2)
711     htsbuf_qprintf(hq, "#NAME %s\n", tag->ct_name);
712   for (idx = 0; idx < count; idx++) {
713     ch = chlist[idx];
714     if (http_access_verify_channel(hc, ACCESS_STREAMING, ch))
715       continue;
716     snprintf(buf, sizeof(buf), "/stream/channelid/%d", channel_get_id(ch));
717     name = channel_get_name(ch);
718     if (pltype == PLAYLIST_M3U) {
719       http_m3u_playlist_add(hq, hostpath, buf, profile, name,
720                             channel_get_icon(ch),
721                             channel_get_uuid(ch, ubuf),
722                             hc->hc_access);
723     } else if (pltype == PLAYLIST_E2) {
724       htsbuf_qprintf(hq, "#NAME %s\n", name);
725       http_e2_playlist_add(hq, hostpath, buf, profile, name);
726     } else if (pltype == PLAYLIST_SATIP_M3U) {
727       http_satip_m3u_playlist_add(hq, hostpath, ch);
728     }
729   }
730 
731   free(chlist);
732 
733   free(hostpath);
734   free(profile);
735   return 0;
736 }
737 
738 
739 /**
740  * Output a playlist pointing to tag-specific playlists
741  */
742 static int
http_tag_list_playlist(http_connection_t * hc,int pltype)743 http_tag_list_playlist(http_connection_t *hc, int pltype)
744 {
745   htsbuf_queue_t *hq;
746   char buf[255];
747   channel_tag_t *ct;
748   channel_tag_t **ctlist;
749   channel_t *ch;
750   channel_t **chlist;
751   int labelidx = 0;
752   int idx = 0, count = 0;
753   int chidx = 0, chcount = 0;
754   char *profile, *hostpath;
755   idnode_list_mapping_t *ilm;
756 
757   if(hc->hc_access == NULL ||
758      access_verify2(hc->hc_access, ACCESS_STREAMING))
759     return HTTP_STATUS_UNAUTHORIZED;
760 
761   hq = &hc->hc_reply;
762 
763   profile = profile_validate_name(http_arg_get(&hc->hc_req_args, "profile"));
764 
765   hostpath = http_get_hostpath(hc);
766 
767   TAILQ_FOREACH(ct, &channel_tags, ct_link)
768     if(ct->ct_enabled && !ct->ct_internal)
769       count++;
770 
771   ctlist = malloc(count * sizeof(channel_tag_t *));
772 
773   TAILQ_FOREACH(ct, &channel_tags, ct_link)
774     if(ct->ct_enabled && !ct->ct_internal)
775       ctlist[idx++] = ct;
776 
777   assert(idx == count);
778 
779   qsort(ctlist, count, sizeof(channel_tag_t *), http_channel_tag_playlist_sfcn(hc));
780 
781   if (pltype == PLAYLIST_E2 || pltype == PLAYLIST_SATIP_M3U) {
782     CHANNEL_FOREACH(ch)
783       if (ch->ch_enabled)
784         chcount++;
785 
786     chlist = malloc(chcount * sizeof(channel_t *));
787 
788     CHANNEL_FOREACH(ch)
789       if (ch->ch_enabled)
790         chlist[chidx++] = ch;
791 
792     assert(chidx == chcount);
793 
794     qsort(chlist, chcount, sizeof(channel_t *), http_channel_playlist_sfcn(hc));
795   } else {
796     chlist = NULL;
797   }
798 
799   htsbuf_append_str(hq, pltype == PLAYLIST_E2 ? "#NAME Tvheadend Channels\n" : "#EXTM3U\n");
800   for (idx = 0; idx < count; idx++) {
801     ct = ctlist[idx];
802 
803     if (pltype == PLAYLIST_M3U) {
804       snprintf(buf, sizeof(buf), "/playlist/tagid/%d", idnode_get_short_uuid(&ct->ct_id));
805       http_m3u_playlist_add(hq, hostpath, buf, profile, ct->ct_name,
806                             channel_tag_get_icon(ct), NULL, hc->hc_access);
807     } else if (pltype == PLAYLIST_E2) {
808       htsbuf_qprintf(hq, "#SERVICE 1:64:%d:0:0:0:0:0:0:0::%s\n", labelidx++, ct->ct_name);
809       htsbuf_qprintf(hq, "#DESCRIPTION %s\n", ct->ct_name);
810       for (chidx = 0; chidx < chcount; chidx++) {
811         ch = chlist[chidx];
812         LIST_FOREACH(ilm, &ct->ct_ctms, ilm_in1_link)
813           if (ch == (channel_t *)ilm->ilm_in2) {
814             snprintf(buf, sizeof(buf), "/stream/channelid/%d", channel_get_id(ch));
815             http_e2_playlist_add(hq, hostpath, buf, profile, channel_get_name(ch));
816             break;
817           }
818       }
819     } else if (pltype == PLAYLIST_SATIP_M3U) {
820       for (chidx = 0; chidx < chcount; chidx++) {
821         ch = chlist[chidx];
822         LIST_FOREACH(ilm, &ct->ct_ctms, ilm_in1_link)
823           if (ch == (channel_t *)ilm->ilm_in2)
824             http_satip_m3u_playlist_add(hq, hostpath, ch);
825       }
826     }
827   }
828 
829   free(ctlist);
830   free(chlist);
831 
832   free(hostpath);
833   free(profile);
834   return 0;
835 }
836 
837 
838 /**
839  * Output a flat playlist with all channels
840  */
841 static int
http_channel_list_playlist(http_connection_t * hc,int pltype)842 http_channel_list_playlist(http_connection_t *hc, int pltype)
843 {
844   htsbuf_queue_t *hq;
845   char buf[255], ubuf[UUID_HEX_SIZE];
846   channel_t *ch;
847   channel_t **chlist;
848   int idx = 0, count = 0;
849   char *profile, *hostpath;
850   const char *name;
851 
852   if(hc->hc_access == NULL ||
853      access_verify2(hc->hc_access, ACCESS_STREAMING))
854     return HTTP_STATUS_UNAUTHORIZED;
855 
856   hq = &hc->hc_reply;
857 
858   profile = profile_validate_name(http_arg_get(&hc->hc_req_args, "profile"));
859   hostpath = http_get_hostpath(hc);
860 
861   CHANNEL_FOREACH(ch)
862     if (ch->ch_enabled)
863       count++;
864 
865   chlist = malloc(count * sizeof(channel_t *));
866 
867   CHANNEL_FOREACH(ch)
868     if (ch->ch_enabled)
869       chlist[idx++] = ch;
870 
871   assert(idx == count);
872 
873   qsort(chlist, count, sizeof(channel_t *), http_channel_playlist_sfcn(hc));
874 
875   htsbuf_append_str(hq, pltype == PLAYLIST_E2 ? "#NAME Tvheadend Channels\n" : "#EXTM3U\n");
876   for (idx = 0; idx < count; idx++) {
877     ch = chlist[idx];
878 
879     if (http_access_verify_channel(hc, ACCESS_STREAMING, ch))
880       continue;
881 
882     name = channel_get_name(ch);
883     snprintf(buf, sizeof(buf), "/stream/channelid/%d", channel_get_id(ch));
884 
885     if (pltype == PLAYLIST_M3U) {
886       http_m3u_playlist_add(hq, hostpath, buf, profile, name,
887                             channel_get_icon(ch),
888                             channel_get_uuid(ch, ubuf),
889                             hc->hc_access);
890     } else if (pltype == PLAYLIST_E2) {
891       http_e2_playlist_add(hq, hostpath, buf, profile, name);
892     } else if (pltype == PLAYLIST_SATIP_M3U) {
893       http_satip_m3u_playlist_add(hq, hostpath, ch);
894     }
895   }
896 
897   free(chlist);
898 
899   free(hostpath);
900   free(profile);
901   return 0;
902 }
903 
904 
905 
906 /**
907  * Output a playlist of all recordings.
908  */
909 static int
http_dvr_list_playlist(http_connection_t * hc,int pltype)910 http_dvr_list_playlist(http_connection_t *hc, int pltype)
911 {
912   htsbuf_queue_t *hq;
913   char buf[255], ubuf[UUID_HEX_SIZE];
914   dvr_entry_t *de;
915   const char *uuid;
916   char *hostpath;
917   off_t fsize;
918   time_t durration;
919   struct tm tm;
920   int bandwidth;
921 
922   if (pltype != PLAYLIST_M3U)
923     return HTTP_STATUS_BAD_REQUEST;
924 
925   hq = &hc->hc_reply;
926   hostpath = http_get_hostpath(hc);
927 
928   htsbuf_append_str(hq, "#EXTM3U\n");
929   LIST_FOREACH(de, &dvrentries, de_global_link) {
930     fsize = dvr_get_filesize(de, 0);
931     if(!fsize)
932       continue;
933 
934     if (de->de_channel &&
935         http_access_verify_channel(hc, ACCESS_RECORDER, de->de_channel))
936       continue;
937 
938     if (hc->hc_access == NULL)
939       continue;
940 
941     durration  = dvr_entry_get_stop_time(de) - dvr_entry_get_start_time(de, 0);
942     bandwidth = ((8*fsize) / (durration*1024.0));
943     strftime(buf, sizeof(buf), "%FT%T%z", localtime_r(&(de->de_start), &tm));
944 
945     htsbuf_qprintf(hq, "#EXTINF:%"PRItime_t",%s\n", durration, lang_str_get(de->de_title, NULL));
946 
947     htsbuf_qprintf(hq, "#EXT-X-TARGETDURATION:%"PRItime_t"\n", durration);
948     uuid = idnode_uuid_as_str(&de->de_id, ubuf);
949     htsbuf_qprintf(hq, "#EXT-X-STREAM-INF:PROGRAM-ID=%s,BANDWIDTH=%d\n", uuid, bandwidth);
950     htsbuf_qprintf(hq, "#EXT-X-PROGRAM-DATE-TIME:%s\n", buf);
951 
952     snprintf(buf, sizeof(buf), "/dvrfile/%s", uuid);
953     htsbuf_qprintf(hq, "%s%s?ticket=%s\n", hostpath, buf,
954        access_ticket_create(buf, hc->hc_access));
955   }
956 
957   free(hostpath);
958   return 0;
959 }
960 
961 /**
962  * Output a playlist with a http stream for a dvr entry (.m3u format)
963  */
964 static int
http_dvr_playlist(http_connection_t * hc,int pltype,dvr_entry_t * de)965 http_dvr_playlist(http_connection_t *hc, int pltype, dvr_entry_t *de)
966 {
967   htsbuf_queue_t *hq = &hc->hc_reply;
968   char buf[255], ubuf[UUID_HEX_SIZE];
969   const char *ticket_id = NULL, *uuid;
970   time_t durration = 0;
971   off_t fsize = 0;
972   int bandwidth = 0, ret = 0;
973   struct tm tm;
974   char *hostpath;
975 
976   if(pltype != PLAYLIST_M3U)
977     return HTTP_STATUS_BAD_REQUEST;
978 
979   if(http_access_verify(hc, ACCESS_RECORDER))
980     return HTTP_STATUS_UNAUTHORIZED;
981 
982   if(dvr_entry_verify(de, hc->hc_access, 1))
983     return HTTP_STATUS_NOT_FOUND;
984 
985   hostpath  = http_get_hostpath(hc);
986   durration  = dvr_entry_get_stop_time(de) - dvr_entry_get_start_time(de, 0);
987   fsize = dvr_get_filesize(de, 0);
988 
989   if(fsize) {
990     bandwidth = ((8*fsize) / (durration*1024.0));
991     strftime(buf, sizeof(buf), "%FT%T%z", localtime_r(&(de->de_start), &tm));
992 
993     htsbuf_append_str(hq, "#EXTM3U\n");
994     htsbuf_qprintf(hq, "#EXTINF:%"PRItime_t",%s\n", durration, lang_str_get(de->de_title, NULL));
995 
996     htsbuf_qprintf(hq, "#EXT-X-TARGETDURATION:%"PRItime_t"\n", durration);
997     uuid = idnode_uuid_as_str(&de->de_id, ubuf);
998     htsbuf_qprintf(hq, "#EXT-X-STREAM-INF:PROGRAM-ID=%s,BANDWIDTH=%d\n", uuid, bandwidth);
999     htsbuf_qprintf(hq, "#EXT-X-PROGRAM-DATE-TIME:%s\n", buf);
1000 
1001     snprintf(buf, sizeof(buf), "/dvrfile/%s", uuid);
1002     ticket_id = access_ticket_create(buf, hc->hc_access);
1003     htsbuf_qprintf(hq, "%s%s?ticket=%s\n", hostpath, buf, ticket_id);
1004   } else {
1005     ret = HTTP_STATUS_NOT_FOUND;
1006   }
1007 
1008   free(hostpath);
1009   return ret;
1010 }
1011 
1012 
1013 /**
1014  * Handle requests for playlists.
1015  */
1016 static int
page_http_playlist(http_connection_t * hc,const char * remain,void * opaque)1017 page_http_playlist(http_connection_t *hc, const char *remain, void *opaque)
1018 {
1019   char *components[2], *cmd, *s;
1020   int nc, r, pltype = PLAYLIST_M3U;
1021   channel_t *ch = NULL;
1022   dvr_entry_t *de = NULL;
1023   channel_tag_t *tag = NULL;
1024 
1025   if (remain && !strcmp(remain, "e2")) {
1026     pltype = PLAYLIST_E2;
1027     remain = NULL;
1028   }
1029 
1030   if (remain && !strcmp(remain, "satip")) {
1031     pltype = PLAYLIST_SATIP_M3U;
1032     remain = NULL;
1033   }
1034 
1035   if (remain && !strncmp(remain, "e2/", 3)) {
1036     pltype = PLAYLIST_E2;
1037     remain += 3;
1038   }
1039 
1040   if (remain && !strncmp(remain, "satip/", 6)) {
1041     pltype = PLAYLIST_SATIP_M3U;
1042     remain += 6;
1043   }
1044 
1045   if(!remain || *remain == '\0') {
1046     http_redirect(hc, pltype == PLAYLIST_E2 ? "/playlist/e2/channels" :
1047                       (pltype == PLAYLIST_SATIP_M3U ?
1048                         "/playlist/satip/channels" :
1049                         "/playlist/channels"),
1050                       &hc->hc_req_args, 0);
1051     return HTTP_STATUS_FOUND;
1052   }
1053 
1054   nc = http_tokenize((char *)remain, components, 2, '/');
1055   if(!nc)
1056     return HTTP_STATUS_BAD_REQUEST;
1057 
1058   cmd = tvh_strdupa(components[0]);
1059 
1060   if(nc == 2)
1061     http_deescape(components[1]);
1062 
1063   pthread_mutex_lock(&global_lock);
1064 
1065   if(nc == 2 && !strcmp(components[0], "channelid"))
1066     ch = channel_find_by_id(atoi(components[1]));
1067   else if(nc == 2 && !strcmp(components[0], "channelnumber"))
1068     ch = channel_find_by_number(components[1]);
1069   else if(nc == 2 && !strcmp(components[0], "channelname"))
1070     ch = channel_find_by_name(components[1]);
1071   else if(nc == 2 && !strcmp(components[0], "channel"))
1072    ch = channel_find(components[1]);
1073   else if(nc == 2 && !strcmp(components[0], "dvrid"))
1074     de = dvr_entry_find_by_id(atoi(components[1]));
1075   else if(nc == 2 && !strcmp(components[0], "tagid"))
1076     tag = channel_tag_find_by_identifier(atoi(components[1]));
1077   else if(nc == 2 && !strcmp(components[0], "tagname"))
1078     tag = channel_tag_find_by_name(components[1], 0);
1079   else if(nc == 2 && !strcmp(components[0], "tag")) {
1080     if (uuid_hexvalid(components[1]))
1081       tag = channel_tag_find_by_uuid(components[1]);
1082     else
1083       tag = channel_tag_find_by_name(components[1], 0);
1084   }
1085 
1086   if(ch)
1087     r = http_channel_playlist(hc, pltype, ch);
1088   else if(tag)
1089     r = http_tag_playlist(hc, pltype, tag);
1090   else if(de) {
1091     if (pltype == PLAYLIST_SATIP_M3U)
1092       r = HTTP_STATUS_BAD_REQUEST;
1093     else
1094       r = http_dvr_playlist(hc, pltype, de);
1095   } else {
1096     cmd = s = tvh_strdupa(components[0]);
1097     while (*s && *s != '.') s++;
1098     if (*s == '.') {
1099       *s = '\0';
1100       s++;
1101     }
1102     if (s[0] != '\0' && strcmp(s, "m3u") && strcmp(s, "m3u8"))
1103       r = HTTP_STATUS_BAD_REQUEST;
1104     else if(!strcmp(cmd, "tags"))
1105       r = http_tag_list_playlist(hc, pltype);
1106     else if(!strcmp(cmd, "channels"))
1107       r = http_channel_list_playlist(hc, pltype);
1108     else if(pltype != PLAYLIST_SATIP_M3U &&
1109             !strcmp(cmd, "recordings"))
1110       r = http_dvr_list_playlist(hc, pltype);
1111     else {
1112       r = HTTP_STATUS_BAD_REQUEST;
1113     }
1114   }
1115 
1116   pthread_mutex_unlock(&global_lock);
1117 
1118   if (r == 0)
1119     http_output_content(hc, pltype == PLAYLIST_E2 ? MIME_E2 : MIME_M3U);
1120 
1121   return r;
1122 }
1123 
1124 
1125 /**
1126  * Subscribes to a service and starts the streaming loop
1127  */
1128 static int
http_stream_service(http_connection_t * hc,service_t * service,int weight)1129 http_stream_service(http_connection_t *hc, service_t *service, int weight)
1130 {
1131   th_subscription_t *s;
1132   profile_t *pro;
1133   profile_chain_t prch;
1134   const char *str;
1135   size_t qsize;
1136   const char *name;
1137   void *tcp_id;
1138   int res = HTTP_STATUS_SERVICE;
1139   int flags, eflags = 0;
1140 
1141   if(http_access_verify(hc, ACCESS_ADVANCED_STREAMING))
1142     return HTTP_STATUS_UNAUTHORIZED;
1143 
1144   if ((str = http_arg_get(&hc->hc_req_args, "descramble")))
1145     if (strcmp(str, "0") == 0)
1146       eflags |= SUBSCRIPTION_NODESCR;
1147 
1148   if ((str = http_arg_get(&hc->hc_req_args, "emm")))
1149     if (strcmp(str, "1") == 0)
1150       eflags |= SUBSCRIPTION_EMM;
1151 
1152   flags = SUBSCRIPTION_MPEGTS | eflags;
1153   if ((eflags & SUBSCRIPTION_NODESCR) == 0)
1154     flags |= SUBSCRIPTION_PACKET;
1155   if(!(pro = profile_find_by_list(hc->hc_access->aa_profiles,
1156                                   http_arg_get(&hc->hc_req_args, "profile"),
1157                                   "service", flags)))
1158     return HTTP_STATUS_NOT_ALLOWED;
1159 
1160   if((tcp_id = http_stream_preop(hc)) == NULL)
1161     return HTTP_STATUS_NOT_ALLOWED;
1162 
1163   if ((str = http_arg_get(&hc->hc_req_args, "qsize")))
1164     qsize = atoll(str);
1165   else
1166     qsize = 1500000;
1167 
1168   profile_chain_init(&prch, pro, service, 1);
1169   if (!profile_chain_open(&prch, NULL, 0, qsize)) {
1170 
1171     s = subscription_create_from_service(&prch, NULL, weight, "HTTP",
1172                                          prch.prch_flags | SUBSCRIPTION_STREAMING |
1173                                            eflags,
1174                                          hc->hc_peer_ipstr,
1175 				         hc->hc_username,
1176 				         http_arg_get(&hc->hc_args, "User-Agent"),
1177 				         NULL);
1178     if(s) {
1179       name = tvh_strdupa(service->s_nicename);
1180       pthread_mutex_unlock(&global_lock);
1181       http_stream_run(hc, &prch, name, s);
1182       pthread_mutex_lock(&global_lock);
1183       subscription_unsubscribe(s, UNSUBSCRIBE_FINAL);
1184       res = 0;
1185     }
1186   }
1187 
1188   profile_chain_close(&prch);
1189   http_stream_postop(tcp_id);
1190   return res;
1191 }
1192 
1193 /**
1194  * Subscribe to a mux for grabbing a raw dump
1195  *
1196  * TODO: can't currently force this to be on a particular input
1197  */
1198 #if ENABLE_MPEGTS
1199 static int
http_stream_mux(http_connection_t * hc,mpegts_mux_t * mm,int weight)1200 http_stream_mux(http_connection_t *hc, mpegts_mux_t *mm, int weight)
1201 {
1202   th_subscription_t *s;
1203   profile_chain_t prch;
1204   size_t qsize;
1205   const char *name, *str;
1206   void *tcp_id;
1207   char *p, *saveptr = NULL;
1208   mpegts_apids_t pids;
1209   mpegts_service_t *ms;
1210   int res = HTTP_STATUS_SERVICE, i;
1211 
1212   if(http_access_verify(hc, ACCESS_ADVANCED_STREAMING))
1213     return HTTP_STATUS_UNAUTHORIZED;
1214 
1215   if((tcp_id = http_stream_preop(hc)) == NULL)
1216     return HTTP_STATUS_NOT_ALLOWED;
1217 
1218   if ((str = http_arg_get(&hc->hc_req_args, "qsize")))
1219     qsize = atoll(str);
1220   else
1221     qsize = 10000000;
1222 
1223   mpegts_pid_init(&pids);
1224   if ((str = http_arg_get(&hc->hc_req_args, "pids"))) {
1225     p = tvh_strdupa(str);
1226     p = strtok_r(p, ",", &saveptr);
1227     while (p) {
1228       if (strcmp(p, "all") == 0) {
1229         pids.all = 1;
1230       } else {
1231         i = atoi(p);
1232         if (i < 0 || i > 8192)
1233           return HTTP_STATUS_BAD_REQUEST;
1234         if (i == 8192)
1235           pids.all = 1;
1236         else
1237           mpegts_pid_add(&pids, i, MPS_WEIGHT_RAW);
1238       }
1239       p = strtok_r(NULL, ",", &saveptr);
1240     }
1241     if (!pids.all && pids.count <= 0)
1242       return HTTP_STATUS_BAD_REQUEST;
1243   } else {
1244     pids.all = 1;
1245   }
1246 
1247   if (!profile_chain_raw_open(&prch, mm, qsize, 1)) {
1248 
1249     s = subscription_create_from_mux(&prch, NULL, weight ?: 10, "HTTP",
1250                                      prch.prch_flags |
1251                                      SUBSCRIPTION_STREAMING,
1252                                      hc->hc_peer_ipstr, hc->hc_username,
1253                                      http_arg_get(&hc->hc_args, "User-Agent"),
1254                                      NULL);
1255     if (s) {
1256       name = tvh_strdupa(s->ths_title);
1257       ms = (mpegts_service_t *)s->ths_service;
1258       if (ms->s_update_pids(ms, &pids) == 0) {
1259         pthread_mutex_unlock(&global_lock);
1260         http_stream_run(hc, &prch, name, s);
1261         pthread_mutex_lock(&global_lock);
1262       }
1263       subscription_unsubscribe(s, UNSUBSCRIBE_FINAL);
1264       res = 0;
1265     }
1266   }
1267 
1268   profile_chain_close(&prch);
1269   http_stream_postop(tcp_id);
1270 
1271   return res;
1272 }
1273 #endif
1274 
1275 /**
1276  * Subscribes to a channel and starts the streaming loop
1277  */
1278 static int
http_stream_channel(http_connection_t * hc,channel_t * ch,int weight)1279 http_stream_channel(http_connection_t *hc, channel_t *ch, int weight)
1280 {
1281   th_subscription_t *s;
1282   profile_t *pro;
1283   profile_chain_t prch;
1284   char *str;
1285   size_t qsize;
1286   const char *name;
1287   void *tcp_id;
1288   int res = HTTP_STATUS_SERVICE;
1289 
1290   if (http_access_verify_channel(hc, ACCESS_STREAMING, ch))
1291     return HTTP_STATUS_UNAUTHORIZED;
1292 
1293   if(!(pro = profile_find_by_list(hc->hc_access->aa_profiles,
1294                                   http_arg_get(&hc->hc_req_args, "profile"),
1295                                   "channel",
1296                                   SUBSCRIPTION_PACKET | SUBSCRIPTION_MPEGTS)))
1297     return HTTP_STATUS_NOT_ALLOWED;
1298 
1299   if((tcp_id = http_stream_preop(hc)) == NULL)
1300     return HTTP_STATUS_NOT_ALLOWED;
1301 
1302   if ((str = http_arg_get(&hc->hc_req_args, "qsize")))
1303     qsize = atoll(str);
1304   else
1305     qsize = 1500000;
1306 
1307   profile_chain_init(&prch, pro, ch, 1);
1308   if (!profile_chain_open(&prch, NULL, 0, qsize)) {
1309 
1310     s = subscription_create_from_channel(&prch,
1311                  NULL, weight, "HTTP",
1312                  prch.prch_flags | SUBSCRIPTION_STREAMING,
1313                  hc->hc_peer_ipstr, hc->hc_username,
1314                  http_arg_get(&hc->hc_args, "User-Agent"),
1315                  NULL);
1316 
1317     if(s) {
1318       name = tvh_strdupa(channel_get_name(ch));
1319       pthread_mutex_unlock(&global_lock);
1320       http_stream_run(hc, &prch, name, s);
1321       pthread_mutex_lock(&global_lock);
1322       subscription_unsubscribe(s, UNSUBSCRIBE_FINAL);
1323       res = 0;
1324     }
1325   }
1326 
1327   profile_chain_close(&prch);
1328   http_stream_postop(tcp_id);
1329 
1330   return res;
1331 }
1332 
1333 
1334 /**
1335  * Handle the http request. http://tvheadend/stream/channelid/<chid>
1336  *                          http://tvheadend/stream/channel/<uuid>
1337  *                          http://tvheadend/stream/channelnumber/<channelnumber>
1338  *                          http://tvheadend/stream/channelname/<channelname>
1339  *                          http://tvheadend/stream/service/<servicename>
1340  *                          http://tvheadend/stream/mux/<muxid>
1341  */
1342 static int
http_stream(http_connection_t * hc,const char * remain,void * opaque)1343 http_stream(http_connection_t *hc, const char *remain, void *opaque)
1344 {
1345   char *components[2];
1346   channel_t *ch = NULL;
1347   service_t *service = NULL;
1348 #if ENABLE_MPEGTS
1349   mpegts_mux_t *mm = NULL;
1350 #endif
1351   const char *str;
1352   int weight = 0;
1353 
1354   hc->hc_keep_alive = 0;
1355 
1356   if(remain == NULL)
1357     return HTTP_STATUS_BAD_REQUEST;
1358 
1359   if(http_tokenize((char *)remain, components, 2, '/') != 2)
1360     return HTTP_STATUS_BAD_REQUEST;
1361 
1362   http_deescape(components[1]);
1363 
1364   if ((str = http_arg_get(&hc->hc_req_args, "weight")))
1365     weight = atoi(str);
1366 
1367   scopedgloballock();
1368 
1369   if(!strcmp(components[0], "channelid")) {
1370     ch = channel_find_by_id(atoi(components[1]));
1371   } else if(!strcmp(components[0], "channelnumber")) {
1372     ch = channel_find_by_number(components[1]);
1373   } else if(!strcmp(components[0], "channelname")) {
1374     ch = channel_find_by_name(components[1]);
1375   } else if(!strcmp(components[0], "channel")) {
1376     ch = channel_find(components[1]);
1377   } else if(!strcmp(components[0], "service")) {
1378     service = service_find_by_identifier(components[1]);
1379 #if ENABLE_MPEGTS
1380   } else if(!strcmp(components[0], "mux")) {
1381     // TODO: do we want to be able to force starting a particular instance
1382     mm      = mpegts_mux_find(components[1]);
1383 #endif
1384   }
1385 
1386   if(ch != NULL) {
1387     return http_stream_channel(hc, ch, weight);
1388   } else if(service != NULL) {
1389     return http_stream_service(hc, service, weight);
1390 #if ENABLE_MPEGTS
1391   } else if(mm != NULL) {
1392     return http_stream_mux(hc, mm, weight);
1393 #endif
1394   } else {
1395     return HTTP_STATUS_BAD_REQUEST;
1396   }
1397 }
1398 
1399 /**
1400  * Generate a xspf playlist
1401  * http://en.wikipedia.org/wiki/XML_Shareable_Playlist_Format
1402  */
1403 static int
page_xspf(http_connection_t * hc,const char * remain,void * opaque)1404 page_xspf(http_connection_t *hc, const char *remain, void *opaque)
1405 {
1406   size_t maxlen;
1407   char *buf, *hostpath = http_get_hostpath(hc);
1408   const char *title, *profile, *ticket, *image;
1409   size_t len;
1410 
1411   if ((title = http_arg_get(&hc->hc_req_args, "title")) == NULL)
1412     title = "TVHeadend Stream";
1413   profile = http_arg_get(&hc->hc_req_args, "profile");
1414   ticket  = http_arg_get(&hc->hc_req_args, "ticket");
1415   image   = http_arg_get(&hc->hc_req_args, "image");
1416 
1417   maxlen = strlen(remain) + strlen(title) + 512;
1418   buf = alloca(maxlen);
1419 
1420   pthread_mutex_lock(&global_lock);
1421   if (ticket == NULL) {
1422     snprintf(buf, maxlen, "/%s", remain);
1423     ticket = access_ticket_create(buf, hc->hc_access);
1424   }
1425   snprintf(buf, maxlen, "\
1426 <?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n\
1427 <playlist version=\"1\" xmlns=\"http://xspf.org/ns/0/\">\r\n\
1428   <trackList>\r\n\
1429      <track>\r\n\
1430        <title>%s</title>\r\n\
1431        <location>%s/%s%s%s%s%s</location>\r\n%s%s%s\
1432      </track>\r\n\
1433   </trackList>\r\n\
1434 </playlist>\r\n", title, hostpath, remain,
1435     profile ? "?profile=" : "", profile ?: "",
1436     profile ? "&ticket=" : "?ticket=", ticket,
1437     image ? "       <image>" : "", image ?: "", image ? "</image>\r\n" : "");
1438   pthread_mutex_unlock(&global_lock);
1439 
1440   len = strlen(buf);
1441   http_send_begin(hc);
1442   http_send_header(hc, 200, "application/xspf+xml", len, 0, NULL, 10, 0, NULL, NULL);
1443   tvh_write(hc->hc_fd, buf, len);
1444   http_send_end(hc);
1445 
1446   free(hostpath);
1447   return 0;
1448 }
1449 
1450 /**
1451  * Generate an M3U playlist
1452  * http://en.wikipedia.org/wiki/M3U
1453  */
1454 static int
page_m3u(http_connection_t * hc,const char * remain,void * opaque)1455 page_m3u(http_connection_t *hc, const char *remain, void *opaque)
1456 {
1457   size_t maxlen;
1458   char *buf, *hostpath = http_get_hostpath(hc);
1459   const char *title, *profile, *ticket;
1460   size_t len;
1461 
1462   if ((title = http_arg_get(&hc->hc_req_args, "title")) == NULL)
1463     title = "TVHeadend Stream";
1464   profile = http_arg_get(&hc->hc_req_args, "profile");
1465   ticket = http_arg_get(&hc->hc_req_args, "ticket");
1466 
1467   maxlen = strlen(remain) + strlen(title) + 256;
1468   buf = alloca(maxlen);
1469 
1470   pthread_mutex_lock(&global_lock);
1471   if (ticket == NULL) {
1472     snprintf(buf, maxlen, "/%s", remain);
1473     ticket = access_ticket_create(buf, hc->hc_access);
1474   }
1475   snprintf(buf, maxlen, "\
1476 #EXTM3U\r\n\
1477 #EXTINF:-1,%s\r\n\
1478 %s/%s%s%s%s%s\r\n", title, hostpath, remain,
1479     profile ? "?profile=" : "", profile ?: "",
1480     profile ? "&ticket=" : "?ticket=", ticket);
1481   pthread_mutex_unlock(&global_lock);
1482 
1483   len = strlen(buf);
1484   http_send_begin(hc);
1485   http_send_header(hc, 200, MIME_M3U, len, 0, NULL, 10, 0, NULL, NULL);
1486   tvh_write(hc->hc_fd, buf, len);
1487   http_send_end(hc);
1488 
1489   free(hostpath);
1490   return 0;
1491 }
1492 
1493 static char *
page_play_path_modify(http_connection_t * hc,const char * path,int * cut)1494 page_play_path_modify(http_connection_t *hc, const char *path, int *cut)
1495 {
1496   /*
1497    * For curl, wget and TVHeadend do not send the playlist, stream directly
1498    */
1499   const char *agent = http_arg_get(&hc->hc_args, "User-Agent");
1500   int direct = 0;
1501 
1502   if (agent == NULL)
1503     direct = 1; /* direct streaming for no user-agent providers */
1504   else if (strncasecmp(agent, "MPlayer ", 8) == 0)
1505     direct = 1;
1506   else if (strncasecmp(agent, "curl/", 5) == 0)
1507     direct = 1;
1508   else if (strncasecmp(agent, "wget/", 5) == 0)
1509     direct = 1;
1510   else if (strncasecmp(agent, "TVHeadend/", 10) == 0)
1511     direct = 1;
1512   else if (strncasecmp(agent, "Lavf/", 5) == 0) /* ffmpeg, libav */
1513     direct = 1;
1514   else if (strstr(agent, "shoutcastsource")) /* some media players */
1515     direct = 1;
1516 
1517   if (direct)
1518     return strdup(path + 5); /* note: skip the /play */
1519   return NULL;
1520 }
1521 
1522 static int
page_play(http_connection_t * hc,const char * remain,void * opaque)1523 page_play(http_connection_t *hc, const char *remain, void *opaque)
1524 {
1525   char *playlist;
1526 
1527   if(remain == NULL)
1528     return HTTP_STATUS_NOT_FOUND;
1529 
1530   if(hc->hc_access == NULL ||
1531      access_verify2(hc->hc_access, ACCESS_OR |
1532                                    ACCESS_STREAMING |
1533                                    ACCESS_ADVANCED_STREAMING |
1534                                    ACCESS_RECORDER))
1535     return HTTP_STATUS_UNAUTHORIZED;
1536 
1537   playlist = http_arg_get(&hc->hc_req_args, "playlist");
1538   if (playlist) {
1539     if (strcmp(playlist, "xspf") == 0)
1540       return page_xspf(hc, remain, opaque);
1541     if (strcmp(playlist, "m3u") == 0)
1542       return page_m3u(hc, remain, opaque);
1543   }
1544   if (webui_xspf)
1545     return page_xspf(hc, remain, opaque);
1546   return page_m3u(hc, remain, opaque);
1547 }
1548 
1549 /**
1550  * Send a file
1551  */
1552 int
http_serve_file(http_connection_t * hc,const char * fname,int fconv,const char * content,int (* preop)(http_connection_t * hc,off_t file_start,size_t content_len,void * opaque),void (* stats)(http_connection_t * hc,size_t len,void * opaque),void * opaque)1553 http_serve_file(http_connection_t *hc, const char *fname,
1554                 int fconv, const char *content,
1555                 int (*preop)(http_connection_t *hc, off_t file_start,
1556                              size_t content_len, void *opaque),
1557                 void (*stats)(http_connection_t *hc, size_t len, void *opaque),
1558                 void *opaque)
1559 {
1560   int fd, ret;
1561   struct stat st;
1562   const char *range;
1563   char *basename;
1564   char *str, *str0;
1565   char range_buf[255];
1566   char *disposition = NULL;
1567   off_t content_len, chunk;
1568   intmax_t file_start, file_end;
1569   htsbuf_queue_t q;
1570 #if defined(PLATFORM_LINUX)
1571   ssize_t r;
1572 #elif defined(PLATFORM_FREEBSD) || defined(PLATFORM_DARWIN)
1573   off_t o, r;
1574 #endif
1575 
1576   if (fconv) {
1577     basename = strrchr(fname, '/');
1578     if (basename) {
1579       basename++; /* Skip '/' */
1580       str0 = intlconv_utf8safestr(intlconv_charset_id("ASCII", 1, 1),
1581                                   basename, strlen(basename) * 3);
1582       if (str0 == NULL)
1583         return HTTP_STATUS_INTERNAL;
1584       htsbuf_queue_init(&q, 0);
1585       htsbuf_append_and_escape_rfc8187(&q, basename);
1586       str = htsbuf_to_string(&q);
1587       r = 50 + strlen(str0) + strlen(str);
1588       disposition = alloca(r);
1589       snprintf(disposition, r,
1590                "attachment; filename=\"%s\"; filename*=UTF-8''%s",
1591                str0, str);
1592       htsbuf_queue_flush(&q);
1593       free(str);
1594       free(str0);
1595     }
1596   }
1597 
1598   fd = tvh_open(fname, O_RDONLY, 0);
1599   if(fd < 0)
1600     return HTTP_STATUS_NOT_FOUND;
1601 
1602   if(fstat(fd, &st) < 0) {
1603     close(fd);
1604     return HTTP_STATUS_NOT_FOUND;
1605   }
1606 
1607   file_start = 0;
1608   file_end = st.st_size-1;
1609 
1610   range = http_arg_get(&hc->hc_args, "Range");
1611   if(range != NULL)
1612     sscanf(range, "bytes=%jd-%jd", &file_start, &file_end);
1613 
1614   //Sanity checks
1615   if(file_start < 0 || file_start >= st.st_size) {
1616     close(fd);
1617     return HTTP_STATUS_OK;
1618   }
1619   if(file_end < 0 || file_end >= st.st_size) {
1620     close(fd);
1621     return HTTP_STATUS_OK;
1622   }
1623 
1624   if(file_start > file_end) {
1625     close(fd);
1626     return HTTP_STATUS_OK;
1627   }
1628 
1629   content_len = file_end - file_start+1;
1630 
1631   sprintf(range_buf, "bytes %jd-%jd/%jd",
1632     file_start, file_end, (intmax_t)st.st_size);
1633 
1634 #if defined(PLATFORM_LINUX)
1635   if(file_start > 0)
1636     if (lseek(fd, file_start, SEEK_SET) != file_start) {
1637       close(fd);
1638       return HTTP_STATUS_INTERNAL;
1639     }
1640 #elif defined(PLATFORM_FREEBSD) || defined(PLATFORM_DARWIN)
1641   o = file_start;
1642 #endif
1643 
1644   if (preop) {
1645     ret = preop(hc, file_start, content_len, opaque);
1646     if (ret) {
1647       close(fd);
1648       return ret;
1649     }
1650   }
1651 
1652   http_send_begin(hc);
1653   http_send_header(hc, range ? HTTP_STATUS_PARTIAL_CONTENT : HTTP_STATUS_OK,
1654        content, content_len, NULL, NULL, 10,
1655        range ? range_buf : NULL, disposition, NULL);
1656 
1657   ret = 0;
1658   if(!hc->hc_no_output) {
1659     while(content_len > 0) {
1660       chunk = MIN(1024 * ((stats ? 128 : 1024) * 1024), content_len);
1661 #if defined(PLATFORM_LINUX)
1662       r = sendfile(hc->hc_fd, fd, NULL, chunk);
1663       if (r < 0) {
1664         ret = -1;
1665         break;
1666       }
1667 #elif defined(PLATFORM_FREEBSD)
1668       ret = sendfile(fd, hc->hc_fd, o, chunk, NULL, &r, 0);
1669       if (ret < 0)
1670         break;
1671       o += r;
1672 #elif defined(PLATFORM_DARWIN)
1673       r = chunk;
1674       ret = sendfile(fd, hc->hc_fd, o, &r, NULL, 0);
1675       if (ret < 0)
1676         break;
1677       o += r;
1678 #endif
1679       content_len -= r;
1680       if (stats)
1681         stats(hc, r, opaque);
1682     }
1683   }
1684   http_send_end(hc);
1685   close(fd);
1686 
1687   return ret;
1688 }
1689 
1690 /**
1691  * Download a recorded file
1692  */
1693 typedef struct page_dvrfile_priv {
1694   const char *uuid;
1695   char *fname;
1696   const char *charset;
1697   const char *content;
1698   void *tcp_id;
1699   th_subscription_t *sub;
1700 } page_dvrfile_priv_t;
1701 
1702 static int
page_dvrfile_preop(http_connection_t * hc,off_t file_start,size_t content_len,void * opaque)1703 page_dvrfile_preop(http_connection_t *hc, off_t file_start,
1704                    size_t content_len, void *opaque)
1705 {
1706   page_dvrfile_priv_t *priv = opaque;
1707   dvr_entry_t *de;
1708 
1709   pthread_mutex_lock(&global_lock);
1710   priv->tcp_id = http_stream_preop(hc);
1711   priv->sub = NULL;
1712   if (priv->tcp_id && !hc->hc_no_output && content_len > 64*1024) {
1713     priv->sub = subscription_create_from_file("HTTP", priv->charset,
1714                                               priv->fname, hc->hc_peer_ipstr,
1715                                               hc->hc_username,
1716                                               http_arg_get(&hc->hc_args, "User-Agent"));
1717     if (priv->sub == NULL) {
1718       http_stream_postop(priv->tcp_id);
1719       priv->tcp_id = NULL;
1720     }
1721   }
1722   /* Play count + 1 when write access */
1723   if (!hc->hc_no_output && file_start <= 0) {
1724     de = dvr_entry_find_by_uuid(priv->uuid);
1725     if (de == NULL)
1726       de = dvr_entry_find_by_id(atoi(priv->uuid));
1727     if (de && !dvr_entry_verify(de, hc->hc_access, 0)) {
1728       de->de_playcount = de->de_playcount + 1;
1729       dvr_entry_changed_notify(de);
1730     }
1731   }
1732   pthread_mutex_unlock(&global_lock);
1733   if (priv->tcp_id == NULL)
1734     return HTTP_STATUS_NOT_ALLOWED;
1735   return 0;
1736 }
1737 
1738 static void
page_dvrfile_stats(http_connection_t * hc,size_t len,void * opaque)1739 page_dvrfile_stats(http_connection_t *hc, size_t len, void *opaque)
1740 {
1741   page_dvrfile_priv_t *priv = opaque;
1742   if (priv->sub) {
1743     subscription_add_bytes_in(priv->sub, len);
1744     subscription_add_bytes_out(priv->sub, len);
1745   }
1746 }
1747 
1748 static int
page_dvrfile(http_connection_t * hc,const char * remain,void * opaque)1749 page_dvrfile(http_connection_t *hc, const char *remain, void *opaque)
1750 {
1751   int ret;
1752   const char *filename;
1753   dvr_entry_t *de;
1754   page_dvrfile_priv_t priv;
1755 
1756   if(remain == NULL)
1757     return HTTP_STATUS_BAD_REQUEST;
1758 
1759   if(hc->hc_access == NULL ||
1760      (access_verify2(hc->hc_access, ACCESS_OR |
1761                                     ACCESS_STREAMING |
1762                                     ACCESS_ADVANCED_STREAMING |
1763                                     ACCESS_RECORDER)))
1764     return HTTP_STATUS_UNAUTHORIZED;
1765 
1766   pthread_mutex_lock(&global_lock);
1767   de = dvr_entry_find_by_uuid(remain);
1768   if (de == NULL)
1769     de = dvr_entry_find_by_id(atoi(remain));
1770   if(de == NULL || (filename = dvr_get_filename(de)) == NULL) {
1771     pthread_mutex_unlock(&global_lock);
1772     return HTTP_STATUS_NOT_FOUND;
1773   }
1774   if(dvr_entry_verify(de, hc->hc_access, 1)) {
1775     pthread_mutex_unlock(&global_lock);
1776     return HTTP_STATUS_UNAUTHORIZED;
1777   }
1778 
1779   priv.uuid = remain;
1780   priv.fname = tvh_strdupa(filename);
1781   priv.content = muxer_container_filename2mime(priv.fname, 1);
1782   priv.charset = de->de_config ? de->de_config->dvr_charset_id : NULL;
1783   priv.tcp_id = NULL;
1784   priv.sub = NULL;
1785 
1786   pthread_mutex_unlock(&global_lock);
1787 
1788   ret = http_serve_file(hc, priv.fname, 1, priv.content,
1789                         page_dvrfile_preop, page_dvrfile_stats, &priv);
1790 
1791   pthread_mutex_lock(&global_lock);
1792   if (priv.sub)
1793     subscription_unsubscribe(priv.sub, UNSUBSCRIBE_FINAL);
1794   http_stream_postop(priv.tcp_id);
1795   pthread_mutex_unlock(&global_lock);
1796   return ret;
1797 }
1798 
1799 /**
1800  * Fetch image cache image
1801  */
1802 /**
1803  * Static download of a file from the filesystem
1804  */
1805 static int
page_imagecache(http_connection_t * hc,const char * remain,void * opaque)1806 page_imagecache(http_connection_t *hc, const char *remain, void *opaque)
1807 {
1808   uint32_t id;
1809   int r;
1810   char fname[PATH_MAX];
1811 
1812   if(remain == NULL)
1813     return HTTP_STATUS_NOT_FOUND;
1814 
1815   if(hc->hc_access == NULL ||
1816      (access_verify2(hc->hc_access, ACCESS_OR |
1817                                     ACCESS_WEB_INTERFACE |
1818                                     ACCESS_STREAMING |
1819                                     ACCESS_ADVANCED_STREAMING |
1820                                     ACCESS_HTSP_STREAMING |
1821                                     ACCESS_HTSP_RECORDER |
1822                                     ACCESS_RECORDER)))
1823     return HTTP_STATUS_UNAUTHORIZED;
1824 
1825   if(sscanf(remain, "%d", &id) != 1)
1826     return HTTP_STATUS_BAD_REQUEST;
1827 
1828   /* Fetch details */
1829   pthread_mutex_lock(&global_lock);
1830   r = imagecache_filename(id, fname, sizeof(fname));
1831   pthread_mutex_unlock(&global_lock);
1832 
1833   if (r)
1834     return HTTP_STATUS_NOT_FOUND;
1835 
1836   return http_serve_file(hc, fname, 0, NULL, NULL, NULL, NULL);
1837 }
1838 
1839 /**
1840  *
1841  */
1842 static void
webui_static_content(const char * http_path,const char * source)1843 webui_static_content(const char *http_path, const char *source)
1844 {
1845   http_path_add(http_path, (void *)source, page_static_file,
1846                 ACCESS_WEB_INTERFACE);
1847 }
1848 
1849 
1850 /**
1851  *
1852  */
1853 static int
favicon(http_connection_t * hc,const char * remain,void * opaque)1854 favicon(http_connection_t *hc, const char *remain, void *opaque)
1855 {
1856   return page_static_file(hc, "logo.png", (void *)"src/webui/static/img");
1857 }
1858 
1859 /**
1860  *
1861  */
http_file_test(const char * path)1862 static int http_file_test(const char *path)
1863 {
1864   fb_file *fb = fb_open(path, 0, 0);
1865   if (fb) {
1866     fb_close(fb);
1867     return 0;
1868   }
1869   return -1;
1870 }
1871 
1872 /**
1873  *
1874  */
1875 static int
http_redir(http_connection_t * hc,const char * remain,void * opaque)1876 http_redir(http_connection_t *hc, const char *remain, void *opaque)
1877 {
1878   const char *lang, *theme;
1879   char *components[3];
1880   char buf[256];
1881   int nc;
1882 
1883   if (!remain)
1884     return HTTP_STATUS_BAD_REQUEST;
1885   nc = http_tokenize((char *)remain, components, 3, '/');
1886   if(!nc)
1887     return HTTP_STATUS_BAD_REQUEST;
1888 
1889   if (nc == 1) {
1890     if (!strcmp(components[0], "locale.js")) {
1891       lang = tvh_gettext_get_lang(hc->hc_access->aa_lang_ui);
1892       if (lang) {
1893         snprintf(buf, sizeof(buf), "src/webui/static/intl/tvh.%s.js.gz", lang);
1894         if (!http_file_test(buf)) {
1895           snprintf(buf, sizeof(buf), "/static/intl/tvh.%s.js.gz", lang);
1896           http_redirect(hc, buf, NULL, 0);
1897           return 0;
1898         }
1899       }
1900       snprintf(buf, sizeof(buf), "tvh_locale={};tvh_locale_lang='';");
1901       http_send_begin(hc);
1902       http_send_header(hc, 200, "text/javascript; charset=UTF-8", strlen(buf), 0, NULL, 10, 0, NULL, NULL);
1903       tvh_write(hc->hc_fd, buf, strlen(buf));
1904       http_send_end(hc);
1905       return 0;
1906     }
1907     if (!strcmp(components[0], "theme.css")) {
1908       theme = access_get_theme(hc->hc_access);
1909       if (theme) {
1910         snprintf(buf, sizeof(buf), "src/webui/static/tvh.%s.css.gz", theme);
1911         if (!http_file_test(buf)) {
1912           snprintf(buf, sizeof(buf), "/static/tvh.%s.css.gz", theme);
1913           http_css_import(hc, buf);
1914           return 0;
1915         }
1916       }
1917       return HTTP_STATUS_BAD_REQUEST;
1918     }
1919     if (!strcmp(components[0], "theme.debug.css")) {
1920       theme = access_get_theme(hc->hc_access);
1921       if (theme) {
1922         snprintf(buf, sizeof(buf), "src/webui/static/extjs/resources/css/xtheme-%s.css", theme);
1923         if (!http_file_test(buf)) {
1924           snprintf(buf, sizeof(buf), "/static/extjs/resources/css/xtheme-%s.css", theme);
1925           http_css_import(hc, buf);
1926           return 0;
1927         }
1928       }
1929       return HTTP_STATUS_BAD_REQUEST;
1930     }
1931     if (!strcmp(components[0], "theme.app.debug.css")) {
1932       theme = access_get_theme(hc->hc_access);
1933       if (theme) {
1934         snprintf(buf, sizeof(buf), "src/webui/static/app/ext-%s.css", theme);
1935         if (!http_file_test(buf)) {
1936           snprintf(buf, sizeof(buf), "/static/app/ext-%s.css", theme);
1937           http_css_import(hc, buf);
1938           return 0;
1939         }
1940       }
1941       return HTTP_STATUS_BAD_REQUEST;
1942     }
1943   }
1944 
1945   return HTTP_STATUS_BAD_REQUEST;
1946 }
1947 
1948 int page_statedump(http_connection_t *hc, const char *remain, void *opaque);
1949 
1950 /**
1951  * WEB user interface
1952  */
1953 void
webui_init(int xspf)1954 webui_init(int xspf)
1955 {
1956   const char *s;
1957   http_path_t *hp;
1958 
1959   webui_xspf = xspf;
1960 
1961   if (tvheadend_webui_debug)
1962     tvhinfo(LS_WEBUI, "Running web interface in debug mode");
1963 
1964   s = tvheadend_webroot;
1965   tvheadend_webroot = NULL;
1966   http_path_add("", NULL, page_no_webroot, ACCESS_WEB_INTERFACE);
1967   tvheadend_webroot = s;
1968 
1969   http_path_add("", NULL, page_root2, ACCESS_WEB_INTERFACE);
1970   hp = http_path_add("/", NULL, page_root, ACCESS_WEB_INTERFACE);
1971   hp->hp_no_verification = 1; /* redirect only */
1972   http_path_add("/login", NULL, page_login, ACCESS_WEB_INTERFACE);
1973   hp = http_path_add("/logout", NULL, page_logout, ACCESS_WEB_INTERFACE);
1974   hp->hp_no_verification = 1;
1975 
1976 #if CONFIG_SATIP_SERVER
1977   http_path_add("/satip_server", NULL, satip_server_http_page, ACCESS_ANONYMOUS);
1978 #endif
1979 
1980   http_path_add_modify("/play", NULL, page_play, ACCESS_ANONYMOUS, page_play_path_modify);
1981   http_path_add("/dvrfile", NULL, page_dvrfile, ACCESS_ANONYMOUS);
1982   http_path_add("/favicon.ico", NULL, favicon, ACCESS_WEB_INTERFACE);
1983   http_path_add("/playlist", NULL, page_http_playlist, ACCESS_ANONYMOUS);
1984   http_path_add("/xmltv", NULL, page_xmltv, ACCESS_ANONYMOUS);
1985   http_path_add("/markdown", NULL, page_markdown, ACCESS_ANONYMOUS);
1986 
1987   http_path_add("/state", NULL, page_statedump, ACCESS_ADMIN);
1988 
1989   http_path_add("/stream",  NULL, http_stream,  ACCESS_ANONYMOUS);
1990 
1991   http_path_add("/imagecache", NULL, page_imagecache, ACCESS_ANONYMOUS);
1992 
1993   http_path_add("/redir",  NULL, http_redir, ACCESS_ANONYMOUS);
1994 
1995   webui_static_content("/static",        "src/webui/static");
1996 
1997   simpleui_start();
1998   extjs_start();
1999   comet_init();
2000   webui_api_init();
2001 }
2002 
2003 void
webui_done(void)2004 webui_done(void)
2005 {
2006   comet_done();
2007 }
2008