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